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>