purchase suggested report
This commit is contained in:
parent
08dfb7fd0f
commit
d8b3ae28b4
|
@ -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')
|
||||
|
|
64
locale/es.po
64
locale/es.po
|
@ -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"
|
||||
|
|
252
purchase.py
252
purchase.py
|
@ -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
|
||||
|
|
25
purchase.xml
25
purchase.xml
|
@ -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.
2
setup.py
2
setup.py
|
@ -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',
|
||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue