# 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 decimal import ROUND_DOWN, ROUND_HALF_EVEN, Decimal from operator import itemgetter from trytond.model import ModelView, Workflow, fields from trytond.modules.product import price_digits, round_price from trytond.modules.stock_shipment_cost import ShipmentCostMixin from trytond.pool import Pool, PoolMeta from trytond.pyson import Eval class ShipmentIn(ShipmentCostMixin, metaclass=PoolMeta): __name__ = 'stock.shipment.in' @fields.depends('state') def on_change_with_shipment_cost_readonly(self, name=None): return self.state != 'draft' @property def shipment_cost_moves(self): return [ m for m in self.incoming_moves if m.state not in {'done', 'cancelled'}] def allocate_cost_by_value(self): pool = Pool() Currency = pool.get('currency.currency') Move = pool.get('stock.move') cost = self._get_shipment_cost() if not cost: return moves = self.shipment_cost_moves sum_value = 0 unit_prices = {} for move in moves: unit_price = Currency.compute(move.currency, move.unit_price, self.company.currency, round=False) unit_prices[move.id] = unit_price sum_value += unit_price * Decimal(str(move.quantity)) costs = [] digit = Move.unit_price.digits[1] exp = Decimal(str(10.0 ** -digit)) difference = cost for move in moves: quantity = Decimal(str(move.quantity)) if not sum_value: move_cost = cost / Decimal(len(moves)) else: move_cost = cost * quantity * unit_prices[move.id] / sum_value unit_shipment_cost = round_price( move_cost / quantity, rounding=ROUND_DOWN) costs.append({ 'unit_shipment_cost': unit_shipment_cost, 'difference': move_cost - (unit_shipment_cost * quantity), 'move': move, }) difference -= unit_shipment_cost * quantity costs.sort(key=itemgetter('difference')) for cost in costs: move = cost['move'] quantity = Decimal(str(move.quantity)) if exp * quantity < difference: cost['unit_shipment_cost'] += exp difference -= exp * quantity if difference < exp: break for cost in costs: move = cost['move'] unit_shipment_cost = Currency.compute( self.company.currency, cost['unit_shipment_cost'], move.currency, round=False) unit_shipment_cost = round_price( unit_shipment_cost, rounding=ROUND_HALF_EVEN) move.unit_price += unit_shipment_cost move.unit_shipment_cost = unit_shipment_cost Move.save(moves) @classmethod @ModelView.button @Workflow.transition('received') def receive(cls, shipments): Carrier = Pool().get('carrier') for shipment in shipments: shipment.cost = shipment.cost_used shipment.cost_currency = shipment.cost_currency_used if shipment.carrier: allocation_method = \ shipment.carrier.carrier_cost_allocation_method else: allocation_method = \ Carrier.default_carrier_cost_allocation_method() getattr(shipment, 'allocate_cost_by_%s' % allocation_method)() super().receive(shipments) @classmethod @ModelView.button @Workflow.transition('cancelled') def cancel(cls, shipments): for shipment in shipments: shipment.cost = None shipment.cost_currency = None cls.save(shipments) super().cancel(shipments) class Move(metaclass=PoolMeta): __name__ = 'stock.move' unit_shipment_cost = fields.Numeric( "Unit Shipment Cost", digits=price_digits, readonly=True, states={ 'invisible': ~Eval('unit_shipment_cost'), }) def _compute_unit_price(self, unit_price): if self.unit_shipment_cost: unit_price -= self.unit_shipment_cost unit_price = super()._compute_unit_price(unit_price) if self.unit_shipment_cost: unit_price += self.unit_shipment_cost return unit_price def _compute_component_unit_price(self, unit_price): if self.unit_shipment_cost: unit_price -= self.unit_shipment_cost unit_price = super()._compute_component_unit_price(unit_price) if self.unit_shipment_cost: unit_price += self.unit_shipment_cost return unit_price # Split the shipment cost if account_stock_continental is activated def _get_account_stock_move_lines(self, type_): pool = Pool() AccountMoveLine = pool.get('account.move.line') Currency = pool.get('currency.currency') move_lines = super()._get_account_stock_move_lines(type_) if (type_.startswith('in_') and self.unit_shipment_cost and self.shipment and self.shipment.carrier): shipment_cost = Currency.compute(self.currency, Decimal(str(self.quantity)) * self.unit_shipment_cost, self.company.currency) shipment_cost_account = \ self.shipment.carrier.carrier_product.account_expense_used account = self.product.account_stock_in_used for move_line in move_lines: if move_line.account == account: move_line.credit -= shipment_cost shipment_cost_line = AccountMoveLine( debit=Decimal('0'), credit=shipment_cost, account=shipment_cost_account, ) move_lines.append(shipment_cost_line) break else: raise AssertionError('missing account_stock_supplier') return move_lines # Remove shipment cost if account_stock_anglo_saxon is activated @classmethod def _get_anglo_saxon_move(cls, moves, quantity, type_): pool = Pool() Currency = pool.get('currency.currency') Uom = pool.get('product.uom') for move, qty, cost_price in super()._get_anglo_saxon_move( moves, quantity, type_): if (type_.startswith('in_') and move.unit_shipment_cost): shipment_cost = Uom.compute_price( move.unit, move.unit_shipment_cost, move.product.default_uom) shipment_cost = Currency.compute(move.currency, shipment_cost, move.company.currency) cost_price -= shipment_cost yield move, qty, cost_price