From fb367aca548feb8da8bb311dc73f75371642c3b3 Mon Sep 17 00:00:00 2001 From: ptpu-odoo Date: Wed, 19 Mar 2025 18:59:35 +0530 Subject: [PATCH 1/2] [ADD] accountant: Added Custom duty on Import and Export Transactions With this Commit ================ - To facilitate the Indian integration for custom duty calculations, the following configurations are required: - Input two accounts and one journal ID for processing custom duty charges. - These accounts and the journal will be used to compute the journal entry for custom duty expenses. - Vendor Bill Processing A dedicated functionality is provided for vendor bills that meet the following criteria: - The bill must be already posted. - It must belong to one of the following categories: - Overseas - Special Economic Zone (SEZ) - Deemed Export - Price Unit Calculation in INR - The price unit will be converted into Indian Rupees (INR). - The calculation will include: - Custom duty charges - Additional tax amount - Journal Entry Creation - Upon confirmation, a journal entry will be generated based on the computed data from the Bill of Entry wizard. - Navigation to Journal Entry - After confirmation, a stat button will appear. - Clicking this button will redirect the user to the journal entry page for review and verification. --- accountant_custom_duty/__init__.py | 2 + accountant_custom_duty/__manifest__.py | 16 ++ accountant_custom_duty/models/__init__.py | 4 + accountant_custom_duty/models/account_move.py | 56 +++++ .../models/account_move_bill_of_entry_line.py | 29 +++ accountant_custom_duty/models/res_company.py | 17 ++ .../models/res_config_settings.py | 51 +++++ .../security/ir.model.access.csv | 4 + .../views/account_move_views_inherit.xml | 78 +++++++ .../views/res_config_settings_views.xml | 52 +++++ accountant_custom_duty/wizard/__init__.py | 2 + .../wizard/account_bill_of_entry_wizard.py | 208 ++++++++++++++++++ .../wizard/account_bill_of_entry_wizard.xml | 69 ++++++ .../wizard/account_move_line_wizard.py | 44 ++++ 14 files changed, 632 insertions(+) create mode 100644 accountant_custom_duty/__init__.py create mode 100644 accountant_custom_duty/__manifest__.py create mode 100644 accountant_custom_duty/models/__init__.py create mode 100644 accountant_custom_duty/models/account_move.py create mode 100644 accountant_custom_duty/models/account_move_bill_of_entry_line.py create mode 100644 accountant_custom_duty/models/res_company.py create mode 100644 accountant_custom_duty/models/res_config_settings.py create mode 100644 accountant_custom_duty/security/ir.model.access.csv create mode 100644 accountant_custom_duty/views/account_move_views_inherit.xml create mode 100644 accountant_custom_duty/views/res_config_settings_views.xml create mode 100644 accountant_custom_duty/wizard/__init__.py create mode 100644 accountant_custom_duty/wizard/account_bill_of_entry_wizard.py create mode 100644 accountant_custom_duty/wizard/account_bill_of_entry_wizard.xml create mode 100644 accountant_custom_duty/wizard/account_move_line_wizard.py diff --git a/accountant_custom_duty/__init__.py b/accountant_custom_duty/__init__.py new file mode 100644 index 00000000000..9b4296142f4 --- /dev/null +++ b/accountant_custom_duty/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard diff --git a/accountant_custom_duty/__manifest__.py b/accountant_custom_duty/__manifest__.py new file mode 100644 index 00000000000..e0e2e352384 --- /dev/null +++ b/accountant_custom_duty/__manifest__.py @@ -0,0 +1,16 @@ +{ + 'name': 'Custom duty on Import and Export', + 'version': '1.0', + 'license': 'LGPL-3', + 'depends': ['accountant', 'l10n_in'], + 'auto_install': True, + 'application': True, + 'data':[ + 'security/ir.model.access.csv', + + 'wizard/account_bill_of_entry_wizard.xml', + + 'views/account_move_views_inherit.xml', + 'views/res_config_settings_views.xml' + ] +} diff --git a/accountant_custom_duty/models/__init__.py b/accountant_custom_duty/models/__init__.py new file mode 100644 index 00000000000..74a00714f8c --- /dev/null +++ b/accountant_custom_duty/models/__init__.py @@ -0,0 +1,4 @@ +from . import res_company +from . import res_config_settings +from . import account_move +from . import account_move_bill_of_entry_line diff --git a/accountant_custom_duty/models/account_move.py b/accountant_custom_duty/models/account_move.py new file mode 100644 index 00000000000..96279d978f6 --- /dev/null +++ b/accountant_custom_duty/models/account_move.py @@ -0,0 +1,56 @@ +from odoo import fields, models + + +class AccountMove(models.Model): + _inherit = "account.move" + + is_confirmed = fields.Boolean(string="Confirmed", default=False) + l10n_in_journal_entry_number = fields.Char(string="Journal Entry Number") + l10n_in_company_currency_id = fields.Many2one("res.currency", string="Company Currency ID", default=lambda self: self.env.ref("base.INR")) + l10n_in_custom_currency_rate = fields.Monetary(string="Custom Currency Rate", currency_field="l10n_in_company_currency_id") + l10n_in_reference = fields.Char(string="Bill Number") + l10n_in_total_custom_duty = fields.Monetary(string="Total Custom Duty", currency_field="l10n_in_company_currency_id") + l10n_in_total_l10n_in_tax_amount = fields.Monetary(string="Total Tax Amount", currency_field="l10n_in_company_currency_id") + l10n_in_total_amount_payable = fields.Monetary(string="Total Amount Payable", currency_field="l10n_in_company_currency_id") + + bill_of_entry_line_ids = fields.One2many("account.move.bill.of.entry.line", "move_id", string="Bill of Entry Details") + + def action_open_wizard(self): + """Opens the Bill of Entry Wizard.""" + return { + "type": "ir.actions.act_window", + "name": "Bill of Entry Wizard", + "res_model": "account.bill.of.entry.wizard", + "view_mode": "form", + "target": "new", + "context": { + "default_move_id": self.id, + "default_l10n_in_reference": self.name, + "default_l10n_in_custom_duty_import_journal_id": self.env.company.l10n_in_custom_duty_import_journal_id.id, + "default_l10n_in_account_custom_duty_income_id": self.env.company.l10n_in_account_custom_duty_income_id.id, + "default_l10n_in_import_default_tax_account": self.env.company.l10n_in_import_default_tax_account.id, + "default_l10n_in_custom_duty_tax_payable_account_import": self.env.company.l10n_in_custom_duty_tax_payable_account_import.id, + "default_l10n_in_shipping_bill_number": self.l10n_in_shipping_bill_number, + "default_l10n_in_shipping_bill_date": self.l10n_in_shipping_bill_date, + "default_l10n_in_shipping_port_code_id": self.l10n_in_shipping_port_code_id, + }, + } + + def action_move_journal_line_bill_of_entry(self): + """Opens the related journal entry.""" + matching_move = self.env["account.move"].search([("name", "=", self.l10n_in_journal_entry_number)], limit=1) + if matching_move: + return { + "type": "ir.actions.act_window", + "res_model": "account.move", + "views": [[False, "form"]], + "res_id": matching_move.id, + } + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + + l10n_in_assessable_value = fields.Monetary(string="Assessable Value") + l10n_in_custom_duty_additional = fields.Monetary(string="Custom Duty + Additional Charges") + l10n_in_taxable_amount = fields.Monetary(string="Taxable Amount") + l10n_in_tax_amount = fields.Monetary(string="Tax Amount") diff --git a/accountant_custom_duty/models/account_move_bill_of_entry_line.py b/accountant_custom_duty/models/account_move_bill_of_entry_line.py new file mode 100644 index 00000000000..4879eedec60 --- /dev/null +++ b/accountant_custom_duty/models/account_move_bill_of_entry_line.py @@ -0,0 +1,29 @@ +from odoo import fields, models + + +class AccountMoveBillOfEntryLine(models.Model): + _name = "account.move.bill.of.entry.line" + _description = "Bill of Entry Line" + + move_id = fields.Many2one("account.move", string="Journal Entry") + l10n_in_company_currency_id = fields.Many2one("res.currency", related="move_id.l10n_in_company_currency_id", string="Company Currency", readonly=True, default=lambda self: self.env.ref("base.INR")) + account_id = fields.Many2one("account.account", string="Account") + label = fields.Char(string="Label") + debit = fields.Monetary(string="Debit", currency_field="l10n_in_company_currency_id") + credit = fields.Monetary(string="Credit", currency_field="l10n_in_company_currency_id") + + product_id = fields.Many2one("product.product", string="Product") + name = fields.Char(string="Description") + quantity = fields.Float(string="Quantity") + price_unit = fields.Monetary(string="Unit Price", currency_field="l10n_in_company_currency_id") + tax_ids = fields.Many2many("account.tax", string="Taxes", domain=[("type_tax_use", "=", "purchase")]) + + l10n_in_custom_currency_rate = fields.Monetary(string="Custom Currency Rate", related="move_id.l10n_in_custom_currency_rate", currency_field="l10n_in_company_currency_id", readonly=True) + l10n_in_assessable_value = fields.Monetary(string="Assessable Value", store=True, currency_field="l10n_in_company_currency_id") + l10n_in_custom_duty_additional = fields.Monetary(string="Custom Duty + Additional Charges", currency_field="l10n_in_company_currency_id", help="Enter any additional custom duty charges manually.") + l10n_in_taxable_amount = fields.Monetary(string="Taxable Amount", store=True, currency_field="l10n_in_company_currency_id") + l10n_in_tax_amount = fields.Monetary(string="Tax Amount", store=True, currency_field="l10n_in_company_currency_id") + + l10n_in_shipping_bill_number = fields.Char(string="Shipping Bill Number", related="move_id.l10n_in_shipping_bill_number", readonly=True) + l10n_in_shipping_bill_date = fields.Date(string="Shipping Bill Date", related="move_id.l10n_in_shipping_bill_date", readonly=True) + l10n_in_shipping_port_code_id = fields.Many2one("l10n_in.port.code", string="Shipping Port Code", related="move_id.l10n_in_shipping_port_code_id", readonly=True) diff --git a/accountant_custom_duty/models/res_company.py b/accountant_custom_duty/models/res_company.py new file mode 100644 index 00000000000..cf673ee9ff8 --- /dev/null +++ b/accountant_custom_duty/models/res_company.py @@ -0,0 +1,17 @@ +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + l10n_in_custom_duty = fields.Boolean(string="Enable Custom Duty") + + l10n_in_custom_duty_import_journal_id = fields.Many2one(comodel_name="account.journal",string="Import Journal",check_company=True) + l10n_in_account_custom_duty_income_id = fields.Many2one(comodel_name="account.account",string="Import Custom Duty Income Account",check_company=True) + l10n_in_import_default_tax_account = fields.Many2one(comodel_name="account.account",string="Import Journal Suspense Account",check_company=True) + + l10n_in_custom_duty_export_journal_id = fields.Many2one(comodel_name="account.journal",string="Export Journal",check_company=True) + l10n_in_account_custom_duty_expense_income_id = fields.Many2one(comodel_name="account.account",string="Export Custom Duty Expense/Income Account",check_company=True) + l10n_in_export_default_tax_account = fields.Many2one(comodel_name="account.account",string="Export Journal Suspense Account",check_company=True) + + l10n_in_custom_duty_tax_payable_account_import = fields.Many2one(comodel_name="account.account",string="Custom Duty Tax Payable Account",check_company=True) diff --git a/accountant_custom_duty/models/res_config_settings.py b/accountant_custom_duty/models/res_config_settings.py new file mode 100644 index 00000000000..37f88b75bbe --- /dev/null +++ b/accountant_custom_duty/models/res_config_settings.py @@ -0,0 +1,51 @@ +from odoo import api, fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + l10n_in_custom_duty = fields.Boolean(related="company_id.l10n_in_custom_duty", readonly=False, help="Enable Custom Duty.") + l10n_in_custom_duty_tax_payable_account_import = fields.Many2one(comodel_name="account.account", related="company_id.l10n_in_custom_duty_tax_payable_account_import", readonly=False, help="Import and Export tax payable account.") + + # Import + l10n_in_custom_duty_import_journal_id = fields.Many2one(comodel_name="account.journal", related="company_id.l10n_in_custom_duty_import_journal_id", readonly=False, help="Journal used for import custom duty.") + l10n_in_account_custom_duty_income_id = fields.Many2one(comodel_name="account.account", related="company_id.l10n_in_account_custom_duty_income_id", readonly=False, domain="[('account_type', '=', 'income')]", help="Import custom duty account.") + l10n_in_import_default_tax_account = fields.Many2one(comodel_name="account.account", related="company_id.l10n_in_import_default_tax_account", readonly=False, domain="[('reconcile', '=', True), ('deprecated', '=', False), ('account_type', 'in', ('asset_current', 'liability_current'))]", help="Import account which is of type Current Asset or Liability.") + + # Export + l10n_in_custom_duty_export_journal_id = fields.Many2one(comodel_name="account.journal", related="company_id.l10n_in_custom_duty_export_journal_id", readonly=False, help="Journal used for export custom duty.") + l10n_in_account_custom_duty_expense_income_id = fields.Many2one(comodel_name="account.account", related="company_id.l10n_in_account_custom_duty_expense_income_id", readonly=False, domain="[('account_type', 'in', ('income', 'expense'))]", help="Export custom duty account.") + l10n_in_export_default_tax_account = fields.Many2one(comodel_name="account.account", related="company_id.l10n_in_export_default_tax_account", readonly=False, domain="[('reconcile', '=', True), ('deprecated', '=', False), ('account_type', 'in', ('asset_current', 'liability_current'))]", help="Export account which is of type Current Asset or Liability.") + + @api.model + def get_values(self): + res = super().get_values() + company = self.env.company + res.update( + l10n_in_custom_duty=company.l10n_in_custom_duty, + l10n_in_custom_duty_import_journal_id=company.l10n_in_custom_duty_import_journal_id.id, + l10n_in_account_custom_duty_income_id=company.l10n_in_account_custom_duty_income_id.id, + l10n_in_import_default_tax_account=company.l10n_in_import_default_tax_account.id, + l10n_in_custom_duty_tax_payable_account_import=company.l10n_in_custom_duty_tax_payable_account_import.id, + l10n_in_custom_duty_export_journal_id=company.l10n_in_custom_duty_export_journal_id.id, + l10n_in_account_custom_duty_expense_income_id=company.l10n_in_account_custom_duty_expense_income_id.id, + l10n_in_export_default_tax_account=company.l10n_in_export_default_tax_account.id, + ) + return res + + def set_values(self): + super().set_values() + company = self.env.company + + company.write( + { + "l10n_in_custom_duty": self.l10n_in_custom_duty, + "l10n_in_custom_duty_import_journal_id": self.l10n_in_custom_duty_import_journal_id.id, + "l10n_in_account_custom_duty_income_id": self.l10n_in_account_custom_duty_income_id.id, + "l10n_in_import_default_tax_account": self.l10n_in_import_default_tax_account.id, + "l10n_in_custom_duty_tax_payable_account_import": self.l10n_in_custom_duty_tax_payable_account_import.id, + "l10n_in_custom_duty_export_journal_id": self.l10n_in_custom_duty_export_journal_id.id, + "l10n_in_account_custom_duty_expense_income_id": self.l10n_in_account_custom_duty_expense_income_id.id, + "l10n_in_export_default_tax_account": self.l10n_in_export_default_tax_account.id, + } + ) diff --git a/accountant_custom_duty/security/ir.model.access.csv b/accountant_custom_duty/security/ir.model.access.csv new file mode 100644 index 00000000000..a12519705be --- /dev/null +++ b/accountant_custom_duty/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_account_bill_of_entry_wizard,access_account_bill_of_entry_wizard,model_account_bill_of_entry_wizard,,1,1,1,0 +access_account_move_line_wizard,Access Account Move Line Wizard,model_account_move_line_wizard,,1,1,1,1 +access_account_move_bill_of_entry_line,access_account_move_bill_of_entry_line,model_account_move_bill_of_entry_line,,1,1,1,0 diff --git a/accountant_custom_duty/views/account_move_views_inherit.xml b/accountant_custom_duty/views/account_move_views_inherit.xml new file mode 100644 index 00000000000..c45f8440c9d --- /dev/null +++ b/accountant_custom_duty/views/account_move_views_inherit.xml @@ -0,0 +1,78 @@ + + + + + account.move.form.import.export.custom.duty + account.move + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + not (not l10n_in_custom_currency_rate or move_type in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt') or state == 'draft') + + + not (not l10n_in_custom_currency_rate or move_type in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt') or state == 'draft') + + + not (not l10n_in_custom_currency_rate or move_type in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt') or state == 'draft') + + + (l10n_in_custom_currency_rate or move_type in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt') or state == 'draft') + + + + + diff --git a/accountant_custom_duty/views/res_config_settings_views.xml b/accountant_custom_duty/views/res_config_settings_views.xml new file mode 100644 index 00000000000..06453776505 --- /dev/null +++ b/accountant_custom_duty/views/res_config_settings_views.xml @@ -0,0 +1,52 @@ + + + + res.config.settings.view.form.inherit.account.base.import.export.custom.duty + res.config.settings + + + + + + +
+

Import

+
+
+
+
+
+
+ +

Export

+
+
+
+
+
+
+ +

Default Tax Account

+
+
+
+
+
+
+
+
+
diff --git a/accountant_custom_duty/wizard/__init__.py b/accountant_custom_duty/wizard/__init__.py new file mode 100644 index 00000000000..976866c06e6 --- /dev/null +++ b/accountant_custom_duty/wizard/__init__.py @@ -0,0 +1,2 @@ +from . import account_bill_of_entry_wizard +from . import account_move_line_wizard diff --git a/accountant_custom_duty/wizard/account_bill_of_entry_wizard.py b/accountant_custom_duty/wizard/account_bill_of_entry_wizard.py new file mode 100644 index 00000000000..025e9d9e194 --- /dev/null +++ b/accountant_custom_duty/wizard/account_bill_of_entry_wizard.py @@ -0,0 +1,208 @@ +from odoo import api, Command, fields, models + + +class CustomWizard(models.TransientModel): + _name = "account.bill.of.entry.wizard" + _description = "Custom Wizard" + + l10n_in_journal_entry_date = fields.Date(string="Journal Entry Date", default=fields.Date.today()) + l10n_in_custom_duty_import_journal_id = fields.Many2one("account.journal", string="Journal") + l10n_in_account_custom_duty_income_id = fields.Many2one(comodel_name="account.account", string="Separate account for income discount") + + l10n_in_import_default_tax_account = fields.Many2one("account.account", string="Journal Suspense Account", check_company=True) + l10n_in_custom_duty_tax_payable_account_import = fields.Many2one("account.account", string="Custom Duty Tax Payable Account", check_company=True) + + l10n_in_company_currency_id = fields.Many2one("res.currency", string="Company Currency ID", default=lambda self: self.env.ref("base.INR")) + l10n_in_custom_currency_rate = fields.Monetary(string="Custom Currency Rate", currency_field="l10n_in_company_currency_id", default=1.0) + + l10n_in_reference = fields.Char(string="Bill Number", help="Bill No. on which this model is accessed", readonly=True) + l10n_in_journal_entry_number = fields.Char(string="Journal Entry Number", compute="_compute_l10n_in_journal_entry_number", store=True) + + l10n_in_shipping_bill_number = fields.Char("Bill of Entry No.") + l10n_in_shipping_bill_date = fields.Date("Bill of Entry Date") + l10n_in_shipping_port_code_id = fields.Many2one("l10n_in.port.code", "Port code") + + l10n_in_move_line_ids = fields.One2many("account.move.line.wizard", "wizard_id", string="Product Lines") + + l10n_in_total_custom_duty = fields.Monetary(string="Total Custom Duty", compute="_compute_l10n_in_total_custom_duty", store=True, currency_field="l10n_in_company_currency_id") + + l10n_in_total_l10n_in_tax_amount = fields.Monetary(string="Total Tax Amount", compute="_compute_l10n_in_total_l10n_in_tax_amount", store=True, currency_field="l10n_in_company_currency_id") + + l10n_in_total_amount_payable = fields.Monetary(string="Total Amount Payable", compute="_compute_l10n_in_total_amount_payable", store=True, currency_field="l10n_in_company_currency_id") + l10n_in_journal_item_ids = fields.One2many("account.move.line.wizard", "wizard_id", compute="_compute_journal_items", string="Journal Items") + move_id = fields.Many2one("account.move", string="Journal Entry", required=True) + + @api.depends("l10n_in_custom_duty_import_journal_id") + def _compute_l10n_in_journal_entry_number(self): + """Compute the next journal entry number based on the selected journal.""" + for wizard in self: + if wizard.l10n_in_custom_duty_import_journal_id: + wizard.l10n_in_journal_entry_number = wizard._get_next_sequence(wizard.l10n_in_custom_duty_import_journal_id) + + def _get_next_sequence(self, journal): + """Fetch the next sequence number for the provided journal.""" + last_move = self.env["account.move"].search([("journal_id", "=", journal.id)], order="name desc", limit=1) + last_sequence = last_move.name if last_move else None + + today = fields.Date.today() + fy_start = today.year if today.month >= 4 else today.year - 1 + fy_end = fy_start + 1 + fy_format = f"{str(fy_end)}/{today.month:02d}" + + if last_sequence and last_sequence.split("/")[2] == f"{today.month:02d}": + parts = last_sequence.split("/") + last_number = int(parts[-1]) + new_number = str(last_number + 1).zfill(4) + parts[-1] = new_number + return "/".join(parts) + else: + return f"{journal.code}/{fy_format}/0001" + + @api.depends("l10n_in_total_custom_duty", "l10n_in_total_l10n_in_tax_amount", "l10n_in_total_amount_payable") + def _compute_journal_items(self): + for wizard in self: + journal_items = [ + (0,0, + { + "account_id": wizard.env.company.l10n_in_account_custom_duty_income_id.id, + "label": "Custom Duty Account", + "debit": wizard.l10n_in_total_custom_duty, + "credit": 0.0, + }, + ), + (0,0, + { + "account_id": wizard.env.company.l10n_in_import_default_tax_account.id, + "label": "IGST on Import Account", + "debit": wizard.l10n_in_total_l10n_in_tax_amount, + "credit": 0.0, + }, + ), + (0,0, + { + "account_id": wizard.env.company.l10n_in_custom_duty_tax_payable_account_import.id, + "label": "Custom Duty Tax Payable Account", + "debit": 0.0, + "credit": wizard.l10n_in_total_amount_payable, + }, + ), + ] + wizard.l10n_in_journal_item_ids = journal_items + + @api.depends("l10n_in_move_line_ids.l10n_in_custom_duty_additional") + def _compute_l10n_in_total_custom_duty(self): + for wizard in self: + wizard.l10n_in_total_custom_duty = sum( + wizard.l10n_in_move_line_ids.mapped("l10n_in_custom_duty_additional") + ) + + @api.depends("l10n_in_move_line_ids.l10n_in_tax_amount") + def _compute_l10n_in_total_l10n_in_tax_amount(self): + for wizard in self: + wizard.l10n_in_total_l10n_in_tax_amount = sum( + wizard.l10n_in_move_line_ids.mapped("l10n_in_tax_amount") + ) + + @api.depends("l10n_in_move_line_ids.l10n_in_custom_duty_additional", "l10n_in_move_line_ids.l10n_in_tax_amount") + def _compute_l10n_in_total_amount_payable(self): + for wizard in self: + wizard.l10n_in_total_amount_payable = (wizard.l10n_in_total_custom_duty + wizard.l10n_in_total_l10n_in_tax_amount) + + @api.model + def default_get(self, fields_list): + defaults = super().default_get(fields_list) + company = self.env.company + defaults["l10n_in_custom_duty_import_journal_id"] = (company.l10n_in_custom_duty_import_journal_id.id) + defaults["l10n_in_account_custom_duty_income_id"] = (company.l10n_in_account_custom_duty_income_id.id) + defaults["l10n_in_import_default_tax_account"] = (company.l10n_in_import_default_tax_account.id) + defaults["l10n_in_custom_duty_tax_payable_account_import"] = (company.l10n_in_custom_duty_tax_payable_account_import.id) + + move_id = self._context.get("default_move_id") + if move_id: + move = self.env["account.move"].browse(move_id) + lines = [] + defaults["l10n_in_shipping_port_code_id"] = ( + move.l10n_in_shipping_port_code_id.id + ) + for line in move.invoice_line_ids: + lines.append( + (0,0, + { + "wizard_id": self.id, + "product_id": line.product_id.id, + "name": line.name, + "quantity": line.quantity, + "price_unit": line.price_unit, + "account_id": line.account_id.id, + "tax_ids": [(6, 0, line.tax_ids.ids)], + "l10n_in_assessable_value": line.l10n_in_assessable_value, + "l10n_in_custom_duty_additional": line.l10n_in_custom_duty_additional, + "l10n_in_taxable_amount": line.l10n_in_taxable_amount, + "l10n_in_tax_amount": line.l10n_in_tax_amount, + }, + ) + ) + + defaults["l10n_in_move_line_ids"] = lines + + return defaults + + def action_confirm(self): + move_vals = { + "date": self.l10n_in_journal_entry_date, + "journal_id": self.l10n_in_custom_duty_import_journal_id.id, + "l10n_in_custom_currency_rate": self.l10n_in_custom_currency_rate, + "l10n_in_total_custom_duty": self.l10n_in_total_custom_duty, + "l10n_in_total_l10n_in_tax_amount": self.l10n_in_total_l10n_in_tax_amount, + "l10n_in_total_amount_payable": self.l10n_in_total_amount_payable, + "l10n_in_shipping_bill_number": self.l10n_in_shipping_bill_number, + "l10n_in_shipping_bill_date": self.l10n_in_shipping_bill_date, + "l10n_in_shipping_port_code_id": self.l10n_in_shipping_port_code_id.id, + "line_ids": [] + } + + move = self.env["account.move"].create(move_vals) + + line_ids = [] + for line in self.l10n_in_journal_item_ids: + line_ids.append( + (0,0, + { + "account_id": line.account_id.id, + "name": line.label, + "debit": line.debit, + "credit": line.credit, + }, + ) + ) + move.write({"line_ids": line_ids}) + + bill_of_entry_line_ids = [] + for line in self.l10n_in_move_line_ids: + bill_of_entry_line_ids.append( + (0,0, + { + "account_id": line.account_id.id, + "name": line.label or "Bill Entry", + "product_id": line.product_id.id or False, + "quantity": line.quantity or 1.0, + "price_unit": line.price_unit * self.l10n_in_custom_currency_rate, + "tax_ids": [(6, 0, line.tax_ids.ids)] if line.tax_ids else [], + "l10n_in_assessable_value": line.l10n_in_assessable_value, + "l10n_in_custom_duty_additional": line.l10n_in_custom_duty_additional, + "l10n_in_taxable_amount": line.l10n_in_taxable_amount, + "l10n_in_tax_amount": line.l10n_in_tax_amount, + }) + ) + move.write({"bill_of_entry_line_ids": bill_of_entry_line_ids}) + + + self.move_id.write( + { + "l10n_in_journal_entry_number": self.l10n_in_journal_entry_number, + } + ) + move.action_post() + + self.move_id.is_confirmed = True + return {"type": "ir.actions.act_window_close"} diff --git a/accountant_custom_duty/wizard/account_bill_of_entry_wizard.xml b/accountant_custom_duty/wizard/account_bill_of_entry_wizard.xml new file mode 100644 index 00000000000..dc9d2c2a0a0 --- /dev/null +++ b/accountant_custom_duty/wizard/account_bill_of_entry_wizard.xml @@ -0,0 +1,69 @@ + + + + + account.bill.of.entry.wizard.form + account.bill.of.entry.wizard + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+ + + Custom Wizard + account.bill.of.entry.wizard + form + new + + +
diff --git a/accountant_custom_duty/wizard/account_move_line_wizard.py b/accountant_custom_duty/wizard/account_move_line_wizard.py new file mode 100644 index 00000000000..01ca793e912 --- /dev/null +++ b/accountant_custom_duty/wizard/account_move_line_wizard.py @@ -0,0 +1,44 @@ +from odoo import api, fields, models + + +class AccountMoveLineWizard(models.TransientModel): + _name = "account.move.line.wizard" + _description = "Account Move Line Wizard" + + wizard_id = fields.Many2one("account.bill.of.entry.wizard", string="Wizard", required=True, ondelete="cascade") + l10n_in_company_currency_id = fields.Many2one("res.currency", related="wizard_id.l10n_in_company_currency_id", string="Company Currency", readonly=True, default=lambda self: self.env.company.currency_id.id) + account_id = fields.Many2one("account.account", string="Account") + label = fields.Char(string="Label") + debit = fields.Monetary(string="Debit", currency_field="l10n_in_company_currency_id") + credit = fields.Monetary(string="Credit", currency_field="l10n_in_company_currency_id") + + product_id = fields.Many2one("product.product", string="Product") + name = fields.Char(string="Description") + quantity = fields.Float(string="Quantity") + price_unit = fields.Monetary(string="Unit Price", currency_field="l10n_in_company_currency_id") + tax_ids = fields.Many2many("account.tax", string="Taxes", domain=[("type_tax_use", "=", "purchase")]) + + l10n_in_custom_currency_rate = fields.Monetary(string="Custom Currency Rate", related="wizard_id.l10n_in_custom_currency_rate", currency_field="l10n_in_company_currency_id", readonly=True) + + l10n_in_assessable_value = fields.Monetary(string="Assessable Value", compute="_compute_l10n_in_assessable_value", store=True, currency_field="l10n_in_company_currency_id") + + l10n_in_custom_duty_additional = fields.Monetary(string="Custom Duty + Additional Charges", currency_field="l10n_in_company_currency_id", help="Enter any additional custom duty charges manually.") + + l10n_in_taxable_amount = fields.Monetary(string="Taxable Amount", compute="_compute_l10n_in_taxable_amount", store=True, currency_field="l10n_in_company_currency_id") + + l10n_in_tax_amount = fields.Monetary(string="Tax Amount", compute="_compute_l10n_in_tax_amount", store=True, currency_field="l10n_in_company_currency_id") + + @api.depends("quantity", "price_unit", "l10n_in_custom_currency_rate") + def _compute_l10n_in_assessable_value(self): + for line in self: + line.l10n_in_assessable_value = (line.quantity * line.price_unit * line.l10n_in_custom_currency_rate) + + @api.depends("l10n_in_assessable_value", "l10n_in_custom_duty_additional") + def _compute_l10n_in_taxable_amount(self): + for line in self: + line.l10n_in_taxable_amount = (line.l10n_in_assessable_value + line.l10n_in_custom_duty_additional) + + @api.depends("l10n_in_taxable_amount", "tax_ids") + def _compute_l10n_in_tax_amount(self): + for line in self: + line.l10n_in_tax_amount = sum((line.l10n_in_taxable_amount * tax.amount) / 100 for tax in line.tax_ids) From c83953deef8d929e3fa132a711960c88b4204d83 Mon Sep 17 00:00:00 2001 From: ptpu-odoo Date: Thu, 20 Mar 2025 15:46:22 +0530 Subject: [PATCH 2/2] [IMP] accountant: Added Test cases and made improvements With this Commit ================ - Added test cases to ensure functionality and reliability. - Improved the conditional display of the Bill of Entry button by validating the Import-Export setting from the configuration menu. - Refactored and optimized code for better maintainability and performance. --- accountant_custom_duty/__init__.py | 1 + accountant_custom_duty/models/account_move.py | 5 +- .../models/account_move_bill_of_entry_line.py | 2 +- accountant_custom_duty/models/res_company.py | 4 +- .../models/res_config_settings.py | 9 +- accountant_custom_duty/tests/__init__.py | 1 + .../tests/test_account_move.py | 83 +++++++++++++++++++ .../views/account_move_views_inherit.xml | 12 +-- .../views/res_config_settings_views.xml | 24 +++--- .../wizard/account_bill_of_entry_wizard.py | 13 ++- .../wizard/account_move_line_wizard.py | 2 +- 11 files changed, 121 insertions(+), 35 deletions(-) create mode 100644 accountant_custom_duty/tests/__init__.py create mode 100644 accountant_custom_duty/tests/test_account_move.py diff --git a/accountant_custom_duty/__init__.py b/accountant_custom_duty/__init__.py index 9b4296142f4..2c9b3790d63 100644 --- a/accountant_custom_duty/__init__.py +++ b/accountant_custom_duty/__init__.py @@ -1,2 +1,3 @@ from . import models from . import wizard +from . import tests diff --git a/accountant_custom_duty/models/account_move.py b/accountant_custom_duty/models/account_move.py index 96279d978f6..ac0ef6b8dbe 100644 --- a/accountant_custom_duty/models/account_move.py +++ b/accountant_custom_duty/models/account_move.py @@ -5,11 +5,12 @@ class AccountMove(models.Model): _inherit = "account.move" is_confirmed = fields.Boolean(string="Confirmed", default=False) + l10n_in_custom_duty = fields.Boolean(related="company_id.l10n_in_custom_duty", readonly=False) l10n_in_journal_entry_number = fields.Char(string="Journal Entry Number") l10n_in_company_currency_id = fields.Many2one("res.currency", string="Company Currency ID", default=lambda self: self.env.ref("base.INR")) l10n_in_custom_currency_rate = fields.Monetary(string="Custom Currency Rate", currency_field="l10n_in_company_currency_id") l10n_in_reference = fields.Char(string="Bill Number") - l10n_in_total_custom_duty = fields.Monetary(string="Total Custom Duty", currency_field="l10n_in_company_currency_id") + l10n_in_total_custom_duty = fields.Monetary(string="Total Custom Duty + Additional Charges", currency_field="l10n_in_company_currency_id") l10n_in_total_l10n_in_tax_amount = fields.Monetary(string="Total Tax Amount", currency_field="l10n_in_company_currency_id") l10n_in_total_amount_payable = fields.Monetary(string="Total Amount Payable", currency_field="l10n_in_company_currency_id") @@ -29,7 +30,7 @@ def action_open_wizard(self): "default_l10n_in_custom_duty_import_journal_id": self.env.company.l10n_in_custom_duty_import_journal_id.id, "default_l10n_in_account_custom_duty_income_id": self.env.company.l10n_in_account_custom_duty_income_id.id, "default_l10n_in_import_default_tax_account": self.env.company.l10n_in_import_default_tax_account.id, - "default_l10n_in_custom_duty_tax_payable_account_import": self.env.company.l10n_in_custom_duty_tax_payable_account_import.id, + "default_l10n_in_custom_duty_tax_payable_account": self.env.company.l10n_in_custom_duty_tax_payable_account.id, "default_l10n_in_shipping_bill_number": self.l10n_in_shipping_bill_number, "default_l10n_in_shipping_bill_date": self.l10n_in_shipping_bill_date, "default_l10n_in_shipping_port_code_id": self.l10n_in_shipping_port_code_id, diff --git a/accountant_custom_duty/models/account_move_bill_of_entry_line.py b/accountant_custom_duty/models/account_move_bill_of_entry_line.py index 4879eedec60..d82f04d6017 100644 --- a/accountant_custom_duty/models/account_move_bill_of_entry_line.py +++ b/accountant_custom_duty/models/account_move_bill_of_entry_line.py @@ -6,7 +6,7 @@ class AccountMoveBillOfEntryLine(models.Model): _description = "Bill of Entry Line" move_id = fields.Many2one("account.move", string="Journal Entry") - l10n_in_company_currency_id = fields.Many2one("res.currency", related="move_id.l10n_in_company_currency_id", string="Company Currency", readonly=True, default=lambda self: self.env.ref("base.INR")) + l10n_in_company_currency_id = fields.Many2one("res.currency", related="move_id.l10n_in_company_currency_id", string="Company Currency", readonly=True) account_id = fields.Many2one("account.account", string="Account") label = fields.Char(string="Label") debit = fields.Monetary(string="Debit", currency_field="l10n_in_company_currency_id") diff --git a/accountant_custom_duty/models/res_company.py b/accountant_custom_duty/models/res_company.py index cf673ee9ff8..5461092ec33 100644 --- a/accountant_custom_duty/models/res_company.py +++ b/accountant_custom_duty/models/res_company.py @@ -4,7 +4,7 @@ class ResCompany(models.Model): _inherit = "res.company" - l10n_in_custom_duty = fields.Boolean(string="Enable Custom Duty") + l10n_in_custom_duty = fields.Boolean(string="Enable Custom Duty", default=False) l10n_in_custom_duty_import_journal_id = fields.Many2one(comodel_name="account.journal",string="Import Journal",check_company=True) l10n_in_account_custom_duty_income_id = fields.Many2one(comodel_name="account.account",string="Import Custom Duty Income Account",check_company=True) @@ -14,4 +14,4 @@ class ResCompany(models.Model): l10n_in_account_custom_duty_expense_income_id = fields.Many2one(comodel_name="account.account",string="Export Custom Duty Expense/Income Account",check_company=True) l10n_in_export_default_tax_account = fields.Many2one(comodel_name="account.account",string="Export Journal Suspense Account",check_company=True) - l10n_in_custom_duty_tax_payable_account_import = fields.Many2one(comodel_name="account.account",string="Custom Duty Tax Payable Account",check_company=True) + l10n_in_custom_duty_tax_payable_account = fields.Many2one(comodel_name="account.account",string="Custom Duty Tax Payable Account",check_company=True) diff --git a/accountant_custom_duty/models/res_config_settings.py b/accountant_custom_duty/models/res_config_settings.py index 37f88b75bbe..3ab56d65d36 100644 --- a/accountant_custom_duty/models/res_config_settings.py +++ b/accountant_custom_duty/models/res_config_settings.py @@ -5,11 +5,11 @@ class ResConfigSettings(models.TransientModel): _inherit = "res.config.settings" l10n_in_custom_duty = fields.Boolean(related="company_id.l10n_in_custom_duty", readonly=False, help="Enable Custom Duty.") - l10n_in_custom_duty_tax_payable_account_import = fields.Many2one(comodel_name="account.account", related="company_id.l10n_in_custom_duty_tax_payable_account_import", readonly=False, help="Import and Export tax payable account.") + l10n_in_custom_duty_tax_payable_account = fields.Many2one(comodel_name="account.account", related="company_id.l10n_in_custom_duty_tax_payable_account", readonly=False, help="Import and Export tax payable account.") # Import l10n_in_custom_duty_import_journal_id = fields.Many2one(comodel_name="account.journal", related="company_id.l10n_in_custom_duty_import_journal_id", readonly=False, help="Journal used for import custom duty.") - l10n_in_account_custom_duty_income_id = fields.Many2one(comodel_name="account.account", related="company_id.l10n_in_account_custom_duty_income_id", readonly=False, domain="[('account_type', '=', 'income')]", help="Import custom duty account.") + l10n_in_account_custom_duty_income_id = fields.Many2one(comodel_name="account.account", related="company_id.l10n_in_account_custom_duty_income_id", readonly=False, domain="[('account_type', '=', 'expense')]", help="Import custom duty account.") l10n_in_import_default_tax_account = fields.Many2one(comodel_name="account.account", related="company_id.l10n_in_import_default_tax_account", readonly=False, domain="[('reconcile', '=', True), ('deprecated', '=', False), ('account_type', 'in', ('asset_current', 'liability_current'))]", help="Import account which is of type Current Asset or Liability.") # Export @@ -21,12 +21,13 @@ class ResConfigSettings(models.TransientModel): def get_values(self): res = super().get_values() company = self.env.company + res.update( l10n_in_custom_duty=company.l10n_in_custom_duty, l10n_in_custom_duty_import_journal_id=company.l10n_in_custom_duty_import_journal_id.id, l10n_in_account_custom_duty_income_id=company.l10n_in_account_custom_duty_income_id.id, l10n_in_import_default_tax_account=company.l10n_in_import_default_tax_account.id, - l10n_in_custom_duty_tax_payable_account_import=company.l10n_in_custom_duty_tax_payable_account_import.id, + l10n_in_custom_duty_tax_payable_account=company.l10n_in_custom_duty_tax_payable_account.id, l10n_in_custom_duty_export_journal_id=company.l10n_in_custom_duty_export_journal_id.id, l10n_in_account_custom_duty_expense_income_id=company.l10n_in_account_custom_duty_expense_income_id.id, l10n_in_export_default_tax_account=company.l10n_in_export_default_tax_account.id, @@ -43,7 +44,7 @@ def set_values(self): "l10n_in_custom_duty_import_journal_id": self.l10n_in_custom_duty_import_journal_id.id, "l10n_in_account_custom_duty_income_id": self.l10n_in_account_custom_duty_income_id.id, "l10n_in_import_default_tax_account": self.l10n_in_import_default_tax_account.id, - "l10n_in_custom_duty_tax_payable_account_import": self.l10n_in_custom_duty_tax_payable_account_import.id, + "l10n_in_custom_duty_tax_payable_account": self.l10n_in_custom_duty_tax_payable_account.id, "l10n_in_custom_duty_export_journal_id": self.l10n_in_custom_duty_export_journal_id.id, "l10n_in_account_custom_duty_expense_income_id": self.l10n_in_account_custom_duty_expense_income_id.id, "l10n_in_export_default_tax_account": self.l10n_in_export_default_tax_account.id, diff --git a/accountant_custom_duty/tests/__init__.py b/accountant_custom_duty/tests/__init__.py new file mode 100644 index 00000000000..f15da2000e3 --- /dev/null +++ b/accountant_custom_duty/tests/__init__.py @@ -0,0 +1 @@ +from . import test_account_move diff --git a/accountant_custom_duty/tests/test_account_move.py b/accountant_custom_duty/tests/test_account_move.py new file mode 100644 index 00000000000..dbe6f177043 --- /dev/null +++ b/accountant_custom_duty/tests/test_account_move.py @@ -0,0 +1,83 @@ +from odoo import fields +from odoo.tests import tagged, TransactionCase + + +@tagged('post_install', '-at_install') +class TestAccountMove(TransactionCase): + def setUp(self): + super().setUp() + self.partner_overseas = self.env['res.partner'].create({ + 'name': 'Overseas Supplier', + 'l10n_in_gst_treatment': 'overseas', + }) + + self.journal = self.env['account.journal'].search([('type', '=', 'purchase')], limit=1) + self.expense_account = self.env['account.account'].search([('account_type', '=', 'expense')], limit=1) + + self.bill = self.env['account.move'].create({ + 'move_type': 'in_invoice', + 'partner_id': self.partner_overseas.id, + 'invoice_date': fields.Date.today(), + 'journal_id': self.journal.id, + 'state': 'draft', + 'line_ids': [(0, 0, { + 'name': 'Test Product', + 'quantity': 1, + 'price_unit': 1000.0, + 'account_id': self.expense_account.id, + })] + }) + self.bill.action_post() + + def test_action_open_wizard(self): + """Test that the Bill of Entry Wizard opens with correct context.""" + action = self.bill.action_open_wizard() + self.assertEqual(action["res_model"], "account.bill.of.entry.wizard") + self.assertEqual(action["view_mode"], "form") + self.assertEqual(action["target"], "new") + self.assertEqual(action["context"]["default_move_id"], self.bill.id) + self.assertEqual(action["context"]["default_l10n_in_reference"], self.bill.name) + + def test_wizard_computations(self): + """Test computed fields in Account Move Line Wizard.""" + self.wizard = self.env['account.bill.of.entry.wizard'].create({ + 'move_id': self.bill.id, + 'l10n_in_custom_currency_rate': 1.2, + }) + + self.line = self.env['account.move.line.wizard'].create({ + 'wizard_id': self.wizard.id, + 'quantity': 10, + 'price_unit': 200, + 'l10n_in_custom_duty_additional': 50, + 'tax_ids': [(6, 0, self.env['account.tax'].search([('type_tax_use', '=', 'purchase')], limit=1).ids)], + }) + + self.line._compute_l10n_in_assessable_value() + self.line._compute_l10n_in_taxable_amount() + self.line._compute_l10n_in_tax_amount() + + expected_assessable_value = 10 * 200 * 1.2 + expected_taxable_amount = expected_assessable_value + 50 + expected_tax_amount = sum((expected_taxable_amount * tax.amount) / 100 for tax in self.line.tax_ids) + + self.assertEqual(self.line.l10n_in_assessable_value, expected_assessable_value, "Assessable Value computation is incorrect.") + self.assertEqual(self.line.l10n_in_taxable_amount, expected_taxable_amount, "Taxable Amount computation is incorrect.") + self.assertEqual(self.line.l10n_in_tax_amount, expected_tax_amount, "Total Tax Amount computation is incorrect.") + + def test_journal_entry_creation(self): + """Test that confirming the wizard creates a journal entry.""" + self.wizard = self.env['account.bill.of.entry.wizard'].create({ + 'move_id': self.bill.id, + 'l10n_in_custom_currency_rate': 1.2, + }) + + self.wizard.action_confirm() + journal_entry = self.env['account.move'].search([ + ('name', '=', self.bill.name), + ('journal_id', '=', self.journal.id), + ('state', '=', 'posted') + ], limit=1) + + self.assertTrue(journal_entry, "Journal entry was not created.") + self.assertEqual(journal_entry.state, 'posted', "Journal entry is not posted.") diff --git a/accountant_custom_duty/views/account_move_views_inherit.xml b/accountant_custom_duty/views/account_move_views_inherit.xml index c45f8440c9d..90dcf371b23 100644 --- a/accountant_custom_duty/views/account_move_views_inherit.xml +++ b/accountant_custom_duty/views/account_move_views_inherit.xml @@ -7,7 +7,7 @@ -