mirror of
https://bitbucket.org/presik/trytonpsk-stock_co.git
synced 2023-12-14 05:43:05 +01:00
278 lines
11 KiB
Python
Executable file
278 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'
|