diff --git a/CHANGELOG b/CHANGELOG index fc5dfd1bed..c400439cc0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Recompute cost price of drop shipment * Add support for Python 3.10 * Remove support for Python 3.6 diff --git a/__init__.py b/__init__.py index 6cab8e7a1d..6294905539 100644 --- a/__init__.py +++ b/__init__.py @@ -3,7 +3,7 @@ from trytond.pool import Pool -from . import party, purchase, sale, stock +from . import party, product, purchase, sale, stock def register(): @@ -22,6 +22,7 @@ def register(): purchase.Purchase, purchase.Line, purchase.ProductSupplier, + product.Product, module='sale_supply_drop_shipment', type_='model') Pool.register( purchase.RequestCreatePurchase, diff --git a/product.py b/product.py new file mode 100644 index 0000000000..fbd774b8fa --- /dev/null +++ b/product.py @@ -0,0 +1,29 @@ +# 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.pool import Pool, PoolMeta +from trytond.tools import grouped_slice + + +class Product(metaclass=PoolMeta): + __name__ = 'product.product' + + @classmethod + def recompute_cost_price(cls, products, start=None): + pool = Pool() + Move = pool.get('stock.move') + Shipment = pool.get('stock.shipment.drop') + shipments = set() + for sub_products in grouped_slice(products): + domain = [ + ('unit_price_updated', '=', True), + cls._domain_moves_cost(), + ('product', 'in', [p.id for p in sub_products]), + ('shipment', 'like', 'stock.shipment.drop,%'), + ] + if start: + domain.append(('effective_date', '>=', start)) + for move in Move.search(domain, order=[]): + shipments.add(move.shipment) + shipments = Shipment.browse(list(shipments)) + Shipment.set_cost(shipments) + super().recompute_cost_price(products, start=start) diff --git a/stock.py b/stock.py index 2c695afe9d..9f0fa4f81d 100644 --- a/stock.py +++ b/stock.py @@ -443,7 +443,6 @@ class ShipmentDrop(Workflow, ModelSQL, ModelView): to_save = [] for shipment in shipments: product_qty = defaultdict(int) - product_cost = defaultdict(int) for c_move in shipment.customer_moves: if c_move.state == 'cancelled': @@ -456,10 +455,6 @@ class ShipmentDrop(Workflow, ModelSQL, ModelView): for s_move in shipment.supplier_moves: if s_move.state == 'cancelled': continue - internal_quantity = Decimal(str(s_move.internal_quantity)) - product_cost[s_move.product] += ( - s_move.get_cost_price() * internal_quantity) - quantity = UoM.compute_qty( s_move.uom, s_move.quantity, s_move.product.default_uom, round=False) @@ -488,15 +483,8 @@ class ShipmentDrop(Workflow, ModelSQL, ModelView): new_customer_move.unit_price = unit_price to_save.append(new_customer_move) - for product, cost in product_cost.items(): - qty = Decimal(str(s_product_qty[product])) - if qty: - product_cost[product] = round_price(cost / qty) for c_move in list(shipment.customer_moves) + to_save: - if c_move.id is not None and c_move.state == 'cancelled': - continue - c_move.cost_price = product_cost[c_move.product] - if c_move.id is None: + if c_move.id is None or c_move.state == 'cancelled': continue if product_qty[c_move.product] > 0: exc_qty = UoM.compute_qty( @@ -527,6 +515,41 @@ class ShipmentDrop(Workflow, ModelSQL, ModelView): company=move.company, ) + @classmethod + def set_cost(cls, shipments): + pool = Pool() + Move = pool.get('stock.move') + UoM = pool.get('product.uom') + + to_save = [] + for shipment in shipments: + product_cost = defaultdict(int) + s_product_qty = defaultdict(int) + for s_move in shipment.supplier_moves: + if s_move.state == 'cancelled': + continue + internal_quantity = Decimal(str(s_move.internal_quantity)) + product_cost[s_move.product] += ( + s_move.get_cost_price() * internal_quantity) + + quantity = UoM.compute_qty( + s_move.uom, s_move.quantity, s_move.product.default_uom, + round=False) + s_product_qty[s_move.product] += quantity + + for product, cost in product_cost.items(): + qty = Decimal(str(s_product_qty[product])) + if qty: + product_cost[product] = round_price(cost / qty) + + for c_move in shipment.customer_moves: + cost_price = product_cost[c_move.product] + if cost_price != c_move.cost_price: + c_move.cost_price = cost_price + to_save.append(c_move) + if to_save: + Move.save(to_save) + @classmethod @ModelView.button @Workflow.transition('waiting') @@ -590,6 +613,7 @@ class ShipmentDrop(Workflow, ModelSQL, ModelView): pool = Pool() Move = pool.get('stock.move') Date = pool.get('ir.date') + cls.set_cost(shipments) Move.do([m for s in shipments for m in s.customer_moves]) for company, shipments in groupby(shipments, key=lambda s: s.company): with Transaction().set_context(company=company.id): diff --git a/tests/scenario_sale_supply_drop_shipment.rst b/tests/scenario_sale_supply_drop_shipment.rst index 143707624c..c7035e8a54 100644 --- a/tests/scenario_sale_supply_drop_shipment.rst +++ b/tests/scenario_sale_supply_drop_shipment.rst @@ -62,10 +62,23 @@ Create stock user:: >>> stock_force_group, = Group.find([ ... ('name', '=', 'Stock Force Assignment'), ... ]) + >>> product_admin_group, = Group.find([ + ... ('name', '=', "Product Administration"), + ... ]) >>> stock_user.groups.append(stock_group) >>> stock_user.groups.append(stock_force_group) + >>> stock_user.groups.append(product_admin_group) >>> stock_user.save() +Create account user:: + + >>> account_user = User() + >>> account_user.name = "Account" + >>> account_user.login = 'account' + >>> account_group, = Group.find([('name', '=', "Account")]) + >>> account_user.groups.append(account_group) + >>> account_user.save() + Create fiscal year:: >>> fiscalyear = set_fiscalyear_invoice_sequences( @@ -190,7 +203,6 @@ Receiving only 100 products:: >>> move.unit_price Decimal('10.0000') >>> move.cost_price - Decimal('3.0000') >>> set_user(sale_user) >>> sale.reload() >>> sale.shipments @@ -204,6 +216,9 @@ Receiving only 100 products:: >>> shipment.click('done') >>> shipment.state 'done' + >>> move, = shipment.customer_moves + >>> move.cost_price + Decimal('3.0000') >>> set_user(sale_user) >>> sale.reload() >>> sale.shipments @@ -251,8 +266,29 @@ As a consequence the sale order is now in exception:: >>> sale.shipment_state 'exception' +Receive purchase invoice at different price:: + + >>> set_user(account_user) + >>> invoice, = purchase.invoices + >>> invoice_line, = invoice.lines + >>> invoice_line.unit_price = Decimal('4.0000') + >>> invoice.invoice_date = today + >>> invoice.click('post') + + >>> set_user(stock_user) + + >>> recompute = Wizard('product.recompute_cost_price', [product]) + >>> recompute.execute('recompute') + + >>> shipment, = [s for s in purchase.drop_shipments + ... if s.state == 'done'] + >>> move, = shipment.customer_moves + >>> move.cost_price + Decimal('4.0000') + Cancelling the workflow on the purchase step:: + >>> set_user(sale_user) >>> sale = Sale() >>> sale.party = customer >>> sale.payment_term = payment_term