|
|
@ -18,9 +18,8 @@ |
|
|
|
# If not, see <http://www.gnu.org/licenses/>. |
|
|
|
# |
|
|
|
############################################################################# |
|
|
|
from datetime import datetime |
|
|
|
from dateutil.relativedelta import relativedelta |
|
|
|
from odoo import api, fields, models |
|
|
|
from odoo import api, fields, models, _ |
|
|
|
from odoo.exceptions import ValidationError |
|
|
|
|
|
|
|
|
|
|
|
class ForecastAnalysisReportWizard(models.TransientModel): |
|
|
@ -33,9 +32,6 @@ class ForecastAnalysisReportWizard(models.TransientModel): |
|
|
|
parent_category_id = fields.Many2one( |
|
|
|
'product.category', string="Parent Category", |
|
|
|
help="Parent category of product") |
|
|
|
supplier_id = fields.Many2one( |
|
|
|
'product.supplierinfo', string="Supplier", |
|
|
|
help="Supplier/Vendor of the Product.") |
|
|
|
product_brand_id = fields.Many2one('product.brand', string="Product Brand", |
|
|
|
help="Brand of the Product.") |
|
|
|
period = fields.Selection([('1week', 'Last 1 week'), |
|
|
@ -72,30 +68,30 @@ class ForecastAnalysisReportWizard(models.TransientModel): |
|
|
|
return {'domain': { |
|
|
|
'product_category_id': ()}} |
|
|
|
|
|
|
|
def _compute_date(self): |
|
|
|
def get_start_date(self, today): |
|
|
|
"""This function will calculate the start_date with respect to the |
|
|
|
period and returns the result""" |
|
|
|
res = datetime.today() + relativedelta(months=-3) |
|
|
|
res = fields.Date.subtract(today, months=3) |
|
|
|
if self.period == '1week': |
|
|
|
res = datetime.today() + relativedelta(weeks=-1) |
|
|
|
res = fields.Date.subtract(today, weeks=1) |
|
|
|
elif self.period == '2week': |
|
|
|
res = datetime.today() + relativedelta(weeks=-2) |
|
|
|
res = fields.Date.subtract(today, weeks=2) |
|
|
|
elif self.period == '3week': |
|
|
|
res = datetime.today() + relativedelta(weeks=-3) |
|
|
|
res = fields.Date.subtract(today, weeks=3) |
|
|
|
elif self.period == '1month': |
|
|
|
res = datetime.today() + relativedelta(months=-1) |
|
|
|
res = fields.Date.subtract(today, months=1) |
|
|
|
elif self.period == '6months': |
|
|
|
res = datetime.today() + relativedelta(months=-6) |
|
|
|
res = fields.Date.subtract(today, months=6) |
|
|
|
elif self.period == '12months': |
|
|
|
res = datetime.today() + relativedelta(months=-12) |
|
|
|
res = fields.Date.subtract(today, months=12) |
|
|
|
elif self.period == '24months': |
|
|
|
res = datetime.today() + relativedelta(months=-24) |
|
|
|
res = fields.Date.subtract(today, months=24) |
|
|
|
elif self.period == '36months': |
|
|
|
res = datetime.today() + relativedelta(months=-36) |
|
|
|
res = fields.Date.subtract(today, months=36) |
|
|
|
elif self.period == '2months': |
|
|
|
res = datetime.today() + relativedelta(months=-2) |
|
|
|
res = fields.Date.subtract(today, months=2) |
|
|
|
elif self.period == '5months': |
|
|
|
res = datetime.today() + relativedelta(months=-5) |
|
|
|
res = fields.Date.subtract(today, months=5) |
|
|
|
return res |
|
|
|
|
|
|
|
def action_print_report(self): |
|
|
@ -103,88 +99,81 @@ class ForecastAnalysisReportWizard(models.TransientModel): |
|
|
|
on the report wizard and returns the report.""" |
|
|
|
previous_report = self.env['forecast.report'].search([]) |
|
|
|
previous_report.unlink() if previous_report else False |
|
|
|
suppliers = self.env['product.supplierinfo'].search( |
|
|
|
[('partner_id', '=', self.partner_id.id)]) |
|
|
|
category_ids = [] |
|
|
|
if self.parent_category_id: |
|
|
|
# if there is a parent category, the report will be generated based |
|
|
|
# on the product category as the sub-sub categories |
|
|
|
category_ids = self.env['product.category'].search( |
|
|
|
[('parent_id', '=', self.parent_category_id.id)]) |
|
|
|
categ_list = category_ids.ids if category_ids else [] |
|
|
|
if categ_list: |
|
|
|
for rec in categ_list: |
|
|
|
sub_category = self.env['product.category'].search( |
|
|
|
[('parent_id', '=', rec)]) |
|
|
|
for category in sub_category.ids: |
|
|
|
if category not in categ_list: |
|
|
|
categ_list.append(category) |
|
|
|
category_ids = self.env['product.category'].browse(categ_list) |
|
|
|
if (not self.product_category_id and not self.parent_category_id and |
|
|
|
not self.partner_id and not self.product_brand_id and |
|
|
|
not self.location_ids): |
|
|
|
raise ValidationError(_("Data missing")) |
|
|
|
domain = [] |
|
|
|
# if both categories are present, it will generate the |
|
|
|
# report based on the product category only |
|
|
|
if self.parent_category_id and self.product_category_id: |
|
|
|
domain += [('categ_id', '=', self.product_category_id.id)] |
|
|
|
elif self.product_category_id and not self.parent_category_id: |
|
|
|
if self.product_category_id: |
|
|
|
domain += [('categ_id', '=', self.product_category_id.id)] |
|
|
|
elif not self.product_category_id and self.parent_category_id: |
|
|
|
domain += [('categ_id', '=', self.parent_category_id.id)] |
|
|
|
if self.partner_id: |
|
|
|
domain += [('supplier_id', 'in', suppliers.ids)] |
|
|
|
suppliers = self.env['product.supplierinfo'].search( |
|
|
|
[('partner_id', '=', self.partner_id.id)]) |
|
|
|
domain += [('seller_ids', 'in', suppliers.ids)] |
|
|
|
if self.product_brand_id: |
|
|
|
domain += [('product_brand_id', '=', self.product_brand_id.id)] |
|
|
|
products = self.env['product.product'].search(domain) |
|
|
|
product_ids = tuple([product.id for product in products]) |
|
|
|
start_date = self._compute_date() |
|
|
|
current_date = datetime.today() |
|
|
|
current_date = fields.date.today() |
|
|
|
start_date = self.get_start_date(current_date) |
|
|
|
query = """ |
|
|
|
SELECT sum(sl.product_uom_qty) AS product_uom_qty, |
|
|
|
sl.product_id, sum(sl.qty_invoiced) AS qty_invoiced |
|
|
|
FROM sale_order_line AS sl |
|
|
|
JOIN sale_order AS so ON sl.order_id = so.id |
|
|
|
WHERE so.state IN ('sale','done') |
|
|
|
AND so.date_order::date >= %s |
|
|
|
AND so.date_order::date <= %s |
|
|
|
AND sl.product_id in %s |
|
|
|
group by sl.product_id""" |
|
|
|
params = start_date.date(), current_date.date(), \ |
|
|
|
SELECT sum(sl.product_uom_qty) AS product_uom_qty, |
|
|
|
sl.product_id, sum(sl.qty_invoiced) AS qty_invoiced |
|
|
|
FROM sale_order_line AS sl |
|
|
|
JOIN sale_order AS so ON sl.order_id = so.id |
|
|
|
WHERE so.state IN ('sale','done') |
|
|
|
AND so.date_order::date >= %s |
|
|
|
AND so.date_order::date <= %s |
|
|
|
AND sl.product_id in %s |
|
|
|
group by sl.product_id""" |
|
|
|
params = start_date, current_date, \ |
|
|
|
product_ids if product_ids else (0, 0, 0, 0) |
|
|
|
self._cr.execute(query, params) |
|
|
|
result = self._cr.dictfetchall() |
|
|
|
locations = self.location_ids |
|
|
|
if not locations: |
|
|
|
locations = self.env['stock.location'].search([ |
|
|
|
('name', '=', 'Stock')]) |
|
|
|
for product in products: |
|
|
|
for location in locations: |
|
|
|
warehouse = location.warehouse_id.id |
|
|
|
internal_locations = self.env['stock.location'].search( |
|
|
|
[('usage', '=', 'internal')]) |
|
|
|
locations = self.env['stock.quant'].search([ |
|
|
|
('product_id', '=', product.id), |
|
|
|
('location_id', 'in', internal_locations.ids)]).location_id |
|
|
|
for location in self.location_ids if self.location_ids else locations: |
|
|
|
stock_quant = self.env['stock.quant'].search([ |
|
|
|
('location_id', '=', location.id), |
|
|
|
('product_id', '=', product.id), |
|
|
|
('write_date', '>=', start_date), |
|
|
|
('write_date', '<=', current_date)]) |
|
|
|
available_qty = sum( |
|
|
|
[quant.quantity for quant in stock_quant]) |
|
|
|
sold = 0 |
|
|
|
for sol_product in result: |
|
|
|
if sol_product['product_id'] == product.id: |
|
|
|
sold = sol_product['qty_invoiced'] |
|
|
|
available_qty = product.with_context( |
|
|
|
{'from_date': start_date, 'to_date': current_date, |
|
|
|
'warehouse': warehouse}).qty_available |
|
|
|
forecasted_qty = product.with_context( |
|
|
|
{'warehouse': warehouse}).virtual_available |
|
|
|
{'warehouse': location.warehouse_id.id}).virtual_available |
|
|
|
reorder_qty = self.env['stock.warehouse.orderpoint'].search( |
|
|
|
[('product_id', '=', product.id), |
|
|
|
('location_id', '=', product.id)]) |
|
|
|
('location_id', '=', location.id)]) |
|
|
|
reorder_min = sum( |
|
|
|
[q.product_min_qty for q in reorder_qty]) |
|
|
|
minimum_qty = 0 |
|
|
|
if available_qty < reorder_min: |
|
|
|
minimum_qty = reorder_min |
|
|
|
[qty.product_min_qty for qty in reorder_qty]) |
|
|
|
minimum_qty = reorder_min if available_qty < reorder_min else 0 |
|
|
|
pending = product.with_context( |
|
|
|
{'from_date': start_date, 'to_date': current_date, |
|
|
|
'location': location.id}).incoming_qty |
|
|
|
'location_id': location.id}).incoming_qty |
|
|
|
suggested = sold - (forecasted_qty + pending + minimum_qty) |
|
|
|
if self.partner_id: |
|
|
|
supplier = product.seller_ids.filtered( |
|
|
|
lambda seller: seller.partner_id == self.partner_id).id |
|
|
|
elif not self.partner_id and product.seller_ids: |
|
|
|
supplier = product.seller_ids.ids[0] |
|
|
|
else: |
|
|
|
supplier = False |
|
|
|
vals = { |
|
|
|
'sold': sold, |
|
|
|
'product_id': product.id, |
|
|
|
'product_category_id': product.categ_id.id, |
|
|
|
'supplier_id': product.seller_ids.ids[ |
|
|
|
0] if product.seller_ids else False, |
|
|
|
'supplier_id': supplier, |
|
|
|
'product_brand_id': product.product_brand_id.id, |
|
|
|
'on_hand': available_qty, |
|
|
|
'pending': pending, |
|
|
|