trytonpsk-stock_co/product.py

279 lines
11 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, timedelta, datetime
from trytond.model import fields, ModelSQL, ModelView
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from trytond.transaction import Transaction
from trytond.wizard import Wizard, StateTransition, StateView, Button, StateReport
STATES = {'invisible': (Eval('type') != 'goods')}
TIME_HISTORY = 90 # Days
class Product(metaclass=PoolMeta):
__name__ = 'product.product'
stock_duration = fields.Function(fields.Integer('Stock Duration',
states=STATES), 'get_stock_duration')
stock_level = fields.Function(fields.Selection([
('point_order', 'Point Order'),
('overstock', 'Overstock'),
('exhausted', 'Exhausted'),
('normal', 'Normal'),
], 'Stock Level', states=STATES), 'get_stock_level')
avg_cost_price = fields.Function(fields.Numeric("Average Cost Price",
required=True, digits=(16, 4)), 'get_avg_cost_price')
amount_cost = fields.Function(fields.Numeric("Amiunt Cost",
required=True, digits=(16, 4)), 'get_amount_cost')
@classmethod
def __setup__(cls):
super(Product, cls).__setup__()
def get_amount_cost(self, name=None):
if self.avg_cost_price and self.quantity:
return float(self.avg_cost_price) * self.quantity
return
def get_avg_cost_price(self, name=None):
target_date = date.today()
stock_date_end = Transaction().context.get('stock_date_end')
if stock_date_end:
target_date = stock_date_end
AverageCost = Pool().get('product.average_cost')
avg_product = AverageCost.search([
('product', '=', self.id),
('effective_date', '<=', target_date),
], order=[('effective_date', 'DESC')], limit=1)
if avg_product:
return avg_product[0].cost_price
else:
return self.cost_price
def get_stock_duration(self, name):
res = None
if Transaction().context.get('stock_date_end'):
today = Transaction().context.get('stock_date_end')
else:
today = date.today()
start_date = today - timedelta(TIME_HISTORY)
Move = Pool().get('stock.move')
domain = [
('effective_date', '>=', start_date),
('effective_date', '<=', today),
('state', '=', 'done'),
('product', '=', self.id),
('to_location.type', 'in', ['customer', 'production']),
]
if Transaction().context.get('locations'):
domain.append(('from_location', 'in', Transaction().context.get('locations')))
move_history_quantity = 0
real_historical_time = None
for move in Move.search(domain, order=[('effective_date', 'ASC')]):
move_history_quantity += move.quantity
if not real_historical_time:
real_historical_time = (today-move.effective_date).days
if move_history_quantity > 0:
res = int((real_historical_time * self.quantity) / move_history_quantity)
return res
def get_stock_level(self, name):
res = 'normal'
#FIXME
"""
if self.max_stock and self.quantity >= self.max_stock:
res = 'overstock'
elif self.min_stock and self.quantity <= self.min_stock:
if self.quantity >= (self.min_stock * 0.5):
res = 'point_order'
else:
res = 'exhausted'
"""
return res
@classmethod
def get_quantity(cls, products, name):
context = Transaction().context
if context.get('stock_date_end') and not isinstance(context['stock_date_end'], date):
context['stock_date_end'] = datetime.strptime(context['stock_date_end'], "%Y-%m-%d").date()
with Transaction().set_context(context=context):
return super(Product, cls).get_quantity(products, name)
# remove this function for new version api-fast>=0.1.2
@classmethod
def search_quantity(cls, name, domain=None):
context = Transaction().context
if context.get('stock_date_end') and not isinstance(context['stock_date_end'], date):
context['stock_date_end'] = datetime.strptime(context['stock_date_end'], "%Y-%m-%d").date()
with Transaction().set_context(context=context):
return super(Product, cls).search_quantity(name, domain)
class Template(metaclass=PoolMeta):
__name__ = 'product.template'
positions = fields.One2Many('product_template.position','template', 'Positions')
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
else:
default = default.copy()
default.setdefault('positions', None)
return super(Template, cls).copy(records, default=default)
class AverageCost(ModelSQL, ModelView):
"Average Cost"
__name__ = "product.average_cost"
product = fields.Many2One('product.product', 'Product', required=True,
domain=[('template.type', '=', 'goods')], readonly=True
)
effective_date = fields.Date('Effective Date', required=True,
readonly=True)
cost_price = fields.Numeric("Cost Price", required=True, digits=(16, 4),
readonly=True)
stock_move = fields.Many2One('stock.move', 'Move', readonly=True)
@classmethod
def __setup__(cls):
super(AverageCost, cls).__setup__()
cls._order.insert(0, ('effective_date', 'DESC'))
class ChangeUdmProductStart(ModelView):
'Change Udm Product Start'
__name__ = 'stock_co.change_udm_product.start'
udm = fields.Many2One('product.uom', "UOM", required=True,
help="If change the uom in product, this update the uom in sales, purchases \b \
invoices and moves stock")
class ChangeUdmProduct(Wizard):
'Change Udm Product'
__name__ = 'stock_co.change_udm_product'
start = StateView('stock_co.change_udm_product.start',
'stock_co.change_udm_product_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Accept', 'accept', 'tryton-ok', default=True),
])
accept = StateTransition()
def transition_accept(self):
pool = Pool()
cursor = Transaction().connection.cursor()
Template = pool.get('product.template')
Sale = pool.get('sale.line')
Purchase = pool.get('purchase.line')
Invoice = pool.get('account.invoice.line')
Move = pool.get('stock.move')
sale = Sale.__table__()
purchase = Purchase.__table__()
invoice = Invoice.__table__()
move = Move.__table__()
template = Template.__table__()
product_template = Template(Transaction().context.get('active_id'))
product_id = product_template.products[0].id
uom_update = self.start.udm.id
sale_uom = product_template.sale_uom.id
purchase_uom = product_template.purchase_uom.id
default_uom = product_template.default_uom.id
cursor.execute(*sale.update(
[sale.unit], [uom_update],
where=(sale.unit == sale_uom) & (sale.product == product_id)))
cursor.execute(*purchase.update(
[purchase.unit], [uom_update],
where=(purchase.unit == purchase_uom) & (purchase.product == product_id)))
cursor.execute(*invoice.update(
[invoice.unit], [uom_update],
where=(invoice.unit == purchase_uom)
& (invoice.invoice_type == 'in')
& (invoice.product == product_id)))
cursor.execute(*invoice.update(
[invoice.unit], [uom_update],
where=(invoice.unit == sale_uom)
& (invoice.invoice_type == 'out')
& (invoice.product == product_id)))
cursor.execute(*move.update(
[move.uom], [uom_update],
where=(move.uom == default_uom) & (move.product == product_id)))
cursor.execute(*template.update(
[template.default_uom, template.purchase_uom, template.sale_uom],
[uom_update, uom_update, uom_update],
where=(template.id == product_template.id)))
return 'end'
# class UpdateAverageCosts(Wizard):
# 'Update Average Costs'
# __name__ = 'stock_co.update_average_costs'
# start_state = 'update_records'
# update_records = StateTransition()
#
# def transition_update_records(self):
# pool = Pool()
# Product = pool.get('product.product')
# Move = pool.get('stock.move')
# AverageCost = pool.get('product.average_cost')
# records = AverageCost.search_read([], fields_names=['product'])
# product_ids = [rec.product for rec in records]
# products = Product.search([
# ('id', 'not in', product_ids)
# ('template.type', '=', 'goods')
# ], limit=10)
# for product in products:
# print('Name: ', product.rec_name)
# moves = Move.search([
# 'OR', [
# ('product', '=', product.id),
# ('from_location.type', 'in', ['supplier', 'lost_found', 'customer']),
# ], [
# ('product', '=', product.id),
# ('to_location.type', 'in', ['customer', 'supplier']),
# ]
# ], order=[('effective_date', 'ASC')])
#
#
#
# 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'