@@ -1,3 +1,4 @@
|
||||
* Add pack wizard
|
||||
* Add support for Python 3.14
|
||||
* Remove support for Python 3.9
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ from trytond.model import (
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.pyson import Bool, Eval, Id, If
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.wizard import (
|
||||
Button, StateAction, StateTransition, StateView, Wizard)
|
||||
|
||||
from .exceptions import PackageError, PackageValidationError
|
||||
|
||||
@@ -200,16 +202,21 @@ class Package(tree(), MeasurementsMixin, ModelSQL, ModelView):
|
||||
],
|
||||
states={
|
||||
'readonly': Eval('state') == 'closed',
|
||||
})
|
||||
},
|
||||
help="The package that contains this package.")
|
||||
children = fields.One2Many(
|
||||
'stock.package', 'parent', 'Children',
|
||||
domain=[
|
||||
('company', '=', Eval('company', -1)),
|
||||
('shipment', '=', Eval('shipment')),
|
||||
],
|
||||
add_remove=[
|
||||
('parent', '=', None),
|
||||
],
|
||||
states={
|
||||
'readonly': Eval('state') == 'closed',
|
||||
})
|
||||
},
|
||||
help="The packages contained in this package.")
|
||||
state = fields.Function(fields.Selection([
|
||||
('open', "Open"),
|
||||
('closed', "Closed"),
|
||||
@@ -229,6 +236,12 @@ class Package(tree(), MeasurementsMixin, ModelSQL, ModelView):
|
||||
field.states = {
|
||||
'readonly': Eval('state') == 'closed',
|
||||
}
|
||||
cls._buttons.update(
|
||||
fill={
|
||||
'invisible': Eval('state') != 'open',
|
||||
'icon': 'tryton-launch',
|
||||
'depends': ['state'],
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def __register__(cls, module):
|
||||
@@ -286,6 +299,11 @@ class Package(tree(), MeasurementsMixin, ModelSQL, ModelView):
|
||||
if isinstance(getattr(MeasurementsMixin, name), fields.Field):
|
||||
setattr(self, name, getattr(self.type, name))
|
||||
|
||||
@classmethod
|
||||
@ModelView.button_action('stock_package.wizard_package_pack')
|
||||
def fill(cls, packages):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def validate(cls, packages):
|
||||
super().validate(packages)
|
||||
@@ -393,6 +411,13 @@ class PackageMixin(object):
|
||||
('parent', '=', None),
|
||||
])
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls._buttons.update(pack_wizard={
|
||||
'icon': 'tryton-launch',
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def check_packages(cls, shipments):
|
||||
for shipment in shipments:
|
||||
@@ -408,6 +433,11 @@ class PackageMixin(object):
|
||||
def packages_moves(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
@ModelView.button_action('stock_package.wizard_shipment_pack')
|
||||
def pack_wizard(cls, shipments):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def copy(cls, shipments, default=None):
|
||||
default = default.copy() if default is not None else {}
|
||||
@@ -428,6 +458,9 @@ class ShipmentOut(PackageMixin, object, metaclass=PoolMeta):
|
||||
Eval('state') != 'picked')
|
||||
for field in [cls.packages, cls.root_packages]:
|
||||
field.states['readonly'] = packages_readonly
|
||||
cls._buttons['pack_wizard']['invisible'] = packages_readonly
|
||||
cls._buttons['pack_wizard']['depends'] = [
|
||||
'warehouse_storage', 'warehouse_output', 'state']
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@@ -482,6 +515,8 @@ class ShipmentInReturn(PackageMixin, object, metaclass=PoolMeta):
|
||||
packages_readonly = ~Eval('state').in_(['waiting', 'assigned'])
|
||||
for field in [cls.packages, cls.root_packages]:
|
||||
field.states['readonly'] = packages_readonly
|
||||
cls._buttons['pack_wizard']['invisible'] = packages_readonly
|
||||
cls._buttons['pack_wizard']['depends'] = ['state']
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@@ -508,6 +543,8 @@ class ShipmentInternal(PackageMixin, object, metaclass=PoolMeta):
|
||||
for field in [cls.packages, cls.root_packages]:
|
||||
field.states['readonly'] = packages_readonly
|
||||
field.states['invisible'] = packages_invisible
|
||||
cls._buttons['pack_wizard']['invisible'] = packages_invisible
|
||||
cls._buttons['pack_wizard']['depends'] = ['state', 'transit_location']
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@@ -521,3 +558,117 @@ class ShipmentInternal(PackageMixin, object, metaclass=PoolMeta):
|
||||
return [
|
||||
m for m in self.outgoing_moves
|
||||
if m.state != 'cancelled' and m.quantity]
|
||||
|
||||
|
||||
class ShipmentPack(Wizard):
|
||||
__name__ = 'stock.shipment.pack'
|
||||
start_state = 'package'
|
||||
package = StateView(
|
||||
'stock.package',
|
||||
'stock_package.package_view_form_pack', [
|
||||
Button("End", 'end', 'tryton-cancel'),
|
||||
Button("Add Package", 'add_package', 'tryton-ok'),
|
||||
Button(
|
||||
"Add Package and Fill", 'add_fill_package', 'tryton-add',
|
||||
default=True),
|
||||
])
|
||||
add_package = StateTransition()
|
||||
add_fill_package = StateAction('stock_package.wizard_package_pack')
|
||||
|
||||
def default_package(self, fields):
|
||||
return {
|
||||
'company': self.record.company,
|
||||
'shipment': self.record,
|
||||
}
|
||||
|
||||
def transition_add_package(self):
|
||||
self.package.save()
|
||||
return 'package'
|
||||
|
||||
def do_add_fill_package(self, action):
|
||||
self.package.save()
|
||||
return action, {
|
||||
'id': self.package.id,
|
||||
'ids': [self.package.id],
|
||||
'model': self.package.__name__,
|
||||
}
|
||||
|
||||
def transition_add_fill_package(self):
|
||||
return 'package'
|
||||
|
||||
|
||||
class PackagePack(Wizard):
|
||||
__name__ = 'stock.package.pack'
|
||||
start_state = 'next_'
|
||||
next_ = StateTransition()
|
||||
move = StateView(
|
||||
'stock.package.pack.move',
|
||||
'stock_package.package_pack_move_view_form', [
|
||||
Button("End", 'end', 'tryton-ok'),
|
||||
Button("Add", 'add_move', 'tryton-forward', default=True),
|
||||
])
|
||||
add_move = StateTransition()
|
||||
|
||||
def transition_next_(self):
|
||||
if any(not m.package for m in self.record.allowed_moves):
|
||||
return 'move'
|
||||
else:
|
||||
return 'end'
|
||||
|
||||
def default_move(self, fields):
|
||||
return {
|
||||
'allowed_moves': [
|
||||
m for m in self.record.allowed_moves if not m.package],
|
||||
}
|
||||
|
||||
def transition_add_move(self):
|
||||
pool = Pool()
|
||||
Move = pool.get('stock.move')
|
||||
move = self.move.source
|
||||
if self.move.quantity and self.move.quantity != move.quantity:
|
||||
with Transaction().set_context(_stock_move_split=True):
|
||||
Move.copy([move], {
|
||||
'quantity': move.quantity - self.move.quantity,
|
||||
})
|
||||
move.quantity = self.move.quantity
|
||||
move.package = self.record
|
||||
move.save()
|
||||
return 'next_'
|
||||
|
||||
|
||||
class PackagePackMove(ModelView):
|
||||
__name__ = 'stock.package.pack.move'
|
||||
|
||||
source = fields.Many2One(
|
||||
'stock.move', "Source", required=True,
|
||||
domain=[
|
||||
('id', 'in', Eval('allowed_moves', [])),
|
||||
('package', '=', None),
|
||||
])
|
||||
quantity = fields.Float(
|
||||
"Quantity", digits='unit',
|
||||
domain=['OR',
|
||||
('quantity', '=', None),
|
||||
[
|
||||
('quantity', '>', 0),
|
||||
('quantity', '<=', Eval('move_quantity', 0)),
|
||||
],
|
||||
],
|
||||
help="The quantity to pack from the move.\n"
|
||||
"Leave empty for the full quantity of the move.")
|
||||
unit = fields.Function(
|
||||
fields.Many2One('product.uom', "Unit"),
|
||||
'on_change_with_unit')
|
||||
move_quantity = fields.Function(
|
||||
fields.Float("Move Quantity"),
|
||||
'on_change_with_move_quantity')
|
||||
allowed_moves = fields.Many2Many(
|
||||
'stock.move', None, None, "Allowed Moves", readonly=True)
|
||||
|
||||
@fields.depends('source')
|
||||
def on_change_with_unit(self, name=None):
|
||||
return self.source.unit if self.source else None
|
||||
|
||||
@fields.depends('source')
|
||||
def on_change_with_move_quantity(self, name=None):
|
||||
return self.source.quantity if self.source else None
|
||||
|
||||
@@ -31,22 +31,38 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<record model="ir.ui.view" id="package_view_form">
|
||||
<field name="model">stock.package</field>
|
||||
<field name="type">form</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">package_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="package_view_form_pack">
|
||||
<field name="model">stock.package</field>
|
||||
<field name="type">form</field>
|
||||
<field name="priority" eval="20"/>
|
||||
<field name="name">package_form_pack</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="package_view_tree">
|
||||
<field name="model">stock.package</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="field_childs">children</field>
|
||||
<field name="priority" eval="20"/>
|
||||
<field name="name">package_tree</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="package_view_list">
|
||||
<field name="model">stock.package</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">package_list</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.button" id="package_fill_button">
|
||||
<field name="model">stock.package</field>
|
||||
<field name="name">fill</field>
|
||||
<field name="string">Fill</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.rule.group" id="rule_group_package_companies">
|
||||
<field name="name">User in companies</field>
|
||||
<field name="model">stock.package</field>
|
||||
@@ -137,17 +153,52 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="name">shipment_out_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.button" id="shipment_out_pack_wizard_button">
|
||||
<field name="model">stock.shipment.out</field>
|
||||
<field name="name">pack_wizard</field>
|
||||
<field name="string">Pack</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="shipment_in_return_view_form">
|
||||
<field name="model">stock.shipment.in.return</field>
|
||||
<field name="inherit" ref="stock.shipment_in_return_view_form"/>
|
||||
<field name="name">shipment_in_return_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.button" id="shipment_in_return_pack_wizard_button">
|
||||
<field name="model">stock.shipment.in.return</field>
|
||||
<field name="name">pack_wizard</field>
|
||||
<field name="string">Fill</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="shipment_internal_view_form">
|
||||
<field name="model">stock.shipment.internal</field>
|
||||
<field name="inherit" ref="stock.shipment_internal_view_form"/>
|
||||
<field name="name">shipment_internal_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.button" id="shipment_internal_pack_wizard_button">
|
||||
<field name="model">stock.shipment.internal</field>
|
||||
<field name="name">pack_wizard</field>
|
||||
<field name="string">Add Package</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.wizard" id="wizard_shipment_pack">
|
||||
<field name="name">Pack Shipment</field>
|
||||
<field name="wiz_name">stock.shipment.pack</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.wizard" id="wizard_package_pack">
|
||||
<field name="name">Pack Package</field>
|
||||
<field name="wiz_name">stock.package.pack</field>
|
||||
<field name="model">stock.package</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="package_pack_move_view_form">
|
||||
<field name="model">stock.package.pack.move</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">package_pack_move_form</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
||||
|
||||
@@ -7,7 +7,7 @@ Imports::
|
||||
>>> import datetime as dt
|
||||
>>> from decimal import Decimal
|
||||
|
||||
>>> from proteus import Model
|
||||
>>> from proteus import Model, Wizard
|
||||
>>> from trytond.modules.company.tests.tools import create_company
|
||||
>>> from trytond.modules.currency.tests.tools import get_currency
|
||||
>>> from trytond.tests.tools import activate_modules
|
||||
@@ -18,6 +18,8 @@ Activate modules::
|
||||
|
||||
>>> config = activate_modules('stock_package', create_company)
|
||||
|
||||
>>> Package = Model.get('stock.package')
|
||||
|
||||
Get currency::
|
||||
|
||||
>>> currency = get_currency()
|
||||
@@ -66,7 +68,7 @@ Add two shipment lines of same product::
|
||||
>>> for move in shipment_out.outgoing_moves:
|
||||
... move.product = product
|
||||
... move.unit = unit
|
||||
... move.quantity = 1
|
||||
... move.quantity = 2
|
||||
... move.from_location = output_loc
|
||||
... move.to_location = customer_loc
|
||||
... move.unit_price = Decimal('1')
|
||||
@@ -91,37 +93,60 @@ Package products::
|
||||
>>> box.packaging_volume
|
||||
>>> box.packaging_volume_uom, = ProductUom.find([('name', '=', "Cubic meter")])
|
||||
>>> box.save()
|
||||
>>> package1 = shipment_out.packages.new(type=box)
|
||||
>>> package1.length
|
||||
80.0
|
||||
>>> package1.height = 50
|
||||
>>> package1.packaging_volume
|
||||
0.4
|
||||
>>> package_child = package1.children.new(shipment=shipment_out, type=box)
|
||||
>>> package_child.height = 100
|
||||
>>> moves = package_child.moves.find()
|
||||
|
||||
>>> shipment_pack = Wizard('stock.shipment.pack', [shipment_out])
|
||||
|
||||
>>> shipment_pack.form.type = box
|
||||
>>> shipment_pack.form.height = 100
|
||||
>>> shipment_pack.execute('add_fill_package')
|
||||
|
||||
>>> package_pack, = shipment_pack.actions
|
||||
>>> moves = package_pack.form.allowed_moves
|
||||
>>> len(moves)
|
||||
2
|
||||
>>> package_child.moves.append(moves[0])
|
||||
>>> package_pack.form.source = moves[0]
|
||||
>>> package_pack.execute('add_move')
|
||||
>>> package_pack.execute('end')
|
||||
|
||||
>>> shipment_out.save()
|
||||
>>> shipment_out.reload()
|
||||
>>> package, = shipment_out.root_packages
|
||||
|
||||
>>> shipment_pack.form.type = box
|
||||
>>> shipment_pack.form.children.append(Package(package.id))
|
||||
>>> shipment_pack.form.length
|
||||
80.0
|
||||
>>> shipment_pack.form.height = 50
|
||||
>>> shipment_pack.form.packaging_volume
|
||||
0.4
|
||||
|
||||
>>> shipment_pack.execute('add_package')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
PackageValidationError: ...
|
||||
|
||||
>>> package1.height = 120
|
||||
>>> package1.packaging_volume
|
||||
>>> shipment_pack.form.height = 120
|
||||
>>> shipment_pack.form.packaging_volume
|
||||
0.96
|
||||
>>> shipment_pack.execute('add_package')
|
||||
|
||||
>>> shipment_out.reload()
|
||||
>>> shipment_out.click('pack')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
PackageError: ...
|
||||
|
||||
>>> package2 = shipment_out.packages.new(type=box)
|
||||
>>> moves = package2.moves.find()
|
||||
>>> len(moves)
|
||||
1
|
||||
>>> package2.moves.append(moves[0])
|
||||
>>> shipment_pack.form.type = box
|
||||
>>> shipment_pack.execute('add_fill_package')
|
||||
>>> package_pack, = shipment_pack.actions
|
||||
>>> package_pack.form.source, = package_pack.form.allowed_moves
|
||||
>>> package_pack.form.quantity = 1
|
||||
>>> package_pack.execute('add_move')
|
||||
>>> package_pack.form.source, = package_pack.form.allowed_moves
|
||||
>>> package_pack.execute('add_move')
|
||||
>>> package_pack.execute('end')
|
||||
>>> shipment_pack.execute('end')
|
||||
|
||||
>>> shipment_out.click('pack')
|
||||
>>> shipment_out.state
|
||||
'packed'
|
||||
|
||||
@@ -22,3 +22,7 @@ model:
|
||||
stock.ShipmentOut
|
||||
stock.ShipmentInReturn
|
||||
stock.ShipmentInternal
|
||||
stock.PackagePackMove
|
||||
wizard:
|
||||
stock.ShipmentPack
|
||||
stock.PackagePack
|
||||
|
||||
@@ -15,6 +15,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<notebook colspan="4">
|
||||
<page name="moves">
|
||||
<field name="moves" colspan="4" widget="many2many"/>
|
||||
<button name="fill" colspan="4"/>
|
||||
</page>
|
||||
<page name="children">
|
||||
<field name="children" colspan="4"/>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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="type"/>
|
||||
<field name="type" widget="selection"/>
|
||||
<label name="number"/>
|
||||
<field name="number"/>
|
||||
<label name="parent" string="Is contained in:"/>
|
||||
<field name="parent"/>
|
||||
<notebook colspan="4">
|
||||
<page name="children" string="Containing">
|
||||
<field name="children" colspan="4" widget="many2many" string="Containing"/>
|
||||
</page>
|
||||
<page string="Measurements" col="3" id="measurements">
|
||||
<label name="length"/>
|
||||
<field name="length"/>
|
||||
<field name="length_uom" widget="selection"/>
|
||||
|
||||
<label name="height"/>
|
||||
<field name="height"/>
|
||||
<field name="height_uom" widget="selection"/>
|
||||
|
||||
<label name="width"/>
|
||||
<field name="width"/>
|
||||
<field name="width_uom" widget="selection"/>
|
||||
|
||||
<label name="packaging_volume"/>
|
||||
<field name="packaging_volume"/>
|
||||
<field name="packaging_volume_uom" widget="selection"/>
|
||||
|
||||
<label name="packaging_weight"/>
|
||||
<field name="packaging_weight"/>
|
||||
<field name="packaging_weight_uom"/>
|
||||
</page>
|
||||
</notebook>
|
||||
<field name="company" invisible="1" colspan="4"/>
|
||||
<field name="shipment" invisible="1" colspan="4"/>
|
||||
</form>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?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 col="2">
|
||||
<label name="source"/>
|
||||
<field name="source"/>
|
||||
|
||||
<label name="quantity"/>
|
||||
<field name="quantity"/>
|
||||
</form>
|
||||
@@ -2,8 +2,10 @@
|
||||
<!-- 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='moves']" position="after">
|
||||
<field name="root_packages" colspan="4"
|
||||
view_ids="stock_package.package_view_tree"/>
|
||||
<xpath expr="//page[@name='moves']" position="after">
|
||||
<page name="root_packages">
|
||||
<field name="root_packages" colspan="4" view_ids="stock_package.package_view_tree"/>
|
||||
<button name="pack_wizard" colspan="4"/>
|
||||
</page>
|
||||
</xpath>
|
||||
</data>
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
<!-- 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="//page[@name='outgoing_moves']" position="inside">
|
||||
<field name="root_packages" colspan="4" view_ids="stock_package.package_view_tree"/>
|
||||
<xpath expr="//page[@name='outgoing_moves']" position="after">
|
||||
<page name="root_packages">
|
||||
<field name="root_packages" colspan="4" view_ids="stock_package.package_view_tree"/>
|
||||
<button name="pack_wizard" colspan="4"/>
|
||||
</page>
|
||||
</xpath>
|
||||
</data>
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
<!-- 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="/form/notebook/page[@id='outgoing_moves']" position="inside">
|
||||
<field name="root_packages" colspan="4"
|
||||
view_ids="stock_package.package_view_tree"/>
|
||||
<xpath expr="//page[@id='outgoing_moves']" position="after">
|
||||
<page name="root_packages">
|
||||
<field name="root_packages" colspan="4" view_ids="stock_package.package_view_tree"/>
|
||||
<button name="pack_wizard" colspan="4"/>
|
||||
</page>
|
||||
</xpath>
|
||||
</data>
|
||||
|
||||
Reference in New Issue
Block a user