1 parent
9b2564145e
commit
914d87ff78
3 files changed
+153
-1
No files matched your search
@@ -1,3 +1,4 @@
|
||||
* Check inventory lot quantities of supplier shipment
|
||||
|
||||
Version 6.8.0 - 2023-05-01
|
||||
--------------------------
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# 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 collections import defaultdict
|
||||
from copy import copy
|
||||
from functools import wraps
|
||||
|
||||
@@ -13,6 +14,7 @@ from trytond.model import (
|
||||
from trytond.model.exceptions import (
|
||||
AccessError, RequiredValidationError, ValidationError)
|
||||
from trytond.modules.stock import StockMixin
|
||||
from trytond.modules.stock.exceptions import ShipmentCheckQuantityWarning
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.pyson import Bool, Eval, Len
|
||||
from trytond.tools import grouped_slice
|
||||
@@ -653,7 +655,59 @@ class MoveAddLotsStartLot(ModelView, LotMixin):
|
||||
}
|
||||
|
||||
|
||||
class ShipmentIn(metaclass=PoolMeta):
|
||||
class ShipmentCheckQuantity:
|
||||
"Check quantities per lot between source and target moves"
|
||||
__slots__ = ()
|
||||
|
||||
def check_quantity(self):
|
||||
pool = Pool()
|
||||
Warning = pool.get('res.user.warning')
|
||||
Lang = pool.get('ir.lang')
|
||||
lang = Lang.get()
|
||||
|
||||
super().check_quantity()
|
||||
|
||||
products_with_lot = set()
|
||||
source_qties = defaultdict(float)
|
||||
for move in self._check_quantity_source_moves:
|
||||
if move.lot:
|
||||
products_with_lot.add(move.product)
|
||||
source_qties[move.lot] += move.internal_quantity
|
||||
|
||||
target_qties = defaultdict(float)
|
||||
for move in self._check_quantity_target_moves:
|
||||
if move.lot:
|
||||
target_qties[move.lot] += move.internal_quantity
|
||||
|
||||
diffs = {}
|
||||
for lot, incoming_qty in target_qties.items():
|
||||
if (lot not in source_qties
|
||||
and lot.product not in products_with_lot):
|
||||
continue
|
||||
unit = lot.product.default_uom
|
||||
incoming_qty = unit.round(incoming_qty)
|
||||
inventory_qty = unit.round(source_qties.pop(lot, 0))
|
||||
diff = inventory_qty - incoming_qty
|
||||
if diff:
|
||||
diffs[lot] = diff
|
||||
|
||||
if diffs:
|
||||
warning_name = Warning.format(
|
||||
'check_quantity_lot', [self])
|
||||
if Warning.check(warning_name):
|
||||
quantities = []
|
||||
for lot, quantity in diffs.items():
|
||||
quantity = lang.format_number_symbol(
|
||||
quantity, lot.product.default_uom)
|
||||
quantities.append(f"{lot.rec_name}: {quantity}")
|
||||
raise ShipmentCheckQuantityWarning(warning_name,
|
||||
gettext(
|
||||
'stock.msg_shipment_check_quantity',
|
||||
shipment=self.rec_name,
|
||||
quantities=', '.join(quantities)))
|
||||
|
||||
|
||||
class ShipmentIn(ShipmentCheckQuantity, metaclass=PoolMeta):
|
||||
__name__ = 'stock.shipment.in'
|
||||
|
||||
def _get_inventory_move(self, incoming_move):
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
==============================
|
||||
Stock Lot Shipment In Scenario
|
||||
==============================
|
||||
|
||||
Imports::
|
||||
|
||||
>>> from decimal import Decimal
|
||||
>>> from proteus import Model
|
||||
>>> from trytond.tests.tools import activate_modules
|
||||
>>> from trytond.modules.company.tests.tools import (
|
||||
... create_company, get_company)
|
||||
|
||||
Activate modules::
|
||||
|
||||
>>> config = activate_modules('stock_lot')
|
||||
|
||||
>>> Location = Model.get('stock.location')
|
||||
>>> Lot = Model.get('stock.lot')
|
||||
>>> Party = Model.get('party.party')
|
||||
>>> ProductTemplate = Model.get('product.template')
|
||||
>>> ProductUom = Model.get('product.uom')
|
||||
>>> ShipmentIn = Model.get('stock.shipment.in')
|
||||
|
||||
Create company::
|
||||
|
||||
>>> _ = create_company()
|
||||
>>> company = get_company()
|
||||
|
||||
Create supplier::
|
||||
|
||||
>>> supplier = Party(name='Supplier')
|
||||
>>> supplier.save()
|
||||
|
||||
Create product::
|
||||
|
||||
>>> 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::
|
||||
|
||||
>>> supplier_loc, = Location.find([('code', '=', 'SUP')])
|
||||
>>> input_loc, = Location.find([('code', '=', 'IN')])
|
||||
>>> storage_loc, = Location.find([('code', '=', 'STO')])
|
||||
|
||||
Set 2 lots::
|
||||
|
||||
>>> lot1 = Lot(number="1", product=product)
|
||||
>>> lot1.save()
|
||||
>>> lot2 = Lot(number="2", product=product)
|
||||
>>> lot2.save()
|
||||
|
||||
Create a shipment::
|
||||
|
||||
>>> shipment = ShipmentIn()
|
||||
>>> shipment.supplier = supplier
|
||||
>>> move = shipment.incoming_moves.new()
|
||||
>>> move.product = product
|
||||
>>> move.quantity = 10
|
||||
>>> move.from_location = supplier_loc
|
||||
>>> move.to_location = input_loc
|
||||
>>> move.unit_price = Decimal('5')
|
||||
>>> move.currency = company.currency
|
||||
>>> shipment.save()
|
||||
|
||||
Receive the shipment with one lot::
|
||||
|
||||
>>> incoming_move, = shipment.incoming_moves
|
||||
>>> incoming_move.lot = lot1
|
||||
>>> shipment.click('receive')
|
||||
>>> shipment.state
|
||||
'received'
|
||||
|
||||
Change lot and try to finish::
|
||||
|
||||
>>> incoming_move, = shipment.inventory_moves
|
||||
>>> incoming_move.lot == lot1
|
||||
True
|
||||
>>> incoming_move.lot = lot2
|
||||
>>> shipment.click('done') # doctest: +IGNORE_EXCEPTION_DETAIL
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ShipmentCheckQuantityWarning: ...
|
||||
>>> incoming_move.reload()
|
||||
>>> incoming_move.lot = lot1
|
||||
>>> incoming_move.save()
|
||||
|
||||
Finish the shipment::
|
||||
|
||||
>>> shipment.click('done')
|
||||
>>> shipment.state
|
||||
'done'
|
||||
Reference in new issue
Block a user