1165 lines
47 KiB
Python
Executable File
1165 lines
47 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.
|
|
import copy
|
|
from datetime import date, datetime, timedelta
|
|
from decimal import Decimal
|
|
from trytond.model import fields, ModelView
|
|
from trytond.pool import Pool, PoolMeta
|
|
from trytond.pyson import Eval, Not, Bool
|
|
from trytond.wizard import Wizard, StateView, StateReport, Button, StateTransition
|
|
from trytond.report import Report
|
|
from trytond.transaction import Transaction
|
|
from trytond.pyson import Eval, Equal, If, In
|
|
|
|
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
|
|
|
|
|
|
class Move(metaclass=PoolMeta):
|
|
__name__ = 'stock.move'
|
|
description = fields.Char('Description')
|
|
current_stock = fields.Function(fields.Float('Current Stock',
|
|
depends=['product']), 'on_change_with_current_stock')
|
|
reference = fields.Function(fields.Char('Reference',
|
|
depends=['product'], help='reference of product'), 'get_reference')
|
|
|
|
@fields.depends('current_stock')
|
|
def on_change_product(self, name=None):
|
|
super(Move, self).on_change_product()
|
|
self.current_stock = self.on_change_with_current_stock()
|
|
|
|
@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
|
|
|
|
@fields.depends('product', 'from_location', 'to_location')
|
|
def on_change_with_current_stock(self, name=None):
|
|
res = 0
|
|
location = None
|
|
if self.from_location and self.from_location.type == 'storage' and self.from_location.parent:
|
|
location = self.from_location.parent.storage_location
|
|
elif self.to_location and self.to_location.type == 'storage' and self.to_location.parent:
|
|
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(
|
|
[self.product],
|
|
'quantity',
|
|
[location.id],
|
|
grouping_filter=([self.product.id],)
|
|
)
|
|
if res_dict.get(self.product.id):
|
|
res += res_dict[self.product.id]
|
|
return res
|
|
|
|
@fields.depends('product')
|
|
def on_change_with_description(self, name=None):
|
|
res = ''
|
|
if self.product and self.product.description:
|
|
description = self.product.description
|
|
res = description.rstrip('\n')
|
|
return res
|
|
|
|
@classmethod
|
|
def do(cls, moves):
|
|
super(Move, cls).do(moves)
|
|
for move in moves:
|
|
product = move.product
|
|
if not move.description:
|
|
move.description = cls.on_change_with_description(move)
|
|
move.save()
|
|
# remove origin 'stock.inventory.line' for set average cost
|
|
if move.origin and move.origin.__name__ in ('purchase.line'):
|
|
if product.cost_price_method == 'fixed':
|
|
product.cost_price = move.unit_price
|
|
product.save()
|
|
move.set_average_cost()
|
|
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()
|
|
|
|
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])
|
|
|
|
|
|
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')
|
|
|
|
@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:
|
|
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')
|
|
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'])
|
|
for move in moves:
|
|
product = move.product
|
|
amount = move.quantity * float(product.cost_price)
|
|
origin = None
|
|
id_dict = product.id
|
|
if location.type == 'customer':
|
|
id_dict = move.origin.sale.party.name + str(product.id)
|
|
origin = move.origin.sale.party.name if move.origin else None
|
|
try:
|
|
row = products[id_dict]
|
|
row[4].append(move.quantity)
|
|
row[6].append(amount)
|
|
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
|
|
products[id_dict] = [
|
|
product.code,
|
|
origin,
|
|
product.rec_name,
|
|
template.default_uom.symbol,
|
|
[move.quantity],
|
|
product.cost_price,
|
|
[amount],
|
|
category,
|
|
brand,
|
|
template.list_price,
|
|
effective_date,
|
|
]
|
|
|
|
report_context['records'] = products.values()
|
|
report_context['from_location'] = from_location
|
|
report_context['to_location'] = location
|
|
report_context['customer_location'] = location.type == 'customer'
|
|
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')
|
|
group_by_supplier = fields.Boolean('Group By Supplier')
|
|
category = fields.Many2One('product.category', 'Category')
|
|
suppliers = fields.Many2Many('party.party', None, None, "Suppliers",
|
|
domain=[
|
|
('active', '=', True)
|
|
])
|
|
zero_quantity = fields.Boolean('Zero Quantity')
|
|
|
|
@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,
|
|
'group_by_supplier': self.start.group_by_supplier,
|
|
'zero_quantity': self.start.zero_quantity,
|
|
'to_date': self.start.to_date,
|
|
'category': category_id,
|
|
'suppliers': [l.id for l in self.start.suppliers],
|
|
}
|
|
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'])
|
|
Move = pool.get('stock.move')
|
|
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']]),
|
|
],
|
|
]])
|
|
if not data['zero_quantity']:
|
|
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)),
|
|
('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})
|
|
products_ids = min_quantities.keys()
|
|
dom_products.append(('id', 'in', products_ids))
|
|
if data['suppliers']:
|
|
dom_products.append([('template.product_suppliers.party', 'in', data['suppliers'])])
|
|
|
|
total_amount = 0
|
|
total_amount_imp = 0
|
|
values = {}
|
|
products = []
|
|
if data['group_by_location']:
|
|
for l in locations:
|
|
days = []
|
|
stock_context['locations'] = [l.id]
|
|
with Transaction().set_context(stock_context):
|
|
prdts = Product.search(dom_products, order=[('code', 'ASC')])
|
|
suppliers = {}
|
|
for p in prdts:
|
|
move = Move.search([
|
|
('to_location', '=', l.id),
|
|
('product', '=', p.id),
|
|
('state', '=', 'done'),
|
|
], order=[('effective_date', 'DESC')])
|
|
date_start = move[0].effective_date if len(move) > 0 else None
|
|
date_now = date.today()
|
|
date_result = date_now - date_start if date_start else 0
|
|
days.append(date_result.days) if date_start else days.append('error')
|
|
if data['group_by_supplier']:
|
|
for p in prdts:
|
|
if not p.template.product_suppliers:
|
|
continue
|
|
for prod_sup in p.template.product_suppliers:
|
|
sup_id = prod_sup.party.id
|
|
try:
|
|
suppliers[sup_id]['products'].append(p)
|
|
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)
|
|
except:
|
|
suppliers[sup_id] = {}
|
|
suppliers[sup_id]['products'] = [p]
|
|
suppliers[sup_id]['party'] = prod_sup.party
|
|
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]
|
|
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])
|
|
values[l.id] = {
|
|
'name': l.name,
|
|
'products': prdts,
|
|
'days': days,
|
|
'suppliers': suppliers.values(),
|
|
'total_amount': total_amount,
|
|
'total_amount_imp': total_amount_imp
|
|
}
|
|
products = values.values()
|
|
else:
|
|
with Transaction().set_context(stock_context):
|
|
products = Product.search(dom_products, order=[('code', 'ASC')])
|
|
|
|
if data['only_minimal_level']:
|
|
products = [p for p in products if p.quantity <= min_quantities[p.id]]
|
|
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])
|
|
suppliers = {}
|
|
if data['group_by_supplier']:
|
|
for p in products:
|
|
if not p.template.product_suppliers:
|
|
continue
|
|
for prod_sup in p.template.product_suppliers:
|
|
sup_id = prod_sup.party.id
|
|
try:
|
|
suppliers[sup_id]['products'].append(p)
|
|
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)
|
|
except:
|
|
suppliers[sup_id] = {}
|
|
suppliers[sup_id]['products'] = [p]
|
|
suppliers[sup_id]['party'] = prod_sup.party
|
|
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]
|
|
products = suppliers.values()
|
|
|
|
cursor = Transaction().connection.cursor()
|
|
# 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'])
|
|
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;"
|
|
|
|
cursor.execute(query)
|
|
columns = list(cursor.description)
|
|
result = cursor.fetchall()
|
|
last_purchase = {}
|
|
|
|
for row in result:
|
|
row_dict = {}
|
|
for i, col in enumerate(columns):
|
|
row_dict[col.name] = row[i]
|
|
last_purchase[row[0]] = row_dict
|
|
|
|
report_context['group_by_location'] = data['group_by_location']
|
|
report_context['group_by_supplier'] = data['group_by_supplier']
|
|
report_context['records'] = products
|
|
report_context['total_amount'] = total_amount
|
|
report_context['total_amount_imp'] = total_amount_imp
|
|
report_context['last_purchase'] = last_purchase
|
|
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
|
|
|
|
|
|
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)
|
|
lot = fields.Boolean('Grouping Lot')
|
|
|
|
@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')])
|
|
return [l.storage_location.id for l in locations if l.storage_location.id]
|
|
|
|
|
|
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,
|
|
'lot': self.start.lot,
|
|
}
|
|
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')
|
|
LotbyLocation = pool.get('stock.lots_by_locations')
|
|
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']))
|
|
|
|
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"
|
|
cursor.execute(query)
|
|
columns = list(cursor.description)
|
|
result = cursor.fetchall()
|
|
last_purchase = {}
|
|
|
|
for row in result:
|
|
row_dict = {}
|
|
for i, col in enumerate(columns):
|
|
row_dict[col.name] = row[i]
|
|
last_purchase[row[0]] = row_dict
|
|
|
|
values = []
|
|
add_ = values.append
|
|
for l in locations:
|
|
stock_context['locations'] = [l.id]
|
|
with Transaction().set_context(stock_context):
|
|
prdts = Product.search(dom_products, order=[('code', 'ASC')])
|
|
if data['lot']:
|
|
prd_ids = [p.id for p in prdts]
|
|
prdts = LotbyLocation.search([('product' , 'in', prd_ids)])
|
|
for p in prdts:
|
|
add_({
|
|
'parent_name': l.parent.name if l.parent else '',
|
|
'name': l.name,
|
|
'product': p.product if data['lot'] else p,
|
|
'lot': p if data['lot'] else None,
|
|
})
|
|
|
|
products = values
|
|
report_context['records'] = products
|
|
report_context['Decimal'] = Decimal
|
|
report_context['last_purchase'] = last_purchase
|
|
report_context['stock_date_end'] = data['to_date']
|
|
report_context['company'] = Company(data['company'])
|
|
return report_context
|
|
|
|
|
|
class WarehouseKardexStockStart(ModelView):
|
|
'Warehouse Kardex Stock Start'
|
|
__name__ = 'stock_co.warehouse_kardex_stock.start'
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
locations = fields.Many2Many('stock.location', None, None, "Location",
|
|
domain=[
|
|
('type', 'in', ['warehouse']),
|
|
('active', '=', True)
|
|
], required=True)
|
|
from_date = fields.Date('From Date', required=True)
|
|
to_date = fields.Date('To Date', required=True)
|
|
detail_by_product = fields.Boolean('Detail By Product')
|
|
categories = fields.Many2Many('product.category', None, None, 'Categories')
|
|
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'])
|
|
|
|
@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,
|
|
# 'location': self.start.location.id,
|
|
'from_date': self.start.from_date,
|
|
'to_date': self.start.to_date,
|
|
'detail_by_product': self.start.detail_by_product,
|
|
'categories': [l.id for l in self.start.categories],
|
|
'locations': [l.id for l in self.start.locations],
|
|
'products': [l.id for l in self.start.products],
|
|
}
|
|
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')
|
|
warehouses = Location.browse(data['locations'])
|
|
id_locations = [w.storage_location.id for w in warehouses if w.storage_location]
|
|
dom_products = [
|
|
('active', '=', True),
|
|
('template.active', '=', True),
|
|
('type', '=', 'goods'),
|
|
('consumable', '=', False),
|
|
('quantity', '!=', 0),
|
|
]
|
|
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)),
|
|
'locations': id_locations,
|
|
}
|
|
stock_context_end = {
|
|
'stock_date_end': data['to_date'],
|
|
'locations': id_locations,
|
|
}
|
|
|
|
products = {}
|
|
products_detail = []
|
|
fields_names = ['code', 'name', 'brand', 'reference', 'quantity']
|
|
# 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}'
|
|
# }
|
|
tup_locations = str(tuple(id_locations)).replace(',', '') if len(
|
|
id_locations) == 1 else str(tuple(id_locations))
|
|
type = {
|
|
'input': f'and to_location in {tup_locations}',
|
|
'output': f'and from_location in {tup_locations}',
|
|
}
|
|
moves = {}
|
|
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
|
|
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)
|
|
|
|
else:
|
|
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
|
|
from stock_move as s
|
|
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
|
|
where s.effective_date >= '{str(data['from_date'])}'
|
|
and s.effective_date <= '{str(data['to_date'])}'
|
|
AND p.id IN ({product})
|
|
ORDER BY s.effective_date ASC, p.id ASC
|
|
"""
|
|
# and (s.origin LIKE '%purchase.line%' or s.origin LIKE '%sale.line%')
|
|
start, end = cls.get_balances(Product, product, data['from_date'] - timedelta(days=1), id_locations)
|
|
products_detail.append(cls.query_to_dict_detail(query, end))
|
|
wh_name = ''
|
|
for l in warehouses:
|
|
wh_name += (l.name + ', ')
|
|
report_context['products'] = products.values()
|
|
report_context['products_detail'] = products_detail
|
|
report_context['detail_by_product'] = data['detail_by_product']
|
|
report_context['warehouse'] = wh_name
|
|
report_context['company'] = Company(data['company'])
|
|
return report_context
|
|
|
|
@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']
|
|
})
|
|
|
|
@classmethod
|
|
def set_value_detail(cls, p, key, Product, id_locations):
|
|
start, end = cls.get_balances(Product, p['pid'], p['effective_date'], id_locations)
|
|
|
|
@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):
|
|
products_start = Product.search_read(dom_products, fields_names=fields_names)
|
|
|
|
with Transaction().set_context(stock_context_end):
|
|
products_end = Product.search_read(dom_products, fields_names=fields_names)
|
|
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']
|
|
|
|
@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
|
|
|
|
@classmethod
|
|
def query_to_dict_detail(cls, query, end):
|
|
pool = Pool()
|
|
SaleLine = pool.get('sale.line')
|
|
PurchaseLine = pool.get('purchase.line')
|
|
StockMove = pool.get('stock.move')
|
|
cursor = Transaction().connection.cursor()
|
|
cursor.execute(query)
|
|
result = cursor.fetchall()
|
|
sale_line_ids = []
|
|
purchase_line_ids = []
|
|
stock_inventory_id = []
|
|
for row in result:
|
|
if row[6] == 'sale.line':
|
|
sale_line_ids.append(row[7])
|
|
elif row[6] == 'purchase.line':
|
|
purchase_line_ids.append(row[7])
|
|
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}
|
|
res = []
|
|
balance = end
|
|
cost_price = 0
|
|
for row in result:
|
|
qty_input = 0
|
|
qty_output = 0
|
|
list_price = 0
|
|
party = ''
|
|
number = ''
|
|
add_item = False
|
|
print(row[8], row[0], 'origens')
|
|
if row[6] == 'sale.line':
|
|
print('paso por if')
|
|
party, number, list_price, qty_output = sale_lines_dict[int(row[7])]
|
|
balance -= qty_output
|
|
add_item = True
|
|
elif row[6] == 'purchase.line':
|
|
# qty_input = row[9]
|
|
party, number, cost_price, qty_input = purchase_lines_dict[int(row[7])]
|
|
balance += qty_input
|
|
add_item = True
|
|
else:
|
|
move = StockMove(row[8])
|
|
print('paso por else')
|
|
if move and (move.from_location.type == 'lost_found' or move.from_location.type == 'customer' or move.from_location.type == 'supplier'):
|
|
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)
|
|
return res
|
|
|
|
|
|
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'
|