diff options
Diffstat (limited to 'addons/stock_account/models/account_move.py')
| -rw-r--r-- | addons/stock_account/models/account_move.py | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/addons/stock_account/models/account_move.py b/addons/stock_account/models/account_move.py new file mode 100644 index 00000000..244c910c --- /dev/null +++ b/addons/stock_account/models/account_move.py @@ -0,0 +1,236 @@ +# -*- coding: utf-8 -*- + +from odoo import fields, models + + +class AccountMove(models.Model): + _inherit = 'account.move' + + stock_move_id = fields.Many2one('stock.move', string='Stock Move', index=True) + stock_valuation_layer_ids = fields.One2many('stock.valuation.layer', 'account_move_id', string='Stock Valuation Layer') + + # ------------------------------------------------------------------------- + # OVERRIDE METHODS + # ------------------------------------------------------------------------- + + def _get_lines_onchange_currency(self): + # OVERRIDE + return self.line_ids.filtered(lambda l: not l.is_anglo_saxon_line) + + def _reverse_move_vals(self, default_values, cancel=True): + # OVERRIDE + # Don't keep anglo-saxon lines if not cancelling an existing invoice. + move_vals = super(AccountMove, self)._reverse_move_vals(default_values, cancel=cancel) + if not cancel: + move_vals['line_ids'] = [vals for vals in move_vals['line_ids'] if not vals[2]['is_anglo_saxon_line']] + return move_vals + + def copy_data(self, default=None): + # OVERRIDE + # Don't keep anglo-saxon lines when copying a journal entry. + res = super().copy_data(default=default) + + if not self._context.get('move_reverse_cancel'): + for copy_vals in res: + if 'line_ids' in copy_vals: + copy_vals['line_ids'] = [line_vals for line_vals in copy_vals['line_ids'] + if line_vals[0] != 0 or not line_vals[2].get('is_anglo_saxon_line')] + + return res + + def _post(self, soft=True): + # OVERRIDE + + # Don't change anything on moves used to cancel another ones. + if self._context.get('move_reverse_cancel'): + return super()._post(soft) + + # Create additional COGS lines for customer invoices. + self.env['account.move.line'].create(self._stock_account_prepare_anglo_saxon_out_lines_vals()) + + # Post entries. + posted = super()._post(soft) + + # Reconcile COGS lines in case of anglo-saxon accounting with perpetual valuation. + posted._stock_account_anglo_saxon_reconcile_valuation() + return posted + + def button_draft(self): + res = super(AccountMove, self).button_draft() + + # Unlink the COGS lines generated during the 'post' method. + self.mapped('line_ids').filtered(lambda line: line.is_anglo_saxon_line).unlink() + return res + + def button_cancel(self): + # OVERRIDE + res = super(AccountMove, self).button_cancel() + + # Unlink the COGS lines generated during the 'post' method. + # In most cases it shouldn't be necessary since they should be unlinked with 'button_draft'. + # However, since it can be called in RPC, better be safe. + self.mapped('line_ids').filtered(lambda line: line.is_anglo_saxon_line).unlink() + return res + + # ------------------------------------------------------------------------- + # COGS METHODS + # ------------------------------------------------------------------------- + + def _stock_account_prepare_anglo_saxon_out_lines_vals(self): + ''' Prepare values used to create the journal items (account.move.line) corresponding to the Cost of Good Sold + lines (COGS) for customer invoices. + + Example: + + Buy a product having a cost of 9 being a storable product and having a perpetual valuation in FIFO. + Sell this product at a price of 10. The customer invoice's journal entries looks like: + + Account | Debit | Credit + --------------------------------------------------------------- + 200000 Product Sales | | 10.0 + --------------------------------------------------------------- + 101200 Account Receivable | 10.0 | + --------------------------------------------------------------- + + This method computes values used to make two additional journal items: + + --------------------------------------------------------------- + 220000 Expenses | 9.0 | + --------------------------------------------------------------- + 101130 Stock Interim Account (Delivered) | | 9.0 + --------------------------------------------------------------- + + Note: COGS are only generated for customer invoices except refund made to cancel an invoice. + + :return: A list of Python dictionary to be passed to env['account.move.line'].create. + ''' + lines_vals_list = [] + for move in self: + if not move.is_sale_document(include_receipts=True) or not move.company_id.anglo_saxon_accounting: + continue + + for line in move.invoice_line_ids: + + # Filter out lines being not eligible for COGS. + if line.product_id.type != 'product' or line.product_id.valuation != 'real_time': + continue + + # Retrieve accounts needed to generate the COGS. + accounts = ( + line.product_id.product_tmpl_id + .with_company(line.company_id) + .get_product_accounts(fiscal_pos=move.fiscal_position_id) + ) + debit_interim_account = accounts['stock_output'] + credit_expense_account = accounts['expense'] or self.journal_id.default_account_id + if not debit_interim_account or not credit_expense_account: + continue + + # Compute accounting fields. + sign = -1 if move.move_type == 'out_refund' else 1 + price_unit = line._stock_account_get_anglo_saxon_price_unit() + balance = sign * line.quantity * price_unit + + # Add interim account line. + lines_vals_list.append({ + 'name': line.name[:64], + 'move_id': move.id, + 'product_id': line.product_id.id, + 'product_uom_id': line.product_uom_id.id, + 'quantity': line.quantity, + 'price_unit': price_unit, + 'debit': balance < 0.0 and -balance or 0.0, + 'credit': balance > 0.0 and balance or 0.0, + 'account_id': debit_interim_account.id, + 'exclude_from_invoice_tab': True, + 'is_anglo_saxon_line': True, + }) + + # Add expense account line. + lines_vals_list.append({ + 'name': line.name[:64], + 'move_id': move.id, + 'product_id': line.product_id.id, + 'product_uom_id': line.product_uom_id.id, + 'quantity': line.quantity, + 'price_unit': -price_unit, + 'debit': balance > 0.0 and balance or 0.0, + 'credit': balance < 0.0 and -balance or 0.0, + 'account_id': credit_expense_account.id, + 'analytic_account_id': line.analytic_account_id.id, + 'analytic_tag_ids': [(6, 0, line.analytic_tag_ids.ids)], + 'exclude_from_invoice_tab': True, + 'is_anglo_saxon_line': True, + }) + return lines_vals_list + + def _stock_account_get_last_step_stock_moves(self): + """ To be overridden for customer invoices and vendor bills in order to + return the stock moves related to the invoices in self. + """ + return self.env['stock.move'] + + def _stock_account_anglo_saxon_reconcile_valuation(self, product=False): + """ Reconciles the entries made in the interim accounts in anglosaxon accounting, + reconciling stock valuation move lines with the invoice's. + """ + for move in self: + if not move.is_invoice(): + continue + if not move.company_id.anglo_saxon_accounting: + continue + + stock_moves = move._stock_account_get_last_step_stock_moves() + + if not stock_moves: + continue + + products = product or move.mapped('invoice_line_ids.product_id') + for prod in products: + if prod.valuation != 'real_time': + continue + + # We first get the invoices move lines (taking the invoice and the previous ones into account)... + product_accounts = prod.product_tmpl_id._get_product_accounts() + if move.is_sale_document(): + product_interim_account = product_accounts['stock_output'] + else: + product_interim_account = product_accounts['stock_input'] + + if product_interim_account.reconcile: + # Search for anglo-saxon lines linked to the product in the journal entry. + product_account_moves = move.line_ids.filtered( + lambda line: line.product_id == prod and line.account_id == product_interim_account and not line.reconciled) + + # Search for anglo-saxon lines linked to the product in the stock moves. + product_stock_moves = stock_moves.filtered(lambda stock_move: stock_move.product_id == prod) + product_account_moves += product_stock_moves.mapped('account_move_ids.line_ids')\ + .filtered(lambda line: line.account_id == product_interim_account and not line.reconciled) + + # Reconcile. + product_account_moves.reconcile() + + +class AccountMoveLine(models.Model): + _inherit = 'account.move.line' + + is_anglo_saxon_line = fields.Boolean(help="Technical field used to retrieve the anglo-saxon lines.") + + def _get_computed_account(self): + # OVERRIDE to use the stock input account by default on vendor bills when dealing + # with anglo-saxon accounting. + self.ensure_one() + if self.product_id.type == 'product' \ + and self.move_id.company_id.anglo_saxon_accounting \ + and self.move_id.is_purchase_document(): + fiscal_position = self.move_id.fiscal_position_id + accounts = self.product_id.product_tmpl_id.get_product_accounts(fiscal_pos=fiscal_position) + if accounts['stock_input']: + return accounts['stock_input'] + return super(AccountMoveLine, self)._get_computed_account() + + def _stock_account_get_anglo_saxon_price_unit(self): + self.ensure_one() + if not self.product_id: + return self.price_unit + return self.product_id._stock_account_get_anglo_saxon_price_unit(uom=self.product_uom_id) |
