diff --git a/purchase.py b/purchase.py index 25171f3a9a..ee293df065 100644 --- a/purchase.py +++ b/purchase.py @@ -652,17 +652,6 @@ class Purchase(Workflow, ModelSQL, ModelView, TaxableMixin): 'total_amount_cache': purchase.total_amount, }) - def _get_invoice_line_purchase_line(self, invoice_type): - ''' - Return invoice line for each purchase lines - ''' - res = {} - for line in self.lines: - val = line.get_invoice_line(invoice_type) - if val: - res[line.id] = val - return res - def _get_invoice_purchase(self, invoice_type): ''' Return invoice of type invoice_type @@ -704,7 +693,11 @@ class Purchase(Workflow, ModelSQL, ModelView, TaxableMixin): self.raise_user_error('missing_account_payable', error_args=(self.party.rec_name,)) - invoice_lines = self._get_invoice_line_purchase_line(invoice_type) + invoice_lines = {} + for line in self.lines: + ilines = line.get_invoice_line(invoice_type) + if ilines: + invoice_lines[line.id] = ilines if not invoice_lines: return @@ -721,14 +714,16 @@ class Purchase(Workflow, ModelSQL, ModelView, TaxableMixin): ''' Create move for each purchase lines ''' - new_moves = [] + pool = Pool() + Move = pool.get('stock.move') + + moves = [] for line in self.lines: - if (line.quantity >= 0) != (move_type == 'in'): - continue - move = line.create_move() + move = line.get_move(move_type) if move: - new_moves.append(move) - return new_moves + moves.append(move) + Move.save(moves) + return moves def _get_return_shipment(self): ShipmentInReturn = Pool().get('stock.shipment.in.return') @@ -1212,51 +1207,15 @@ class PurchaseLine(ModelSQL, ModelView): invoice_line.note = self.note invoice_line.origin = self if self.type != 'line': - if (self.purchase.invoice_method == 'order' - and not self.invoice_lines - and ((all(l.quantity >= 0 for l in self.purchase.lines - if l.type == 'line') - and invoice_type == 'in_invoice') - or (all(l.quantity <= 0 for l in self.purchase.lines - if l.type == 'line') - and invoice_type == 'in_credit_note'))): + if self._get_invoice_not_line(invoice_type): return [invoice_line] else: return [] if (invoice_type == 'in_invoice') != (self.quantity >= 0): return [] - stock_moves = [] - if (self.purchase.invoice_method == 'order' - or not self.product - or self.product.type == 'service'): - quantity = abs(self.quantity) - stock_moves = self.moves - else: - quantity = 0.0 - for move in self.moves: - if move.state == 'done': - quantity += Uom.compute_qty(move.uom, move.quantity, - self.unit) - if move.invoiced_quantity < move.quantity: - stock_moves.append(move) - invoice_line.stock_moves = stock_moves - - skip_ids = set(l.id for i in self.purchase.invoices_recreated - for l in i.lines) - for old_invoice_line in self.invoice_lines: - if old_invoice_line.type != 'line': - continue - if old_invoice_line.id not in skip_ids: - if old_invoice_line.invoice: - old_invoice_type = old_invoice_line.invoice.type - else: - old_invoice_type = old_invoice_line.invoice_type - - sign = 1 if old_invoice_type == invoice_type else -1 - - quantity -= Uom.compute_qty(old_invoice_line.unit, - sign * old_invoice_line.quantity, self.unit) + quantity = (self._get_invoice_line_quantity(invoice_type) + - self._get_invoiced_quantity(invoice_type)) rounding = self.unit.rounding if self.unit else 0.01 invoice_line.quantity = Uom.round(quantity, rounding) @@ -1283,26 +1242,72 @@ class PurchaseLine(ModelSQL, ModelView): if not invoice_line.account: self.raise_user_error('missing_account_expense_property', {'purchase': self.purchase.rec_name}) + invoice_line.stock_moves = self._get_invoice_line_moves(invoice_type) return [invoice_line] - @classmethod - def view_attributes(cls): - return [ - ('/form//field[@name="note"]|/form//field[@name="description"]', - 'spell', Eval('_parent_purchase', {}).get('party_lang'))] + def _get_invoice_not_line(self, invoice_type): + 'Return if the not line should be invoiced' + return (self.purchase.invoice_method == 'order' + and not self.invoice_lines + and ((all(l.quantity >= 0 for l in self.purchase.lines + if l.type == 'line') + and invoice_type == 'in_invoice') + or (all(l.quantity <= 0 for l in self.purchase.lines + if l.type == 'line') + and invoice_type == 'in_credit_note'))) - @classmethod - def copy(cls, lines, default=None): - if default is None: - default = {} - default = default.copy() - default['moves'] = None - default['moves_ignored'] = None - default['moves_recreated'] = None - default['invoice_lines'] = None - return super(PurchaseLine, cls).copy(lines, default=default) + def _get_invoice_line_quantity(self, invoice_type): + 'Return the quantity that should be invoiced' + pool = Pool() + Uom = pool.get('product.uom') - def get_move(self): + if (self.purchase.invoice_method == 'order' + or not self.product + or self.product.type == 'service'): + return abs(self.quantity) + elif self.purchase.invoice_method == 'shipment': + quantity = 0.0 + for move in self.moves: + if move.state == 'done': + quantity += Uom.compute_qty(move.uom, move.quantity, + self.unit) + return quantity + + def _get_invoiced_quantity(self, invoice_type): + 'Return the quantity already invoiced' + pool = Pool() + Uom = pool.get('product.uom') + + quantity = 0 + skips = {l for i in self.purchase.invoices_recreated for l in i.lines} + for invoice_line in self.invoice_lines: + if invoice_line.type != 'line': + continue + if invoice_line not in skips: + if invoice_line.invoice: + line_type = invoice_line.invoice.type + else: + line_type = invoice_line.invoice_type + + sign = 1 if line_type == invoice_type else -1 + + quantity += Uom.compute_qty(invoice_line.unit, + sign * invoice_line.quantity, self.unit) + return quantity + + def _get_invoice_line_moves(self, invoice_type): + 'Return the stock moves that should be invoiced' + moves = [] + if self.purchase.invoice_method == 'order': + moves.extend(self.moves) + elif self.purchase.invoice_method == 'shipment': + for move in self.moves: + if move.state == 'done': + if move.invoiced_quantity < move.quantity: + moves.append(move) + return moves + + def get_move(self, move_type): ''' Return move values for purchase line ''' @@ -1316,12 +1321,12 @@ class PurchaseLine(ModelSQL, ModelView): return if self.product.type == 'service': return - skip = set(self.moves_recreated) - quantity = abs(self.quantity) - for move in self.moves: - if move not in skip: - quantity -= Uom.compute_qty(move.uom, move.quantity, - self.unit) + + if (self.quantity >= 0) != (move_type == 'in'): + return + + quantity = (self._get_move_quantity(move_type) + - self._get_shipped_quantity(move_type)) quantity = Uom.round(quantity, self.unit.rounding) if quantity <= 0: @@ -1343,20 +1348,47 @@ class PurchaseLine(ModelSQL, ModelView): move.unit_price = self.unit_price move.currency = self.purchase.currency move.planned_date = self.delivery_date - move.invoice_lines = [l for l in self.invoice_lines - if not l.stock_moves] + move.invoice_lines = self._get_move_invoice_lines(move_type) move.origin = self return move - def create_move(self): - ''' - Create move line - ''' - move = self.get_move() - if not move: - return - move.save() - return move + def _get_move_quantity(self, move_type): + 'Return the quantity that should be shipped' + return abs(self.quantity) + + def _get_shipped_quantity(self, move_type): + 'Return the quantity already shipped' + pool = Pool() + Uom = pool.get('product.uom') + + quantity = 0 + skip = set(self.moves_recreated) + for move in self.moves: + if move not in skip: + quantity += Uom.compute_qty(move.uom, move.quantity, + self.unit) + return quantity + + def _get_move_invoice_lines(self, move_type): + 'Return the invoice lines that should be shipped' + return [l for l in self.invoice_lines if not l.stock_moves] + + @classmethod + def view_attributes(cls): + return [ + ('/form//field[@name="note"]|/form//field[@name="description"]', + 'spell', Eval('_parent_purchase', {}).get('party_lang'))] + + @classmethod + def copy(cls, lines, default=None): + if default is None: + default = {} + default = default.copy() + default['moves'] = None + default['moves_ignored'] = None + default['moves_recreated'] = None + default['invoice_lines'] = None + return super(PurchaseLine, cls).copy(lines, default=default) class PurchaseLineTax(ModelSQL):