Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ADD] sale_forecast #2

Open
wants to merge 6 commits into
base: 16.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions sale_forecast/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
=============
Sale Forecast
=============

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:c228a211927681f6c87e30ba3f2776cfb98fb52e4009f211d7d90bff440e1c61
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
:target: https://odoo-community.org/page/development-status
:alt: Alpha
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github
:target: https://github.com/OCA/sale-workflow/tree/16.0/sale_forecast
:alt: OCA/sale-workflow
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/sale-workflow-16-0/sale-workflow-16-0-sale_forecast
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&target_branch=16.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module allows to create demand estimates for a given product and
location, on configurable time periods.

The module does not provide in itself any specific usage of the estimates.

.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
`More details on development status <https://odoo-community.org/page/development-status>`_

**Table of contents**

.. contents::
:local:

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/sale-workflow/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/sale-workflow/issues/new?body=module:%20sale_forecast%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
~~~~~~~

* Therp BV

Contributors
~~~~~~~~~~~~

* Jordi Ballester Alomar <[email protected]>
* Lois Rilo <[email protected]>

Maintainers
~~~~~~~~~~~

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/sale-workflow <https://github.com/OCA/sale-workflow/tree/16.0/sale_forecast>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
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
25 changes: 25 additions & 0 deletions sale_forecast/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2016-20 ForgeFlow S.L. (https://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
{
"name": "Sale Forecast",
"summary": "Enable forecast for Sales",
"version": "16.0.1.0.0",
"author": "Therp BV, Odoo Community Association (OCA)",
"development_status": "Alpha",
"website": "https://github.com/OCA/sale-workflow",
"category": "Generic Modules/Sale",
"depends": [
"sale",
"stock",
"web_widget_x2many_2d_matrix",
"date_range",
],
"data": [
"security/ir.model.access.csv",
"security/sale_security.xml",
"views/sale_forecast_view.xml",
"wizards/sale_forecast_wizard_view.xml",
],
"license": "LGPL-3",
"installable": True,
}
2 changes: 2 additions & 0 deletions sale_forecast/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import date_range
from . import sale_forecast
19 changes: 19 additions & 0 deletions sale_forecast/models/date_range.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2019 ForgeFlow S.L. (https://www.forgeflow.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from odoo import api, fields, models


class DateRange(models.Model):
_inherit = "date.range"

days = fields.Integer(
string="Days between dates",
compute="_compute_days",
readonly=True,
)

@api.depends("date_start", "date_end")
def _compute_days(self):
for rec in self.filtered(lambda x: x.date_start and x.date_end):
rec.days = abs((rec.date_end - rec.date_start).days) + 1
158 changes: 158 additions & 0 deletions sale_forecast/models/sale_forecast.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Copyright 2016-20 ForgeFlow S.L. (https://www.forgeflow.com)
# Copyright 2016 Aleph Objects, Inc. (https://www.alephobjects.com/)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).

from datetime import date, timedelta

from odoo import _, api, fields, models
from odoo.exceptions import UserError


class SaleForecast(models.Model):
_name = "sale.forecast"
_description = "Sale Forecast"

date_from = fields.Date(
compute="_compute_dates", string="From (computed)", store=True
)
date_to = fields.Date(compute="_compute_dates", string="To (computed)", store=True)
manual_date_from = fields.Date(string="From")
manual_date_to = fields.Date(string="To")
manual_duration = fields.Integer(
string="Duration", help="Duration (in days)", default=1
)
duration = fields.Integer(
compute="_compute_dates", string="Duration (computed))", store=True
)
product_id = fields.Many2one(
comodel_name="product.product", string="Product", required=True
)
categ_id = fields.Many2one(related="product_id.categ_id", store=True)
product_uom = fields.Many2one(comodel_name="uom.uom", string="Unit of measure")
location_id = fields.Many2one(
comodel_name="stock.location", string="Location", required=True
)
product_uom_qty = fields.Float(string="Quantity", digits="Product Unit of Measure")
product_target_uom_qty = fields.Float(
string="Target Quantity", digits="Product Unit of Measure"
)
product_qty = fields.Float(
string="Quantity (Product UoM)",
compute="_compute_product_quantity",
inverse="_inverse_product_quantity",
digits=0,
store=True,
help="Quantity in the default UoM of the product",
readonly=True,
)
daily_qty = fields.Float(string="Quantity / Day", compute="_compute_daily_qty")
company_id = fields.Many2one(
comodel_name="res.company",
string="Company",
required=True,
default=lambda self: self.env.company,
)
date_range_id = fields.Many2one(
comodel_name="date.range", string="Forecasting Period", ondelete="restrict"
)

@api.depends(
"date_range_id", "manual_duration", "manual_date_from", "manual_date_to"
)
def _compute_dates(self):
today = date.today()
for rec in self:
if rec.date_range_id:
rec.date_from = rec.date_range_id.date_start
rec.date_to = rec.date_range_id.date_end
rec.duration = rec.date_range_id.days
continue
rec.date_from = rec.manual_date_from or today
if rec.manual_date_to:
rec.date_to = rec.manual_date_to
rec.duration = (rec.manual_date_to - rec.date_from).days + 1
elif rec.manual_duration:
rec.date_to = rec.date_from + timedelta(days=rec.manual_duration - 1)
rec.duration = rec.manual_duration
else:
rec.date_to = rec.date_from + timedelta(days=1)
rec.duration = 2

@api.depends("product_qty", "duration")
def _compute_daily_qty(self):
for rec in self:
if rec.duration:
rec.daily_qty = rec.product_qty / rec.duration
else:
rec.daily_qty = 0.0

@api.depends("product_id", "product_uom", "product_uom_qty")
def _compute_product_quantity(self):
for rec in self:
if rec.product_uom:
rec.product_qty = rec.product_uom._compute_quantity(
rec.product_uom_qty, rec.product_id.uom_id
)
else:
rec.product_qty = rec.product_uom_qty

def _inverse_product_quantity(self):
raise UserError(
_(
"The requested operation cannot be "
"processed because of a programming error "
"setting the `product_qty` field instead "
"of the `product_uom_qty`."
)
)

def name_get(self):
res = []
for rec in self:
if rec.date_range_id:
name = "{} - {} - {}".format(
rec.date_range_id.name,
rec.product_id.name,
rec.location_id.name,
)
else:
name = "{} - {}: {} - {}".format(
rec.date_from,
rec.date_to,
rec.product_id.name,
rec.location_id.name,
)
res.append((rec.id, name))
return res

@api.onchange("manual_date_to")
def _onchange_manual_date_to(self):
for rec in self:
if rec.manual_date_from:
rec.manual_duration = (
rec.manual_date_to - rec.manual_date_from
).days + 1

@api.onchange("manual_duration")
def _onchange_manual_duration(self):
for rec in self:
if rec.manual_date_from:
rec.manual_date_to = rec.manual_date_from + timedelta(
days=rec.manual_duration - 1
)

@api.model
def get_quantity_by_date_range(self, date_start, date_end):
"""To be used in other modules"""
# Check if the dates overlap with the period
period_date_start = self.date_from
period_date_end = self.date_to

# We need only the periods that overlap
# the dates introduced by the user.
if period_date_start <= date_end and period_date_end >= date_start:
overlap_date_start = max(period_date_start, date_start)
overlap_date_end = min(period_date_end, date_end)
days = (abs(overlap_date_end - overlap_date_start)).days + 1
return days * self.daily_qty
return 0.0
2 changes: 2 additions & 0 deletions sale_forecast/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* Jordi Ballester Alomar <[email protected]>
* Lois Rilo <[email protected]>
4 changes: 4 additions & 0 deletions sale_forecast/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
This module allows to create demand estimates for a given product and
location, on configurable time periods.

The module does not provide in itself any specific usage of the estimates.
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.forecast,model_sale_forecast,sales_team.group_sale_salesman,1,0,0,0
access_sale_forecast_system,sale.forecast.system,model_sale_forecast,sales_team.group_sale_manager,1,1,1,1
access_sale_forecast_sheet,access_sale_forecast_sheet,sale_forecast.model_sale_forecast_sheet,sales_team.group_sale_manager,1,1,1,1
access_sale_forecast_sheet_line,access_sale_forecast_sheet_line,sale_forecast.model_sale_forecast_sheet_line,sales_team.group_sale_manager,1,1,1,1
access_sale_forecast_wizard,access_sale_forecast_wizard,sale_forecast.model_sale_forecast_wizard,sales_team.group_sale_manager,1,1,1,1
11 changes: 11 additions & 0 deletions sale_forecast/security/sale_security.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="sale_forecast_comp_rule" model="ir.rule">
<field name="name">Sale forecast multi-company</field>
<field name="model_id" ref="model_sale_forecast" />
<field name="global" eval="True" />
<field
name="domain_force"
>['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
</odoo>
Binary file added sale_forecast/static/description/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading