mirror of
https://bitbucket.org/presik/trytonpsk-stock_co.git
synced 2023-12-14 05:43:05 +01:00
573 lines
20 KiB
Python
573 lines
20 KiB
Python
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
# this repository contains the full copyright notices and license terms.
|
|
from 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')
|
|
|
|
@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):
|
|
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,
|
|
}
|
|
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'])
|
|
]
|
|
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'])
|
|
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'
|