Skip to content

Commit 7ede9d6

Browse files
author
matteo.tognini
committed
[IMP]l10n_it_riba_oca: riba policy expenses
1 parent 381481c commit 7ede9d6

4 files changed

Lines changed: 186 additions & 66 deletions

File tree

l10n_it_riba_oca/models/account.py

Lines changed: 128 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -173,17 +173,39 @@ def _onchange_riba_partner_bank_id(self):
173173
bank_ids = allowed_banks
174174
self.riba_partner_bank_id = bank_ids[0] if bank_ids else None
175175

176-
def month_check(self, invoice_date_due, all_date_due):
176+
def month_check(self, all_invoice_date):
177177
"""
178-
:param invoice_date_due: first due date of invoice
179-
:param all_date_due: list of due dates for partner
180-
:return: True if month of invoice_date_due is in a list of all_date_due
178+
Check if collection fees should be applied based on invoice date month.
179+
:param all_invoice_date: list of invoice dates for partner
180+
:return: True if month of current invoice date is already in all_invoice_date
181181
"""
182-
for d in all_date_due:
183-
if invoice_date_due.month == d.month and invoice_date_due.year == d.year:
182+
self.ensure_one()
183+
date = self.invoice_date or self.date or fields.Date.context_today(self)
184+
current_invoice_month = date.strftime("%Y-%m")
185+
for d in all_invoice_date:
186+
if d and current_invoice_month == d.strftime("%Y-%m"):
184187
return True
185188
return False
186189

190+
def maturity_check(self, invoice_date_due, all_date_due):
191+
"""
192+
Check if expenses should be applied based on exact maturity date.
193+
Used for 'one_a_maturity' policy.
194+
:param invoice_date_due: due date of current invoice
195+
:param all_date_due: list of existing due dates for partner
196+
:return: True if invoice_date_due already exists in all_date_due
197+
Example:
198+
- Invoice 1: Oct -> Dec (60 days) -> expenses YES
199+
- Invoice 2: Nov -> Dec (30 days) -> expenses NO (Dec already exists)
200+
- Invoice with 30/60 days: 2 different dates -> 2 expenses
201+
"""
202+
self.ensure_one()
203+
if self.partner_id.riba_policy_expenses == "one_a_maturity":
204+
for d in all_date_due:
205+
if invoice_date_due == d:
206+
return True
207+
return False
208+
187209
def _post(self, soft=True):
188210
inv_riba_no_bank = self.filtered(
189211
lambda x: x.is_riba_payment
@@ -210,80 +232,120 @@ def _post(self, soft=True):
210232
)
211233
return super()._post(soft=soft)
212234

235+
def _get_riba_expense_line_vals(self, pay_date=None):
236+
"""
237+
Prepare values for RiBa collection fees invoice line.
238+
:param pay_date: optional date for the expense description
239+
:return: dict with invoice line values
240+
"""
241+
self.ensure_one()
242+
service_prod = self.company_id.due_cost_service_id
243+
account = service_prod.product_tmpl_id.get_product_accounts(
244+
self.fiscal_position_id
245+
)["income"]
246+
line_vals = {
247+
"partner_id": self.partner_id.id,
248+
"product_id": service_prod.id,
249+
"move_id": self.id,
250+
"price_unit": self.invoice_payment_term_id.riba_payment_cost,
251+
"due_cost_line": True,
252+
"account_id": account.id,
253+
"sequence": 9999,
254+
}
255+
if pay_date:
256+
line_vals["name"] = self.env._("{line_name} for {month}-{year}").format(
257+
line_name=service_prod.name,
258+
month=pay_date.month,
259+
year=pay_date.year,
260+
)
261+
if self.company_id.due_cost_service_id.taxes_id:
262+
tax = self.fiscal_position_id.map_tax(service_prod.taxes_id)
263+
line_vals["tax_ids"] = [(4, tax.id)]
264+
return line_vals
265+
266+
def _add_riba_expense_line(self, pay_date=None):
267+
"""Add a RiBa collection fees line to the invoice."""
268+
self.ensure_one()
269+
line_vals = self._get_riba_expense_line_vals(pay_date)
270+
self.write({"invoice_line_ids": [(0, 0, line_vals)]})
271+
272+
def _apply_riba_collection_fees(self):
273+
"""
274+
Apply collection fees based on partner's riba_policy_expenses.
275+
"""
276+
self.ensure_one()
277+
278+
# Get existing move lines with RiBa expenses for this partner
279+
move_line = self.env["account.move.line"].search(
280+
[
281+
("partner_id", "=", self.partner_id.id),
282+
("move_id.invoice_payment_term_id.riba", "=", True),
283+
("move_id.state", "=", "posted"),
284+
]
285+
)
286+
move_line = move_line.filtered(
287+
lambda line: any(
288+
inv_line.due_cost_line for inv_line in line.move_id.invoice_line_ids
289+
)
290+
)
291+
move_line = move_line.filtered(lambda line: line.date_maturity is not False)
292+
move_line = move_line.sorted(key=lambda r: r.date_maturity)
293+
294+
previous_date_due = move_line.mapped("date_maturity")
295+
all_invoice_date = move_line.mapped("invoice_date")
296+
297+
# Compute payment term dates
298+
pterm_list = self.invoice_payment_term_id._compute_terms(
299+
date_ref=self.invoice_date or self.date or fields.Date.context_today(self),
300+
currency=self.currency_id,
301+
company=self.company_id,
302+
tax_amount=1,
303+
tax_amount_currency=1,
304+
untaxed_amount=0,
305+
sign=1 if self.is_inbound(include_receipts=True) else -1,
306+
untaxed_amount_currency=self.amount_untaxed,
307+
)
308+
309+
policy = self.partner_id.riba_policy_expenses
310+
311+
if policy == "one_per_invoice":
312+
# One expense per invoice, no date in description
313+
self._add_riba_expense_line()
314+
elif policy == "unlimited":
315+
# One expense for each due date, no checks
316+
for pay_date in pterm_list["line_ids"]:
317+
self._add_riba_expense_line(pay_date["date"])
318+
elif policy == "one_a_maturity":
319+
# One expense per maturity date, skip if date already exists
320+
for pay_date in pterm_list["line_ids"]:
321+
if not self.maturity_check(pay_date["date"], previous_date_due):
322+
self._add_riba_expense_line(pay_date["date"])
323+
else:
324+
# Default: one_a_month - one expense per month
325+
if not self.month_check(all_invoice_date):
326+
pay_date = pterm_list["line_ids"][0]
327+
self._add_riba_expense_line(pay_date["date"])
328+
329+
# Recompute invoice taxes
330+
self._sync_dynamic_lines(container={"records": self, "self": self})
331+
213332
def action_post(self):
214333
for invoice in self:
215-
# ---- Add a line with collection fees for each due date only for first due
216-
# ---- date of the month
334+
# Check if collection fees should be applied
217335
if (
218336
invoice.move_type != "out_invoice"
219337
or not invoice.invoice_payment_term_id
220338
or not invoice.invoice_payment_term_id.riba
221339
or invoice.invoice_payment_term_id.riba_payment_cost == 0.0
340+
or invoice.partner_id.commercial_partner_id.riba_exclude_expenses
222341
):
223342
continue
224343
if not invoice.company_id.due_cost_service_id:
225344
raise UserError(
226345
self.env._("Set a Service for Collection Fees in Company Config.")
227346
)
228-
# ---- Apply Collection Fees on invoice only on first due date of the month
229-
# ---- Get Date of first due date
230-
move_line = self.env["account.move.line"].search(
231-
[("partner_id", "=", invoice.partner_id.id)]
232-
)
233-
if not any(line.due_cost_line for line in move_line):
234-
move_line = self.env["account.move.line"]
235-
# ---- Filtered recordset with date_maturity
236-
move_line = move_line.filtered(lambda line: line.date_maturity is not False)
237-
# ---- Sorted
238-
move_line = move_line.sorted(key=lambda r: r.date_maturity)
239-
# ---- Get date
240-
previous_date_due = move_line.mapped("date_maturity")
241-
pterm = self.env["account.payment.term"].browse(
242-
self.invoice_payment_term_id.id
243-
)
244-
pterm_list = pterm._compute_terms(
245-
date_ref=self.invoice_date,
246-
currency=self.currency_id,
247-
company=self.company_id,
248-
tax_amount=1,
249-
tax_amount_currency=1,
250-
untaxed_amount=0,
251-
untaxed_amount_currency=0,
252-
sign=1,
253-
)
347+
invoice._apply_riba_collection_fees()
254348

255-
for pay_date in pterm_list["line_ids"]:
256-
if not self.month_check(pay_date["date"], previous_date_due):
257-
# ---- Get Line values for service product
258-
service_prod = invoice.company_id.due_cost_service_id
259-
account = service_prod.product_tmpl_id.get_product_accounts(
260-
invoice.fiscal_position_id
261-
)["income"]
262-
line_vals = {
263-
"partner_id": invoice.partner_id.id,
264-
"product_id": service_prod.id,
265-
"move_id": invoice.id,
266-
"price_unit": (
267-
invoice.invoice_payment_term_id.riba_payment_cost
268-
),
269-
"due_cost_line": True,
270-
"name": self.env._("{line_name} for {month}-{year}").format(
271-
line_name=service_prod.name,
272-
month=pay_date["date"].month,
273-
year=pay_date["date"].year,
274-
),
275-
"account_id": account.id,
276-
"sequence": 9999,
277-
}
278-
# ---- Update Line Value with tax if is set on product
279-
if invoice.company_id.due_cost_service_id.taxes_id:
280-
tax = invoice.fiscal_position_id.map_tax(service_prod.taxes_id)
281-
line_vals.update({"tax_ids": [(4, tax.id)]})
282-
invoice.write({"invoice_line_ids": [(0, 0, line_vals)]})
283-
# ---- recompute invoice taxes
284-
invoice._sync_dynamic_lines(
285-
container={"records": invoice, "self": invoice}
286-
)
287349
res = super().action_post()
288350

289351
# Automatic reconciliation for RiBa credit moves

l10n_it_riba_oca/models/partner.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ class ResPartner(models.Model):
2222
readonly=True,
2323
)
2424

25+
riba_exclude_expenses = fields.Boolean(
26+
string="Exclude expenses Ri.Ba.",
27+
)
28+
riba_policy_expenses = fields.Selection(
29+
[
30+
("one_a_month", "More invoices, one expense per Month"),
31+
("unlimited", "One expense per maturity"),
32+
("one_a_maturity", "More invoices, one expense per maturity"),
33+
("one_per_invoice", "One expense per invoice"),
34+
],
35+
default="one_a_month",
36+
string="Ri.Ba. Policy expenses",
37+
)
38+
2539
def _domain_property_riba_supplier_company_bank_id(self):
2640
"""Allow to select bank accounts linked to the current company."""
2741
return self.env["res.partner.bank"]._domain_riba_partner_bank_id()

l10n_it_riba_oca/tests/test_riba.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class TestInvoiceDueCost(riba_common.TestRibaCommon):
2121
def test_add_due_cost(self):
2222
# ---- Set Service in Company Config
2323
self.invoice.company_id.due_cost_service_id = self.service_due_cost.id
24+
self.invoice.partner_id.riba_policy_expenses = "unlimited"
2425
# ---- Validate Invoice
2526
self.invoice.action_post()
2627
# ---- Test Invoice has 2 line
@@ -774,6 +775,7 @@ def test_riba_inv_no_bank(self):
774775
cannot be confirmed (e.g. via the list view)
775776
"""
776777
self.invoice.company_id.due_cost_service_id = self.service_due_cost.id
778+
self.invoice.partner_id.riba_policy_expenses = "unlimited"
777779
self.invoice.riba_partner_bank_id = False
778780
with self.assertRaises(UserError) as err:
779781
self.invoice.action_post()
@@ -1035,3 +1037,40 @@ def test_charge_to_customer_without_partner(self):
10351037
bank_fee_line.partner_id,
10361038
"Bank fee line should not have partner_id when charge_to_customer is False",
10371039
)
1040+
1041+
def test_add_one_per_invoice_due_cost(self):
1042+
# ---- Set Service in Company Config
1043+
self.invoice.company_id.due_cost_service_id = self.service_due_cost.id
1044+
self.invoice.partner_id.riba_policy_expenses = "one_per_invoice"
1045+
# ---- Validate Invoice
1046+
self.invoice.action_post()
1047+
# ---- Test Invoice has 2 line
1048+
self.assertEqual(len(self.invoice.invoice_line_ids), 2)
1049+
# ---- Test Invoice Line for service cost
1050+
self.assertEqual(
1051+
self.invoice.invoice_line_ids[1].product_id.id, self.service_due_cost.id
1052+
)
1053+
new_inv = self.invoice.copy()
1054+
new_inv.action_post()
1055+
# ---- New invoice should have due cost line
1056+
self.assertEqual(
1057+
new_inv.invoice_line_ids[1].product_id.id, self.service_due_cost.id
1058+
)
1059+
self.assertEqual(len(new_inv.invoice_line_ids), 2)
1060+
1061+
def test_add_one_a_month_due_cost(self):
1062+
# ---- Set Service in Company Config
1063+
self.invoice.company_id.due_cost_service_id = self.service_due_cost.id
1064+
self.invoice.partner_id.riba_policy_expenses = "one_a_month"
1065+
# ---- Validate Invoice
1066+
self.invoice.action_post()
1067+
# ---- Test Invoice has 2 line
1068+
self.assertEqual(len(self.invoice.invoice_line_ids), 2)
1069+
# ---- Test Invoice Line for service cost
1070+
self.assertEqual(
1071+
self.invoice.invoice_line_ids[1].product_id.id, self.service_due_cost.id
1072+
)
1073+
new_inv = self.invoice.copy()
1074+
new_inv.action_post()
1075+
# ---- New invoice shouldn't have due cost line
1076+
self.assertEqual(len(new_inv.invoice_line_ids), 1)

l10n_it_riba_oca/views/partner_view.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
<group name="accounting_entries" position="after">
1616
<group string="RiBa" name="riba">
1717
<field name="group_riba" />
18+
<field name="riba_exclude_expenses" />
19+
<field
20+
name="riba_policy_expenses"
21+
invisible="riba_exclude_expenses"
22+
/>
1823
</group>
1924
</group>
2025
<field name="property_supplier_payment_term_id" position="after">

0 commit comments

Comments
 (0)