Table of Contents

BR-STK — Regole di business del dominio Stock / Magazzino

Regole di business estratte dal flusso pilota FLOW-STK-001 (registrazione di un movimento di magazzino: ordine / carico / scarico / inventario) e dai sorgenti del modulo Stock. Sistema Infocad (Descor): backend Oracle (nessun ORM, stored procedure via DAL Execute*StoredProcedure), frontend ASP.NET Web Forms (VB.NET) sotto la shell CASSANDRA (StockCenterWeb), controller/BL/DAL in-process (Descor.Stock.StockControllers / StockBL / StockDAL). Prosa in italiano; identificatori (classi/metodi/proc/colonne/costanti) non tradotti.

  • Dominio: Stock / Magazzino (registrazione movimenti, giacenza, ordinato, storicizzazione)
  • Flusso di riferimento: docs/07-business-flows/FLOW-STK-001.md
  • Confidenza complessiva: VERIFIED (catena UI → code-behind → controller → BL → DAL → stored procedure, confermata su codice e su sorgente DB USER_SOURCE / USER_TRIGGERS / USER_CONSTRAINTS / USER_CONS_COLUMNS / USER_TAB_COLUMNS, letti in sola lettura)
  • Schema DB pilota: INFOCAD_TEST38 (osservato solo ambiente TEST38)

Legenda enforcement

  • UI = validato solo nel code-behind/controlli ASP.NET (rischio: bypassabile chiamando BL/DAL direttamente)
  • BL = logica nel Business Layer VB.NET (StockBL, DTO StockItemHistory)
  • DB = vincolo / sequence / trigger / procedura Oracle
  • mixed = combinazione dei precedenti

Registro delle regole

id regola (sintesi) tipo enforcement conf.
BR-STK-001 Accesso alla sezione movimenti solo con grant 6410 o SuperAdmin permission UI VERIFIED
BR-STK-002 Comando Save visibile solo con grant 6420 o SuperAdmin permission UI VERIFIED
BR-STK-003 Comando Delete / aggiunta movimento visibile solo con grant 6430 o SuperAdmin permission UI VERIFIED
BR-STK-004 Selezione fornitore/costo reale abilitata solo con grant 6450 (e 6440 per costo reale) permission UI VERIFIED
BR-STK-005 Quattro tipi di movimento hard-coded: Order=1, Load=2, Unload=3, StockTaking=4 config-flag mixed (BL+DB) VERIFIED
BR-STK-006 Ricalcolo giacenza/ordinato dipende dalla direzione del movimento calculation DB VERIFIED
BR-STK-007 Lo scarico (dir. 3) non decrementa la giacenza se l'articolo è price-list state-transition DB VERIFIED
BR-STK-008 L'inventario (dir. 4) imposta la giacenza al valore assoluto rilevato calculation DB VERIFIED
BR-STK-009 MOVE_DATE sempre = SYSDATE lato DB; DateMove calcolato in UI non è passato alla proc date-logic DB VERIFIED
BR-STK-010 Quantità obbligatoria e strettamente > 0 validation UI VERIFIED
BR-STK-011 Scarico non oltre la giacenza disponibile per articoli non price-list validation UI VERIFIED
BR-STK-012 Tipo operazione obbligatorio prima del salvataggio validation UI VERIFIED
BR-STK-013 Validazione DTO minima: IdStockItem, QuantityMoved, DateMove, Direction non nulli validation BL VERIFIED
BR-STK-014 Save disabilitato su Scarico se giacenza ≤ 0 e articolo non price-list permission/validation UI VERIFIED
BR-STK-015 Carico (dir. 2) limitato in UI alla quantità ordinata validation UI VERIFIED
BR-STK-016 ID_ITEM_HISTORY generato da trigger BEFORE INSERT via SEQ_STOCK_ITEM_HISTORY (con gap-recovery) calculation DB VERIFIED
BR-STK-017 Nuovo id restituito via SEQ...CURRVAL nel parametro OUT PNEWINDEX calculation mixed (BL+DB) VERIFIED
BR-STK-018 Riga di collegamento in STOCK_ITEM_LINK creata solo se PIDLINK IS NOT NULL state-transition DB VERIFIED
BR-STK-019 LinkApplicationID valorizzato in UI con il GUID strong-name "RequestCenter" config-flag mixed (UI+BL) VERIFIED
BR-STK-020 Vincoli NOT NULL su STOCK_ITEM_HISTORY (ID_ITEM, QMOVE, MOVE_DATE, DIRECTION, ID_CONTACT, costi) validation DB VERIFIED
BR-STK-021 Integrità referenziale movimento → articolo (FK_STOCK_ITEM_HIST) validation DB VERIFIED
BR-STK-022 Multi-tenant: ID_TENANCY propagato solo se la tenancy è abilitata config-flag mixed (UI+BL) VERIFIED
BR-STK-023 Cancellazione logica (ISCANCELLED=1) storna la giacenza in senso inverso con clamp a 0; l'inventario NON è stornato state-transition DB VERIFIED
BR-STK-024 Su Ordine con richieste: chiusura/decurtazione progressiva delle richieste collegate state-transition BL VERIFIED
BR-STK-025 La proc STK_ADDITEMMOVE non esegue check di business (no under-zero guard) validation (gap) DB VERIFIED
BR-STK-026 SELECT ... INTO TMP_ISPRICELIST presuppone esattamente una riga stock per l'articolo validation (gap) DB VERIFIED
BR-STK-027 Update movimento fa upsert del collegamento in STOCK_ITEM_LINK state-transition DB VERIFIED
BR-STK-028 Secondo chiamante DAL di STK_ADDITEMMOVE con 5 parametri, incompatibile con la firma config-flag BL INFERRED

Dettaglio regole

BR-STK-001 — Gate di accesso alla sezione movimenti

Statement: L'accesso alla scheda movimento è consentito solo se l'utente possiede il grant funzionale 6410, oppure è SuperAdmin; altrimenti viene sollevata ThrowSectionAccessException().

  • Tipo: permission · Enforcement: UI · Confidence: VERIFIED · customer_specific: false
  • Evidenza: MovementDetailControl.ascx.vb:170-174 (CheckPermissions(): If Not Me.HasUserGrant("6410") And Not WS.SessionProfile.SuperAdmin Then ThrowSectionAccessException()).
  • Flag: UI-only (nessun controllo di grant a livello BL/DAL); hard-coded id grant 6410.

BR-STK-002 — Grant di salvataggio (Save)

Statement: La visibilità del comando Save della toolbar richiede il grant 6420 (o SuperAdmin).

  • Tipo: permission · Enforcement: UI · Confidence: VERIFIED · customer_specific: false
  • Evidenza: MovementDetailControl.ascx.vb:188 (FindItemByValue("Save").Visible = Me.HasUserGrant("6420") Or WS.SessionProfile.SuperAdmin), ripetuto per ogni tipo movimento (:574,591,609,623).
  • Flag: UI-only; hard-coded id grant 6420. Nota: in ramo di nuovo record (:210) il Save è talvolta legato a 6430 — vedi Open questions.

BR-STK-003 — Grant di cancellazione / aggiunta movimento

Statement: La visibilità del comando Delete (e l'abilitazione all'aggiunta di un movimento) richiede il grant 6430 (o SuperAdmin).

  • Tipo: permission · Enforcement: UI · Confidence: VERIFIED · customer_specific: false
  • Evidenza: MovementDetailControl.ascx.vb:189 (FindItemByValue("Delete").Visible = Me.HasUserGrant("6430") Or WS.SessionProfile.SuperAdmin), :210,575,592,610,624.
  • Flag: UI-only; hard-coded id grant 6430.

BR-STK-004 — Grant selezione fornitore e costo reale

Statement: La combo fornitore (rcbCompany) e i campi del costo reale del movimento sono abilitati/visibili solo con grant 6450 (o SuperAdmin); la visibilità del costo reale totale è concessa anche col grant 6440.

  • Tipo: permission · Enforcement: UI · Confidence: VERIFIED · customer_specific: false
  • Evidenza: MovementDetailControl.ascx.vb:213 (If rblMovementType.SelectedValue = "3" AndAlso (HasUserGrant("6450") Or ...)), :285,561; costo reale :579-580 (... = Me.HasUserGrant("6450") Or Me.HasUserGrant("6440") Or WS.SessionProfile.SuperAdmin).
  • Flag: UI-only; hard-coded id grant 6450, 6440.

BR-STK-005 — Quattro tipi di movimento (enum + magic numbers DB)

Statement: Il movimento ammette quattro direzioni codificate come interi: Order=1 (ordine), Load=2 (carico), Unload=3 (scarico), StockTaking=4 (inventario). Gli stessi valori numerici sono usati come rblMovementType in UI, come enum StockMoveDirection nel DTO e come PDIRECTION nella procedura Oracle.

  • Tipo: config-flag (magic numbers) · Enforcement: mixed (BL+DB) · Confidence: VERIFIED · customer_specific: false
  • Evidenza: enum StockSharedObjects/Objects/StockItemHistory.vb:322-329 (Order=1, Load=2, Unload=3, StockTaking=4); UI MovementDetailControl.ascx.vb:195 (rblMovementType ... "3"), ramo rblMovementType_SelectedIndexChanged (:582 "2", :601 "1", :615 "4"); DTO InstantiateDto :692 (itemHis.Direction = DirectCast(CInt(rblMovementType.SelectedValue), StockMoveDirection)); DB STK_ADDITEMMOVE src:35,42,49,57 (IF PDIRECTION = 1..4).
  • Flag: magic numbers replicati su 3 livelli (UI/BL/DB) senza costante condivisa lato DB.

BR-STK-006 — Semantica della direzione sul ricalcolo giacenza/ordinato

Statement: La procedura STK_ADDITEMMOVE, dopo l'INSERT del movimento, aggiorna STOCK_ITEM in base a PDIRECTION:

  • Order (1): QORDERED = QORDERED + PQMOVE (incrementa l'ordinato);
  • Load (2): QSTOCK = QSTOCK + PQMOVE, QORDERED = QORDERED - PQMOVE (carica a magazzino e scarica l'ordinato);
  • Unload (3): QSTOCK = QSTOCK - PQMOVE (solo se non price-list, vedi BR-STK-007);
  • StockTaking (4): QSTOCK = PQMOVE (rettifica assoluta, vedi BR-STK-008).
  • Tipo: calculation / state-transition · Enforcement: DB · Confidence: VERIFIED · customer_specific: false
  • Evidenza (sorgente proc USER_SOURCE, letto read-only): STK_ADDITEMMOVE src:35-40 (dir 1), src:42-47 (dir 2), src:49-55 (dir 3), src:57-62 (dir 4).
  • Flag: DB-only (semantica di segno interamente nel PL/SQL; la UI non ricalcola la giacenza, si limita a leggerla).

BR-STK-007 — Scarico neutro su articolo price-list

Statement: Il ramo di scarico (PDIRECTION=3) decrementa QSTOCK solo se l'articolo non è price-list (TMP_ISPRICELIST = 0); per gli articoli price-list il movimento di scarico è storicizzato ma non intacca la giacenza.

  • Tipo: state-transition / config-flag · Enforcement: DB · Confidence: VERIFIED · customer_specific: false
  • Evidenza: STK_ADDITEMMOVE src:23-28 (SELECT STOCK_STOCK.ISPRICELIST INTO TMP_ISPRICELIST ... JOIN STOCK_ITEM ON ID_STOCK ... AND ID_ITEM = PIDITEM), src:49-50 (IF PDIRECTION = 3 AND TMP_ISPRICELIST = 0 THEN). Coerente con la guardia UI :207 e :754.
  • Flag: hidden flag STOCK_STOCK.ISPRICELIST (proprietà dello stock, non del movimento).

BR-STK-008 — Inventario come rettifica assoluta

Statement: Il movimento di inventario (PDIRECTION=4) imposta la giacenza al valore rilevato: QSTOCK = PQMOVE (non è un delta ma un valore assoluto). La differenza rispetto alla giacenza precedente è mostrata solo informativamente in UI (StockTakingDifference = QuantityMoved - OldQuantity).

  • Tipo: calculation · Enforcement: DB · Confidence: VERIFIED · customer_specific: false
  • Evidenza: STK_ADDITEMMOVE src:57-62 (IF PDIRECTION = 4 THEN UPDATE STOCK_ITEM SET QSTOCK = PQMOVE); differenza informativa UI MovementDetailControl.ascx.vb:738-740.

BR-STK-009 — Data movimento sempre = SYSDATE (data server)

Statement: La data del movimento (MOVE_DATE) è impostata a SYSDATE direttamente nell'INSERT lato DB; il valore DateMove = Date.Now calcolato in UI e propagato fino al DAL come parametro moveDate non è passato come OracleParameter alla procedura, quindi è di fatto ignorato dalla persistenza. La colonna ha inoltre default DB SYSDATE ed è NOT NULL.

  • Tipo: date-logic · Enforcement: DB · Confidence: VERIFIED · customer_specific: false
  • Evidenza: STK_ADDITEMMOVE src:30-33 (INSERT ... MOVE_DATE ... VALUES (..., SYSDATE, ...)); default/NOT NULL da USER_TAB_COLUMNS (MOVE_DATE NULLABLE=N DEFAULT=SYSDATE); UI MovementDetailControl.ascx.vb:691 (itemHis.DateMove = Date.Now); DAL riceve moveDate (StockItemHistory_DAL.vb:128) ma non lo aggiunge ai parametri (:144-183).
  • Flag: date-sensitive — la data movimento non è modificabile dall'operatore (sempre data del server DB).

BR-STK-010 — Quantità obbligatoria e > 0

Statement: In UI la quantità è obbligatoria e deve essere strettamente maggiore di zero: campo vuoto → errore QuantityRequiredString; valore = 0 → errore QuantityGreaterString.

  • Tipo: validation · Enforcement: UI · Confidence: VERIFIED · customer_specific: false
  • Evidenza: MovementDetailControl.ascx.vb:757-761 (SetErrorTooltips: If rnbQuantity.Text = String.Empty ... ElseIf rnbQuantity.Value = 0 ...).
  • Flag: UI-only — il DTO valida solo QuantityMoved <> Nothing (≠ 0/null), la proc non ha check di segno (vedi BR-STK-013, BR-STK-025).

BR-STK-011 — Scarico entro la giacenza (articoli non price-list)

Statement: Per lo scarico (dir. 3) di un articolo non price-list, se la quantità richiesta supera la giacenza (rnbQuantity.Value > Me.StockQuantity) viene mostrato l'errore NoItemsInStockString e il salvataggio è bloccato; il campo quantità ha inoltre MaxValue = StockQuantity.

  • Tipo: validation · Enforcement: UI · Confidence: VERIFIED · customer_specific: false
  • Evidenza: MovementDetailControl.ascx.vb:754-756 (If rblMovementType.SelectedValue = "3" AndAlso rnbQuantity.Value > Me.StockQuantity AndAlso Not Me.StockIsPriceList), :567 (If Not Me.StockIsPriceList Then rnbQuantity.MaxValue = Me.StockQuantity).
  • Flag: UI-only — l'articolo price-list è escluso dal controllo (coerente con BR-STK-007).

BR-STK-012 — Tipo operazione obbligatorio

Statement: Se nessun tipo movimento è selezionato (rblMovementType.SelectedValue = String.Empty), viene mostrato l'errore OperationRequiredString e il salvataggio è bloccato.

  • Tipo: validation · Enforcement: UI · Confidence: VERIFIED · customer_specific: false
  • Evidenza: MovementDetailControl.ascx.vb:751-753.
  • Flag: UI-only.

BR-STK-013 — Validazione DTO minima

Statement: Prima dell'insert il DTO StockItemHistory.Validate() verifica la valorizzazione di IdStockItem, QuantityMoved, DateMove e Direction; se uno è Nothing la validazione fallisce con messaggio. È l'unico controllo ripetuto fuori dalla UI.

  • Tipo: validation · Enforcement: BL · Confidence: VERIFIED · customer_specific: false
  • Evidenza: StockSharedObjects/Objects/StockItemHistory.vb:284-312 (Validate: check IdStockItem/QuantityMoved/DateMove/Direction = Nothing); invocato da DetailBase.Save.
  • Nota: = Nothing per un Decimal equivale a = 0, quindi copre indirettamente quantità nulla ma non quantità negativa.

BR-STK-014 — Save inibito su scarico con giacenza esaurita

Statement: In fase di nuovo record, se il tipo è Scarico (dir. 3) e la giacenza è ≤ 0 su articolo non price-list, il pulsante Save è reso invisibile (impossibile registrare lo scarico).

  • Tipo: permission / validation · Enforcement: UI · Confidence: VERIFIED · customer_specific: false
  • Evidenza: MovementDetailControl.ascx.vb:207-211 (If rblMovementType.Items.FindByValue("3").Selected AndAlso (Me.StockQuantity <= 0 AndAlso Not Me.StockIsPriceList) Then ...Save.Visible = False), :569-577.
  • Flag: UI-only.

BR-STK-015 — Carico limitato alla quantità ordinata

Statement: Per il carico (dir. 2) la quantità movimentabile è limitata in UI al valore ordinato (rnbQuantity.MaxValue = Me.OrderedQuantity); se l'ordinato è ≤ 0 il comando Save è nascosto.

  • Tipo: validation · Enforcement: UI · Confidence: VERIFIED · customer_specific: false
  • Evidenza: MovementDetailControl.ascx.vb:585-594 (Case "2" ... rnbQuantity.MaxValue = Me.OrderedQuantity ... If Me.OrderedQuantity <= 0 Then Save.Visible = False).
  • Flag: UI-only (la proc non verifica il rapporto carico/ordinato; per Ordine (dir 1) e Inventario (dir 4) il MaxValue è Decimal.MaxValue, :608,621).

BR-STK-016 — Storicizzazione: id da trigger + sequence (con gap-recovery)

Statement: La chiave ID_ITEM_HISTORY è generata dal trigger BEFORE INSERT su STOCK_ITEM_HISTORY: se :NEW.ID_ITEM_HISTORY IS NULL viene assegnato SEQ_STOCK_ITEM_HISTORY.NEXTVAL; se l'id è fornito esplicitamente e supera il valore corrente della sequence, la sequence viene fatta avanzare in loop fino a superarlo (recupero dei gap / import controllato).

  • Tipo: calculation · Enforcement: DB · Confidence: VERIFIED · customer_specific: false
  • Evidenza (trigger USER_TRIGGERS, letto read-only): trigger INSERT BEFORE EACH ROW su STOCK_ITEM_HISTORY, corpo: IF (:NEW."ID_ITEM_HISTORY" IS NULL) THEN SELECT "SEQ_STOCK_ITEM_HISTORY".NEXTVAL INTO :NEW."ID_ITEM_HISTORY" FROM DUAL; ELSE ... WHILE (last_InsertID > last_Sequence) LOOP SELECT SEQ...NEXTVAL .... Trigger analogo esiste su STOCK_ITEM (SEQ_STOCK_ITEM).
  • Flag: DB-only — logica di identity applicativa (non IDENTITY/auto-increment nativo).

BR-STK-017 — Ritorno del nuovo id via parametro OUT

Statement: Il nuovo id del movimento è restituito leggendo SEQ_STOCK_ITEM_HISTORY.CURRVAL nel parametro OUT PNEWINDEX subito dopo gli UPDATE di giacenza; il DAL lo converte da OracleDecimal e lo propaga come ID_DTO.

  • Tipo: calculation · Enforcement: mixed (BL+DB) · Confidence: VERIFIED · customer_specific: false
  • Evidenza: STK_ADDITEMMOVE src:64-66 (SELECT SEQ_STOCK_ITEM_HISTORY.CURRVAL INTO PNEWINDEX FROM DUAL); DAL StockItemHistory_DAL.vb:165 (param OUT PNEWINDEX), :187 (Return DirectCast(indexparam.Value, Types.OracleDecimal).ToInt32).
  • Nota: dipendenza dalla sessione/transazione — CURRVAL è affidabile perché la NEXTVAL (nel trigger) e la CURRVAL avvengono nella stessa chiamata.

BR-STK-018 — Collegamento a ticket/richiesta condizionale

Statement: Se PIDLINK IS NOT NULL la procedura inserisce una riga in STOCK_ITEM_LINK (ID_LINK, ID_ITEM_HISTORY, APPLICATION_ID, LINKDESCRIPTION) che lega il movimento a un'entità di un'altra applicazione (tipicamente una richiesta/ticket di RequestCenter). Se PIDLINK è nullo il collegamento non è creato.

  • Tipo: state-transition · Enforcement: DB · Confidence: VERIFIED · customer_specific: false
  • Evidenza: STK_ADDITEMMOVE src:68-74 (IF PIDLINK IS NOT NULL THEN INSERT INTO STOCK_ITEM_LINK ...).

BR-STK-019 — Application ID del collegamento hard-coded a "RequestCenter"

Statement: In fase di costruzione del DTO, LinkApplicationID è sempre impostato al GUID strong-name dell'applicazione RequestCenter; il collegamento (BR-STK-018) punta quindi per costruzione al modulo Service Desk.

  • Tipo: config-flag · Enforcement: mixed (UI+BL) · Confidence: VERIFIED · customer_specific: false
  • Evidenza: MovementDetailControl.ascx.vb:703 (itemHis.LinkApplicationID = Cassandra.WebSharedObject.Applications.GetKeyForStrongName("RequestCenter")); mappato al param PLINKAPPLICATIONID (RAW 16) in StockItemHistory_DAL.vb:152-158.
  • Flag: hard-coded id (strong-name "RequestCenter" risolto a GUID); collegamento cross-modulo.

BR-STK-020 — Vincoli NOT NULL sulla riga di movimento

Statement: La tabella STOCK_ITEM_HISTORY impone NOT NULL su ID_ITEM, QMOVE, MOVE_DATE, DIRECTION, ID_CONTACT, MOVE_TOTAL_COST, MOVE_REAL_COST. Le colonne MOVE_TOTAL_COST/MOVE_REAL_COST hanno default 0, MOVE_DATE default SYSDATE, ISCANCELLED default 0. CAUSE, ID_ITEM_COMPANY, OLD_QUANTITY, PRICELIST_TYPE, ID_TENANCY, PROGRESSIVE sono nullabili.

  • Tipo: validation (integrità dati) · Enforcement: DB · Confidence: VERIFIED · customer_specific: false
  • Evidenza (USER_CONSTRAINTS / USER_TAB_COLUMNS, letti read-only): check NOT NULL SYS_C001714474 "ID_ITEM", SYS_C001714475 "QMOVE", SYS_C001714476 "MOVE_DATE", SYS_C001714477 "DIRECTION", SYS_C001714478 "MOVE_TOTAL_COST", SYS_C001714479 "ID_CONTACT", SYS_C001714480 "MOVE_REAL_COST"; PK PK_STOCK_ITEM_HISTORY.
  • Flag: DB-onlyID_CONTACT NOT NULL implica che l'operatore/contatto sia sempre valorizzato (dal DTO ContactOperator.ID_DTO, StockItemHistory_BL.vb:20).

BR-STK-021 — Integrità referenziale movimento → articolo

Statement: Ogni riga di STOCK_ITEM_HISTORY.ID_ITEM deve riferire un articolo esistente in STOCK_ITEM (FK FK_STOCK_ITEM_HISTPK_STOCK_ITEM); non è possibile storicizzare un movimento su un articolo inesistente.

  • Tipo: validation (integrità referenziale) · Enforcement: DB · Confidence: VERIFIED · customer_specific: false
  • Evidenza (USER_CONSTRAINTS / USER_CONS_COLUMNS): FK_STOCK_ITEM_HIST (ID_ITEM) → PK_STOCK_ITEM (STOCK_ITEM). STOCK_ITEM ha a sua volta FK verso stock/categoria/unità di misura (FK_STOCK_ITEM_STK_STK, FK_STOCK_ITEM_STK_CAT, FK_STOCK_ITEM_STOCK_MEAS) e NOT NULL su QSTOCK/QORDERED/QALERT/QMINIMUM.
  • Flag: DB-only.

BR-STK-022 — Propagazione tenancy condizionata

Statement: ID_TENANCY è valorizzato sul DTO (e propagato fino a PIDTENANCY) solo se la tenancy è abilitata nella sessione web; altrimenti resta nullo (colonna nullabile).

  • Tipo: config-flag · Enforcement: mixed (UI+BL) · Confidence: VERIFIED · customer_specific: false
  • Evidenza: MovementDetailControl.ascx.vb:715-717 (If WS.IsTenantEnabled Then itemHis.ID_Tenancy = WS.CurrentTenant.ID_DTO); mappatura BL StockItemHistory_BL.vb:28; INSERT STK_ADDITEMMOVE src:31,33 (ID_TENANCY ... PIDTENANCY).
  • Flag: hidden flag multi-tenant.

BR-STK-023 — Cancellazione logica con storno inverso della giacenza

Statement: La cancellazione di un movimento è logica: STK_DELETEITEMHISTORY_LOGICAL imposta ISCANCELLED = 1 e storna la giacenza in senso inverso rispetto alla direzione originale — Order(1): QORDERED -= QMOVE (con clamp a 0 se negativo); Load(2): QSTOCK -= QMOVE e QORDERED += QMOVE (con clamp QSTOCK a 0); Unload(3): QSTOCK += QMOVE. L'inventario (dir. 4) NON è stornato (nessun ramo ELSIF).

  • Tipo: state-transition · Enforcement: DB · Confidence: VERIFIED · customer_specific: false
  • Evidenza (USER_SOURCE, read-only): STK_DELETEITEMHISTORY_LOGICAL src:9-11 (UPDATE ... SET ISCANCELLED = 1), src:22-36 (dir 1 + clamp), src:37-56 (dir 2 + clamp), src:59-62 (dir 3); nessun ELSIF NDIRECTION = 4. DAL StockItemHistory_DAL.vb:14-23; controller StockItemHistoryController.vb (LogicalDelete).
  • Flag: magic number ISCANCELLED = 1; gap: l'annullamento di un inventario non ripristina la giacenza precedente (vedi Open questions).

BR-STK-024 — Chiusura/decurtazione richieste su Ordine

Statement: Dopo il salvataggio di un movimento di tipo Ordine (dir. 1) con richieste collegate, la UI decurta progressivamente le QEscape delle richieste: se QRequest ≤ residuo la richiesta è chiusa (CloseRequest), altrimenti la quota residua è aggiornata (UpdateEscape). Sulla voce "(Tutti)" lo storno è progressivo dall'ultima richiesta.

  • Tipo: state-transition · Enforcement: BL (via controller) · Confidence: VERIFIED · customer_specific: false
  • Evidenza: MovementDetailControl.ascx.vb:326-354 (If rblMovementType.SelectedValue = "1" AndAlso _arrRequestData IsNot Nothing ... CloseRequest / UpdateEscape); proc backend STK_UPDATEREQUEST / STK_UPDATEESCAPE (via StockItemRequestController).
  • Flag: side-effect cross-modulo verso RequestCenter.

BR-STK-025 — La procedura non applica guardie di business

Statement: STK_ADDITEMMOVE non contiene alcun controllo di business (nessuna verifica che la giacenza non scenda sotto zero, che la quantità sia positiva, che l'ordinato sia sufficiente per il carico). La coerenza è demandata interamente al livello UI (BR-STK-010/011/014/015). Una chiamata diretta al DAL/proc può portare la giacenza in negativo.

  • Tipo: validation (gap) · Enforcement: DB · Confidence: VERIFIED · customer_specific: false
  • Evidenza: STK_ADDITEMMOVE src:1-76 (assenza di IF/RAISE/check oltre allo split per direzione); nessun CHECK constraint di segno su QSTOCK/QORDERED in USER_CONSTRAINTS (solo NOT NULL).
  • Flag: DB-only gap — le regole di quantità/segno sono UI-only e quindi bypassabili.

BR-STK-026 — Presupposto di unicità dello stock dell'articolo

Statement: La SELECT STOCK_STOCK.ISPRICELIST INTO TMP_ISPRICELIST (join STOCK_ITEM su ID_ITEM = PIDITEM) presuppone che esista esattamente una riga stock per l'articolo: assenza di righe solleverebbe NO_DATA_FOUND, righe multiple TOO_MANY_ROWS — nessuna delle due eccezioni è gestita esplicitamente.

  • Tipo: validation (gap) · Enforcement: DB · Confidence: VERIFIED · customer_specific: false
  • Evidenza: STK_ADDITEMMOVE src:23-28; assenza di blocco EXCEPTION nella procedura (src:21-76).
  • Flag: DB-only gap — comportamento non definito per articolo senza stock associato.

BR-STK-027 — Update movimento: upsert del collegamento

Statement: L'aggiornamento di un movimento (STK_UPDATEITEMMOVE) riscrive i campi della riga STOCK_ITEM_HISTORY e gestisce il collegamento con logica di upsert: se esiste già una riga in STOCK_ITEM_LINK per il movimento la aggiorna, altrimenti la inserisce. Nota: l'update NON ricalcola la giacenza/ordinato (nessun UPDATE su STOCK_ITEM).

  • Tipo: state-transition · Enforcement: DB · Confidence: VERIFIED · customer_specific: false
  • Evidenza (USER_SOURCE, read-only): STK_UPDATEITEMMOVE src:19-21 (UPDATE riga history), src:23-32 (IF PLINKCOUNT > 0 THEN UPDATE ... ELSE INSERT INTO STOCK_ITEM_LINK); DAL StockItemHistory_DAL.vb:254.
  • Flag: gap: modificare quantità/direzione di un movimento esistente non riallinea la giacenza (a differenza di insert/cancellazione logica).

BR-STK-028 — Secondo chiamante legacy di STK_ADDITEMMOVE

Statement: Oltre al path canonico (StockItemHistory_DAL.Insert, 16 parametri coerenti con la firma DB), esiste un secondo chiamante in StockItem_DAL che invoca STK_ADDITEMMOVE con soli 5 parametri di input + OUT, incompatibile con la firma attuale a 16 parametri: probabile codice legacy/morto o riferito a un overload non più presente.

  • Tipo: config-flag · Enforcement: BL · Confidence: INFERRED · customer_specific: false
  • Evidenza: StockDAL/OracleODP/StockItem_DAL.vb:301 e StockDAL/Oracle/StockItem_DAL.vb:328 (5 param IN); firma DB a 16 parametri STK_ADDITEMMOVE src:1-16.
  • Flag: possibile dead code / disallineamento firma — da verificare (vedi Open questions del flusso).

Sintesi flag di rischio

  • Regole UI-only (bypassabili via BL/DAL diretto): BR-STK-001, BR-STK-002, BR-STK-003, BR-STK-004, BR-STK-010, BR-STK-011, BR-STK-012, BR-STK-014, BR-STK-015. Le regole di quantità/segno/giacenza (BR-STK-010/011/014/015) non sono ripetute a livello DB (vedi BR-STK-025): la procedura può portare la giacenza in negativo se chiamata direttamente.
  • Regole DB-only: BR-STK-006, BR-STK-007, BR-STK-008 (semantica direzione), BR-STK-016 (trigger+sequence), BR-STK-018, BR-STK-020, BR-STK-021, BR-STK-023, BR-STK-026, BR-STK-027. La UI non replica il ricalcolo della giacenza.
  • Magic numbers / hard-coded ids: grant 6410/6420/6430/6440/6450 (BR-STK-001..004); direzioni 1/2/3/4 replicate su UI/BL/DB senza costante condivisa lato DB (BR-STK-005); ISCANCELLED = 1 (BR-STK-023); strong-name "RequestCenter" (BR-STK-019).
  • Hidden flags: STOCK_STOCK.ISPRICELIST (BR-STK-007), tenancy IsTenantEnabled (BR-STK-022).
  • Date-sensitive: BR-STK-009 (MOVE_DATE = SYSDATE, data movimento non modificabile).
  • Gap funzionali: BR-STK-023 (annullamento inventario non stornato), BR-STK-025 (nessuna guardia under-zero), BR-STK-026 (NO_DATA_FOUND su articolo senza stock), BR-STK-027 (update non riallinea la giacenza).
  • Customer-specific: nessuna regola risulta variante per cliente in questo dominio (il modulo Stock non usa il motore parametrico DynamicParametric osservato in RequestCenter).

Open questions

  • BR-STK-002: in ramo di nuovo record (MovementDetailControl.ascx.vb:210) la visibilità del Save è talvolta legata al grant 6430 anziché 6420: verificare se intenzionale o refuso.
  • BR-STK-023: l'annullamento logico di un movimento di inventario (dir. 4) non ripristina la giacenza precedente — confermare se è comportamento voluto (l'inventario è una rettifica assoluta) o una lacuna.
  • BR-STK-027: la modifica di quantità/direzione di un movimento esistente non ricalcola QSTOCK/QORDERED — verificare se l'update è ammesso solo per campi non impattanti (note/costo/collegamento).
  • BR-STK-028: confermare se il chiamante a 5 parametri di StockItem_DAL è codice morto.
  • Comportamento in configurazione PROD (osservato solo ambiente TEST38).