diff --git a/README.md b/README.md
index 773cae7..66dba58 100755
--- a/README.md
+++ b/README.md
@@ -44,12 +44,9 @@ This module allows you to create backdated inventory adjustments with a specific
### How It Works
1. When you validate a backdated adjustment, the module creates standard stock moves
-2. After the moves are processed, it updates the dates via SQL to ensure proper backdating:
- - Stock move `date` field
- - Stock move line `date` field
- - Stock valuation layer `create_date` field
- - Account move `date` field (if real-time valuation is enabled)
- - Account move `account_id` field: Overrides the interim account with `521301 Selisih Persediaan` for the non-valuation side of the entry.
+2. The system performs a **Physical Stock Adjustment Only**.
+3. After the moves are processed, it updates the dates via SQL for the moves and lines to ensure proper backdating.
+4. Any generated `stock.valuation.layer` or `account.move` (accounting) records are automatically **removed** to ensure no financial impact.
### Models
@@ -63,6 +60,25 @@ This module allows you to create backdated inventory adjustments with a specific
## Version History
+### Version 17.0.2.5.1
+- Added **PDF Report** for backdated inventory adjustments.
+- Added **XLSX Export** feature using `xlsxwriter`.
+- Fixed module initialization and updated XML syntax for **Odoo 17** compatibility.
+
+### Version 17.0.2.4.0
+- Refactored to **Selective Valuation**: Adjustment now generates its primary journal entry, but side-effect "Revaluation" layers are strictly suppressed.
+- This ensures the BIA itself is visible in accounting while preventing doubling/tripling of journal entries on other moves.
+
+### Version 17.0.2.3.0
+- Implemented comprehensive **Valuation Bypass** via Odoo context.
+- Prevents both primary valuation layers and **recursive revaluation side-effects** on other moves.
+- Guarantees strictly **Physical Stock Only** adjustments even with automated valuation enabled.
+
+### Version 17.0.2.2.0
+- Changed logic to perform **Physical Stock Adjustments Only**.
+- Automatically removes `stock.valuation.layer` and `account.move` records after validation to ensure no financial impact.
+- Ideal for correcting stock levels without affecting accounting.
+
### Version 17.0.2.1.0
- Refactored validation logic to use batch processing for improved performance and reliability.
- Fixed uniqueness constraint conflicts on account moves by optimizing the sequence of operations.
diff --git a/__init__.py b/__init__.py
index 0650744..9b42961 100755
--- a/__init__.py
+++ b/__init__.py
@@ -1 +1,2 @@
from . import models
+from . import wizard
diff --git a/__manifest__.py b/__manifest__.py
index 3e3165c..846b930 100755
--- a/__manifest__.py
+++ b/__manifest__.py
@@ -1,7 +1,7 @@
{
"name": "Stock Inventory Backdate",
"summary": "Create backdated inventory adjustments with historical position view",
- "version": "17.0.2.1.0",
+ "version": "17.0.2.5.1",
"category": "Warehouse",
"author": "Suherdy Yacob",
"license": "AGPL-3",
@@ -9,7 +9,10 @@
"data": [
"security/ir.model.access.csv",
"data/sequence_data.xml",
+ "wizard/stock_inventory_backdate_export_view.xml",
"views/stock_inventory_backdate_views.xml",
+ "report/stock_inventory_backdate_reports.xml",
+ "report/report_stock_inventory_backdate.xml",
],
"installable": True,
}
diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc
index 7401036..128b35e 100644
Binary files a/__pycache__/__init__.cpython-312.pyc and b/__pycache__/__init__.cpython-312.pyc differ
diff --git a/models/stock_inventory_backdate.py b/models/stock_inventory_backdate.py
index a862d45..400415a 100755
--- a/models/stock_inventory_backdate.py
+++ b/models/stock_inventory_backdate.py
@@ -250,79 +250,55 @@ class StockInventoryBackdate(models.Model):
self.env['stock.move.line'].create(ml_vals)
# Step 3: Action Done on all moves at once
- _logger.info(f"Validating {len(moves_to_process)} moves for {self.name}")
- moves_to_process._action_done()
+ # Using context backdate_inventory_mode to allow primary valuation but suppress side-effect revaluations
+ _logger.info(f"Validating {len(moves_to_process)} moves for {self.name} (Selective Valuation)")
+ moves_to_process.with_context(backdate_inventory_mode=True)._action_done()
- # Step 4: Post-process all moves (backdating and accounting)
+ # Step 4: Post-process all moves
self._post_process_validated_moves(moves_to_process)
self.write({'state': 'done'})
return True
def _post_process_validated_moves(self, moves):
- """Handle backdating and accounting adjustments for a batch of moves"""
+ """Handle backdating for a batch of moves, including valuation and accounting"""
self.ensure_one()
backdate = self.backdate_datetime
- account_date = backdate.date()
# Flush all pending ORM operations to DB before running raw SQL
self.env.flush_all()
+ move_ids = tuple(moves.ids)
+
# 1. Update stock move dates
self.env.cr.execute(
"UPDATE stock_move SET date = %s WHERE id IN %s",
- (backdate, tuple(moves.ids))
+ (backdate, move_ids)
)
# 2. Update stock move line dates
self.env.cr.execute(
"UPDATE stock_move_line SET date = %s WHERE move_id IN %s",
- (backdate, tuple(moves.ids))
+ (backdate, move_ids)
)
-
- # 3. Update stock valuation layers
- svls = self.env['stock.valuation.layer'].search([('stock_move_id', 'in', moves.ids)])
- if svls:
- self.env.cr.execute(
- "UPDATE stock_valuation_layer SET create_date = %s WHERE id IN %s",
- (backdate, tuple(svls.ids))
+
+ # 3. Update stock valuation layer dates
+ self.env.cr.execute(
+ "UPDATE stock_valuation_layer SET create_date = %s WHERE stock_move_id IN %s",
+ (backdate, move_ids)
+ )
+
+ # 4. Update account move dates (journal entries)
+ # We find AMs linked to these moves via SVLs
+ self.env.cr.execute("""
+ UPDATE account_move
+ SET date = %s
+ WHERE id IN (
+ SELECT account_move_id FROM stock_valuation_layer WHERE stock_move_id IN %s
)
+ """, (backdate.date(), move_ids))
- # 4. Update account moves
- account_moves = moves.account_move_ids
- if account_moves:
- # Update account move dates
- self.env.cr.execute(
- "UPDATE account_move SET date = %s WHERE id IN %s",
- (account_date, tuple(account_moves.ids))
- )
-
- # Update account move line dates
- self.env.cr.execute(
- "UPDATE account_move_line SET date = %s WHERE move_id IN %s",
- (account_date, tuple(account_moves.ids))
- )
-
- # Update account for non-valuation lines if target account 521301 exists
- target_account = self.env['account.account'].search([
- ('code', '=', '521301'),
- ('company_id', '=', self.company_id.id)
- ], limit=1)
-
- if target_account:
- for move in moves:
- product = move.product_id
- valuation_account = product.categ_id.property_stock_valuation_account_id
- if valuation_account and move.account_move_ids:
- # Update the line that is NOT the stock valuation account
- self.env.cr.execute(
- """UPDATE account_move_line
- SET account_id = %s
- WHERE move_id IN %s AND account_id != %s""",
- (target_account.id, tuple(move.account_move_ids.ids), valuation_account.id)
- )
-
- # Clear cache to reflect changes
+ # 5. Clear cache to reflect changes
self.env.invalidate_all()
def action_cancel(self):
@@ -333,6 +309,23 @@ class StockInventoryBackdate(models.Model):
self.write({'state': 'cancel'})
return True
+ def action_print_pdf(self):
+ """Print the PDF report"""
+ self.ensure_one()
+ return self.env.ref('stock_inventory_backdate.action_report_inventory_backdate').report_action(self)
+
+ def action_export_xlsx(self):
+ """Open XLSX export wizard"""
+ self.ensure_one()
+ return {
+ 'type': 'ir.actions.act_window',
+ 'name': _('Export Inventory Backdate'),
+ 'res_model': 'stock.inventory.backdate.export.wizard',
+ 'view_mode': 'form',
+ 'target': 'new',
+ 'context': {'default_inventory_id': self.id}
+ }
+
def action_draft(self):
"""Reset to draft"""
self.ensure_one()
@@ -340,6 +333,47 @@ class StockInventoryBackdate(models.Model):
return True
+class StockMove(models.Model):
+ _inherit = 'stock.move'
+
+ def _create_in_svl(self, forced_quantity=None):
+ """Allow primary SVL creation for backdated adjustments, but handle backdating"""
+ svls = super()._create_in_svl(forced_quantity=forced_quantity)
+ if self.env.context.get('backdate_inventory_mode') and svls:
+ # We will backdate SVLs in post-processing
+ pass
+ return svls
+
+ def _create_out_svl(self, forced_quantity=None):
+ """Allow primary SVL creation for backdated adjustments, but handle backdating"""
+ svls = super()._create_out_svl(forced_quantity=forced_quantity)
+ if self.env.context.get('backdate_inventory_mode') and svls:
+ # We will backdate SVLs in post-processing
+ pass
+ return svls
+
+ def product_price_update_before_done(self, forced_qty=None):
+ """
+ In backdated adjustments, we allow the price update for the move itself,
+ but we bypass the recursive revaluation of older moves (vacuuming).
+ """
+ if self.env.context.get('backdate_inventory_mode'):
+ # Call super but with a context that bypasses _run_fifo_vacuum
+ return super(StockMove, self.with_context(skip_fifo_vacuum=True)).product_price_update_before_done(forced_qty=forced_qty)
+ return super().product_price_update_before_done(forced_qty=forced_qty)
+
+
+class ProductProduct(models.Model):
+ _inherit = 'product.product'
+
+ def _run_fifo_vacuum(self, company=None):
+ """Bypass revaluation side-effects during backdated adjustments"""
+ if self.env.context.get('skip_fifo_vacuum') or self.env.context.get('backdate_inventory_mode'):
+ return
+ return super()._run_fifo_vacuum(company=company)
+
+
+
class StockInventoryBackdateLine(models.Model):
_name = 'stock.inventory.backdate.line'
_description = 'Backdated Inventory Adjustment Line'
diff --git a/report/report_stock_inventory_backdate.xml b/report/report_stock_inventory_backdate.xml
new file mode 100644
index 0000000..ab4de9d
--- /dev/null
+++ b/report/report_stock_inventory_backdate.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+