From 0bd181040526b9531530aa4e0058fc2c20a7a4b3 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Tue, 19 May 2026 20:58:47 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20create=20expense=20report=20immediately?= =?UTF-8?q?=20=E2=80=94=20remove=20broken=20confirmation=20gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old flow required a "confirm" reply after showing a parsed-receipt table, but that follow-up dispatch call carries no receipts (they only exist in the /upload context). The confirmation gate was architecturally broken: the second turn would always create nothing. Fix: create the expense sheet immediately when receipts are present. Byte-exact and semantic duplicates are auto-skipped; the count of skipped items is reported in the success message. The report is always created in Odoo as a draft so users can review amounts and submit manually via Odoo > Expenses. Co-Authored-By: Claude Sonnet 4.6 --- agent_service/agents/expenses_agent.py | 59 ++++++-------------------- 1 file changed, 13 insertions(+), 46 deletions(-) diff --git a/agent_service/agents/expenses_agent.py b/agent_service/agents/expenses_agent.py index c809aa7..32ac3a1 100644 --- a/agent_service/agents/expenses_agent.py +++ b/agent_service/agents/expenses_agent.py @@ -186,18 +186,14 @@ class ExpensesAgent(BaseAgent): else: deduped.append((receipt, parsed)) - # Always show confirmation summary before creating — lets user verify - # parsed amounts and review flagged duplicates in one step. - if not user_confirmed: - self._gathered_data['mode'] = 'awaiting_confirmation' - self._confirmation_items = [ - (receipt, parsed, i in dup_indices) - for i, (receipt, parsed) in enumerate(paired) - ] - self._deduped = deduped - return [] - - # User confirmed — apply dup decision + # Auto-skip semantic duplicates by default; keep_all only if user explicitly asked. + # Receipts are only available in this single /upload request — there is no + # persistent receipt store across turns, so a "confirm then create" flow would + # always fail on the follow-up turn (no receipts in context). Creating + # immediately in draft state is the correct approach: users review and + # submit inside Odoo > Expenses. + n_skipped = len(paired) - len(deduped) + self._gathered_data['n_skipped'] = n_skipped final_list = paired if user_dup_decision == 'keep_all' else deduped sheet_name = f'Expense Report - {_date.today().isoformat()}' @@ -384,44 +380,15 @@ class ExpensesAgent(BaseAgent): data = self._gathered_data directive_id = self._directive.directive_id if self._directive else '' - if data.get('mode') == 'awaiting_confirmation': - items = getattr(self, '_confirmation_items', []) - n_dups = sum(1 for _, _, is_dup in items if is_dup) - lines = [f'I parsed {len(items)} receipt(s). Please review before I create the expense report:\n'] - lines.append(f' {"#":>3} {"Vendor":<30} {"Amount":>8} {"Date":<12}') - lines.append(f' {"---":>3} {"-"*30} {"-"*8} {"-"*12}') - for i, (receipt, parsed, is_dup) in enumerate(items, 1): - vendor = str(parsed.get('vendor') or receipt.get('filename', '?'))[:30] - amt = float(parsed.get('amount') or 0) - dt = str(parsed.get('date') or '') - flag = ' !! duplicate' if is_dup else '' - lines.append(f' {i:>3}. {vendor:<30} ${amt:>7.2f} {dt}{flag}') - lines.append('') - if n_dups: - lines.append( - f'{n_dups} item(s) marked "!! duplicate" appear to be the same receipt ' - f'as another entry (possibly an OCR amount mismatch).' - ) - lines.append( - 'Reply "confirm" to create the report and exclude duplicates (recommended).' - ) - lines.append( - 'Reply "confirm, keep all" to include every item even if duplicated.' - ) - else: - lines.append('Reply "confirm" to create the expense report.') - return AgentReport( - directive_id=directive_id, agent=self.name, status='complete', - summary='\n'.join(lines), data=data, - escalations=[], actions_taken=[]) - if data.get('mode') == 'create_from_receipts': if self._actions_taken: lines = '\n'.join(f' • {a}' for a in self._actions_taken) + n_skipped = data.get('n_skipped', 0) + dup_note = f'\n({n_skipped} duplicate receipt(s) were automatically skipped.)' if n_skipped else '' summary = ( - f'Expense report created successfully:\n{lines}\n\n' - 'The report is in draft. Please open Odoo › Expenses, ' - 'review the entries, and click Submit to send for approval.' + f'Expense report created successfully:\n{lines}{dup_note}\n\n' + 'The report is in draft — open Odoo › Expenses, ' + 'review the amounts, and click Submit to send for approval.' ) status = 'complete' else: