File: mrp_wip_accounting.py

package info (click to toggle)
odoo 18.0.0%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 878,716 kB
  • sloc: javascript: 927,937; python: 685,670; xml: 388,524; sh: 1,033; sql: 415; makefile: 26
file content (150 lines) | stat: -rw-r--r-- 7,089 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, time
from dateutil.relativedelta import relativedelta

from odoo import fields, models, _, api, Command
from odoo.exceptions import UserError
from odoo.tools import format_list


class MrpWipAccountingLine(models.TransientModel):
    _name = 'mrp.account.wip.accounting.line'
    _description = 'Account move line to be created when posting WIP account move'

    account_id = fields.Many2one('account.account', "Account")
    label = fields.Char("Label")
    debit = fields.Monetary("Debit", compute='_compute_debit', store=True, readonly=False)
    credit = fields.Monetary("Credit", compute='_compute_credit', store=True, readonly=False)
    currency_id = fields.Many2one('res.currency', "Currency", default=lambda self: self.env.company.currency_id)
    wip_accounting_id = fields.Many2one('mrp.account.wip.accounting', "WIP accounting wizard")

    _sql_constraints = [
        ('check_debit_credit', 'CHECK ( debit = 0 OR credit = 0 )',
         'A single line cannot be both credit and debit.')
    ]

    @api.depends('credit')
    def _compute_debit(self):
        for record in self:
            if not record.currency_id.is_zero(record.credit):
                record.debit = 0

    @api.depends('debit')
    def _compute_credit(self):
        for record in self:
            if not record.currency_id.is_zero(record.debit):
                record.credit = 0


class MrpWipAccounting(models.TransientModel):
    _name = 'mrp.account.wip.accounting'
    _description = 'Wizard to post Manufacturing WIP account move'

    @api.model
    def default_get(self, fields_list):
        res = super().default_get(fields_list)
        productions = self.env['mrp.production'].browse(self.env.context.get('active_ids'))
        # ignore selected MOs that aren't a WIP
        productions = productions.filtered(lambda mo: mo.state in ['progress', 'to_close', 'confirmed'])
        if 'journal_id' in fields_list:
            default = self.env['product.category']._fields['property_stock_journal'].get_company_dependent_fallback(self.env['product.category'])
            if default:
                res['journal_id'] = default.id
        if 'reference' in fields_list:
            res['reference'] = _("Manufacturing WIP - %(orders_list)s", orders_list=productions and format_list(self.env, productions.mapped('name')) or _("Manual Entry"))
        if 'mo_ids' in fields_list:
            res['mo_ids'] = [Command.set(productions.ids)]
        return res

    date = fields.Date("Date", default=fields.Datetime.now)
    reversal_date = fields.Date(
        "Reversal Date", compute="_compute_reversal_date", required=True,
        store=True, readonly=False)
    journal_id = fields.Many2one('account.journal', "Journal", required=True)
    reference = fields.Char("Reference")
    line_ids = fields.One2many(
        'mrp.account.wip.accounting.line', 'wip_accounting_id', "WIP accounting lines",
        compute="_compute_line_ids", store=True, readonly=False)
    mo_ids = fields.Many2many('mrp.production')

    def _get_overhead_account(self):
        overhead_account = self.env.company.account_production_wip_overhead_account_id
        if overhead_account:
            return overhead_account.id
        ProductCategory = self.env['product.category']
        cop_acc = ProductCategory._fields['property_stock_account_production_cost_id'].get_company_dependent_fallback(ProductCategory)
        if cop_acc:
            return cop_acc.id
        return ProductCategory._fields['property_stock_account_input_categ_id'].get_company_dependent_fallback(ProductCategory).id

    def _get_line_vals(self, productions=False, date=False):
        if not productions:
            productions = self.env['mrp.production']
        if not date:
            date = datetime.now().replace(hour=23, minute=59, second=59)
        compo_value = sum(
            ml.quantity_product_uom * (ml.product_id.lot_valuated and ml.lot_id and ml.lot_id.standard_price or ml.product_id.standard_price)
            for ml in productions.move_raw_ids.move_line_ids.filtered(lambda ml: ml.picked and ml.quantity and ml.date <= date)
        )
        overhead_value = productions.workorder_ids._cal_cost(date)
        sval_acc = self.env['product.category']._fields['property_stock_valuation_account_id'].get_company_dependent_fallback(self.env['product.category']).id
        return [
            Command.create({
                'label': _("WIP - Component Value"),
                'credit': compo_value,
                'account_id': sval_acc,
            }),
            Command.create({
                'label': _("WIP - Overhead"),
                'credit': overhead_value,
                'account_id': self._get_overhead_account(),
            }),
            Command.create({
                'label': _("Manufacturing WIP - %(orders_list)s", orders_list=productions and format_list(self.env, productions.mapped('name')) or _("Manual Entry")),
                'debit': compo_value + overhead_value,
                'account_id': self.env.company.account_production_wip_account_id.id,
            })
        ]

    @api.depends('date')
    def _compute_reversal_date(self):
        for wizard in self:
            if not wizard.reversal_date or wizard.reversal_date <= wizard.date:
                wizard.reversal_date = wizard.date + relativedelta(days=1)
            else:
                wizard.reversal_date = wizard.reversal_date

    @api.depends('date')
    def _compute_line_ids(self):
        for wizard in self:
            # don't update lines when manual (i.e. no applicable MOs) entry
            if not wizard.line_ids or wizard.mo_ids:
                wizard.line_ids = [Command.clear()] + wizard._get_line_vals(wizard.mo_ids, datetime.combine(wizard.date, time.max))

    def confirm(self):
        self.ensure_one()
        if self.env.company.currency_id.compare_amounts(sum(self.line_ids.mapped('credit')), sum(self.line_ids.mapped('debit'))) != 0:
            raise UserError(_("Please make sure the total credit amount equals the total debit amount."))
        if self.reversal_date <= self.date:
            raise UserError(_("Reversal date must be after the posting date."))
        move = self.env['account.move'].sudo().create({
            'journal_id': self.journal_id.id,
            'wip_production_ids': self.mo_ids.ids,
            'date': self.date,
            'ref': self.reference,
            'move_type': 'entry',
            'line_ids': [
                Command.create({
                    'name': line.label,
                    'account_id': line.account_id.id,
                    'debit': line.debit,
                    'credit': line.credit,
                }) for line in self.line_ids
            ]
        })
        move._post()
        move._reverse_moves(default_values_list=[{
            'ref': _("Reversal of: %s", self.reference),
            'wip_production_ids': self.mo_ids.ids,
            'date': self.reversal_date,
        }])._post()