@@ -123,176 +123,199 @@ def _compute_analysis_message(self):
123
123
for partner in self :
124
124
partner .analysis_message = message
125
125
126
- def _compute_sales_info (self ):
127
- """Compute stats for sale orders and invoices ."""
126
+ def _get_analysis_months (self ):
127
+ """Retorna o número de meses configurados para análise ."""
128
128
config_param = self .env ["ir.config_parameter" ].sudo ()
129
- months = config_param .get_param (
130
- "engenere_partner_sales_info.default_analysis_months" , 24
129
+ return int (
130
+ config_param .get_param (
131
+ "engenere_partner_sales_info.default_analysis_months" , 24
132
+ )
131
133
)
132
- analysis_months = int (months )
133
- start_date = fields .Date .context_today (self ) - relativedelta (
134
- months = analysis_months
134
+
135
+ def _get_start_date (self , analysis_months ):
136
+ """Calcula a data de início com base nos meses de análise."""
137
+ return fields .Date .context_today (self ) - relativedelta (months = analysis_months )
138
+
139
+ def _group_records_by_partner (self , records ):
140
+ """Agrupa registros por partner_id."""
141
+ grouped = defaultdict (list )
142
+ for record in records :
143
+ grouped [record .partner_id .id ].append (record )
144
+ return grouped
145
+
146
+ def _compute_record_stats (self , records , date_extractor , amount_extractor ):
147
+ """Calcula estatísticas comuns para uma lista de registros."""
148
+ if not records :
149
+ return None
150
+
151
+ sorted_records = sorted (records , key = lambda r : date_extractor (r ))
152
+ dates = [date_extractor (r ) for r in sorted_records ]
153
+ amounts = [amount_extractor (r ) for r in sorted_records ]
154
+ count = len (sorted_records )
155
+ total = sum (amounts )
156
+ average = total / count if count else 0
157
+
158
+ avg_no_outliers = average
159
+ if count >= 3 :
160
+ mean = average
161
+ variance = sum ((x - mean ) ** 2 for x in amounts ) / count
162
+ std_dev = math .sqrt (variance )
163
+ filtered = [
164
+ x
165
+ for x in amounts
166
+ if (mean - 1.5 * std_dev ) <= x <= (mean + 1.5 * std_dev )
167
+ ]
168
+ avg_no_outliers = sum (filtered ) / len (filtered ) if filtered else mean
169
+
170
+ avg_time_between = 0.0
171
+ if count >= 2 :
172
+ total_days = sum ((dates [i ] - dates [i - 1 ]).days for i in range (1 , count ))
173
+ avg_time_between = total_days / (count - 1 )
174
+
175
+ today = fields .Date .context_today (self )
176
+ return {
177
+ "last_record" : sorted_records [- 1 ],
178
+ "last_date" : dates [- 1 ] if dates else None ,
179
+ "count" : count ,
180
+ "total" : total ,
181
+ "average" : average ,
182
+ "avg_no_outliers" : avg_no_outliers ,
183
+ "avg_time_between" : avg_time_between ,
184
+ "days_since_last" : (today - dates [- 1 ]).days if dates else 0 ,
185
+ }
186
+
187
+ def _update_sales_fields (self , stats ):
188
+ """Atualiza campos de vendas com base nas estatísticas."""
189
+ self .update (
190
+ {
191
+ "last_order_id" : stats ["last_record" ].id if stats else False ,
192
+ "last_order_date" : stats ["last_date" ],
193
+ "last_order_status" : stats ["last_record" ].state if stats else False ,
194
+ "order_count" : stats ["count" ] or 0 ,
195
+ "total_ordered" : stats ["total" ] or 0 ,
196
+ "average_ordered" : stats ["average" ] or 0 ,
197
+ "average_ordered_no_discrepancies" : stats ["avg_no_outliers" ] or 0 ,
198
+ "average_time_between_orders" : stats ["avg_time_between" ] or 0 ,
199
+ "days_since_last_order" : stats ["days_since_last" ] or 0 ,
200
+ }
135
201
)
136
- partners = self .filtered (lambda p : p .customer_rank > 0 )
137
- partner_ids = partners .ids
138
202
139
- # Sale Orders
203
+ def _update_invoice_fields (self , stats ):
204
+ """Atualiza campos de faturas com base nas estatísticas."""
205
+ self .update (
206
+ {
207
+ "last_invoice_id" : stats ["last_record" ].id if stats else False ,
208
+ "last_invoice_date" : stats ["last_date" ],
209
+ "invoice_count" : stats ["count" ] or 0 ,
210
+ "total_invoiced" : stats ["total" ] or 0 ,
211
+ "average_invoiced" : stats ["average" ] or 0 ,
212
+ "average_invoiced_no_discrepancies" : stats ["avg_no_outliers" ] or 0 ,
213
+ "average_time_between_invoices" : stats ["avg_time_between" ] or 0 ,
214
+ "days_since_last_invoice" : stats ["days_since_last" ] or 0 ,
215
+ }
216
+ )
217
+
218
+ def _reset_sales_fields (self ):
219
+ """Reseta campos relacionados a vendas."""
220
+ self .update (
221
+ {
222
+ "last_order_id" : False ,
223
+ "last_order_date" : False ,
224
+ "last_order_status" : False ,
225
+ "order_count" : 0 ,
226
+ "total_ordered" : 0 ,
227
+ "average_ordered" : 0 ,
228
+ "average_ordered_no_discrepancies" : 0 ,
229
+ "average_time_between_orders" : 0 ,
230
+ "days_since_last_order" : 0 ,
231
+ }
232
+ )
233
+
234
+ def _reset_invoice_fields (self ):
235
+ """Reseta campos relacionados a faturas."""
236
+ self .update (
237
+ {
238
+ "last_invoice_id" : False ,
239
+ "last_invoice_date" : False ,
240
+ "invoice_count" : 0 ,
241
+ "total_invoiced" : 0 ,
242
+ "average_invoiced" : 0 ,
243
+ "average_invoiced_no_discrepancies" : 0 ,
244
+ "average_time_between_invoices" : 0 ,
245
+ "days_since_last_invoice" : 0 ,
246
+ }
247
+ )
248
+
249
+ def _compute_sales_info (self ):
250
+ """Calcula principais métricas de vendas e faturas."""
251
+ analysis_months = self ._get_analysis_months ()
252
+ start_date = self ._get_start_date (analysis_months )
253
+ customer_partners = self .filtered (lambda p : p .customer_rank > 0 )
254
+
255
+ # Processar pedidos de venda
140
256
sale_orders = self .env ["sale.order" ].search (
141
257
[
142
- ("partner_id" , "in" , partner_ids ),
258
+ ("partner_id" , "in" , customer_partners . ids ),
143
259
("state" , "!=" , "cancel" ),
144
260
("date_order" , ">=" , start_date ),
145
261
]
146
262
)
147
- from_so = defaultdict (list )
148
- for so in sale_orders :
149
- from_so [so .partner_id .id ].append (so )
263
+ sales_group = self ._group_records_by_partner (sale_orders )
150
264
151
- # Invoices
265
+ # Processar faturas
152
266
invoices = self .env ["account.move" ].search (
153
267
[
154
- ("partner_id" , "in" , partner_ids ),
268
+ ("partner_id" , "in" , customer_partners . ids ),
155
269
("move_type" , "=" , "out_invoice" ),
156
270
("state" , "=" , "posted" ),
157
271
("reversed_entry_id" , "=" , False ),
158
272
("invoice_date" , ">=" , start_date ),
159
273
]
160
274
)
161
- from_inv = defaultdict (list )
162
- for inv in invoices :
163
- from_inv [inv .partner_id .id ].append (inv )
275
+ invoices_group = self ._group_records_by_partner (invoices )
164
276
165
- for partner in partners :
166
- so_list = sorted (from_so .get (partner .id , []), key = lambda x : x .date_order )
167
- if so_list :
168
- partner .last_order_id = so_list [- 1 ].id
169
- partner .last_order_date = so_list [- 1 ].date_order .date ()
170
- partner .last_order_status = so_list [- 1 ].state
171
- count_so = len (so_list )
172
- total_so = sum (o .amount_total for o in so_list )
173
- avg_so = total_so / count_so if count_so else 0
174
- if count_so >= 3 :
175
- amounts_so = [o .amount_total for o in so_list ]
176
- mean_so = total_so / count_so
177
- var_so = sum ((x - mean_so ) ** 2 for x in amounts_so ) / count_so
178
- std_dev_so = math .sqrt (var_so )
179
- lower_so = mean_so - 1.5 * std_dev_so
180
- upper_so = mean_so + 1.5 * std_dev_so
181
- filtered_so = [x for x in amounts_so if lower_so <= x <= upper_so ]
182
- avg_no_disc_so = (
183
- (sum (filtered_so ) / len (filtered_so ))
184
- if filtered_so
185
- else mean_so
186
- )
187
- else :
188
- avg_no_disc_so = avg_so
189
-
190
- avg_time_so = 0
191
- if count_so >= 2 :
192
- dates_so = [o .date_order .date () for o in so_list ]
193
- total_days_so = 0
194
- for idx in range (1 , len (dates_so )):
195
- total_days_so += (dates_so [idx ] - dates_so [idx - 1 ]).days
196
- avg_time_so = total_days_so / (count_so - 1 )
197
-
198
- partner .order_count = count_so
199
- partner .total_ordered = total_so
200
- partner .average_ordered = avg_so
201
- partner .average_ordered_no_discrepancies = avg_no_disc_so
202
- partner .average_time_between_orders = avg_time_so
203
- today = fields .Date .context_today (self )
204
- partner .days_since_last_order = (
205
- (today - so_list [- 1 ].date_order .date ()).days
206
- if so_list [- 1 ].date_order
207
- else 0
208
- )
277
+ for partner in customer_partners :
278
+ # Processar vendas
279
+ so_stats = self ._compute_record_stats (
280
+ sales_group .get (partner .id , []),
281
+ lambda r : r .date_order .date (),
282
+ lambda r : r .amount_total ,
283
+ )
284
+ if so_stats :
285
+ partner ._update_sales_fields (so_stats )
209
286
else :
210
- partner .last_order_id = False
211
- partner .last_order_date = False
212
- partner .last_order_status = False
213
- partner .order_count = 0
214
- partner .total_ordered = 0
215
- partner .average_ordered = 0
216
- partner .average_ordered_no_discrepancies = 0
217
- partner .average_time_between_orders = 0
218
- partner .days_since_last_order = 0
287
+ partner ._reset_sales_fields ()
219
288
220
- inv_list = sorted (
221
- from_inv .get (partner .id , []), key = lambda x : x .invoice_date
289
+ # Processar faturas
290
+ inv_stats = self ._compute_record_stats (
291
+ invoices_group .get (partner .id , []),
292
+ lambda r : r .invoice_date ,
293
+ lambda r : r .amount_total ,
222
294
)
223
- if inv_list :
224
- partner .last_invoice_id = inv_list [- 1 ].id
225
- partner .last_invoice_date = inv_list [- 1 ].invoice_date
226
- count_inv = len (inv_list )
227
- total_inv = sum (i .amount_total for i in inv_list )
228
- avg_inv = total_inv / count_inv if count_inv else 0
229
- if count_inv >= 3 :
230
- amounts_inv = [i .amount_total for i in inv_list ]
231
- mean_inv = total_inv / count_inv
232
- var_inv = sum ((x - mean_inv ) ** 2 for x in amounts_inv ) / count_inv
233
- std_dev_inv = math .sqrt (var_inv )
234
- lower_inv = mean_inv - 1.5 * std_dev_inv
235
- upper_inv = mean_inv + 1.5 * std_dev_inv
236
- filtered_inv = [
237
- x for x in amounts_inv if lower_inv <= x <= upper_inv
238
- ]
239
- avg_no_disc_inv = (
240
- (sum (filtered_inv ) / len (filtered_inv ))
241
- if filtered_inv
242
- else mean_inv
243
- )
244
- else :
245
- avg_no_disc_inv = avg_inv
246
-
247
- avg_time_inv = 0
248
- if count_inv >= 2 :
249
- dates_inv = [i .invoice_date for i in inv_list ]
250
- total_days_inv = 0
251
- for i in range (1 , len (dates_inv )):
252
- total_days_inv += (dates_inv [i ] - dates_inv [i - 1 ]).days
253
- avg_time_inv = total_days_inv / (count_inv - 1 )
254
-
255
- partner .invoice_count = count_inv
256
- partner .total_invoiced = total_inv
257
- partner .average_invoiced = avg_inv
258
- partner .average_invoiced_no_discrepancies = avg_no_disc_inv
259
- partner .average_time_between_invoices = avg_time_inv
260
- today = fields .Date .context_today (self )
261
- partner .days_since_last_invoice = (
262
- (today - partner .last_invoice_date ).days
263
- if partner .last_invoice_date
264
- else 0
265
- )
295
+ if inv_stats :
296
+ partner ._update_invoice_fields (inv_stats )
266
297
else :
267
- partner .last_invoice_id = False
268
- partner .last_invoice_date = False
269
- partner .invoice_count = 0
270
- partner .total_invoiced = 0
271
- partner .average_invoiced = 0
272
- partner .average_invoiced_no_discrepancies = 0
273
- partner .average_time_between_invoices = 0
274
- partner .days_since_last_invoice = 0
298
+ partner ._reset_invoice_fields ()
275
299
276
- # Reset stats for non-customers
277
- for partner in self - partners :
278
- partner .update (
279
- {
280
- "last_order_id" : False ,
281
- "last_order_date" : False ,
282
- "last_order_status" : False ,
283
- "order_count" : 0 ,
284
- "total_ordered" : 0 ,
285
- "average_ordered" : 0 ,
286
- "average_ordered_no_discrepancies" : 0 ,
287
- "average_time_between_orders" : 0 ,
288
- "days_since_last_order" : 0 ,
289
- "last_invoice_date" : False ,
290
- "invoice_count" : 0 ,
291
- "total_invoiced" : 0 ,
292
- "average_invoiced" : 0 ,
293
- "average_invoiced_no_discrepancies" : 0 ,
294
- "average_time_between_invoices" : 0 ,
295
- "last_invoice_id" : False ,
296
- "days_since_last_invoice" : 0 ,
297
- }
298
- )
300
+ # Resetar parceiros que não são clientes
301
+ (self - customer_partners ).write (
302
+ {
303
+ "last_order_id" : False ,
304
+ "last_order_date" : False ,
305
+ "last_order_status" : False ,
306
+ "order_count" : 0 ,
307
+ "total_ordered" : 0 ,
308
+ "average_ordered" : 0 ,
309
+ "average_ordered_no_discrepancies" : 0 ,
310
+ "average_time_between_orders" : 0 ,
311
+ "days_since_last_order" : 0 ,
312
+ "last_invoice_date" : False ,
313
+ "invoice_count" : 0 ,
314
+ "total_invoiced" : 0 ,
315
+ "average_invoiced" : 0 ,
316
+ "average_invoiced_no_discrepancies" : 0 ,
317
+ "average_time_between_invoices" : 0 ,
318
+ "last_invoice_id" : False ,
319
+ "days_since_last_invoice" : 0 ,
320
+ }
321
+ )
0 commit comments