purchase suggested report

This commit is contained in:
hmvr_tryton 2021-05-03 19:54:15 -05:00
parent 08dfb7fd0f
commit d8b3ae28b4
7 changed files with 354 additions and 13 deletions

View File

@ -3,7 +3,9 @@
from trytond.pool import Pool
from .purchase import (CreatePurchaseSuggestedStart, CreatePurchaseSuggested,
CreatePurchaseRequestBySupplier, CreateRequestBySupplierStart,
CreateRequestBySupplier, CreatePurchaseRequestBySupplierStart)
CreateRequestBySupplier, CreatePurchaseRequestBySupplierStart,
PurchaseSuggestedReportStart, PrintPurchaseSuggestedReport,
PurchaseSuggestedReport)
from .product import (UpdateProductBySupplier, AddSupplierToProducts,
AddSupplierToProductsStart, Product, Template, UpdateCostPriceStart,
UpdateCostPrice)
@ -18,6 +20,7 @@ def register():
CreatePurchaseRequestBySupplierStart,
CreateRequestBySupplierStart,
UpdateCostPriceStart,
PurchaseSuggestedReportStart,
module='purchase_suggested', type_='model')
Pool.register(
AddSupplierToProducts,
@ -26,4 +29,8 @@ def register():
CreatePurchaseRequestBySupplier,
CreateRequestBySupplier,
UpdateCostPrice,
PrintPurchaseSuggestedReport,
module='purchase_suggested', type_='wizard')
Pool.register(
PurchaseSuggestedReport,
module='purchase_suggested', type_='report')

View File

@ -61,6 +61,31 @@ msgctxt ""
msgid "Warehouse Location"
msgstr "Ubicación del Almacén"
msgctxt "field:purchase_suggested.purchase_suggested_report.start,company:"
msgid "Company"
msgstr "Empresa"
msgctxt ""
"field:purchase_suggested.purchase_suggested_report.start,historical_time:"
msgid "Historical Time"
msgstr "Tiempo Historico"
msgctxt "field:purchase_suggested.purchase_suggested_report.start,id:"
msgid "ID"
msgstr "ID"
msgctxt "field:purchase_suggested.purchase_suggested_report.start,location:"
msgid "Location"
msgstr "Ubicación"
msgctxt "field:purchase_suggested.purchase_suggested_report.start,supplier:"
msgid "Supplier"
msgstr "Proveedor"
msgctxt "field:purchase_suggested.purchase_suggested_report.start,time_stock:"
msgid "Time Stock"
msgstr "Tiempo de Stock"
msgctxt "field:purchase_suggested.update_cost_price.start,cost_price:"
msgid "New Cost Price"
msgstr "Nuevo Costo"
@ -77,6 +102,15 @@ msgctxt "help:purchase_suggested.create_order.start,time_stock:"
msgid "In days"
msgstr "En días"
msgctxt ""
"help:purchase_suggested.purchase_suggested_report.start,historical_time:"
msgid "In days"
msgstr "En días"
msgctxt "help:purchase_suggested.purchase_suggested_report.start,time_stock:"
msgid "In days"
msgstr "En días"
msgctxt "model:ir.action,name:"
msgid "Auto Request"
msgstr "Auto Requisición"
@ -105,6 +139,14 @@ msgctxt "model:ir.action,name:act_update_product_supplier_list"
msgid "Update Product Supplier"
msgstr "Actualizar Proveedores de Producto"
msgctxt "model:ir.action,name:purchase_suggested_report"
msgid "Purchase Suggested Report"
msgstr "Compra Sugerida "
msgctxt "model:ir.action,name:wizard_purchase_suggested_report"
msgid "Purchase Suggested Report"
msgstr "Compra Sugerida "
msgctxt "model:ir.ui.menu,name:menu_create_purchase_suggested"
msgid "Create Purchase Suggested"
msgstr "Crear Compra Sugerida"
@ -113,10 +155,18 @@ msgctxt "model:ir.ui.menu,name:menu_create_request_by_supplier"
msgid "Create Request By Supplier"
msgstr "Crear Solicitud por Proveedor"
msgctxt "model:ir.ui.menu,name:menu_purchase_reporting"
msgid "Reporting"
msgstr "Reportes"
msgctxt "model:ir.ui.menu,name:menu_purchase_request_create"
msgid "Create Purchase Requests"
msgstr "Crear Solicitudes De Compra Por Proveedor"
msgctxt "model:ir.ui.menu,name:menu_purchase_suggested_report"
msgid "Purchase Suggested Report"
msgstr "Compra Sugerida "
msgctxt "model:purchase_suggested.add_supplier.start,name:"
msgid "Add Supplier To Products Start"
msgstr "Agregar Proveedor a Productos"
@ -134,6 +184,10 @@ msgctxt "model:purchase_suggested.create_request_by_supplier.start,name:"
msgid "Create Request By Supplier Start"
msgstr "Crear Solicitud por Proveedor"
msgctxt "model:purchase_suggested.purchase_suggested_report.start,name:"
msgid "Purchase Suggested Report Start"
msgstr "Compra Sugerida "
msgctxt "model:purchase_suggested.update_cost_price.start,name:"
msgid "Update Cost Price Start"
msgstr "Actualizar Costo"
@ -186,6 +240,16 @@ msgctxt ""
msgid "Cancel"
msgstr "Cancelar"
msgctxt ""
"wizard_button:purchase_suggested.print_purchase_suggested_report,start,end:"
msgid "Cancel"
msgstr "Cancelar"
msgctxt ""
"wizard_button:purchase_suggested.print_purchase_suggested_report,start,print_:"
msgid "Print"
msgstr "Imprimir"
msgctxt "wizard_button:purchase_suggested.update_cost_price,start,accept:"
msgid "Ok"
msgstr "Aceptar"

View File

@ -4,9 +4,10 @@
from decimal import Decimal
from datetime import date, timedelta
from trytond.model import ModelView, fields
from trytond.wizard import Wizard, StateView, StateTransition, Button, StateAction
from trytond.wizard import Wizard, StateView, StateTransition, Button, StateAction, StateReport
from trytond.pool import Pool
from trytond.transaction import Transaction
from trytond.report import Report
__all__ = [
'CreatePurchaseSuggestedStart', 'CreatePurchaseSuggested',
@ -46,12 +47,11 @@ class CreatePurchaseSuggested(Wizard):
accept = StateTransition()
def get_pending_purchase(self, product_id):
min_date = date.today()-timedelta(30)
PurchaseLine = Pool().get('purchase.line')
qty_records = PurchaseLine.search_read([
StockMove = Pool().get('stock.move')
qty_records = StockMove.search_read([
('product', '=', product_id),
('purchase.state', '=', 'quotation'),
('purchase.purchase_date', '>=', min_date),
('state', '=', 'draft'),
('from_location.type', 'in', ['supplier']),
], fields_names=['quantity'])
return sum([l['quantity'] for l in qty_records])
@ -116,15 +116,13 @@ class CreatePurchaseSuggested(Wizard):
for product, quantities in target_products.items():
""" stock necesario para n dias - existencia"""
current_stock = self.get_product_stock(product.id, location_ids)
# print(product.code, current_stock, self.get_pending_purchase(product.id))
real_historical_time = historical_time
if product.historical_time > start_date:
real_historical_time = historical_time - (product.historical_time - start_date).days
# suggest_qty = int(time_stock * sum(quantities) / real_historical_time
# - current_stock - self.get_pending_purchase(product.id))
suggest_qty = int(time_stock * sum(quantities) / real_historical_time - current_stock)
# print(suggest_qty, sum(quantities), real_historical_time, sum(quantities) / real_historical_time)
if suggest_qty <= 2:
if suggest_qty <= 0:
continue
order = {
'product': product,
@ -144,8 +142,6 @@ class CreatePurchaseSuggested(Wizard):
ProductSupplier = Pool().get('purchase.product_supplier')
products_supplier = ProductSupplier.search([
('party', '=', supplier.id),
('product.active', '=', True),
('product.purchasable', '=', True),
])
products_ids = []
for ps in products_supplier:
@ -327,3 +323,237 @@ class CreateRequestBySupplier(Wizard):
})
PurchaseRequest.create(request_to_create)
return 'end'
class PurchaseSuggestedReportStart(ModelView):
'Purchase Suggested Report Start'
__name__ = 'purchase_suggested.purchase_suggested_report.start'
company = fields.Many2One('company.company', 'Company', required=True)
time_stock = fields.Integer('Time Stock', required=True, help='In days')
supplier = fields.Many2One('party.party', 'Supplier')
location = fields.Many2One('stock.location', 'Location', domain=[
('type', '=', 'warehouse')
])
historical_time = fields.Integer('Historical Time', required=True,
help='In days')
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_historical_time():
return 90
@staticmethod
def default_time_stock():
return 30
class PrintPurchaseSuggestedReport(Wizard):
'Purchase Suggested Report Wizard'
__name__ = 'purchase_suggested.print_purchase_suggested_report'
start = StateView('purchase_suggested.purchase_suggested_report.start',
'purchase_suggested.print_purchase_suggested_report_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-print', default=True),
])
print_ = StateReport('purchase_suggested.purchase_suggested_report')
def do_print_(self, action):
supplier = None
location_id = None
location_name = None
if self.start.location:
location_id = self.start.location.storage_location.id
location_name = self.start.location.storage_location.name
if self.start.supplier:
supplier = self.start.supplier.id
data = {
'company': self.start.company.id,
'supplier': supplier,
'location': location_id,
'location_name': location_name,
'time_stock': self.start.time_stock,
'historical_time': self.start.historical_time
}
return action, data
def transition_print_(self):
return 'end'
class PurchaseSuggestedReport(Report):
'Purchase Suggested Report'
__name__ = 'purchase_suggested.purchase_suggested_report'
@classmethod
def get_supplier_products(cls, supplier):
""" Return a list of ids supplier products """
ProductSupplier = Pool().get('purchase.product_supplier')
domain = [
('product.active', '=', True),
('product.purchasable', '=', True),
]
if supplier:
domain.append(
('party', '=', supplier)
)
products_supplier = ProductSupplier.search(domain)
suppliers_dict = {}
for ps in products_supplier:
products_ids = [p.id for p in ps.product.products]
try:
suppliers_dict[ps.party.id]['products'].extend(products_ids)
except:
suppliers_dict[ps.party.id] = {
'name': ps.party.name,
'products': products_ids
}
return suppliers_dict
@classmethod
def get_last_purchase(cls, product_id):
PurchaseLine = Pool().get('purchase.line')
lines = PurchaseLine.search([
('product', '=', product_id),
('purchase.state', 'in', ('processing', 'done')),
], order=[('create_date', 'DESC')], limit=1)
if lines:
return (lines[0].purchase.purchase_date,
lines[0].quantity, lines[0].unit_price)
else:
return None, None, 0
@classmethod
def get_pending_purchase(cls, product_id):
StockMove = Pool().get('stock.move')
qty_records = StockMove.search_read([
('product', '=', product_id),
('state', '=', 'draft'),
('from_location.type', 'in', ['supplier']),
], fields_names=['quantity'])
return sum([l['quantity'] for l in qty_records])
@classmethod
def get_product_stock(cls, product_id, locations):
pool = Pool()
Product = pool.get('product.product')
stock_context = {
'stock_date_end': date.today(),
'locations': locations
}
with Transaction().set_context(stock_context):
product, = Product.search([('id', '=', product_id)])
return product.quantity, product.stock_duration
@classmethod
def get_last_out_moves(cls, product_id, location_id, end_date, days):
StockMove = Pool().get('stock.move')
start_date = end_date - timedelta(days=days)
dom_stock = [
('product', '=', product_id),
('effective_date', '>=', start_date),
('effective_date', '<=', end_date),
('state', '=', 'done'),
('to_location.type', 'in', ['customer', 'production']),
]
if location_id:
dom_stock.append(('from_location', '=', location_id))
else:
dom_stock.append(('from_location.type', '=', 'storage'))
moves = StockMove.search(dom_stock)
return sum([l.quantity for l in moves])
@classmethod
def get_context(cls, records, data):
report_context = super(PurchaseSuggestedReport, cls).get_context(records, data)
pool = Pool()
Company = pool.get('company.company')
company = Company(data['company'])
StockMove = pool.get('stock.move')
Location = pool.get('stock.location')
today = date.today()
start_date = today - timedelta(data['historical_time'])
suppliers_dict = cls.get_supplier_products(data['supplier'])
historical_time = data['historical_time']
time_stock = data['time_stock']
dom_stock = [
('effective_date', '>=', start_date),
('effective_date', '<=', today),
('state', '=', 'done'),
('to_location.type', 'in', ['customer', 'production']),
]
records = []
location_id = None
if data['location']:
location_id = data['location']
location_ids = [location_id]
dom_stock.append(('from_location', '=', location_id))
else:
dom_stock.append(('from_location.type', '=', 'storage'))
locations = Location.search([
('type', '=', 'warehouse')
])
location_ids = [l.storage_location.id for l in locations]
for supplier in suppliers_dict.values():
dom_stock.append(('product', 'in', supplier['products']))
supplier['products'] = []
moves = StockMove.search(dom_stock, order=[('effective_date', 'ASC')])
target_products = {}
supplier_cost = []
supplier_daily_sale = []
for move in moves:
if move.product not in target_products.keys():
setattr(move.product, 'historical_time', move.effective_date)
target_products[move.product] = [move.quantity]
else:
target_products[move.product].append(move.quantity)
for product, quantities in target_products.items():
""" stock necesario para n dias - existencia"""
current_stock, stock_duration = cls.get_product_stock(product.id, location_ids)
""" search the real historical time, not the default (90) """
real_historical_time = historical_time
if product.historical_time > start_date:
real_historical_time = historical_time - (product.historical_time - start_date).days
suggest_qty = int(time_stock * sum(quantities) / real_historical_time - current_stock)
sales_30 = cls.get_last_out_moves(product.id, data['location'], today, 30)
sales_60 = cls.get_last_out_moves(product.id, data['location'], today-timedelta(30), 30)
last_purchase_date, last_quantity, last_cost_price = cls.get_last_purchase(product.id)
last_cost_price += product.extra_tax or 0 # add extra_tax (impoconsumo)
list_price = product.list_price + product.extra_tax or 0
cost_price = int(Decimal(current_stock) * last_cost_price)
daily_sale = int(Decimal(sum(quantities) / real_historical_time) * list_price)
supplier_cost.append(cost_price)
supplier_daily_sale.append(daily_sale)
supplier['products'].append({
'product_id': product.id,
'code': product.code,
'name': product.template.name,
'current_stock': current_stock,
'uom': product.template.default_uom.name,
'stock_duration': stock_duration,
'pending_purchase': cls.get_pending_purchase(product.id),
'30': sales_30,
'60': sales_60,
'daily_rotation': sum(quantities) / real_historical_time,
'daily_sale': daily_sale,
'suggest_qty': suggest_qty,
'last_purchase': last_purchase_date,
'last_quantity': last_quantity,
'last_cost_price': last_cost_price,
'amount_stock': cost_price
})
supplier['total_cost_price'] = sum(supplier_cost)
supplier['total_daily_sale'] = sum(supplier_daily_sale)
records.append(supplier)
report_context['records'] = records
report_context['today'] = date.today()
report_context['company'] = company.party.name
report_context['nit'] = company.party.id_number
report_context['location'] = data['location_name']
report_context['time_stock'] = time_stock
report_context['historical_time'] = historical_time
return report_context

View File

@ -4,6 +4,31 @@ The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<menuitem parent="purchase.menu_purchase" id="menu_purchase_reporting"
name="Reporting" sequence="180" icon="tryton-folder"/>
<record model="ir.ui.view" id="print_purchase_suggested_report_start_view_form">
<field name="model">purchase_suggested.purchase_suggested_report.start</field>
<field name="type">form</field>
<field name="name">purchase_suggested_report_start_form</field>
</record>
<record model="ir.action.wizard" id="wizard_purchase_suggested_report">
<field name="name">Purchase Suggested Report</field>
<field name="wiz_name">purchase_suggested.print_purchase_suggested_report</field>
</record>
<record model="ir.action.report" id="purchase_suggested_report">
<field name="name">Purchase Suggested Report</field>
<field name="model"></field>
<field name="report_name">purchase_suggested.purchase_suggested_report</field>
<field name="report">purchase_suggested/purchase_suggest_by_supplier.ods</field>
<field name="template_extension">ods</field>
<field name="translatable">False</field>
</record>
<menuitem parent="menu_purchase_reporting" sequence="10"
action="wizard_purchase_suggested_report" id="menu_purchase_suggested_report"/>
<record model="ir.ui.view" id="create_purchase_suggested_view_form">
<field name="model">purchase_suggested.create_order.start</field>

Binary file not shown.

View File

@ -79,7 +79,7 @@ setup(name=name,
],
package_data={
'trytond.modules.%s' % MODULE: (info.get('xml', [])
+ ['tryton.cfg', 'view/*.xml', 'locale/*.po', ]),
+ ['tryton.cfg', 'view/*.xml', 'locale/*.po', '*.ods' ]),
},
classifiers=[
'Development Status :: 5 - Production/Stable',

View File

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form >
<label name="company"/>
<field name="company"/>
<label name="supplier"/>
<field name="supplier"/>
<label name="time_stock"/>
<field name="time_stock"/>
<label name="historical_time"/>
<field name="historical_time"/>
<label name="location"/>
<field name="location" widget="selection"/>
</form>