details:   https://code.tryton.org/tryton/commit/d5ea3f916372
branch:    default
user:      Cédric Krier <[email protected]>
date:      Mon Sep 21 15:28:29 2026 +0200
description:
        Add lot to POS line

        Closes #15109
diffstat:

 modules/sale_point/sale.py                                |    6 +
 modules/sale_point/view/sale_line_list.xml                |    2 +-
 modules/stock_lot/CHANGELOG                               |    1 +
 modules/stock_lot/sale.py                                 |   44 ++++
 modules/stock_lot/sale.xml                                |   18 +
 modules/stock_lot/tests/scenario_stock_lot_sale_point.rst |  141 ++++++++++++++
 modules/stock_lot/tests/test_module.py                    |   17 +-
 modules/stock_lot/tryton.cfg                              |    6 +
 modules/stock_lot/view/sale_point_sale_line_form.xml      |    9 +
 modules/stock_lot/view/sale_point_sale_line_list.xml      |    8 +
 10 files changed, 250 insertions(+), 2 deletions(-)

diffs (360 lines):

diff -r 1d8ace864c38 -r d5ea3f916372 modules/sale_point/sale.py
--- a/modules/sale_point/sale.py        Tue Sep 22 11:13:55 2026 +0200
+++ b/modules/sale_point/sale.py        Mon Sep 21 15:28:29 2026 +0200
@@ -663,14 +663,20 @@
         return move
 
     @property
+    @fields.depends('quantity', 'sale', '_parent_sale.point')
     def from_location(self):
+        if self.quantity is None:
+            return
         if self.quantity >= 0:
             return self.sale.point.storage_location
         else:
             return self.sale.point.customer_location
 
     @property
+    @fields.depends('quantity', 'sale', '_parent_sale.point')
     def to_location(self):
+        if self.quantity is None:
+            return
         if self.quantity >= 0:
             return self.sale.point.customer_location
         else:
diff -r 1d8ace864c38 -r d5ea3f916372 modules/sale_point/view/sale_line_list.xml
--- a/modules/sale_point/view/sale_line_list.xml        Tue Sep 22 11:13:55 
2026 +0200
+++ b/modules/sale_point/view/sale_line_list.xml        Mon Sep 21 15:28:29 
2026 +0200
@@ -3,7 +3,7 @@
 this repository contains the full copyright notices and license terms. -->
 <tree>
     <field name="sale"/>
-    <field name="product" expand="1"/>
+    <field name="product" expand="2"/>
     <field name="unit_price"/>
     <field name="quantity" symbol="unit"/>
     <field name="amount"/>
diff -r 1d8ace864c38 -r d5ea3f916372 modules/stock_lot/CHANGELOG
--- a/modules/stock_lot/CHANGELOG       Tue Sep 22 11:13:55 2026 +0200
+++ b/modules/stock_lot/CHANGELOG       Mon Sep 21 15:28:29 2026 +0200
@@ -1,3 +1,4 @@
+* Add lot to POS line
 
 Version 8.0.0 - 2026-04-20
 --------------------------
diff -r 1d8ace864c38 -r d5ea3f916372 modules/stock_lot/sale.py
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/stock_lot/sale.py Mon Sep 21 15:28:29 2026 +0200
@@ -0,0 +1,44 @@
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+from trytond.model import fields
+from trytond.pool import PoolMeta
+from trytond.pyson import Eval
+
+
+class POSSaleLine(metaclass=PoolMeta):
+    __name__ = 'sale.point.sale.line'
+
+    lot = fields.Many2One(
+        'stock.lot', "Lot", ondelete='RESTRICT',
+        domain=[
+            ('product', '=', Eval('product', -1)),
+            ],
+        states={
+            'readonly': Eval('sale_state') != 'open',
+            'required': Eval('lot_required', False),
+            })
+    lot_required = fields.Function(
+        fields.Boolean("Lot Required"),
+        'on_change_with_lot_required')
+
+    @fields.depends('product', methods=['from_location', 'to_location'])
+    def on_change_with_lot_required(self, name=None):
+        if self.product and self.from_location and self.to_location:
+            return self.product.lot_is_required(
+                self.from_location, self.to_location)
+
+    @fields.depends('product', 'lot')
+    def on_change_product(self):
+        try:
+            super().on_change_product()
+        except AttributeError:
+            pass
+        if self.lot and self.lot.product != self.product:
+            self.lot = None
+
+    def get_stock_move(self):
+        move = super().get_stock_move()
+        if move:
+            move.lot = self.lot
+        return move
diff -r 1d8ace864c38 -r d5ea3f916372 modules/stock_lot/sale.xml
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/stock_lot/sale.xml        Mon Sep 21 15:28:29 2026 +0200
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton.  The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<tryton>
+    <data depends="sale_point">
+        <record model="ir.ui.view" id="sale_point_sale_line_view_form">
+            <field name="model">sale.point.sale.line</field>
+            <field name="inherit" ref="sale_point.sale_line_view_form"/>
+            <field name="name">sale_point_sale_line_form</field>
+        </record>
+
+        <record model="ir.ui.view" id="sale_point_sale_line_view_list">
+            <field name="model">sale.point.sale.line</field>
+            <field name="inherit" ref="sale_point.sale_line_view_list"/>
+            <field name="name">sale_point_sale_line_list</field>
+        </record>
+    </data>
+</tryton>
diff -r 1d8ace864c38 -r d5ea3f916372 
modules/stock_lot/tests/scenario_stock_lot_sale_point.rst
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/stock_lot/tests/scenario_stock_lot_sale_point.rst Mon Sep 21 
15:28:29 2026 +0200
@@ -0,0 +1,141 @@
+======================
+Stock Lot POS Scenario
+======================
+
+Imports::
+
+    >>> from decimal import Decimal
+
+    >>> from proteus import Model
+    >>> from trytond.modules.account.tests.tools import (
+    ...     create_chart, create_fiscalyear, get_accounts)
+    >>> from trytond.modules.account_invoice.tests.tools import (
+    ...     set_fiscalyear_invoice_sequences)
+    >>> from trytond.modules.company.tests.tools import create_company
+    >>> from trytond.tests.tools import activate_modules, assertEqual
+
+Activate modules::
+
+    >>> config = activate_modules(
+    ...     ['stock_lot', 'sale_point'], create_company, create_chart)
+
+    >>> Journal = Model.get('account.journal')
+    >>> Location = Model.get('stock.location')
+    >>> Lot = Model.get('stock.lot')
+    >>> POS = Model.get('sale.point')
+    >>> PaymentMethod = Model.get('sale.point.payment.method')
+    >>> ProductCategory = Model.get('product.category')
+    >>> ProductTemplate = Model.get('product.template')
+    >>> ProductUom = Model.get('product.uom')
+    >>> Sale = Model.get('sale.point.sale')
+    >>> SequenceStrict = Model.get('ir.sequence.strict')
+    >>> SequenceType = Model.get('ir.sequence.type')
+    >>> StockMove = Model.get('stock.move')
+
+Create fiscal year::
+
+    >>> fiscalyear = set_fiscalyear_invoice_sequences(create_fiscalyear())
+    >>> fiscalyear.click('create_period')
+
+Get accounts::
+
+    >>> accounts = get_accounts()
+
+Create account categories::
+
+    >>> account_category = ProductCategory(name="Account Category")
+    >>> account_category.accounting = True
+    >>> account_category.account_revenue = accounts['revenue']
+    >>> account_category.save()
+
+Create product::
+
+    >>> unit, = ProductUom.find([('name', '=', 'Unit')])
+
+    >>> template = ProductTemplate()
+    >>> template.name = 'product'
+    >>> template.default_uom = unit
+    >>> template.type = 'goods'
+    >>> template.salable = True
+    >>> template.account_category = account_category
+    >>> template.gross_price = Decimal('10.0000')
+    >>> template.lot_required = ['storage', 'customer']
+    >>> template.save()
+    >>> goods, = template.products
+
+    >>> lot = Lot(number='0001', product=goods)
+    >>> lot.save()
+
+    >>> template = ProductTemplate()
+    >>> template.name = 'service'
+    >>> template.default_uom = unit
+    >>> template.type = 'service'
+    >>> template.salable = True
+    >>> template.account_category = account_category
+    >>> template.gross_price = Decimal('10.0000')
+    >>> template.save()
+    >>> service, = template.products
+
+Get journal::
+
+    >>> journal_revenue, = Journal.find([('type', '=', 'revenue')], limit=1)
+
+Get stock locations::
+
+    >>> storage_loc, = Location.find([('code', '=', 'STO')])
+    >>> customer_loc, = Location.find([('code', '=', 'CUS')])
+
+Create POS::
+
+    >>> pos = POS(name="POS")
+    >>> pos.journal = journal_revenue
+    >>> pos.sequence = SequenceStrict(name="POS", company=pos.company)
+    >>> pos.sequence.sequence_type, = SequenceType.find(
+    ...     [('name', '=', "POS")], limit=1)
+    >>> pos.sequence.save()
+    >>> pos.storage_location = storage_loc
+    >>> pos.customer_location = customer_loc
+    >>> pos.save()
+
+Setup a payment method::
+
+    >>> cash_method = PaymentMethod(name="Cash")
+    >>> cash_method.account = accounts['cash']
+    >>> cash_method.cash = True
+    >>> cash_method.save()
+
+Make a sale with lot::
+
+    >>> sale = Sale(point=pos)
+    >>> line = sale.lines.new()
+    >>> line.product = goods
+    >>> line.lot = lot
+    >>> line.quantity = 1
+    >>> line = sale.lines.new()
+    >>> line.product = service
+    >>> line.quantity = 1
+    >>> sale.save()
+    >>> sale.state
+    'open'
+
+Pay the sale::
+
+    >>> payment = sale.click('pay')
+    >>> payment.form.method = cash_method
+    >>> payment.execute('pay')
+    >>> sale.state
+    'done'
+
+Post the sale::
+
+    >>> sale.click('post')
+    >>> sale.state
+    'posted'
+
+Check stock move::
+
+    >>> move, = StockMove.find([
+    ...     ('origin.sale', '=', sale.id, 'sale.point.sale.line')
+    ...     ])
+    >>> assertEqual(move.product, goods)
+    >>> assertEqual(move.lot, lot)
diff -r 1d8ace864c38 -r d5ea3f916372 modules/stock_lot/tests/test_module.py
--- a/modules/stock_lot/tests/test_module.py    Tue Sep 22 11:13:55 2026 +0200
+++ b/modules/stock_lot/tests/test_module.py    Mon Sep 21 15:28:29 2026 +0200
@@ -3,6 +3,8 @@
 
 import datetime as dt
 from decimal import Decimal
+from functools import wraps
+from unittest.mock import patch
 
 from trytond.modules.company.tests import (
     CompanyTestMixin, create_company, set_company)
@@ -11,12 +13,24 @@
 from trytond.transaction import Transaction
 
 
+def disable_check_origin(func):
+    @wraps(func)
+    def wrapper(*args, **kwargs):
+        pool = Pool()
+        Move = pool.get('stock.move')
+        with patch.object(Move, 'check_origin') as check_origin:
+            check_origin.return_value = set()
+            return func(*args, **kwargs)
+    return wrapper
+
+
 class StockLotTestCase(CompanyTestMixin, ModuleTestCase):
     'Test Stock Lot module'
     module = 'stock_lot'
-    extras = ['stock_split', 'stock_conversion']
+    extras = ['sale_point', 'stock_split', 'stock_conversion']
 
     @with_transaction()
+    @disable_check_origin
     def test_products_by_location(self):
         'Test products_by_location'
         pool = Pool()
@@ -215,6 +229,7 @@
                     quantities[(lot_cache.location, lot_cache.lot)])
 
     @with_transaction()
+    @disable_check_origin
     def test_assign_try_with_lot(self):
         "Test Move assign_try with lot"
         pool = Pool()
diff -r 1d8ace864c38 -r d5ea3f916372 modules/stock_lot/tryton.cfg
--- a/modules/stock_lot/tryton.cfg      Tue Sep 22 11:13:55 2026 +0200
+++ b/modules/stock_lot/tryton.cfg      Mon Sep 21 15:28:29 2026 +0200
@@ -5,6 +5,7 @@
     product
     stock
 extras_depend:
+    sale_point
     sale_supply_drop_shipment
     stock_conversion
     stock_split
@@ -12,6 +13,7 @@
     ir.xml
     stock.xml
     product.xml
+    sale.xml
     message.xml
 
 [register]
@@ -42,6 +44,10 @@
     stock.MoveAddLots
     stock.InventoryCount
 
+[register sale_point]
+model:
+    sale.POSSaleLine
+
 [register sale_supply_drop_shipment]
 model:
     stock.ShipmentDrop
diff -r 1d8ace864c38 -r d5ea3f916372 
modules/stock_lot/view/sale_point_sale_line_form.xml
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/stock_lot/view/sale_point_sale_line_form.xml      Mon Sep 21 
15:28:29 2026 +0200
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton.  The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<data>
+    <xpath expr="//field[@name='product']" position="after">
+        <label name="lot"/>
+        <field name="lot"/>
+    </xpath>
+</data>
diff -r 1d8ace864c38 -r d5ea3f916372 
modules/stock_lot/view/sale_point_sale_line_list.xml
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/stock_lot/view/sale_point_sale_line_list.xml      Mon Sep 21 
15:28:29 2026 +0200
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton.  The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<data>
+    <xpath expr="//field[@name='product']" position="after">
+        <field name="lot" expand="1"/>
+    </xpath>
+</data>

Reply via email to