
    ^DjE2                        d Z ddlZddlZddlZddlmZ ddlmZ  ee      j                         j                  Z
e
dz  Z ej                         Zg dZddd	d
ddddZ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Zd/d0d1Zd2d/d2d3d4d0d4d3gZd5efd6Zd7ed5dfd8Zd7ed5efd9Zd5efd:Zd;ed5efd<Zd;ed5efd=Zd5efd>Zd?ed5efd@Zd5efdAZdBedCefdDZ dIdEefdFZ!dEedGe"fdHZ#y)JuJ  
winston_date_logic.py — Winston "Date Logic" pipeline config (DVI-834).

A "Date Logic" pipeline pairs a display name with a date-resolution rule
(``base_rule``) and per-process display labels. The two seeded built-ins mirror
the historical schedule profiles:

  prev_biz   Previous business day (the 7am pattern)
  today      Today's date (the 11am / 3pm pattern)

The list of *processes* (the linear steps Winston runs per quote task) is fixed
and shared by every pipeline — see PROCESS_KEYS. Renaming a process only changes
its display label; the underlying pipeline code (winston_pipeline.py) is
unchanged. New pipelines are additional named views over an existing base_rule
and become selectable as a schedule's Date Logic.

Both app.py and winston_scheduler.py import this module; it depends on stdlib
only to avoid import cycles.
    N)datetime)Pathzwinston_date_logic.json)clickup_pullfind_downloadopenpyxl_extractgraph_convertoutlook_draftclickup_writebackrouted_uploadzClickUp pullzFind + download workbookzExtract quote sheetzConvert to PDF (Graph)zCreate mail draftzClickUp writebackzRouted PDF uploadzOFetches the quote tasks to process for the pipeline's target date from ClickUp.zClickUp APIzQuote tasks in the configured ClickUp list (clickup_list_id); if no list is set, a workspace/team-wide query (clickup_team_id). Tasks are filtered by the configured date field (default: date_updated) matching the target date.zzFor each matching task: quote number, ClickUp task id, owner email, recipient email, and the Quote Amount custom-field id.z|The list of quote tasks (quote numbers) to the next step, which locates each workbook. If no tasks match, the run ends here.)summarysystemqueriespullspushesz@Locates and downloads the quote's source workbook from OneDrive.zMicrosoft Graph (OneDrive)u  Searches for the workbook named for the quote number ({quote}.xlsm) using a shared-first → personal-fallback rule. First checks the shared Quote Take-offs folder (shared_quote_takeoff_location in Togen Admin; falls back to the legacy Takeoffs folder when unset). If not found there, falls back to the quote owner's personal Quote Take-offs folder (quote_takeoff_location from their User Settings Documents tab), searching their own OneDrive.zRThe raw .xlsm workbook bytes (via the Graph download URL, or a direct drive read).zqThe workbook bytes to the extract step. If the workbook isn't found or the download fails, that quote is skipped.zMStrips the workbook down to the single quote sheet and reads the quote total.zLocal (openpyxl, in-memory)uE   No external system — operates on the downloaded workbook in memory.zReads the grand-total cell (H137) on the "Quote (Automated)" sheet; sets the print area (A1:AM138) and deletes every other sheet (removing the large Structure Input sheet that otherwise breaks PDF conversion).zdA slimmed single-sheet .xlsx plus the grand-total value to the PDF, mail-draft and write-back steps.z&Converts the slimmed .xlsx into a PDF.zMicrosoft Graph (Files)zUploads the .xlsx to a scratch location on the service drive, requests it back as PDF (?format=pdf), then deletes the scratch file.zThe rendered PDF bytes.zrThe PDF to the mail-draft, ClickUp write-back and routed-upload steps. If conversion fails, that quote is skipped.zICreates a draft quote email (with the PDF attached) in the sales mailbox.zMicrosoft Graph (Outlook mail)u   Checks the mailbox Drafts folder for an existing message whose subject contains the quote number (dedupe — skips creating a duplicate).z?Nothing new; reuses the PDF and grand total from earlier steps.u  A new draft in the configured mailbox (default sales@icastinc.com): subject "Quote {quote} — iCast Infrastructure", addressed to the task's recipient, quote total in the body, PDF attached. The draft is left for a human to review and send — the pipeline never sends it.z:Writes the quote total and PDF back onto the ClickUp task.zRChecks whether the PDF is already attached to the task (skips re-attaching if so).zNothing new.zdSets the task's Quote Amount custom field to the grand total and attaches the quote PDF to the task.zSUploads the final quote PDF to its Completed Quotes destination folder in OneDrive.u2  Resolves the Completed Quotes destination by owner: if the quote owner has a personal Completed Quotes path (quotes_location) set in their User Settings Documents tab, that OneDrive folder is used; otherwise the shared Completed Quotes path (shared_quotes_location in Togen Admin → Documents → Quotes).zlUploads the PDF to the resolved Completed Quotes destination folder. This is the final step of the pipeline.zPrevious business dayzToday's date)prev_biztodayr   )idname	base_ruler   returnc                  0   t         j                         r\	 t        j                  t         j	                               } t        | t              r!| D cg c]  }t        |t              s| c}S 	 g S g S c c}w # t        j                  t        f$ r Y g S w xY wN)
_STORE_FILEis_filejsonloads	read_text
isinstancelistdictJSONDecodeErrorOSError)dataps     )/var/www/html/togen/winston_date_logic.py	_load_rawr&   }   s    	::k3356D$%#'?a:a+>?? & I2I @$$g. 	I	s)   ;A8 A3'A3+A8 3A8 8BB	pipelinesc                 X    t         j                  t        j                  | d             y )N   )indent)r   
write_textr   dumps)r'   s    r%   	_save_rawr-      s    4::i:;    c                     | r| S t        j                         j                         }g }t        D ]$  }|j	                  |d   |d   |d   di ||d       & t        |       |S )Nr   r   r   Tr   r   r   builtinprocess_labels
created_at
updated_at)r   utcnow	isoformat	_BUILTINSappendr-   )r'   nowseededbs       r%   _seed_if_emptyr<      su    
//

%
%
'CF 	D'fI; 
 		 fMr.   c                  `    t         5  t        t                     cddd       S # 1 sw Y   yxY w)z7Load pipelines, seeding the two built-ins on first use.N)_LOCKr<   r&    r.   r%   _loadr@      s$    	 +ik*+ + +s   $-pipelinec                 (   | j                  d      xs i }g }t        t              D ]h  \  }}t        |   }|j	                  |dz   ||j                  |      xs ||t        |j                  |            t        j                  |i       d       j |S )Nr2      )seqkeylabeldefault_labelcustomdetail)get	enumeratePROCESS_KEYSDEFAULT_PROCESS_LABELSr8   boolPROCESS_DETAILS)rA   labelsoutirE   defaults         r%   _process_listrT      s    \\*+1rF
CL) 	3(-

q5ZZ_/$6::c?+%))#r2
 		 Jr.   c           	         | j                  d      | j                  d      | j                  d      t        j                  | j                  d      | j                  d            t        | j                  d            t        |       dS )Nr   r   r   r1   )r   r   r   base_rule_labelr1   	processes)rJ   
BASE_RULESrN   rT   )rA   s    r%   _publicrY      sj    ll4 V$\\+.%>>(,,{*CX\\R]E^_Y/0"8, r.   c                  N    t               D  cg c]  } t        |        c} S c c} w r   )r@   rY   r$   s    r%   list_pipelinesr\      s     %(1GAJ(((s   "profilec                     t               D ]7  }|j                  d      | k(  s|j                  d      }|t        v r|c S dc S  | t        v r| S dS )u4  Map a schedule's stored Date Logic id → its date-resolution base_rule.

    Backwards compatible: legacy schedules store "prev_biz"/"today" directly,
    which are also the built-in ids, so they resolve to themselves. Unknown
    values fall back to "today" (matches winston_scheduler.target_dates()).
    r   r   r   )r@   rJ   rX   )r]   r$   brs      r%   resolve_base_ruler`      sY     W 755;'!{#Bz)26w67 +788r.   c                  Z    t               D  ch c]  } | j                  d       c} S c c} w )Nr   )r@   rJ   r[   s    r%   valid_pipeline_idsrb      s     !&)AAEE$K)))s   (r   r   c                    | xs dj                         } | syt        |       dkD  ry|t        vrdddj                  t               dfS t	        j
                         j                         }t        t        j                               | |d	i ||d
}t        5  t        t                     }|j                  |       t        |       ddd       t        |      dfS # 1 sw Y   xY w)zFCreate a new (non-builtin) Date Logic pipeline. Returns (public, err). NzName is required.x   Nz&Name is too long (max 120 characters).Nz#Invalid base rule; expected one of z, .Fr0   )striplenrX   joinr   r5   r6   struuiduuid4r>   r<   r&   r8   r-   rY   )r   r   r9   rA   r'   s        r%   createro      s    JBD(
4y3=
":499Z;P:QQRSSS
//

%
%
'C$**,H 
 "9;/	") 8d""	 s   0CC%pipeline_idc                     t         5  t        t                     }t         fdt	        |      D        d      }|
	 ddd       y||   }|=|xs dj                         }|s
	 ddd       yt        |      dkD  r
	 ddd       y||d<   |t        |t              s
	 ddd       y	t        |j                  d
      xs i       }|j                         D ]]  \  }}|t        vr|t        |      ndj                         }t        |      dkD  r
 ddd       y|r|||<   L|j                  |d       _ ||d
<   t        j                         j!                         |d<   |||<   t#        |       ddd       t%              dfS # 1 sw Y   xY w)zNUpdate a pipeline's name and/or process display labels. Returns (public, err).c              3   R   K   | ]  \  }}|j                  d       k(  s|   ywr   NrJ   ).0rR   r$   rp   s      r%   	<genexpr>zupdate.<locals>.<genexpr>   s$     U$!Q!%%+:TAUs   ''N)NDate Logic not found.rd   re   rf   rg   r   )Nz!process_labels must be an object.r2   )Nz.Process name is too long (max 120 characters).r4   )r>   r<   r&   nextrK   ri   rj   r   r    rJ   itemsrM   rl   popr   r5   r6   r-   rY   )	rp   r   r2   r'   idxrA   rP   rE   vals	   `        r%   updater}      s   	 !"9;/	U)I"6UW[\;0	! !
 S>JB%%'D0! ! 4y3E! !  $HV%nd3@#! !$ (,,'78>B?F*002 	*S44#&?s3xAACs8c>Q1! !2 "%F3KJJsD)	* *0H%&!)!2!<!<!>!	#)C!D 8d""E! !s+   5FF-FF&A)FAFF
referencedc           	      X    t         5  t        t                     }t         fd|D        d      }|
	 ddd       y|j	                  d      r
	 ddd       y|r
	 ddd       yt        |D cg c]  }|j	                  d       k7  s| c}       ddd       yc c}w # 1 sw Y   yxY w)	z9Delete a non-builtin pipeline. Returns (ok, err, status).c              3   L   K   | ]  }|j                  d       k(  s|  ywrs   rt   )ru   r$   rp   s     r%   rv   zdelete.<locals>.<genexpr>  s      Lqt1KLs   $$N)Frw   i  r1   )Fz0Built-in Date Logic pipelines cannot be deleted.i  )Fz1This Date Logic is used by one or more schedules.i  r   )TN   )r>   r<   r&   rx   rJ   r-   )rp   r~   r'   rA   r$   s   `    r%   deleter     s    	 	H"9;/	LILdS6		H 	H
 <<	"Q	H 	H R	H 	H 	iF155;++E1FG	H  G	H s4   ,B B B $	B -B
B
B B  B))NN)$__doc__r   	threadingrm   r   pathlibr   __file__resolveparent	_BASE_DIRr   Lockr>   rL   rM   rO   rX   r7   r   r&   r-   r<   r@   r    rT   rY   r\   rl   r`   setrb   ro   r}   rN   r   r?   r.   r%   <module>r      s.  (     N""$++	33	 #/--(,(  e w N Q V. Qe F c/Z gx <+ Y* G _2 _R h Pgx i. H AW2n (
 6ZPNA	4 < <$ <d t &+t +D T  d t ) )9s 9s 9*C *# # #4$# $#N  r.   