trytond-carrier_load/load.py

1191 lines
44 KiB
Python

# The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime
from functools import partial
from itertools import groupby
from dateutil.relativedelta import relativedelta
from sql import Literal, Null
from sql.aggregate import Count
from sql.functions import CharLength
from sql.operators import Concat
from trytond.model import ModelSQL, ModelView, fields, Workflow, Model
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
from trytond.modules.incoterm.incoterm import (
IncotermDocumentMixin, IncotermMixin)
from trytond.modules.stock_location_dock.stock import DockMixin
from trytond.modules.product import price_digits
from trytond.wizard import StateReport, StateTransition, Wizard
__all__ = ['Load', 'LoadOrder', 'LoadOrderLine',
'LoadOrderIncoterm', 'LoadSheet', 'CMR', 'RoadTransportNote',
'PrintLoadOrderShipment']
class Load(Workflow, ModelView, ModelSQL, DockMixin):
"""Carrier load"""
__name__ = 'carrier.load'
_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,
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__()
t = cls.__table__()
cls._sql_constraints = [
('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'])},
'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')},
'do': {'invisible': Eval('state') != 'confirmed'},
'create_purchase': {'invisible': (Eval('state') != 'done')
| (Bool(Eval('purchase')))}
})
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')
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':
cls.raise_user_error('delete_cancel', record.rec_name)
super(Load, cls).delete(records)
@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)
@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):
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()
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)
_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
_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:])]
# TODO: check party matches with party of origin in lines
class LoadOrder(Workflow, ModelView, ModelSQL, IncotermDocumentMixin):
"""Carrier load order"""
__name__ = 'carrier.load.order'
_rec_name = 'code'
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')
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'])
incoterms = fields.One2Many('carrier.load.order.incoterm', 'order', 'Incoterms',
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)
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__()
t = cls.__table__()
cls._sql_constraints = [
('code_uk1', Unique(t, t.code),
'Code must be unique.')
]
cls._transitions |= set((('draft', 'waiting'),
('waiting', 'draft'),
('draft', 'running'),
('waiting', 'running'),
('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'])},
'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'},
})
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))
@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'
@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
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)
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',
'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)
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()
def create_sale(self):
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):
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])
def _get_load_sale(self, Sale):
pool = Pool()
SaleIncoterm = pool.get('sale.incoterm')
Conf = pool.get('carrier.configuration')
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)
incoterms = [
SaleIncoterm(rule=incoterm.rule,
value=incoterm.value,
currency=incoterm.currency,
place=incoterm.place)
for incoterm in self.incoterms]
sale = Sale(company=self.company,
warehouse=self.load.warehouse,
sale_date=_date,
incoterm_version=self.incoterm_version)
sale.party = self.party
sale.on_change_party()
sale.incoterms = incoterms
sale.origin = self
return sale
def _get_shipment_out(self, sale):
pool = Pool()
Shipment = pool.get('stock.shipment.out')
ShipmentIncoterm = pool.get('stock.shipment.out.incoterm')
shipment = sale._get_shipment_sale(
Shipment, key=(('planned_date', self.end_date.date()),
('warehouse', self.load.warehouse.id),))
shipment.reference = sale.reference
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')
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 []
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')
values = {
'quantity': sum(Uom.compute_qty(m.uom, m.quantity,
m.product.default_uom)
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)
line.on_change_product()
line.from_location = self.load.warehouse_output
line.to_location = self.party.customer_location
line.shipping_date = line.on_change_with_shipping_date(None)
return line
@classmethod
def _group_line_key(cls, items, item):
return (
('product', item.product.id),
('unit', item.product.default_uom.id))
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')})]
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)
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',
required=True, select=True, readonly=True,
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'])
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'
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):
@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):
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':
if not order.shipment:
return None
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
@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')
report_context = super(RoadTransportNote, cls).get_context(
records, data)
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]}