# 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 Decimal from trytond.model import ModelView, Workflow, fields from trytond.modules.product import round_price from trytond.pool import Pool, PoolMeta from trytond.pyson import Eval from trytond.transaction import Transaction class Move(metaclass=PoolMeta): __name__ = 'stock.move' invoice_lines = fields.Many2Many( 'account.invoice.line-stock.move', 'stock_move', 'invoice_line', "Invoice Lines", domain=[ ('product.default_uom_category', '=', Eval('product_uom_category', -1)), ('type', '=', 'line'), ['OR', ('invoice.type', 'in', Eval('invoice_types', [])), ('invoice_type', 'in', Eval('invoice_types', [])), ], ], states={ 'invisible': ~Eval('invoice_types', []), }) invoice_types = fields.Function( fields.MultiSelection('get_invoice_types', "Invoice Types"), 'on_change_with_invoice_types') @classmethod def __setup__(cls): super().__setup__() cls._allow_modify_closed_period.add('invoice_lines') @classmethod def get_invoice_types(cls): pool = Pool() Invoice = pool.get('account.invoice') return Invoice.fields_get(['type'])['type']['selection'] @fields.depends('from_location', 'to_location') def on_change_with_invoice_types(self, name=None): types = set() for location in [self.from_location, self.to_location]: if location: if location.type == 'customer': types.add('out') elif location.type == 'supplier': types.add('in') return list(types) @property def invoiced_quantity(self): 'The quantity from linked invoice lines in move unit' pool = Pool() Uom = pool.get('product.uom') quantity = 0 for invoice_line in self.invoice_lines: if invoice_line.invoice_state != 'cancelled': quantity += Uom.compute_qty( invoice_line.unit, invoice_line.quantity, self.unit) return quantity @classmethod def copy(cls, moves, default=None): if default is None: default = {} else: default = default.copy() if not Transaction().context.get('_stock_move_split'): default.setdefault('invoice_lines', None) return super().copy(moves, default=default) @classmethod @ModelView.button @Workflow.transition('done') def do(cls, moves): super().do(moves) cls.update_unit_price(moves) @classmethod def write(cls, *args): super().write(*args) moves = sum(args[0:None:2], []) cls.update_unit_price(moves) @classmethod def update_unit_price(cls, moves): for move in moves: if move.state == 'done' and move.unit_price_required: unit_price = move._compute_unit_price( unit_price=move.unit_price) if unit_price != move.unit_price: move.unit_price = unit_price cls.save(moves) def _compute_unit_price(self, unit_price): pool = Pool() UoM = pool.get('product.uom') Currency = pool.get('currency.currency') amount, quantity = 0, 0 for line in self.invoice_lines: if line.invoice and line.invoice.state in {'posted', 'paid'}: with Transaction().set_context(date=self.effective_date): amount += Currency.compute( line.invoice.currency, line.amount, self.currency) if line.invoice.type == 'out' or not line.correction: quantity += UoM.compute_qty( line.unit, line.quantity, self.unit) if quantity: unit_price = amount / Decimal(str(quantity)) if unit_price is not None: unit_price = round_price(unit_price) return unit_price class ShipmentOut(metaclass=PoolMeta): __name__ = 'stock.shipment.out' def _sync_outgoing_move(self, template=None): move = super()._sync_outgoing_move(template=template) if template and template.invoice_lines: move.invoice_lines = list(template.invoice_lines) return move