
     hN                         d dl Z d dlZd dlZd dlmZmZ d dlmZ d dlmZ d dl	m
Z
  ej                  ej                  d        G d d	e      Zy)
    N)ListOptional)datetime)BaseBankStatementParser)Transactionz&%(asctime)s %(levelname)s: %(message)s)levelformatc                   t   e Zd ZdZ ej
                  d      Zd Zdede	e
   fdZde	e   defdZde	e   defd	Zde	e   defd
Zde	e	e      de	e	e      fdZdede	e   fdZdedededede
f
dZd&dZdedefdZdedefdZdedefdZdedefdZdedededefdZdeded ed!ed"ed#ed$ede
fd%Zy)'PrivatBankPdfParserun  
    Парсер PDF‑выписок ПриватБанка.

    Подход:
      1) Собираем все строки всех таблиц (со всех страниц).
      2) Фильтруем «итоговые» строки ("За dd.mm.yyyy..."), заголовки и «балансовые» строки.
      3) Склеиваем строки, где первая ячейка пуста (продолжение предыдущей).
      4) Разбираем каждую строку => Transaction.
      5) Если в тексте назначения найдена «комиссия» (ком бан XX.XX грн и т.п.) —
         создаём отдельную минусовую транзакцию.
      6) Фильтруем дубли транзакций (по номеру документа, дате и сумме).
    u]   (?i)(?:ком[\s\.]*бан(?:ку)?|комiсiя)(?:\s+|\s*\.\s*|\s*\:\s*)([\d\.,]+)\s*грнc                 X    d | _         d | _        d | _        d | _        d | _        d | _        y N)our_company_nameour_company_innour_company_accountour_bank_nameour_bank_edrpouour_bank_branch)selfs    2/var/www/html/app/parsers/privatbank_pdf_parser.py__init__zPrivatBankPdfParser.__init__    s2    /3.226 ,0.2.2    	file_pathreturnc                    g }t        j                  d|        t        j                  |      5 }|j                  r3t        j                  d       | j                  |j                  d          g }t        |j                  d      D ]`  \  }}|j                         }|s|D ]C  }|D ]<  }	|	D 
cg c]  }
|
|
nd
 }	}
|	st        d |	D              s,|j                  |	       > E b t        j                  dt        |              t        |d      D ])  \  }}t        j                  d	| d
t        |              + g }|D ]  }	|	dgdt        |	      z
  z  z   }	| j                  |	      r*| j                  |	      r<| j                  |	      rNdj                  d |	D              j!                         }d|v r!d|v rd|v rt        j                  d|	        |j                  |	        t        j                  dt        |              | j#                  |      }t        j                  dt        |              t        |d      D ])  \  }}t        j                  d| d
t        |              + g }|D ]^  }|d   xs dj%                         }|d   xs dj%                         }|d   xs dj'                  dd      j'                  dd      }|d   xs dj%                         }| j)                  |      }| j+                  |      }	 t-        |      }|d   xs dj1                         }|d   xs dj1                         }dj                  ||z         j%                         }t3        j4                  dd|      }t        j                  d| d
t        |              | j7                  |      }|s| j8                  r| j8                  }nd}| j;                  |      }| j=                  |||      }t        j                  d| d | d!| d"|        | j?                  |||||||#      } |dk  r|| _         || _!        n|| _"        || _#        |j                  |        |dkD  s| jI                  |      }!|!rt        j                  d$| d
|!        t        |!d      D ]1  \  }"}#| d%|" }$| jK                  |$||#|      }%|j                  |%       3 a tM               }&|D ]  }'|'jN                  |'jP                  |'jR                  f}(|(|&vr#|&jU                  |(       |j                  |'       Mt        j                  d&|'jN                   d |'jP                   d!|'jR                           	 d d d        t        j                  d't        |              |S c c}
w # t.        $ r d}Y w xY w# 1 sw Y   CxY w)(Nu*   Начало парсинга файла: uM   Извлекаем данные шапки из первой страницыr      )start c              3   <   K   | ]  }|j                           y wr   strip.0cells     r   	<genexpr>z,PrivatBankPdfParser.parse.<locals>.<genexpr><   s     &Dtzz|&Ds   uI   Всего строк после извлечения из таблиц: zRAW row z:     c              3   B   K   | ]  }|s|j                           y wr   r   )r"   cs     r   r$   z,PrivatBankPdfParser.parse.<locals>.<genexpr>O   s     &CQqwwy&Cs   u   найменуванняu   рахунокu
   єдрпоuD   Пропуск строки с заголовком колонок: u7   Всего строк после фильтрации: uF   Всего строк после мерджа разорванных: zMERGED row    ,.   g              \s+zContragent for doc 1u3   Обрабатываем транзакцию: doc=z, date=z	, amount=z
, details=)numberop_dateamountpayment_detailscontragent_namecontragent_inncontragent_accountu8   Найдены комиссии в транзакции _COMuZ   Дублирующаяся транзакция найдена и пропущена: doc=uW   Парсинг завершён, всего уникальных транзакций: )+loggingdebug
pdfplumberopenpages_extract_our_company_data	enumerateextract_tablesanyappendlenrepr_is_summary_line_looks_like_header_is_balance_linejoinlower_merge_continuation_rowsr    replace_to_single_line_parse_datefloat
ValueError
splitlinesresub	_find_innr   _find_account_clean_name_build_transactionrecipient_innrecipient_account	payer_innpayer_account_extract_commissions_build_commission_transactionsetr1   dater3   add))r   r   transactionspdfall_rows
page_indexpage
raw_tablestblrowr#   iraw_rowfiltered_rowstext_joinedmerged_rows
merged_rowtemp_transactionsrow_data
doc_numberdate_str
amount_strdetails_multiliner4   r2   r3   part1part2contragent_fullr6   r7   r5   main_txcommissionsidxcomm_valcomm_doc_numcomm_txseentxkeys)                                            r   parsezPrivatBankPdfParser.parse(   s,   *,B9+NO__Y' D	3yymn..syy|< H$-ciiq$A 	1 
D!002
!% 1C" 1NQRd(8b @RR3&D&D#D$OOC0	11		1 MMefijrfsetuv (: ?
72d7m_=>? M *RDACL11((-**3/((-!hh&C#&CCIIK.+=BRVaBafr  wB  gBMM$hilhm"no$$S)* MMSTWXeTfSghi 77FKMMbcfgrcsbtuv!*;a!@ E:A3bj1A0BCDE !#' C:&qk/R668
$QK-2446&qk/R88cBJJ3PRS
%-a[%6B$=$=$?!"&"6"67H"I**84!":.F
 "!*668!!*668"%((55="9"?"?"A"$&&o"F 3J<r$BWAXYZ "&!@%++)-)=)=),%)%7%7%H""&"2"2?NTf"gI*U\]e\f g$XZ/@B
 11%#!$3$3#1'9 2  A:,:G)0BG-(6G%,>G)!((1 A:"&";";O"LK"(`ak`llnozn{&|})2;a)H :X*4T#'?"&"D"D('8_# *009:}C:L 5D' 	yy"''2995d?HHSM ''+MM!yyk	299+O	wD	L 	ops  uA  qB  pC  D  	Eq S^ " ! F!}D	 D	sW   B W0V+=WWIWV0&EW6D	W+W0V?;W>V??WWrg   c                 R    |d   xs dj                         }|j                  d      S )u   Строка вида «За 01.03.2025...». Если начинается с 'За ', считаем дневным итогом.r   r   u   За )r    
startswith)r   rg   	first_cols      r   rE   z$PrivatBankPdfParser._is_summary_line   s)    V\r((*	##G,,r   c                 b    dj                  d |D              }t        j                  d|      ryy)uZ   Повторные заголовки: «Номер документа / Дата / ...».r&   c              3   ^   K   | ]%  }|s|j                         j                          ' y wr   r    rI   r!   s     r   r$   z9PrivatBankPdfParser._looks_like_header.<locals>.<genexpr>   s!     JdTTZZ\//1J   -#-u   (номер\s*документ|дата\s*та\s*час|призначення\s*платежу|реквізити\s*контрагента)TF)rH   rQ   search)r   rg   	full_texts      r   rF   z&PrivatBankPdfParser._looks_like_header   s1    HHJcJJ	99  e r   c                 F    dj                  d |D              }d|v xs d|v S )u`   Строки с «вхідний залишок», «вихідний залишок» и т.п.r&   c              3   ^   K   | ]%  }|s|j                         j                          ' y wr   r   r!   s     r   r$   z7PrivatBankPdfParser._is_balance_line.<locals>.<genexpr>   s!     ITDDJJL..0Ir   u   вхідний залишокu   вихідний залишок)rH   )r   rg   row_texts      r   rG   z$PrivatBankPdfParser._is_balance_line   s-    88ISII/8;oAbfnAnor   rowsc                 j   g }d}|D ]  }|dgdt        |      z
  z  z   }|d   xs dj                         }|r|r|j                  |       |}G|rL|d   xs ddz   |d   xs dz   |d<   |d   xs ddz   |d   xs dz   |d<   |d   xs ddz   |d   xs dz   |d<   |} |r|j                  |       |S )	u   
        Если первая ячейка (doc_number) пуста, считаем, что это продолжение предыдущей.
        Тогда доклеиваем текст в колонки [3,5,6].
        Nr   r%   r   r,   
r-   r.   )rC   r    rB   )r   r   mergedcurrentrg   rp   s         r   rJ   z,PrivatBankPdfParser._merge_continuation_rows   s    
  	"CCH--Ca&,B--/JMM'* ")!*"2d!:c!fl!KGAJ")!*"2d!:c!fl!KGAJ")!*"2d!:c!fl!KGAJ!G	"" MM'"r   detailsc                     g }| j                   j                  |      D ]@  }|j                  d      j                  dd      }	 t	        |      }|j                  |       B |S # t        $ r Y Pw xY w)ug   Находим все фразы «ком бан 25.99 грн», «комiсiя 5.00 грн» и т.д.r   r*   r+   )RE_COMMISSIONfinditergrouprK   rN   rB   rO   )r   r   resultsmval_strvals         r   r[   z(PrivatBankPdfParser._extract_commissions   sv    ##,,W5 	Aggaj((c2GGns#		   s   A##	A/.A/rp   r2   
comm_valueoriginal_detailsc                     | j                   xs d}| j                  xs d}t        ||r|j                         ndt	        |       d||dddd| dd|r|j                         	      S d	      S )
ut   
        Создаём минусовую (expense) транзакцию на комиссию банка.
        OUR_COMPANYr   Nr0   14360570u   ПРИВАТБАНКu4   Комиссия банка (из операции: )r1   r^   r3   rY   
payer_namerZ   rW   recipient_namerX   r4   date_incomedate_outcome)r   r   r   r^   abs)r   rp   r2   r   r   our_nameour_accounts          r   r\   z1PrivatBankPdfParser._build_commission_transaction   s     ((9M..4"#*
O#%$1 RScRddef+2
 	
 9=
 	
r   Nc                    |j                         xs d}t        j                  d       |j                  d      D ]K  }|j	                         }t        j                  d|      }|r[|j                  d      | _        |j                  d      | _	        t        j                  d| j                   d| j                          t        j                  d	|      }|r[|j                  d      | _
        |j                  d      | _        t        j                  d
| j                   d| j                          t        j                  d|      }|s|j                  d      | _        t        j                  d| j                          N y )Nr   u.   Извлекаем шапку компанииr   u0   (АТ\s+КБ\s+"[^"]+"),?\s*ЄДРПОУ\s+(\d+)r   r)   u   Найден банк: u   , ЄДРПОУ: u,   Клієнт\s+(.+?),\s+ЄДРПОУ\s+(\d+)u   Найден клиент: u
   , ІПН: u*   Поточний рахунок\s+№(\w+)u(   Найден счёт компании: )extract_textr9   r:   splitr    rQ   r   r   r   r   r   r   r   )r   rd   textlineline_stripped
match_bankmatch_companymatch_accounts           r   r>   z-PrivatBankPdfParser._extract_our_company_data  sT     "(bFGJJt$ 	eD JJLM#VXefJ%/%5%5a%8"'1'7'7':$ 78J8J7KK[\`\p\p[qrsII&UWdeM(5(;(;A(>%'4':':1'=$ ;D<Q<Q;RR\]a]q]q\rstII&SUbcM+8+>+>q+A( HIaIaHbcd%	er   r   c                 F    |sydj                  |j                               S )uw   Склеиваем многострочный text в одну строку с одиночными пробелами.r   r&   )rH   r   )r   r   s     r   rL   z#PrivatBankPdfParser._to_single_line4  s    xx

%%r   rq   c                     |j                  dd      }dD ](  }	 t        j                  |j                         |      c S  t        j                  d|        t        j                         S # t        $ r Y bw xY w)uV   Пробуем распарсить дату в нескольких форматах.r   r&   )z%d.%m.%Y %H:%Mz%d.%m.%Yz%d/%m/%Y %H:%Mu3   Не удалось распарсить дату: )rK   r   strptimer    rO   r9   r:   now)r   rq   fmts      r   rM   zPrivatBankPdfParser._parse_date:  sx    ##D#.C 	C(()93??	
 	KH:VW||~  s   #A,,	A87A8c                     t        j                  d|      }|r|j                  d      S t        j                  d|      }|r|j                  d      S y)u   
        Ищем ЄДРПОУ: либо шаблон «ЄДРПОУ: 12345678»,
        либо любую группу подряд идущих 8..10 цифр.
        u   ЄДРПОУ:\s*(\d+)r   z\b\d{8,10}\br   r   )rQ   r   r   r   r   matchmatch2s       r   rS   zPrivatBankPdfParser._find_innE  sJ    
 		2D9;;q>!?D1<<?"r   c                     t        j                  d|      }|r!|j                  d      j                  dd      S t        j                  d|      }|r|j                  d      S y)uM   Ищем «IBAN»: строку, начинающуюся с UA + цифры.u   Рахунок:\s*(UA[\w\d]+)r   r&   r   z\b(UA\d{2,})\b)rQ   r   r   rK   r   s       r   rT   z!PrivatBankPdfParser._find_accountS  sV    		:DA;;q>))#r22,d3<<?"r   innaccountc                     t        j                  dd|      }|r|j                  |d      }t        j                  dd|      }|r|j                  |d      }t        j                  dd|      j                         }|S )u   Убираем из строки всё, что относится к ЄДРПОУ/Рахунок, чтобы получить «чистое название».u   ЄДРПОУ:\s*\d+r   u   Рахунок:\s*UA[\w\d]+r/   r&   )rQ   rR   rK   r    )r   r   r   r   cleaneds        r   rU   zPrivatBankPdfParser._clean_name^  sn    &&/T:ooc2.G&&7WEoogr2G&&g.446r   r1   r3   r4   r5   r6   r7   c                 D   | j                   xs d}| j                  xs d}	|dk  r#d}
|}|	}|}|}|}|r|j                         nd}d}n"|}
|}|}d}|}|	}d}|r|j                         nd}t        ||r|j                         nd||
||||||j	                         ||      S )u   
        Создаём обычную транзакцию (приход или расход).
        Если amount < 0, это расход (мы платим).
        Если amount >= 0, это приход (нам платят).
        r   r   r   r0   Nr   )r   r   r^   r   r    )r   r1   r2   r3   r4   r5   r6   r7   r   r   rY   r   rZ   rW   r   rX   r   r   s                     r   rV   z&PrivatBankPdfParser._build_transactionj  s     ((9M..4"A:I!J'M*M,N 2-47<<>$LK 'I(J.MM%N +L,3',,.K#*!''*0+113#%
 	
r   )r   N)__name__
__module____qualname____doc__rQ   compiler   r   strr   r   r   boolrE   rF   rG   rJ   rN   r[   r   r\   r>   rL   rM   rS   rT   rU   rV    r   r   r   r      s    BJJhM3Ks KtK'8 K`-DI -$ -
d3i D pDI p$ pT$s)_ d3i B
C 
DK 


 
 	

 
 

>e6&C &C &	C 	H 	c c 	# 	# 	
 
# 
 
 
4
4
 4
 	4

 4
 4
 4
  4
 
4
r   r   )r;   rQ   r9   typingr   r   r   app.parsers.base_parserr   app.models.transactionr   basicConfigDEBUGr   r   r   r   <module>r      sB     	  !  ; .   '--0X YP
1 P
r   