
    ]i3                       d Z ddlmZ ddlmZ ddlmZ ddlmZm	Z	 ddl
mZ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mZ dd
lmZmZ ddlmZmZmZ ddlm Z  ddl!m"Z"m#Z#m$Z$ ddZ%ddZ&ddZ'ddZ(	 	 	 	 	 	 	 	 	 	 ddZ)	 	 	 	 	 	 	 	 	 	 	 	 d dZ*	 	 	 	 	 	 	 	 	 	 d!dZ+	 	 	 	 	 	 	 	 	 	 d!dZ,	 	 	 	 	 d"	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d#dZ-	 	 	 	 	 	 	 	 d$dZ.	 	 	 	 	 	 	 	 	 	 d%dZ/	 	 	 	 	 	 	 	 	 	 d%dZ0y)&u  Rekonsiliasi Harian service — daily sounding-based reconciliation logic.

Formula per tangki:
  Stok Awal       = volume_final dari sounding shift pertama hari ini
  + Penerimaan    = BBM diterima hari itu untuk tangki ini
  + Pemindahan In
  - Penjualan     = Volume dari nozzle bersumber tangki ini
  - Pemindahan Out
  = Stok Teoritis

  Stok Aktual     = volume_final dari sounding shift terakhir hari ini
  Losses / Gain   = Stok Aktual - Stok Teoritis

Prerequisite: all shifts for the day must be at least SUBMITTED.
    )annotations)date)Decimal)funcselect)IntegrityErrorSQLAlchemyError)AsyncSession)LaporanShiftPenjualanNozzleStatusLaporanStockAdjustmentStockAdjustmentItem)
PenerimaanPenerimaanItem)RekonsiliasiHarianStatusRekonsiliasi)NozzleShiftTangki)rekonsiliasi_repository)RekonsiliasiHarianResponseRekonsiliasiListResponseRekonsiliasiTangkiResponsec                \   g }| j                   D ]  }|j                  t        |j                  |j                  |j
                  r|j
                  j                  nd |j
                  rLt        |j
                  d      r6|j
                  j                  r |j
                  j                  j                  nd |j                  |j                  |j                  |j                  |j                  |j                  |j                  |j                                
 t#        | j                  | j$                  | j&                  | j(                  | j*                  r| j*                  j,                  nd | j.                  r| j.                  j,                  nd || j0                  | j2                  	      S )Nproduk)id	tangki_idtangki_namaproduk_nama	stok_awal
penerimaanpemindahan_inpemindahan_out	penjualanstok_teoritisstok_aktuallosses)	r   spbu_idtanggalstatusrun_by_nameapproved_by_nameitems
created_at
updated_at)r.   appendr   r   r   tangkinamahasattrr   r!   r"   r#   r$   r%   r&   r'   r(   r   r)   r*   r+   run_bynameapproved_byr/   r0   )rr.   items      C/var/www/html/spbu.com/backend/app/services/rekonsiliasi_service.py_build_detail_responser;   1   s*   E/wwnn,0KK((T ;;74;;#AdkkFXFX ""''nn,,..nn,,((;;!
 	 & &44				xx%&XXAHHMM4/0}}++$<<<<
 
    c                   t        d | j                  D        t        d            }t        | j                  | j
                  | j                  | j                  t        | j                        || j                  r| j                  j                  nd | j                  r| j                  j                  nd | j                  | j                  
      S )Nc              3  4   K   | ]  }|j                     y w)N)r(   ).0r9   s     r:   	<genexpr>z'_build_list_response.<locals>.<genexpr>T   s     8s   0)
r   r)   r*   r+   jumlah_tangkitotal_lossesr,   r-   r/   r0   )sumr.   r   r   r   r)   r*   r+   lenr5   r6   r7   r/   r0   )r8   rC   s     r:   _build_list_responserF   S   s    88'#,GL#44				xx!''l!%&XXAHHMM4/0}}++$<<<< r<   c           	       K   | j                  t        t              j                  t        j                  |k(  t        j
                  j                  d      t        j                  j                  d            j                  t        j                               d{   }t        |j                         j                               S 7 +w)z4Get active shifts for an SPBU, ordered by jam_mulai.TN)executer   r   wherer)   	is_activeis_
deleted_atorder_by	jam_mulailistscalarsalldbr)   results      r:   _get_active_shiftsrU   c   s     ::u	u}}')<)<T)BEDTDTDXDXY]D^	_	%//	" F
  $$&''s   BCC	,Cc           	     `  K   | j                  t        t              j                  t        j                  |k(  t        j
                  j                  d      t        j                  j                  d                   d{   }t        |j                         j                               S 7 +w)zGet active tanks for an SPBU.TN)rH   r   r   rI   r)   rJ   rK   rL   rO   rP   rQ   rR   s      r:   _get_active_tangkirW   m   s|     ::v	v~~(&*:*:*>*>t*DfFWFWF[F[\`Fa	b F  $$&''	s   A>B. B,,B.c           	       K   g }t         j                  t         j                  t         j                  h}|D ]D  }| j	                  t        t        j                        j                  t        j                  |k(  t        j                  |j                  k(  t        j                  |k(               d{   }|j                         }| j	                  t        t        j                        j                  t        j                  |k(  t        j                  |j                  k(  t        j                  |k(               d{   }	|	j                         }
||vs|
|vs*|j                  |j                          G |S 7 7 @w)zBReturn list of shift names that are NOT yet submitted (or higher).N)r   	SUBMITTEDAPPROVEDLOCKEDrH   r   r   r+   rI   r)   shift_idr   r*   scalar_one_or_noner   r1   r3   )rS   r)   r*   shiftsnot_submittedacceptedshift	sa_result	sa_status	ls_result	ls_statuss              r:   _check_all_shifts_submittedrf   v   s?     M'')?)?AUAUVH**?))*00''72((EHH4''72
 
	 002	 **<&&'--$$/%%1$$/
 
	 002	H$	(A  ,- 0 -

s+   B1F3F 4BFFF !FFc           	       K   | j                  t        t        j                        j	                  t
        t        j                  t
        j                  k(        j                  t
        j                  |k(  t
        j                  |k(  t
        j                  |k(  t        j                  |k(               d{   }|j                         S 7 w)zCGet volume_final_liter for a specific tank from a stock adjustment.N)rH   r   r   volume_final_literjoinr   stock_adjustment_idr   rI   r)   r\   r*   r   r]   )rS   r)   r\   r*   r   rT   s         r:   _get_sounding_volumerk      s      ::"556	o2FF/J\J\\	]	##w.$$0##w.))Y6	

	 	F $$&&	s   B5C7C8Cc           
       K   | j                  t        t        j                  t        j                  t
        j                        t        d                  j                  t        t
        j                  t        j                  k(        j                  t        j                  |k(  t        j                  |k(  t
        j                  |k(               d{   }|j!                         xs t        d      S 7 !w)z.Sum of BBM received for a tank on a given day.rA   N)rH   r   r   coalescerD   r   volume_diterimar   ri   r   penerimaan_idr   rI   r)   r*   r   
scalar_onerS   r)   r*   r   rT   s        r:   _get_penerimaan_volumerr      s      ::t}}TXXn&D&DEws|TU	j.66*--G	H	')')$$	1

 F .'#,.s   CC9C7"C9c           
     \  K   | j                  t        t        j                  t        j                  t
        j                        t        d                  j                  t        t
        j                  t        j                  k(        j                  t        t
        j                  t        j                  k(        j                  t        j                  |k(  t        j                   |k(  t        j"                  |k(               d{   }|j%                         xs t        d      S 7 !w)zDSum of sales volume from nozzles sourced from a tank on a given day.rA   N)rH   r   r   rm   rD   r   volumer   ri   r   laporan_shift_idr   r   	nozzle_idrI   r)   r*   r   rp   rq   s        r:   _get_penjualan_volumerw      s      ::t}}TXXo&<&<=ws|LM	lO<<O	P	fo//699<	=	  G+  G+	)

		 	F .'#,.	s   DD,D*	"D,Nc           	        K   t        j                  | ||||||       d {   \  }}|D 	cg c]  }	t        |	       c}	|fS 7 "c c}	w w)N)tanggal_mulaitanggal_akhirr+   skiplimit)r   get_allrF   )
rS   r)   ry   rz   r+   r{   r|   rowstotalr8   s
             r:   list_rekonsiliasir      sa      077
G## KD% .22T #T2E99 3s     AAAA AAc                h   K   t        j                  | ||       d{   }|yt        |      S 7 w)z?Get reconciliation for a specific date, or None if not yet run.N)r   get_by_tanggalr;   )rS   r)   r*   rekons       r:   get_rekonsiliasir      s8      *88WgNNE}!%(( Os   202c                |  K   t        | |       d{   }|st        d      t        | |||       d{   }|rt        ddj                  |       d      t	        | |       d{   }|st        d      |d   }|d   }g }	|D ]1  }
t        | ||j                  ||
j                         d{   }|t        d	      }t        | ||j                  ||
j                         d{   }|t        d	      }t        | |||
j                         d{   }t        | |||
j                         d{   }t        d	      }t        d	      }||z   |z   |z
  |z
  }||z
  }|	j                  |
j                  ||||||||d
	       ddlm}  || ||
j                  ||||t        |             d{    4 	 t        j                  | ||       d{   }|Pt        j                   | |t"        j$                  |d       d{    t        j&                  | ||	       d{   }nPt        j(                  | ||t"        j$                  |d       d{   }t        j&                  | ||	       d{   }| j+                          d{    t        j                  | ||       d{   }t3        |      S 7 7 7 _7 7 7 7 7 !7 7 7 7 7 g7 Q7 4# t,        $ r% | j/                          d{  7   t        d      t0        $ r | j/                          d{  7    w xY ww)a  Run (or re-run) daily reconciliation for a given date.

    Steps:
    1. Validate all shifts have submitted data
    2. For each active tank, calculate:
       stok_awal, penerimaan, penjualan, stok_teoritis, stok_aktual, losses
    3. Create or update RekonsiliasiHarian + items
    Nz#SPBU ini tidak memiliki shift aktifz$Shift berikut belum submit laporan: z, zI. Semua shift harus sudah disubmit sebelum bisa menjalankan rekonsiliasi.z$SPBU ini tidak memiliki tangki aktifr   rA   )	r   r!   r"   r#   r$   r%   r&   r'   r(   )run_rekonsiliasi_checks)r+   	run_by_id)r)   r*   r+   r   z,Rekonsiliasi sudah ada atau constraint error)rU   
ValueErrorrf   ri   rW   rk   r   r   rr   rw   r1   app.utils.anomalir   strr   r   update_statusr   PENDINGupsertcreatecommitr   rollbackr	   r;   )rS   r)   r*   user_idr^   r_   tangki_listfirst_shift
last_shift
items_datar2   r!   r'   r"   r%   r#   r$   r&   r(   r   existingr   s                         r:   run_rekonsiliasir      sZ     &b'22F>??5b'7FSSM2499]3K2L MV V
 	
 +2w77K?@@)KJ J.&))
 
	 I
 1
 
 !#,K1"gw		RR
/GWfiiPP	   !J.>J^[},"$*,"*&

 
	 	>%M;z3w<
 	
 	
S ^0??GWUU)77H);)C)CRYZ   288XzRRE188"",44$	>  E 288UJOOEiik-<<R'RR "%(({ 3 T 8

 SP.	
 V S PR IkkmGHH kkms;  L<K"L<K3L<)K*A	L<3K47L<+K,,L<KL<8K9A=L<6K7L<?K) K1K) KK) *K+1K) K!K) ;K#<K) K%K) 2K'3K) 7L<L<L<L<L<L<L<L<K) K) K) !K) #K) %K) 'K) )L9L+L91L42L99L<c                  K   t        j                  | ||       d{   }|t        d      |j                  t        j
                  k7  rt        d|j                   d      	 t        j                  | |t        j                  |d       d{   }| j                          d{    t        j                  | ||       d{   }t        |      S 7 7 H7 27 # t        $ r | j                          d{  7    w xY ww)z!Approve a pending reconciliation.Nz/Rekonsiliasi belum dijalankan untuk tanggal inizRekonsiliasi status 'z+', hanya bisa approve dari status 'pending')r+   approved_by_id)r   r   r   r+   r   r   r   rZ   r   r	   r   r;   )rS   r)   r*   r   r   updateds         r:   approve_rekonsiliasir   Y  s      *88WgNNE}JKK||)111#ELL>1\]
 	
/=="4"="=QXY
 
 iik/>>r7GTT
 "'**% O
 	T kkmsj   DCAD&+C  CC  )C*C  C	C  DC  C  C   D<C?=DD)r8   r   returnr   )r8   r   r   r   )rS   r
   r)   intr   list[Shift])rS   r
   r)   r   r   zlist[Tangki])
rS   r
   r)   r   r*   r   r^   r   r   z	list[str])rS   r
   r)   r   r\   r   r*   r   r   r   r   zDecimal | None)
rS   r
   r)   r   r*   r   r   r   r   r   )NNNr   2   )rS   r
   r)   r   ry   date | Nonerz   r   r+   z
str | Noner{   r   r|   r   r   z*tuple[list[RekonsiliasiListResponse], int])rS   r
   r)   r   r*   r   r   z!RekonsiliasiHarianResponse | None)
rS   r
   r)   r   r*   r   r   r   r   r   )1__doc__
__future__r   datetimer   decimalr   
sqlalchemyr   r   sqlalchemy.excr   r	   sqlalchemy.ext.asyncior
   app.models.operationalr   r   r   r   r   app.models.penerimaanr   r   app.models.rekonsiliasir   r   app.models.spbur   r   r   app.repositoriesr   app.schemas.rekonsiliasir   r   r   r;   rF   rU   rW   rf   rk   rr   rw   r   r   r   r    r<   r:   <module>r      s    #   # : /  = J 1 1 4 D (("-1;FD''"'.1'<@'MP''"//"/-1/>A// //"/-1/>A//2 "&!%::: : 	:
 : : : 0:())")-1)&)i)i)"i)-1i)<?i)i)X++"+-1+<?++r<   