commit 66770bc471fa4cc8c00b3cc6bf94f399d178eb3e Author: admin.suherdy Date: Sat Dec 6 19:03:29 2025 +0700 first commit diff --git a/__init__.py b/__init__.py new file mode 100644 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 100644 index 0000000..98e732a --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,14 @@ +{ + "name": "Stock Inventory Backdate", + "summary": "Allow backdating of physical stock adjustments and valuations.", + "version": "17.0.1.0.0", + "category": "Warehouse", + "author": "Antigravity", + "license": "AGPL-3", + "depends": ["stock_account"], + "data": [ + "security/ir.model.access.csv", + "views/stock_quant_views.xml", + ], + "installable": True, +} diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..00e3e45 Binary files /dev/null and b/__pycache__/__init__.cpython-312.pyc differ diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..c1e37b5 --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,2 @@ +from . import stock_quant +from . import stock_move diff --git a/models/__pycache__/__init__.cpython-312.pyc b/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..58b324f Binary files /dev/null and b/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/models/__pycache__/stock_move.cpython-312.pyc b/models/__pycache__/stock_move.cpython-312.pyc new file mode 100644 index 0000000..67217ab Binary files /dev/null and b/models/__pycache__/stock_move.cpython-312.pyc differ diff --git a/models/__pycache__/stock_quant.cpython-312.pyc b/models/__pycache__/stock_quant.cpython-312.pyc new file mode 100644 index 0000000..179e6b2 Binary files /dev/null and b/models/__pycache__/stock_quant.cpython-312.pyc differ diff --git a/models/stock_move.py b/models/stock_move.py new file mode 100644 index 0000000..76820bf --- /dev/null +++ b/models/stock_move.py @@ -0,0 +1,81 @@ +from odoo import api, models, fields +from odoo.tools import float_round + +class StockMove(models.Model): + _inherit = 'stock.move' + + def _action_done(self, cancel_backorder=False): + moves = super(StockMove, self)._action_done(cancel_backorder=cancel_backorder) + + forced_inventory_date = self.env.context.get('force_inventory_date') + if forced_inventory_date: + for move in moves: + move.write({'date': forced_inventory_date}) + # If valuation is real-time, we might need to adjust the account move date too. + # But account move creation usually happens in _action_done -> _create_account_move_line + # which might use the move date. + # Let's check if we need to update account moves. + if move.account_move_ids: + move.account_move_ids.write({'date': forced_inventory_date}) + + return moves + + def _create_account_move_line(self, credit_account_id, debit_account_id, journal_id, qty, description, svl_id, cost): + # Override to force date on account move creation if needed. + # However, if we update the move date in _action_done, it might be too late for this method + # if it's called during super()._action_done(). + # So we might need to rely on context here too. + + forced_inventory_date = self.env.context.get('force_inventory_date') + forced_valuation_date = self.env.context.get('force_valuation_date') + + # Use valuation date if present, otherwise inventory date + target_date = forced_valuation_date or forced_inventory_date + + if target_date: + # We can't easily change the arguments passed to this method without signature change, + # but we can patch the context or check if we can modify the created move later. + # Actually, this method creates 'account.move'. + # Let's see if we can intercept the creation. + pass + + return super(StockMove, self)._create_account_move_line(credit_account_id, debit_account_id, journal_id, qty, description, svl_id, cost) + + def _prepare_account_move_vals(self, credit_account_id, debit_account_id, journal_id, qty, description, svl_id, cost): + # This method prepares the values for account.move.create. + vals = super(StockMove, self)._prepare_account_move_vals(credit_account_id, debit_account_id, journal_id, qty, description, svl_id, cost) + + forced_inventory_date = self.env.context.get('force_inventory_date') + forced_valuation_date = self.env.context.get('force_valuation_date') + target_date = forced_valuation_date or forced_inventory_date + + if target_date: + vals['date'] = target_date + + return vals + + def _create_in_svl(self, forced_quantity=None): + # Override to force date on stock valuation layer + svl = super(StockMove, self)._create_in_svl(forced_quantity=forced_quantity) + self._update_svl_date(svl) + return svl + + def _create_out_svl(self, forced_quantity=None): + # Override to force date on stock valuation layer + svl = super(StockMove, self)._create_out_svl(forced_quantity=forced_quantity) + self._update_svl_date(svl) + return svl + + def _update_svl_date(self, svl): + forced_inventory_date = self.env.context.get('force_inventory_date') + forced_valuation_date = self.env.context.get('force_valuation_date') + target_date = forced_valuation_date or forced_inventory_date + + if target_date and svl: + # create_date is a magic field, we need to update it via SQL + self.env.cr.execute( + "UPDATE stock_valuation_layer SET create_date = %s WHERE id IN %s", + (target_date, tuple(svl.ids)) + ) + svl.invalidate_recordset(['create_date']) + diff --git a/models/stock_quant.py b/models/stock_quant.py new file mode 100644 index 0000000..cb5d0b6 --- /dev/null +++ b/models/stock_quant.py @@ -0,0 +1,53 @@ +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError + +class StockQuant(models.Model): + _inherit = 'stock.quant' + + force_inventory_date = fields.Date( + string="Force Inventory Date", + help="Choose a specific date for the inventory adjustment. " + "If set, the stock move will be created with this date." + ) + force_valuation_date = fields.Date( + string="Force Valuation Date", + help="Choose a specific date for the stock valuation. " + "If set, the valuation layer will be created with this date." + ) + + @api.model + def _get_inventory_fields_create(self): + """ Allow the new fields to be set during inventory creation """ + res = super(StockQuant, self)._get_inventory_fields_create() + res += ['force_inventory_date', 'force_valuation_date'] + return res + + @api.model + def _get_inventory_fields_write(self): + """ Allow the new fields to be set during inventory write """ + res = super(StockQuant, self)._get_inventory_fields_write() + res += ['force_inventory_date', 'force_valuation_date'] + return res + + def _apply_inventory(self): + """Override to pass forced dates to the context""" + # We need to handle quants with different forced dates separately + # Group by (force_inventory_date, force_valuation_date) + # If no forced date, key is (False, False) + + grouped_quants = {} + for quant in self: + key = (quant.force_inventory_date, quant.force_valuation_date) + if key not in grouped_quants: + grouped_quants[key] = self.env['stock.quant'] + grouped_quants[key] |= quant + + for (force_inventory_date, force_valuation_date), quants in grouped_quants.items(): + ctx = dict(self.env.context) + if force_inventory_date: + ctx['force_inventory_date'] = force_inventory_date + if force_valuation_date: + ctx['force_valuation_date'] = force_valuation_date + + super(StockQuant, quants.with_context(ctx))._apply_inventory() + diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv new file mode 100644 index 0000000..715a26f --- /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_quant_backdate,stock.quant.backdate,stock.model_stock_quant,stock.group_stock_user,1,1,1,1 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..eee6fc9 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +from . import test_stock_backdate diff --git a/tests/test_stock_backdate.py b/tests/test_stock_backdate.py new file mode 100644 index 0000000..6805a2a --- /dev/null +++ b/tests/test_stock_backdate.py @@ -0,0 +1,59 @@ +from odoo.tests.common import TransactionCase +from odoo import fields +from datetime import timedelta + +class TestStockBackdate(TransactionCase): + + def setUp(self): + super(TestStockBackdate, self).setUp() + self.product = self.env['product.product'].create({ + 'name': 'Test Product Backdate', + 'type': 'product', + 'categ_id': self.env.ref('product.product_category_all').id, + }) + # Enable automated valuation for the category if needed, + # but for simplicity we test the move date primarily. + self.product.categ_id.property_valuation = 'real_time' + self.product.categ_id.property_cost_method = 'average' + + self.stock_location = self.env.ref('stock.stock_location_stock') + + def test_inventory_backdate(self): + """Test that inventory adjustment backdating works""" + backdate = fields.Date.today() - timedelta(days=10) + + quant = self.env['stock.quant'].create({ + 'product_id': self.product.id, + 'location_id': self.stock_location.id, + 'inventory_quantity': 100, + }) + + # Set forced dates + quant.force_inventory_date = backdate + quant.force_valuation_date = backdate + + # Apply inventory + quant.action_apply_inventory() + + # Check stock move date + move = self.env['stock.move'].search([ + ('product_id', '=', self.product.id), + ('is_inventory', '=', True) + ], limit=1) + + self.assertTrue(move, "Stock move should be created") + self.assertEqual(move.date.date(), backdate, "Stock move date should be backdated") + + # Check account move date if exists + if move.account_move_ids: + self.assertEqual(move.account_move_ids[0].date, backdate, "Account move date should be backdated") + + # Check valuation layer date (create_date) + svl = self.env['stock.valuation.layer'].search([ + ('stock_move_id', '=', move.id) + ], limit=1) + + if svl: + # create_date is datetime, backdate is date. + # We check if the date part matches. + self.assertEqual(svl.create_date.date(), backdate, "SVL create_date should be backdated") diff --git a/views/stock_quant_views.xml b/views/stock_quant_views.xml new file mode 100644 index 0000000..419c753 --- /dev/null +++ b/views/stock_quant_views.xml @@ -0,0 +1,14 @@ + + + + stock.quant.inventory.tree.editable.inherit.backdate + stock.quant + + + + + + + + +