Skip to content

Commit ee058ef

Browse files
committed
[IMP] mis_builder_cash_flow_sale
1 parent 46a1a6a commit ee058ef

File tree

16 files changed

+624
-0
lines changed

16 files changed

+624
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from . import models
2+
from . import report
3+
from .hooks import create_cashflow_lines
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Copyright 2022-2023 Sergio Corato <https://github.com/sergiocorato>
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
{
4+
"name": "MIS Builder sale cash flow",
5+
"version": "14.0.1.0.0",
6+
"category": "other",
7+
"author": "Sergio Corato",
8+
"summary": "Generate automatically cash flow lines from sale order line.",
9+
"website": "https://github.com/sergiocorato/e-account",
10+
"license": "AGPL-3",
11+
"depends": [
12+
"mis_builder_cash_flow_inheritable",
13+
"account_payment_sale",
14+
"sale",
15+
"sale_order_line_date",
16+
],
17+
"data": [
18+
"views/sale.xml",
19+
"views/cashflow_line.xml",
20+
],
21+
"installable": True,
22+
"post_init_hook": "create_cashflow_lines",
23+
}

mis_builder_cash_flow_sale/hooks.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright 2022 Sergio Corato <https://github.com/sergiocorato>
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
import logging
4+
5+
from odoo import SUPERUSER_ID
6+
from odoo.api import Environment
7+
8+
_logger = logging.getLogger(__name__)
9+
10+
11+
def create_cashflow_lines(cr, registry):
12+
with Environment.manage():
13+
env = Environment(cr, SUPERUSER_ID, {})
14+
sales = env["sale.order"].search([], order="id")
15+
i_max = len(sales)
16+
i = 0
17+
for sale in sales:
18+
i += 1
19+
sale.order_line._refresh_cashflow_line()
20+
_logger.info(
21+
"Creating cashflow line for sale order #%s/%s" % (i, i_max)
22+
)

mis_builder_cash_flow_sale/i18n/it.po

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Translation of Odoo Server.
2+
# This file contains the translation of the following modules:
3+
# * mis_builder_cash_flow_purchase
4+
#
5+
msgid ""
6+
msgstr ""
7+
"Project-Id-Version: Odoo Server 12.0\n"
8+
"Report-Msgid-Bugs-To: \n"
9+
"POT-Creation-Date: 2023-05-01 14:57+0000\n"
10+
"PO-Revision-Date: 2023-05-01 14:57+0000\n"
11+
"Last-Translator: <>\n"
12+
"Language-Team: \n"
13+
"MIME-Version: 1.0\n"
14+
"Content-Type: text/plain; charset=UTF-8\n"
15+
"Content-Transfer-Encoding: \n"
16+
"Plural-Forms: \n"
17+
18+
#. module: mis_builder_cash_flow_purchase
19+
#: model_terms:ir.ui.view,arch_db:mis_builder_cash_flow_purchase.purchase_order_form
20+
msgid "Cash flow"
21+
msgstr ""
22+
23+
#. module: mis_builder_cash_flow_purchase
24+
#: model_terms:ir.ui.view,arch_db:mis_builder_cash_flow_purchase.purchase_order_form
25+
msgid "Cash flow lines"
26+
msgstr "Righe cash flow"
27+
28+
#. module: mis_builder_cash_flow_purchase
29+
#: code:addons/mis_builder_cash_flow_purchase/models/purchase.py:119
30+
#, python-format
31+
msgid "Due line #%s/%s of Purchase order %s"
32+
msgstr "Riga cash flow #%s/%s dell'ordine di acquisto %s"
33+
34+
#. module: mis_builder_cash_flow_purchase
35+
#: model:ir.model.fields,field_description:mis_builder_cash_flow_purchase.field_mis_cash_flow_forecast_line__purchase_balance_forecast
36+
msgid "Forecast balance"
37+
msgstr "Saldo previsto"
38+
39+
#. module: mis_builder_cash_flow_purchase
40+
#: model:ir.model.fields,field_description:mis_builder_cash_flow_purchase.field_purchase_order_line__cashflow_line_ids
41+
msgid "Forecast cashflow line"
42+
msgstr "Riga saldo previsto"
43+
44+
#. module: mis_builder_cash_flow_purchase
45+
#: model:ir.model,name:mis_builder_cash_flow_purchase.model_mis_cash_flow
46+
msgid "MIS Cash Flow"
47+
msgstr "MIS Cash flow"
48+
49+
#. module: mis_builder_cash_flow_purchase
50+
#: model:ir.model,name:mis_builder_cash_flow_purchase.model_mis_cash_flow_forecast_line
51+
msgid "MIS Cash Flow Forecast Line"
52+
msgstr "MIS Cash flow riga previsione"
53+
54+
#. module: mis_builder_cash_flow_purchase
55+
#: code:addons/mis_builder_cash_flow_purchase/models/purchase.py:35
56+
#, python-format
57+
msgid "Payment mode %s used in purchase orders must be of type fixed."
58+
msgstr "Il modo di pagamento %s usato negli ordini di acquisto deve essere di tipo fisso."
59+
60+
#. module: mis_builder_cash_flow_purchase
61+
#: model:ir.model.fields,field_description:mis_builder_cash_flow_purchase.field_mis_cash_flow_forecast_line__purchase_balance_currency
62+
msgid "Purchase Balance Currency"
63+
msgstr "Saldo acquisto in valuta"
64+
65+
#. module: mis_builder_cash_flow_purchase
66+
#: model:ir.model.fields,field_description:mis_builder_cash_flow_purchase.field_mis_cash_flow_forecast_line__purchase_invoiced_percent
67+
msgid "Purchase Invoiced Percent"
68+
msgstr "Fatturazione acquisto (%)"
69+
70+
#. module: mis_builder_cash_flow_purchase
71+
#: model:ir.model,name:mis_builder_cash_flow_purchase.model_purchase_order
72+
msgid "Purchase Order"
73+
msgstr "Ordine di acquisto"
74+
75+
#. module: mis_builder_cash_flow_purchase
76+
#: model:ir.model,name:mis_builder_cash_flow_purchase.model_purchase_order_line
77+
msgid "Purchase Order Line"
78+
msgstr "Riga ordine di acquisto"
79+
80+
#. module: mis_builder_cash_flow_purchase
81+
#: model:ir.model.fields,help:mis_builder_cash_flow_purchase.field_mis_cash_flow_forecast_line__purchase_balance_currency
82+
msgid "Purchase amount in vendor currency recomputed with delivered qty"
83+
msgstr "L'importo dell'acquisto in valuta del fornitore ricalcolato sulla quantità consegnata"
84+
85+
#. module: mis_builder_cash_flow_purchase
86+
#: model:ir.model.fields,field_description:mis_builder_cash_flow_purchase.field_mis_cash_flow_forecast_line__purchase_line_id
87+
msgid "Purchase order line"
88+
msgstr "Riga ordine di acquisto"
89+
90+
#. module: mis_builder_cash_flow_purchase
91+
#: model_terms:ir.ui.view,arch_db:mis_builder_cash_flow_purchase.mis_cash_flow_forecast_line_view_form
92+
#: model_terms:ir.ui.view,arch_db:mis_builder_cash_flow_purchase.mis_cash_flow_forecast_line_view_tree
93+
#: model_terms:ir.ui.view,arch_db:mis_builder_cash_flow_purchase.purchase_order_form
94+
msgid "Total forecast balance"
95+
msgstr "Totale saldo previsto"
96+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from . import cashflow_line
2+
from . import sale
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Copyright 2022-2023 Sergio Corato <https://github.com/sergiocorato>
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
from odoo import api, fields, models
4+
5+
6+
class CashFlowForecastLine(models.Model):
7+
_inherit = "mis.cash_flow.forecast_line"
8+
9+
sale_line_id = fields.Many2one(
10+
comodel_name="sale.order.line",
11+
ondelete="cascade",
12+
string="Sale order line",
13+
)
14+
sale_balance_currency = fields.Monetary(
15+
currency_field="currency_id",
16+
help="Sale amount in vendor currency recomputed with delivered qty",
17+
)
18+
sale_invoiced_percent = fields.Float(
19+
compute="_compute_balance_forecast", store=True
20+
)
21+
sale_balance_forecast = fields.Float(
22+
compute="_compute_balance_forecast",
23+
string="Forecast balance",
24+
store=True,
25+
)
26+
27+
@api.depends(
28+
"balance",
29+
"sale_balance_currency",
30+
"sale_line_id.qty_invoiced",
31+
"sale_line_id.product_uom_qty",
32+
"sale_line_id.qty_delivered",
33+
"sale_line_id.order_id.commitment_date",
34+
"sale_line_id.order_id.date_order",
35+
"sale_line_id.order_id.currency_id.rate",
36+
)
37+
def _compute_balance_forecast(self):
38+
for line in self:
39+
if line.sale_line_id:
40+
line.sale_invoiced_percent = line.sale_line_id.qty_invoiced / (
41+
max(
42+
[
43+
line.sale_line_id.product_uom_qty,
44+
line.sale_line_id.qty_delivered,
45+
1,
46+
]
47+
)
48+
)
49+
line.sale_balance_forecast = -line.currency_id._convert(
50+
line.sale_balance_currency or line.balance,
51+
line.sale_line_id.order_id.company_id.currency_id,
52+
line.sale_line_id.order_id.company_id,
53+
line.date,
54+
) * (1 - line.sale_invoiced_percent)
55+
else:
56+
line.sale_invoiced_percent = 0
57+
line.sale_balance_forecast = line.balance
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# Copyright 2022-2023 Sergio Corato <https://github.com/sergiocorato>
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
from odoo import _, api, fields, models
4+
from odoo.exceptions import ValidationError
5+
from odoo.tools import float_is_zero, float_round
6+
7+
8+
class SaleOrder(models.Model):
9+
_inherit = "sale.order"
10+
11+
def action_confirm(self):
12+
res = super().action_confirm()
13+
self.filtered(lambda x: x.state == "sale").mapped(
14+
"order_line"
15+
)._refresh_cashflow_line()
16+
return res
17+
18+
def write(self, vals):
19+
res = super().write(vals)
20+
for sale_order in self:
21+
if (
22+
vals.get("payment_term_id")
23+
or vals.get("commitment_date")
24+
or vals.get("payment_mode_id")
25+
):
26+
sale_order.order_line._refresh_cashflow_line()
27+
return res
28+
29+
@api.constrains("payment_mode_id")
30+
def _check_payment_mode(self):
31+
for record in self:
32+
if (
33+
record.payment_mode_id
34+
and record.payment_mode_id.bank_account_link != "fixed"
35+
):
36+
raise ValidationError(
37+
_("Payment mode %s used in sale orders must be of type fixed.")
38+
% record.payment_mode_id.name
39+
)
40+
41+
42+
class SaleOrderLine(models.Model):
43+
_inherit = "sale.order.line"
44+
45+
cashflow_line_ids = fields.One2many(
46+
comodel_name="mis.cash_flow.forecast_line",
47+
inverse_name="sale_line_id",
48+
string="Forecast cashflow line",
49+
)
50+
51+
@api.model
52+
def create(self, vals):
53+
line = super().create(vals)
54+
line._refresh_cashflow_line()
55+
return line
56+
57+
def write(self, vals):
58+
res = super().write(vals)
59+
if (
60+
vals.get("price_unit")
61+
or vals.get("commitment_date")
62+
or vals.get("product_uom_qty")
63+
or vals.get("discount")
64+
or vals.get("discount2")
65+
or vals.get("discount3")
66+
):
67+
self._refresh_cashflow_line()
68+
return res
69+
70+
def _refresh_cashflow_line(self):
71+
for line in self:
72+
line.cashflow_line_ids.unlink()
73+
if line.order_id.payment_mode_id.fixed_journal_id:
74+
journal_id = line.order_id.payment_mode_id.fixed_journal_id
75+
if line.price_total < 0:
76+
account_id = journal_id.payment_credit_account_id
77+
else:
78+
account_id = journal_id.payment_debit_account_id
79+
else:
80+
account_ids = self.env["account.account"].search(
81+
[
82+
(
83+
"user_type_id",
84+
"=",
85+
self.env.ref("account.data_account_type_liquidity").id,
86+
),
87+
("company_id", "=", line.order_id.company_id.id),
88+
],
89+
limit=1,
90+
)
91+
if not account_ids:
92+
return False
93+
account_id = account_ids[0]
94+
95+
# check is there is a residual prevision of amount to pay
96+
# compute actual value of sale_order row
97+
# as price_total do not change if delivered is more than ordered
98+
# (net unit price row * max between ordered and invoiced qty)
99+
max_qty = max([line.product_uom_qty, line.qty_delivered, 1])
100+
sale_balance_total_currency = (
101+
float_round(
102+
line.price_total / (line.product_uom_qty or 1),
103+
precision_rounding=line.order_id.currency_id.rounding,
104+
)
105+
) * max_qty
106+
# with this value compute not invoiced amount (delivered or not)
107+
# residual balance must be computed on cashflow line as it depends on
108+
# current invoice factor and currency rate
109+
# residual_balance = actual_row_balance *
110+
# (1 - (line.qty_invoiced / max_qty))
111+
112+
if not float_is_zero(
113+
sale_balance_total_currency,
114+
precision_rounding=line.order_id.currency_id.rounding,
115+
):
116+
totlines = [
117+
(
118+
(
119+
line.commitment_date
120+
or line.order_id.commitment_date
121+
or line.order_id.date_order
122+
).strftime("%Y-%m-%d"),
123+
sale_balance_total_currency,
124+
)
125+
]
126+
if line.order_id.payment_term_id:
127+
totlines = line.order_id.payment_term_id.compute(
128+
sale_balance_total_currency,
129+
line.commitment_date
130+
or line.order_id.commitment_date
131+
or line.order_id.date_order,
132+
)
133+
for i, dueline in enumerate(totlines, start=1):
134+
line.write(
135+
{
136+
"cashflow_line_ids": [
137+
(
138+
0,
139+
0,
140+
{
141+
"name": _(
142+
"Due line #%s/%s of Sale order %s"
143+
)
144+
% (i, len(totlines), line.order_id.name),
145+
"date": dueline[0],
146+
"sale_balance_currency": dueline[1],
147+
"currency_id": line.order_id.currency_id.id,
148+
"balance": 0,
149+
"sale_line_id": line.id,
150+
"account_id": account_id.id,
151+
"partner_id": line.order_id.partner_id.id,
152+
"res_id": line.id,
153+
"res_model_id": self.env.ref(
154+
"sale.model_sale_order_line"
155+
).id,
156+
},
157+
)
158+
]
159+
}
160+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import mis_cash_flow

0 commit comments

Comments
 (0)