trytonpsk-stock_co/product.py

279 lines
11 KiB
Python
Raw Permalink Normal View History

2021-08-26 20:16:55 +02:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
2023-01-21 18:11:49 +01:00
from datetime import date, timedelta, datetime
2022-04-27 00:20:35 +02:00
from trytond.model import fields, ModelSQL, ModelView
2021-08-26 20:16:55 +02:00
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from trytond.transaction import Transaction
2022-04-27 15:23:44 +02:00
from trytond.wizard import Wizard, StateTransition, StateView, Button, StateReport
2021-08-26 20:16:55 +02:00
STATES = {'invisible': (Eval('type') != 'goods')}
TIME_HISTORY = 90 # Days
class Product(metaclass=PoolMeta):
__name__ = 'product.product'
2022-04-27 00:20:35 +02:00
2021-08-28 18:27:55 +02:00
stock_duration = fields.Function(fields.Integer('Stock Duration',
states=STATES), 'get_stock_duration')
2021-08-26 20:16:55 +02:00
stock_level = fields.Function(fields.Selection([
('point_order', 'Point Order'),
('overstock', 'Overstock'),
('exhausted', 'Exhausted'),
('normal', 'Normal'),
], 'Stock Level', states=STATES), 'get_stock_level')
2022-04-27 00:20:35 +02:00
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')
2021-08-26 20:16:55 +02:00
@classmethod
def __setup__(cls):
super(Product, cls).__setup__()
2022-04-27 00:20:35 +02:00
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
2021-08-28 18:27:55 +02:00
def get_stock_duration(self, name):
2021-08-26 20:16:55 +02:00
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
2022-03-03 14:41:01 +01:00
2023-01-21 18:11:49 +01:00
@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
2023-01-21 18:11:49 +01:00
@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)
2022-03-03 14:41:01 +01:00
class Template(metaclass=PoolMeta):
__name__ = 'product.template'
2022-03-03 17:33:36 +01:00
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)
2022-04-27 00:20:35 +02:00
return super(Template, cls).copy(records, default=default)
class AverageCost(ModelSQL, ModelView):
"Average Cost"
__name__ = "product.average_cost"
2023-04-20 01:08:34 +02:00
product = fields.Many2One('product.product', 'Product', required=True,
domain=[('template.type', '=', 'goods')], readonly=True
2022-04-27 00:20:35 +02:00
)
2023-04-20 01:08:34 +02:00
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)
2022-04-27 00:20:35 +02:00
@classmethod
def __setup__(cls):
super(AverageCost, cls).__setup__()
cls._order.insert(0, ('effective_date', 'DESC'))
2022-04-27 15:23:44 +02:00
2022-05-13 21:26:29 +02:00
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")
2023-01-26 20:54:19 +01:00
2022-05-13 21:26:29 +02:00
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'
2022-04-27 15:23:44 +02:00
# 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'