trytond-carrier_load/load.py

1191 lines
44 KiB
Python
Raw Normal View History

# The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime
2015-10-20 19:27:58 +02:00
from functools import partial
from itertools import groupby
2016-03-25 09:25:24 +01:00
from dateutil.relativedelta import relativedelta
from sql import Literal, Null
from sql.aggregate import Count
2016-02-18 10:37:52 +01:00
from sql.functions import CharLength
from sql.operators import Concat
2015-10-23 18:55:29 +02:00
from trytond.model import ModelSQL, ModelView, fields, Workflow, Model
2016-08-22 13:58:00 +02:00
from trytond.model import Unique
from trytond.modules.company import CompanyReport
from trytond.pool import Pool
from trytond.pyson import Eval, If, Bool
from trytond.transaction import Transaction
2015-10-15 17:15:27 +02:00
from trytond.modules.incoterm.incoterm import (
IncotermDocumentMixin, IncotermMixin)
2015-10-20 19:27:58 +02:00
from trytond.modules.stock_location_dock.stock import DockMixin
from trytond.modules.product import price_digits
from trytond.wizard import StateReport, StateTransition, Wizard
2015-10-15 17:15:27 +02:00
__all__ = ['Load', 'LoadOrder', 'LoadOrderLine',
'LoadOrderIncoterm', 'LoadSheet', 'CMR', 'RoadTransportNote',
'PrintLoadOrderShipment']
2015-10-20 19:27:58 +02:00
class Load(Workflow, ModelView, ModelSQL, DockMixin):
"""Carrier load"""
__name__ = 'carrier.load'
2016-01-21 00:38:08 +01:00
_rec_name = 'code'
code = fields.Char('Code', required=True, select=True,
states={'readonly': Eval('code_readonly', True)},
depends=['code_readonly'])
code_readonly = fields.Function(fields.Boolean('Code Readonly'),
'get_code_readonly')
company = fields.Many2One('company.company', 'Company', required=True,
states={'readonly': Eval('state') != 'draft'},
domain=[('id', If(Eval('context', {}).contains('company'), '=', '!='),
Eval('context', {}).get('company', -1))],
depends=['state'], select=True)
carrier = fields.Many2One('carrier', 'Carrier', required=True, select=True,
ondelete='RESTRICT',
states={'readonly': Eval('state') != 'draft'},
depends=['state'])
vehicle = fields.Many2One('carrier.vehicle', 'Vehicle', required=True,
ondelete='RESTRICT',
domain=[('carrier', '=', Eval('carrier'))],
states={'readonly': Eval('state') != 'draft'},
depends=['state', 'carrier'])
date = fields.Date('Effective date', required=True,
states={'readonly': Eval('state') != 'draft'},
depends=['state'])
warehouse = fields.Many2One('stock.location', 'Warehouse',
required=True,
domain=[('type', '=', 'warehouse')],
states={'readonly': Eval('state') != 'draft'},
depends=['state'])
warehouse_output = fields.Function(
fields.Many2One('stock.location', 'Warehouse output'),
'on_change_with_warehouse_output')
orders = fields.One2Many('carrier.load.order', 'load', 'Orders',
states={'readonly': Eval('state') != 'draft'},
depends=['state'])
state = fields.Selection([
('draft', 'Draft'),
('confirmed', 'Confirmed'),
('done', 'Done'),
('cancel', 'Cancel')], 'State',
readonly=True, required=True)
purchasable = fields.Boolean('Purchasable',
states={'readonly': ((~Eval('state').in_(['draft', 'confirmed']))
| (Bool(Eval('purchase'))))},
depends=['state', 'purchase'])
unit_price = fields.Numeric('Unit Price', digits=price_digits,
2015-12-21 12:46:30 +01:00
states={'readonly': ((~Eval('state').in_(['draft', 'confirmed', 'done']))
| (Bool(Eval('purchase')))),
'invisible': ~Eval('purchasable')},
depends=['state', 'purchase', 'purchasable'])
currency = fields.Many2One('currency.currency', 'Currency',
states={'readonly': ((~Eval('state').in_(['draft', 'confirmed']))
| (Bool(Eval('purchase')))),
'invisible': ~Eval('purchasable')},
depends=['state', 'purchase', 'purchasable'])
currency_digits = fields.Function(fields.Integer('Currency Digits'),
'on_change_with_currency_digits')
purchase = fields.Many2One('purchase.purchase', 'Purchase', readonly=True,
states={'invisible': ~Eval('purchasable')},
depends=['purchasable'])
purchase_state = fields.Function(
fields.Selection([(None, '')], 'Purchase state',
states={'invisible': ~Eval('purchasable')},
depends=['purchasable']),
'get_purchase_state', searcher='search_purchase_state')
parties = fields.Function(
fields.Char('Parties'), 'get_parties', searcher='search_parties')
@classmethod
def __setup__(cls):
super(Load, cls).__setup__()
2016-08-22 13:58:00 +02:00
t = cls.__table__()
cls._sql_constraints = [
2016-08-22 13:58:00 +02:00
('code_uk1', Unique(t, t.code),
'Code must be unique.')
]
cls._transitions |= set((('draft', 'confirmed'),
('confirmed', 'draft'),
('confirmed', 'done'),
('draft', 'cancel'),
('cancel', 'draft')))
cls._error_messages.update({
'delete_cancel': 'Carrier load "%s" must be cancelled before deletion.',
'purchase_price': 'Unit price in Load "%s" must be defined.'})
cls._buttons.update({
'cancel': {'invisible': Eval('state').in_(['cancel', 'done'])},
2016-03-16 16:42:49 +01:00
'draft': {'invisible': ~Eval('state').in_(['cancel', 'confirmed']),
'icon': If(Eval('state') == 'confirmed', 'tryton-go-previous', 'tryton-go-next')},
'confirm': {'invisible': Eval('state') != 'draft',
'icon': If(Eval('state') == 'draft', 'tryton-go-next', 'tryton-go-previous')},
2015-12-15 19:26:03 +01:00
'do': {'invisible': Eval('state') != 'confirmed'},
'create_purchase': {'invisible': (Eval('state') != 'done')
| (Bool(Eval('purchase')))}
})
2015-10-20 19:27:58 +02:00
if not cls.dock.required:
cls.dock.required = True
if len(cls.purchase_state._field.selection) == 1:
pool = Pool()
Purchase = pool.get('purchase.purchase')
cls.purchase_state._field.selection.extend(Purchase.state.selection)
@classmethod
def view_attributes(cls):
if Transaction().context.get('loading_shipment', False):
return [('//group[@id="state_buttons"]', 'states',
{'invisible': True})]
return []
@staticmethod
def default_code_readonly():
Configuration = Pool().get('carrier.configuration')
config = Configuration(1)
return bool(config.load_sequence)
def get_code_readonly(self, name):
return True
@classmethod
def create(cls, vlist):
Sequence = Pool().get('ir.sequence')
Configuration = Pool().get('carrier.configuration')
vlist = [x.copy() for x in vlist]
config = Configuration(1)
for values in vlist:
if not values.get('code'):
values['code'] = Sequence.get_id(config.load_sequence.id)
return super(Load, cls).create(vlist)
@classmethod
def copy(cls, items, default=None):
if default is None:
default = {}
default = default.copy()
default['code'] = None
default['orders'] = None
return super(Load, cls).copy(items, default=default)
@staticmethod
def default_company():
return Transaction().context.get('company')
@classmethod
def default_state(cls):
return 'draft'
@staticmethod
def default_date():
Date_ = Pool().get('ir.date')
return Date_.today()
@classmethod
def default_warehouse(cls):
Location = Pool().get('stock.location')
locations = Location.search(cls.warehouse.domain)
if len(locations) == 1:
return locations[0].id
@staticmethod
def default_currency():
Company = Pool().get('company.company')
company = Transaction().context.get('company')
if company:
company = Company(company)
return company.currency.id
@staticmethod
def default_currency_digits():
Company = Pool().get('company.company')
company = Transaction().context.get('company')
if company:
company = Company(company)
return company.currency.digits
return 2
@classmethod
def default_purchasable(cls):
pool = Pool()
Configuration = pool.get('carrier.configuration')
conf = Configuration(1)
return conf.load_purchasable
@classmethod
def default_purchase_state(cls):
return None
@fields.depends('carrier', 'purchase')
def on_change_carrier(self):
pool = Pool()
Currency = pool.get('currency.currency')
2016-08-22 13:58:00 +02:00
cursor = Transaction().connection.cursor()
table = self.__table__()
if self.carrier:
if len(self.carrier.vehicles) == 1:
self.vehicle = self.carrier.vehicles[0]
if not self.purchase:
subquery = table.select(table.currency,
where=(table.carrier == self.carrier.id) &
(table.currency != None),
order_by=table.id,
limit=10)
cursor.execute(*subquery.select(subquery.currency,
group_by=subquery.currency,
order_by=Count(Literal(1)).desc))
row = cursor.fetchone()
if row:
currency_id, = row
self.currency = Currency(currency_id)
self.currency_digits = self.currency.digits
@fields.depends('currency')
def on_change_with_currency_digits(self, name=None):
if self.currency:
return self.currency.digits
return 2
def get_purchase_state(self, name=None):
if not self.purchase:
return self.default_purchase_state()
return self.purchase.state
@classmethod
def search_purchase_state(cls, name, clause):
return [('purchase.state', ) + tuple(clause[1:])]
@fields.depends('warehouse')
def on_change_with_warehouse_output(self, name=None):
if self.warehouse:
return self.warehouse.output_location.id
return None
@classmethod
def delete(cls, records):
cls.cancel(records)
for record in records:
if record.state != 'cancel':
2016-09-08 09:57:54 +02:00
cls.raise_user_error('delete_cancel', record.rec_name)
super(Load, cls).delete(records)
2016-09-08 09:57:54 +02:00
@classmethod
@ModelView.button
@Workflow.transition('cancel')
def cancel(cls, records):
Order = Pool().get('carrier.load.order')
orders = [o for r in records for o in r.orders]
Order.cancel(orders)
2016-09-08 09:57:54 +02:00
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, records):
Order = Pool().get('carrier.load.order')
orders = [o for r in records for o in r.orders if o.state == 'cancel']
Order.draft(orders)
@classmethod
@ModelView.button
@Workflow.transition('confirmed')
def confirm(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('done')
def do(cls, records):
cls.create_purchase(records)
@classmethod
@ModelView.button
def create_purchase(cls, records):
2016-02-01 12:53:26 +01:00
pool = Pool()
Purchase = pool.get('purchase.purchase')
to_save = []
for record in records:
if record.state not in ('confirmed', 'done'):
continue
if record.purchase:
continue
if not record.purchasable:
continue
purchase = record.get_purchase()
if purchase:
purchase.save()
2016-02-01 12:53:26 +01:00
Purchase.quote([purchase])
record.purchase = purchase
to_save.append(record)
if to_save:
cls.save(to_save)
def get_purchase(self):
pool = Pool()
Purchase = pool.get('purchase.purchase')
PurchaseLine = pool.get('purchase.line')
if not self.unit_price:
self.raise_user_error('purchase_price', self.rec_name)
2016-11-21 13:07:26 +01:00
_party = (getattr(self.carrier.party, 'supplier_to_invoice', None)
or self.carrier.party)
purchase = Purchase(company=self.company,
party=_party, purchase_date=self.date)
purchase.on_change_party()
purchase.warehouse = self.warehouse
purchase.currency = self.currency
line = PurchaseLine(purchase=purchase,
type='line',
quantity=1,
product=self.carrier.carrier_product,
unit=self.carrier.carrier_product.purchase_uom)
line.on_change_product()
line.unit_price = line.amount = self.unit_price
purchase.lines = [line]
return purchase
def get_parties(self, name=None):
if not self.orders:
return None
2016-09-08 13:58:48 +02:00
_parties = set(o.party for o in self.orders if o.party)
return ';'.join(p.rec_name for p in _parties)
@classmethod
def search_parties(cls, name, clause):
return [('orders.party', ) + tuple(clause[1:])]
2015-10-20 19:27:58 +02:00
# TODO: check party matches with party of origin in lines
2015-10-15 17:15:27 +02:00
class LoadOrder(Workflow, ModelView, ModelSQL, IncotermDocumentMixin):
"""Carrier load order"""
__name__ = 'carrier.load.order'
2016-01-21 00:38:08 +01:00
_rec_name = 'code'
2016-10-24 09:20:42 +02:00
load = fields.Many2One('carrier.load', 'Load', required=True, select=True, ondelete='CASCADE',
states={'readonly': Eval('state') != 'draft'},
depends=['state'])
code = fields.Char('Code', required=True, select=True,
states={'readonly': Eval('code_readonly', True)},
depends=['code_readonly'])
code_readonly = fields.Function(fields.Boolean('Code Readonly'),
'get_code_readonly')
2015-10-20 19:27:58 +02:00
company = fields.Many2One('company.company', 'Company', required=True,
states={'readonly': Eval('state') != 'draft'},
domain=[('id', If(Eval('context', {}).contains('company'), '=', '!='),
Eval('context', {}).get('company', -1))],
depends=['state'], select=True)
date = fields.Function(fields.Date('Effective date'),
'on_change_with_date')
start_date = fields.DateTime('Start date',
states={'readonly': ~Eval('state').in_(['draft', 'waiting'])},
depends=['state'])
end_date = fields.DateTime('End date',
states={'readonly': ~Eval('state').in_(['draft', 'waiting'])},
depends=['state'])
arrival_date = fields.Date('Arrival date',
states={'readonly': Eval('state') != 'draft'},
depends=['state'])
lines = fields.One2Many('carrier.load.order.line', 'order', 'Lines',
states={'readonly': Eval('state') != 'draft'},
depends=['state'])
party = fields.Many2One('party.party', 'Party', select=True, states={
'readonly': (Eval('state') != 'draft') |
(Eval('lines', [0])),
'required': (Eval('state') == 'done') &
(Eval('type') != 'internal'),
'invisible': Eval('type') == 'internal'},
depends=['state', 'lines', 'type'])
2015-10-15 17:15:27 +02:00
incoterms = fields.One2Many('carrier.load.order.incoterm', 'order', 'Incoterms',
2015-10-20 19:27:58 +02:00
states={'readonly': ~Eval('state').in_(['draft', 'waiting']),
'invisible': ~Eval('party')},
depends=['state', 'party'])
sale = fields.Many2One('sale.sale', 'Sale', readonly=True,
states={'invisible': ~Eval('party') | (Eval('type') != 'out')},
depends=['party', 'type'])
shipment = fields.Reference('Shipment', selection='get_shipments',
readonly=True, select=True)
state = fields.Selection([
('draft', 'Draft'),
('waiting', 'Waiting'),
('running', 'Running'),
('done', 'Done'),
('cancel', 'Cancel')], 'State',
readonly=True, required=True)
2015-10-26 20:39:12 +01:00
inventory_moves = fields.Function(
fields.One2Many('stock.move', None, 'Inventory moves'),
'get_inventory_moves')
outgoing_moves = fields.Function(
fields.One2Many('stock.move', None, 'Outgoing moves'),
'get_outgoing_moves')
carrier_amount = fields.Function(
fields.Numeric('Carrier amount',
digits=(16, Eval('_parent_order', {}).get('currency_digits', 2))),
'get_carrier_amount')
type = fields.Selection([
('out', 'Out'),
('internal', 'Internal'),
('in_return', 'In return')], 'Type', required=True, select=True,
states={'readonly': Bool(Eval('lines', [])) |
Bool(Eval('shipment', None)) |
(Eval('state') != 'draft')},
depends=['lines', 'shipment', 'state'])
warehouse = fields.Function(fields.Many2One('stock.location', 'Warehouse'),
'get_warehouse')
to_location = fields.Many2One('stock.location', 'To location',
domain=[('type', '=', 'storage')],
states={'required': (Eval('type') == 'internal') &
~Eval('shipment', None),
'readonly': Eval('state') != 'draft',
'invisible': Eval('type') != 'internal'},
depends=['type', 'state'])
@classmethod
def __setup__(cls):
super(LoadOrder, cls).__setup__()
2016-08-22 13:58:00 +02:00
t = cls.__table__()
cls._sql_constraints = [
2016-08-22 13:58:00 +02:00
('code_uk1', Unique(t, t.code),
'Code must be unique.')
]
cls._transitions |= set((('draft', 'waiting'),
('waiting', 'draft'),
2016-04-02 11:28:02 +02:00
('draft', 'running'),
('waiting', 'running'),
2016-01-24 21:05:14 +01:00
('running', 'waiting'),
('running', 'done'),
('draft', 'cancel'),
('cancel', 'draft')))
cls._error_messages.update({
'delete_cancel': 'Carrier load order "%s" must be ' +
'cancelled before deletion.',
'non_salable_product': 'Product "%s" is not salable.'
})
cls._buttons.update({
'cancel': {'invisible': Eval('state').in_(['cancel', 'done'])},
2016-01-24 21:05:14 +01:00
'draft': {'invisible': ~Eval('state').in_(['cancel', 'waiting']),
'icon': If(Eval('state') == 'cancel', 'tryton-clear', 'tryton-go-previous')},
'wait': {'invisible': ~Eval('state').in_(['draft', 'running']),
'icon': If(Eval('state') == 'draft', 'tryton-go-next', 'tryton-go-previous')},
'do': {'invisible': Eval('state') != 'running',
'icon': 'tryton-ok'},
})
2015-10-20 19:27:58 +02:00
if cls.incoterm_version.states.get('invisible'):
cls.incoterm_version.states['invisible'] |= (~Eval('party'))
else:
cls.incoterm_version.states['invisible'] = (~Eval('party'))
if 'party' not in cls.incoterm_version.depends:
cls.incoterm_version.depends.append('party')
@classmethod
def __register__(cls, module_name):
pool = Pool()
Sale = pool.get('sale.sale')
Saleline = pool.get('sale.line')
Move = pool.get('stock.move')
sale = Sale.__table__()
sale_line = Saleline.__table__()
move = Move.__table__()
sql_table = cls.__table__()
super(LoadOrder, cls).__register__(module_name)
cursor = Transaction().connection.cursor()
# Migration from 3.6: type is required
cursor.execute(*sql_table.update([sql_table.type], ['out'],
where=sql_table.type == Null))
# Migration from 3.6: set shipment
cursor.execute(*sql_table.join(sale,
condition=Concat(
cls.__name__ + ',', sql_table.id) == sale.origin
).join(sale_line, condition=sale_line.sale == sale.id
).join(move, condition=move.origin == Concat(
Saleline.__name__ + ',', sale_line.id)
).select(sql_table.id, move.shipment,
where=(sql_table.shipment == Null) &
(sql_table.state == 'done') &
(sql_table.type == 'out'),
group_by=[sql_table.id, move.shipment])
)
for order_id, shipment_id in cursor.fetchall():
cursor.execute(*sql_table.update([sql_table.shipment], [shipment_id],
where=sql_table.id == order_id))
2016-02-18 10:37:52 +01:00
@staticmethod
def order_code(tables):
table, _ = tables[None]
return [CharLength(table.code), table.code]
@staticmethod
def default_type():
return 'out'
@staticmethod
def default_code_readonly():
Configuration = Pool().get('carrier.configuration')
config = Configuration(1)
return bool(config.load_order_sequence)
def get_code_readonly(self, name):
return True
@classmethod
def default_state(cls):
return 'draft'
2015-10-20 19:27:58 +02:00
@staticmethod
def default_company():
return Transaction().context.get('company')
def get_warehouse(self, name=None):
if self.load:
return self.load.warehouse.id
return None
2015-10-26 20:39:12 +01:00
def get_inventory_moves(self, name=None):
if self.lines:
return [m.id for l in self.lines for m in l.inventory_moves]
return []
def get_outgoing_moves(self, name=None):
if self.lines:
return [m.id for l in self.lines for m in l.outgoing_moves]
return []
def get_carrier_amount(self, name=None):
if not self.load.unit_price:
return 0
return self.load.currency.round(
(1 / len(self.load.orders)) * self.load.unit_price)
@classmethod
def create(cls, vlist):
Sequence = Pool().get('ir.sequence')
Configuration = Pool().get('carrier.configuration')
vlist = [x.copy() for x in vlist]
config = Configuration(1)
for values in vlist:
if not values.get('code'):
values['code'] = Sequence.get_id(config.load_order_sequence.id)
2016-09-08 09:57:54 +02:00
return super(LoadOrder, cls).create(vlist)
@classmethod
def copy(cls, items, default=None):
if default is None:
default = {}
default = default.copy()
default['code'] = None
default['lines'] = None
default['shipment'] = None
return super(LoadOrder, cls).copy(items, default=default)
@classmethod
def get_models(cls, models):
Model = Pool().get('ir.model')
models = Model.search([
('model', 'in', models),
])
return [('', '')] + [(m.model, m.name) for m in models]
@classmethod
def get_shipments(cls):
return cls.get_models(cls._get_shipments())
@classmethod
def _get_shipments(cls):
return ['stock.shipment.out',
2018-02-19 09:07:54 +01:00
'stock.shipment.out.return',
'stock.shipment.internal',
'stock.shipment.in.return']
@fields.depends('load')
def on_change_with_date(self, name=None):
if self.load:
return self.load.date
return None
@classmethod
def delete(cls, records):
cls.cancel(records)
for record in records:
if record.state != 'cancel':
cls.raise_user_error('delete_cancel', record.rec_name)
2015-10-15 17:15:27 +02:00
super(LoadOrder, cls).delete(records)
@classmethod
@ModelView.button
@Workflow.transition('cancel')
def cancel(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('waiting')
def wait(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('running')
def run(cls, records):
to_update = [r for r in records if not r.start_date]
if to_update:
cls.write(to_update, {'start_date': datetime.datetime.now()})
@classmethod
@ModelView.button
@Workflow.transition('done')
def do(cls, records):
_end_date = datetime.datetime.now()
for record in records:
if not record.end_date:
record.end_date = _end_date
record.save()
record.create_sale()
record.create_shipment()
2015-10-20 19:27:58 +02:00
def create_sale(self):
2015-10-20 19:27:58 +02:00
pool = Pool()
Sale = pool.get('sale.sale')
if self.type != 'out':
return
if not self.party:
return
if self.sale:
return
_sale = self._get_load_sale(Sale)
items = self._get_items()
keyfunc = partial(self._group_line_key, items)
items = sorted(items, key=keyfunc)
lines = []
for key, grouped_items in groupby(items, key=keyfunc):
_groupitems = list(grouped_items)
line = self._get_load_sale_line(key, _groupitems)
lines.append(line)
_sale.lines = lines
_sale.save()
self.sale = _sale
self.save()
Sale.quote([_sale])
def create_shipment(self):
2015-10-23 18:55:29 +02:00
pool = Pool()
Shipment = pool.get('stock.shipment.%s' % self.type)
if self.shipment:
return
if self.type == 'out':
if not self.party:
return
if self.shipment:
return
if self.sale.shipment_method != 'manual':
return
shipment = self._get_shipment_out(self.sale)
elif self.type == 'internal':
if not self.to_location:
return
shipment = self._get_shipment_internal()
else:
raise NotImplementedError()
items = self._get_items()
keyfunc = partial(self._group_line_key, items)
items = sorted(items, key=keyfunc)
for key, grouped_items in groupby(items, key=keyfunc):
_groupitems = list(grouped_items)
key_dict = dict(key)
_fields = key_dict.keys()
def get_line_values(line):
line_values = []
for _field in _fields:
value = getattr(line, _field, None)
if isinstance(value, Model):
value = int(value)
line_values.append(value)
return line_values
if self.type == 'out':
sale_line, = [l for l in self.sale.lines
if get_line_values(l) == key_dict.values()]
else:
sale_line = None
shipment.moves = (list(getattr(shipment, 'moves', [])) +
self._get_shipment_moves(sale_line, _groupitems))
shipment.save()
self.shipment = shipment
self.save()
Shipment.wait([shipment])
if not Shipment.assign_try([shipment]):
Shipment.assign_force([shipment])
if self.type == 'out':
Shipment.pack([shipment])
2015-10-20 19:27:58 +02:00
def _get_load_sale(self, Sale):
pool = Pool()
SaleIncoterm = pool.get('sale.incoterm')
2016-03-25 09:25:24 +01:00
Conf = pool.get('carrier.configuration')
2015-10-20 19:27:58 +02:00
2016-03-25 09:25:24 +01:00
conf = Conf(1)
_date = self.end_date.date()
if conf.work_end_time and self.end_date.time() < conf.work_end_time:
_date -= relativedelta(days=1)
2015-10-20 19:27:58 +02:00
incoterms = [
SaleIncoterm(rule=incoterm.rule,
value=incoterm.value,
currency=incoterm.currency,
place=incoterm.place)
for incoterm in self.incoterms]
2015-10-23 18:55:29 +02:00
sale = Sale(company=self.company,
2015-10-20 19:27:58 +02:00
warehouse=self.load.warehouse,
2016-03-25 09:25:24 +01:00
sale_date=_date,
2015-10-20 19:27:58 +02:00
incoterm_version=self.incoterm_version)
2015-10-23 18:55:29 +02:00
sale.party = self.party
sale.on_change_party()
2015-10-20 19:27:58 +02:00
sale.incoterms = incoterms
sale.origin = self
2015-10-20 19:27:58 +02:00
return sale
def _get_shipment_out(self, sale):
2015-10-20 19:27:58 +02:00
pool = Pool()
Shipment = pool.get('stock.shipment.out')
ShipmentIncoterm = pool.get('stock.shipment.out.incoterm')
shipment = sale._get_shipment_sale(
2016-02-22 09:03:14 +01:00
Shipment, key=(('planned_date', self.end_date.date()),
2015-10-20 19:27:58 +02:00
('warehouse', self.load.warehouse.id),))
2017-10-24 09:41:04 +02:00
shipment.reference = sale.reference
2015-10-20 19:27:58 +02:00
shipment.dock = self.load.dock
shipment.incoterm_version = sale.incoterm_version
shipment.incoterms = [
ShipmentIncoterm(rule=incoterm.rule,
value=incoterm.value,
currency=incoterm.currency,
place=incoterm.place)
for incoterm in self.incoterms]
return shipment
def _get_shipment_internal(self):
pool = Pool()
Shipment = pool.get('stock.shipment.internal')
2017-07-28 18:55:55 +02:00
shipment = Shipment(
company=self.company,
planned_date=self.end_date.date(),
planned_start_date=self.end_date.date(),
effective_date=self.end_date.date(),
from_location=self.warehouse.storage_location,
to_location=self.to_location)
shipment.dock = self.load.dock
return shipment
def _get_shipment_moves(self, origin, grouped_items):
if self.type == 'out':
return [origin.get_move(shipment_type='out')]
elif self.type == 'internal':
return []
return []
2015-10-20 19:27:58 +02:00
def _get_load_sale_line(self, key, grouped_items):
pool = Pool()
Uom = pool.get('product.uom')
Saleline = pool.get('sale.line')
Product = pool.get('product.product')
2015-10-20 19:27:58 +02:00
values = {
'quantity': sum(Uom.compute_qty(m.uom, m.quantity,
m.product.default_uom)
2015-10-20 19:27:58 +02:00
for m in grouped_items)
}
values.update(dict(key))
line = Saleline(**values)
product = Product(line.product)
if not product.salable:
self.raise_user_error('non_salable_product', product.rec_name)
2015-10-20 19:27:58 +02:00
line.on_change_product()
line.from_location = self.load.warehouse_output
line.to_location = self.party.customer_location
2016-08-23 09:17:44 +02:00
line.shipping_date = line.on_change_with_shipping_date(None)
2015-10-20 19:27:58 +02:00
return line
@classmethod
def _group_line_key(cls, items, item):
2015-10-20 19:27:58 +02:00
return (
2015-10-23 18:55:29 +02:00
('product', item.product.id),
('unit', item.product.default_uom.id))
2015-10-20 19:27:58 +02:00
def _get_items(self):
return self.lines
@classmethod
def view_attributes(cls):
return super(LoadOrder, cls).view_attributes() + [
('//page[@id="incoterms"]', 'states', {
'invisible': ~Eval('party')})]
2015-10-15 17:15:27 +02:00
class LoadOrderIncoterm(ModelView, ModelSQL, IncotermMixin):
"""Load order Incoterm"""
__name__ = 'carrier.load.order.incoterm'
order = fields.Many2One('carrier.load.order', 'Order', required=True,
ondelete='CASCADE')
def get_rec_name(self, name):
return '%s %s' % (self.rule.rec_name, self.place)
2015-10-15 17:15:27 +02:00
def _get_relation_version(self):
return self.order
@fields.depends('order')
def on_change_with_version(self, name=None):
return super(LoadOrderIncoterm, self).on_change_with_version(name)
class LoadOrderLine(ModelView, ModelSQL):
"""Carrier load order line"""
__name__ = 'carrier.load.order.line'
order = fields.Many2One('carrier.load.order', 'Load order',
2016-04-05 12:10:40 +02:00
required=True, select=True, readonly=True,
2015-10-15 17:15:27 +02:00
ondelete='CASCADE')
origin = fields.Reference('Origin', selection='get_origin',
readonly=True)
product = fields.Function(
fields.Many2One('product.product', 'Product'),
'on_change_with_product')
uom = fields.Function(
fields.Many2One('product.uom', 'UOM'),
'on_change_with_uom')
unit_digits = fields.Function(fields.Integer('Unit Digits'),
'on_change_with_unit_digits')
quantity = fields.Float('Quantity',
digits=(16, Eval('unit_digits', 2)),
depends=['unit_digits'])
2015-10-26 20:39:12 +01:00
moves = fields.One2Many('stock.move', 'origin', 'Moves', readonly=True)
inventory_moves = fields.Function(
fields.One2Many('stock.move', None, 'Inventory moves'),
'get_inventory_moves')
outgoing_moves = fields.Function(
fields.One2Many('stock.move', None, 'Outgoing moves'),
'get_outgoing_moves')
@classmethod
def __setup__(cls):
super(LoadOrderLine, cls).__setup__()
cls._error_messages.update({
'quantity_exceeded': 'Cannot exceed quantity of "%s".',
})
@classmethod
def _get_origin(cls):
return ['']
@classmethod
def get_origin(cls):
return cls.get_models(cls._get_origin())
@classmethod
def get_models(cls, models):
Model = Pool().get('ir.model')
models = Model.search([
('model', 'in', models),
])
return [('', '')] + [(m.model, m.name) for m in models]
@fields.depends('origin')
def on_change_with_product(self, name=None):
if self.origin and getattr(self.origin, 'product', None):
return self.origin.product.id
return None
@fields.depends('origin')
def on_change_with_uom(self, name=None):
if self.origin and getattr(self.origin, 'uom', None):
return self.origin.uom.id
return None
@fields.depends('origin')
def on_change_with_unit_digits(self, name=None):
if self.origin and getattr(self.origin, 'uom', None):
return self.origin.uom.digits
return 2
@classmethod
def validate(cls, records):
cls.check_origin_quantity(records)
super(LoadOrderLine, cls).validate(records)
@classmethod
def check_origin_quantity(cls, records):
values = {}
_field = cls._get_quantity_field()
for record in records:
if not record.origin:
continue
values.setdefault(record.origin, 0)
values[record.origin] += getattr(record, _field, 0)
record_ids = map(int, records)
for key, value in values.iteritems():
others = cls.search([('origin', '=', '%s,%s' % (key.__name__, key.id)),
('id', 'not in', record_ids)])
if others:
value += sum(getattr(o, _field, 0) for o in others)
if getattr(key, _field, 0) < value:
cls.raise_user_error('quantity_exceeded', key.rec_name)
@classmethod
def _get_quantity_field(cls):
return 'quantity'
2015-10-26 20:39:12 +01:00
def get_inventory_moves(self, name=None):
if not self.moves:
return []
return [m.id for m in self.moves if m.to_location == self.order.load.warehouse_output]
def get_outgoing_moves(self, name=None):
if not self.moves:
return []
return [m.id for m in self.moves if m.from_location == self.order.load.warehouse_output]
class LoadSheet(CompanyReport):
"""Carrier load report"""
__name__ = 'carrier.load.sheet'
@classmethod
def get_context(cls, records, data):
report_context = super(LoadSheet, cls).get_context(records, data)
report_context['product_quantity'] = lambda order, product: \
cls.product_quantity(order, product)
products = {}
for record in list(set(records)):
for order in record.orders:
products.update({order.id: cls._get_products(order)})
report_context['order_products'] = products
return report_context
@classmethod
def _get_products(cls, order):
return list(set([l.product for l in order.lines]))
@classmethod
def product_quantity(cls, order, product):
"""Returns product quantity in load order"""
Uom = Pool().get('product.uom')
value = 0
for line in order.lines:
if line.product and line.product.id == product.id:
value += Uom.compute_qty(line.uom, line.quantity, product.default_uom)
return value
class NoteMixin(object):
2017-10-06 14:27:07 +02:00
@classmethod
def get_context(cls, records, data):
report_context = super(NoteMixin, cls).get_context(records, data)
report_context['delivery_address'] = (lambda order:
cls.delivery_address(order))
report_context['consignee_address'] = (lambda order:
cls.consignee_address(order))
report_context['product_name'] = (lambda product_id, order, language:
cls.product_name(product_id, order, language))
report_context['product_brand'] = (
lambda product, language: cls.product_brand(product, language))
report_context['product_packages'] = (lambda order, product, language:
cls.product_packages(order, product, language))
report_context['product_packing'] = (lambda product, language:
cls.product_packing(product, language))
report_context['product_weight'] = (lambda order, product, language:
cls.product_weight(order, product, language))
report_context['product_volume'] = (lambda order, product, language:
cls.product_volume(order, product, language))
report_context['instructions'] = (
lambda order, language: cls.instructions(order, language))
report_context['sender'] = lambda order: cls.sender(order)
report_context['consignee'] = lambda order: cls.consignee(order)
products = {}
for record in list(set(records)):
products.update({record.id: cls._get_products(record)})
report_context['order_products'] = products
return report_context
@classmethod
def _get_products(cls, order):
if order.lines:
return list(set([l.product for l in order.lines]))
if order.shipment:
return list(set([l.product for l in order.shipment.moves]))
return []
@classmethod
def consignee_address(cls, order):
2017-10-06 14:27:07 +02:00
if order.type == 'out':
party = order.sale and order.sale.shipment_party
if not party:
party = order.party
return party.address_get(type='invoice')
return order.company.party.address_get(type='invoice')
@classmethod
def delivery_address(cls, order):
if order.type == 'internal':
return order.to_location.warehouse.address
elif order.type == 'out':
2017-10-06 14:27:07 +02:00
if not order.shipment:
return None
2017-10-06 14:27:07 +02:00
return order.shipment.delivery_address
return None
@classmethod
def product_name(cls, product_id, order, language):
Product = Pool().get('product.product')
with Transaction().set_context(language=language):
return Product(product_id).rec_name
@classmethod
def product_brand(cls, product, language):
return None
@classmethod
def product_packages(cls, order, product, language):
return None
@classmethod
def product_packing(cls, product, language):
return None
@classmethod
def product_weight(cls, order, product, language):
return None
@classmethod
def product_volume(cls, order, product, language):
return None
2017-10-06 14:27:07 +02:00
@classmethod
def sender(cls, order):
if order.type == 'out' and order.sale and order.sale.shipment_party:
return order.sale.party
return order.company.party
@classmethod
def consignee(cls, order):
if order.type == 'out':
if order.sale and order.sale.shipment_party:
return order.sale.shipment_party
return order.sale and order.sale.party or order.party
return order.company.party
class CMR(NoteMixin, CompanyReport):
"""CMR report"""
__name__ = 'carrier.load.order.cmr'
@classmethod
def get_context(cls, records, data):
Configuration = Pool().get('carrier.configuration')
report_context = super(CMR, cls).get_context(records, data)
report_context['copies'] = Configuration(1).cmr_copies or 3
return report_context
@classmethod
def instructions(cls, order, language):
Configuration = Pool().get('carrier.configuration')
with Transaction().set_context(language=language):
value = Configuration(1).cmr_instructions
if value:
value = value.splitlines()
return value or []
class RoadTransportNote(NoteMixin, CompanyReport):
"""Road transport note"""
__name__ = 'carrier.load.order.road_note'
@classmethod
def get_context(cls, records, data):
Configuration = Pool().get('carrier.configuration')
2017-10-06 14:27:07 +02:00
report_context = super(RoadTransportNote, cls).get_context(
records, data)
2017-10-06 14:27:07 +02:00
report_context['law_header'] = lambda language: \
cls.law_header(language)
report_context['law_footer'] = lambda language: \
cls.law_footer(language)
report_context['copies'] = Configuration(1).road_note_copies or 3
return report_context
@classmethod
def law_header(cls, language):
Configuration = Pool().get('carrier.configuration')
with Transaction().set_context(language=language):
return Configuration(1).road_note_header
@classmethod
def law_footer(cls, language):
Configuration = Pool().get('carrier.configuration')
with Transaction().set_context(language=language):
return Configuration(1).road_note_footer
@classmethod
def instructions(cls, order, language):
Configuration = Pool().get('carrier.configuration')
with Transaction().set_context(language=language):
value = Configuration(1).road_note_instructions
if value:
value = value.splitlines()
return value or []
class PrintLoadOrderShipment(Wizard):
"""Print load order shipment"""
__name__ = 'carrier.load.order.print_shipment'
start = StateTransition()
internal_shipment = StateReport('stock.shipment.internal.report')
delivery_note = StateReport('stock.shipment.out.delivery_note')
def transition_start(self):
Order = Pool().get('carrier.load.order')
order = Order(Transaction().context['active_id'])
if not order.shipment:
return 'end'
return self._get_shipment_report_state()[order.shipment.__name__]
@classmethod
def _get_shipment_report_state(cls):
return {
'stock.shipment.internal': 'internal_shipment',
'stock.shipment.out': 'delivery_note'
}
def do_internal_shipment(self, action):
return self._print_shipment(action)
def do_delivery_note(self, action):
return self._print_shipment(action)
def _print_shipment(self, action):
Order = Pool().get('carrier.load.order')
order = Order(Transaction().context['active_id'])
return action, {'ids': [order.shipment.id]}