first commit

This commit is contained in:
admin.suherdy 2025-12-06 19:03:29 +07:00
commit 66770bc471
13 changed files with 227 additions and 0 deletions

1
__init__.py Normal file
View File

@ -0,0 +1 @@
from . import models

14
__manifest__.py Normal file
View File

@ -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,
}

Binary file not shown.

2
models/__init__.py Normal file
View File

@ -0,0 +1,2 @@
from . import stock_quant
from . import stock_move

Binary file not shown.

Binary file not shown.

Binary file not shown.

81
models/stock_move.py Normal file
View File

@ -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'])

53
models/stock_quant.py Normal file
View File

@ -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()

View File

@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_stock_quant_backdate stock.quant.backdate stock.model_stock_quant stock.group_stock_user 1 1 1 1

1
tests/__init__.py Normal file
View File

@ -0,0 +1 @@
from . import test_stock_backdate

View File

@ -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")

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_stock_quant_tree_inventory_editable_inherit_backdate" model="ir.ui.view">
<field name="name">stock.quant.inventory.tree.editable.inherit.backdate</field>
<field name="model">stock.quant</field>
<field name="inherit_id" ref="stock.view_stock_quant_tree_inventory_editable"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='inventory_date']" position="after">
<field name="force_inventory_date" optional="show"/>
<field name="force_valuation_date" optional="hide"/>
</xpath>
</field>
</record>
</odoo>