Files
enterprise-addons/account_asset/controller/asset_template_controller.py
T

173 lines
9.8 KiB
Python

import io
import xlsxwriter
from odoo import http, _
from odoo.http import request
class AssetTemplateController(http.Controller):
@http.route('/web/binary/download_asset_template/<int:company_id>', type='http', auth='user')
def download_template(self, company_id):
"""
Handles the HTTP request and serves the generated Excel file for download.
"""
content = self._generate_asset_import_template(company_id)
return request.make_response(
content,
headers=[
('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'),
('Content-Disposition', 'attachment; filename="asset_import_template.xlsx"'),
]
)
def _get_technical_headers(self):
""" Defines and returns the common functional and technical headers. """
return [item[0] for item in self._get_instructions_data()]
def _get_localized_example_data(self, current_company_id, env):
"""
Dynamically fetches localized example values for accounts and journal
based on the current company and generates multiple example rows.
"""
current_company = env['res.company'].browse(current_company_id)
def get_account_name_by_type(account_types):
for acc_type in account_types:
if account := env['account.account'].with_company(current_company).search([
*env['account.account']._check_company_domain(current_company),
('account_type', '=', acc_type),
], limit=1):
return account.display_name if account.display_name.startswith(account.code) else f"{account.code} {account.display_name}"
return ''
def get_journal_name_by_type(journal_type):
journal = env['account.journal'].with_company(current_company).search([
*env['account.journal']._check_company_domain(current_company),
('type', '=', journal_type),
], limit=1)
return journal.name or ''
asset_account_example = get_account_name_by_type(['asset_fixed', 'asset_non_current'])
depreciation_account_example = get_account_name_by_type(['asset_fixed', 'asset_non_current'])
expense_account_example = get_account_name_by_type(['expense_depreciation', 'expense'])
journal_example = get_journal_name_by_type('general')
all_example_rows = [
[
'Computer 1', '800.00', '01-01-2025', '', 'Straight Line', '4', 'Years', '', 'No Prorata', '01-01-2025',
'200.00', '0.00', current_company.name,
asset_account_example, depreciation_account_example,
expense_account_example, journal_example,
],
[
'Computer 2', '25000.00', '03-15-2025', '', 'Declining', '5', 'Years', '2', 'Based on days per period', '03-15-2025',
'0.00', '2000.00', current_company.name,
asset_account_example, depreciation_account_example,
expense_account_example, journal_example,
],
[
'Machine A', '15000.00', '09-01-2024', '', 'Straight Line', '10', 'Years', '', 'Constant Periods', '09-01-2024',
'1500.00', '500.00', current_company.name,
asset_account_example, depreciation_account_example,
expense_account_example, journal_example,
],
[
'Machine B', '100000.00', '06-20-2025', '', 'Straight Line', '60', 'Months', '', 'No Prorata', '06-20-2025',
'10000.00', '10000.00', current_company.name,
asset_account_example, depreciation_account_example,
expense_account_example, journal_example,
]
]
return all_example_rows
def _get_instructions_data(self):
"""Defines and returns the data for the 'Instructions' sheet."""
return [
('name', _('Asset Name'), True, _('Mandatory column. This is the name of the asset.')),
('original_value', _('Original Value'), True, _('The amount will be considered in company currency.')),
('acquisition_date', _('Acquisition Date'), True, _('e.g. 01-27-2025 (format: MM-DD-YYYY)')),
('asset_group_id', _('Asset Group'), False, _('Optional. The name or external ID of the asset group. Must exist in Odoo (e.g., Office Equipment, Vehicles, Machinery).')),
('method', _('Method'), True, _('e.g. Straight Line, Declining Balance. Determines how depreciation is calculated.')),
('method_number', _('Duration'), True, _('e.g. 3 for 3 years, or 12 for 12 months. It must be an integer representing the total number of periods.')),
('method_period', _('Months/Years'), True, _('Either "Months" or "Years" (case-insensitive). Specifies the unit of duration.')),
('method_progress_factor', _('Declining Factor'), False, _('Only applicable for Declining Balance method (e.g., 2 for double declining). Ignored for Straight Line.')),
('prorata_computation_type', _('Computation'), True, _('e.g. No Prorata, Constant Periods, Based on days per period. This determines how the first depreciation entry is calculated.')),
('prorata_date', _('Prorata Date'), True, _('Start date of the depreciation period. Should be in MM-DD-YYYY format. If left blank, it defaults to the acquisition date.')),
('already_depreciated_amount_import', _('Depreciated Amount'), False, _('The total amount of depreciation already recorded for the asset before import. This amount will be considered in company currency.')),
('salvage_value', _('Not Depreciable Value'), False, _('The estimated residual value of the asset at the end of its useful life. This amount will not be depreciated. Considered in company currency.')),
('company_id', _('Company'), True, _("Must match the selected company during import. Use the company's display name.")),
('account_asset_id', _('Fixed Asset Account'), True, _('The balance sheet account for the asset itself (e.g., "151000 Fixed Asset"). Must exist in Odoo and be of "Fixed Asset" or "Non-current Assets" type.')),
('account_depreciation_id', _('Depreciation Account'), True, _('The accumulated depreciation account (contra-asset account). Must exist in Odoo and be of "Fixed Asset" or "Non-current Assets" type.')),
('account_depreciation_expense_id', _('Expense Account'), True, _('The expense account for posting periodic depreciation entries (e.g., "630000 Depreciation Expenses"). Must exist in Odoo and be of "Depreciation" or "Expense" type.')),
('journal_id', _('Journal'), True, _('The code or name of the associated journal in Odoo for depreciation entries (e.g., MISC - Miscellaneous Operations, INV - Customer Invoices, BILL - Vendor Bills).')),
]
def _write_asset_sheet(self, workbook, headers, all_example_rows):
"""
Creates and populates the 'Assets' sheet with headers, multiple example data rows, and comments.
"""
worksheet_assets = workbook.add_worksheet('Assets')
header_format = workbook.add_format({'bold': True})
for col, header in enumerate(headers):
worksheet_assets.write(0, col, header, header_format)
for row_idx, example_row in enumerate(all_example_rows, start=1):
for col, example_value in enumerate(example_row):
worksheet_assets.write(row_idx, col, example_value)
for col, header in enumerate(headers):
max_len = len(str(header))
for example_row in all_example_rows:
if col < len(example_row):
max_len = max(max_len, len(str(example_row[col])))
worksheet_assets.set_column(col, col, max_len + 4)
def _write_instructions_sheet(self, workbook, instructions_data):
"""
Creates and populates the 'Instructions' sheet with column details and notes.
"""
worksheet_instructions = workbook.add_worksheet('Instructions')
instruction_headers = ['Column Name', 'Column Functional Name', 'Comments / Notes']
instruction_header_format = workbook.add_format({'bold': True})
for col_idx, header_text in enumerate(instruction_headers):
worksheet_instructions.write(0, col_idx, header_text, instruction_header_format)
for row_idx, (field, label, mandatory, help_text) in enumerate(instructions_data):
display_label = f"{label}*" if mandatory else label
worksheet_instructions.write(row_idx + 1, 0, field)
worksheet_instructions.write(row_idx + 1, 1, display_label)
worksheet_instructions.write(row_idx + 1, 2, help_text)
for col_idx, header_text in enumerate(instruction_headers):
max_len = len(header_text)
for row_data in instructions_data:
current_len = len(str(row_data[col_idx]))
if current_len > max_len:
max_len = current_len
worksheet_instructions.set_column(col_idx, col_idx, max_len + 4)
def _generate_asset_import_template(self, company_id):
"""
Generates an Excel file with:
- Sheet 1: Asset import template (headers, example, comments)
- Sheet 2: Instructions (column details, notes)
"""
technical_headers = self._get_technical_headers()
all_example_rows = self._get_localized_example_data(company_id, request.env)
instructions_data = self._get_instructions_data()
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
self._write_asset_sheet(workbook, technical_headers, all_example_rows)
self._write_instructions_sheet(workbook, instructions_data)
workbook.close()
return output.getvalue()