feat: add PDF report and XLSX export functionality for backdated inventory adjustments and refine valuation logic
This commit is contained in:
parent
5062762237
commit
005062bf0a
28
README.md
28
README.md
@ -44,12 +44,9 @@ This module allows you to create backdated inventory adjustments with a specific
|
|||||||
### How It Works
|
### How It Works
|
||||||
|
|
||||||
1. When you validate a backdated adjustment, the module creates standard stock moves
|
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:
|
2. The system performs a **Physical Stock Adjustment Only**.
|
||||||
- Stock move `date` field
|
3. After the moves are processed, it updates the dates via SQL for the moves and lines to ensure proper backdating.
|
||||||
- Stock move line `date` field
|
4. Any generated `stock.valuation.layer` or `account.move` (accounting) records are automatically **removed** to ensure no financial impact.
|
||||||
- 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.
|
|
||||||
|
|
||||||
### Models
|
### Models
|
||||||
|
|
||||||
@ -63,6 +60,25 @@ This module allows you to create backdated inventory adjustments with a specific
|
|||||||
|
|
||||||
## Version History
|
## 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
|
### Version 17.0.2.1.0
|
||||||
- Refactored validation logic to use batch processing for improved performance and reliability.
|
- 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.
|
- Fixed uniqueness constraint conflicts on account moves by optimizing the sequence of operations.
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
from . import models
|
from . import models
|
||||||
|
from . import wizard
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Stock Inventory Backdate",
|
"name": "Stock Inventory Backdate",
|
||||||
"summary": "Create backdated inventory adjustments with historical position view",
|
"summary": "Create backdated inventory adjustments with historical position view",
|
||||||
"version": "17.0.2.1.0",
|
"version": "17.0.2.5.1",
|
||||||
"category": "Warehouse",
|
"category": "Warehouse",
|
||||||
"author": "Suherdy Yacob",
|
"author": "Suherdy Yacob",
|
||||||
"license": "AGPL-3",
|
"license": "AGPL-3",
|
||||||
@ -9,7 +9,10 @@
|
|||||||
"data": [
|
"data": [
|
||||||
"security/ir.model.access.csv",
|
"security/ir.model.access.csv",
|
||||||
"data/sequence_data.xml",
|
"data/sequence_data.xml",
|
||||||
|
"wizard/stock_inventory_backdate_export_view.xml",
|
||||||
"views/stock_inventory_backdate_views.xml",
|
"views/stock_inventory_backdate_views.xml",
|
||||||
|
"report/stock_inventory_backdate_reports.xml",
|
||||||
|
"report/report_stock_inventory_backdate.xml",
|
||||||
],
|
],
|
||||||
"installable": True,
|
"installable": True,
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@ -250,79 +250,55 @@ class StockInventoryBackdate(models.Model):
|
|||||||
self.env['stock.move.line'].create(ml_vals)
|
self.env['stock.move.line'].create(ml_vals)
|
||||||
|
|
||||||
# Step 3: Action Done on all moves at once
|
# Step 3: Action Done on all moves at once
|
||||||
_logger.info(f"Validating {len(moves_to_process)} moves for {self.name}")
|
# Using context backdate_inventory_mode to allow primary valuation but suppress side-effect revaluations
|
||||||
moves_to_process._action_done()
|
_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._post_process_validated_moves(moves_to_process)
|
||||||
|
|
||||||
self.write({'state': 'done'})
|
self.write({'state': 'done'})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _post_process_validated_moves(self, moves):
|
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()
|
self.ensure_one()
|
||||||
backdate = self.backdate_datetime
|
backdate = self.backdate_datetime
|
||||||
account_date = backdate.date()
|
|
||||||
|
|
||||||
# Flush all pending ORM operations to DB before running raw SQL
|
# Flush all pending ORM operations to DB before running raw SQL
|
||||||
self.env.flush_all()
|
self.env.flush_all()
|
||||||
|
|
||||||
|
move_ids = tuple(moves.ids)
|
||||||
|
|
||||||
# 1. Update stock move dates
|
# 1. Update stock move dates
|
||||||
self.env.cr.execute(
|
self.env.cr.execute(
|
||||||
"UPDATE stock_move SET date = %s WHERE id IN %s",
|
"UPDATE stock_move SET date = %s WHERE id IN %s",
|
||||||
(backdate, tuple(moves.ids))
|
(backdate, move_ids)
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2. Update stock move line dates
|
# 2. Update stock move line dates
|
||||||
self.env.cr.execute(
|
self.env.cr.execute(
|
||||||
"UPDATE stock_move_line SET date = %s WHERE move_id IN %s",
|
"UPDATE stock_move_line SET date = %s WHERE move_id IN %s",
|
||||||
(backdate, tuple(moves.ids))
|
(backdate, move_ids)
|
||||||
)
|
)
|
||||||
|
|
||||||
# 3. Update stock valuation layers
|
# 3. Update stock valuation layer dates
|
||||||
svls = self.env['stock.valuation.layer'].search([('stock_move_id', 'in', moves.ids)])
|
|
||||||
if svls:
|
|
||||||
self.env.cr.execute(
|
self.env.cr.execute(
|
||||||
"UPDATE stock_valuation_layer SET create_date = %s WHERE id IN %s",
|
"UPDATE stock_valuation_layer SET create_date = %s WHERE stock_move_id IN %s",
|
||||||
(backdate, tuple(svls.ids))
|
(backdate, move_ids)
|
||||||
)
|
)
|
||||||
|
|
||||||
# 4. Update account moves
|
# 4. Update account move dates (journal entries)
|
||||||
account_moves = moves.account_move_ids
|
# We find AMs linked to these moves via SVLs
|
||||||
if account_moves:
|
self.env.cr.execute("""
|
||||||
# Update account move dates
|
UPDATE account_move
|
||||||
self.env.cr.execute(
|
SET date = %s
|
||||||
"UPDATE account_move SET date = %s WHERE id IN %s",
|
WHERE id IN (
|
||||||
(account_date, tuple(account_moves.ids))
|
SELECT account_move_id FROM stock_valuation_layer WHERE stock_move_id IN %s
|
||||||
)
|
)
|
||||||
|
""", (backdate.date(), move_ids))
|
||||||
|
|
||||||
# Update account move line dates
|
# 5. Clear cache to reflect changes
|
||||||
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
|
|
||||||
self.env.invalidate_all()
|
self.env.invalidate_all()
|
||||||
|
|
||||||
def action_cancel(self):
|
def action_cancel(self):
|
||||||
@ -333,6 +309,23 @@ class StockInventoryBackdate(models.Model):
|
|||||||
self.write({'state': 'cancel'})
|
self.write({'state': 'cancel'})
|
||||||
return True
|
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):
|
def action_draft(self):
|
||||||
"""Reset to draft"""
|
"""Reset to draft"""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
@ -340,6 +333,47 @@ class StockInventoryBackdate(models.Model):
|
|||||||
return True
|
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):
|
class StockInventoryBackdateLine(models.Model):
|
||||||
_name = 'stock.inventory.backdate.line'
|
_name = 'stock.inventory.backdate.line'
|
||||||
_description = 'Backdated Inventory Adjustment Line'
|
_description = 'Backdated Inventory Adjustment Line'
|
||||||
|
|||||||
70
report/report_stock_inventory_backdate.xml
Normal file
70
report/report_stock_inventory_backdate.xml
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<template id="report_inventory_backdate_template">
|
||||||
|
<t t-call="web.html_container">
|
||||||
|
<t t-foreach="docs" t-as="o">
|
||||||
|
<t t-call="web.external_layout">
|
||||||
|
<div class="page">
|
||||||
|
<div class="oe_structure"/>
|
||||||
|
|
||||||
|
<h2>Backdated Inventory Adjustment: <span t-field="o.name"/></h2>
|
||||||
|
|
||||||
|
<div class="row mt32 mb32">
|
||||||
|
<div class="col-3">
|
||||||
|
<strong>Adjustment Date:</strong>
|
||||||
|
<p t-field="o.backdate_datetime"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<strong>Location:</strong>
|
||||||
|
<p t-field="o.location_id"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<strong>Company:</strong>
|
||||||
|
<p t-field="o.company_id"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<strong>Status:</strong>
|
||||||
|
<p t-field="o.state"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-sm o_main_table mt16">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th name="th_product"><strong>Product</strong></th>
|
||||||
|
<th name="th_lot" t-if="any(l.lot_id for l in o.line_ids)"><strong>Lot/Serial</strong></th>
|
||||||
|
<th name="th_package" t-if="any(l.package_id for l in o.line_ids)"><strong>Package</strong></th>
|
||||||
|
<th class="text-end"><strong>Theoretical</strong></th>
|
||||||
|
<th class="text-end"><strong>Counted</strong></th>
|
||||||
|
<th class="text-end"><strong>Difference</strong></th>
|
||||||
|
<th class="text-end"><strong>UoM</strong></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<t t-foreach="o.line_ids" t-as="line">
|
||||||
|
<tr>
|
||||||
|
<td><span t-field="line.product_id"/></td>
|
||||||
|
<td t-if="any(l.lot_id for l in o.line_ids)"><span t-field="line.lot_id"/></td>
|
||||||
|
<td t-if="any(l.package_id for l in o.line_ids)"><span t-field="line.package_id"/></td>
|
||||||
|
<td class="text-end"><span t-field="line.theoretical_qty"/></td>
|
||||||
|
<td class="text-end"><span t-field="line.counted_qty"/></td>
|
||||||
|
<td class="text-end"><span t-field="line.difference_qty"/></td>
|
||||||
|
<td class="text-end"><span t-field="line.product_uom_id"/></td>
|
||||||
|
</tr>
|
||||||
|
</t>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p t-if="o.notes" class="mt32">
|
||||||
|
<strong>Notes:</strong>
|
||||||
|
<br/>
|
||||||
|
<span t-field="o.notes"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="oe_structure"/>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
||||||
13
report/stock_inventory_backdate_reports.xml
Normal file
13
report/stock_inventory_backdate_reports.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="action_report_inventory_backdate" model="ir.actions.report">
|
||||||
|
<field name="name">Inventory Backdate Report</field>
|
||||||
|
<field name="model">stock.inventory.backdate</field>
|
||||||
|
<field name="report_type">qweb-pdf</field>
|
||||||
|
<field name="report_name">stock_inventory_backdate.report_inventory_backdate_template</field>
|
||||||
|
<field name="report_file">stock_inventory_backdate.report_inventory_backdate</field>
|
||||||
|
<field name="print_report_name">'Inventory Adjustment - %s' % (object.name)</field>
|
||||||
|
<field name="binding_model_id" ref="model_stock_inventory_backdate"/>
|
||||||
|
<field name="binding_type">report</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
@ -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_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_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_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
|
||||||
|
|||||||
|
@ -26,6 +26,8 @@
|
|||||||
<button name="action_validate" string="Validate" type="object" class="oe_highlight" invisible="state != 'draft'"/>
|
<button name="action_validate" string="Validate" type="object" class="oe_highlight" invisible="state != 'draft'"/>
|
||||||
<button name="action_cancel" string="Cancel" type="object" invisible="state != 'draft'"/>
|
<button name="action_cancel" string="Cancel" type="object" invisible="state != 'draft'"/>
|
||||||
<button name="action_draft" string="Set to Draft" type="object" invisible="state != 'cancel'"/>
|
<button name="action_draft" string="Set to Draft" type="object" invisible="state != 'cancel'"/>
|
||||||
|
<button name="action_print_pdf" string="Print PDF" type="object" class="btn-secondary" invisible="state != 'done'"/>
|
||||||
|
<button name="action_export_xlsx" string="Export XLSX" type="object" class="btn-secondary" invisible="state != 'done'"/>
|
||||||
<field name="state" widget="statusbar" statusbar_visible="draft,done"/>
|
<field name="state" widget="statusbar" statusbar_visible="draft,done"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
|
|||||||
1
wizard/__init__.py
Normal file
1
wizard/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from . import stock_inventory_backdate_export
|
||||||
92
wizard/stock_inventory_backdate_export.py
Normal file
92
wizard/stock_inventory_backdate_export.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import io
|
||||||
|
import base64
|
||||||
|
from odoo import models, fields, api, _
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
try:
|
||||||
|
import xlsxwriter
|
||||||
|
except ImportError:
|
||||||
|
xlsxwriter = None
|
||||||
|
|
||||||
|
|
||||||
|
class StockInventoryBackdateExportWizard(models.TransientModel):
|
||||||
|
_name = 'stock.inventory.backdate.export.wizard'
|
||||||
|
_description = 'Export Inventory Backdate to XLSX'
|
||||||
|
|
||||||
|
inventory_id = fields.Many2one('stock.inventory.backdate', string='Inventory Adjustment', readonly=True)
|
||||||
|
file_data = fields.Binary(string='Download File', readonly=True)
|
||||||
|
file_name = fields.Char(string='File Name', readonly=True)
|
||||||
|
state = fields.Selection([
|
||||||
|
('choose', 'Choose'),
|
||||||
|
('get', 'Get'),
|
||||||
|
], default='choose')
|
||||||
|
|
||||||
|
def action_export(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if not xlsxwriter:
|
||||||
|
raise UserError(_('The xlsxwriter library is not installed. Please contact your administrator.'))
|
||||||
|
|
||||||
|
output = io.BytesIO()
|
||||||
|
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
|
||||||
|
sheet = workbook.add_worksheet('Inventory Adjustment')
|
||||||
|
|
||||||
|
# Formats
|
||||||
|
header_format = workbook.add_format({
|
||||||
|
'bold': True, 'bg_color': '#D3D3D3', 'border': 1, 'align': 'center'
|
||||||
|
})
|
||||||
|
title_format = workbook.add_format({'bold': True, 'font_size': 14})
|
||||||
|
date_format = workbook.add_format({'num_format': 'yyyy-mm-dd hh:mm:ss'})
|
||||||
|
number_format = workbook.add_format({'num_format': '#,##0.00'})
|
||||||
|
|
||||||
|
# Title
|
||||||
|
sheet.write(0, 0, _('Backdated Inventory Adjustment: %s') % self.inventory_id.name, title_format)
|
||||||
|
sheet.write(2, 0, _('Date:'), header_format)
|
||||||
|
sheet.write(2, 1, self.inventory_id.backdate_datetime.strftime('%Y-%m-%d %H:%M:%S') if self.inventory_id.backdate_datetime else '', date_format)
|
||||||
|
sheet.write(3, 0, _('Location:'), header_format)
|
||||||
|
sheet.write(3, 1, self.inventory_id.location_id.display_name or '')
|
||||||
|
|
||||||
|
# Table Header
|
||||||
|
row = 5
|
||||||
|
col = 0
|
||||||
|
headers = [_('Product'), _('Lot/Serial'), _('Package'), _('Theoretical Qty'), _('Counted Qty'), _('Difference'), _('UoM')]
|
||||||
|
for h in headers:
|
||||||
|
sheet.write(row, col, h, header_format)
|
||||||
|
col += 1
|
||||||
|
|
||||||
|
# Data
|
||||||
|
row += 1
|
||||||
|
for line in self.inventory_id.line_ids:
|
||||||
|
col = 0
|
||||||
|
sheet.write(row, col, line.product_id.display_name)
|
||||||
|
col += 1
|
||||||
|
sheet.write(row, col, line.lot_id.name or '')
|
||||||
|
col += 1
|
||||||
|
sheet.write(row, col, line.package_id.name or '')
|
||||||
|
col += 1
|
||||||
|
sheet.write(row, col, line.theoretical_qty, number_format)
|
||||||
|
col += 1
|
||||||
|
sheet.write(row, col, line.counted_qty, number_format)
|
||||||
|
col += 1
|
||||||
|
sheet.write(row, col, line.difference_qty, number_format)
|
||||||
|
col += 1
|
||||||
|
sheet.write(row, col, line.product_uom_id.name or '')
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
workbook.close()
|
||||||
|
output.seek(0)
|
||||||
|
|
||||||
|
file_name = 'Inventory_Backdate_%s.xlsx' % self.inventory_id.name.replace('/', '_')
|
||||||
|
self.write({
|
||||||
|
'state': 'get',
|
||||||
|
'file_data': base64.b64encode(output.read()),
|
||||||
|
'file_name': file_name
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': 'stock.inventory.backdate.export.wizard',
|
||||||
|
'view_mode': 'form',
|
||||||
|
'res_id': self.id,
|
||||||
|
'target': 'new',
|
||||||
|
}
|
||||||
31
wizard/stock_inventory_backdate_export_view.xml
Normal file
31
wizard/stock_inventory_backdate_export_view.xml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_stock_inventory_backdate_export_wizard_form" model="ir.ui.view">
|
||||||
|
<field name="name">stock.inventory.backdate.export.wizard.form</field>
|
||||||
|
<field name="model">stock.inventory.backdate.export.wizard</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Export Inventory Backdate">
|
||||||
|
<field name="state" invisible="1"/>
|
||||||
|
<div invisible="state != 'choose'">
|
||||||
|
<p>Click "Export" to generate the XLSX file for this adjustment.</p>
|
||||||
|
</div>
|
||||||
|
<div invisible="state != 'get'">
|
||||||
|
<p>Your file is ready for download.</p>
|
||||||
|
<field name="file_name" invisible="1"/>
|
||||||
|
<field name="file_data" filename="file_name" readonly="1"/>
|
||||||
|
</div>
|
||||||
|
<footer>
|
||||||
|
<button name="action_export" string="Export" type="object" class="btn-primary" invisible="state != 'choose'"/>
|
||||||
|
<button string="Close" class="btn-secondary" special="cancel"/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_stock_inventory_backdate_export" model="ir.actions.act_window">
|
||||||
|
<field name="name">Export Inventory Backdate</field>
|
||||||
|
<field name="res_model">stock.inventory.backdate.export.wizard</field>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="target">new</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
Loading…
Reference in New Issue
Block a user