Skip to content

Commit 077487f

Browse files
author
fw-bot-adhoc
committed
Cherry pick of e121812 failed
stdout: Auto-merging account_interests/views/res_company_views.xml Auto-merging account_interests/models/res_company_interest.py Auto-merging account_interests/__manifest__.py CONFLICT (content): Merge conflict in account_interests/__manifest__.py stderr: 10:41:54.010297 git.c:444 trace: built-in: git cherry-pick e121812 error: could not apply e121812... [ADD] account_interests: late payment interest feature hint: after resolving the conflicts, mark the corrected paths hint: with 'git add <paths>' or 'git rm <paths>' hint: and commit the result with 'git commit' ---------- status:
1 parent ed4db8c commit 077487f

File tree

3 files changed

+168
-70
lines changed

3 files changed

+168
-70
lines changed

account_interests/__manifest__.py

+6
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@
1919
##############################################################################
2020
{
2121
'name': 'Interests Management',
22+
<<<<<<< HEAD
2223
'version': "18.0.1.0.0",
24+
||||||| parent of 13395e4a (temp)
25+
'version': "17.0.1.1.0",
26+
=======
27+
'version': "17.0.1.2.0",
28+
>>>>>>> 13395e4a (temp)
2329
'category': 'Accounting',
2430
'sequence': 14,
2531
'summary': 'Calculate interests for selected partners',

account_interests/models/res_company_interest.py

+161-68
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,6 @@ class ResCompanyInterest(models.Model):
6565
default=1,
6666
help="Repeat every (Days/Week/Month/Year)"
6767
)
68-
tolerance_interval = fields.Integer(
69-
'Tolerance',
70-
default=1,
71-
help="Number of periods of tolerance for dues. 0 = no tolerance"
72-
)
7368
next_date = fields.Date(
7469
'Date of Next Invoice',
7570
default=fields.Date.today,
@@ -81,6 +76,8 @@ class ResCompanyInterest(models.Model):
8176
)
8277
has_domain = fields.Boolean(compute="_compute_has_domain")
8378

79+
late_payment_interest = fields.Boolean('Late payment interest', default=False, help="The interest calculation takes into account all late payments from the previous period. To obtain the daily rate, the interest is divided by the period. These days are considered depending on the type of period: 360 for annual, 30 for monthly and 7 for weekly.")
80+
8481
@api.model
8582
def _cron_recurring_interests_invoices(self):
8683
_logger.info('Running Interest Invoices Cron Job')
@@ -91,8 +88,8 @@ def _cron_recurring_interests_invoices(self):
9188
try:
9289
rec.create_interest_invoices()
9390
rec.env.cr.commit()
94-
except:
95-
_logger.error('Error creating interest invoices for company: %s', rec.company_id.name)
91+
except Exception as e:
92+
_logger.error('Error creating interest invoices for company: %s, %s', rec.company_id.name, str(e))
9693
companies_with_errors.append(rec.company_id.name)
9794
rec.env.cr.rollback()
9895

@@ -101,63 +98,149 @@ def _cron_recurring_interests_invoices(self):
10198
error_message = _("We couldn't run interest invoices cron job in the following companies: %s.") % company_names
10299
raise UserError(error_message)
103100

101+
<<<<<<< HEAD
102+
||||||| parent of 13395e4a (temp)
103+
104+
=======
105+
def _calculate_date_deltas(self, rule_type, interval):
106+
"""
107+
Calcula los intervalos de fechas para la generación de intereses.
108+
"""
109+
deltas = {
110+
'daily': relativedelta(days=interval),
111+
'weekly': relativedelta(weeks=interval),
112+
'monthly': relativedelta(months=interval),
113+
'yearly': relativedelta(years=interval),
114+
}
115+
return deltas.get(rule_type, relativedelta(months=interval))
116+
117+
118+
>>>>>>> 13395e4a (temp)
104119
def create_interest_invoices(self):
105120
for rec in self:
106121
_logger.info(
107122
'Creating Interest Invoices (id: %s, company: %s)', rec.id,
108123
rec.company_id.name)
109124
# hacemos un commit para refrescar cache
110125
self.env.cr.commit()
111-
interests_date = rec.next_date
126+
to_date = rec.next_date
112127

113128
rule_type = rec.rule_type
114129
interval = rec.interval
115-
tolerance_interval = rec.tolerance_interval
116-
117-
if rule_type == 'daily':
118-
next_delta = relativedelta(days=+interval)
119-
tolerance_delta = relativedelta(days=+tolerance_interval)
120-
elif rule_type == 'weekly':
121-
next_delta = relativedelta(weeks=+interval)
122-
tolerance_delta = relativedelta(weeks=+tolerance_interval)
123-
elif rule_type == 'monthly':
124-
next_delta = relativedelta(months=+interval)
125-
tolerance_delta = relativedelta(months=+tolerance_interval)
126-
else:
127-
next_delta = relativedelta(years=+interval)
128-
tolerance_delta = relativedelta(years=+tolerance_interval)
129-
130-
# buscamos solo facturas que vencieron
131-
# antes de hoy menos un periodo
132-
# TODO ver si queremos que tambien se calcule interes proporcional
133-
# para lo que vencio en este ultimo periodo
134-
to_date = interests_date - tolerance_delta
135-
from_date = to_date - tolerance_delta
130+
131+
next_delta = self._calculate_date_deltas(rule_type, interval)
132+
from_date_delta = self._calculate_date_deltas(rule_type, -interval)
133+
134+
from_date = to_date + from_date_delta
135+
136136
# llamamos a crear las facturas con la compañia del interes para
137137
# que tome correctamente las cuentas
138138
rec.with_company(rec.company_id).with_context(default_l10n_ar_afip_asoc_period_start=from_date,
139-
default_l10n_ar_afip_asoc_period_end=to_date).create_invoices(to_date)
139+
default_l10n_ar_afip_asoc_period_end=to_date).create_invoices(from_date, to_date)
140140

141141
# seteamos proxima corrida en hoy mas un periodo
142-
rec.next_date = interests_date + next_delta
142+
rec.next_date = to_date + next_delta
143143

144-
def _get_move_line_domains(self, to_date):
144+
def _get_move_line_domains(self):
145145
self.ensure_one()
146146
move_line_domain = [
147147
('account_id', 'in', self.receivable_account_ids.ids),
148-
('full_reconcile_id', '=', False),
149-
('date_maturity', '<', to_date),
150148
('partner_id.active', '=', True),
151149
('parent_state', '=', 'posted'),
152150
]
153151
return move_line_domain
154152

155-
def create_invoices(self, to_date, groupby=['partner_id']):
153+
def _update_deuda(self, deuda, partner, key, value):
154+
"""
155+
Actualiza el diccionario de deuda para un partner específico.
156+
Si el partner no existe en la deuda, lo inicializa.
157+
Si la clave no existe para el partner, la agrega.
158+
"""
159+
if partner not in deuda:
160+
deuda[partner] = {}
161+
deuda[partner][key] = deuda[partner].get(key, 0) + value
162+
163+
def _calculate_debts(self, from_date, to_date, groupby=['partner_id']):
164+
"""
165+
Calcula las deudas e intereses por partner.
166+
Retorna un diccionario estructurado con los cálculos.
167+
"""
168+
deuda = {}
169+
170+
interest_rate = {
171+
'daily': 1,
172+
'weekly': 7,
173+
'monthly': 30,
174+
'yearly': 360,
175+
}
176+
177+
# Deudas de períodos anteriores
178+
previous_grouped_lines = self.env['account.move.line']._read_group(
179+
domain=self._get_move_line_domains() + [('full_reconcile_id', '=', False), ('date_maturity', '<', from_date)],
180+
groupby=groupby,
181+
aggregates=['amount_residual:sum'],
182+
)
183+
for x in previous_grouped_lines:
184+
self._update_deuda(deuda, x[0], 'Deuda periodos anteriores', x[1] * self.rate)
185+
186+
# Intereses por el último período
187+
last_period_lines = self.env['account.move.line'].search(
188+
self._get_move_line_domains() + [('amount_residual', '>', 0), ('date_maturity', '>=', from_date), ('date_maturity', '<', to_date)]
189+
)
190+
for partner, amls in last_period_lines.grouped('partner_id').items():
191+
interest = sum(
192+
move.amount_residual * ((to_date - move.invoice_date_due).days - 1) * (self.rate / interest_rate[self.rule_type])
193+
for move, lines in amls.grouped('move_id').items()
194+
)
195+
self._update_deuda(deuda, partner, 'Deuda último periodo', interest)
196+
197+
# Intereses por pagos tardíos
198+
if self.late_payment_interest:
199+
200+
partials = self.env['account.partial.reconcile'].search([
201+
# lo dejamos para NTH
202+
# debit_move_id. safe eval domain
203+
('debit_move_id.partner_id.active', '=', True),
204+
('debit_move_id.date_maturity', '>=', from_date),
205+
('debit_move_id.date_maturity', '<=', to_date),
206+
('debit_move_id.parent_state', '=', 'posted'),
207+
('debit_move_id.account_id', 'in', self.receivable_account_ids.ids),
208+
('credit_move_id.date', '>=', from_date),
209+
('credit_move_id.date', '<', to_date)]).grouped('debit_move_id')
210+
211+
for move_line, parts in partials.items():
212+
due_payments = parts.filtered(lambda x: x.credit_move_id.date > x.debit_move_id.date_maturity)
213+
interest = 0
214+
if due_payments:
215+
due_payments_amount = sum(due_payments.mapped('amount'))
216+
last_date_payment = parts.filtered(lambda x: x.credit_move_id.date > x.debit_move_id.date_maturity).sorted('max_date')[-1].max_date
217+
days = (last_date_payment - move_line.date_maturity).days
218+
interest += due_payments_amount * days * (self.rate / interest_rate[self.rule_type])
219+
self._update_deuda(deuda, move_line.partner_id, 'Deuda pagos vencidos', interest)
220+
221+
return deuda
222+
223+
def create_invoices(self, from_date, to_date):
224+
"""
225+
Crea facturas de intereses a cada partner basadas en los cálculos de deuda.
226+
Ejemplo:
227+
Tengo deudas viejas por 2000 (super viejas)
228+
el 1 facturo 1000 que vencen el 20
229+
el 25 pagó 400.
230+
Detalle de cálculo de intereses:
231+
* interés por todo lo viejo (2000) x el rate
232+
* interés de todo lo que venció en el último período ($600) x días que estuvo vencido (10 días)
233+
* si además marcó "late payment interest" se agrega interés por los días que pagó tarde, es decir $400 x 5 días
234+
"""
156235
self.ensure_one()
157236

237+
# Calcular deudas e intereses
238+
deuda = self._calculate_debts(from_date, to_date)
239+
158240
journal = self.env['account.journal'].search([
159241
('type', '=', 'sale'),
160242
('company_id', '=', self.company_id.id)], limit=1)
243+
<<<<<<< HEAD
161244

162245
if self.receivable_account_ids != journal.default_account_id:
163246
journal = self.env['account.journal'].search([('default_account_id','in',self.receivable_account_ids.ids)], limit=1) or journal
@@ -166,33 +249,30 @@ def create_invoices(self, to_date, groupby=['partner_id']):
166249
# Check if a filter is set
167250
if self.domain:
168251
move_line_domain += safe_eval(self.domain)
252+
||||||| parent of 13395e4a (temp)
253+
254+
if self.receivable_account_ids != journal.default_account_id:
255+
journal = self.env['account.journal'].search([('default_account_id','in',self.receivable_account_ids.ids)], limit=1) or journal
169256

170-
fields = ['id:recordset', 'amount_residual:sum', 'partner_id:recordset', 'account_id:recordset']
171-
172-
move_line = self.env['account.move.line']
173-
grouped_lines = move_line._read_group(
174-
domain=move_line_domain,
175-
groupby=groupby,
176-
aggregates=fields,
177-
)
178-
self = self.with_context(
179-
company_id=self.company_id.id,
180-
mail_notrack=True,
181-
prefetch_fields=False).with_company(self.company_id)
257+
move_line_domain = self._get_move_line_domains(to_date)
258+
# Check if a filter is set
259+
if self.domain:
260+
move_line_domain += safe_eval(self.domain)
261+
=======
262+
>>>>>>> 13395e4a (temp)
182263

183-
total_items = len(grouped_lines)
264+
total_items = len(deuda)
184265
_logger.info('%s interest invoices will be generated', total_items)
185-
for idx, line in enumerate(grouped_lines):
186-
move_vals = self._prepare_interest_invoice(
187-
line, to_date, journal)
188266

267+
# Crear facturas
268+
for idx, partner in enumerate(deuda):
269+
move_vals = self._prepare_interest_invoice(partner, deuda[partner], to_date, journal)
189270
if not move_vals:
190271
continue
191272

192-
_logger.info('Creating Interest Invoice (%s of %s) with values:\n%s', idx + 1, total_items, line)
273+
_logger.info('Creating Interest Invoice (%s of %s) for partner ID: %s', idx + 1, total_items, partner.id)
193274

194275
move = self.env['account.move'].create(move_vals)
195-
196276
if self.automatic_validation:
197277
try:
198278
move.action_post()
@@ -201,7 +281,10 @@ def create_invoices(self, to_date, groupby=['partner_id']):
201281
"Something went wrong creating "
202282
"interests invoice: {}".format(e))
203283

204-
def prepare_info(self, to_date, debt):
284+
285+
286+
287+
def _prepare_info(self, to_date):
205288
self.ensure_one()
206289

207290
# Format date to customer language
@@ -211,23 +294,18 @@ def prepare_info(self, to_date, debt):
211294
to_date_format = to_date.strftime(date_format)
212295

213296
res = _(
214-
'Deuda Vencida al %s: %s\n'
215-
'Tasa de interés: %s') % (
216-
to_date_format, debt, self.rate)
297+
'Deuda Vencida al %s con tasa de interés de %s') % (
298+
to_date_format, self.rate)
217299

218300
return res
219301

220-
def _prepare_interest_invoice(self, line, to_date, journal):
302+
def _prepare_interest_invoice(self, partner, debt, to_date, journal):
303+
"""
304+
Retorna un diccionario con los datos para crear la factura
305+
"""
221306
self.ensure_one()
222-
debt = line[2]
223-
224-
if not debt or debt <= 0.0:
225-
_logger.info("Debt is negative, skipping...")
226-
return
227307

228-
partner_id = line[0].id
229-
partner = self.env['res.partner'].browse(partner_id)
230-
comment = self.prepare_info(to_date, debt)
308+
comment = self._prepare_info(to_date)
231309
fpos = partner.property_account_position_id
232310
taxes = self.interest_product_id.taxes_id.filtered(
233311
lambda r: r.company_id == self.company_id)
@@ -243,16 +321,31 @@ def _prepare_interest_invoice(self, line, to_date, journal):
243321
'invoice_origin': "Interests Invoice",
244322
'invoice_payment_term_id': False,
245323
'narration': self.interest_product_id.name + '.\n' + comment,
324+
<<<<<<< HEAD
246325
'is_move_sent': True,
247326
'invoice_line_ids': [(0, 0, {
327+
||||||| parent of 13395e4a (temp)
328+
'invoice_line_ids': [(0, 0, {
329+
=======
330+
'invoice_line_ids': [(0, 0,
331+
{
332+
>>>>>>> 13395e4a (temp)
248333
"product_id": self.interest_product_id.id,
249334
"quantity": 1.0,
250-
"price_unit": self.rate * debt,
335+
"price_unit": value,
251336
"partner_id": partner.id,
252-
"name": self.interest_product_id.name + '.\n' + comment,
337+
"name": self.interest_product_id.name + '.\n' + key,
253338
"analytic_distribution": {self.analytic_account_id.id: 100.0} if self.analytic_account_id.id else False,
339+
<<<<<<< HEAD
254340
"tax_ids": [(6, 0, tax_id.ids)],
255341
})],
342+
||||||| parent of 13395e4a (temp)
343+
"tax_ids": [(6, 0, tax_id.ids)]
344+
})],
345+
=======
346+
"tax_ids": [(6, 0, tax_id.ids)]
347+
}) for key, value in debt.items() if isinstance(value, (int, float)) and value > 0],
348+
>>>>>>> 13395e4a (temp)
256349
}
257350

258351
# hack para evitar modulo glue con l10n_latam_document

account_interests/views/res_company_views.xml

+1-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
<field name="interval"/>
2121
<field name="rule_type"/>
2222
<field name="next_date"/>
23-
<field name="tolerance_interval"/>
2423
<field name="has_domain"/>
2524
</list>
2625
<form>
@@ -37,7 +36,7 @@
3736
<field name="interval"/>
3837
<field name="rule_type"/>
3938
<field name="next_date"/>
40-
<field name="tolerance_interval"/>
39+
<field name="late_payment_interest"/>
4140
</group>
4241
<field name="domain" widget="domain" options="{ 'model': 'account.move.line', 'in_dialog': True }" />
4342
</group>

0 commit comments

Comments
 (0)