trytonpsk-stock_co/shipment.py

596 lines
21 KiB
Python
Executable File

# 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 datetime import date
from decimal import Decimal
from operator import itemgetter
from sql import Table
from trytond.modules.company import CompanyReport
from trytond.pool import Pool, PoolMeta
from trytond.wizard import Wizard, StateTransition, StateView, Button, StateReport
from trytond.transaction import Transaction
from trytond.model import ModelView, fields
from trytond.report import Report
from trytond.pyson import Eval
type_shipment = {
'out': 'Envio a Clientes',
'in': 'Envio de Proveedor',
'internal': 'Envio Interno',
}
class ShipmentIn(metaclass=PoolMeta):
'Shipment In'
__name__ = 'stock.shipment.in'
@classmethod
def __setup__(cls):
super(ShipmentIn, cls).__setup__()
cls.reference.states = {
'required': Eval('state').in_(['received']),
}
class InternalShipment(metaclass=PoolMeta):
'Internal Shipment'
__name__ = 'stock.shipment.internal'
@classmethod
def __setup__(cls):
super(InternalShipment, cls).__setup__()
cls.from_location.domain = [
('name', 'ilike', 'ZA%'),
('active', '=', True)
]
cls.to_location.domain = [
('name', 'ilike', 'ZA%'),
('active', '=', True)
]
@classmethod
def wait(cls, shipments):
super(InternalShipment, cls).wait(shipments)
cls.set_employee(shipments, 'shipped_by')
@classmethod
@ModelView.button_action('stock.wizard_shipment_internal_assign')
def assign_wizard(cls, shipments):
super(InternalShipment, cls).assign_wizard(shipments)
for sh in shipments:
cls.assign_lots(sh)
@classmethod
def done(cls, shipments):
super(InternalShipment, cls).done(shipments)
cls.set_employee(shipments, 'done_by')
@classmethod
def set_employee(cls, shipments, field):
employee_id = Transaction().context.get('employee')
if employee_id:
cls.write(shipments, {field: employee_id})
@classmethod
def assign_lots(cls, shipment):
"""
Look for the oldest lot or the one closest to the expiration
date and try assign it to shipment moves
"""
Move = Pool().get('stock.move')
Lot = Pool().get('stock.lot')
def _get_lots(product_id):
locations_ids = {'locations': [shipment.from_location.id]}
order = []
fields = Lot.fields_get(fields_names=['expiration_date'])
if 'expiration_date' in fields.keys():
order.append(('expiration_date', 'ASC NULLS LAST'))
order.append(('create_date', 'ASC'))
with Transaction().set_context(locations_ids):
dom = [
('product', '=', product_id),
('active', '=', True),
('quantity', '>', 0),
]
lots = Lot.search(dom, order=order)
return lots
for move in shipment.moves:
if move.product.lot_is_required:
lots_list = _get_lots(move.product.id)
balance_qty = move.quantity
for lt in lots_list:
if lt.quantity >= balance_qty:
move.lot = lt.id
move.save()
break
else:
balance_qty = balance_qty - lt.quantity
move.quantity = lt.quantity
move.lot = lt.id
move.save()
if balance_qty:
move, = Move.create([{
'quantity': balance_qty,
'internal_quantity': balance_qty,
'product': move.product.id,
'lot': lt.id,
'from_location': move.from_location.id,
'to_location': move.to_location.id,
'shipment': str(shipment),
'uom': move.uom.id,
'company': move.company.id,
'state': move.state,
}])
def load_current_stock(self):
pool = Pool()
Internal = pool.get('stock.shipment.internal')
Product = pool.get('product.product')
moves_target = []
ctx = {
'locations': [self.from_location.id],
'stock_date_end': self.planned_date,
}
with Transaction().set_context(ctx):
products = Product.search([
('quantity', '>', 0)
])
for product in products:
if product.quantity <= 0:
continue
moves_target.append({
'product': product.id,
'uom': product.default_uom.id,
'quantity': product.quantity,
'internal_quantity': product.quantity,
'from_location': self.from_location.id,
'to_location': self.to_location.id,
'planned_date': self.planned_date or self.effective_date,
'effective_date': self.planned_date or self.effective_date,
'state': 'draft',
})
Internal.write([self], {
'moves': [('create', moves_target)],
})
return 'end'
class CreateInternalShipmentStart(ModelView):
'Create Internal Shipment Start'
__name__ = 'stock_co.create_internal_shipment.start'
from_location = fields.Many2One('stock.location', "From Location",
required=True, domain=[
('type', 'in', [
'view', 'storage', 'lost_found']),
])
to_location = fields.Many2One('stock.location', "To Location",
required=True, domain=[
('type', 'in', [
'view', 'storage', 'lost_found']),
])
class CreateInternalShipment(Wizard):
'Create Internal Shipment'
__name__ = 'stock_co.create_internal_shipment'
start = StateView('stock_co.create_internal_shipment.start',
'stock_co.create_internal_shipment_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Ok', 'accept', 'tryton-ok', default=True),
])
accept = StateTransition()
def transition_accept(self):
pool = Pool()
Internal = pool.get('stock.shipment.internal')
ShipmentSupplier = pool.get('stock.shipment.in')
shipment_id = Transaction().context.get('active_id')
shipment_supplier = ShipmentSupplier(shipment_id)
moves_target = []
today = date.today()
for move in shipment_supplier.incoming_moves:
moves_target.append({
'product': move.product.id,
'uom': move.product.default_uom.id,
'quantity': move.quantity,
'internal_quantity': move.quantity,
'from_location': self.start.from_location.id,
'to_location': self.start.to_location.id,
'planned_date': today,
'effective_date': today,
'state': 'draft',
})
data = {
'company': shipment_supplier.company.id,
'effective_date': today,
'planned_date': today,
'effective_start_date': today,
'planned_start_date': today,
'from_location': self.start.from_location.id,
'to_location': self.start.to_location.id,
'state': 'draft',
'moves': [('create', moves_target)],
}
Internal.create([data])
return 'end'
class ShipmentInReturnReport(CompanyReport):
'Shipment In Return Report'
__name__ = 'stock.shipment.in.return.report'
class ShipmentOutForceDraft(Wizard):
'Shipment Out Force Draft'
__name__ = 'stock.shipment.out.force_draft'
start_state = 'force_draft'
force_draft = StateTransition()
def transition_force_draft(self):
id_ = Transaction().context['active_id']
Shipment = Pool().get('stock.shipment.out')
stock_move = Table('stock_move')
if id_:
shipment = Shipment(id_)
cursor = Transaction().connection.cursor()
for rec in shipment.inventory_moves:
cursor.execute(*stock_move.delete(
where=stock_move.id == rec.id)
)
for rec in shipment.outgoing_moves:
cursor.execute(*stock_move.update(
columns=[stock_move.state],
values=['draft'],
where=stock_move.id == rec.id)
)
shipment.state = 'draft'
shipment.save()
return 'end'
class ShipmentInternalForceDraft(Wizard):
'Shipment Internal Force Draft'
__name__ = 'stock.shipment.internal.force_draft'
start_state = 'force_draft'
force_draft = StateTransition()
def transition_force_draft(self):
id_ = Transaction().context['active_id']
Shipment = Pool().get('stock.shipment.internal')
stock_move = Table('stock_move')
if id_:
shipment = Shipment(id_)
cursor = Transaction().connection.cursor()
for rec in shipment.moves:
cursor.execute(*stock_move.update(
columns=[stock_move.state],
values=['draft'],
where=stock_move.id == rec.id)
)
shipment.state = 'draft'
shipment.save()
return 'end'
class ShipmentInForceDraft(Wizard):
'Shipment In Force Draft'
__name__ = 'stock.shipment.in.force_draft'
start_state = 'force_draft'
force_draft = StateTransition()
def transition_force_draft(self):
shipment = Table('stock_shipment_in')
move = Table('stock_move')
id_shipment = Transaction().context['active_id']
cursor = Transaction().connection.cursor()
if not id_shipment:
return 'end'
cursor.execute(*shipment.update(
columns=[shipment.state],
values=['draft'],
where=shipment.id == id_shipment)
)
pool = Pool()
ShipmentIn = pool.get('stock.shipment.in')
shipmentin = ShipmentIn(id_shipment)
moves_ids = [m.id for m in shipmentin.inventory_moves]
if moves_ids:
cursor.execute(*move.delete(
where=move.id.in_(moves_ids))
)
moves_ids = [m.id for m in shipmentin.incoming_moves]
if moves_ids:
cursor.execute(*move.update(
columns=[move.state],
values=['draft'],
where=move.id.in_(moves_ids))
)
return 'end'
class ShipmentInReturnForceDraft(Wizard):
'Shipment In Return Force Draft'
__name__ = 'stock.shipment.in.return.force_draft'
start_state = 'force_draft'
force_draft = StateTransition()
def transition_force_draft(self):
shipment = Table('stock_shipment_in_return')
move = Table('stock_move')
id_shipment = Transaction().context['active_id']
cursor = Transaction().connection.cursor()
if not id_shipment:
return 'end'
cursor.execute(*shipment.update(
columns=[shipment.state],
values=['draft'],
where=shipment.id == id_shipment)
)
pool = Pool()
ShipmentIn = pool.get('stock.shipment.in.return')
shipment_in = ShipmentIn(id_shipment)
moves_ids = [m.id for m in shipment_in.moves]
if moves_ids:
cursor.execute(*move.update(
columns=[move.state],
values=['draft'],
where=move.id.in_(moves_ids))
)
return 'end'
class ShipmentDetailedStart(ModelView):
'Shipment Detailed Start'
__name__ = 'stock.shipment.shipment_detailed.start'
company = fields.Many2One('company.company', 'Company', required=True)
start_date = fields.Date('Start Date', required=True)
end_date = fields.Date('End Date', required=True)
type_shipment = fields.Selection([
('in', 'Supplier'),
('out', 'Customer'),
('internal', 'Internal'),
], 'Type Shipment', required=True)
grouped = fields.Boolean('Grouped')
from_locations = fields.Many2Many('stock.location', None, None, 'From Location', domain=[
('name', 'ilike', 'ZA%'),
('active', '=', True)
])
to_locations = fields.Many2Many('stock.location', None, None, 'To Location', domain=[
('name', 'ilike', 'ZA%'),
('active', '=', True)
])
@staticmethod
def default_company():
return Transaction().context.get('company')
class ShipmentDetailed(Wizard):
'Shipment Detailed'
__name__ = 'stock.shipment.shipment_detailed'
start = StateView('stock.shipment.shipment_detailed.start',
'stock_co.print_shipment_detailed_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('stock.shipment.shipment_detailed.report')
def do_print_(self, action):
from_locations=None
to_locations=None
if self.start.from_locations:
from_locations= [l.id for l in self.start.from_locations]
if self.start.to_locations:
to_locations= [l.id for l in self.start.to_locations]
data = {
'company': self.start.company.id,
'start_date': self.start.start_date,
'end_date': self.start.end_date,
'type_shipment': self.start.type_shipment,
'grouped': self.start.grouped,
'from_locations': from_locations,
'to_locations': to_locations
}
return action, data
def transition_print_(self):
return 'end'
class ShipmentDetailedReport(Report):
'Shipment Detailed Report'
__name__ = 'stock.shipment.shipment_detailed.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
company = Transaction().context.get('company.rec_name')
type_shipment_ = data['type_shipment']
model = 'stock.shipment.' + type_shipment_
ModelShipment = pool.get(model)
Move = pool.get('stock.move')
Product = pool.get('product.product')
dom_shipment = [
('company', '=', data['company']),
('effective_date', '>=', data['start_date']),
('effective_date', '<=', data['end_date'])
]
if data['from_locations']:
dom_shipment.append(('from_location' , 'in', data['from_locations']))
if data['to_locations']:
dom_shipment.append(('to_location' , 'in', data['to_locations']))
fields_names = ['id']
shipments = ModelShipment.search_read(dom_shipment,
fields_names=fields_names,
order=[('effective_date', 'ASC')]
)
shipments_id = [model + ',' + str(sh['id']) for sh in shipments]
fields_names = [
'product.account_category.name', 'product.name', 'product.cost_price',
'quantity', 'to_location.name', 'from_location.name', 'shipment.reference',
'effective_date'
]
fields = ModelShipment.fields_get(fields_names=['operation_center', 'customer', 'supplier', 'incoming_moves'])
if 'operation_center' in fields.keys():
fields_names.append('shipment.operation_center.rec_name')
if type_shipment_ == 'in':
fields_names.append('shipment.supplier.name')
elif type_shipment_ == 'out':
fields_names.append('shipment.customer.name')
moves = Move.search_read(
('shipment', 'in', shipments_id),
fields_names=fields_names,
order=[('to_location', 'DESC'), ('create_date', 'ASC')]
)
dgetter = itemgetter('product.', 'quantity')
product_browse = Product.browse
for m in moves:
product, quantity = dgetter(m)
product_, = product_browse([product['id']])
try:
oc = m['shipment.']['operation_center.']['rec_name']
except:
oc = ''
if type_shipment_ == 'in':
party = m['shipment.']['supplier.']['name']
elif type_shipment_ == 'out':
party = m['shipment.']['customer.']['name']
else:
party = ''
cost_price = product['cost_price']
category = product.get('account_category.', '')
if category:
category = category['name']
category_ad = ''
if product_.categories:
category_ad = product_.categories[0].name
value = {
'party': party,
'oc': oc,
'product': product['name'],
'cost_price': cost_price,
'category': category,
'category_ad': category_ad,
'cost_base': Decimal(str(round(float(cost_price) * quantity, 2))),
}
try:
value['cost_unit_w_tax'] = float(product_.cost_price_taxed)
value['cost_w_tax'] = float(
product_.cost_price_taxed) * quantity
value['last_cost'] = product_.last_cost
except:
value['cost_w_tax'] = 0
value['cost_unit_w_tax'] = 0
value['last_cost'] = 0
try:
m.update(product_.attributes)
except:
pass
try:
value['price_w_tax'] = float(
product_.sale_price_w_tax) * quantity
except:
value['price_w_tax'] = 0
try:
value['section'] = product_.section.name
value['conservation'] = product_.conservation.name
except:
value['conservation'] = None
value['section'] = None
m.update(value)
if not data['grouped']:
report_context['records'] = moves
else:
records = {}
for m in moves:
key = str(m['to_location.']['id']) + \
'_' + str(m['product.']['id'])
try:
records[key]['cost_w_tax'] += m['cost_w_tax']
records[key]['price_w_tax'] += m['price_w_tax']
records[key]['quantity'] += m['quantity']
records[key]['cost_base'] += m['cost_base']
records[key]['last_cost'] = m['last_cost']
except:
records[key] = m
records[key]['shipment.']['reference'] = ''
records[key]['effective_date'] = ''
report_context['records'] = records.values()
report_context['company'] = company
report_context['Decimal'] = Decimal
report_context['kind'] = type_shipment[data['type_shipment']]
return report_context
class Assign(metaclass=PoolMeta):
"Assign Shipment"
__name__ = 'stock.shipment.assign'
split = StateTransition()
@classmethod
def __setup__(cls):
super(Assign, cls).__setup__()
cls.partial.buttons.append(Button('Split', 'split', 'tryton-ok'))
def transition_split(self):
self.split_shipment()
return 'end'
def split_shipment(self):
pool = Pool()
Move = pool.get('stock.move')
Shipment = pool.get(Transaction().context.get('active_model'))
shipment = Shipment(Transaction().context.get('active_id'))
number_reference = shipment.number
if Shipment.__name__ == 'stock.shipment.out':
Move.draft(shipment.inventory_moves)
Move.delete(
[m for m in shipment.inventory_moves if m.state == 'draft'])
shipment, = Shipment.copy([shipment], default={'moves': None})
Shipment.write([shipment], {'reference': number_reference})
Move.write(list(self.partial.moves), {'shipment': str(shipment)})
return 'end'
class ShipmentInternalLoadStock(Wizard):
'Shipment Internal Load Stock'
__name__ = 'stock_co.shipment_internal_load_stock'
start_state = 'create_moves'
create_moves = StateTransition()
def transition_create_moves(self):
Internal = Pool().get('stock.shipment.internal')
active_id = Transaction().context['active_id']
internal = Internal(active_id)
if not internal.moves:
internal.load_current_stock()
return 'end'