@@ -65,11 +65,6 @@ class ResCompanyInterest(models.Model):
65
65
default = 1 ,
66
66
help = "Repeat every (Days/Week/Month/Year)"
67
67
)
68
- tolerance_interval = fields .Integer (
69
- 'Tolerance' ,
70
- default = 1 ,
71
- help = "Number of periods of tolerance for dues. 0 = no tolerance"
72
- )
73
68
next_date = fields .Date (
74
69
'Date of Next Invoice' ,
75
70
default = fields .Date .today ,
@@ -81,6 +76,8 @@ class ResCompanyInterest(models.Model):
81
76
)
82
77
has_domain = fields .Boolean (compute = "_compute_has_domain" )
83
78
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
+
84
81
@api .model
85
82
def _cron_recurring_interests_invoices (self ):
86
83
_logger .info ('Running Interest Invoices Cron Job' )
@@ -91,8 +88,8 @@ def _cron_recurring_interests_invoices(self):
91
88
try :
92
89
rec .create_interest_invoices ()
93
90
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 ) )
96
93
companies_with_errors .append (rec .company_id .name )
97
94
rec .env .cr .rollback ()
98
95
@@ -101,63 +98,149 @@ def _cron_recurring_interests_invoices(self):
101
98
error_message = _ ("We couldn't run interest invoices cron job in the following companies: %s." ) % company_names
102
99
raise UserError (error_message )
103
100
101
+ < << << << HEAD
102
+ | | | | | | | parent of 13395e4 a (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
+ >> >> >> > 13395e4 a (temp )
104
119
def create_interest_invoices (self ):
105
120
for rec in self :
106
121
_logger .info (
107
122
'Creating Interest Invoices (id: %s, company: %s)' , rec .id ,
108
123
rec .company_id .name )
109
124
# hacemos un commit para refrescar cache
110
125
self .env .cr .commit ()
111
- interests_date = rec .next_date
126
+ to_date = rec .next_date
112
127
113
128
rule_type = rec .rule_type
114
129
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
+
136
136
# llamamos a crear las facturas con la compañia del interes para
137
137
# que tome correctamente las cuentas
138
138
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 )
140
140
141
141
# seteamos proxima corrida en hoy mas un periodo
142
- rec .next_date = interests_date + next_delta
142
+ rec .next_date = to_date + next_delta
143
143
144
- def _get_move_line_domains (self , to_date ):
144
+ def _get_move_line_domains (self ):
145
145
self .ensure_one ()
146
146
move_line_domain = [
147
147
('account_id' , 'in' , self .receivable_account_ids .ids ),
148
- ('full_reconcile_id' , '=' , False ),
149
- ('date_maturity' , '<' , to_date ),
150
148
('partner_id.active' , '=' , True ),
151
149
('parent_state' , '=' , 'posted' ),
152
150
]
153
151
return move_line_domain
154
152
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
+ """
156
235
self .ensure_one ()
157
236
237
+ # Calcular deudas e intereses
238
+ deuda = self ._calculate_debts (from_date , to_date )
239
+
158
240
journal = self .env ['account.journal' ].search ([
159
241
('type' , '=' , 'sale' ),
160
242
('company_id' , '=' , self .company_id .id )], limit = 1 )
243
+ < << << << HEAD
161
244
162
245
if self .receivable_account_ids != journal .default_account_id :
163
246
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']):
166
249
# Check if a filter is set
167
250
if self .domain :
168
251
move_line_domain += safe_eval (self .domain )
252
+ | | | | | | | parent of 13395e4 a (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
169
256
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
+ >> >> >> > 13395e4 a (temp )
182
263
183
- total_items = len (grouped_lines )
264
+ total_items = len (deuda )
184
265
_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 )
188
266
267
+ # Crear facturas
268
+ for idx , partner in enumerate (deuda ):
269
+ move_vals = self ._prepare_interest_invoice (partner , deuda [partner ], to_date , journal )
189
270
if not move_vals :
190
271
continue
191
272
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 )
193
274
194
275
move = self .env ['account.move' ].create (move_vals )
195
-
196
276
if self .automatic_validation :
197
277
try :
198
278
move .action_post ()
@@ -201,7 +281,10 @@ def create_invoices(self, to_date, groupby=['partner_id']):
201
281
"Something went wrong creating "
202
282
"interests invoice: {}" .format (e ))
203
283
204
- def prepare_info (self , to_date , debt ):
284
+
285
+
286
+
287
+ def _prepare_info (self , to_date ):
205
288
self .ensure_one ()
206
289
207
290
# Format date to customer language
@@ -211,23 +294,18 @@ def prepare_info(self, to_date, debt):
211
294
to_date_format = to_date .strftime (date_format )
212
295
213
296
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 )
217
299
218
300
return res
219
301
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
+ """
221
306
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
227
307
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 )
231
309
fpos = partner .property_account_position_id
232
310
taxes = self .interest_product_id .taxes_id .filtered (
233
311
lambda r : r .company_id == self .company_id )
@@ -243,16 +321,31 @@ def _prepare_interest_invoice(self, line, to_date, journal):
243
321
'invoice_origin' : "Interests Invoice" ,
244
322
'invoice_payment_term_id' : False ,
245
323
'narration' : self .interest_product_id .name + '.\n ' + comment ,
324
+ << << << < HEAD
246
325
'is_move_sent' : True ,
247
326
'invoice_line_ids' : [(0 , 0 , {
327
+ | | | | | | | parent of 13395e4 a (temp )
328
+ 'invoice_line_ids' : [(0 , 0 , {
329
+ == == == =
330
+ 'invoice_line_ids' : [(0 , 0 ,
331
+ {
332
+ >> >> >> > 13395e4 a (temp )
248
333
"product_id" : self .interest_product_id .id ,
249
334
"quantity" : 1.0 ,
250
- "price_unit" : self . rate * debt ,
335
+ "price_unit" : value ,
251
336
"partner_id" : partner .id ,
252
- "name" : self .interest_product_id .name + '.\n ' + comment ,
337
+ "name" : self .interest_product_id .name + '.\n ' + key ,
253
338
"analytic_distribution" : {self .analytic_account_id .id : 100.0 } if self .analytic_account_id .id else False ,
339
+ << << << < HEAD
254
340
"tax_ids" : [(6 , 0 , tax_id .ids )],
255
341
})],
342
+ | | | | | | | parent of 13395e4 a (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
+ >> >> >> > 13395e4 a (temp )
256
349
}
257
350
258
351
# hack para evitar modulo glue con l10n_latam_document
0 commit comments