313 lines
11 KiB
Python
313 lines
11 KiB
Python
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
# this repository contains the full copyright notices and license terms.
|
|
|
|
from trytond.model import ModelSQL, ModelView, Unique, fields
|
|
from trytond.pool import Pool, PoolMeta
|
|
from trytond.pyson import Eval, If
|
|
from trytond.transaction import Transaction
|
|
|
|
from .party import EXCISE_IDENTIFIER_TYPES, IDENTIFIER_TYPES
|
|
|
|
|
|
class Location(metaclass=PoolMeta):
|
|
__name__ = 'stock.location'
|
|
|
|
eu_excise_numbers = fields.One2Many(
|
|
'stock.location.eu_excise_number',
|
|
'warehouse', "Excise Number",
|
|
states={
|
|
'invisible': Eval('type') != 'warehouse',
|
|
})
|
|
|
|
def get_eu_excise_number(self, company):
|
|
for excise_number in self.eu_excise_numbers:
|
|
if excise_number.company == company:
|
|
return excise_number.eu_excise_number
|
|
|
|
|
|
class WarehouseEUExciseNumber(ModelSQL, ModelView):
|
|
__name__ = 'stock.location.eu_excise_number'
|
|
|
|
company = fields.Many2One(
|
|
'company.company', "Company", required=True, ondelete='CASCADE')
|
|
warehouse = fields.Many2One(
|
|
'stock.location', "Warehouse", required=True, ondelete='CASCADE',
|
|
domain=[
|
|
('type', '=', 'warehouse'),
|
|
],
|
|
context={
|
|
'company': Eval('company', -1),
|
|
},
|
|
depends=['company'])
|
|
eu_excise_number = fields.Many2One(
|
|
'party.identifier', "Excise Number", required=True,
|
|
domain=[
|
|
('type', 'in', EXCISE_IDENTIFIER_TYPES),
|
|
('party', '=', Eval('company_party', -1)),
|
|
('address', '=', Eval('warehouse_address', -1)),
|
|
])
|
|
company_party = fields.Function(
|
|
fields.Many2One(
|
|
'party.party', "Company Party",
|
|
context={
|
|
'company': Eval('company', -1),
|
|
},
|
|
depends={'company'}),
|
|
'on_change_with_company_party')
|
|
warehouse_address = fields.Function(
|
|
fields.Many2One('party.address', "Warehouse Address"),
|
|
'on_change_with_warehouse_address')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
t = cls.__table__()
|
|
cls.__access__.add('warehouse')
|
|
cls._sql_constraints += [
|
|
('warehouse_company_unique',
|
|
Unique(t, t.warehouse, t.company),
|
|
'account_stock_eu_excise.msg_warehouse_excise_company_unique'),
|
|
]
|
|
|
|
@classmethod
|
|
def default_company(cls):
|
|
return Transaction().context.get('company')
|
|
|
|
@fields.depends('company')
|
|
def on_change_with_company_party(self, name=None):
|
|
return self.company.party if self.company else None
|
|
|
|
@fields.depends('warehouse', '_parent_warehouse.address')
|
|
def on_change_with_warehouse_address(self, name=None):
|
|
return self.warehouse.address if self.warehouse else None
|
|
|
|
|
|
class ShipmentEUExciseMixin:
|
|
__slots__ = ()
|
|
|
|
warehouse_eu_excise_number = fields.Function(
|
|
fields.Many2One('party.identifier', "Warehouse Excise Number"),
|
|
'on_change_with_warehouse_eu_excise_number')
|
|
eu_excise_number = fields.Many2One(
|
|
'party.identifier', "Excise Number",
|
|
domain=[
|
|
('type', 'in', Eval('eu_excise_types', [])),
|
|
('party', '=', Eval('eu_excise_party', -1)),
|
|
],
|
|
states={
|
|
'invisible': (
|
|
~Eval('warehouse_eu_excise_number')
|
|
| ~Eval('has_eu_excise_goods')),
|
|
})
|
|
eu_excise_types = fields.Function(
|
|
fields.MultiSelection(IDENTIFIER_TYPES, "Excise Types"),
|
|
'on_change_with_eu_excise_types')
|
|
eu_excise_party = fields.Function(
|
|
fields.Many2One(
|
|
'party.party', "Excise Party",
|
|
context={
|
|
'company': Eval('company', -1),
|
|
},
|
|
depends=['company']),
|
|
'on_change_with_eu_excise_party')
|
|
has_eu_excise_goods = fields.Function(
|
|
fields.Boolean("Has Excise Goods"),
|
|
'on_change_with_has_eu_excise_goods')
|
|
|
|
@fields.depends('warehouse', 'company')
|
|
def on_change_with_warehouse_eu_excise_number(self, name=None):
|
|
if self.warehouse:
|
|
return self.warehouse.get_eu_excise_number(self.company)
|
|
|
|
@fields.depends('warehouse_eu_excise_number')
|
|
def on_change_with_eu_excise_types(self, name=None):
|
|
types = set()
|
|
if self.warehouse_eu_excise_number:
|
|
types.add('eu_excise')
|
|
types.add(self.warehouse_eu_excise_number.type)
|
|
return list(types)
|
|
|
|
def on_change_with_eu_excise_party(self, name=None):
|
|
raise NotImplementedError
|
|
|
|
@fields.depends(methods=['eu_excise_moves'])
|
|
def on_change_with_has_eu_excise_goods(self, name=None):
|
|
if self.warehouse_eu_excise_number:
|
|
excise_number = self.warehouse_eu_excise_number
|
|
for move in self.eu_excise_moves:
|
|
if (move.product
|
|
and excise_number.is_excise_product(move.product)):
|
|
return True
|
|
return False
|
|
|
|
@property
|
|
def eu_excise_moves(self):
|
|
"The moves on which the excise duty may apply"
|
|
raise NotImplementedError
|
|
|
|
@classmethod
|
|
def on_modification(cls, mode, shipments, field_names=None):
|
|
super().on_modification(mode, shipments, field_names=field_names)
|
|
cls._sync_eu_excise_duty(shipments)
|
|
|
|
@classmethod
|
|
def _sync_eu_excise_duty(cls, shipments):
|
|
pool = Pool()
|
|
Move = pool.get('stock.move')
|
|
moves = []
|
|
for shipment in shipments:
|
|
warehouse_excise_number = shipment.warehouse_eu_excise_number
|
|
excise_number = shipment.eu_excise_number
|
|
for move in shipment.eu_excise_moves:
|
|
excise_duty = None
|
|
if warehouse_excise_number and excise_number:
|
|
if (warehouse_excise_number.is_excise_product(move.product)
|
|
and excise_number.is_excise_product(move.product)):
|
|
# TODO: free
|
|
excise_duty = 'suspension'
|
|
if move.eu_excise_duty != excise_duty:
|
|
move.eu_excise_duty = excise_duty
|
|
moves.append(move)
|
|
Move.save(moves)
|
|
|
|
|
|
class ShipmentIn(ShipmentEUExciseMixin, metaclass=PoolMeta):
|
|
__name__ = 'stock.shipment.in'
|
|
|
|
@fields.depends('supplier')
|
|
def on_change_with_eu_excise_party(self, name=None):
|
|
return self.supplier
|
|
|
|
@property
|
|
@fields.depends('incoming_moves')
|
|
def eu_excise_moves(self):
|
|
return self.incoming_moves
|
|
|
|
|
|
class ShipmentInReturn(ShipmentEUExciseMixin, metaclass=PoolMeta):
|
|
__name__ = 'stock.shipment.in.return'
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls.eu_excise_number.domain = [
|
|
cls.eu_excise_number.domain,
|
|
('address', '=', Eval('delivery_address', -1)),
|
|
]
|
|
|
|
@fields.depends('supplier')
|
|
def on_change_with_eu_excise_party(self, name=None):
|
|
return self.supplier
|
|
|
|
@property
|
|
@fields.depends('moves')
|
|
def eu_excise_moves(self):
|
|
return self.moves
|
|
|
|
|
|
class ShipmentOut(ShipmentEUExciseMixin, metaclass=PoolMeta):
|
|
__name__ = 'stock.shipment.out'
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls.eu_excise_number.domain = [
|
|
cls.eu_excise_number.domain,
|
|
('address', '=', Eval('delivery_address', -1)),
|
|
]
|
|
|
|
@fields.depends('customer')
|
|
def on_change_with_eu_excise_party(self, name=None):
|
|
return self.customer
|
|
|
|
@property
|
|
@fields.depends('outgoing_moves')
|
|
def eu_excise_moves(self):
|
|
return self.outgoing_moves
|
|
|
|
|
|
class ShipmentOutReturn(ShipmentEUExciseMixin, metaclass=PoolMeta):
|
|
__name__ = 'stock.shipment.out.return'
|
|
|
|
@fields.depends('customer')
|
|
def on_change_with_eu_excise_party(self, name=None):
|
|
return self.customer
|
|
|
|
@property
|
|
@fields.depends('incoming_moves')
|
|
def eu_excise_moves(self):
|
|
return self.incoming_moves
|
|
|
|
|
|
class ShipmentInternal(metaclass=PoolMeta):
|
|
__name__ = 'stock.shipment.internal'
|
|
|
|
@classmethod
|
|
def on_modification(cls, mode, shipments, field_names=None):
|
|
super().on_modification(mode, shipments, field_names=field_names)
|
|
cls._sync_eu_excise_duty(shipments)
|
|
|
|
@classmethod
|
|
def _sync_eu_excise_duty(cls, shipments):
|
|
pool = Pool()
|
|
Move = pool.get('stock.move')
|
|
moves = []
|
|
for shipment in shipments:
|
|
if (shipment.from_location.warehouse
|
|
== shipment.to_location.warehouse):
|
|
continue
|
|
if from_warehouse := shipment.from_location.warehouse:
|
|
from_excise_number = from_warehouse.get_eu_excise_number(
|
|
shipment.company)
|
|
else:
|
|
from_excise_number = None
|
|
if to_warehouse := shipment.to_location.warehouse:
|
|
to_excise_number = to_warehouse.get_eu_excise_number(
|
|
shipment.company)
|
|
else:
|
|
to_excise_number = None
|
|
for move in shipment.moves:
|
|
excise_duty = None
|
|
if (from_excise_number and to_excise_number
|
|
and from_excise_number != to_excise_number):
|
|
if (from_excise_number.is_excise_product(move.product)
|
|
and to_excise_number.is_excise_product(
|
|
move.product)):
|
|
# TODO: free
|
|
excise_duty = 'suspension'
|
|
if move.eu_excise_duty != excise_duty:
|
|
move.eu_excise_duty = excise_duty
|
|
moves.append(move)
|
|
Move.save(moves)
|
|
|
|
|
|
class Move(metaclass=PoolMeta):
|
|
__name__ = 'stock.move'
|
|
|
|
eu_excise_duty = fields.Selection([
|
|
(None, ""),
|
|
('suspension', "Suspension"),
|
|
('free', "Free"),
|
|
], "Excise Duty", readonly=True,
|
|
domain=[If(~Eval('eu_excise_duty_applicable', False),
|
|
('eu_excise_duty', '=', None),
|
|
()),
|
|
],
|
|
states={
|
|
'invisible': ~Eval('eu_excise_duty_applicable'),
|
|
})
|
|
eu_excise_duty_applicable = fields.Function(
|
|
fields.Boolean("Excise Duty Applicable"),
|
|
'on_change_with_eu_excise_duty_applicable')
|
|
|
|
@fields.depends('from_location', 'to_location', 'company')
|
|
def on_change_with_eu_excise_duty_applicable(self, name=None):
|
|
if not self.from_location or not self.to_location:
|
|
return False
|
|
if self.from_location.warehouse == self.to_location.warehouse:
|
|
return False
|
|
if (self.from_location.type not in {'storage', 'view'}
|
|
and self.to_location.type not in {'storage', 'view'}):
|
|
return False
|
|
return True
|