
    pj                    &   d dl mZ d dlZd dlmc mZ d dlZd dl	m
Z
 d dlmZ d dlmZ d dlmZ d dlmZmZmZmZmZ d dlmZ d d	lmZ d
 Zd Zdddddd dZd!dZej                             dg d          d"d            Z!d!dZ"d!dZ#d!dZ$dS )#    )annotationsN)HTTPException)confirm_create_budget_account)confirm_create_category)import_budget_csv_text)batch_mark_candidatesconfirm_duplicate_overrideconfirm_transaction_candidatelist_transaction_candidates preview_batch_confirm_candidates)connect_memory)apply_migrationsc                 @    t                      } t          |            | S )N)r   r   )conns    j/home/agent/.hermes/worktrees/FinanceManager-sprint20d/tests/unit/test_budget_duplicate_handling_hotfix.pydbr      s     DTK    c                p    t          | dddd          d         }t          | ddd          d         }||fS )	NzBudget KontocashCHF)nameaccount_typecurrency	entity_idzEssen & Haushaltexpense)r   category_type)r   r   )r   accountfoods      r   
setup_baser      sS    +D>[aot2u2uvv  xC  DG"42DW`)a)abbcnoDD=r   pendingz
M Windischmigros_receiptsstatuscategory_iddescriptionsource_typecidstrr#   r$   
str | Noner%   r&   returnNonec               j    |                      d||||||||f           |                                  d S )Nu  
        INSERT INTO budget_transaction_candidates(
            transaction_candidate_id, source_file_label, source_row_or_range, source_type,
            transaction_date, description, merchant, amount_original, currency_original,
            proposed_category_id, proposed_category_name, confidence, requires_review, status,
            classification, review_reason, raw_fingerprint, created_at, updated_at
        ) VALUES (?, '2026 Migros Einkäufe.csv', ?, ?, '2026-03-20', ?, 'M Windisch', '0.40', 'CHF', ?,
            CASE WHEN ? IS NULL THEN NULL ELSE 'Essen & Haushalt' END, '0.80', 0, ?, 'migros_receipt_food_household',
            'duplicate_detection_v1', ?, '2026-01-01T00:00:00', '2026-01-01T00:00:00')
        )executecommit)r   r'   r#   r$   r%   r&   s         r   	candidater/       sF    LL		 
c;[+vsS   	KKMMMMMr   c                 j   t                      } t          |           \  }}t          | dd|           t          | dd|           t          | dd|           t          | dd	|           d
 t          | d          D             }d t          | dd          D             }d t          | dd          D             }dh}||k    }|st	          j        d|fd||f          dt          j                    v st	          j        |          rt	          j	        |          ndt	          j	        |          dz  }dd|iz  }	t          t	          j        |	                    d x}}dh}||k    }|st	          j        d|fd||f          dt          j                    v st	          j        |          rt	          j	        |          ndt	          j	        |          dz  }dd|iz  }	t          t	          j        |	                    d x}}h d}||k    }|st	          j        d|fd||f          dt          j                    v st	          j        |          rt	          j	        |          ndt	          j	        |          dz  }dd|iz  }	t          t	          j        |	                    d x}}d S )Nopen1r    r#   r$   dup1duplicate_candidateblocked1duplicate_blocked	possible1possible_duplicatec                    h | ]
}|d          S transaction_candidate_id .0rs     r   	<setcomp>zetest_normal_review_and_migros_tab_exclude_duplicates_but_duplicates_tab_shows_them.<locals>.<setcomp>8   s    oooA!./ooor   2026)budget_yearc                    h | ]
}|d          S r:   r<   r=   s     r   r@   zetest_normal_review_and_migros_tab_exclude_duplicates_but_duplicates_tab_shows_them.<locals>.<setcomp>9   s    }}}A!./}}}r   migros)tabrB   c                    h | ]
}|d          S r:   r<   r=   s     r   r@   zetest_normal_review_and_migros_tab_exclude_duplicates_but_duplicates_tab_shows_them.<locals>.<setcomp>:   s0      E  E  EqQ12  E  E  Er   
duplicates==)z%(py0)s == %(py3)s
normal_ids)py0py3assert %(py5)spy5
migros_ids>   r3   r5   r7   duplicate_ids)r   r   r/   r   
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanation)
r   _accountr   rJ   rO   rP   @py_assert2@py_assert1@py_format4@py_format6s
             r   Rtest_normal_review_and_migros_tab_exclude_duplicates_but_duplicates_tab_shows_themr^   1   s^   44D*T"2"24dGI4@@@@dF#8dKKKKdJ':MMMMdK(<$OOOOoo9TUYgm9n9n9noooJ}}9TUY_gu{9|9|9|}}}J E  E<WX\bn  }C  =D  =D  =D  E  E  EM!":"""""""""":"""""""""""":""""":"""""""""""""""""""""""""""""""!":"""""""""":"""""""""""":""""":"""""""""""""""""""""""""""""""=======================================================================r   )r4   r6   r8   
supersededcovered_by_migrosignoredc           	        t                      }t          |          \  }}t          |d|  | |           t          j        t
                    5 }t          |d|  ||dd           d d d            n# 1 swxY w Y   |j        }|j        }d}||k    }|st          j
        d|fd||f          dt          j                    v st          j        |          rt          j        |          ndt          j        |          t          j        |          t          j        |          d	z  }	d
d|	iz  }
t          t          j        |
                    d x}x}x}}d}|j        }|j        }t%          |          }||v }|st          j
        d|fd||f          t          j        |          dt          j                    v st          j        t$                    rt          j        t$                    nddt          j                    v st          j        |          rt          j        |          ndt          j        |          t          j        |          t          j        |          dz  }dd|iz  }t          t          j        |                    d x}x}x}x}}|                    d                                          d         }d}||k    }|slt          j
        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t          t          j        |                    d x}x}}d S )Ncand_r2   rA   
account_idr$   rB   i  rH   zN%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.value
}.status_code
} == %(py7)sexcrK   py2py4py7assert %(py9)spy9z2Kandidat ist als Duplikat/covered/ignored markiertin)zi%(py1)s in %(py10)s
{%(py10)s = %(py3)s(%(py8)s
{%(py8)s = %(py6)s
{%(py6)s = %(py4)s.value
}.detail
})
}r(   )py1rL   rj   py6py8py10zassert %(py12)spy12(SELECT COUNT(*) FROM budget_transactionsr   z%(py1)s == %(py4)srp   rj   assert %(py6)srq   )r   r   r/   pytestraisesr   r
   valuestatus_coderQ   rR   rS   rT   rU   rV   rW   rX   detailr(   r-   fetchone)r#   r   r   r   rg   r[   @py_assert3@py_assert6@py_assert5@py_format8@py_format10@py_assert0@py_assert7@py_assert9rZ   @py_format11@py_format13@py_format5@py_format7s                      r   ?test_normal_confirm_guard_blocks_duplicate_and_covered_statusesr   A   s   44DD!1!1$d$F$$VFFFF	}	%	% C%d,<F,<,<Wei  {A  ?B  ?B  	C  	C  	CC C C C C C C C C C C C C C C 9'9 'C' C'''''''''' C''''''''''''3'''''3''''''9'''' ''''C'''''''''''''''''''''''''''''?XsyXyGWX3GWCXCXX?CXXXXXXXXXXX?CXXXXXX?XXXXXXXXXXX3XXXXX3XXXXXXXXXXXXXsXXXXXsXXXXXXyXXXXGWXXXXCXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<<BCCLLNNqQVUVVQUVVVVVVVVVVVQUVVVVVVQVVVVUVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVs   A55A9<A9c            	        t                      } t          |           \  }}t          | dd|           t          j        t
                    5 }t          | d||dd           d d d            n# 1 swxY w Y   |j        }|j        }d}||k    }|st          j
        d|fd||f          d	t          j                    v st          j        |          rt          j        |          nd	t          j        |          t          j        |          t          j        |          d
z  }dd|iz  }	t          t          j        |	                    d x}x}x}}t          | d||ddd          }
|
d         }d}||k    }|slt          j
        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t          t          j        |                    d x}x}}|                     d                                          d         }d}||k    }|slt          j
        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t          t          j        |                    d x}x}}d |                     d                                          D             }d}||v }|st          j
        d|fd||f          t          j        |          dt          j                    v st          j        |          rt          j        |          nddz  }dd |iz  }t          t          j        |                    d x}}d S )!Nr3   r4   r2   rA   rd   i  rH   rf   rg   rh   rl   rm   z#Bewusst separater Bon trotz Treffer)re   r$   rB   reasonr#   	confirmedrv   rw   rx   rq   ru   r      c                    h | ]
}|d          S )r   r<   r=   s     r   r@   zVtest_duplicate_override_requires_reason_and_audits_explicit_booking.<locals>.<setcomp>Z   s    [[[aQqT[[[r   zSELECT action FROM audit_log&candidate_duplicate_override_confirmedrn   )z%(py1)s in %(py3)saudit_actions)rp   rL   rM   rN   )r   r   r/   ry   rz   r   r	   r{   r|   rQ   rR   rS   rT   rU   rV   rW   rX   r-   r~   fetchall)r   r   r   rg   r[   r   r   r   r   r   resultr   rZ   r   r   r   r\   r]   s                     r   Ctest_duplicate_override_requires_reason_and_audits_explicit_bookingr   N   sU   44DD!1!1$dF#8dKKKK	}	%	% v"4X\ms1t1tuuuv v v v v v v v v v v v v v v9'9 'C' C'''''''''' C''''''''''''3'''''3''''''9'''' ''''C''''''''''''''''''''''''''''''fW]arx  Ej  7k  7k  l  lF(*{*{**********{*********{***************************<<BCCLLNNqQVUVVQUVVVVVVVVVVVQUVVVVVVQVVVVUVVVVVVVVVVVVVVVVVVVVVVVVVVVV[[4<<0N#O#O#X#X#Z#Z[[[M3D3}DDDDDDDDDD3}DDDDD3DDDDDDDDDDD}DDDDD}DDDDDDDDDDDDDDDDDDDDDDDDDDDDDs   A//A36A3c                    t                      } t          |           \  }}t          | dd|dd           t          | dd|           t          | ddg|d	
          }|d         }d}||k    }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d x}x}}|d         }d}||u }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d x}x}}|d         }dddg}||k    }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d x}x}}t          | ddgd||d         d	          }	|	d         }d}||k    }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d x}x}}|	d         }d}||k    }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d x}x}}|	d         }dddg}||k    }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d x}x}}| 
                    d                                           d!         }d}||k    }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d x}x}}d S )"Nok1r    zCoop Einkaufvisa_credit_cardr"   r3   r6   r2   rA   )candidate_idsre   rB   countr   rH   rv   rw   rx   rq   can_confirmF)is)z%(py1)s is %(py4)s	conflictsduplicate_candidate_blocked)candidate_idr   confirm
preview_id)r   actionre   r   rB   confirmed_countskipped_counterrors)r   errorzVSELECT status FROM budget_transaction_candidates WHERE transaction_candidate_id='dup1'r   )r   r   r/   r   rQ   rR   rV   rW   rX   r   r-   r~   )
r   r   r   previewr   r   rZ   r   r   r   s
             r   Ctest_batch_confirm_skips_duplicates_and_reports_per_candidate_errorr   ^   s3   44DD!1!1$dE)>gyzzzzdF#6DIIII.tE6?_ftz{{{G7 q q          q         q                           =!*U*!U**********!U*****!****U***************************;fVGd$e$e#ff#fffffffffff#ffffffffff#ffffffffffffffffffffffffffff"4vyel  zA  BN  zO  ]c  d  d  dF#$))$))))))))))$)))))$)))))))))))))))))))))))))))))))/"'a'"a''''''''''"a'''''"''''a'''''''''''''''''''''''''''(aB_ ` `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<<pqqzz||}~  W  DW  W  DW  W  W  W  W  W  W  W  W  W  W  DW  W  W  W  W  W  W  W  W  W  DW  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  W  Wr   c                 R   t                      } t          |           \  }}d}t          | d|dd|          }t          | d|dd|          }|d         }d}||k    }|slt          j        d	|fd
||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}|d         }d}||k    }|slt          j        d	|fd
||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}|                     d          	                                d         }d}||k    }|slt          j        d	|fd
||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}|                     d          	                                d         }d}||k    }|slt          j        d	|fd
||f          t          j        |          t          j        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}d S )NzDatum;Zeit;Filiale;Kassennummer;Transaktionsnummer;Artikel;Menge;Aktion;Umsatz
02.01.2026;10:00;Migros Test;1;99;Artikel A;1;;1.10
02.01.2026;10:00;Migros Test;1;99;Artikel B;1;;2.20
r!   u   2026 Migros Einkäufe.csvF)source_file_labeldry_rundefault_category_idzsame receipt exported again.csvcandidate_countr   rH   rv   rw   rx   rq   r   zlSELECT COUNT(*) FROM budget_transaction_candidates WHERE source_type IN ('migros_receipts','migros_receipt')z-SELECT COUNT(*) FROM budget_import_line_items   )
r   r   r   rQ   rR   rV   rW   rX   r-   r~   )r   rY   r   csv_textfirstsecondr   r   rZ   r   r   s              r   ^test_migros_receipt_key_import_is_idempotent_across_reimports_and_article_rows_are_detail_onlyr   o   s.   44D*T"2"24 LH"4):HXs  ~C  Y]  ^  ^  ^E#D*;XYz  EJ  `d  e  e  eF"#(q(#q((((((((((#q(((((#((((q(((((((((((((((((((((((((((#$))$))))))))))$)))))$)))))))))))))))))))))))))))))))<<  G  H  H  Q  Q  S  S  TU  V  [  Z[  [  V  Z[  [  [  [  [  [  [  [  [  [  [  V  Z[  [  [  [  [  [  V  [  [  [  [  Z[  [  [  [  [  [  [  [  [  [  [  [  [  [  [  [  [  [  [  [  [  [  [  [  [  [  [  [<<GHHQQSSTUV[Z[[VZ[[[[[[[[[[[VZ[[[[[[V[[[[Z[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[r   )r'   r(   r#   r(   r$   r)   r%   r(   r&   r(   r*   r+   )r*   r+   )r#   r(   r*   r+   )%
__future__r   builtinsrS   _pytest.assertion.rewrite	assertionrewriterQ   ry   fastapir   'jarvis_finance.services.budget_accountsr   )jarvis_finance.services.budget_categoriesr   *jarvis_finance.services.budget_csv_importsr   &jarvis_finance.services.budget_importsr   r	   r
   r   r   jarvis_finance.storage.databaser   !jarvis_finance.storage.migrationsr   r   r   r/   r^   markparametrizer   r   r   r   r<   r   r   <module>r      s   " " " " " "               ! ! ! ! ! ! Q Q Q Q Q Q M M M M M M M M M M M M              ; : : : : : > > > > > >     09TXmy  O`      "> > > >    $T  $T  $T  U  U	W 	W 	W U  U	WE E E E W W W W"
\ 
\ 
\ 
\ 
\ 
\r   