trytonpsk-stock_co/stock.py

1165 lines
47 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.
2022-01-18 22:23:18 +01:00
import copy
2021-12-29 17:41:02 +01:00
from datetime import date, datetime, timedelta
2021-08-28 18:27:55 +02:00
from decimal import Decimal
2021-08-26 20:16:55 +02:00
from trytond.model import fields, ModelView
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval, Not, Bool
2023-12-04 01:28:55 +01:00
from trytond.wizard import Wizard, StateView, StateReport, Button, StateTransition
2021-08-26 20:16:55 +02:00
from trytond.report import Report
from trytond.transaction import Transaction
2023-12-04 01:28:55 +01:00
from trytond.pyson import Eval, Equal, If, In
2023-10-24 21:58:28 +02:00
2021-08-26 20:16:55 +02:00
STATES = {'invisible': (Eval('type') != 'goods')}
STATES_MOVE = {
'readonly': Eval('state').in_(['cancel', 'assigned', 'done']),
}
class Lot(metaclass=PoolMeta):
__name__ = 'stock.lot'
active = fields.Boolean('Active')
@staticmethod
def default_active():
return True
2021-08-26 20:16:55 +02:00
class Move(metaclass=PoolMeta):
__name__ = 'stock.move'
2022-08-30 19:18:05 +02:00
description = fields.Char('Description')
2021-08-26 20:16:55 +02:00
current_stock = fields.Function(fields.Float('Current Stock',
depends=['product']), 'on_change_with_current_stock')
2021-12-09 20:10:33 +01:00
reference = fields.Function(fields.Char('Reference',
depends=['product'], help='reference of product'), 'get_reference')
2021-08-26 20:16:55 +02:00
2021-09-25 07:02:37 +02:00
@fields.depends('current_stock')
2021-08-26 20:16:55 +02:00
def on_change_product(self, name=None):
super(Move, self).on_change_product()
self.current_stock = self.on_change_with_current_stock()
2021-12-09 20:10:33 +01:00
@fields.depends('product')
def get_reference(self, name=None):
reference = None
if self.product and hasattr(self.product, 'reference') and self.product.reference:
reference = self.product.reference
return reference
2021-08-26 20:16:55 +02:00
@fields.depends('product', 'from_location', 'to_location')
def on_change_with_current_stock(self, name=None):
res = 0
location = None
2022-01-24 15:57:35 +01:00
if self.from_location and self.from_location.type == 'storage' and self.from_location.parent:
2021-08-26 20:16:55 +02:00
location = self.from_location.parent.storage_location
2022-01-24 15:57:35 +01:00
elif self.to_location and self.to_location.type == 'storage' and self.to_location.parent:
2021-08-26 20:16:55 +02:00
location = self.to_location.parent.storage_location
if self.product and location:
context = {
'location_ids': [location.id],
'stock_date_end': date.today(),
}
with Transaction().set_context(context):
res_dict = self.product._get_quantity(
2021-09-25 07:02:37 +02:00
[self.product],
'quantity',
[location.id],
grouping_filter=([self.product.id],)
)
2021-08-26 20:16:55 +02:00
if res_dict.get(self.product.id):
res += res_dict[self.product.id]
2022-02-18 16:22:54 +01:00
return res
2021-08-26 20:16:55 +02:00
2022-02-21 18:27:26 +01:00
@fields.depends('product')
def on_change_with_description(self, name=None):
res = ''
2022-10-11 17:50:04 +02:00
if self.product and self.product.description:
2022-02-21 18:27:26 +01:00
description = self.product.description
res = description.rstrip('\n')
return res
2022-04-27 00:20:35 +02:00
@classmethod
def do(cls, moves):
super(Move, cls).do(moves)
for move in moves:
2023-09-14 22:50:19 +02:00
product = move.product
2022-10-10 23:46:59 +02:00
if not move.description:
move.description = cls.on_change_with_description(move)
move.save()
2022-06-08 00:35:08 +02:00
# remove origin 'stock.inventory.line' for set average cost
if move.origin and move.origin.__name__ in ('purchase.line'):
2023-09-14 22:50:19 +02:00
if product.cost_price_method == 'fixed':
product.cost_price = move.unit_price
product.save()
2022-04-27 00:20:35 +02:00
move.set_average_cost()
2023-11-29 15:40:52 +01:00
if hasattr(product, 'standard_margin') and product.standard_margin and product.standard_margin > 0 and move.product.cost_price > 0:
template = product.template
percentage_calculation = round(move.product.cost_price * Decimal(move.product.standard_margin/100), 4)
template.list_price = product.cost_price + percentage_calculation
template.on_change_list_price()
template.save()
2022-04-27 00:20:35 +02:00
def set_average_cost(self):
AverageCost = Pool().get('product.average_cost')
data = {
"stock_move": self.id,
"product": self.product.id,
"effective_date": self.effective_date,
"cost_price": self.product.cost_price,
}
AverageCost.create([data])
2021-08-26 20:16:55 +02:00
class MoveByProductStart(ModelView):
'Move By Product Start'
__name__ = 'stock_co.move_by_product.start'
start_date = fields.Date('Start Date', required=True)
end_date = fields.Date('End Date', required=True)
from_location = fields.Many2One('stock.location', 'From Location')
to_location = fields.Many2One('stock.location', 'To Location',
required=True)
categories = fields.Many2Many('product.category', None, None, 'Categories')
company = fields.Many2One('company.company', 'Company',
required=True)
party = fields.Many2One('party.party', 'Party')
shipment_draft = fields.Boolean('Shipment Draft')
start_time = fields.Time('Start Time',)
end_time = fields.Time('End Time', states={
'required': Bool(Eval('start_time')),
'invisible': Not(Bool(Eval('start_time'))),
}, depends=['start_time'])
product = fields.Many2One('product.product', 'Product')
brand = fields.Many2One('product.brand', 'Brand')
2021-08-26 20:16:55 +02:00
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_end_date():
Date_ = Pool().get('ir.date')
return Date_.today()
@staticmethod
def default_start_date():
Date_ = Pool().get('ir.date')
return Date_.today()
class PrintMoveByProduct(Wizard):
'Move By Product'
__name__ = 'stock_co.print_move_by_product'
start = StateView('stock_co.move_by_product.start',
'stock_co.print_move_by_product_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-print', default=True),
])
print_ = StateReport('stock_co.move_by_product')
def do_print_(self, action):
category_ids = []
from_location_id = None
brand_id = None
party_id = None
start_time = None
end_time = None
if self.start.from_location:
from_location_id = self.start.from_location.id
if self.start.categories:
category_ids = [acc.id for acc in self.start.categories]
if self.start.brand:
2021-08-26 20:16:55 +02:00
brand_id = self.start.brand.id
if self.start.party:
party_id = self.start.party.id
if self.start.start_time:
start_time = self.start.start_time
if self.start.end_time:
end_time = self.start.end_time
data = {
'company': self.start.company.id,
'from_location': from_location_id,
'to_location': self.start.to_location.id,
'start_date': self.start.start_date,
'end_date': self.start.end_date,
'categories': category_ids,
'brand': brand_id,
'party': party_id,
'shipment_draft': self.start.shipment_draft,
'start_time': start_time,
'end_time': end_time,
'product': self.start.product.id if self.start.product else None
}
return action, data
def transition_print_(self):
return 'end'
class MoveByProduct(Report):
'Move By Product Report'
__name__ = 'stock_co.move_by_product'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
Move = pool.get('stock.move')
Location = pool.get('stock.location')
Party = pool.get('party.party')
Category = pool.get('product.template-product.category')
company = Company(data['company'])
from_location = None
brand_name = ''
party_name = ''
start_date = None
end_date = None
categories = Category.search([('category', 'in', data['categories'])])
products_ids = [c.template.id for c in categories]
dom_products = []
if data['start_time']:
start_date = datetime.combine(data['start_date'], data['start_time'])
end_date = datetime.combine(data['end_date'], data['end_time'])
_start_date = Company.convert_timezone(start_date, True)
_end_date = Company.convert_timezone(end_date, True)
dom_products.append(
('to_location.id', '=', data['to_location']),
('create_date', '>=', _start_date),
('create_date', '<=', _end_date)
)
else:
start_date = data['start_date']
end_date = data['end_date']
dom_products.extend([
('to_location', '=', data['to_location']),
['AND',
['OR', [
('planned_date', '>=', start_date),
('planned_date', '<=', end_date),
], [
('effective_date', '>=', start_date),
('effective_date', '<=', end_date),
],
]]
])
if data['shipment_draft']:
dom_products.append(('state', '=', 'draft'))
if data['from_location']:
dom_products.append(
('from_location.id', '=', data['from_location']),
)
from_location = Location(data['from_location'])
if data['categories']:
if products_ids:
dom_products.append(
('product.template', 'in', products_ids)
)
else:
dom_products.append(
('product.template.account_category', 'in', data['categories'])
)
if data['brand']:
Brand = pool.get('product.brand')
2021-08-26 20:16:55 +02:00
dom_products.append(
('product.template.brand', '=', data['brand']),
)
brand_name = Brand(data['brand']).name
if data['party']:
dom_products.append(
('invoice_lines.invoice.party', '=', data['party']),
)
party_name = Party(data['party']).name
if data['product']:
dom_products.append(
('product', '=', data['product']),
)
moves = Move.search([dom_products])
products = {}
Location = pool.get('stock.location')
location = Location(data['to_location'])
2021-08-26 20:16:55 +02:00
for move in moves:
product = move.product
amount = move.quantity * float(product.cost_price)
origin = None
2023-09-23 19:39:30 +02:00
id_dict = product.id
if location.type == 'customer':
2023-09-23 19:39:30 +02:00
id_dict = move.origin.sale.party.name + str(product.id)
origin = move.origin.sale.party.name if move.origin else None
2021-08-26 20:16:55 +02:00
try:
2023-09-23 19:39:30 +02:00
row = products[id_dict]
row[4].append(move.quantity)
row[6].append(amount)
2021-08-26 20:16:55 +02:00
except Exception as e:
template = product.template
brand = template.brand and template.brand.name
category = template.account_category and template.account_category.name
effective_date = move.effective_date
2023-09-23 19:39:30 +02:00
products[id_dict] = [
2021-08-26 20:16:55 +02:00
product.code,
origin,
2021-08-26 20:16:55 +02:00
product.rec_name,
template.default_uom.symbol,
[move.quantity],
product.cost_price,
[amount],
category,
brand,
template.list_price,
effective_date,
2021-08-26 20:16:55 +02:00
]
2023-09-23 19:39:30 +02:00
2021-08-26 20:16:55 +02:00
report_context['records'] = products.values()
report_context['from_location'] = from_location
report_context['to_location'] = location
report_context['customer_location'] = location.type == 'customer'
2021-08-26 20:16:55 +02:00
report_context['start_date'] = start_date
report_context['end_date'] = end_date
report_context['company'] = company
report_context['brand_name'] = brand_name
report_context['party_name'] = party_name
return report_context
class WarehouseStockStart(ModelView):
'Warehouse Stock Start'
__name__ = 'stock_co.warehouse_stock.start'
company = fields.Many2One('company.company', 'Company', required=True)
locations = fields.Many2Many('stock.location', None, None, "Locations",
domain=[
('type', 'in', ['storage', 'customer']),
('active', '=', True)
], required=True)
to_date = fields.Date('To Date', required=True)
only_minimal_level = fields.Boolean('Only Minimal Level')
group_by_location = fields.Boolean('Group By Location')
2022-01-12 15:35:56 +01:00
group_by_supplier = fields.Boolean('Group By Supplier')
2021-08-26 20:16:55 +02:00
category = fields.Many2One('product.category', 'Category')
2022-01-12 15:35:56 +01:00
suppliers = fields.Many2Many('party.party', None, None, "Suppliers",
domain=[
('active', '=', True)
])
2021-09-24 05:16:20 +02:00
zero_quantity = fields.Boolean('Zero Quantity')
2021-08-26 20:16:55 +02:00
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_to_date():
Date_ = Pool().get('ir.date')
return Date_.today()
class WarehouseStock(Wizard):
'Warehouse Stock'
__name__ = 'stock_co.warehouse_stock'
start = StateView('stock_co.warehouse_stock.start',
'stock_co.warehouse_stock_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('stock_co.warehouse_stock.report')
def do_print_(self, action):
category_id = None
if self.start.category:
category_id = self.start.category.id
data = {
'company': self.start.company.id,
'locations': [l.id for l in self.start.locations],
'location_names': ', '.join([l.name for l in self.start.locations]),
'only_minimal_level': self.start.only_minimal_level,
'group_by_location': self.start.group_by_location,
2022-01-12 15:35:56 +01:00
'group_by_supplier': self.start.group_by_supplier,
2021-09-24 05:16:20 +02:00
'zero_quantity': self.start.zero_quantity,
2021-08-26 20:16:55 +02:00
'to_date': self.start.to_date,
'category': category_id,
2022-01-12 15:35:56 +01:00
'suppliers': [l.id for l in self.start.suppliers],
2021-08-26 20:16:55 +02:00
}
return action, data
class WarehouseReport(Report):
'Warehouse Report'
__name__ = 'stock_co.warehouse_stock.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
Product = pool.get('product.product')
OrderPoint = pool.get('stock.order_point')
Location = pool.get('stock.location')
ids_location = data['locations']
locations = Location.browse(data['locations'])
2023-03-27 15:44:21 +02:00
Move = pool.get('stock.move')
2021-08-26 20:16:55 +02:00
dom_products = [
('active', '=', True),
('template.active', '=', True),
('type', '=', 'goods'),
('consumable', '=', False),
]
stock_context = {
'stock_date_end': data['to_date'],
'locations': ids_location,
}
if data['category']:
dom_products.append(['AND', ['OR', [
('account_category', '=', data['category']),
], [
('categories', 'in', [data['category']]),
],
]])
2021-09-24 05:16:20 +02:00
if not data['zero_quantity']:
2021-08-26 20:16:55 +02:00
dom_products.append([('quantity', '!=', 0)])
if data['only_minimal_level']:
locations = Location.search([('id', 'in', ids_location)])
location_parents = [location.parent.id for location in locations]
order_points_wl = OrderPoint.search([
('warehouse_location', 'in', set(location_parents)),
2021-08-26 20:16:55 +02:00
('type', '=', 'purchase'),
])
min_quantities = {op.product.id: op.min_quantity for op in order_points_wl}
order_points_sl = OrderPoint.search([
('storage_location', 'in', ids_location),
('type', '=', 'internal'),
])
min_quantities.update({op.product.id: op.min_quantity for op in order_points_sl})
2021-08-26 20:16:55 +02:00
products_ids = min_quantities.keys()
dom_products.append(('id', 'in', products_ids))
2022-01-12 15:35:56 +01:00
if data['suppliers']:
2022-01-14 00:00:08 +01:00
dom_products.append([('template.product_suppliers.party', 'in', data['suppliers'])])
2021-08-26 20:16:55 +02:00
total_amount = 0
total_amount_imp = 0
2021-08-26 20:16:55 +02:00
values = {}
products = []
if data['group_by_location']:
for l in locations:
2023-09-12 00:16:57 +02:00
days = []
2021-08-26 20:16:55 +02:00
stock_context['locations'] = [l.id]
with Transaction().set_context(stock_context):
prdts = Product.search(dom_products, order=[('code', 'ASC')])
2022-01-12 15:35:56 +01:00
suppliers = {}
2023-03-27 15:44:21 +02:00
for p in prdts:
move = Move.search([
('to_location', '=', l.id),
('product', '=', p.id),
2023-04-03 15:31:01 +02:00
('state', '=', 'done'),
], order=[('effective_date', 'DESC')])
2023-09-12 00:16:57 +02:00
date_start = move[0].effective_date if len(move) > 0 else None
2023-03-27 15:44:21 +02:00
date_now = date.today()
2023-05-29 16:43:10 +02:00
date_result = date_now - date_start if date_start else 0
days.append(date_result.days) if date_start else days.append('error')
2022-01-12 15:35:56 +01:00
if data['group_by_supplier']:
for p in prdts:
if not p.template.product_suppliers:
continue
2022-01-14 00:00:08 +01:00
for prod_sup in p.template.product_suppliers:
sup_id = prod_sup.party.id
2022-01-13 18:38:19 +01:00
try:
suppliers[sup_id]['products'].append(p)
2022-05-14 17:09:14 +02:00
suppliers[sup_id]['total_amount'].append(p.amount_cost if p.amount_cost else 0)
suppliers[sup_id]['total_amount_imp'].append(p.extra_tax * Deciaml(p.quantity) if p.extra_tax else 0)
2022-01-13 18:38:19 +01:00
except:
suppliers[sup_id] = {}
suppliers[sup_id]['products'] = [p]
2022-01-14 00:00:08 +01:00
suppliers[sup_id]['party'] = prod_sup.party
2022-05-14 17:09:14 +02:00
suppliers[sup_id]['total_amount'] = [p.amount_cost if p.amount_cost else 0]
suppliers[sup_id]['total_amount_imp'] = [p.extra_tax * Decimal(p.quantity) if p.extra_tax else 0]
2022-04-27 00:20:35 +02:00
total_amount = sum([p.amount_cost for p in prdts if p.amount_cost])
total_amount_imp = sum([p.extra_tax * Decimal(p.quantity) for p in prdts if p.extra_tax])
2021-08-26 20:16:55 +02:00
values[l.id] = {
'name': l.name,
'products': prdts,
2023-03-27 15:44:21 +02:00
'days': days,
2022-01-12 15:35:56 +01:00
'suppliers': suppliers.values(),
'total_amount': total_amount,
'total_amount_imp': total_amount_imp
2021-08-26 20:16:55 +02:00
}
products = values.values()
else:
with Transaction().set_context(stock_context):
products = Product.search(dom_products, order=[('code', 'ASC')])
2022-01-12 15:35:56 +01:00
if data['only_minimal_level']:
products = [p for p in products if p.quantity <= min_quantities[p.id]]
2022-04-27 00:20:35 +02:00
total_amount = sum([p.amount_cost for p in products if p.amount_cost])
total_amount_imp = sum([p.extra_tax * Decimal(p.quantity) for p in products if p.extra_tax])
2022-01-12 15:35:56 +01:00
suppliers = {}
if data['group_by_supplier']:
for p in products:
if not p.template.product_suppliers:
continue
2022-01-14 00:00:08 +01:00
for prod_sup in p.template.product_suppliers:
sup_id = prod_sup.party.id
2022-01-13 18:38:19 +01:00
try:
suppliers[sup_id]['products'].append(p)
2022-05-14 17:05:15 +02:00
suppliers[sup_id]['total_amount'].append(p.amount_cost if p.amount_cost else 0)
suppliers[sup_id]['total_amount_imp'].append(p.extra_tax * Decimal(p.quantity) if p.extra_tax else 0)
2022-01-13 18:38:19 +01:00
except:
suppliers[sup_id] = {}
suppliers[sup_id]['products'] = [p]
2022-01-14 00:00:08 +01:00
suppliers[sup_id]['party'] = prod_sup.party
2022-05-14 17:05:15 +02:00
suppliers[sup_id]['total_amount'] = [p.amount_cost if p.amount_cost else 0]
suppliers[sup_id]['total_amount_imp'] = [p.extra_tax * Decimal(p.quantity) if p.extra_tax else 0]
2022-01-12 15:35:56 +01:00
products = suppliers.values()
2021-08-26 20:16:55 +02:00
2021-09-13 20:14:58 +02:00
cursor = Transaction().connection.cursor()
2023-03-27 15:44:21 +02:00
# query = "select distinct on(p.id) p.id, t.name, p.code, s.effective_date from product_product as p right join stock_move as s on p.id=s.product join product_template as t on p.template=t.id where (date_part('day', TIMESTAMP '{}') - date_part('day', s.effective_date))>45 and s.shipment ilike 'stock.shipment.in,%' and state='done' order by p.id, s.effective_date DESC;".format(data['to_date'])
2021-09-13 20:14:58 +02:00
query = "select distinct on(p.id) p.id, t.name, p.code, s.effective_date from product_product as p right join stock_move as s on p.id=s.product join product_template as t on p.template=t.id where s.shipment ilike 'stock.shipment.in,%' and state='done' order by p.id, s.effective_date DESC;"
2023-03-27 15:44:21 +02:00
2021-09-13 20:14:58 +02:00
cursor.execute(query)
columns = list(cursor.description)
result = cursor.fetchall()
2021-09-24 05:16:20 +02:00
last_purchase = {}
2021-09-13 20:14:58 +02:00
for row in result:
row_dict = {}
for i, col in enumerate(columns):
row_dict[col.name] = row[i]
2021-09-24 05:16:20 +02:00
last_purchase[row[0]] = row_dict
2021-09-13 20:14:58 +02:00
2021-08-26 20:16:55 +02:00
report_context['group_by_location'] = data['group_by_location']
2022-01-12 15:35:56 +01:00
report_context['group_by_supplier'] = data['group_by_supplier']
2021-08-26 20:16:55 +02:00
report_context['records'] = products
report_context['total_amount'] = total_amount
report_context['total_amount_imp'] = total_amount_imp
2021-09-24 05:16:20 +02:00
report_context['last_purchase'] = last_purchase
2021-08-26 20:16:55 +02:00
report_context['location'] = data['location_names']
report_context['stock_date_end'] = data['to_date']
report_context['company'] = Company(data['company'])
return report_context
class PrintProductsStart(ModelView):
'Products Start'
__name__ = 'stock_co.print_products.start'
company = fields.Many2One('company.company', 'Company', required=True)
category = fields.Many2One('product.category', 'Category')
@staticmethod
def default_company():
return Transaction().context.get('company')
class PrintProducts(Wizard):
'Warehouse Stock'
__name__ = 'stock_co.print_products'
start = StateView('stock_co.print_products.start',
'stock_co.print_products_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('stock_co.print_products.report')
def do_print_(self, action):
category_id = None
if self.start.category:
category_id = self.start.category.id
data = {
'company': self.start.company.id,
'category': category_id,
}
return action, data
class PrintProductsReport(Report):
'Warehouse Report'
__name__ = 'stock_co.print_products.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
Product = pool.get('product.product')
Date_ = Pool().get('ir.date')
dom_products = [
('active', '=', True),
('type', '=', 'goods'),
('consumable', '=', False),
]
if data['category']:
dom_products.append(('account_category', '=', data['category']))
products = Product.search(dom_products, order=[('code', 'ASC')])
report_context['records'] = products
report_context['stock_date_end'] = Date_.today()
report_context['company'] = Company(data['company'])
return report_context
2021-08-28 18:27:55 +02:00
class WarehouseStockDetailedStart(ModelView):
'Warehouse Stock Detailed Start'
__name__ = 'stock_co.warehouse_stock_detailed.start'
company = fields.Many2One('company.company', 'Company', required=True)
locations = fields.Many2Many('stock.location', None, None, "Locations",
domain=[
('id', 'in', Eval('location_storage')),
('active', '=', True)
], depends=['location_storage'])
location_storage = fields.One2Many('stock.location', None, 'Locations Storage')
to_date = fields.Date('To Date', required=True)
2022-04-18 20:15:11 +02:00
lot = fields.Boolean('Grouping Lot')
2021-08-28 18:27:55 +02:00
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_to_date():
Date_ = Pool().get('ir.date')
return Date_.today()
@staticmethod
def default_location_storage():
Location = Pool().get('stock.location')
locations = Location.search([('type', '=', 'warehouse')])
2021-12-29 21:48:04 +01:00
return [l.storage_location.id for l in locations if l.storage_location.id]
2021-08-28 18:27:55 +02:00
class WarehouseStockDetailed(Wizard):
'Warehouse Stock Detailed'
__name__ = 'stock_co.warehouse_stock_detailed'
start = StateView('stock_co.warehouse_stock_detailed.start',
'stock_co.warehouse_stock_detailed_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('stock_co.warehouse_stock_detailed.report')
def do_print_(self, action):
location_ids = None
if self.start.locations:
location_ids = [l.id for l in self.start.locations]
data = {
'company': self.start.company.id,
'locations': location_ids,
'to_date': self.start.to_date,
2022-04-18 20:15:11 +02:00
'lot': self.start.lot,
2021-08-28 18:27:55 +02:00
}
return action, data
class WarehouseStockDetailedReport(Report):
'Warehouse Stock Detailed Report'
__name__ = 'stock_co.warehouse_stock_detailed.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
Product = pool.get('product.product')
Location = pool.get('stock.location')
2022-04-18 20:15:11 +02:00
LotbyLocation = pool.get('stock.lots_by_locations')
2021-08-28 18:27:55 +02:00
if data['locations']:
ids_location = data['locations']
locations = Location.browse(ids_location)
else:
locations = Location.search([
('type', '=', 'warehouse')
])
ids_location = [l.storage_location.id for l in locations if l.storage_location]
dom_products = [
('active', '=', True),
('template.active', '=', True),
('type', '=', 'goods'),
('consumable', '=', False),
]
stock_context = {
'stock_date_end': data['to_date'],
'locations': ids_location,
}
# dom_products.append(('account_category', '=', data['category']))
2021-08-30 22:39:59 +02:00
cursor = Transaction().connection.cursor()
query = "select distinct on(p.id) p.id, t.name, p.code, t.reference, s.effective_date from product_product as p right join stock_move as s on p.id=s.product join product_template as t on p.template=t.id where s.shipment ilike 'stock.shipment.in,%' and state='done' order by p.id, s.effective_date DESC"
2021-08-30 22:39:59 +02:00
cursor.execute(query)
columns = list(cursor.description)
result = cursor.fetchall()
2021-09-24 05:16:20 +02:00
last_purchase = {}
2021-08-30 22:39:59 +02:00
for row in result:
row_dict = {}
for i, col in enumerate(columns):
row_dict[col.name] = row[i]
2021-09-24 05:16:20 +02:00
last_purchase[row[0]] = row_dict
2021-08-30 22:39:59 +02:00
2021-08-28 18:27:55 +02:00
values = []
add_ = values.append
2021-08-28 18:27:55 +02:00
for l in locations:
stock_context['locations'] = [l.id]
with Transaction().set_context(stock_context):
prdts = Product.search(dom_products, order=[('code', 'ASC')])
2022-04-18 20:15:11 +02:00
if data['lot']:
prd_ids = [p.id for p in prdts]
prdts = LotbyLocation.search([('product' , 'in', prd_ids)])
2021-08-28 18:27:55 +02:00
for p in prdts:
add_({
2021-08-28 18:27:55 +02:00
'parent_name': l.parent.name if l.parent else '',
'name': l.name,
2022-04-18 20:15:11 +02:00
'product': p.product if data['lot'] else p,
'lot': p if data['lot'] else None,
2021-08-28 18:27:55 +02:00
})
2021-08-30 22:39:59 +02:00
2021-08-28 18:27:55 +02:00
products = values
report_context['records'] = products
report_context['Decimal'] = Decimal
2021-09-24 05:16:20 +02:00
report_context['last_purchase'] = last_purchase
2021-08-28 18:27:55 +02:00
report_context['stock_date_end'] = data['to_date']
report_context['company'] = Company(data['company'])
return report_context
2021-12-29 17:41:02 +01:00
class WarehouseKardexStockStart(ModelView):
'Warehouse Kardex Stock Start'
__name__ = 'stock_co.warehouse_kardex_stock.start'
company = fields.Many2One('company.company', 'Company', required=True)
2022-02-07 23:48:18 +01:00
locations = fields.Many2Many('stock.location', None, None, "Location",
2021-12-29 17:41:02 +01:00
domain=[
('type', 'in', ['warehouse']),
('active', '=', True)
], required=True)
from_date = fields.Date('From Date', required=True)
to_date = fields.Date('To Date', required=True)
2023-10-24 21:58:28 +02:00
detail_by_product = fields.Boolean('Detail By Product')
2021-12-29 17:41:02 +01:00
categories = fields.Many2Many('product.category', None, None, 'Categories')
2023-10-24 21:58:28 +02:00
products = fields.Many2Many('product.product', None, None, 'Products',
domain=[
('active', '=', True),
('template.active', '=', True),
('type', '=', 'goods'),
('consumable', '=', False),
('quantity', '!=', 0),
],
states={'required': Eval('detail_by_product', True)},
depends=['detail_by_product'])
2021-12-29 17:41:02 +01:00
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_to_date():
Date_ = Pool().get('ir.date')
return Date_.today()
class WarehouseKardexStock(Wizard):
'Warehouse Kardex Stock'
__name__ = 'stock_co.warehouse_kardex_stock'
start = StateView('stock_co.warehouse_kardex_stock.start',
'stock_co.warehouse_kardex_stock_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('stock_co.warehouse_kardex_stock.report')
def do_print_(self, action):
data = {
'company': self.start.company.id,
2022-02-07 23:48:18 +01:00
# 'location': self.start.location.id,
2021-12-29 17:41:02 +01:00
'from_date': self.start.from_date,
'to_date': self.start.to_date,
2023-10-24 21:58:28 +02:00
'detail_by_product': self.start.detail_by_product,
2021-12-29 17:41:02 +01:00
'categories': [l.id for l in self.start.categories],
2022-02-07 23:48:18 +01:00
'locations': [l.id for l in self.start.locations],
2023-10-24 21:58:28 +02:00
'products': [l.id for l in self.start.products],
2021-12-29 17:41:02 +01:00
}
return action, data
class WarehouseKardexReport(Report):
'Warehouse Kardex Report'
__name__ = 'stock_co.warehouse_kardex_stock.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
Product = pool.get('product.product')
Location = pool.get('stock.location')
2022-02-07 23:48:18 +01:00
warehouses = Location.browse(data['locations'])
id_locations = [w.storage_location.id for w in warehouses if w.storage_location]
2021-12-29 17:41:02 +01:00
dom_products = [
('active', '=', True),
('template.active', '=', True),
('type', '=', 'goods'),
('consumable', '=', False),
2022-04-08 21:25:14 +02:00
('quantity', '!=', 0),
2021-12-29 17:41:02 +01:00
]
if data['categories']:
dom_products.append(['AND', ['OR', [
('account_category', 'in', data['categories']),
], [
('categories', 'in', data['categories']),
],
]])
stock_context_start = {
'stock_date_end': (data['from_date'] - timedelta(1)),
2022-02-07 23:48:18 +01:00
'locations': id_locations,
2021-12-29 17:41:02 +01:00
}
stock_context_end = {
'stock_date_end': data['to_date'],
2022-02-07 23:48:18 +01:00
'locations': id_locations,
2021-12-29 17:41:02 +01:00
}
2022-01-18 22:23:18 +01:00
products = {}
2023-10-28 17:36:36 +02:00
products_detail = []
2023-10-24 21:58:28 +02:00
fields_names = ['code', 'name', 'brand', 'reference', 'quantity']
2022-01-18 22:23:18 +01:00
# products = {p['id']: p for p in products_start}
# type = {'input': f'and from_location != {output_location} and to_location = {id_location}',
# 'input_dif': f'and from_location = {id_location} and to_location = {input_location}',
# 'output': f'and from_location = {id_location} and to_location != {output_location}',
# 'output_dif': f'and from_location = {output_location} and to_location = {id_location}'
# }
2022-02-07 23:48:18 +01:00
tup_locations = str(tuple(id_locations)).replace(',', '') if len(
id_locations) == 1 else str(tuple(id_locations))
2022-01-18 22:23:18 +01:00
type = {
2022-02-07 23:48:18 +01:00
'input': f'and to_location in {tup_locations}',
'output': f'and from_location in {tup_locations}',
2022-01-18 22:23:18 +01:00
}
2021-12-29 17:41:02 +01:00
moves = {}
2023-10-24 21:58:28 +02:00
if not data['detail_by_product']:
with Transaction().set_context(stock_context_start):
products_start = Product.search_read(dom_products, fields_names=fields_names, order=[('code', 'ASC')])
with Transaction().set_context(stock_context_end):
products_end = Product.search_read(dom_products, fields_names=fields_names, order=[('code', 'ASC')])
for p in products_start:
cls.set_value(p, 'start', products)
for p in products_end:
cls.set_value(p, 'end', products)
for k, v in type.items():
clause = v
2023-10-26 18:17:27 +02:00
query = f"""select s.product as id, p.code, t.name, t.reference, sum(s.quantity) as quantity from stock_move as s
join product_product as p on p.id = s.product
join product_template as t on p.template = t.id
where effective_date >= '{str(data['from_date'])}'
and effective_date <= '{str(data['to_date'])}'
{clause}
group by s.product, p.code, t.name, t.reference ;"""
moves = cls.query_to_dict(query)
for m, v in moves.items():
cls.set_value(v, k, products)
2022-01-18 22:23:18 +01:00
2023-10-24 21:58:28 +02:00
else:
2023-10-28 17:36:36 +02:00
for product in data['products']:
query = f"""select p.id as pid, p.code, t.name, pb.name as brand, t.reference, s.effective_date,
SPLIT_PART(s.origin, ',', 1) as model,
SPLIT_PART(s.origin, ',', 2) as line_id,
s.id as move_id, s.quantity as quantity
2023-10-26 18:17:27 +02:00
from stock_move as s
2023-10-28 17:36:36 +02:00
INNER JOIN product_product as p on p.id = s.product
INNER JOIN product_template as t on p.template = t.id
INNER JOIN product_brand as pb on pb.id = t.brand
2023-10-31 22:08:40 +01:00
where s.effective_date >= '{str(data['from_date'])}'
and s.effective_date <= '{str(data['to_date'])}'
2023-10-28 17:36:36 +02:00
AND p.id IN ({product})
ORDER BY s.effective_date ASC, p.id ASC
"""
2023-10-31 22:08:40 +01:00
# and (s.origin LIKE '%purchase.line%' or s.origin LIKE '%sale.line%')
2023-11-21 23:13:00 +01:00
start, end = cls.get_balances(Product, product, data['from_date'] - timedelta(days=1), id_locations)
2023-10-28 17:36:36 +02:00
products_detail.append(cls.query_to_dict_detail(query, end))
2022-02-07 23:48:18 +01:00
wh_name = ''
for l in warehouses:
2023-10-24 21:58:28 +02:00
wh_name += (l.name + ', ')
2022-01-18 22:23:18 +01:00
report_context['products'] = products.values()
2023-10-28 17:36:36 +02:00
report_context['products_detail'] = products_detail
2023-10-24 21:58:28 +02:00
report_context['detail_by_product'] = data['detail_by_product']
2022-02-07 23:48:18 +01:00
report_context['warehouse'] = wh_name
2021-12-29 17:41:02 +01:00
report_context['company'] = Company(data['company'])
return report_context
2022-01-18 22:23:18 +01:00
@classmethod
def set_value(cls, p, key, products):
defaults = {
'start': 0,
'input': 0,
'output': 0,
'end': 0
}
try:
products[p['id']].update({
key: p['quantity']
})
except:
products[p['id']] = copy.deepcopy(defaults)
products[p['id']].update({
'code': p['code'],
'name': p['name'],
'reference': p['reference'],
key: p['quantity']
})
2023-10-24 21:58:28 +02:00
@classmethod
2023-10-28 17:36:36 +02:00
def set_value_detail(cls, p, key, Product, id_locations):
2023-10-26 22:59:40 +02:00
start, end = cls.get_balances(Product, p['pid'], p['effective_date'], id_locations)
2023-10-24 21:58:28 +02:00
@classmethod
def get_balances(cls, Product, id, effective_data, locations):
dom_products = [('id', '=', id)]
fields_names = ['code', 'name', 'brand', 'reference', 'quantity']
products = {}
stock_context = {
'stock_date_end': effective_data - timedelta(1),
'locations': locations,
}
stock_context_end = {
'stock_date_end': effective_data,
'locations': locations,
}
with Transaction().set_context(stock_context):
2023-10-26 18:17:27 +02:00
products_start = Product.search_read(dom_products, fields_names=fields_names)
2023-10-24 21:58:28 +02:00
with Transaction().set_context(stock_context_end):
2023-10-26 18:17:27 +02:00
products_end = Product.search_read(dom_products, fields_names=fields_names)
2023-10-24 21:58:28 +02:00
for p in products_start:
cls.set_value(p, 'start', products)
for p in products_end:
cls.set_value(p, 'end', products)
return products[id]['start'], products[id]['end']
2021-12-29 17:41:02 +01:00
@classmethod
def query_to_dict(cls, query):
cursor = Transaction().connection.cursor()
cursor.execute(query)
columns = list(cursor.description)
result = cursor.fetchall()
res_dict = {}
for row in result:
row_dict = {}
for i, col in enumerate(columns):
row_dict[col.name] = row[i]
res_dict[row[0]] = row_dict
return res_dict
2023-10-28 17:36:36 +02:00
@classmethod
def query_to_dict_detail(cls, query, end):
pool = Pool()
SaleLine = pool.get('sale.line')
PurchaseLine = pool.get('purchase.line')
2023-10-31 22:08:40 +01:00
StockMove = pool.get('stock.move')
2023-10-28 17:36:36 +02:00
cursor = Transaction().connection.cursor()
cursor.execute(query)
result = cursor.fetchall()
sale_line_ids = []
purchase_line_ids = []
2023-10-31 22:08:40 +01:00
stock_inventory_id = []
2023-10-28 17:36:36 +02:00
for row in result:
if row[6] == 'sale.line':
sale_line_ids.append(row[7])
2023-10-31 22:08:40 +01:00
elif row[6] == 'purchase.line':
2023-10-28 17:36:36 +02:00
purchase_line_ids.append(row[7])
2023-10-31 22:08:40 +01:00
elif row[6] == 'stock.inventory':
stock_inventory_id.append(row[7])
sale_lines = SaleLine.search_read([('id', 'in', sale_line_ids)], fields_names=['sale.party.name', 'sale.number', 'unit_price', 'quantity'])
sale_lines_dict = {sl['id']: (sl['sale.']['party.']['name'], sl['sale.']['number'], sl['unit_price'], sl['quantity']) for sl in sale_lines}
purchase_lines = PurchaseLine.search_read([('id', 'in', purchase_line_ids)], fields_names=['purchase.party.name', 'purchase.number', 'unit_price', 'quantity'])
purchase_lines_dict = {pl['id']: (pl['purchase.']['party.']['name'], pl['purchase.']['number'], pl['unit_price'], pl['quantity']) for pl in purchase_lines}
2023-10-28 17:36:36 +02:00
res = []
balance = end
cost_price = 0
for row in result:
qty_input = 0
qty_output = 0
list_price = 0
party = ''
number = ''
2023-10-31 22:08:40 +01:00
add_item = False
2023-11-21 23:13:00 +01:00
print(row[8], row[0], 'origens')
2023-10-28 17:36:36 +02:00
if row[6] == 'sale.line':
2023-11-21 23:13:00 +01:00
print('paso por if')
2023-10-31 22:08:40 +01:00
party, number, list_price, qty_output = sale_lines_dict[int(row[7])]
2023-10-28 17:36:36 +02:00
balance -= qty_output
2023-10-31 22:08:40 +01:00
add_item = True
elif row[6] == 'purchase.line':
# qty_input = row[9]
party, number, cost_price, qty_input = purchase_lines_dict[int(row[7])]
2023-10-28 17:36:36 +02:00
balance += qty_input
2023-10-31 22:08:40 +01:00
add_item = True
else:
move = StockMove(row[8])
2023-11-21 23:13:00 +01:00
print('paso por else')
2023-11-21 14:46:02 +01:00
if move and (move.from_location.type == 'lost_found' or move.from_location.type == 'customer' or move.from_location.type == 'supplier'):
2023-10-31 22:08:40 +01:00
qty_input = row[9]
balance += qty_input
add_item = True
if add_item:
value = {
'product': row[2],
'code': row[1],
'brand': row[3],
'reference': row[4],
'effective_date': row[5],
'party': party,
'number': number,
'end_balance': balance,
'input': qty_input,
'output': qty_output,
'list_price': list_price,
'cost_price': cost_price,
}
res.append(value)
2023-10-28 17:36:36 +02:00
return res
2023-12-04 01:28:55 +01:00
class CreateOrderPointStart(ModelView):
'Create Order Point Start'
__name__ = 'stock_co.create_order_point.start'
category = fields.Many2One('product.category', 'Category', required=True,
domain=[('accounting', '=', False)])
warehouse_location = fields.Many2One('stock.location',
'Warehouse Location', select=True,
domain=[('type', '=', 'warehouse')],
states={
'invisible': Not(Equal(Eval('type'), 'purchase')),
'required': Equal(Eval('type'), 'purchase'),
},
depends=['type'])
storage_location = fields.Many2One('stock.location', 'Storage Location',
select=True,
domain=[('type', '=', 'storage')],
states={
'invisible': Not(Equal(Eval('type'), 'internal')),
'required': Equal(Eval('type'), 'internal'),
},
depends=['type'])
location = fields.Function(fields.Many2One('stock.location', 'Location'),
'get_location', searcher='search_location')
provisioning_location = fields.Many2One(
'stock.location', 'Provisioning Location',
domain=[('type', 'in', ['storage', 'view'])],
states={
'invisible': Not(Equal(Eval('type'), 'internal')),
'required': ((Eval('type') == 'internal')
& (Eval('min_quantity', None) != None)), # noqa: E711
},
depends=['type', 'min_quantity'])
overflowing_location = fields.Many2One(
'stock.location', 'Overflowing Location',
domain=[('type', 'in', ['storage', 'view'])],
states={
'invisible': Eval('type') != 'internal',
'required': ((Eval('type') == 'internal')
& (Eval('max_quantity', None) != None)), # noqa: E711
},
depends=['type', 'max_quantity'])
type = fields.Selection(
[('internal', 'Internal'),
('purchase', 'Purchase')],
'Type', select=True, required=True)
min_quantity = fields.Float('Minimal Quantity',
digits=(16, Eval('unit_digits', 2)),
states={
# required for purchase and production types
'required': Eval('type') != 'internal',
},
domain=['OR',
('min_quantity', '=', None),
('min_quantity', '<=', Eval('target_quantity', 0)),
],
depends=['unit_digits', 'target_quantity', 'type'])
target_quantity = fields.Float('Target Quantity', required=True,
digits=(16, Eval('unit_digits', 2)),
domain=[
['OR',
('min_quantity', '=', None),
('target_quantity', '>=', Eval('min_quantity', 0)),
],
['OR',
('max_quantity', '=', None),
('target_quantity', '<=', Eval('max_quantity', 0)),
],
],
depends=['unit_digits', 'min_quantity', 'max_quantity'])
max_quantity = fields.Float('Maximal Quantity',
digits=(16, Eval('unit_digits', 2)),
states={
'invisible': Eval('type') != 'internal',
},
domain=['OR',
('max_quantity', '=', None),
('max_quantity', '>=', Eval('target_quantity', 0)),
],
depends=['unit_digits', 'type', 'target_quantity'])
company = fields.Many2One('company.company', 'Company', required=True,
domain=[
('id', If(In('company', Eval('context', {})), '=', '!='),
Eval('context', {}).get('company', -1)),
])
unit = fields.Function(fields.Many2One('product.uom', 'Unit'), 'get_unit')
unit_digits = fields.Function(fields.Integer('Unit Digits'),
'get_unit_digits')
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_type():
return "purchase"
class CreateOrderPoint(Wizard):
'Create Order Point'
__name__ = 'stock_co.create_order_point'
start = StateView('stock_co.create_order_point.start',
'stock_co.create_order_point_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Ok', 'accept', 'tryton-ok', default=True),
])
accept = StateTransition()
def transition_accept(self):
pool = Pool()
OrderPoint = pool.get('stock.order_point')
Product = pool.get('product.product')
order_points = []
type = self.start.type
company = self.start.company
warehouse_location = self.start.warehouse_location
min_quantity = self.start.min_quantity
target_quantity = self.start.target_quantity
products = Product.search([('categories', 'in', [self.start.category])])
print(products)
for product in products:
order_points.append({
'type': type,
'company': company,
'warehouse_location': warehouse_location.id,
'product': product.id,
'min_quantity': min_quantity,
'target_quantity': target_quantity,
})
OrderPoint.create(order_points)
return 'end'