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 @@ + + + + diff --git a/report/stock_inventory_backdate_reports.xml b/report/stock_inventory_backdate_reports.xml new file mode 100644 index 0000000..bdbffaa --- /dev/null +++ b/report/stock_inventory_backdate_reports.xml @@ -0,0 +1,13 @@ + + + + Inventory Backdate Report + stock.inventory.backdate + qweb-pdf + stock_inventory_backdate.report_inventory_backdate_template + stock_inventory_backdate.report_inventory_backdate + 'Inventory Adjustment - %s' % (object.name) + + report + + diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv index e81ae50..290ceb7 100755 --- a/security/ir.model.access.csv +++ b/security/ir.model.access.csv @@ -3,3 +3,5 @@ access_stock_inventory_backdate_user,stock.inventory.backdate.user,model_stock_i access_stock_inventory_backdate_line_user,stock.inventory.backdate.line.user,model_stock_inventory_backdate_line,stock.group_stock_user,1,1,1,1 access_stock_inventory_backdate_manager,stock.inventory.backdate.manager,model_stock_inventory_backdate,stock.group_stock_manager,1,1,1,1 access_stock_inventory_backdate_line_manager,stock.inventory.backdate.line.manager,model_stock_inventory_backdate_line,stock.group_stock_manager,1,1,1,1 +access_stock_inventory_backdate_export_wizard_user,stock.inventory.backdate.export.wizard,model_stock_inventory_backdate_export_wizard,stock.group_stock_user,1,1,1,1 +access_stock_inventory_backdate_export_wizard_manager,stock.inventory.backdate.export.wizard,model_stock_inventory_backdate_export_wizard,stock.group_stock_manager,1,1,1,1 diff --git a/views/stock_inventory_backdate_views.xml b/views/stock_inventory_backdate_views.xml index cf84f67..694bb97 100755 --- a/views/stock_inventory_backdate_views.xml +++ b/views/stock_inventory_backdate_views.xml @@ -26,6 +26,8 @@