
    8Xj                        d Z ddlZddlZddlZddlZddlmZ ddlmZ  ej                  ej                  j                          ej                  e      ZdZdZdZdZ G d	 d
e      Z G d de      ZdddZ G d d      Zd Zd Zy)a  SAP Business One Service Layer client, embedded in Odoo.

Thin adapter over the B1 Service Layer REST/OData API. Handles session reuse,
transparent re-auth on expiry (-5002 / HTTP 401), and retry/backoff on
object-locked (-2028). Connection settings come from Odoo system parameters
(ir.config_parameter) so no credentials live in source.

Config keys (set via Settings > SAP B1, or ir.config_parameter):
  sapb1.base_url      e.g. https://192.168.68.16:50000/b1s/v1
  sapb1.company_db    e.g. XEN
  sapb1.username      e.g. manager
  sapb1.password      (secret)
  sapb1.verify_ssl    "true"/"false"  (self-signed on-prem cert -> false)

Write-safety guard (added 2026-07-10, after a stale-cache bug let a test
write reach production XEN):
  sapb1.production_company_db     defaults to "XEN" if unset
  sapb1.confirm_production_writes "true"/"false", defaults to false

Any code path that WRITES to B1 (push, absence log) must call
check_production_write_allowed() before touching the network. If the
configured company_db matches the production one and the confirm flag
isn't explicitly set, the write is blocked -- interactively with a clear
UserError, or silently-skipped-with-a-log-warning for best-effort
background paths that must never block an unrelated Odoo action (like
approving a leave).
    N)_)	UserErrorivi      c                         e Zd Zd fd	Z xZS )B1Errorc                 ^    || _         || _        t        |   |d| d|        y |       y )Nz[B1 z] )codestatussuper__init__)selfr
   messager   	__class__s       &xeno_sapb1_sync/models/sapb1_client.pyr   zB1Error.__init__/   s5    	T5E4vRy1S7S    N)__name__
__module____qualname__r   __classcell__)r   s   @r   r   r   .   s    T Tr   r   c                       e Zd ZdZy)B1WriteBlockedzCRaised when a write to B1 is blocked by the production-write guard.N)r   r   r   __doc__ r   r   r   r   5   s    Mr   r   F)softc                   | d   j                         }|j                  d      xs dj                         }|j                  d      xs dj                         }|j                  dd      j                         dk(  }|j	                         |j	                         k(  r3|s1t        d	|
      }|rt        j                  d|       yt        |      y)a  Return True if a B1 write may proceed, False/raise otherwise.

    soft=False (interactive actions, e.g. the "Push to SAP B1" button):
      raises B1WriteBlocked with a clear, actionable message.
    soft=True (best-effort background paths, e.g. the leave-approval hook
      and retry crons): logs a warning and returns False so the caller can
      skip quietly, matching the existing "B1 outage never blocks Odoo"
      philosophy -- a safety block should behave the same way.
    ir.config_parametersapb1.company_db zsapb1.production_company_dbXENzsapb1.confirm_production_writesfalsetruezSAP B1 writes to the production company '%(prod_db)s' are blocked by default. If this is intentional, enable 'Confirm production writes' in Settings -> SAP B1 Sync first.)prod_dbz#B1 write blocked (safety guard): %sFT)	sudo	get_paramstriplowerupperr   _loggerwarningr   )envr   p
company_dbr$   	confirmedmsgs          r   check_production_write_allowedr1   9   s     	!"'')A++017R>>@J{{89BUIIKG=wGMMOSYYIW]]_,YL 	
 OOA3GS!!r   c                   ^    e Zd ZddZed        Zd Zd Zd Zd Z	d Z
dd	Zdd
Zd Zd Zy)SAPB1Clientc                     |r|st        t        d            |j                  d      | _        || _        || _        || _        || _        t        j                         | _
        || j                  _        d| _        y )NzbSAP B1 is not configured. Set sapb1.base_url / company_db / username / password in Settings first./F)r   r   rstripbase_urlr.   usernamepassword
verify_sslrequestsSession_sverify
_logged_in)r   r7   r.   r8   r9   r:   s         r   r   zSAPB1Client.__init__W   sp    xA9   !,$  $""$#r   c           
          |d   j                         } | |j                  d      |j                  d      |j                  d      |j                  d      |j                  dd      j                         dk(  	      S )
Nr   zsapb1.base_urlr   zsapb1.usernamezsapb1.passwordzsapb1.verify_sslr"   r#   )r7   r.   r8   r9   r:   )r%   r&   r(   )clsr,   r-   s      r   from_envzSAPB1Client.from_envf   st    %&++-[[!12{{#56[[!12[[!12$6@FFHFR
 	
r   c                 b   | j                   j                  | j                   d| j                  | j                  | j
                  dt              }|j                  dk7  r(t        |      \  }}t        |d| |j                        d| _
        t        j                  d| j                         y )Nz/Login)	CompanyDBUserNamePassword)jsontimeout   zlogin failed: TzSAP B1 login OK (company=%s))r=   postr7   r.   r8   r9   _TIMEOUTstatus_code_parse_errorr   r?   r*   info)r   respr
   r0   s       r   loginzSAPB1Client.logins   s    ww||}}oV$!__ MM MM
   
 s"$T*ID#$. 68H8HII3T__Er   c                     | j                   r7	 | j                  j                  | j                   dt               d| _         y y # t
        j                  $ r Y w xY w)Nz/Logout)rH   F)r?   r=   rJ   r7   rK   r;   RequestExceptionr   s    r   logoutzSAPB1Client.logout   sU    ??g6I $DO  ,, s   .A AAc                 &    | j                          | S r   )rP   rS   s    r   	__enter__zSAPB1Client.__enter__   s    

r   c                 $    | j                          y r   )rT   )r   excs     r   __exit__zSAPB1Client.__exit__   s    r   c                    | j                   s| j                          | j                   d|j                  d       }d }t	        dt
        dz         D ]  }	  | j                  j                  ||fdt        i|}|j                  dv r1|j                  dk(  s|j                  sd c S |j                         c S t        |      \  }	}
|j                  dk(  s	|	t         k(  rd| _         | j                          |	t"        k(  rt        |       t%        |	|
|j                         t%        d dt
         d	|       # t        j                  $ r}|}t        |       Y d }~d }~ww xY w)
Nr5      rH   )rI         r]   i  Fzrequest failed after z
 retries: )r?   rP   r7   lstriprange_MAX_RETRIESr=   requestrK   r;   rR   _backoffrL   contentrG   rM   _SESSION_EXPIRED_OBJECT_LOCKEDr   )r   methodpathkwargsurllastattemptrO   rX   r
   r0   s              r   ra   zSAPB1Client.request   sQ   JJLqS!1 23Qq 01 	7G&twwvsOHOO
 ?2#//36dllt[PTPYPYP[[$T*ID#3&$2B*B"'

~%!$T%5%566#	7$ d3L>D6RSS ,, !s   $D::E%E  E%Nc                 *    | j                  d||      S )NGETparamsra   )r   rg   ro   s      r   getzSAPB1Client.get   s    ||E4|77r   c                    t        |xs i       }g }| j                  ||      }|r^|j                  |j                  dg              |j                  d      xs |j                  d      }|s	 |S | j                  |      }|r^|S )zAFollow @odata.nextLink pagination and return the full value list.rn   valuezodata.nextLinkz@odata.nextLink)dictrq   extend)r   rg   ro   rowsdatanxts         r   get_allzSAPB1Client.get_all   s    fl#xxVx,KK"-.((+,K9J0KC 88C=D  r   c                 *    | j                  d||      S )NPOSTrG   rp   r   rg   rG   s      r   rJ   zSAPB1Client.post   s    ||FDt|44r   c                 *    | j                  d||      S )NPATCHr|   rp   r}   s      r   patchzSAPB1Client.patch   s    ||GT|55r   )Fr   )r   r   r   r   classmethodrB   rP   rT   rV   rY   ra   rq   ry   rJ   r   r   r   r   r3   r3   V   sJ      
 
F $
T2856r   r3   c                 n   	 | j                         j                  di       }|j                  d      }|j                  di       }t        |t              r|j                  dd      }||xs | j                  fS # t
        t        f$ r, d | j                  r| j                  d d n| j                  fcY S w xY w)Nerrorr
   r   rs   r    rI   )rG   rq   
isinstancert   reason
ValueErrorAttributeErrortext)rO   errr
   r0   s       r   rM   rM      s    Eiikoogr*wwvggi$c4 '''2&CS'DKK''' EdiioDDEs   A6A9 98B43B4c                 N    t        j                  t        d| z  dz  d             y )N   g      ?g      @)timesleepmin)rk   s    r   rb   rb      s    JJs1<$&,-r   )r   loggingr   r;   urllib3odoor   odoo.exceptionsr   disable_warnings
exceptionsInsecureRequestWarning	getLoggerr   r*   rd   re   rK   r`   RuntimeErrorr   r   r1   r3   rM   rb   r   r   r   <module>r      s   6      %   ++BB C
'

H
% Tl TNY N 16 :k6 k6\	E.r   