odoo-addon-l10n-it-riba-oca 18.0.1.0.0.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. odoo/addons/l10n_it_riba_oca/README.rst +175 -0
  2. odoo/addons/l10n_it_riba_oca/__init__.py +9 -0
  3. odoo/addons/l10n_it_riba_oca/__manifest__.py +59 -0
  4. odoo/addons/l10n_it_riba_oca/data/riba_sequence.xml +14 -0
  5. odoo/addons/l10n_it_riba_oca/demo/riba_demo.xml +39 -0
  6. odoo/addons/l10n_it_riba_oca/hooks.py +17 -0
  7. odoo/addons/l10n_it_riba_oca/i18n/it.po +1686 -0
  8. odoo/addons/l10n_it_riba_oca/i18n/l10n_it_riba.pot +1573 -0
  9. odoo/addons/l10n_it_riba_oca/i18n/l10n_it_riba_oca.pot +1554 -0
  10. odoo/addons/l10n_it_riba_oca/migrations/18.0.1.0.0/pre-migrate.py +16 -0
  11. odoo/addons/l10n_it_riba_oca/models/__init__.py +14 -0
  12. odoo/addons/l10n_it_riba_oca/models/account.py +563 -0
  13. odoo/addons/l10n_it_riba_oca/models/account_config.py +35 -0
  14. odoo/addons/l10n_it_riba_oca/models/ir_ui_menu.py +27 -0
  15. odoo/addons/l10n_it_riba_oca/models/partner.py +35 -0
  16. odoo/addons/l10n_it_riba_oca/models/riba.py +560 -0
  17. odoo/addons/l10n_it_riba_oca/models/riba_config.py +120 -0
  18. odoo/addons/l10n_it_riba_oca/readme/CONFIGURE.md +33 -0
  19. odoo/addons/l10n_it_riba_oca/readme/CONTRIBUTORS.md +17 -0
  20. odoo/addons/l10n_it_riba_oca/readme/DESCRIPTION.md +3 -0
  21. odoo/addons/l10n_it_riba_oca/readme/USAGE.md +35 -0
  22. odoo/addons/l10n_it_riba_oca/report/__init__.py +4 -0
  23. odoo/addons/l10n_it_riba_oca/report/report.xml +16 -0
  24. odoo/addons/l10n_it_riba_oca/report/slip_qweb.py +18 -0
  25. odoo/addons/l10n_it_riba_oca/security/ir.model.access.csv +23 -0
  26. odoo/addons/l10n_it_riba_oca/security/riba_security.xml +34 -0
  27. odoo/addons/l10n_it_riba_oca/static/description/icon.png +0 -0
  28. odoo/addons/l10n_it_riba_oca/static/description/index.html +512 -0
  29. odoo/addons/l10n_it_riba_oca/tests/__init__.py +10 -0
  30. odoo/addons/l10n_it_riba_oca/tests/riba_common.py +339 -0
  31. odoo/addons/l10n_it_riba_oca/tests/test_account_move.py +54 -0
  32. odoo/addons/l10n_it_riba_oca/tests/test_menu.py +51 -0
  33. odoo/addons/l10n_it_riba_oca/tests/test_riba.py +905 -0
  34. odoo/addons/l10n_it_riba_oca/views/account_config_view.xml +46 -0
  35. odoo/addons/l10n_it_riba_oca/views/account_view.xml +205 -0
  36. odoo/addons/l10n_it_riba_oca/views/configuration_view.xml +94 -0
  37. odoo/addons/l10n_it_riba_oca/views/partner_view.xml +29 -0
  38. odoo/addons/l10n_it_riba_oca/views/riba_detail_view.xml +97 -0
  39. odoo/addons/l10n_it_riba_oca/views/riba_view.xml +296 -0
  40. odoo/addons/l10n_it_riba_oca/views/slip_report.xml +149 -0
  41. odoo/addons/l10n_it_riba_oca/views/wizard_credit.xml +46 -0
  42. odoo/addons/l10n_it_riba_oca/views/wizard_due_date_settlement.xml +37 -0
  43. odoo/addons/l10n_it_riba_oca/views/wizard_past_due.xml +67 -0
  44. odoo/addons/l10n_it_riba_oca/views/wizard_presentation.xml +48 -0
  45. odoo/addons/l10n_it_riba_oca/views/wizard_riba_file_export.xml +33 -0
  46. odoo/addons/l10n_it_riba_oca/views/wizard_riba_issue.xml +46 -0
  47. odoo/addons/l10n_it_riba_oca/views/wizard_riba_payment_date.xml +45 -0
  48. odoo/addons/l10n_it_riba_oca/wizard/__init__.py +16 -0
  49. odoo/addons/l10n_it_riba_oca/wizard/wizard_credit.py +280 -0
  50. odoo/addons/l10n_it_riba_oca/wizard/wizard_due_date_settlement.py +24 -0
  51. odoo/addons/l10n_it_riba_oca/wizard/wizard_past_due.py +319 -0
  52. odoo/addons/l10n_it_riba_oca/wizard/wizard_presentation_riba.py +47 -0
  53. odoo/addons/l10n_it_riba_oca/wizard/wizard_riba_file_export.py +429 -0
  54. odoo/addons/l10n_it_riba_oca/wizard/wizard_riba_issue.py +143 -0
  55. odoo/addons/l10n_it_riba_oca/wizard/wizard_riba_multiple_payment.py +107 -0
  56. odoo/addons/l10n_it_riba_oca/wizard/wizard_riba_multiple_payment_views.xml +46 -0
  57. odoo/addons/l10n_it_riba_oca/wizard/wizard_riba_payment_date.py +31 -0
  58. odoo_addon_l10n_it_riba_oca-18.0.1.0.0.8.dist-info/METADATA +197 -0
  59. odoo_addon_l10n_it_riba_oca-18.0.1.0.0.8.dist-info/RECORD +61 -0
  60. odoo_addon_l10n_it_riba_oca-18.0.1.0.0.8.dist-info/WHEEL +5 -0
  61. odoo_addon_l10n_it_riba_oca-18.0.1.0.0.8.dist-info/top_level.txt +1 -0
@@ -0,0 +1,16 @@
1
+ # Copyright 2023 Simone Rubino - AionTech
2
+ # Copyright 2024 Nextev Srl
3
+ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
4
+
5
+
6
+ # pylint: disable=odoo-addons-relative-import
7
+ # because
8
+ # from ... import hooks
9
+ # raises
10
+ # ImportError: attempted relative import with no known parent package
11
+ from odoo.addons.l10n_it_riba_oca import hooks
12
+
13
+
14
+ def migrate(cr, installed_version):
15
+ # Used by OpenUpgrade when module is in `apriori`
16
+ hooks.migrate_old_module(cr)
@@ -0,0 +1,14 @@
1
+ # Copyright (C) 2012 Andrea Cometa.
2
+ # Email: info@andreacometa.it
3
+ # Web site: http://www.andreacometa.it
4
+ # Copyright (C) 2012 Associazione OpenERP Italia
5
+ # (<http://www.odoo-italia.org>).
6
+ # Copyright (C) 2012-2017 Lorenzo Battistini - Agile Business Group
7
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
8
+
9
+ from . import account
10
+ from . import account_config
11
+ from . import ir_ui_menu
12
+ from . import partner
13
+ from . import riba
14
+ from . import riba_config
@@ -0,0 +1,563 @@
1
+ # Copyright (C) 2012 Andrea Cometa.
2
+ # Email: info@andreacometa.it
3
+ # Web site: http://www.andreacometa.it
4
+ # Copyright (C) 2012 Associazione OpenERP Italia
5
+ # (<http://www.odoo-italia.org>).
6
+ # Copyright (C) 2012-2018 Lorenzo Battistini - Agile Business Group
7
+ # Copyright 2023 Simone Rubino - Aion Tech
8
+ # Copyright 2024 Nextev Srl
9
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
10
+
11
+ from odoo import api, fields, models
12
+ from odoo.exceptions import UserError
13
+
14
+
15
+ class AccountPaymentTerm(models.Model):
16
+ # flag riba utile a distinguere la modalità di pagamento
17
+ _inherit = "account.payment.term"
18
+
19
+ riba = fields.Boolean("RiBa", default=False)
20
+ riba_payment_cost = fields.Float(
21
+ "RiBa Collection Fees",
22
+ digits="Account",
23
+ help="Collection fees amount. If different from 0, "
24
+ "for each payment deadline an invoice line will be added "
25
+ "to invoice, with this amount.",
26
+ )
27
+
28
+
29
+ class ResBankAddField(models.Model):
30
+ _inherit = "res.bank"
31
+ banca_estera = fields.Boolean("Foreign Bank")
32
+
33
+
34
+ class ResPartnerBankAdd(models.Model):
35
+ _inherit = "res.partner.bank"
36
+ codice_sia = fields.Char(
37
+ "SIA Code",
38
+ size=5,
39
+ help="Identification Code of the Company in the Interbank System.",
40
+ )
41
+
42
+ @api.model
43
+ def _domain_riba_partner_bank_id(self):
44
+ """Domain to select bank accounts linked to the current company."""
45
+ company = self.env.company
46
+ return [
47
+ ("partner_id", "=", company.partner_id.id),
48
+ ]
49
+
50
+ def _check_protected_records(self):
51
+ protected_records = (
52
+ self.env["account.move"]
53
+ .search([("riba_partner_bank_id", "in", self.ids)])
54
+ .mapped("riba_partner_bank_id")
55
+ )
56
+ if protected_records:
57
+ acc_numbers = [bank.acc_number for bank in protected_records]
58
+ message = self.env._(
59
+ "The bank accounts with accreditation code {acc_codes}s"
60
+ " cannot be deleted as they are used in invoices."
61
+ " If possible, archive the bank account",
62
+ acc_codes=", ".join(acc_numbers),
63
+ )
64
+ raise UserError(message)
65
+
66
+ def unlink(self):
67
+ self._check_protected_records()
68
+ return super().unlink()
69
+
70
+
71
+ class AccountMove(models.Model):
72
+ _inherit = "account.move"
73
+
74
+ def _compute_open_amount(self):
75
+ today = fields.Date.today()
76
+ for invoice in self:
77
+ if invoice.is_riba_payment:
78
+ open_amount_line_ids = invoice.line_ids.filtered(
79
+ lambda line, today=today: line.riba
80
+ and line.display_type == "payment_term"
81
+ and line.date_maturity > today
82
+ )
83
+ invoice.open_amount = sum(open_amount_line_ids.mapped("balance"))
84
+ else:
85
+ invoice.open_amount = 0.0
86
+
87
+ riba_credited_ids = fields.One2many(
88
+ "riba.slip", "credit_move_id", "Credited RiBa Slips", readonly=True
89
+ )
90
+
91
+ open_amount = fields.Float(
92
+ digits="Account",
93
+ compute="_compute_open_amount",
94
+ default=0.0,
95
+ help="Amount currently only supposed to be paid, but has actually not happened",
96
+ )
97
+
98
+ riba_past_due_ids = fields.One2many(
99
+ "riba.slip.line", "past_due_move_id", "Past Due RiBa Slips", readonly=True
100
+ )
101
+
102
+ past_due_move_line_ids = fields.Many2many(
103
+ "account.move.line",
104
+ "invoice_past_due_line_rel",
105
+ "move_id",
106
+ "line_id",
107
+ "Past Due Journal Items",
108
+ )
109
+
110
+ is_riba_payment = fields.Boolean(
111
+ "Is RiBa Payment", related="invoice_payment_term_id.riba"
112
+ )
113
+
114
+ riba_partner_bank_id = fields.Many2one(
115
+ "res.partner.bank",
116
+ string="RiBa Bank Account",
117
+ help="Bank Account Number to which the RiBa will be debited. "
118
+ "If not set, first bank in partner will be used.",
119
+ )
120
+
121
+ def _domain_riba_supplier_company_bank_id(self):
122
+ """Allow to select bank accounts linked to the current company."""
123
+ return self.env["res.partner.bank"]._domain_riba_partner_bank_id()
124
+
125
+ riba_supplier_company_bank_id = fields.Many2one(
126
+ comodel_name="res.partner.bank",
127
+ compute="_compute_riba_supplier_company_bank_id",
128
+ domain=_domain_riba_supplier_company_bank_id,
129
+ help="Bank account used for the RiBa of this vendor bill.",
130
+ store=True,
131
+ string="Company Bank Account for Supplier",
132
+ )
133
+
134
+ @api.depends(
135
+ "is_riba_payment",
136
+ )
137
+ def _compute_riba_supplier_company_bank_id(self):
138
+ for move in self:
139
+ is_riba_payment = move.is_riba_payment
140
+ is_purchase_document = move.is_purchase_document(include_receipts=True)
141
+ partner_riba_bank_account = (
142
+ move.partner_id.property_riba_supplier_company_bank_id
143
+ )
144
+ if is_riba_payment and is_purchase_document and partner_riba_bank_account:
145
+ riba_bank_account = partner_riba_bank_account
146
+ else:
147
+ riba_bank_account = False
148
+ move.riba_supplier_company_bank_id = riba_bank_account
149
+
150
+ @api.model_create_multi
151
+ def create(self, vals_list):
152
+ invoices = super().create(vals_list)
153
+ for invoice in invoices:
154
+ if not invoice.riba_partner_bank_id:
155
+ invoice._onchange_riba_partner_bank_id()
156
+ return invoices
157
+
158
+ @api.onchange("partner_id", "invoice_payment_term_id", "move_type")
159
+ def _onchange_riba_partner_bank_id(self):
160
+ allowed_banks = (
161
+ self.partner_id.bank_ids or self.partner_id.commercial_partner_id.bank_ids
162
+ )
163
+ if (
164
+ not self.riba_partner_bank_id
165
+ or self.riba_partner_bank_id not in allowed_banks
166
+ ):
167
+ bank_ids = self.env["res.partner.bank"]
168
+ if (
169
+ self.partner_id
170
+ and self.is_riba_payment
171
+ and self.move_type in ["out_invoice", "out_refund"]
172
+ ):
173
+ bank_ids = allowed_banks
174
+ self.riba_partner_bank_id = bank_ids[0] if bank_ids else None
175
+
176
+ def month_check(self, invoice_date_due, all_date_due):
177
+ """
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
181
+ """
182
+ for d in all_date_due:
183
+ if invoice_date_due.month == d.month and invoice_date_due.year == d.year:
184
+ return True
185
+ return False
186
+
187
+ def _post(self, soft=True):
188
+ inv_riba_no_bank = self.filtered(
189
+ lambda x: x.is_riba_payment
190
+ and x.move_type == "out_invoice"
191
+ and not x.riba_partner_bank_id
192
+ )
193
+ if inv_riba_no_bank:
194
+ inv_details = (
195
+ self.env._(
196
+ 'Invoice %(name)s for customer "%(customer_name)s", '
197
+ "total %(amount)s",
198
+ name=inv.display_name,
199
+ customer_name=inv.partner_id.display_name,
200
+ amount=inv.amount_total,
201
+ )
202
+ for inv in inv_riba_no_bank
203
+ )
204
+ raise UserError(
205
+ self.env._(
206
+ "Cannot post invoices with C/O payments without bank. "
207
+ "Please check the following invoices:\n\n- "
208
+ + "\n- ".join(inv_details)
209
+ )
210
+ )
211
+ return super()._post(soft=soft)
212
+
213
+ def action_post(self):
214
+ for invoice in self:
215
+ # ---- Add a line with collection fees for each due date only for first due
216
+ # ---- date of the month
217
+ if (
218
+ invoice.move_type != "out_invoice"
219
+ or not invoice.invoice_payment_term_id
220
+ or not invoice.invoice_payment_term_id.riba
221
+ or invoice.invoice_payment_term_id.riba_payment_cost == 0.0
222
+ ):
223
+ continue
224
+ if not invoice.company_id.due_cost_service_id:
225
+ raise UserError(
226
+ self.env._("Set a Service for Collection Fees in Company Config.")
227
+ )
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
+ )
254
+
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
+ )
287
+ res = super().action_post()
288
+
289
+ # Automatic reconciliation for RiBa credit moves
290
+ # When a credit move is posted and there are related RiBa slips,
291
+ # we need to reconcile the acceptance and credit move lines
292
+ if self.riba_credited_ids:
293
+ # Get the acceptance account from the RiBa configuration
294
+ # Note: All RiBa slips should have the same configuration
295
+ acceptance_account_id = self.riba_credited_ids[
296
+ 0
297
+ ].config_id.acceptance_account_id
298
+
299
+ # Find credit move lines with the acceptance account
300
+ credit_lines = self.line_ids.filtered(
301
+ lambda line: line.account_id == acceptance_account_id
302
+ )
303
+
304
+ # Find acceptance move lines with the same account
305
+ acceptance_lines = (
306
+ self.riba_credited_ids.acceptance_move_ids.line_ids.filtered(
307
+ lambda line: line.account_id == acceptance_account_id
308
+ )
309
+ )
310
+
311
+ # Reconcile the matching lines to complete the RiBa credit flow
312
+ if credit_lines and acceptance_lines:
313
+ lines = credit_lines | acceptance_lines
314
+ lines.reconcile()
315
+ return res
316
+
317
+ def button_draft(self):
318
+ # ---- Delete Collection Fees Line of invoice when set Back to Draft
319
+ # ---- line was added on new validate
320
+ res = super().button_draft()
321
+ for invoice in self:
322
+ due_cost_line_ids = invoice.get_due_cost_line_ids()
323
+ if due_cost_line_ids:
324
+ invoice.write(
325
+ {"invoice_line_ids": [(2, id, 0) for id in due_cost_line_ids]}
326
+ )
327
+ invoice._sync_dynamic_lines(
328
+ container={"records": invoice, "self": invoice}
329
+ )
330
+ return res
331
+
332
+ def button_cancel(self):
333
+ for invoice in self:
334
+ # we get move_lines with date_maturity and check if they are
335
+ # present in some riba_slip_line
336
+ move_line_model = self.env["account.move.line"]
337
+ rdml_model = self.env["riba.slip.move.line"]
338
+ move_line_ids = move_line_model.search(
339
+ [("move_id", "=", invoice.id), ("date_maturity", "!=", False)]
340
+ )
341
+ if move_line_ids:
342
+ riba_line_ids = rdml_model.search(
343
+ [("move_line_id", "in", [m.id for m in move_line_ids])]
344
+ )
345
+ if riba_line_ids:
346
+ if len(riba_line_ids) > 1:
347
+ riba_line_ids = riba_line_ids[0]
348
+ raise UserError(
349
+ self.env._(
350
+ "Invoice is linked to RiBa slip No. %(riba)s",
351
+ riba=riba_line_ids.riba_line_id.slip_id.name,
352
+ )
353
+ )
354
+ return super().button_cancel()
355
+
356
+ def copy(self, default=None):
357
+ # Delete Collection Fees Line of invoice when copying
358
+ invoices = super().copy(
359
+ default=default,
360
+ )
361
+ for invoice in invoices:
362
+ due_cost_line_ids = invoice.get_due_cost_line_ids()
363
+ if due_cost_line_ids:
364
+ invoice.write(
365
+ {"invoice_line_ids": [(2, id, 0) for id in due_cost_line_ids]}
366
+ )
367
+ invoice._sync_dynamic_lines(
368
+ container={"records": invoice, "self": invoice}
369
+ )
370
+ # if the bank account is archived do not allow the use in the new invoice
371
+ if not invoice.riba_partner_bank_id.active:
372
+ invoice.riba_partner_bank_id = False
373
+ return invoices
374
+
375
+ def get_due_cost_line_ids(self):
376
+ return self.invoice_line_ids.filtered(lambda line: line.due_cost_line).ids
377
+
378
+ def action_riba_payment_date(self):
379
+ return {
380
+ "type": "ir.actions.act_window",
381
+ "name": "RiBa Payment Date",
382
+ "res_model": "riba.payment.date",
383
+ "view_mode": "form",
384
+ "target": "new",
385
+ "context": self.env.context,
386
+ }
387
+
388
+
389
+ # se slip_line_ids == None allora non è stata emessa
390
+ class AccountMoveLine(models.Model):
391
+ _inherit = "account.move.line"
392
+
393
+ slip_line_id = fields.Many2one("riba.slip.line", "RiBa Line", readonly=True)
394
+ slip_line_ids = fields.One2many(
395
+ "riba.slip.move.line", "move_line_id", "RiBa Detail"
396
+ )
397
+ riba = fields.Boolean(
398
+ related="move_id.invoice_payment_term_id.riba", string="RiBa", store=False
399
+ )
400
+ past_due_invoice_ids = fields.Many2many(
401
+ "account.move",
402
+ "invoice_past_due_line_rel",
403
+ "line_id",
404
+ "move_id",
405
+ "Past Due Invoices",
406
+ )
407
+ iban = fields.Char(
408
+ related="move_id.riba_partner_bank_id.acc_number", string="IBAN", store=False
409
+ )
410
+ due_cost_line = fields.Boolean("RiBa Collection Fees Line")
411
+
412
+ @api.model
413
+ def fields_view_get(
414
+ self, view_id=None, view_type="form", toolbar=False, submenu=False
415
+ ):
416
+ model_data_obj = self.env["ir.model.data"]
417
+ ids = model_data_obj.search(
418
+ [
419
+ ("module", "=", "l10n_it_riba_oca"),
420
+ ("name", "=", "view_riba_to_issue_tree"),
421
+ ]
422
+ )
423
+ if ids:
424
+ view_payments_tree_id = model_data_obj.get_object_reference(
425
+ "l10n_it_riba_oca", "view_riba_to_issue_tree"
426
+ )
427
+ if ids and view_id == view_payments_tree_id[1]:
428
+ # Use RiBa slip
429
+ result = super(models.Model, self).fields_view_get(
430
+ view_id=view_id,
431
+ view_type=view_type,
432
+ toolbar=toolbar,
433
+ submenu=submenu,
434
+ )
435
+ else:
436
+ # Use special views for account.move.line object
437
+ # (for ex. list view contains user defined fields)
438
+ result = super().fields_view_get(
439
+ view_id=view_id,
440
+ view_type=view_type,
441
+ toolbar=toolbar,
442
+ submenu=submenu,
443
+ )
444
+ return result
445
+
446
+ def update_paid_riba_lines(self):
447
+ """
448
+ Update RiBa line status to 'paid' when move lines are reconciled.
449
+
450
+ This method is called during reconciliation to mark RiBa lines as paid
451
+ when the related account move lines are reconciled, but only if:
452
+ - We're not in a past_due_reconciliation context
453
+ - The RiBa line is in a state that allows transition to 'paid'
454
+ """
455
+ sl = self.slip_line_id
456
+ # Only update if not processing past due reconciliation and line exists
457
+ if not self.env.context.get("past_due_reconciliation") and sl.state in [
458
+ "confirmed", # Accepted by bank but not yet credited
459
+ "credited", # Already credited by bank
460
+ ]:
461
+ # Mark the RiBa line as paid
462
+ sl.state = "paid"
463
+
464
+ def reconcile(self):
465
+ """
466
+ Override reconcile to update RiBa line states.
467
+
468
+ When account move lines are reconciled, we need to check if any
469
+ of them are related to RiBa lines and update their status accordingly.
470
+ """
471
+ res = super().reconcile()
472
+ # Update RiBa line states for each reconciled line
473
+ for line in self:
474
+ line.update_paid_riba_lines()
475
+ return res
476
+
477
+ def action_riba_issue(self):
478
+ for line in self:
479
+ if not line.move_id.riba_partner_bank_id.active:
480
+ raise UserError(
481
+ self.env._(
482
+ "Non è possibile emettere una riba legata ad un IBAN "
483
+ "archiviato; riga: %s , contatto: %s"
484
+ )
485
+ % (line.name, line.partner_id.name)
486
+ )
487
+ ctx = dict(self.env.context)
488
+ ctx.pop("active_id", None)
489
+ ctx["active_ids"] = self.ids
490
+ ctx["active_model"] = "account.move.line"
491
+ return {
492
+ "type": "ir.actions.act_window",
493
+ "name": "Issue RiBa",
494
+ "res_model": "riba.issue",
495
+ "view_mode": "form",
496
+ "target": "new",
497
+ "context": ctx,
498
+ }
499
+
500
+
501
+ class AccountFullReconcile(models.Model):
502
+ _inherit = "account.full.reconcile"
503
+
504
+ def get_riba_lines(self):
505
+ riba_lines = self.env["riba.slip.line"]
506
+ for move_line in self.reconciled_line_ids:
507
+ riba_lines |= riba_lines.search(
508
+ [("acceptance_move_id", "=", move_line.move_id.id)]
509
+ )
510
+ return riba_lines
511
+
512
+ def unreconcile_riba_lines(self, riba_lines):
513
+ """
514
+ Reset RiBa line states when reconciliation is undone.
515
+
516
+ When account move lines are unreconciled, we need to revert RiBa lines
517
+ to their previous state to maintain consistency in the RiBa workflow.
518
+ """
519
+ for riba_line in riba_lines:
520
+ # Only process lines that are in final states (paid or past_due)
521
+ if riba_line.state in ["paid", "past_due"]:
522
+ # Only revert if there's no specific payment recorded
523
+ if not riba_line.payment_id:
524
+ # If the RiBa slip has a credit move, revert to "credited" state
525
+ if riba_line.slip_id.credit_move_id:
526
+ riba_line.state = "credited"
527
+ riba_line.slip_id.state = "credited"
528
+ else:
529
+ # Otherwise, revert to "confirmed" state (acceptance phase)
530
+ riba_line.state = "confirmed"
531
+ riba_line.slip_id.state = "accepted"
532
+
533
+ def unlink(self):
534
+ riba_lines = None
535
+ for rec in self:
536
+ riba_lines = rec.get_riba_lines()
537
+ res = super().unlink()
538
+ if riba_lines:
539
+ self.unreconcile_riba_lines(riba_lines)
540
+ return res
541
+
542
+
543
+ class AccountPartialReconcile(models.Model):
544
+ _inherit = "account.partial.reconcile"
545
+
546
+ def unlink(self):
547
+ riba_lines = None
548
+ for rec in self:
549
+ riba_lines = rec.get_riba_lines()
550
+ res = super().unlink()
551
+ if riba_lines:
552
+ self.env["account.full.reconcile"].unreconcile_riba_lines(riba_lines)
553
+ return res
554
+
555
+ def get_riba_lines(self):
556
+ riba_lines = self.env["riba.slip.line"]
557
+ riba_lines |= riba_lines.search(
558
+ [("acceptance_move_id", "=", self.debit_move_id.move_id.id)]
559
+ )
560
+ riba_lines |= riba_lines.search(
561
+ [("acceptance_move_id", "=", self.credit_move_id.move_id.id)]
562
+ )
563
+ return riba_lines
@@ -0,0 +1,35 @@
1
+ # Copyright (C) 2012 Andrea Cometa.
2
+ # Email: info@andreacometa.it
3
+ # Web site: http://www.andreacometa.it
4
+ # Copyright (C) 2012 Associazione OpenERP Italia
5
+ # (<http://www.odoo-italia.org>).
6
+ # Copyright (C) 2012-2017 Lorenzo Battistini - Agile Business Group
7
+ # Copyright 2023 Simone Rubino - Aion Tech
8
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
9
+
10
+ from odoo import api, fields, models
11
+
12
+
13
+ class ResConfigSettings(models.TransientModel):
14
+ _inherit = "res.config.settings"
15
+
16
+ due_cost_service_id = fields.Many2one(
17
+ string="Default Collection Fees Service",
18
+ related="company_id.due_cost_service_id",
19
+ help="Default Service for RiBa Collection Fees on invoice.",
20
+ domain=[("type", "=", "service")],
21
+ readonly=False,
22
+ )
23
+
24
+ @api.model
25
+ def default_get(self, fields_list):
26
+ res = super().default_get(fields_list)
27
+ if res:
28
+ res["due_cost_service_id"] = self.env.user.company_id.due_cost_service_id.id
29
+ return res
30
+
31
+
32
+ class ResCompany(models.Model):
33
+ _inherit = "res.company"
34
+
35
+ due_cost_service_id = fields.Many2one("product.product", "Collection Fees Service")
@@ -0,0 +1,27 @@
1
+ # Copyright 2022 Simone Rubino - Agile Business Group
2
+ # Copyright 2023 Simone Rubino - Aion Tech
3
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
4
+
5
+ from odoo import models
6
+
7
+
8
+ class IrUiMenu(models.Model):
9
+ _inherit = "ir.ui.menu"
10
+
11
+ def write(self, vals):
12
+ old_parent = self.parent_id
13
+ new_parent_id = vals.get("parent_id")
14
+
15
+ res = super().write(vals)
16
+
17
+ if new_parent_id:
18
+ # Move the RiBa menu if any of
19
+ # its siblings (menu having same parent before write)
20
+ # is moved (parent changes).
21
+ # This happens when account_accountant (enterprise)
22
+ # is installed or uninstalled.
23
+ root_riba_menu = self.env.ref("l10n_it_riba_oca.menu_riba")
24
+ parent_riba_menu = root_riba_menu.parent_id
25
+ if old_parent == parent_riba_menu and new_parent_id != old_parent.id:
26
+ root_riba_menu.parent_id = new_parent_id
27
+ return res
@@ -0,0 +1,35 @@
1
+ # Copyright (C) 2012 Andrea Cometa.
2
+ # Email: info@andreacometa.it
3
+ # Web site: http://www.andreacometa.it
4
+ # Copyright (C) 2012 Associazione OpenERP Italia
5
+ # (<http://www.odoo-italia.org>).
6
+ # Copyright (C) 2012-2017 Lorenzo Battistini - Agile Business Group
7
+ # Copyright 2023 Simone Rubino - Aion Tech
8
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
9
+
10
+ from odoo import fields, models
11
+
12
+
13
+ class ResPartner(models.Model):
14
+ _inherit = "res.partner"
15
+
16
+ group_riba = fields.Boolean(
17
+ "Group RiBa", help="Group RiBa by customer while issuing."
18
+ )
19
+ is_supplier_payment_riba = fields.Boolean(
20
+ string="Is RiBa Payment",
21
+ related="property_supplier_payment_term_id.riba",
22
+ readonly=True,
23
+ )
24
+
25
+ def _domain_property_riba_supplier_company_bank_id(self):
26
+ """Allow to select bank accounts linked to the current company."""
27
+ return self.env["res.partner.bank"]._domain_riba_partner_bank_id()
28
+
29
+ property_riba_supplier_company_bank_id = fields.Many2one(
30
+ comodel_name="res.partner.bank",
31
+ company_dependent=True,
32
+ string="Company Bank Account for Supplier",
33
+ domain=_domain_property_riba_supplier_company_bank_id,
34
+ help="Bank account used for the RiBa of this supplier.",
35
+ )