From f2490ffcb86274d1a83d9d1abf9e5134cd321d17 Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Tue, 23 Dec 2025 15:00:00 +0700 Subject: [PATCH] first commit --- __init__.py | 1 + __manifest__.py | 23 +++ __pycache__/__init__.cpython-312.pyc | Bin 0 -> 233 bytes models/__init__.py | 1 + models/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 261 bytes ...tock_inventory_revaluation.cpython-312.pyc | Bin 0 -> 8457 bytes models/stock_inventory_revaluation.py | 145 ++++++++++++++++++ security/ir.model.access.csv | 2 + views/stock_inventory_revaluation_views.xml | 93 +++++++++++ 9 files changed, 265 insertions(+) create mode 100755 __init__.py create mode 100755 __manifest__.py create mode 100755 __pycache__/__init__.cpython-312.pyc create mode 100755 models/__init__.py create mode 100755 models/__pycache__/__init__.cpython-312.pyc create mode 100755 models/__pycache__/stock_inventory_revaluation.cpython-312.pyc create mode 100755 models/stock_inventory_revaluation.py create mode 100755 security/ir.model.access.csv create mode 100755 views/stock_inventory_revaluation_views.xml diff --git a/__init__.py b/__init__.py new file mode 100755 index 0000000..0650744 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/__manifest__.py b/__manifest__.py new file mode 100755 index 0000000..93ed414 --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +{ + 'name': "Warehouse Stock Value Revaluation", + 'summary': """ + Inventory Cost Revaluation, Stock Revaluation for Accurate Accounting + """, + 'description': """ + Inventory Revaluation Odoo App provides a solution for businesses to reassess and recalculate the value of their stock. + It allows creating backdate inventory adjustment and revaluation. + """, + 'author': "Antigravity", + 'website': "https://www.mapan.co.id", + 'category': 'Inventory/Inventory', + 'version': '17.0.1.0.0', + 'depends': ['stock_account'], + 'data': [ + 'security/ir.model.access.csv', + 'views/stock_inventory_revaluation_views.xml', + ], + 'license': 'LGPL-3', + 'installable': True, + 'application': True, +} diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc new file mode 100755 index 0000000000000000000000000000000000000000..047cde5060c8f7faf29d9e301faefd813510de69 GIT binary patch literal 233 zcmXwzv1-FG6h-xH2&Q1jkkW7PVv(s!3GEMr&RK*isaV9)Gm;$8m*gArEnPch$kHiW zAe*N?LwlEZ?uE;{Uv>S!NZ(Iy_L1(-Z2ndJ!F@?^WSVK{nS&hw%SBUOf#L?v3y0eD zaoZGU7D~!a>^?WAPu`(3JdT|SI=5LW{CIV8kiJ#eTFu{c>d-5ybJU3IXRgMW&`GV) zM^32r!uo^p2}2ga9ArPrWYKd$xlKZ}L!PP^S}^uz6DoQ;uZlC=N&vX9RalVu1qH-E A*Z=?k literal 0 HcmV?d00001 diff --git a/models/__init__.py b/models/__init__.py new file mode 100755 index 0000000..64e8444 --- /dev/null +++ b/models/__init__.py @@ -0,0 +1 @@ +from . import stock_inventory_revaluation diff --git a/models/__pycache__/__init__.cpython-312.pyc b/models/__pycache__/__init__.cpython-312.pyc new file mode 100755 index 0000000000000000000000000000000000000000..a48fff378f4f3f98c5bbff333a7bc7bf3f77e02c GIT binary patch literal 261 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 literal 0 HcmV?d00001 diff --git a/models/stock_inventory_revaluation.py b/models/stock_inventory_revaluation.py new file mode 100755 index 0000000..32be72a --- /dev/null +++ b/models/stock_inventory_revaluation.py @@ -0,0 +1,145 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import UserError +from odoo.tools import float_compare, float_is_zero + +class StockInventoryRevaluation(models.Model): + _name = 'stock.inventory.revaluation' + _description = 'Stock Inventory Revaluation' + _inherit = ['mail.thread', 'mail.activity.mixin'] + + name = fields.Char(string='Reference', required=True, copy=False, readonly=True, default=lambda self: _('New')) + date = fields.Datetime(string='Date', required=True, default=fields.Datetime.now) + product_id = fields.Many2one('product.product', string='Product', required=True, domain=[('type', '=', 'product')]) + account_journal_id = fields.Many2one('account.journal', string='Journal', required=True) + account_id = fields.Many2one('account.account', string='Account', help="Counterpart account for the revaluation") + + current_value = fields.Float(string='Current Value', compute='_compute_current_value', store=True) + quantity = fields.Float(string='Quantity', compute='_compute_current_value', store=True) + + extra_cost = fields.Float(string='Extra Cost', help="Amount to add to the stock value") + + state = fields.Selection([ + ('draft', 'Draft'), + ('done', 'Done'), + ('cancel', 'Cancelled') + ], string='Status', default='draft', tracking=True) + + company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.company) + + @api.depends('product_id', 'date') + def _compute_current_value(self): + for record in self: + if record.product_id and record.date: + # Calculate quantity and value at the specific date + layers = self.env['stock.valuation.layer'].search([ + ('product_id', '=', record.product_id.id), + ('create_date', '<=', record.date), + ('company_id', '=', record.company_id.id) + ]) + record.quantity = sum(layers.mapped('quantity')) + record.current_value = sum(layers.mapped('value')) + elif record.product_id: + record.quantity = record.product_id.quantity_svl + record.current_value = record.product_id.value_svl + else: + record.quantity = 0.0 + record.current_value = 0.0 + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if vals.get('name', _('New')) == _('New'): + vals['name'] = self.env['ir.sequence'].next_by_code('stock.inventory.revaluation') or _('New') + return super().create(vals_list) + + def action_validate(self): + self.ensure_one() + if float_is_zero(self.extra_cost, precision_rounding=self.currency_id.rounding): + raise UserError(_("The Extra Cost cannot be zero.")) + + # Create Accounting Entry + move_vals = self._prepare_account_move_vals() + move = self.env['account.move'].create(move_vals) + move.action_post() + + # Create Stock Valuation Layer + self._create_valuation_layer(move) + + self.state = 'done' + + def _prepare_account_move_vals(self): + self.ensure_one() + debit_account_id = self.product_id.categ_id.property_stock_valuation_account_id.id + + # Auto-detect counterpart account if not set + credit_account_id = self.account_id.id + if not credit_account_id: + if self.extra_cost > 0: + credit_account_id = self.product_id.categ_id.property_stock_account_input_categ_id.id + else: + credit_account_id = self.product_id.categ_id.property_stock_account_output_categ_id.id + + if not debit_account_id: + raise UserError(_("Please define the Stock Valuation Account for product category: %s") % self.product_id.categ_id.name) + if not credit_account_id: + raise UserError(_("Please define the Stock Input/Output Account for product category: %s, or select an Account manually.") % self.product_id.categ_id.name) + + amount = self.extra_cost + name = _('%s - Revaluation') % self.name + + # If amount is negative, swap accounts/logic or just let debits be negative? + # Usually easier to swap or just have positive/negative balance. + # Standard: Debit Stock, Credit Counterpart for increase. + + lines = [ + (0, 0, { + 'name': name, + 'account_id': debit_account_id, + 'debit': amount if amount > 0 else 0, + 'credit': -amount if amount < 0 else 0, + 'product_id': self.product_id.id, + }), + (0, 0, { + 'name': name, + 'account_id': credit_account_id, + 'debit': -amount if amount < 0 else 0, + 'credit': amount if amount > 0 else 0, + }), + ] + + return { + 'ref': self.name, + 'date': self.date.date(), # BACKDATE HERE + 'journal_id': self.account_journal_id.id, + 'line_ids': lines, + 'move_type': 'entry', + } + + def _create_valuation_layer(self, move): + self.ensure_one() + layer_vals = { + 'product_id': self.product_id.id, + 'value': self.extra_cost, + 'unit_cost': 0, # Not adjusting unit cost directly, just total value + 'quantity': 0, + 'remaining_qty': 0, + 'description': _('Revaluation: %s') % self.name, + 'account_move_id': move.id, + 'company_id': self.company_id.id, + # We try to force the date if the model allows it, but stock.valuation.layer usually takes create_date. + # However, for reporting, Odoo joins with account_move. + } + # Note: stock.valuation.layer 'create_date' is automatic. + # But we can try to override it or rely on the account move date for reports. + # Standard Odoo valuation reports often rely on the move date. + + layer = self.env['stock.valuation.layer'].create(layer_vals) + + # Force backdate the validation layer's create_date to match the revaluation date + # This is critical for "Inventory Valuation at Date" reports. + self.env.cr.execute('UPDATE stock_valuation_layer SET create_date = %s WHERE id = %s', (self.date, layer.id)) + + + @property + def currency_id(self): + return self.company_id.currency_id diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv new file mode 100755 index 0000000..303ef83 --- /dev/null +++ b/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_stock_inventory_revaluation,stock.inventory.revaluation,model_stock_inventory_revaluation,base.group_user,1,1,1,1 diff --git a/views/stock_inventory_revaluation_views.xml b/views/stock_inventory_revaluation_views.xml new file mode 100755 index 0000000..951511e --- /dev/null +++ b/views/stock_inventory_revaluation_views.xml @@ -0,0 +1,93 @@ + + + + + + Stock Revaluation + stock.inventory.revaluation + REV/ + 5 + + + + + + stock.inventory.revaluation.form + stock.inventory.revaluation + +
+
+
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + + + stock.inventory.revaluation.tree + stock.inventory.revaluation + + + + + + + + + + + + + + Inventory Revaluation + stock.inventory.revaluation + tree,form + +

+ Create a new Inventory Revaluation +

+
+
+ + + +
+