import logging import re _logger = logging.getLogger('poynt_match') SL = env['poynt.settlement.line'].sudo() Partner = env['res.partner'].sudo() Invoice = env['account.move'].sudo() lines = SL.search([('state', '=', 'fetched'), ('action', '=', 'SALE')]) print(f'Unmatched SALE lines: {len(lines)}', flush=True) matched_by_name = 0 matched_by_amount = 0 no_match = 0 for sl in lines: name = (sl.card_holder_name or '').strip() amount = sl.amount txn_date = sl.transaction_date.date() if sl.transaction_date else None partner = None # --- Strategy 1: Match by cardholder name --- if name and name != '/': # Parse LASTNAME/FIRSTNAME or FIRSTNAME LASTNAME if '/' in name: parts = name.split('/') last_name = parts[0].strip().title() first_name = parts[1].strip().title() if len(parts) > 1 else '' else: parts = name.split() first_name = parts[0].title() if parts else '' last_name = parts[-1].title() if len(parts) > 1 else '' if last_name and first_name: # Exact match: "Lastname, Firstname" or "Firstname Lastname" candidates = Partner.search([ ('customer_rank', '>', 0), '|', ('name', 'ilike', f'{last_name}, {first_name}'), ('name', 'ilike', f'{first_name} {last_name}'), ], limit=5) # If still ambiguous (>1), try to narrow by matching amount to open invoices if len(candidates) == 1: partner = candidates[0] elif len(candidates) > 1: # Pick the one with an open invoice matching the amount for c in candidates: inv = Invoice.search([ ('partner_id', '=', c.id), ('move_type', '=', 'out_invoice'), ('payment_state', 'in', ('not_paid', 'partial')), ('amount_residual', '>=', amount - 1), ('amount_residual', '<=', amount + 1), ], limit=1) if inv: partner = c break if not partner: partner = candidates[0] # Take first if no invoice match # --- Strategy 2: Match by amount against open invoices (if no name match) --- if not partner and txn_date and amount > 50: # Look for open invoices with matching amount within ±30 days invs = Invoice.search([ ('move_type', '=', 'out_invoice'), ('payment_state', 'in', ('not_paid', 'partial')), ('amount_residual', '>=', amount - 0.50), ('amount_residual', '<=', amount + 0.50), ('invoice_date', '>=', str(txn_date - __import__('datetime').timedelta(days=60))), ('invoice_date', '<=', str(txn_date + __import__('datetime').timedelta(days=5))), ], limit=3) if len(invs) == 1: partner = invs[0].partner_id sl.invoice_id = invs[0].id # --- Write match --- if partner: vals = {'partner_id': partner.id, 'state': 'matched'} if name and name != '/': vals['match_method'] = 'cardholder_name' matched_by_name += 1 else: vals['match_method'] = 'invoice_amount' matched_by_amount += 1 # Also try to find matching invoice if not already set if not sl.invoice_id and txn_date: inv = Invoice.search([ ('partner_id', '=', partner.id), ('move_type', '=', 'out_invoice'), ('payment_state', 'in', ('not_paid', 'partial')), ('amount_residual', '>=', amount - 1), ('amount_residual', '<=', amount + 1), ], limit=1) if inv: vals['invoice_id'] = inv.id sl.write(vals) else: no_match += 1 if (matched_by_name + matched_by_amount + no_match) % 100 == 0: env.cr.commit() env.cr.commit() total_matched = matched_by_name + matched_by_amount print(f'\nRESULTS:', flush=True) print(f' Matched by name: {matched_by_name}', flush=True) print(f' Matched by invoice amount: {matched_by_amount}', flush=True) print(f' No match: {no_match}', flush=True) print(f' Total matched: {total_matched}/{len(lines)} ({round(100*total_matched/len(lines),1) if lines else 0}%)', flush=True) # Summary all_lines = SL.search([]) print(f'\nOVERALL:', flush=True) print(f' Total lines: {len(all_lines)}', flush=True) print(f' Matched: {SL.search_count([("state", "=", "matched")])}', flush=True) print(f' With invoice: {SL.search_count([("invoice_id", "!=", False)])}', flush=True) print(f' Fetched (unmatched): {SL.search_count([("state", "=", "fetched")])}', flush=True)