summaryrefslogtreecommitdiff
path: root/addons/stock_account/models/account_move.py
diff options
context:
space:
mode:
Diffstat (limited to 'addons/stock_account/models/account_move.py')
-rw-r--r--addons/stock_account/models/account_move.py236
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)