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,560 @@
|
|
|
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
|
+
# Copyright 2024 Nextev Srl
|
|
9
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from odoo import api, fields, models
|
|
13
|
+
from odoo.exceptions import UserError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RibaList(models.Model):
|
|
17
|
+
def _compute_acceptance_move_ids(self):
|
|
18
|
+
for riba in self:
|
|
19
|
+
move_ids = self.env["account.move"]
|
|
20
|
+
for line in riba.line_ids:
|
|
21
|
+
move_ids |= line.acceptance_move_id
|
|
22
|
+
riba.acceptance_move_ids = move_ids
|
|
23
|
+
|
|
24
|
+
def _compute_past_due_move_ids(self):
|
|
25
|
+
for riba in self:
|
|
26
|
+
move_ids = self.env["account.move"]
|
|
27
|
+
for line in riba.line_ids:
|
|
28
|
+
move_ids |= line.past_due_move_id
|
|
29
|
+
riba.past_due_move_ids = move_ids
|
|
30
|
+
|
|
31
|
+
def _compute_payment_ids(self):
|
|
32
|
+
"""
|
|
33
|
+
Compute payment-related move lines for RiBa slips.
|
|
34
|
+
|
|
35
|
+
The computed field helps track payment status and provides visibility
|
|
36
|
+
into which specific accounting entries are related to each RiBa slip.
|
|
37
|
+
|
|
38
|
+
Migration Note (Odoo 18.0):
|
|
39
|
+
This implementation uses a direct search on slip_line_id field, which is
|
|
40
|
+
more efficient and reliable than the previous complex logic that tried
|
|
41
|
+
to match payments through reconciliation records. The slip_line_id field
|
|
42
|
+
is explicitly set during settlement creation, ensuring accurate tracking.
|
|
43
|
+
"""
|
|
44
|
+
for riba in self:
|
|
45
|
+
# Find all move lines that reference this RiBa's slip lines
|
|
46
|
+
# These are created during settlement and represent actual payments
|
|
47
|
+
payment_lines = self.env["account.move.line"].search(
|
|
48
|
+
[("slip_line_id", "in", riba.line_ids.ids)]
|
|
49
|
+
)
|
|
50
|
+
riba.payment_ids = payment_lines
|
|
51
|
+
|
|
52
|
+
def _compute_total_amount(self):
|
|
53
|
+
for riba in self:
|
|
54
|
+
riba.total_amount = 0.0
|
|
55
|
+
for line in riba.line_ids:
|
|
56
|
+
riba.total_amount += line.amount
|
|
57
|
+
|
|
58
|
+
_name = "riba.slip"
|
|
59
|
+
_description = "RiBa Slip"
|
|
60
|
+
_inherit = ["mail.thread"]
|
|
61
|
+
_order = "date_created desc"
|
|
62
|
+
|
|
63
|
+
name = fields.Char(
|
|
64
|
+
"Reference",
|
|
65
|
+
required=True,
|
|
66
|
+
default=(lambda self: self.env["ir.sequence"].next_by_code("riba.slip")),
|
|
67
|
+
)
|
|
68
|
+
config_id = fields.Many2one(
|
|
69
|
+
"riba.configuration",
|
|
70
|
+
string="Configuration",
|
|
71
|
+
index=True,
|
|
72
|
+
help="RiBa configuration to be used.",
|
|
73
|
+
)
|
|
74
|
+
state = fields.Selection(
|
|
75
|
+
[
|
|
76
|
+
("draft", "Draft"),
|
|
77
|
+
("accepted", "Accepted"),
|
|
78
|
+
("credited", "Credited"),
|
|
79
|
+
("paid", "Paid"),
|
|
80
|
+
("past_due", "Past Due"),
|
|
81
|
+
("cancel", "Canceled"),
|
|
82
|
+
],
|
|
83
|
+
readonly=True,
|
|
84
|
+
default="draft",
|
|
85
|
+
)
|
|
86
|
+
line_ids = fields.One2many(
|
|
87
|
+
"riba.slip.line",
|
|
88
|
+
"slip_id",
|
|
89
|
+
"RiBa Due Dates",
|
|
90
|
+
)
|
|
91
|
+
user_id = fields.Many2one(
|
|
92
|
+
"res.users",
|
|
93
|
+
"User",
|
|
94
|
+
required=True,
|
|
95
|
+
default=lambda self: self.env.user,
|
|
96
|
+
)
|
|
97
|
+
date_created = fields.Date(
|
|
98
|
+
"Creation Date",
|
|
99
|
+
readonly=True,
|
|
100
|
+
default=lambda self: fields.Date.context_today(self),
|
|
101
|
+
)
|
|
102
|
+
date_accepted = fields.Date("Acceptance Date")
|
|
103
|
+
date_credited = fields.Date("Credit Date")
|
|
104
|
+
date_paid = fields.Date(
|
|
105
|
+
string="Payment Date",
|
|
106
|
+
help="Default date for payments.",
|
|
107
|
+
)
|
|
108
|
+
date_past_due = fields.Date("Past Due Date", readonly=True)
|
|
109
|
+
company_id = fields.Many2one(
|
|
110
|
+
"res.company",
|
|
111
|
+
"Company",
|
|
112
|
+
required=True,
|
|
113
|
+
default=lambda self: self.env.company,
|
|
114
|
+
)
|
|
115
|
+
acceptance_move_ids = fields.Many2many(
|
|
116
|
+
"account.move",
|
|
117
|
+
compute="_compute_acceptance_move_ids",
|
|
118
|
+
string="Acceptance Entries",
|
|
119
|
+
)
|
|
120
|
+
credit_move_id = fields.Many2one("account.move", "Credit Entry", readonly=True)
|
|
121
|
+
payment_ids = fields.Many2many(
|
|
122
|
+
"account.move.line", compute="_compute_payment_ids", string="Payments"
|
|
123
|
+
)
|
|
124
|
+
past_due_move_ids = fields.Many2many(
|
|
125
|
+
"account.move", compute="_compute_past_due_move_ids", string="Past Due Entries"
|
|
126
|
+
)
|
|
127
|
+
type = fields.Selection(string="Type", related="config_id.type", readonly=True)
|
|
128
|
+
registration_date = fields.Date(
|
|
129
|
+
required=True,
|
|
130
|
+
default=lambda self: fields.Date.context_today(self),
|
|
131
|
+
help="Keep empty to use the current date.",
|
|
132
|
+
)
|
|
133
|
+
total_amount = fields.Float(
|
|
134
|
+
string="Amount",
|
|
135
|
+
compute="_compute_total_amount",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def action_riba_export(self):
|
|
139
|
+
return {
|
|
140
|
+
"type": "ir.actions.act_window",
|
|
141
|
+
"name": "Issue RiBa",
|
|
142
|
+
"res_model": "riba.file.export",
|
|
143
|
+
"view_mode": "form",
|
|
144
|
+
"target": "new",
|
|
145
|
+
"context": self.env.context,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
def action_riba_due_date_settlement(self):
|
|
149
|
+
return {
|
|
150
|
+
"type": "ir.actions.act_window",
|
|
151
|
+
"name": "C/O Due Date Settlement",
|
|
152
|
+
"res_model": "riba.due.date.settlement",
|
|
153
|
+
"view_mode": "form",
|
|
154
|
+
"target": "new",
|
|
155
|
+
"context": self.env.context,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@api.ondelete(at_uninstall=False)
|
|
159
|
+
def _unlink_if_not_confirmed(self):
|
|
160
|
+
for riba_list in self:
|
|
161
|
+
if riba_list.state not in ("draft", "cancel"):
|
|
162
|
+
raise UserError(
|
|
163
|
+
self.env._(
|
|
164
|
+
"Slip %(name)s is in state '%(state)s'."
|
|
165
|
+
" You can only delete documents"
|
|
166
|
+
" in state 'Draft' or 'Canceled'.",
|
|
167
|
+
name=riba_list.name,
|
|
168
|
+
state=riba_list.state,
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def confirm(self):
|
|
173
|
+
for slip in self:
|
|
174
|
+
for line in slip.line_ids:
|
|
175
|
+
line.confirm()
|
|
176
|
+
|
|
177
|
+
def riba_cancel(self):
|
|
178
|
+
for slip in self:
|
|
179
|
+
for line in slip.line_ids:
|
|
180
|
+
line.state = "cancel"
|
|
181
|
+
if line.acceptance_move_id:
|
|
182
|
+
line.acceptance_move_id.unlink()
|
|
183
|
+
if line.past_due_move_id:
|
|
184
|
+
line.past_due_move_id.unlink()
|
|
185
|
+
if slip.credit_move_id:
|
|
186
|
+
slip.credit_move_id.unlink()
|
|
187
|
+
slip.state = "cancel"
|
|
188
|
+
|
|
189
|
+
def settle_all_line(self):
|
|
190
|
+
payment_wizard_action = (
|
|
191
|
+
self.env["riba.payment.multiple"]
|
|
192
|
+
.with_context(
|
|
193
|
+
active_ids=self.ids,
|
|
194
|
+
)
|
|
195
|
+
.get_formview_action()
|
|
196
|
+
)
|
|
197
|
+
payment_wizard_action.update(
|
|
198
|
+
name=self.env._("Settle lines"),
|
|
199
|
+
target="new",
|
|
200
|
+
)
|
|
201
|
+
return payment_wizard_action
|
|
202
|
+
|
|
203
|
+
@api.onchange("date_accepted", "date_credited")
|
|
204
|
+
def _onchange_date(self):
|
|
205
|
+
if self.date_accepted and self.date_credited:
|
|
206
|
+
if self.date_accepted > self.date_credited:
|
|
207
|
+
raise UserError(
|
|
208
|
+
self.env._(
|
|
209
|
+
"Credit date must be greater or equal to acceptance date."
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
def riba_past_due(self):
|
|
214
|
+
self.state = "past_due"
|
|
215
|
+
self.date_past_due = fields.Date.context_today(self)
|
|
216
|
+
|
|
217
|
+
def test_state(self, state):
|
|
218
|
+
for riba_list in self:
|
|
219
|
+
for line in riba_list.line_ids:
|
|
220
|
+
if line.state != state:
|
|
221
|
+
return False
|
|
222
|
+
return True
|
|
223
|
+
|
|
224
|
+
def test_accepted(self):
|
|
225
|
+
return self.test_state("confirmed")
|
|
226
|
+
|
|
227
|
+
def test_past_due(self):
|
|
228
|
+
return self.test_state("past_due")
|
|
229
|
+
|
|
230
|
+
def test_paid(self):
|
|
231
|
+
return self.test_state("paid")
|
|
232
|
+
|
|
233
|
+
def action_cancel_draft(self):
|
|
234
|
+
for riba_list in self:
|
|
235
|
+
riba_list.state = "draft"
|
|
236
|
+
for line in riba_list.line_ids:
|
|
237
|
+
line.state = "draft"
|
|
238
|
+
|
|
239
|
+
def action_open_lines(self):
|
|
240
|
+
action = self.env.ref("l10n_it_riba_oca.detail_riba_action").read()[0]
|
|
241
|
+
action["domain"] = [("slip_id", "=", self.id)]
|
|
242
|
+
return action
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class RibaListLine(models.Model):
|
|
246
|
+
_name = "riba.slip.line"
|
|
247
|
+
_inherit = "mail.thread"
|
|
248
|
+
_description = "RiBa Details"
|
|
249
|
+
_rec_name = "sequence"
|
|
250
|
+
|
|
251
|
+
def _compute_line_values(self):
|
|
252
|
+
for line in self:
|
|
253
|
+
line.amount = 0.0
|
|
254
|
+
line.invoice_date = ""
|
|
255
|
+
line.invoice_number = ""
|
|
256
|
+
for move_line in line.move_line_ids:
|
|
257
|
+
line.amount += move_line.amount
|
|
258
|
+
move_date = move_line.move_line_id.move_id.invoice_date
|
|
259
|
+
if move_date:
|
|
260
|
+
move_date = str(
|
|
261
|
+
fields.Date.from_string(move_date).strftime("%d/%m/%Y")
|
|
262
|
+
)
|
|
263
|
+
if not line.invoice_date:
|
|
264
|
+
line.invoice_date = move_date
|
|
265
|
+
else:
|
|
266
|
+
line.invoice_date = f"{line.invoice_date}, {move_date}"
|
|
267
|
+
if not line.invoice_number:
|
|
268
|
+
line.invoice_number = str(
|
|
269
|
+
move_line.move_line_id.move_id.name
|
|
270
|
+
if move_line.move_line_id.move_id.display_name == "/"
|
|
271
|
+
else move_line.move_line_id.move_id.display_name
|
|
272
|
+
)
|
|
273
|
+
else:
|
|
274
|
+
line.invoice_number = "{}, {}".format(
|
|
275
|
+
line.invoice_number,
|
|
276
|
+
str(
|
|
277
|
+
move_line.move_line_id.move_id.name
|
|
278
|
+
if move_line.move_line_id.move_id.display_name == "/"
|
|
279
|
+
else move_line.move_line_id.move_id.display_name
|
|
280
|
+
),
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
amount = fields.Float(compute="_compute_line_values")
|
|
284
|
+
invoice_date = fields.Char(compute="_compute_line_values", size=256)
|
|
285
|
+
invoice_number = fields.Char(compute="_compute_line_values", size=256)
|
|
286
|
+
cig = fields.Char(string="CIG", size=256)
|
|
287
|
+
cup = fields.Char(string="CUP", size=256)
|
|
288
|
+
|
|
289
|
+
sequence = fields.Integer("Number")
|
|
290
|
+
move_line_ids = fields.One2many(
|
|
291
|
+
"riba.slip.move.line", "riba_line_id", string="Credit Move Lines"
|
|
292
|
+
)
|
|
293
|
+
acceptance_move_id = fields.Many2one(
|
|
294
|
+
"account.move", string="Acceptance Entry", readonly=True
|
|
295
|
+
)
|
|
296
|
+
credit_move_id = fields.Many2one(
|
|
297
|
+
"account.move", string="Credit Entry", readonly=True
|
|
298
|
+
)
|
|
299
|
+
past_due_move_id = fields.Many2one(
|
|
300
|
+
"account.move", string="Past Due Entry", readonly=True
|
|
301
|
+
)
|
|
302
|
+
acceptance_account_id = fields.Many2one(
|
|
303
|
+
"account.account", string="Acceptance Account"
|
|
304
|
+
)
|
|
305
|
+
bank_id = fields.Many2one("res.partner.bank", string="Debtor Bank")
|
|
306
|
+
iban = fields.Char(
|
|
307
|
+
related="bank_id.acc_number", string="IBAN", store=False, readonly=True
|
|
308
|
+
)
|
|
309
|
+
slip_id = fields.Many2one(
|
|
310
|
+
"riba.slip", string="Slip", required=True, ondelete="cascade"
|
|
311
|
+
)
|
|
312
|
+
partner_id = fields.Many2one("res.partner", string="Customer", readonly=True)
|
|
313
|
+
due_date = fields.Date(readonly=True)
|
|
314
|
+
state = fields.Selection(
|
|
315
|
+
[
|
|
316
|
+
("draft", "Draft"),
|
|
317
|
+
("confirmed", "Confirmed"),
|
|
318
|
+
("credited", "Credited"),
|
|
319
|
+
("paid", "Paid"),
|
|
320
|
+
("past_due", "Past Due"),
|
|
321
|
+
("cancel", "Canceled"),
|
|
322
|
+
],
|
|
323
|
+
readonly=True,
|
|
324
|
+
tracking=True,
|
|
325
|
+
)
|
|
326
|
+
payment_id = fields.Many2one("account.move.line", string="Payments", readonly=True)
|
|
327
|
+
type = fields.Selection(
|
|
328
|
+
string="Type", related="slip_id.config_id.type", readonly=True
|
|
329
|
+
)
|
|
330
|
+
config_id = fields.Many2one(
|
|
331
|
+
string="Configuration", related="slip_id.config_id", readonly=True
|
|
332
|
+
)
|
|
333
|
+
company_id = fields.Many2one(
|
|
334
|
+
"res.company",
|
|
335
|
+
string="Company",
|
|
336
|
+
related="slip_id.company_id",
|
|
337
|
+
store=True,
|
|
338
|
+
readonly=True,
|
|
339
|
+
related_sudo=False,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
def confirm(self):
|
|
343
|
+
move_model = self.env["account.move"]
|
|
344
|
+
move_line_model = self.env["account.move.line"]
|
|
345
|
+
for line in self:
|
|
346
|
+
journal = line.slip_id.config_id.acceptance_journal_id
|
|
347
|
+
total_credit = 0.0
|
|
348
|
+
move = move_model.create(
|
|
349
|
+
{
|
|
350
|
+
"ref": f"{line.invoice_number} RiBa {line.slip_id.name} \
|
|
351
|
+
- Line {line.sequence}",
|
|
352
|
+
"journal_id": journal.id,
|
|
353
|
+
"date": line.due_date,
|
|
354
|
+
}
|
|
355
|
+
)
|
|
356
|
+
to_be_reconciled = self.env["account.move.line"]
|
|
357
|
+
riba_move_line_name = ""
|
|
358
|
+
for riba_move_line in line.move_line_ids:
|
|
359
|
+
total_credit += riba_move_line.amount
|
|
360
|
+
if (
|
|
361
|
+
str(riba_move_line.move_line_id.move_id.sequence_number)
|
|
362
|
+
and str(riba_move_line.move_line_id.move_id.sequence_number)
|
|
363
|
+
not in riba_move_line_name
|
|
364
|
+
):
|
|
365
|
+
riba_move_line_name = " ".join(
|
|
366
|
+
[
|
|
367
|
+
riba_move_line_name,
|
|
368
|
+
str(riba_move_line.move_line_id.move_id.sequence_number),
|
|
369
|
+
]
|
|
370
|
+
).lstrip()
|
|
371
|
+
elif (
|
|
372
|
+
riba_move_line.move_line_id.name
|
|
373
|
+
and riba_move_line.move_line_id.name not in riba_move_line_name
|
|
374
|
+
):
|
|
375
|
+
riba_move_line_name = " ".join(
|
|
376
|
+
[riba_move_line_name, riba_move_line.move_line_id.name]
|
|
377
|
+
).lstrip()
|
|
378
|
+
move_line = move_line_model.with_context(
|
|
379
|
+
check_move_validity=False
|
|
380
|
+
).create(
|
|
381
|
+
{
|
|
382
|
+
"name": (
|
|
383
|
+
riba_move_line.move_line_id.move_id
|
|
384
|
+
and riba_move_line.move_line_id.move_id.sequence_number
|
|
385
|
+
or riba_move_line.move_line_id.name
|
|
386
|
+
),
|
|
387
|
+
"partner_id": line.partner_id.id,
|
|
388
|
+
"account_id": riba_move_line.move_line_id.account_id.id,
|
|
389
|
+
"credit": riba_move_line.amount,
|
|
390
|
+
"debit": 0.0,
|
|
391
|
+
"move_id": move.id,
|
|
392
|
+
}
|
|
393
|
+
)
|
|
394
|
+
to_be_reconciled |= move_line
|
|
395
|
+
to_be_reconciled |= riba_move_line.move_line_id
|
|
396
|
+
move_line_model.with_context(check_move_validity=False).create(
|
|
397
|
+
{
|
|
398
|
+
"name": f"{line.invoice_number} RiBa {line.slip_id.name}-\
|
|
399
|
+
{line.sequence} Ref. {riba_move_line_name} - \
|
|
400
|
+
{line.partner_id.name}",
|
|
401
|
+
"account_id": (
|
|
402
|
+
line.acceptance_account_id.id
|
|
403
|
+
or line.slip_id.config_id.acceptance_account_id.id
|
|
404
|
+
# in questo modo se la riga non ha conto accettazione
|
|
405
|
+
# viene prelevato il conto in configuration riba
|
|
406
|
+
),
|
|
407
|
+
"partner_id": line.partner_id.id,
|
|
408
|
+
"date_maturity": line.due_date,
|
|
409
|
+
"credit": 0.0,
|
|
410
|
+
"debit": total_credit,
|
|
411
|
+
"move_id": move.id,
|
|
412
|
+
}
|
|
413
|
+
)
|
|
414
|
+
move.action_post()
|
|
415
|
+
to_be_reconciled.reconcile()
|
|
416
|
+
line.write(
|
|
417
|
+
{
|
|
418
|
+
"acceptance_move_id": move.id,
|
|
419
|
+
"state": "confirmed",
|
|
420
|
+
}
|
|
421
|
+
)
|
|
422
|
+
line.slip_id.state = "accepted"
|
|
423
|
+
if not line.slip_id.date_accepted:
|
|
424
|
+
line.slip_id.date_accepted = fields.Date.context_today(self)
|
|
425
|
+
|
|
426
|
+
def button_settle(self):
|
|
427
|
+
payment_wizard_action = (
|
|
428
|
+
self.env["riba.payment.multiple"]
|
|
429
|
+
.with_context(
|
|
430
|
+
active_ids=self.slip_id.ids,
|
|
431
|
+
default_riba_line_ids=self.ids,
|
|
432
|
+
)
|
|
433
|
+
.get_formview_action()
|
|
434
|
+
)
|
|
435
|
+
payment_wizard_action.update(
|
|
436
|
+
name=self.env._("Settle line"),
|
|
437
|
+
target="new",
|
|
438
|
+
)
|
|
439
|
+
return payment_wizard_action
|
|
440
|
+
|
|
441
|
+
def riba_line_settlement(self, date=None):
|
|
442
|
+
"""
|
|
443
|
+
Create settlement payment entries for RiBa lines.
|
|
444
|
+
|
|
445
|
+
This method handles the final settlement of RiBa lines by creating
|
|
446
|
+
accounting entries that record the actual payment received from customers
|
|
447
|
+
through the bank. It reconciles the credit account with the bank account
|
|
448
|
+
to complete the RiBa collection process.
|
|
449
|
+
|
|
450
|
+
Settlement Process:
|
|
451
|
+
1. Find the original credit move line that needs to be settled
|
|
452
|
+
2. Calculate the settlement amount (considering partial payments for SBF)
|
|
453
|
+
3. Create settlement move with:
|
|
454
|
+
- Credit line: reduces the RiBa credit account
|
|
455
|
+
- Debit line: increases the bank account
|
|
456
|
+
4. Reconcile all related move lines to close the collection cycle
|
|
457
|
+
|
|
458
|
+
:param date: The settlement date. If not provided, uses today's date.
|
|
459
|
+
:type date: date or None
|
|
460
|
+
:raises UserError: If settlement journal is not configured
|
|
461
|
+
|
|
462
|
+
Business Flow:
|
|
463
|
+
- Customer pays invoice → Bank receives payment → Bank notifies company
|
|
464
|
+
- Company records settlement → RiBa line moves to 'paid' state
|
|
465
|
+
"""
|
|
466
|
+
# Validate configuration before proceeding
|
|
467
|
+
if not self.slip_id.config_id.settlement_journal_id:
|
|
468
|
+
raise UserError(self.env._("Please define a Settlement Journal."))
|
|
469
|
+
|
|
470
|
+
# Initialize models for creating accounting entries
|
|
471
|
+
move_model = self.env["account.move"]
|
|
472
|
+
move_line_model = self.env["account.move.line"]
|
|
473
|
+
|
|
474
|
+
# Find the original credit move line that needs to be settled
|
|
475
|
+
# This represents the amount that was credited when the RiBa was issued
|
|
476
|
+
settlement_move_line = move_line_model.search(
|
|
477
|
+
[
|
|
478
|
+
("account_id", "=", self.slip_id.config_id.credit_account_id.id),
|
|
479
|
+
("move_id", "=", self.credit_move_id.id),
|
|
480
|
+
("debit", "!=", 0), # We need the debit side of the credit entry
|
|
481
|
+
]
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
# Reduce settlement amount by amounts already matched (partial payments)
|
|
485
|
+
settlement_move_amount = settlement_move_line.debit - sum(
|
|
486
|
+
settlement_move_line.mapped("matched_credit_ids.amount")
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
# Prepare settlement move data
|
|
490
|
+
move_ref = f"Settlement RiBa {self.slip_id.name}"
|
|
491
|
+
move_date = date or fields.Date.context_today(self)
|
|
492
|
+
|
|
493
|
+
# Create the settlement accounting move
|
|
494
|
+
settlement_move = move_model.create(
|
|
495
|
+
{
|
|
496
|
+
"journal_id": (self.slip_id.config_id.settlement_journal_id.id),
|
|
497
|
+
"date": move_date,
|
|
498
|
+
"ref": move_ref,
|
|
499
|
+
}
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
# Create credit move lines for each RiBa line being settled
|
|
503
|
+
move_lines_credit = self.env["account.move.line"]
|
|
504
|
+
for riba_line in self:
|
|
505
|
+
# Credit the RiBa account to reduce the outstanding amount
|
|
506
|
+
move_line = move_line_model.with_context(check_move_validity=False).create(
|
|
507
|
+
{
|
|
508
|
+
"name": move_ref,
|
|
509
|
+
"partner_id": riba_line.partner_id.id,
|
|
510
|
+
"account_id": riba_line.slip_id.config_id.credit_account_id.id,
|
|
511
|
+
"credit": riba_line.amount, # Reduce RiBa credit account
|
|
512
|
+
"debit": 0.0,
|
|
513
|
+
"move_id": settlement_move.id,
|
|
514
|
+
"slip_line_id": riba_line.id, # Link back to RiBa line
|
|
515
|
+
}
|
|
516
|
+
)
|
|
517
|
+
move_lines_credit |= move_line
|
|
518
|
+
|
|
519
|
+
# Create the corresponding bank debit entry
|
|
520
|
+
bank_account = riba_line.slip_id.config_id.bank_account_id
|
|
521
|
+
move_line_model.with_context(check_move_validity=False).create(
|
|
522
|
+
{
|
|
523
|
+
"name": move_ref,
|
|
524
|
+
"account_id": bank_account.id,
|
|
525
|
+
"credit": 0.0,
|
|
526
|
+
"debit": settlement_move_amount, # Increase bank account
|
|
527
|
+
"move_id": settlement_move.id,
|
|
528
|
+
}
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
# Post the settlement move
|
|
532
|
+
move_lines_credit.mapped("move_id").action_post()
|
|
533
|
+
|
|
534
|
+
# Prepare reconciliation: collect all move lines that need to be reconciled
|
|
535
|
+
to_be_settled = self.env["account.move.line"]
|
|
536
|
+
for move_line in move_lines_credit:
|
|
537
|
+
to_be_settled |= move_line
|
|
538
|
+
# Add the original credit move line
|
|
539
|
+
to_be_settled |= settlement_move_line
|
|
540
|
+
|
|
541
|
+
# Reconcile all related move lines to complete the settlement
|
|
542
|
+
# This closes the accounting cycle for this RiBa collection
|
|
543
|
+
to_be_settled.reconcile()
|
|
544
|
+
|
|
545
|
+
def settle_riba_line(self):
|
|
546
|
+
for line in self:
|
|
547
|
+
if line.state == "credited":
|
|
548
|
+
line.riba_line_settlement()
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
class RibaListMoveLine(models.Model):
|
|
552
|
+
_name = "riba.slip.move.line"
|
|
553
|
+
_description = "RiBa Details"
|
|
554
|
+
_rec_name = "amount"
|
|
555
|
+
|
|
556
|
+
amount = fields.Float(digits="Account")
|
|
557
|
+
move_line_id = fields.Many2one("account.move.line", string="Credit Move Line")
|
|
558
|
+
riba_line_id = fields.Many2one(
|
|
559
|
+
"riba.slip.line", string="Slip Line", ondelete="cascade"
|
|
560
|
+
)
|
|
@@ -0,0 +1,120 @@
|
|
|
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 RibaConfiguration(models.Model):
|
|
14
|
+
_name = "riba.configuration"
|
|
15
|
+
_description = "Configuration parameters for RiBa"
|
|
16
|
+
_check_company_auto = True
|
|
17
|
+
|
|
18
|
+
name = fields.Char("Description", size=64, required=True)
|
|
19
|
+
type = fields.Selection(
|
|
20
|
+
[("sbf", "Subject To Collection"), ("incasso", "After Collection")],
|
|
21
|
+
"Issue Mode",
|
|
22
|
+
required=True,
|
|
23
|
+
)
|
|
24
|
+
bank_id = fields.Many2one(
|
|
25
|
+
"res.partner.bank",
|
|
26
|
+
"Bank Account",
|
|
27
|
+
required=True,
|
|
28
|
+
check_company=True,
|
|
29
|
+
help="Bank account used for RiBa issuing.",
|
|
30
|
+
)
|
|
31
|
+
acceptance_journal_id = fields.Many2one(
|
|
32
|
+
"account.journal",
|
|
33
|
+
"Acceptance Journal",
|
|
34
|
+
required=True,
|
|
35
|
+
check_company=True,
|
|
36
|
+
help="Journal used when RiBa is accepted by the bank.",
|
|
37
|
+
)
|
|
38
|
+
acceptance_account_id = fields.Many2one(
|
|
39
|
+
"account.account",
|
|
40
|
+
"Acceptance Account",
|
|
41
|
+
required=True,
|
|
42
|
+
check_company=True,
|
|
43
|
+
help="Account used when RiBa is accepted by the bank.",
|
|
44
|
+
)
|
|
45
|
+
company_id = fields.Many2one(
|
|
46
|
+
"res.company",
|
|
47
|
+
"Company",
|
|
48
|
+
required=True,
|
|
49
|
+
default=lambda self: self.env.company,
|
|
50
|
+
)
|
|
51
|
+
credit_journal_id = fields.Many2one(
|
|
52
|
+
"account.journal",
|
|
53
|
+
"Credit Journal",
|
|
54
|
+
check_company=True,
|
|
55
|
+
help="Journal used when RiBa amount is credited by the bank.",
|
|
56
|
+
)
|
|
57
|
+
credit_account_id = fields.Many2one(
|
|
58
|
+
"account.account",
|
|
59
|
+
"RiBa Account",
|
|
60
|
+
check_company=True,
|
|
61
|
+
help="Account used when RiBa amount is credited by the bank.",
|
|
62
|
+
domain="[('account_type', '!=', 'liability_credit_card')]",
|
|
63
|
+
)
|
|
64
|
+
bank_account_id = fields.Many2one(
|
|
65
|
+
"account.account",
|
|
66
|
+
"A/C Bank Account",
|
|
67
|
+
check_company=True,
|
|
68
|
+
)
|
|
69
|
+
bank_expense_account_id = fields.Many2one("account.account", "Bank Fees Account")
|
|
70
|
+
past_due_journal_id = fields.Many2one(
|
|
71
|
+
"account.journal",
|
|
72
|
+
"Past Due Journal",
|
|
73
|
+
check_company=True,
|
|
74
|
+
help="Journal used when RiBa is past due.",
|
|
75
|
+
)
|
|
76
|
+
overdue_credit_account_id = fields.Many2one(
|
|
77
|
+
"account.account",
|
|
78
|
+
"Past Due Bills Account",
|
|
79
|
+
check_company=True,
|
|
80
|
+
)
|
|
81
|
+
past_due_fee_amount = fields.Float(
|
|
82
|
+
"Fee Amount",
|
|
83
|
+
)
|
|
84
|
+
protest_charge_account_id = fields.Many2one(
|
|
85
|
+
"account.account",
|
|
86
|
+
"Protest Fee Account",
|
|
87
|
+
check_company=True,
|
|
88
|
+
)
|
|
89
|
+
settlement_journal_id = fields.Many2one(
|
|
90
|
+
"account.journal",
|
|
91
|
+
"Settlement Journal",
|
|
92
|
+
check_company=True,
|
|
93
|
+
help="Journal used when customers finally pay the invoice to bank.",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def get_default_value_by_list(self, field_name):
|
|
97
|
+
if not self.env.context.get("active_id", False):
|
|
98
|
+
return False
|
|
99
|
+
ribalist_model = self.env["riba.slip"]
|
|
100
|
+
ribalist = ribalist_model.browse(self.env.context["active_id"])
|
|
101
|
+
return (
|
|
102
|
+
ribalist.config_id[field_name]
|
|
103
|
+
and ribalist.config_id[field_name].id
|
|
104
|
+
or False
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def get_default_value_by_list_line(self, field_name):
|
|
108
|
+
if not self.env.context.get("active_id", False):
|
|
109
|
+
return False if field_name != "past_due_fee_amount" else 0.0
|
|
110
|
+
ribalist_line = self.env["riba.slip.line"].browse(self.env.context["active_id"])
|
|
111
|
+
res = ribalist_line.slip_id.config_id[field_name]
|
|
112
|
+
if field_name == "past_due_fee_amount":
|
|
113
|
+
return res
|
|
114
|
+
if res:
|
|
115
|
+
# we need to check if the field is string like "config_type" or
|
|
116
|
+
# many2one like journals and accounts
|
|
117
|
+
if isinstance(res, str):
|
|
118
|
+
return res
|
|
119
|
+
return res.id
|
|
120
|
+
return False
|