From 0666d6bcdf6a9479b35a8f79bfe15e63d4b70394 Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Fri, 20 Mar 2026 11:57:57 +0000 Subject: [PATCH] fix: defer kit discount insertion to avoid duplicate check collision (separate_line) When 2+ kits produce discount lines with the same unit price and VAT rate, adauga_articol_comanda raises RAISE_APPLICATION_ERROR(-20000) on the duplicate (ID_ARTICOL, PTVA, PRET, SIGN(CANTITATE)) check. Defer discount insertion until after the main article loop, accumulating cross-kit discounts and merging collisions by summing qty. Different prices remain as separate lines. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../06_pack_import_comenzi.pck | 124 ++++++++++++------ 1 file changed, 84 insertions(+), 40 deletions(-) diff --git a/api/database-scripts/06_pack_import_comenzi.pck b/api/database-scripts/06_pack_import_comenzi.pck index 109a421..f9dd7e1 100644 --- a/api/database-scripts/06_pack_import_comenzi.pck +++ b/api/database-scripts/06_pack_import_comenzi.pck @@ -61,6 +61,7 @@ -- DBMS_OUTPUT.PUT_LINE('ID comanda: ' || v_id); -- END; -- 20.03.2026 - dual policy vanzare/productie, kit pricing distributed/separate_line, SKU→CODMAT via ARTICOLE_TERTI +-- 20.03.2026 - kit discount deferred cross-kit (separate_line, merge-on-collision) -- ==================================================================== CREATE OR REPLACE PACKAGE PACK_IMPORT_COMENZI AS @@ -217,6 +218,17 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS v_pret_ajustat NUMBER; v_discount_allocated NUMBER; + -- Acumulare discount-uri kit cross-kit (separate_line, deferred insertion) + TYPE t_kit_disc_entry IS RECORD ( + ptva NUMBER, + pret NUMBER, -- pret unitar (disc_amt / cantitate_web) + qty NUMBER -- cantitate negativa acumulata + ); + TYPE t_kit_disc_list IS TABLE OF t_kit_disc_entry INDEX BY PLS_INTEGER; + v_kit_disc_list t_kit_disc_list; + v_kit_disc_count PLS_INTEGER := 0; + v_kit_disc_found BOOLEAN; + -- pljson l_json_articole CLOB := p_json_articole; v_json_arr pljson_list; @@ -448,16 +460,16 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS END LOOP; ELSIF p_kit_mode = 'separate_line' THEN - -- Mode B: componente la pret plin + linii discount separate pe cota TVA + -- Mode B: componente la pret plin, discount deferred cross-kit DECLARE TYPE t_vat_discount IS TABLE OF NUMBER INDEX BY PLS_INTEGER; v_vat_disc t_vat_discount; v_vat_key PLS_INTEGER; - v_disc_artid NUMBER; v_vat_disc_alloc NUMBER; v_disc_amt NUMBER; + v_unit_pret NUMBER; BEGIN - -- Inserare componente la pret plin + acumulare discount pe cota TVA + -- Inserare componente la pret plin + acumulare discount pe cota TVA (per kit) FOR i_comp IN 1 .. v_kit_comps.COUNT LOOP IF v_kit_comps(i_comp).id_articol IS NOT NULL THEN BEGIN @@ -479,7 +491,7 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS v_kit_comps(i_comp).codmat || ': ' || SQLERRM; END; - -- Acumuleaza discountul pe cota TVA (proportional cu valoarea componentei) + -- Acumuleaza discountul pe cota TVA (per kit, local) v_vat_key := v_kit_comps(i_comp).ptva; IF v_sum_list_prices != 0 THEN IF v_vat_disc.EXISTS(v_vat_key) THEN @@ -497,45 +509,43 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS END IF; END LOOP; - -- Rezolva articolul discount si insereaza liniile de discount - v_disc_artid := resolve_id_articol(p_kit_discount_codmat, p_id_gestiune); + -- Merge per-kit discounts into cross-kit list (v_kit_disc_list) + v_vat_disc_alloc := 0; + v_vat_key := v_vat_disc.FIRST; + WHILE v_vat_key IS NOT NULL LOOP + -- Remainder trick per kit + IF v_vat_key = v_vat_disc.LAST THEN + v_disc_amt := v_discount_total - v_vat_disc_alloc; + ELSE + v_disc_amt := v_vat_disc(v_vat_key); + v_vat_disc_alloc := v_vat_disc_alloc + v_disc_amt; + END IF; - IF v_disc_artid IS NOT NULL AND v_vat_disc.COUNT > 0 THEN - v_vat_disc_alloc := 0; - v_vat_key := v_vat_disc.FIRST; - WHILE v_vat_key IS NOT NULL LOOP - -- Ultima cota TVA primeste remainder pentru precizie exacta - IF v_vat_key = v_vat_disc.LAST THEN - v_disc_amt := v_discount_total - v_vat_disc_alloc; - ELSE - v_disc_amt := v_vat_disc(v_vat_key); - v_vat_disc_alloc := v_vat_disc_alloc + v_disc_amt; + IF v_disc_amt != 0 THEN + v_unit_pret := v_disc_amt / v_cantitate_web; + + -- Search for existing entry with same (ptva, pret) to merge qty + v_kit_disc_found := FALSE; + FOR j IN 1 .. v_kit_disc_count LOOP + IF v_kit_disc_list(j).ptva = v_vat_key + AND v_kit_disc_list(j).pret = v_unit_pret THEN + v_kit_disc_list(j).qty := v_kit_disc_list(j).qty + (-1 * v_cantitate_web); + v_kit_disc_found := TRUE; + EXIT; + END IF; + END LOOP; + + IF NOT v_kit_disc_found THEN + v_kit_disc_count := v_kit_disc_count + 1; + v_kit_disc_list(v_kit_disc_count).ptva := v_vat_key; + v_kit_disc_list(v_kit_disc_count).pret := v_unit_pret; + v_kit_disc_list(v_kit_disc_count).qty := -1 * v_cantitate_web; END IF; + END IF; - IF v_disc_amt != 0 THEN - BEGIN - PACK_COMENZI.adauga_articol_comanda( - V_ID_COMANDA => v_id_comanda, - V_ID_ARTICOL => v_disc_artid, - V_ID_POL => NVL(p_kit_discount_id_pol, p_id_pol), - V_CANTITATE => -1 * v_cantitate_web, - V_PRET => v_disc_amt / v_cantitate_web, - V_ID_UTIL => c_id_util, - V_ID_SECTIE => p_id_sectie, - V_PTVA => v_vat_key); - v_articole_procesate := v_articole_procesate + 1; - EXCEPTION - WHEN OTHERS THEN - v_articole_eroare := v_articole_eroare + 1; - g_last_error := g_last_error || CHR(10) || - 'Eroare linie discount kit TVA=' || v_vat_key || '%: ' || SQLERRM; - END; - END IF; - - v_vat_key := v_vat_disc.NEXT(v_vat_key); - END LOOP; - END IF; - END; -- end mode B block + v_vat_key := v_vat_disc.NEXT(v_vat_key); + END LOOP; + END; -- end mode B per-kit block END IF; -- end kit mode branching ELSE @@ -619,6 +629,40 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS END LOOP; + -- ============================================================ + -- INSERARE DISCOUNT-URI KIT DEFERRED (separate_line) + -- Linii cu preturi diferite raman separate, coliziuni merged pe qty + -- ============================================================ + IF p_kit_mode = 'separate_line' AND v_kit_disc_count > 0 THEN + DECLARE + v_disc_artid NUMBER; + BEGIN + v_disc_artid := resolve_id_articol(p_kit_discount_codmat, p_id_gestiune); + + IF v_disc_artid IS NOT NULL THEN + FOR j IN 1 .. v_kit_disc_count LOOP + BEGIN + PACK_COMENZI.adauga_articol_comanda( + V_ID_COMANDA => v_id_comanda, + V_ID_ARTICOL => v_disc_artid, + V_ID_POL => NVL(p_kit_discount_id_pol, p_id_pol), + V_CANTITATE => v_kit_disc_list(j).qty, + V_PRET => v_kit_disc_list(j).pret, + V_ID_UTIL => c_id_util, + V_ID_SECTIE => p_id_sectie, + V_PTVA => v_kit_disc_list(j).ptva); + v_articole_procesate := v_articole_procesate + 1; + EXCEPTION + WHEN OTHERS THEN + v_articole_eroare := v_articole_eroare + 1; + g_last_error := g_last_error || CHR(10) || + 'Eroare linie discount kit TVA=' || v_kit_disc_list(j).ptva || '%: ' || SQLERRM; + END; + END LOOP; + END IF; + END; + END IF; + -- Verifica daca s-au procesat articole cu succes IF v_articole_procesate = 0 THEN g_last_error := g_last_error || CHR(10) || 'IMPORTA_COMANDA ' ||