details: https://code.tryton.org/tryton/commit/cedac0e38ccc
branch: default
user: Cédric Krier <[email protected]>
date: Sat Feb 01 01:52:03 2025 +0100
description:
Add import/export agent for customs
diffstat:
modules/customs/CHANGELOG | 1 +
modules/customs/customs.py | 81 +++++++-
modules/customs/customs.xml | 114
++++++++++
modules/customs/doc/design.rst | 34 ++
modules/customs/tests/test_module.py | 53 ++++-
modules/customs/tryton.cfg | 4 +
modules/customs/view/customs_agent_form.xml | 14 +
modules/customs/view/customs_agent_list.xml | 8 +
modules/customs/view/customs_agent_selection_form.xml | 21 +
modules/customs/view/customs_agent_selection_list.xml | 9 +
modules/customs/view/customs_agent_selection_list_sequence.xml | 9 +
11 files changed, 346 insertions(+), 2 deletions(-)
diffs (448 lines):
diff -r 7e5d3e3bef70 -r cedac0e38ccc modules/customs/CHANGELOG
--- a/modules/customs/CHANGELOG Thu Feb 13 23:47:46 2025 +0100
+++ b/modules/customs/CHANGELOG Sat Feb 01 01:52:03 2025 +0100
@@ -1,3 +1,4 @@
+* Add import/export agent
Version 7.6.0 - 2025-04-28
--------------------------
diff -r 7e5d3e3bef70 -r cedac0e38ccc modules/customs/customs.py
--- a/modules/customs/customs.py Thu Feb 13 23:47:46 2025 +0100
+++ b/modules/customs/customs.py Sat Feb 01 01:52:03 2025 +0100
@@ -7,7 +7,8 @@
from trytond import backend
from trytond.model import (
- DeactivableMixin, MatchMixin, ModelSQL, ModelView, fields)
+ DeactivableMixin, MatchMixin, ModelSQL, ModelView, fields,
+ sequence_ordered)
from trytond.modules.product import price_digits
from trytond.pool import Pool
from trytond.pyson import Bool, Eval, If
@@ -229,3 +230,81 @@
amount = Uom.compute_price(self.uom, self.amount, uom)
amount *= Decimal(str(quantity))
return Currency.compute(self.currency, amount, currency)
+
+
+class Agent(DeactivableMixin, ModelSQL, ModelView):
+ __name__ = 'customs.agent'
+
+ party = fields.Many2One(
+ 'party.party', "Party", required=True, ondelete='CASCADE',
+ help="The party which represents the import/export agent.")
+ address = fields.Many2One(
+ 'party.address', "Address", required=True, ondelete='RESTRICT',
+ domain=[
+ ('party', '=', Eval('party', -1)),
+ ],
+ help="The address of the agent.")
+ tax_identifier = fields.Many2One(
+ 'party.identifier', "Tax Identifier",
+ required=True, ondelete='RESTRICT',
+ help="The identifier of the agent for tax report.")
+
+ @classmethod
+ def __setup__(cls):
+ pool = Pool()
+ Party = pool.get('party.party')
+ super().__setup__()
+ tax_identifier_types = Party.tax_identifier_types()
+ cls.tax_identifier.domain = [
+ ('party', '=', Eval('party', -1)),
+ ('type', 'in', tax_identifier_types),
+ ]
+
+ @fields.depends('party', 'address', 'tax_identifier')
+ def on_change_party(self):
+ if self.party:
+ if not self.address or self.address.party != self.party:
+ self.address = self.party.address_get()
+ if (not self.tax_identifier
+ or self.tax_identifier.party != self.party):
+ self.tax_identifier = self.party.tax_identifier
+ else:
+ self.address = self.tax_identifier = None
+
+ def get_rec_name(self, name):
+ return self.party.rec_name
+
+ @classmethod
+ def search_rec_name(cls, name, clause):
+ return [('party.rec_name', *clause[1:])]
+
+
+class AgentSelection(
+ DeactivableMixin, sequence_ordered(), MatchMixin, ModelSQL, ModelView):
+ __name__ = 'customs.agent.selection'
+
+ company = fields.Many2One(
+ 'company.company', "Company", required=True)
+ from_country = fields.Many2One(
+ 'country.country', "From Country", ondelete='RESTRICT',
+ help="Apply only when shipping from this country.\n"
+ "Leave empty for any countries.")
+ to_country = fields.Many2One(
+ 'country.country', "To Country", ondelete='RESTRICT',
+ help="Apply only when shipping to this country.\n"
+ "Leave empty for any countries.")
+ agent = fields.Many2One(
+ 'customs.agent', "Agent", required=True, ondelete='CASCADE',
+ help="The selected agent.")
+
+ @classmethod
+ def default_company(cls):
+ return Transaction().context.get('company')
+
+ @classmethod
+ def get_agent(cls, company, pattern):
+ for selection in cls.search([
+ ('company', '=', company),
+ ]):
+ if selection.match(pattern):
+ return selection.agent
diff -r 7e5d3e3bef70 -r cedac0e38ccc modules/customs/customs.xml
--- a/modules/customs/customs.xml Thu Feb 13 23:47:46 2025 +0100
+++ b/modules/customs/customs.xml Sat Feb 01 01:52:03 2025 +0100
@@ -135,5 +135,119 @@
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
+
+ <record model="ir.ui.view" id="customs_agent_view_form">
+ <field name="model">customs.agent</field>
+ <field name="type">form</field>
+ <field name="name">customs_agent_form</field>
+ </record>
+
+ <record model="ir.ui.view" id="customs_agent_view_list">
+ <field name="model">customs.agent</field>
+ <field name="type">tree</field>
+ <field name="name">customs_agent_list</field>
+ </record>
+
+ <record model="ir.action.act_window" id="act_customs_agent_form">
+ <field name="name">Agents</field>
+ <field name="res_model">customs.agent</field>
+ </record>
+ <record model="ir.action.act_window.view"
id="act_customs_agent_form_view1">
+ <field name="sequence" eval="10"/>
+ <field name="view" ref="customs_agent_view_list"/>
+ <field name="act_window" ref="act_customs_agent_form"/>
+ </record>
+ <record model="ir.action.act_window.view"
id="act_customs_agent_form_view2">
+ <field name="sequence" eval="20"/>
+ <field name="view" ref="customs_agent_view_form"/>
+ <field name="act_window" ref="act_customs_agent_form"/>
+ </record>
+ <menuitem
+ parent="menu_customs"
+ action="act_customs_agent_form"
+ sequence="20"
+ id="menu_customs_agent_form"/>
+
+ <record model="ir.model.access" id="access_customs_agent">
+ <field name="model">customs.agent</field>
+ <field name="perm_read" eval="True"/>
+ <field name="perm_write" eval="False"/>
+ <field name="perm_create" eval="False"/>
+ <field name="perm_delete" eval="False"/>
+ </record>
+ <record model="ir.model.access" id="access_customs_agent_admin">
+ <field name="model">customs.agent</field>
+ <field name="group" ref="group_customs_admin"/>
+ <field name="perm_read" eval="True"/>
+ <field name="perm_write" eval="True"/>
+ <field name="perm_create" eval="True"/>
+ <field name="perm_delete" eval="True"/>
+ </record>
+
+ <record model="ir.ui.view" id="customs_agent_selection_view_form">
+ <field name="model">customs.agent.selection</field>
+ <field name="type">form</field>
+ <field name="name">customs_agent_selection_form</field>
+ </record>
+
+ <record model="ir.ui.view" id="customs_agent_selection_view_list">
+ <field name="model">customs.agent.selection</field>
+ <field name="type">tree</field>
+ <field name="priority" eval="10"/>
+ <field name="name">customs_agent_selection_list</field>
+ </record>
+
+ <record model="ir.ui.view"
id="customs_agent_selection_view_list_sequence">
+ <field name="model">customs.agent.selection</field>
+ <field name="type">tree</field>
+ <field name="priority" eval="20"/>
+ <field name="name">customs_agent_selection_list_sequence</field>
+ </record>
+
+ <record model="ir.action.act_window"
id="act_customs_agent_selection_form">
+ <field name="name">Selection</field>
+ <field name="res_model">customs.agent.selection</field>
+ </record>
+ <record model="ir.action.act_window.view"
id="act_customs_agent_selection_form_view1">
+ <field name="sequence" eval="10"/>
+ <field name="view"
ref="customs_agent_selection_view_list_sequence"/>
+ <field name="act_window" ref="act_customs_agent_selection_form"/>
+ </record>
+ <record model="ir.action.act_window.view"
id="act_customs_agent_selection_form_view2">
+ <field name="sequence" eval="20"/>
+ <field name="view" ref="customs_agent_selection_view_form"/>
+ <field name="act_window" ref="act_customs_agent_selection_form"/>
+ </record>
+ <menuitem
+ parent="menu_customs_agent_form"
+ action="act_customs_agent_selection_form"
+ sequence="10"
+ id="menu_customs_agent_selection_form"/>
+
+ <record model="ir.model.access" id="access_customs_agent_selection">
+ <field name="model">customs.agent.selection</field>
+ <field name="perm_read" eval="True"/>
+ <field name="perm_write" eval="False"/>
+ <field name="perm_create" eval="False"/>
+ <field name="perm_delete" eval="False"/>
+ </record>
+ <record model="ir.model.access"
id="access_customs_agent_selection_admin">
+ <field name="model">customs.agent.selection</field>
+ <field name="group" ref="group_customs_admin"/>
+ <field name="perm_read" eval="True"/>
+ <field name="perm_write" eval="True"/>
+ <field name="perm_create" eval="True"/>
+ <field name="perm_delete" eval="True"/>
+ </record>
+
+ <record model="ir.rule.group"
id="rule_group_customs_agent_selection_companies">
+ <field name="name">User in companies</field>
+ <field name="model">customs.agent.selection</field>
+ <field name="global_p" eval="True"/>
+ </record>
+ <record model="ir.rule" id="rule_customs_agent_selection_companies">
+ <field name="domain" eval="[('company', 'in', Eval('companies',
[]))]" pyson="1"/>
+ <field name="rule_group"
ref="rule_group_customs_agent_selection_companies"/>
+ </record>
</data>
</tryton>
diff -r 7e5d3e3bef70 -r cedac0e38ccc modules/customs/doc/design.rst
--- a/modules/customs/doc/design.rst Thu Feb 13 23:47:46 2025 +0100
+++ b/modules/customs/doc/design.rst Sat Feb 01 01:52:03 2025 +0100
@@ -44,6 +44,40 @@
.. |Customs --> Duty Rates| replace:: :menuselection:`Customs --> Duty
Rates`
__ https://demo.tryton.org/model/customs.duty.rate
+.. _model-customs.agent:
+
+Agent
+=====
+
+The *Agent* defines a `Party <party:model-party.party>` as a import or export
+agent.
+
+.. seealso::
+
+ The *Agents* can be found by opening the main menu item:
+
+ |Customs --> Agents|__
+
+ .. |Customs --> Agents| replace:: :menuselection:`Customs --> Agents`
+ __ https://demo.tryton.org/model/customs.agent
+
+.. _model-customs.agent.selection:
+
+Agent Selection
+===============
+
+The *Agent Selection* defines which agent to use based on criteria such as the
+origin and destination `Countries <country:model-country.country>`.
+
+.. seealso::
+
+ The *Agent Selections* can be found by opening the main menu item:
+
+ |Customs --> Agents --> Selection|__
+
+ .. |Customs --> Agents --> Selection| replace:: :menuselection:`Customs -->
Agents --> Selection`
+ __ https://demo.tryton.org/model/customs.agent.selection
+
.. _concept-product:
Product
diff -r 7e5d3e3bef70 -r cedac0e38ccc modules/customs/tests/test_module.py
--- a/modules/customs/tests/test_module.py Thu Feb 13 23:47:46 2025 +0100
+++ b/modules/customs/tests/test_module.py Sat Feb 01 01:52:03 2025 +0100
@@ -4,11 +4,13 @@
from datetime import date
from decimal import Decimal
+from trytond.modules.company.tests import CompanyTestMixin, create_company
from trytond.pool import Pool
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
+from trytond.transaction import Transaction
-class CustomsTestCase(ModuleTestCase):
+class CustomsTestCase(CompanyTestMixin, ModuleTestCase):
'Test Customs module'
module = 'customs'
@@ -260,5 +262,54 @@
('id', '=', product_tariff_code.id),
]), [])
+ @with_transaction()
+ def test_agent_selection(self):
+ "Test agent selection"
+ pool = Pool()
+ Party = pool.get('party.party')
+ Identifier = pool.get('party.identifier')
+ Agent = pool.get('customs.agent')
+ Country = pool.get('country.country')
+ Selection = pool.get('customs.agent.selection')
+
+ party = Party(name="Agent", addresses=[{}])
+ party.save()
+ address, = party.addresses
+ tax_identifier = Identifier(
+ party=party, type='be_vat', code="BE403019261")
+ tax_identifier.save()
+ agent1 = Agent(
+ party=party,
+ address=address,
+ tax_identifier=tax_identifier)
+ agent1.save()
+ agent2 = Agent(
+ party=party,
+ address=address,
+ tax_identifier=tax_identifier)
+ agent2.save()
+
+ country1 = Country(name="Country 1")
+ country1.save()
+ country2 = Country(name="Country 2")
+ country2.save()
+
+ company = create_company()
+
+ with Transaction().set_context(company=company.id):
+ Selection.create([{
+ 'to_country': country1,
+ 'agent': agent1,
+ }, {
+ 'agent': agent2,
+ }])
+
+ self.assertEqual(Selection.get_agent(company, {
+ 'to_country': country1.id,
+ }), agent1)
+ self.assertEqual(Selection.get_agent(company, {
+ 'to_country': country2.id,
+ }), agent2)
+
del ModuleTestCase
diff -r 7e5d3e3bef70 -r cedac0e38ccc modules/customs/tryton.cfg
--- a/modules/customs/tryton.cfg Thu Feb 13 23:47:46 2025 +0100
+++ b/modules/customs/tryton.cfg Sat Feb 01 01:52:03 2025 +0100
@@ -1,9 +1,11 @@
[tryton]
version=7.7.0
depends:
+ company
country
currency
ir
+ party
product
res
xml:
@@ -14,6 +16,8 @@
model:
customs.TariffCode
customs.DutyRate
+ customs.Agent
+ customs.AgentSelection
product.Category
product.Template
product.Product_TariffCode
diff -r 7e5d3e3bef70 -r cedac0e38ccc modules/customs/view/customs_agent_form.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/customs/view/customs_agent_form.xml Sat Feb 01 01:52:03
2025 +0100
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<form>
+ <label name="party"/>
+ <field name="party"/>
+ <label name="active"/>
+ <field name="active"/>
+
+ <label name="address"/>
+ <field name="address"/>
+ <label name="tax_identifier"/>
+ <field name="tax_identifier"/>
+</form>
diff -r 7e5d3e3bef70 -r cedac0e38ccc modules/customs/view/customs_agent_list.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/customs/view/customs_agent_list.xml Sat Feb 01 01:52:03
2025 +0100
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<tree>
+ <field name="party" expand="2"/>
+ <field name="address" expand="1" optional="0"/>
+ <field name="tax_identifier" expand="1" optional="1"/>
+</tree>
diff -r 7e5d3e3bef70 -r cedac0e38ccc
modules/customs/view/customs_agent_selection_form.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/customs/view/customs_agent_selection_form.xml Sat Feb 01
01:52:03 2025 +0100
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<form cursor="agent">
+ <label name="company"/>
+ <field name="company"/>
+ <label name="active"/>
+ <field name="active"/>
+
+ <label name="agent"/>
+ <field name="agent"/>
+ <label name="sequence"/>
+ <field name="sequence"/>
+
+ <separator string="Criteria" id="criteria" colspan="4"/>
+
+ <label name="from_country"/>
+ <field name="from_country"/>
+ <label name="to_country"/>
+ <field name="to_country"/>
+</form>
diff -r 7e5d3e3bef70 -r cedac0e38ccc
modules/customs/view/customs_agent_selection_list.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/customs/view/customs_agent_selection_list.xml Sat Feb 01
01:52:03 2025 +0100
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<tree>
+ <field name="company" expand="1" optional="1"/>
+ <field name="from_country" expand="1" optional="0"/>
+ <field name="to_country" expand="1"/>
+ <field name="agent" expand="2"/>
+</tree>
diff -r 7e5d3e3bef70 -r cedac0e38ccc
modules/customs/view/customs_agent_selection_list_sequence.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/customs/view/customs_agent_selection_list_sequence.xml Sat Feb
01 01:52:03 2025 +0100
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<tree editable="1" sequence="sequence">
+ <field name="company" expand="1" optional="1"/>
+ <field name="from_country" expand="1" optional="0"/>
+ <field name="to_country" expand="1"/>
+ <field name="agent" expand="2"/>
+</tree>