first commit
This commit is contained in:
310
modules/customs/customs.py
Normal file
310
modules/customs/customs.py
Normal file
@@ -0,0 +1,310 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from sql import Null
|
||||
|
||||
from trytond import backend
|
||||
from trytond.model import (
|
||||
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
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
|
||||
class CountryMatchMixin(MatchMixin):
|
||||
country = fields.Many2One(
|
||||
'country.country', "Country",
|
||||
states={
|
||||
'invisible': Bool(Eval('organization')),
|
||||
})
|
||||
organization = fields.Many2One(
|
||||
'country.organization', "Organization",
|
||||
states={
|
||||
'invisible': Bool(Eval('country')),
|
||||
})
|
||||
|
||||
def match(self, pattern, match_none=False):
|
||||
if 'country' in pattern:
|
||||
pattern = pattern.copy()
|
||||
country = pattern.pop('country')
|
||||
if country is not None or match_none:
|
||||
if self.country and self.country.id != country:
|
||||
return False
|
||||
if (self.organization
|
||||
and country not in [
|
||||
c.id for c in self.organization.countries]):
|
||||
return False
|
||||
return super().match(pattern, match_none=match_none)
|
||||
|
||||
|
||||
class TariffCode(DeactivableMixin, CountryMatchMixin, ModelSQL, ModelView):
|
||||
__name__ = 'customs.tariff.code'
|
||||
_rec_name = 'code'
|
||||
code = fields.Char('Code', required=True,
|
||||
help='The code from Harmonized System of Nomenclature.')
|
||||
description = fields.Char('Description', translate=True)
|
||||
start_month = fields.Many2One('ir.calendar.month', "Start Month",
|
||||
states={
|
||||
'required': Eval('end_month') | Eval('start_day'),
|
||||
})
|
||||
start_day = fields.Integer('Start Day',
|
||||
domain=['OR',
|
||||
('start_day', '=', None),
|
||||
[('start_day', '>=', 1), ('start_day', '<=', 31)],
|
||||
],
|
||||
states={
|
||||
'required': Bool(Eval('start_month')),
|
||||
})
|
||||
end_month = fields.Many2One('ir.calendar.month', "End Month",
|
||||
states={
|
||||
'required': Eval('start_month') | Eval('end_day'),
|
||||
})
|
||||
end_day = fields.Integer('End Day',
|
||||
domain=['OR',
|
||||
('end_day', '=', None),
|
||||
[('end_day', '>=', 1), ('end_day', '<=', 31)],
|
||||
],
|
||||
states={
|
||||
'required': Bool(Eval('end_month')),
|
||||
})
|
||||
duty_rates = fields.One2Many('customs.duty.rate', 'tariff_code',
|
||||
'Duty Rates')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls._order.insert(0, ('code', 'ASC'))
|
||||
|
||||
@classmethod
|
||||
def __register__(cls, module_name):
|
||||
transaction = Transaction()
|
||||
cursor = transaction.connection.cursor()
|
||||
pool = Pool()
|
||||
Month = pool.get('ir.calendar.month')
|
||||
sql_table = cls.__table__()
|
||||
month = Month.__table__()
|
||||
table_h = cls.__table_handler__(module_name)
|
||||
|
||||
# Migration from 6.6: use ir.calendar
|
||||
migrate_calendar = False
|
||||
if (backend.TableHandler.table_exist(cls._table)
|
||||
and table_h.column_exist('start_month')
|
||||
and table_h.column_exist('end_month')):
|
||||
migrate_calendar = (
|
||||
table_h.column_is_type('start_month', 'VARCHAR')
|
||||
or table_h.column_is_type('end_month', 'VARCHAR'))
|
||||
if migrate_calendar:
|
||||
table_h.column_rename('start_month', '_temp_start_month')
|
||||
table_h.column_rename('end_month', '_temp_end_month')
|
||||
|
||||
super().__register__(module_name)
|
||||
|
||||
table_h = cls.__table_handler__(module_name)
|
||||
|
||||
# Migration from 6.6: use ir.calendar
|
||||
if migrate_calendar:
|
||||
update = transaction.connection.cursor()
|
||||
cursor.execute(*month.select(month.id, month.index))
|
||||
for month_id, index in cursor:
|
||||
str_index = f'{index:02d}'
|
||||
update.execute(*sql_table.update(
|
||||
[sql_table.start_month], [month_id],
|
||||
where=sql_table._temp_start_month == str_index))
|
||||
update.execute(*sql_table.update(
|
||||
[sql_table.end_month], [month_id],
|
||||
where=sql_table._temp_end_month == str_index))
|
||||
table_h.drop_column('_temp_start_month')
|
||||
table_h.drop_column('_temp_end_month')
|
||||
|
||||
def match(self, pattern, match_none=False):
|
||||
if 'date' in pattern:
|
||||
pattern = pattern.copy()
|
||||
date = pattern.pop('date')
|
||||
if self.start_month and self.end_month:
|
||||
start = (self.start_month.index, self.start_day)
|
||||
end = (self.end_month.index, self.end_day)
|
||||
date = (date.month, date.day)
|
||||
if start <= end:
|
||||
if not (start <= date <= end):
|
||||
return False
|
||||
else:
|
||||
if end <= date <= start:
|
||||
return False
|
||||
return super().match(pattern, match_none=match_none)
|
||||
|
||||
def get_duty_rate(self, pattern):
|
||||
for rate in self.duty_rates:
|
||||
if rate.match(pattern):
|
||||
return rate
|
||||
|
||||
|
||||
class DutyRate(CountryMatchMixin, ModelSQL, ModelView):
|
||||
__name__ = 'customs.duty.rate'
|
||||
tariff_code = fields.Many2One(
|
||||
'customs.tariff.code', "Tariff Code", required=True)
|
||||
type = fields.Selection([
|
||||
('import', 'Import'),
|
||||
('export', 'Export'),
|
||||
], 'Type')
|
||||
start_date = fields.Date('Start Date',
|
||||
domain=['OR',
|
||||
('start_date', '<=', If(Bool(Eval('end_date')),
|
||||
Eval('end_date', datetime.date.max), datetime.date.max)),
|
||||
('start_date', '=', None),
|
||||
])
|
||||
end_date = fields.Date('End Date',
|
||||
domain=['OR',
|
||||
('end_date', '>=', If(Bool(Eval('start_date')),
|
||||
Eval('start_date', datetime.date.min), datetime.date.min)),
|
||||
('end_date', '=', None),
|
||||
])
|
||||
computation_type = fields.Selection([
|
||||
('amount', 'Amount'),
|
||||
('quantity', 'Quantity'),
|
||||
], 'Computation Type')
|
||||
amount = fields.Numeric('Amount', digits=price_digits,
|
||||
states={
|
||||
'required': Eval('computation_type').in_(['amount', 'quantity']),
|
||||
'invisible': ~Eval('computation_type').in_(['amount', 'quantity']),
|
||||
})
|
||||
currency = fields.Many2One('currency.currency', 'Currency',
|
||||
states={
|
||||
'required': Eval('computation_type').in_(['amount', 'quantity']),
|
||||
'invisible': ~Eval('computation_type').in_(['amount', 'quantity']),
|
||||
})
|
||||
uom = fields.Many2One(
|
||||
'product.uom', "UoM",
|
||||
states={
|
||||
'required': Eval('computation_type') == 'quantity',
|
||||
'invisible': Eval('computation_type') != 'quantity',
|
||||
},
|
||||
help="The Unit of Measure.")
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls._order.insert(0, ('start_date', 'ASC'))
|
||||
cls._order.insert(0, ('end_date', 'ASC'))
|
||||
|
||||
@classmethod
|
||||
def default_type(cls):
|
||||
return 'import'
|
||||
|
||||
@staticmethod
|
||||
def order_start_date(tables):
|
||||
table, _ = tables[None]
|
||||
return [table.start_date == Null, table.start_date]
|
||||
|
||||
@staticmethod
|
||||
def order_end_date(tables):
|
||||
table, _ = tables[None]
|
||||
return [table.end_date == Null, table.end_date]
|
||||
|
||||
def match(self, pattern):
|
||||
if 'date' in pattern:
|
||||
pattern = pattern.copy()
|
||||
start = self.start_date or datetime.date.min
|
||||
end = self.end_date or datetime.date.max
|
||||
if not (start <= pattern.pop('date') <= end):
|
||||
return False
|
||||
return super().match(pattern)
|
||||
|
||||
def compute(self, currency, quantity, uom, **kwargs):
|
||||
return getattr(self, 'compute_%s' % self.computation_type)(
|
||||
currency, quantity, uom, **kwargs)
|
||||
|
||||
def compute_amount(self, currency, quantity, uom, **kwargs):
|
||||
pool = Pool()
|
||||
Currency = pool.get('currency.currency')
|
||||
return Currency.compute(self.currency, self.amount, currency)
|
||||
|
||||
def compute_quantity(self, currency, quantity, uom, **kwargs):
|
||||
pool = Pool()
|
||||
Currency = pool.get('currency.currency')
|
||||
Uom = pool.get('product.uom')
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user