
    koiT                        U d Z ddlZddlZddlZddlmZ ddlmZ ddlmZm	Z	m
Z
mZ ddlmZ  ej                  e      Zg dZg dgdd	gg d
g dg d
g ddZe	eee   f   ed<   ddddddddZe	eef   ed<    ee      j.                  j.                  dz  dz  dz  Z ee      j.                  j.                  dz  dz  dz  ZdededefdZdedededefdZd1d ed!ede
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f
d&Z!defd'Z"ded(eded edef
d)Z#d#ededed ede	eef   f
d*Z$	 d2d#ede
e   defd+Z%d2d#ede
e   defd,Z&d#edefd-Z'	 d2d(ed.ed/e
e   fd0Z(y)3u  
CSV Task Orchestrator for Smart Inventory
==========================================

This module provides utilities to manage CSV processing Celery tasks with:
- Dependency ordering between CSV types (product -> purchase_order -> purchase_receive -> etc.)
- FIFO execution for same CSV type per company
- HOLD status for tasks waiting on dependencies
- Polling for HOLD tasks to start when dependencies complete

CSV Dependency Chain:
    product → [] (no dependencies)
    purchase_order → [product]
    purchase_receive → [product, purchase_order]
    sales_order → [product, purchase_order, purchase_receive]
    sales_return → [product, purchase_order, purchase_receive, sales_order]
    purchase_return → [product, purchase_order, purchase_receive]
    stock_transfer → [product, purchase_order, purchase_receive, sales_order, sales_return, purchase_return]

Usage:
    from src.smart_inventory.utils.csv_task_orchestrator import trigger_csv_processing_task
    
    # After saving CSV file
    task_id = trigger_csv_processing_task(
        db=db,
        company_id=1,
        upload_type="sales_order",
        file_path="/path/to/sales_order_1_2026-01-20_103000.csv"
    )
    N)datetime)Path)ListDictOptionalAny)Session)productpurchase_orderpurchase_receivesales_ordersales_returnpurchase_returnstock_transferr
   r   )r
   r   r   )r
   r   r   r   )r
   r   r   r   r   r   CSV_TASK_DEPENDENCIESzAsrc.smart_inventory.tasks.csv_processing_task.process_product_csvzHsrc.smart_inventory.tasks.csv_processing_task.process_purchase_order_csvzJsrc.smart_inventory.tasks.csv_processing_task.process_purchase_receive_csvzEsrc.smart_inventory.tasks.csv_processing_task.process_sales_order_csvzFsrc.smart_inventory.tasks.csv_processing_task.process_sales_return_csvzIsrc.smart_inventory.tasks.csv_processing_task.process_purchase_return_csvzHsrc.smart_inventory.tasks.csv_processing_task.process_stock_transfer_csvCSV_TASK_NAME_MAPPINGappsdata_importuploaded_filesarchived_filesupload_type
company_idreturnc                 ^    t        j                         j                  d      }|  d| d| dS )z
    Generate a unique filename for the CSV file.
    Format: {upload_type}_{company_id}_{YYYY-MM-DD_HHmmss}.csv
    
    Args:
        upload_type: Type of CSV upload
        company_id: Company ID
        
    Returns:
        Filename string
    z%Y-%m-%d_%H%M%S_z.csv)r   nowstrftime)r   r   	timestamps      N/var/www/html/hubwallet-dev/src/smart_inventory/utils/csv_task_orchestrator.pyget_csv_filenamer    Z   s4     ''(9:I]!J<q488    file_contentc                    t         j                  dd       t        ||      }t         |z  }t        |d      5 }|j	                  |        ddd       t
        j                  d|        t        |      S # 1 sw Y   ,xY w)z
    Save uploaded CSV file to the uploads directory.
    
    Args:
        file_content: Raw file content bytes
        upload_type: Type of CSV upload
        company_id: Company ID
        
    Returns:
        Absolute path to the saved file
    Tparentsexist_okwbNzSaved CSV file: )
UPLOAD_DIRmkdirr    openwriteloggerinfostr)r"   r   r   filename	file_pathfs         r   save_uploaded_filer2   j   sz     TD1Z8HX%I	i	 !	 KK"9+./y>	 s   A66A?r0   failedc                    	 t        |       }|j                         st        j                  d|         yt        j                  dd       |r|j                  dz   |j                  z   }n|j                  }t        |z  }d}|j                         r\|r|j                   d| |j                   }n|j                   d| |j                   }t        |z  }|dz  }|j                         r\|j                  |       t        j                  d	|        t        |      S # t        $ r%}t        j                  d
|  d|        Y d}~yd}~ww xY w)z
    Move processed CSV file to the archive directory.
    
    Args:
        file_path: Path to the file to archive
        failed: If True, append '_failed' suffix to filename
        
    Returns:
        Path to archived file, or None if failed
    zFile not found for archiving: NTr$   _failed   _failed_r   zArchived CSV file: zFailed to archive file : )r   existsr,   warningARCHIVE_DIRr)   stemsuffixnamerenamer-   r.   	Exceptionerror)r0   r3   source	dest_namedestcounteres          r   archive_filerG      s6   !i}}NN;I;GH 	$6 i/&--?IIY& kkm%{{m8G9V]]OL	%{{m1WIfmm_E	*DqLG kkm 	d)$014y .ykA3?@s#   3D# B8D# /3D# #	E,EEdbc                    ddl m}m} t        j	                  |g       }|sy|j
                  |j                  |j                  g}|D ]  }| j                  |      j                  |j                  |k(  |j                  |k(  |j                  j                  |            j                         }|sit        j!                  d| d| d|j"                   d|j                  j$                   d	        y	 y)
a  
    Check if all dependencies for a CSV task have completed successfully.
    
    A CSV task can only start when ALL tasks of dependent types for the same
    company are complete (SUCCESS status). This includes tasks from any batch,
    not just the current batch.
    
    Args:
        db: Database session
        company_id: Company ID
        upload_type: The CSV upload type to check dependencies for
        
    Returns:
        True if all dependencies are met (no pending/started/hold tasks of dependent types)
    r   CeleryTaskTrackerCeleryTaskStatusTCSV z waiting for z (task: z
, status: )F))src.smart_inventory.apps.inventory.modelsrK   rL   r   getPENDINGSTARTEDHOLDqueryfilterr   r   statusin_firstr,   debug	task_namevalue)	rH   r   r   rK   rL   dependenciespending_statusesdep_typepending_dep_taskss	            r   check_csv_dependencies_metr`      s   ( ^(,,["=L 	     ! HH%67>>((J6))X5$$(()9:
 %'	 	 LL{m=
 ;+556jARAYAYA_A_@``ac  r!   current_task_idc           	      @   ddl m}m} |j                  |j                  |j
                  g}| j                  |      j                  |j                  |k(        j                         }|sy| j                  |      j                  |j                  |k(  |j                  |k(  |j                  j                  |      |j                  |j                  k  |j                  |k7        j                         }|r*t        j!                  d| d| d|j                   d       yy)	a  
    Check if this task can run based on FIFO ordering for same upload type.
    
    A CSV task can only start when no earlier tasks of the SAME type for the
    same company are still pending/started/hold.
    
    Args:
        db: Database session
        company_id: Company ID
        upload_type: The CSV upload type
        current_task_id: The current task's ID
        
    Returns:
        True if no earlier same-type tasks are pending
    r   rJ   TrM   z task z waiting for earlier task z (FIFO)F)rO   rK   rL   rQ   rR   rS   rT   rU   task_idrX   r   r   rV   rW   
created_atr,   rY   )	rH   r   r   ra   rK   rL   r]   current_taskearlier_taskss	            r   check_same_type_fiforg      s!   * ^ 	     88-.55!!_4eg   HH./66$$
2%%4  $$%56$$|'>'>>!!_4 eg  ;-vo%66P$$%W.	
 r!   c                     	 | dk(  rddl m} |S | dk(  rddl m} |S | dk(  rddl m} |S | dk(  rdd	l m} |S | d
k(  rddl m} |S | dk(  rddl m} |S | dk(  rddl m} |S 	 y# t        $ r%}t        j                  d|  d|        Y d}~yd}~ww xY w)z@Get the actual Celery task function by upload type (for Windows)r
   r   )process_product_csvr   )process_purchase_order_csvr   )process_purchase_receive_csvr   )process_sales_order_csvr   )process_sales_return_csvr   )process_purchase_return_csvr   )process_stock_transfer_csvzCould not import CSV task for r8   N)-src.smart_inventory.tasks.csv_processing_taskri   rj   rk   rl   rm   rn   ro   ImportErrorr,   r:   )	r   ri   rj   rk   rl   rm   rn   ro   rF   s	            r   _get_csv_celery_taskrr   %  s    )#Y&&,,`--..b//M)]**N*^++--a..,,`-- -   7}BqcJKs7   A A A A A A A 	B(BBrc   c                    	 ||d}t         j                  dk(  r=t        |       }|r/|j                  ||       t        j                  d|  d|        yy
ddlm} t        j                  |       }|r0|j                  |||       t        j                  d|  d	|        yy
# t        $ r%}t        j                  d|  d|        Y d}~y
d}~ww xY w)a  
    Dispatch a CSV processing Celery task with platform-specific handling.
    
    Windows (solo pool): Uses direct task import and apply_async
    Linux (prefork pool): Uses send_task with task name string
    
    Args:
        upload_type: The CSV upload type
        task_id: Task ID to use
        company_id: Company ID
        file_path: Path to the CSV file
        
    Returns:
        True if task was dispatched successfully
    )r   r0   win32)kwargsrc   zDispatched CSV task z (Windows): Tr   
celery_appz
 (Linux): FzFailed to dispatch CSV task r8   N)sysplatformrr   apply_asyncr,   r-   src.utils.celery_workerrw   r   rP   	send_taskr@   rA   )	r   rc   r   r0   task_kwargscelery_taskrw   celery_namerF   s	            r   _dispatch_csv_celery_taskr   C  s    *$"

 <<7".{;K''{G'L2;-|G9UV  ;/33K@K$$[g$V2;-z'ST 3K=1#FGs   AB% AB% %	C.CCc                    ddl m}m} t        t	        j
                               }t        j                         j                  d      }d| d| d| }t        | ||      }	|	r|j                  n|j                  }
 |||||
||d|	rt        j                         nd      }| j                  |       | j                          t        j                  d	| d
| d|
j                           |	rQt#        ||||      rt        j                  d| d       nN|j$                  |_        d|_        | j                          n%t        j                  d| d       t+        | |       |||
j                   |dS )aV  
    Create a CSV processing task and trigger it if dependencies are met.
    
    This is the main entry point for triggering CSV processing. Call this after
    saving the uploaded CSV file.
    
    Args:
        db: Database session
        company_id: Company ID
        upload_type: Type of CSV (product, purchase_order, etc.)
        file_path: Path to the saved CSV file
        
    Returns:
        Dict with task information:
        {
            "task_id": str,
            "task_name": str,
            "status": str,  # "pending" or "hold"
            "file_path": str
        }
    r   rJ   z%Y%m%d_%H%M%Scsv_r   
csv_importN)rc   rZ   r   rV   r0   r   type
started_atzCreated CSV task: z (z) - status: z	CSV task z dispatched to Celery!Failed to dispatch task to Celeryz' set to HOLD (waiting for dependencies))rc   rZ   rV   r0   )rO   rK   rL   r.   uuiduuid4r   r   r   r`   rQ   rS   addcommitr,   r-   r[   r   FAILURErV   error_message_ensure_csv_hold_poller_running)rH   r   r   r0   rK   rL   rc   r   rZ   dependencies_metinitial_statustrackers               r   trigger_csv_processing_taskr   x  sg   6 ^ $**,G''8I{m1ZL)=I 2"j+N 2B%--GWG\G\N  %58<<>4	G FF7OIIK
KK
YKr+l>CWCWBXY
 $[':yQKK)I;.CDE .55GN$GG!IIKi	{*QRS'J7  &&	 r!   c                    ddl m}m} | j                  |      j	                  |j
                  |j                  k(  |j                  j                  d            j                  |j                        }|r|j	                  |j                  |k(        }|j                         }|syd}|D ]  }t        | |j                  |j                        s&t        | |j                  |j                  |j                        sSt!        |j                  |j                  |j                  |j"                        rb|j$                  |_        t'        j(                         |_        | j-                          |dz  }t.        j1                  d|j2                          |j4                  |_        d|_        | j-                           |S )z
    Check HOLD CSV tasks and start them if dependencies are met.
    
    Args:
        db: Database session
        company_id: Optional filter by company
        
    Returns:
        Number of tasks started
    r   rJ   Nr6   zStarted HOLD CSV task: r   )rO   rK   rL   rT   rU   rV   rS   r   isnotorder_byrd   r   allr`   rg   rc   r   r0   rQ   r   r   r   r   r,   r-   rZ   r   r   )rH   r   rK   rL   rT   
hold_taskstasks_startedr   s           r   process_hold_csv_tasksr     s    ^ HH&'..  $4$9$99%%++D1 h ++, 

 .99ZGHJM )"g.@.@'BUBUV $B(:(:G<O<OQXQ`Q`a %OO	
 .55GN!)GIIKQMKK1'2C2C1DEF-55GN$GG!IIK14 r!   c                    ddl m}m} | j                  |      j	                  |j
                  |j                  k(  |j                  j                  d            }|r|j	                  |j                  |k(        }|j                         duS )z/Check if there are any CSV tasks in HOLD statusr   rJ   N)rO   rK   rL   rT   rU   rV   rS   r   r   r   rX   )rH   r   rK   rL   rT   s        r   has_hold_csv_tasksr     sx    ]HH&'..  $4$9$99%%++D1E
 .99ZGH;;=$$r!   c                     	 t         j                  dk(  rddlm} |j	                  |       nddlm} |j                  dd|i       t        j                  d	|        y# t        $ r"}t        j                  d
|        Y d}~yd}~ww xY w)z
    Ensure the background poller for HOLD tasks is running.
    Uses the existing hold_task_poller which now handles both CSV and snapshot tasks.
    rt   r   )poll_hold_tasks)r   rv   z:src.smart_inventory.tasks.hold_task_poller.poll_hold_tasksr   )ru   z Started hold poller for company z"Failed to start hold task poller: N)rx   ry   *src.smart_inventory.tasks.hold_task_pollerr   delayr{   rw   r|   r,   r-   r@   r:   )rH   r   r   rw   rF   s        r   r   r     s    
A<<7"R!!Z!8 ;  L$j1 !  	6zlCD A;A3?@@As   AA" "	B+BBrV   r   c                    	 ddl m} ddlm}m}  |       }	 |j                  |      j                  |j                  | k(        j                         }|r|dk(  r+|j                  |_
        t        j                         |_        nf|dk(  r+|j                  |_
        t        j                         |_        n6|dk(  r1|j                   |_
        t        j                         |_        ||_        |j%                          t&        j)                  d|  d|        |dk(  rf|j*                  rZt-        ||j*                        }|dkD  r?t&        j/                  d	| d
|j0                   d       nt&        j3                  d|         |j5                          y# |j5                          w xY w# t6        $ r"}	t&        j9                  d|	        Y d}	~	yd}	~	ww xY w)a  
    Update CeleryTaskTracker status for a CSV processing task.
    Call this from within Celery tasks to update their tracking status.
    
    When status is 'success', automatically triggers processing of HOLD tasks
    that may have been waiting for this task to complete.
    
    Args:
        task_id: The Celery task ID
        status: New status (started, success, failure)
        error_message: Optional error message for failure status
    r   )get_db_sessionrJ   startedsuccessfailurezUpdated CSV task z status to zStarted z HOLD CSV tasks after z
 completedzCSV task tracker not found: z*Failed to update CSV task tracker status: N)src.utils.dbr   rO   rK   rL   rT   rU   rc   rX   rR   rV   r   r   r   SUCCESScompleted_atr   r   r   r,   rY   r   r   r-   rZ   r:   closer@   rA   )
rc   rV   r   r   rK   rL   rH   r   r   rF   s
             r   update_csv_task_statusr   .  s   "#G/a	hh0188!))W4eg  Y&%5%=%=GN)1G&y(%5%=%=GN+3<<>G(y(%5%=%=GN+3<<>G(,9G)		0	VHMN Y&7+=+=4R9K9KLG{hwi7MgN_N_M``j$kl!=gYGHHHJBHHJ GA!EFFGs/   F( E*F F( F%%F( (	G1GG)F)N))__doc__loggingrx   r   r   pathlibr   typingr   r   r   r   sqlalchemy.ormr	   	getLogger__name__r,   CSV_UPLOAD_TYPESr   r.   __annotations__r   __file__parentr(   r;   intr    bytesr2   boolrG   r`   rg   rr   r   r   r   r   r   r    r!   r   <module>r      s  >  
    , , "			8	$   k"$45DTHy/ tCcN+  S`dZ\b`) tCH~   (^""))F2]BEUU
8n##**V3mCFVV9# 93 93 9 U  # RU 2,C , ,(3- ,f000 0 
	0f555 5 	5
 
5xc <... . 	.
 
.jMMM M 	M
 
#s(^Md !%::: 	:z%7 % % %A AS A: $(4G4G4G C=4Gr!   