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.
- odoo/addons/l10n_it_riba_oca/README.rst +175 -0
- odoo/addons/l10n_it_riba_oca/__init__.py +9 -0
- odoo/addons/l10n_it_riba_oca/__manifest__.py +59 -0
- odoo/addons/l10n_it_riba_oca/data/riba_sequence.xml +14 -0
- odoo/addons/l10n_it_riba_oca/demo/riba_demo.xml +39 -0
- odoo/addons/l10n_it_riba_oca/hooks.py +17 -0
- odoo/addons/l10n_it_riba_oca/i18n/it.po +1686 -0
- odoo/addons/l10n_it_riba_oca/i18n/l10n_it_riba.pot +1573 -0
- odoo/addons/l10n_it_riba_oca/i18n/l10n_it_riba_oca.pot +1554 -0
- odoo/addons/l10n_it_riba_oca/migrations/18.0.1.0.0/pre-migrate.py +16 -0
- odoo/addons/l10n_it_riba_oca/models/__init__.py +14 -0
- odoo/addons/l10n_it_riba_oca/models/account.py +563 -0
- odoo/addons/l10n_it_riba_oca/models/account_config.py +35 -0
- odoo/addons/l10n_it_riba_oca/models/ir_ui_menu.py +27 -0
- odoo/addons/l10n_it_riba_oca/models/partner.py +35 -0
- odoo/addons/l10n_it_riba_oca/models/riba.py +560 -0
- odoo/addons/l10n_it_riba_oca/models/riba_config.py +120 -0
- odoo/addons/l10n_it_riba_oca/readme/CONFIGURE.md +33 -0
- odoo/addons/l10n_it_riba_oca/readme/CONTRIBUTORS.md +17 -0
- odoo/addons/l10n_it_riba_oca/readme/DESCRIPTION.md +3 -0
- odoo/addons/l10n_it_riba_oca/readme/USAGE.md +35 -0
- odoo/addons/l10n_it_riba_oca/report/__init__.py +4 -0
- odoo/addons/l10n_it_riba_oca/report/report.xml +16 -0
- odoo/addons/l10n_it_riba_oca/report/slip_qweb.py +18 -0
- odoo/addons/l10n_it_riba_oca/security/ir.model.access.csv +23 -0
- odoo/addons/l10n_it_riba_oca/security/riba_security.xml +34 -0
- odoo/addons/l10n_it_riba_oca/static/description/icon.png +0 -0
- odoo/addons/l10n_it_riba_oca/static/description/index.html +512 -0
- odoo/addons/l10n_it_riba_oca/tests/__init__.py +10 -0
- odoo/addons/l10n_it_riba_oca/tests/riba_common.py +339 -0
- odoo/addons/l10n_it_riba_oca/tests/test_account_move.py +54 -0
- odoo/addons/l10n_it_riba_oca/tests/test_menu.py +51 -0
- odoo/addons/l10n_it_riba_oca/tests/test_riba.py +905 -0
- odoo/addons/l10n_it_riba_oca/views/account_config_view.xml +46 -0
- odoo/addons/l10n_it_riba_oca/views/account_view.xml +205 -0
- odoo/addons/l10n_it_riba_oca/views/configuration_view.xml +94 -0
- odoo/addons/l10n_it_riba_oca/views/partner_view.xml +29 -0
- odoo/addons/l10n_it_riba_oca/views/riba_detail_view.xml +97 -0
- odoo/addons/l10n_it_riba_oca/views/riba_view.xml +296 -0
- odoo/addons/l10n_it_riba_oca/views/slip_report.xml +149 -0
- odoo/addons/l10n_it_riba_oca/views/wizard_credit.xml +46 -0
- odoo/addons/l10n_it_riba_oca/views/wizard_due_date_settlement.xml +37 -0
- odoo/addons/l10n_it_riba_oca/views/wizard_past_due.xml +67 -0
- odoo/addons/l10n_it_riba_oca/views/wizard_presentation.xml +48 -0
- odoo/addons/l10n_it_riba_oca/views/wizard_riba_file_export.xml +33 -0
- odoo/addons/l10n_it_riba_oca/views/wizard_riba_issue.xml +46 -0
- odoo/addons/l10n_it_riba_oca/views/wizard_riba_payment_date.xml +45 -0
- odoo/addons/l10n_it_riba_oca/wizard/__init__.py +16 -0
- odoo/addons/l10n_it_riba_oca/wizard/wizard_credit.py +280 -0
- odoo/addons/l10n_it_riba_oca/wizard/wizard_due_date_settlement.py +24 -0
- odoo/addons/l10n_it_riba_oca/wizard/wizard_past_due.py +319 -0
- odoo/addons/l10n_it_riba_oca/wizard/wizard_presentation_riba.py +47 -0
- odoo/addons/l10n_it_riba_oca/wizard/wizard_riba_file_export.py +429 -0
- odoo/addons/l10n_it_riba_oca/wizard/wizard_riba_issue.py +143 -0
- odoo/addons/l10n_it_riba_oca/wizard/wizard_riba_multiple_payment.py +107 -0
- odoo/addons/l10n_it_riba_oca/wizard/wizard_riba_multiple_payment_views.xml +46 -0
- odoo/addons/l10n_it_riba_oca/wizard/wizard_riba_payment_date.py +31 -0
- odoo_addon_l10n_it_riba_oca-18.0.1.0.0.8.dist-info/METADATA +197 -0
- odoo_addon_l10n_it_riba_oca-18.0.1.0.0.8.dist-info/RECORD +61 -0
- odoo_addon_l10n_it_riba_oca-18.0.1.0.0.8.dist-info/WHEEL +5 -0
- 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
|
+
)
|