Skip to content

Commit

Permalink
[ADD] sale_forecast
Browse files Browse the repository at this point in the history
  • Loading branch information
ntsirintanis committed Mar 12, 2024
1 parent 40e3219 commit 06a0889
Show file tree
Hide file tree
Showing 12 changed files with 394 additions and 0 deletions.
2 changes: 2 additions & 0 deletions sale_forecast/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from . import wizards
18 changes: 18 additions & 0 deletions sale_forecast/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2024 Therp BV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
"name": "Sale Forecast",
"summary": "Sale forecast report",
"version": "16.0.1.0.0",
"category": "Generic Modules/Sale",
"author": "Therp BV, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/sale-workflow",
"depends": ["sale", "stock_demand_estimate", "stock_demand_estimate_matrix"],
"data": [
"security/ir.model.access.csv",
"views/date_range.xml",
"views/sale_forecast.xml",
"wizards/wizard_view.xml",
],
"license": "AGPL-3",
}
1 change: 1 addition & 0 deletions sale_forecast/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import sale_forecast
11 changes: 11 additions & 0 deletions sale_forecast/models/sale_forecast.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from odoo import fields, models


class SaleForecast(models.Model):
_name = "sale.forecast"
_inherits = {"stock.demand.estimate": "stock_demand_id"}
_description = "Sale forecast"

stock_demand_id = fields.Many2one(
comodel_name="stock.demand.estimate", required=True, ondelete="cascade"
)
6 changes: 6 additions & 0 deletions sale_forecast/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sale_forecast,sale.forcast,model_sale_forecast,stock.group_stock_user,1,0,0,0
access_sale_forecast_system,sale.forecast.system,model_sale_forecast,stock.group_stock_manager,1,1,1,1
sale_forecast.access_sale_forecast_sheet,access_sale_forecast_sheet,sale_forecast.model_sale_forecast_sheet,stock.group_stock_manager,1,1,1,1
sale_forecast.access_sale_forecast_sheet_line,access_sale_forecast_sheet_line,sale_forecast.model_sale_forecast_sheet_line,stock.group_stock_manager,1,1,1,1
sale_forecast.access_sale_forecast_wizard,access_sale_forecast_wizard,sale_forecast.model_sale_forecast_wizard,stock.group_stock_manager,1,1,1,1
10 changes: 10 additions & 0 deletions sale_forecast/views/date_range.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" ?>
<odoo>
<menuitem
id="date_range_menu"
name="Date Ranges"
parent="sale.menu_sale_config"
action="date_range.date_range_action"
sequence="99"
/>
</odoo>
138 changes: 138 additions & 0 deletions sale_forecast/views/sale_forecast.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?xml version="1.0" ?>
<odoo>
<record id="sale_forecast_view_tree" model="ir.ui.view">
<field name="name">sale.forecast.tree</field>
<field name="model">sale.forecast</field>
<field name="arch" type="xml">
<tree edit="1" editable="bottom">
<field name="date_range_id" />
<field name="date_from" string="Date From" />
<field name="date_to" string="Date To" />
<field name="product_id" />
<field name="location_id" />
<field name="product_uom_qty" />
<field name="product_uom" />
<field name="product_qty" />
<field name="daily_qty" />
</tree>
</field>
</record>
<record id="sale_forecast_view_form" model="ir.ui.view">
<field name="name">sale.forecast.form</field>
<field name="model">sale.forecast</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group name="dates_read" class="oe_read_only">
<field name="date_from" string="Date From" />
<field name="date_to" string="Date To" />
<field name="duration" string="Duration" />
</group>
<group name="dates_edit" class="oe_edit_only">
<field name="manual_date_from" string="Date From" />
<field name="manual_date_to" string="Date To" />
<field name="manual_duration" string="Duration" />
</group>
<group>
<field name="product_id" />
<field name="location_id" />
<field name="product_uom_qty" />
<field name="product_uom" />
<field name="daily_qty" />
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="sale_forecast_view_pivot" model="ir.ui.view">
<field name="name">sale.forecast.pivot</field>
<field name="model">sale.forecast</field>
<field name="arch" type="xml">
<pivot string="Sale Forecast">
<field name="product_qty" type="measure" />
<field name="product_id" type="row" />
<field name="date_range_id" type="col" />
<field name="date_from" type="col" />
</pivot>
</field>
</record>

<record id="sale_forecast_view_graph" model="ir.ui.view">
<field name="name">sale.forecast.graph</field>
<field name="model">sale.forecast</field>
<field name="arch" type="xml">
<graph type="bar" stacked="True">
<field name="product_qty" type="measure" />
<field name="date_from" />
<field name="product_id" />
</graph>
</field>
</record>

<record id="sale_forecast_view_search" model="ir.ui.view">
<field name="name">sale.forecast.search</field>
<field name="model">sale.forecast</field>
<field name="arch" type="xml">
<search string="Search Sale Forecast">
<field name="product_id" />
<field name="location_id" />
<field name="date_range_id" />
<separator />
<filter
name="expired"
string="Expired"
domain="[('date_to', '&lt;', context_today().strftime('%Y-%m-%d'))]"
/>
<filter
name="not_expired"
string="Not Expired"
domain="['|', ('date_to', '=', False), ('date_to', '&gt;', context_today().strftime('%Y-%m-%d'))]"
/>
<separator />
<group expand="0" string="Group By">
<filter
string="Product"
name="groupby_product"
domain="[]"
context="{'group_by':'product_id'}"
/>
<filter
string="Location"
name="groupby_location"
domain="[]"
context="{'group_by':'location_id'}"
/>
<filter
string="Date (From)"
name="groupby_date_from"
domain="[]"
context="{'group_by':'date_from'}"
/>
</group>
</search>
</field>
</record>
<record id="sale_forecast_action" model="ir.actions.act_window">
<field name="name">Sale Forecast</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sale.forecast</field>
<field name="view_mode">tree,form,pivot,graph</field>
<field name="search_view_id" ref="sale_forecast_view_search" />
<field name="context">{"search_default_not_expired": 1}</field>
</record>
<menuitem
id="sale_forecast_menu"
name="Forecast"
parent="sale.sale_menu_root"
sequence="10"
/>
<menuitem
id="sale_forecast_record_menu"
name="Forecast records"
parent="sale_forecast_menu"
action="sale_forecast_action"
sequence="1"
/>
</odoo>
1 change: 1 addition & 0 deletions sale_forecast/wizards/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import wizards
104 changes: 104 additions & 0 deletions sale_forecast/wizards/wizard_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?xml version="1.0" ?>
<odoo>
<record id="sale_forecast_sheet_view_form" model="ir.ui.view">
<field name="name">sale.forecast.sheet.form</field>
<field name="model">sale.forecast.sheet</field>
<field name="arch" type="xml">
<form string="Sale Forecast Sheet">
<group name="dates">
<label for="date_start" string="Period" />
<div><field name="date_start" class="oe_inline" /> to <field
name="date_end"
class="oe_inline"
/></div>
</group>
<group>
<group name="attributes" colspan="2">
<field name="date_range_type_id" />
<field name="location_id" />
</group>
</group>
<notebook>
<page name="estimated_quantity" string="Estimated quantity">
<field
name="line_ids"
nolabel="1"
widget="x2many_2d_matrix"
field_x_axis="value_x"
field_y_axis="value_y"
field_value="product_uom_qty"
x_axis_clickable="0"
y_axis_clickable="0"
>
<tree>
<field name="value_x" />
<field name="value_y" />
<field name="product_uom_qty" />
</tree>
</field>
</page>
</notebook>
<footer>
<button
name="button_validate"
type="object"
string="Validate"
class="oe_highlight"
/>
<button class="oe_link" special="cancel" string="Cancel" />
</footer>
</form>
</field>
</record>
<record id="sale_forecast_wizard_view_form" model="ir.ui.view">
<field name="name">sale.forecast.wizard.form</field>
<field name="model">sale.forecast.wizard</field>
<field name="arch" type="xml">
<form string="Stock Demand Estimate Wizard">
<sheet>
<group name="dates">
<label for="date_start" string="Period" />
<div>
<field name="date_start" class="oe_inline" />
to
<field name="date_end" class="oe_inline" />
</div>
</group>
<group>
<group name="attributes">
<field name="date_range_type_id" />
<field name="location_id" />
</group>
</group>
<notebook>
<page name="products" string="Products">
<field name="product_ids" nolabel="1" />
</page>
</notebook>
</sheet>
<footer>
<button
name="create_sheet"
string="Prepare"
type="object"
class="oe_highlight"
/>
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="sale_forecast_wizard_action" model="ir.actions.act_window">
<field name="name">Create Sale Forecast</field>
<field name="res_model">sale.forecast.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="binding_model_id" ref="model_sale_forecast_sheet" />
</record>
<menuitem
id="sale_forecast_wizard_menu"
parent="sale_forecast.sale_forecast_menu"
action="sale_forecast_wizard_action"
sequence="3"
/>
</odoo>
96 changes: 96 additions & 0 deletions sale_forecast/wizards/wizards.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from odoo import _, api, fields, models
from odoo.exceptions import UserError


class SaleForecastSheet(models.TransientModel):
_name = "sale.forecast.sheet"
_inherits = {"stock.demand.estimate.sheet": "sheet_id"}
_description = "Stock Demand Estimate Sheet"

sheet_id = fields.Many2one(
comodel_name="stock.demand.estimate.sheet", required=True, ondelete="cascade"
)

def button_validate(self):
res = []
for line in self.line_ids:
if line.estimate_id:
line.estimate_id.product_uom_qty = line.product_uom_qty
res.append(line.estimate_id.id)
else:
data = self._prepare_estimate_data(line)
estimate = self.env["sale.forecast"].create(data)
res.append(estimate.id)
res = {
"domain": [("id", "in", res)],
"name": _("Sale Forecast"),
"src_model": "sale.forecast.wizard",
"view_type": "form",
"view_mode": "tree",
"res_model": "sale.forecast",
"type": "ir.actions.act_window",
}
return res

@api.onchange(
"date_start",
"date_end",
"date_range_type_id",
)
def _onchange_dates(self):
return self.sheet_id._onchange_dates()

@api.model
def _prepare_estimate_data(self, line):
return self.sheet_id._prepare_estimate_data(line)


class SaleForecastSheetLine(models.TransientModel):
_name = "sale.forecast.sheet.line"
_inherits = {"stock.demand.estimate.sheet.line": "sheet_line_id"}
_description = "Stock Demand Estimate Sheet Line"

sheet_line_id = fields.Many2one(
comodel_name="stock.demand.estimate.sheet.line",
required=True,
ondelete="cascade",
)


class SaleForecastWizard(models.TransientModel):
_name = "sale.forecast.wizard"
_inherits = {"stock.demand.estimate.wizard": "stock_wizard_id"}
_description = "Sale Forecast Wizard"

stock_wizard_id = fields.Many2one(
comodel_name="stock.demand.estimate.wizard", required=True, ondelete="cascade"
)

def create_sheet(self):
self.ensure_one()
if not self.product_ids:
raise UserError(_("You must select at least one product."))

# 2d matrix widget need real records to work
sheet = self.env["sale.forecast.sheet"].create(
{
"date_start": self.date_start,
"date_end": self.date_end,
"date_range_type_id": self.date_range_type_id.id,
"location_id": self.location_id.id,
"product_ids": [(6, 0, self.product_ids.ids)],
}
)
sheet._onchange_dates()

res = {
"name": _("Forecast Sheet"),
"src_model": "sale.forecast.wizard",
"view_type": "form",
"view_mode": "form",
"target": "new",
"res_model": "sale.forecast.sheet",
"res_id": sheet.id,
"type": "ir.actions.act_window",
}
return res
1 change: 1 addition & 0 deletions setup/sale_forecast/odoo/addons/sale_forecast
Loading

0 comments on commit 06a0889

Please sign in to comment.