227 lines
7.7 KiB
Python
Executable File
227 lines
7.7 KiB
Python
Executable File
from trytond.model import ModelSQL, ModelView, fields
|
|
from trytond.pool import PoolMeta, Pool
|
|
from trytond.exceptions import UserError
|
|
from trytond.modules.purchase_trade.purchase import (TRIGGERS)
|
|
from trytond.transaction import Transaction
|
|
from decimal import getcontext, Decimal, ROUND_HALF_UP
|
|
from sql import Table
|
|
from trytond.pyson import Bool, Eval, Id, If
|
|
|
|
class PartyExecution(ModelSQL,ModelView):
|
|
"Party Execution"
|
|
__name__ = 'party.execution'
|
|
|
|
party = fields.Many2One('party.party',"Party")
|
|
area = fields.Many2One('country.region',"Area")
|
|
percent = fields.Numeric("% targeted")
|
|
achieved_percent = fields.Function(fields.Numeric("% achieved"),'get_percent')
|
|
|
|
@staticmethod
|
|
def _to_decimal(value):
|
|
if value is None:
|
|
return Decimal('0')
|
|
if isinstance(value, Decimal):
|
|
return value
|
|
return Decimal(str(value))
|
|
|
|
@classmethod
|
|
def _round_percent(cls, value):
|
|
return cls._to_decimal(value).quantize(
|
|
Decimal('0.01'), rounding=ROUND_HALF_UP)
|
|
|
|
def matches_country(self, country):
|
|
if not self.area or not country or not getattr(country, 'region', None):
|
|
return False
|
|
region = country.region
|
|
while region:
|
|
if region.id == self.area.id:
|
|
return True
|
|
region = getattr(region, 'parent', None)
|
|
return False
|
|
|
|
def matches_shipment(self, shipment):
|
|
location = getattr(shipment, 'to_location', None)
|
|
country = getattr(location, 'country', None)
|
|
return self.matches_country(country)
|
|
|
|
@classmethod
|
|
def compute_achieved_percent_for(cls, party, area):
|
|
if not party or not area:
|
|
return Decimal('0')
|
|
Shipment = Pool().get('stock.shipment.in')
|
|
shipments = Shipment.search([
|
|
('controller', '!=', None),
|
|
])
|
|
execution = cls()
|
|
execution.area = area
|
|
shipments = [
|
|
shipment for shipment in shipments
|
|
if execution.matches_shipment(shipment)]
|
|
total = len(shipments)
|
|
if not total:
|
|
return Decimal('0')
|
|
achieved = sum(
|
|
1 for shipment in shipments
|
|
if shipment.controller and shipment.controller.id == party.id)
|
|
return cls._round_percent(
|
|
(Decimal(achieved) * Decimal('100')) / Decimal(total))
|
|
|
|
def compute_achieved_percent(self):
|
|
return self.__class__.compute_achieved_percent_for(
|
|
self.party, self.area)
|
|
|
|
def get_target_gap(self):
|
|
return self._to_decimal(self.percent) - self.compute_achieved_percent()
|
|
|
|
def get_percent(self,name):
|
|
return self.compute_achieved_percent()
|
|
|
|
class PartyExecutionSla(ModelSQL,ModelView):
|
|
"Party Execution Sla"
|
|
__name__ = 'party.execution.sla'
|
|
|
|
party = fields.Many2One('party.party',"Party")
|
|
reference = fields.Char("Reference")
|
|
product = fields.Many2One('product.product',"Product")
|
|
date_from = fields.Date("From")
|
|
date_to = fields.Date("To")
|
|
places = fields.One2Many('party.execution.place','pes',"")
|
|
|
|
class PartyExecutionPlace(ModelSQL,ModelView):
|
|
"Party Sla Place"
|
|
__name__ = 'party.execution.place'
|
|
|
|
pes = fields.Many2One('party.execution.sla',"Sla")
|
|
country = fields.Many2One('country.country',"Country")
|
|
location = fields.Many2One('stock.location',"Location")
|
|
cost = fields.Numeric("Cost",digits=(16,4))
|
|
mode = fields.Selection([
|
|
('lumpsum', 'Lump sum'),
|
|
('perqt', 'Per qt'),
|
|
('pprice', '% price'),
|
|
('rate', '% rate'),
|
|
('pcost', '% cost price'),
|
|
('ppack', 'Per packing'),
|
|
], 'Mode', required=True)
|
|
currency = fields.Many2One('currency.currency',"Currency")
|
|
unit = fields.Many2One('product.uom',"Unit",domain=[
|
|
If(Eval('mode') == 'ppack',
|
|
('category', '=', 8),
|
|
()),
|
|
],
|
|
states={
|
|
'readonly': Eval('mode') != 'ppack',
|
|
})
|
|
|
|
class Party(metaclass=PoolMeta):
|
|
__name__ = 'party.party'
|
|
|
|
tol_min = fields.Numeric("Tol - in %")
|
|
tol_max = fields.Numeric("Tol + in %")
|
|
wb = fields.Many2One('purchase.weight.basis',"Weight basis")
|
|
association = fields.Many2One('purchase.association',"Association")
|
|
origin =fields.Char("Origin")
|
|
execution = fields.One2Many('party.execution','party',"")
|
|
sla = fields.One2Many('party.execution.sla','party', "Sla")
|
|
initial = fields.Char("Initials")
|
|
|
|
def IsAvailableForControl(self,sh):
|
|
return True
|
|
|
|
def get_controller_execution_priority(self, shipment):
|
|
best_rule = None
|
|
best_gap = None
|
|
for execution in self.execution or []:
|
|
if not execution.matches_shipment(shipment):
|
|
continue
|
|
gap = execution.get_target_gap()
|
|
if best_gap is None or gap > best_gap:
|
|
best_gap = gap
|
|
best_rule = execution
|
|
return best_gap, best_rule
|
|
|
|
def get_sla_cost(self, location):
|
|
if self.sla:
|
|
country = getattr(location, 'country', None)
|
|
for sla in self.sla:
|
|
SlaPlace = Pool().get('party.execution.place')
|
|
domain = [
|
|
('pes', '=', sla.id),
|
|
[
|
|
'OR',
|
|
('location', '=', location),
|
|
('location', '=', None),
|
|
],
|
|
]
|
|
if country:
|
|
domain.append([
|
|
'OR',
|
|
('country', '=', country),
|
|
('country', '=', None),
|
|
])
|
|
else:
|
|
domain.append(('country', '=', None))
|
|
sp = SlaPlace.search(domain)
|
|
sp = [
|
|
place for place in sp
|
|
if place.location or place.country
|
|
]
|
|
sp.sort(
|
|
key=lambda place: (
|
|
0 if place.location and place.country else
|
|
1 if place.location else
|
|
2 if place.country else
|
|
3))
|
|
if sp:
|
|
return sp[0].cost,sp[0].mode,sp[0].currency,sp[0].unit
|
|
return None, None, None, None
|
|
|
|
def get_alf(self):
|
|
if self.name == 'CARGO CONTROL':
|
|
return 105
|
|
t = Table('alf')
|
|
cursor = Transaction().connection.cursor()
|
|
cursor.execute(*t.select(
|
|
t.ALF_CODE,
|
|
where=t.SHORT_NAME.ilike(f'%{self.name}%')
|
|
))
|
|
rows = cursor.fetchall()
|
|
if rows:
|
|
return int(rows[0][0])
|
|
|
|
@classmethod
|
|
def _ensure_category(cls, party, category_name):
|
|
if not (party and category_name):
|
|
return
|
|
Category = Pool().get('party.category')
|
|
PartyCategory = Pool().get('party.party-party.category')
|
|
cat = Category.search(['name', '=', category_name], limit=1)
|
|
if not cat:
|
|
return
|
|
cat = cat[0]
|
|
existing = PartyCategory.search([
|
|
('party', '=', party.id),
|
|
('category', '=', cat.id),
|
|
], limit=1)
|
|
if existing:
|
|
return
|
|
pc = PartyCategory()
|
|
pc.party = party.id
|
|
pc.category = cat.id
|
|
PartyCategory.save([pc])
|
|
|
|
@classmethod
|
|
def getPartyByName(cls, party, category=None):
|
|
party = party.upper()
|
|
p = cls.search([('name', '=', party)], limit=1)
|
|
if p:
|
|
cls._ensure_category(p[0], category)
|
|
return p[0]
|
|
else:
|
|
p = cls()
|
|
p.name = party
|
|
cls.save([p])
|
|
cls._ensure_category(p, category)
|
|
return p
|
|
|