changeset 3d08a1207d55 in modules/stock_lot:default
details: https://hg.tryton.org/modules/stock_lot?cmd=changeset;node=3d08a1207d55
description:
        Add wizard to add lots and sequence for lot number

        issue9800
        review326511002
diffstat:

 CHANGELOG                                   |    2 +
 __init__.py                                 |    9 +
 ir.xml                                      |   19 ++
 message.xml                                 |    3 +
 product.py                                  |   43 ++++-
 product.xml                                 |    6 +
 stock.py                                    |  259 +++++++++++++++++++++++++++-
 stock.xml                                   |   30 +++
 tests/scenario_stock_lot_move_add.rst       |  108 +++++++++++
 tests/scenario_stock_lot_number.rst         |   51 +++++
 tests/test_stock_lot.py                     |    8 +
 tryton.cfg                                  |    1 +
 view/move_form.xml                          |    3 +
 view/move_tree.xml                          |    3 +
 view/product_configuration_form.xml         |    9 +
 view/stock_move_add_lots_start_form.xml     |   20 ++
 view/stock_move_add_lots_start_lot_list.xml |    9 +
 view/template_form.xml                      |    2 +
 18 files changed, 578 insertions(+), 7 deletions(-)

diffs (795 lines):

diff -r e49aaf8553e1 -r 3d08a1207d55 CHANGELOG
--- a/CHANGELOG Wed Dec 23 21:55:13 2020 +0100
+++ b/CHANGELOG Wed Feb 03 21:19:06 2021 +0100
@@ -1,3 +1,5 @@
+* Add wizard to add lots
+* Allow lot sequence number to be configured on product
 * Add lots_by_locations Model
 
 Version 5.8.0 - 2020-11-02
diff -r e49aaf8553e1 -r 3d08a1207d55 __init__.py
--- a/__init__.py       Wed Dec 23 21:55:13 2020 +0100
+++ b/__init__.py       Wed Feb 03 21:19:06 2021 +0100
@@ -5,6 +5,10 @@
 from . import stock
 from . import product
 
+from .stock import LotMixin
+
+__all__ = ['LotMixin', 'register']
+
 
 def register():
     Pool.register(
@@ -14,6 +18,8 @@
         stock.LotByWarehouseContext,
         stock.Location,
         stock.Move,
+        stock.MoveAddLotsStart,
+        stock.MoveAddLotsStartLot,
         stock.ShipmentIn,
         stock.ShipmentOut,
         stock.ShipmentOutReturn,
@@ -23,9 +29,12 @@
         stock.Inventory,
         stock.InventoryLine,
         stock.InventoryCountSearch,
+        product.Configuration,
+        product.ConfigurationDefaultLotSequence,
         product.Template,
         product.Product,
         module='stock_lot', type_='model')
     Pool.register(
+        stock.MoveAddLots,
         stock.InventoryCount,
         module='stock_lot', type_='wizard')
diff -r e49aaf8553e1 -r 3d08a1207d55 ir.xml
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/ir.xml    Wed Feb 03 21:19:06 2021 +0100
@@ -0,0 +1,19 @@
+<?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>
+        <record model="ir.sequence.type" id="sequence_type_stock_lot">
+            <field name="name">Stock Lot</field>
+            <field name="code">stock.lot</field>
+        </record>
+        <record model="ir.sequence.type-res.group" 
id="sequence_type_stock_lot_group_admin">
+            <field name="sequence_type" ref="sequence_type_stock_lot"/>
+            <field name="group" ref="res.group_admin"/>
+        </record>
+        <record model="ir.sequence.type-res.group" 
id="sequence_type_stock_lot_group_product_admin">
+            <field name="sequence_type" ref="sequence_type_stock_lot"/>
+            <field name="group" ref="product.group_product_admin"/>
+        </record>
+    </data>
+</tryton>
diff -r e49aaf8553e1 -r 3d08a1207d55 message.xml
--- a/message.xml       Wed Dec 23 21:55:13 2020 +0100
+++ b/message.xml       Wed Feb 03 21:19:06 2021 +0100
@@ -12,5 +12,8 @@
         <record model="ir.message" id="msg_only_lot">
             <field name="text">You must select a stock lot instead of a 
product for "%(product)s".</field>
         </record>
+        <record model="ir.message" id="msg_move_add_lot_quantity">
+            <field name="text">The total quantity "%(lot_quantity)s" of the 
lots must be less than or equal to the quantity "%(move_quantity)s" of the 
move.</field>
+        </record>
     </data>
 </tryton>
diff -r e49aaf8553e1 -r 3d08a1207d55 product.py
--- a/product.py        Wed Dec 23 21:55:13 2020 +0100
+++ b/product.py        Wed Feb 03 21:19:06 2021 +0100
@@ -3,12 +3,33 @@
 from sql import Table
 
 from trytond.config import config
-from trytond.model import fields
+from trytond.model import ModelSQL, ValueMixin, fields
 from trytond.pyson import Eval
-from trytond.pool import PoolMeta
+from trytond.pool import PoolMeta, Pool
 from trytond.transaction import Transaction
 
 
+class Configuration(metaclass=PoolMeta):
+    __name__ = 'product.configuration'
+
+    default_lot_sequence = fields.MultiValue(
+        fields.Many2One(
+            'ir.sequence', "Default Lot Sequence",
+            domain=[
+                ('code', '=', 'stock.lot'),
+                ]))
+
+
+class ConfigurationDefaultLotSequence(ModelSQL, ValueMixin):
+    "Product Configuration Default Lot Sequence"
+    __name__ = 'product.configuration.default_lot_sequence'
+    default_lot_sequence = fields.Many2One(
+        'ir.sequence', "Default Lot Sequence",
+        domain=[
+            ('code', '=', 'stock.lot'),
+            ])
+
+
 class Template(metaclass=PoolMeta):
     __name__ = 'product.template'
 
@@ -24,6 +45,16 @@
             'invisible': ~Eval('type').in_(['goods', 'assets']),
             },
         depends=['type'])
+    lot_sequence = fields.Many2One(
+        'ir.sequence', "Lot Sequence",
+        domain=[
+            ('code', '=', 'stock.lot'),
+            ],
+        states={
+            'invisible': ~Eval('type').in_(['goods', 'assets']),
+            },
+        depends=['type'],
+        help="The sequence used to automatically number lots.")
 
     @classmethod
     def __register__(cls, module):
@@ -66,6 +97,14 @@
                 template_lot_type_table_name)
             table_h.drop_table('stock.lot.type', lot_type_table_name)
 
+    @classmethod
+    def default_lot_sequence(cls, **pattern):
+        pool = Pool()
+        Configuration = pool.get('product.configuration')
+        sequence = Configuration(1).get_multivalue(
+            'default_lot_sequence', **pattern)
+        return sequence.id if sequence else None
+
 
 class Product(metaclass=PoolMeta):
     __name__ = 'product.product'
diff -r e49aaf8553e1 -r 3d08a1207d55 product.xml
--- a/product.xml       Wed Dec 23 21:55:13 2020 +0100
+++ b/product.xml       Wed Feb 03 21:19:06 2021 +0100
@@ -3,6 +3,12 @@
 this repository contains the full copyright notices and license terms. -->
 <tryton>
     <data>
+        <record model="ir.ui.view" id="product_configuration_view_form">
+            <field name="model">product.configuration</field>
+            <field name="inherit" 
ref="product.product_configuration_view_form"/>
+            <field name="name">product_configuration_form</field>
+        </record>
+
         <record model="ir.ui.view" id="template_view_form">
             <field name="model">product.template</field>
             <field name="inherit" ref="product.template_view_form"/>
diff -r e49aaf8553e1 -r 3d08a1207d55 stock.py
--- a/stock.py  Wed Dec 23 21:55:13 2020 +0100
+++ b/stock.py  Wed Feb 03 21:19:06 2021 +0100
@@ -1,18 +1,21 @@
 # This file is part of Tryton.  The COPYRIGHT file at the top level of
 # this repository contains the full copyright notices and license terms.
 import datetime
+from copy import copy
 from functools import wraps
 
 from sql import Column
 
 from trytond.i18n import gettext
 from trytond.model import Model, ModelView, ModelSQL, fields
-from trytond.model.exceptions import AccessError, RequiredValidationError
-from trytond.pyson import Eval
+from trytond.model.exceptions import (
+    AccessError, RequiredValidationError, ValidationError)
+from trytond.pyson import Eval, Bool, Len
 from trytond.pool import Pool, PoolMeta
 from trytond.tools import grouped_slice
 from trytond.transaction import Transaction
 from trytond.modules.stock import StockMixin
+from trytond.wizard import Wizard, StateView, StateTransition, Button
 
 
 def check_no_move(func):
@@ -47,12 +50,29 @@
     return decorator
 
 
-class Lot(ModelSQL, ModelView, StockMixin):
+class LotMixin:
+
+    number = fields.Char(
+        "Number", required=True, select=True,
+        states={
+            'required': ~Eval('has_sequence') | (Eval('id', -1) >= 0),
+            },
+        depends=['has_sequence'])
+    product = fields.Many2One('product.product', 'Product', required=True)
+    has_sequence = fields.Function(
+        fields.Boolean("Has Sequence"), 'on_change_with_has_sequence')
+
+    @fields.depends('product')
+    def on_change_with_has_sequence(self, name=None):
+        if self.product:
+            return bool(self.product.lot_sequence)
+
+
+class Lot(ModelSQL, ModelView, LotMixin, StockMixin):
     "Stock Lot"
     __name__ = 'stock.lot'
     _rec_name = 'number'
-    number = fields.Char('Number', required=True, select=True)
-    product = fields.Many2One('product.product', 'Product', required=True)
+
     quantity = fields.Function(fields.Float('Quantity'), 'get_quantity',
         searcher='search_quantity')
     forecast_quantity = fields.Function(fields.Float('Forecast Quantity'),
@@ -94,6 +114,24 @@
             return self.product.default_uom.digits
 
     @classmethod
+    def create(cls, vlist):
+        vlist = [v.copy() for v in vlist]
+        for values in vlist:
+            if not values.get('number'):
+                values['number'] = cls._new_number(values)
+        return super().create(vlist)
+
+    @classmethod
+    def _new_number(cls, values):
+        pool = Pool()
+        Product = pool.get('product.product')
+        Sequence = pool.get('ir.sequence')
+        if values.get('product'):
+            product = Product(values['product'])
+            if product.lot_sequence:
+                return Sequence.get_id(product.lot_sequence.id)
+
+    @classmethod
     @check_no_move
     def write(cls, *args):
         super(Lot, cls).write(*args)
@@ -235,6 +273,22 @@
             },
         depends=['state', 'product'])
 
+    @classmethod
+    def __setup__(cls):
+        super().__setup__()
+        cls._buttons.update({
+                'add_lots_wizard': {
+                    'invisible': ~Eval('state').in_(['draft', 'assigned']),
+                    'readonly': Bool(Eval('lot')),
+                    'depends': ['lot', 'state'],
+                    },
+                })
+
+    @classmethod
+    @ModelView.button_action('stock_lot.wizard_move_add_lots')
+    def add_lots_wizard(cls, moves):
+        pass
+
     def check_lot(self):
         "Check if lot is required"
         if (self.state == 'done'
@@ -253,6 +307,201 @@
             move.check_lot()
 
 
+class MoveAddLots(Wizard):
+    "Add Lots"
+    __name__ = 'stock.move.add.lots'
+    start = StateView('stock.move.add.lots.start',
+        'stock_lot.move_add_lots_start_view_form', [
+            Button("Cancel", 'end', 'tryton-cancel'),
+            Button("Add", 'add', 'tryton-ok', default=True),
+            ])
+    add = StateTransition()
+
+    def default_start(self, fields):
+        default = {}
+        if 'product' in fields:
+            default['product'] = self.record.product.id
+        if 'quantity' in fields:
+            default['quantity'] = self.record.quantity
+        if 'unit' in fields:
+            default['unit'] = self.record.uom.id
+        return default
+
+    def transition_add(self):
+        pool = Pool()
+        Lang = pool.get('ir.lang')
+        Lot = pool.get('stock.lot')
+        lang = Lang.get()
+        quantity_remaining = self.start.on_change_with_quantity_remaining()
+        if quantity_remaining < 0:
+            digits = self.record.uom.digits
+            move_quantity = self.record.quantity
+            lot_quantity = self.record.quantity - quantity_remaining
+            raise ValidationError(gettext(
+                    'stock_lot.msg_move_add_lot_quantity',
+                    lot_quantity=lang.format('%.*f', (digits, lot_quantity)),
+                    move_quantity=lang.format(
+                        '%.*f', (digits, move_quantity))))
+        lots = []
+        for line in self.start.lots:
+            lot = line.get_lot(self.record)
+            lots.append(lot)
+        Lot.save(lots)
+        if quantity_remaining:
+            self.record.quantity = quantity_remaining
+            self.record.save()
+        for i, (line, lot) in enumerate(zip(self.start.lots, lots)):
+            if not i and not quantity_remaining:
+                self.record.quantity = line.quantity
+                self.record.lot = lot
+                self.record.save()
+            else:
+                with Transaction().set_context(_stock_move_split=True):
+                    self.model.copy([self.record], {
+                            'quantity': line.quantity,
+                            'lot': lot.id,
+                            })
+        return 'end'
+
+
+class MoveAddLotsStart(ModelView):
+    "Add Lots"
+    __name__ = 'stock.move.add.lots.start'
+
+    product = fields.Many2One('product.product', "Product", readonly=True)
+    quantity = fields.Float(
+        "Quantity", digits=(16, Eval('unit_digits', 2)), readonly=True,
+        depends=['unit_digits'])
+    unit = fields.Many2One('product.uom', "Unit", readonly=True)
+    unit_digits = fields.Function(
+        fields.Integer("Unit Digits"), 'on_change_with_unit_digits')
+    quantity_remaining = fields.Function(
+        fields.Float(
+            "Quantity Remaining", digits=(16, Eval('unit_digits', 2)),
+            depends=['unit_digits']),
+        'on_change_with_quantity_remaining')
+
+    lots = fields.One2Many(
+        'stock.move.add.lots.start.lot', 'parent', "Lots",
+        domain=[
+            ('product', '=', Eval('product', -1)),
+            ],
+        states={
+            'readonly': ~Eval('quantity_remaining', 0),
+            },
+        depends=['product', 'quantity_remaining'])
+
+    duplicate_lot_number = fields.Integer(
+        "Duplicate Lot Number",
+        states={
+            'invisible': Len(Eval('lots')) != 1,
+            },
+        depends=['lots'],
+        help="The number of times the lot must be duplicated.")
+
+    @classmethod
+    def __setup__(cls):
+        super().__setup__()
+        cls._buttons.update(
+            duplicate_lot={
+                'invisible': Len(Eval('lots')) != 1,
+                'readonly': (~Eval('duplicate_lot_number')
+                    | (Eval('duplicate_lot_number', 0) <= 0)),
+                'depends': ['lots', 'duplicate_lot_number'],
+                },
+            )
+
+    @fields.depends('unit')
+    def on_change_with_unit_digits(self, name=None):
+        if self.unit:
+            return self.unit.digits
+
+    @fields.depends('quantity', 'lots', 'unit')
+    def on_change_with_quantity_remaining(self, name=None):
+        if self.quantity is not None:
+            quantity = self.quantity
+            if self.lots:
+                for lot in self.lots:
+                    quantity -= getattr(lot, 'quantity', 0) or 0
+            if self.unit:
+                quantity = self.unit.round(quantity)
+            return quantity
+
+    @ModelView.button_change(
+        'lots', 'duplicate_lot_number',
+        methods=['on_change_with_quantity_remaining'])
+    def duplicate_lot(self):
+        lots = list(self.lots)
+        template, = self.lots
+        for i in range(self.duplicate_lot_number):
+            lot = copy(template)
+            lot._id = None
+            lots.append(lot)
+        self.lots = lots
+        self.quantity_remaining = self.on_change_with_quantity_remaining()
+
+
+class MoveAddLotsStartLot(ModelView, LotMixin):
+    "Add Lots"
+    __name__ = 'stock.move.add.lots.start.lot'
+
+    parent = fields.Many2One('stock.move.add.lots.start', "Parent")
+    quantity = fields.Float(
+        "Quantity", digits=(16, Eval('unit_digits', 2)), required=True,
+        depends=['unit_digits'])
+    unit_digits = fields.Function(
+        fields.Integer("Unit Digits"), 'on_change_with_unit_digits')
+
+    @fields.depends(
+        'parent', '_parent_parent.quantity_remaining')
+    def on_change_parent(self):
+        if (self.parent
+                and self.parent.quantity_remaining is not None):
+            self.quantity = self.parent.quantity_remaining
+
+    @fields.depends('parent', '_parent_parent.unit_digits')
+    def on_change_with_unit_digits(self, name=None):
+        if self.parent:
+            return self.parent.unit_digits
+
+    @fields.depends('number', 'product', methods=['_set_lot_values'])
+    def on_change_number(self):
+        pool = Pool()
+        Lot = pool.get('stock.lot')
+        if self.number and self.product:
+            lots = Lot.search([
+                    ('number', '=', self.number),
+                    ('product', '=', self.product.id),
+                    ])
+            if len(lots) == 1:
+                lot, = lots
+                self._set_lot_values(lot)
+
+    def _set_lot_values(self, lot):
+        pass
+
+    def get_lot(self, move):
+        pool = Pool()
+        Lot = pool.get('stock.lot')
+        values = self._get_lot_values(move)
+        lots = Lot.search(
+            [(k, '=', v) for k, v in values.items()],
+            limit=1)
+        if lots:
+            lot, = lots
+        else:
+            lot = Lot()
+            for k, v in values.items():
+                setattr(lot, k, v)
+        return lot
+
+    def _get_lot_values(self, move):
+        return {
+            'number': self.number,
+            'product': move.product,
+            }
+
+
 class ShipmentIn(metaclass=PoolMeta):
     __name__ = 'stock.shipment.in'
 
diff -r e49aaf8553e1 -r 3d08a1207d55 stock.xml
--- a/stock.xml Wed Dec 23 21:55:13 2020 +0100
+++ b/stock.xml Wed Feb 03 21:19:06 2021 +0100
@@ -201,6 +201,36 @@
             <field name="name">move_tree</field>
         </record>
 
+        <record model="ir.model.button" id="move_add_lots_wizard_button">
+            <field name="name">add_lots_wizard</field>
+            <field name="string">Add Lots</field>
+            <field name="model" search="[('model', '=', 'stock.move')]"/>
+        </record>
+
+        <record model="ir.action.wizard" id="wizard_move_add_lots">
+            <field name="name">Add Lots</field>
+            <field name="wiz_name">stock.move.add.lots</field>
+            <field name="model">stock.move</field>
+        </record>
+
+        <record model="ir.ui.view" id="move_add_lots_start_view_form">
+            <field name="model">stock.move.add.lots.start</field>
+            <field name="type">form</field>
+            <field name="name">stock_move_add_lots_start_form</field>
+        </record>
+
+        <record model="ir.ui.view" id="move_add_lots_start_lot_view_list">
+            <field name="model">stock.move.add.lots.start.lot</field>
+            <field name="type">tree</field>
+            <field name="name">stock_move_add_lots_start_lot_list</field>
+        </record>
+
+        <record model="ir.model.button" 
id="move_add_lots_start_duplicate_lot_button">
+            <field name="name">duplicate_lot</field>
+            <field name="string">Duplicate Lot</field>
+            <field name="model" search="[('model', '=', 
'stock.move.add.lots.start')]"/>
+        </record>
+
         <record model="ir.ui.view" id="period_cache_lot_view_form">
             <field name="model">stock.period.cache.lot</field>
             <field name="type">form</field>
diff -r e49aaf8553e1 -r 3d08a1207d55 tests/scenario_stock_lot_move_add.rst
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/scenario_stock_lot_move_add.rst     Wed Feb 03 21:19:06 2021 +0100
@@ -0,0 +1,108 @@
+===========================
+Stock Lot Move Add Scenario
+===========================
+
+Imports::
+
+    >>> from decimal import Decimal
+    >>> from proteus import Model, Wizard
+    >>> from trytond.tests.tools import activate_modules
+    >>> from trytond.modules.company.tests.tools import create_company
+
+Activate modules::
+
+    >>> config = activate_modules('stock_lot')
+
+Create company::
+
+    >>> _ = create_company()
+
+Create product::
+
+    >>> ProductUom = Model.get('product.uom')
+    >>> ProductTemplate = Model.get('product.template')
+    >>> unit, = ProductUom.find([('name', '=', 'Unit')])
+
+    >>> template = ProductTemplate()
+    >>> template.name = 'Product'
+    >>> template.default_uom = unit
+    >>> template.type = 'goods'
+    >>> template.list_price = Decimal('20')
+    >>> template.save()
+    >>> product, = template.products
+
+Get stock locations::
+
+    >>> Location = Model.get('stock.location')
+    >>> storage_loc, = Location.find([('code', '=', 'STO')])
+    >>> customer_loc, = Location.find([('code', '=', 'CUS')])
+
+Create a move::
+
+    >>> Move = Model.get('stock.move')
+    >>> move = Move()
+    >>> move.from_location = storage_loc
+    >>> move.to_location = customer_loc
+    >>> move.product = product
+    >>> move.quantity = 10
+    >>> move.unit_price = Decimal('20')
+    >>> move.save()
+
+Create a lot::
+
+    >>> Lot = Model.get('stock.lot')
+    >>> lot2 = Lot(number='02', product=product)
+    >>> lot2.save()
+
+Add few lots::
+
+    >>> add_lots = Wizard('stock.move.add.lots', [move])
+    >>> add_lots.form.product== product
+    True
+    >>> add_lots.form.quantity
+    10.0
+    >>> add_lots.form.unit == unit
+    True
+    >>> add_lots.form.quantity_remaining
+    10.0
+
+    >>> lot = add_lots.form.lots.new()
+    >>> lot.quantity
+    10.0
+    >>> lot.number = '01'
+    >>> lot.quantity = 2
+    >>> lot = add_lots.form.lots.new()
+    >>> lot.quantity
+    8.0
+    >>> lot.number = '02'
+    >>> lot.quantity = 1
+
+    >>> add_lots.execute('add')
+
+Check moves::
+
+    >>> move, = Move.find([('lot.number', '=', '01')])
+    >>> move.quantity
+    2.0
+    >>> move, = Move.find([('lot.number', '=', '02')])
+    >>> move.quantity
+    1.0
+    >>> move.lot == lot2
+    True
+    >>> move, = Move.find([('lot', '=', None)])
+    >>> move.quantity
+    7.0
+
+Add lot to remaining::
+
+    >>> add_lots = Wizard('stock.move.add.lots', [move])
+    >>> lot = add_lots.form.lots.new()
+    >>> lot.number = '03'
+    >>> add_lots.execute('add')
+
+    >>> len(Move.find([]))
+    3
+    >>> move.lot.number
+    '03'
+    >>> move.quantity
+    7.0
diff -r e49aaf8553e1 -r 3d08a1207d55 tests/scenario_stock_lot_number.rst
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/scenario_stock_lot_number.rst       Wed Feb 03 21:19:06 2021 +0100
@@ -0,0 +1,51 @@
+=========================
+Stock Lot Number Scenario
+=========================
+
+Imports::
+
+    >>> from decimal import Decimal
+    >>> from proteus import Model
+    >>> from trytond.tests.tools import activate_modules
+
+Activate modules::
+
+    >>> config = activate_modules('stock_lot')
+
+Create lot sequence::
+
+    >>> Sequence = Model.get('ir.sequence')
+    >>> sequence = Sequence(name="Lot", code='stock.lot')
+    >>> sequence.save()
+
+Set default sequence::
+
+    >>> Configuration = Model.get('product.configuration')
+    >>> configuration = Configuration(1)
+    >>> configuration.default_lot_sequence = sequence
+    >>> configuration.save()
+
+Create product::
+
+    >>> ProductUom = Model.get('product.uom')
+    >>> ProductTemplate = Model.get('product.template')
+    >>> unit, = ProductUom.find([('name', '=', 'Unit')])
+
+    >>> template = ProductTemplate()
+    >>> template.lot_sequence == sequence
+    True
+    >>> template.name = 'Product'
+    >>> template.default_uom = unit
+    >>> template.type = 'goods'
+    >>> template.list_price = Decimal('20')
+    >>> template.save()
+    >>> product, = template.products
+
+Create lot without number::
+
+    >>> Lot = Model.get('stock.lot')
+    >>> lot = Lot(product=product)
+    >>> lot.save()
+
+    >>> lot.number
+    '1'
diff -r e49aaf8553e1 -r 3d08a1207d55 tests/test_stock_lot.py
--- a/tests/test_stock_lot.py   Wed Dec 23 21:55:13 2020 +0100
+++ b/tests/test_stock_lot.py   Wed Feb 03 21:19:06 2021 +0100
@@ -325,4 +325,12 @@
             tearDown=doctest_teardown, encoding='utf-8',
             checker=doctest_checker,
             optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+    suite.addTests(doctest.DocFileSuite('scenario_stock_lot_number.rst',
+            tearDown=doctest_teardown, encoding='utf-8',
+            checker=doctest_checker,
+            optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+    suite.addTests(doctest.DocFileSuite('scenario_stock_lot_move_add.rst',
+            tearDown=doctest_teardown, encoding='utf-8',
+            checker=doctest_checker,
+            optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
     return suite
diff -r e49aaf8553e1 -r 3d08a1207d55 tryton.cfg
--- a/tryton.cfg        Wed Dec 23 21:55:13 2020 +0100
+++ b/tryton.cfg        Wed Feb 03 21:19:06 2021 +0100
@@ -5,6 +5,7 @@
     product
     stock
 xml:
+    ir.xml
     stock.xml
     product.xml
     message.xml
diff -r e49aaf8553e1 -r 3d08a1207d55 view/move_form.xml
--- a/view/move_form.xml        Wed Dec 23 21:55:13 2020 +0100
+++ b/view/move_form.xml        Wed Feb 03 21:19:06 2021 +0100
@@ -7,4 +7,7 @@
         <field name="lot"/>
         <newline/>
     </xpath>
+    <xpath expr="/form/group[@id='buttons']" position="inside">
+        <button name="add_lots_wizard" icon="tryton-add"/>
+    </xpath>
 </data>
diff -r e49aaf8553e1 -r 3d08a1207d55 view/move_tree.xml
--- a/view/move_tree.xml        Wed Dec 23 21:55:13 2020 +0100
+++ b/view/move_tree.xml        Wed Feb 03 21:19:06 2021 +0100
@@ -5,4 +5,7 @@
     <xpath expr="/tree/field[@name='product']" position="after">
         <field name="lot" expand="1"/>
     </xpath>
+    <xpath expr="/tree" position="inside">
+        <button name="add_lots_wizard"/>
+    </xpath>
 </data>
diff -r e49aaf8553e1 -r 3d08a1207d55 view/product_configuration_form.xml
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/view/product_configuration_form.xml       Wed Feb 03 21:19:06 2021 +0100
@@ -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_sequence']" position="after">
+        <label name="default_lot_sequence"/>
+        <field name="default_lot_sequence"/>
+    </xpath>
+</data>
diff -r e49aaf8553e1 -r 3d08a1207d55 view/stock_move_add_lots_start_form.xml
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/view/stock_move_add_lots_start_form.xml   Wed Feb 03 21:19:06 2021 +0100
@@ -0,0 +1,20 @@
+<?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. -->
+<form>
+    <label name="product"/>
+    <field name="product" colspan="3"/>
+
+    <label name="quantity"/>
+    <field name="quantity" symbol="unit"/>
+    <label name="quantity_remaining" string="Remaining:"/>
+    <field name="quantity_remaining" symbol="unit"/>
+
+    <field name="lots" colspan="4"/>
+
+    <group col="-1" colspan="2" id="duplicate_lot">
+        <button name="duplicate_lot" icon="tryton-copy"/>
+        <field name="duplicate_lot_number" xexpand="0"/>
+        <label name="duplicate_lot_number" string="times"/>
+    </group>
+</form>
diff -r e49aaf8553e1 -r 3d08a1207d55 view/stock_move_add_lots_start_lot_list.xml
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/view/stock_move_add_lots_start_lot_list.xml       Wed Feb 03 21:19:06 
2021 +0100
@@ -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. -->
+<tree editable="1">
+    <field name="parent" expand="1"/>
+    <field name="number" expand="2"/>
+    <field name="product" expand="1"/>
+    <field name="quantity"/>
+</tree>
diff -r e49aaf8553e1 -r 3d08a1207d55 view/template_form.xml
--- a/view/template_form.xml    Wed Dec 23 21:55:13 2020 +0100
+++ b/view/template_form.xml    Wed Feb 03 21:19:06 2021 +0100
@@ -6,6 +6,8 @@
         <page string="Lots" name="lot_required">
             <label name="lot_required"/>
             <field name="lot_required" yexpand="0"/>
+            <label name="lot_sequence"/>
+            <field name="lot_sequence"/>
         </page>
     </xpath>
 </data>

Reply via email to