diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index bf8f8bc231..6709a52b42 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,33 +35,42 @@ jobs: # Install Java - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '${{ env.java }}' distribution: ${{ env.java_distribution }} + check-latest: true + + # setup maven to 3.9 for tycho + - name: Set up Maven + uses: stCarolas/setup-maven@v5 + with: + maven-version: 3.9.6 # on case PR it check out to commit is merger of PR to base (master) - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # fetch all commit so sornar can know who change a line, it's resolved Warning: Shallow clone detected, no blame information will be provided. You can convert to non-shallow with 'git fetch --unshallow'. fetch-depth: 0 + # restore sonar cache - name: Cache sonar material restore id: cache-sonar-material-restore - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: | ~/.sonar/cache key: ${{ runner.os }}-sonar-${{ env.branch_name }} + # restore maven cache - name: Cache maven material restore id: cache-maven-material-restore - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: | - ~/.m2 - key: ${{ runner.os }}-maven-${{ env.branch_name }} + ~/.m2/repository + key: ${{ runner.os }}-maven-${{ env.branch_name }}-repository # run sonar on master only because sonar for PR come from other repository isn't support at moment (already on develop) # https://stackoverflow.com/a/39720346 @@ -78,19 +87,21 @@ jobs: codeql="-Dfindbugs.skip -Dcheckstyle.skip -Dpmd.skip=true -Denforcer.skip -Dmaven.javadoc.skip -DskipTests -Dmaven.test.skip.exec -Dlicense.skip=true -Drat.skip=true" mvn -B -V -e $codeql $sonar $sonarProject $sonarExclusions package org.sonarsource.scanner.maven:sonar-maven-plugin:sonar + # save sonar cache - name: Cache sonar material save - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: | ~/.sonar/cache key: ${{ steps.cache-sonar-material-restore.outputs.cache-primary-key }} - + + # save maven cache - name: Cache maven material save - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: | - ~/.m2 - key: ${{ steps.cache-maven-material-restore.outputs.cache-primary-key }} + ~/.m2/repository + key: ${{ steps.cache-maven-material-restore.outputs.cache-primary-key }}-repository analyze_java_codeQL: name: Analyze java by code QL @@ -103,26 +114,34 @@ jobs: # Install Java - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '${{ env.java }}' distribution: ${{ env.java_distribution }} + check-latest: true + + # setup maven to 3.9 for tycho + - name: Set up Maven + uses: stCarolas/setup-maven@v5 + with: + maven-version: 3.9.6 # on case PR it check out to commit is merger of PR to base (master) - name: Checkout repository - uses: actions/checkout@v3 - + uses: actions/checkout@v4 + + # restore maven cache - name: Cache maven material restore id: cache-maven-material-restore - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: | - ~/.m2 - key: ${{ runner.os }}-maven-${{ env.branch_name }} + ~/.m2/repository + key: ${{ runner.os }}-maven-${{ env.branch_name }}-repository # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: java # If you wish to specify custom queries, you can do so here or in a config file. @@ -131,17 +150,18 @@ jobs: # queries: ./path/to/local/query, your-org/your-repo/queries@main - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 + # save maven cache - name: Cache maven material save - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: | - ~/.m2 - key: ${{ steps.cache-maven-material-restore.outputs.cache-primary-key }} + ~/.m2/repository + key: ${{ steps.cache-maven-material-restore.outputs.cache-primary-key }}-repository analyze_javascript_codeQL: @@ -155,12 +175,12 @@ jobs: # on case PR it check out to commit is merger of PR to base (master) - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: javascript - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/db/oracle/functions/BOM_PriceLimit.sql b/db/oracle/functions/BOM_PriceLimit.sql index 8ca102b31c..f946df8932 100644 --- a/db/oracle/functions/BOM_PriceLimit.sql +++ b/db/oracle/functions/BOM_PriceLimit.sql @@ -27,6 +27,7 @@ AS WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=Product_ID AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') AND b.IsActive='Y'; -- BEGIN @@ -34,7 +35,7 @@ BEGIN SELECT COALESCE (SUM(PriceLimit), 0) INTO v_Price FROM M_PRODUCTPRICE - WHERE M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; -- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); -- No Price - Check if BOM diff --git a/db/oracle/functions/BOM_PriceList.sql b/db/oracle/functions/BOM_PriceList.sql index b9590f4501..20a634f5d8 100644 --- a/db/oracle/functions/BOM_PriceList.sql +++ b/db/oracle/functions/BOM_PriceList.sql @@ -27,6 +27,7 @@ AS WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=Product_ID AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') AND b.IsActive='Y'; -- BEGIN @@ -34,7 +35,7 @@ BEGIN SELECT COALESCE (SUM(PriceList), 0) INTO v_Price FROM M_PRODUCTPRICE - WHERE M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; -- DBMS_OUTPUT.PUT_LINE('Price=' || Price); -- No Price - Check if BOM diff --git a/db/oracle/functions/BOM_PriceStd.sql b/db/oracle/functions/BOM_PriceStd.sql index 143dbf87d6..528ee669b3 100644 --- a/db/oracle/functions/BOM_PriceStd.sql +++ b/db/oracle/functions/BOM_PriceStd.sql @@ -27,6 +27,7 @@ AS WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=Product_ID AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') AND b.IsActive='Y'; -- BEGIN @@ -34,7 +35,7 @@ BEGIN SELECT COALESCE(SUM(PriceStd), 0) INTO v_Price FROM M_PRODUCTPRICE - WHERE M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; -- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); -- No Price - Check if BOM diff --git a/db/oracle/functions/BOM_Qty_OnHand.sql b/db/oracle/functions/BOM_Qty_OnHand.sql index 285c5ffe5a..e199e43bc4 100644 --- a/db/oracle/functions/BOM_Qty_OnHand.sql +++ b/db/oracle/functions/BOM_Qty_OnHand.sql @@ -23,13 +23,11 @@ AS StdPrecision NUMBER; -- Get BOM Product info CURSOR CUR_BOM IS - SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified FROM M_PRODUCT_BOM b, M_PRODUCT p WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=Product_ID AND b.M_ProductBOM_ID != Product_ID - AND p.IsBOM='Y' - AND p.IsVerified='Y' AND b.IsActive='Y'; -- BEGIN @@ -87,19 +85,14 @@ BEGIN FROM M_Storageonhand s JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID; - -- Get Rounding Precision - SELECT NVL(MAX(u.StdPrecision), 0) - INTO StdPrecision - FROM C_UOM u, M_PRODUCT p - WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; -- How much can we make with this product - ProductQty := ROUND (ProductQty/bom.BOMQty, StdPrecision); + ProductQty := ProductQty/bom.BOMQty; -- How much can we make overall IF (ProductQty < Quantity) THEN Quantity := ProductQty; END IF; -- Another BOM - ELSIF (bom.IsBOM = 'Y') THEN + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN ProductQty := Bomqtyonhand (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); -- How much can we make overall IF (ProductQty < Quantity) THEN @@ -115,7 +108,7 @@ BEGIN FROM C_UOM u, M_PRODUCT p WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; -- - RETURN ROUND (Quantity, StdPrecision); + RETURN TRUNC(Quantity, StdPrecision); -- RoundDown END IF; RETURN 0; END Bomqtyonhand; diff --git a/db/oracle/functions/BOM_Qty_OnHandForReservation.sql b/db/oracle/functions/BOM_Qty_OnHandForReservation.sql index 125893a0e1..e7160d8658 100644 --- a/db/oracle/functions/BOM_Qty_OnHandForReservation.sql +++ b/db/oracle/functions/BOM_Qty_OnHandForReservation.sql @@ -23,13 +23,11 @@ AS StdPrecision NUMBER; -- Get BOM Product info CURSOR CUR_BOM IS - SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified FROM M_PRODUCT_BOM b, M_PRODUCT p WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=Product_ID AND b.M_ProductBOM_ID != Product_ID - AND p.IsBOM='Y' - AND p.IsVerified='Y' AND b.IsActive='Y'; -- BEGIN @@ -90,20 +88,15 @@ BEGIN JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) LEFT JOIN M_LocatorType lt ON (l.M_LocatorType_ID=lt.M_LocatorType_ID) WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID - AND COALESCE(lt.IsAvailableForReservation,'Y')='Y'; - -- Get Rounding Precision - SELECT NVL(MAX(u.StdPrecision), 0) - INTO StdPrecision - FROM C_UOM u, M_PRODUCT p - WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; + AND COALESCE(lt.IsAvailableForReservation,'Y')='Y'; -- How much can we make with this product - ProductQty := ROUND (ProductQty/bom.BOMQty, StdPrecision); + ProductQty := ProductQty/bom.BOMQty; -- How much can we make overall IF (ProductQty < Quantity) THEN Quantity := ProductQty; END IF; -- Another BOM - ELSIF (bom.IsBOM = 'Y') THEN + ELSIF (bom.IsBOM = 'Y' AND bom.IsVerified = 'Y') THEN ProductQty := BomqtyonhandForReservation (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); -- How much can we make overall IF (ProductQty < Quantity) THEN @@ -119,7 +112,7 @@ BEGIN FROM C_UOM u, M_PRODUCT p WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; -- - RETURN ROUND (Quantity, StdPrecision); + RETURN TRUNC(Quantity, StdPrecision); -- RoundDown END IF; RETURN 0; END BOMQTYONHANDFORRESERVATION; diff --git a/db/oracle/functions/BOM_Qty_Ordered.sql b/db/oracle/functions/BOM_Qty_Ordered.sql index 3fb7ec0651..a7d89aee11 100644 --- a/db/oracle/functions/BOM_Qty_Ordered.sql +++ b/db/oracle/functions/BOM_Qty_Ordered.sql @@ -23,13 +23,11 @@ AS v_StdPrecision NUMBER; -- Get BOM Product info CURSOR CUR_BOM IS - SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified FROM M_PRODUCT_BOM b, M_PRODUCT p WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=p_Product_ID AND b.M_ProductBOM_ID != p_Product_ID - AND p.IsBOM='Y' - AND p.IsVerified='Y' AND b.IsActive='Y'; -- BEGIN @@ -91,19 +89,14 @@ BEGIN AND M_Warehouse_ID=v_Warehouse_ID AND IsSOTrx='N' AND IsActive='Y'; - -- Get Rounding Precision - SELECT NVL(MAX(u.StdPrecision), 0) - INTO v_StdPrecision - FROM C_UOM u, M_PRODUCT p - WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; -- How much can we make with this product - v_ProductQty := ROUND (v_ProductQty/bom.BOMQty, v_StdPrecision); + v_ProductQty := v_ProductQty/bom.BOMQty; -- How much can we make overall IF (v_ProductQty < v_Quantity) THEN v_Quantity := v_ProductQty; END IF; -- Another BOM - ELSIF (bom.IsBOM = 'Y') THEN + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN v_ProductQty := Bomqtyordered (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); -- How much can we make overall IF (v_ProductQty < v_Quantity) THEN @@ -124,7 +117,7 @@ BEGIN FROM C_UOM u, M_PRODUCT p WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; -- - RETURN ROUND (v_Quantity, v_StdPrecision); + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown END IF; -- RETURN 0; diff --git a/db/oracle/functions/BOM_Qty_Reserved.sql b/db/oracle/functions/BOM_Qty_Reserved.sql index 1d07949d8d..812f5fd3b3 100644 --- a/db/oracle/functions/BOM_Qty_Reserved.sql +++ b/db/oracle/functions/BOM_Qty_Reserved.sql @@ -23,13 +23,11 @@ AS v_StdPrecision NUMBER; -- Get BOM Product info CURSOR CUR_BOM IS - SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified FROM M_PRODUCT_BOM b, M_PRODUCT p WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=p_Product_ID AND b.M_ProductBOM_ID != p_Product_ID - AND p.IsBOM='Y' - AND p.IsVerified='Y' AND b.IsActive='Y'; -- BEGIN @@ -91,19 +89,14 @@ BEGIN AND M_Warehouse_ID=v_Warehouse_ID AND IsSOTrx='Y' AND IsActive='Y'; - -- Get Rounding Precision - SELECT NVL(MAX(u.StdPrecision), 0) - INTO v_StdPrecision - FROM C_UOM u, M_PRODUCT p - WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; -- How much can we make with this product - v_ProductQty := ROUND (v_ProductQty/bom.BOMQty, v_StdPrecision); + v_ProductQty := v_ProductQty/bom.BOMQty; -- How much can we make overall IF (v_ProductQty < v_Quantity) THEN v_Quantity := v_ProductQty; END IF; -- Another BOM - ELSIF (bom.IsBOM = 'Y') THEN + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN v_ProductQty := Bomqtyreserved (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); -- How much can we make overall IF (v_ProductQty < v_Quantity) THEN @@ -124,7 +117,7 @@ BEGIN FROM C_UOM u, M_PRODUCT p WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; -- - RETURN ROUND (v_Quantity, v_StdPrecision); + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown END IF; RETURN 0; END Bomqtyreserved; diff --git a/db/postgresql/functions/BOM_PriceLimit.sql b/db/postgresql/functions/BOM_PriceLimit.sql index 216dce20a5..fb22fcf3a6 100644 --- a/db/postgresql/functions/BOM_PriceLimit.sql +++ b/db/postgresql/functions/BOM_PriceLimit.sql @@ -10,7 +10,7 @@ BEGIN SELECT COALESCE (SUM(PriceLimit), 0) INTO v_Price FROM M_ProductPrice - WHERE M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; -- No Price - Check if BOM IF (v_Price = 0) THEN @@ -20,6 +20,7 @@ BEGIN WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=Product_ID AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') AND b.IsActive='Y' LOOP v_ProductPrice := bomPriceLimit (bom.M_ProductBOM_ID, PriceList_Version_ID); @@ -30,7 +31,6 @@ BEGIN RETURN v_Price; END; - $BODY$ LANGUAGE 'plpgsql' STABLE ; diff --git a/db/postgresql/functions/BOM_PriceList.sql b/db/postgresql/functions/BOM_PriceList.sql index 03f69e0dae..d0f007fafa 100644 --- a/db/postgresql/functions/BOM_PriceList.sql +++ b/db/postgresql/functions/BOM_PriceList.sql @@ -10,7 +10,7 @@ BEGIN SELECT COALESCE (SUM(PriceList), 0) INTO v_Price FROM M_ProductPrice - WHERE M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; -- No Price - Check if BOM IF (v_Price = 0) THEN @@ -20,6 +20,7 @@ BEGIN WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=Product_ID AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') AND b.IsActive='Y' LOOP v_ProductPrice := bomPriceList (bom.M_ProductBOM_ID, PriceList_Version_ID); @@ -30,7 +31,6 @@ BEGIN RETURN v_Price; END; - $BODY$ LANGUAGE 'plpgsql' STABLE ; diff --git a/db/postgresql/functions/BOM_PriceStd.sql b/db/postgresql/functions/BOM_PriceStd.sql index 5c34e42dca..bf3a3f0711 100644 --- a/db/postgresql/functions/BOM_PriceStd.sql +++ b/db/postgresql/functions/BOM_PriceStd.sql @@ -10,7 +10,7 @@ BEGIN SELECT COALESCE(SUM(PriceStd), 0) INTO v_Price FROM M_ProductPrice - WHERE M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; -- No Price - Check if BOM IF (v_Price = 0) THEN @@ -20,6 +20,7 @@ BEGIN WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=Product_ID AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') AND b.IsActive='Y' LOOP v_ProductPrice := bomPriceStd (bom.M_ProductBOM_ID, PriceList_Version_ID); @@ -30,7 +31,6 @@ BEGIN RETURN v_Price; END; - $BODY$ LANGUAGE 'plpgsql' STABLE ; diff --git a/db/postgresql/functions/BOM_Qty_OnHand.sql b/db/postgresql/functions/BOM_Qty_OnHand.sql index 697b2de662..b6060f6a40 100644 --- a/db/postgresql/functions/BOM_Qty_OnHand.sql +++ b/db/postgresql/functions/BOM_Qty_OnHand.sql @@ -54,13 +54,11 @@ BEGIN -- Go through BOM FOR bom IN -- Get BOM Product info - SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified FROM M_PRODUCT_BOM b, M_PRODUCT p WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=product_ID AND b.M_ProductBOM_ID != Product_ID - AND p.IsBOM='Y' - AND p.IsVerified='Y' AND b.IsActive='Y' LOOP -- Stocked Items "leaf node" @@ -71,19 +69,14 @@ BEGIN FROM M_Storageonhand s JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID; - -- Get Rounding Precision - SELECT COALESCE(MAX(u.StdPrecision), 0) - INTO v_StdPrecision - FROM C_UOM u, M_PRODUCT p - WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; -- How much can we make with this product - v_ProductQty := ROUND (v_ProductQty/bom.BOMQty, v_StdPrecision); + v_ProductQty := v_ProductQty/bom.BOMQty; -- How much can we make overall IF (v_ProductQty < v_Quantity) THEN v_Quantity := v_ProductQty; END IF; -- Another BOM - ELSIF (bom.IsBOM = 'Y') THEN + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN v_ProductQty := Bomqtyonhand (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); -- How much can we make overall IF (v_ProductQty < v_Quantity) THEN @@ -99,7 +92,7 @@ BEGIN FROM C_UOM u, M_PRODUCT p WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; -- - RETURN ROUND (v_Quantity, v_StdPrecision); + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown END IF; RETURN 0; END; diff --git a/db/postgresql/functions/BOM_Qty_OnHandForReservation.sql b/db/postgresql/functions/BOM_Qty_OnHandForReservation.sql index 6cde685873..a3b8cf7555 100644 --- a/db/postgresql/functions/BOM_Qty_OnHandForReservation.sql +++ b/db/postgresql/functions/BOM_Qty_OnHandForReservation.sql @@ -56,13 +56,11 @@ BEGIN -- Go through BOM FOR bom IN -- Get BOM Product info - SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified FROM M_PRODUCT_BOM b, M_PRODUCT p WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=product_ID AND b.M_ProductBOM_ID != Product_ID - AND p.IsBOM='Y' - AND p.IsVerified='Y' AND b.IsActive='Y' LOOP -- Stocked Items "leaf node" @@ -75,19 +73,14 @@ BEGIN LEFT JOIN M_LocatorType lt ON (l.M_LocatorType_ID=lt.M_LocatorType_ID) WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID AND COALESCE(lt.IsAvailableForReservation,'Y')='Y'; - -- Get Rounding Precision - SELECT COALESCE(MAX(u.StdPrecision), 0) - INTO v_StdPrecision - FROM C_UOM u, M_PRODUCT p - WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; -- How much can we make with this product - v_ProductQty := ROUND (v_ProductQty/bom.BOMQty, v_StdPrecision); + v_ProductQty := v_ProductQty/bom.BOMQty; -- How much can we make overall IF (v_ProductQty < v_Quantity) THEN v_Quantity := v_ProductQty; END IF; -- Another BOM - ELSIF (bom.IsBOM = 'Y') THEN + ELSIF (bom.IsBOM = 'Y' AND bom.IsVerified = 'Y') THEN v_ProductQty := BOMQtyOnHandForReservation (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); -- How much can we make overall IF (v_ProductQty < v_Quantity) THEN @@ -103,7 +96,7 @@ BEGIN FROM C_UOM u, M_PRODUCT p WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; -- - RETURN ROUND (v_Quantity, v_StdPrecision); + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown END IF; RETURN 0; END; diff --git a/db/postgresql/functions/BOM_Qty_Ordered.sql b/db/postgresql/functions/BOM_Qty_Ordered.sql index 79c0fda12e..d2bc166dbd 100644 --- a/db/postgresql/functions/BOM_Qty_Ordered.sql +++ b/db/postgresql/functions/BOM_Qty_Ordered.sql @@ -57,13 +57,11 @@ BEGIN -- Go though BOM FOR bom IN -- Get BOM Product info - SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified FROM M_PRODUCT_BOM b, M_PRODUCT p WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=p_Product_ID AND b.M_ProductBOM_ID != p_Product_ID - AND p.IsBOM='Y' - AND p.IsVerified='Y' AND b.IsActive='Y' LOOP -- Stocked Items "leaf node" @@ -76,20 +74,15 @@ BEGIN AND M_Warehouse_ID=v_Warehouse_ID AND IsSOTrx='N' AND IsActive='Y'; - -- Get Rounding Precision - SELECT COALESCE(MAX(u.StdPrecision), 0) - INTO v_StdPrecision - FROM C_UOM u, M_PRODUCT p - WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; -- How much can we make with this product - v_ProductQty := ROUND (v_ProductQty/bom.BOMQty, v_StdPrecision ); + v_ProductQty := v_ProductQty/bom.BOMQty; -- How much can we make overall IF (v_ProductQty < v_Quantity) THEN v_Quantity := v_ProductQty; END IF; -- Another BOM - ELSIF (bom.IsBOM = 'Y') THEN + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN v_ProductQty := Bomqtyordered (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); -- How much can we make overall IF (v_ProductQty < v_Quantity) THEN @@ -110,11 +103,12 @@ BEGIN FROM C_UOM u, M_PRODUCT p WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; -- - RETURN ROUND (v_Quantity, v_StdPrecision ); + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown END IF; -- RETURN 0; END; $BODY$ - LANGUAGE plpgsql STABLE; +LANGUAGE 'plpgsql' STABLE +; diff --git a/db/postgresql/functions/BOM_Qty_Reserved.sql b/db/postgresql/functions/BOM_Qty_Reserved.sql index 2f2b4f428a..e93f19044d 100644 --- a/db/postgresql/functions/BOM_Qty_Reserved.sql +++ b/db/postgresql/functions/BOM_Qty_Reserved.sql @@ -57,13 +57,11 @@ BEGIN -- Go though BOM FOR bom IN -- Get BOM Product info - SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified FROM M_PRODUCT_BOM b, M_PRODUCT p WHERE b.M_ProductBOM_ID=p.M_Product_ID AND b.M_Product_ID=p_Product_ID AND b.M_ProductBOM_ID != p_Product_ID - AND p.IsBOM='Y' - AND p.IsVerified='Y' AND b.IsActive='Y' LOOP -- Stocked Items "leaf node" @@ -76,19 +74,14 @@ BEGIN AND M_Warehouse_ID =v_Warehouse_ID AND IsSOTrx='Y' AND IsActive='Y'; - -- Get Rounding Precision - SELECT COALESCE(MAX(u.StdPrecision), 0) - INTO v_StdPrecision - FROM C_UOM u, M_PRODUCT p - WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; -- How much can we make with this product - v_ProductQty := ROUND (v_ProductQty/bom.BOMQty, v_StdPrecision); + v_ProductQty := v_ProductQty/bom.BOMQty; -- How much can we make overall IF (v_ProductQty < v_Quantity) THEN v_Quantity := v_ProductQty; END IF; -- Another BOM - ELSIF (bom.IsBOM = 'Y') THEN + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN v_ProductQty := Bomqtyreserved (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); -- How much can we make overall IF (v_ProductQty < v_Quantity) THEN @@ -109,11 +102,11 @@ BEGIN FROM C_UOM u, M_PRODUCT p WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; -- - RETURN ROUND (v_Quantity, v_StdPrecision); + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown END IF; RETURN 0; END; $BODY$ - LANGUAGE plpgsql STABLE; - +LANGUAGE 'plpgsql' STABLE +; diff --git a/db/postgresql/functions/ProductAttribute.sql b/db/postgresql/functions/ProductAttribute.sql index 426b74fefe..3f24880458 100644 --- a/db/postgresql/functions/ProductAttribute.sql +++ b/db/postgresql/functions/ProductAttribute.sql @@ -4,7 +4,8 @@ CREATE OR REPLACE FUNCTION ProductAttribute ( p_M_AttributeSetInstance_ID NUMERIC ) -RETURNS VARCHAR AS $body$ +RETURNS VARCHAR AS +$BODY$ /************************************************************************* * The contents of this file are subject to the Compiere License. You may @@ -87,6 +88,7 @@ BEGIN END IF; RETURN v_Name; END; - -$body$ LANGUAGE plpgsql STABLE; +$BODY$ +LANGUAGE 'plpgsql' STABLE +; diff --git a/db/postgresql/functions/dbreplicasyncverifier.sql b/db/postgresql/functions/dbreplicasyncverifier.sql new file mode 100644 index 0000000000..f02a615e22 --- /dev/null +++ b/db/postgresql/functions/dbreplicasyncverifier.sql @@ -0,0 +1,29 @@ +/* +CREATE TABLE dbreplicasyncverifier (lastupdate date) +; + +INSERT INTO dbreplicasyncverifier values (to_date('1900-01-01 00:00:00', 'yyyy-mm-dd HH24:MI:SS')) +; +*/ + +CREATE OR REPLACE FUNCTION forbid_multiple_rows_in_dbreplicasyncverifier() +RETURNS TRIGGER AS $$ +BEGIN + -- Check if the table already contains a row + IF (SELECT COUNT(*) FROM dbreplicasyncverifier) > 0 THEN + RAISE EXCEPTION 'Table can only contain one row.'; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql +; + +CREATE TRIGGER single_row_only_trigger_dbreplicasyncverifier +BEFORE INSERT ON dbreplicasyncverifier +FOR EACH ROW +EXECUTE FUNCTION forbid_multiple_rows_in_dbreplicasyncverifier() +; + +CREATE OR REPLACE RULE delete_dbreplicasyncverifier AS ON DELETE TO dbreplicasyncverifier DO INSTEAD NOTHING +; + diff --git a/db/postgresql/functions/register_migration_script.sql b/db/postgresql/functions/register_migration_script.sql index 4d07a06bc4..5133534159 100644 --- a/db/postgresql/functions/register_migration_script.sql +++ b/db/postgresql/functions/register_migration_script.sql @@ -41,13 +41,6 @@ END; $BODY$ LANGUAGE plpgsql; -CREATE TABLE dual ( dummy char ); - -INSERT INTO dual values ( 'X' ); - -CREATE OR REPLACE RULE insert_dual AS ON INSERT TO dual DO INSTEAD NOTHING; - -CREATE OR REPLACE RULE update_dual AS ON UPDATE TO dual DO INSTEAD NOTHING; - -CREATE OR REPLACE RULE delete_dual AS ON DELETE TO dual DO INSTEAD NOTHING; +CREATE VIEW dual AS SELECT 'X'::varchar AS dummy +; diff --git a/migration/iD11/oracle/202402261300_IDEMPIERE-2981.sql b/migration/iD11/oracle/202402261300_IDEMPIERE-2981.sql new file mode 100644 index 0000000000..aab134c6d2 --- /dev/null +++ b/migration/iD11/oracle/202402261300_IDEMPIERE-2981.sql @@ -0,0 +1,10 @@ +-- IDEMPIERE-2981 - Implement JSON Field type +SELECT register_migration_script('202402261300_IDEMPIERE-2981.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Feb 26, 2024, 1:00:29 PM CET +INSERT INTO AD_Reference (AD_Reference_ID,Name,Description,ValidationType,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,EntityType,IsOrderByValue,AD_Reference_UU,ShowInactive) VALUES (200267,'JSON','JSON format values','D',0,0,'Y',TO_TIMESTAMP('2024-02-26 13:00:28','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:00:28','YYYY-MM-DD HH24:MI:SS'),100,'D','N','b6fcc751-edd8-4421-acd0-3cde02a9576d','N') +; + diff --git a/migration/iD11/oracle/202402261354_IDEMPIERE-2981.sql b/migration/iD11/oracle/202402261354_IDEMPIERE-2981.sql new file mode 100644 index 0000000000..d43ee4bb10 --- /dev/null +++ b/migration/iD11/oracle/202402261354_IDEMPIERE-2981.sql @@ -0,0 +1,30 @@ +-- IDEMPIERE-2981 - Implement JSON Field type +SELECT register_migration_script('202402261354_IDEMPIERE-2981.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Feb 26, 2024, 1:54:35 PM CET +INSERT INTO AD_Element (AD_Element_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,ColumnName,Name,Description,PrintName,EntityType,AD_Element_UU) VALUES (203924,0,0,'Y',TO_TIMESTAMP('2024-02-26 13:54:35','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:54:35','YYYY-MM-DD HH24:MI:SS'),100,'JsonData','JSON Data','The json field stores json data.','JSON Data','D','c4ea7a81-96a9-4a5d-bb87-e913e1c8ed48') +; + +-- Feb 26, 2024, 1:55:37 PM CET +INSERT INTO AD_Column (AD_Column_ID,Version,Name,Description,AD_Table_ID,ColumnName,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,IsHtml,IsPartitionKey) VALUES (216570,0,'JSON Data','The json field stores json data.',135,'JsonData',0,'N','N','N','N','N',0,'N',200267,0,0,'Y',TO_TIMESTAMP('2024-02-26 13:55:36','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:55:36','YYYY-MM-DD HH24:MI:SS'),100,203924,'Y','N','D','N','N','N','Y','927b83df-d161-4332-ad44-8ffed99e8cf4','Y',0,'N','N','N','N') +; + +-- Feb 28, 2024, 5:41:55 PM CET +ALTER TABLE Test ADD JsonData CLOB DEFAULT NULL CONSTRAINT test_jsondata_ij CHECK (JsonData IS JSON) +; + +-- Feb 26, 2024, 1:56:08 PM CET +INSERT INTO AD_Field (AD_Field_ID,Name,Description,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,ColumnSpan) VALUES (208472,'JSON Data','The json field stores json data.',152,216570,'Y',100,310,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2024-02-26 13:56:08','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:56:08','YYYY-MM-DD HH24:MI:SS'),100,'N','Y','D','e47ef529-71ba-4f9b-9014-b188e17e8ef4','Y',290,5) +; + +-- Feb 29, 2024, 1:52:50 PM CET +UPDATE AD_Field SET NumLines=5,Updated=TO_TIMESTAMP('2024-02-29 13:52:50','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208472 +; + +-- Feb 29, 2024, 2:07:30 PM CET +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','Invalid JSON',0,0,'Y',TO_TIMESTAMP('2024-02-29 14:07:30','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-29 14:07:30','YYYY-MM-DD HH24:MI:SS'),100,200876,'InvalidJSON','D','a263376f-a12e-4943-92f1-7d7ce8a67a2b') +; + diff --git a/migration/iD11/oracle/202404171125_IDEMPIERE-6110.sql b/migration/iD11/oracle/202404171125_IDEMPIERE-6110.sql new file mode 100644 index 0000000000..a603f62ddc --- /dev/null +++ b/migration/iD11/oracle/202404171125_IDEMPIERE-6110.sql @@ -0,0 +1,10 @@ +-- IDEMPIERE-6110 +SELECT register_migration_script('202404171125_IDEMPIERE-6110.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Apr 17, 2024, 11:25:53 AM BRT +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','Products or charges configured as ''Freight Product'' or ''Freight Charge'' cannot be added to this order due to the combination of delivery via rule and freight cost rule',0,0,'Y',TO_TIMESTAMP('2024-04-17 11:25:53','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-04-17 11:25:53','YYYY-MM-DD HH24:MI:SS'),100,200897,'FreightOrderLineNotAllowed','D','204c8bb9-d002-4beb-bbc3-1d1a11d7471d') +; + diff --git a/migration/iD11/oracle/202404220830_IDEMPIERE-5136_MissingDropIndex.sql b/migration/iD11/oracle/202404220830_IDEMPIERE-5136_MissingDropIndex.sql new file mode 100644 index 0000000000..f87d9fb8b7 --- /dev/null +++ b/migration/iD11/oracle/202404220830_IDEMPIERE-5136_MissingDropIndex.sql @@ -0,0 +1,8 @@ +-- IDEMPIERE-5136 +SELECT register_migration_script('202404220830_IDEMPIERE-5136_MissingDropIndex.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +DROP INDEX AD_MESSAGE_TRL_KEY +; diff --git a/migration/iD11/oracle/202404302320_IDEMPIERE-6123.sql b/migration/iD11/oracle/202404302320_IDEMPIERE-6123.sql new file mode 100644 index 0000000000..2d3aacce8e --- /dev/null +++ b/migration/iD11/oracle/202404302320_IDEMPIERE-6123.sql @@ -0,0 +1,22 @@ +-- IDEMPIERE-6123 Query in search window causing slowness and load spikes in the database (FHCA-5356) +SELECT register_migration_script('202404302320_IDEMPIERE-6123.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Apr 30, 2024, 11:20:08 PM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200244,0,0,TO_TIMESTAMP('2024-04-30 23:20:08','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-04-30 23:20:08','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','GRIDTABLE_INITIAL_COUNT_TIMEOUT_IN_SECONDS','1','Timeout for the initial count on windows','D','C','5fae1af7-74ca-41d8-bbd3-d506c6c23b6a') +; + +-- Apr 30, 2024, 11:22:16 PM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200245,0,0,TO_TIMESTAMP('2024-04-30 23:22:16','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-04-30 23:22:16','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','GLOBAL_MAX_QUERY_RECORDS','100000','Maximum number of records allowed to search in a window, can be overriden per Role or Tab','D','C','840fb67c-4609-41f2-9e20-e0ea9d839065') +; + +-- Apr 30, 2024, 11:23:28 PM CEST +UPDATE AD_Message SET MsgText='The query returned more records than allowed, consider adding more filters.',Updated=TO_TIMESTAMP('2024-04-30 23:23:28','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Message_ID=852 +; + +-- Apr 30, 2024, 11:24:06 PM CEST +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('I','The initial count query timed out, loading records ...',0,0,'Y',TO_TIMESTAMP('2024-04-30 23:24:06','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-04-30 23:24:06','YYYY-MM-DD HH24:MI:SS'),100,200887,'CountQueryTimeoutLoadBackground','D','988292d7-175f-41c2-b560-43d62b8326a9') +; + diff --git a/migration/iD11/oracle/202405071219_IDEMPIERE-6137.sql b/migration/iD11/oracle/202405071219_IDEMPIERE-6137.sql new file mode 100644 index 0000000000..8344ff4658 --- /dev/null +++ b/migration/iD11/oracle/202405071219_IDEMPIERE-6137.sql @@ -0,0 +1,10 @@ +-- IDEMPIERE-6137 Payment Rule does not appear in reports from Sales Order +SELECT register_migration_script('202405071219_IDEMPIERE-6137.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- May 7, 2024, 12:19:00 PM CEST +UPDATE AD_Column SET AD_Reference_Value_ID=195,Updated=TO_TIMESTAMP('2024-05-07 12:19:00','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Reference_ID=200012 AND COALESCE(AD_Reference_Value_ID,0)!=195 +; + diff --git a/migration/iD11/oracle/202405080101_IDEMPIERE-6123.sql b/migration/iD11/oracle/202405080101_IDEMPIERE-6123.sql new file mode 100644 index 0000000000..ffd792aa44 --- /dev/null +++ b/migration/iD11/oracle/202405080101_IDEMPIERE-6123.sql @@ -0,0 +1,22 @@ +-- IDEMPIERE-6123 Query in search window causing slowness and load spikes in the database (FHCA-5356) +SELECT register_migration_script('202405080101_IDEMPIERE-6123.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- May 8, 2024, 1:01:16 AM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200247,0,0,TO_TIMESTAMP('2024-05-08 01:01:16','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-05-08 01:01:16','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','REPORT_LOAD_TIMEOUT_IN_SECONDS','120','Timeout in seconds when loading a report','D','C','14e838b1-c25c-400e-b39c-61da9bf92099') +; + +-- May 8, 2024, 1:01:42 AM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200248,0,0,TO_TIMESTAMP('2024-05-08 01:01:41','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-05-08 01:01:41','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','GLOBAL_MAX_REPORT_RECORDS','100000','Max number of records allowed in a report','D','C','7030640a-1aa7-4ac7-a894-b4fe0dfde530') +; + +-- May 8, 2024, 1:06:19 AM CEST +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('I','The data query for the report took too much time to execute (over {0} seconds) exceeding the allowed limit',0,0,'Y',TO_TIMESTAMP('2024-05-08 01:06:18','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-05-08 01:06:18','YYYY-MM-DD HH24:MI:SS'),100,200893,'ReportQueryTimeout','D','5f17f55f-adbe-4d97-bf83-9447983b4946') +; + +-- May 8, 2024, 1:07:10 AM CEST +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('I','The report data exceeds the maximum limit of {0} rows',0,0,'Y',TO_TIMESTAMP('2024-05-08 01:07:09','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-05-08 01:07:09','YYYY-MM-DD HH24:MI:SS'),100,200894,'ReportMaxRowsReached','D','a4b55c31-0df0-4302-a62a-91cb7e79be0d') +; + diff --git a/migration/iD11/oracle/202405131534_IDEMPIERE-6040.sql b/migration/iD11/oracle/202405131534_IDEMPIERE-6040.sql new file mode 100644 index 0000000000..57a9109402 --- /dev/null +++ b/migration/iD11/oracle/202405131534_IDEMPIERE-6040.sql @@ -0,0 +1,25 @@ +-- IDEMPIERE-6040 Improvements for CSV import template +SELECT register_migration_script('202405131534_IDEMPIERE-6040.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- May 13, 2024, 3:34:49 PM CEST +UPDATE AD_Ref_List SET Name='Comma-separated values (CSV)',Updated=TO_TIMESTAMP('2024-05-13 15:34:49','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Ref_List_ID=200704 +; + +-- May 13, 2024, 3:35:58 PM CEST +UPDATE AD_Ref_List SET Name='Excel (XLS/XLSX)',Updated=TO_TIMESTAMP('2024-05-13 15:35:58','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Ref_List_ID=200706 +; + +-- May 13, 2024, 3:36:02 PM CEST +UPDATE AD_Ref_List SET IsActive='N',Updated=TO_TIMESTAMP('2024-05-13 15:36:02','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Ref_List_ID=200705 +; + +UPDATE AD_ImportTemplate SET ImportTemplateType='XLSX' WHERE ImportTemplateType='XLS' +; + +-- May 13, 2024, 3:59:49 PM CEST +UPDATE AD_Field SET SeqNo=120, ColumnSpan=2,Updated=TO_TIMESTAMP('2024-05-13 15:59:49','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208476 +; + diff --git a/migration/iD11/oracle/202405151228_IDEMPIERE-5728.sql b/migration/iD11/oracle/202405151228_IDEMPIERE-5728.sql new file mode 100644 index 0000000000..0e88421aed --- /dev/null +++ b/migration/iD11/oracle/202405151228_IDEMPIERE-5728.sql @@ -0,0 +1,10 @@ +-- IDEMPIERE-5728 +SELECT register_migration_script('202405151228_IDEMPIERE-5728.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- May 15, 2024, 12:28:30 PM CEST +UPDATE AD_ViewColumn SET ColumnName='RV_UnPosted_UU',Updated=TO_TIMESTAMP('2024-05-15 12:28:30','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_ViewColumn_ID=217690 +; + diff --git a/migration/iD11/oracle/202406072107_IDEMPIERE-6166.sql b/migration/iD11/oracle/202406072107_IDEMPIERE-6166.sql new file mode 100644 index 0000000000..fe015d0e68 --- /dev/null +++ b/migration/iD11/oracle/202406072107_IDEMPIERE-6166.sql @@ -0,0 +1,5 @@ +-- IDEMPIERE-6166 PostgreSQL DUAL table with more than one record +SELECT register_migration_script('202406072107_IDEMPIERE-6166.sql') FROM dual; + +-- placeholder, this is just required for postgresql + diff --git a/migration/iD11/oracle/202406072206_IDEMPIERE-6167.sql b/migration/iD11/oracle/202406072206_IDEMPIERE-6167.sql new file mode 100644 index 0000000000..52857fe3e4 --- /dev/null +++ b/migration/iD11/oracle/202406072206_IDEMPIERE-6167.sql @@ -0,0 +1,161 @@ +-- IDEMPIERE-6167 BOM Price List must search just for Verified BOMs +SELECT register_migration_script('202406072206_IDEMPIERE-6167.sql') FROM dual; + +CREATE OR REPLACE FUNCTION BOMPRICELIMIT +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceLimit.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return Limit Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from PriceList directly + SELECT COALESCE (SUM(PriceLimit), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricelimit (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; +END Bompricelimit; +/ + +CREATE OR REPLACE FUNCTION BOMPRICELIST +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceList.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return List Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE (SUM(PriceList), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricelist (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + -- DBMS_OUTPUT.PUT_LINE('Qry=' || bom.BOMQty || ' @ ' || v_ProductPrice || ', Price=' || v_Price); + END LOOP; -- BOM + END IF; + -- + RETURN v_Price; +END Bompricelist; +/ + +CREATE OR REPLACE FUNCTION BOMPRICESTD +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceStd.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return Standard Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE(SUM(PriceStd), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricestd (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + -- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + END LOOP; -- BOM + END IF; + -- + RETURN v_Price; +END Bompricestd; +/ + diff --git a/migration/iD11/oracle/202406131736_IDEMPIERE-6169.sql b/migration/iD11/oracle/202406131736_IDEMPIERE-6169.sql new file mode 100644 index 0000000000..f1564872e8 --- /dev/null +++ b/migration/iD11/oracle/202406131736_IDEMPIERE-6169.sql @@ -0,0 +1,12 @@ +-- IDEMPIERE-6169 Performance on AD_ChangeLog with Record_UU +SELECT register_migration_script('202406131736_IDEMPIERE-6169.sql') FROM dual; + +ALTER TABLE ad_changelog DROP CONSTRAINT ad_changelog_key +; + +DROP INDEX ad_changelog_key +; + +ALTER TABLE ad_changelog ADD CONSTRAINT ad_changelog_pkey PRIMARY KEY (ad_session_id, ad_column_id, ad_changelog_id) +; + diff --git a/migration/iD11/oracle/202406180027_IDEMPIERE-6176.sql b/migration/iD11/oracle/202406180027_IDEMPIERE-6176.sql new file mode 100644 index 0000000000..cdc959eda6 --- /dev/null +++ b/migration/iD11/oracle/202406180027_IDEMPIERE-6176.sql @@ -0,0 +1,22 @@ +-- IDEMPIERE-6176 UUID indexes without constraint +SELECT register_migration_script('202406180027_IDEMPIERE-6176.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Jun 18, 2024, 12:29:20 AM CEST +UPDATE AD_IndexColumn SET IsActive='N',Updated=TO_TIMESTAMP('2024-06-18 00:29:20','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_IndexColumn_ID=200980 +; + +-- Jun 18, 2024, 12:29:27 AM CEST +UPDATE AD_TableIndex SET IsActive='N', IsCreateConstraint='Y',Updated=TO_TIMESTAMP('2024-06-18 00:29:27','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_TableIndex_ID=200806 +; + +-- Jun 18, 2024, 12:30:44 AM CEST +UPDATE AD_TableIndex SET IsCreateConstraint='Y',Updated=TO_TIMESTAMP('2024-06-18 00:30:44','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_TableIndex_ID=201272 +; + +-- Jun 18, 2024, 12:31:49 AM CEST +UPDATE AD_TableIndex SET IsCreateConstraint='Y',Updated=TO_TIMESTAMP('2024-06-18 00:31:49','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_TableIndex_ID=201275 +; + diff --git a/migration/iD11/oracle/202407061229_IDEMPIERE-6188.sql b/migration/iD11/oracle/202407061229_IDEMPIERE-6188.sql new file mode 100644 index 0000000000..e1d06a1f6b --- /dev/null +++ b/migration/iD11/oracle/202407061229_IDEMPIERE-6188.sql @@ -0,0 +1,50 @@ +-- IDEMPIERE-6188 Read-Only Session +SELECT register_migration_script('202407061229_IDEMPIERE-6188.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Jul 6, 2024, 12:29:56 PM CEST +INSERT INTO AD_Element (AD_Element_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,ColumnName,Name,Description,Help,PrintName,EntityType,AD_Element_UU) VALUES (203938,0,0,'Y',TO_TIMESTAMP('2024-07-06 12:29:42','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-07-06 12:29:42','YYYY-MM-DD HH24:MI:SS'),100,'IsReadOnlySession','Read Only Session',NULL,NULL,'Read Only Session','D','6ee179e4-9573-4a3d-8815-23723ef241d7') +; + +-- Jul 6, 2024, 12:30:19 PM CEST +INSERT INTO AD_Column (AD_Column_ID,Version,Name,AD_Table_ID,ColumnName,DefaultValue,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,IsHtml,IsPartitionKey) VALUES (216614,0,'Read Only Session',200174,'IsReadOnlySession','N',1,'N','N','Y','N','N',0,'N',20,0,0,'Y',TO_TIMESTAMP('2024-07-06 12:30:18','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-07-06 12:30:18','YYYY-MM-DD HH24:MI:SS'),100,203938,'Y','N','D','N','N','N','Y','8c255baa-fbec-4a90-a43d-5461060368e9','Y',0,'N','N','N','N') +; + +-- Jul 6, 2024, 12:30:29 PM CEST +ALTER TABLE AD_UserPreference ADD IsReadOnlySession CHAR(1) DEFAULT 'N' CHECK (IsReadOnlySession IN ('Y','N')) NOT NULL +; + +-- Jul 6, 2024, 12:30:45 PM CEST +INSERT INTO AD_Field (AD_Field_ID,Name,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,XPosition,ColumnSpan) VALUES (208494,'Read Only Session',200189,216614,'Y',1,170,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2024-07-06 12:30:45','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-07-06 12:30:45','YYYY-MM-DD HH24:MI:SS'),100,'N','Y','D','f69131e7-4fca-4011-89d5-650ca506c920','Y',170,2,2) +; + +-- Jul 6, 2024, 12:31:06 PM CEST +UPDATE AD_Field SET SeqNo=110,Updated=TO_TIMESTAMP('2024-07-06 12:31:06','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=206133 +; + +-- Jul 6, 2024, 12:31:06 PM CEST +UPDATE AD_Field SET SeqNo=120,Updated=TO_TIMESTAMP('2024-07-06 12:31:06','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=206134 +; + +-- Jul 6, 2024, 12:31:06 PM CEST +UPDATE AD_Field SET SeqNo=130,Updated=TO_TIMESTAMP('2024-07-06 12:31:06','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=206407 +; + +-- Jul 6, 2024, 12:31:06 PM CEST +UPDATE AD_Field SET SeqNo=140,Updated=TO_TIMESTAMP('2024-07-06 12:31:06','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208189 +; + +-- Jul 6, 2024, 12:31:06 PM CEST +UPDATE AD_Field SET IsDisplayed='Y', SeqNo=150, XPosition=5,Updated=TO_TIMESTAMP('2024-07-06 12:31:06','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208494 +; + +-- Jul 6, 2024, 12:31:25 PM CEST +UPDATE AD_Field SET IsQuickEntry='Y',Updated=TO_TIMESTAMP('2024-07-06 12:31:25','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208494 +; + +-- Jul 9, 2024, 7:48:40 PM CEST +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('I','Read-only session',0,0,'Y',TO_TIMESTAMP('2024-07-09 19:48:39','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-07-09 19:48:39','YYYY-MM-DD HH24:MI:SS'),100,200900,'ReadOnlySession','D','dc819f55-662d-4d30-b68d-0a93b46a8458') +; + diff --git a/migration/iD11/oracle/202407231647_IDEMPIERE-6196.sql b/migration/iD11/oracle/202407231647_IDEMPIERE-6196.sql new file mode 100644 index 0000000000..95f96e20c9 --- /dev/null +++ b/migration/iD11/oracle/202407231647_IDEMPIERE-6196.sql @@ -0,0 +1,18 @@ +-- IDEMPIERE-6196 +SELECT register_migration_script('202407231647_IDEMPIERE-6196.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Jul 23, 2024, 4:47:40 PM CEST +UPDATE AD_Column SET SeqNoSelection=30,Updated=TO_TIMESTAMP('2024-07-23 16:47:40','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=10 WHERE AD_Column_ID=103 +; + +-- Jul 23, 2024, 4:47:43 PM CEST +UPDATE AD_Column SET SeqNoSelection=20,Updated=TO_TIMESTAMP('2024-07-23 16:47:43','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=10 WHERE AD_Column_ID=102 +; + +-- Jul 23, 2024, 4:47:52 PM CEST +UPDATE AD_Column SET SeqNoSelection=10,Updated=TO_TIMESTAMP('2024-07-23 16:47:52','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=10 WHERE AD_Column_ID=107 +; + diff --git a/migration/iD11/oracle/202408191316_IDEMPIERE-6189.sql b/migration/iD11/oracle/202408191316_IDEMPIERE-6189.sql new file mode 100644 index 0000000000..c5fefb5fa7 --- /dev/null +++ b/migration/iD11/oracle/202408191316_IDEMPIERE-6189.sql @@ -0,0 +1,26 @@ +-- IDEMPIERE-6189 Performance: queries on RV_C_Invoice doing unnecesary index or seq scan +SELECT register_migration_script('202408191316_IDEMPIERE-6189.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Aug 19, 2024, 1:16:19 PM CEST +UPDATE AD_ViewComponent SET FromClause='FROM c_invoice i +LEFT JOIN c_doctype d ON i.c_doctype_id = d.c_doctype_id +LEFT JOIN c_bpartner b ON i.c_bpartner_id = b.c_bpartner_id +LEFT JOIN c_bpartner_location bpl ON i.c_bpartner_location_id = bpl.c_bpartner_location_id +LEFT JOIN c_location loc ON bpl.c_location_id = loc.c_location_id',Updated=TO_TIMESTAMP('2024-08-19 13:16:19','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_ViewComponent_ID=200116 +; + +-- Aug 19, 2024, 1:16:27 PM CEST +CREATE OR REPLACE VIEW RV_C_Invoice(C_Invoice_ID, AD_Client_ID, AD_Org_ID, IsActive, Created, CreatedBy, Updated, UpdatedBy, IsSOTrx, DocumentNo, DocStatus, DocAction, IsPrinted, IsDiscountPrinted, Processing, Processed, IsTransferred, IsPaid, C_DocType_ID, C_DocTypeTarget_ID, C_Order_ID, Description, IsApproved, SalesRep_ID, DateInvoiced, DatePrinted, DateAcct, C_BPartner_ID, C_BPartner_Location_ID, AD_User_ID, C_BP_Group_ID, POReference, DateOrdered, C_Currency_ID, C_ConversionType_ID, PaymentRule, C_PaymentTerm_ID, M_PriceList_ID, C_Campaign_ID, C_Project_ID, C_Activity_ID, IsPayScheduleValid, InvoiceCollectionType, C_Country_ID, C_Region_ID, Postal, City, C_Charge_ID, ChargeAmt, TotalLines, GrandTotal, Multiplier, C_Invoice_AD_OrgTrx_ID, C_Invoice_C_ConversionType_ID, C_DunningLevel_ID, C_Payment_ID, c_invoice_dateordered, DunningGrace, GenerateTo, IsInDispute, c_invoice_ispayschedulevalid, c_invoice_isselfservice, IsTaxIncluded, M_RMA_ID, Posted, ProcessedOn, Ref_Invoice_ID, Reversal_ID, SendEMail, User1_ID, User2_ID, c_bp_acqusitioncost, c_bp_actuallifetimevalue, c_bp_ad_language, C_BP_AD_OrgBP_ID, C_BP_AD_Org_ID, C_BP_BPartner_Parent_ID, C_BP_C_Dunning_ID, C_BP_C_Greeting_ID, C_BP_C_InvoiceSchedule_ID, C_BP_C_PaymentTerm_ID, c_bp_created, C_BP_CreatedBy, C_BP_C_TaxGroup_ID, c_bp_deliveryrule, c_bp_deliveryviarule, c_bp_description, c_bp_dunninggrace, c_bp_duns, c_bp_firstsale, c_bp_flatdiscount, c_bp_freightcostrule, c_bp_invoicerule, c_bp_isactive, c_bp_iscustomer, c_bp_isdiscountprinted, c_bp_isemployee, c_bp_ismanufacturer, c_bp_isonetime, c_bp_ispotaxexempt, c_bp_isprospect, c_bp_issalesrep, c_bp_issummary, c_bp_istaxexempt, c_bp_isvendor, C_BP_Logo_ID, C_BP_M_DiscountSchema_ID, C_BP_M_PriceList_ID, c_bp_naics, c_bp_name, c_bp_name2, c_bp_numberemployees, c_bp_paymentrule, c_bp_paymentrulepo, C_BP_PO_DiscountSchema_ID, C_BP_PO_PaymentTerm_ID, C_BP_PO_PriceList_ID, c_bp_poreference, c_bp_potentiallifetimevalue, c_bp_rating, c_bp_referenceno, C_BP_SalesRep_ID, c_bp_salesvolume, c_bp_sendemail, c_bp_shareofcustomer, c_bp_shelflifeminpct, c_bp_so_creditlimit, c_bp_socreditstatus, c_bp_so_creditused, c_bp_so_description, TaxID, c_bp_totalopenbalance, c_bp_updated, C_BP_UpdatedBy, c_bp_url, c_bp_value, C_BP_Location_AD_Org_ID, C_BP_Location_C_BPartner_ID, C_BP_Location_C_Location_ID, c_bp_location_created, C_BP_Location_CreatedBy, C_SalesRegion_ID, c_bp_location_fax, c_bp_location_isactive, IsBillTo, ISDN, IsPayFrom, IsRemitTo, IsShipTo, +c_bp_location_name, c_bp_location_phone, c_bp_location_phone2, c_bp_location_updated, C_BP_Location_UpdatedBy, Address1, Address2, Address3, Address4, C_Location_AD_Org_ID, C_City_ID, c_location_created, C_Location_CreatedBy, c_location_isactive, Postal_Add, RegionName, c_location_updated, C_Location_UpdatedBy) AS +SELECT i.c_invoice_id AS C_Invoice_ID, i.ad_client_id AS AD_Client_ID, i.ad_org_id AS AD_Org_ID, i.isactive AS IsActive, i.created AS Created, i.createdby AS CreatedBy, i.updated AS Updated, i.updatedby AS UpdatedBy, i.issotrx AS IsSOTrx, i.documentno AS DocumentNo, i.docstatus AS DocStatus, i.docaction AS DocAction, i.isprinted AS IsPrinted, i.isdiscountprinted AS IsDiscountPrinted, i.processing AS Processing, i.processed AS Processed, i.istransferred AS IsTransferred, i.ispaid AS IsPaid, i.c_doctype_id AS C_DocType_ID, i.c_doctypetarget_id AS C_DocTypeTarget_ID, i.c_order_id AS C_Order_ID, i.description AS Description, i.isapproved AS IsApproved, i.salesrep_id AS SalesRep_ID, i.dateinvoiced AS DateInvoiced, i.dateprinted AS DatePrinted, i.dateacct AS DateAcct, i.c_bpartner_id AS C_BPartner_ID, i.c_bpartner_location_id AS C_BPartner_Location_ID, i.ad_user_id AS AD_User_ID, b.c_bp_group_id AS C_BP_Group_ID, i.poreference AS POReference, i.dateordered AS DateOrdered, i.c_currency_id AS C_Currency_ID, i.c_conversiontype_id AS C_ConversionType_ID, i.paymentrule AS PaymentRule, i.c_paymentterm_id AS C_PaymentTerm_ID, i.m_pricelist_id AS M_PriceList_ID, i.c_campaign_id AS C_Campaign_ID, i.c_project_id AS C_Project_ID, i.c_activity_id AS C_Activity_ID, i.ispayschedulevalid AS IsPayScheduleValid, i.invoicecollectiontype AS InvoiceCollectionType, loc.c_country_id AS C_Country_ID, loc.c_region_id AS C_Region_ID, loc.postal AS Postal, loc.city AS City, i.c_charge_id AS C_Charge_ID, CASE WHEN charat(d.docbasetype, 3) = 'C' THEN i.chargeamt * '-1' ELSE i.chargeamt END AS ChargeAmt, CASE WHEN charat(d.docbasetype, 3) = 'C' THEN i.totallines * '-1' ELSE i.totallines END AS TotalLines, CASE WHEN charat(d.docbasetype, 3) = 'C' THEN i.grandtotal * '-1' ELSE i.grandtotal END AS GrandTotal, CASE WHEN charat(d.docbasetype, 3) = 'C' THEN -1 ELSE 1 END AS Multiplier, i.ad_orgtrx_id AS C_Invoice_AD_OrgTrx_ID, i.c_conversiontype_id AS C_Invoice_C_ConversionType_ID, i.c_dunninglevel_id AS C_DunningLevel_ID, i.c_payment_id AS C_Payment_ID, i.dateordered AS c_invoice_dateordered, i.dunninggrace AS DunningGrace, i.generateto AS GenerateTo, i.isindispute AS IsInDispute, i.ispayschedulevalid AS c_invoice_ispayschedulevalid, i.isselfservice AS c_invoice_isselfservice, i.istaxincluded AS IsTaxIncluded, i.m_rma_id AS M_RMA_ID, i.posted AS Posted, i.processedon AS ProcessedOn, i.ref_invoice_id AS Ref_Invoice_ID, i.reversal_id AS Reversal_ID, i.sendemail AS SendEMail, +i.user1_id AS User1_ID, i.user2_id AS User2_ID, b.acqusitioncost AS c_bp_acqusitioncost, b.actuallifetimevalue AS c_bp_actuallifetimevalue, b.ad_language AS c_bp_ad_language, b.ad_orgbp_id AS C_BP_AD_OrgBP_ID, b.ad_org_id AS C_BP_AD_Org_ID, b.bpartner_parent_id AS C_BP_BPartner_Parent_ID, b.c_dunning_id AS C_BP_C_Dunning_ID, b.c_greeting_id AS C_BP_C_Greeting_ID, b.c_invoiceschedule_id AS C_BP_C_InvoiceSchedule_ID, b.c_paymentterm_id AS C_BP_C_PaymentTerm_ID, b.created AS c_bp_created, b.createdby AS C_BP_CreatedBy, b.c_taxgroup_id AS C_BP_C_TaxGroup_ID, b.deliveryrule AS c_bp_deliveryrule, b.deliveryviarule AS c_bp_deliveryviarule, b.description AS c_bp_description, b.dunninggrace AS c_bp_dunninggrace, b.duns AS c_bp_duns, b.firstsale AS c_bp_firstsale, b.flatdiscount AS c_bp_flatdiscount, b.freightcostrule AS c_bp_freightcostrule, b.invoicerule AS c_bp_invoicerule, b.isactive AS c_bp_isactive, b.iscustomer AS c_bp_iscustomer, b.isdiscountprinted AS c_bp_isdiscountprinted, b.isemployee AS c_bp_isemployee, b.ismanufacturer AS c_bp_ismanufacturer, b.isonetime AS c_bp_isonetime, b.ispotaxexempt AS c_bp_ispotaxexempt, b.isprospect AS c_bp_isprospect, b.issalesrep AS c_bp_issalesrep, b.issummary AS c_bp_issummary, b.istaxexempt AS c_bp_istaxexempt, b.isvendor AS c_bp_isvendor, b.logo_id AS C_BP_Logo_ID, b.m_discountschema_id AS C_BP_M_DiscountSchema_ID, b.m_pricelist_id AS C_BP_M_PriceList_ID, b.naics AS c_bp_naics, b.name AS c_bp_name, b.name2 AS c_bp_name2, b.numberemployees AS c_bp_numberemployees, b.paymentrule AS c_bp_paymentrule, b.paymentrulepo AS c_bp_paymentrulepo, b.po_discountschema_id AS C_BP_PO_DiscountSchema_ID, b.po_paymentterm_id AS C_BP_PO_PaymentTerm_ID, b.po_pricelist_id AS C_BP_PO_PriceList_ID, b.poreference AS c_bp_poreference, b.potentiallifetimevalue AS c_bp_potentiallifetimevalue, b.rating AS c_bp_rating, b.referenceno AS c_bp_referenceno, b.salesrep_id AS C_BP_SalesRep_ID, b.salesvolume AS c_bp_salesvolume, b.sendemail AS c_bp_sendemail, b.shareofcustomer AS c_bp_shareofcustomer, b.shelflifeminpct AS c_bp_shelflifeminpct, b.so_creditlimit AS c_bp_so_creditlimit, b.socreditstatus AS c_bp_socreditstatus, b.so_creditused AS c_bp_so_creditused, b.so_description AS c_bp_so_description, b.taxid AS TaxID, b.totalopenbalance AS c_bp_totalopenbalance, b.updated AS c_bp_updated, b.updatedby AS C_BP_UpdatedBy, b.url AS c_bp_url, b.value AS c_bp_value, bpl.ad_org_id AS C_BP_Location_AD_Org_ID, bpl.c_bpartner_id AS C_BP_Location_C_BPartner_ID, +bpl.c_location_id AS C_BP_Location_C_Location_ID, bpl.created AS c_bp_location_created, bpl.createdby AS C_BP_Location_CreatedBy, bpl.c_salesregion_id AS C_SalesRegion_ID, bpl.fax AS c_bp_location_fax, bpl.isactive AS c_bp_location_isactive, bpl.isbillto AS IsBillTo, bpl.isdn AS ISDN, bpl.ispayfrom AS IsPayFrom, bpl.isremitto AS IsRemitTo, bpl.isshipto AS IsShipTo, bpl.name AS c_bp_location_name, bpl.phone AS c_bp_location_phone, bpl.phone2 AS c_bp_location_phone2, bpl.updated AS c_bp_location_updated, bpl.updatedby AS C_BP_Location_UpdatedBy, loc.address1 AS Address1, loc.address2 AS Address2, loc.address3 AS Address3, loc.address4 AS Address4, loc.ad_org_id AS C_Location_AD_Org_ID, loc.c_city_id AS C_City_ID, loc.created AS c_location_created, loc.createdby AS C_Location_CreatedBy, loc.isactive AS c_location_isactive, loc.postal_add AS Postal_Add, loc.regionname AS RegionName, loc.updated AS c_location_updated, loc.updatedby AS C_Location_UpdatedBy FROM c_invoice i +LEFT JOIN c_doctype d ON i.c_doctype_id = d.c_doctype_id +LEFT JOIN c_bpartner b ON i.c_bpartner_id = b.c_bpartner_id +LEFT JOIN c_bpartner_location bpl ON i.c_bpartner_location_id = bpl.c_bpartner_location_id +LEFT JOIN c_location loc ON bpl.c_location_id = loc.c_location_id +; + diff --git a/migration/iD11/oracle/202408201236_IDEMPIERE-6216.sql b/migration/iD11/oracle/202408201236_IDEMPIERE-6216.sql new file mode 100644 index 0000000000..1f7e7bc4fd --- /dev/null +++ b/migration/iD11/oracle/202408201236_IDEMPIERE-6216.sql @@ -0,0 +1,10 @@ +-- IDEMPIERE-6216 +SELECT register_migration_script('202408201236_IDEMPIERE-6216.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Aug 20, 2024, 12:36:08 PM CEST +UPDATE AD_Process_Para SET MandatoryLogic='@UseDefaultCoA@=N',Updated=TO_TIMESTAMP('2024-08-20 12:36:08','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Process_Para_ID=53296 +; + diff --git a/migration/iD11/oracle/202409041533_IDEMPIERE-6223.sql b/migration/iD11/oracle/202409041533_IDEMPIERE-6223.sql new file mode 100644 index 0000000000..e7fe46ff9a --- /dev/null +++ b/migration/iD11/oracle/202409041533_IDEMPIERE-6223.sql @@ -0,0 +1,94 @@ +-- Adding new fields to AD_PInstance and AD_PInstance_Log +SELECT register_migration_script('202409041533_IDEMPIERE-6223.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Sep 4, 2024, 3:33:12 PM BRT +INSERT INTO AD_Column (AD_Column_ID,Version,Name,Description,AD_Table_ID,ColumnName,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,FKConstraintType,IsHtml,IsPartitionKey) VALUES (216788,0,'JSON Data','The json field stores json data.',282,'JsonData',0,'N','N','N','N','N',0,'N',200267,0,0,'Y',TO_TIMESTAMP('2024-09-04 15:33:12','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-09-04 15:33:12','YYYY-MM-DD HH24:MI:SS'),100,203924,'Y','N','D','N','N','N','Y','6ec3e423-205d-482f-b7e2-3dea6b66d5d0','Y',0,'N','N','N','N','N') +; + +-- Sep 4, 2024, 3:33:16 PM BRT +ALTER TABLE AD_PInstance ADD JsonData CLOB DEFAULT NULL CONSTRAINT AD_PInstance_JsonData_isjson CHECK (JsonData IS JSON) +; + +-- Sep 4, 2024, 3:33:39 PM BRT +INSERT INTO AD_Field (AD_Field_ID,Name,Description,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,ColumnSpan,NumLines) VALUES (208511,'JSON Data','The json field stores json data.',663,216788,'Y',0,210,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2024-09-04 15:33:39','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-09-04 15:33:39','YYYY-MM-DD HH24:MI:SS'),100,'N','Y','D','2455e488-36a8-48d2-8410-2ff0808915e4','Y',200,2,5) +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET IsDisplayed='Y', SeqNo=110, XPosition=1, ColumnSpan=5,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208511 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=120,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=10501 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=130,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=207416 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=140,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=10495 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=150,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=202845 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=160,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=202847 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=170,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=207405 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=180,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=207407 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=190,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=207406 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=200,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=207408 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=210,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=207720 +; + +-- Sep 4, 2024, 3:34:40 PM BRT +INSERT INTO AD_Column (AD_Column_ID,Version,Name,Description,AD_Table_ID,ColumnName,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,FKConstraintType,IsHtml,IsPartitionKey) VALUES (216789,0,'JSON Data','The json field stores json data.',578,'JsonData',0,'N','N','N','N','N',0,'N',200267,0,0,'Y',TO_TIMESTAMP('2024-09-04 15:34:40','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-09-04 15:34:40','YYYY-MM-DD HH24:MI:SS'),100,203924,'Y','N','D','N','N','N','Y','7539f67f-922d-4e27-881f-c04f0dc8c160','Y',0,'N','N','N','N','N') +; + +-- Sep 4, 2024, 3:34:43 PM BRT +ALTER TABLE AD_PInstance_Log ADD JsonData CLOB DEFAULT NULL CONSTRAINT AD_PInstance_Log_JsonData_isjson CHECK (JsonData IS JSON) +; + +-- Sep 4, 2024, 3:35:54 PM BRT +INSERT INTO AD_Field (AD_Field_ID,Name,Description,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,ColumnSpan,NumLines) VALUES (208512,'JSON Data','The json field stores json data.',665,216789,'Y',0,100,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2024-09-04 15:35:54','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-09-04 15:35:54','YYYY-MM-DD HH24:MI:SS'),100,'N','Y','D','27e958ae-9e31-4e8b-824e-e571df7da15c','Y',100,2,5) +; + +-- Sep 4, 2024, 3:36:15 PM BRT +UPDATE AD_Field SET IsDisplayed='Y', SeqNo=70, XPosition=1, ColumnSpan=5,Updated=TO_TIMESTAMP('2024-09-04 15:36:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208512 +; + +-- Sep 4, 2024, 3:36:15 PM BRT +UPDATE AD_Field SET SeqNo=80,Updated=TO_TIMESTAMP('2024-09-04 15:36:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=200309 +; + +-- Sep 4, 2024, 3:36:15 PM BRT +UPDATE AD_Field SET SeqNo=90,Updated=TO_TIMESTAMP('2024-09-04 15:36:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=200310 +; + +-- Sep 4, 2024, 3:36:15 PM BRT +UPDATE AD_Field SET SeqNo=100,Updated=TO_TIMESTAMP('2024-09-04 15:36:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=207622 +; + +-- Sep 4, 2024, 3:36:15 PM BRT +UPDATE AD_Field SET SeqNo=0,Updated=TO_TIMESTAMP('2024-09-04 15:36:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=204554 +; + diff --git a/migration/iD11/oracle/202409141654_IDEMPIERE-5567.sql b/migration/iD11/oracle/202409141654_IDEMPIERE-5567.sql new file mode 100644 index 0000000000..e0f93c97e0 --- /dev/null +++ b/migration/iD11/oracle/202409141654_IDEMPIERE-5567.sql @@ -0,0 +1,22 @@ +-- IDEMPIERE-5567 Permalink for UUID multi-key tables +SELECT register_migration_script('202409141654_IDEMPIERE-5567.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Sep 14, 2024, 4:54:43 PM CEST +INSERT INTO AD_ZoomCondition (AD_Client_ID,AD_Org_ID,AD_Table_ID,AD_Window_ID,AD_ZoomCondition_ID,Created,CreatedBy,IsActive,Updated,UpdatedBy,SeqNo,Name,AD_ZoomCondition_UU,ZoomLogic,EntityType) VALUES (0,0,201,111,200009,TO_TIMESTAMP('2024-09-14 16:54:42','YYYY-MM-DD HH24:MI:SS'),100,'Y',TO_TIMESTAMP('2024-09-14 16:54:42','YYYY-MM-DD HH24:MI:SS'),100,10,'Zoom to role on tenants','f8ddf8c5-4bf6-4343-a2f3-c88c1f007344','@#AD_Client_ID@>0','D') +; + +-- Sep 14, 2024, 4:56:45 PM CEST +INSERT INTO AD_ZoomCondition (AD_Client_ID,AD_Org_ID,AD_Table_ID,AD_Window_ID,AD_ZoomCondition_ID,Created,CreatedBy,IsActive,Updated,UpdatedBy,SeqNo,Name,AD_ZoomCondition_UU,ZoomLogic,EntityType) VALUES (0,0,378,111,200010,TO_TIMESTAMP('2024-09-14 16:56:45','YYYY-MM-DD HH24:MI:SS'),100,'Y',TO_TIMESTAMP('2024-09-14 16:56:45','YYYY-MM-DD HH24:MI:SS'),100,10,'Zoom to role on tenants','32bab358-373f-476a-a52c-5c700044478d','@#AD_Client_ID@>0','D') +; + +-- Sep 14, 2024, 4:57:50 PM CEST +INSERT INTO AD_ZoomCondition (AD_Client_ID,AD_Org_ID,AD_Table_ID,AD_Window_ID,AD_ZoomCondition_ID,Created,CreatedBy,IsActive,Updated,UpdatedBy,SeqNo,Name,AD_ZoomCondition_UU,ZoomLogic,EntityType) VALUES (0,0,197,111,200011,TO_TIMESTAMP('2024-09-14 16:57:50','YYYY-MM-DD HH24:MI:SS'),100,'Y',TO_TIMESTAMP('2024-09-14 16:57:50','YYYY-MM-DD HH24:MI:SS'),100,10,'Zoom to role on tenants','ebea9694-6348-444e-8b7f-f5f1c75a3c65','@#AD_Client_ID@>0','D') +; + +-- Sep 14, 2024, 4:58:46 PM CEST +INSERT INTO AD_ZoomCondition (AD_Client_ID,AD_Org_ID,AD_Table_ID,AD_Window_ID,AD_ZoomCondition_ID,Created,CreatedBy,IsActive,Updated,UpdatedBy,SeqNo,Name,AD_ZoomCondition_UU,ZoomLogic,EntityType) VALUES (0,0,199,111,200012,TO_TIMESTAMP('2024-09-14 16:58:46','YYYY-MM-DD HH24:MI:SS'),100,'Y',TO_TIMESTAMP('2024-09-14 16:58:46','YYYY-MM-DD HH24:MI:SS'),100,10,'Zoom to role on tenants','91ce33c0-fb91-4c9e-894e-8e8f49d7677f','@#AD_Client_ID@>0','D') +; + diff --git a/migration/iD11/oracle/202409141807_IDEMPIERE-6007.sql b/migration/iD11/oracle/202409141807_IDEMPIERE-6007.sql new file mode 100644 index 0000000000..b214d9f4a6 --- /dev/null +++ b/migration/iD11/oracle/202409141807_IDEMPIERE-6007.sql @@ -0,0 +1,10 @@ +-- IDEMPIERE-6007 +SELECT register_migration_script('202409141807_IDEMPIERE-6007.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Sep 14, 2024, 6:07:20 PM CEST +UPDATE AD_Field SET ReadOnlyLogic='@SQL=SELECT 1 FROM AD_Field WHERE AD_Tab_ID=@AD_Tab_ID:0@',Updated=TO_TIMESTAMP('2024-09-14 18:07:20','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=131 +; + diff --git a/migration/iD11/oracle/202409231820_IDEMPIERE-6248.sql b/migration/iD11/oracle/202409231820_IDEMPIERE-6248.sql new file mode 100644 index 0000000000..5872cf9c4b --- /dev/null +++ b/migration/iD11/oracle/202409231820_IDEMPIERE-6248.sql @@ -0,0 +1,10 @@ +-- IDEMPIERE-6248 +SELECT register_migration_script('202409231820_IDEMPIERE-6248.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Sep 23, 2024, 6:20:30 PM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200250,0,0,TO_TIMESTAMP('2024-09-23 18:20:29','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-09-23 18:20:29','YYYY-MM-DD HH24:MI:SS'),10,10,'Y','FULL_EXCEPTION_TRACE_IN_LOG','N','If set to Y, the stack trace is not cut in the log (see https://idempiere.atlassian.net/browse/IDEMPIERE-6248)','D','S','8308ac18-d0a6-479f-ab59-7edd0bcb0b54') +; + diff --git a/migration/iD11/oracle/202409231821_IDEMPIERE-6248_DelSysConfig.sql b/migration/iD11/oracle/202409231821_IDEMPIERE-6248_DelSysConfig.sql new file mode 100644 index 0000000000..df393c1950 --- /dev/null +++ b/migration/iD11/oracle/202409231821_IDEMPIERE-6248_DelSysConfig.sql @@ -0,0 +1,8 @@ +-- IDEMPIERE-6248 +SELECT register_migration_script('202409231821_IDEMPIERE-6248_DelSysConfig.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +DELETE FROM AD_SysConfig WHERE AD_SysConfig_ID = 200250 +; diff --git a/migration/iD11/oracle/202409241248_IDEMPIERE-5760.sql b/migration/iD11/oracle/202409241248_IDEMPIERE-5760.sql new file mode 100644 index 0000000000..1720ded3ec --- /dev/null +++ b/migration/iD11/oracle/202409241248_IDEMPIERE-5760.sql @@ -0,0 +1,14 @@ +-- IDEMPIERE-5760 Manage mail.smtp.timeout using SysConfig +SELECT register_migration_script('202409241248_IDEMPIERE-5760.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Sep 24, 2024, 12:48:56 PM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200253,0,0,TO_TIMESTAMP('2024-09-24 12:48:55','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-09-24 12:48:55','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','MAIL_SMTP_CONNECTIONTIMEOUT','-1','Timeout in milliseconds to wait for SMTP connection, -1 leaves the java default','D','C','36be68b7-7b4b-4725-9a51-aeb11bb7b699') +; + +-- Sep 24, 2024, 12:49:10 PM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200254,0,0,TO_TIMESTAMP('2024-09-24 12:49:09','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-09-24 12:49:09','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','MAIL_SMTP_WRITETIMEOUT','-1','Timeout in milliseconds to wait for writing on SMTP connection, -1 leaves the java default','D','C','e11d83c4-8500-400a-b9d2-358c30c910fb') +; + diff --git a/migration/iD11/oracle/202410071412_IDEMPIERE-6196.sql b/migration/iD11/oracle/202410071412_IDEMPIERE-6196.sql new file mode 100644 index 0000000000..b733163f02 --- /dev/null +++ b/migration/iD11/oracle/202410071412_IDEMPIERE-6196.sql @@ -0,0 +1,18 @@ +-- IDEMPIERE-6196_SysConfig +SELECT register_migration_script('202410071412_IDEMPIERE-6196.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Oct 7, 2024, 2:12:19 PM CEST +UPDATE AD_Column SET SeqNoSelection=10,Updated=TO_TIMESTAMP('2024-10-07 14:12:19','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=10 WHERE AD_Column_ID=50195 +; + +-- Oct 7, 2024, 2:12:23 PM CEST +UPDATE AD_Column SET SeqNoSelection=20,Updated=TO_TIMESTAMP('2024-10-07 14:12:23','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=10 WHERE AD_Column_ID=50197 +; + +-- Oct 7, 2024, 2:12:27 PM CEST +UPDATE AD_Column SET SeqNoSelection=30,Updated=TO_TIMESTAMP('2024-10-07 14:12:27','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=10 WHERE AD_Column_ID=50196 +; + diff --git a/migration/iD11/oracle/202411260821_IDEMPIERE-6317.sql b/migration/iD11/oracle/202411260821_IDEMPIERE-6317.sql new file mode 100644 index 0000000000..2a508f6f1d --- /dev/null +++ b/migration/iD11/oracle/202411260821_IDEMPIERE-6317.sql @@ -0,0 +1,18 @@ +-- IDEMPIERE-6317 +SELECT register_migration_script('202411260821_IDEMPIERE-6317.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Nov 26, 2024, 8:34:46 AM BRT +UPDATE AD_Message SET Value='Can''t Save Tenant Level',Updated=TO_TIMESTAMP('2024-11-26 08:34:46','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Message_ID=53010 +; + +-- Nov 26, 2024, 8:36:49 AM BRT +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','This is a system or tenant parameter, you can''t save it as organization parameter',0,0,'Y',TO_TIMESTAMP('2024-11-26 08:36:48','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-11-26 08:36:48','YYYY-MM-DD HH24:MI:SS'),100,200914,'ThisIsSystemOrTenantParameter','D','8980c935-1c23-4d83-8b26-70eb3a749797') +; + +-- Nov 26, 2024, 8:38:56 AM BRT +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','This is a system parameter, you can''t save it as tenant parameter',0,0,'Y',TO_TIMESTAMP('2024-11-26 08:38:55','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-11-26 08:38:55','YYYY-MM-DD HH24:MI:SS'),100,200915,'ThisIsSystemParameter','D','6e6b2060-ec2d-4d87-b499-7250e93f7cec') +; + diff --git a/migration/iD11/oracle/202411301155_IDEMPIERE-6329.sql b/migration/iD11/oracle/202411301155_IDEMPIERE-6329.sql new file mode 100644 index 0000000000..b95223fa46 --- /dev/null +++ b/migration/iD11/oracle/202411301155_IDEMPIERE-6329.sql @@ -0,0 +1,676 @@ +-- IDEMPIERE-6329 Bug in BOM* SQL functions not getting the correct BOM children +SELECT register_migration_script('202411301155_IDEMPIERE-6329.sql') FROM dual; + +CREATE OR REPLACE FUNCTION BOMPRICELIMIT +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceLimit.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return Limit Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_Product_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from PriceList directly + SELECT COALESCE (SUM(PriceLimit), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricelimit (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; +END Bompricelimit; +/ + +CREATE OR REPLACE FUNCTION BOMPRICELIST +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceList.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return List Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_Product_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE (SUM(PriceList), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricelist (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + -- DBMS_OUTPUT.PUT_LINE('Qry=' || bom.BOMQty || ' @ ' || v_ProductPrice || ', Price=' || v_Price); + END LOOP; -- BOM + END IF; + -- + RETURN v_Price; +END Bompricelist; +/ + +CREATE OR REPLACE FUNCTION BOMPRICESTD +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceStd.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return Standard Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_Product_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE(SUM(PriceStd), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricestd (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + -- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + END LOOP; -- BOM + END IF; + -- + RETURN v_Price; +END Bompricestd; +/ + +CREATE OR REPLACE FUNCTION BOMQTYONHANDFORRESERVATION +( + Product_ID IN NUMBER, + Warehouse_ID IN NUMBER, + Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity on hand for BOM + */ +AS + myWarehouse_ID NUMBER; + Quantity NUMBER := 99999; -- unlimited + IsBOM CHAR(1); + IsStocked CHAR(1); + ProductType CHAR(1); + ProductQty NUMBER; + StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_Product_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsBOM='Y' + AND p.IsVerified='Y' + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + myWarehouse_ID := Warehouse_ID; + IF (myWarehouse_ID IS NULL) THEN + IF (Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT SUM(M_Warehouse_ID) INTO myWarehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=Locator_ID; + END IF; + END IF; + IF (myWarehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || myWarehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO IsBOM, ProductType, IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + -- Unlimited capacity if no item + IF (IsBOM='N' AND (ProductType<>'I' OR IsStocked='N')) THEN + RETURN Quantity; + -- Stocked item + ELSIF (IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + LEFT JOIN M_LocatorType lt ON (l.M_LocatorType_ID=lt.M_LocatorType_ID) + WHERE s.M_Product_ID=Product_ID AND l.M_Warehouse_ID=myWarehouse_ID + AND COALESCE(lt.IsAvailableForReservation,'Y')='Y'; + -- + -- DBMS_OUTPUT.PUT_LINE('Qty=' || ProductQty); + RETURN ProductQty; + END IF; + + -- Go through BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + LEFT JOIN M_LocatorType lt ON (l.M_LocatorType_ID=lt.M_LocatorType_ID) + WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID + AND COALESCE(lt.IsAvailableForReservation,'Y')='Y'; + -- Get Rounding Precision + SELECT NVL(MAX(u.StdPrecision), 0) + INTO StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; + -- How much can we make with this product + ProductQty := ROUND (ProductQty/bom.BOMQty, StdPrecision); + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y') THEN + ProductQty := BomqtyonhandForReservation (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + IF (Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; + -- + RETURN ROUND (Quantity, StdPrecision); + END IF; + RETURN 0; +END BOMQTYONHANDFORRESERVATION; +/ + +CREATE OR REPLACE FUNCTION BOMQTYONHAND +( + Product_ID IN NUMBER, + Warehouse_ID IN NUMBER, + Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity on hand for BOM + */ +AS + myWarehouse_ID NUMBER; + Quantity NUMBER := 99999; -- unlimited + IsBOM CHAR(1); + IsStocked CHAR(1); + ProductType CHAR(1); + ProductQty NUMBER; + StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_Product_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsBOM='Y' + AND p.IsVerified='Y' + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + myWarehouse_ID := Warehouse_ID; + IF (myWarehouse_ID IS NULL) THEN + IF (Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT SUM(M_Warehouse_ID) INTO myWarehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=Locator_ID; + END IF; + END IF; + IF (myWarehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || myWarehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO IsBOM, ProductType, IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + -- Unlimited capacity if no item + IF (IsBOM='N' AND (ProductType<>'I' OR IsStocked='N')) THEN + RETURN Quantity; + -- Stocked item + ELSIF (IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + WHERE s.M_Product_ID=Product_ID AND l.M_Warehouse_ID=myWarehouse_ID; + -- + -- DBMS_OUTPUT.PUT_LINE('Qty=' || ProductQty); + RETURN ProductQty; + END IF; + + -- Go through BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID; + -- Get Rounding Precision + SELECT NVL(MAX(u.StdPrecision), 0) + INTO StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; + -- How much can we make with this product + ProductQty := ROUND (ProductQty/bom.BOMQty, StdPrecision); + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y') THEN + ProductQty := Bomqtyonhand (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + IF (Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; + -- + RETURN ROUND (Quantity, StdPrecision); + END IF; + RETURN 0; +END Bomqtyonhand; +/ + +CREATE OR REPLACE FUNCTION BOMQTYORDERED +( + p_Product_ID IN NUMBER, + p_Warehouse_ID IN NUMBER, + p_Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity ordered for BOM + */ +AS + v_Warehouse_ID NUMBER; + v_Quantity NUMBER := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty NUMBER; + v_StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_Product_ID=p.M_Product_ID + AND b.M_Product_ID=p_Product_ID + AND b.M_ProductBOM_ID != p_Product_ID + AND p.IsBOM='Y' + AND p.IsVerified='Y' + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + v_Warehouse_ID := p_Warehouse_ID; + IF (v_Warehouse_ID IS NULL) THEN + IF (p_Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT MAX(M_Warehouse_ID) INTO v_Warehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=p_Locator_ID; + END IF; + END IF; + IF (v_Warehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || v_Warehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=p_Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + + -- No reservation for non-stocked + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN 0; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='N' + AND IsActive='Y'; + -- + RETURN v_ProductQty; + END IF; + + -- Go though BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='N' + AND IsActive='Y'; + -- Get Rounding Precision + SELECT NVL(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; + -- How much can we make with this product + v_ProductQty := ROUND (v_ProductQty/bom.BOMQty, v_StdPrecision); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y') THEN + v_ProductQty := Bomqtyordered (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + -- Unlimited (e.g. only services) + IF (v_Quantity = 99999) THEN + RETURN 0; + END IF; + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; + -- + RETURN ROUND (v_Quantity, v_StdPrecision); + END IF; + -- + RETURN 0; +END Bomqtyordered; +/ + +CREATE OR REPLACE FUNCTION BOMQTYRESERVED +( + p_Product_ID IN NUMBER, + p_Warehouse_ID IN NUMBER, + p_Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity reserved for BOM + */ +AS + v_Warehouse_ID NUMBER; + v_Quantity NUMBER := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty NUMBER; + v_StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_Product_ID=p.M_Product_ID + AND b.M_Product_ID=p_Product_ID + AND b.M_ProductBOM_ID != p_Product_ID + AND p.IsBOM='Y' + AND p.IsVerified='Y' + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + v_Warehouse_ID := p_Warehouse_ID; + IF (v_Warehouse_ID IS NULL) THEN + IF (p_Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT MAX(M_Warehouse_ID) INTO v_Warehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=p_Locator_ID; + END IF; + END IF; + IF (v_Warehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || v_Warehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=p_Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + + -- No reservation for non-stocked + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN 0; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='Y' + AND IsActive='Y'; + -- + RETURN v_ProductQty; + END IF; + + -- Go though BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=bom.M_ProductBOM_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='Y' + AND IsActive='Y'; + -- Get Rounding Precision + SELECT NVL(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; + -- How much can we make with this product + v_ProductQty := ROUND (v_ProductQty/bom.BOMQty, v_StdPrecision); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y') THEN + v_ProductQty := Bomqtyreserved (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + -- Unlimited (e.g. only services) + IF (v_Quantity = 99999) THEN + RETURN 0; + END IF; + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; + -- + RETURN ROUND (v_Quantity, v_StdPrecision); + END IF; + RETURN 0; +END Bomqtyreserved; +/ + diff --git a/migration/iD11/oracle/202412021125_IDEMPIERE-6327.sql b/migration/iD11/oracle/202412021125_IDEMPIERE-6327.sql new file mode 100644 index 0000000000..b2d9f52249 --- /dev/null +++ b/migration/iD11/oracle/202412021125_IDEMPIERE-6327.sql @@ -0,0 +1,14 @@ +-- IDEMPIERE-6327 Interest Area Window: Subscription tab should not enable "Insert Record" +SELECT register_migration_script('202412021125_IDEMPIERE-6327.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Dec 2, 2024, 11:25:16 AM MYT +UPDATE AD_Tab SET IsInsertRecord='N',Updated=TO_TIMESTAMP('2024-12-02 11:25:16','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tab_ID=536 +; + +-- Dec 2, 2024, 11:25:32 AM MYT +UPDATE AD_Field SET IsReadOnly='Y',Updated=TO_TIMESTAMP('2024-12-02 11:25:32','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=7625 +; + diff --git a/migration/iD11/oracle/202412041755_IDEMPIERE-6334.sql b/migration/iD11/oracle/202412041755_IDEMPIERE-6334.sql new file mode 100644 index 0000000000..679a90a59e --- /dev/null +++ b/migration/iD11/oracle/202412041755_IDEMPIERE-6334.sql @@ -0,0 +1,10 @@ +-- +SELECT register_migration_script('202412041755_IDEMPIERE-6334.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Dec 4, 2024, 5:55:11 PM CET +UPDATE AD_Tab SET IsAdvancedTab='Y',Updated=TO_TIMESTAMP('2024-12-04 17:55:11','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tab_ID=200329 +; + diff --git a/migration/iD11/oracle/202412091327_IDEMPIERE-6329.sql b/migration/iD11/oracle/202412091327_IDEMPIERE-6329.sql new file mode 100644 index 0000000000..574576593d --- /dev/null +++ b/migration/iD11/oracle/202412091327_IDEMPIERE-6329.sql @@ -0,0 +1,3245 @@ +-- IDEMPIERE-6329 Bug in BOM* SQL functions not getting the correct BOM children +SELECT register_migration_script('202412091327_IDEMPIERE-6329.sql') FROM dual; + +CREATE OR REPLACE FUNCTION BOMPRICELIMIT +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceLimit.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return Limit Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from PriceList directly + SELECT COALESCE (SUM(PriceLimit), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricelimit (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; +END Bompricelimit; +/ + +CREATE OR REPLACE FUNCTION BOMPRICELIST +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceList.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return List Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE (SUM(PriceList), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricelist (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + -- DBMS_OUTPUT.PUT_LINE('Qry=' || bom.BOMQty || ' @ ' || v_ProductPrice || ', Price=' || v_Price); + END LOOP; -- BOM + END IF; + -- + RETURN v_Price; +END Bompricelist; +/ + +CREATE OR REPLACE FUNCTION BOMPRICESTD +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceStd.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return Standard Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE(SUM(PriceStd), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricestd (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + -- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + END LOOP; -- BOM + END IF; + -- + RETURN v_Price; +END Bompricestd; +/ + +CREATE OR REPLACE FUNCTION BOMQTYONHANDFORRESERVATION +( + Product_ID IN NUMBER, + Warehouse_ID IN NUMBER, + Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity on hand for BOM + */ +AS + myWarehouse_ID NUMBER; + Quantity NUMBER := 99999; -- unlimited + IsBOM CHAR(1); + IsStocked CHAR(1); + ProductType CHAR(1); + ProductQty NUMBER; + StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + myWarehouse_ID := Warehouse_ID; + IF (myWarehouse_ID IS NULL) THEN + IF (Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT SUM(M_Warehouse_ID) INTO myWarehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=Locator_ID; + END IF; + END IF; + IF (myWarehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || myWarehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO IsBOM, ProductType, IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + -- Unlimited capacity if no item + IF (IsBOM='N' AND (ProductType<>'I' OR IsStocked='N')) THEN + RETURN Quantity; + -- Stocked item + ELSIF (IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + LEFT JOIN M_LocatorType lt ON (l.M_LocatorType_ID=lt.M_LocatorType_ID) + WHERE s.M_Product_ID=Product_ID AND l.M_Warehouse_ID=myWarehouse_ID + AND COALESCE(lt.IsAvailableForReservation,'Y')='Y'; + -- + -- DBMS_OUTPUT.PUT_LINE('Qty=' || ProductQty); + RETURN ProductQty; + END IF; + + -- Go through BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + LEFT JOIN M_LocatorType lt ON (l.M_LocatorType_ID=lt.M_LocatorType_ID) + WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID + AND COALESCE(lt.IsAvailableForReservation,'Y')='Y'; + -- Get Rounding Precision + SELECT NVL(MAX(u.StdPrecision), 0) + INTO StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; + -- How much can we make with this product + ProductQty := ROUND (ProductQty/bom.BOMQty, StdPrecision); + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND bom.IsVerified = 'Y') THEN + ProductQty := BomqtyonhandForReservation (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + IF (Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; + -- + RETURN ROUND (Quantity, StdPrecision); + END IF; + RETURN 0; +END BOMQTYONHANDFORRESERVATION; +/ + +CREATE OR REPLACE FUNCTION BOMQTYONHAND +( + Product_ID IN NUMBER, + Warehouse_ID IN NUMBER, + Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity on hand for BOM + */ +AS + myWarehouse_ID NUMBER; + Quantity NUMBER := 99999; -- unlimited + IsBOM CHAR(1); + IsStocked CHAR(1); + ProductType CHAR(1); + ProductQty NUMBER; + StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + myWarehouse_ID := Warehouse_ID; + IF (myWarehouse_ID IS NULL) THEN + IF (Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT SUM(M_Warehouse_ID) INTO myWarehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=Locator_ID; + END IF; + END IF; + IF (myWarehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || myWarehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO IsBOM, ProductType, IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + -- Unlimited capacity if no item + IF (IsBOM='N' AND (ProductType<>'I' OR IsStocked='N')) THEN + RETURN Quantity; + -- Stocked item + ELSIF (IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + WHERE s.M_Product_ID=Product_ID AND l.M_Warehouse_ID=myWarehouse_ID; + -- + -- DBMS_OUTPUT.PUT_LINE('Qty=' || ProductQty); + RETURN ProductQty; + END IF; + + -- Go through BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID; + -- Get Rounding Precision + SELECT NVL(MAX(u.StdPrecision), 0) + INTO StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; + -- How much can we make with this product + ProductQty := ROUND (ProductQty/bom.BOMQty, StdPrecision); + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN + ProductQty := Bomqtyonhand (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + IF (Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; + -- + RETURN ROUND (Quantity, StdPrecision); + END IF; + RETURN 0; +END Bomqtyonhand; +/ + +CREATE OR REPLACE FUNCTION BOMQTYORDERED +( + p_Product_ID IN NUMBER, + p_Warehouse_ID IN NUMBER, + p_Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity ordered for BOM + */ +AS + v_Warehouse_ID NUMBER; + v_Quantity NUMBER := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty NUMBER; + v_StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=p_Product_ID + AND b.M_ProductBOM_ID != p_Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + v_Warehouse_ID := p_Warehouse_ID; + IF (v_Warehouse_ID IS NULL) THEN + IF (p_Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT MAX(M_Warehouse_ID) INTO v_Warehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=p_Locator_ID; + END IF; + END IF; + IF (v_Warehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || v_Warehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=p_Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + + -- No reservation for non-stocked + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN 0; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='N' + AND IsActive='Y'; + -- + RETURN v_ProductQty; + END IF; + + -- Go though BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='N' + AND IsActive='Y'; + -- Get Rounding Precision + SELECT NVL(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; + -- How much can we make with this product + v_ProductQty := ROUND (v_ProductQty/bom.BOMQty, v_StdPrecision); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN + v_ProductQty := Bomqtyordered (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + -- Unlimited (e.g. only services) + IF (v_Quantity = 99999) THEN + RETURN 0; + END IF; + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; + -- + RETURN ROUND (v_Quantity, v_StdPrecision); + END IF; + -- + RETURN 0; +END Bomqtyordered; +/ + +CREATE OR REPLACE FUNCTION BOMQTYRESERVED +( + p_Product_ID IN NUMBER, + p_Warehouse_ID IN NUMBER, + p_Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity reserved for BOM + */ +AS + v_Warehouse_ID NUMBER; + v_Quantity NUMBER := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty NUMBER; + v_StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=p_Product_ID + AND b.M_ProductBOM_ID != p_Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + v_Warehouse_ID := p_Warehouse_ID; + IF (v_Warehouse_ID IS NULL) THEN + IF (p_Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT MAX(M_Warehouse_ID) INTO v_Warehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=p_Locator_ID; + END IF; + END IF; + IF (v_Warehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || v_Warehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=p_Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + + -- No reservation for non-stocked + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN 0; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='Y' + AND IsActive='Y'; + -- + RETURN v_ProductQty; + END IF; + + -- Go though BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=bom.M_ProductBOM_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='Y' + AND IsActive='Y'; + -- Get Rounding Precision + SELECT NVL(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; + -- How much can we make with this product + v_ProductQty := ROUND (v_ProductQty/bom.BOMQty, v_StdPrecision); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN + v_ProductQty := Bomqtyreserved (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + -- Unlimited (e.g. only services) + IF (v_Quantity = 99999) THEN + RETURN 0; + END IF; + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; + -- + RETURN ROUND (v_Quantity, v_StdPrecision); + END IF; + RETURN 0; +END Bomqtyreserved; +/ + +CREATE OR REPLACE FUNCTION BOMPRICELIMIT +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceLimit.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return Limit Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from PriceList directly + SELECT COALESCE (SUM(PriceLimit), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricelimit (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; +END Bompricelimit; +/ + +CREATE OR REPLACE FUNCTION BOMPRICELIST +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceList.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return List Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE (SUM(PriceList), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricelist (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + -- DBMS_OUTPUT.PUT_LINE('Qry=' || bom.BOMQty || ' @ ' || v_ProductPrice || ', Price=' || v_Price); + END LOOP; -- BOM + END IF; + -- + RETURN v_Price; +END Bompricelist; +/ + +CREATE OR REPLACE FUNCTION BOMPRICESTD +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceStd.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return Standard Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE(SUM(PriceStd), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricestd (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + -- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + END LOOP; -- BOM + END IF; + -- + RETURN v_Price; +END Bompricestd; +/ + +CREATE OR REPLACE FUNCTION BOMQTYONHANDFORRESERVATION +( + Product_ID IN NUMBER, + Warehouse_ID IN NUMBER, + Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity on hand for BOM + */ +AS + myWarehouse_ID NUMBER; + Quantity NUMBER := 99999; -- unlimited + IsBOM CHAR(1); + IsStocked CHAR(1); + ProductType CHAR(1); + ProductQty NUMBER; + StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + myWarehouse_ID := Warehouse_ID; + IF (myWarehouse_ID IS NULL) THEN + IF (Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT SUM(M_Warehouse_ID) INTO myWarehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=Locator_ID; + END IF; + END IF; + IF (myWarehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || myWarehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO IsBOM, ProductType, IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + -- Unlimited capacity if no item + IF (IsBOM='N' AND (ProductType<>'I' OR IsStocked='N')) THEN + RETURN Quantity; + -- Stocked item + ELSIF (IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + LEFT JOIN M_LocatorType lt ON (l.M_LocatorType_ID=lt.M_LocatorType_ID) + WHERE s.M_Product_ID=Product_ID AND l.M_Warehouse_ID=myWarehouse_ID + AND COALESCE(lt.IsAvailableForReservation,'Y')='Y'; + -- + -- DBMS_OUTPUT.PUT_LINE('Qty=' || ProductQty); + RETURN ProductQty; + END IF; + + -- Go through BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + LEFT JOIN M_LocatorType lt ON (l.M_LocatorType_ID=lt.M_LocatorType_ID) + WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID + -- How much can we make with this product + ProductQty := ProductQty/bom.BOMQty; + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND bom.IsVerified = 'Y') THEN + ProductQty := BomqtyonhandForReservation (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + IF (Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; + -- + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown + END IF; + RETURN 0; +END BOMQTYONHANDFORRESERVATION; +/ + +CREATE OR REPLACE FUNCTION BOMQTYONHAND +( + Product_ID IN NUMBER, + Warehouse_ID IN NUMBER, + Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity on hand for BOM + */ +AS + myWarehouse_ID NUMBER; + Quantity NUMBER := 99999; -- unlimited + IsBOM CHAR(1); + IsStocked CHAR(1); + ProductType CHAR(1); + ProductQty NUMBER; + StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + myWarehouse_ID := Warehouse_ID; + IF (myWarehouse_ID IS NULL) THEN + IF (Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT SUM(M_Warehouse_ID) INTO myWarehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=Locator_ID; + END IF; + END IF; + IF (myWarehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || myWarehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO IsBOM, ProductType, IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + -- Unlimited capacity if no item + IF (IsBOM='N' AND (ProductType<>'I' OR IsStocked='N')) THEN + RETURN Quantity; + -- Stocked item + ELSIF (IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + WHERE s.M_Product_ID=Product_ID AND l.M_Warehouse_ID=myWarehouse_ID; + -- + -- DBMS_OUTPUT.PUT_LINE('Qty=' || ProductQty); + RETURN ProductQty; + END IF; + + -- Go through BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID; + -- How much can we make with this product + ProductQty := ProductQty/bom.BOMQty; + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN + ProductQty := Bomqtyonhand (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + IF (Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; + -- + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown + END IF; + RETURN 0; +END Bomqtyonhand; +/ + +CREATE OR REPLACE FUNCTION BOMQTYORDERED +( + p_Product_ID IN NUMBER, + p_Warehouse_ID IN NUMBER, + p_Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity ordered for BOM + */ +AS + v_Warehouse_ID NUMBER; + v_Quantity NUMBER := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty NUMBER; + v_StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=p_Product_ID + AND b.M_ProductBOM_ID != p_Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + v_Warehouse_ID := p_Warehouse_ID; + IF (v_Warehouse_ID IS NULL) THEN + IF (p_Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT MAX(M_Warehouse_ID) INTO v_Warehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=p_Locator_ID; + END IF; + END IF; + IF (v_Warehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || v_Warehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=p_Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + + -- No reservation for non-stocked + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN 0; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='N' + AND IsActive='Y'; + -- + RETURN v_ProductQty; + END IF; + + -- Go though BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='N' + AND IsActive='Y'; + -- How much can we make with this product + v_ProductQty := v_ProductQty/bom.BOMQty; + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN + v_ProductQty := Bomqtyordered (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + -- Unlimited (e.g. only services) + IF (v_Quantity = 99999) THEN + RETURN 0; + END IF; + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; + -- + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown + END IF; + -- + RETURN 0; +END Bomqtyordered; +/ + +CREATE OR REPLACE FUNCTION BOMQTYRESERVED +( + p_Product_ID IN NUMBER, + p_Warehouse_ID IN NUMBER, + p_Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity reserved for BOM + */ +AS + v_Warehouse_ID NUMBER; + v_Quantity NUMBER := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty NUMBER; + v_StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=p_Product_ID + AND b.M_ProductBOM_ID != p_Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + v_Warehouse_ID := p_Warehouse_ID; + IF (v_Warehouse_ID IS NULL) THEN + IF (p_Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT MAX(M_Warehouse_ID) INTO v_Warehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=p_Locator_ID; + END IF; + END IF; + IF (v_Warehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || v_Warehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=p_Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + + -- No reservation for non-stocked + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN 0; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='Y' + AND IsActive='Y'; + -- + RETURN v_ProductQty; + END IF; + + -- Go though BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=bom.M_ProductBOM_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='Y' + AND IsActive='Y'; + -- How much can we make with this product + v_ProductQty := v_ProductQty/bom.BOMQty; + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN + v_ProductQty := Bomqtyreserved (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + -- Unlimited (e.g. only services) + IF (v_Quantity = 99999) THEN + RETURN 0; + END IF; + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; + -- + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown + END IF; + RETURN 0; +END Bomqtyreserved; +/ + +CREATE OR REPLACE FUNCTION BOMPRICELIMIT +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceLimit.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return Limit Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from PriceList directly + SELECT COALESCE (SUM(PriceLimit), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricelimit (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; +END Bompricelimit; +/ + +CREATE OR REPLACE FUNCTION BOMPRICELIST +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceList.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return List Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE (SUM(PriceList), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricelist (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + -- DBMS_OUTPUT.PUT_LINE('Qry=' || bom.BOMQty || ' @ ' || v_ProductPrice || ', Price=' || v_Price); + END LOOP; -- BOM + END IF; + -- + RETURN v_Price; +END Bompricelist; +/ + +CREATE OR REPLACE FUNCTION BOMPRICESTD +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceStd.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return Standard Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE(SUM(PriceStd), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricestd (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + -- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + END LOOP; -- BOM + END IF; + -- + RETURN v_Price; +END Bompricestd; +/ + +CREATE OR REPLACE FUNCTION BOMQTYONHANDFORRESERVATION +( + Product_ID IN NUMBER, + Warehouse_ID IN NUMBER, + Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity on hand for BOM + */ +AS + myWarehouse_ID NUMBER; + Quantity NUMBER := 99999; -- unlimited + IsBOM CHAR(1); + IsStocked CHAR(1); + ProductType CHAR(1); + ProductQty NUMBER; + StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + myWarehouse_ID := Warehouse_ID; + IF (myWarehouse_ID IS NULL) THEN + IF (Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT SUM(M_Warehouse_ID) INTO myWarehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=Locator_ID; + END IF; + END IF; + IF (myWarehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || myWarehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO IsBOM, ProductType, IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + -- Unlimited capacity if no item + IF (IsBOM='N' AND (ProductType<>'I' OR IsStocked='N')) THEN + RETURN Quantity; + -- Stocked item + ELSIF (IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + LEFT JOIN M_LocatorType lt ON (l.M_LocatorType_ID=lt.M_LocatorType_ID) + WHERE s.M_Product_ID=Product_ID AND l.M_Warehouse_ID=myWarehouse_ID + AND COALESCE(lt.IsAvailableForReservation,'Y')='Y'; + -- + -- DBMS_OUTPUT.PUT_LINE('Qty=' || ProductQty); + RETURN ProductQty; + END IF; + + -- Go through BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + LEFT JOIN M_LocatorType lt ON (l.M_LocatorType_ID=lt.M_LocatorType_ID) + WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID + -- How much can we make with this product + ProductQty := ProductQty/bom.BOMQty; + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND bom.IsVerified = 'Y') THEN + ProductQty := BomqtyonhandForReservation (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + IF (Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; + -- + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown + END IF; + RETURN 0; +END BOMQTYONHANDFORRESERVATION; +/ + +CREATE OR REPLACE FUNCTION BOMQTYONHAND +( + Product_ID IN NUMBER, + Warehouse_ID IN NUMBER, + Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity on hand for BOM + */ +AS + myWarehouse_ID NUMBER; + Quantity NUMBER := 99999; -- unlimited + IsBOM CHAR(1); + IsStocked CHAR(1); + ProductType CHAR(1); + ProductQty NUMBER; + StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + myWarehouse_ID := Warehouse_ID; + IF (myWarehouse_ID IS NULL) THEN + IF (Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT SUM(M_Warehouse_ID) INTO myWarehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=Locator_ID; + END IF; + END IF; + IF (myWarehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || myWarehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO IsBOM, ProductType, IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + -- Unlimited capacity if no item + IF (IsBOM='N' AND (ProductType<>'I' OR IsStocked='N')) THEN + RETURN Quantity; + -- Stocked item + ELSIF (IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + WHERE s.M_Product_ID=Product_ID AND l.M_Warehouse_ID=myWarehouse_ID; + -- + -- DBMS_OUTPUT.PUT_LINE('Qty=' || ProductQty); + RETURN ProductQty; + END IF; + + -- Go through BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID; + -- How much can we make with this product + ProductQty := ProductQty/bom.BOMQty; + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN + ProductQty := Bomqtyonhand (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + IF (Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; + -- + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown + END IF; + RETURN 0; +END Bomqtyonhand; +/ + +CREATE OR REPLACE FUNCTION BOMQTYORDERED +( + p_Product_ID IN NUMBER, + p_Warehouse_ID IN NUMBER, + p_Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity ordered for BOM + */ +AS + v_Warehouse_ID NUMBER; + v_Quantity NUMBER := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty NUMBER; + v_StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=p_Product_ID + AND b.M_ProductBOM_ID != p_Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + v_Warehouse_ID := p_Warehouse_ID; + IF (v_Warehouse_ID IS NULL) THEN + IF (p_Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT MAX(M_Warehouse_ID) INTO v_Warehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=p_Locator_ID; + END IF; + END IF; + IF (v_Warehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || v_Warehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=p_Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + + -- No reservation for non-stocked + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN 0; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='N' + AND IsActive='Y'; + -- + RETURN v_ProductQty; + END IF; + + -- Go though BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='N' + AND IsActive='Y'; + -- How much can we make with this product + v_ProductQty := v_ProductQty/bom.BOMQty; + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN + v_ProductQty := Bomqtyordered (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + -- Unlimited (e.g. only services) + IF (v_Quantity = 99999) THEN + RETURN 0; + END IF; + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; + -- + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown + END IF; + -- + RETURN 0; +END Bomqtyordered; +/ + +CREATE OR REPLACE FUNCTION BOMQTYRESERVED +( + p_Product_ID IN NUMBER, + p_Warehouse_ID IN NUMBER, + p_Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity reserved for BOM + */ +AS + v_Warehouse_ID NUMBER; + v_Quantity NUMBER := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty NUMBER; + v_StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=p_Product_ID + AND b.M_ProductBOM_ID != p_Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + v_Warehouse_ID := p_Warehouse_ID; + IF (v_Warehouse_ID IS NULL) THEN + IF (p_Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT MAX(M_Warehouse_ID) INTO v_Warehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=p_Locator_ID; + END IF; + END IF; + IF (v_Warehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || v_Warehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=p_Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + + -- No reservation for non-stocked + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN 0; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='Y' + AND IsActive='Y'; + -- + RETURN v_ProductQty; + END IF; + + -- Go though BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=bom.M_ProductBOM_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='Y' + AND IsActive='Y'; + -- How much can we make with this product + v_ProductQty := v_ProductQty/bom.BOMQty; + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN + v_ProductQty := Bomqtyreserved (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + -- Unlimited (e.g. only services) + IF (v_Quantity = 99999) THEN + RETURN 0; + END IF; + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; + -- + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown + END IF; + RETURN 0; +END Bomqtyreserved; +/ + +CREATE OR REPLACE FUNCTION BOMPRICELIMIT +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceLimit.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return Limit Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from PriceList directly + SELECT COALESCE (SUM(PriceLimit), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricelimit (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; +END Bompricelimit; +/ + +CREATE OR REPLACE FUNCTION BOMPRICELIST +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceList.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return List Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE (SUM(PriceList), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricelist (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + -- DBMS_OUTPUT.PUT_LINE('Qry=' || bom.BOMQty || ' @ ' || v_ProductPrice || ', Price=' || v_Price); + END LOOP; -- BOM + END IF; + -- + RETURN v_Price; +END Bompricelist; +/ + +CREATE OR REPLACE FUNCTION BOMPRICESTD +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceStd.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return Standard Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE(SUM(PriceStd), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricestd (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + -- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + END LOOP; -- BOM + END IF; + -- + RETURN v_Price; +END Bompricestd; +/ + +CREATE OR REPLACE FUNCTION BOMQTYONHANDFORRESERVATION +( + Product_ID IN NUMBER, + Warehouse_ID IN NUMBER, + Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity on hand for BOM + */ +AS + myWarehouse_ID NUMBER; + Quantity NUMBER := 99999; -- unlimited + IsBOM CHAR(1); + IsStocked CHAR(1); + ProductType CHAR(1); + ProductQty NUMBER; + StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + myWarehouse_ID := Warehouse_ID; + IF (myWarehouse_ID IS NULL) THEN + IF (Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT SUM(M_Warehouse_ID) INTO myWarehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=Locator_ID; + END IF; + END IF; + IF (myWarehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || myWarehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO IsBOM, ProductType, IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + -- Unlimited capacity if no item + IF (IsBOM='N' AND (ProductType<>'I' OR IsStocked='N')) THEN + RETURN Quantity; + -- Stocked item + ELSIF (IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + LEFT JOIN M_LocatorType lt ON (l.M_LocatorType_ID=lt.M_LocatorType_ID) + WHERE s.M_Product_ID=Product_ID AND l.M_Warehouse_ID=myWarehouse_ID + AND COALESCE(lt.IsAvailableForReservation,'Y')='Y'; + -- + -- DBMS_OUTPUT.PUT_LINE('Qty=' || ProductQty); + RETURN ProductQty; + END IF; + + -- Go through BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + LEFT JOIN M_LocatorType lt ON (l.M_LocatorType_ID=lt.M_LocatorType_ID) + WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID + -- How much can we make with this product + ProductQty := ProductQty/bom.BOMQty; + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND bom.IsVerified = 'Y') THEN + ProductQty := BomqtyonhandForReservation (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + IF (Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; + -- + RETURN TRUNC(Quantity, StdPrecision); -- RoundDown + END IF; + RETURN 0; +END BOMQTYONHANDFORRESERVATION; +/ + +CREATE OR REPLACE FUNCTION BOMQTYONHAND +( + Product_ID IN NUMBER, + Warehouse_ID IN NUMBER, + Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity on hand for BOM + */ +AS + myWarehouse_ID NUMBER; + Quantity NUMBER := 99999; -- unlimited + IsBOM CHAR(1); + IsStocked CHAR(1); + ProductType CHAR(1); + ProductQty NUMBER; + StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + myWarehouse_ID := Warehouse_ID; + IF (myWarehouse_ID IS NULL) THEN + IF (Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT SUM(M_Warehouse_ID) INTO myWarehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=Locator_ID; + END IF; + END IF; + IF (myWarehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || myWarehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO IsBOM, ProductType, IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + -- Unlimited capacity if no item + IF (IsBOM='N' AND (ProductType<>'I' OR IsStocked='N')) THEN + RETURN Quantity; + -- Stocked item + ELSIF (IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + WHERE s.M_Product_ID=Product_ID AND l.M_Warehouse_ID=myWarehouse_ID; + -- + -- DBMS_OUTPUT.PUT_LINE('Qty=' || ProductQty); + RETURN ProductQty; + END IF; + + -- Go through BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID; + -- How much can we make with this product + ProductQty := ProductQty/bom.BOMQty; + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN + ProductQty := Bomqtyonhand (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + IF (Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; + -- + RETURN TRUNC(Quantity, StdPrecision); -- RoundDown + END IF; + RETURN 0; +END Bomqtyonhand; +/ + +CREATE OR REPLACE FUNCTION BOMQTYORDERED +( + p_Product_ID IN NUMBER, + p_Warehouse_ID IN NUMBER, + p_Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity ordered for BOM + */ +AS + v_Warehouse_ID NUMBER; + v_Quantity NUMBER := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty NUMBER; + v_StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=p_Product_ID + AND b.M_ProductBOM_ID != p_Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + v_Warehouse_ID := p_Warehouse_ID; + IF (v_Warehouse_ID IS NULL) THEN + IF (p_Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT MAX(M_Warehouse_ID) INTO v_Warehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=p_Locator_ID; + END IF; + END IF; + IF (v_Warehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || v_Warehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=p_Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + + -- No reservation for non-stocked + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN 0; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='N' + AND IsActive='Y'; + -- + RETURN v_ProductQty; + END IF; + + -- Go though BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='N' + AND IsActive='Y'; + -- How much can we make with this product + v_ProductQty := v_ProductQty/bom.BOMQty; + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN + v_ProductQty := Bomqtyordered (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + -- Unlimited (e.g. only services) + IF (v_Quantity = 99999) THEN + RETURN 0; + END IF; + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; + -- + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown + END IF; + -- + RETURN 0; +END Bomqtyordered; +/ + +CREATE OR REPLACE FUNCTION BOMQTYRESERVED +( + p_Product_ID IN NUMBER, + p_Warehouse_ID IN NUMBER, + p_Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity reserved for BOM + */ +AS + v_Warehouse_ID NUMBER; + v_Quantity NUMBER := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty NUMBER; + v_StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=p_Product_ID + AND b.M_ProductBOM_ID != p_Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + v_Warehouse_ID := p_Warehouse_ID; + IF (v_Warehouse_ID IS NULL) THEN + IF (p_Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT MAX(M_Warehouse_ID) INTO v_Warehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=p_Locator_ID; + END IF; + END IF; + IF (v_Warehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || v_Warehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=p_Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + + -- No reservation for non-stocked + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN 0; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='Y' + AND IsActive='Y'; + -- + RETURN v_ProductQty; + END IF; + + -- Go though BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=bom.M_ProductBOM_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='Y' + AND IsActive='Y'; + -- How much can we make with this product + v_ProductQty := v_ProductQty/bom.BOMQty; + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN + v_ProductQty := Bomqtyreserved (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + -- Unlimited (e.g. only services) + IF (v_Quantity = 99999) THEN + RETURN 0; + END IF; + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; + -- + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown + END IF; + RETURN 0; +END Bomqtyreserved; +/ + +CREATE OR REPLACE FUNCTION BOMPRICELIMIT +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceLimit.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return Limit Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from PriceList directly + SELECT COALESCE (SUM(PriceLimit), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricelimit (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; +END Bompricelimit; +/ + +CREATE OR REPLACE FUNCTION BOMPRICELIST +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceList.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return List Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE (SUM(PriceList), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricelist (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + -- DBMS_OUTPUT.PUT_LINE('Qry=' || bom.BOMQty || ' @ ' || v_ProductPrice || ', Price=' || v_Price); + END LOOP; -- BOM + END IF; + -- + RETURN v_Price; +END Bompricelist; +/ + +CREATE OR REPLACE FUNCTION BOMPRICESTD +( + Product_ID IN NUMBER, + PriceList_Version_ID IN NUMBER +) +RETURN NUMBER +/************************************************************************* + * The contents of this file are subject to the Compiere License. You may + * obtain a copy of the License at http://www.compiere.org/license.html + * Software is on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for details. Code: Compiere ERP+CRM + * Copyright (C) 1999-2002 Jorg Janke, ComPiere, Inc. All Rights Reserved. + ************************************************************************* + * $Id: BOM_PriceStd.sql,v 1.1 2006/04/21 17:51:58 jjanke Exp $ + *** + * Title: Return Standard Price of Product/BOM + * Description: + * if not found: 0 + ************************************************************************/ +AS + v_Price NUMBER; + v_ProductPrice NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') + AND b.IsActive='Y'; + -- +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE(SUM(PriceStd), 0) + INTO v_Price + FROM M_PRODUCTPRICE + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; +-- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN CUR_BOM LOOP + v_ProductPrice := Bompricestd (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + -- DBMS_OUTPUT.PUT_LINE('Price=' || v_Price); + END LOOP; -- BOM + END IF; + -- + RETURN v_Price; +END Bompricestd; +/ + +CREATE OR REPLACE FUNCTION BOMQTYONHANDFORRESERVATION +( + Product_ID IN NUMBER, + Warehouse_ID IN NUMBER, + Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity on hand for BOM + */ +AS + myWarehouse_ID NUMBER; + Quantity NUMBER := 99999; -- unlimited + IsBOM CHAR(1); + IsStocked CHAR(1); + ProductType CHAR(1); + ProductQty NUMBER; + StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + myWarehouse_ID := Warehouse_ID; + IF (myWarehouse_ID IS NULL) THEN + IF (Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT SUM(M_Warehouse_ID) INTO myWarehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=Locator_ID; + END IF; + END IF; + IF (myWarehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || myWarehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO IsBOM, ProductType, IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + -- Unlimited capacity if no item + IF (IsBOM='N' AND (ProductType<>'I' OR IsStocked='N')) THEN + RETURN Quantity; + -- Stocked item + ELSIF (IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + LEFT JOIN M_LocatorType lt ON (l.M_LocatorType_ID=lt.M_LocatorType_ID) + WHERE s.M_Product_ID=Product_ID AND l.M_Warehouse_ID=myWarehouse_ID + AND COALESCE(lt.IsAvailableForReservation,'Y')='Y'; + -- + -- DBMS_OUTPUT.PUT_LINE('Qty=' || ProductQty); + RETURN ProductQty; + END IF; + + -- Go through BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + LEFT JOIN M_LocatorType lt ON (l.M_LocatorType_ID=lt.M_LocatorType_ID) + WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID + AND COALESCE(lt.IsAvailableForReservation,'Y')='Y'; + -- How much can we make with this product + ProductQty := ProductQty/bom.BOMQty; + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND bom.IsVerified = 'Y') THEN + ProductQty := BomqtyonhandForReservation (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + IF (Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; + -- + RETURN TRUNC(Quantity, StdPrecision); -- RoundDown + END IF; + RETURN 0; +END BOMQTYONHANDFORRESERVATION; +/ + +CREATE OR REPLACE FUNCTION BOMQTYONHAND +( + Product_ID IN NUMBER, + Warehouse_ID IN NUMBER, + Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity on hand for BOM + */ +AS + myWarehouse_ID NUMBER; + Quantity NUMBER := 99999; -- unlimited + IsBOM CHAR(1); + IsStocked CHAR(1); + ProductType CHAR(1); + ProductQty NUMBER; + StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + myWarehouse_ID := Warehouse_ID; + IF (myWarehouse_ID IS NULL) THEN + IF (Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT SUM(M_Warehouse_ID) INTO myWarehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=Locator_ID; + END IF; + END IF; + IF (myWarehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || myWarehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO IsBOM, ProductType, IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + -- Unlimited capacity if no item + IF (IsBOM='N' AND (ProductType<>'I' OR IsStocked='N')) THEN + RETURN Quantity; + -- Stocked item + ELSIF (IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + WHERE s.M_Product_ID=Product_ID AND l.M_Warehouse_ID=myWarehouse_ID; + -- + -- DBMS_OUTPUT.PUT_LINE('Qty=' || ProductQty); + RETURN ProductQty; + END IF; + + -- Go through BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(QtyOnHand), 0) + INTO ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID; + -- How much can we make with this product + ProductQty := ProductQty/bom.BOMQty; + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN + ProductQty := Bomqtyonhand (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); + -- How much can we make overall + IF (ProductQty < Quantity) THEN + Quantity := ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + IF (Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; + -- + RETURN TRUNC(Quantity, StdPrecision); -- RoundDown + END IF; + RETURN 0; +END Bomqtyonhand; +/ + +CREATE OR REPLACE FUNCTION BOMQTYORDERED +( + p_Product_ID IN NUMBER, + p_Warehouse_ID IN NUMBER, + p_Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity ordered for BOM + */ +AS + v_Warehouse_ID NUMBER; + v_Quantity NUMBER := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty NUMBER; + v_StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=p_Product_ID + AND b.M_ProductBOM_ID != p_Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + v_Warehouse_ID := p_Warehouse_ID; + IF (v_Warehouse_ID IS NULL) THEN + IF (p_Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT MAX(M_Warehouse_ID) INTO v_Warehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=p_Locator_ID; + END IF; + END IF; + IF (v_Warehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || v_Warehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=p_Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + + -- No reservation for non-stocked + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN 0; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='N' + AND IsActive='Y'; + -- + RETURN v_ProductQty; + END IF; + + -- Go though BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='N' + AND IsActive='Y'; + -- How much can we make with this product + v_ProductQty := v_ProductQty/bom.BOMQty; + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN + v_ProductQty := Bomqtyordered (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + -- Unlimited (e.g. only services) + IF (v_Quantity = 99999) THEN + RETURN 0; + END IF; + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; + -- + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown + END IF; + -- + RETURN 0; +END Bomqtyordered; +/ + +CREATE OR REPLACE FUNCTION BOMQTYRESERVED +( + p_Product_ID IN NUMBER, + p_Warehouse_ID IN NUMBER, + p_Locator_ID IN NUMBER -- Only used, if warehouse is null +) +RETURN NUMBER +/****************************************************************************** + * ** Compiere Product ** Copyright (c) 1999-2001 Accorto, Inc. USA + * Open Source Software Provided "AS IS" without warranty or liability + * When you use any parts (changed or unchanged), add "Powered by Compiere" to + * your product name; See license details http://www.compiere.org/license.html + ****************************************************************************** + * Return quantity reserved for BOM + */ +AS + v_Warehouse_ID NUMBER; + v_Quantity NUMBER := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty NUMBER; + v_StdPrecision NUMBER; + -- Get BOM Product info + CURSOR CUR_BOM IS + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=p_Product_ID + AND b.M_ProductBOM_ID != p_Product_ID + AND b.IsActive='Y'; + -- +BEGIN + -- Check Parameters + v_Warehouse_ID := p_Warehouse_ID; + IF (v_Warehouse_ID IS NULL) THEN + IF (p_Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT MAX(M_Warehouse_ID) INTO v_Warehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=p_Locator_ID; + END IF; + END IF; + IF (v_Warehouse_ID IS NULL) THEN + RETURN 0; + END IF; +-- DBMS_OUTPUT.PUT_LINE('Warehouse=' || v_Warehouse_ID); + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=p_Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + + -- No reservation for non-stocked + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN 0; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='Y' + AND IsActive='Y'; + -- + RETURN v_ProductQty; + END IF; + + -- Go though BOM +-- DBMS_OUTPUT.PUT_LINE('BOM'); + FOR bom IN CUR_BOM LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT NVL(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=bom.M_ProductBOM_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='Y' + AND IsActive='Y'; + -- How much can we make with this product + v_ProductQty := v_ProductQty/bom.BOMQty; + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN + v_ProductQty := Bomqtyreserved (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + -- Unlimited (e.g. only services) + IF (v_Quantity = 99999) THEN + RETURN 0; + END IF; + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT NVL(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; + -- + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown + END IF; + RETURN 0; +END Bomqtyreserved; +/ + diff --git a/migration/iD11/oracle/202412101102_IDEMPIERE-6335.sql b/migration/iD11/oracle/202412101102_IDEMPIERE-6335.sql new file mode 100644 index 0000000000..57a2a7375d --- /dev/null +++ b/migration/iD11/oracle/202412101102_IDEMPIERE-6335.sql @@ -0,0 +1,10 @@ +-- IDEMPIERE-6335 +SELECT register_migration_script('202412101102_IDEMPIERE-6335.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Dec 10, 2024, 11:06:45 AM BRT +INSERT INTO AD_Process_Para (AD_Process_Para_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,Name,Description,Help,AD_Process_ID,SeqNo,AD_Reference_ID,IsRange,AD_Val_Rule_ID,FieldLength,IsMandatory,ColumnName,IsCentrallyMaintained,EntityType,AD_Element_ID,AD_Process_Para_UU,IsEncrypted,IsAutocomplete,DateRangeOption,IsShowNegateButton) VALUES (200484,0,0,'Y',TO_TIMESTAMP('2024-12-10 11:06:44','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-12-10 11:06:44','YYYY-MM-DD HH24:MI:SS'),100,'Document Type','Document type or rules','The Document Type determines document sequence and processing rules',142,20,19,'N',52054,0,'N','C_DocType_ID','Y','D',196,'2b9ac743-0893-474a-aa96-c1eafe8fba86','N','N','D','N') +; + diff --git a/migration/iD11/postgresql/202402261300_IDEMPIERE-2981.sql b/migration/iD11/postgresql/202402261300_IDEMPIERE-2981.sql new file mode 100644 index 0000000000..e12163649a --- /dev/null +++ b/migration/iD11/postgresql/202402261300_IDEMPIERE-2981.sql @@ -0,0 +1,7 @@ +-- IDEMPIERE-2981 - Implement JSON Field type +SELECT register_migration_script('202402261300_IDEMPIERE-2981.sql') FROM dual; + +-- Feb 26, 2024, 1:00:29 PM CET +INSERT INTO AD_Reference (AD_Reference_ID,Name,Description,ValidationType,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,EntityType,IsOrderByValue,AD_Reference_UU,ShowInactive) VALUES (200267,'JSON','JSON format values','D',0,0,'Y',TO_TIMESTAMP('2024-02-26 13:00:28','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:00:28','YYYY-MM-DD HH24:MI:SS'),100,'D','N','b6fcc751-edd8-4421-acd0-3cde02a9576d','N') +; + diff --git a/migration/iD11/postgresql/202402261354_IDEMPIERE-2981.sql b/migration/iD11/postgresql/202402261354_IDEMPIERE-2981.sql new file mode 100644 index 0000000000..57daa577b4 --- /dev/null +++ b/migration/iD11/postgresql/202402261354_IDEMPIERE-2981.sql @@ -0,0 +1,27 @@ +-- IDEMPIERE-2981 - Implement JSON Field type +SELECT register_migration_script('202402261354_IDEMPIERE-2981.sql') FROM dual; + +-- Feb 26, 2024, 1:54:35 PM CET +INSERT INTO AD_Element (AD_Element_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,ColumnName,Name,Description,PrintName,EntityType,AD_Element_UU) VALUES (203924,0,0,'Y',TO_TIMESTAMP('2024-02-26 13:54:35','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:54:35','YYYY-MM-DD HH24:MI:SS'),100,'JsonData','JSON Data','The json field stores json data.','JSON Data','D','c4ea7a81-96a9-4a5d-bb87-e913e1c8ed48') +; + +-- Feb 26, 2024, 1:55:37 PM CET +INSERT INTO AD_Column (AD_Column_ID,Version,Name,Description,AD_Table_ID,ColumnName,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,IsHtml,IsPartitionKey) VALUES (216570,0,'JSON Data','The json field stores json data.',135,'JsonData',0,'N','N','N','N','N',0,'N',200267,0,0,'Y',TO_TIMESTAMP('2024-02-26 13:55:36','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:55:36','YYYY-MM-DD HH24:MI:SS'),100,203924,'Y','N','D','N','N','N','Y','927b83df-d161-4332-ad44-8ffed99e8cf4','Y',0,'N','N','N','N') +; + +-- Feb 26, 2024, 1:55:55 PM CET +ALTER TABLE Test ADD COLUMN JsonData JSON DEFAULT NULL +; + +-- Feb 26, 2024, 1:56:08 PM CET +INSERT INTO AD_Field (AD_Field_ID,Name,Description,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,ColumnSpan) VALUES (208472,'JSON Data','The json field stores json data.',152,216570,'Y',100,310,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2024-02-26 13:56:08','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:56:08','YYYY-MM-DD HH24:MI:SS'),100,'N','Y','D','e47ef529-71ba-4f9b-9014-b188e17e8ef4','Y',290,5) +; + +-- Feb 29, 2024, 1:52:50 PM CET +UPDATE AD_Field SET NumLines=5,Updated=TO_TIMESTAMP('2024-02-29 13:52:50','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208472 +; + +-- Feb 29, 2024, 2:07:30 PM CET +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','Invalid JSON',0,0,'Y',TO_TIMESTAMP('2024-02-29 14:07:30','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-29 14:07:30','YYYY-MM-DD HH24:MI:SS'),100,200876,'InvalidJSON','D','a263376f-a12e-4943-92f1-7d7ce8a67a2b') +; + diff --git a/migration/iD11/postgresql/202404171125_IDEMPIERE-6110.sql b/migration/iD11/postgresql/202404171125_IDEMPIERE-6110.sql new file mode 100644 index 0000000000..8680472e4f --- /dev/null +++ b/migration/iD11/postgresql/202404171125_IDEMPIERE-6110.sql @@ -0,0 +1,7 @@ +-- IDEMPIERE-6110 +SELECT register_migration_script('202404171125_IDEMPIERE-6110.sql') FROM dual; + +-- Apr 17, 2024, 11:25:53 AM BRT +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','Products or charges configured as ''Freight Product'' or ''Freight Charge'' cannot be added to this order due to the combination of delivery via rule and freight cost rule',0,0,'Y',TO_TIMESTAMP('2024-04-17 11:25:53','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-04-17 11:25:53','YYYY-MM-DD HH24:MI:SS'),100,200897,'FreightOrderLineNotAllowed','D','204c8bb9-d002-4beb-bbc3-1d1a11d7471d') +; + diff --git a/migration/iD11/postgresql/202404220830_IDEMPIERE-5136_MissingDropIndex.sql b/migration/iD11/postgresql/202404220830_IDEMPIERE-5136_MissingDropIndex.sql new file mode 100644 index 0000000000..ceb792daea --- /dev/null +++ b/migration/iD11/postgresql/202404220830_IDEMPIERE-5136_MissingDropIndex.sql @@ -0,0 +1,4 @@ +-- IDEMPIERE-5136 +SELECT register_migration_script('202404220830_IDEMPIERE-5136_MissingDropIndex.sql') FROM dual; + +-- only for Oracle as there was a missing DROP INDEX instruction diff --git a/migration/iD11/postgresql/202404302320_IDEMPIERE-6123.sql b/migration/iD11/postgresql/202404302320_IDEMPIERE-6123.sql new file mode 100644 index 0000000000..ad27fcaac4 --- /dev/null +++ b/migration/iD11/postgresql/202404302320_IDEMPIERE-6123.sql @@ -0,0 +1,19 @@ +-- IDEMPIERE-6123 Query in search window causing slowness and load spikes in the database (FHCA-5356) +SELECT register_migration_script('202404302320_IDEMPIERE-6123.sql') FROM dual; + +-- Apr 30, 2024, 11:20:08 PM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200244,0,0,TO_TIMESTAMP('2024-04-30 23:20:08','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-04-30 23:20:08','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','GRIDTABLE_INITIAL_COUNT_TIMEOUT_IN_SECONDS','1','Timeout for the initial count on windows','D','C','5fae1af7-74ca-41d8-bbd3-d506c6c23b6a') +; + +-- Apr 30, 2024, 11:22:16 PM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200245,0,0,TO_TIMESTAMP('2024-04-30 23:22:16','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-04-30 23:22:16','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','GLOBAL_MAX_QUERY_RECORDS','100000','Maximum number of records allowed to search in a window, can be overriden per Role or Tab','D','C','840fb67c-4609-41f2-9e20-e0ea9d839065') +; + +-- Apr 30, 2024, 11:23:28 PM CEST +UPDATE AD_Message SET MsgText='The query returned more records than allowed, consider adding more filters.',Updated=TO_TIMESTAMP('2024-04-30 23:23:28','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Message_ID=852 +; + +-- Apr 30, 2024, 11:24:06 PM CEST +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('I','The initial count query timed out, loading records ...',0,0,'Y',TO_TIMESTAMP('2024-04-30 23:24:06','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-04-30 23:24:06','YYYY-MM-DD HH24:MI:SS'),100,200887,'CountQueryTimeoutLoadBackground','D','988292d7-175f-41c2-b560-43d62b8326a9') +; + diff --git a/migration/iD11/postgresql/202405071219_IDEMPIERE-6137.sql b/migration/iD11/postgresql/202405071219_IDEMPIERE-6137.sql new file mode 100644 index 0000000000..8ea63cd938 --- /dev/null +++ b/migration/iD11/postgresql/202405071219_IDEMPIERE-6137.sql @@ -0,0 +1,7 @@ +-- IDEMPIERE-6137 Payment Rule does not appear in reports from Sales Order +SELECT register_migration_script('202405071219_IDEMPIERE-6137.sql') FROM dual; + +-- May 7, 2024, 12:19:00 PM CEST +UPDATE AD_Column SET AD_Reference_Value_ID=195,Updated=TO_TIMESTAMP('2024-05-07 12:19:00','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Reference_ID=200012 AND COALESCE(AD_Reference_Value_ID,0)!=195 +; + diff --git a/migration/iD11/postgresql/202405080101_IDEMPIERE-6123.sql b/migration/iD11/postgresql/202405080101_IDEMPIERE-6123.sql new file mode 100644 index 0000000000..be168edcfe --- /dev/null +++ b/migration/iD11/postgresql/202405080101_IDEMPIERE-6123.sql @@ -0,0 +1,19 @@ +-- IDEMPIERE-6123 Query in search window causing slowness and load spikes in the database (FHCA-5356) +SELECT register_migration_script('202405080101_IDEMPIERE-6123.sql') FROM dual; + +-- May 8, 2024, 1:01:16 AM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200247,0,0,TO_TIMESTAMP('2024-05-08 01:01:16','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-05-08 01:01:16','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','REPORT_LOAD_TIMEOUT_IN_SECONDS','120','Timeout in seconds when loading a report','D','C','14e838b1-c25c-400e-b39c-61da9bf92099') +; + +-- May 8, 2024, 1:01:42 AM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200248,0,0,TO_TIMESTAMP('2024-05-08 01:01:41','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-05-08 01:01:41','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','GLOBAL_MAX_REPORT_RECORDS','100000','Max number of records allowed in a report','D','C','7030640a-1aa7-4ac7-a894-b4fe0dfde530') +; + +-- May 8, 2024, 1:06:19 AM CEST +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('I','The data query for the report took too much time to execute (over {0} seconds) exceeding the allowed limit',0,0,'Y',TO_TIMESTAMP('2024-05-08 01:06:18','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-05-08 01:06:18','YYYY-MM-DD HH24:MI:SS'),100,200893,'ReportQueryTimeout','D','5f17f55f-adbe-4d97-bf83-9447983b4946') +; + +-- May 8, 2024, 1:07:10 AM CEST +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('I','The report data exceeds the maximum limit of {0} rows',0,0,'Y',TO_TIMESTAMP('2024-05-08 01:07:09','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-05-08 01:07:09','YYYY-MM-DD HH24:MI:SS'),100,200894,'ReportMaxRowsReached','D','a4b55c31-0df0-4302-a62a-91cb7e79be0d') +; + diff --git a/migration/iD11/postgresql/202405131534_IDEMPIERE-6040.sql b/migration/iD11/postgresql/202405131534_IDEMPIERE-6040.sql new file mode 100644 index 0000000000..962d64dfb4 --- /dev/null +++ b/migration/iD11/postgresql/202405131534_IDEMPIERE-6040.sql @@ -0,0 +1,22 @@ +-- IDEMPIERE-6040 Improvements for CSV import template +SELECT register_migration_script('202405131534_IDEMPIERE-6040.sql') FROM dual; + +-- May 13, 2024, 3:34:49 PM CEST +UPDATE AD_Ref_List SET Name='Comma-separated values (CSV)',Updated=TO_TIMESTAMP('2024-05-13 15:34:49','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Ref_List_ID=200704 +; + +-- May 13, 2024, 3:35:58 PM CEST +UPDATE AD_Ref_List SET Name='Excel (XLS/XLSX)',Updated=TO_TIMESTAMP('2024-05-13 15:35:58','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Ref_List_ID=200706 +; + +-- May 13, 2024, 3:36:02 PM CEST +UPDATE AD_Ref_List SET IsActive='N',Updated=TO_TIMESTAMP('2024-05-13 15:36:02','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Ref_List_ID=200705 +; + +UPDATE AD_ImportTemplate SET ImportTemplateType='XLSX' WHERE ImportTemplateType='XLS' +; + +-- May 13, 2024, 3:59:49 PM CEST +UPDATE AD_Field SET SeqNo=120, ColumnSpan=2,Updated=TO_TIMESTAMP('2024-05-13 15:59:49','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208476 +; + diff --git a/migration/iD11/postgresql/202405151228_IDEMPIERE-5728.sql b/migration/iD11/postgresql/202405151228_IDEMPIERE-5728.sql new file mode 100644 index 0000000000..e5da84f8db --- /dev/null +++ b/migration/iD11/postgresql/202405151228_IDEMPIERE-5728.sql @@ -0,0 +1,7 @@ +-- IDEMPIERE-5728 +SELECT register_migration_script('202405151228_IDEMPIERE-5728.sql') FROM dual; + +-- May 15, 2024, 12:28:30 PM CEST +UPDATE AD_ViewColumn SET ColumnName='RV_UnPosted_UU',Updated=TO_TIMESTAMP('2024-05-15 12:28:30','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_ViewColumn_ID=217690 +; + diff --git a/migration/iD11/postgresql/202406072107_IDEMPIERE-6166.sql b/migration/iD11/postgresql/202406072107_IDEMPIERE-6166.sql new file mode 100644 index 0000000000..9d12bb5b3d --- /dev/null +++ b/migration/iD11/postgresql/202406072107_IDEMPIERE-6166.sql @@ -0,0 +1,30 @@ +-- IDEMPIERE-6166 PostgreSQL DUAL table with more than one record +SELECT register_migration_script('202406072107_IDEMPIERE-6166.sql') FROM dual; + +DROP TABLE IF EXISTS dual +; + +CREATE VIEW dual AS SELECT 'X'::varchar AS dummy +; + +DROP RULE insert_dbreplicasyncverifier ON dbreplicasyncverifier +; + +CREATE OR REPLACE FUNCTION forbid_multiple_rows_in_dbreplicasyncverifier() +RETURNS TRIGGER AS $$ +BEGIN + -- Check if the table already contains a row + IF (SELECT COUNT(*) FROM dbreplicasyncverifier) > 0 THEN + RAISE EXCEPTION 'Table dbreplicasyncverifier can only contain one row.'; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql +; + +CREATE TRIGGER single_row_only_trigger_dbreplicasyncverifier +BEFORE INSERT ON dbreplicasyncverifier +FOR EACH ROW +EXECUTE FUNCTION forbid_multiple_rows_in_dbreplicasyncverifier() +; + diff --git a/migration/iD11/postgresql/202406072206_IDEMPIERE-6167.sql b/migration/iD11/postgresql/202406072206_IDEMPIERE-6167.sql new file mode 100644 index 0000000000..74210abf71 --- /dev/null +++ b/migration/iD11/postgresql/202406072206_IDEMPIERE-6167.sql @@ -0,0 +1,117 @@ +-- IDEMPIERE-6167 BOM Price List must search just for Verified BOMs +SELECT register_migration_script('202406072206_IDEMPIERE-6167.sql') FROM dual; + +CREATE OR REPLACE FUNCTION bompricelimit (in product_id numeric, in pricelist_version_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + v_Price NUMERIC; + v_ProductPrice NUMERIC; + bom RECORD; + +BEGIN + -- Try to get price from PriceList directly + SELECT COALESCE (SUM(PriceLimit), 0) + INTO v_Price + FROM M_ProductPrice + WHERE M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_Product_BOM b, M_Product p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' + AND b.IsActive='Y' + LOOP + v_ProductPrice := bomPriceLimit (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; + +END; + +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + +CREATE OR REPLACE FUNCTION bompricelist (in product_id numeric, in pricelist_version_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + v_Price NUMERIC; + v_ProductPrice NUMERIC; + bom RECORD; + +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE (SUM(PriceList), 0) + INTO v_Price + FROM M_ProductPrice + WHERE M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_Product_BOM b, M_Product p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' + AND b.IsActive='Y' + LOOP + v_ProductPrice := bomPriceList (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; + +END; + +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + +CREATE OR REPLACE FUNCTION bompricestd (in product_id numeric, in pricelist_version_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + v_Price NUMERIC; + v_ProductPrice NUMERIC; + bom RECORD; + +BEGIN + -- Try to get price from PriceList directly + SELECT COALESCE(SUM(PriceStd), 0) + INTO v_Price + FROM M_ProductPrice + WHERE M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_Product_BOM b, M_Product p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' + AND b.IsActive='Y' + LOOP + v_ProductPrice := bomPriceStd (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; + +END; + +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + diff --git a/migration/iD11/postgresql/202406131736_IDEMPIERE-6169.sql b/migration/iD11/postgresql/202406131736_IDEMPIERE-6169.sql new file mode 100644 index 0000000000..f1c233ddaf --- /dev/null +++ b/migration/iD11/postgresql/202406131736_IDEMPIERE-6169.sql @@ -0,0 +1,9 @@ +-- IDEMPIERE-6169 Performance on AD_ChangeLog with Record_UU +SELECT register_migration_script('202406131736_IDEMPIERE-6169.sql') FROM dual; + +ALTER TABLE ad_changelog DROP CONSTRAINT ad_changelog_pkey +; + +ALTER TABLE ad_changelog ADD CONSTRAINT ad_changelog_pkey PRIMARY KEY (ad_session_id, ad_column_id, ad_changelog_id) +; + diff --git a/migration/iD11/postgresql/202406180027_IDEMPIERE-6176.sql b/migration/iD11/postgresql/202406180027_IDEMPIERE-6176.sql new file mode 100644 index 0000000000..3c5551609e --- /dev/null +++ b/migration/iD11/postgresql/202406180027_IDEMPIERE-6176.sql @@ -0,0 +1,19 @@ +-- IDEMPIERE-6176 UUID indexes without constraint +SELECT register_migration_script('202406180027_IDEMPIERE-6176.sql') FROM dual; + +-- Jun 18, 2024, 12:29:20 AM CEST +UPDATE AD_IndexColumn SET IsActive='N',Updated=TO_TIMESTAMP('2024-06-18 00:29:20','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_IndexColumn_ID=200980 +; + +-- Jun 18, 2024, 12:29:27 AM CEST +UPDATE AD_TableIndex SET IsActive='N', IsCreateConstraint='Y',Updated=TO_TIMESTAMP('2024-06-18 00:29:27','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_TableIndex_ID=200806 +; + +-- Jun 18, 2024, 12:30:44 AM CEST +UPDATE AD_TableIndex SET IsCreateConstraint='Y',Updated=TO_TIMESTAMP('2024-06-18 00:30:44','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_TableIndex_ID=201272 +; + +-- Jun 18, 2024, 12:31:49 AM CEST +UPDATE AD_TableIndex SET IsCreateConstraint='Y',Updated=TO_TIMESTAMP('2024-06-18 00:31:49','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_TableIndex_ID=201275 +; + diff --git a/migration/iD11/postgresql/202407061229_IDEMPIERE-6188.sql b/migration/iD11/postgresql/202407061229_IDEMPIERE-6188.sql new file mode 100644 index 0000000000..069052d6ad --- /dev/null +++ b/migration/iD11/postgresql/202407061229_IDEMPIERE-6188.sql @@ -0,0 +1,47 @@ +-- IDEMPIERE-6188 Read-Only Session +SELECT register_migration_script('202407061229_IDEMPIERE-6188.sql') FROM dual; + +-- Jul 6, 2024, 12:29:56 PM CEST +INSERT INTO AD_Element (AD_Element_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,ColumnName,Name,Description,Help,PrintName,EntityType,AD_Element_UU) VALUES (203938,0,0,'Y',TO_TIMESTAMP('2024-07-06 12:29:42','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-07-06 12:29:42','YYYY-MM-DD HH24:MI:SS'),100,'IsReadOnlySession','Read Only Session',NULL,NULL,'Read Only Session','D','6ee179e4-9573-4a3d-8815-23723ef241d7') +; + +-- Jul 6, 2024, 12:30:19 PM CEST +INSERT INTO AD_Column (AD_Column_ID,Version,Name,AD_Table_ID,ColumnName,DefaultValue,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,IsHtml,IsPartitionKey) VALUES (216614,0,'Read Only Session',200174,'IsReadOnlySession','N',1,'N','N','Y','N','N',0,'N',20,0,0,'Y',TO_TIMESTAMP('2024-07-06 12:30:18','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-07-06 12:30:18','YYYY-MM-DD HH24:MI:SS'),100,203938,'Y','N','D','N','N','N','Y','8c255baa-fbec-4a90-a43d-5461060368e9','Y',0,'N','N','N','N') +; + +-- Jul 6, 2024, 12:30:29 PM CEST +ALTER TABLE AD_UserPreference ADD COLUMN IsReadOnlySession CHAR(1) DEFAULT 'N' CHECK (IsReadOnlySession IN ('Y','N')) NOT NULL +; + +-- Jul 6, 2024, 12:30:45 PM CEST +INSERT INTO AD_Field (AD_Field_ID,Name,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,XPosition,ColumnSpan) VALUES (208494,'Read Only Session',200189,216614,'Y',1,170,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2024-07-06 12:30:45','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-07-06 12:30:45','YYYY-MM-DD HH24:MI:SS'),100,'N','Y','D','f69131e7-4fca-4011-89d5-650ca506c920','Y',170,2,2) +; + +-- Jul 6, 2024, 12:31:06 PM CEST +UPDATE AD_Field SET SeqNo=110,Updated=TO_TIMESTAMP('2024-07-06 12:31:06','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=206133 +; + +-- Jul 6, 2024, 12:31:06 PM CEST +UPDATE AD_Field SET SeqNo=120,Updated=TO_TIMESTAMP('2024-07-06 12:31:06','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=206134 +; + +-- Jul 6, 2024, 12:31:06 PM CEST +UPDATE AD_Field SET SeqNo=130,Updated=TO_TIMESTAMP('2024-07-06 12:31:06','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=206407 +; + +-- Jul 6, 2024, 12:31:06 PM CEST +UPDATE AD_Field SET SeqNo=140,Updated=TO_TIMESTAMP('2024-07-06 12:31:06','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208189 +; + +-- Jul 6, 2024, 12:31:06 PM CEST +UPDATE AD_Field SET IsDisplayed='Y', SeqNo=150, XPosition=5,Updated=TO_TIMESTAMP('2024-07-06 12:31:06','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208494 +; + +-- Jul 6, 2024, 12:31:25 PM CEST +UPDATE AD_Field SET IsQuickEntry='Y',Updated=TO_TIMESTAMP('2024-07-06 12:31:25','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208494 +; + +-- Jul 9, 2024, 7:48:40 PM CEST +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('I','Read-only session',0,0,'Y',TO_TIMESTAMP('2024-07-09 19:48:39','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-07-09 19:48:39','YYYY-MM-DD HH24:MI:SS'),100,200900,'ReadOnlySession','D','dc819f55-662d-4d30-b68d-0a93b46a8458') +; + diff --git a/migration/iD11/postgresql/202407231647_IDEMPIERE-6196.sql b/migration/iD11/postgresql/202407231647_IDEMPIERE-6196.sql new file mode 100644 index 0000000000..79820482c8 --- /dev/null +++ b/migration/iD11/postgresql/202407231647_IDEMPIERE-6196.sql @@ -0,0 +1,15 @@ +-- IDEMPIERE-6196 +SELECT register_migration_script('202407231647_IDEMPIERE-6196.sql') FROM dual; + +-- Jul 23, 2024, 4:47:40 PM CEST +UPDATE AD_Column SET SeqNoSelection=30,Updated=TO_TIMESTAMP('2024-07-23 16:47:40','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=10 WHERE AD_Column_ID=103 +; + +-- Jul 23, 2024, 4:47:43 PM CEST +UPDATE AD_Column SET SeqNoSelection=20,Updated=TO_TIMESTAMP('2024-07-23 16:47:43','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=10 WHERE AD_Column_ID=102 +; + +-- Jul 23, 2024, 4:47:52 PM CEST +UPDATE AD_Column SET SeqNoSelection=10,Updated=TO_TIMESTAMP('2024-07-23 16:47:52','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=10 WHERE AD_Column_ID=107 +; + diff --git a/migration/iD11/postgresql/202408191316_IDEMPIERE-6189.sql b/migration/iD11/postgresql/202408191316_IDEMPIERE-6189.sql new file mode 100644 index 0000000000..8ee6a91ad5 --- /dev/null +++ b/migration/iD11/postgresql/202408191316_IDEMPIERE-6189.sql @@ -0,0 +1,15 @@ +-- IDEMPIERE-6189 Performance: queries on RV_C_Invoice doing unnecesary index or seq scan +SELECT register_migration_script('202408191316_IDEMPIERE-6189.sql') FROM dual; + +-- Aug 19, 2024, 1:16:19 PM CEST +UPDATE AD_ViewComponent SET FromClause='FROM c_invoice i +LEFT JOIN c_doctype d ON i.c_doctype_id = d.c_doctype_id +LEFT JOIN c_bpartner b ON i.c_bpartner_id = b.c_bpartner_id +LEFT JOIN c_bpartner_location bpl ON i.c_bpartner_location_id = bpl.c_bpartner_location_id +LEFT JOIN c_location loc ON bpl.c_location_id = loc.c_location_id',Updated=TO_TIMESTAMP('2024-08-19 13:16:19','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_ViewComponent_ID=200116 +; + +-- Aug 19, 2024, 1:16:27 PM CEST +CREATE OR REPLACE VIEW RV_C_Invoice(C_Invoice_ID, AD_Client_ID, AD_Org_ID, IsActive, Created, CreatedBy, Updated, UpdatedBy, IsSOTrx, DocumentNo, DocStatus, DocAction, IsPrinted, IsDiscountPrinted, Processing, Processed, IsTransferred, IsPaid, C_DocType_ID, C_DocTypeTarget_ID, C_Order_ID, Description, IsApproved, SalesRep_ID, DateInvoiced, DatePrinted, DateAcct, C_BPartner_ID, C_BPartner_Location_ID, AD_User_ID, C_BP_Group_ID, POReference, DateOrdered, C_Currency_ID, C_ConversionType_ID, PaymentRule, C_PaymentTerm_ID, M_PriceList_ID, C_Campaign_ID, C_Project_ID, C_Activity_ID, IsPayScheduleValid, InvoiceCollectionType, C_Country_ID, C_Region_ID, Postal, City, C_Charge_ID, ChargeAmt, TotalLines, GrandTotal, Multiplier, C_Invoice_AD_OrgTrx_ID, C_Invoice_C_ConversionType_ID, C_DunningLevel_ID, C_Payment_ID, c_invoice_dateordered, DunningGrace, GenerateTo, IsInDispute, c_invoice_ispayschedulevalid, c_invoice_isselfservice, IsTaxIncluded, M_RMA_ID, Posted, ProcessedOn, Ref_Invoice_ID, Reversal_ID, SendEMail, User1_ID, User2_ID, c_bp_acqusitioncost, c_bp_actuallifetimevalue, c_bp_ad_language, C_BP_AD_OrgBP_ID, C_BP_AD_Org_ID, C_BP_BPartner_Parent_ID, C_BP_C_Dunning_ID, C_BP_C_Greeting_ID, C_BP_C_InvoiceSchedule_ID, C_BP_C_PaymentTerm_ID, c_bp_created, C_BP_CreatedBy, C_BP_C_TaxGroup_ID, c_bp_deliveryrule, c_bp_deliveryviarule, c_bp_description, c_bp_dunninggrace, c_bp_duns, c_bp_firstsale, c_bp_flatdiscount, c_bp_freightcostrule, c_bp_invoicerule, c_bp_isactive, c_bp_iscustomer, c_bp_isdiscountprinted, c_bp_isemployee, c_bp_ismanufacturer, c_bp_isonetime, c_bp_ispotaxexempt, c_bp_isprospect, c_bp_issalesrep, c_bp_issummary, c_bp_istaxexempt, c_bp_isvendor, C_BP_Logo_ID, C_BP_M_DiscountSchema_ID, C_BP_M_PriceList_ID, c_bp_naics, c_bp_name, c_bp_name2, c_bp_numberemployees, c_bp_paymentrule, c_bp_paymentrulepo, C_BP_PO_DiscountSchema_ID, C_BP_PO_PaymentTerm_ID, C_BP_PO_PriceList_ID, c_bp_poreference, c_bp_potentiallifetimevalue, c_bp_rating, c_bp_referenceno, C_BP_SalesRep_ID, c_bp_salesvolume, c_bp_sendemail, c_bp_shareofcustomer, c_bp_shelflifeminpct, c_bp_so_creditlimit, c_bp_socreditstatus, c_bp_so_creditused, c_bp_so_description, TaxID, c_bp_totalopenbalance, c_bp_updated, C_BP_UpdatedBy, c_bp_url, c_bp_value, C_BP_Location_AD_Org_ID, C_BP_Location_C_BPartner_ID, C_BP_Location_C_Location_ID, c_bp_location_created, C_BP_Location_CreatedBy, C_SalesRegion_ID, c_bp_location_fax, c_bp_location_isactive, IsBillTo, ISDN, IsPayFrom, IsRemitTo, IsShipTo, c_bp_location_name, c_bp_location_phone, c_bp_location_phone2, c_bp_location_updated, C_BP_Location_UpdatedBy, Address1, Address2, Address3, Address4, C_Location_AD_Org_ID, C_City_ID, c_location_created, C_Location_CreatedBy, c_location_isactive, Postal_Add, RegionName, c_location_updated, C_Location_UpdatedBy) AS SELECT i.c_invoice_id AS C_Invoice_ID, i.ad_client_id AS AD_Client_ID, i.ad_org_id AS AD_Org_ID, i.isactive AS IsActive, i.created AS Created, i.createdby AS CreatedBy, i.updated AS Updated, i.updatedby AS UpdatedBy, i.issotrx AS IsSOTrx, i.documentno AS DocumentNo, i.docstatus AS DocStatus, i.docaction AS DocAction, i.isprinted AS IsPrinted, i.isdiscountprinted AS IsDiscountPrinted, i.processing AS Processing, i.processed AS Processed, i.istransferred AS IsTransferred, i.ispaid AS IsPaid, i.c_doctype_id AS C_DocType_ID, i.c_doctypetarget_id AS C_DocTypeTarget_ID, i.c_order_id AS C_Order_ID, i.description AS Description, i.isapproved AS IsApproved, i.salesrep_id AS SalesRep_ID, i.dateinvoiced AS DateInvoiced, i.dateprinted AS DatePrinted, i.dateacct AS DateAcct, i.c_bpartner_id AS C_BPartner_ID, i.c_bpartner_location_id AS C_BPartner_Location_ID, i.ad_user_id AS AD_User_ID, b.c_bp_group_id AS C_BP_Group_ID, i.poreference AS POReference, i.dateordered AS DateOrdered, i.c_currency_id AS C_Currency_ID, i.c_conversiontype_id AS C_ConversionType_ID, i.paymentrule AS PaymentRule, i.c_paymentterm_id AS C_PaymentTerm_ID, i.m_pricelist_id AS M_PriceList_ID, i.c_campaign_id AS C_Campaign_ID, i.c_project_id AS C_Project_ID, i.c_activity_id AS C_Activity_ID, i.ispayschedulevalid AS IsPayScheduleValid, i.invoicecollectiontype AS InvoiceCollectionType, loc.c_country_id AS C_Country_ID, loc.c_region_id AS C_Region_ID, loc.postal AS Postal, loc.city AS City, i.c_charge_id AS C_Charge_ID, CASE WHEN charat(d.docbasetype, 3) = 'C' THEN i.chargeamt * '-1' ELSE i.chargeamt END AS ChargeAmt, CASE WHEN charat(d.docbasetype, 3) = 'C' THEN i.totallines * '-1' ELSE i.totallines END AS TotalLines, CASE WHEN charat(d.docbasetype, 3) = 'C' THEN i.grandtotal * '-1' ELSE i.grandtotal END AS GrandTotal, CASE WHEN charat(d.docbasetype, 3) = 'C' THEN -1 ELSE 1 END AS Multiplier, i.ad_orgtrx_id AS C_Invoice_AD_OrgTrx_ID, i.c_conversiontype_id AS C_Invoice_C_ConversionType_ID, i.c_dunninglevel_id AS C_DunningLevel_ID, i.c_payment_id AS C_Payment_ID, i.dateordered AS c_invoice_dateordered, i.dunninggrace AS DunningGrace, i.generateto AS GenerateTo, i.isindispute AS IsInDispute, i.ispayschedulevalid AS c_invoice_ispayschedulevalid, i.isselfservice AS c_invoice_isselfservice, i.istaxincluded AS IsTaxIncluded, i.m_rma_id AS M_RMA_ID, i.posted AS Posted, i.processedon AS ProcessedOn, i.ref_invoice_id AS Ref_Invoice_ID, i.reversal_id AS Reversal_ID, i.sendemail AS SendEMail, i.user1_id AS User1_ID, i.user2_id AS User2_ID, b.acqusitioncost AS c_bp_acqusitioncost, b.actuallifetimevalue AS c_bp_actuallifetimevalue, b.ad_language AS c_bp_ad_language, b.ad_orgbp_id AS C_BP_AD_OrgBP_ID, b.ad_org_id AS C_BP_AD_Org_ID, b.bpartner_parent_id AS C_BP_BPartner_Parent_ID, b.c_dunning_id AS C_BP_C_Dunning_ID, b.c_greeting_id AS C_BP_C_Greeting_ID, b.c_invoiceschedule_id AS C_BP_C_InvoiceSchedule_ID, b.c_paymentterm_id AS C_BP_C_PaymentTerm_ID, b.created AS c_bp_created, b.createdby AS C_BP_CreatedBy, b.c_taxgroup_id AS C_BP_C_TaxGroup_ID, b.deliveryrule AS c_bp_deliveryrule, b.deliveryviarule AS c_bp_deliveryviarule, b.description AS c_bp_description, b.dunninggrace AS c_bp_dunninggrace, b.duns AS c_bp_duns, b.firstsale AS c_bp_firstsale, b.flatdiscount AS c_bp_flatdiscount, b.freightcostrule AS c_bp_freightcostrule, b.invoicerule AS c_bp_invoicerule, b.isactive AS c_bp_isactive, b.iscustomer AS c_bp_iscustomer, b.isdiscountprinted AS c_bp_isdiscountprinted, b.isemployee AS c_bp_isemployee, b.ismanufacturer AS c_bp_ismanufacturer, b.isonetime AS c_bp_isonetime, b.ispotaxexempt AS c_bp_ispotaxexempt, b.isprospect AS c_bp_isprospect, b.issalesrep AS c_bp_issalesrep, b.issummary AS c_bp_issummary, b.istaxexempt AS c_bp_istaxexempt, b.isvendor AS c_bp_isvendor, b.logo_id AS C_BP_Logo_ID, b.m_discountschema_id AS C_BP_M_DiscountSchema_ID, b.m_pricelist_id AS C_BP_M_PriceList_ID, b.naics AS c_bp_naics, b.name AS c_bp_name, b.name2 AS c_bp_name2, b.numberemployees AS c_bp_numberemployees, b.paymentrule AS c_bp_paymentrule, b.paymentrulepo AS c_bp_paymentrulepo, b.po_discountschema_id AS C_BP_PO_DiscountSchema_ID, b.po_paymentterm_id AS C_BP_PO_PaymentTerm_ID, b.po_pricelist_id AS C_BP_PO_PriceList_ID, b.poreference AS c_bp_poreference, b.potentiallifetimevalue AS c_bp_potentiallifetimevalue, b.rating AS c_bp_rating, b.referenceno AS c_bp_referenceno, b.salesrep_id AS C_BP_SalesRep_ID, b.salesvolume AS c_bp_salesvolume, b.sendemail AS c_bp_sendemail, b.shareofcustomer AS c_bp_shareofcustomer, b.shelflifeminpct AS c_bp_shelflifeminpct, b.so_creditlimit AS c_bp_so_creditlimit, b.socreditstatus AS c_bp_socreditstatus, b.so_creditused AS c_bp_so_creditused, b.so_description AS c_bp_so_description, b.taxid AS TaxID, b.totalopenbalance AS c_bp_totalopenbalance, b.updated AS c_bp_updated, b.updatedby AS C_BP_UpdatedBy, b.url AS c_bp_url, b.value AS c_bp_value, bpl.ad_org_id AS C_BP_Location_AD_Org_ID, bpl.c_bpartner_id AS C_BP_Location_C_BPartner_ID, bpl.c_location_id AS C_BP_Location_C_Location_ID, bpl.created AS c_bp_location_created, bpl.createdby AS C_BP_Location_CreatedBy, bpl.c_salesregion_id AS C_SalesRegion_ID, bpl.fax AS c_bp_location_fax, bpl.isactive AS c_bp_location_isactive, bpl.isbillto AS IsBillTo, bpl.isdn AS ISDN, bpl.ispayfrom AS IsPayFrom, bpl.isremitto AS IsRemitTo, bpl.isshipto AS IsShipTo, bpl.name AS c_bp_location_name, bpl.phone AS c_bp_location_phone, bpl.phone2 AS c_bp_location_phone2, bpl.updated AS c_bp_location_updated, bpl.updatedby AS C_BP_Location_UpdatedBy, loc.address1 AS Address1, loc.address2 AS Address2, loc.address3 AS Address3, loc.address4 AS Address4, loc.ad_org_id AS C_Location_AD_Org_ID, loc.c_city_id AS C_City_ID, loc.created AS c_location_created, loc.createdby AS C_Location_CreatedBy, loc.isactive AS c_location_isactive, loc.postal_add AS Postal_Add, loc.regionname AS RegionName, loc.updated AS c_location_updated, loc.updatedby AS C_Location_UpdatedBy FROM c_invoice i LEFT JOIN c_doctype d ON i.c_doctype_id = d.c_doctype_id LEFT JOIN c_bpartner b ON i.c_bpartner_id = b.c_bpartner_id LEFT JOIN c_bpartner_location bpl ON i.c_bpartner_location_id = bpl.c_bpartner_location_id LEFT JOIN c_location loc ON bpl.c_location_id = loc.c_location_id +; + diff --git a/migration/iD11/postgresql/202408201236_IDEMPIERE-6216.sql b/migration/iD11/postgresql/202408201236_IDEMPIERE-6216.sql new file mode 100644 index 0000000000..f88dfade9b --- /dev/null +++ b/migration/iD11/postgresql/202408201236_IDEMPIERE-6216.sql @@ -0,0 +1,7 @@ +-- IDEMPIERE-6216 +SELECT register_migration_script('202408201236_IDEMPIERE-6216.sql') FROM dual; + +-- Aug 20, 2024, 12:36:08 PM CEST +UPDATE AD_Process_Para SET MandatoryLogic='@UseDefaultCoA@=N',Updated=TO_TIMESTAMP('2024-08-20 12:36:08','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Process_Para_ID=53296 +; + diff --git a/migration/iD11/postgresql/202409041533_IDEMPIERE-6223.sql b/migration/iD11/postgresql/202409041533_IDEMPIERE-6223.sql new file mode 100644 index 0000000000..faa51c3861 --- /dev/null +++ b/migration/iD11/postgresql/202409041533_IDEMPIERE-6223.sql @@ -0,0 +1,91 @@ +-- Adding new fields to AD_PInstance and AD_PInstance_Log +SELECT register_migration_script('202409041533_IDEMPIERE-6223.sql') FROM dual; + +-- Sep 4, 2024, 3:33:12 PM BRT +INSERT INTO AD_Column (AD_Column_ID,Version,Name,Description,AD_Table_ID,ColumnName,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,FKConstraintType,IsHtml,IsPartitionKey) VALUES (216788,0,'JSON Data','The json field stores json data.',282,'JsonData',0,'N','N','N','N','N',0,'N',200267,0,0,'Y',TO_TIMESTAMP('2024-09-04 15:33:12','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-09-04 15:33:12','YYYY-MM-DD HH24:MI:SS'),100,203924,'Y','N','D','N','N','N','Y','6ec3e423-205d-482f-b7e2-3dea6b66d5d0','Y',0,'N','N','N','N','N') +; + +-- Sep 4, 2024, 3:33:16 PM BRT +ALTER TABLE AD_PInstance ADD COLUMN JsonData JSON DEFAULT NULL +; + +-- Sep 4, 2024, 3:33:39 PM BRT +INSERT INTO AD_Field (AD_Field_ID,Name,Description,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,ColumnSpan,NumLines) VALUES (208511,'JSON Data','The json field stores json data.',663,216788,'Y',0,210,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2024-09-04 15:33:39','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-09-04 15:33:39','YYYY-MM-DD HH24:MI:SS'),100,'N','Y','D','2455e488-36a8-48d2-8410-2ff0808915e4','Y',200,2,5) +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET IsDisplayed='Y', SeqNo=110, XPosition=1, ColumnSpan=5,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208511 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=120,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=10501 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=130,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=207416 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=140,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=10495 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=150,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=202845 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=160,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=202847 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=170,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=207405 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=180,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=207407 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=190,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=207406 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=200,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=207408 +; + +-- Sep 4, 2024, 3:34:12 PM BRT +UPDATE AD_Field SET SeqNo=210,Updated=TO_TIMESTAMP('2024-09-04 15:34:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=207720 +; + +-- Sep 4, 2024, 3:34:40 PM BRT +INSERT INTO AD_Column (AD_Column_ID,Version,Name,Description,AD_Table_ID,ColumnName,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,FKConstraintType,IsHtml,IsPartitionKey) VALUES (216789,0,'JSON Data','The json field stores json data.',578,'JsonData',0,'N','N','N','N','N',0,'N',200267,0,0,'Y',TO_TIMESTAMP('2024-09-04 15:34:40','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-09-04 15:34:40','YYYY-MM-DD HH24:MI:SS'),100,203924,'Y','N','D','N','N','N','Y','7539f67f-922d-4e27-881f-c04f0dc8c160','Y',0,'N','N','N','N','N') +; + +-- Sep 4, 2024, 3:34:43 PM BRT +ALTER TABLE AD_PInstance_Log ADD COLUMN JsonData JSON DEFAULT NULL +; + +-- Sep 4, 2024, 3:35:54 PM BRT +INSERT INTO AD_Field (AD_Field_ID,Name,Description,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,ColumnSpan,NumLines) VALUES (208512,'JSON Data','The json field stores json data.',665,216789,'Y',0,100,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2024-09-04 15:35:54','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-09-04 15:35:54','YYYY-MM-DD HH24:MI:SS'),100,'N','Y','D','27e958ae-9e31-4e8b-824e-e571df7da15c','Y',100,2,5) +; + +-- Sep 4, 2024, 3:36:15 PM BRT +UPDATE AD_Field SET IsDisplayed='Y', SeqNo=70, XPosition=1, ColumnSpan=5,Updated=TO_TIMESTAMP('2024-09-04 15:36:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208512 +; + +-- Sep 4, 2024, 3:36:15 PM BRT +UPDATE AD_Field SET SeqNo=80,Updated=TO_TIMESTAMP('2024-09-04 15:36:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=200309 +; + +-- Sep 4, 2024, 3:36:15 PM BRT +UPDATE AD_Field SET SeqNo=90,Updated=TO_TIMESTAMP('2024-09-04 15:36:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=200310 +; + +-- Sep 4, 2024, 3:36:15 PM BRT +UPDATE AD_Field SET SeqNo=100,Updated=TO_TIMESTAMP('2024-09-04 15:36:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=207622 +; + +-- Sep 4, 2024, 3:36:15 PM BRT +UPDATE AD_Field SET SeqNo=0,Updated=TO_TIMESTAMP('2024-09-04 15:36:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=204554 +; + diff --git a/migration/iD11/postgresql/202409141654_IDEMPIERE-5567.sql b/migration/iD11/postgresql/202409141654_IDEMPIERE-5567.sql new file mode 100644 index 0000000000..0d2ae8e785 --- /dev/null +++ b/migration/iD11/postgresql/202409141654_IDEMPIERE-5567.sql @@ -0,0 +1,19 @@ +-- IDEMPIERE-5567 Permalink for UUID multi-key tables +SELECT register_migration_script('202409141654_IDEMPIERE-5567.sql') FROM dual; + +-- Sep 14, 2024, 4:54:43 PM CEST +INSERT INTO AD_ZoomCondition (AD_Client_ID,AD_Org_ID,AD_Table_ID,AD_Window_ID,AD_ZoomCondition_ID,Created,CreatedBy,IsActive,Updated,UpdatedBy,SeqNo,Name,AD_ZoomCondition_UU,ZoomLogic,EntityType) VALUES (0,0,201,111,200009,TO_TIMESTAMP('2024-09-14 16:54:42','YYYY-MM-DD HH24:MI:SS'),100,'Y',TO_TIMESTAMP('2024-09-14 16:54:42','YYYY-MM-DD HH24:MI:SS'),100,10,'Zoom to role on tenants','f8ddf8c5-4bf6-4343-a2f3-c88c1f007344','@#AD_Client_ID@>0','D') +; + +-- Sep 14, 2024, 4:56:45 PM CEST +INSERT INTO AD_ZoomCondition (AD_Client_ID,AD_Org_ID,AD_Table_ID,AD_Window_ID,AD_ZoomCondition_ID,Created,CreatedBy,IsActive,Updated,UpdatedBy,SeqNo,Name,AD_ZoomCondition_UU,ZoomLogic,EntityType) VALUES (0,0,378,111,200010,TO_TIMESTAMP('2024-09-14 16:56:45','YYYY-MM-DD HH24:MI:SS'),100,'Y',TO_TIMESTAMP('2024-09-14 16:56:45','YYYY-MM-DD HH24:MI:SS'),100,10,'Zoom to role on tenants','32bab358-373f-476a-a52c-5c700044478d','@#AD_Client_ID@>0','D') +; + +-- Sep 14, 2024, 4:57:50 PM CEST +INSERT INTO AD_ZoomCondition (AD_Client_ID,AD_Org_ID,AD_Table_ID,AD_Window_ID,AD_ZoomCondition_ID,Created,CreatedBy,IsActive,Updated,UpdatedBy,SeqNo,Name,AD_ZoomCondition_UU,ZoomLogic,EntityType) VALUES (0,0,197,111,200011,TO_TIMESTAMP('2024-09-14 16:57:50','YYYY-MM-DD HH24:MI:SS'),100,'Y',TO_TIMESTAMP('2024-09-14 16:57:50','YYYY-MM-DD HH24:MI:SS'),100,10,'Zoom to role on tenants','ebea9694-6348-444e-8b7f-f5f1c75a3c65','@#AD_Client_ID@>0','D') +; + +-- Sep 14, 2024, 4:58:46 PM CEST +INSERT INTO AD_ZoomCondition (AD_Client_ID,AD_Org_ID,AD_Table_ID,AD_Window_ID,AD_ZoomCondition_ID,Created,CreatedBy,IsActive,Updated,UpdatedBy,SeqNo,Name,AD_ZoomCondition_UU,ZoomLogic,EntityType) VALUES (0,0,199,111,200012,TO_TIMESTAMP('2024-09-14 16:58:46','YYYY-MM-DD HH24:MI:SS'),100,'Y',TO_TIMESTAMP('2024-09-14 16:58:46','YYYY-MM-DD HH24:MI:SS'),100,10,'Zoom to role on tenants','91ce33c0-fb91-4c9e-894e-8e8f49d7677f','@#AD_Client_ID@>0','D') +; + diff --git a/migration/iD11/postgresql/202409141807_IDEMPIERE-6007.sql b/migration/iD11/postgresql/202409141807_IDEMPIERE-6007.sql new file mode 100644 index 0000000000..101ede3031 --- /dev/null +++ b/migration/iD11/postgresql/202409141807_IDEMPIERE-6007.sql @@ -0,0 +1,7 @@ +-- IDEMPIERE-6007 +SELECT register_migration_script('202409141807_IDEMPIERE-6007.sql') FROM dual; + +-- Sep 14, 2024, 6:07:20 PM CEST +UPDATE AD_Field SET ReadOnlyLogic='@SQL=SELECT 1 FROM AD_Field WHERE AD_Tab_ID=@AD_Tab_ID:0@',Updated=TO_TIMESTAMP('2024-09-14 18:07:20','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=131 +; + diff --git a/migration/iD11/postgresql/202409231820_IDEMPIERE-6248.sql b/migration/iD11/postgresql/202409231820_IDEMPIERE-6248.sql new file mode 100644 index 0000000000..0f4f5dc2ba --- /dev/null +++ b/migration/iD11/postgresql/202409231820_IDEMPIERE-6248.sql @@ -0,0 +1,7 @@ +-- IDEMPIERE-6248 +SELECT register_migration_script('202409231820_IDEMPIERE-6248.sql') FROM dual; + +-- Sep 23, 2024, 6:20:30 PM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200250,0,0,TO_TIMESTAMP('2024-09-23 18:20:29','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-09-23 18:20:29','YYYY-MM-DD HH24:MI:SS'),10,10,'Y','FULL_EXCEPTION_TRACE_IN_LOG','N','If set to Y, the stack trace is not cut in the log (see https://idempiere.atlassian.net/browse/IDEMPIERE-6248)','D','S','8308ac18-d0a6-479f-ab59-7edd0bcb0b54') +; + diff --git a/migration/iD11/postgresql/202409231821_IDEMPIERE-6248_DelSysConfig.sql b/migration/iD11/postgresql/202409231821_IDEMPIERE-6248_DelSysConfig.sql new file mode 100644 index 0000000000..9146a8de45 --- /dev/null +++ b/migration/iD11/postgresql/202409231821_IDEMPIERE-6248_DelSysConfig.sql @@ -0,0 +1,5 @@ +-- IDEMPIERE-6248 +SELECT register_migration_script('202409231821_IDEMPIERE-6248_DelSysConfig.sql') FROM dual; + +DELETE FROM AD_SysConfig WHERE AD_SysConfig_ID = 200250 +; diff --git a/migration/iD11/postgresql/202409241248_IDEMPIERE-5760.sql b/migration/iD11/postgresql/202409241248_IDEMPIERE-5760.sql new file mode 100644 index 0000000000..23e65099c2 --- /dev/null +++ b/migration/iD11/postgresql/202409241248_IDEMPIERE-5760.sql @@ -0,0 +1,11 @@ +-- IDEMPIERE-5760 Manage mail.smtp.timeout using SysConfig +SELECT register_migration_script('202409241248_IDEMPIERE-5760.sql') FROM dual; + +-- Sep 24, 2024, 12:48:56 PM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200253,0,0,TO_TIMESTAMP('2024-09-24 12:48:55','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-09-24 12:48:55','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','MAIL_SMTP_CONNECTIONTIMEOUT','-1','Timeout in milliseconds to wait for SMTP connection, -1 leaves the java default','D','C','36be68b7-7b4b-4725-9a51-aeb11bb7b699') +; + +-- Sep 24, 2024, 12:49:10 PM CEST +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200254,0,0,TO_TIMESTAMP('2024-09-24 12:49:09','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2024-09-24 12:49:09','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','MAIL_SMTP_WRITETIMEOUT','-1','Timeout in milliseconds to wait for writing on SMTP connection, -1 leaves the java default','D','C','e11d83c4-8500-400a-b9d2-358c30c910fb') +; + diff --git a/migration/iD11/postgresql/202410071412_IDEMPIERE-6196.sql b/migration/iD11/postgresql/202410071412_IDEMPIERE-6196.sql new file mode 100644 index 0000000000..63bde69934 --- /dev/null +++ b/migration/iD11/postgresql/202410071412_IDEMPIERE-6196.sql @@ -0,0 +1,15 @@ +-- IDEMPIERE-6196_SysConfig +SELECT register_migration_script('202410071412_IDEMPIERE-6196.sql') FROM dual; + +-- Oct 7, 2024, 2:12:19 PM CEST +UPDATE AD_Column SET SeqNoSelection=10,Updated=TO_TIMESTAMP('2024-10-07 14:12:19','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=10 WHERE AD_Column_ID=50195 +; + +-- Oct 7, 2024, 2:12:23 PM CEST +UPDATE AD_Column SET SeqNoSelection=20,Updated=TO_TIMESTAMP('2024-10-07 14:12:23','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=10 WHERE AD_Column_ID=50197 +; + +-- Oct 7, 2024, 2:12:27 PM CEST +UPDATE AD_Column SET SeqNoSelection=30,Updated=TO_TIMESTAMP('2024-10-07 14:12:27','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=10 WHERE AD_Column_ID=50196 +; + diff --git a/migration/iD11/postgresql/202411260821_IDEMPIERE-6317.sql b/migration/iD11/postgresql/202411260821_IDEMPIERE-6317.sql new file mode 100644 index 0000000000..46d56ebbba --- /dev/null +++ b/migration/iD11/postgresql/202411260821_IDEMPIERE-6317.sql @@ -0,0 +1,15 @@ +-- IDEMPIERE-6317 +SELECT register_migration_script('202411260821_IDEMPIERE-6317.sql') FROM dual; + +-- Nov 26, 2024, 8:34:46 AM BRT +UPDATE AD_Message SET Value='Can''t Save Tenant Level',Updated=TO_TIMESTAMP('2024-11-26 08:34:46','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Message_ID=53010 +; + +-- Nov 26, 2024, 8:36:49 AM BRT +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','This is a system or tenant parameter, you can''t save it as organization parameter',0,0,'Y',TO_TIMESTAMP('2024-11-26 08:36:48','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-11-26 08:36:48','YYYY-MM-DD HH24:MI:SS'),100,200914,'ThisIsSystemOrTenantParameter','D','8980c935-1c23-4d83-8b26-70eb3a749797') +; + +-- Nov 26, 2024, 8:38:56 AM BRT +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','This is a system parameter, you can''t save it as tenant parameter',0,0,'Y',TO_TIMESTAMP('2024-11-26 08:38:55','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-11-26 08:38:55','YYYY-MM-DD HH24:MI:SS'),100,200915,'ThisIsSystemParameter','D','6e6b2060-ec2d-4d87-b499-7250e93f7cec') +; + diff --git a/migration/iD11/postgresql/202411301155_IDEMPIERE-6329.sql b/migration/iD11/postgresql/202411301155_IDEMPIERE-6329.sql new file mode 100644 index 0000000000..beb85d4cd2 --- /dev/null +++ b/migration/iD11/postgresql/202411301155_IDEMPIERE-6329.sql @@ -0,0 +1,576 @@ +-- IDEMPIERE-6329 Bug in BOM* SQL functions not getting the correct BOM children +SELECT register_migration_script('202411301155_IDEMPIERE-6329.sql') FROM dual; + +CREATE OR REPLACE FUNCTION bompricelimit (in product_id numeric, in pricelist_version_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + v_Price NUMERIC; + v_ProductPrice NUMERIC; + bom RECORD; + +BEGIN + -- Try to get price from PriceList directly + SELECT COALESCE (SUM(PriceLimit), 0) + INTO v_Price + FROM M_ProductPrice + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_Product_BOM b, M_Product p + WHERE b.M_Product_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' + AND b.IsActive='Y' + LOOP + v_ProductPrice := bomPriceLimit (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; + +END; +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + +CREATE OR REPLACE FUNCTION bompricelist (in product_id numeric, in pricelist_version_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + v_Price NUMERIC; + v_ProductPrice NUMERIC; + bom RECORD; + +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE (SUM(PriceList), 0) + INTO v_Price + FROM M_ProductPrice + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_Product_BOM b, M_Product p + WHERE b.M_Product_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' + AND b.IsActive='Y' + LOOP + v_ProductPrice := bomPriceList (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; + +END; +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + +CREATE OR REPLACE FUNCTION bompricestd (in product_id numeric, in pricelist_version_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + v_Price NUMERIC; + v_ProductPrice NUMERIC; + bom RECORD; + +BEGIN + -- Try to get price from PriceList directly + SELECT COALESCE(SUM(PriceStd), 0) + INTO v_Price + FROM M_ProductPrice + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_Product_BOM b, M_Product p + WHERE b.M_Product_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsVerified='Y' + AND b.IsActive='Y' + LOOP + v_ProductPrice := bomPriceStd (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; + +END; +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + +CREATE OR REPLACE FUNCTION BOMQtyOnHandForReservation (in product_id numeric, in warehouse_id numeric, in locator_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + myWarehouse_ID numeric; + v_Quantity numeric := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty numeric; + v_StdPrecision int; + bom record; + +BEGIN + -- Check Parameters + myWarehouse_ID := Warehouse_ID; + IF (myWarehouse_ID IS NULL) THEN + IF (Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT SUM(M_Warehouse_ID) INTO myWarehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=Locator_ID; + END IF; + END IF; + IF (myWarehouse_ID IS NULL) THEN + RETURN 0; + END IF; + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + -- Unlimited capacity if no item + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN v_Quantity; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT COALESCE(SUM(QtyOnHand), 0) + INTO v_ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + LEFT JOIN M_LocatorType lt ON (l.M_LocatorType_ID=lt.M_LocatorType_ID) + WHERE s.M_Product_ID=Product_ID AND l.M_Warehouse_ID=myWarehouse_ID + AND COALESCE(lt.IsAvailableForReservation,'Y')='Y'; + -- + RETURN v_ProductQty; + END IF; + + -- Go through BOM + FOR bom IN -- Get BOM Product info + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_Product_ID=p.M_Product_ID + AND b.M_Product_ID=product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsBOM='Y' + AND p.IsVerified='Y' + AND b.IsActive='Y' + LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get v_ProductQty + SELECT COALESCE(SUM(QtyOnHand), 0) + INTO v_ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + LEFT JOIN M_LocatorType lt ON (l.M_LocatorType_ID=lt.M_LocatorType_ID) + WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID + AND COALESCE(lt.IsAvailableForReservation,'Y')='Y'; + -- Get Rounding Precision + SELECT COALESCE(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; + -- How much can we make with this product + v_ProductQty := ROUND (v_ProductQty/bom.BOMQty, v_StdPrecision); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y') THEN + v_ProductQty := BOMQtyOnHandForReservation (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT COALESCE(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; + -- + RETURN ROUND (v_Quantity, v_StdPrecision); + END IF; + RETURN 0; +END; +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + +CREATE OR REPLACE FUNCTION BOMQtyOnHand (in product_id numeric, in warehouse_id numeric, in locator_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + myWarehouse_ID numeric; + v_Quantity numeric := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty numeric; + v_StdPrecision int; + bom record; + +BEGIN + -- Check Parameters + myWarehouse_ID := Warehouse_ID; + IF (myWarehouse_ID IS NULL) THEN + IF (Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT SUM(M_Warehouse_ID) INTO myWarehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=Locator_ID; + END IF; + END IF; + IF (myWarehouse_ID IS NULL) THEN + RETURN 0; + END IF; + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + -- Unlimited capacity if no item + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN v_Quantity; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT COALESCE(SUM(QtyOnHand), 0) + INTO v_ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + WHERE s.M_Product_ID=Product_ID AND l.M_Warehouse_ID=myWarehouse_ID; + -- + RETURN v_ProductQty; + END IF; + + -- Go through BOM + FOR bom IN -- Get BOM Product info + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_Product_ID=p.M_Product_ID + AND b.M_Product_ID=product_ID + AND b.M_ProductBOM_ID != Product_ID + AND p.IsBOM='Y' + AND p.IsVerified='Y' + AND b.IsActive='Y' + LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get v_ProductQty + SELECT COALESCE(SUM(QtyOnHand), 0) + INTO v_ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID; + -- Get Rounding Precision + SELECT COALESCE(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; + -- How much can we make with this product + v_ProductQty := ROUND (v_ProductQty/bom.BOMQty, v_StdPrecision); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y') THEN + v_ProductQty := Bomqtyonhand (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT COALESCE(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; + -- + RETURN ROUND (v_Quantity, v_StdPrecision); + END IF; + RETURN 0; +END; +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + +CREATE OR REPLACE FUNCTION bomqtyordered (in p_product_id numeric, in p_warehouse_id numeric, in p_locator_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + v_Warehouse_ID numeric; + v_Quantity numeric := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty numeric; + v_StdPrecision int; + bom record; +BEGIN + -- Check Parameters + v_Warehouse_ID := p_Warehouse_ID; + IF (v_Warehouse_ID IS NULL) THEN + IF (p_Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT MAX(M_Warehouse_ID) INTO v_Warehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=p_Locator_ID; + END IF; + END IF; + IF (v_Warehouse_ID IS NULL) THEN + RETURN 0; + END IF; + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=p_Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + + -- No reservation for non-stocked + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN 0; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT COALESCE(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='N' + AND IsActive='Y'; + -- + RETURN v_ProductQty; + END IF; + + -- Go though BOM + FOR bom IN + -- Get BOM Product info + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_Product_ID=p.M_Product_ID + AND b.M_Product_ID=p_Product_ID + AND b.M_ProductBOM_ID != p_Product_ID + AND p.IsBOM='Y' + AND p.IsVerified='Y' + AND b.IsActive='Y' + LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT COALESCE(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='N' + AND IsActive='Y'; + -- Get Rounding Precision + SELECT COALESCE(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; + -- How much can we make with this product + v_ProductQty := ROUND (v_ProductQty/bom.BOMQty, v_StdPrecision ); + + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y') THEN + v_ProductQty := Bomqtyordered (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + -- Unlimited (e.g. only services) + IF (v_Quantity = 99999) THEN + RETURN 0; + END IF; + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT COALESCE(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; + -- + RETURN ROUND (v_Quantity, v_StdPrecision ); + END IF; + -- + RETURN 0; +END; +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + +CREATE OR REPLACE FUNCTION bomqtyreserved (in p_product_id numeric, in p_warehouse_id numeric, in p_locator_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + v_Warehouse_ID numeric; + v_Quantity numeric := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty numeric; + v_StdPrecision int; + bom record; +BEGIN + -- Check Parameters + v_Warehouse_ID := p_Warehouse_ID; + IF (v_Warehouse_ID IS NULL) THEN + IF (p_Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT MAX(M_Warehouse_ID) INTO v_Warehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=p_Locator_ID; + END IF; + END IF; + IF (v_Warehouse_ID IS NULL) THEN + RETURN 0; + END IF; + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=p_Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + + -- No reservation for non-stocked + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN 0; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT COALESCE(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='Y' + AND IsActive='Y'; + -- + RETURN v_ProductQty; + END IF; + + -- Go though BOM + FOR bom IN + -- Get BOM Product info + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_Product_ID=p.M_Product_ID + AND b.M_Product_ID=p_Product_ID + AND b.M_ProductBOM_ID != p_Product_ID + AND p.IsBOM='Y' + AND p.IsVerified='Y' + AND b.IsActive='Y' + LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT COALESCE(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=bom.M_ProductBOM_ID + AND M_Warehouse_ID =v_Warehouse_ID + AND IsSOTrx='Y' + AND IsActive='Y'; + -- Get Rounding Precision + SELECT COALESCE(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=bom.M_ProductBOM_ID; + -- How much can we make with this product + v_ProductQty := ROUND (v_ProductQty/bom.BOMQty, v_StdPrecision); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y') THEN + v_ProductQty := Bomqtyreserved (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + -- Unlimited (e.g. only services) + IF (v_Quantity = 99999) THEN + RETURN 0; + END IF; + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT COALESCE(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; + -- + RETURN ROUND (v_Quantity, v_StdPrecision); + END IF; + RETURN 0; +END; +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + diff --git a/migration/iD11/postgresql/202412021125_IDEMPIERE-6327.sql b/migration/iD11/postgresql/202412021125_IDEMPIERE-6327.sql new file mode 100644 index 0000000000..e53b64ae66 --- /dev/null +++ b/migration/iD11/postgresql/202412021125_IDEMPIERE-6327.sql @@ -0,0 +1,11 @@ +-- IDEMPIERE-6327 Interest Area Window: Subscription tab should not enable "Insert Record" +SELECT register_migration_script('202412021125_IDEMPIERE-6327.sql') FROM dual; + +-- Dec 2, 2024, 11:25:16 AM MYT +UPDATE AD_Tab SET IsInsertRecord='N',Updated=TO_TIMESTAMP('2024-12-02 11:25:16','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tab_ID=536 +; + +-- Dec 2, 2024, 11:25:32 AM MYT +UPDATE AD_Field SET IsReadOnly='Y',Updated=TO_TIMESTAMP('2024-12-02 11:25:32','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=7625 +; + diff --git a/migration/iD11/postgresql/202412041755_IDEMPIERE-6334.sql b/migration/iD11/postgresql/202412041755_IDEMPIERE-6334.sql new file mode 100644 index 0000000000..73d98efa10 --- /dev/null +++ b/migration/iD11/postgresql/202412041755_IDEMPIERE-6334.sql @@ -0,0 +1,7 @@ +-- +SELECT register_migration_script('202412041755_IDEMPIERE-6334.sql') FROM dual; + +-- Dec 4, 2024, 5:55:11 PM CET +UPDATE AD_Tab SET IsAdvancedTab='Y',Updated=TO_TIMESTAMP('2024-12-04 17:55:11','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tab_ID=200329 +; + diff --git a/migration/iD11/postgresql/202412091327_IDEMPIERE-6329.sql b/migration/iD11/postgresql/202412091327_IDEMPIERE-6329.sql new file mode 100644 index 0000000000..d989554c41 --- /dev/null +++ b/migration/iD11/postgresql/202412091327_IDEMPIERE-6329.sql @@ -0,0 +1,548 @@ +-- IDEMPIERE-6329 Bug in BOM* SQL functions not getting the correct BOM children +SELECT register_migration_script('202412091327_IDEMPIERE-6329.sql') FROM dual; + +CREATE OR REPLACE FUNCTION bompricelimit (in product_id numeric, in pricelist_version_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + v_Price NUMERIC; + v_ProductPrice NUMERIC; + bom RECORD; + +BEGIN + -- Try to get price from PriceList directly + SELECT COALESCE (SUM(PriceLimit), 0) + INTO v_Price + FROM M_ProductPrice + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_Product_BOM b, M_Product p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') + AND b.IsActive='Y' + LOOP + v_ProductPrice := bomPriceLimit (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; + +END; +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + +CREATE OR REPLACE FUNCTION bompricelist (in product_id numeric, in pricelist_version_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + v_Price NUMERIC; + v_ProductPrice NUMERIC; + bom RECORD; + +BEGIN + -- Try to get price from pricelist directly + SELECT COALESCE (SUM(PriceList), 0) + INTO v_Price + FROM M_ProductPrice + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_Product_BOM b, M_Product p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') + AND b.IsActive='Y' + LOOP + v_ProductPrice := bomPriceList (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; + +END; +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + +CREATE OR REPLACE FUNCTION bompricestd (in product_id numeric, in pricelist_version_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + v_Price NUMERIC; + v_ProductPrice NUMERIC; + bom RECORD; + +BEGIN + -- Try to get price from PriceList directly + SELECT COALESCE(SUM(PriceStd), 0) + INTO v_Price + FROM M_ProductPrice + WHERE IsActive='Y' AND M_PriceList_Version_ID=PriceList_Version_ID AND M_Product_ID=Product_ID; + + -- No Price - Check if BOM + IF (v_Price = 0) THEN + FOR bom IN + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM + FROM M_Product_BOM b, M_Product p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=Product_ID + AND b.M_ProductBOM_ID != Product_ID + AND (p.IsBOM='N' OR p.IsVerified='Y') + AND b.IsActive='Y' + LOOP + v_ProductPrice := bomPriceStd (bom.M_ProductBOM_ID, PriceList_Version_ID); + v_Price := v_Price + (bom.BOMQty * v_ProductPrice); + END LOOP; + END IF; + -- + RETURN v_Price; + +END; +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + +CREATE OR REPLACE FUNCTION BOMQtyOnHandForReservation (in product_id numeric, in warehouse_id numeric, in locator_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + myWarehouse_ID numeric; + v_Quantity numeric := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty numeric; + v_StdPrecision int; + bom record; + +BEGIN + -- Check Parameters + myWarehouse_ID := Warehouse_ID; + IF (myWarehouse_ID IS NULL) THEN + IF (Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT SUM(M_Warehouse_ID) INTO myWarehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=Locator_ID; + END IF; + END IF; + IF (myWarehouse_ID IS NULL) THEN + RETURN 0; + END IF; + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + -- Unlimited capacity if no item + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN v_Quantity; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT COALESCE(SUM(QtyOnHand), 0) + INTO v_ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + LEFT JOIN M_LocatorType lt ON (l.M_LocatorType_ID=lt.M_LocatorType_ID) + WHERE s.M_Product_ID=Product_ID AND l.M_Warehouse_ID=myWarehouse_ID + AND COALESCE(lt.IsAvailableForReservation,'Y')='Y'; + -- + RETURN v_ProductQty; + END IF; + + -- Go through BOM + FOR bom IN -- Get BOM Product info + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=product_ID + AND b.M_ProductBOM_ID != Product_ID + AND b.IsActive='Y' + LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get v_ProductQty + SELECT COALESCE(SUM(QtyOnHand), 0) + INTO v_ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + LEFT JOIN M_LocatorType lt ON (l.M_LocatorType_ID=lt.M_LocatorType_ID) + WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID + AND COALESCE(lt.IsAvailableForReservation,'Y')='Y'; + -- How much can we make with this product + v_ProductQty := v_ProductQty/bom.BOMQty; + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND bom.IsVerified = 'Y') THEN + v_ProductQty := BOMQtyOnHandForReservation (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT COALESCE(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; + -- + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown + END IF; + RETURN 0; +END; +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + +CREATE OR REPLACE FUNCTION BOMQtyOnHand (in product_id numeric, in warehouse_id numeric, in locator_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + myWarehouse_ID numeric; + v_Quantity numeric := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty numeric; + v_StdPrecision int; + bom record; + +BEGIN + -- Check Parameters + myWarehouse_ID := Warehouse_ID; + IF (myWarehouse_ID IS NULL) THEN + IF (Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT SUM(M_Warehouse_ID) INTO myWarehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=Locator_ID; + END IF; + END IF; + IF (myWarehouse_ID IS NULL) THEN + RETURN 0; + END IF; + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + -- Unlimited capacity if no item + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN v_Quantity; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT COALESCE(SUM(QtyOnHand), 0) + INTO v_ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + WHERE s.M_Product_ID=Product_ID AND l.M_Warehouse_ID=myWarehouse_ID; + -- + RETURN v_ProductQty; + END IF; + + -- Go through BOM + FOR bom IN -- Get BOM Product info + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=product_ID + AND b.M_ProductBOM_ID != Product_ID + AND b.IsActive='Y' + LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get v_ProductQty + SELECT COALESCE(SUM(QtyOnHand), 0) + INTO v_ProductQty + FROM M_Storageonhand s + JOIN M_Locator l ON (s.M_Locator_ID=l.M_Locator_ID) + WHERE s.M_Product_ID=bom.M_ProductBOM_ID AND l.M_Warehouse_ID=myWarehouse_ID; + -- How much can we make with this product + v_ProductQty := v_ProductQty/bom.BOMQty; + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN + v_ProductQty := Bomqtyonhand (bom.M_ProductBOM_ID, myWarehouse_ID, Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT COALESCE(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=Product_ID; + -- + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown + END IF; + RETURN 0; +END; +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + +CREATE OR REPLACE FUNCTION bomqtyordered (in p_product_id numeric, in p_warehouse_id numeric, in p_locator_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + v_Warehouse_ID numeric; + v_Quantity numeric := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty numeric; + v_StdPrecision int; + bom record; +BEGIN + -- Check Parameters + v_Warehouse_ID := p_Warehouse_ID; + IF (v_Warehouse_ID IS NULL) THEN + IF (p_Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT MAX(M_Warehouse_ID) INTO v_Warehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=p_Locator_ID; + END IF; + END IF; + IF (v_Warehouse_ID IS NULL) THEN + RETURN 0; + END IF; + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=p_Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + + -- No reservation for non-stocked + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN 0; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT COALESCE(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='N' + AND IsActive='Y'; + -- + RETURN v_ProductQty; + END IF; + + -- Go though BOM + FOR bom IN + -- Get BOM Product info + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=p_Product_ID + AND b.M_ProductBOM_ID != p_Product_ID + AND b.IsActive='Y' + LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT COALESCE(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='N' + AND IsActive='Y'; + -- How much can we make with this product + v_ProductQty := v_ProductQty/bom.BOMQty; + + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN + v_ProductQty := Bomqtyordered (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + -- Unlimited (e.g. only services) + IF (v_Quantity = 99999) THEN + RETURN 0; + END IF; + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT COALESCE(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; + -- + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown + END IF; + -- + RETURN 0; +END; +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + +CREATE OR REPLACE FUNCTION bomqtyreserved (in p_product_id numeric, in p_warehouse_id numeric, in p_locator_id numeric) RETURNS numeric AS +$BODY$ +DECLARE + v_Warehouse_ID numeric; + v_Quantity numeric := 99999; -- unlimited + v_IsBOM CHAR(1); + v_IsStocked CHAR(1); + v_ProductType CHAR(1); + v_ProductQty numeric; + v_StdPrecision int; + bom record; +BEGIN + -- Check Parameters + v_Warehouse_ID := p_Warehouse_ID; + IF (v_Warehouse_ID IS NULL) THEN + IF (p_Locator_ID IS NULL) THEN + RETURN 0; + ELSE + SELECT MAX(M_Warehouse_ID) INTO v_Warehouse_ID + FROM M_LOCATOR + WHERE M_Locator_ID=p_Locator_ID; + END IF; + END IF; + IF (v_Warehouse_ID IS NULL) THEN + RETURN 0; + END IF; + + -- Check, if product exists and if it is stocked + BEGIN + SELECT IsBOM, ProductType, IsStocked + INTO v_IsBOM, v_ProductType, v_IsStocked + FROM M_PRODUCT + WHERE M_Product_ID=p_Product_ID; + -- + EXCEPTION -- not found + WHEN OTHERS THEN + RETURN 0; + END; + + -- No reservation for non-stocked + IF (v_IsBOM='N' AND (v_ProductType<>'I' OR v_IsStocked='N')) THEN + RETURN 0; + -- Stocked item + ELSIF (v_IsStocked='Y') THEN + -- Get ProductQty + SELECT COALESCE(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=p_Product_ID + AND M_Warehouse_ID=v_Warehouse_ID + AND IsSOTrx='Y' + AND IsActive='Y'; + -- + RETURN v_ProductQty; + END IF; + + -- Go though BOM + FOR bom IN + -- Get BOM Product info + SELECT b.M_ProductBOM_ID, b.BOMQty, p.IsBOM, p.IsStocked, p.ProductType, p.IsVerified + FROM M_PRODUCT_BOM b, M_PRODUCT p + WHERE b.M_ProductBOM_ID=p.M_Product_ID + AND b.M_Product_ID=p_Product_ID + AND b.M_ProductBOM_ID != p_Product_ID + AND b.IsActive='Y' + LOOP + -- Stocked Items "leaf node" + IF (bom.ProductType = 'I' AND bom.IsStocked = 'Y') THEN + -- Get ProductQty + SELECT COALESCE(SUM(Qty), 0) + INTO v_ProductQty + FROM M_StorageReservation + WHERE M_Product_ID=bom.M_ProductBOM_ID + AND M_Warehouse_ID =v_Warehouse_ID + AND IsSOTrx='Y' + AND IsActive='Y'; + -- How much can we make with this product + v_ProductQty := v_ProductQty/bom.BOMQty; + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + -- Another BOM + ELSIF (bom.IsBOM = 'Y' AND BOM.IsVerified = 'Y') THEN + v_ProductQty := Bomqtyreserved (bom.M_ProductBOM_ID, v_Warehouse_ID, p_Locator_ID); + -- How much can we make overall + IF (v_ProductQty < v_Quantity) THEN + v_Quantity := v_ProductQty; + END IF; + END IF; + END LOOP; -- BOM + + -- Unlimited (e.g. only services) + IF (v_Quantity = 99999) THEN + RETURN 0; + END IF; + + IF (v_Quantity > 0) THEN + -- Get Rounding Precision for Product + SELECT COALESCE(MAX(u.StdPrecision), 0) + INTO v_StdPrecision + FROM C_UOM u, M_PRODUCT p + WHERE u.C_UOM_ID=p.C_UOM_ID AND p.M_Product_ID=p_Product_ID; + -- + RETURN TRUNC(v_Quantity, v_StdPrecision); -- RoundDown + END IF; + RETURN 0; +END; +$BODY$ +LANGUAGE 'plpgsql' STABLE +; + diff --git a/migration/iD11/postgresql/202412101102_IDEMPIERE-6335.sql b/migration/iD11/postgresql/202412101102_IDEMPIERE-6335.sql new file mode 100644 index 0000000000..2fb5a6ed5b --- /dev/null +++ b/migration/iD11/postgresql/202412101102_IDEMPIERE-6335.sql @@ -0,0 +1,7 @@ +-- IDEMPIERE-6335 +SELECT register_migration_script('202412101102_IDEMPIERE-6335.sql') FROM dual; + +-- Dec 10, 2024, 11:06:45 AM BRT +INSERT INTO AD_Process_Para (AD_Process_Para_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,Name,Description,Help,AD_Process_ID,SeqNo,AD_Reference_ID,IsRange,AD_Val_Rule_ID,FieldLength,IsMandatory,ColumnName,IsCentrallyMaintained,EntityType,AD_Element_ID,AD_Process_Para_UU,IsEncrypted,IsAutocomplete,DateRangeOption,IsShowNegateButton) VALUES (200484,0,0,'Y',TO_TIMESTAMP('2024-12-10 11:06:44','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-12-10 11:06:44','YYYY-MM-DD HH24:MI:SS'),100,'Document Type','Document type or rules','The Document Type determines document sequence and processing rules',142,20,19,'N',52054,0,'N','C_DocType_ID','Y','D',196,'2b9ac743-0893-474a-aa96-c1eafe8fba86','N','N','D','N') +; + diff --git a/migration/processes_post_migration/oracle/01_add_missing_Translations.sql b/migration/processes_post_migration/oracle/01_add_missing_Translations.sql index bfca3cf421..2785ff38c1 100644 --- a/migration/processes_post_migration/oracle/01_add_missing_Translations.sql +++ b/migration/processes_post_migration/oracle/01_add_missing_Translations.sql @@ -32,12 +32,14 @@ BEGIN ins := 'INSERT INTO ' || t.tablename - || '_TRL (' + || '_Trl (' || 'ad_language,ad_client_id,ad_org_id,created,createdby,updated,updatedby,isactive,istranslated,' || t.tablename + || '_Trl_UU,' + || t.tablename || suffix; sel := - 'SELECT l.ad_language,t.ad_client_id,t.ad_org_id,t.created,t.createdby,t.updated,t.updatedby,t.isactive,''N'' as istranslated,' + 'SELECT l.ad_language,t.ad_client_id,t.ad_org_id,SYSDATE,10,SYSDATE,10,t.isactive,''N'' as istranslated,generate_uuid(),' || t.tablename || suffix; @@ -65,7 +67,7 @@ BEGIN || t.tablename || ' t, ad_language l WHERE l.issystemlanguage=''Y'' AND NOT EXISTS (SELECT 1 FROM ' || t.tablename - || '_TRL b WHERE b.' + || '_Trl b WHERE b.' || t.tablename || suffix || '=t.' || t.tablename diff --git a/migration/processes_post_migration/postgresql/01_add_missing_translations.sql b/migration/processes_post_migration/postgresql/01_add_missing_translations.sql index 1fb245d2f5..98344bfba4 100644 --- a/migration/processes_post_migration/postgresql/01_add_missing_translations.sql +++ b/migration/processes_post_migration/postgresql/01_add_missing_translations.sql @@ -28,12 +28,14 @@ BEGIN ins := 'INSERT INTO ' || t.tablename - || '_TRL (' + || '_Trl (' || 'ad_language,ad_client_id,ad_org_id,created,createdby,updated,updatedby,isactive,istranslated,' || t.tablename + || '_Trl_UU,' + || t.tablename || suffix; sel := - 'SELECT l.ad_language,t.ad_client_id,t.ad_org_id,t.created,t.createdby,t.updated,t.updatedby,t.isactive,''N'' as istranslated,' + 'SELECT l.ad_language,t.ad_client_id,t.ad_org_id,statement_timestamp(),10,statement_timestamp(),10,t.isactive,''N'' as istranslated,generate_uuid(),' || t.tablename || suffix; diff --git a/org.adempiere.base-feature/feature.xml b/org.adempiere.base-feature/feature.xml index 839e7dd653..ad1ffdab44 100644 --- a/org.adempiere.base-feature/feature.xml +++ b/org.adempiere.base-feature/feature.xml @@ -273,6 +273,10 @@ id="org.apache.commons.commons-compress" version="0.0.0"/> + + diff --git a/org.adempiere.base-feature/model.generator.launch b/org.adempiere.base-feature/model.generator.launch index 560b048128..b6b342a05e 100644 --- a/org.adempiere.base-feature/model.generator.launch +++ b/org.adempiere.base-feature/model.generator.launch @@ -62,6 +62,7 @@ + diff --git a/org.adempiere.base-feature/packinfolder.app.launch b/org.adempiere.base-feature/packinfolder.app.launch index 3a3c509882..e04ab163ee 100644 --- a/org.adempiere.base-feature/packinfolder.app.launch +++ b/org.adempiere.base-feature/packinfolder.app.launch @@ -68,6 +68,7 @@ + diff --git a/org.adempiere.base-feature/sign.database.build.launch b/org.adempiere.base-feature/sign.database.build.launch index e84c656caf..b3efd9a7a6 100644 --- a/org.adempiere.base-feature/sign.database.build.launch +++ b/org.adempiere.base-feature/sign.database.build.launch @@ -69,6 +69,7 @@ + diff --git a/org.adempiere.base-feature/synchronize-terminology.app.launch b/org.adempiere.base-feature/synchronize-terminology.app.launch index bf30922042..d907243845 100644 --- a/org.adempiere.base-feature/synchronize-terminology.app.launch +++ b/org.adempiere.base-feature/synchronize-terminology.app.launch @@ -69,6 +69,7 @@ + diff --git a/org.adempiere.base-feature/translation.app.launch b/org.adempiere.base-feature/translation.app.launch index e7d3cc10d8..e42b430b8b 100644 --- a/org.adempiere.base-feature/translation.app.launch +++ b/org.adempiere.base-feature/translation.app.launch @@ -69,6 +69,7 @@ + diff --git a/org.adempiere.base.callout/src/org/adempiere/base/callout/PackageExpDetail.java b/org.adempiere.base.callout/src/org/adempiere/base/callout/PackageExpDetail.java new file mode 100644 index 0000000000..b80fef8493 --- /dev/null +++ b/org.adempiere.base.callout/src/org/adempiere/base/callout/PackageExpDetail.java @@ -0,0 +1,59 @@ +/*********************************************************************** + * This file is part of iDempiere ERP Open Source * + * http://www.idempiere.org * + * * + * Copyright (C) Contributors * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301, USA. * + * * + * Contributors: * + * - Nicolas Micoud (TGI) * + **********************************************************************/ +package org.adempiere.base.callout; + +import java.util.Properties; + +import org.adempiere.base.IColumnCallout; +import org.adempiere.base.annotation.Callout; +import org.compiere.model.GridField; +import org.compiere.model.GridTab; +import org.compiere.model.MTable; + +/** + * + * @author Nicolas Micoud (TGI) + * + */ +@Callout(tableName = "AD_Package_Exp_Detail", columnName = {"SQLStatement"}) +public class PackageExpDetail implements IColumnCallout { + @Override + public String start(Properties ctx, int WindowNo, GridTab mTab, GridField mField, Object value, Object oldValue) { + + if (value != null && mTab.getValue("AD_Table_ID") == null) { + String sql = value.toString(); + if (sql.startsWith("SELECT ") && sql.contains(" FROM ")) { + int start = sql.indexOf(" FROM ") + 6; + int end = sql.indexOf(" ", start + 1); + + String tablename = sql.substring(start, end); + + if (MTable.get(ctx, tablename) != null) + mTab.setValue("AD_Table_ID", MTable.get(ctx, tablename).getAD_Table_ID()); + } + } + return null; + } +} \ No newline at end of file diff --git a/org.adempiere.base.callout/src/org/adempiere/model/CalloutRMA.java b/org.adempiere.base.callout/src/org/adempiere/model/CalloutRMA.java index d71138b047..338762ac5a 100644 --- a/org.adempiere.base.callout/src/org/adempiere/model/CalloutRMA.java +++ b/org.adempiere.base.callout/src/org/adempiere/model/CalloutRMA.java @@ -156,6 +156,11 @@ public class CalloutRMA extends CalloutEngine { MInvoice invoice = rma.getOriginalInvoice(); if (invoice != null) { + int dropshipLocationId = -1; + MOrder order = invoice.getOriginalOrder(); + if (order != null) + dropshipLocationId = order.getDropShip_Location_ID(); + pp.setM_PriceList_ID(invoice.getM_PriceList_ID()); pp.setPriceDate(invoice.getDateInvoiced()); @@ -168,7 +173,7 @@ public class CalloutRMA extends CalloutEngine { invoice.getDateInvoiced(), invoice.getDateInvoiced(), AD_Org_ID, rma.getShipment().getM_Warehouse_ID(), invoice.getC_BPartner_Location_ID(), // should be bill to - invoice.getC_BPartner_Location_ID(), rma.isSOTrx(), deliveryViaRule, null); + invoice.getC_BPartner_Location_ID(), dropshipLocationId, rma.isSOTrx(), deliveryViaRule, null); } else { @@ -183,7 +188,7 @@ public class CalloutRMA extends CalloutEngine { order.getDateOrdered(), order.getDateOrdered(), AD_Org_ID, order.getM_Warehouse_ID(), order.getC_BPartner_Location_ID(), // should be bill to - order.getC_BPartner_Location_ID(), rma.isSOTrx(), order.getDeliveryViaRule(), null); + order.getC_BPartner_Location_ID(), order.getDropShip_Location_ID(), rma.isSOTrx(), order.getDeliveryViaRule(), null); } else return "No Invoice/Order found the Shipment/Receipt associated"; diff --git a/org.adempiere.base.callout/src/org/compiere/model/CalloutInOut.java b/org.adempiere.base.callout/src/org/compiere/model/CalloutInOut.java index 412e02f231..8ca716ff72 100644 --- a/org.adempiere.base.callout/src/org/compiere/model/CalloutInOut.java +++ b/org.adempiere.base.callout/src/org/compiere/model/CalloutInOut.java @@ -525,7 +525,7 @@ public class CalloutInOut extends CalloutEngine mTab.setValue("M_Locator_ID", Integer.valueOf(M_Locator_ID)); } else - mTab.setValue("M_AttributeSetInstance_ID", null); + mTab.setValue("M_AttributeSetInstance_ID", 0); // int M_Warehouse_ID = Env.getContextAsInt(ctx, WindowNo, "M_Warehouse_ID"); boolean IsSOTrx = "Y".equals(Env.getContext(ctx, WindowNo, "IsSOTrx")); diff --git a/org.adempiere.base.callout/src/org/compiere/model/CalloutInventory.java b/org.adempiere.base.callout/src/org/compiere/model/CalloutInventory.java index 9e6d034539..0d9b8b2a9e 100644 --- a/org.adempiere.base.callout/src/org/compiere/model/CalloutInventory.java +++ b/org.adempiere.base.callout/src/org/compiere/model/CalloutInventory.java @@ -60,7 +60,7 @@ public class CalloutInventory extends CalloutEngine if ("M_Product_ID".equals(mField.getColumnName())) { // product changed - remove old ASI - mTab.setValue("M_AttributeSetInstance_ID", null); + mTab.setValue("M_AttributeSetInstance_ID", 0); } // Get Book Value @@ -96,7 +96,7 @@ public class CalloutInventory extends CalloutEngine if (M_AttributeSetInstance_ID != 0) mTab.setValue(MInventoryLine.COLUMNNAME_M_AttributeSetInstance_ID, M_AttributeSetInstance_ID); else - mTab.setValue(MInventoryLine.COLUMNNAME_M_AttributeSetInstance_ID, null); + mTab.setValue(MInventoryLine.COLUMNNAME_M_AttributeSetInstance_ID, 0); } // Set QtyBook from first storage location diff --git a/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoice.java b/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoice.java index b22f690162..f4b8ae324d 100644 --- a/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoice.java +++ b/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoice.java @@ -325,7 +325,7 @@ public class CalloutInvoice extends CalloutEngine && Env.getContextAsInt(ctx, WindowNo, Env.TAB_INFO, "M_AttributeSetInstance_ID") != 0) mTab.setValue("M_AttributeSetInstance_ID", Env.getContextAsInt(ctx, WindowNo, Env.TAB_INFO, "M_AttributeSetInstance_ID")); else - mTab.setValue("M_AttributeSetInstance_ID", null); + mTab.setValue("M_AttributeSetInstance_ID", 0); /***** Price Calculation see also qty ****/ boolean IsSOTrx = Env.getContext(ctx, WindowNo, "IsSOTrx").equals("Y"); @@ -488,8 +488,9 @@ public class CalloutInvoice extends CalloutEngine // String deliveryViaRule = getLineDeliveryViaRule(ctx, WindowNo, mTab); + int dropshipLocationId = getDropShipLocationId(ctx, WindowNo, mTab); int C_Tax_ID = Core.getTaxLookup().get(ctx, M_Product_ID, C_Charge_ID, billDate, shipDate, - AD_Org_ID, M_Warehouse_ID, billC_BPartner_Location_ID, shipC_BPartner_Location_ID, + AD_Org_ID, M_Warehouse_ID, billC_BPartner_Location_ID, shipC_BPartner_Location_ID, dropshipLocationId, Env.getContext(ctx, WindowNo, "IsSOTrx").equals("Y"), deliveryViaRule, null); if (log.isLoggable(Level.INFO)) log.info("Tax ID=" + C_Tax_ID); // @@ -501,6 +502,13 @@ public class CalloutInvoice extends CalloutEngine return amt (ctx, WindowNo, mTab, mField, value); } // tax + /** + * Get the delivery via rule from the related order + * @param ctx + * @param windowNo + * @param mTab + * @return + */ private String getLineDeliveryViaRule(Properties ctx, int windowNo, GridTab mTab) { if (mTab.getValue("C_OrderLine_ID") != null) { int C_OrderLine_ID = (Integer) mTab.getValue("C_OrderLine_ID"); @@ -523,7 +531,30 @@ public class CalloutInvoice extends CalloutEngine } return null; } - + + /** + * Get the drop shipment location ID from the related order + * @param ctx + * @param windowNo + * @param mTab + * @return + */ + private int getDropShipLocationId(Properties ctx, int windowNo, GridTab mTab) { + if (mTab.getValue("C_OrderLine_ID") != null) { + int C_OrderLine_ID = (Integer) mTab.getValue("C_OrderLine_ID"); + if (C_OrderLine_ID > 0) { + MOrderLine orderLine = new MOrderLine(ctx, C_OrderLine_ID, null); + return orderLine.getParent().getDropShip_Location_ID(); + } + } + int C_Order_ID = Env.getContextAsInt(ctx, windowNo, "C_Order_ID", true); + if (C_Order_ID > 0) { + MOrder order = new MOrder(ctx, C_Order_ID, null); + return order.getDropShip_Location_ID(); + } + return -1; + } + /** * Invoice - Amount. * - called from QtyInvoiced, PriceActual diff --git a/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoiceBatch.java b/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoiceBatch.java index cee2fe3d5c..52e367199e 100644 --- a/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoiceBatch.java +++ b/org.adempiere.base.callout/src/org/compiere/model/CalloutInvoiceBatch.java @@ -319,8 +319,9 @@ public class CalloutInvoiceBatch extends CalloutEngine // String deliveryViaRule = getLineDeliveryViaRule(ctx, WindowNo, mTab); + int dropshipLocationId = getDropShipLocationId(ctx, WindowNo, mTab); int C_Tax_ID = Core.getTaxLookup().get(ctx, 0, C_Charge_ID, billDate, shipDate, - AD_Org_ID, M_Warehouse_ID, C_BPartner_Location_ID, C_BPartner_Location_ID, + AD_Org_ID, M_Warehouse_ID, C_BPartner_Location_ID, C_BPartner_Location_ID, dropshipLocationId, Env.getContext(ctx, WindowNo, "IsSOTrx").equals("Y"), deliveryViaRule, null); if (log.isLoggable(Level.INFO)) log.info("Tax ID=" + C_Tax_ID); // @@ -332,6 +333,13 @@ public class CalloutInvoiceBatch extends CalloutEngine return amt (ctx, WindowNo, mTab, mField, value); } // tax + /** + * Get the drop shipment location ID from the related order + * @param ctx + * @param windowNo + * @param mTab + * @return + */ private String getLineDeliveryViaRule(Properties ctx, int windowNo, GridTab mTab) { if (mTab.getValue("C_InvoiceLine_ID") != null) { int C_InvoiceLine_ID = (Integer) mTab.getValue("C_InvoiceLine_ID"); @@ -361,7 +369,39 @@ public class CalloutInvoiceBatch extends CalloutEngine } return null; } - + + /** + * Get the drop shipment location ID from the related order + * @param ctx + * @param windowNo + * @param mTab + * @return + */ + private int getDropShipLocationId(Properties ctx, int windowNo, GridTab mTab) { + if (mTab.getValue("C_InvoiceLine_ID") != null) { + int C_InvoiceLine_ID = (Integer) mTab.getValue("C_InvoiceLine_ID"); + if (C_InvoiceLine_ID > 0) { + MInvoiceLine invoiceLine = new MInvoiceLine(ctx, C_InvoiceLine_ID, null); + int C_OrderLine_ID = invoiceLine.getC_OrderLine_ID(); + if (C_OrderLine_ID > 0) { + MOrderLine orderLine = new MOrderLine(ctx, C_OrderLine_ID, null); + return orderLine.getParent().getDropShip_Location_ID(); + } + } + } + if (mTab.getValue("C_Invoice_ID") != null) { + int C_Invoice_ID = (Integer) mTab.getValue("C_Invoice_ID"); + if (C_Invoice_ID > 0) { + MInvoice invoice = new MInvoice(ctx, C_Invoice_ID, null); + I_C_Order order = invoice.getC_Order(); + if (order != null) { + return order.getDropShip_Location_ID(); + } + } + } + return -1; + } + /** * Invoice - Amount. * - called from QtyEntered, PriceEntered diff --git a/org.adempiere.base.callout/src/org/compiere/model/CalloutMovement.java b/org.adempiere.base.callout/src/org/compiere/model/CalloutMovement.java index cc2f6466bc..78f8712f23 100644 --- a/org.adempiere.base.callout/src/org/compiere/model/CalloutMovement.java +++ b/org.adempiere.base.callout/src/org/compiere/model/CalloutMovement.java @@ -54,7 +54,7 @@ public class CalloutMovement extends CalloutEngine && Env.getContextAsInt(ctx, WindowNo, Env.TAB_INFO, "M_AttributeSetInstance_ID") != 0) mTab.setValue("M_AttributeSetInstance_ID", Env.getContextAsInt(ctx, WindowNo, Env.TAB_INFO, "M_AttributeSetInstance_ID")); else - mTab.setValue("M_AttributeSetInstance_ID", null); + mTab.setValue("M_AttributeSetInstance_ID", 0); checkQtyAvailable(ctx, mTab, WindowNo, M_Product_ID, null); return ""; diff --git a/org.adempiere.base.callout/src/org/compiere/model/CalloutOrder.java b/org.adempiere.base.callout/src/org/compiere/model/CalloutOrder.java index 2684da6eba..2df87e1ee1 100644 --- a/org.adempiere.base.callout/src/org/compiere/model/CalloutOrder.java +++ b/org.adempiere.base.callout/src/org/compiere/model/CalloutOrder.java @@ -759,7 +759,7 @@ public class CalloutOrder extends CalloutEngine && Env.getContextAsInt(ctx, WindowNo, Env.TAB_INFO, "M_AttributeSetInstance_ID") != 0) mTab.setValue("M_AttributeSetInstance_ID", Env.getContextAsInt(ctx, WindowNo, Env.TAB_INFO, "M_AttributeSetInstance_ID")); else - mTab.setValue("M_AttributeSetInstance_ID", null); + mTab.setValue("M_AttributeSetInstance_ID", 0); /***** Price Calculation see also qty ****/ int C_BPartner_ID = Env.getContextAsInt(ctx, WindowNo, "C_BPartner_ID"); @@ -972,8 +972,9 @@ public class CalloutOrder extends CalloutEngine // String deliveryViaRule = Env.getContext(ctx, WindowNo, I_C_Order.COLUMNNAME_DeliveryViaRule, true); + int dropshipLocationId = Env.getContextAsInt(ctx, WindowNo, I_C_Order.COLUMNNAME_DropShip_Location_ID, true); int C_Tax_ID = Core.getTaxLookup().get(ctx, M_Product_ID, C_Charge_ID, billDate, shipDate, - AD_Org_ID, M_Warehouse_ID, billC_BPartner_Location_ID, shipC_BPartner_Location_ID, + AD_Org_ID, M_Warehouse_ID, billC_BPartner_Location_ID, shipC_BPartner_Location_ID, dropshipLocationId, "Y".equals(Env.getContext(ctx, WindowNo, "IsSOTrx")), deliveryViaRule, null); if (log.isLoggable(Level.INFO)) log.info("Tax ID=" + C_Tax_ID); // diff --git a/org.adempiere.base.callout/src/org/compiere/model/CalloutProduction.java b/org.adempiere.base.callout/src/org/compiere/model/CalloutProduction.java index 8d686f27e6..f4d727d817 100644 --- a/org.adempiere.base.callout/src/org/compiere/model/CalloutProduction.java +++ b/org.adempiere.base.callout/src/org/compiere/model/CalloutProduction.java @@ -55,7 +55,7 @@ public class CalloutProduction extends CalloutEngine } else { - mTab.setValue("M_AttributeSetInstance_ID", null); + mTab.setValue("M_AttributeSetInstance_ID", 0); } MProduct product = MProduct.get(ctx, M_Product_ID); diff --git a/org.adempiere.base.process/src/org/compiere/process/InvoiceCreateInOut.java b/org.adempiere.base.process/src/org/compiere/process/InvoiceCreateInOut.java index 78eea6d754..1f1a3ee1b7 100644 --- a/org.adempiere.base.process/src/org/compiere/process/InvoiceCreateInOut.java +++ b/org.adempiere.base.process/src/org/compiere/process/InvoiceCreateInOut.java @@ -42,6 +42,7 @@ import org.compiere.util.Env; public class InvoiceCreateInOut extends SvrProcess { public static final String PARAM_M_Warehouse_ID = MInOut.COLUMNNAME_M_Warehouse_ID; + public static final String PARAM_C_DocType_ID = MInOut.COLUMNNAME_C_DocType_ID; /** Warehouse */ private int p_M_Warehouse_ID = 0; @@ -49,7 +50,9 @@ public class InvoiceCreateInOut extends SvrProcess private int p_C_Invoice_ID = 0; /** Receipt */ private MInOut m_inout = null; - + /** Document Type */ + private int p_C_DocType_ID = 0; + /** * Prepare - e.g., get Parameters. */ @@ -62,6 +65,8 @@ public class InvoiceCreateInOut extends SvrProcess ; else if (name.equals(PARAM_M_Warehouse_ID)) p_M_Warehouse_ID = para.getParameterAsInt(); + else if (name.equals(PARAM_C_DocType_ID)) + p_C_DocType_ID = para.getParameterAsInt(); else MProcessPara.validateUnknownParameter(getProcessInfo().getAD_Process_ID(), para); } @@ -110,6 +115,8 @@ public class InvoiceCreateInOut extends SvrProcess if (m_inout != null) return m_inout; m_inout = new MInOut (invoice, 0, null, p_M_Warehouse_ID); + if (p_C_DocType_ID != 0) + m_inout.setC_DocType_ID(p_C_DocType_ID); m_inout.saveEx(); return m_inout; } diff --git a/org.adempiere.base.process/src/org/compiere/process/InvoiceGenerate.java b/org.adempiere.base.process/src/org/compiere/process/InvoiceGenerate.java index 677c4fd9b2..dfa1e35ad6 100644 --- a/org.adempiere.base.process/src/org/compiere/process/InvoiceGenerate.java +++ b/org.adempiere.base.process/src/org/compiere/process/InvoiceGenerate.java @@ -476,7 +476,24 @@ public class InvoiceGenerate extends SvrProcess MInvoiceLine line = new MInvoiceLine (m_invoice); line.setShipLine(sLine); if (sLine.sameOrderLineUOM()) - line.setQtyEntered(sLine.getQtyEntered()); + { + MDocType docType = MDocType.get(ship.getC_DocType_ID()); + if (docType.isShipConfirm() + && (order.getInvoiceRule().equals(MOrder.INVOICERULE_AfterDelivery) + || order.getInvoiceRule().equals(MOrder.INVOICERULE_AfterOrderDelivered) + || order.getInvoiceRule().equals(MOrder.INVOICERULE_CustomerScheduleAfterDelivery)) + && sLine.getTargetQty() != null && sLine.getTargetQty().signum() != 0) + { + if (sLine.getQtyEntered().compareTo(sLine.getTargetQty()) == 0) + line.setQtyEntered(sLine.getMovementQty()); + else + line.setQtyEntered(sLine.getMovementQty() + .multiply(sLine.getQtyEntered()) + .divide(sLine.getTargetQty(), 12, RoundingMode.HALF_UP)); + } + else + line.setQtyEntered(sLine.getQtyEntered()); + } else line.setQtyEntered(sLine.getMovementQty()); line.setQtyInvoiced(sLine.getMovementQty()); diff --git a/org.adempiere.base.process/src/org/compiere/process/OrderCreateProduction.java b/org.adempiere.base.process/src/org/compiere/process/OrderCreateProduction.java index f2d548c0e3..7dd1768a97 100644 --- a/org.adempiere.base.process/src/org/compiere/process/OrderCreateProduction.java +++ b/org.adempiere.base.process/src/org/compiere/process/OrderCreateProduction.java @@ -37,6 +37,7 @@ import org.compiere.model.MWarehouse; import org.compiere.model.Query; import org.compiere.util.Env; import org.compiere.util.Msg; +import org.eevolution.model.MPPProductBOM; /** * @@ -90,11 +91,13 @@ public class OrderCreateProduction extends SvrProcess { MProduction production = new MProduction(line); MProduct product = new MProduct(getCtx(), line.getM_Product_ID(), get_TrxName()); + MPPProductBOM productBOM = MPPProductBOM.getDefault(product, get_TrxName()); production.setM_Product_ID(line.getM_Product_ID()); production.setProductionQty(line.getQtyOrdered().subtract(line.getQtyDelivered())); production.setDatePromised(line.getDatePromised()); production.setC_OrderLine_ID(line.getC_OrderLine_ID()); + production.setPP_Product_BOM_ID(productBOM.getPP_Product_BOM_ID()); int locator = product.getM_Locator_ID(); if (locator == 0) diff --git a/org.adempiere.base.process/src/org/compiere/process/OrderLineCreateProduction.java b/org.adempiere.base.process/src/org/compiere/process/OrderLineCreateProduction.java index 9f821365dc..ba8514ae05 100644 --- a/org.adempiere.base.process/src/org/compiere/process/OrderLineCreateProduction.java +++ b/org.adempiere.base.process/src/org/compiere/process/OrderLineCreateProduction.java @@ -27,6 +27,7 @@ import org.compiere.model.MWarehouse; import org.compiere.util.DB; import org.compiere.util.Env; import org.compiere.util.Msg; +import org.eevolution.model.MPPProductBOM; /** * Create (Generate) Production from OrderLine @@ -86,11 +87,13 @@ public class OrderLineCreateProduction extends SvrProcess MProduction production = new MProduction( line ); MProduct product = new MProduct (getCtx(), line.getM_Product_ID(), get_TrxName()); + MPPProductBOM productBOM = MPPProductBOM.getDefault(product, get_TrxName()); production.setM_Product_ID(line.getM_Product_ID()); production.setProductionQty(line.getQtyOrdered().subtract(line.getQtyDelivered())); production.setDatePromised(line.getDatePromised()); production.setC_OrderLine_ID(p_C_OrderLine_ID); + production.setPP_Product_BOM_ID(productBOM.getPP_Product_BOM_ID()); int locator = product.getM_Locator_ID(); if ( locator == 0 ) diff --git a/org.adempiere.base.process/src/org/compiere/process/RequisitionPOCreate.java b/org.adempiere.base.process/src/org/compiere/process/RequisitionPOCreate.java index 9d2c758c24..33e0926730 100644 --- a/org.adempiere.base.process/src/org/compiere/process/RequisitionPOCreate.java +++ b/org.adempiere.base.process/src/org/compiere/process/RequisitionPOCreate.java @@ -304,6 +304,7 @@ public class RequisitionPOCreate extends SvrProcess || rLine.getM_AttributeSetInstance_ID() != m_M_AttributeSetInstance_ID || rLine.getC_Charge_ID() != 0 // single line per charge || m_order == null + || (rLine.getC_BPartner_ID() > 0 && m_order.getC_BPartner_ID() != rLine.getC_BPartner_ID()) || m_order.getDatePromised().compareTo(rLine.getDateRequired()) != 0 ) { diff --git a/org.adempiere.base.process/src/org/compiere/process/TabCopy.java b/org.adempiere.base.process/src/org/compiere/process/TabCopy.java index c730a0187a..402566bfe6 100644 --- a/org.adempiere.base.process/src/org/compiere/process/TabCopy.java +++ b/org.adempiere.base.process/src/org/compiere/process/TabCopy.java @@ -21,6 +21,7 @@ import java.util.logging.Level; import org.compiere.model.MField; import org.compiere.model.MProcessPara; import org.compiere.model.MTab; +import org.compiere.model.Query; import org.compiere.util.AdempiereUserError; import org.compiere.util.DB; @@ -113,6 +114,11 @@ public class TabCopy extends SvrProcess int count = 0; for (MField oldField : from.getFields(false, get_TrxName())) { + // ignore it when newField already exists + if (new Query(getCtx(), MField.Table_Name, "AD_Tab_ID=? AND AD_Column_ID=?", get_TrxName()) + .setParameters(to.getAD_Tab_ID(), oldField.getAD_Column_ID()) + .match()) + continue; MField newField = new MField (to, oldField); if (! oldField.isActive()) newField.setIsActive(false); diff --git a/org.adempiere.base.process/src/org/compiere/process/TabCreateFields.java b/org.adempiere.base.process/src/org/compiere/process/TabCreateFields.java index f3d7559dbd..3b2b4fe042 100644 --- a/org.adempiere.base.process/src/org/compiere/process/TabCreateFields.java +++ b/org.adempiere.base.process/src/org/compiere/process/TabCreateFields.java @@ -100,11 +100,11 @@ public class TabCreateFields extends SvrProcess + " AND IsActive='Y' "; if(!Util.isEmpty(p_EntityType)) - sql += " AND c.entitytype = ?"; + sql += " AND c.entitytype = ? "; if(p_CreatedSince != null) sql += " AND c.created >= ? "; - sql += "ORDER BY CASE " + sql += " ORDER BY CASE " + " WHEN c.ColumnName = 'AD_Client_ID' THEN -100 " + " WHEN c.ColumnName = 'AD_Org_ID' THEN -90 " + " WHEN c.IsParent = 'Y' THEN -85 " @@ -167,7 +167,7 @@ public class TabCreateFields extends SvrProcess } if (column.getAD_Reference_ID() == DisplayType.Text) { field.setNumLines(3); - } else if (column.getAD_Reference_ID() == DisplayType.TextLong) { + } else if (column.getAD_Reference_ID() == DisplayType.TextLong || column.getAD_Reference_ID() == DisplayType.JSON) { field.setNumLines(5); } else if (column.getAD_Reference_ID() == DisplayType.Memo) { field.setNumLines(8); diff --git a/org.adempiere.base/src/org/adempiere/base/BaseActivator.java b/org.adempiere.base/src/org/adempiere/base/BaseActivator.java index d042c7c473..c10fc3534a 100644 --- a/org.adempiere.base/src/org/adempiere/base/BaseActivator.java +++ b/org.adempiere.base/src/org/adempiere/base/BaseActivator.java @@ -28,6 +28,14 @@ import java.util.Map; import java.util.Properties; import org.adempiere.base.equinox.StackTraceCommand; +import org.apache.poi.extractor.ExtractorFactory; +import org.apache.poi.extractor.MainExtractorFactory; +import org.apache.poi.hssf.usermodel.HSSFWorkbookFactory; +import org.apache.poi.ooxml.extractor.POIXMLExtractorFactory; +import org.apache.poi.sl.usermodel.SlideShowFactory; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.poi.xssf.usermodel.XSSFWorkbookFactory; +import org.apache.poi.xslf.usermodel.XSLFSlideShowFactory; import org.compiere.Adempiere; import org.eclipse.osgi.framework.console.CommandProvider; import org.osgi.framework.BundleActivator; @@ -61,6 +69,13 @@ public class BaseActivator implements BundleActivator { context.registerService(CommandProvider.class.getName(), new StackTraceCommand(), null); blacklistService = new ComponentBlackListService(context); + + //setup poi factory + WorkbookFactory.addProvider(new HSSFWorkbookFactory()); + WorkbookFactory.addProvider(new XSSFWorkbookFactory()); + SlideShowFactory.addProvider(new XSLFSlideShowFactory()); + ExtractorFactory.addProvider(new POIXMLExtractorFactory()); + ExtractorFactory.addProvider(new MainExtractorFactory()); } /* @@ -120,6 +135,14 @@ public class BaseActivator implements BundleActivator { blacklistService.stop(context); blacklistService = null; } + + //remove poi factory + WorkbookFactory.removeProvider(HSSFWorkbookFactory.class); + WorkbookFactory.removeProvider(XSSFWorkbookFactory.class); + SlideShowFactory.removeProvider(XSLFSlideShowFactory.class); + ExtractorFactory.removeProvider(POIXMLExtractorFactory.class); + ExtractorFactory.removeProvider(MainExtractorFactory.class); + Adempiere.stop(); } diff --git a/org.adempiere.base/src/org/adempiere/base/DefaultTaxLookup.java b/org.adempiere.base/src/org/adempiere/base/DefaultTaxLookup.java index 997083ae95..93b38eb052 100644 --- a/org.adempiere.base/src/org/adempiere/base/DefaultTaxLookup.java +++ b/org.adempiere.base/src/org/adempiere/base/DefaultTaxLookup.java @@ -51,10 +51,19 @@ public class DefaultTaxLookup implements ITaxLookup { public int get(Properties ctx, int M_Product_ID, int C_Charge_ID, Timestamp billDate, Timestamp shipDate, int AD_Org_ID, int M_Warehouse_ID, int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, boolean IsSOTrx, String deliveryViaRule, String trxName) { - return Tax.get(ctx, M_Product_ID, C_Charge_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, billC_BPartner_Location_ID, shipC_BPartner_Location_ID, + return Tax.get(ctx, M_Product_ID, C_Charge_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, billC_BPartner_Location_ID, shipC_BPartner_Location_ID, IsSOTrx, deliveryViaRule, trxName); } + @Override + public int get(Properties ctx, int M_Product_ID, int C_Charge_ID, Timestamp billDate, Timestamp shipDate, + int AD_Org_ID, int M_Warehouse_ID, int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, + int dropshipC_BPartner_Location_ID, + boolean IsSOTrx, String deliveryViaRule, String trxName) { + return Tax.get(ctx, M_Product_ID, C_Charge_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, billC_BPartner_Location_ID, shipC_BPartner_Location_ID, + dropshipC_BPartner_Location_ID, IsSOTrx, deliveryViaRule, trxName); + } + @Override public int get(Properties ctx, int C_TaxCategory_ID, boolean IsSOTrx, Timestamp shipDate, int shipFromC_Location_ID, int shipToC_Location_ID, Timestamp billDate, int billFromC_Location_ID, int billToC_Location_ID, @@ -62,4 +71,11 @@ public class DefaultTaxLookup implements ITaxLookup { return Tax.get(ctx, C_TaxCategory_ID, IsSOTrx, shipDate, shipFromC_Location_ID, shipToC_Location_ID, billDate, billFromC_Location_ID, billToC_Location_ID, trxName); } + @Override + public int get(Properties ctx, int C_TaxCategory_ID, boolean IsSOTrx, Timestamp shipDate, int shipFromC_Location_ID, + int shipToC_Location_ID, int dropshipC_Location_ID, Timestamp billDate, int billFromC_Location_ID, int billToC_Location_ID, + String trxName) { + return Tax.get(ctx, C_TaxCategory_ID, IsSOTrx, shipDate, shipFromC_Location_ID, shipToC_Location_ID, dropshipC_Location_ID, billDate, billFromC_Location_ID, billToC_Location_ID, trxName); + } + } diff --git a/org.adempiere.base/src/org/adempiere/base/ITaxLookup.java b/org.adempiere.base/src/org/adempiere/base/ITaxLookup.java index 7861059f86..6d014aa019 100644 --- a/org.adempiere.base/src/org/adempiere/base/ITaxLookup.java +++ b/org.adempiere.base/src/org/adempiere/base/ITaxLookup.java @@ -55,6 +55,37 @@ public interface ITaxLookup { int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, boolean IsSOTrx, String deliveryViaRule, String trxName); + /** + * Find C_Tax_ID by Product/Charge + Warehouse Location + BPartner Location + DeliveryViaRule + * @param ctx + * @param M_Product_ID + * @param C_Charge_ID + * @param billDate Billing Date + * @param shipDate Shipment Date + * @param AD_Org_ID + * @param M_Warehouse_ID + * @param billC_BPartner_Location_ID Bill to location + * @param shipC_BPartner_Location_ID Ship to location + * @param dropshipC_BPartner_Location_ID Drop Ship to location (ignored if not implemented) + * @param IsSOTrx + * @param deliveryViaRule Order/Invoice's Delivery Via Rule + * @param trxName + * @return C_Tax_ID + */ + public default int get (Properties ctx, int M_Product_ID, int C_Charge_ID, + Timestamp billDate, Timestamp shipDate, + int AD_Org_ID, int M_Warehouse_ID, + int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, + int dropshipC_BPartner_Location_ID, + boolean IsSOTrx, String deliveryViaRule, String trxName) { + // fallback to default method without dropshipC_BPartner_Location_ID if not implemented + return get(ctx, M_Product_ID, C_Charge_ID, + billDate, shipDate, + AD_Org_ID, M_Warehouse_ID, + billC_BPartner_Location_ID, shipC_BPartner_Location_ID, + IsSOTrx, deliveryViaRule, trxName); + } + /** * Find C_Tax_ID * @param ctx @@ -73,4 +104,33 @@ public interface ITaxLookup { int C_TaxCategory_ID, boolean IsSOTrx, Timestamp shipDate, int shipFromC_Location_ID, int shipToC_Location_ID, Timestamp billDate, int billFromC_Location_ID, int billToC_Location_ID, String trxName); + + /** + * Find C_Tax_ID + * @param ctx + * @param C_TaxCategory_ID + * @param IsSOTrx + * @param shipDate Shipment Date + * @param shipFromC_Location_ID Shipping from (not use in default lookup implementation) + * @param shipToC_Location_ID Shipping to (not use in default lookup implementation) + * @param dropshipC_Location_ID Drop Ship location + * @param billDate Billing Date + * @param billFromC_Location_ID Billing from (Tax Location from) + * @param billToC_Location_ID Billing to (Tax Location to) + * @param deliveryRule Order/Invoice's Delivery Via Rule + * @param trxName + * @return C_Tax_ID + */ + public default int get (Properties ctx, + int C_TaxCategory_ID, boolean IsSOTrx, + Timestamp shipDate, int shipFromC_Location_ID, int shipToC_Location_ID, + int dropshipC_Location_ID, + Timestamp billDate, int billFromC_Location_ID, int billToC_Location_ID, String trxName) { + // fallback to default method without dropshipC_BPartner_Location_ID if not implemented + return get(ctx, + C_TaxCategory_ID, IsSOTrx, + shipDate, shipFromC_Location_ID, shipToC_Location_ID, + billDate, billFromC_Location_ID, billToC_Location_ID, trxName); + } + } diff --git a/org.adempiere.base/src/org/adempiere/base/event/EventManager.java b/org.adempiere.base/src/org/adempiere/base/event/EventManager.java index e6d9838162..61a6a28a59 100644 --- a/org.adempiere.base/src/org/adempiere/base/event/EventManager.java +++ b/org.adempiere.base/src/org/adempiere/base/event/EventManager.java @@ -31,6 +31,7 @@ import java.util.stream.Collectors; import org.adempiere.base.BaseActivator; import org.adempiere.base.event.annotations.BaseEventHandler; +import org.compiere.model.PO; import org.compiere.util.CLogger; import org.compiere.util.Env; import org.compiere.util.Ini; @@ -332,8 +333,11 @@ public class EventManager implements IEventManager { } else { Map map = new HashMap(3); map.put(EventConstants.EVENT_TOPIC, topic); - if (data != null) + if (data != null) { map.put(EVENT_DATA, data); + if (data instanceof PO po) + map.put(TABLE_NAME_PROPERTY, po.get_TableName()); + } map.put(EVENT_ERROR_MESSAGES, new ArrayList()); if (copySessionContext) map.put(EVENT_CONTEXT, getCurrentSessionContext()); diff --git a/org.adempiere.base/src/org/adempiere/base/sso/ISSOPrincipalService.java b/org.adempiere.base/src/org/adempiere/base/sso/ISSOPrincipalService.java index 908a822ffd..0df7488b1e 100644 --- a/org.adempiere.base/src/org/adempiere/base/sso/ISSOPrincipalService.java +++ b/org.adempiere.base/src/org/adempiere/base/sso/ISSOPrincipalService.java @@ -96,4 +96,12 @@ public interface ISSOPrincipalService * @throws ParseException */ public Language getLanguage(Object token) throws ParseException; + + /** + * Get logout url + * @return logout url or null + */ + default String getLogoutURL() { + return null; + } } diff --git a/org.adempiere.base/src/org/adempiere/exceptions/ProductNotOnPriceListException.java b/org.adempiere.base/src/org/adempiere/exceptions/ProductNotOnPriceListException.java index 982a9b6ba1..eea06a63fc 100644 --- a/org.adempiere.base/src/org/adempiere/exceptions/ProductNotOnPriceListException.java +++ b/org.adempiere.base/src/org/adempiere/exceptions/ProductNotOnPriceListException.java @@ -64,24 +64,24 @@ public class ProductNotOnPriceListException extends AdempiereException MProduct p = MProduct.get(Env.getCtx(), pp.getM_Product_ID()); if (sb.length() > 0) sb.append(", "); - sb.append("@M_Product_ID@:").append(p == null ? "?" : p.get_Translation(MProduct.COLUMNNAME_Name)); + sb.append("@M_Product_ID@: ").append(p == null ? "?" : (p.getValue() + " - " + p.get_Translation(MProduct.COLUMNNAME_Name))); } if (pp.getM_PriceList_ID() > 0) { MPriceList pl = MPriceList.get(Env.getCtx(), pp.getM_PriceList_ID(), null); if (sb.length() > 0) sb.append(", "); - sb.append("@M_PriceList_ID@:").append(pl == null ? "?" : pl.get_Translation(MPriceList.COLUMNNAME_Name)); + sb.append("@M_PriceList_ID@: ").append(pl == null ? "?" : pl.get_Translation(MPriceList.COLUMNNAME_Name)); } if (pp.getPriceDate() != null) { DateFormat df = DisplayType.getDateFormat(DisplayType.Date); if (sb.length() > 0) sb.append(", "); - sb.append("@Date@:").append(df.format(pp.getPriceDate())); + sb.append("@Date@: ").append(df.format(pp.getPriceDate())); } // - sb.insert(0, "@"+AD_Message+"@ - "); + sb.insert(0, "@"+AD_Message+"@ -> "); return sb.toString(); } } diff --git a/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVExporter.java b/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVExporter.java index 87a874a8aa..ea5d7c4a70 100644 --- a/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVExporter.java +++ b/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVExporter.java @@ -106,6 +106,8 @@ public class GridTabCSVExporter implements IGridTabExporter specialHDispayType = DisplayType.Location; continue; } else if (! (field.isDisplayed() || field.isDisplayedGrid())) { + continue; + } else if (DisplayType.Binary == field.getDisplayType()) { continue; } String headName = resolveColumnName(table, column); @@ -438,15 +440,16 @@ public class GridTabCSVExporter implements IGridTabExporter String ref = (String) idO; value = MRefList.getListName(Env.getCtx(), column.getAD_Reference_Value_ID(), ref); } else { - int id = (Integer) idO; + MTable forTab = MTable.get(Env.getCtx(), foreignTable); + String foreignKeyCol = forTab.getKeyColumns()[0]; int start = headName.indexOf("[")+1; int end = headName.length()-1; String foreignColumn = headName.substring(start, end); StringBuilder select = new StringBuilder("SELECT ") .append(foreignColumn).append(" FROM ") .append(foreignTable).append(" WHERE ") - .append(foreignTable).append("_ID=?"); - value = DB.getSQLValueStringEx(null, select.toString(), id); + .append(foreignKeyCol).append("=?"); + value = DB.getSQLValueStringEx(null, select.toString(), idO); } } } else { @@ -478,7 +481,7 @@ public class GridTabCSVExporter implements IGridTabExporter */ private String resolveColumnName(MTable table, MColumn column) { StringBuilder name = new StringBuilder(column.getColumnName()); - if (DisplayType.isLookup(column.getAD_Reference_ID())) { + if (DisplayType.isLookup(column.getAD_Reference_ID()) && !DisplayType.isMultiID(column.getAD_Reference_ID())) { // resolve to identifier - search for value first, if not search for name - if not use the ID String foreignTable = column.getReferenceTableName(); if ("AD_EntityType".equals(foreignTable) && I_AD_EntityType.COLUMNNAME_AD_EntityType_ID.equals(column.getColumnName())){ @@ -579,7 +582,8 @@ public class GridTabCSVExporter implements IGridTabExporter || gridField.isEncryptedColumn() || !(gridField.isDisplayed() || gridField.isDisplayedGrid()) || gridField.isReadOnly() - || (DisplayType.Button == MColumn.get(Env.getCtx(),gridField.getAD_Column_ID()).getAD_Reference_ID()) + || DisplayType.Button == gridField.getDisplayType() + || DisplayType.Binary == gridField.getDisplayType() ) continue; @@ -598,7 +602,8 @@ public class GridTabCSVExporter implements IGridTabExporter { if ("AD_Client_ID".equals(field.getColumnName())) continue; - if (DisplayType.Button == MColumn.get(Env.getCtx(),field.getAD_Column_ID()).getAD_Reference_ID()) + if ( DisplayType.Button == field.getDisplayType() + || DisplayType.Binary == field.getDisplayType()) continue; if ( field.isVirtualColumn() || field.isEncrypted() diff --git a/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVImporter.java b/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVImporter.java index df5cd9deac..ff2c026629 100644 --- a/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVImporter.java +++ b/org.adempiere.base/src/org/adempiere/impexp/GridTabCSVImporter.java @@ -1022,17 +1022,16 @@ public class GridTabCSVImporter implements IGridTabImporter if (isForeing && value != null && !"(null)".equals(value)){ String foreignTable = column.getReferenceTableName(); - String idS = null; - int id = -1; + Object idS = null; if("AD_Ref_List".equals(foreignTable)) - idS= resolveForeignList(column,foreignColumn,value,null); + idS = resolveForeignList(column,foreignColumn,value,null); else - id = resolveForeign(foreignTable,foreignColumn,value,field,null); + idS = resolveForeign(foreignTable,foreignColumn,value,field,null); - if(idS == null && id < 0){ + if(idS == null){ //it could be that record still doesn't exist if import mode is inserting or merging if(isUpdateMode()) - return new StringBuilder(Msg.getMsg(Env.getCtx(),id==-2?"ForeignMultipleResolved":"ForeignNotResolved",new Object[]{header.get(i),value})); + return new StringBuilder(Msg.getMsg(Env.getCtx(),(idS instanceof Integer && (int)idS==-2)?"ForeignMultipleResolved":"ForeignNotResolved",new Object[]{header.get(i),value})); } } else { // TODO: we could validate length of string or min/max @@ -1068,6 +1067,8 @@ public class GridTabCSVImporter implements IGridTabImporter //without Country any address would be invalid boolean thereIsCountry = false ; boolean isEmptyRow = true; + Object countryId = null; + int regionIndex = -1; for(int j= i;j< header.size();j++){ if(!header.get(j).contains(MTable.getTableName(Env.getCtx(),MLocation.Table_ID))) break; @@ -1080,30 +1081,59 @@ public class GridTabCSVImporter implements IGridTabImporter String columnName = header.get(j); Object value = tmpRow.get(j); if(value!=null){ - if(columnName.contains("C_Country_ID")) + if(columnName.contains("C_Country_ID")) { thereIsCountry= true; + countryId = value; + } }else continue; boolean isKeyColumn = columnName.indexOf("/") > 0; - boolean isForeing = columnName.indexOf("[") > 0 && columnName.indexOf("]")>0; + boolean isForeign = columnName.indexOf("[") > 0 && columnName.indexOf("]")>0; boolean isDetail = columnName.indexOf(">") > 0; String foreignColumn = null; - columnName = getColumnName(isKeyColumn,isForeing,isDetail,columnName); - if(isForeing) + columnName = getColumnName(isKeyColumn,isForeign,isDetail,columnName); + if(isForeign) foreignColumn = header.get(j).substring(header.get(j).indexOf("[")+1, header.get(j).indexOf("]")); - if(isForeing && !"(null)".equals(value)){ + if(isForeign && !"(null)".equals(value)){ String foreignTable = columnName.substring(0,columnName.length()-3); - int id = resolveForeign(foreignTable,foreignColumn,value,field,null); - if (id < 0) - return new StringBuilder(Msg.getMsg(Env.getCtx(), id==-2?"ForeignMultipleResolved":"ForeignNotResolved" ,new Object[]{header.get(j),value})); + if ("C_Region".equals(foreignTable)) { + regionIndex = j; + } else { + Object id = resolveForeign(foreignTable,foreignColumn,value,field,null); + if (id == null || (id instanceof Integer && (int)id < 0)) + return new StringBuilder(Msg.getMsg(Env.getCtx(),(id instanceof Integer && (int)id==-2)?"ForeignMultipleResolved":"ForeignNotResolved" ,new Object[]{header.get(j),value})); + if (columnName.contains("C_Country_ID")) { + countryId = id; + } + } } isEmptyRow=false; } MColumn column = MColumn.get(Env.getCtx(), field.getAD_Column_ID()); if((field.isMandatory(true) || column.isMandatory()) && !isEmptyRow && !thereIsCountry) return new StringBuilder(Msg.getMsg(Env.getCtx(), "FillMandatory")+" "+field.getColumnName()+"["+"C_Country_ID]"); + + if (countryId != null && regionIndex != -1) { + String columnName = header.get(regionIndex); + Object value = tmpRow.get(regionIndex); + + boolean isKeyColumn = columnName.indexOf("/") > 0; + boolean isForeign = columnName.indexOf("[") > 0 && columnName.indexOf("]") > 0; + boolean isDetail = columnName.indexOf(">") > 0; + String foreignColumn = null; + columnName = getColumnName(isKeyColumn, isForeign, isDetail, columnName); + if (isForeign) + foreignColumn = header.get(regionIndex).substring(header.get(regionIndex).indexOf("[") + 1, header.get(regionIndex).indexOf("]")); + + if (isForeign && !"(null)".equals(value)) { + int id = resolveForeignRegionByCountry(foreignColumn, value, field, null, (Integer) countryId); + if (id < 0) + return new StringBuilder(Msg.getMsg(Env.getCtx(), id == -2 ? "ForeignMultipleResolved" : "ForeignNotResolved",new Object[] { header.get(regionIndex), value })); + } + } + } return null; } @@ -1125,6 +1155,7 @@ public class GridTabCSVImporter implements IGridTabImporter boolean isThereRow = false; MLocation address = null; List parentColumns = new ArrayList(); + int regionIndex = -1; for(int i = startindx ; i < endindx + 1 ; i++){ String columnName = header.get(i); Object value = map.get(header.get(i)); @@ -1136,15 +1167,15 @@ public class GridTabCSVImporter implements IGridTabImporter continue; boolean isKeyColumn= columnName.indexOf("/") > 0; - boolean isForeing = columnName.indexOf("[") > 0 && columnName.indexOf("]")>0; + boolean isForeign = columnName.indexOf("[") > 0 && columnName.indexOf("]")>0; isDetail = columnName.indexOf(">") > 0; - columnName = getColumnName(isKeyColumn,isForeing,isDetail,columnName); + columnName = getColumnName(isKeyColumn,isForeign,isDetail,columnName); String foreignColumn = null; Object setValue = null; - if(isForeing) + if(isForeign) foreignColumn = header.get(i).substring(header.get(i).indexOf("[")+1,header.get(i).indexOf("]")); - if(!isForeing && !isKeyColumn && ("AD_Language".equals(columnName) || "EntityType".equals(columnName))) { + if(!isForeign && !isKeyColumn && ("AD_Language".equals(columnName) || "EntityType".equals(columnName))) { setValue = value; GridField field = gridTab.getField(columnName); logMsg = gridTab.setValue(field,setValue); @@ -1163,9 +1194,12 @@ public class GridTabCSVImporter implements IGridTabImporter } GridField field = gridTab.getField(columnName); if(!"(null)".equals(value.toString().trim())){ - if(isForeing) { + if(isForeign) { String foreignTable = columnName.substring(0,columnName.length()-3); - setValue = resolveForeign(foreignTable,foreignColumn,value,field,trx); + if("C_Region".equals(foreignTable)) + regionIndex = i; + else + setValue = resolveForeign(foreignTable,foreignColumn,value,field,trx); if("C_City".equals(foreignTable)) address.setCity(value.toString()); }else @@ -1184,7 +1218,7 @@ public class GridTabCSVImporter implements IGridTabImporter break; } - if(isForeing && masterRecord!=null){ + if(isForeign && masterRecord!=null){ if (masterRecord.get_Value(foreignColumn).toString().equals(value)){ logMsg = gridTab.setValue(field,masterRecord.get_ID()); if(logMsg.equals("")) @@ -1196,7 +1230,7 @@ public class GridTabCSVImporter implements IGridTabImporter break; } } - }else if(isForeing && masterRecord==null && gridTab.getTabLevel()>0){ + }else if(isForeign && masterRecord==null && gridTab.getTabLevel()>0){ Object master =gridTab.getParentTab().getValue(foreignColumn); if (master!=null && value!=null && !master.toString().equals(value)){ logMsg = header.get(i)+" - "+Msg.getMsg(Env.getCtx(),"DiffParentValue", new Object[] {master.toString(),value}); @@ -1205,19 +1239,18 @@ public class GridTabCSVImporter implements IGridTabImporter }else if (masterRecord==null && isDetail){ MColumn column = MColumn.get(Env.getCtx(),field.getAD_Column_ID()); String foreignTable = column.getReferenceTableName(); - String idS = null; - int id = -1; + Object idS = null; if ("AD_Ref_List".equals(foreignTable)) - idS= resolveForeignList(column, foreignColumn, value,trx); + idS = resolveForeignList(column, foreignColumn, value,trx); else - id = resolveForeign(foreignTable,foreignColumn,value, field, trx); + idS = resolveForeign(foreignTable,foreignColumn,value, field, trx); - if(idS == null && id < 0) - return Msg.getMsg(Env.getCtx(),id==-2?"ForeignMultipleResolved":"ForeignNotResolved",new Object[]{header.get(i),value}); + if (idS == null || (idS instanceof Integer && (int)idS < 0)) + return Msg.getMsg(Env.getCtx(),(idS instanceof Integer && (int)idS==-2)?"ForeignMultipleResolved":"ForeignNotResolved",new Object[]{header.get(i),value}); - if(id >= 0) - logMsg = gridTab.setValue(field,id); + if (idS instanceof Integer && (int)idS >= 0) + logMsg = gridTab.setValue(field,idS); else if (idS != null) logMsg = gridTab.setValue(field,idS); @@ -1240,7 +1273,7 @@ public class GridTabCSVImporter implements IGridTabImporter }else{ MColumn column = MColumn.get(Env.getCtx(),field.getAD_Column_ID()); - if (isForeing){ + if (isForeign){ String foreignTable = column.getReferenceTableName(); if ("AD_Ref_List".equals(foreignTable)) { String idS = resolveForeignList(column, foreignColumn, value,trx); @@ -1251,14 +1284,14 @@ public class GridTabCSVImporter implements IGridTabImporter isThereRow =true; } else { - int id = resolveForeign(foreignTable, foreignColumn, value,field,trx); - if(id < 0) - return Msg.getMsg(Env.getCtx(),id==-2?"ForeignMultipleResolved":"ForeignNotResolved",new Object[]{header.get(i),value}); + Object id = resolveForeign(foreignTable, foreignColumn, value,field,trx); + if (id == null || (id instanceof Integer && (int)id < 0)) + return Msg.getMsg(Env.getCtx(),(id instanceof Integer && (int)id==-2)?"ForeignMultipleResolved":"ForeignNotResolved",new Object[]{header.get(i),value}); setValue = id; if (field.isParentValue()) { - int actualId = (Integer) field.getValue(); - if (actualId != id) { + Object actualId = field.getValue(); + if (actualId != null && ! actualId.equals(id)) { logMsg = Msg.getMsg(Env.getCtx(), "ParentCannotChange",new Object[]{header.get(i)}); break; } @@ -1334,6 +1367,29 @@ public class GridTabCSVImporter implements IGridTabImporter } if(address!=null){ + if (regionIndex != -1 && address.getC_Country_ID() > 0) { + String columnName = header.get(regionIndex); + Object value = map.get(header.get(regionIndex)); + boolean isDetail = false; + boolean isKeyColumn= columnName.indexOf("/") > 0; + boolean isForeign = columnName.indexOf("[") > 0 && columnName.indexOf("]")>0; + isDetail = columnName.indexOf(">") > 0; + columnName = getColumnName(isKeyColumn,isForeign,isDetail,columnName); + String foreignColumn = null; + Object setValue = null; + + if(isForeign) + foreignColumn = header.get(regionIndex).substring(header.get(regionIndex).indexOf("[")+1,header.get(regionIndex).indexOf("]")); + + GridField field = gridTab.getField(columnName); + if(!"(null)".equals(value.toString().trim())){ + if(isForeign) { + setValue = resolveForeignRegionByCountry(foreignColumn, value, field, trx, address.getC_Country_ID()); + }else + setValue = value; + } + address.set_ValueOfColumn(columnName,setValue); + } if (!address.save()){ logMsg = CLogger.retrieveError()+" Address : "+address; }else { @@ -1380,9 +1436,9 @@ public class GridTabCSVImporter implements IGridTabImporter setValue = idS; } else { - int id = resolveForeign(foreignTable, foreignColumn, setValue, field, trx); - if (id < 0) - return Msg.getMsg(Env.getCtx(),id==-2?"ForeignMultipleResolved":"ForeignNotResolved",new Object[]{columnName,setValue}); + Object id = resolveForeign(foreignTable, foreignColumn, setValue, field, trx); + if (id == null || (id instanceof Integer && (int)id < 0)) + return Msg.getMsg(Env.getCtx(),(id instanceof Integer && (int)id==-2)?"ForeignMultipleResolved":"ForeignNotResolved",new Object[]{columnName,setValue}); setValue = id; } @@ -1423,7 +1479,7 @@ public class GridTabCSVImporter implements IGridTabImporter return (new Optional(new ParseBigDecimal(new DecimalFormatSymbols(Language.getLoginLanguage().getLocale())))); } else if (DisplayType.YesNo == field.getDisplayType()) { return (new Optional(new ParseBool("y", "n"))); - } else if (DisplayType.TextLong == field.getDisplayType()) { + } else if (DisplayType.TextLong == field.getDisplayType() || DisplayType.JSON == field.getDisplayType()) { return (new Optional(new StrMinMax(1, Long.MAX_VALUE))); } else if (DisplayType.isText(field.getDisplayType())) { return (new Optional(new StrMinMax(1, field.getFieldLength()))); @@ -1478,7 +1534,7 @@ public class GridTabCSVImporter implements IGridTabImporter String idS = resolveForeignList(column, foreignColumn, tmpValue,trx); setValue = idS; }else { - int id = resolveForeign(foreignTable, foreignColumn, tmpValue,field,trx); + Object id = resolveForeign(foreignTable, foreignColumn, tmpValue,field,trx); setValue = id; } }else{ @@ -1519,8 +1575,7 @@ public class GridTabCSVImporter implements IGridTabImporter String idS = resolveForeignList(column,foreignColumn,value,trx); value = idS; }else { - int id = resolveForeign(foreignTable,foreignColumn,value,field,trx); - value = id; + value = resolveForeign(foreignTable,foreignColumn,value,field,trx); } } }else{ //mandatory key not found @@ -1587,7 +1642,7 @@ public class GridTabCSVImporter implements IGridTabImporter * @param trx * @return -3 for not found, -2 for more than 1 match and > 0 for foreign id */ - private int resolveForeign(String foreignTable, String foreignColumn, Object value, GridField field, Trx trx) { + private Object resolveForeign(String foreignTable, String foreignColumn, Object value, GridField field, Trx trx) { boolean systemAccess = false; if (!"AD_Client".equals(foreignTable)) { MTable ft = MTable.get(Env.getCtx(), foreignTable); @@ -1632,10 +1687,14 @@ public class GridTabCSVImporter implements IGridTabImporter } } StringBuilder selectCount = new StringBuilder("SELECT COUNT(*)").append(postSelect); - StringBuilder selectId = new StringBuilder("SELECT ").append(foreignTable).append("_ID").append(postSelect); + MTable forTab = MTable.get(Env.getCtx(), foreignTable); + StringBuilder selectId = new StringBuilder("SELECT ").append(forTab.getKeyColumns()[0]).append(postSelect); int count = DB.getSQLValueEx(trxName, selectCount.toString(), value, thisClientId); if (count == 1) { // single value found, OK - return DB.getSQLValueEx(trxName, selectId.toString(), value, thisClientId); + if (forTab.isUUIDKeyTable()) + return DB.getSQLValueStringEx(trxName, selectId.toString(), value, thisClientId); + else + return DB.getSQLValueEx(trxName, selectId.toString(), value, thisClientId); } else if (count > 1) { // multiple values found, error ForeignMultipleResolved return -2; } else if (count == 0) { // no values found, error ForeignNotResolved @@ -1643,7 +1702,10 @@ public class GridTabCSVImporter implements IGridTabImporter // not found in client, try with System count = DB.getSQLValueEx(trxName, selectCount.toString(), value, 0 /* System */); if (count == 1) { // single value found, OK - return DB.getSQLValueEx(trxName, selectId.toString(), value, 0 /* System */); + if (forTab.isUUIDKeyTable()) + return DB.getSQLValueStringEx(trxName, selectId.toString(), value, 0 /* System */); + else + return DB.getSQLValueEx(trxName, selectId.toString(), value, 0 /* System */); } else if (count > 1) { // multiple values found, error ForeignMultipleResolved return -2; } @@ -1652,6 +1714,36 @@ public class GridTabCSVImporter implements IGridTabImporter return -3; // no values found, error ForeignNotResolved } + private int resolveForeignRegionByCountry(String foreignColumn, Object regionValue, GridField field, Trx trx, int countryId) { + String foreignTable = "C_Region"; + boolean systemAccess = true; + int thisClientId = Env.getAD_Client_ID(Env.getCtx()); + + StringBuilder postSelect = new StringBuilder(" FROM ") + .append(foreignTable).append(" WHERE ") + .append(foreignColumn).append("=? AND C_Country_ID=? AND IsActive='Y' AND AD_Client_ID=?"); + + StringBuilder selectCount = new StringBuilder("SELECT COUNT(*)").append(postSelect); + StringBuilder selectId = new StringBuilder("SELECT ").append(foreignTable).append("_ID").append(postSelect); + int count = DB.getSQLValueEx(trxName, selectCount.toString(), regionValue, countryId, thisClientId); + if (count == 1) { // single value found, OK + return DB.getSQLValueEx(trxName, selectId.toString(), regionValue, countryId, thisClientId); + } else if (count > 1) { // multiple values found, error ForeignMultipleResolved + return -2; + } else if (count == 0) { // no values found, error ForeignNotResolved + if (systemAccess && thisClientId != 0) { + // not found in client, try with System + count = DB.getSQLValueEx(trxName, selectCount.toString(), regionValue, countryId, 0 /* System */); + if (count == 1) { // single value found, OK + return DB.getSQLValueEx(trxName, selectId.toString(), regionValue, countryId, 0 /* System */); + } else if (count > 1) { // multiple values found, error ForeignMultipleResolved + return -2; + } + } + } + return -3; // no values found, error ForeignNotResolved + } + //Copy from GridTable @SuppressWarnings("unchecked") private boolean isValueChanged(Object oldValue, Object value) diff --git a/org.adempiere.base/src/org/adempiere/model/GenericPO.java b/org.adempiere.base/src/org/adempiere/model/GenericPO.java index 23656aa137..50463421ea 100644 --- a/org.adempiere.base/src/org/adempiere/model/GenericPO.java +++ b/org.adempiere.base/src/org/adempiere/model/GenericPO.java @@ -26,6 +26,7 @@ import java.math.BigDecimal; import java.sql.ResultSet; import java.util.Properties; +import org.adempiere.util.ServerContextPropertiesWrapper; import org.compiere.model.MTable; import org.compiere.model.PO; import org.compiere.model.POInfo; @@ -346,7 +347,7 @@ public class GenericPO extends PO implements DocAction { * @author Low Heng Sin * */ -class PropertiesWrapper extends Properties { +class PropertiesWrapper extends ServerContextPropertiesWrapper { /** * generated serial id */ diff --git a/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java b/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java index 87a71cb5f6..3ec85f8da1 100644 --- a/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java +++ b/org.adempiere.base/src/org/adempiere/util/ModelClassGenerator.java @@ -19,8 +19,6 @@ *****************************************************************************/ package org.adempiere.util; -import static org.compiere.model.SystemIDs.REFERENCE_PAYMENTRULE; - import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; @@ -31,7 +29,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.Collection; -import java.util.StringTokenizer; import java.util.TreeSet; import java.util.logging.Level; @@ -319,6 +316,10 @@ public class ModelClassGenerator + " AND c.IsActive='Y' AND (c.ColumnSQL IS NULL OR c.ColumnSQL NOT LIKE '@SQL%') " + (!Util.isEmpty(entityTypeFilter) ? " AND c." + entityTypeFilter : "") + " ORDER BY c.ColumnName"; + if (DB.isOracle()) + sql += " COLLATE \"BINARY\""; + else if (DB.isPostgreSQL()) + sql += " COLLATE \"C\""; boolean isKeyNamePairCreated = false; // true if the method "getKeyNamePair" is already generated PreparedStatement pstmt = null; ResultSet rs = null; @@ -472,13 +473,6 @@ public class ModelClassGenerator String staticVar = addListValidation (sb, AD_Reference_ID, columnName); sb.insert(0, staticVar); } - - // Payment Validation - if (displayType == DisplayType.Payment) - { - String staticVar = addListValidation (sb, REFERENCE_PAYMENTRULE, columnName); - sb.insert(0, staticVar); - } // setValue ("ColumnName", xx); if (virtualColumn) @@ -670,6 +664,10 @@ public class ModelClassGenerator StringBuilder statement = new StringBuilder(); // String sql = "SELECT Value, Name FROM AD_Ref_List WHERE AD_Reference_ID=? ORDER BY Value"; // even inactive, see IDEMPIERE-4979 + if (DB.isOracle()) + sql += " COLLATE \"BINARY\""; + else if (DB.isPostgreSQL()) + sql += " COLLATE \"C\""; PreparedStatement pstmt = null; ResultSet rs = null; try @@ -914,121 +912,6 @@ public class ModelClassGenerator */ public static void generateSource(String sourceFolder, String packageName, String entityType, String tableName, String columnEntityType) { - if (sourceFolder == null || sourceFolder.trim().length() == 0) - throw new IllegalArgumentException("Must specify source folder"); - - File file = new File(sourceFolder); - if (!file.exists()) - throw new IllegalArgumentException("Source folder doesn't exists. sourceFolder="+sourceFolder); - - if (packageName == null || packageName.trim().length() == 0) - throw new IllegalArgumentException("Must specify package name"); - - if (tableName == null || tableName.trim().length() == 0) - throw new IllegalArgumentException("Must specify table name"); - - StringBuilder tableLike = new StringBuilder().append(tableName.trim()); - if (!tableLike.toString().startsWith("'") || !tableLike.toString().endsWith("'")) - tableLike = new StringBuilder("'").append(tableLike).append("'"); - - StringBuilder entityTypeFilter = new StringBuilder(); - if (entityType != null && entityType.trim().length() > 0) - { - entityTypeFilter.append("EntityType IN ("); - StringTokenizer tokenizer = new StringTokenizer(entityType, ","); - int i = 0; - while(tokenizer.hasMoreTokens()) { - StringBuilder token = new StringBuilder().append(tokenizer.nextToken().trim()); - if (!token.toString().startsWith("'") || !token.toString().endsWith("'")) - token = new StringBuilder("'").append(token).append("'"); - if (i > 0) - entityTypeFilter.append(","); - entityTypeFilter.append(token); - i++; - } - entityTypeFilter.append(")"); - } - else - { - entityTypeFilter.append("EntityType IN ('U','A')"); - } - - StringBuilder directory = new StringBuilder().append(sourceFolder.trim()); - String packagePath = packageName.replace(".", File.separator); - if (!(directory.toString().endsWith("/") || directory.toString().endsWith("\\"))) - { - directory.append(File.separator); - } - if (File.separator.equals("/")) - directory = new StringBuilder(directory.toString().replaceAll("[\\\\]", File.separator)); - else - directory = new StringBuilder(directory.toString().replaceAll("[/]", File.separator)); - directory.append(packagePath); - file = new File(directory.toString()); - if (!file.exists()) - file.mkdirs(); - - // complete sql - String filterViews = null; - if (tableLike.toString().contains("%")) { - filterViews = " AND (TableName IN ('RV_WarehousePrice','RV_BPartner') OR IsView='N')"; // special views - } - if (tableLike.toString().equals("'%'")) { - filterViews += " AND TableName NOT LIKE 'W|_%' ESCAPE '|'"; // exclude webstore from general model generator - } - StringBuilder sql = new StringBuilder(); - sql.append("SELECT AD_Table_ID ") - .append("FROM AD_Table ") - .append("WHERE IsActive = 'Y' AND TableName NOT LIKE '%_Trl' "); - // Autodetect if we need to use IN or LIKE clause - teo_sarca [ 3020640 ] - if (tableLike.indexOf(",") == -1) - sql.append(" AND TableName LIKE ").append(tableLike); - else - sql.append(" AND TableName IN (").append(tableLike).append(")"); // only specific tables - sql.append(" AND ").append(entityTypeFilter.toString()); - if (filterViews != null) { - sql.append(filterViews); - } - sql.append(" ORDER BY TableName"); - // - StringBuilder columnFilterBuilder = new StringBuilder(); - if (!Util.isEmpty(columnEntityType, true)) - { - columnFilterBuilder.append("EntityType IN ("); - StringTokenizer tokenizer = new StringTokenizer(columnEntityType, ","); - int i = 0; - while(tokenizer.hasMoreTokens()) { - StringBuilder token = new StringBuilder().append(tokenizer.nextToken().trim()); - if (!token.toString().startsWith("'") || !token.toString().endsWith("'")) - token = new StringBuilder("'").append(token).append("'"); - if (i > 0) - columnFilterBuilder.append(","); - columnFilterBuilder.append(token); - i++; - } - columnFilterBuilder.append(")"); - } - String columnFilter = columnFilterBuilder.length() > 0 ? columnFilterBuilder.toString() : null; - - PreparedStatement pstmt = null; - ResultSet rs = null; - try - { - pstmt = DB.prepareStatement(sql.toString(), null); - rs = pstmt.executeQuery(); - while (rs.next()) - { - new ModelClassGenerator(rs.getInt(1), directory.toString(), packageName, columnFilter); - } - } - catch (SQLException e) - { - throw new DBException(e, sql.toString()); - } - finally - { - DB.close(rs, pstmt); - rs = null; pstmt = null; - } + ModelInterfaceGenerator.generateSource(ModelInterfaceGenerator.GEN_SOURCE_CLASS, sourceFolder, packageName, entityType, tableName, columnEntityType); } } diff --git a/org.adempiere.base/src/org/adempiere/util/ModelInterfaceGenerator.java b/org.adempiere.base/src/org/adempiere/util/ModelInterfaceGenerator.java index 832ffdc4d9..20951e585d 100644 --- a/org.adempiere.base/src/org/adempiere/util/ModelInterfaceGenerator.java +++ b/org.adempiere.base/src/org/adempiere/util/ModelInterfaceGenerator.java @@ -106,6 +106,9 @@ public class ModelInterfaceGenerator /** Logger */ private static final CLogger log = CLogger.getCLogger(ModelInterfaceGenerator.class); + public final static String GEN_SOURCE_INTERFACE = "I"; + public final static String GEN_SOURCE_CLASS = "C"; + /** * @param AD_Table_ID * @param directory @@ -249,6 +252,10 @@ public class ModelInterfaceGenerator + " AND c.IsActive='Y' AND (c.ColumnSQL IS NULL OR c.ColumnSQL NOT LIKE '@SQL%') " + (!Util.isEmpty(entityTypeFilter) ? " AND c." + entityTypeFilter : "") + " ORDER BY c.ColumnName"; + if (DB.isOracle()) + sql += " COLLATE \"BINARY\""; + else if (DB.isPostgreSQL()) + sql += " COLLATE \"C\""; PreparedStatement pstmt = null; ResultSet rs = null; try { @@ -778,6 +785,18 @@ public class ModelInterfaceGenerator * @param columnEntityType */ public static void generateSource(String sourceFolder, String packageName, String entityType, String tableName, String columnEntityType) + { + generateSource(GEN_SOURCE_INTERFACE, sourceFolder, packageName, entityType, tableName, columnEntityType); + } + + /** + * @param sourceFolder + * @param packageName + * @param entityType + * @param tableName table Like + * @param columnEntityType + */ + public static void generateSource(String type, String sourceFolder, String packageName, String entityType, String tableName, String columnEntityType) { if (sourceFolder == null || sourceFolder.trim().length() == 0) throw new IllegalArgumentException("Must specify source folder"); @@ -792,9 +811,7 @@ public class ModelInterfaceGenerator if (tableName == null || tableName.trim().length() == 0) throw new IllegalArgumentException("Must specify table name"); - StringBuilder tableLike = new StringBuilder().append(tableName.trim()); - if (!tableLike.toString().startsWith("'") || !tableLike.toString().endsWith("'")) - tableLike = new StringBuilder("'").append(tableLike).append("'"); + StringBuilder tableLike = new StringBuilder().append(tableName.trim().toUpperCase().replaceAll("'", "")); StringBuilder entityTypeFilter = new StringBuilder(); if (entityType != null && entityType.trim().length() > 0) @@ -803,7 +820,7 @@ public class ModelInterfaceGenerator StringTokenizer tokenizer = new StringTokenizer(entityType, ","); int i = 0; while(tokenizer.hasMoreTokens()) { - StringBuilder token = new StringBuilder(tokenizer.nextToken().trim()); + StringBuilder token = new StringBuilder().append(tokenizer.nextToken().trim()); if (!token.toString().startsWith("'") || !token.toString().endsWith("'")) token = new StringBuilder("'").append(token).append("'"); if (i > 0) @@ -828,7 +845,7 @@ public class ModelInterfaceGenerator directory = new StringBuilder(directory.toString().replaceAll("[\\\\]", File.separator)); else directory = new StringBuilder(directory.toString().replaceAll("[/]", File.separator)); - directory = new StringBuilder(directory).append(packagePath); + directory.append(packagePath); file = new File(directory.toString()); if (!file.exists()) file.mkdirs(); @@ -847,9 +864,19 @@ public class ModelInterfaceGenerator .append("WHERE IsActive = 'Y' AND TableName NOT LIKE '%_Trl' "); // Autodetect if we need to use IN or LIKE clause - teo_sarca [ 3020640 ] if (tableLike.indexOf(",") == -1) - sql.append(" AND TableName LIKE ").append(tableLike); - else - sql.append(" AND TableName IN (").append(tableLike).append(")"); // only specific tables + sql.append(" AND UPPER(TableName) LIKE ").append(DB.TO_STRING(tableLike.toString())); + else { // only specific tables + StringBuilder finalTableLike = new StringBuilder(""); + for (String table : tableLike.toString().split(",")) { + if (finalTableLike.length() > 0) + finalTableLike.append(", "); + + finalTableLike.append(DB.TO_STRING(table.replaceAll("'", "").trim())); + } + + sql.append(" AND UPPER(TableName) IN (").append(finalTableLike).append(")"); + } + sql.append(" AND ").append(entityTypeFilter.toString()); if (filterViews != null) { sql.append(filterViews); @@ -882,10 +909,19 @@ public class ModelInterfaceGenerator { pstmt = DB.prepareStatement(sql.toString(), null); rs = pstmt.executeQuery(); + + boolean isEmpty = true; while (rs.next()) { - new ModelInterfaceGenerator(rs.getInt(1), directory.toString(), packageName, columnFilter); + isEmpty = false; + if (type.equals(GEN_SOURCE_INTERFACE)) + new ModelInterfaceGenerator(rs.getInt(1), directory.toString(), packageName, columnFilter); + else if (type.equals(GEN_SOURCE_CLASS)) + new ModelClassGenerator(rs.getInt(1), directory.toString(), packageName, columnFilter); } + + if (isEmpty) + System.out.println("No data found for the table with name " + tableName); } catch (SQLException e) { diff --git a/org.adempiere.base/src/org/compiere/acct/Doc.java b/org.adempiere.base/src/org/compiere/acct/Doc.java index 2bc7b926ce..ebd72a137e 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc.java @@ -30,6 +30,7 @@ import java.util.Iterator; import java.util.Properties; import java.util.logging.Level; +import org.adempiere.exceptions.AdempiereException; import org.adempiere.exceptions.DBException; import org.compiere.model.I_C_AllocationHdr; import org.compiere.model.I_C_Cash; @@ -665,7 +666,7 @@ public abstract class Doc String AD_MessageValue = "PostingError-" + p_Status; int AD_User_ID = p_po.getUpdatedBy(); MNote note = new MNote (getCtx(), AD_MessageValue, AD_User_ID, - getAD_Client_ID(), getAD_Org_ID(), p_po.get_TrxName()); + getAD_Client_ID(), getAD_Org_ID(), null); note.setRecord(p_po.get_Table_ID(), p_po.get_ID()); // Reference note.setReference(toString()); // Document @@ -684,7 +685,15 @@ public abstract class Doc .append(" - " + Msg.getElement(Env.getCtx(),"IsBalanced") + "=").append( Msg.getMsg(Env.getCtx(), String.valueOf(isBalanced()))) .append(" - " + Msg.getElement(Env.getCtx(),"C_AcctSchema_ID") + "=").append(m_as.getName()); note.setTextMsg(Text.toString()); - note.saveEx(); + try { + note.saveEx(); + } catch (AdempiereException e) { + if (e.getMessage() != null && e.getMessage().startsWith("Foreign ID " + p_po.get_ID() + " not found in ")) { + ; //ignore, in unit test + } else { + throw e; + } + } p_Error = Text.toString(); } diff --git a/org.adempiere.base/src/org/compiere/acct/Doc_AllocationHdr.java b/org.adempiere.base/src/org/compiere/acct/Doc_AllocationHdr.java index e4a5ba0c70..74322274e6 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc_AllocationHdr.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc_AllocationHdr.java @@ -195,6 +195,24 @@ public class Doc_AllocationHdr extends Doc invGainLossFactLines = new ArrayList(); payGainLossFactLines = new ArrayList(); + // Do not create fact lines for reversal of invoice + if (p_lines.length == 2) + { + DocLine_Allocation line1 = (DocLine_Allocation)p_lines[0]; + DocLine_Allocation line2 = (DocLine_Allocation)p_lines[1]; + if (line1.getC_Payment_ID() == 0 && line1.getC_Order_ID() == 0 && line1.getC_CashLine_ID() == 0 && line1.getC_Invoice_ID() > 0 + && line2.getC_Payment_ID() == 0 && line2.getC_Order_ID() == 0 && line2.getC_CashLine_ID() == 0 && line2.getC_Invoice_ID() > 0) + { + MInvoice invoice1 = new MInvoice(Env.getCtx(), line1.getC_Invoice_ID(), getTrxName()); + MInvoice invoice2 = new MInvoice(Env.getCtx(), line2.getC_Invoice_ID(), getTrxName()); + if (invoice1.getGrandTotal().equals(invoice2.getGrandTotal().negate()) + && invoice2.getReversal_ID() == invoice1.getC_Invoice_ID()) + { + return m_facts; + } + } + } + // create Fact Header Fact fact = new Fact(this, as, Fact.POST_Actual); Fact factForRGL = new Fact(this, as, Fact.POST_Actual); // dummy fact (not posted) to calculate Realized Gain & Loss diff --git a/org.adempiere.base/src/org/compiere/acct/Doc_Invoice.java b/org.adempiere.base/src/org/compiere/acct/Doc_Invoice.java index 1948462568..a16f32653c 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc_Invoice.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc_Invoice.java @@ -25,6 +25,7 @@ import java.sql.Savepoint; import java.sql.Timestamp; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.logging.Level; @@ -34,14 +35,18 @@ import org.compiere.model.MAccount; import org.compiere.model.MAcctSchema; import org.compiere.model.MClientInfo; import org.compiere.model.MConversionRate; +import org.compiere.model.MCost; import org.compiere.model.MCostDetail; +import org.compiere.model.MCostElement; import org.compiere.model.MCurrency; +import org.compiere.model.MFactAcct; import org.compiere.model.MInvoice; import org.compiere.model.MInvoiceLine; import org.compiere.model.MLandedCostAllocation; import org.compiere.model.MOrderLandedCostAllocation; import org.compiere.model.MTax; import org.compiere.model.ProductCost; +import org.compiere.model.Query; import org.compiere.model.X_M_Cost; import org.compiere.util.DB; import org.compiere.util.Env; @@ -422,7 +427,7 @@ public class Doc_Invoice extends Doc // ** ARI, ARF if (getDocumentType().equals(DOCTYPE_ARInvoice) || getDocumentType().equals(DOCTYPE_ARProForma)) - { + { BigDecimal grossAmt = getAmount(Doc.AMTTYPE_Gross); BigDecimal serviceAmt = Env.ZERO; @@ -440,7 +445,7 @@ public class Doc_Invoice extends Doc FactLine tl = fact.createLine(null, m_taxes[i].getAccount(DocTax.ACCTTYPE_TaxDue, as), getC_Currency_ID(), null, amt); if (tl != null) - tl.setC_Tax_ID(m_taxes[i].getC_Tax_ID()); + tl.setC_Tax_ID(m_taxes[i].getC_Tax_ID()); } } // Revenue CR @@ -588,6 +593,12 @@ public class Doc_Invoice extends Doc // ** API else if (getDocumentType().equals(DOCTYPE_APInvoice)) { + MInvoice invoice = (MInvoice)getPO(); + MInvoice originalInvoice = null; + if (invoice.getReversal_ID() > 0 && invoice.getReversal_ID() < invoice.getC_Invoice_ID()) + { + originalInvoice = new MInvoice(Env.getCtx(), invoice.getReversal_ID(), invoice.get_TrxName()); + } BigDecimal grossAmt = getAmount(Doc.AMTTYPE_Gross); BigDecimal serviceAmt = Env.ZERO; @@ -601,6 +612,10 @@ public class Doc_Invoice extends Doc getC_Currency_ID(), m_taxes[i].getAmount(), null); if (tl != null) tl.setC_Tax_ID(m_taxes[i].getC_Tax_ID()); + if (tl != null && invoice.getReversal_ID() > 0 && invoice.getReversal_ID() < invoice.getC_Invoice_ID()) + { + tl.updateReverseLine(MInvoice.Table_ID, invoice.getReversal_ID(), 0, BigDecimal.ONE); + } } // Expense DR for (int i = 0; i < p_lines.length; i++) @@ -620,6 +635,17 @@ public class Doc_Invoice extends Doc else desc += " 100%"; fl.setDescription(desc); + if (invoice.getReversal_ID() > 0 && invoice.getReversal_ID() < invoice.getC_Invoice_ID()) + { + int lineId = 0; + if (originalInvoice != null) + { + MInvoiceLine[] lines = originalInvoice.getLines(); + if (lines.length > i) + lineId = lines[i].getC_InvoiceLine_ID(); + } + fl.updateReverseLine(MInvoice.Table_ID, invoice.getReversal_ID(), lineId, BigDecimal.ONE); + } } if (!landedCost) { @@ -636,12 +662,34 @@ public class Doc_Invoice extends Doc amt = amt.add(discount); dAmt = discount; MAccount tradeDiscountReceived = line.getAccount(ProductCost.ACCTTYPE_P_TDiscountRec, as); - fact.createLine (line, tradeDiscountReceived, + FactLine fl = fact.createLine (line, tradeDiscountReceived, getC_Currency_ID(), null, dAmt); + if (fl != null && invoice.getReversal_ID() > 0 && invoice.getReversal_ID() < invoice.getC_Invoice_ID()) + { + int lineId = 0; + if (originalInvoice != null) + { + MInvoiceLine[] lines = originalInvoice.getLines(); + if (lines.length > i) + lineId = lines[i].getC_InvoiceLine_ID(); + } + fl.updateReverseLine(MInvoice.Table_ID, invoice.getReversal_ID(), lineId, BigDecimal.ONE); + } } } - fact.createLine (line, expense, + FactLine fl = fact.createLine (line, expense, getC_Currency_ID(), amt, null); + if (fl != null && invoice.getReversal_ID() > 0 && invoice.getReversal_ID() < invoice.getC_Invoice_ID()) + { + int lineId = 0; + if (originalInvoice != null) + { + MInvoiceLine[] lines = originalInvoice.getLines(); + if (lines.length > i) + lineId = lines[i].getC_InvoiceLine_ID(); + } + fl.updateReverseLine(MInvoice.Table_ID, invoice.getReversal_ID(), lineId, BigDecimal.ONE); + } if (!line.isItem()) { grossAmt = grossAmt.subtract(amt); @@ -672,13 +720,23 @@ public class Doc_Invoice extends Doc serviceAmt = getAmount(Doc.AMTTYPE_Gross); grossAmt = Env.ZERO; } - if (grossAmt.signum() != 0) - fact.createLine(null, MAccount.get(getCtx(), payables_ID), + FactLine fl = null; + if (grossAmt.signum() > 0) + fl = fact.createLine(null, MAccount.get(getCtx(), payables_ID), getC_Currency_ID(), null, grossAmt); - if (serviceAmt.signum() != 0) - fact.createLine(null, MAccount.get(getCtx(), payablesServices_ID), + else if (grossAmt.signum() < 0) + fl = fact.createLine(null, MAccount.get(getCtx(), payables_ID), + getC_Currency_ID(), grossAmt.negate(), null); + if (serviceAmt.signum() > 0) + fl = fact.createLine(null, MAccount.get(getCtx(), payablesServices_ID), getC_Currency_ID(), null, serviceAmt); - + else if (serviceAmt.signum() < 0) + fl = fact.createLine(null, MAccount.get(getCtx(), payablesServices_ID), + getC_Currency_ID(), serviceAmt.negate(), null); + if (fl != null && invoice.getReversal_ID() > 0 && invoice.getReversal_ID() < invoice.getC_Invoice_ID()) + { + fl.updateReverseLine(MInvoice.Table_ID, invoice.getReversal_ID(), 0, BigDecimal.ONE); + } // Set Locations FactLine[] fLines = fact.getLines(); for (int i = 0; i < fLines.length; i++) @@ -934,7 +992,8 @@ public class Doc_Invoice extends Doc for (int i = 0; i < lcas.length; i++) totalBase += lcas[i].getBase().doubleValue(); - Map costDetailAmtMap = new HashMap(); + Map costDetailAmtMap = new HashMap<>(); + Map mcostQtyMap = new HashMap<>(); // Create New MInvoiceLine il = new MInvoiceLine (getCtx(), C_InvoiceLine_ID, getTrxName()); @@ -962,95 +1021,159 @@ public class Doc_Invoice extends Doc if (X_M_Cost.COSTINGMETHOD_AverageInvoice.equals(costingMethod) || X_M_Cost.COSTINGMETHOD_AveragePO.equals(costingMethod)) { - BigDecimal allocationAmt = lca.getAmt(); + BigDecimal allocationAmt = lca.getAmt(); + boolean reversal = false; + if (allocationAmt.signum() < 0) //reversal + { + allocationAmt = allocationAmt.negate(); + reversal = true; + } + BigDecimal estimatedAmt = BigDecimal.ZERO; - int oCurrencyId = 0; + BigDecimal costAdjustmentAmt = BigDecimal.ZERO; boolean usesSchemaCurrency = false; - Timestamp oDateAcct = getDateAcct(); - if (lca.getM_InOutLine_ID() > 0) + MInvoiceLine reversalLine = null; + if (reversal) { - I_M_InOutLine iol = lca.getM_InOutLine(); - if (iol.getC_OrderLine_ID() > 0) + MInvoice invoice = (MInvoice)getPO(); + MInvoice reversalInvoice = new MInvoice(getCtx(), invoice.getReversal_ID(), getTrxName()); + MInvoiceLine[] lines = invoice.getLines(); + MInvoiceLine[] reversalLines = reversalInvoice.getLines(); + for(int j = 0; j < lines.length; j++) { + if (lines[j].get_ID() == il.get_ID()) { + reversalLine = reversalLines[j]; + break; + } + } + } + else + { + int oCurrencyId = 0; + Timestamp oDateAcct = getDateAcct(); + if (lca.getM_InOutLine_ID() > 0) { - oCurrencyId = iol.getC_OrderLine().getC_Currency_ID(); - oDateAcct = iol.getC_OrderLine().getC_Order().getDateAcct(); - MOrderLandedCostAllocation[] allocations = MOrderLandedCostAllocation.getOfOrderLine(iol.getC_OrderLine_ID(), getTrxName()); - for(MOrderLandedCostAllocation allocation : allocations) + I_M_InOutLine iol = lca.getM_InOutLine(); + if (iol.getC_OrderLine_ID() > 0) { - if (allocation.getC_OrderLandedCost().getM_CostElement_ID() != lca.getM_CostElement_ID()) - continue; - - BigDecimal amt = allocation.getAmt(); - BigDecimal qty = allocation.getQty(); - if (qty.compareTo(iol.getMovementQty()) != 0) + oCurrencyId = iol.getC_OrderLine().getC_Currency_ID(); + oDateAcct = iol.getC_OrderLine().getC_Order().getDateAcct(); + MOrderLandedCostAllocation[] allocations = MOrderLandedCostAllocation.getOfOrderLine(iol.getC_OrderLine_ID(), getTrxName()); + for(MOrderLandedCostAllocation allocation : allocations) { - amt = amt.multiply(iol.getMovementQty()).divide(qty, 12, RoundingMode.HALF_UP); + if (allocation.getC_OrderLandedCost().getM_CostElement_ID() != lca.getM_CostElement_ID()) + continue; + + BigDecimal amt = allocation.getAmt(); + BigDecimal qty = allocation.getQty(); + if (qty.compareTo(iol.getMovementQty()) != 0) + { + amt = amt.multiply(iol.getMovementQty()).divide(qty, 12, RoundingMode.HALF_UP); + } + estimatedAmt = estimatedAmt.add(amt); } - estimatedAmt = estimatedAmt.add(amt); } } - } - - if (estimatedAmt.scale() > as.getCostingPrecision()) - { - estimatedAmt = estimatedAmt.setScale(as.getCostingPrecision(), RoundingMode.HALF_UP); - } - BigDecimal costAdjustmentAmt = allocationAmt; - if (estimatedAmt.signum() > 0) - { - //get other allocation amt - StringBuilder sql = new StringBuilder("SELECT Sum(Amt) FROM C_LandedCostAllocation WHERE M_InOutLine_ID=? ") - .append("AND C_LandedCostAllocation_ID<>? ") - .append("AND M_CostElement_ID=? ") - .append("AND AD_Client_ID=? "); - BigDecimal otherAmt = DB.getSQLValueBD(getTrxName(), sql.toString(), lca.getM_InOutLine_ID(), lca.getC_LandedCostAllocation_ID(), - lca.getM_CostElement_ID(), lca.getAD_Client_ID()); - if (otherAmt != null) + + if (estimatedAmt.scale() > as.getCostingPrecision()) { - estimatedAmt = estimatedAmt.subtract(otherAmt); - if (allocationAmt.signum() < 0) - { - //add back since the sum above would include the original trx - estimatedAmt = estimatedAmt.add(allocationAmt.negate()); - } - } - //added for IDEMPIERE-3014 - //convert to accounting schema currency - if (estimatedAmt.signum() > 0 && oCurrencyId != getC_Currency_ID()) - { - estimatedAmt = MConversionRate.convert(getCtx(), estimatedAmt, - oCurrencyId, as.getC_Currency_ID(), - oDateAcct, getC_ConversionType_ID(), - getAD_Client_ID(), getAD_Org_ID()); - - allocationAmt = MConversionRate.convert(getCtx(), allocationAmt, - getC_Currency_ID(), as.getC_Currency_ID(), - getDateAcct(), getC_ConversionType_ID(), - getAD_Client_ID(), getAD_Org_ID()); - setC_Currency_ID(as.getC_Currency_ID()); - usesSchemaCurrency = true; + estimatedAmt = estimatedAmt.setScale(as.getCostingPrecision(), RoundingMode.HALF_UP); } - + costAdjustmentAmt = allocationAmt; if (estimatedAmt.signum() > 0) - { - if (allocationAmt.signum() > 0) - costAdjustmentAmt = allocationAmt.subtract(estimatedAmt); - else if (allocationAmt.signum() < 0) - costAdjustmentAmt = allocationAmt.add(estimatedAmt); - } - } - - if (!dr) - costAdjustmentAmt = costAdjustmentAmt.negate(); + { + //get other allocation amt + StringBuilder sql = new StringBuilder("SELECT Sum(Amt) FROM C_LandedCostAllocation WHERE M_InOutLine_ID=? ") + .append("AND C_LandedCostAllocation_ID<>? ") + .append("AND M_CostElement_ID=? ") + .append("AND AD_Client_ID=? "); + BigDecimal otherAmt = DB.getSQLValueBD(getTrxName(), sql.toString(), lca.getM_InOutLine_ID(), lca.getC_LandedCostAllocation_ID(), + lca.getM_CostElement_ID(), lca.getAD_Client_ID()); + if (otherAmt != null) + { + estimatedAmt = estimatedAmt.subtract(otherAmt); + } + //added for IDEMPIERE-3014 + //convert to accounting schema currency + if (estimatedAmt.signum() > 0 && oCurrencyId != getC_Currency_ID()) + { + estimatedAmt = MConversionRate.convert(getCtx(), estimatedAmt, + oCurrencyId, as.getC_Currency_ID(), + oDateAcct, getC_ConversionType_ID(), + getAD_Client_ID(), getAD_Org_ID()); - boolean zeroQty = false; - if (costAdjustmentAmt.signum() != 0) + allocationAmt = MConversionRate.convert(getCtx(), allocationAmt, + getC_Currency_ID(), as.getC_Currency_ID(), + getDateAcct(), getC_ConversionType_ID(), + getAD_Client_ID(), getAD_Org_ID()); + setC_Currency_ID(as.getC_Currency_ID()); + usesSchemaCurrency = true; + } + + if (estimatedAmt.signum() > 0) + { + costAdjustmentAmt = allocationAmt.subtract(estimatedAmt); + } + } + + if (!dr) + costAdjustmentAmt = costAdjustmentAmt.negate(); + } + + BigDecimal amtAsset = Env.ZERO; + BigDecimal amtVariance = Env.ZERO; + BigDecimal costDetailQty = lca.getQty(); + if (costAdjustmentAmt.signum() != 0 && !reversal) { Trx trx = Trx.get(getTrxName(), false); Savepoint savepoint = null; try { savepoint = trx.setSavepoint(null); - BigDecimal costDetailAmt = costAdjustmentAmt; + + amtVariance = Env.ZERO; + amtAsset = costAdjustmentAmt; + + if(X_M_Cost.COSTINGMETHOD_AveragePO.equals(costingMethod)) + { + int AD_Org_ID = lca.getAD_Org_ID(); + int M_AttributeSetInstance_ID = lca.getM_AttributeSetInstance_ID(); + + if (MAcctSchema.COSTINGLEVEL_Client.equals(as.getCostingLevel())) + { + AD_Org_ID = 0; + M_AttributeSetInstance_ID = 0; + } + else if (MAcctSchema.COSTINGLEVEL_Organization.equals(as.getCostingLevel())) + M_AttributeSetInstance_ID = 0; + else if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(as.getCostingLevel())) + AD_Org_ID = 0; + + MCostElement ce = MCostElement.getMaterialCostElement(getCtx(), as.getCostingMethod(), + AD_Org_ID); + MCost c = MCost.get(getCtx(), getAD_Client_ID(), AD_Org_ID, lca.getM_Product_ID(), + as.getM_CostType_ID(), as.getC_AcctSchema_ID(), ce.getM_CostElement_ID(), + M_AttributeSetInstance_ID, getTrxName()); + if (c != null) + { + BigDecimal mcostQty = c.getCurrentQty(); + if (mcostQtyMap.containsKey(c.get_UUID())) { + mcostQty = mcostQty.subtract(mcostQtyMap.get(c.get_UUID())); + if (mcostQty.signum() < 0) + mcostQty = new BigDecimal("0.00"); + } + if (mcostQty.compareTo(lca.getQty()) < 0) { + amtAsset = mcostQty.multiply(costAdjustmentAmt.divide(lca.getQty(), as.getCostingPrecision(), RoundingMode.HALF_UP)); + amtVariance = costAdjustmentAmt.subtract(amtAsset); + costDetailQty = mcostQty; + } + if (mcostQtyMap.containsKey(c.get_UUID())) { + mcostQtyMap.put(c.get_UUID(), mcostQtyMap.get(c.get_UUID()).add(costDetailQty)); + } else { + mcostQtyMap.put(c.get_UUID(), costDetailQty); + } + } + } + + BigDecimal costDetailAmt = amtAsset; //convert to accounting schema currency if (getC_Currency_ID() != as.getC_Currency_ID()) costDetailAmt = MConversionRate.convert(getCtx(), costDetailAmt, @@ -1066,18 +1189,20 @@ public class Doc_Invoice extends Doc costDetailAmt = costDetailAmt.add(prevAmt); } costDetailAmtMap.put(key, costDetailAmt); - if (!MCostDetail.createInvoice(as, lca.getAD_Org_ID(), + if (costDetailAmt.signum() != 0 && + !MCostDetail.createInvoice(as, lca.getAD_Org_ID(), lca.getM_Product_ID(), lca.getM_AttributeSetInstance_ID(), C_InvoiceLine_ID, lca.getM_CostElement_ID(), - costDetailAmt, lca.getQty(), + costDetailAmt, costDetailQty, desc, getTrxName())) { throw new RuntimeException("Failed to create cost detail record."); } } catch (SQLException e) { throw new RuntimeException(e.getLocalizedMessage(), e); } catch (AverageCostingZeroQtyException e) { - zeroQty = true; - try { + try { + amtAsset = BigDecimal.ZERO; + amtVariance = costAdjustmentAmt; trx.rollback(savepoint); savepoint = null; } catch (SQLException e1) { @@ -1090,16 +1215,113 @@ public class Doc_Invoice extends Doc } catch (SQLException e) {} } } + } else if (reversal) { + costDetailQty = BigDecimal.ZERO; + int AD_Org_ID = lca.getAD_Org_ID(); + int M_AttributeSetInstance_ID = lca.getM_AttributeSetInstance_ID(); + + if (MAcctSchema.COSTINGLEVEL_Client.equals(as.getCostingLevel())) + { + AD_Org_ID = 0; + M_AttributeSetInstance_ID = 0; + } + else if (MAcctSchema.COSTINGLEVEL_Organization.equals(as.getCostingLevel())) + M_AttributeSetInstance_ID = 0; + else if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(as.getCostingLevel())) + AD_Org_ID = 0; + String key = lca.getM_Product_ID()+"_"+M_AttributeSetInstance_ID; + if (!costDetailAmtMap.containsKey(key)) { + costDetailAmtMap.put(key, BigDecimal.ZERO); + amtAsset = BigDecimal.ZERO; + amtVariance = BigDecimal.ZERO; + MAccount varianceAccount = pc.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as); + MAccount assetAccount = pc.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + Query query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, reversalLine.getC_Invoice_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + for(MFactAcct factAcct : factAccts) { + if (factAcct.getM_Product_ID() != lca.getM_Product_ID()) + continue; + if (factAcct.getLine_ID() != reversalLine.get_ID()) + continue; + if (factAcct.getAccount_ID() == assetAccount.getAccount_ID()) { + if (factAcct.getAmtAcctDr().signum() != 0) + amtAsset = amtAsset.add(factAcct.getAmtAcctDr()); + else if (factAcct.getAmtAcctCr().signum() != 0) + amtAsset = amtAsset.subtract(factAcct.getAmtAcctCr()); + } else if (factAcct.getAccount_ID() == varianceAccount.getAccount_ID()) { + if (factAcct.getAmtAcctDr().signum() != 0) + amtVariance = amtVariance.add(factAcct.getAmtAcctDr()); + else if (factAcct.getAmtAcctCr().signum() != 0) + amtVariance = amtVariance.subtract(factAcct.getAmtAcctCr()); + } + } + if (lca.getM_AttributeSetInstance_ID() > 0 && M_AttributeSetInstance_ID == 0) { + String sql = + """ + SELECT SUM(Qty) + FROM M_CostDetail + WHERE C_InvoiceLine_ID=? AND Coalesce(M_CostElement_ID,0)=? + AND M_Product_ID=? AND C_AcctSchema_ID=? + """; + costDetailQty = DB.getSQLValueBDEx(getTrxName(), sql, reversalLine.get_ID(), lca.getM_CostElement_ID(), lca.getM_Product_ID(), as.getC_AcctSchema_ID()); + if (costDetailQty == null) + costDetailQty = BigDecimal.ZERO; + } else if (lca.getM_AttributeSetInstance_ID() > 0 && M_AttributeSetInstance_ID > 0) { + MCostDetail cd = MCostDetail.get (as.getCtx(), "C_InvoiceLine_ID=? AND Coalesce(M_CostElement_ID,0)="+lca.getM_CostElement_ID()+" AND M_Product_ID="+lca.getM_Product_ID(), + reversalLine.get_ID(), lca.getM_AttributeSetInstance_ID(), as.getC_AcctSchema_ID(), getTrxName()); + costDetailQty = cd != null ? cd.getQty() : BigDecimal.ZERO; + if (cd != null) { + amtAsset = cd.getAmt(); + } + if (i > 0) { + for(int j = 0; j < i; j++) { + if (lcas[j].getM_Product_ID() == lca.getM_Product_ID()) { + //variance have been posted by product + amtVariance = BigDecimal.ZERO; + } + } + } + } else { + MCostDetail cd = MCostDetail.get (as.getCtx(), "C_InvoiceLine_ID=? AND Coalesce(M_CostElement_ID,0)="+lca.getM_CostElement_ID()+" AND M_Product_ID="+lca.getM_Product_ID(), + reversalLine.get_ID(), lca.getM_AttributeSetInstance_ID(), as.getC_AcctSchema_ID(), getTrxName()); + costDetailQty = cd != null ? cd.getQty() : BigDecimal.ZERO; + } + if (costDetailQty.signum() != 0) + { + MCostElement ce = MCostElement.getMaterialCostElement(getCtx(), as.getCostingMethod(), + AD_Org_ID); + MCost c = MCost.get(getCtx(), getAD_Client_ID(), AD_Org_ID, lca.getM_Product_ID(), + as.getM_CostType_ID(), as.getC_AcctSchema_ID(), ce.getM_CostElement_ID(), + M_AttributeSetInstance_ID, getTrxName()); + if (c != null) { + if (c.getCurrentQty().signum() == 0) { + amtVariance = amtVariance.add(amtAsset); + amtAsset = BigDecimal.ZERO; + } else if (c.getCurrentQty().compareTo(costDetailQty) < 0) { + BigDecimal currentAmtAsset = amtAsset; + amtAsset = amtAsset.divide(costDetailQty, RoundingMode.HALF_UP).multiply(c.getCurrentQty()); + amtVariance = amtVariance.add(currentAmtAsset.subtract(amtAsset)); + costDetailQty = c.getCurrentQty(); + } + } + } + if (amtAsset.signum() != 0) { + if (!MCostDetail.createInvoice(as, lca.getAD_Org_ID(), + lca.getM_Product_ID(), lca.getM_AttributeSetInstance_ID(), + C_InvoiceLine_ID, lca.getM_CostElement_ID(), + amtAsset.negate(), costDetailQty, + desc, getTrxName())) { + throw new RuntimeException("Failed to create cost detail record."); + } + } + if (getC_Currency_ID() != as.getC_Currency_ID()) { + usesSchemaCurrency = true; + setC_Currency_ID(as.getC_Currency_ID()); + } + } } - boolean reversal = false; - if (allocationAmt.signum() < 0) //reversal - { - allocationAmt = allocationAmt.negate(); - reversal = true; - } - - if (allocationAmt.signum() > 0) + if (allocationAmt.signum() > 0 && !reversal) { if (allocationAmt.scale() > as.getStdPrecision()) { @@ -1109,46 +1331,53 @@ public class Doc_Invoice extends Doc { estimatedAmt = estimatedAmt.setScale(as.getStdPrecision(), RoundingMode.HALF_UP); } - int compare = allocationAmt.compareTo(estimatedAmt); - if (compare > 0) + if (allocationAmt.compareTo(estimatedAmt)!=0) { - drAmt = dr ? (reversal ? null : estimatedAmt): (reversal ? estimatedAmt : null); - crAmt = dr ? (reversal ? estimatedAmt : null): (reversal ? null : estimatedAmt); - account = pc.getAccount(ProductCost.ACCTTYPE_P_LandedCostClearing, as); - FactLine fl = fact.createLine (line, account, getC_Currency_ID(), drAmt, crAmt); - fl.setDescription(desc); - fl.setM_Product_ID(lca.getM_Product_ID()); - fl.setQty(line.getQty()); + if (estimatedAmt.signum() != 0) + { + drAmt = dr ? (reversal ? null : estimatedAmt): (reversal ? estimatedAmt : null); + crAmt = dr ? (reversal ? estimatedAmt : null): (reversal ? null : estimatedAmt); + account = pc.getAccount(ProductCost.ACCTTYPE_P_LandedCostClearing, as); + FactLine fl = fact.createLine (line, account, getC_Currency_ID(), drAmt, crAmt); + fl.setDescription(desc); + fl.setM_Product_ID(lca.getM_Product_ID()); + fl.setQty(line.getQty()); + } - BigDecimal overAmt = allocationAmt.subtract(estimatedAmt); - drAmt = dr ? (reversal ? null : overAmt) : (reversal ? overAmt : null); - crAmt = dr ? (reversal ? overAmt : null) : (reversal ? null : overAmt); - account = zeroQty ? pc.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as) : pc.getAccount(ProductCost.ACCTTYPE_P_Asset, as); - fl = fact.createLine (line, account, getC_Currency_ID(), drAmt, crAmt); - fl.setDescription(desc); - fl.setM_Product_ID(lca.getM_Product_ID()); - fl.setQty(line.getQty()); + if (amtVariance.signum() != 0) { + if (amtVariance.signum() > 0) { + drAmt = dr ? amtVariance : null; + crAmt = dr ? null : amtVariance; + } else { + BigDecimal underAmt = amtVariance.negate(); + drAmt = dr ? null : underAmt; + crAmt = dr ? underAmt : null; + } + + account = pc.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as); + FactLine fl = fact.createLine(line, account, getC_Currency_ID(), drAmt, crAmt); + fl.setDescription(desc); + fl.setM_Product_ID(lca.getM_Product_ID()); + fl.setQty(line.getQty()); + } + + if (amtAsset.signum() != 0) { + if (amtAsset.signum() > 0) { + drAmt = dr ? amtAsset : null; + crAmt = dr ? null : amtAsset; + } else { + BigDecimal underAmt = amtAsset.negate(); + drAmt = dr ? null : underAmt; + crAmt = dr ? underAmt : null; + } + account = pc.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + FactLine fl = fact.createLine(line, account, getC_Currency_ID(), drAmt, crAmt); + fl.setDescription(desc); + fl.setM_Product_ID(lca.getM_Product_ID()); + fl.setQty(line.getQty()); + } } - else if (compare < 0) - { - drAmt = dr ? (reversal ? null : estimatedAmt) : (reversal ? estimatedAmt : null); - crAmt = dr ? (reversal ? estimatedAmt : null) : (reversal ? null : estimatedAmt); - account = pc.getAccount(ProductCost.ACCTTYPE_P_LandedCostClearing, as); - FactLine fl = fact.createLine (line, account, getC_Currency_ID(), drAmt, crAmt); - fl.setDescription(desc); - fl.setM_Product_ID(lca.getM_Product_ID()); - fl.setQty(line.getQty()); - - BigDecimal underAmt = estimatedAmt.subtract(allocationAmt); - drAmt = dr ? (reversal ? underAmt : null) : (reversal ? null : underAmt); - crAmt = dr ? (reversal ? null : underAmt) : (reversal ? underAmt : null); - account = zeroQty ? pc.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as) : pc.getAccount(ProductCost.ACCTTYPE_P_Asset, as); - fl = fact.createLine (line, account, getC_Currency_ID(), drAmt, crAmt); - fl.setDescription(desc); - fl.setM_Product_ID(lca.getM_Product_ID()); - fl.setQty(line.getQty()); - } - else + else if (allocationAmt.signum() != 0) { drAmt = dr ? (reversal ? null : allocationAmt) : (reversal ? allocationAmt : null); crAmt = dr ? (reversal ? allocationAmt : null) : (reversal ? null : allocationAmt); @@ -1158,7 +1387,46 @@ public class Doc_Invoice extends Doc fl.setM_Product_ID(lca.getM_Product_ID()); fl.setQty(line.getQty()); } - } + } else if (reversal) { + account = pc.getAccount(ProductCost.ACCTTYPE_P_LandedCostClearing, as); + FactLine fl = fact.createLine (line, account, getC_Currency_ID(), BigDecimal.ZERO, BigDecimal.ZERO); + fl.updateReverseLine(MInvoice.Table_ID, reversalLine.getC_Invoice_ID(), reversalLine.get_ID(), BigDecimal.ONE); + if (fl.getAmtAcctCr().signum() == 0 && fl.getAmtAcctDr().signum() == 0) + fact.remove(fl); + + if (amtVariance.signum() != 0) { + if (amtVariance.signum() > 0) { + drAmt = dr ? null : amtVariance; + crAmt = dr ? amtVariance : null; + } else { + BigDecimal underAmt = amtVariance.negate(); + drAmt = dr ? underAmt : null; + crAmt = dr ? null : underAmt; + } + + account = pc.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as); + fl = fact.createLine(line, account, getC_Currency_ID(), drAmt, crAmt); + fl.setDescription(desc); + fl.setM_Product_ID(lca.getM_Product_ID()); + fl.setQty(line.getQty()); + } + + if (amtAsset.signum() != 0) { + if (amtAsset.signum() > 0) { + drAmt = dr ? null : amtAsset; + crAmt = dr ? amtAsset : null; + } else { + BigDecimal underAmt = amtAsset.negate(); + drAmt = dr ? underAmt : null; + crAmt = dr ? null : underAmt; + } + account = pc.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + fl = fact.createLine(line, account, getC_Currency_ID(), drAmt, crAmt); + fl.setDescription(desc); + fl.setM_Product_ID(lca.getM_Product_ID()); + fl.setQty(line.getQty()); + } + } if (usesSchemaCurrency) setC_Currency_ID(line.getC_Currency_ID()); } diff --git a/org.adempiere.base/src/org/compiere/acct/Doc_MatchInv.java b/org.adempiere.base/src/org/compiere/acct/Doc_MatchInv.java index 4628d49620..a07ee1bafd 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc_MatchInv.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc_MatchInv.java @@ -36,7 +36,9 @@ import org.compiere.model.MAccount; import org.compiere.model.MAcctSchema; import org.compiere.model.MAcctSchemaElement; import org.compiere.model.MConversionRate; +import org.compiere.model.MCost; import org.compiere.model.MCostDetail; +import org.compiere.model.MCostElement; import org.compiere.model.MCurrency; import org.compiere.model.MFactAcct; import org.compiere.model.MInOut; @@ -389,7 +391,8 @@ public class Doc_MatchInv extends Doc // Invoice Price Variance difference BigDecimal ipv = cr.getAcctBalance().add(dr.getAcctBalance()).negate(); - processInvoicePriceVariance(as, fact, ipv); + BigDecimal ipvSource = dr.getAmtSourceDr().subtract(cr.getAmtSourceCr()).negate(); + processInvoicePriceVariance(as, fact, ipv, ipvSource); if (log.isLoggable(Level.FINE)) log.fine("IPV=" + ipv + "; Balance=" + fact.getSourceBalance()); String error = createMatchInvCostDetail(as); @@ -421,15 +424,70 @@ public class Doc_MatchInv extends Doc * @param ipv */ protected void processInvoicePriceVariance(MAcctSchema as, Fact fact, - BigDecimal ipv) { + BigDecimal ipv, BigDecimal ipvSource) { if (ipv.signum() == 0) return; - FactLine pv = fact.createLine(null, - m_pc.getAccount(ProductCost.ACCTTYPE_P_IPV, as), - as.getC_Currency_ID(), ipv); - updateFactLine(pv); - MMatchInv matchInv = (MMatchInv)getPO(); + String costingMethod = m_pc.getProduct().getCostingMethod(as); + BigDecimal amtVariance = Env.ZERO; + BigDecimal amtAsset = Env.ZERO; + BigDecimal qtyMatched = matchInv.getQty(); + BigDecimal qtyCost = null; + Boolean isStockCoverage = false; + + boolean isReversal = matchInv.getReversal_ID() > 0 && matchInv.getReversal_ID() < matchInv.get_ID(); + if (X_M_Cost.COSTINGMETHOD_AveragePO.equals(costingMethod) && m_invoiceLine.getM_Product_ID() > 0 && !isReversal) + { + isStockCoverage = true; + + int AD_Org_ID = m_receiptLine.getAD_Org_ID(); + int M_AttributeSetInstance_ID = matchInv.getM_AttributeSetInstance_ID(); + + if (MAcctSchema.COSTINGLEVEL_Client.equals(as.getCostingLevel())) + { + AD_Org_ID = 0; + M_AttributeSetInstance_ID = 0; + } + else if (MAcctSchema.COSTINGLEVEL_Organization.equals(as.getCostingLevel())) + M_AttributeSetInstance_ID = 0; + else if (MAcctSchema.COSTINGLEVEL_BatchLot.equals(as.getCostingLevel())) + AD_Org_ID = 0; + + MCostElement ce = MCostElement.getMaterialCostElement(getCtx(), costingMethod, AD_Org_ID); + + MCostDetail cd = MCostDetail.get (as.getCtx(), "M_MatchInv_ID=? AND Coalesce(M_CostElement_ID,0)=0", + matchInv.getM_MatchInv_ID(), M_AttributeSetInstance_ID, as.getC_AcctSchema_ID(), getTrxName()); + if(cd!=null){ + qtyCost = cd.getCurrentQty(); + }else{ + MCost c = MCost.get(getCtx(), getAD_Client_ID(), AD_Org_ID, m_invoiceLine.getM_Product_ID(), + as.getM_CostType_ID(), as.getC_AcctSchema_ID(), ce.getM_CostElement_ID(), + M_AttributeSetInstance_ID, getTrxName()); + qtyCost = (c!=null? c.getCurrentQty():Env.ZERO); + } + + if (qtyCost != null && qtyCost.compareTo(qtyMatched) < 0 ) + { + //If current cost qty < invoice qty + amtAsset = qtyCost.multiply(ipv).divide(qtyMatched,as.getCostingPrecision(),RoundingMode.HALF_UP); + amtVariance = ipv.subtract(amtAsset); + + }else{ + //If current qty >= invoice qty + amtAsset = ipv; + } + + } + else if (X_M_Cost.COSTINGMETHOD_AveragePO.equals(costingMethod) && m_invoiceLine.getM_Product_ID() > 0 && isReversal) + { + isStockCoverage = true; + int M_AttributeSetInstance_ID = matchInv.getM_AttributeSetInstance_ID(); + MCostDetail cd = MCostDetail.get (as.getCtx(), "M_MatchInv_ID=? AND Coalesce(M_CostElement_ID,0)=0", + matchInv.getReversal_ID(), M_AttributeSetInstance_ID, as.getC_AcctSchema_ID(), getTrxName()); + amtAsset = cd != null ? cd.getAmt().negate() : BigDecimal.ZERO; + amtVariance = ipv.subtract(amtAsset); + } + Trx trx = Trx.get(getTrxName(), false); Savepoint savepoint = null; boolean zeroQty = false; @@ -439,7 +497,7 @@ public class Doc_MatchInv extends Doc if (!MCostDetail.createMatchInvoice(as, m_invoiceLine.getAD_Org_ID(), m_invoiceLine.getM_Product_ID(), m_invoiceLine.getM_AttributeSetInstance_ID(), matchInv.getM_MatchInv_ID(), 0, - ipv, BigDecimal.ZERO, "Invoice Price Variance", getTrxName())) { + isStockCoverage ? amtAsset: ipv, BigDecimal.ZERO, "Invoice Price Variance", getTrxName())) { throw new RuntimeException("Failed to create cost detail record."); } } catch (SQLException e) { @@ -460,36 +518,57 @@ public class Doc_MatchInv extends Doc } } - String costingMethod = m_pc.getProduct().getCostingMethod(as); MAccount account = m_pc.getAccount(ProductCost.ACCTTYPE_P_Asset, as); if (m_pc.isService()) account = m_pc.getAccount(ProductCost.ACCTTYPE_P_Expense, as); if (X_M_Cost.COSTINGMETHOD_AveragePO.equals(costingMethod)) { - if (zeroQty) - account = m_pc.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as); - FactLine line = fact.createLine(null, - m_pc.getAccount(ProductCost.ACCTTYPE_P_IPV, as), - as.getC_Currency_ID(), ipv.negate()); - updateFactLine(line); - line.setQty(getQty().negate()); - - line = fact.createLine(null, account, as.getC_Currency_ID(), ipv); - updateFactLine(line); + FactLine varianceLine = null; + if (amtVariance.compareTo(Env.ZERO) != 0) + { + varianceLine = fact.createLine(null, + m_pc.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as), as.getC_Currency_ID(), + amtVariance); + updateFactLine(varianceLine); + + if (m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) + { + updateFactLineAmtSource(varianceLine, ipvSource.multiply(amtVariance).divide(ipv)); + } + } + if (amtAsset.compareTo(Env.ZERO) != 0) + { + FactLine line = fact.createLine(null, account, as.getC_Currency_ID(), amtAsset); + updateFactLine(line); + + if (m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) + { + updateFactLineAmtSource(line, ipvSource.multiply(amtAsset).divide(ipv)); + } + } } else if (X_M_Cost.COSTINGMETHOD_AverageInvoice.equals(costingMethod) && !zeroQty) { - FactLine line = fact.createLine(null, - m_pc.getAccount(ProductCost.ACCTTYPE_P_IPV, as), - as.getC_Currency_ID(), ipv.negate()); + //TODO test for avg Invoice costing method as here dropped posting of posting to IPV account + FactLine line = fact.createLine(null, account, as.getC_Currency_ID(), ipv); updateFactLine(line); - line.setQty(getQty().negate()); - line = fact.createLine(null, account, as.getC_Currency_ID(), ipv); - updateFactLine(line); + if (m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) + { + updateFactLineAmtSource(line, ipvSource); + } + }else{ + //For standard costing post to IPV account + FactLine pv = fact.createLine(null, + m_pc.getAccount(ProductCost.ACCTTYPE_P_IPV, as), + as.getC_Currency_ID(), ipv); + updateFactLine(pv); + if (m_invoiceLine.getParent().getC_Currency_ID() != as.getC_Currency_ID()) + { + updateFactLineAmtSource(pv, ipvSource); + } } } - /** - * Verify if the posting involves two or more organizations - * @return true if there are more than one org involved on the posting + /** Verify if the posting involves two or more organizations + @return true if there are more than one org involved on the posting */ private boolean isInterOrg(MAcctSchema as) { MAcctSchemaElement elementorg = as.getAcctSchemaElement(MAcctSchemaElement.ELEMENTTYPE_Organization); @@ -907,7 +986,8 @@ public class Doc_MatchInv extends Doc // Invoice Price Variance difference BigDecimal ipv = cr.getAcctBalance().add(dr.getAcctBalance()).negate(); - processInvoicePriceVariance(as, fact, ipv); + BigDecimal ipvSource = dr.getAmtSourceDr().subtract(cr.getAmtSourceCr()).negate(); + processInvoicePriceVariance(as, fact, ipv, ipvSource); if (log.isLoggable(Level.FINE)) log.fine("IPV=" + ipv + "; Balance=" + fact.getSourceBalance()); String error = createMatchInvCostDetail(as); @@ -1258,6 +1338,29 @@ public class Doc_MatchInv extends Doc factLine.setQty(getQty()); } + /** + * Invoice currency & acct schema currency are not same then update AmtSource value + * to avoid source not balanced error/ignore suspense balancing. + * + * @param factLine + * @param ipvSource + */ + protected void updateFactLineAmtSource(FactLine factLine, BigDecimal ipvSource) + { + // When only Rate differ then set Dr & Cr Source amount as zero. + factLine.setAmtSourceCr(Env.ZERO); + factLine.setAmtSourceDr(Env.ZERO); + + // Price is vary then set Source amount according to source variance + if (ipvSource.compareTo(Env.ZERO) != 0) + { + if (ipvSource.signum() < 0) + factLine.setAmtSourceCr(ipvSource); + else + factLine.setAmtSourceDr(ipvSource); + } + } + /** * Create Gain/Loss for invoice * @param as accounting schema diff --git a/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java b/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java index 9b4e0afc18..6be146daa3 100644 --- a/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java +++ b/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java @@ -202,7 +202,18 @@ public interface AdempiereDatabase */ public String TO_NUMBER (BigDecimal number, int displayType); + /** + * Return string as JSON object for INSERT statements + * @param value + * @return value as JSON + */ + public String TO_JSON (String value); + /** + * @return string with right casting for JSON inserts + */ + public String getJSONCast (); + /** * Get next sequence number in this Sequence * @param Name Sequence name @@ -312,7 +323,7 @@ public interface AdempiereDatabase public boolean isPagingSupported(); /** - * modify sql to return a subset of the query result + * modify sql to return a subset of the query result. use 1 base index for start and end parameter * @param sql * @param start * @param end @@ -424,6 +435,11 @@ public interface AdempiereDatabase */ public String getClobDataType(); + /** + * @return json object data type name + */ + public String getJsonDataType(); + /** * @return time stamp data type name */ diff --git a/org.adempiere.base/src/org/compiere/db/StatementProxy.java b/org.adempiere.base/src/org/compiere/db/StatementProxy.java index 9fd61a7d8e..2da803be93 100644 --- a/org.adempiere.base/src/org/compiere/db/StatementProxy.java +++ b/org.adempiere.base/src/org/compiere/db/StatementProxy.java @@ -25,6 +25,7 @@ import java.util.logging.Level; import javax.sql.RowSet; import org.adempiere.exceptions.DBException; +import org.compiere.model.SystemProperties; import org.compiere.util.CCachedRowSet; import org.compiere.util.CLogger; import org.compiere.util.CStatementVO; @@ -133,8 +134,11 @@ public class StatementProxy implements InvocationHandler { } } Method m = p_stmt.getClass().getMethod(name, method.getParameterTypes()); + String nullTrxName = null; try { + if (SystemProperties.isTraceNullTrxConnection() && p_vo.getTrxName() == null) + nullTrxName = Trx.registerNullTrx(); return m.invoke(p_stmt, args); } catch (InvocationTargetException e) @@ -143,6 +147,8 @@ public class StatementProxy implements InvocationHandler { } finally { + if (nullTrxName != null && p_vo.getTrxName() == null) + Trx.unregisterNullTrx(nullTrxName); if (log.isLoggable(Level.FINE) && logSql != null && logOperation != null) { log.fine((DisplayType.getDateFormat(DisplayType.DateTime)).format(new Date(System.currentTimeMillis()))+","+logOperation+","+logSql+","+(p_vo.getTrxName() != null ? p_vo.getTrxName() : "")+" (end)"); diff --git a/org.adempiere.base/src/org/compiere/dbPort/Convert.java b/org.adempiere.base/src/org/compiere/dbPort/Convert.java index 0d8c5896ef..a3d894d0d5 100644 --- a/org.adempiere.base/src/org/compiere/dbPort/Convert.java +++ b/org.adempiere.base/src/org/compiere/dbPort/Convert.java @@ -83,10 +83,12 @@ public abstract class Convert /** Logger */ private static final CLogger log = CLogger.getCLogger (Convert.class); + private static File fileOr = null; private static FileOutputStream fosScriptOr = null; - private static Writer writerOr; + private static Writer writerOr = null; + private static File filePg = null; private static FileOutputStream fosScriptPg = null; - private static Writer writerPg; + private static Writer writerPg = null; /** * Set Verbose @@ -471,7 +473,7 @@ public abstract class Convert Files.createDirectories(Paths.get(folderPg)); } if (fosScriptOr == null) { - File fileOr = new File(folderOr + fileName); + fileOr = new File(folderOr + fileName); fosScriptOr = new FileOutputStream(fileOr, true); writerOr = new BufferedWriter(new OutputStreamWriter(fosScriptOr, "UTF8")); writerOr.append("-- "); @@ -488,7 +490,7 @@ public abstract class Convert pgStatement = r[0]; } if (fosScriptPg == null) { - File filePg = new File(folderPg + fileName); + filePg = new File(folderPg + fileName); fosScriptPg = new FileOutputStream(filePg, true); writerPg = new BufferedWriter(new OutputStreamWriter(fosScriptPg, "UTF8")); writerPg.append("-- "); @@ -688,4 +690,48 @@ public abstract class Convert w.flush(); } + /** + * Close the files for migration scripts, used just on Tests + */ + public static void closeLogMigrationScript() { + try { + if (writerOr != null) { + writerOr.flush(); + writerOr.close(); + writerOr = null; + } + if (writerPg != null) { + writerPg.flush(); + writerPg.close(); + writerPg = null; + } + if (fosScriptOr != null) { + fosScriptOr.flush(); + fosScriptOr.close(); + fosScriptOr = null; + } + if (fosScriptPg != null) { + fosScriptPg.flush(); + fosScriptPg.close(); + fosScriptPg = null; + } + fileOr = null; + filePg = null; + } catch (IOException e) { + // ignore + e.printStackTrace(); + } + } + + /** + * Get the name of the migration script file + * @return + */ + public static String getGeneratedMigrationScriptFileName() { + if (filePg != null) { + return filePg.getName(); + } + return null; + } + } // Convert \ No newline at end of file diff --git a/org.adempiere.base/src/org/compiere/impexp/OFXBankStatementHandler.java b/org.adempiere.base/src/org/compiere/impexp/OFXBankStatementHandler.java index 0b8fd4a8f9..fa0c096fbd 100644 --- a/org.adempiere.base/src/org/compiere/impexp/OFXBankStatementHandler.java +++ b/org.adempiere.base/src/org/compiere/impexp/OFXBankStatementHandler.java @@ -245,7 +245,8 @@ public abstract class OFXBankStatementHandler extends DefaultHandler if (isOfx1) { - m_reader = new BufferedReader(new InputStreamReader(new OFX1ToXML(reader))); + OFX1ToXML in = new OFX1ToXML(reader); + m_reader = new BufferedReader(new InputStreamReader(in)); } else { @@ -257,9 +258,12 @@ public abstract class OFXBankStatementHandler extends DefaultHandler { m_errorMessage = new StringBuffer("ErrorReadingData"); m_errorDescription = new StringBuffer(e.getMessage()); - closeBufferedReader(); return result; } + finally + { + closeBufferedReader(); + } return result; } // attachInput diff --git a/org.adempiere.base/src/org/compiere/impexp/OFXFileBankStatementLoader.java b/org.adempiere.base/src/org/compiere/impexp/OFXFileBankStatementLoader.java index dc86eeb1f4..7b3daed703 100644 --- a/org.adempiere.base/src/org/compiere/impexp/OFXFileBankStatementLoader.java +++ b/org.adempiere.base/src/org/compiere/impexp/OFXFileBankStatementLoader.java @@ -17,6 +17,7 @@ package org.compiere.impexp; import java.io.FileInputStream; +import java.io.IOException; import org.compiere.model.MBankStatementLoader; import org.xml.sax.SAXException; @@ -70,6 +71,13 @@ public final class OFXFileBankStatementLoader extends OFXBankStatementHandler im m_errorMessage = new StringBuffer("ErrorReadingData"); m_errorDescription = new StringBuffer(); } + finally + { + if (m_stream != null) + try { + m_stream.close(); + } catch (IOException e) {} + } return result; } // init diff --git a/org.adempiere.base/src/org/compiere/model/DataStatusEvent.java b/org.adempiere.base/src/org/compiere/model/DataStatusEvent.java index 5fbbb1e161..313c44331c 100644 --- a/org.adempiere.base/src/org/compiere/model/DataStatusEvent.java +++ b/org.adempiere.base/src/org/compiere/model/DataStatusEvent.java @@ -257,10 +257,9 @@ public final class DataStatusEvent extends EventObject implements Serializable retValue.append(m_currentRow+1); // of retValue.append("/"); - if (m_allLoaded) - retValue.append(m_totalRows); - else - retValue.append(m_loadedRows).append("->").append(m_totalRows); + if (! m_allLoaded) + retValue.append(m_loadedRows).append("->"); + retValue.append(m_totalRows); // return retValue.toString(); } // getMessage @@ -358,6 +357,7 @@ public final class DataStatusEvent extends EventObject implements Serializable e.m_changedColumn == m_changedColumn && Util.equals(e.m_columnName, m_columnName) && e.m_currentRow == m_currentRow && + e.m_loadedRows == m_loadedRows && e.isInitEdit == isInitEdit; } diff --git a/org.adempiere.base/src/org/compiere/model/GridField.java b/org.adempiere.base/src/org/compiere/model/GridField.java index 69ac181f94..0e29105ab1 100644 --- a/org.adempiere.base/src/org/compiere/model/GridField.java +++ b/org.adempiere.base/src/org/compiere/model/GridField.java @@ -881,6 +881,7 @@ public class GridField if (m_vo.DefaultValue != null && !m_vo.DefaultValue.equals("") && !m_vo.DefaultValue.startsWith(MColumn.VIRTUAL_UI_COLUMN_PREFIX)) { String defStr = ""; // problem is with texts like 'sss;sss' + String defStrMultipleSelect = ""; // It is one or more variables/constants StringTokenizer st = new StringTokenizer(m_vo.DefaultValue, ",;", false); while (st.hasMoreTokens()) @@ -892,7 +893,16 @@ public class GridField defStr = Env.parseContext(m_vo.ctx, m_vo.WindowNo, m_vo.TabNo, defStr.trim(), false, false); else if (defStr.indexOf("'") != -1) // it is a 'String' defStr = defStr.replace('\'', ' ').trim(); - + + if (DisplayType.isChosenMultipleSelection(m_vo.displayType)) { + defStrMultipleSelect += defStr + ","; + if (!st.hasMoreTokens()) { + defStr = defStrMultipleSelect.substring(0, defStrMultipleSelect.length() - 1); + } else { + continue; + } + } + if (!defStr.equals("")) { if (log.isLoggable(Level.FINE)) log.fine("[DefaultValue] " + m_vo.ColumnName + "=" + defStr); @@ -2106,6 +2116,7 @@ public class GridField if (m_vo.displayType == DisplayType.Text || m_vo.displayType == DisplayType.Memo || m_vo.displayType == DisplayType.TextLong + || m_vo.displayType == DisplayType.JSON || m_vo.displayType == DisplayType.Binary || m_vo.displayType == DisplayType.RowID || isEncrypted()) diff --git a/org.adempiere.base/src/org/compiere/model/GridTab.java b/org.adempiere.base/src/org/compiere/model/GridTab.java index 29297e0e24..30c9a6961f 100644 --- a/org.adempiere.base/src/org/compiere/model/GridTab.java +++ b/org.adempiere.base/src/org/compiere/model/GridTab.java @@ -235,6 +235,8 @@ public class GridTab implements DataStatusListener, Evaluatee, Serializable public static final String CTX_IsLookupOnlySelection = "_TabInfo_IsLookupOnlySelection"; public static final String CTX_IsAllowAdvancedLookup = "_TabInfo_IsAllowAdvancedLookup"; + public static final int DEFAULT_GLOBAL_MAX_QUERY_RECORDS = 100000; + /** * Tab loader for Tabs > 0 */ @@ -2544,20 +2546,23 @@ public class GridTab implements DataStatusListener, Evaluatee, Serializable } // Row Count int rows = getRowCount(); - if (rows == 0) + if (rows == 0 && !m_mTable.isLoading()) { log.fine("No Rows"); return -1; } if (newRow >= rows) { - newRow = rows-1; - if (log.isLoggable(Level.FINE)) log.fine("Set to max Row: " + newRow); + if (!m_mTable.isLoading()) + { + newRow = rows-1; + if (log.isLoggable(Level.FINE)) log.fine("Set to max Row: " + newRow); + } } else if (newRow < 0) { newRow = 0; - log.fine("Set to first Row"); + if (log.isLoggable(Level.FINE)) log.fine("Set to first Row"); } m_mTable.waitLoadingForRow(newRow); @@ -2860,7 +2865,8 @@ public class GridTab implements DataStatusListener, Evaluatee, Serializable MLookup mLookup = (MLookup)dependentField.getLookup(); // if the lookup is dynamic (i.e. contains this columnName as variable) if (mLookup.getValidation().indexOf("@"+columnName+"@") != -1 - || mLookup.getValidation().matches(".*[@]"+columnName+"[:].+[@].*$")) + || mLookup.getValidation().matches(".*[@]"+getTabNo()+"[|]"+columnName+"([:].+)?[@].*") + || mLookup.getValidation().matches(".*[@][~]?"+columnName+"([:].+)?[@].*")) { if (log.isLoggable(Level.FINE)) log.fine(columnName + " changed - " + dependentField.getColumnName() + " set to null"); @@ -3556,6 +3562,9 @@ public class GridTab implements DataStatusListener, Evaluatee, Serializable int tabMaxQueryRecords = m_vo.MaxQueryRecords; if (roleMaxQueryRecords > 0 && (roleMaxQueryRecords < tabMaxQueryRecords || tabMaxQueryRecords == 0)) tabMaxQueryRecords = roleMaxQueryRecords; + if (tabMaxQueryRecords == 0) + tabMaxQueryRecords = MSysConfig.getIntValue(MSysConfig.GLOBAL_MAX_QUERY_RECORDS, + DEFAULT_GLOBAL_MAX_QUERY_RECORDS, Env.getAD_Client_ID(Env.getCtx())); return tabMaxQueryRecords; } diff --git a/org.adempiere.base/src/org/compiere/model/GridTable.java b/org.adempiere.base/src/org/compiere/model/GridTable.java index 7893bc8355..ae7af29209 100644 --- a/org.adempiere.base/src/org/compiere/model/GridTable.java +++ b/org.adempiere.base/src/org/compiere/model/GridTable.java @@ -46,6 +46,7 @@ import java.util.logging.Level; import javax.swing.event.TableModelListener; import javax.swing.table.AbstractTableModel; +import org.adempiere.exceptions.AdempiereException; import org.adempiere.exceptions.DBException; import org.adempiere.util.ServerContext; import org.compiere.Adempiere; @@ -95,14 +96,15 @@ public class GridTable extends AbstractTableModel implements Serializable { /** - * generated serial id + * */ - private static final long serialVersionUID = -5564364545827057092L; + private static final long serialVersionUID = 3948220810042370826L; protected static final String SORTED_DSE_EVENT = "Sorted"; public static final int DEFAULT_GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS = 30; - + public static final int DEFAULT_GRIDTABLE_COUNT_TIMEOUT_IN_SECONDS = 1; + public static final String LOAD_TIMEOUT_ERROR_MESSAGE = "GridTabLoadTimeoutError"; public static final String DATA_REFRESH_MESSAGE = "Refreshed"; @@ -169,6 +171,8 @@ public class GridTable extends AbstractTableModel /** Rowcount */ private int m_rowCount = 0; + private boolean m_rowCountTimeout = false; + private boolean m_rowLoadTimeout = false; /** Has Data changed? */ private boolean m_changed = false; /** Index of changed row via SetValueAt */ @@ -426,7 +430,8 @@ public class GridTable extends AbstractTableModel //IDEMPIERE-5193 Add Limit to Query if(m_maxRows > 0 && DB.getDatabase().isPagingSupported()) { - m_SQL = DB.getDatabase().addPagingSQL(m_SQL, 1, m_maxRows); + // set to maxRows plus one to trigger FindOverMax on overflow + m_SQL = DB.getDatabase().addPagingSQL(m_SQL, 1, m_maxRows+1); } // @@ -645,7 +650,8 @@ public class GridTable extends AbstractTableModel m_buffer = new ArrayList(m_rowCount+10); } m_sort = new ArrayList(m_rowCount+10); - if (m_rowCount > 0) + // actual row count or timeout + if (m_rowCount > 0 || m_rowCountTimeout) { m_loader.setContext(ServerContext.getCurrentInstance()); m_loaderFuture = Adempiere.getThreadPoolExecutor().submit(m_loader); @@ -1155,11 +1161,14 @@ public class GridTable extends AbstractTableModel if (savedEx != null) throw new IllegalStateException(savedEx); } + // zero rows found without load timeout + if (row == 0 && m_sort.size() == 0 && m_rowCountTimeout && !m_rowLoadTimeout) + throw new AdempiereException(Msg.getMsg(Env.getCtx(), "FindZeroRecords")); if (row >= m_sort.size()) { log.warning("Reached " + timeout + " seconds timeout loading row " + (row+1) + " for SQL=" + m_SQL); //adjust row count m_rowCount = m_sort.size(); - throw new DBException("GridTabLoadTimeoutError"); + throw new AdempiereException(Msg.getMsg(Env.getCtx(), LOAD_TIMEOUT_ERROR_MESSAGE)); } } @@ -1263,9 +1272,13 @@ public class GridTable extends AbstractTableModel PreparedStatement stmt = null; ResultSet rs = null; + m_rowLoadTimeout = false; try { stmt = DB.prepareStatement(sql.toString(), null); + int timeout = MSysConfig.getIntValue(MSysConfig.GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, DEFAULT_GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, Env.getAD_Client_ID(Env.getCtx())); + if (timeout > 0) + stmt.setQueryTimeout(timeout); rs = stmt.executeQuery(); while(rs.next()) { @@ -1297,6 +1310,8 @@ public class GridTable extends AbstractTableModel } catch (SQLException e) { + if (DB.getDatabase().isQueryTimeout(e)) + m_rowLoadTimeout = true; log.log(Level.SEVERE, e.getLocalizedMessage(), e); } finally @@ -3014,12 +3029,14 @@ public class GridTable extends AbstractTableModel // Get Number of Rows rows = 0; PreparedStatement pstmt = null; - ResultSet rs = null; + ResultSet rs = null; + m_rowCountTimeout = false; try { pstmt = DB.prepareStatement(m_SQL_Count, get_TrxName()); setParameter (pstmt, true); - int timeout = MSysConfig.getIntValue(MSysConfig.GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, DEFAULT_GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, Env.getAD_Client_ID(Env.getCtx())); + int timeout = MSysConfig.getIntValue(MSysConfig.GRIDTABLE_INITIAL_COUNT_TIMEOUT_IN_SECONDS, + DEFAULT_GRIDTABLE_COUNT_TIMEOUT_IN_SECONDS, Env.getAD_Client_ID(Env.getCtx())); if (timeout > 0) pstmt.setQueryTimeout(timeout); rs = pstmt.executeQuery(); @@ -3028,7 +3045,13 @@ public class GridTable extends AbstractTableModel } catch (SQLException e0) { - throw new DBException(e0); + if (DB.getDatabase().isQueryTimeout(e0)) + { + m_rowCountTimeout = true; + return 0; + } + else + throw new DBException(e0); } finally { @@ -3064,11 +3087,7 @@ public class GridTable extends AbstractTableModel try { m_pstmt = DB.prepareStatement(m_SQL, trxName); - if (this.maxRows > 0 && rows == this.maxRows) - { - m_pstmt.setMaxRows(this.maxRows); - } - //ensure not all row is fectch into memory for virtual table + //ensure not all rows are fetch into memory for virtual table if (m_virtual) m_pstmt.setFetchSize(100); setParameter (m_pstmt, false); @@ -3079,8 +3098,13 @@ public class GridTable extends AbstractTableModel } catch (SQLException e) { - log.saveError(e.getLocalizedMessage(), e); - throw new DBException(e); + if (DB.getDatabase().isQueryTimeout(e)) { + m_rowLoadTimeout = true; + throw new AdempiereException(Msg.getMsg(Env.getCtx(), LOAD_TIMEOUT_ERROR_MESSAGE), e); + } else { + log.saveError(e.getLocalizedMessage(), e); + throw new DBException(e); + } } } @@ -3118,6 +3142,7 @@ public class GridTable extends AbstractTableModel * Fill buffer from result set */ private void doRun() { + boolean isFindOverMax = false; try { openResultSet(); @@ -3126,6 +3151,11 @@ public class GridTable extends AbstractTableModel while (m_rs.next()) { + if (maxRows > 0 && m_sort.size() == maxRows) { + isFindOverMax = true; + break; + } + if (Thread.interrupted()) { if (log.isLoggable(Level.FINE)) log.fine("Interrupted"); @@ -3149,9 +3179,25 @@ public class GridTable extends AbstractTableModel } m_sort.add(sort); + // Start with rowCount=0, inform loading of first row + if (m_rowCountTimeout) + { + m_rowCount++; + if (m_rowCount == 1) + { + DataStatusEvent evt = createDSE(); + evt.setLoading(m_sort.size()); + evt.setInfo("CountQueryTimeoutLoadBackground", null, false, true); + fireDataStatusChanged(evt); + } + } + // Statement all 1000 rows & sleep if (m_sort.size() % 1000 == 0) { + DataStatusEvent evt = createDSE(); + evt.setLoading(m_sort.size()); + fireDataStatusChanged(evt); // give the other processes a chance try { @@ -3164,9 +3210,6 @@ public class GridTable extends AbstractTableModel close(); return; } - DataStatusEvent evt = createDSE(); - evt.setLoading(m_sort.size()); - fireDataStatusChanged(evt); } } // while(rs.next()) } @@ -3178,6 +3221,17 @@ public class GridTable extends AbstractTableModel { close(); } + + // Background loading without initial rowCount, inform final loaded rows + if (m_rowCountTimeout && m_sort.size() > 0) + { + DataStatusEvent evt = createDSE(); + evt.setLoading(m_sort.size()); + if (isFindOverMax) + evt.setInfo("FindOverMax", " > " + m_sort.size(), false, true); + fireDataStatusChanged(evt); + } + fireDataStatusIEvent("", ""); } diff --git a/org.adempiere.base/src/org/compiere/model/I_AD_ImportTemplate.java b/org.adempiere.base/src/org/compiere/model/I_AD_ImportTemplate.java index daa56bd6f7..54420f4dc5 100644 --- a/org.adempiere.base/src/org/compiere/model/I_AD_ImportTemplate.java +++ b/org.adempiere.base/src/org/compiere/model/I_AD_ImportTemplate.java @@ -22,7 +22,7 @@ import org.compiere.util.KeyNamePair; /** Generated Interface for AD_ImportTemplate * @author iDempiere (generated) - * @version Release 11 + * @version Release 12 */ public interface I_AD_ImportTemplate { diff --git a/org.adempiere.base/src/org/compiere/model/I_AD_PInstance.java b/org.adempiere.base/src/org/compiere/model/I_AD_PInstance.java index b1a37ab417..3b7207d80c 100644 --- a/org.adempiere.base/src/org/compiere/model/I_AD_PInstance.java +++ b/org.adempiere.base/src/org/compiere/model/I_AD_PInstance.java @@ -239,6 +239,19 @@ public interface I_AD_PInstance */ public boolean isSummary(); + /** Column name JsonData */ + public static final String COLUMNNAME_JsonData = "JsonData"; + + /** Set JSON Data. + * The json field stores json data. + */ + public void setJsonData (String JsonData); + + /** Get JSON Data. + * The json field stores json data. + */ + public String getJsonData(); + /** Column name Name */ public static final String COLUMNNAME_Name = "Name"; diff --git a/org.adempiere.base/src/org/compiere/model/I_AD_PInstance_Log.java b/org.adempiere.base/src/org/compiere/model/I_AD_PInstance_Log.java index 188c2adc36..a260f03f24 100644 --- a/org.adempiere.base/src/org/compiere/model/I_AD_PInstance_Log.java +++ b/org.adempiere.base/src/org/compiere/model/I_AD_PInstance_Log.java @@ -80,6 +80,19 @@ public interface I_AD_PInstance_Log public org.compiere.model.I_AD_Table getAD_Table() throws RuntimeException; + /** Column name JsonData */ + public static final String COLUMNNAME_JsonData = "JsonData"; + + /** Set JSON Data. + * The json field stores json data. + */ + public void setJsonData (String JsonData); + + /** Get JSON Data. + * The json field stores json data. + */ + public String getJsonData(); + /** Column name Log_ID */ public static final String COLUMNNAME_Log_ID = "Log_ID"; diff --git a/org.adempiere.base/src/org/compiere/model/I_AD_UserPreference.java b/org.adempiere.base/src/org/compiere/model/I_AD_UserPreference.java index 135b9ca181..d18abe4201 100644 --- a/org.adempiere.base/src/org/compiere/model/I_AD_UserPreference.java +++ b/org.adempiere.base/src/org/compiere/model/I_AD_UserPreference.java @@ -62,21 +62,6 @@ public interface I_AD_UserPreference */ public int getAD_Org_ID(); - /** Column name AD_User_ID */ - public static final String COLUMNNAME_AD_User_ID = "AD_User_ID"; - - /** Set User/Contact. - * User within the system - Internal or Business Partner Contact - */ - public void setAD_User_ID (int AD_User_ID); - - /** Get User/Contact. - * User within the system - Internal or Business Partner Contact - */ - public int getAD_User_ID(); - - public org.compiere.model.I_AD_User getAD_User() throws RuntimeException; - /** Column name AD_UserPreference_ID */ public static final String COLUMNNAME_AD_UserPreference_ID = "AD_UserPreference_ID"; @@ -95,6 +80,21 @@ public interface I_AD_UserPreference /** Get AD_UserPreference_UU */ public String getAD_UserPreference_UU(); + /** Column name AD_User_ID */ + public static final String COLUMNNAME_AD_User_ID = "AD_User_ID"; + + /** Set User/Contact. + * User within the system - Internal or Business Partner Contact + */ + public void setAD_User_ID (int AD_User_ID); + + /** Get User/Contact. + * User within the system - Internal or Business Partner Contact + */ + public int getAD_User_ID(); + + public org.compiere.model.I_AD_User getAD_User() throws RuntimeException; + /** Column name AutoCommit */ public static final String COLUMNNAME_AutoCommit = "AutoCommit"; @@ -104,6 +104,15 @@ public interface I_AD_UserPreference /** Get Automatic Commit */ public boolean isAutoCommit(); + /** Column name AutoNew */ + public static final String COLUMNNAME_AutoNew = "AutoNew"; + + /** Set Automatic New Record */ + public void setAutoNew (boolean AutoNew); + + /** Get Automatic New Record */ + public boolean isAutoNew(); + /** Column name AutomaticDecimalPlacesForAmoun */ public static final String COLUMNNAME_AutomaticDecimalPlacesForAmoun = "AutomaticDecimalPlacesForAmoun"; @@ -117,15 +126,6 @@ public interface I_AD_UserPreference */ public int getAutomaticDecimalPlacesForAmoun(); - /** Column name AutoNew */ - public static final String COLUMNNAME_AutoNew = "AutoNew"; - - /** Set Automatic New Record */ - public void setAutoNew (boolean AutoNew); - - /** Get Automatic New Record */ - public boolean isAutoNew(); - /** Column name Created */ public static final String COLUMNNAME_Created = "Created"; @@ -177,6 +177,15 @@ public interface I_AD_UserPreference /** Get Detailed Zoom Across */ public boolean isDetailedZoomAcross(); + /** Column name IsReadOnlySession */ + public static final String COLUMNNAME_IsReadOnlySession = "IsReadOnlySession"; + + /** Set Read Only Session */ + public void setIsReadOnlySession (boolean IsReadOnlySession); + + /** Get Read Only Session */ + public boolean isReadOnlySession(); + /** Column name IsUseSimilarTo */ public static final String COLUMNNAME_IsUseSimilarTo = "IsUseSimilarTo"; diff --git a/org.adempiere.base/src/org/compiere/model/I_Test.java b/org.adempiere.base/src/org/compiere/model/I_Test.java index 5c508de115..7988b0a1ea 100644 --- a/org.adempiere.base/src/org/compiere/model/I_Test.java +++ b/org.adempiere.base/src/org/compiere/model/I_Test.java @@ -22,7 +22,7 @@ import org.compiere.util.KeyNamePair; /** Generated Interface for Test * @author iDempiere (generated) - * @version Release 11 + * @version Release 12 */ public interface I_Test { @@ -41,17 +41,6 @@ public interface I_Test /** Load Meta Data */ - /** Column name Account_Acct */ - public static final String COLUMNNAME_Account_Acct = "Account_Acct"; - - /** Set Account_Acct */ - public void setAccount_Acct (int Account_Acct); - - /** Get Account_Acct */ - public int getAccount_Acct(); - - public I_C_ValidCombination getAccount_A() throws RuntimeException; - /** Column name AD_Client_ID */ public static final String COLUMNNAME_AD_Client_ID = "AD_Client_ID"; @@ -88,18 +77,29 @@ public interface I_Test public org.compiere.model.I_AD_Table getAD_Table() throws RuntimeException; + /** Column name Account_Acct */ + public static final String COLUMNNAME_Account_Acct = "Account_Acct"; + + /** Set Account_Acct */ + public void setAccount_Acct (int Account_Acct); + + /** Get Account_Acct */ + public int getAccount_Acct(); + + public I_C_ValidCombination getAccount_A() throws RuntimeException; + /** Column name BinaryData */ public static final String COLUMNNAME_BinaryData = "BinaryData"; /** Set Binary Data. * Binary Data */ - public void setBinaryData (int BinaryData); + public void setBinaryData (byte[] BinaryData); /** Get Binary Data. * Binary Data */ - public int getBinaryData(); + public byte[] getBinaryData(); /** Column name C_BPartner_ID */ public static final String COLUMNNAME_C_BPartner_ID = "C_BPartner_ID"; @@ -131,19 +131,6 @@ public interface I_Test public org.compiere.model.I_C_Currency getC_Currency() throws RuntimeException; - /** Column name CharacterData */ - public static final String COLUMNNAME_CharacterData = "CharacterData"; - - /** Set Character Data. - * Long Character Field - */ - public void setCharacterData (String CharacterData); - - /** Get Character Data. - * Long Character Field - */ - public String getCharacterData(); - /** Column name C_Location_ID */ public static final String COLUMNNAME_C_Location_ID = "C_Location_ID"; @@ -159,15 +146,6 @@ public interface I_Test public I_C_Location getC_Location() throws RuntimeException; - /** Column name Color */ - public static final String COLUMNNAME_Color = "Color"; - - /** Set Color */ - public void setColor (String Color); - - /** Get Color */ - public String getColor(); - /** Column name C_Payment_ID */ public static final String COLUMNNAME_C_Payment_ID = "C_Payment_ID"; @@ -183,6 +161,43 @@ public interface I_Test public org.compiere.model.I_C_Payment getC_Payment() throws RuntimeException; + /** Column name C_UOM_ID */ + public static final String COLUMNNAME_C_UOM_ID = "C_UOM_ID"; + + /** Set UOM. + * Unit of Measure + */ + public void setC_UOM_ID (int C_UOM_ID); + + /** Get UOM. + * Unit of Measure + */ + public int getC_UOM_ID(); + + public org.compiere.model.I_C_UOM getC_UOM() throws RuntimeException; + + /** Column name CharacterData */ + public static final String COLUMNNAME_CharacterData = "CharacterData"; + + /** Set Character Data. + * Long Character Field + */ + public void setCharacterData (String CharacterData); + + /** Get Character Data. + * Long Character Field + */ + public String getCharacterData(); + + /** Column name Color */ + public static final String COLUMNNAME_Color = "Color"; + + /** Set Color */ + public void setColor (String Color); + + /** Get Color */ + public String getColor(); + /** Column name Created */ public static final String COLUMNNAME_Created = "Created"; @@ -199,21 +214,6 @@ public interface I_Test */ public int getCreatedBy(); - /** Column name C_UOM_ID */ - public static final String COLUMNNAME_C_UOM_ID = "C_UOM_ID"; - - /** Set UOM. - * Unit of Measure - */ - public void setC_UOM_ID (int C_UOM_ID); - - /** Get UOM. - * Unit of Measure - */ - public int getC_UOM_ID(); - - public org.compiere.model.I_C_UOM getC_UOM() throws RuntimeException; - /** Column name Description */ public static final String COLUMNNAME_Description = "Description"; @@ -253,6 +253,19 @@ public interface I_Test */ public boolean isActive(); + /** Column name JsonData */ + public static final String COLUMNNAME_JsonData = "JsonData"; + + /** Set JSON Data. + * The json field stores json data. + */ + public void setJsonData (String JsonData); + + /** Get JSON Data. + * The json field stores json data. + */ + public String getJsonData(); + /** Column name M_Locator_ID */ public static final String COLUMNNAME_M_Locator_ID = "M_Locator_ID"; @@ -367,37 +380,6 @@ public interface I_Test /** Get DateTime */ public Timestamp getT_DateTime(); - /** Column name Test_ID */ - public static final String COLUMNNAME_Test_ID = "Test_ID"; - - /** Set Test ID */ - public void setTest_ID (int Test_ID); - - /** Get Test ID */ - public int getTest_ID(); - - /** Column name Test_UU */ - public static final String COLUMNNAME_Test_UU = "Test_UU"; - - /** Set Test_UU */ - public void setTest_UU (String Test_UU); - - /** Get Test_UU */ - public String getTest_UU(); - - /** Column name TestVirtualQty */ - public static final String COLUMNNAME_TestVirtualQty = "TestVirtualQty"; - - /** Set Virtual Quantity. - * Used only for testing purposes - */ - public void setTestVirtualQty (BigDecimal TestVirtualQty); - - /** Get Virtual Quantity. - * Used only for testing purposes - */ - public BigDecimal getTestVirtualQty(); - /** Column name T_Integer */ public static final String COLUMNNAME_T_Integer = "T_Integer"; @@ -438,6 +420,37 @@ public interface I_Test */ public Timestamp getT_Timestamp(); + /** Column name TestVirtualQty */ + public static final String COLUMNNAME_TestVirtualQty = "TestVirtualQty"; + + /** Set Virtual Quantity. + * Used only for testing purposes + */ + public void setTestVirtualQty (BigDecimal TestVirtualQty); + + /** Get Virtual Quantity. + * Used only for testing purposes + */ + public BigDecimal getTestVirtualQty(); + + /** Column name Test_ID */ + public static final String COLUMNNAME_Test_ID = "Test_ID"; + + /** Set Test ID */ + public void setTest_ID (int Test_ID); + + /** Get Test ID */ + public int getTest_ID(); + + /** Column name Test_UU */ + public static final String COLUMNNAME_Test_UU = "Test_UU"; + + /** Set Test_UU */ + public void setTest_UU (String Test_UU); + + /** Get Test_UU */ + public String getTest_UU(); + /** Column name Updated */ public static final String COLUMNNAME_Updated = "Updated"; diff --git a/org.adempiere.base/src/org/compiere/model/MAccount.java b/org.adempiere.base/src/org/compiere/model/MAccount.java index bd1682e19c..4317fc8240 100644 --- a/org.adempiere.base/src/org/compiere/model/MAccount.java +++ b/org.adempiere.base/src/org/compiere/model/MAccount.java @@ -276,6 +276,17 @@ public class MAccount extends X_C_ValidCombination implements ImmutablePOSupport * @return account */ public static MAccount get (X_Fact_Acct fa) + { + return get(fa, (String)null); + } + + /** + * Get from existing Accounting fact + * @param fa accounting fact + * @param trxName + * @return account + */ + public static MAccount get (X_Fact_Acct fa, String trxName) { MAccount acct = get (fa.getCtx(), fa.getAD_Client_ID(), fa.getAD_Org_ID(), fa.getC_AcctSchema_ID(), @@ -283,7 +294,7 @@ public class MAccount extends X_C_ValidCombination implements ImmutablePOSupport fa.getM_Product_ID(), fa.getC_BPartner_ID(), fa.getAD_OrgTrx_ID(), fa.getC_LocFrom_ID(), fa.getC_LocTo_ID(), fa.getC_SalesRegion_ID(), fa.getC_Project_ID(), fa.getC_Campaign_ID(), fa.getC_Activity_ID(), - fa.getUser1_ID(), fa.getUser2_ID(), fa.getUserElement1_ID(), fa.getUserElement2_ID()); + fa.getUser1_ID(), fa.getUser2_ID(), fa.getUserElement1_ID(), fa.getUserElement2_ID(), trxName); return acct; } // get diff --git a/org.adempiere.base/src/org/compiere/model/MAcctSchema.java b/org.adempiere.base/src/org/compiere/model/MAcctSchema.java index 8976261c95..7ec3cf820f 100644 --- a/org.adempiere.base/src/org/compiere/model/MAcctSchema.java +++ b/org.adempiere.base/src/org/compiere/model/MAcctSchema.java @@ -744,7 +744,7 @@ public class MAcctSchema extends X_C_AcctSchema implements ImmutablePOSupport StringBuilder sql = new StringBuilder("SELECT DISTINCT p.Value FROM M_Product p JOIN M_CostDetail d ON p.M_Product_ID=d.M_Product_ID"); sql.append(" JOIN M_Product_Category_Acct pc ON p.M_Product_Category_ID=pc.M_Product_Category_ID AND d.C_AcctSchema_ID=pc.C_AcctSchema_ID"); sql.append(" WHERE p.IsActive='Y' AND pc.IsActive='Y' AND pc.CostingLevel IS NULL AND d.C_AcctSchema_ID=?"); - String query = DB.getDatabase().addPagingSQL(sql.toString(), 0, 50); + String query = DB.getDatabase().addPagingSQL(sql.toString(), 1, 50); List> list = DB.getSQLArrayObjectsEx(get_TrxName(), query, getC_AcctSchema_ID()); if (list != null) { for(List entry : list) { diff --git a/org.adempiere.base/src/org/compiere/model/MAttachment.java b/org.adempiere.base/src/org/compiere/model/MAttachment.java index 24a03827a7..21fbff855b 100644 --- a/org.adempiere.base/src/org/compiere/model/MAttachment.java +++ b/org.adempiere.base/src/org/compiere/model/MAttachment.java @@ -38,6 +38,7 @@ import org.compiere.tools.FileUtil; import org.compiere.util.CLogger; import org.compiere.util.DB; import org.compiere.util.Env; +import org.compiere.util.Msg; import org.compiere.util.Util; /** @@ -55,9 +56,9 @@ import org.compiere.util.Util; public class MAttachment extends X_AD_Attachment { /** - * generated serial id + * */ - private static final long serialVersionUID = 5615231734722570658L; + private static final long serialVersionUID = 5422581050563711060L; /** * @param ctx @@ -209,7 +210,31 @@ public class MAttachment extends X_AD_Attachment /** string replaces the attachment root in stored xml file * to allow the changing of the attachment root. */ public final String ATTACHMENT_FOLDER_PLACEHOLDER = "%ATTACHMENT_FOLDER%"; - + + /* Attachment files can be read, but not written/deleted */ + private Boolean isReadOnly = null; + + /** + * If the related record is on System and the user is operating on Tenant, the attachment is read-only + * @return + */ + public boolean isReadOnly() { + if (isReadOnly == null) { + isReadOnly = true; + MTable table = MTable.get(getAD_Table_ID()); + if (table != null) { + PO po = null; + if (table.isUUIDKeyTable()) + po = table.getPOByUU(getRecord_UU(), get_TrxName()); + else + po = table.getPO(getRecord_ID(), get_TrxName()); + if (po != null && ! po.is_new() && po.getAD_Client_ID() == Env.getAD_Client_ID(getCtx())) + isReadOnly = false; + } + } + return isReadOnly; + } + /** * Initialize storage provider * @param ctx @@ -427,6 +452,8 @@ public class MAttachment extends X_AD_Attachment * @return true if deleted */ public boolean deleteEntry(int index) { + if (isReadOnly()) + throw new AdempiereException(Msg.getMsg(getCtx(), "R/O")); if (m_items == null) loadLOBData(); if (index >= 0 && index < m_items.size()) { @@ -570,6 +597,8 @@ public class MAttachment extends X_AD_Attachment @Override protected boolean beforeSave (boolean newRecord) { + if (isReadOnly()) + throw new AdempiereException(Msg.getMsg(getCtx(), "R/O")); if (Util.isEmpty(getTitle())) setTitle(NONE); if (getRecord_ID() > 0 && getAD_Table_ID() > 0 && Util.isEmpty(getRecord_UU())) { @@ -581,6 +610,13 @@ public class MAttachment extends X_AD_Attachment return saveLOBData(); // save in BinaryData } // beforeSave + @Override + protected boolean beforeDelete() { + if (isReadOnly()) + throw new AdempiereException(Msg.getMsg(getCtx(), "R/O")); + return true; + } + /** * Ask storage provider to remove attachment content * @return true if saved diff --git a/org.adempiere.base/src/org/compiere/model/MBPartner.java b/org.adempiere.base/src/org/compiere/model/MBPartner.java index 510a804522..f07f68f057 100644 --- a/org.adempiere.base/src/org/compiere/model/MBPartner.java +++ b/org.adempiere.base/src/org/compiere/model/MBPartner.java @@ -686,7 +686,7 @@ public class MBPartner extends X_C_BPartner implements ImmutablePOSupport /** * Get Primary AD_User_ID - * @return AD_User_ID or 0 + * @return AD_User_ID or -1 */ public int getPrimaryAD_User_ID() { @@ -697,7 +697,7 @@ public class MBPartner extends X_C_BPartner implements ImmutablePOSupport setPrimaryAD_User_ID(users[0].getAD_User_ID()); } if (m_primaryAD_User_ID == null) - return 0; + return -1; return m_primaryAD_User_ID.intValue(); } // getPrimaryAD_User_ID diff --git a/org.adempiere.base/src/org/compiere/model/MChart.java b/org.adempiere.base/src/org/compiere/model/MChart.java index a3e6147a50..bcfdefec23 100644 --- a/org.adempiere.base/src/org/compiere/model/MChart.java +++ b/org.adempiere.base/src/org/compiere/model/MChart.java @@ -21,18 +21,31 @@ **********************************************************************/ package org.compiere.model; +import java.awt.image.BufferedImage; import java.sql.ResultSet; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; import java.util.Properties; +import org.adempiere.apps.graph.ChartBuilder; import org.compiere.util.Env; +import org.jfree.chart.ChartRenderingInfo; +import org.jfree.chart.JFreeChart; +import org.jfree.data.category.CategoryDataset; +import org.jfree.data.general.Dataset; +import org.jfree.data.general.PieDataset; +import org.jfree.data.xy.XYDataset; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; public class MChart extends X_AD_Chart { /** * generated serial id */ - private static final long serialVersionUID = 5720760885280644477L; - + private static final long serialVersionUID = -6709381648397609341L; + private int windowNo=0; /** @@ -87,4 +100,117 @@ public class MChart extends X_AD_Chart { public int getWindowNo() { return windowNo; } + + /** + * Get chart image + * @param id + * @param width + * @param height + * @return chart image + */ + public BufferedImage getChartImage(int width, int height) { + if (width <= 0) + width = getWinHeight(); + if (width <= 0) + width = 100; // default + if (height <= 0) + height = getWinHeight(); // default to make a square + if (height <= 0) + height = 100; // default to make a square of 100px + ChartBuilder chartBuilder = new ChartBuilder(this); + JFreeChart chart = chartBuilder.createChart(); + chart.getPlot().setForegroundAlpha(0.8f); + ChartRenderingInfo info = new ChartRenderingInfo(); + BufferedImage bi = chart.createBufferedImage(width, height, BufferedImage.TRANSLUCENT, info); + return bi; + } + + /** + * Get the data from the chart on JSON format + * @return + */ + public JsonObject getData() { + JsonObject json = new JsonObject(); + ChartBuilder cb = new ChartBuilder(this); + Dataset ds; + JsonArray array = new JsonArray(); + String type = getChartType(); + if ( isTimeSeries() + && ( MChart.CHARTTYPE_BarChart.equals(type) + || MChart.CHARTTYPE_LineChart.equals(type) + || MChart.CHARTTYPE_StackedBarChart.equals(type) + ) + ) { + ds = cb.getXYDataset(); + } else if ( MChart.CHARTTYPE_3DPieChart.equals(type) + || MChart.CHARTTYPE_PieChart.equals(type) + || MChart.CHARTTYPE_RingChart.equals(type) + ) { + ds = cb.getPieDataset(); + } else { + ds = cb.getCategoryDataset(); + } + if (ds instanceof CategoryDataset) { + json.addProperty("name", get_Translation(COLUMNNAME_Name)); + json.addProperty("domain-label", get_Translation(COLUMNNAME_DomainLabel)); + json.addProperty("range-label", get_Translation(COLUMNNAME_RangeLabel)); + json.addProperty("display-legend", isDisplayLegend()); + json.addProperty("orientation", getChartOrientation()); + json.addProperty("chart-type", getChartType()); + CategoryDataset cds = (CategoryDataset) ds; + int rowCount = cds.getRowCount(); + int columnCount = cds.getColumnCount(); + for (int row = 0; row < rowCount; row++) { + for (int col = 0; col < columnCount; col++) { + Number value = cds.getValue(row, col); + Comparable rowKey = cds.getRowKey(row); + Comparable colKey = cds.getColumnKey(col); + JsonObject rec = new JsonObject(); + rec.addProperty("row", rowKey.toString()); + rec.addProperty("column", colKey.toString()); + rec.addProperty("value", value); + array.add(rec); + } + } + } else if (ds instanceof XYDataset) { + json.addProperty("name", get_Translation(COLUMNNAME_Name)); + json.addProperty("domain-label", get_Translation(COLUMNNAME_DomainLabel)); + json.addProperty("range-label", get_Translation(COLUMNNAME_RangeLabel)); + json.addProperty("display-legend", isDisplayLegend()); + json.addProperty("orientation", getChartOrientation()); + json.addProperty("chart-type", getChartType()); + XYDataset xyds = (XYDataset) ds; + int seriesCount = xyds.getSeriesCount(); + for (int series = 0; series < seriesCount; series++) { + Comparable seriesKey = xyds.getSeriesKey(series); + int itemCount = xyds.getItemCount(series); + for (int item = 0; item < itemCount; item++) { + Number xValue = xyds.getX(series, item); + Date date = new Date((long) xValue); + String strDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date); + Number yValue = xyds.getY(series, item); + JsonObject rec = new JsonObject(); + rec.addProperty("series", seriesKey.toString()); + rec.addProperty("x", strDate); + rec.addProperty("y", yValue); + array.add(rec); + } + } + } else if (ds instanceof PieDataset) { + json.addProperty("name", get_Translation(COLUMNNAME_Name)); + json.addProperty("display-legend", isDisplayLegend()); + json.addProperty("chart-type", getChartType()); + PieDataset pds = (PieDataset) ds; + for (int i = 0; i < pds.getKeys().size(); i++) { + Comparable key = pds.getKey(i); + Number value = pds.getValue(key); + JsonObject rec = new JsonObject(); + rec.addProperty("key", key.toString()); + rec.addProperty("value", value); + array.add(rec); + } + } + json.add("data", array); + return json; + } } diff --git a/org.adempiere.base/src/org/compiere/model/MColumn.java b/org.adempiere.base/src/org/compiere/model/MColumn.java index 0c3234581c..ff854894ab 100644 --- a/org.adempiere.base/src/org/compiere/model/MColumn.java +++ b/org.adempiere.base/src/org/compiere/model/MColumn.java @@ -380,7 +380,7 @@ public class MColumn extends X_AD_Column implements ImmutablePOSupport } int displayType = getAD_Reference_ID(); - if (DisplayType.isLOB(displayType)) // LOBs are 0 + if (DisplayType.isLOB(displayType) || displayType == DisplayType.JSON) // LOBs are 0 { if (getFieldLength() != 0) setFieldLength(0); @@ -592,11 +592,12 @@ public class MColumn extends X_AD_Column implements ImmutablePOSupport setSeqNoPartition(ii); } - if (is_ValueChanged(COLUMNNAME_IsPartitionKey) + // Validate partition column changes + if (!newRecord && (is_ValueChanged(COLUMNNAME_IsPartitionKey) || is_ValueChanged(COLUMNNAME_PartitioningMethod) || (isPartitionKey() && is_ValueChanged(COLUMNNAME_IsActive)) || (isPartitionKey() && is_ValueChanged(COLUMNNAME_SeqNoPartition)) - || (isPartitionKey() && is_ValueChanged(COLUMNNAME_RangePartitionInterval))) { + || (isPartitionKey() && is_ValueChanged(COLUMNNAME_RangePartitionInterval)))) { ITablePartitionService service = DB.getDatabase().getTablePartitionService(); if (service == null) { log.saveError("Error", Msg.getMsg(getCtx(), "DBAdapterNoTablePartitionSupport")); @@ -609,6 +610,9 @@ public class MColumn extends X_AD_Column implements ImmutablePOSupport } } + if (getAD_Reference_ID() == DisplayType.Payment) + setAD_Reference_Value_ID(SystemIDs.REFERENCE_PAYMENTRULE); + return true; } // beforeSave @@ -1031,6 +1035,10 @@ public class MColumn extends X_AD_Column implements ImmutablePOSupport String referenceTableName = column.getReferenceTableName(); if (referenceTableName != null) { + // Fk doesn't work for partitioned PostgreSQL table + if (DB.isPostgreSQL() && MTable.get(Env.getCtx(), referenceTableName) != null && MTable.get(Env.getCtx(), referenceTableName).isPartition()) + return null; + Hashtable htForeignKeys = new Hashtable(); if (md.storesUpperCaseIdentifiers()) { @@ -1376,6 +1384,8 @@ public class MColumn extends X_AD_Column implements ImmutablePOSupport if (query != null && query.length() > 0) { if (query.startsWith(VIRTUAL_UI_COLUMN_PREFIX) && nullForUI) query = "NULL"; + else if (query.startsWith(VIRTUAL_UI_COLUMN_PREFIX) && !nullForUI) + query = query.substring(5); else if (query.startsWith(VIRTUAL_SEARCH_COLUMN_PREFIX) && nullForSearch) query = "NULL"; else if (query.startsWith(VIRTUAL_SEARCH_COLUMN_PREFIX) && !nullForSearch) diff --git a/org.adempiere.base/src/org/compiere/model/MFactAcct.java b/org.adempiere.base/src/org/compiere/model/MFactAcct.java index 1f179fbe8b..3cc4983be8 100644 --- a/org.adempiere.base/src/org/compiere/model/MFactAcct.java +++ b/org.adempiere.base/src/org/compiere/model/MFactAcct.java @@ -126,6 +126,7 @@ public class MFactAcct extends X_Fact_Acct sb.append(get_ID()).append("-Acct=").append(getAccount_ID()) .append(",Dr=").append(getAmtSourceDr()).append("|").append(getAmtAcctDr()) .append(",Cr=").append(getAmtSourceCr()).append("|").append(getAmtAcctCr()) + .append(",C_Currency_ID=").append(getC_Currency_ID()) .append ("]"); return sb.toString (); } // toString diff --git a/org.adempiere.base/src/org/compiere/model/MField.java b/org.adempiere.base/src/org/compiere/model/MField.java index 14a4d638fa..ea4fc53e75 100644 --- a/org.adempiere.base/src/org/compiere/model/MField.java +++ b/org.adempiere.base/src/org/compiere/model/MField.java @@ -232,6 +232,15 @@ public class MField extends X_AD_Field implements ImmutablePOSupport setAD_Val_Rule_ID(0); if (getIsToolbarButton() != null) setIsToolbarButton(null); + } + + //If the column is a virtual search column - set displayed to false + if (isDisplayed()) { + MColumn column = (MColumn) getAD_Column(); + if (column.isVirtualSearchColumn()) { + setIsDisplayed(false); + setIsDisplayedGrid(false); + } } //validate logic expression diff --git a/org.adempiere.base/src/org/compiere/model/MGoal.java b/org.adempiere.base/src/org/compiere/model/MGoal.java index 2078e4e928..39111ef953 100644 --- a/org.adempiere.base/src/org/compiere/model/MGoal.java +++ b/org.adempiere.base/src/org/compiere/model/MGoal.java @@ -383,6 +383,8 @@ public class MGoal extends X_PA_Goal public boolean updateGoal(boolean force) { if (log.isLoggable(Level.CONFIG)) log.config("Force=" + force); + if (Env.isReadOnlySession()) + return false; MMeasure measure = MMeasure.get(getPA_Measure_ID()); boolean isUpdateByInterfal = false; diff --git a/org.adempiere.base/src/org/compiere/model/MImportTemplate.java b/org.adempiere.base/src/org/compiere/model/MImportTemplate.java index d0579c5f4a..bb19a22df1 100644 --- a/org.adempiere.base/src/org/compiere/model/MImportTemplate.java +++ b/org.adempiere.base/src/org/compiere/model/MImportTemplate.java @@ -34,13 +34,12 @@ import java.util.Properties; import java.util.logging.Level; import org.adempiere.exceptions.AdempiereException; -import org.apache.poi.hssf.usermodel.HSSFWorkbookFactory; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; -import org.apache.poi.xssf.usermodel.XSSFWorkbookFactory; +import org.apache.poi.ss.usermodel.WorkbookFactory; import org.compiere.util.CCache; import org.compiere.util.CLogger; import org.compiere.util.DB; @@ -238,8 +237,7 @@ public class MImportTemplate extends X_AD_ImportTemplate implements ImmutablePOS */ public InputStream validateFile(InputStream in) { - if ( MImportTemplate.IMPORTTEMPLATETYPE_XLS.equals(getImportTemplateType()) - || MImportTemplate.IMPORTTEMPLATETYPE_XLSX.equals(getImportTemplateType())) { + if (MImportTemplate.IMPORTTEMPLATETYPE_ExcelXLSXLSX.equals(getImportTemplateType())) { try { in = convertExcelToCSV(in); } catch (Exception e) { @@ -308,17 +306,7 @@ public class MImportTemplate extends X_AD_ImportTemplate implements ImmutablePOS */ public InputStream convertExcelToCSV(InputStream excelIs) throws IOException { - Workbook workbook = null; - if (MImportTemplate.IMPORTTEMPLATETYPE_XLS.equals(getImportTemplateType())) { - HSSFWorkbookFactory xlsWbf = new HSSFWorkbookFactory(); - workbook = xlsWbf.create(excelIs); - } else if (MImportTemplate.IMPORTTEMPLATETYPE_XLSX.equals(getImportTemplateType())) { - XSSFWorkbookFactory xlsxWbf = new XSSFWorkbookFactory(); - workbook = xlsxWbf.create(excelIs); - } else { - // unexpected error - throw new AdempiereException("Wrong template type -> " + getImportTemplateType()); - } + Workbook workbook = WorkbookFactory.create(excelIs); List colTypes = calculateAndValidateColumnTypes(); diff --git a/org.adempiere.base/src/org/compiere/model/MInvoiceLine.java b/org.adempiere.base/src/org/compiere/model/MInvoiceLine.java index 3bd339d7d2..16c5c758e8 100644 --- a/org.adempiere.base/src/org/compiere/model/MInvoiceLine.java +++ b/org.adempiere.base/src/org/compiere/model/MInvoiceLine.java @@ -500,8 +500,11 @@ public class MInvoiceLine extends X_C_InvoiceLine int M_Warehouse_ID = Env.getContextAsInt(getCtx(), Env.M_WAREHOUSE_ID); // String deliveryViaRule = null; + int dropShipLocationId = -1; if (getC_OrderLine_ID() > 0) { - deliveryViaRule = new MOrderLine(getCtx(), getC_OrderLine_ID(), get_TrxName()).getParent().getDeliveryViaRule(); + MOrder order = new MOrderLine(getCtx(), getC_OrderLine_ID(), get_TrxName()).getParent(); + deliveryViaRule = order.getDeliveryViaRule(); + dropShipLocationId = order.getDropShip_Location_ID(); } else if (getM_InOutLine_ID() > 0) { deliveryViaRule = new MInOutLine(getCtx(), getM_InOutLine_ID(), get_TrxName()).getParent().getDeliveryViaRule(); } else if (getParent().getC_Order_ID() > 0) { @@ -510,7 +513,7 @@ public class MInvoiceLine extends X_C_InvoiceLine int C_Tax_ID = Core.getTaxLookup().get(getCtx(), getM_Product_ID(), getC_Charge_ID() , m_DateInvoiced, m_DateInvoiced, getAD_Org_ID(), M_Warehouse_ID, m_C_BPartner_Location_ID, // should be bill to - m_C_BPartner_Location_ID, m_IsSOTrx, deliveryViaRule, get_TrxName()); + m_C_BPartner_Location_ID, dropShipLocationId, m_IsSOTrx, deliveryViaRule, get_TrxName()); if (C_Tax_ID == 0) { log.log(Level.SEVERE, "No Tax found"); diff --git a/org.adempiere.base/src/org/compiere/model/MLookup.java b/org.adempiere.base/src/org/compiere/model/MLookup.java index 65731d521b..4747f754f0 100644 --- a/org.adempiere.base/src/org/compiere/model/MLookup.java +++ b/org.adempiere.base/src/org/compiere/model/MLookup.java @@ -58,9 +58,9 @@ import org.compiere.util.ValueNamePair; public final class MLookup extends Lookup implements Serializable { /** - * generated serial id + * */ - private static final long serialVersionUID = 2288661955135689187L; + private static final long serialVersionUID = 3339750658316918418L; /** * MLookup Constructor @@ -1083,9 +1083,9 @@ public final class MLookup extends Lookup implements Serializable protected class MLoader extends ContextRunnable implements Serializable { /** - * generated serial id + * */ - private static final long serialVersionUID = -7868426685745727939L; + private static final long serialVersionUID = -5752931726580011885L; /** * MLoader Constructor @@ -1228,7 +1228,13 @@ public final class MLookup extends Lookup implements Serializable try { // SELECT Key, Value, Name, IsActive FROM ... - pstmt = DB.prepareStatement(sql.toString(), null); + String sqlFirstRows = DB.getDatabase().addPagingSQL(sql.toString(), 1, MAX_ROWS+1); + pstmt = DB.prepareStatement(sqlFirstRows, null); + if (! DB.getDatabase().isPagingSupported()) + pstmt.setMaxRows(MAX_ROWS+1); + int timeout = MSysConfig.getIntValue(MSysConfig.GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, GridTable.DEFAULT_GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, Env.getAD_Client_ID(Env.getCtx())); + if (timeout > 0) + pstmt.setQueryTimeout(timeout); rs = pstmt.executeQuery(); // Get first ... rows @@ -1237,19 +1243,10 @@ public final class MLookup extends Lookup implements Serializable { if (rows++ > MAX_ROWS) { - StringBuilder s = new StringBuilder().append(m_info.KeyColumn).append(": Loader - Too many records"); - if (m_info.Column_ID > 0) - { - MColumn mColumn = MColumn.get(m_info.ctx, m_info.Column_ID); - String column = mColumn.getColumnName(); - s.append(", Column=").append(column); - String tableName = MTable.getTableName(m_info.ctx, mColumn.getAD_Table_ID()); - s.append(", Table=").append(tableName); - } - log.warning(s.toString()); + logLookup(Level.WARNING, "Too many records"); break; } - // check for interrupted every 10 rows + // check for interrupted every 20 rows if (rows % 20 == 0 && Thread.interrupted()) break; @@ -1294,8 +1291,11 @@ public final class MLookup extends Lookup implements Serializable } catch (SQLException e) { - log.log(Level.SEVERE, m_info.KeyColumn + ", " + m_info.Column_ID + " : Loader - " + sql, e); m_allLoaded = false; + if (DB.getDatabase().isQueryTimeout(e)) + logLookup(Level.WARNING, "Too slow query"); + else + logLookup(Level.SEVERE, e.getLocalizedMessage()); } finally { DB.close(rs, pstmt); @@ -1307,6 +1307,25 @@ public final class MLookup extends Lookup implements Serializable + " - ms=" + String.valueOf(System.currentTimeMillis()-m_startTime) + " (" + String.valueOf(System.currentTimeMillis()-startTime) + ")"); } // run + + /** + * Log a warning for the lookup problem found + * @param problem + */ + private void logLookup(Level level, String problem) { + if (log.isLoggable(level)) { + StringBuilder msg = new StringBuilder().append(m_info.KeyColumn).append(": Loader - ").append(problem); + if (m_info.Column_ID > 0) { + MColumn mColumn = MColumn.get(m_info.ctx, m_info.Column_ID); + String column = mColumn.getColumnName(); + msg.append(", Column=").append(column); + String tableName = MTable.getTableName(m_info.ctx, mColumn.getAD_Table_ID()); + msg.append(", Table=").append(tableName); + } + log.log(level, msg.toString()); + } + } + } // Loader } // MLookup diff --git a/org.adempiere.base/src/org/compiere/model/MMailText.java b/org.adempiere.base/src/org/compiere/model/MMailText.java index 94cc816225..5e49f654c2 100644 --- a/org.adempiere.base/src/org/compiere/model/MMailText.java +++ b/org.adempiere.base/src/org/compiere/model/MMailText.java @@ -332,7 +332,7 @@ public class MMailText extends X_R_MailText m_MailText3 = super.getMailText3(); if ((m_bpartner != null && m_bpartner.getAD_Language() != null) || !Util.isEmpty(m_language)) { - String adLanguage = m_bpartner != null ? m_bpartner.getAD_Language() : m_language; + String adLanguage = m_bpartner != null && m_bpartner.getAD_Language() != null ? m_bpartner.getAD_Language() : m_language; StringBuilder key = new StringBuilder().append(adLanguage).append(get_ID()); MMailTextTrl trl = s_cacheTrl.get(key.toString()); if (trl == null) diff --git a/org.adempiere.base/src/org/compiere/model/MMeasureCalc.java b/org.adempiere.base/src/org/compiere/model/MMeasureCalc.java index 73ae930eb5..5c55c3bebc 100644 --- a/org.adempiere.base/src/org/compiere/model/MMeasureCalc.java +++ b/org.adempiere.base/src/org/compiere/model/MMeasureCalc.java @@ -533,7 +533,7 @@ public class MMeasureCalc extends X_PA_MeasureCalc implements ImmutablePOSupport @Override public String getWhereClause() { String whereClause = super.getWhereClause(); - if (! whereClause.toLowerCase().startsWith("where ")) + if (! whereClause.matches("(?si)\\s*where\\s+.*")) whereClause = "WHERE " + whereClause; return whereClause; } diff --git a/org.adempiere.base/src/org/compiere/model/MOrder.java b/org.adempiere.base/src/org/compiere/model/MOrder.java index 7851c95837..8e09314bd5 100644 --- a/org.adempiere.base/src/org/compiere/model/MOrder.java +++ b/org.adempiere.base/src/org/compiere/model/MOrder.java @@ -41,12 +41,9 @@ import org.adempiere.model.ITaxProvider; import org.adempiere.process.SalesOrderRateInquiryProcess; import org.adempiere.util.IReservationTracer; import org.adempiere.util.IReservationTracerFactory; -import org.compiere.print.MPrintFormat; import org.compiere.print.ReportEngine; import org.compiere.process.DocAction; import org.compiere.process.DocumentEngine; -import org.compiere.process.ProcessInfo; -import org.compiere.process.ServerProcessCtl; import org.compiere.util.CLogger; import org.compiere.util.DB; import org.compiere.util.Env; @@ -887,16 +884,7 @@ public class MOrder extends X_C_Order implements DocAction @Override public File createPDF () { - try - { - File temp = File.createTempFile(get_TableName()+get_ID()+"_", ".pdf"); - return createPDF (temp); - } - catch (Exception e) - { - log.severe("Could not create PDF - " + e.getMessage()); - } - return null; + return createPDF (null); } // getPDF /** @@ -909,21 +897,6 @@ public class MOrder extends X_C_Order implements DocAction ReportEngine re = ReportEngine.get (getCtx(), ReportEngine.ORDER, getC_Order_ID(), get_TrxName()); if (re == null) return null; - MPrintFormat format = re.getPrintFormat(); - // We have a Jasper Print Format - // ============================== - if(format.getJasperProcess_ID() > 0) - { - ProcessInfo pi = new ProcessInfo ("", format.getJasperProcess_ID()); - pi.setRecord_ID ( getC_Order_ID() ); - pi.setIsBatch(true); - - ServerProcessCtl.process(pi, null); - - return pi.getPDFReport(); - } - // Standard Print Format (Non-Jasper) - // ================================== return re.getPDF(file); } // createPDF @@ -1275,7 +1248,7 @@ public class MOrder extends X_C_Order implements DocAction set_ValueNoCheck(COLUMNNAME_C_BPartner_Location_ID, null); } } - if (getAD_User_ID() > 0) { + if (getAD_User_ID() >= 0) { int bpId = DB.getSQLValueEx(get_TrxName(), sqlBPIdFromUser, getAD_User_ID()); if (bpId != getC_BPartner_ID()) { set_Value(COLUMNNAME_AD_User_ID, null); @@ -1289,7 +1262,7 @@ public class MOrder extends X_C_Order implements DocAction set_Value(COLUMNNAME_Bill_Location_ID, null); } } - if (getBill_User_ID() > 0) { + if (getBill_User_ID() >= 0) { int bpId = DB.getSQLValueEx(get_TrxName(), sqlBPIdFromUser, getBill_User_ID()); if (bpId != getBill_BPartner_ID()) { setBill_User_ID(-1); diff --git a/org.adempiere.base/src/org/compiere/model/MOrderLine.java b/org.adempiere.base/src/org/compiere/model/MOrderLine.java index e2f8a1e097..0213e477d1 100644 --- a/org.adempiere.base/src/org/compiere/model/MOrderLine.java +++ b/org.adempiere.base/src/org/compiere/model/MOrderLine.java @@ -348,7 +348,7 @@ public class MOrderLine extends X_C_OrderLine int ii = Core.getTaxLookup().get(getCtx(), getM_Product_ID(), getC_Charge_ID(), getDateOrdered(), getDateOrdered(), getAD_Org_ID(), getM_Warehouse_ID(), getC_BPartner_Location_ID(), // should be bill to - getC_BPartner_Location_ID(), m_IsSOTrx, getParent().getDeliveryViaRule(), get_TrxName()); + getC_BPartner_Location_ID(), getParent().getDropShip_Location_ID(), m_IsSOTrx, getParent().getDeliveryViaRule(), get_TrxName()); if (ii == 0) { log.log(Level.SEVERE, "No Tax found"); @@ -808,8 +808,10 @@ public class MOrderLine extends X_C_OrderLine // R/O Check - Product/Warehouse Change if (!newRecord - && (is_ValueChanged("M_Product_ID") || is_ValueChanged("M_Warehouse_ID") || - (!getParent().isProcessed() && is_ValueChanged(COLUMNNAME_M_AttributeSetInstance_ID)))) + && ( is_ValueChanged("M_Product_ID") + || is_ValueChanged("M_Warehouse_ID") + || ( !getParent().isProcessed() + && getM_AttributeSetInstance_ID() != get_ValueOldAsInt(COLUMNNAME_M_AttributeSetInstance_ID)))) { if (!canChangeWarehouse()) return false; @@ -906,6 +908,16 @@ public class MOrderLine extends X_C_OrderLine } } + MClientInfo ci = MClientInfo.get(getCtx(), getAD_Client_ID(), get_TrxName()); + if (MOrder.DELIVERYVIARULE_Shipper.equals(getParent().getDeliveryViaRule()) && MOrder.FREIGHTCOSTRULE_FreightIncluded.equals(getParent().getFreightCostRule()) + && ( (getM_Product_ID() > 0 && getM_Product_ID() == ci.getM_ProductFreight_ID()) + || (getC_Charge_ID() > 0 && getC_Charge_ID() == ci.getC_ChargeFreight_ID()) + ) + ) { + log.saveError("Error", Msg.getMsg(getCtx(), "FreightOrderLineNotAllowed")); + return false; + } + return true; } // beforeSave diff --git a/org.adempiere.base/src/org/compiere/model/MPInstance.java b/org.adempiere.base/src/org/compiere/model/MPInstance.java index 4f9e80f781..a92b9cc307 100644 --- a/org.adempiere.base/src/org/compiere/model/MPInstance.java +++ b/org.adempiere.base/src/org/compiere/model/MPInstance.java @@ -30,6 +30,7 @@ import java.util.logging.Level; import org.adempiere.base.Core; import org.adempiere.base.event.EventManager; +import org.adempiere.exceptions.AdempiereException; import org.compiere.print.MPrintFormat; import org.compiere.util.CLogger; import org.compiere.util.DB; @@ -343,7 +344,10 @@ public class MPInstance extends X_AD_PInstance procMsg.append(proc.get_Translation("Name")).append(" / "); } procMsg.append(proc.getName()).append("]"); - throw new IllegalStateException(Msg.getMsg(getCtx(), "CannotAccessProcess", new Object[] {procMsg.toString(), role.getName()})); + if (Env.isReadOnlySession()) + throw new AdempiereException(Msg.getMsg(getCtx(), "ReadOnlySession")); + else + throw new IllegalStateException(Msg.getMsg(getCtx(), "CannotAccessProcess", new Object[] {procMsg.toString(), role.getName()})); } } super.setAD_Process_ID (AD_Process_ID); diff --git a/org.adempiere.base/src/org/compiere/model/MPInstanceLog.java b/org.adempiere.base/src/org/compiere/model/MPInstanceLog.java index 48fa6e3f63..bbaa7884f0 100644 --- a/org.adempiere.base/src/org/compiere/model/MPInstanceLog.java +++ b/org.adempiere.base/src/org/compiere/model/MPInstanceLog.java @@ -47,7 +47,7 @@ public class MPInstanceLog public MPInstanceLog (int AD_PInstance_ID, int Log_ID, Timestamp P_Date, int P_ID, BigDecimal P_Number, String P_Msg) { - this(AD_PInstance_ID, Log_ID, P_Date, P_ID, P_Number, P_Msg, 0, 0, X_AD_PInstance_Log.PINSTANCELOGTYPE_Result); + this(AD_PInstance_ID, Log_ID, P_Date, P_ID, P_Number, P_Msg, 0, 0, null, X_AD_PInstance_Log.PINSTANCELOGTYPE_Result); } // MPInstance_Log /** @@ -63,7 +63,7 @@ public class MPInstanceLog public MPInstanceLog (int AD_PInstance_ID, int Log_ID, Timestamp P_Date, int P_ID, BigDecimal P_Number, String P_Msg, int AD_Table_ID, int Record_ID) { - this(AD_PInstance_ID, Log_ID, P_Date, P_ID, P_Number, P_Msg, AD_Table_ID, Record_ID, X_AD_PInstance_Log.PINSTANCELOGTYPE_Result); + this(AD_PInstance_ID, Log_ID, P_Date, P_ID, P_Number, P_Msg, AD_Table_ID, Record_ID, null, X_AD_PInstance_Log.PINSTANCELOGTYPE_Result); } // MPInstance_Log /** @@ -78,9 +78,9 @@ public class MPInstanceLog * @param PInstanceLogType Log Type X_AD_PInstance_Log.PINSTANCELOGTYPE_* */ public MPInstanceLog (int AD_PInstance_ID, int Log_ID, Timestamp P_Date, - int P_ID, BigDecimal P_Number, String P_Msg, int AD_Table_ID, int Record_ID, String PInstanceLogType) + int P_ID, BigDecimal P_Number, String P_Msg, int AD_Table_ID, int Record_ID, String jsonData, String PInstanceLogType) { - this("", AD_PInstance_ID, Log_ID, P_Date, P_ID, P_Number, P_Msg, AD_Table_ID, Record_ID, PInstanceLogType); + this("", AD_PInstance_ID, Log_ID, P_Date, P_ID, P_Number, P_Msg, AD_Table_ID, Record_ID, jsonData, PInstanceLogType); } /** @@ -97,7 +97,7 @@ public class MPInstanceLog * @param PInstanceLogType Log Type X_AD_PInstance_Log.PINSTANCELOGTYPE_* */ public MPInstanceLog (String AD_PInstance_Log_UU, int AD_PInstance_ID, int Log_ID, Timestamp P_Date, - int P_ID, BigDecimal P_Number, String P_Msg, int AD_Table_ID, int Record_ID, String PInstanceLogType) + int P_ID, BigDecimal P_Number, String P_Msg, int AD_Table_ID, int Record_ID, String jsonData, String PInstanceLogType) { setAD_PInstance_ID(AD_PInstance_ID); setLog_ID(Log_ID); @@ -107,6 +107,7 @@ public class MPInstanceLog setP_Msg(P_Msg); setAD_Table_ID(AD_Table_ID); setRecord_ID(Record_ID); + setJsonData(jsonData); setPInstanceLogType(PInstanceLogType); if(!Util.isEmpty(AD_PInstance_Log_UU)) setAD_PInstance_Log_UU(AD_PInstance_Log_UU); @@ -127,6 +128,7 @@ public class MPInstanceLog setP_Msg(rs.getString(X_AD_PInstance_Log.COLUMNNAME_P_Msg)); setAD_Table_ID(rs.getInt(X_AD_PInstance_Log.COLUMNNAME_AD_Table_ID)); setRecord_ID(rs.getInt(X_AD_PInstance_Log.COLUMNNAME_Record_ID)); + setJsonData(rs.getString(X_AD_PInstance_Log.COLUMNNAME_JsonData)); setPInstanceLogType(rs.getString(X_AD_PInstance_Log.COLUMNNAME_PInstanceLogType)); setAD_PInstance_Log_UU(rs.getString(X_AD_PInstance_Log.COLUMNNAME_AD_PInstance_Log_UU)); } // MPInstance_Log @@ -139,6 +141,7 @@ public class MPInstanceLog private String m_P_Msg; private int m_AD_Table_ID; private int m_Record_ID; + private String m_jsonData; private String m_PInstanceLogType; private String m_AD_PInstance_Log_UU; @@ -159,13 +162,15 @@ public class MPInstanceLog sb.append(",Number=").append(m_P_Number); if (m_P_Msg != null) sb.append(",").append(m_P_Msg); + if (m_jsonData != null) + sb.append(",").append(m_jsonData); sb.append("]"); return sb.toString(); } // toString private final static String insertSql = "INSERT INTO AD_PInstance_Log " - + "(AD_PInstance_ID, Log_ID, P_Date, P_ID, P_Number, P_Msg, AD_Table_ID, Record_ID, AD_PInstance_Log_UU, PInstanceLogType)" - + " VALUES (?,?,?,?,?,?,?,?,?,?) "; + + "(AD_PInstance_ID, Log_ID, P_Date, P_ID, P_Number, P_Msg, AD_Table_ID, Record_ID, AD_PInstance_Log_UU, JsonData, PInstanceLogType)" + + " VALUES (?,?,?,?,?,?,?,?,?," + DB.getJSONCast() + ",?) "; private final static String updateSql = "UPDATE AD_PInstance_Log " + " SET P_Date = ?, " @@ -174,6 +179,7 @@ public class MPInstanceLog + " P_Msg = ?, " + " AD_Table_ID = ?, " + " Record_ID = ?, " + + " JsonData = " + DB.getJSONCast() + ", " + " PInstanceLogType = ? " + " WHERE AD_PInstance_Log_UU = ? "; @@ -247,6 +253,8 @@ public class MPInstanceLog if(isInsert) params.add(getAD_PInstance_Log_UU()); + params.add(m_jsonData); + params.add(m_PInstanceLogType); if(!isInsert) @@ -399,6 +407,21 @@ public class MPInstanceLog m_Record_ID = recordId; } + /** + * Get JsonData + * @return JsonData + */ + public String getJsonData() { + return m_jsonData; + } + /** + * Set JsonData + * @param jsonData + */ + public void setJsonData(String jsonData) { + this.m_jsonData = jsonData; + } + /** * Get Log Type * @return Instance Log Type (X_AD_PInstance_Log.PINSTANCELOGTYPE_*) diff --git a/org.adempiere.base/src/org/compiere/model/MProcess.java b/org.adempiere.base/src/org/compiere/model/MProcess.java index cdbb1cfea2..60714a1313 100644 --- a/org.adempiere.base/src/org/compiere/model/MProcess.java +++ b/org.adempiere.base/src/org/compiere/model/MProcess.java @@ -327,6 +327,7 @@ public class MProcess extends X_AD_Process implements ImmutablePOSupport // Unlock pInstance.setResult(ok ? MPInstance.RESULT_OK : MPInstance.RESULT_ERROR); pInstance.setErrorMsg(processInfo.getSummary()); + pInstance.setJsonData(processInfo.getJsonData()); pInstance.setIsProcessing(false); pInstance.saveEx(); // @@ -379,6 +380,7 @@ public class MProcess extends X_AD_Process implements ImmutablePOSupport String errmsg = pi.getSummary(); pinstance.setResult(!pi.isError()); pinstance.setErrorMsg(errmsg); + pinstance.setJsonData(pi.getJsonData()); pinstance.saveEx(); ok = !pi.isError(); } diff --git a/org.adempiere.base/src/org/compiere/model/MProductCategoryAcct.java b/org.adempiere.base/src/org/compiere/model/MProductCategoryAcct.java index 63497f8d39..653e9c41a9 100644 --- a/org.adempiere.base/src/org/compiere/model/MProductCategoryAcct.java +++ b/org.adempiere.base/src/org/compiere/model/MProductCategoryAcct.java @@ -236,7 +236,7 @@ public class MProductCategoryAcct extends X_M_Product_Category_Acct implements I StringBuilder products = new StringBuilder(); StringBuilder sql = new StringBuilder("SELECT DISTINCT p.Value FROM M_Product p JOIN M_CostDetail d ON p.M_Product_ID=d.M_Product_ID"); sql.append(" WHERE p.IsActive='Y' AND p.M_Product_Category_ID=? AND d.C_AcctSchema_ID=?"); - String query = DB.getDatabase().addPagingSQL(sql.toString(), 0, 50); + String query = DB.getDatabase().addPagingSQL(sql.toString(), 1, 50); List> list = DB.getSQLArrayObjectsEx(get_TrxName(), query, getM_Product_Category_ID(), getC_AcctSchema_ID()); if (list != null) { for(List entry : list) { diff --git a/org.adempiere.base/src/org/compiere/model/MQuery.java b/org.adempiere.base/src/org/compiere/model/MQuery.java index 036f0917f0..5bef7ea887 100644 --- a/org.adempiere.base/src/org/compiere/model/MQuery.java +++ b/org.adempiere.base/src/org/compiere/model/MQuery.java @@ -1845,7 +1845,7 @@ class QueryEvaluatee implements Evaluatee { } String value = null; - if (variableName.startsWith("#") || variableName.startsWith("$")) { + if (Env.isGlobalVariable(variableName)) { value = Env.getContext(ctx, variableName); } else { value = parameterMap.get(variableName); @@ -1857,7 +1857,7 @@ class QueryEvaluatee implements Evaluatee { id = Integer.parseInt(value); } catch (Exception e){} if (id > 0) { - if (variableName.startsWith("#") || variableName.startsWith("$")) { + if (Env.isGlobalVariable(variableName)) { variableName = variableName.substring(1); } else if (variableName.indexOf("|") > 0) { variableName = variableName.substring(variableName.lastIndexOf("|")+1); diff --git a/org.adempiere.base/src/org/compiere/model/MRMALine.java b/org.adempiere.base/src/org/compiere/model/MRMALine.java index 3d6295e213..0a89a4bb5d 100644 --- a/org.adempiere.base/src/org/compiere/model/MRMALine.java +++ b/org.adempiere.base/src/org/compiere/model/MRMALine.java @@ -181,6 +181,10 @@ public class MRMALine extends X_M_RMALine MInvoice invoice = getParent().getOriginalInvoice(); if (invoice != null) { + int dropshipLocationId = -1; + MOrder order = invoice.getOriginalOrder(); + if (order != null) + dropshipLocationId = order.getDropShip_Location_ID(); pp.setM_PriceList_ID(invoice.getM_PriceList_ID()); pp.setPriceDate(invoice.getDateInvoiced()); @@ -192,7 +196,7 @@ public class MRMALine extends X_M_RMALine taxId = Core.getTaxLookup().get(getCtx(), getM_Product_ID(), getC_Charge_ID(), invoice.getDateInvoiced(), invoice.getDateInvoiced(), getAD_Org_ID(), getParent().getShipment().getM_Warehouse_ID(), invoice.getC_BPartner_Location_ID(), // should be bill to - invoice.getC_BPartner_Location_ID(), getParent().isSOTrx(), deliveryViaRule, get_TrxName()); + invoice.getC_BPartner_Location_ID(), dropshipLocationId, getParent().isSOTrx(), deliveryViaRule, get_TrxName()); } else { @@ -206,7 +210,7 @@ public class MRMALine extends X_M_RMALine taxId = Core.getTaxLookup().get(getCtx(), getM_Product_ID(), getC_Charge_ID(), order.getDateOrdered(), order.getDateOrdered(), getAD_Org_ID(), order.getM_Warehouse_ID(), order.getC_BPartner_Location_ID(), // should be bill to - order.getC_BPartner_Location_ID(), getParent().isSOTrx(), order.getDeliveryViaRule(), get_TrxName()); + order.getC_BPartner_Location_ID(), order.getDropShip_Location_ID(), getParent().isSOTrx(), order.getDeliveryViaRule(), get_TrxName()); } else throw new IllegalStateException("No Invoice/Order found the Shipment/Receipt associated"); diff --git a/org.adempiere.base/src/org/compiere/model/MRole.java b/org.adempiere.base/src/org/compiere/model/MRole.java index ef59a77fc8..5591bc7c85 100644 --- a/org.adempiere.base/src/org/compiere/model/MRole.java +++ b/org.adempiere.base/src/org/compiere/model/MRole.java @@ -1673,6 +1673,9 @@ public final class MRole extends X_AD_Role implements ImmutablePOSupport if (log.isLoggable(Level.FINE)) log.fine("#" + m_windowAccess.size()); } // reload Boolean retValue = m_windowAccess.get(AD_Window_ID); + // User Preference window is excluded - otherwise the user would not be able to reset the read-only session preference + if (retValue != null && AD_Window_ID != SystemIDs.WINDOW_USER_PREFERENCE && Env.isReadOnlySession()) + retValue = Boolean.FALSE; if (log.isLoggable(Level.FINE)) log.fine("getWindowAccess - AD_Window_ID=" + AD_Window_ID + " - " + retValue); return retValue; } // getWindowAccess @@ -1765,6 +1768,8 @@ public final class MRole extends X_AD_Role implements ImmutablePOSupport retValue = null; } } + if (retValue != null && Env.isReadOnlySession()) + retValue = Boolean.FALSE; return retValue; } // getProcessAccess @@ -1854,6 +1859,8 @@ public final class MRole extends X_AD_Role implements ImmutablePOSupport retValue = null; } } + if (retValue != null && Env.isReadOnlySession()) + retValue = Boolean.FALSE; return retValue; } // getTaskAccess @@ -1943,6 +1950,8 @@ public final class MRole extends X_AD_Role implements ImmutablePOSupport retValue = null; } } + if (retValue != null && Env.isReadOnlySession()) + retValue = Boolean.FALSE; return retValue; } // getFormAccess @@ -2032,6 +2041,8 @@ public final class MRole extends X_AD_Role implements ImmutablePOSupport retValue = null; } } + if (retValue != null && Env.isReadOnlySession()) + retValue = Boolean.FALSE; return retValue; } // getWorkflowAccess @@ -2146,7 +2157,7 @@ public final class MRole extends X_AD_Role implements ImmutablePOSupport keyColumnName += getIdColumnName(TableName); //log.fine("addAccessSQL - " + TableName + "(" + AD_Table_ID + ") " + keyColumnName); - String recordWhere = getRecordWhere (AD_Table_ID, keyColumnName, rw); + String recordWhere = getRecordWhere (AD_Table_ID, keyColumnName, rw, TableName, ti[i].getSynonym()); if (recordWhere.length() > 0) { retSQL.append(" AND ").append(recordWhere); @@ -2481,9 +2492,11 @@ public final class MRole extends X_AD_Role implements ImmutablePOSupport * @param AD_Table_ID table * @param keyColumnName (fully qualified) key column name * @param rw true if read write + * @param tableName + * @param alias * @return where clause or "" */ - private String getRecordWhere (int AD_Table_ID, String keyColumnName, boolean rw) + private String getRecordWhere (int AD_Table_ID, String keyColumnName, boolean rw, String tableName, String alias) { loadRecordAccess(false); // @@ -2545,6 +2558,8 @@ public final class MRole extends X_AD_Role implements ImmutablePOSupport if (sb.length() > 0) sb.append(" AND "); String wherevr = Env.parseContext(p_ctx, 0, tvr.getCode(), false); + if (! Util.isEmpty(alias) && ! alias.equals(tableName)) + wherevr = wherevr.replaceAll("\\b" + tableName + "\\b", alias); sb.append(" (").append(wherevr).append(") "); } @@ -3449,4 +3464,29 @@ public final class MRole extends X_AD_Role implements ImmutablePOSupport return this; } + /** + * Check record access through {@link #addAccessSQL(String, String, boolean, boolean)} using
+ * either record id or record uuid + * @param table + * @param recordId ignore if uuid is use + * @param uuid null to use recordId + * @param rw true for writable, false for readonly + * @return true if role has access to record + */ + public boolean checkAccessSQL(MTable table, int recordId, String uuid, boolean rw) { + StringBuilder sql = new StringBuilder("SELECT 1 FROM ") + .append(table.getTableName()) + .append(" WHERE ") + .append(table.getTableName()) + .append("."); + if (!Util.isEmpty(uuid, true) ) { + sql.append(PO.getUUIDColumnName(table.getTableName())) + .append("=?"); + return DB.getSQLValueEx(null, addAccessSQL(sql.toString(), table.getTableName(), true, rw), uuid) == 1; + } else { + sql.append(table.getKeyColumns()[0]) + .append("=?"); + return DB.getSQLValueEx(null, addAccessSQL(sql.toString(), table.getTableName(), true, rw), recordId) == 1; + } + } } // MRole diff --git a/org.adempiere.base/src/org/compiere/model/MSysConfig.java b/org.adempiere.base/src/org/compiere/model/MSysConfig.java index 4b3277f1e2..ca70c9e6ed 100644 --- a/org.adempiere.base/src/org/compiere/model/MSysConfig.java +++ b/org.adempiere.base/src/org/compiere/model/MSysConfig.java @@ -30,6 +30,7 @@ import org.compiere.util.CLogger; import org.compiere.util.CacheMgt; import org.compiere.util.DB; import org.compiere.util.DisplayType; +import org.compiere.util.Msg; import org.compiere.util.Util; /** @@ -46,7 +47,7 @@ public class MSysConfig extends X_AD_SysConfig /** * */ - private static final long serialVersionUID = -4149262106340017798L; + private static final long serialVersionUID = 8636352432923806208L; /** Constant for Predefine System Configuration Names (in alphabetical order) */ @@ -66,6 +67,7 @@ public class MSysConfig extends X_AD_SysConfig public static final String APPLICATION_IMPLEMENTATION_VENDOR = "APPLICATION_IMPLEMENTATION_VENDOR"; public static final String APPLICATION_IMPLEMENTATION_VENDOR_SHOWN = "APPLICATION_IMPLEMENTATION_VENDOR_SHOWN"; public static final String APPLICATION_JVM_VERSION_SHOWN = "APPLICATION_JVM_VERSION_SHOWN"; + public static final String APPLICATION_LOGIN_LEFT_PANEL_SHOWN = "APPLICATION_LOGIN_LEFT_PANEL_SHOWN"; public static final String APPLICATION_MAIN_VERSION = "APPLICATION_MAIN_VERSION"; public static final String APPLICATION_MAIN_VERSION_SHOWN = "APPLICATION_MAIN_VERSION_SHOWN"; public static final String APPLICATION_OS_INFO_SHOWN = "APPLICATION_OS_INFO_SHOWN"; @@ -119,7 +121,10 @@ public class MSysConfig extends X_AD_SysConfig public static final String FORM_SQL_QUERY_LOG_ISSUE = "FORM_SQL_QUERY_LOG_ISSUE"; public static final String FORM_SQL_QUERY_MAX_RECORDS = "FORM_SQL_QUERY_MAX_RECORDS"; public static final String FORM_SQL_QUERY_TIMEOUT_IN_SECONDS = "FORM_SQL_QUERY_TIMEOUT_IN_SECONDS"; + public static final String GLOBAL_MAX_QUERY_RECORDS = "GLOBAL_MAX_QUERY_RECORDS"; + public static final String GLOBAL_MAX_REPORT_RECORDS = "GLOBAL_MAX_REPORT_RECORDS"; public static final String GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS = "GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS"; + public static final String GRIDTABLE_INITIAL_COUNT_TIMEOUT_IN_SECONDS = "GRIDTABLE_INITIAL_COUNT_TIMEOUT_IN_SECONDS"; public static final String HTML_REPORT_MINIFY = "HTML_REPORT_MINIFY"; public static final String HTML_REPORT_THEME = "HTML_REPORT_THEME"; public static final String IBAN_VALIDATION = "IBAN_VALIDATION"; @@ -149,7 +154,9 @@ public class MSysConfig extends X_AD_SysConfig public static final String MAIL_SEND_BCC_TO_ADDRESS = "MAIL_SEND_BCC_TO_ADDRESS"; public static final String MAIL_SEND_BCC_TO_FROM = "MAIL_SEND_BCC_TO_FROM"; public static final String MAIL_SEND_CREDENTIALS = "MAIL_SEND_CREDENTIALS"; + public static final String MAIL_SMTP_CONNECTIONTIMEOUT = "MAIL_SMTP_CONNECTIONTIMEOUT"; public static final String MAIL_SMTP_TIMEOUT = "MAIL_SMTP_TIMEOUT"; + public static final String MAIL_SMTP_WRITETIMEOUT = "MAIL_SMTP_WRITETIMEOUT"; public static final String MAX_ACTIVITIES_IN_LIST = "MAX_ACTIVITIES_IN_LIST"; public static final String MAX_RESULTS_PER_SEARCH_IN_DOCUMENT_CONTROLLER = "MAX_RESULTS_PER_SEARCH_IN_DOCUMENT_CONTROLLER"; public static final String MAX_ROWS_IN_TABLE_COMBOLIST = "MAX_ROWS_IN_TABLE_COMBOLIST"; @@ -183,6 +190,7 @@ public class MSysConfig extends X_AD_SysConfig public static final String REAL_TIME_POS = "REAL_TIME_POS"; public static final String RecentItems_MaxSaved = "RecentItems_MaxSaved"; public static final String RecentItems_MaxShown = "RecentItems_MaxShown"; + public static final String REPORT_LOAD_TIMEOUT_IN_SECONDS = "REPORT_LOAD_TIMEOUT_IN_SECONDS"; public static final String REPORT_SWAP_MAX_ROWS = "REPORT_SWAP_MAX_ROWS"; public static final String SHIPPING_DEFAULT_WEIGHT_PER_PACKAGE = "SHIPPING_DEFAULT_WEIGHT_PER_PACKAGE"; public static final String STANDARD_REPORT_FOOTER_TRADEMARK_TEXT = "STANDARD_REPORT_FOOTER_TRADEMARK_TEXT"; @@ -270,16 +278,16 @@ public class MSysConfig extends X_AD_SysConfig public static final String ZK_USE_PDF_JS_VIEWER = "ZK_USE_PDF_JS_VIEWER"; public static final String ZOOM_ACROSS_QUERY_TIMEOUT = "ZOOM_ACROSS_QUERY_TIMEOUT"; - /** - * UUID based Constructor - * @param ctx Context - * @param AD_SysConfig_UU UUID key - * @param trxName Transaction - */ - public MSysConfig(Properties ctx, String AD_SysConfig_UU, String trxName) { - super(ctx, AD_SysConfig_UU, trxName); - } - + /** + * UUID based Constructor + * @param ctx Context + * @param AD_SysConfig_UU UUID key + * @param trxName Transaction + */ + public MSysConfig(Properties ctx, String AD_SysConfig_UU, String trxName) { + super(ctx, AD_SysConfig_UU, trxName); + } + /** * Standard Constructor * @param ctx context @@ -870,13 +878,13 @@ public class MSysConfig extends X_AD_SysConfig if (getAD_Org_ID() != 0 && (configLevel.equals(MSysConfig.CONFIGURATIONLEVEL_System) || configLevel.equals(MSysConfig.CONFIGURATIONLEVEL_Client))) { - log.saveError( "Can't Save Org Level", "This is a system or tenant parameter, you can't save it as organization parameter" ); + log.saveError( "Can't Save Org Level",Msg.getMsg(p_ctx, "ThisIsSystemOrTenantParameter")); return false; } // Disallow saving client parameter if the system parameter is marked as 'S' if (getAD_Client_ID() != 0 && configLevel.equals(MSysConfig.CONFIGURATIONLEVEL_System)) { - log.saveError( "Can't Save Tenant Level", "This is a system parameter, you can't save it as tenant parameter" ); + log.saveError( "Can't Save Tenant Level",Msg.getMsg(p_ctx, "ThisIsSystemParameter")); return false; } @@ -919,4 +927,12 @@ public class MSysConfig extends X_AD_SysConfig return success; } + @Override + protected boolean afterDelete(boolean success) { + if (success && ! getName().endsWith("_NOCACHE")) { + Adempiere.getThreadPoolExecutor().submit(() -> CacheMgt.get().reset(Table_Name)); + } + return success; + } + } // MSysConfig; diff --git a/org.adempiere.base/src/org/compiere/model/MTable.java b/org.adempiere.base/src/org/compiere/model/MTable.java index d924232831..ce0b26503f 100644 --- a/org.adempiere.base/src/org/compiere/model/MTable.java +++ b/org.adempiere.base/src/org/compiere/model/MTable.java @@ -23,10 +23,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; import org.adempiere.base.IModelFactory; @@ -67,7 +67,11 @@ public class MTable extends X_AD_Table implements ImmutablePOSupport /** * */ - private static final long serialVersionUID = -167824144142429242L; + private static final long serialVersionUID = -2459194178797758731L; + + /** + * + */ public final static int MAX_OFFICIAL_ID = 999999; @@ -247,18 +251,18 @@ public class MTable extends X_AD_Table implements ImmutablePOSupport return null; } // getClass - /** - * UUID based Constructor - * @param ctx Context - * @param AD_Table_UU UUID key - * @param trxName Transaction - */ - public MTable(Properties ctx, String AD_Table_UU, String trxName) { - super(ctx, AD_Table_UU, trxName); + /** + * UUID based Constructor + * @param ctx Context + * @param AD_Table_UU UUID key + * @param trxName Transaction + */ + public MTable(Properties ctx, String AD_Table_UU, String trxName) { + super(ctx, AD_Table_UU, trxName); if (Util.isEmpty(AD_Table_UU)) setInitialDefaults(); - } - + } + /** * Standard Constructor * @param ctx context @@ -328,8 +332,8 @@ public class MTable extends X_AD_Table implements ImmutablePOSupport this(ctx, -1, trxName); copyPO(copy); this.m_columns = copy.m_columns != null ? Arrays.stream(copy.m_columns).map(e -> {return new MColumn(ctx, e, trxName);}).toArray(MColumn[]::new): null; - this.m_columnNameMap = copy.m_columnNameMap != null ? new HashMap(copy.m_columnNameMap) : null; - this.m_columnIdMap = copy.m_columnIdMap != null ? new HashMap(copy.m_columnIdMap) : null; + this.m_columnNameMap = copy.m_columnNameMap != null ? new ConcurrentHashMap(copy.m_columnNameMap) : null; + this.m_columnIdMap = copy.m_columnIdMap != null ? new ConcurrentHashMap(copy.m_columnIdMap) : null; this.m_viewComponents = copy.m_viewComponents != null ? Arrays.stream(copy.m_viewComponents).map(e -> {return new MViewComponent(ctx, e, trxName);}).toArray(MViewComponent[]::new) : null; } @@ -338,9 +342,9 @@ public class MTable extends X_AD_Table implements ImmutablePOSupport /** Key Columns */ private String[] m_KeyColumns = null; /** column name to column index map **/ - private Map m_columnNameMap; + private ConcurrentMap m_columnNameMap; /** ad_column_id to column index map **/ - private Map m_columnIdMap; + private ConcurrentMap m_columnIdMap; /** View Components */ private MViewComponent[] m_viewComponents = null; @@ -353,8 +357,8 @@ public class MTable extends X_AD_Table implements ImmutablePOSupport { if (m_columns != null && !requery) return m_columns; - m_columnNameMap = new HashMap(); - m_columnIdMap = new HashMap(); + m_columnNameMap = new ConcurrentHashMap(); + m_columnIdMap = new ConcurrentHashMap(); String sql = "SELECT * FROM AD_Column WHERE AD_Table_ID=? AND IsActive='Y' ORDER BY ColumnName"; ArrayList list = new ArrayList(); PreparedStatement pstmt = null; @@ -1098,4 +1102,41 @@ public class MTable extends X_AD_Table implements ImmutablePOSupport return indexName.toString(); } + private Boolean hasCustomTree = null; + + /** + * If the table has a custom tree defined + * @return + */ + public boolean hasCustomTree() { + if (hasCustomTree == null) { + int exists = DB.getSQLValueEx(get_TrxName(), "SELECT 1 FROM AD_Tree WHERE TreeType=? AND AD_Table_ID=? AND IsActive='Y'", MTree_Base.TREETYPE_CustomTable, getAD_Table_ID()); + hasCustomTree = Boolean.valueOf(exists == 1); + } + return hasCustomTree.booleanValue(); + } + + /** + * Get Partition Name of the table of the given level + * @param tableName + * @param primaryLevelOnly - if true, ignore the sub-partition, if exists + * @return table partition name, or empty + */ + public static String getPartitionName(Properties ctx, String tableName, boolean primaryLevelOnly, String trxName) { + if(Util.isEmpty(tableName)) + return ""; + + String[] partitionColsAll = MTablePartition.getPartitionKeyColumns(ctx, tableName, trxName); + + if(partitionColsAll.length == 0) + return tableName; + + int level = primaryLevelOnly ? 1 : partitionColsAll.length; + StringBuilder partitionName = new StringBuilder(); + partitionName.append(tableName); + for(int i = 0; i < level; i++) { + partitionName.append("_").append(partitionColsAll[i]); + } + return partitionName.toString(); + } } // MTable diff --git a/org.adempiere.base/src/org/compiere/model/MTableIndex.java b/org.adempiere.base/src/org/compiere/model/MTableIndex.java index 1cdb8579f0..f5b2367c74 100644 --- a/org.adempiere.base/src/org/compiere/model/MTableIndex.java +++ b/org.adempiere.base/src/org/compiere/model/MTableIndex.java @@ -104,7 +104,6 @@ public class MTableIndex extends X_AD_TableIndex { public MTableIndex(Properties ctx, ResultSet rs, String trxName) { super(ctx, rs, trxName); - m_ddl = createDDL(); } /** diff --git a/org.adempiere.base/src/org/compiere/model/MTablePartition.java b/org.adempiere.base/src/org/compiere/model/MTablePartition.java new file mode 100644 index 0000000000..3a1a55de52 --- /dev/null +++ b/org.adempiere.base/src/org/compiere/model/MTablePartition.java @@ -0,0 +1,164 @@ +/********************************************************************** +* This file is part of iDempiere ERP Open Source * +* http://www.idempiere.org * +* * +* Copyright (C) Contributors * +* * +* This program is free software; you can redistribute it and/or * +* modify it under the terms of the GNU General Public License * +* as published by the Free Software Foundation; either version 2 * +* of the License, or (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with this program; if not, write to the Free Software * +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * +* MA 02110-1301, USA. * +* * +* Contributors: * +* - Peter Takacs, Cloudempiere * +**********************************************************************/ +package org.compiere.model; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Properties; +import java.util.stream.Collectors; + +import org.compiere.util.DB; + +public class MTablePartition extends X_AD_TablePartition { + + /** Serial Version ID */ + private static final long serialVersionUID = 1L; + + /** + * @param ctx + * @param AD_TablePartition_ID + * @param trxName + */ + public MTablePartition(Properties ctx, int AD_TablePartition_ID, String trxName) { + super(ctx, AD_TablePartition_ID, trxName); + } + + /** + * @param ctx + * @param AD_TablePartition_ID + * @param trxName + * @param virtualColumns + */ + public MTablePartition(Properties ctx, int AD_TablePartition_ID, String trxName, String[] virtualColumns) { + super(ctx, AD_TablePartition_ID, trxName, virtualColumns); + } + + /** + * @param ctx + * @param rs + * @param trxName + */ + public MTablePartition(Properties ctx, ResultSet rs, String trxName) { + super(ctx, rs, trxName); + } + + /** + * UUID based Constructor + * @param ctx + * @param AD_TablePartition_UU + * @param trxName + */ + public MTablePartition(Properties ctx, String AD_TablePartition_UU, String trxName) { + super(ctx, AD_TablePartition_UU, trxName); + } + + /** + * UUID based Constructor + * @param ctx + * @param AD_TablePartition_UU + * @param trxName + * @param virtualColumns + */ + public MTablePartition(Properties ctx, String AD_TablePartition_UU, String trxName, String[] virtualColumns) { + super(ctx, AD_TablePartition_UU, trxName, virtualColumns); + } + + /** + * Check if the given table partition exists for the table + * @param tableName + * @param partitionName + * @return true if partition already exists + */ + public static boolean partitionExists(Properties ctx, String tableName, String partitionName, String trxName) { + return partitionExists(ctx, MTable.getTable_ID(tableName), partitionName, trxName); + } + + /** + * Check if the given table partition exists for the table + * @param tableId + * @param partitionName + * @return true if partition already exists + */ + public static boolean partitionExists(Properties ctx, int tableId, String partitionName, String trxName) { + boolean returnVal = false; + StringBuilder sqlSelect = new StringBuilder(); + sqlSelect.append("SELECT 1 FROM ") + .append(X_AD_TablePartition.Table_Name) + .append(" WHERE AD_Table_ID = ? AND UPPER(Name) LIKE UPPER(?) "); + PreparedStatement ps = null; + ResultSet rs = null; + try { + ps = DB.prepareStatement(sqlSelect.toString(), trxName); + ps.setInt(1,tableId); + ps.setString(2, partitionName); + rs = ps.executeQuery(); + if (rs.next()) { + returnVal = true; + } + } catch (SQLException e) { + e.printStackTrace(); + } finally { + DB.close(rs, ps); + rs = null; ps = null; + } + + return returnVal; + } + + /** + * Get list of partition key columns for the given table + * @param ctx + * @param tableName + * @param trxName + * @return array of column names + */ + public static String[] getPartitionKeyColumns(Properties ctx, String tableName, String trxName) { + return getPartitionKeyColumns(ctx, MTable.getTable_ID(tableName), trxName); + } + + /** + * Get list of partition key columns for the given table + * @param ctx + * @param tableId + * @param trxName + * @return array of column names + */ + public static String[] getPartitionKeyColumns(Properties ctx, int tableId, String trxName) { + String whereClause = "AD_Table_ID=? AND IsPartitionKey='Y'"; + List keyCols = new Query(ctx, MColumn.Table_Name, whereClause, trxName) + .setParameters(tableId) + .setOnlyActiveRecords(true) + .setOrderBy("SeqNoPartition ASC") + .list(); + + List keyColNames = keyCols.stream() + .map(MColumn::getColumnName) + .collect(Collectors.toList()); + + return keyColNames.toArray(new String[0]); + } +} diff --git a/org.adempiere.base/src/org/compiere/model/MUserPreference.java b/org.adempiere.base/src/org/compiere/model/MUserPreference.java index 9016f77ad6..c91e35b557 100644 --- a/org.adempiere.base/src/org/compiere/model/MUserPreference.java +++ b/org.adempiere.base/src/org/compiere/model/MUserPreference.java @@ -28,6 +28,7 @@ package org.compiere.model; import java.sql.ResultSet; import java.util.Properties; +import org.compiere.util.CacheMgt; import org.compiere.util.Env; public class MUserPreference extends X_AD_UserPreference { @@ -124,8 +125,17 @@ public class MUserPreference extends X_AD_UserPreference { @Override protected boolean afterSave(boolean newRecord, boolean success) { - if (success) + if (success) { fillPreferences(); + if (is_ValueChanged(COLUMNNAME_IsReadOnlySession)) { + // Cache reset in same thread + CacheMgt.get().reset(MRole.Table_Name); + // reset cache to re-read the ReadOnly logic + CacheMgt.get().reset(MWindow.Table_Name); + CacheMgt.get().reset(MTab.Table_Name); + CacheMgt.get().reset(MField.Table_Name); + } + } return success; } diff --git a/org.adempiere.base/src/org/compiere/model/MZoomCondition.java b/org.adempiere.base/src/org/compiere/model/MZoomCondition.java index 2e1675fcd4..4ab169c9d1 100644 --- a/org.adempiere.base/src/org/compiere/model/MZoomCondition.java +++ b/org.adempiere.base/src/org/compiere/model/MZoomCondition.java @@ -385,7 +385,7 @@ public class MZoomCondition extends X_AD_ZoomCondition implements ImmutablePOSup MTable table = MTable.get(Env.getCtx(), getAD_Table_ID()); String tableName = table.getTableName(); - StringBuilder builder = new StringBuilder("SELECT Count(*) FROM "); + StringBuilder builder = new StringBuilder("SELECT 1 FROM "); builder.append(tableName) .append(" WHERE ") .append(whereClause) diff --git a/org.adempiere.base/src/org/compiere/model/PO.java b/org.adempiere.base/src/org/compiere/model/PO.java index b674852e8a..4458fc646b 100644 --- a/org.adempiere.base/src/org/compiere/model/PO.java +++ b/org.adempiere.base/src/org/compiere/model/PO.java @@ -115,7 +115,7 @@ public abstract class PO /** * */ - private static final long serialVersionUID = -7758079724744033518L; + private static final long serialVersionUID = 3145791881535121558L; /* String key to create a new record based in UUID constructor */ public static final String UUID_NEW_RECORD = ""; @@ -1945,6 +1945,7 @@ public abstract class PO */ private void setKeyInfo() { + m_KeyColumns = null; // Search for Primary Key for (int i = 0; i < p_info.getColumnCount(); i++) { @@ -2345,6 +2346,8 @@ public abstract class PO return true; } + if (!checkReadOnlySession()) + return false; checkImmutable(); checkValidContext(); checkCrossTenant(true); @@ -2569,6 +2572,32 @@ public abstract class PO } } // save + + /** + * Tables allowed to be written in a read-only session + */ + final Set ALLOWED_TABLES_IN_RO_SESSION = new HashSet<>(Arrays.asList(new String[] { + "AD_ChangeLog", + "AD_Preference", + "AD_Session", + "AD_UserPreference", + "AD_Wlistbox_Customization" + })); + + /** + * Do not allow saving if in a read-only session, except the allowed tables + * @return + */ + private boolean checkReadOnlySession() { + if (Env.isReadOnlySession()) { + if (! ALLOWED_TABLES_IN_RO_SESSION.contains(get_TableName())) { + log.saveError("Error", Msg.getMsg(getCtx(), "ReadOnlySession") + " [" + get_TableName() + "]"); + return false; + } + } + return true; + } + /** * Update Value or create new record. * @throws AdempiereException @@ -2577,15 +2606,23 @@ public abstract class PO public void saveEx() throws AdempiereException { if (!save()) { - String msg = null; + StringBuilder msg = new StringBuilder(); ValueNamePair err = CLogger.retrieveError(); String val = err != null ? Msg.translate(getCtx(), err.getValue()) : ""; - if (err != null) - msg = (val != null ? val + ": " : "") + err.getName(); - if (msg == null || msg.length() == 0) - msg = "SaveError"; + if (err != null) { + if (val != null) { + msg.append(val); + if (val.endsWith(":")) + msg.append(" "); + else if (! val.endsWith(": ")) + msg.append(": "); + } + msg.append(err.getName()); + } + if (msg.length() == 0) + msg.append("SaveError"); Exception ex = CLogger.retrieveException(); - throw new AdempiereException(msg, ex); + throw new AdempiereException(msg.toString(), ex); } } @@ -2641,10 +2678,10 @@ public abstract class PO // table with potential tree if (get_ColumnIndex("IsSummary") >= 0) { - if (newRecord) + if (newRecord && getTable().hasCustomTree()) insert_Tree(MTree_Base.TREETYPE_CustomTable); int idxValue = get_ColumnIndex("Value"); - if (newRecord || (idxValue >= 0 && is_ValueChanged(idxValue))) + if (getTable().hasCustomTree() && (newRecord || (idxValue >= 0 && is_ValueChanged(idxValue)))) update_Tree(MTree_Base.TREETYPE_CustomTable); } } @@ -2749,8 +2786,16 @@ public abstract class PO } // saveFinish /** - * Update Value or create new record. - * To reload call load() - not updated + * Get the MTable object associated to this PO + * @return MTable + */ + private MTable getTable() { + return MTable.get(getCtx(), get_TableName()); + } + + /** + * Update or insert new record.
+ * To reload call load(). * @param trxName transaction * @return true if saved */ @@ -3038,6 +3083,8 @@ public abstract class PO { if (value instanceof Timestamp && dt == DisplayType.Date) sql.append("trunc(cast(? as date))"); + else if (dt == DisplayType.JSON) + sql.append(DB.getJSONCast()); else sql.append("?"); @@ -3063,7 +3110,7 @@ public abstract class PO } else { params.add(encrypt(i,value)); } - } + } else { params.add(value); @@ -3652,6 +3699,8 @@ public abstract class PO { if (value instanceof Timestamp && dt == DisplayType.Date) sqlValues.append("trunc(cast(? as date))"); + else if (dt == DisplayType.JSON) + sqlValues.append(DB.getJSONCast()); else sqlValues.append("?"); @@ -3915,6 +3964,8 @@ public abstract class PO if (is_new()) return true; + if (!checkReadOnlySession()) + return false; checkImmutable(); checkValidContext(); checkCrossTenant(true); @@ -4063,21 +4114,21 @@ public abstract class PO { // deleteTranslations(localTrxName); - if (get_ColumnIndex("IsSummary") >= 0) { + if (get_ColumnIndex("IsSummary") >= 0 && getTable().hasCustomTree()) { delete_Tree(MTree_Base.TREETYPE_CustomTable); } - if (m_KeyColumns != null && m_KeyColumns.length == 1) { + if (m_KeyColumns != null && m_KeyColumns.length == 1 && !getTable().isUUIDKeyTable()) { //delete cascade only for single key column record PO_Record.deleteModelCascade(p_info.getTableName(), Record_ID, localTrxName); - // Delete Cascade AD_Table_ID/Record_ID (Attachments, ..) - PO_Record.deleteRecordCascade(AD_Table_ID, Record_ID, localTrxName); + // Delete Cascade AD_Table_ID/Record_ID except Attachments/Archive (that's postponed until trx commit) + PO_Record.deleteRecordCascade(AD_Table_ID, Record_ID, "AD_Table.TableName NOT IN ('AD_Attachment','AD_Archive')", localTrxName); // Set referencing Record_ID Null AD_Table_ID/Record_ID PO_Record.setRecordNull(AD_Table_ID, Record_ID, localTrxName); } if (Record_UU != null) { PO_Record.deleteModelCascade(p_info.getTableName(), Record_UU, localTrxName); - PO_Record.deleteRecordCascade(AD_Table_ID, Record_UU, localTrxName); + PO_Record.deleteRecordCascade(AD_Table_ID, Record_UU, "AD_Table.TableName NOT IN ('AD_Attachment','AD_Archive')", localTrxName); PO_Record.setRecordNull(AD_Table_ID, Record_UU, localTrxName); } @@ -4218,9 +4269,10 @@ public abstract class PO } else { - if (CacheMgt.get().hasCache(p_info.getTableName())) { - Trx trxdel = Trx.get(get_TrxName(), false); - if (trxdel != null) { + Trx trxdel = Trx.get(get_TrxName(), false); + if (trxdel != null) { + // Schedule the reset cache for after committed the delete + if (CacheMgt.get().hasCache(p_info.getTableName())) { trxdel.addTrxEventListener(new TrxEventListener() { @Override public void afterRollback(Trx trxdel, boolean success) { @@ -4237,6 +4289,28 @@ public abstract class PO } }); } + // trigger the deletion of attachments and archives for after committed the delete + trxdel.addTrxEventListener(new TrxEventListener() { + @Override + public void afterRollback(Trx trxdel, boolean success) { + trxdel.removeTrxEventListener(this); + } + @Override + public void afterCommit(Trx trxdel, boolean success) { + if (success) { + if (m_KeyColumns != null && m_KeyColumns.length == 1 && !getTable().isUUIDKeyTable()) + // Delete Cascade AD_Table_ID/Record_ID on Attachments/Archive + // after commit because operations on external storage providers don't have rollback + PO_Record.deleteRecordCascade(AD_Table_ID, Record_ID, "AD_Table.TableName IN ('AD_Attachment','AD_Archive')", null); + if (Record_UU != null) + PO_Record.deleteRecordCascade(AD_Table_ID, Record_UU, "AD_Table.TableName IN ('AD_Attachment','AD_Archive')", null); + } + trxdel.removeTrxEventListener(this); + } + @Override + public void afterClose(Trx trxdel) { + } + }); } if (localTrx != null) { diff --git a/org.adempiere.base/src/org/compiere/model/POInfo.java b/org.adempiere.base/src/org/compiere/model/POInfo.java index ce09001f51..4e4e559351 100644 --- a/org.adempiere.base/src/org/compiere/model/POInfo.java +++ b/org.adempiere.base/src/org/compiere/model/POInfo.java @@ -584,6 +584,7 @@ public class POInfo implements Serializable } catch (Exception e) { + CLogger.get().log(Level.WARNING, "Cannot create Lookup for " + m_columns[index].ColumnName + "[" + m_columns[index].AD_Column_ID + "]", e); lookup = null; // cannot create Lookup } return lookup; diff --git a/org.adempiere.base/src/org/compiere/model/PO_LOB.java b/org.adempiere.base/src/org/compiere/model/PO_LOB.java index a42085939d..200715ce25 100644 --- a/org.adempiere.base/src/org/compiere/model/PO_LOB.java +++ b/org.adempiere.base/src/org/compiere/model/PO_LOB.java @@ -154,7 +154,7 @@ public class PO_LOB implements Serializable try { pstmt = con.prepareStatement(sql.toString()); - if (m_displayType == DisplayType.TextLong) + if (m_displayType == DisplayType.TextLong || m_displayType == DisplayType.JSON) pstmt.setString(1, (String)m_value); else pstmt.setBytes(1, (byte[])m_value); diff --git a/org.adempiere.base/src/org/compiere/model/PO_Record.java b/org.adempiere.base/src/org/compiere/model/PO_Record.java index d26daaa888..0e65126c6a 100644 --- a/org.adempiere.base/src/org/compiere/model/PO_Record.java +++ b/org.adempiere.base/src/org/compiere/model/PO_Record.java @@ -28,6 +28,7 @@ import org.compiere.util.DisplayType; import org.compiere.util.Env; import org.compiere.util.KeyNamePair; import org.compiere.util.Msg; +import org.compiere.util.Util; /** * Maintain AD_Table_ID/Record_ID constraint @@ -47,10 +48,11 @@ public class PO_Record * Delete Cascade including (selected)parent relationships * @param AD_Table_ID table * @param Record_IDorUU record ID (int) or UUID (String) + * @param whereTables filter for the Tables * @param trxName transaction * @return false if could not be deleted */ - protected static boolean deleteRecordCascade (int AD_Table_ID, Serializable Record_IDorUU, String trxName) + protected static boolean deleteRecordCascade (int AD_Table_ID, Serializable Record_IDorUU, String whereTables, String trxName) { int refId; String columnName; @@ -63,7 +65,7 @@ public class PO_Record } else { throw new IllegalArgumentException(Record_IDorUU.getClass().getName() + " not supported for ID/UUID"); } - KeyNamePair[] cascades = getTablesWithConstraintType(refId, MColumn.FKCONSTRAINTTYPE_ModelCascade, trxName); + KeyNamePair[] cascades = getTablesWithConstraintType(refId, MColumn.FKCONSTRAINTTYPE_ModelCascade, whereTables, trxName); // Table Loop StringBuilder whereClause = new StringBuilder("AD_Table_ID=? AND ").append(columnName).append("=?"); for (KeyNamePair table : cascades) @@ -146,21 +148,21 @@ public class PO_Record KeyNamePair[] tables = s_po_record_tables_cache.get(key.toString()); if (tables != null) return tables; - final String sql = "" - + "SELECT t.AD_Table_ID, " - + " c.ColumnName " - + "FROM AD_Column c " - + " JOIN AD_Table t ON c.AD_Table_ID = t.AD_Table_ID " - + " LEFT JOIN AD_Ref_Table r ON c.AD_Reference_Value_ID = r.AD_Reference_ID " - + " LEFT JOIN AD_Table tr ON r.AD_Table_ID = tr.AD_Table_ID " - + "WHERE t.IsView = 'N' " - + " AND t.IsActive = 'Y' " - + " AND c.IsActive = 'Y' " - + " AND ( ( c.AD_Reference_ID = ? " - + " AND c.ColumnName = ? || '_ID' ) " - + " OR ( c.AD_Reference_ID IN (? , ?) " - + " AND ( tr.TableName = ? OR ( tr.TableName IS NULL AND c.ColumnName = ? || '_ID' ) ) ) ) " - + " AND c.FKConstraintType = ?"; + final String sql = """ + SELECT t.AD_Table_ID, + c.ColumnName + FROM AD_Column c + JOIN AD_Table t ON c.AD_Table_ID = t.AD_Table_ID + LEFT JOIN AD_Ref_Table r ON c.AD_Reference_Value_ID = r.AD_Reference_ID + LEFT JOIN AD_Table tr ON r.AD_Table_ID = tr.AD_Table_ID + WHERE t.IsView = 'N' + AND t.IsActive = 'Y' + AND c.IsActive = 'Y' + AND ( ( c.AD_Reference_ID = ? + AND c.ColumnName = ? || '_ID' ) + OR ( c.AD_Reference_ID IN (? , ?) + AND ( tr.TableName = ? OR ( tr.TableName IS NULL AND c.ColumnName = ? || '_ID' ) ) ) ) + AND c.FKConstraintType = ?"""; List> dependents = DB.getSQLArrayObjectsEx(trxName, sql, refTableDirId, tableName, refTableId, refTableSearchId, tableName, tableName, MColumn.FKCONSTRAINTTYPE_ModelCascade); if (dependents != null) { @@ -275,6 +277,18 @@ public class PO_Record * @return array of KeyNamePair */ private static KeyNamePair[] getTablesWithConstraintType(int refId, String constraintType, String trxName) { + return getTablesWithConstraintType(refId, constraintType, null, trxName); + } + + /** + * Get array of tables which has a refId column with the defined Constraint Type + * @param refId AD_Reference_ID - Record_ID or Record_UU + * @param constraintType - FKConstraintType of AD_Column + * @param whereTables - optional filter + * @param trxName + * @return array of KeyNamePair + */ + private static KeyNamePair[] getTablesWithConstraintType(int refId, String constraintType, String whereTables, String trxName) { String columnName; if (refId == DisplayType.RecordID) { columnName = "Record_ID"; @@ -284,11 +298,14 @@ public class PO_Record log.warning(refId + " not supported for ID/UUID"); return null; } - StringBuilder key = new StringBuilder(constraintType).append("|").append(refId); + StringBuilder key = new StringBuilder(constraintType).append("|").append(refId).append("|").append(whereTables); KeyNamePair[] tables = s_po_record_tables_cache.get(key.toString()); if (tables != null) return tables; - List listTables = new Query(Env.getCtx(), MTable.Table_Name, "c.AD_Reference_ID=? AND c.FKConstraintType=? AND AD_Table.IsView='N' AND c.ColumnName=?", trxName) + String whereClause = "c.AD_Reference_ID=? AND c.FKConstraintType=? AND AD_Table.IsView='N' AND c.ColumnName=?"; + if (! Util.isEmpty(whereTables)) + whereClause = whereClause + " AND (" + whereTables + ")"; + List listTables = new Query(Env.getCtx(), MTable.Table_Name, whereClause, trxName) .addJoinClause("JOIN AD_Column c ON (c.AD_Table_ID=AD_Table.AD_Table_ID)") .setOnlyActiveRecords(true) .setParameters(refId, constraintType, columnName) diff --git a/org.adempiere.base/src/org/compiere/model/SystemIDs.java b/org.adempiere.base/src/org/compiere/model/SystemIDs.java index e4270ae248..9a044064f3 100644 --- a/org.adempiere.base/src/org/compiere/model/SystemIDs.java +++ b/org.adempiere.base/src/org/compiere/model/SystemIDs.java @@ -164,6 +164,7 @@ public class SystemIDs public final static int REFERENCE_DATATYPE_TABLEDIR_UU = 200234; public final static int REFERENCE_DATATYPE_TEXT = 14; public final static int REFERENCE_DATATYPE_TEXTLONG = 36; + public final static int REFERENCE_DATATYPE_JSON = 200267; public final static int REFERENCE_DATATYPE_TIME = 24; public final static int REFERENCE_DATATYPE_TIMESTAMP_WITH_TIMEZONE = 200133; public final static int REFERENCE_DATATYPE_TIMEZONE = 200135; @@ -219,6 +220,7 @@ public class SystemIDs public final static int WINDOW_LOT = 257; public final static int WINDOW_MATERIAL_RECEIPT = 184; public final static int WINDOW_MATERIALTRANSACTIONS_INDIRECTUSER = 223; + public final static int WINDOW_MENU = 105; public final static int WINDOW_MY_REQUESTS = 237; public final static int WINDOW_NOTICE = 193; public final static int WINDOW_PAYMENTS_INTO_BATCH = 200031; @@ -228,6 +230,7 @@ public class SystemIDs public final static int WINDOW_RETURNTOVENDOR = 53098; public final static int WINDOW_SALES_ORDER = 143; public final static int WINDOW_SHIPMENT_CUSTOMER = 169; + public final static int WINDOW_USER_PREFERENCE = 200073; public final static int WINDOW_VENDOR_RMA = 53099; public final static int WINDOW_WAREHOUSE_LOCATOR = 139; public final static int WINDOW_WINDOW_TAB_FIELD = 102; diff --git a/org.adempiere.base/src/org/compiere/model/SystemProperties.java b/org.adempiere.base/src/org/compiere/model/SystemProperties.java index d9d368d27f..9920d40e7d 100644 --- a/org.adempiere.base/src/org/compiere/model/SystemProperties.java +++ b/org.adempiere.base/src/org/compiere/model/SystemProperties.java @@ -47,14 +47,17 @@ public class SystemProperties { private static final String org_adempiere_po_useTimeoutForUpdate = "org.adempiere.po.useTimeoutForUpdate"; private static final String org_compiere_report_path = "org.compiere.report.path"; private static final String org_idempiere_db_debug = "org.idempiere.db.debug"; + private static final String org_idempiere_db_debug_convert = "org.idempiere.db.debug.convert"; private static final String org_idempiere_db_debug_filter = "org.idempiere.db.debug.filter"; private static final String org_idempiere_FileLogPrefix = "org.idempiere.FileLogPrefix"; + private static final String org_idempiere_FullExceptionTraceInLog = "org.idempiere.FullExceptionTraceInLog"; private static final String org_idempiere_postgresql_URLParameters = "org.idempiere.postgresql.URLParameters"; private static final String org_idempiere_po_useOptimisticLocking = "org.idempiere.po.useOptimisticLocking"; private static final String PostgreSQLNative = "PostgreSQLNative"; private static final String PropertyFile = "PropertyFile"; private static final String PropertyHomeFile = "PropertyHomeFile"; private static final String TestOCI = "TestOCI"; + private static final String TRACE_NULL_TRX_CONNECTION = "TRACE_NULL_TRX_CONNECTION"; private static final String ZK_THEME = MSysConfig.ZK_THEME; private static final String ZkUnitTest = "ZkUnitTest"; @@ -177,6 +180,14 @@ public class SystemProperties { return System.getProperty(org_idempiere_db_debug_filter); } + /** + * org.idempiere.db.convert=true to print also Oracle SQL Statements being converted + * @return + */ + public static boolean isDBDebugConvert() { + return "true".equals(System.getProperty(org_idempiere_db_debug_convert)); + } + /** * org.idempiere.FileLogPrefix defines the template prefix to write logs * @return @@ -258,4 +269,21 @@ public class SystemProperties { return "true".equals(System.getProperty(ZkUnitTest)); } + /** + * TRACE_NULL_TRX_CONNECTION=true to allow tracing null transactions on idempiereMonitor + * WARNING! this setting can have a big performance impact, it is disabled by default + * use it with care in production just temporarily to trace problematic connection slowness or leaks + * @return + */ + public static boolean isTraceNullTrxConnection() { + return "true".equals(System.getProperty(TRACE_NULL_TRX_CONNECTION)); + } + + /** + * org_idempiere_FullExceptionTraceInLog=true to not cut trace log + * @return + */ + public static boolean isFullExceptionTraceInLog() { + return "true".equals(System.getProperty(org_idempiere_FullExceptionTraceInLog)); + } } diff --git a/org.adempiere.base/src/org/compiere/model/Tax.java b/org.adempiere.base/src/org/compiere/model/Tax.java index 77127e2d87..876b0412aa 100644 --- a/org.adempiere.base/src/org/compiere/model/Tax.java +++ b/org.adempiere.base/src/org/compiere/model/Tax.java @@ -68,7 +68,7 @@ public class Tax int AD_Org_ID, int M_Warehouse_ID, int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, boolean IsSOTrx) { - return get(ctx, M_Product_ID, C_Charge_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, billC_BPartner_Location_ID, shipC_BPartner_Location_ID, IsSOTrx, null); + return get(ctx, M_Product_ID, C_Charge_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, billC_BPartner_Location_ID, shipC_BPartner_Location_ID, -1, IsSOTrx, null); } @@ -93,6 +93,7 @@ public class Tax * @param M_Warehouse_ID warehouse (ignored) * @param billC_BPartner_Location_ID invoice location * @param shipC_BPartner_Location_ID ship location (ignored) + * @param dropshipC_BPartner_Location_ID ship location (ignored) * @param IsSOTrx is a sales trx * @param trxName * @return C_Tax_ID @@ -101,11 +102,11 @@ public class Tax public static int get (Properties ctx, int M_Product_ID, int C_Charge_ID, Timestamp billDate, Timestamp shipDate, int AD_Org_ID, int M_Warehouse_ID, - int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, + int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, int dropshipC_BPartner_Location_ID, boolean IsSOTrx, String trxName) { return get(ctx, M_Product_ID, C_Charge_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, - billC_BPartner_Location_ID, shipC_BPartner_Location_ID, IsSOTrx, null, trxName); + billC_BPartner_Location_ID, shipC_BPartner_Location_ID, dropshipC_BPartner_Location_ID, IsSOTrx, null, trxName); } /************************************************************************** @@ -135,18 +136,59 @@ public class Tax * @return C_Tax_ID * @throws TaxCriteriaNotFoundException if a criteria was not found */ + public static int get (Properties ctx, int M_Product_ID, int C_Charge_ID, + Timestamp billDate, Timestamp shipDate, + int AD_Org_ID, int M_Warehouse_ID, + int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, + boolean IsSOTrx, String deliveryViaRule, String trxName) + { + return get(ctx, M_Product_ID, C_Charge_ID, + billDate, shipDate, + AD_Org_ID, M_Warehouse_ID, + billC_BPartner_Location_ID, shipC_BPartner_Location_ID, -1, + IsSOTrx, deliveryViaRule, trxName); + } + + /************************************************************************** + * Get Tax ID - converts parameters to call Get Tax. + *
{@code
+	 *		M_Product_ID/C_Charge_ID	->	C_TaxCategory_ID
+	 *		billDate, shipDate			->	billDate, shipDate
+	 *		AD_Org_ID					->	billFromC_Location_ID
+	 *		M_Warehouse_ID				->	shipFromC_Location_ID
+	 *		billC_BPartner_Location_ID  ->	billToC_Location_ID
+	 *		shipC_BPartner_Location_ID 	->	shipToC_Location_ID
+	 *
+	 *  if IsSOTrx is false, bill and ship are reversed
+	 *  }
+ * @param ctx context + * @param M_Product_ID product + * @param C_Charge_ID product + * @param billDate invoice date + * @param shipDate ship date (ignored) + * @param AD_Org_ID org + * @param M_Warehouse_ID warehouse (ignored) + * @param billC_BPartner_Location_ID invoice location + * @param shipC_BPartner_Location_ID ship location (ignored) + * @param dropshipC_BPartner_Location_ID dropship location + * @param IsSOTrx is a sales trx + * @param deliveryViaRule if Delivery Via Rule is PickUp, use Warehouse Location instead of Billing Location as Tax Location to + * @param trxName + * @return C_Tax_ID + * @throws TaxCriteriaNotFoundException if a criteria was not found + */ public static int get (Properties ctx, int M_Product_ID, int C_Charge_ID, Timestamp billDate, Timestamp shipDate, int AD_Org_ID, int M_Warehouse_ID, - int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, + int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, int dropshipC_BPartner_Location_ID, boolean IsSOTrx, String deliveryViaRule, String trxName) { if (M_Product_ID != 0) return getProduct (ctx, M_Product_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, - billC_BPartner_Location_ID, shipC_BPartner_Location_ID, IsSOTrx, deliveryViaRule, trxName); + billC_BPartner_Location_ID, shipC_BPartner_Location_ID, dropshipC_BPartner_Location_ID, IsSOTrx, deliveryViaRule, trxName); else if (C_Charge_ID != 0) return getCharge (ctx, C_Charge_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, - billC_BPartner_Location_ID, shipC_BPartner_Location_ID, IsSOTrx, deliveryViaRule, trxName); + billC_BPartner_Location_ID, shipC_BPartner_Location_ID, dropshipC_BPartner_Location_ID, IsSOTrx, deliveryViaRule, trxName); else return getExemptTax (ctx, AD_Org_ID, trxName); } // get @@ -207,7 +249,7 @@ public class Tax boolean IsSOTrx, String trxName) { return getCharge(ctx, C_Charge_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, - billC_BPartner_Location_ID, shipC_BPartner_Location_ID, IsSOTrx, null, trxName); + billC_BPartner_Location_ID, shipC_BPartner_Location_ID, -1, IsSOTrx, null, trxName); } /** @@ -231,6 +273,7 @@ public class Tax * @param M_Warehouse_ID warehouse (ignored) * @param billC_BPartner_Location_ID invoice location * @param shipC_BPartner_Location_ID ship location (ignored) + * @param dropshipC_BPartner_Location_ID * @param IsSOTrx is a sales trx * @param deliveryViaRule if Delivery Via Rule is PickUp, use Warehouse Location instead of Billing Location as Tax Location to * @param trxName @@ -241,12 +284,13 @@ public class Tax public static int getCharge (Properties ctx, int C_Charge_ID, Timestamp billDate, Timestamp shipDate, int AD_Org_ID, int M_Warehouse_ID, - int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, + int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, int dropshipC_BPartner_Location_ID, boolean IsSOTrx, String deliveryViaRule, String trxName) { int C_TaxCategory_ID = 0; int shipFromC_Location_ID = 0; int shipToC_Location_ID = 0; + int dropshipC_Location_ID = 0; int billFromC_Location_ID = 0; int billToC_Location_ID = 0; int warehouseC_Location_ID = 0; @@ -256,24 +300,26 @@ public class Tax // Get all at once String sql = "SELECT c.C_TaxCategory_ID, o.C_Location_ID, il.C_Location_ID, b.IsTaxExempt, b.IsPOTaxExempt," - + " w.C_Location_ID, sl.C_Location_ID " - + "FROM C_Charge c, AD_OrgInfo o," - + " C_BPartner_Location il INNER JOIN C_BPartner b ON (il.C_BPartner_ID=b.C_BPartner_ID) " - + " LEFT OUTER JOIN M_Warehouse w ON (w.M_Warehouse_ID=?), C_BPartner_Location sl " - + "WHERE c.C_Charge_ID=?" - + " AND o.AD_Org_ID=?" - + " AND il.C_BPartner_Location_ID=?" - + " AND sl.C_BPartner_Location_ID=?"; + + " w.C_Location_ID, sl.C_Location_ID, dsl.C_Location_ID " + + "FROM C_Charge c" + + " JOIN AD_OrgInfo o ON (o.AD_Org_ID=?)" + + " JOIN C_BPartner_Location il ON (il.C_BPartner_Location_ID=?)" + + " INNER JOIN C_BPartner b ON (il.C_BPartner_ID=b.C_BPartner_ID) " + + " LEFT OUTER JOIN M_Warehouse w ON (w.M_Warehouse_ID=?)" + + " JOIN C_BPartner_Location sl ON (sl.C_BPartner_Location_ID=?)" + + " LEFT JOIN C_BPartner_Location dsl ON (dsl.C_BPartner_Location_ID=?)" + + "WHERE c.C_Charge_ID=?"; PreparedStatement pstmt = null; ResultSet rs = null; try { pstmt = DB.prepareStatement (sql, trxName); - pstmt.setInt (1, M_Warehouse_ID); - pstmt.setInt (2, C_Charge_ID); - pstmt.setInt (3, AD_Org_ID); - pstmt.setInt (4, billC_BPartner_Location_ID); - pstmt.setInt (5, shipC_BPartner_Location_ID); + pstmt.setInt (1, AD_Org_ID); + pstmt.setInt (2, billC_BPartner_Location_ID); + pstmt.setInt (3, M_Warehouse_ID); + pstmt.setInt (4, shipC_BPartner_Location_ID); + pstmt.setInt (5, dropshipC_BPartner_Location_ID); + pstmt.setInt (6, C_Charge_ID); rs = pstmt.executeQuery (); boolean found = false; if (rs.next ()) @@ -286,6 +332,7 @@ public class Tax IsTaxExempt = IsSOTrx ? IsSOTaxExempt : IsPOTaxExempt; shipFromC_Location_ID = rs.getInt (6); shipToC_Location_ID = rs.getInt (7); + dropshipC_Location_ID = rs.getInt (8); warehouseC_Location_ID = rs.getInt(6); found = true; } @@ -331,9 +378,10 @@ public class Tax + ", billFromC_Location_ID=" + billFromC_Location_ID + ", billToC_Location_ID=" + billToC_Location_ID + ", shipFromC_Location_ID=" + shipFromC_Location_ID - + ", shipToC_Location_ID=" + shipToC_Location_ID); + + ", shipToC_Location_ID=" + shipToC_Location_ID + + ", dropshipC_Location_ID=" + dropshipC_Location_ID); return Core.getTaxLookup().get (ctx, C_TaxCategory_ID, IsSOTrx, - shipDate, shipFromC_Location_ID, shipToC_Location_ID, + shipDate, shipFromC_Location_ID, shipToC_Location_ID, dropshipC_Location_ID, billDate, billFromC_Location_ID, billToC_Location_ID, trxName); } // getCharge @@ -393,7 +441,7 @@ public class Tax boolean IsSOTrx, String trxName) { return getProduct(ctx, M_Product_ID, billDate, shipDate, AD_Org_ID, M_Warehouse_ID, - billC_BPartner_Location_ID, shipC_BPartner_Location_ID, IsSOTrx, null, trxName); + billC_BPartner_Location_ID, shipC_BPartner_Location_ID, -1, IsSOTrx, null, trxName); } /** @@ -417,6 +465,7 @@ public class Tax * @param M_Warehouse_ID warehouse (ignored) * @param billC_BPartner_Location_ID invoice location * @param shipC_BPartner_Location_ID ship location (ignored) + * @param dropshipC_BPartner_Location_ID * @param IsSOTrx is a sales trx * @param deliveryViaRule if Delivery Via Rule is PickUp, use Warehouse Location instead of Billing Location as Tax Location to * @param trxName @@ -426,7 +475,7 @@ public class Tax public static int getProduct (Properties ctx, int M_Product_ID, Timestamp billDate, Timestamp shipDate, int AD_Org_ID, int M_Warehouse_ID, - int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, + int billC_BPartner_Location_ID, int shipC_BPartner_Location_ID, int dropshipC_BPartner_Location_ID, boolean IsSOTrx, String deliveryViaRule, String trxName) { String variable = ""; @@ -436,6 +485,7 @@ public class Tax int billFromC_Location_ID = 0; int billToC_Location_ID = 0; int warehouseC_Location_ID = 0; + int dropshipC_Location_ID = 0; String IsTaxExempt = null; String IsSOTaxExempt = null; String IsPOTaxExempt = null; @@ -447,20 +497,22 @@ public class Tax { // Get all at once sql = "SELECT p.C_TaxCategory_ID, o.C_Location_ID, il.C_Location_ID, b.IsTaxExempt, b.IsPOTaxExempt, " - + " w.C_Location_ID, sl.C_Location_ID " - + "FROM M_Product p, AD_OrgInfo o," - + " C_BPartner_Location il INNER JOIN C_BPartner b ON (il.C_BPartner_ID=b.C_BPartner_ID) " - + " LEFT OUTER JOIN M_Warehouse w ON (w.M_Warehouse_ID=?), C_BPartner_Location sl " - + "WHERE p.M_Product_ID=?" - + " AND o.AD_Org_ID=?" - + " AND il.C_BPartner_Location_ID=?" - + " AND sl.C_BPartner_Location_ID=?"; + + " w.C_Location_ID, sl.C_Location_ID, dsl.C_Location_ID " + + "FROM M_Product p" + + " JOIN AD_OrgInfo o ON (o.AD_Org_ID=?)" + + " JOIN C_BPartner_Location il ON (il.C_BPartner_Location_ID=?)" + + " INNER JOIN C_BPartner b ON (il.C_BPartner_ID=b.C_BPartner_ID)" + + " LEFT OUTER JOIN M_Warehouse w ON (w.M_Warehouse_ID=?)" + + " JOIN C_BPartner_Location sl ON (sl.C_BPartner_Location_ID=?)" + + " LEFT JOIN C_BPartner_Location dsl ON (dsl.C_BPartner_Location_ID=?) " + + "WHERE p.M_Product_ID=?"; pstmt = DB.prepareStatement(sql, trxName); - pstmt.setInt(1, M_Warehouse_ID); - pstmt.setInt(2, M_Product_ID); - pstmt.setInt(3, AD_Org_ID); - pstmt.setInt(4, billC_BPartner_Location_ID); - pstmt.setInt(5, shipC_BPartner_Location_ID); + pstmt.setInt(1, AD_Org_ID); + pstmt.setInt(2, billC_BPartner_Location_ID); + pstmt.setInt(3, M_Warehouse_ID); + pstmt.setInt(4, shipC_BPartner_Location_ID); + pstmt.setInt(5, dropshipC_BPartner_Location_ID); + pstmt.setInt(6, M_Product_ID); rs = pstmt.executeQuery(); boolean found = false; if (rs.next()) @@ -473,6 +525,7 @@ public class Tax IsTaxExempt = IsSOTrx ? IsSOTaxExempt : IsPOTaxExempt; shipFromC_Location_ID = rs.getInt(6); shipToC_Location_ID = rs.getInt(7); + dropshipC_Location_ID = rs.getInt(8); warehouseC_Location_ID = rs.getInt(6); found = true; } @@ -502,9 +555,10 @@ public class Tax + ", billFromC_Location_ID=" + billFromC_Location_ID + ", billToC_Location_ID=" + billToC_Location_ID + ", shipFromC_Location_ID=" + shipFromC_Location_ID - + ", shipToC_Location_ID=" + shipToC_Location_ID); + + ", shipToC_Location_ID=" + shipToC_Location_ID + + ", dropshipC_Location_ID=" + dropshipC_Location_ID); return Core.getTaxLookup().get(ctx, C_TaxCategory_ID, IsSOTrx, - shipDate, shipFromC_Location_ID, shipToC_Location_ID, + shipDate, shipFromC_Location_ID, shipToC_Location_ID, dropshipC_Location_ID, billDate, billFromC_Location_ID, billToC_Location_ID, trxName); } @@ -665,6 +719,33 @@ public class Tax int C_TaxCategory_ID, boolean IsSOTrx, Timestamp shipDate, int shipFromC_Location_ID, int shipToC_Location_ID, Timestamp billDate, int billFromC_Location_ID, int billToC_Location_ID, String trxName) + { + return get (ctx, + C_TaxCategory_ID, IsSOTrx, + shipDate, shipFromC_Location_ID, shipToC_Location_ID, -1, + billDate, billFromC_Location_ID, billToC_Location_ID, trxName); + } + + /************************************************************************** + * Get Tax ID (Detail). + * @param ctx context + * @param C_TaxCategory_ID tax category + * @param IsSOTrx Sales Order Trx + * @param shipDate ship date (ignored) + * @param shipFromC_Location_ID ship from (ignored) + * @param shipToC_Location_ID ship to (ignored) + * @param dropshipC_Location_ID + * @param billDate invoice date + * @param billFromC_Location_ID invoice from (Tax Location from) + * @param billToC_Location_ID invoice to (Tax Location to) + * @param trxName Transaction + * @return C_Tax_ID + * @throws TaxNotFoundException if no tax found for given criteria + */ + public static int get (Properties ctx, + int C_TaxCategory_ID, boolean IsSOTrx, + Timestamp shipDate, int shipFromC_Location_ID, int shipToC_Location_ID, int dropshipC_Location_ID, + Timestamp billDate, int billFromC_Location_ID, int billToC_Location_ID, String trxName) { // C_TaxCategory contains CommodityCode diff --git a/org.adempiere.base/src/org/compiere/model/X_AD_ImportTemplate.java b/org.adempiere.base/src/org/compiere/model/X_AD_ImportTemplate.java index 5cc228ef54..f114b05a72 100644 --- a/org.adempiere.base/src/org/compiere/model/X_AD_ImportTemplate.java +++ b/org.adempiere.base/src/org/compiere/model/X_AD_ImportTemplate.java @@ -23,7 +23,7 @@ import org.compiere.util.KeyNamePair; /** Generated Model for AD_ImportTemplate * @author iDempiere (generated) - * @version Release 11 - $Id$ */ + * @version Release 12 - $Id$ */ @org.adempiere.base.Model(table="AD_ImportTemplate") public class X_AD_ImportTemplate extends PO implements I_AD_ImportTemplate, I_Persistent { @@ -31,7 +31,7 @@ public class X_AD_ImportTemplate extends PO implements I_AD_ImportTemplate, I_Pe /** * */ - private static final long serialVersionUID = 20240327L; + private static final long serialVersionUID = 20240513L; /** Standard Constructor */ public X_AD_ImportTemplate (Properties ctx, int AD_ImportTemplate_ID, String trxName) @@ -300,12 +300,12 @@ public class X_AD_ImportTemplate extends PO implements I_AD_ImportTemplate, I_Pe /** ImportTemplateType AD_Reference_ID=200268 */ public static final int IMPORTTEMPLATETYPE_AD_Reference_ID=200268; - /** CSV = CSV */ - public static final String IMPORTTEMPLATETYPE_CSV = "CSV"; + /** Comma-separated values (CSV) = CSV */ + public static final String IMPORTTEMPLATETYPE_Comma_SeparatedValuesCSV = "CSV"; /** XLS = XLS */ public static final String IMPORTTEMPLATETYPE_XLS = "XLS"; - /** XLSX = XLSX */ - public static final String IMPORTTEMPLATETYPE_XLSX = "XLSX"; + /** Excel (XLS/XLSX) = XLSX */ + public static final String IMPORTTEMPLATETYPE_ExcelXLSXLSX = "XLSX"; /** Set Import Template Type. @param ImportTemplateType Import Template Type */ diff --git a/org.adempiere.base/src/org/compiere/model/X_AD_PInstance.java b/org.adempiere.base/src/org/compiere/model/X_AD_PInstance.java index a307f366fd..f088bcea72 100644 --- a/org.adempiere.base/src/org/compiere/model/X_AD_PInstance.java +++ b/org.adempiere.base/src/org/compiere/model/X_AD_PInstance.java @@ -415,6 +415,22 @@ public class X_AD_PInstance extends PO implements I_AD_PInstance, I_Persistent return false; } + /** Set JSON Data. + @param JsonData The json field stores json data. + */ + public void setJsonData (String JsonData) + { + set_Value (COLUMNNAME_JsonData, JsonData); + } + + /** Get JSON Data. + @return The json field stores json data. + */ + public String getJsonData() + { + return (String)get_Value(COLUMNNAME_JsonData); + } + /** Set Name. @param Name Alphanumeric identifier of the entity */ diff --git a/org.adempiere.base/src/org/compiere/model/X_AD_PInstance_Log.java b/org.adempiere.base/src/org/compiere/model/X_AD_PInstance_Log.java index 1d1dc3953f..ffe7c28fdc 100644 --- a/org.adempiere.base/src/org/compiere/model/X_AD_PInstance_Log.java +++ b/org.adempiere.base/src/org/compiere/model/X_AD_PInstance_Log.java @@ -178,6 +178,22 @@ public class X_AD_PInstance_Log extends PO implements I_AD_PInstance_Log, I_Pers return ii.intValue(); } + /** Set JSON Data. + @param JsonData The json field stores json data. + */ + public void setJsonData (String JsonData) + { + set_Value (COLUMNNAME_JsonData, JsonData); + } + + /** Get JSON Data. + @return The json field stores json data. + */ + public String getJsonData() + { + return (String)get_Value(COLUMNNAME_JsonData); + } + /** Set Log. @param Log_ID Log */ diff --git a/org.adempiere.base/src/org/compiere/model/X_AD_UserPreference.java b/org.adempiere.base/src/org/compiere/model/X_AD_UserPreference.java index bc8de4bc09..fb4babba61 100644 --- a/org.adempiere.base/src/org/compiere/model/X_AD_UserPreference.java +++ b/org.adempiere.base/src/org/compiere/model/X_AD_UserPreference.java @@ -30,7 +30,7 @@ public class X_AD_UserPreference extends PO implements I_AD_UserPreference, I_Pe /** * */ - private static final long serialVersionUID = 20231222L; + private static final long serialVersionUID = 20240719L; /** Standard Constructor */ public X_AD_UserPreference (Properties ctx, int AD_UserPreference_ID, String trxName) @@ -38,8 +38,10 @@ public class X_AD_UserPreference extends PO implements I_AD_UserPreference, I_Pe super (ctx, AD_UserPreference_ID, trxName); /** if (AD_UserPreference_ID == 0) { - setAD_User_ID (0); setAD_UserPreference_ID (0); + setAD_User_ID (0); + setIsReadOnlySession (false); +// N setViewFindResult (null); // 0 } */ @@ -51,8 +53,10 @@ public class X_AD_UserPreference extends PO implements I_AD_UserPreference, I_Pe super (ctx, AD_UserPreference_ID, trxName, virtualColumns); /** if (AD_UserPreference_ID == 0) { - setAD_User_ID (0); setAD_UserPreference_ID (0); + setAD_User_ID (0); + setIsReadOnlySession (false); +// N setViewFindResult (null); // 0 } */ @@ -64,8 +68,10 @@ public class X_AD_UserPreference extends PO implements I_AD_UserPreference, I_Pe super (ctx, AD_UserPreference_UU, trxName); /** if (AD_UserPreference_UU == null) { - setAD_User_ID (0); setAD_UserPreference_ID (0); + setAD_User_ID (0); + setIsReadOnlySession (false); +// N setViewFindResult (null); // 0 } */ @@ -77,8 +83,10 @@ public class X_AD_UserPreference extends PO implements I_AD_UserPreference, I_Pe super (ctx, AD_UserPreference_UU, trxName, virtualColumns); /** if (AD_UserPreference_UU == null) { - setAD_User_ID (0); setAD_UserPreference_ID (0); + setAD_User_ID (0); + setIsReadOnlySession (false); +// N setViewFindResult (null); // 0 } */ @@ -112,34 +120,6 @@ public class X_AD_UserPreference extends PO implements I_AD_UserPreference, I_Pe return sb.toString(); } - public org.compiere.model.I_AD_User getAD_User() throws RuntimeException - { - return (org.compiere.model.I_AD_User)MTable.get(getCtx(), org.compiere.model.I_AD_User.Table_ID) - .getPO(getAD_User_ID(), get_TrxName()); - } - - /** Set User/Contact. - @param AD_User_ID User within the system - Internal or Business Partner Contact - */ - public void setAD_User_ID (int AD_User_ID) - { - if (AD_User_ID < 1) - set_ValueNoCheck (COLUMNNAME_AD_User_ID, null); - else - set_ValueNoCheck (COLUMNNAME_AD_User_ID, Integer.valueOf(AD_User_ID)); - } - - /** Get User/Contact. - @return User within the system - Internal or Business Partner Contact - */ - public int getAD_User_ID() - { - Integer ii = (Integer)get_Value(COLUMNNAME_AD_User_ID); - if (ii == null) - return 0; - return ii.intValue(); - } - /** Set AD_UserPreference_ID. @param AD_UserPreference_ID AD_UserPreference_ID */ @@ -176,6 +156,34 @@ public class X_AD_UserPreference extends PO implements I_AD_UserPreference, I_Pe return (String)get_Value(COLUMNNAME_AD_UserPreference_UU); } + public org.compiere.model.I_AD_User getAD_User() throws RuntimeException + { + return (org.compiere.model.I_AD_User)MTable.get(getCtx(), org.compiere.model.I_AD_User.Table_ID) + .getPO(getAD_User_ID(), get_TrxName()); + } + + /** Set User/Contact. + @param AD_User_ID User within the system - Internal or Business Partner Contact + */ + public void setAD_User_ID (int AD_User_ID) + { + if (AD_User_ID < 1) + set_ValueNoCheck (COLUMNNAME_AD_User_ID, null); + else + set_ValueNoCheck (COLUMNNAME_AD_User_ID, Integer.valueOf(AD_User_ID)); + } + + /** Get User/Contact. + @return User within the system - Internal or Business Partner Contact + */ + public int getAD_User_ID() + { + Integer ii = (Integer)get_Value(COLUMNNAME_AD_User_ID); + if (ii == null) + return 0; + return ii.intValue(); + } + /** Set Automatic Commit. @param AutoCommit Automatic Commit */ @@ -198,6 +206,28 @@ public class X_AD_UserPreference extends PO implements I_AD_UserPreference, I_Pe return false; } + /** Set Automatic New Record. + @param AutoNew Automatic New Record + */ + public void setAutoNew (boolean AutoNew) + { + set_Value (COLUMNNAME_AutoNew, Boolean.valueOf(AutoNew)); + } + + /** Get Automatic New Record. + @return Automatic New Record */ + public boolean isAutoNew() + { + Object oo = get_Value(COLUMNNAME_AutoNew); + if (oo != null) + { + if (oo instanceof Boolean) + return ((Boolean)oo).booleanValue(); + return "Y".equals(oo); + } + return false; + } + /** Set Automatic Decimal Places For Amounts. @param AutomaticDecimalPlacesForAmoun Automatically insert a decimal point */ @@ -217,28 +247,6 @@ public class X_AD_UserPreference extends PO implements I_AD_UserPreference, I_Pe return ii.intValue(); } - /** Set Automatic New Record. - @param AutoNew Automatic New Record - */ - public void setAutoNew (boolean AutoNew) - { - set_Value (COLUMNNAME_AutoNew, Boolean.valueOf(AutoNew)); - } - - /** Get Automatic New Record. - @return Automatic New Record */ - public boolean isAutoNew() - { - Object oo = get_Value(COLUMNNAME_AutoNew); - if (oo != null) - { - if (oo instanceof Boolean) - return ((Boolean)oo).booleanValue(); - return "Y".equals(oo); - } - return false; - } - /** Set Threshold. @param GridAfterFindThreshold Force grid view when Find panel closes if number of records exceed threshold */ @@ -280,6 +288,28 @@ public class X_AD_UserPreference extends PO implements I_AD_UserPreference, I_Pe return false; } + /** Set Read Only Session. + @param IsReadOnlySession Read Only Session + */ + public void setIsReadOnlySession (boolean IsReadOnlySession) + { + set_Value (COLUMNNAME_IsReadOnlySession, Boolean.valueOf(IsReadOnlySession)); + } + + /** Get Read Only Session. + @return Read Only Session */ + public boolean isReadOnlySession() + { + Object oo = get_Value(COLUMNNAME_IsReadOnlySession); + if (oo != null) + { + if (oo instanceof Boolean) + return ((Boolean)oo).booleanValue(); + return "Y".equals(oo); + } + return false; + } + /** Set Use Similar To. @param IsUseSimilarTo Use Similar To */ diff --git a/org.adempiere.base/src/org/compiere/model/X_Test.java b/org.adempiere.base/src/org/compiere/model/X_Test.java index b553753bdf..9b814d0d7f 100644 --- a/org.adempiere.base/src/org/compiere/model/X_Test.java +++ b/org.adempiere.base/src/org/compiere/model/X_Test.java @@ -26,7 +26,7 @@ import org.compiere.util.KeyNamePair; /** Generated Model for Test * @author iDempiere (generated) - * @version Release 11 - $Id$ */ + * @version Release 12 - $Id$ */ @org.adempiere.base.Model(table="Test") public class X_Test extends PO implements I_Test, I_Persistent { @@ -34,7 +34,7 @@ public class X_Test extends PO implements I_Test, I_Persistent /** * */ - private static final long serialVersionUID = 20231222L; + private static final long serialVersionUID = 20240620L; /** Standard Constructor */ public X_Test (Properties ctx, int Test_ID, String trxName) @@ -108,30 +108,6 @@ public class X_Test extends PO implements I_Test, I_Persistent return sb.toString(); } - public I_C_ValidCombination getAccount_A() throws RuntimeException - { - return (I_C_ValidCombination)MTable.get(getCtx(), I_C_ValidCombination.Table_ID) - .getPO(getAccount_Acct(), get_TrxName()); - } - - /** Set Account_Acct. - @param Account_Acct Account_Acct - */ - public void setAccount_Acct (int Account_Acct) - { - set_Value (COLUMNNAME_Account_Acct, Integer.valueOf(Account_Acct)); - } - - /** Get Account_Acct. - @return Account_Acct */ - public int getAccount_Acct() - { - Integer ii = (Integer)get_Value(COLUMNNAME_Account_Acct); - if (ii == null) - return 0; - return ii.intValue(); - } - public org.compiere.model.I_AD_Table getAD_Table() throws RuntimeException { return (org.compiere.model.I_AD_Table)MTable.get(getCtx(), org.compiere.model.I_AD_Table.Table_ID) @@ -160,23 +136,44 @@ public class X_Test extends PO implements I_Test, I_Persistent return ii.intValue(); } + public I_C_ValidCombination getAccount_A() throws RuntimeException + { + return (I_C_ValidCombination)MTable.get(getCtx(), I_C_ValidCombination.Table_ID) + .getPO(getAccount_Acct(), get_TrxName()); + } + + /** Set Account_Acct. + @param Account_Acct Account_Acct + */ + public void setAccount_Acct (int Account_Acct) + { + set_Value (COLUMNNAME_Account_Acct, Integer.valueOf(Account_Acct)); + } + + /** Get Account_Acct. + @return Account_Acct */ + public int getAccount_Acct() + { + Integer ii = (Integer)get_Value(COLUMNNAME_Account_Acct); + if (ii == null) + return 0; + return ii.intValue(); + } + /** Set Binary Data. @param BinaryData Binary Data */ - public void setBinaryData (int BinaryData) + public void setBinaryData (byte[] BinaryData) { - set_Value (COLUMNNAME_BinaryData, Integer.valueOf(BinaryData)); + set_Value (COLUMNNAME_BinaryData, BinaryData); } /** Get Binary Data. @return Binary Data */ - public int getBinaryData() + public byte[] getBinaryData() { - Integer ii = (Integer)get_Value(COLUMNNAME_BinaryData); - if (ii == null) - return 0; - return ii.intValue(); + return (byte[])get_Value(COLUMNNAME_BinaryData); } public org.compiere.model.I_C_BPartner getC_BPartner() throws RuntimeException @@ -235,22 +232,6 @@ public class X_Test extends PO implements I_Test, I_Persistent return ii.intValue(); } - /** Set Character Data. - @param CharacterData Long Character Field - */ - public void setCharacterData (String CharacterData) - { - set_Value (COLUMNNAME_CharacterData, CharacterData); - } - - /** Get Character Data. - @return Long Character Field - */ - public String getCharacterData() - { - return (String)get_Value(COLUMNNAME_CharacterData); - } - public I_C_Location getC_Location() throws RuntimeException { return (I_C_Location)MTable.get(getCtx(), I_C_Location.Table_ID) @@ -279,21 +260,6 @@ public class X_Test extends PO implements I_Test, I_Persistent return ii.intValue(); } - /** Set Color. - @param Color Color - */ - public void setColor (String Color) - { - set_Value (COLUMNNAME_Color, Color); - } - - /** Get Color. - @return Color */ - public String getColor() - { - return (String)get_Value(COLUMNNAME_Color); - } - public org.compiere.model.I_C_Payment getC_Payment() throws RuntimeException { return (org.compiere.model.I_C_Payment)MTable.get(getCtx(), org.compiere.model.I_C_Payment.Table_ID) @@ -350,6 +316,37 @@ public class X_Test extends PO implements I_Test, I_Persistent return ii.intValue(); } + /** Set Character Data. + @param CharacterData Long Character Field + */ + public void setCharacterData (String CharacterData) + { + set_Value (COLUMNNAME_CharacterData, CharacterData); + } + + /** Get Character Data. + @return Long Character Field + */ + public String getCharacterData() + { + return (String)get_Value(COLUMNNAME_CharacterData); + } + + /** Set Color. + @param Color Color + */ + public void setColor (String Color) + { + set_Value (COLUMNNAME_Color, Color); + } + + /** Get Color. + @return Color */ + public String getColor() + { + return (String)get_Value(COLUMNNAME_Color); + } + /** Set Description. @param Description Optional short description of the record */ @@ -382,6 +379,22 @@ public class X_Test extends PO implements I_Test, I_Persistent return (String)get_Value(COLUMNNAME_Help); } + /** Set JSON Data. + @param JsonData The json field stores json data. + */ + public void setJsonData (String JsonData) + { + set_Value (COLUMNNAME_JsonData, JsonData); + } + + /** Get JSON Data. + @return The json field stores json data. + */ + public String getJsonData() + { + return (String)get_Value(COLUMNNAME_JsonData); + } + public I_M_Locator getM_Locator() throws RuntimeException { return (I_M_Locator)MTable.get(getCtx(), I_M_Locator.Table_ID) @@ -592,60 +605,6 @@ public class X_Test extends PO implements I_Test, I_Persistent return (Timestamp)get_Value(COLUMNNAME_T_DateTime); } - /** Set Test ID. - @param Test_ID Test ID - */ - public void setTest_ID (int Test_ID) - { - if (Test_ID < 1) - set_ValueNoCheck (COLUMNNAME_Test_ID, null); - else - set_ValueNoCheck (COLUMNNAME_Test_ID, Integer.valueOf(Test_ID)); - } - - /** Get Test ID. - @return Test ID */ - public int getTest_ID() - { - Integer ii = (Integer)get_Value(COLUMNNAME_Test_ID); - if (ii == null) - return 0; - return ii.intValue(); - } - - /** Set Test_UU. - @param Test_UU Test_UU - */ - public void setTest_UU (String Test_UU) - { - set_Value (COLUMNNAME_Test_UU, Test_UU); - } - - /** Get Test_UU. - @return Test_UU */ - public String getTest_UU() - { - return (String)get_Value(COLUMNNAME_Test_UU); - } - - /** Set Virtual Quantity. - @param TestVirtualQty Used only for testing purposes - */ - public void setTestVirtualQty (BigDecimal TestVirtualQty) - { - throw new IllegalArgumentException ("TestVirtualQty is virtual column"); } - - /** Get Virtual Quantity. - @return Used only for testing purposes - */ - public BigDecimal getTestVirtualQty() - { - BigDecimal bd = (BigDecimal)get_Value(COLUMNNAME_TestVirtualQty); - if (bd == null) - return Env.ZERO; - return bd; - } - /** Set Integer. @param T_Integer Integer */ @@ -715,4 +674,58 @@ public class X_Test extends PO implements I_Test, I_Persistent { return (Timestamp)get_Value(COLUMNNAME_T_Timestamp); } + + /** Set Virtual Quantity. + @param TestVirtualQty Used only for testing purposes + */ + public void setTestVirtualQty (BigDecimal TestVirtualQty) + { + throw new IllegalArgumentException ("TestVirtualQty is virtual column"); } + + /** Get Virtual Quantity. + @return Used only for testing purposes + */ + public BigDecimal getTestVirtualQty() + { + BigDecimal bd = (BigDecimal)get_Value(COLUMNNAME_TestVirtualQty); + if (bd == null) + return Env.ZERO; + return bd; + } + + /** Set Test ID. + @param Test_ID Test ID + */ + public void setTest_ID (int Test_ID) + { + if (Test_ID < 1) + set_ValueNoCheck (COLUMNNAME_Test_ID, null); + else + set_ValueNoCheck (COLUMNNAME_Test_ID, Integer.valueOf(Test_ID)); + } + + /** Get Test ID. + @return Test ID */ + public int getTest_ID() + { + Integer ii = (Integer)get_Value(COLUMNNAME_Test_ID); + if (ii == null) + return 0; + return ii.intValue(); + } + + /** Set Test_UU. + @param Test_UU Test_UU + */ + public void setTest_UU (String Test_UU) + { + set_Value (COLUMNNAME_Test_UU, Test_UU); + } + + /** Get Test_UU. + @return Test_UU */ + public String getTest_UU() + { + return (String)get_Value(COLUMNNAME_Test_UU); + } } \ No newline at end of file diff --git a/org.adempiere.base/src/org/compiere/print/DataEngine.java b/org.adempiere.base/src/org/compiere/print/DataEngine.java index 416ea7c7d4..64b39fb7c9 100644 --- a/org.adempiere.base/src/org/compiere/print/DataEngine.java +++ b/org.adempiere.base/src/org/compiere/print/DataEngine.java @@ -41,6 +41,7 @@ import org.compiere.model.MLookupFactory; import org.compiere.model.MQuery; import org.compiere.model.MReportView; import org.compiere.model.MRole; +import org.compiere.model.MSysConfig; import org.compiere.model.MTable; import org.compiere.model.SystemIDs; import org.compiere.util.CLogMgt; @@ -142,6 +143,10 @@ public class DataEngine private Map m_summarized = new HashMap(); + public static final int DEFAULT_REPORT_LOAD_TIMEOUT_IN_SECONDS = 120; + + public static final int DEFAULT_GLOBAL_MAX_REPORT_RECORDS = 100000; + /************************************************************************** * Load Data * @@ -927,11 +932,20 @@ public class DataEngine int reportLineID = 0; ArrayList scriptColumns = new ArrayList(); // + int timeout = MSysConfig.getIntValue(MSysConfig.REPORT_LOAD_TIMEOUT_IN_SECONDS, DEFAULT_REPORT_LOAD_TIMEOUT_IN_SECONDS, Env.getAD_Client_ID(Env.getCtx())); PreparedStatement pstmt = null; ResultSet rs = null; + String sql = pd.getSQL(); try { - pstmt = DB.prepareNormalReadReplicaStatement(pd.getSQL(), m_trxName); + int maxRows = MSysConfig.getIntValue(MSysConfig.GLOBAL_MAX_REPORT_RECORDS, DEFAULT_GLOBAL_MAX_REPORT_RECORDS, Env.getAD_Client_ID(Env.getCtx())); + if (maxRows > 0 && DB.getDatabase().isPagingSupported()) + sql = DB.getDatabase().addPagingSQL(sql, 1, maxRows+1); + pstmt = DB.prepareNormalReadReplicaStatement(sql, m_trxName); + if (maxRows > 0 && ! DB.getDatabase().isPagingSupported()) + pstmt.setMaxRows(maxRows+1); + if (timeout > 0) + pstmt.setQueryTimeout(timeout); rs = pstmt.executeQuery(); boolean isExistsT_Report_PA_ReportLine_ID = false; @@ -948,9 +962,13 @@ public class DataEngine } } + int cnt = 0; // Row Loop while (rs.next()) { + cnt++; + if (maxRows > 0 && cnt > maxRows) + throw new AdempiereException(Msg.getMsg(Env.getCtx(), "ReportMaxRowsReached", new Object[] {maxRows})); if (hasLevelNo) { levelNo = rs.getInt("LevelNo"); @@ -1122,7 +1140,7 @@ public class DataEngine pde = new PrintDataElement(pdc.getAD_PrintFormatItem_ID(), pdc.getColumnName(), Boolean.valueOf(b), pdc.getDisplayType(), pdc.getFormatPattern()); } } - else if (pdc.getDisplayType() == DisplayType.TextLong) + else if (pdc.getDisplayType() == DisplayType.TextLong || (pdc.getDisplayType() == DisplayType.JSON && DB.isOracle())) { String value = ""; if ("java.lang.String".equals(rs.getMetaData().getColumnClassName(counter))) @@ -1184,7 +1202,9 @@ public class DataEngine } catch (SQLException e) { - log.log(Level.SEVERE, pdc + " - " + e.getMessage() + "\nSQL=" + pd.getSQL()); + if (DB.getDatabase().isQueryTimeout(e)) + throw new AdempiereException(Msg.getMsg(Env.getCtx(), "ReportQueryTimeout", new Object[] {timeout})); + log.log(Level.SEVERE, pdc + " - " + e.getMessage() + "\nSQL=" + sql); throw new AdempiereException(e); } finally @@ -1303,7 +1323,7 @@ public class DataEngine { if (CLogMgt.isLevelFiner()) log.finer("NO Rows - ms=" + (System.currentTimeMillis()-m_startTime) - + " - " + pd.getSQL()); + + " - " + sql); else log.info("NO Rows - ms=" + (System.currentTimeMillis()-m_startTime)); } diff --git a/org.adempiere.base/src/org/compiere/print/PrintUtil.java b/org.adempiere.base/src/org/compiere/print/PrintUtil.java index de8480dcb2..18c0175fe3 100644 --- a/org.adempiere.base/src/org/compiere/print/PrintUtil.java +++ b/org.adempiere.base/src/org/compiere/print/PrintUtil.java @@ -47,11 +47,6 @@ import javax.print.attribute.standard.JobPriority; import javax.print.attribute.standard.OrientationRequested; import javax.swing.JDialog; -import org.adempiere.process.UUIDGenerator; -import org.compiere.model.MColumn; -import org.compiere.model.PO; -import org.compiere.model.X_AD_PrintForm; -import org.compiere.util.CLogMgt; import org.compiere.util.CLogger; import org.compiere.util.DB; import org.compiere.util.Env; @@ -453,8 +448,7 @@ public class PrintUtil { if (log.isLoggable(Level.CONFIG)) log.config("AD_Client_ID=" + AD_Client_ID); Properties ctx = Env.getCtx(); - CLogMgt.enable(false); - // + // Order Template int Order_PrintFormat_ID = MPrintFormat.copyToClient(ctx, PRINTFORMAT_ORDER_HEADER_TEMPLATE, AD_Client_ID, trxName).get_ID(); int OrderLine_PrintFormat_ID = MPrintFormat.copyToClient(ctx, PRINTFORMAT_ORDER_LINETAX_TEMPLATE, AD_Client_ID, trxName).get_ID(); @@ -475,27 +469,18 @@ public class PrintUtil int Remittance_PrintFormat_ID = MPrintFormat.copyToClient(ctx, PRINTFORMAT_PAYSELECTION_REMITTANCE__TEMPLATE, AD_Client_ID, trxName).get_ID(); updatePrintFormatHeader(Remittance_PrintFormat_ID, RemittanceLine_PrintFormat_ID, trxName); - // TODO: MPrintForm - int AD_PrintForm_ID = DB.getNextID (AD_Client_ID, "AD_PrintForm", null); - String sql = "INSERT INTO AD_PrintForm(AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_PrintForm_ID," + StringBuilder sql = new StringBuilder("INSERT INTO AD_PrintForm(AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_PrintForm_ID,AD_PrintForm_UU," + "Name,Order_PrintFormat_ID,Invoice_PrintFormat_ID,Remittance_PrintFormat_ID,Shipment_PrintFormat_ID)" - // - + " VALUES (" + AD_Client_ID + ",0,'Y',getDate(),0,getDate(),0," + AD_PrintForm_ID + "," - + "'" + Msg.translate(ctx, "Standard") + "'," - + Order_PrintFormat_ID + "," + Invoice_PrintFormat_ID + "," - + Remittance_PrintFormat_ID + "," + Shipment_PrintFormat_ID + ")"; - int no = DB.executeUpdate(sql, trxName); + + " VALUES (") + .append(AD_Client_ID).append(",0,'Y',getDate(),0,getDate(),0,").append(AD_PrintForm_ID).append(",generate_uuid(),") + .append(DB.TO_STRING(Msg.translate(ctx, "Standard"))).append(",") + .append(Order_PrintFormat_ID).append(",").append(Invoice_PrintFormat_ID).append(",") + .append(Remittance_PrintFormat_ID).append(",").append(Shipment_PrintFormat_ID).append(")"); + int no = DB.executeUpdateEx(sql.toString(), trxName); if (no != 1) log.log(Level.SEVERE, "PrintForm NOT inserted"); - if (DB.isGenerateUUIDSupported()) - DB.executeUpdateEx("UPDATE AD_PrintForm SET AD_PrintForm_UU=generate_uuid() WHERE AD_PrintForm_UU IS NULL", trxName); - else - UUIDGenerator.updateUUID(MColumn.get(ctx, X_AD_PrintForm.Table_Name, PO.getUUIDColumnName(X_AD_PrintForm.Table_Name)), trxName); - - // - CLogMgt.enable(true); } // createDocuments /** diff --git a/org.adempiere.base/src/org/compiere/print/ReportEngine.java b/org.adempiere.base/src/org/compiere/print/ReportEngine.java index e7814ae70a..6900da39c0 100644 --- a/org.adempiere.base/src/org/compiere/print/ReportEngine.java +++ b/org.adempiere.base/src/org/compiere/print/ReportEngine.java @@ -857,7 +857,8 @@ queued-job-count = 0 (class javax.print.attribute.standard.QueuedJobCount) try { if (file == null) - file = FileUtil.createTempFile (makePrefix(getName()), ".pdf"); + file = (m_pi != null && !Util.isEmpty(m_pi.getPDFFileName(),true)) ? FileUtil.createFile(m_pi.getPDFFileName()) : + FileUtil.createTempFile (FileUtil.makePrefix(getName()), ".pdf"); } catch (IOException e) { @@ -888,7 +889,7 @@ queued-job-count = 0 (class javax.print.attribute.standard.QueuedJobCount) try { if (file == null) - file = FileUtil.createTempFile (makePrefix(getName()), ".html"); + file = FileUtil.createTempFile (FileUtil.makePrefix(getName()), ".html"); } catch (IOException e) { @@ -919,7 +920,7 @@ queued-job-count = 0 (class javax.print.attribute.standard.QueuedJobCount) try { if (file == null) - file = FileUtil.createTempFile (makePrefix(getName()), ".csv"); + file = FileUtil.createTempFile (FileUtil.makePrefix(getName()), ".csv"); } catch (IOException e) { @@ -950,7 +951,7 @@ queued-job-count = 0 (class javax.print.attribute.standard.QueuedJobCount) try { if (file == null) - file = FileUtil.createTempFile (makePrefix(getName()), ".xls"); + file = FileUtil.createTempFile (FileUtil.makePrefix(getName()), ".xls"); } catch (IOException e) { @@ -988,7 +989,7 @@ queued-job-count = 0 (class javax.print.attribute.standard.QueuedJobCount) try { if (file == null) - file = FileUtil.createTempFile (makePrefix(getName()), ".xlsx"); + file = FileUtil.createTempFile (FileUtil.makePrefix(getName()), ".xlsx"); } catch (IOException e) { @@ -1047,7 +1048,7 @@ queued-job-count = 0 (class javax.print.attribute.standard.QueuedJobCount) } pi.setIsBatch(true); pi.setPDFFileName(fileName); - ServerProcessCtl.process(pi, (m_trxName == null ? null : Trx.get(m_trxName, false))); + ServerProcessCtl.process(pi, (m_trxName == null ? null : Trx.get(m_trxName, false)), false); } else { PDFReportRendererConfiguration config = new PDFReportRendererConfiguration().setOutputFile(file); new PDFReportRenderer().renderReport(this, config); @@ -1064,19 +1065,6 @@ queued-job-count = 0 (class javax.print.attribute.standard.QueuedJobCount) return file2.exists(); } // createPDF - private String makePrefix(String name) { - StringBuilder prefix = new StringBuilder(); - char[] nameArray = name.toCharArray(); - for (char ch : nameArray) { - if (Character.isLetterOrDigit(ch)) { - prefix.append(ch); - } else { - prefix.append("_"); - } - } - return prefix.toString(); - } - /** * Create PDF as Data array * @return pdf data @@ -1686,10 +1674,10 @@ queued-job-count = 0 (class javax.print.attribute.standard.QueuedJobCount) if (DocSubTypeSO == null) DocSubTypeSO = ""; // WalkIn Receipt, WalkIn Invoice, - if (DocSubTypeSO.equals("WR") || DocSubTypeSO.equals("WI")) + if (DocSubTypeSO.equals(MOrder.DocSubTypeSO_POS) || DocSubTypeSO.equals(MOrder.DocSubTypeSO_OnCredit)) what[0] = INVOICE; // WalkIn Pickup, - else if (DocSubTypeSO.equals("WP")) + else if (DocSubTypeSO.equals(MOrder.DocSubTypeSO_Warehouse)) what[0] = SHIPMENT; // Offer Binding, Offer Nonbinding, Standard Order else diff --git a/org.adempiere.base/src/org/compiere/print/ServerReportCtl.java b/org.adempiere.base/src/org/compiere/print/ServerReportCtl.java index 87a1f7ac03..7cd9babe63 100644 --- a/org.adempiere.base/src/org/compiere/print/ServerReportCtl.java +++ b/org.adempiere.base/src/org/compiere/print/ServerReportCtl.java @@ -85,7 +85,10 @@ public class ServerReportCtl { // ============================== if(format.getJasperProcess_ID() > 0) { - boolean result = runJasperProcess(Record_ID, re, true, printerName, pi); + int jasperRecordId = Record_ID; + if (re.getPrintInfo() != null && re.getPrintInfo().getRecord_ID() > 0) + jasperRecordId = re.getPrintInfo().getRecord_ID(); + boolean result = runJasperProcess(jasperRecordId, re, true, printerName, pi); return(result); } else @@ -94,6 +97,7 @@ public class ServerReportCtl { { if (pi != null && pi.isBatch() && pi.isPrintPreview()) { + re.setProcessInfo(pi); if ("HTML".equals(pi.getReportType())) { pi.setExport(true); @@ -163,6 +167,7 @@ public class ServerReportCtl { if (pi != null) { jasperProcessInfo.setPrintPreview(pi.isPrintPreview()); jasperProcessInfo.setIsBatch(pi.isBatch()); + jasperProcessInfo.setPDFFileName(pi.getPDFFileName()); } else { jasperProcessInfo.setPrintPreview( !IsDirectPrint ); } diff --git a/org.adempiere.base/src/org/compiere/print/layout/LayoutEngine.java b/org.adempiere.base/src/org/compiere/print/layout/LayoutEngine.java index 6a98117141..71d1c4b2f0 100644 --- a/org.adempiere.base/src/org/compiere/print/layout/LayoutEngine.java +++ b/org.adempiere.base/src/org/compiere/print/layout/LayoutEngine.java @@ -1131,9 +1131,7 @@ public class LayoutEngine implements Pageable, Printable, Doc /** START DEVCOFFEE: Script print format type **/ else if (item.getPrintFormatType().equals(MPrintFormatItem.PRINTFORMATTYPE_Script)) { - element = createStringElement (item.getName(), - item.getAD_PrintColor_ID (), item.getAD_PrintFont_ID (), - maxWidth, item.getMaxHeight (), item.isHeightOneLine (), alignment, true); + element = createFieldElement (item, maxWidth, alignment, m_format.isForm()); } else // (item.isTypeText()) //** Text { @@ -1409,7 +1407,7 @@ public class LayoutEngine implements Pageable, Printable, Doc content = data.getValue(); // Convert AmtInWords Content to alpha - if (item.getColumnName().equals("AmtInWords")) + if ("AmtInWords".equals(item.getColumnName())) { if (log.isLoggable(Level.FINE)) log.fine("AmtInWords: " + stringContent); diff --git a/org.adempiere.base/src/org/compiere/print/layout/PrintDataEvaluatee.java b/org.adempiere.base/src/org/compiere/print/layout/PrintDataEvaluatee.java index cac55ce991..f4093e41a9 100644 --- a/org.adempiere.base/src/org/compiere/print/layout/PrintDataEvaluatee.java +++ b/org.adempiere.base/src/org/compiere/print/layout/PrintDataEvaluatee.java @@ -55,7 +55,7 @@ public class PrintDataEvaluatee implements Evaluatee { } String value = null; - if (variableName.startsWith("#") || variableName.startsWith("$")) { + if (Env.isGlobalVariable(variableName)) { value = Env.getContext(Env.getCtx(), variableName); } else { Object obj = m_data.getNode(variableName); @@ -89,7 +89,7 @@ public class PrintDataEvaluatee implements Evaluatee { "SELECT " + foreignColumn + " FROM " + foreignTable + " WHERE " + foreignTable + "_ID = ?", id); } else { - if (variableName.startsWith("#") || variableName.startsWith("$")) { + if (Env.isGlobalVariable(variableName)) { variableName = variableName.substring(1); } else if (variableName.indexOf("|") > 0) { variableName = variableName.substring(variableName.lastIndexOf("|")+1); diff --git a/org.adempiere.base/src/org/compiere/process/DatabaseTableRename.java b/org.adempiere.base/src/org/compiere/process/DatabaseTableRename.java index 18032dbab6..104d585ce3 100644 --- a/org.adempiere.base/src/org/compiere/process/DatabaseTableRename.java +++ b/org.adempiere.base/src/org/compiere/process/DatabaseTableRename.java @@ -28,14 +28,19 @@ package org.compiere.process; import java.util.List; import org.adempiere.exceptions.AdempiereException; +import org.compiere.Adempiere; import org.compiere.model.MClient; +import org.compiere.model.MColumn; +import org.compiere.model.MField; import org.compiere.model.MProcessPara; import org.compiere.model.MRefTable; import org.compiere.model.MSequence; import org.compiere.model.MTab; import org.compiere.model.MTable; +import org.compiere.model.MWindow; import org.compiere.model.M_Element; import org.compiere.model.Query; +import org.compiere.util.CacheMgt; import org.compiere.util.DB; import org.compiere.util.Msg; import org.compiere.util.Util; @@ -128,13 +133,11 @@ public class DatabaseTableRename extends SvrProcess { } // Rename table in sequences - String whereSeq = "(Name=? AND Description=? AND IsTableID='Y') OR (Name=? AND Description=? AND IsTableID='N')"; + String whereSeq = "(Name=? AND IsTableID='Y') OR (Name=? AND IsTableID='N')"; List seqs = new Query(getCtx(), MSequence.Table_Name, whereSeq, get_TrxName()) .setParameters( oldTableName, - "Table "+oldTableName, - "DocumentNo_"+oldTableName, - "DocumentNo/Value for Table "+oldTableName + "DocumentNo_"+oldTableName ) .list(); for (MSequence seq : seqs) { @@ -175,7 +178,12 @@ public class DatabaseTableRename extends SvrProcess { table.setTableName(p_NewTableName); table.saveEx(); - + + Adempiere.getThreadPoolExecutor().submit(() -> CacheMgt.get().reset(MColumn.Table_Name)); + Adempiere.getThreadPoolExecutor().submit(() -> CacheMgt.get().reset(MWindow.Table_Name)); + Adempiere.getThreadPoolExecutor().submit(() -> CacheMgt.get().reset(MTab.Table_Name)); + Adempiere.getThreadPoolExecutor().submit(() -> CacheMgt.get().reset(MField.Table_Name)); + return "@OK@"; } } // DatabaseTableRename diff --git a/org.adempiere.base/src/org/compiere/process/DocActionEventData.java b/org.adempiere.base/src/org/compiere/process/DocActionEventData.java index 04ec90d2a1..4724a1531b 100644 --- a/org.adempiere.base/src/org/compiere/process/DocActionEventData.java +++ b/org.adempiere.base/src/org/compiere/process/DocActionEventData.java @@ -28,12 +28,13 @@ import java.util.ArrayList; import java.util.concurrent.atomic.AtomicInteger; import org.adempiere.base.event.IEventTopics; +import org.adempiere.base.event.POEventData; import org.compiere.model.PO; /** * Event data for {@link IEventTopics#DOCACTION}. */ -public class DocActionEventData { +public class DocActionEventData implements POEventData { public String docStatus; public Object processing; @@ -70,4 +71,9 @@ public class DocActionEventData { this.po = po; } + @Override + public PO getPo() { + return po; + } + } diff --git a/org.adempiere.base/src/org/compiere/process/ProcessInfo.java b/org.adempiere.base/src/org/compiere/process/ProcessInfo.java index e8ed29e9ba..d48bbb3d2d 100644 --- a/org.adempiere.base/src/org/compiere/process/ProcessInfo.java +++ b/org.adempiere.base/src/org/compiere/process/ProcessInfo.java @@ -130,6 +130,8 @@ public class ProcessInfo implements Serializable private int m_InfoWindowID = 0; /** Summary of Execution */ private String m_Summary = ""; + /** JsonData of Execution **/ + private String m_jsonData; /** Execution had an error */ private boolean m_Error = false; @@ -262,13 +264,32 @@ public class ProcessInfo implements Serializable if (m_transactionName != null) sb.append(",Trx=").append(m_transactionName); sb.append(",Summary=").append(getSummary()) + .append(",JsonData=").append(getJsonData()) .append(",Log=").append(m_logs == null ? 0 : m_logs.size()); // .append(getLogInfo(false)); sb.append("]"); return sb.toString(); } // toString - + + /************************************************************************** + * Set JsonData + * @param jsonData jsonData (valid json string) + */ + public void setJsonData (String jsonData) + { + if (jsonData != null && !Util.isEmpty(jsonData)) + m_jsonData = Util.prettifyJSONString(jsonData); + } // setJsonData + /** + * Method getJsonData + * @return String + */ + public String getJsonData () + { + return m_jsonData; + } // getJsonData + /************************************************************************** * Set Summary * @param summary summary (will be translated) @@ -775,6 +796,7 @@ public class ProcessInfo implements Serializable logEntry.getP_Msg(), logEntry.getAD_Table_ID(), logEntry.getRecord_ID(), + logEntry.getJsonData(), logEntry.getPInstanceLogType()); il.saveEx(); return il.getAD_PInstance_Log_UU(); @@ -812,6 +834,7 @@ public class ProcessInfo implements Serializable logEntry.getP_Msg(), logEntry.getAD_Table_ID(), logEntry.getRecord_ID(), + logEntry.getJsonData(), logEntry.getPInstanceLogType()); return il.update(); } // saveLog diff --git a/org.adempiere.base/src/org/compiere/process/ProcessInfoLog.java b/org.adempiere.base/src/org/compiere/process/ProcessInfoLog.java index 28bf292523..0d40ab7c42 100644 --- a/org.adempiere.base/src/org/compiere/process/ProcessInfoLog.java +++ b/org.adempiere.base/src/org/compiere/process/ProcessInfoLog.java @@ -43,9 +43,10 @@ public class ProcessInfoLog implements Serializable * @param P_Msg Process Message * @param AD_Table_ID Table ID * @param Record_ID Record ID + * @param jsonData jsonData * @param PInstanceLogType Log Type */ - public ProcessInfoLog (String AD_PInstance_Log_UU, int Log_ID,int P_ID, Timestamp P_Date, BigDecimal P_Number, String P_Msg, int AD_Table_ID ,int Record_ID, String PInstanceLogType) + public ProcessInfoLog (String AD_PInstance_Log_UU, int Log_ID,int P_ID, Timestamp P_Date, BigDecimal P_Number, String P_Msg, int AD_Table_ID ,int Record_ID, String jsonData, String PInstanceLogType) { setLog_ID (Log_ID); setP_ID (P_ID); @@ -54,6 +55,7 @@ public class ProcessInfoLog implements Serializable setP_Msg (P_Msg); setAD_Table_ID(AD_Table_ID); setRecord_ID(Record_ID); + setJsonData(jsonData); setPInstanceLogType(PInstanceLogType); setAD_PInstance_Log_UU(AD_PInstance_Log_UU); @@ -71,7 +73,7 @@ public class ProcessInfoLog implements Serializable */ public ProcessInfoLog (int Log_ID,int P_ID, Timestamp P_Date, BigDecimal P_Number, String P_Msg, int AD_Table_ID ,int Record_ID) { - this("", Log_ID, P_ID, P_Date, P_Number, P_Msg, AD_Table_ID, Record_ID, null); + this("", Log_ID, P_ID, P_Date, P_Number, P_Msg, AD_Table_ID, Record_ID, null, null); } /** @@ -86,7 +88,7 @@ public class ProcessInfoLog implements Serializable */ public ProcessInfoLog (int P_ID, Timestamp P_Date, BigDecimal P_Number, String P_Msg, int AD_Table_ID ,int Record_ID, String PInstanceLogType) { - this("", s_Log_ID++, P_ID, P_Date, P_Number, P_Msg, AD_Table_ID, Record_ID, PInstanceLogType); + this("", s_Log_ID++, P_ID, P_Date, P_Number, P_Msg, AD_Table_ID, Record_ID, null, PInstanceLogType); } /** @@ -101,7 +103,7 @@ public class ProcessInfoLog implements Serializable */ public ProcessInfoLog (String AD_PInstance_Log_UU, int P_ID, Timestamp P_Date, BigDecimal P_Number, String P_Msg, int AD_Table_ID ,int Record_ID) { - this(AD_PInstance_Log_UU, s_Log_ID++, P_ID, P_Date, P_Number, P_Msg, AD_Table_ID, Record_ID, null); + this(AD_PInstance_Log_UU, s_Log_ID++, P_ID, P_Date, P_Number, P_Msg, AD_Table_ID, Record_ID, null, null); } /** @@ -154,7 +156,7 @@ public class ProcessInfoLog implements Serializable */ public ProcessInfoLog (String AD_PInstance_Log_UU, int P_ID, Timestamp P_Date, BigDecimal P_Number, String P_Msg, String PInstanceLogType) { - this (AD_PInstance_Log_UU, s_Log_ID++, P_ID, P_Date, P_Number, P_Msg, 0, 0, PInstanceLogType); + this (AD_PInstance_Log_UU, s_Log_ID++, P_ID, P_Date, P_Number, P_Msg, 0, 0, null, PInstanceLogType); } // ProcessInfoLog /** @@ -167,7 +169,7 @@ public class ProcessInfoLog implements Serializable */ public ProcessInfoLog (String AD_PInstance_Log_UU, int P_ID, Timestamp P_Date, BigDecimal P_Number, String P_Msg) { - this (AD_PInstance_Log_UU, s_Log_ID++, P_ID, P_Date, P_Number, P_Msg, 0,0,null); + this (AD_PInstance_Log_UU, s_Log_ID++, P_ID, P_Date, P_Number, P_Msg, 0,0, null, null); } // ProcessInfoLog /** @@ -181,7 +183,7 @@ public class ProcessInfoLog implements Serializable */ public ProcessInfoLog (int Log_ID, int P_ID, Timestamp P_Date, BigDecimal P_Number, String P_Msg, String PInstanceLogType) { - this ("", Log_ID, P_ID, P_Date, P_Number, P_Msg, 0, 0, PInstanceLogType); + this ("", Log_ID, P_ID, P_Date, P_Number, P_Msg, 0, 0, null, PInstanceLogType); } // ProcessInfoLog private static int s_Log_ID = 0; @@ -193,6 +195,7 @@ public class ProcessInfoLog implements Serializable private String m_P_Msg; private int m_AD_Table_ID; private int m_Record_ID; + private String m_JsonData; private String m_PInstanceLogType; private String m_AD_PInstance_Log_UU; @@ -296,6 +299,21 @@ public class ProcessInfoLog implements Serializable m_P_Msg = P_Msg; } + /** + * Get JsonData + * @returnJsonData + */ + public String getJsonData() { + return m_JsonData; + } + /** + * Set JsonData + * @param jsonData + */ + public void setJsonData(String jsonData) { + this.m_JsonData = jsonData; + } + /** * Get Log Type * @return Log Type diff --git a/org.adempiere.base/src/org/compiere/process/ProcessInfoUtil.java b/org.adempiere.base/src/org/compiere/process/ProcessInfoUtil.java index f666e3857f..eb6fbaedf0 100644 --- a/org.adempiere.base/src/org/compiere/process/ProcessInfoUtil.java +++ b/org.adempiere.base/src/org/compiere/process/ProcessInfoUtil.java @@ -174,7 +174,7 @@ public class ProcessInfoUtil { MPInstanceLog il = new MPInstanceLog(pi.getAD_PInstance_ID(), logs[i].getLog_ID(), logs[i].getP_Date(), logs[i].getP_ID(), logs[i].getP_Number(), logs[i].getP_Msg(), - logs[i].getAD_Table_ID(), logs[i].getRecord_ID(), + logs[i].getAD_Table_ID(), logs[i].getRecord_ID(), logs[i].getJsonData(), !Util.isEmpty(logs[i].getPInstanceLogType()) ? logs[i].getPInstanceLogType() : X_AD_PInstance_Log.PINSTANCELOGTYPE_Result); il.save(); } @@ -187,21 +187,29 @@ public class ProcessInfoUtil public static void setParameterFromDB (ProcessInfo pi) { ArrayList list = new ArrayList(); - String sql = "SELECT p.ParameterName," // 1 + final String sql = "SELECT p.ParameterName," // 1 + " p.P_String,p.P_String_To, p.P_Number,p.P_Number_To," // 2/3 4/5 + " p.P_Date,p.P_Date_To, p.Info,p.Info_To, " // 6/7 8/9 + " i.AD_Client_ID, i.AD_Org_ID, i.AD_User_ID, " // 10..12 - + " p.IsNotClause " // 13 + + " p.IsNotClause, p.SeqNo " // 13..14 + "FROM AD_PInstance_Para p" + " INNER JOIN AD_PInstance i ON (p.AD_PInstance_ID=i.AD_PInstance_ID) " + "WHERE p.AD_PInstance_ID=? " - + "ORDER BY p.SeqNo"; + + " UNION " /* Add as null the parameters that were not passed */ + + " SELECT pp.ColumnName, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, i.AD_Client_ID, i.AD_Org_ID, i.AD_User_ID, 'N', pp.SeqNo " + + " FROM AD_PInstance i " + + " JOIN AD_Process_Para pp ON (pp.AD_Process_ID=i.AD_Process_ID AND pp.IsActive='Y') " + + " WHERE i.AD_PInstance_ID=? " + + " AND pp.ColumnName NOT IN (SELECT ParameterName FROM AD_PInstance_Para p WHERE p.AD_PInstance_ID=?) " + + "ORDER BY SeqNo"; PreparedStatement pstmt = null; ResultSet rs = null; try { pstmt = DB.prepareStatement(sql, null); pstmt.setInt(1, pi.getAD_PInstance_ID()); + pstmt.setInt(2, pi.getAD_PInstance_ID()); + pstmt.setInt(3, pi.getAD_PInstance_ID()); rs = pstmt.executeQuery(); while (rs.next()) { diff --git a/org.adempiere.base/src/org/compiere/process/ServerProcessCtl.java b/org.adempiere.base/src/org/compiere/process/ServerProcessCtl.java index 7106492551..1503af0a32 100644 --- a/org.adempiere.base/src/org/compiere/process/ServerProcessCtl.java +++ b/org.adempiere.base/src/org/compiere/process/ServerProcessCtl.java @@ -256,6 +256,12 @@ public class ServerProcessCtl implements Runnable { m_pi.setReportingProcess(true); m_pi.setClassName(ProcessUtil.JASPER_STARTER_CLASS); startProcess(); + if (m_pi.isError()) { + MPInstance pinstance = new MPInstance(Env.getCtx(), m_pi.getAD_PInstance_ID(), null); + pinstance.setErrorMsg(m_pi.getSummary()); + pinstance.setJsonData(m_pi.getJsonData()); + pinstance.saveEx(); + } return; } diff --git a/org.adempiere.base/src/org/compiere/process/SvrProcess.java b/org.adempiere.base/src/org/compiere/process/SvrProcess.java index d2eeaa554f..e334913e14 100644 --- a/org.adempiere.base/src/org/compiere/process/SvrProcess.java +++ b/org.adempiere.base/src/org/compiere/process/SvrProcess.java @@ -251,7 +251,7 @@ public abstract class SvrProcess implements ProcessCall @SuppressWarnings("unchecked") List errorsBP = (List) eventBP.getProperty(IEventManager.EVENT_ERROR_MESSAGES); if (errorsBP != null && !errorsBP.isEmpty()) { - msg = "@Error@:" + errorsBP.get(0); + msg = "@Error@" + errorsBP.get(0); } else { msg = doIt(); if (msg != null && ! msg.startsWith("@Error@")) { @@ -259,7 +259,7 @@ public abstract class SvrProcess implements ProcessCall @SuppressWarnings("unchecked") List errorsAP = (List) eventAP.getProperty(IEventManager.EVENT_ERROR_MESSAGES); if (errorsAP != null && !errorsAP.isEmpty()) { - msg = "@Error@:" + errorsAP.get(0); + msg = "@Error@" + errorsAP.get(0); } } } @@ -761,6 +761,7 @@ public abstract class SvrProcess implements ProcessCall mpi.setIsProcessing(false); mpi.setResult(!m_pi.isError()); mpi.setErrorMsg(m_pi.getSummary()); + mpi.setJsonData(m_pi.getJsonData()); mpi.saveEx(); if (log.isLoggable(Level.FINE)) log.fine(mpi.toString()); diff --git a/org.adempiere.base/src/org/compiere/report/TrialBalance.java b/org.adempiere.base/src/org/compiere/report/TrialBalance.java index 6b125e6a22..859ca2791d 100644 --- a/org.adempiere.base/src/org/compiere/report/TrialBalance.java +++ b/org.adempiere.base/src/org/compiere/report/TrialBalance.java @@ -45,7 +45,7 @@ import org.compiere.util.Msg; * @see https://sourceforge.net/p/adempiere/feature-requests/631/ * @version $Id: TrialBalance.java,v 1.2 2006/07/30 00:51:05 jjanke Exp $ */ -@org.adempiere.base.annotation.Process +@org.adempiere.base.annotation.Process public class TrialBalance extends SvrProcess { /** AcctSchame Parameter */ @@ -284,8 +284,12 @@ public class TrialBalance extends SvrProcess createBalanceLine(); createDetailLines(); - // int AD_PrintFormat_ID = 134; - // getProcessInfo().setTransientObject (MPrintFormat.get (getCtx(), AD_PrintFormat_ID, false)); + final String sql = """ + DELETE FROM T_TrialBalance WHERE AD_PInstance_ID=? + AND Account_ID IN (SELECT Account_ID FROM T_TrialBalance WHERE AD_PInstance_ID=? AND LevelNo=0 AND AmtAcctBalance = 0) + AND NOT EXISTS (SELECT 1 FROM T_TrialBalance tbi WHERE AD_PInstance_ID=? AND tbi.Account_ID=T_TrialBalance.Account_ID AND LevelNo>0)"""; + + DB.executeUpdateEx(sql, new Object[] {getAD_PInstance_ID(),getAD_PInstance_ID(),getAD_PInstance_ID()}, get_TrxName()); if (log.isLoggable(Level.FINE)) log.fine((System.currentTimeMillis() - m_start) + " ms"); return ""; diff --git a/org.adempiere.base/src/org/compiere/tools/FileUtil.java b/org.adempiere.base/src/org/compiere/tools/FileUtil.java index 2188602115..17cd380c67 100644 --- a/org.adempiere.base/src/org/compiere/tools/FileUtil.java +++ b/org.adempiere.base/src/org/compiere/tools/FileUtil.java @@ -467,22 +467,15 @@ public class FileUtil public static File createTempFile(String prefix, String suffix, File directory) throws IOException { - if (prefix.length() < 3) { - throw new IllegalArgumentException("Prefix string \"" + prefix + - "\" too short: length must be at least 3"); - } + if (Util.isEmpty(prefix)) + throw new IllegalArgumentException("Prefix is required"); prefix = Util.setFilenameCorrect(prefix); if (suffix == null) suffix = ".tmp"; - Calendar cal = Calendar.getInstance(); - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); - String dt = sdf.format(cal.getTime()); - String tmpdirname = (directory != null) ? directory.getCanonicalPath() : System.getProperty("java.io.tmpdir"); - tmpdirname += System.getProperty("file.separator") + "rpttmp_" + dt + "_" + Env.getContext(Env.getCtx(), Env.AD_SESSION_ID) + System.getProperty("file.separator"); - + String tmpdirname = getTempFolderName(directory); File tmpdir = new File(tmpdirname); tmpdir.mkdirs(); @@ -492,11 +485,74 @@ public class FileUtil return f; } + + /** + * Generates a unique temporary folder name based on the current timestamp and session ID.
+ * The folder name is either within the specified directory or the default temporary directory. + * + * @param directory the base directory where the temporary folder will be created; + * if null, the system's default temporary directory is used + * @return a string representing the path to the unique temporary folder + * @throws IOException + */ + public static String getTempFolderName(File directory) throws IOException { + Calendar cal = Calendar.getInstance(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); + String dt = sdf.format(cal.getTime()); + String tmpdirname = (directory != null) ? directory.getCanonicalPath() : System.getProperty("java.io.tmpdir"); + tmpdirname += System.getProperty("file.separator") + "rpttmp_" + dt + "_" + Env.getContext(Env.getCtx(), Env.AD_SESSION_ID) + System.getProperty("file.separator"); + + return tmpdirname; + } public static File createTempFile(String prefix, String suffix) throws IOException { return createTempFile(prefix, suffix, null); } + + /** + * Creates a file with the given filename.
+ * If the filename includes the path, the file is created as requested.
+ * If it only includes the name, the file is created in a thread-safe temporary folder. + * @param fileName + * @return file + * @throws IOException + */ + public static File createFile(String fileName) throws IOException { + if (Util.isEmpty(fileName)) + throw new IllegalArgumentException("Name is required"); + + File file = null; + if (fileName.contains(System.getProperty("file.separator"))) { + file = new File(fileName); + } else { + String tmpdirname = getTempFolderName(null); + File tmpdir = new File(tmpdirname); + tmpdir.mkdirs(); + + file = new File(tmpdirname, fileName); + } + + return file; + } + + /** + * Creates a valid file name prefix from "name" + * @param name + * @return file name prefix + */ + public static String makePrefix(String name) { + StringBuilder prefix = new StringBuilder(); + char[] nameArray = name.toCharArray(); + for (char ch : nameArray) { + if (Character.isLetterOrDigit(ch)) { + prefix.append(ch); + } else { + prefix.append("_"); + } + } + return prefix.toString(); + } /** * diff --git a/org.adempiere.base/src/org/compiere/util/CLogFormatter.java b/org.adempiere.base/src/org/compiere/util/CLogFormatter.java index b9a02aa0f0..8e31e30a2e 100644 --- a/org.adempiere.base/src/org/compiere/util/CLogFormatter.java +++ b/org.adempiere.base/src/org/compiere/util/CLogFormatter.java @@ -300,15 +300,14 @@ public class CLogFormatter extends Formatter int adempiereTraceNo = 0; for (int i=0; i < trace.length; i++) { - adempiereTrace = trace[i].getClassName().startsWith("org.compiere."); + adempiereTrace = trace[i].getClassName().startsWith("org.compiere.") || trace[i].getClassName().startsWith("org.adempiere.") || trace[i].getClassName().startsWith("org.idempiere."); if (thrown instanceof ServerException // RMI || adempiereTrace) { if (adempiereTrace) sb.append("\tat ").append(trace[i]).append(NL); } - else if (i > 20 - || (i > 10 && adempiereTraceNo > 8)) + else if (!SystemProperties.isFullExceptionTraceInLog() && (i > 20 || (i > 10 && adempiereTraceNo > 8))) break; else sb.append("\tat ").append(trace[i]).append(NL); diff --git a/org.adempiere.base/src/org/compiere/util/CLogMgt.java b/org.adempiere.base/src/org/compiere/util/CLogMgt.java index af154d0188..8ebbd4e9ac 100644 --- a/org.adempiere.base/src/org/compiere/util/CLogMgt.java +++ b/org.adempiere.base/src/org/compiere/util/CLogMgt.java @@ -450,18 +450,31 @@ public class CLogMgt return Level.INFO.intValue() >= getLevelAsInt(); } // isLevelFine + /** + * Save the current level when disabling log + */ + private static Level previousLevel = null; /** * Enable/Disable logging (of handlers) * @param enableLogging true if logging enabled + * @deprecated not recommended to use, problematic method to enable/disable the log globally */ public static void enable (boolean enableLogging) { Logger rootLogger = getRootLogger(); if (enableLogging) - setLevel(rootLogger.getLevel()); + { + if (previousLevel != null) + setLevel(previousLevel); + else + setLevel(rootLogger.getLevel()); + reInit(); + previousLevel = null; + } else { + previousLevel = rootLogger.getLevel(); setLevel(Level.OFF); } } // enable diff --git a/org.adempiere.base/src/org/compiere/util/DB.java b/org.adempiere.base/src/org/compiere/util/DB.java index 54426992f8..b1f43c1ec6 100644 --- a/org.adempiere.base/src/org/compiere/util/DB.java +++ b/org.adempiere.base/src/org/compiere/util/DB.java @@ -2208,6 +2208,24 @@ public final class DB // return out.toString(); } // TO_STRING + + /** + * Return string as JSON object for INSERT statements with correct precision + * @param value + * @return value as json + */ + public static String TO_JSON (String value) + { + return s_cc.getDatabase().TO_JSON(value); + } + + /** + * @return string with right casting for JSON inserts + */ + public static String getJSONCast() + { + return s_cc.getDatabase().getJSONCast(); + } /** * Convenient method to close result set diff --git a/org.adempiere.base/src/org/compiere/util/DisplayType.java b/org.adempiere.base/src/org/compiere/util/DisplayType.java index db4121f6c3..8c731ac4c1 100644 --- a/org.adempiere.base/src/org/compiere/util/DisplayType.java +++ b/org.adempiere.base/src/org/compiere/util/DisplayType.java @@ -66,6 +66,7 @@ import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_TIMEZONE; import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_URL; import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_UUID; import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_YES_NO; +import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_JSON; import java.text.DateFormat; import java.text.DecimalFormat; @@ -196,8 +197,9 @@ public final class DisplayType public static final int RecordID = REFERENCE_DATATYPE_RECORD_ID; public static final int RecordUU = REFERENCE_DATATYPE_RECORD_UU; - + public static final int JSON = REFERENCE_DATATYPE_JSON; + public static final int TimestampWithTimeZone = REFERENCE_DATATYPE_TIMESTAMP_WITH_TIMEZONE; public static final int TimeZoneId = REFERENCE_DATATYPE_TIMEZONE; @@ -409,7 +411,7 @@ public final class DisplayType public static boolean isText(int displayType) { if (displayType == String || displayType == Text - || displayType == TextLong || displayType == Memo + || displayType == TextLong || displayType == JSON || displayType == Memo || displayType == FilePath || displayType == FileName || displayType == URL || displayType == PrinterName || displayType == SingleSelectionGrid || displayType == Color @@ -542,7 +544,7 @@ public final class DisplayType */ public static boolean isLookup(int displayType) { - if (displayType == List + if (displayType == List || displayType == Payment || displayType == Table || displayType == TableUU || displayType == TableDir || displayType == TableDirUU || displayType == Search || displayType == SearchUU @@ -588,7 +590,8 @@ public final class DisplayType public static boolean isLOB (int displayType) { if (displayType == Binary - || displayType == TextLong) + || displayType == TextLong + || (displayType == JSON && DB.isOracle())) return true; //not custom type, don't have to check factory @@ -932,7 +935,7 @@ public final class DisplayType */ public static Class getClass (int displayType, boolean yesNoAsBoolean) { - if (isText(displayType) || displayType == List || displayType == Payment || displayType == RadiogroupList) + if (isText(displayType) || displayType == List || displayType == Payment || displayType == RadiogroupList || displayType == JSON) return String.class; else if (isID(displayType) || displayType == Integer) // note that Integer is stored as BD return Integer.class; @@ -972,6 +975,7 @@ public final class DisplayType s_customDisplayTypeNegativeCache.put(customTypeKey, Boolean.TRUE); } } + // return Object.class; } // getClass @@ -1044,7 +1048,9 @@ public final class DisplayType return getDatabase().getNumericDataType()+"(10)"; else return getDatabase().getCharacterDataType()+"(" + fieldLength + ")"; - } + } + if (displayType == DisplayType.JSON) + return getDatabase().getJsonDataType(); IServiceReferenceHolder cache = s_displayTypeFactoryCache.get(displayType); if (cache != null) { @@ -1175,6 +1181,8 @@ public final class DisplayType return "Text"; case TextLong: return "TextLong"; + case JSON: + return "JSON"; case Time: return "Time"; case TimestampWithTimeZone: diff --git a/org.adempiere.base/src/org/compiere/util/EMail.java b/org.adempiere.base/src/org/compiere/util/EMail.java index 9c2a5ac9c5..f8aa3e1462 100644 --- a/org.adempiere.base/src/org/compiere/util/EMail.java +++ b/org.adempiere.base/src/org/compiere/util/EMail.java @@ -302,6 +302,12 @@ public final class EMail implements Serializable props.put("mail.host", m_smtpHost); //Timeout for sending the email defaulted to 20 seconds if not defined in a SysConfig Key props.put("mail.smtp.timeout", MSysConfig.getIntValue(MSysConfig.MAIL_SMTP_TIMEOUT, 20000, Env.getAD_Client_ID(m_ctx))); + int mail_smtp_connectiontimeout = MSysConfig.getIntValue(MSysConfig.MAIL_SMTP_CONNECTIONTIMEOUT, -1, Env.getAD_Client_ID(m_ctx)); + if (mail_smtp_connectiontimeout >= 0) + props.put("mail.smtp.connectiontimeout", mail_smtp_connectiontimeout); + int mail_smtp_writetimeout = MSysConfig.getIntValue(MSysConfig.MAIL_SMTP_WRITETIMEOUT, -1, Env.getAD_Client_ID(m_ctx)); + if (mail_smtp_writetimeout >= 0) + props.put("mail.smtp.writetimeout", mail_smtp_writetimeout); if (CLogMgt.isLevelFinest()) props.put("mail.debug", "true"); diff --git a/org.adempiere.base/src/org/compiere/util/Env.java b/org.adempiere.base/src/org/compiere/util/Env.java index 3c968a6b5b..02e1c01afa 100644 --- a/org.adempiere.base/src/org/compiere/util/Env.java +++ b/org.adempiere.base/src/org/compiere/util/Env.java @@ -117,6 +117,7 @@ public final class Env public static final String HAS_ALIAS = "$HasAlias"; public static final String IS_CAN_APPROVE_OWN_DOC = "#IsCanApproveOwnDoc"; public static final String IS_CLIENT_ADMIN = "#IsClientAdmin"; + public static final String IS_SSO_LOGIN = "#IsSSOLogin"; public static final String DEVELOPER_MODE = "#DeveloperMode"; /** Context Language identifier */ public static final String LANGUAGE = "#AD_Language"; @@ -629,7 +630,7 @@ public final class Env if (s == null) { // Explicit Base Values - if (context.startsWith("#") || context.startsWith("$") || context.startsWith("P|")) + if (Env.isGlobalVariable(context) || Env.isPreference(context)) return getContext(ctx, context); if (onlyWindow) // no Default values return ""; @@ -1039,6 +1040,8 @@ public final class Env retValue = ctx.getProperty("#"+context); // Login setting if (retValue == null) retValue = ctx.getProperty("$"+context); // Accounting setting + if (retValue == null) + retValue = ctx.getProperty("+"+context); // Injected Role Variable } // return (retValue == null ? "" : retValue); @@ -1527,7 +1530,7 @@ public final class Env } String ctxInfo = getContext(ctx, WindowNo, token, onlyWindow); // get context - if (ctxInfo.length() == 0 && (token.startsWith("#") || token.startsWith("$")) ) + if (ctxInfo.length() == 0 && Env.isGlobalVariable(token)) ctxInfo = getContext(ctx, token); // get global context if (ctxInfo.length() == 0 && defaultV != null) @@ -1613,13 +1616,13 @@ public final class Env ctxInfo = getContext(ctx, WindowNo, tabNo, token, onlyTab); // get context } - if (ctxInfo.length() == 0 && (token.startsWith("#") || token.startsWith("$")) ) + if (Util.isEmpty(ctxInfo) && Env.isGlobalVariable(token)) ctxInfo = getContext(ctx, token); // get global context - if (ctxInfo.length() == 0 && defaultV != null) + if (Util.isEmpty(ctxInfo) && defaultV != null) ctxInfo = defaultV; - if (ctxInfo.length() == 0) + if (Util.isEmpty(ctxInfo)) { if (log.isLoggable(Level.CONFIG)) log.config("No Context Win=" + WindowNo + " for: " + token); if (!ignoreUnparsable) @@ -1729,7 +1732,7 @@ public final class Env } Properties ctx = po != null ? po.getCtx() : Env.getCtx(); - if (token.startsWith("#") || token.startsWith("$")) { + if (Env.isGlobalVariable(token)) { //take from context String v = Env.getContext(ctx, token); if (v != null && v.length() > 0) { @@ -1738,6 +1741,8 @@ public final class Env outStr.append("@").append(token); if (!Util.isEmpty(format)) outStr.append("<").append(format).append(">"); + if (!Util.isEmpty(defaultValue)) + outStr.append(":").append(defaultValue); outStr.append("@"); } } else if (po != null && token.startsWith("=")) { @@ -1759,6 +1764,8 @@ public final class Env outStr.append("@").append(token); if (!Util.isEmpty(format)) outStr.append("<").append(format).append(">"); + if (!Util.isEmpty(defaultValue)) + outStr.append(":").append(defaultValue); outStr.append("@"); } } @@ -1776,6 +1783,8 @@ public final class Env outStr.append("@").append(token); if (!Util.isEmpty(format)) outStr.append("<").append(format).append(">"); + if (!Util.isEmpty(defaultValue)) + outStr.append(":").append(defaultValue); outStr.append("@"); } } @@ -1812,7 +1821,7 @@ public final class Env String token, String format, MColumn colToken, Object value, StringBuilder outStr) { if (format != null && format.length() > 0) { String foreignTable = colToken != null ? colToken.getReferenceTableName() : null; - if (value instanceof String && token.endsWith("_ID") && (token.startsWith("#") || token.startsWith("$"))) { + if (value instanceof String && token.endsWith("_ID") && Env.isGlobalVariable(token)) { try { int id = Integer.parseInt((String)value); value = id; @@ -2204,7 +2213,7 @@ public final class Env // PO Zoom ? boolean isSOTrx = true; - if (table.getPO_Window_ID() != 0) + if (table.getPO_Window_ID() != 0 && ((Record_ID > 0 || Record_UU != null))) { String whereClause; if (Record_UU != null) @@ -2329,4 +2338,36 @@ public final class Env return false; } + /** + * Is read only session? Based on user preference + * @return + */ + public static boolean isReadOnlySession() { + return "Y".equals(Env.getContext(Env.getCtx(), "IsReadOnlySession")); + } + + /** + * Verifies if a context variable name is global, this is, starting with: + * # Login + * $ Accounting + * + Role Injected + * @param variable + * @return + */ + public static boolean isGlobalVariable(String variable) { + return variable.startsWith("#") + || variable.startsWith("$") + || variable.startsWith("+"); + } + + /** + * Verifies if a context variable name is a preference, this is, starting with: + * P| Preference + * @param variable + * @return + */ + public static boolean isPreference(String variable) { + return variable.startsWith("P|"); + } + } // Env \ No newline at end of file diff --git a/org.adempiere.base/src/org/compiere/util/Login.java b/org.adempiere.base/src/org/compiere/util/Login.java index 890a087a04..952359a122 100644 --- a/org.adempiere.base/src/org/compiere/util/Login.java +++ b/org.adempiere.base/src/org/compiere/util/Login.java @@ -1626,6 +1626,12 @@ public class Login loginErrMsg = Msg.getMsg(m_ctx, "UserAccountLocked", new Object[] {app_user}); } } + + if (isSSOLogin) + Env.setContext(Env.getCtx(), Env.IS_SSO_LOGIN, true); + else + Env.setContext(Env.getCtx(), Env.IS_SSO_LOGIN, false); + return retValue; } diff --git a/org.adempiere.base/src/org/compiere/util/Msg.java b/org.adempiere.base/src/org/compiere/util/Msg.java index 3e0afd6739..5f0424c115 100644 --- a/org.adempiere.base/src/org/compiere/util/Msg.java +++ b/org.adempiere.base/src/org/compiere/util/Msg.java @@ -708,6 +708,7 @@ public final class Msg /** * "Translate" text. *
+	 *		- Check Context
 	 *		- Check AD_Message.AD_Message 	->	MsgText
 	 *		- Check AD_Element.ColumnName	->	Name
 	 *  
diff --git a/org.adempiere.base/src/org/compiere/util/Trx.java b/org.adempiere.base/src/org/compiere/util/Trx.java index 8dd3302b41..0ae8b2d17c 100644 --- a/org.adempiere.base/src/org/compiere/util/Trx.java +++ b/org.adempiere.base/src/org/compiere/util/Trx.java @@ -34,6 +34,7 @@ import java.util.logging.Level; import org.adempiere.exceptions.AdempiereException; import org.adempiere.exceptions.DBException; import org.compiere.Adempiere; +import org.compiere.db.StatementProxy; import org.compiere.model.MSysConfig; import org.compiere.model.PO; @@ -876,4 +877,40 @@ public class Trx }, 2, TimeUnit.SECONDS); } } + + /** + * Register a null trx + * @return + */ + public static String registerNullTrx() { + String nullTrxName = "NullTrx_" + UUID.randomUUID().toString(); + Trx nullTrx = new Trx(nullTrxName); + nullTrx.trace = new Exception(); + nullTrx.m_startTime = System.currentTimeMillis(); + String displayName = null; + StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); + Optional stackName = walker.walk(frames -> frames.map( + stackFrame -> stackFrame.getClassName() + "." + + stackFrame.getMethodName() + ":" + + stackFrame.getLineNumber()) + .filter(f -> ! (f.startsWith(Trx.class.getName() + ".") || f.startsWith(StatementProxy.class.getName() + ".") || f.startsWith("jdk.proxy") || f.startsWith("org.compiere.util.DB."))) + .findFirst()); + displayName = (stackName.orElse(null)); + if (displayName != null) + nullTrx.setDisplayName(displayName); + s_cache.put(nullTrxName, nullTrx); + return nullTrxName; + } + + /** + * Unregister a null trx + * @param nullTrxName + */ + public static void unregisterNullTrx(String nullTrxName) { + Trx nullTrx = s_cache.get(nullTrxName); + nullTrx.setDisplayName(null); + nullTrx.trace = null; + s_cache.remove(nullTrxName); + } + } // Trx diff --git a/org.adempiere.base/src/org/compiere/util/Util.java b/org.adempiere.base/src/org/compiere/util/Util.java index b1ccb1baa3..6ccc2af021 100644 --- a/org.adempiere.base/src/org/compiere/util/Util.java +++ b/org.adempiere.base/src/org/compiere/util/Util.java @@ -44,8 +44,14 @@ import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.KeyStroke; +import org.adempiere.exceptions.AdempiereException; import org.compiere.Adempiere; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; import com.lowagie.text.Document; import com.lowagie.text.DocumentException; import com.lowagie.text.pdf.PdfContentByte; @@ -783,5 +789,20 @@ public class Util public static boolean isDeveloperMode() { return Files.isDirectory(Paths.get(Adempiere.getAdempiereHome() + File.separator + "org.adempiere.base")) || "Y".equals(System.getProperty("org.idempiere.developermode")); } + + /** + * Returns a string with a formatted JSON object + * @return string with a pretty JSON format + */ + public static String prettifyJSONString(String value) { + Gson gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create(); + try { + JsonElement jsonElement = JsonParser.parseString(value); + return gson.toJson(jsonElement); + } catch (JsonSyntaxException e) { + throw new AdempiereException(Msg.getMsg(Env.getCtx(), "InvalidJSON")); + } + } + } // Util diff --git a/org.adempiere.base/src/org/compiere/util/WebDoc.java b/org.adempiere.base/src/org/compiere/util/WebDoc.java index b735db112e..23ab5f17ad 100644 --- a/org.adempiere.base/src/org/compiere/util/WebDoc.java +++ b/org.adempiere.base/src/org/compiere/util/WebDoc.java @@ -163,12 +163,7 @@ public class WebDoc return; // css, js - if (javaClient) { - m_head.addElement(new StoredHtmlSrc("STYLE", "org/compiere/images/standard.css")); - } else { - m_head.addElement(new link(WebEnv.getStylesheetURL(), link.REL_STYLESHEET, link.TYPE_CSS)); - m_head.addElement(new script((Element)null, WebEnv.getBaseDirectory("/js/standard.js"))); - } + m_head.addElement(new link("/resources/css/idempiereMonitor.css", link.REL_STYLESHEET, link.TYPE_CSS)); m_head.addElement(new meta().setHttpEquiv("Content-Type", "text/html; charset=UTF-8")); m_head.addElement(new meta().setName("description", "iDempiere HTML UI")); @@ -185,13 +180,8 @@ public class WebDoc // Logo m_topRight = new td().setAlign("right"); - if (javaClient) { - m_topRight.addElement(new img("res:org/compiere/images/iD10030.png") + m_topRight.addElement(new img("/webui/images/header-logo.png") .setAlign(AlignType.RIGHT).setAlt("iDempiere")); - } else { - m_topRight.addElement(new img("/webui/images/header-logo.png") - .setAlign(AlignType.RIGHT).setAlt("iDempiere")); - } m_topRow.addElement(m_topRight); m_table.addElement(m_topRow); // diff --git a/org.adempiere.base/src/org/compiere/wf/MWFActivity.java b/org.adempiere.base/src/org/compiere/wf/MWFActivity.java index 28cf6e9606..4d7947aaf0 100644 --- a/org.adempiere.base/src/org/compiere/wf/MWFActivity.java +++ b/org.adempiere.base/src/org/compiere/wf/MWFActivity.java @@ -69,6 +69,7 @@ import org.compiere.util.Trace; import org.compiere.util.Trx; import org.compiere.util.TrxEventListener; import org.compiere.util.Util; +import org.compiere.util.ValueNamePair; /** * Extended Workflow Activity Model for AD_WF_Activity.
@@ -82,9 +83,9 @@ import org.compiere.util.Util; public class MWFActivity extends X_AD_WF_Activity implements Runnable { /** - * generated serial id + * */ - private static final long serialVersionUID = -9119089506977887142L; + private static final long serialVersionUID = 7274149891086011624L; private static final String CURRENT_WORKFLOW_PROCESS_INFO_ATTR = "Workflow.ProcessInfo"; @@ -324,7 +325,7 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable + WFState + ", Current=" + getWFState(); log.log(Level.SEVERE, msg); Trace.printStack(); - setTextMsg(msg); + setTextMsgBefore(msg); saveEx(); // TODO: teo_sarca: throw exception ? please analyze the call hierarchy first } @@ -560,7 +561,7 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable } // isUserChoice /** - * Set Text Msg (add to existing) + * Set Text Msg (add after existing) * @param TextMsg */ public void setTextMsg (String TextMsg) @@ -574,6 +575,21 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable super.setTextMsg (Util.trimSize(oldText + "\n - " + TextMsg,1000)); } // setTextMsg + /** + * Set Text Msg (add before existing) + * @param TextMsg + */ + public void setTextMsgBefore (String TextMsg) + { + if (TextMsg == null || TextMsg.length() == 0) + return; + String oldText = getTextMsg(); + if (oldText == null || oldText.length() == 0) + super.setTextMsg (Util.trimSize(TextMsg,1000)); + else if (TextMsg != null && TextMsg.length() > 0) + super.setTextMsg (Util.trimSize(TextMsg + "\n - " + oldText,1000)); + } // setTextMsgBefore + /** * Add to Text Msg * @param obj some object @@ -906,7 +922,7 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable if (!m_state.isValidAction(StateEngine.ACTION_Start)) { - setTextMsg("State=" + getWFState() + " - cannot start"); + setTextMsgBefore("State=" + getWFState() + " - cannot start"); addTextMsg(new Exception("")); setWFState(StateEngine.STATE_Terminated); return; @@ -916,7 +932,7 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable if (getNode().get_ID() == 0) { - setTextMsg("Node not found - AD_WF_Node_ID=" + getAD_WF_Node_ID()); + setTextMsgBefore("Node not found - AD_WF_Node_ID=" + getAD_WF_Node_ID()); setWFState(StateEngine.STATE_Aborted); return; } @@ -966,7 +982,7 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable String processMsg = e.getLocalizedMessage(); if (processMsg == null || processMsg.length() == 0) processMsg = e.getMessage(); - setTextMsg(processMsg); + setTextMsgBefore(processMsg); // addTextMsg(e); // do not add the exception text boolean contextLost = false; if (e instanceof AdempiereException && "Context lost".equals(e.getMessage())) @@ -1092,8 +1108,6 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable m_process.setProcessMsg(e.getLocalizedMessage()); throw e; } - if (m_process != null) - m_process.setProcessMsg(processMsg); } else throw new IllegalStateException("Persistent Object not DocAction - " @@ -1105,6 +1119,9 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable success = false; m_docStatus = null; processMsg = "SaveError"; + ValueNamePair ppE = CLogger.retrieveError(); + if (ppE != null) + processMsg = ppE.getName(); } if (!success) { @@ -1197,7 +1214,7 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable pi.setAD_Client_ID(getAD_Client_ID()); pi.setAD_PInstance_ID(pInstance.getAD_PInstance_ID()); boolean success = process.processItWithoutTrxClose(pi, trx); - setTextMsg(pi.getSummary()); + setTextMsgBefore(pi.getSummary()); return success; } finally { @@ -1228,7 +1245,7 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable { m_emails = new ArrayList(); sendEMail(); - setTextMsg(m_emails.toString()); + setTextMsgBefore(m_emails.toString()); } else { MClient client = MClient.get(getCtx(), getAD_Client_ID()); @@ -1437,7 +1454,7 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable String msg = getNode().getAttributeName() + "=" + value; if (textMsg != null && textMsg.length() > 0) msg += " - " + textMsg; - setTextMsg (msg); + setTextMsgBefore (msg); m_newValue = value; return true; } // setVariable @@ -1473,7 +1490,7 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable { newState = StateEngine.STATE_Aborted; if (!(doc.processIt (DocAction.ACTION_Reject))) - setTextMsg ("Cannot Reject - Document Status: " + doc.getDocStatus()); + setTextMsgBefore ("Cannot Reject - Document Status: " + doc.getDocStatus()); } else { @@ -1490,7 +1507,7 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable if (nextAD_User_ID <= 0) { newState = StateEngine.STATE_Aborted; - setTextMsg (Msg.getMsg(getCtx(), "NoApprover")); + setTextMsgBefore (Msg.getMsg(getCtx(), "NoApprover")); doc.processIt (DocAction.ACTION_Reject); } else if (startAD_User_ID != nextAD_User_ID) @@ -1503,7 +1520,7 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable if (!(doc.processIt (DocAction.ACTION_Approve))) { newState = StateEngine.STATE_Aborted; - setTextMsg ("Cannot Approve - Document Status: " + doc.getDocStatus()); + setTextMsgBefore ("Cannot Approve - Document Status: " + doc.getDocStatus()); } } } @@ -1511,7 +1528,7 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable else if (!(doc.processIt (DocAction.ACTION_Approve))) { newState = StateEngine.STATE_Aborted; - setTextMsg ("Cannot Approve - Document Status: " + doc.getDocStatus()); + setTextMsgBefore ("Cannot Approve - Document Status: " + doc.getDocStatus()); } } doc.saveEx(); @@ -1519,7 +1536,7 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable catch (Exception e) { newState = StateEngine.STATE_Terminated; - setTextMsg ("User Choice: " + e.toString()); + setTextMsgBefore ("User Choice: " + e.toString()); addTextMsg(e); log.log(Level.WARNING, "", e); } @@ -1610,7 +1627,7 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable setWFState (StateEngine.STATE_Running); setAD_User_ID(AD_User_ID); if (textMsg != null) - setTextMsg (textMsg); + setTextMsgBefore (textMsg); setWFState (StateEngine.STATE_Completed); } // setUserConfirmation diff --git a/org.adempiere.base/src/org/compiere/wf/MWFNodePara.java b/org.adempiere.base/src/org/compiere/wf/MWFNodePara.java index dc68d64367..956148b7e9 100644 --- a/org.adempiere.base/src/org/compiere/wf/MWFNodePara.java +++ b/org.adempiere.base/src/org/compiere/wf/MWFNodePara.java @@ -50,6 +50,7 @@ public class MWFNodePara extends X_AD_WF_Node_Para implements ImmutablePOSupport List list = new Query(ctx, Table_Name, "AD_WF_Node_ID=?", null) .setParameters(new Object[]{AD_WF_Node_ID}) + .setOnlyActiveRecords(true) .list(); MWFNodePara[] retValue = new MWFNodePara[list.size ()]; list.toArray (retValue); diff --git a/org.adempiere.base/src/org/compiere/wf/WFActivityManage.java b/org.adempiere.base/src/org/compiere/wf/WFActivityManage.java index 94b90aa8b3..7e8cdf455c 100644 --- a/org.adempiere.base/src/org/compiere/wf/WFActivityManage.java +++ b/org.adempiere.base/src/org/compiere/wf/WFActivityManage.java @@ -82,7 +82,7 @@ public class WFActivityManage extends SvrProcess if (p_IsAbort) { String msg = user.getName() + ": Abort"; - activity.setTextMsg(msg); + activity.setTextMsgBefore(msg); activity.setAD_User_ID(getAD_User_ID()); // 2007-06-14, matthiasO. // Set the 'processed'-flag when an activity is aborted; not setting this flag diff --git a/org.adempiere.base/src/org/idempiere/process/MigraID.java b/org.adempiere.base/src/org/idempiere/process/MigraID.java index 808a7a4f85..3001953ceb 100644 --- a/org.adempiere.base/src/org/idempiere/process/MigraID.java +++ b/org.adempiere.base/src/org/idempiere/process/MigraID.java @@ -109,7 +109,7 @@ public class MigraID extends SvrProcess { .append(" WHERE ").append(uuidCol).append("=?"); int cnt = DB.executeUpdateEx(updUUIDSB.toString(), new Object[] {p_UUID_To, p_UUID_From}, get_TrxName()); if (cnt <= 0) { - msg = "@Error@: UUID " + p_UUID_From + " not found on table " + l_tableName; + msg = "@Error@ UUID " + p_UUID_From + " not found on table " + l_tableName; } else { int id = -1; msg = "UUID changed on table " + l_tableName + " from " + p_UUID_From + " to " + p_UUID_To; @@ -144,7 +144,7 @@ public class MigraID extends SvrProcess { // convert ID int cnt = updID(l_tableName, idCol, true); if (cnt <= 0) { - msg = "@Error@: ID " + p_ID_From + " not found on table " + l_tableName; + msg = "@Error@ ID " + p_ID_From + " not found on table " + l_tableName; } else { msg = "ID changed on table " + l_tableName + " from " + p_ID_From + " to " + p_ID_To; addBufferLog(0, null, null, msg, p_AD_Table_ID, p_ID_To); diff --git a/org.adempiere.install/install.app.launch b/org.adempiere.install/install.app.launch index a39467ea7d..57d3d1c9c9 100644 --- a/org.adempiere.install/install.app.launch +++ b/org.adempiere.install/install.app.launch @@ -59,6 +59,7 @@ + diff --git a/org.adempiere.install/install.console.app.launch b/org.adempiere.install/install.console.app.launch index 1527c6d139..4e6ab62e21 100644 --- a/org.adempiere.install/install.console.app.launch +++ b/org.adempiere.install/install.console.app.launch @@ -58,6 +58,7 @@ + diff --git a/org.adempiere.install/install.silent.app.launch b/org.adempiere.install/install.silent.app.launch index cad6279072..d8435d2c56 100644 --- a/org.adempiere.install/install.silent.app.launch +++ b/org.adempiere.install/install.silent.app.launch @@ -58,6 +58,7 @@ + diff --git a/org.adempiere.install/src/org/compiere/install/ConfigurationData.java b/org.adempiere.install/src/org/compiere/install/ConfigurationData.java index 572f2ae8e2..b48f0f5720 100644 --- a/org.adempiere.install/src/org/compiere/install/ConfigurationData.java +++ b/org.adempiere.install/src/org/compiere/install/ConfigurationData.java @@ -831,6 +831,13 @@ public class ConfigurationData if (log.isLoggable(Level.FINE)) log.fine(host + ":" + port + " - " + e.getMessage()); return false; } + finally + { + if (pingSocket != null) + try { + pingSocket.close(); + } catch (IOException e) {} + } if (!shouldBeUsed) log.warning("Open Socket " + host + ":" + port + " - " + pingSocket); diff --git a/org.adempiere.pipo/src/org/adempiere/pipo2/PoExporter.java b/org.adempiere.pipo/src/org/adempiere/pipo2/PoExporter.java index 115cad3cdb..f8dd4a3a4f 100644 --- a/org.adempiere.pipo/src/org/adempiere/pipo2/PoExporter.java +++ b/org.adempiere.pipo/src/org/adempiere/pipo2/PoExporter.java @@ -309,6 +309,8 @@ public class PoExporter { String lookupColumn = info.getColumnLookup(i).getColumnName(); tableName = lookupColumn.substring(0, lookupColumn.indexOf(".")); } + if (tableName == null) + throw new AdempiereException("Could not find the related table for column " + po.get_TableName() + "." + columnName); if ( info.getColumnDisplayType(i) == DisplayType.ChosenMultipleSelectionList || DisplayType.isMultiID(info.getColumnDisplayType(i))) { addTableReferenceMulti(columnName, tableName, new AttributesImpl()); diff --git a/org.adempiere.pipo/src/org/adempiere/pipo2/PoFiller.java b/org.adempiere.pipo/src/org/adempiere/pipo2/PoFiller.java index 88a1a674c6..210745f3ce 100644 --- a/org.adempiere.pipo/src/org/adempiere/pipo2/PoFiller.java +++ b/org.adempiere.pipo/src/org/adempiere/pipo2/PoFiller.java @@ -397,7 +397,8 @@ public class PoFiller{ setInteger(qName); } else if (info.getColumnClass(index) == Timestamp.class) { setTimestamp(qName); - }else if(DisplayType.TextLong == info.getColumnDisplayType(index)) {// export column from system have type is normal string, but import to system have this column but type is textlong (mean blob) + } else if(DisplayType.TextLong == info.getColumnDisplayType(index) || DisplayType.JSON == info.getColumnDisplayType(index)) { + // export column from system have type is normal string, but import to system have this column but type is text long (mean blob) if (getStringValue (qName) != null && !isBlobOnPackinFile(qName)) { setString(qName); }else { diff --git a/org.adempiere.plugin.utils/src/org/adempiere/plugin/utils/AbstractActivator.java b/org.adempiere.plugin.utils/src/org/adempiere/plugin/utils/AbstractActivator.java index e2cf4a17ea..1e0f4edf8e 100644 --- a/org.adempiere.plugin.utils/src/org/adempiere/plugin/utils/AbstractActivator.java +++ b/org.adempiere.plugin.utils/src/org/adempiere/plugin/utils/AbstractActivator.java @@ -126,7 +126,7 @@ public abstract class AbstractActivator implements BundleActivator, ServiceTrack logger.warning("Wrong name, ignored " + fileName); return false; } - logger.warning(fileName); + if (logger.isLoggable(Level.INFO)) logger.info(fileName); String clientValue = parts[1]; clientId = DB.getSQLValueEx(null, "SELECT AD_Client_ID FROM AD_Client WHERE Value=?", clientValue); if (clientId < 0) diff --git a/org.adempiere.plugin.utils/src/org/adempiere/plugin/utils/PackInApplicationActivator.java b/org.adempiere.plugin.utils/src/org/adempiere/plugin/utils/PackInApplicationActivator.java index 5a53a619e5..530417b463 100644 --- a/org.adempiere.plugin.utils/src/org/adempiere/plugin/utils/PackInApplicationActivator.java +++ b/org.adempiere.plugin.utils/src/org/adempiere/plugin/utils/PackInApplicationActivator.java @@ -119,7 +119,7 @@ public class PackInApplicationActivator extends AbstractActivator{ addLog(Level.WARNING, msg); if (getProcessInfo() != null) { getProcessInfo().setError(true); - getProcessInfo().setSummary("@Error@: " + msg); + getProcessInfo().setSummary("@Error@ " + msg); } break; } @@ -264,6 +264,7 @@ public class PackInApplicationActivator extends AbstractActivator{ continue; } + logger.warning("Processing " + filePath); processFilePath(toProcess); } diff --git a/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ColumnLookup.java b/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ColumnLookup.java index 5ea5e9811b..a1056d0bce 100644 --- a/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ColumnLookup.java +++ b/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ColumnLookup.java @@ -24,14 +24,12 @@ **********************************************************************/ package org.adempiere.report.jasper; -import java.awt.image.BufferedImage; import java.math.BigDecimal; import java.util.Date; import java.util.function.BiFunction; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.adempiere.apps.graph.ChartBuilder; import org.compiere.model.MAccount; import org.compiere.model.MAttachment; import org.compiere.model.MAttributeSetInstance; @@ -43,14 +41,13 @@ import org.compiere.model.MLocator; import org.compiere.model.MLookup; import org.compiere.model.MLookupFactory; import org.compiere.model.MLookupInfo; +import org.compiere.model.MRole; import org.compiere.model.MTable; import org.compiere.util.DisplayType; import org.compiere.util.Env; import org.compiere.util.Language; import org.compiere.util.Msg; import org.compiere.util.Util; -import org.jfree.chart.ChartRenderingInfo; -import org.jfree.chart.JFreeChart; /** * @author hengsin @@ -173,6 +170,9 @@ public class ColumnLookup implements BiFunction { if (table != null) { int recordId = (key instanceof Number) ? ((Number)key).intValue() : -1; String recordUU = (key instanceof String) ? (String)key : null; + // check security + if (!MRole.getDefault().checkAccessSQL(table, recordId, recordUU, false)) + return null; MAttachment attachment = MAttachment.get(Env.getCtx(), table.get_ID(), recordId, recordUU, null); if (attachment != null && attachment.get_ID() > 0) { //first, check whether is via index @@ -266,15 +266,8 @@ public class ColumnLookup implements BiFunction { */ private Object getChartImage(int id, int width, int height) { MChart mc = new MChart(Env.getCtx(), id, null); - if (mc.get_ID() == id) { - ChartBuilder chartBuilder = new ChartBuilder(mc); - JFreeChart chart = chartBuilder.createChart(); - chart.getPlot().setForegroundAlpha(0.8f); - ChartRenderingInfo info = new ChartRenderingInfo(); - BufferedImage bi = chart.createBufferedImage(width, height, - BufferedImage.TRANSLUCENT, info); - return bi; - } + if (mc.get_ID() == id) + return mc.getChartImage(width, height); return null; } diff --git a/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ReportStarter.java b/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ReportStarter.java index a1eb39be90..94b40863aa 100644 --- a/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ReportStarter.java +++ b/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ReportStarter.java @@ -229,10 +229,7 @@ public class ReportStarter implements ProcessCall, ClientProcess String[] reportPathList = reportFilePath.split(";"); for (String reportPath : reportPathList) { if (Util.isEmpty(reportPath, true)) - { - pi.setSummary("Invalid report file path: " + reportFilePath, true); - return false; - } + throw new AdempiereException("Invalid report file path: " + reportFilePath); if (reportPath.startsWith("@#LocalHttpAddr@")) { String localaddr = Env.getContext(Env.getCtx(), Env.LOCAL_HTTP_ADDRESS); if (!Util.isEmpty(localaddr)) { @@ -263,11 +260,7 @@ public class ReportStarter implements ProcessCall, ClientProcess } if (reportFile == null && reportURL == null) - { - String tmp = "Can not load report from path: " + reportPath; - pi.setSummary(tmp, true); - return false; - } + throw new AdempiereException("Can not load report from path: " + reportPath); JasperInfo jasperInfo = reportFile != null ? getJasperInfo(reportFile) : getJasperInfo(reportURL); JasperReport jasperReport = jasperInfo.getJasperReport(); @@ -499,7 +492,7 @@ public class ReportStarter implements ProcessCall, ClientProcess processInfo.setPDFReport(batchPDFExportList.get(0)); } else { try { - File pdfFile = File.createTempFile(makePrefix(processInfo.getTitle()), ".pdf"); + File pdfFile = File.createTempFile(FileUtil.makePrefix(processInfo.getTitle()), ".pdf"); Util.mergePdf(batchPDFExportList, pdfFile); processInfo.setPDFReport(pdfFile); } catch (Exception e) { @@ -549,7 +542,7 @@ public class ReportStarter implements ProcessCall, ClientProcess } private File createMultiFileArchive(List exportFileList) throws Exception { - File archiveFile = File.createTempFile(makePrefix(processInfo.getTitle()), ".zip"); + File archiveFile = File.createTempFile(FileUtil.makePrefix(processInfo.getTitle()), ".zip"); try (FileOutputStream out = new FileOutputStream(archiveFile)) { try (ZipOutputStream zip = new ZipOutputStream(out);) { zip.setMethod(ZipOutputStream.DEFLATED); @@ -582,9 +575,9 @@ public class ReportStarter implements ProcessCall, ClientProcess { File pdfFile = null; if (processInfo.getPDFFileName() != null) { - pdfFile = new File(processInfo.getPDFFileName()); + pdfFile = FileUtil.createFile(processInfo.getPDFFileName()); } else { - pdfFile = File.createTempFile(makePrefix(jasperPrint.getName()), ".pdf"); + pdfFile = File.createTempFile(FileUtil.makePrefix(jasperPrint.getName()), ".pdf"); } JRPdfExporter exporter = new JRPdfExporter(jasperReportContext); @@ -644,7 +637,7 @@ public class ReportStarter implements ProcessCall, ClientProcess else newQueryText = originalQueryText + " WHERE " + query.toString(); - File jrxmlFile = File.createTempFile(makePrefix(jasperReport.getName()), ".jrxml"); + File jrxmlFile = File.createTempFile(FileUtil.makePrefix(jasperReport.getName()), ".jrxml"); JRXmlWriter.writeReport(jasperReport, new FileOutputStream(jrxmlFile), "UTF-8"); JasperDesign jasperDesign = JRXmlLoader.load(jrxmlFile); @@ -713,7 +706,7 @@ public class ReportStarter implements ProcessCall, ClientProcess if (ext == null) ext = "pdf"; try { - File exportFile = File.createTempFile(makePrefix(jasperPrint.getName()), "." + ext); + File exportFile = File.createTempFile(FileUtil.makePrefix(jasperPrint.getName()), "." + ext); try (FileOutputStream outputStream = new FileOutputStream(exportFile);) { @@ -833,19 +826,6 @@ public class ReportStarter implements ProcessCall, ClientProcess return viewerLauncher; } - private String makePrefix(String name) { - StringBuilder prefix = new StringBuilder(); - char[] nameArray = name.toCharArray(); - for (char ch : nameArray) { - if (Character.isLetterOrDigit(ch)) { - prefix.append(ch); - } else { - prefix.append("_"); - } - } - return prefix.toString(); - } - private WebResourceLoader getWebResourceLoader() { if (webResourceLoader == null) webResourceLoader = new WebResourceLoader(getLocalDownloadFolder()); diff --git a/org.adempiere.server-feature/hazelcast-template.xml b/org.adempiere.server-feature/hazelcast-template.xml index 0b25ff8b56..db007b3c7b 100644 --- a/org.adempiere.server-feature/hazelcast-template.xml +++ b/org.adempiere.server-feature/hazelcast-template.xml @@ -78,7 +78,7 @@ walk through all available discovery strategies and detect the correct one for the current runtime environment. --> - + - + diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/AdempiereWebUI.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/AdempiereWebUI.java index 79a3ec9a1b..077284ef08 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/AdempiereWebUI.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/AdempiereWebUI.java @@ -502,6 +502,15 @@ public class AdempiereWebUI extends Window implements EventListener, IWeb boolean isAdminLogin = false; if (desktop.getSession().getAttribute(ISSOPrincipalService.SSO_ADMIN_LOGIN) != null) isAdminLogin = (boolean)desktop.getSession().getAttribute(ISSOPrincipalService.SSO_ADMIN_LOGIN); + + boolean isSSOLogin = "Y".equals(Env.getContext(Env.getCtx(), Env.IS_SSO_LOGIN)); + String ssoLogoutURL = null; + if (!isAdminLogin && isSSOLogin) + { + ISSOPrincipalService service = SSOUtils.getSSOPrincipalService(); + ssoLogoutURL = service.getLogoutURL(); + } + final Session session = logout0(); //clear context, invalidate session @@ -510,7 +519,21 @@ public class AdempiereWebUI extends Window implements EventListener, IWeb desktop.setAttribute(DESKTOP_SESSION_INVALIDATED_ATTR, Boolean.TRUE); //redirect to login page - Executions.sendRedirect(isAdminLogin ? "admin.zul" : "index.zul"); + if (isAdminLogin) + { + Executions.sendRedirect("admin.zul"); + } + else + { + if (isSSOLogin && !Util.isEmpty(ssoLogoutURL, true)) + { + Executions.sendRedirect(ssoLogoutURL); + } + else + { + Executions.sendRedirect("index.zul"); + } + } try { desktopCache.removeDesktop(desktop); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/WLogin.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/WLogin.java index 2ac97b6ae2..17e0e4c97a 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/WLogin.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/WLogin.java @@ -22,6 +22,7 @@ import javax.servlet.ServletRequest; import org.adempiere.webui.part.AbstractUIPart; import org.adempiere.webui.theme.ThemeManager; import org.adempiere.webui.window.LoginWindow; +import org.compiere.model.MSysConfig; import org.zkoss.web.servlet.Servlets; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.Executions; @@ -85,12 +86,16 @@ public class WLogin extends AbstractUIPart if (ua.contains("ipad") || ua.contains("iphone") || ua.contains("android")) mobile = true; } + + var leftPanelConfig = MSysConfig.getValue(MSysConfig.APPLICATION_LOGIN_LEFT_PANEL_SHOWN, "Y"); West west = layout.getWest(); if (west.getFirstChild() != null && west.getFirstChild().getFirstChild() != null) { west.setCollapsible(true); west.setSplittable(true); - if (mobile) { + if(leftPanelConfig.equals("I")) { + west.setVisible(false); + }else if (mobile || leftPanelConfig.equals("H")) { west.setOpen(false); } } else { diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/acct/WAcctViewer.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/acct/WAcctViewer.java index 312313fead..313634d961 100755 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/acct/WAcctViewer.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/acct/WAcctViewer.java @@ -752,11 +752,11 @@ public class WAcctViewer extends Window implements EventListener { boolean visible = m_data.documentQuery && tabResult.isSelected(); - bRePost.setVisible(visible); + bRePost.setVisible(visible && !Env.isReadOnlySession()); bExport.setVisible(tabResult.isSelected()); bZoom.setVisible(tabResult.isSelected()); - forcePost.setVisible(visible); + forcePost.setVisible(visible && !Env.isReadOnlySession()); } // stateChanged /** diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/ADTabpanel.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/ADTabpanel.java index 7d4e471be1..c9d93742f0 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/ADTabpanel.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/ADTabpanel.java @@ -29,7 +29,6 @@ import java.util.logging.Level; import org.adempiere.base.Core; import org.adempiere.exceptions.AdempiereException; -import org.adempiere.exceptions.DBException; import org.adempiere.util.Callback; import org.adempiere.webui.AdempiereIdGenerator; import org.adempiere.webui.AdempiereWebUI; @@ -1380,23 +1379,9 @@ DataStatusListener, IADTabpanel, IdSpace, IFieldEditorContainer public void query (boolean onlyCurrentRows, int onlyCurrentDays, int maxRows) { boolean open = gridTab.isOpen(); - try - { - gridTab.query(onlyCurrentRows, onlyCurrentDays, maxRows); - if (listPanel.isVisible() && !open) - gridTab.getTableModel().fireTableDataChanged(); - } - catch (Exception e) - { - if (DBException.isTimeout(e)) - { - throw e; - } - else - { - Dialog.error(windowNo, e.getMessage()); - } - } + gridTab.query(onlyCurrentRows, onlyCurrentDays, maxRows); + if (listPanel.isVisible() && !open) + gridTab.getTableModel().fireTableDataChanged(); } /** @@ -1536,7 +1521,7 @@ DataStatusListener, IADTabpanel, IdSpace, IFieldEditorContainer } else if (treePanel != null && event.getTarget() == treePanel.getTree()) { Treeitem item = treePanel.getTree().getSelectedItem(); - if (item.getValue() != null) + if (item != null && item.getValue() != null) navigateTo((DefaultTreeNode)item.getValue()); } else if (ON_DEFER_SET_SELECTED_NODE.equals(event.getName())) { @@ -1612,13 +1597,17 @@ DataStatusListener, IADTabpanel, IdSpace, IFieldEditorContainer if (detailPane.getParent() == null) { formContainer.appendSouth(detailPane); } + IADTabpanel tabPanel = detailPane.getSelectedADTabpanel(); if (tabPanel != null) { - if (!tabPanel.isActivated()) { + if (!tabPanel.isActivated() || !detailPane.isVisible()) { + if (!detailPane.isVisible()) + detailPane.setVisible(true); tabPanel.activate(true); - } else { + } else if (tabPanel.getGridView() != null){ tabPanel.getGridView().invalidateGridView(); } + if (!tabPanel.isGridView()) { if (detailPane.getSelectedPanel().isToggleToFormView()) { detailPane.getSelectedPanel().afterToggle(); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/AbstractADWindowContent.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/AbstractADWindowContent.java index 13dd8c1929..13dc6ee9dd 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/AbstractADWindowContent.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/adwindow/AbstractADWindowContent.java @@ -21,6 +21,8 @@ import static org.compiere.model.MSysConfig.ZK_GRID_AFTER_FIND; import static org.compiere.model.SystemIDs.PROCESS_AD_CHANGELOG_REDO; import static org.compiere.model.SystemIDs.PROCESS_AD_CHANGELOG_UNDO; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; import java.text.MessageFormat; import java.util.ArrayList; @@ -32,6 +34,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.stream.Collectors; @@ -503,7 +506,7 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements * Execute zoom to detail tab. * @param gTab GridTab of tab at tabIndex * @param query detail tab query - * @param tabIndex + * @param tabIndex target tab index * @return true if successfully zoom to detail tab */ private boolean doZoomToDetail(GridTab gTab, MQuery query, int tabIndex) { @@ -515,26 +518,27 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements gridWindow.initTab(tabIndex); //init parent tab by parent ids StringBuilder sql = new StringBuilder("SELECT ").append(gTab.getLinkColumnName()).append(" FROM ").append(gTab.getTableName()).append(" WHERE ").append(query.getWhereClause()); - List parentIds = DB.getSQLValueObjectsEx(null, sql.toString()); + List> parentIds = DB.getSQLArrayObjectsEx(null, sql.toString()); if (parentIds!=null && parentIds.size() > 0) { - GridTab parentTab = null; + //Tab Index:MQuery MapqueryMap = new TreeMap(); - for (Object parentId : parentIds) + for (ListparentIdList : parentIds) { + Object parentId = parentIdList.get(0); + //Tab Index:(ColumnName,Value) MapparentMap = new TreeMap(); int index = tabIndex; Object oldpid = parentId; GridTab currentTab = gTab; + //walk backward to level 0 tab while (index > 0) { index--; GridTab pTab = gridWindow.getTab(index); if (pTab.getTabLevel() < currentTab.getTabLevel()) { - if (parentTab == null) - parentTab = pTab; gridWindow.initTab(index); if (index > 0) { @@ -568,43 +572,82 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements { GridTab pTab = gridWindow.getTab(entry.getKey()); Object[] value = entry.getValue(); - MQuery pquery = queryMap.get(entry.getKey()); - if (pquery == null) - { - pquery = new MQuery(pTab.getAD_Table_ID()); - queryMap.put(entry.getKey(), pquery); - pquery.addRestriction((String)value[0], "=", value[1]); - } - else - { - pquery.addRestriction((String)value[0], "=", value[1], null, null, false, 0); - } + MQuery pquery = new MQuery(pTab.getAD_Table_ID()); + queryMap.put(entry.getKey(), pquery); + pquery.addRestriction((String)value[0], "=", value[1]); } } //execute query for each parent tab + GridTab pTab = null; for (Map.Entry entry : queryMap.entrySet()) { - GridTab pTab = gridWindow.getTab(entry.getKey()); + pTab = gridWindow.getTab(entry.getKey()); IADTabpanel tp = adTabbox.findADTabpanel(pTab); tp.createUI(); if (tp.getTabLevel() == 0) { pTab.setQuery(entry.getValue()); tp.query(); + //update context + if (pTab.getRowCount() > 0) + { + boolean pTabUpdateWindowContext = pTab != null ? pTab.isUpdateWindowContext(): false ; + if (pTab != null && !pTabUpdateWindowContext) + pTab.setUpdateWindowContext(true); + pTab.setCurrentRow(0, false); + if (pTab != null && !pTabUpdateWindowContext) + pTab.setUpdateWindowContext(false); + } } else - { - tp.query(); - pTab.setQuery(entry.getValue()); + { + //retrieve records of sub tab tp.query(); + //find sub tab row that match the stored query + MQuery tabQuery = entry.getValue(); + int rowFound = -1; + for(int i = 0; i < pTab.getRowCount(); i++) + { + Object tabValue = pTab.getValue(i, tabQuery.getColumnName(0)); + if (tabValue != null && tabQuery.getCode(0) != null) + { + //handle potential difference in numeric datatype, for e.g integer vs bigdecimal + if (tabQuery.getColumnName(0).endsWith("_ID") && tabValue instanceof Number n1 && tabQuery.getCode(0) instanceof Number n2) + { + if (n1.intValue() == n2.intValue()) + { + rowFound = i; + break; + } + } + else if (tabValue.equals(tabQuery.getCode(0))) + { + rowFound = i; + break; + } + } + } + if (rowFound == -1) + { + //fall back to execution of stored query + pTab.setQuery(entry.getValue()); + tp.query(); + rowFound = 0; + } + //update context + if (pTab.getRowCount() > 0) { + boolean pTabUpdateWindowContext = pTab != null ? pTab.isUpdateWindowContext(): false ; + if (pTab != null && !pTabUpdateWindowContext) + pTab.setUpdateWindowContext(true); + pTab.setCurrentRow(rowFound, false); + if (pTab != null && !pTabUpdateWindowContext) + pTab.setUpdateWindowContext(false); + } } } //execute query for detail tab - MQuery targetQuery = new MQuery(gTab.getAD_Table_ID()); - targetQuery.addRestriction(gTab.getLinkColumnName(), "=", parentTab.getRecord_ID()); - gTab.setQuery(targetQuery); IADTabpanel gc = null; gc = adTabbox.findADTabpanel(gTab); gc.createUI(); @@ -648,7 +691,13 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements } if (id != null && id.equals(query.getZoomValue())) { + //make sure last parent tab will update window context + boolean pTabUpdateWindowContext = pTab != null ? pTab.isUpdateWindowContext(): false ; + if (pTab != null && !pTabUpdateWindowContext) + pTab.setUpdateWindowContext(true); setActiveTab(gridWindow.getTabIndex(gTab), null); + if (pTab != null && !pTabUpdateWindowContext) + pTab.setUpdateWindowContext(false); gTab.navigate(i); return true; } @@ -697,6 +746,7 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements final GridTab gTab = gridWindow.getTab(tabIndex); Env.setContext(ctx, curWindowNo, tabIndex, GridTab.CTX_TabLevel, Integer.toString(gTab.getTabLevel())); + AtomicBoolean zoomQuery = new AtomicBoolean(false); // Query first tab if (tabIndex == 0) { @@ -729,6 +779,8 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements initQueryOnNew(result); } + if (query != null && query == result) + zoomQuery.set(true); } }); @@ -771,6 +823,15 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements //fallback to ADTabpanel ADTabpanel fTabPanel = new ADTabpanel(); initTabPanel(query, tabIndex, gTab, fTabPanel); + // force single row mode for zoom query that return 1 record + if (query != null && zoomQuery.get()) + { + if (gTab.getRowCount() == 1 && !gTab.isNew() && adTabbox.getSelectedTabpanel().isGridView() + && adTabbox.getSelectedTabpanel().getGridTab() == gTab) + { + adTabbox.getSelectedTabpanel().switchRowPresentation(); + } + } } return gTab; @@ -853,15 +914,9 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements } } // - StringBuffer sql = new StringBuffer("SELECT COUNT(*) FROM ") - .append(mTab.getTableName()); - if (where.length() > 0) - sql.append(" WHERE ").append(where); - String finalSQL = MRole.getDefault().addAccessSQL(sql.toString(), - mTab.getTableName(), MRole.SQL_NOTQUALIFIED, MRole.SQL_RO); - int no = DB.getSQLValue(null, finalSQL.toString()); - // - require = mTab.isQueryRequire(no); + int no = getRecordCount(mTab, where); + // show find dialog if count timeout/exception + require = no == -1 ? true : mTab.isQueryRequire(no); } // Show find window (if required) @@ -916,6 +971,35 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements } } // initialQuery + /** + * Get record count + * @param mTab + * @param where + * @return record count + */ + private int getRecordCount(GridTab mTab, StringBuffer where) { + StringBuffer sql = new StringBuffer("SELECT COUNT(*) FROM ") + .append(mTab.getTableName()); + if (where.length() > 0) + sql.append(" WHERE ").append(where); + String finalSQL = MRole.getDefault().addAccessSQL(sql.toString(), + mTab.getTableName(), MRole.SQL_NOTQUALIFIED, MRole.SQL_RO); + int no = -1; + int timeout = MSysConfig.getIntValue(MSysConfig.GRIDTABLE_INITIAL_COUNT_TIMEOUT_IN_SECONDS, + GridTable.DEFAULT_GRIDTABLE_COUNT_TIMEOUT_IN_SECONDS, Env.getAD_Client_ID(Env.getCtx())); + try (PreparedStatement stmt = DB.prepareStatement(finalSQL, null)) { + if (timeout > 0) + stmt.setQueryTimeout(timeout); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) + no = rs.getInt(1); + } catch (SQLException e) { + logger.log(Level.WARNING, e.getMessage(), e); + no = -1; + } + return no; + } + /** * Setup find window UI properties * @param findWindow @@ -1572,6 +1656,7 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements public void saveAndNavigate(final Callback callback) { if (adTabbox != null) { + boolean newrecod = adTabbox.getSelectedGridTab().isNew(); if (adTabbox.isSortTab()) { onSave(false, true, callback); @@ -1586,12 +1671,16 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements { // new record, but nothing changed adTabbox.dataIgnore(); + if (newrecod) + onRefresh(true, false); callback.onCallback(true); } } // there is a change else { // just in case adTabbox.dataIgnore(); + if (newrecod) + onRefresh(true, false); callback.onCallback(true); } } @@ -1742,7 +1831,16 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements { //ignore non-ui thread event. if (Executions.getCurrent() == null) + { + // Re-post incremental loading event to UI thread + if (e.isLoading() && e.getSource() != null && e.getSource().equals(adTabbox.getSelectedGridTab().getTableModel())) + { + Executions.schedule(getComponent().getDesktop(), evt -> { + this.dataStatusChanged(e); + }, new Event("onAsynchronousDataStatusChanged")); + } return; + } boolean detailTab = false; if (e.getSource() instanceof GridTable) @@ -4298,6 +4396,12 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements * @param pi */ private void onModalClose(ProcessInfo pi) { + if (getActiveGridTab().isQuickForm){ + statusBarQF.setStatusLine(null); + }else{ + statusBar.setStatusLine(null); + } + boolean notPrint = pi != null && pi.getAD_Process_ID() != adTabbox.getSelectedGridTab().getAD_Process_ID() && pi.isReportingProcess() == false; diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/AEnv.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/AEnv.java index f2373144ea..fe5ece4fba 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/AEnv.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/AEnv.java @@ -938,7 +938,7 @@ public final class AEnv */ public static String getZoomUrlTableUU(PO po) { - return getApplicationUrl() + "?Action=Zoom&AD_Table_ID=" + po.get_Table_ID() + "&Record_UU=" + po.get_UUID(); + return getApplicationUrl() + "?Action=Zoom&TableName=" + po.get_TableName() + "&Record_UU=" + po.get_UUID(); } /** @@ -947,7 +947,7 @@ public final class AEnv */ public static String getZoomUrlTableID(PO po) { - return getApplicationUrl() + "?Action=Zoom&AD_Table_ID=" + po.get_Table_ID() + "&Record_ID=" + po.get_ID(); + return getApplicationUrl() + "?Action=Zoom&TableName=" + po.get_TableName() + "&Record_ID=" + po.get_ID(); } /** diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/AbstractProcessDialog.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/AbstractProcessDialog.java index eb388180f6..45957c5d72 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/AbstractProcessDialog.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/AbstractProcessDialog.java @@ -511,10 +511,9 @@ public abstract class AbstractProcessDialog extends Window implements IProcessUI //summary option chbIsSummary = new Checkbox(); chbIsSummary.setSclass("option-input-parameter"); + chbIsSummary.setLabel(Msg.translate(Env.getCtx(), "Summary")); Label lPrintFormat = new Label(Msg.translate(Env.getCtx(), "AD_PrintFormat_ID")); lPrintFormat.setSclass("option-input-parameter print-format-label"); - Label lIsSummary = new Label(Msg.translate(Env.getCtx(), "Summary")); - lIsSummary.setSclass("option-input-parameter"); //print formats MClient client = MClient.get(m_ctx); @@ -531,7 +530,6 @@ public abstract class AbstractProcessDialog extends Window implements IProcessUI } fPrintFormat.getComponent().setSclass("option-input-parameter print-format-list"); fPrintFormat.getComponent().setPlaceholder(lPrintFormat.getValue()); - reportOptionLayout.appendChild(lIsSummary); reportOptionLayout.appendChild(chbIsSummary); } @@ -1421,6 +1419,7 @@ public abstract class AbstractProcessDialog extends Window implements IProcessUI Env.setContext(m_ctx, Env.LANGUAGE, ctx.getProperty(Env.LANGUAGE)); Env.setContext(m_ctx, Env.AD_USER_ID, ctx.getProperty(Env.AD_USER_ID)); Env.setContext(m_ctx, Env.DATE, ctx.getProperty(Env.DATE)); + Env.setContext(m_ctx, Env.AD_SESSION_ID, ctx.getProperty(Env.AD_SESSION_ID)); } @Override diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/DocumentSearchController.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/DocumentSearchController.java index 6cc82161fa..6dd12f4afc 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/DocumentSearchController.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/DocumentSearchController.java @@ -29,6 +29,7 @@ import org.compiere.model.MColumn; import org.compiere.model.MLookup; import org.compiere.model.MLookupFactory; import org.compiere.model.MLookupInfo; +import org.compiere.model.MPayment; import org.compiere.model.MQuery; import org.compiere.model.MRole; import org.compiere.model.MSearchDefinition; @@ -282,14 +283,27 @@ public class DocumentSearchController implements EventListener{ if (sql != null) { if (powindow != null) { - if (window != null) { - doRetrieval(msd, sql, params, lookup, window, table.getTableName(), " AND IsSOTrx='Y' ", list); + String whereCol = null; + if (table.columnExistsInDictionary("IsSOTrx")) { + whereCol = " AND IsSOTrx="; + } else { + if (MPayment.Table_Name.equals(table.getTableName())) { + whereCol = " AND IsReceipt="; + } + } + if (whereCol == null) { + doRetrieval(msd, sql, params, lookup, powindow, table.getTableName(), null, list); + } else { + if (window != null) { + String soWhereTrx = whereCol + "'Y' "; + doRetrieval(msd, sql, params, lookup, window, table.getTableName(), soWhereTrx, list); + } + String poWhereTrx = whereCol + "'N' "; + doRetrieval(msd, sql, params, lookup, powindow, table.getTableName(), poWhereTrx, list); } - doRetrieval(msd, sql, params, lookup, powindow, table.getTableName(), " AND IsSOTrx='N' ", list); } else if (window != null) { doRetrieval(msd, sql, params, lookup, window, table.getTableName(), null, list); } - } } return list; diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/GlobalSearch.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/GlobalSearch.java index 8876e6ce6d..ea17c0128e 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/GlobalSearch.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/GlobalSearch.java @@ -101,8 +101,9 @@ public class GlobalSearch extends Div implements EventListener { bandbox.setCtrlKeys("#up#down"); bandbox.addEventListener(Events.ON_CTRL_KEY, this); bandbox.addEventListener(Events.ON_FOCUS, e -> { - if (!bandbox.isOpen()) - bandbox.setOpen(true); + bandbox.setOpen(true); + if (tabbox.getSelectedIndex() == 0) + menuController.updateRecentItems(); }); Bandpopup popup = new Bandpopup(); @@ -224,6 +225,8 @@ public class GlobalSearch extends Div implements EventListener { } } } else if (event.getName().equals(Events.ON_SELECT)) { + if (tabbox.getSelectedIndex() == 0) + menuController.updateRecentItems(); String value = (String) bandbox.getAttribute(LAST_ONCHANGING_ATTR); if (value == null) { value = bandbox.getValue(); @@ -254,7 +257,7 @@ public class GlobalSearch extends Div implements EventListener { * Handle client info event from browser. */ public void onClientInfo() { - ZKUpdateUtil.setWindowHeightX(bandbox.getDropdown(), ClientInfo.get().desktopHeight-50); + ZKUpdateUtil.setWindowHeightX(bandbox.getDropdown(), ClientInfo.get().desktopHeight-100); } /** diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/MenuSearchController.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/MenuSearchController.java index 28530e2dbb..327662c66c 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/MenuSearchController.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/MenuSearchController.java @@ -31,8 +31,11 @@ import org.adempiere.webui.util.TreeNodeAction; import org.adempiere.webui.util.TreeUtils; import org.adempiere.webui.util.ZKUpdateUtil; import org.compiere.model.MMenu; +import org.compiere.model.MPreference; import org.compiere.model.MToolBarButtonRestrict; import org.compiere.model.MTreeNode; +import org.compiere.model.Query; +import org.compiere.model.SystemIDs; import org.compiere.util.Env; import org.compiere.util.Msg; import org.compiere.util.Util; @@ -68,6 +71,9 @@ import org.zkoss.zul.impl.LabelImageElement; */ public class MenuSearchController implements EventListener{ + /** Initial number of menu items loaded into listbox */ + private static final int INITIAL_LOADING_SIZE = 50; + /** Component attribute to hold reference of {@link MTreeNode} **/ public static final String M_TREE_NODE_ATTR = "MTreeNode"; @@ -77,7 +83,7 @@ public class MenuSearchController implements EventListener{ private static final String Z_ICON_STAR = "z-icon-star"; /** Event echo from {@link #search(String)} to initiate search action **/ private static final String ON_SEARCH_ECHO_EVENT = "onSearchEcho"; - /** Event to load all menu items into {@link #listbox}. Default is to load the first 50 only. **/ + /** Event to load all menu items into {@link #listbox}. Default is to load the first {@link #INITIAL_LOADING_SIZE} only. **/ private static final String ON_LOAD_MORE_EVENT = "onLoadMore"; /** {@link Listitem} attribute to store the last timestamp of ON_CLICK or ON_SELECT event **/ private static final String ONSELECT_TIMESTAMP_ATTR = "onselect.timestamp"; @@ -100,20 +106,58 @@ public class MenuSearchController implements EventListener{ private String highlightText = null; + /** List of recently access menu items (AD_Menu_ID) */ + private List recentMenuItemIds = new ArrayList<>(); + /** Event post from {@link #selectTreeitem(Object, Boolean)} **/ private static final String ON_POST_SELECT_TREEITEM_EVENT = "onPostSelectTreeitem"; /** - * @param tree + * @param tree usually the tree instance from {@link} */ public MenuSearchController(Tree tree) { this.tree = tree; } + /** + * Load recently access menu items + */ + private List loadRecentItems() { + List recents = new ArrayList(); + int AD_User_ID = Env.getAD_User_ID(Env.getCtx()); + int AD_Role_ID = Env.getAD_Role_ID(Env.getCtx()); + int AD_Org_ID = 0; + String attribute = AD_Role_ID+"|RecentMenuItems"; + Query query = new Query(Env.getCtx(), MPreference.Table_Name, "PreferenceFor=? AND Attribute=? AND AD_Org_ID=? AND AD_User_ID=? AND AD_Window_ID=?", null); + MPreference preference = query.setClient_ID().setParameters("W", attribute, AD_Org_ID, AD_User_ID, SystemIDs.WINDOW_MENU).first(); + if (preference != null) { + String[] recentItems = preference.getValue().split("[,]"); + for (String recentItem : recentItems) { + recents.add(recentItem); + } + } + return recents; + } + + /** + * If there are changes in the recent menu items for user, reload and update menu items model + */ + public void updateRecentItems() { + List recents = loadRecentItems(); + if (!recents.equals(recentMenuItemIds)) { + recentMenuItemIds = recents; + sortMenuItemModel(); + moveRecentItems(); + if (fullModel != null) + updateListboxModel(model); + } + } + /** * Populate {@link #model} from {@link #tree} */ public void refreshModel() { + recentMenuItemIds = loadRecentItems(); final List list = new ArrayList(); if (tree.getModel() == null) { TreeUtils.traverse(tree, new TreeItemAction() { @@ -130,8 +174,15 @@ public class MenuSearchController implements EventListener{ }); } model = new ListModelList(list, true); - model.sort(new Comparator() { - + sortMenuItemModel(); + moveRecentItems(); + } + + /** + * Sort menu items model in alphabetical order + */ + private void sortMenuItemModel() { + model.sort(new Comparator() { @Override public int compare(MenuItem o1, MenuItem o2) { return o1.getLabel().compareTo(o2.getLabel()); @@ -139,6 +190,35 @@ public class MenuSearchController implements EventListener{ }, true); } + /** + * Move the 7 most recently access menu items to the top of menu items model + */ + private void moveRecentItems() { + if (recentMenuItemIds.size() > 0) { + List recents = new ArrayList(); + for(String id : recentMenuItemIds) { + for(int i = 0; i < model.getSize(); i++) { + if (model.get(i).getData() instanceof Treeitem ti) { + if (ti.getValue() instanceof String tis) { + if (tis.equals(id)) { + recents.add(model.get(i)); + break; + } + } + } + } + } + if (recents.size() > 0) { + for (MenuItem mi : recents) { + model.remove(mi); + } + for(int i = recents.size()-1; i >= 0; i--) { + model.add(0, recents.get(i)); + } + } + } + } + /** * Add treeNode to list * @param list @@ -373,16 +453,16 @@ public class MenuSearchController implements EventListener{ /** * Load {@link #fullModel} to {@link #listbox}. - * Only first 50 loaded to {@link #listbox} initially. + * Only first {@link #INITIAL_LOADING_SIZE} loaded to {@link #listbox} initially. */ private void loadMore() { ListModel listModel = listbox.getModel(); ListModelList lml = (ListModelList) listModel; lml.remove(lml.size()-1); - List subList = fullModel.subList(50, fullModel.size()); + List subList = fullModel.subList(INITIAL_LOADING_SIZE, fullModel.size()); lml.addAll(subList); fullModel = null; - listbox.setSelectedIndex(50); + listbox.setSelectedIndex(INITIAL_LOADING_SIZE); Clients.scrollIntoView(listbox.getSelectedItem()); } @@ -430,8 +510,8 @@ public class MenuSearchController implements EventListener{ } /** - * Handle {@link #ON_POST_SELECT_TREEITEM_EVENT} event. - * Post ON_CLICK event to link ({@link A} or {@link Treerow}). + * Handle {@link #ON_POST_SELECT_TREEITEM_EVENT} event.
+ * Post ON_CLICK event to link ({@link A} or {@link Treerow}, handle in {@link AbstractMenuPanel}). * @param newRecord */ private void onPostSelectTreeitem(Boolean newRecord) { @@ -441,9 +521,10 @@ public class MenuSearchController implements EventListener{ event = new Event(Events.ON_CLICK, tree.getSelectedItem().getTreerow().getFirstChild().getFirstChild(), newRecord); } else - { + { event = new Event(Events.ON_CLICK, tree.getSelectedItem().getTreerow(), newRecord); } + Events.postEvent(event); } @@ -474,15 +555,15 @@ public class MenuSearchController implements EventListener{ /** * Update {@link #listbox} with newModel. - * If newModel has > 50 items, only first 50 is loaded into {@link #listbox}. + * If newModel has > {@link #INITIAL_LOADING_SIZE} items, only first {@link #INITIAL_LOADING_SIZE} is loaded into {@link #listbox}. * User has to click the load more link (...) to load the rest of the items into {@link #listbox}. * @param newModel */ private void updateListboxModel(ListModelList newModel) { fullModel = null; - if (newModel.size() > 50) { + if (newModel.size() > INITIAL_LOADING_SIZE) { List list = newModel.getInnerList(); - List subList = list.subList(0, 50); + List subList = list.subList(0, INITIAL_LOADING_SIZE); fullModel = newModel; newModel = new ListModelList(subList.toArray(new MenuItem[0])); MenuItem more = new MenuItem(); @@ -587,7 +668,8 @@ public class MenuSearchController implements EventListener{ int count = listbox.getItemCount(); for(int i = 0; i < count; i++) { ListItem item = listbox.getItemAtIndex(i); - String label = item.getLabel(); + MenuItem menuItem = item.getValue(); + String label = menuItem.getLabel(); if (Util.isEmpty(label)) continue; if (label.equalsIgnoreCase(text)) { exact = item; @@ -597,11 +679,13 @@ public class MenuSearchController implements EventListener{ } } if (exact != null) { - textbox.setText(exact.getLabel()); + MenuItem menuItem = exact.getValue(); + textbox.setText(menuItem.getLabel()); onSelect(exact, false); return true; } else if (firstStart != null) { - textbox.setText(firstStart.getLabel()); + MenuItem menuItem = firstStart.getValue(); + textbox.setText(menuItem.getLabel()); onSelect(firstStart, false); return true; } @@ -674,7 +758,7 @@ public class MenuSearchController implements EventListener{ } item.appendChild(cell); - cell.setTooltip(data.getDescription()); + cell.setTooltiptext(data.getDescription()); item.setValue(data); item.addEventListener(Events.ON_CLICK, MenuSearchController.this); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/ProcessParameterPanel.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/ProcessParameterPanel.java index d144f70c1a..199a74727d 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/ProcessParameterPanel.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/ProcessParameterPanel.java @@ -104,7 +104,7 @@ public class ProcessParameterPanel extends Panel implements /** * generated serial id */ - private static final long serialVersionUID = -8847249274740131848L; + private static final long serialVersionUID = -8476698839617674953L; /** Event post from {@link #valueChange(ValueChangeEvent)} **/ private static final String ON_POST_EDITOR_VALUE_CHANGE_EVENT = "onPostEditorValueChange"; @@ -1327,6 +1327,11 @@ public class ProcessParameterPanel extends Panel implements m_processInfo = processInfo; } + + public ProcessInfo getProcessInfo() { + return m_processInfo; + } + /** * focus to first visible field editor. * @return true if there is at least one visible field editor. @@ -1442,6 +1447,7 @@ public class ProcessParameterPanel extends Panel implements if (displayType == DisplayType.Text || displayType == DisplayType.Memo || displayType == DisplayType.TextLong + || displayType == DisplayType.JSON || displayType == DisplayType.Binary || displayType == DisplayType.RowID || editor.getGridField().isEncrypted()) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/WProcessCtl.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/WProcessCtl.java index 789c5e17a4..cb18febf83 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/WProcessCtl.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/WProcessCtl.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.logging.Level; +import org.adempiere.exceptions.AdempiereException; import org.adempiere.util.IProcessUI; import org.adempiere.webui.ISupportMask; import org.adempiere.webui.LayoutUtils; @@ -91,6 +92,8 @@ public class WProcessCtl extends AbstractProcessCtl { } catch (Exception e) { + if (Env.isReadOnlySession()) + throw new AdempiereException(Msg.getMsg(Env.getCtx(), "ReadOnlySession")); pi.setSummary (e.getLocalizedMessage()); pi.setError (true); log.warning(pi.toString()); @@ -168,6 +171,8 @@ public class WProcessCtl extends AbstractProcessCtl { } catch (Exception e) { + if (Env.isReadOnlySession()) + throw new AdempiereException(Msg.getMsg(Env.getCtx(), "ReadOnlySession")); pi.setSummary (e.getLocalizedMessage()); pi.setError (true); log.warning(pi.toString()); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WPayPrint.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WPayPrint.java index 8a5fb6b033..5e921adfbc 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WPayPrint.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WPayPrint.java @@ -436,10 +436,9 @@ public class WPayPrint extends PayPrint implements IFormController, EventListene if (no >= 0) { // Get File Info - tempFile = File.createTempFile(m_PaymentExport.getFilenamePrefix(), m_PaymentExport.getFilenameSuffix()); - filenameForDownload = m_PaymentExport.getFilenamePrefix() + m_PaymentExport.getFilenameSuffix(); - + tempFile = File.createTempFile(m_PaymentExport.getFilenamePrefix(), null); no = m_PaymentExport.exportToFile(m_checks,(Boolean) fDepositBatch.getValue(),PaymentRule, tempFile, err); + filenameForDownload = m_PaymentExport.getFilenamePrefix() + m_PaymentExport.getFilenameSuffix(); } if (no >= 0) { diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/FavoriteSimpleTreeModel.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/FavoriteSimpleTreeModel.java index 680427b055..343ecdfac0 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/FavoriteSimpleTreeModel.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/FavoriteSimpleTreeModel.java @@ -15,19 +15,12 @@ import java.util.List; import java.util.Objects; import java.util.logging.Level; -import org.adempiere.util.Callback; import org.adempiere.webui.ClientInfo; -import org.adempiere.webui.adwindow.ADTabpanel; -import org.adempiere.webui.adwindow.ADWindow; -import org.adempiere.webui.desktop.AbstractDesktop; import org.adempiere.webui.desktop.FavouriteController; -import org.adempiere.webui.desktop.IDesktop; import org.adempiere.webui.exception.ApplicationException; import org.adempiere.webui.session.SessionManager; import org.adempiere.webui.theme.ThemeManager; import org.compiere.model.MMenu; -import org.compiere.model.MQuery; -import org.compiere.model.MTable; import org.compiere.model.MToolBarButtonRestrict; import org.compiere.model.MTreeNode; import org.compiere.util.CLogger; @@ -331,27 +324,7 @@ public class FavoriteSimpleTreeModel extends SimpleTreeModel implements EventLis { try { - MMenu menu = (MMenu) MTable.get(Env.getCtx(), MMenu.Table_ID).getPO(menuID, null); - IDesktop desktop = SessionManager.getAppDesktop(); - if (desktop instanceof AbstractDesktop) - ((AbstractDesktop)desktop).setPredefinedContextVariables(menu.getPredefinedContextVariables()); - - MQuery query = new MQuery(""); - query.addRestriction("1=2"); - query.setRecordCount(0); - - SessionManager.getAppDesktop().openWindow(menu.getAD_Window_ID(), query, new Callback() { - @Override - public void onCallback(ADWindow result) - { - if (result == null) - return; - - result.getADWindowContent().onNew(); - ADTabpanel adtabpanel = (ADTabpanel) result.getADWindowContent().getADTab().getSelectedTabpanel(); - adtabpanel.focusToFirstEditor(false); - } - }); + SessionManager.getAppDesktop().onNewRecord(menuID); } catch (Exception e) { diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/WListbox.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/WListbox.java index 665a703671..8d8ef7ec62 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/WListbox.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/WListbox.java @@ -1234,7 +1234,19 @@ public class WListbox extends Listbox implements IMiniTable, TableValueChangeLis if(amt == null ) amt = Double.valueOf(0); total[col] = subtotal + amt; + } + else if (c == Integer.class) + { + Integer subtotal = Integer.valueOf(0); + if(total[col] != null) + subtotal = (Integer)(total[col]); + Integer amt = (Integer) data; + if(subtotal == null) + subtotal = Integer.valueOf(0); + if(amt == null ) + amt = Integer.valueOf(0); + total[col] = subtotal + amt; } } } @@ -1246,14 +1258,10 @@ public class WListbox extends Listbox implements IMiniTable, TableValueChangeLis for (int col = 0; col < layout.length; col++) { Class c = layout[col].getColClass(); - if (c == BigDecimal.class) + if (c == BigDecimal.class || c == Double.class || c == Integer.class) { setValueAt(total[col] , row - 1, col); } - else if (c == Double.class) - { - setValueAt(total[col] , row -1 , col); - } else { if(col == 0 ) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/CalendarWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/CalendarWindow.java index 4bd07f8d6a..fc1a58d3b7 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/CalendarWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/CalendarWindow.java @@ -314,7 +314,7 @@ public class CalendarWindow extends Window implements EventListener, ITab syncModel(); } } - else if (type.equals(ON_EVENT_CREATE_EVENT)) { + else if (type.equals(ON_EVENT_CREATE_EVENT) && !Env.isReadOnlySession()) { if (e instanceof CalendarsEvent) { CalendarsEvent calendarsEvent = (CalendarsEvent) e; RequestWindow requestWin = new RequestWindow(calendarsEvent, this); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPCalendar.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPCalendar.java index 633e61d315..15aaa09f93 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPCalendar.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPCalendar.java @@ -222,7 +222,7 @@ public class DPCalendar extends DashboardPanel implements EventListener, else if (e.getTarget() == divArrowRight) divArrowClicked(true); } - else if (type.equals(ON_EVENT_CREATE_EVENT)) { + else if (type.equals(ON_EVENT_CREATE_EVENT) && ! Env.isReadOnlySession()) { if (e instanceof CalendarsEvent) { CalendarsEvent calendarsEvent = (CalendarsEvent) e; RequestWindow requestWin = new RequestWindow(calendarsEvent, this); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPMenuTree.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPMenuTree.java index be4cce2dd3..e95fcc4440 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPMenuTree.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPMenuTree.java @@ -15,6 +15,7 @@ package org.adempiere.webui.dashboard; import org.adempiere.webui.panel.MenuTreePanel; +import org.adempiere.webui.util.ZKUpdateUtil; /** * Dashboard gadget: Menu Tree @@ -38,6 +39,7 @@ public class DPMenuTree extends DashboardPanel { menuTreePanel = new MenuTreePanel(this); this.appendChild(menuTreePanel); +// ZKUpdateUtil.setHeight(this, "100%"); } /** diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPRecentItems.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPRecentItems.java index 2b25d13744..af59ca00fa 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPRecentItems.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPRecentItems.java @@ -245,7 +245,7 @@ public class DPRecentItems extends DashboardPanel implements EventListener - * Identifies the action associated with the selected - * menu item and acts accordingly. + * Identifies the action associated with the selected menu item and acts accordingly.
+ * Event from favourite panel, global search and application menu tree will be routed here. * * @param menuId Identifier for the selected menu item * @@ -110,8 +119,78 @@ public abstract class AbstractDesktop extends AbstractUIPart implements IDesktop { setPredefinedContextVariables(null); } + updateRecentMenuItem(menuId); } + /** + * Open AD window in new record mode.
+ * Call by global search, application menu tree and favourite panel. + * @param menuId + */ + @Override + public void onNewRecord(int menuId) { + MMenu menu = new MMenu(Env.getCtx(), menuId, null); + setPredefinedContextVariables(menu.getPredefinedContextVariables()); + + MQuery query = new MQuery(""); + query.addRestriction("1=2"); + query.setRecordCount(0); + + SessionManager.getAppDesktop().openWindow(menu.getAD_Window_ID(), query, new Callback() { + @Override + public void onCallback(ADWindow result) { + if(result == null) + return; + + result.getADWindowContent().onNew(); + ADTabpanel adtabpanel = (ADTabpanel) result.getADWindowContent().getADTab().getSelectedTabpanel(); + adtabpanel.focusToFirstEditor(false); + } + }); + updateRecentMenuItem(menuId); + } + + /** + * Perform asynchronous update of recent menu items preference for user + * @param menuId + */ + protected void updateRecentMenuItem(int menuId) { + Runnable runnable = () -> { + int AD_User_ID = Env.getAD_User_ID(Env.getCtx()); + int AD_Role_ID = Env.getAD_Role_ID(Env.getCtx()); + int AD_Org_ID = 0; + String attribute = AD_Role_ID+"|RecentMenuItems"; + Query query = new Query(Env.getCtx(), MPreference.Table_Name, "PreferenceFor=? AND Attribute=? AND AD_Org_ID=? AND AD_User_ID=? AND AD_Window_ID=?", null); + MPreference preference = query.setClient_ID().setParameters("W", attribute, AD_Org_ID, AD_User_ID, SystemIDs.WINDOW_MENU).first(); + if (preference == null) { + preference = new MPreference(Env.getCtx(), 0, null); + preference.setAD_Org_ID(AD_Org_ID); + preference.setPreferenceFor("W"); + preference.setAttribute(attribute); + preference.setAD_User_ID(AD_User_ID); + preference.setValue(Integer.toString(menuId)); + preference.setAD_Window_ID(SystemIDs.WINDOW_MENU); + preference.saveEx(); + } else { + String recentItemValue = preference.getValue(); + List itemList = new ArrayList(); + String[] recentItemValues = recentItemValue.split("[,]"); + String menuIdValue = Integer.toString(menuId); + itemList.add(menuIdValue); + for (int i = 0; itemList.size() < 7 && i < recentItemValues.length; i++) { + if (!recentItemValues[i].equals(menuIdValue)) + itemList.add(recentItemValues[i]); + } + recentItemValue = itemList.stream().collect(Collectors.joining(",")); + preference.setValue(recentItemValue); + preference.saveEx(); + } + }; + Executions.schedule(getComponent().getDesktop(), e -> { + runnable.run(); + }, new Event("onUpdateRecentMenuItem")); + } + /** * @return {@link ClientInfo} */ diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/DashboardController.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/DashboardController.java index 3b6a460cc7..372c53a093 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/DashboardController.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/DashboardController.java @@ -39,6 +39,7 @@ import org.adempiere.webui.Extensions; import org.adempiere.webui.LayoutUtils; import org.adempiere.webui.apps.AEnv; import org.adempiere.webui.apps.BusyDialog; +import org.adempiere.webui.apps.DesktopRunnable; import org.adempiere.webui.apps.WReport; import org.adempiere.webui.apps.graph.IChartRendererService; import org.adempiere.webui.apps.graph.WGraph; @@ -336,7 +337,7 @@ public class DashboardController implements EventListener { } } }; - Adempiere.getThreadPoolExecutor().submit(cr); + Adempiere.getThreadPoolExecutor().submit(new DesktopRunnable(cr, parent.getDesktop())); } } @@ -543,7 +544,7 @@ public class DashboardController implements EventListener { // Dashboard content Hlayout dashboardLineLayout = null; int currentLineNo = 0; - int noOfLines = 0; + int maxPerLine = 0; int width = 100; try { @@ -562,13 +563,12 @@ public class DashboardController implements EventListener { } } - noOfLines = MDashboardPreference.getForSessionRowCount(isShowInDashboard, AD_User_ID, AD_Role_ID); if (ClientInfo.isMobile() && isShowInDashboard) { if (ClientInfo.maxWidth(ClientInfo.MEDIUM_WIDTH-1)) { if (ClientInfo.maxWidth(ClientInfo.SMALL_WIDTH-1)) { - noOfLines = 1; - } else if (noOfLines > 2) { - noOfLines = 2; + maxPerLine = 1; + } else { + maxPerLine = 2; } } } @@ -586,7 +586,7 @@ public class DashboardController implements EventListener { int lineNo = dp.getLine().intValue(); int flexGrow = (flexGrow = dp.getFlexGrow()) > 0 ? flexGrow : DEFAULT_FLEX_GROW; - if(dashboardLineLayout == null || currentLineNo != lineNo) + if(dashboardLineLayout == null || currentLineNo != lineNo || (maxPerLine > 0 && dashboardLineLayout.getChildren().size() == maxPerLine)) { dashboardLineLayout = new Hlayout(); dashboardLineLayout.setAttribute(LINE_ATTRIBUTE, lineNo); @@ -647,7 +647,7 @@ public class DashboardController implements EventListener { } } }; - Adempiere.getThreadPoolExecutor().submit(cr); + Adempiere.getThreadPoolExecutor().submit(new DesktopRunnable(cr, parent.getDesktop())); } } diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/IDesktop.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/IDesktop.java index e31c4f38b3..8bb70feddb 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/IDesktop.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/desktop/IDesktop.java @@ -49,11 +49,17 @@ public interface IDesktop extends UIPart { public ClientInfo getClientInfo(); /** - * + * Launch menu item * @param nodeId */ public void onMenuSelected(int nodeId); + /** + * Launch AD Window in new record mode + * @param menuId + */ + public void onNewRecord(int menuId); + /** * * @param window diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WEditorPopupMenu.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WEditorPopupMenu.java index 4910be5085..a7644c993b 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WEditorPopupMenu.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WEditorPopupMenu.java @@ -373,6 +373,14 @@ public class WEditorPopupMenu extends Menupopup implements EventListener } } + /** + * Remove drill assistant menu item + */ + public void showDrillAssistant(boolean show) { + if (drillItem != null) + drillItem.setVisible(show); + } + /** * Remove the new and update items from the menu - for ChosenList */ diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WJsonEditor.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WJsonEditor.java new file mode 100644 index 0000000000..b2db7f9c05 --- /dev/null +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WJsonEditor.java @@ -0,0 +1,46 @@ +package org.adempiere.webui.editor; + +import org.adempiere.webui.event.ValueChangeEvent; +import org.compiere.model.GridField; +import org.compiere.util.Util; + + +public class WJsonEditor extends WStringEditor { + + /** + * + * @param gridField + */ + public WJsonEditor(GridField gridField) { + super(gridField); + getComponent().setMultiline(true); + setChangeEventWhenEditing(false); + } + + /** + * + * @param gridField + * @param tableEditor + * @param editorConfiguration + */ + public WJsonEditor(GridField gridField, boolean tableEditor, IEditorConfiguration editorConfiguration) { + super(gridField, tableEditor, editorConfiguration); + getComponent().setMultiline(true); + setChangeEventWhenEditing(false); + } + + @Override + public void setValue(Object value) { + super.setValue(value); + + String prettyValue = null; + if (value != null && !Util.isEmpty(value.toString())) { + prettyValue = Util.prettifyJSONString(value.toString()); + if (! prettyValue.equals(value)) { + ValueChangeEvent vce = new ValueChangeEvent(this, getColumnName(), value, prettyValue); + super.fireValueChange(vce); + } + } + } + +} diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WSearchEditor.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WSearchEditor.java index 242da66eb8..b8c76c5c2c 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WSearchEditor.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WSearchEditor.java @@ -361,6 +361,7 @@ public class WSearchEditor extends WEditor implements ContextMenuListener, Value { getComponent().setText(""); } + popupMenu.showDrillAssistant(value != null); } @Override diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WTableDirEditor.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WTableDirEditor.java index 0b881b2127..e5c94d0ca9 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WTableDirEditor.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WTableDirEditor.java @@ -464,10 +464,13 @@ ContextMenuListener, IZoomableEditor getComponent().setValue(null); getComponent().setSelectedItem(null); oldValue = value; + if (gridField!=null) + gridField.setLockedRecord(false); if (getComponent() instanceof EditorAutoComplete && gridField!=null) // IDEMPIERE-4442 Fix NPE, for Autocomplete in non Grid Usage. updateStyle(); - } + } + popupMenu.showDrillAssistant(value != null); } @Override diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultEditorFactory.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultEditorFactory.java index 5ea62430a9..fc4523ea56 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultEditorFactory.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultEditorFactory.java @@ -30,6 +30,7 @@ import org.adempiere.webui.editor.WFileDirectoryEditor; import org.adempiere.webui.editor.WFilenameEditor; import org.adempiere.webui.editor.WHtmlEditor; import org.adempiere.webui.editor.WImageEditor; +import org.adempiere.webui.editor.WJsonEditor; import org.adempiere.webui.editor.WLocationEditor; import org.adempiere.webui.editor.WLocatorEditor; import org.adempiere.webui.editor.WNumberEditor; @@ -240,6 +241,10 @@ public class DefaultEditorFactory implements IEditorFactory { else if (displayType == DisplayType.RecordUU) { editor = new WRecordUUIDEditor(gridField, tableEditor, editorConfiguration); + } + else if (displayType == DisplayType.JSON) + { + editor = new WJsonEditor(gridField, tableEditor, editorConfiguration); } else { diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultInfoFactory.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultInfoFactory.java index cb61611e36..92b39507f4 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultInfoFactory.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultInfoFactory.java @@ -39,6 +39,7 @@ import org.compiere.model.I_M_InOut; import org.compiere.model.Lookup; import org.compiere.model.MDocType; import org.compiere.model.MInfoWindow; +import org.compiere.model.MTable; import org.compiere.util.Env; /** @@ -56,13 +57,6 @@ public class DefaultInfoFactory implements IInfoFactory { value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, null, null); } - @Override - public InfoPanel create(int WindowNo, String tableName, String keyColumn, String value, boolean multiSelection, - String whereClause, int AD_InfoWindow_ID, Lookup lookup) { - return create(WindowNo, tableName, keyColumn, - value, multiSelection, whereClause, AD_InfoWindow_ID, (lookup != null), null, null, lookup); - } - @Override public InfoPanel create(int WindowNo, String tableName, String keyColumn, String value, boolean multiSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field) { @@ -83,40 +77,20 @@ public class DefaultInfoFactory implements IInfoFactory { * @param predefinedContextVariables * @param field * @return InfoPanel - */ + */ public InfoPanel create(int WindowNo, String tableName, String keyColumn, String value, boolean multiSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, String predefinedContextVariables, GridField field) { - return create(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, predefinedContextVariables, field, - (field != null ? field.getLookup() : null)); - } - - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param value - * @param multiSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param predefinedContextVariables - * @param field - * @param lookupModel - * @return InfoPanel - */ - public InfoPanel create(int WindowNo, String tableName, String keyColumn, - String value, boolean multiSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, String predefinedContextVariables, GridField field, Lookup lookupModel) { InfoPanel info = null; setSOTrxBasedOnDocType(WindowNo); if (tableName.equals("C_BPartner")) { - info = new InfoBPartnerWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, lookupModel); + info = new InfoBPartnerWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); if (!info.loadedOK()) { info = new InfoBPartnerPanel (value,WindowNo, !Env.getContext(Env.getCtx(), WindowNo, "IsSOTrx").equals("N"), multiSelection, whereClause, lookup); } } else if (tableName.equals("M_Product")) { - info = new InfoProductWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, lookupModel); + info = new InfoProductWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); if (!info.loadedOK()) { info = new InfoProductPanel ( WindowNo, Env.getContextAsInt(Env.getCtx(), WindowNo, "M_Warehouse_ID"), @@ -124,31 +98,31 @@ public class DefaultInfoFactory implements IInfoFactory { multiSelection, value,whereClause, lookup); } } else if (tableName.equals("C_Invoice")) { - info = new InfoInvoiceWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, lookupModel); + info = new InfoInvoiceWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); if (!info.loadedOK()) { info = new InfoInvoicePanel ( WindowNo, value, multiSelection, whereClause, lookup); } } else if (tableName.equals("A_Asset")) { - info = new InfoAssetWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, lookupModel); + info = new InfoAssetWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); if (!info.loadedOK()) { info = new InfoAssetPanel (WindowNo, 0, value, multiSelection, whereClause, lookup); } } else if (tableName.equals("C_Order")) { - info = new InfoOrderWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, lookupModel); + info = new InfoOrderWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); if (!info.loadedOK()) { info = new InfoOrderPanel ( WindowNo, value, multiSelection, whereClause, lookup); } } else if (tableName.equals("M_InOut")) { - info = new InfoInOutWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, lookupModel); + info = new InfoInOutWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); if (!info.loadedOK()) { info = new InfoInOutPanel (WindowNo, value, multiSelection, whereClause, lookup); } } else if (tableName.equals("C_Payment")) { - info = new InfoPaymentWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, lookupModel); + info = new InfoPaymentWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); if (!info.loadedOK()) { info = new InfoPaymentPanel (WindowNo, value, multiSelection, whereClause, lookup); } @@ -156,13 +130,13 @@ public class DefaultInfoFactory implements IInfoFactory { info = new InfoCashLinePanel (WindowNo, value, multiSelection, whereClause, lookup); } else if (tableName.equals("S_ResourceAssignment")) { - info = new InfoAssignmentWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, lookupModel); + info = new InfoAssignmentWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); if (!info.loadedOK()) { info = new InfoAssignmentPanel (WindowNo, value, multiSelection, whereClause, lookup); } } else { - info = new InfoWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, lookupModel); + info = new InfoWindow(WindowNo, tableName, keyColumn, value, multiSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); if (!info.loadedOK()) { info = new InfoGeneralPanel (value, WindowNo, tableName, keyColumn, @@ -191,7 +165,7 @@ public class DefaultInfoFactory implements IInfoFactory { if (col.equals("M_Product_ID")) { - InfoWindow infoWindow = new InfoProductWindow(lookup.getWindowNo(), tableName, keyColumn, queryValue, multiSelection, whereClause, AD_InfoWindow_ID, true, field, null, lookup); + InfoWindow infoWindow = new InfoProductWindow(lookup.getWindowNo(), tableName, keyColumn, queryValue, multiSelection, whereClause, AD_InfoWindow_ID, true, field); if (infoWindow.loadedOK()) return infoWindow; @@ -222,7 +196,7 @@ public class DefaultInfoFactory implements IInfoFactory { String tempIsSOTrx = ("Y".equals(originalIsSOTrx) ? "N" : "Y"); Env.setContext(Env.getCtx(), lookup.getWindowNo(), "IsSOTrx", tempIsSOTrx); } - InfoWindow infoWindow = new InfoBPartnerWindow(lookup.getWindowNo(), tableName, keyColumn, queryValue, multiSelection, whereClause, AD_InfoWindow_ID, true, field, null, lookup); + InfoWindow infoWindow = new InfoBPartnerWindow(lookup.getWindowNo(), tableName, keyColumn, queryValue, multiSelection, whereClause, AD_InfoWindow_ID, true, field); if (infoWindow.loadedOK()) return infoWindow; } finally { @@ -240,7 +214,7 @@ public class DefaultInfoFactory implements IInfoFactory { } else // General Info { - info = create(lookup.getWindowNo(), tableName, keyColumn, queryValue, multiSelection, whereClause, AD_InfoWindow_ID, true, (String)null, field, lookup); + info = create(lookup.getWindowNo(), tableName, keyColumn, queryValue, multiSelection, whereClause, AD_InfoWindow_ID, true, field); } return info; } @@ -260,7 +234,10 @@ public class DefaultInfoFactory implements IInfoFactory { public InfoWindow create(int windowNo, int AD_InfoWindow_ID, String predefinedContextVariables) { MInfoWindow infoWindow = MInfoWindow.getInfoWindow(AD_InfoWindow_ID); String tableName = infoWindow.getAD_Table().getTableName(); + MTable table = (MTable)infoWindow.getAD_Table(); String keyColumn = tableName + "_ID"; + if(table.isUUIDKeyTable()) + keyColumn = tableName + "_UU"; InfoPanel info = create(windowNo, tableName, keyColumn, null, false, null, AD_InfoWindow_ID, false, predefinedContextVariables, null); if (info instanceof InfoWindow) return (InfoWindow) info; diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/IInfoFactory.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/IInfoFactory.java index eaf4fbb124..c3b7433a68 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/IInfoFactory.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/IInfoFactory.java @@ -39,22 +39,6 @@ public interface IInfoFactory { public InfoPanel create (int WindowNo, String tableName, String keyColumn, String value, boolean multiSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup); - - /** - * - * @param WindowNo - * @param tableName - * @param keyColumn - * @param value - * @param multiSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @return {@link InfoPanel} - */ - public InfoPanel create (int WindowNo, - String tableName, String keyColumn, String value, - boolean multiSelection, String whereClause, int AD_InfoWindow_ID, Lookup lookup); /** * diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoAssetWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoAssetWindow.java index f4f3591760..fb9554823c 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoAssetWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoAssetWindow.java @@ -25,7 +25,6 @@ package org.adempiere.webui.info; import org.compiere.model.GridField; -import org.compiere.model.Lookup; import org.compiere.model.MAsset; import org.compiere.util.Env; @@ -92,26 +91,6 @@ public class InfoAssetWindow extends InfoWindow { whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); } - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param queryValue - * @param multipleSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param field - * @param predefinedContextVariables - * @param lookupModel - */ - public InfoAssetWindow(int WindowNo, String tableName, String keyColumn, String queryValue, - boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, - String predefinedContextVariables, Lookup lookupModel) { - super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, - predefinedContextVariables, lookupModel); - } - @Override protected void saveSelectionDetail() { int row = contentPanel.getSelectedRow(); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoAssignmentWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoAssignmentWindow.java index 92288d542f..afc83d8890 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoAssignmentWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoAssignmentWindow.java @@ -25,7 +25,6 @@ package org.adempiere.webui.info; import org.compiere.model.GridField; -import org.compiere.model.Lookup; /** * Info window for S_ResourceAssignment @@ -90,25 +89,4 @@ public class InfoAssignmentWindow extends InfoWindow { super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); } - - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param queryValue - * @param multipleSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param field - * @param predefinedContextVariables - * @param lookupModel - */ - public InfoAssignmentWindow(int WindowNo, String tableName, String keyColumn, String queryValue, - boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, - String predefinedContextVariables, Lookup lookupModel) { - super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, - predefinedContextVariables, lookupModel); - } - } diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoBPartnerWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoBPartnerWindow.java index 2d2c9d1d3d..52b3ffb4f0 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoBPartnerWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoBPartnerWindow.java @@ -28,7 +28,6 @@ import java.util.logging.Level; import org.adempiere.webui.panel.InvoiceHistory; import org.compiere.model.GridField; -import org.compiere.model.Lookup; import org.compiere.model.MBPartner; import org.compiere.util.Env; @@ -96,26 +95,6 @@ public class InfoBPartnerWindow extends InfoWindow { whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); } - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param queryValue - * @param multipleSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param field - * @param predefinedContextVariables - * @param lookupModel - */ - public InfoBPartnerWindow(int WindowNo, String tableName, String keyColumn, String queryValue, - boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, - String predefinedContextVariables, Lookup lookupModel) { - super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, - predefinedContextVariables, lookupModel); - } - /** * Has History * @return true diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoInOutWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoInOutWindow.java index 79d055ebc8..e301f49d75 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoInOutWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoInOutWindow.java @@ -25,7 +25,6 @@ package org.adempiere.webui.info; import org.compiere.model.GridField; -import org.compiere.model.Lookup; /** * Info window for M_InOut @@ -89,25 +88,4 @@ public class InfoInOutWindow extends InfoWindow { super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); } - - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param queryValue - * @param multipleSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param field - * @param predefinedContextVariables - * @param lookupModel - */ - public InfoInOutWindow(int WindowNo, String tableName, String keyColumn, String queryValue, - boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, - String predefinedContextVariables, Lookup lookupModel) { - super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, - predefinedContextVariables, lookupModel); - } - } diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoInvoiceWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoInvoiceWindow.java index 2d5fc8b108..d15d43ec08 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoInvoiceWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoInvoiceWindow.java @@ -25,7 +25,6 @@ package org.adempiere.webui.info; import org.compiere.model.GridField; -import org.compiere.model.Lookup; import org.compiere.model.MInvoice; import org.compiere.util.Env; @@ -91,27 +90,7 @@ public class InfoInvoiceWindow extends InfoWindow { super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); } - - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param queryValue - * @param multipleSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param field - * @param predefinedContextVariables - * @param lookupModel - */ - public InfoInvoiceWindow(int WindowNo, String tableName, String keyColumn, String queryValue, - boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, - String predefinedContextVariables, Lookup lookupModel) { - super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, - predefinedContextVariables, lookupModel); - } - + @Override protected void saveSelectionDetail() { int row = contentPanel.getSelectedRow(); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoOrderWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoOrderWindow.java index cd1ca8282e..4276ce61c6 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoOrderWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoOrderWindow.java @@ -25,7 +25,6 @@ package org.adempiere.webui.info; import org.compiere.model.GridField; -import org.compiere.model.Lookup; /** * Info window for C_Order @@ -90,24 +89,4 @@ public class InfoOrderWindow extends InfoWindow { whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); } - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param queryValue - * @param multipleSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param field - * @param predefinedContextVariables - * @param lookupModel - */ - public InfoOrderWindow(int WindowNo, String tableName, String keyColumn, String queryValue, - boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, - String predefinedContextVariables, Lookup lookupModel) { - super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, - predefinedContextVariables, lookupModel); - } - } diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoPaymentWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoPaymentWindow.java index 70263e43eb..a5141d89e1 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoPaymentWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoPaymentWindow.java @@ -25,7 +25,6 @@ package org.adempiere.webui.info; import org.compiere.model.GridField; -import org.compiere.model.Lookup; /** * Info window for C_Payment @@ -90,24 +89,4 @@ public class InfoPaymentWindow extends InfoWindow { whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); } - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param queryValue - * @param multipleSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param field - * @param predefinedContextVariables - * @param lookupModel - */ - public InfoPaymentWindow(int WindowNo, String tableName, String keyColumn, String queryValue, - boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, - String predefinedContextVariables, Lookup lookupModel) { - super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, - predefinedContextVariables, lookupModel); - } - } diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoProductWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoProductWindow.java index 19f4fae4df..7ef815a03d 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoProductWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoProductWindow.java @@ -55,7 +55,6 @@ import org.adempiere.webui.util.ZKUpdateUtil; import org.compiere.minigrid.ColumnInfo; import org.compiere.minigrid.EmbedWinInfo; import org.compiere.model.GridField; -import org.compiere.model.Lookup; import org.compiere.model.MDocType; import org.compiere.model.MInfoWindow; import org.compiere.model.MProduct; @@ -183,26 +182,6 @@ public class InfoProductWindow extends InfoWindow { whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables); } - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param queryValue - * @param multipleSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param field - * @param predefinedContextVariables - * @param lookupModel - */ - public InfoProductWindow(int WindowNo, String tableName, String keyColumn, String queryValue, - boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, - String predefinedContextVariables, Lookup lookupModel) { - super(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, - predefinedContextVariables, lookupModel); - } - @Override protected String getSQLWhere() { /** diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoWindow.java index 477ac312ff..a82fd9d75f 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoWindow.java @@ -31,6 +31,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.function.Consumer; import java.util.Properties; import java.util.TreeMap; import java.util.logging.Level; @@ -164,7 +165,7 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL /** * generated serial id */ - private static final long serialVersionUID = 4004251745919433247L; + private static final long serialVersionUID = 615852605072547785L; private static final String ON_QUERY_AFTER_CHANGE = "onQueryAfterChange"; @@ -224,10 +225,6 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL /** true to auto collapse parameter panel after execution of query */ private boolean autoCollapsedParameterPanel = false; - - protected Lookup lookupModel = null; - - private ArrayList lookupIdentifiers; /** * @param WindowNo @@ -288,25 +285,6 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL */ public InfoWindow(int WindowNo, String tableName, String keyColumn, String queryValue, boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, String predefinedContextVariables) { - this(WindowNo, tableName, keyColumn, queryValue, multipleSelection, whereClause, AD_InfoWindow_ID, lookup, field, predefinedContextVariables, - (field != null ? field.getLookup() : null)); - } - - /** - * @param WindowNo - * @param tableName - * @param keyColumn - * @param queryValue - * @param multipleSelection - * @param whereClause - * @param AD_InfoWindow_ID - * @param lookup - * @param field - * @param predefinedContextVariables - * @param lookupModel - */ - public InfoWindow(int WindowNo, String tableName, String keyColumn, String queryValue, - boolean multipleSelection, String whereClause, int AD_InfoWindow_ID, boolean lookup, GridField field, String predefinedContextVariables, Lookup lookupModel) { super(WindowNo, tableName, keyColumn, multipleSelection, whereClause, lookup, AD_InfoWindow_ID, queryValue); this.m_gridfield = field; @@ -337,14 +315,6 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL Env.setPredefinedVariables(Env.getCtx(), getWindowNo(), predefinedContextVariables); infoContext = new Properties(Env.getCtx()); - if (lookupModel != null) { - this.lookupModel = lookupModel; - if (lookupModel instanceof MLookup mLookup) { - if (mLookup.getLookupInfo().lookupDisplayColumnNames != null && mLookup.getLookupInfo().lookupDisplayColumnNames.size() > 0) { - this.lookupIdentifiers = new ArrayList(mLookup.getLookupInfo().lookupDisplayColumnNames); - } - } - } p_loadedOK = loadInfoDefinition(); // make process button only in window mode @@ -743,12 +713,57 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL } protected void testQueryForSplit(String [] values) { - // do fill value to editor - for(int i = 0; i < values.length && i < identifiers.size(); i++) { - WEditor editor = identifiers.get(i); - editor.setValue(values[i].trim()); + // store identifiers on info window, sort to follow identifier on m_table + List fillIdentifiers = new ArrayList<>(); + // store query value, ignore value for identifier not exists on info window + // this list is sync with fillIdentifiers (size and order) + List fillValues = new ArrayList<>(); + + List tableIdentifiers = null; + if (m_gridfield != null && m_gridfield.getLookup() != null + && m_gridfield.getLookup() instanceof MLookup) { + + MLookup mLookup = (MLookup)m_gridfield.getLookup(); + if (mLookup.getLookupInfo().lookupDisplayColumnNames.size() > 0) + tableIdentifiers = mLookup.getLookupInfo().lookupDisplayColumnNames; + } + + if (tableIdentifiers != null) { + for (int i = 0; i < tableIdentifiers.size(); i++) { + // final local variable to access inside lambda expression + int indexFinal = i; + List tableIdentifiersFinal = tableIdentifiers; + + // sort identifiers of info window to follow m_table + // ignore identifiers exists on m_table but not exists on info window + identifiers.forEach((Consumer)(identifierEditor) -> { + if (identifierEditor.getColumnName().equals(tableIdentifiersFinal.get(indexFinal))) { + fillIdentifiers.add(identifierEditor); + fillValues.add(values[indexFinal]); + } + }); + } + } + + // case not exists mLookup.getLookupInfo().lookupDisplayColumnNames + // or no identifiers on info window exists on m_table + // fall back to old logic and just set values to identifiers + if (fillIdentifiers.size() == 0) { + for(int i = 0; i < values.length && i < identifiers.size(); i++) { + fillIdentifiers.add(identifiers.get(i)); + fillValues.add(values[i]); + } + } + + + + // do fill value to editor (for both corrected order and fall back) + for(int i = 0; i < fillIdentifiers.size(); i++) { + WEditor editor = fillIdentifiers.get(i); + editor.setValue(fillValues.get(i).trim()); } testCount(false); + } @Override @@ -1206,7 +1221,10 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL if (p_whereClause != null && p_whereClause.trim().length() > 0) { builder.append(" AND "); } - builder.append(tableInfos[0].getSynonym()).append(".IsActive='Y'"); + String qualifiedTable = tableInfos[0].getSynonym(); + if (Util.isEmpty(qualifiedTable)) + qualifiedTable = tableInfos[0].getTableName(); + builder.append(qualifiedTable).append(".IsActive='Y'"); } int count = 0; int idx = 0; @@ -1797,13 +1815,16 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL Columns columns = new Columns(); parameterGrid.appendChild(columns); noOfParameterColumn = getNoOfParameterColumns(); - for(int i = 0; i < noOfParameterColumn; i++) - columns.appendChild(new Column()); - - Column column = new Column(); - ZKUpdateUtil.setWidth(column, "100px"); - column.setAlign("right"); - columns.appendChild(column); + String labelWidth = ( 100 / ( 3 * ( getNoOfParameterColumns() / 2 ) ) ) + "%"; + String fieldWidth = ( 100 * 2 / ( 3 * ( getNoOfParameterColumns() / 2 ) ) ) + "%"; + for(int i = 0; i < noOfParameterColumn; i++) { + Column column = new Column(); + if (i%2 == 0) + column.setWidth(labelWidth); + else + column.setWidth(fieldWidth); + columns.appendChild(column); + } if (parameterGrid.getRows() != null) parameterGrid.getRows().detach(); @@ -1844,9 +1865,14 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL if (checkAND == null) { if (parameterGrid.getRows() != null && parameterGrid.getRows().getFirstChild() != null) { - Row row = (Row) parameterGrid.getRows().getFirstChild(); - int col = row.getChildren().size(); - while (col < 6) { + Row row = (Row) parameterGrid.getRows().getLastChild(); + int col = getRowSize(row); + if (col == getNoOfParameterColumns()) { + row = new Row(); + parameterGrid.getRows().appendChild(row); + col = 0; + } + while (col < getNoOfParameterColumns()-1) { row.appendChild(new Space()); col++; } @@ -1880,22 +1906,22 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL if (!isAutoComplete) dynamicDisplay(null); - - //if using lookupIdentifiers, sort identifiers in the order of lookupIdentifiers - if (lookupIdentifiers != null && lookupIdentifiers.size() > 0 && identifiers.size() > 0) { - List list = new ArrayList(); - for(String columnName : lookupIdentifiers) { - for(WEditor editor : identifiers) { - if (columnName.equals(editor.getColumnName())) { - list.add(editor); - break; - } - } - } - identifiers = list; - } } - + + /** + * Get number of children from the row except Menupopup + * @param row + * @return + */ + private int getRowSize(Row row) { + int cnt = 0; + for (Component comp : row.getChildren()) { + if (! (comp instanceof Menupopup || comp instanceof DateRangeButton) ) + cnt++; + } + return cnt; + } + /** * evaluate display logic for input parameters */ @@ -1950,6 +1976,7 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL editor.dynamicDisplay(); editor.addValueChangeListener(this); editor.fillHorizontal(); + ZKUpdateUtil.setWidth((HtmlBasedComponent) editor.getComponent(), "100%"); if (editor instanceof WTableDirEditor) { ((WTableDirEditor) editor).setRetainSelectedValueAfterRefresh(false); @@ -1960,6 +1987,14 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL editor2.dynamicDisplay(); editor2.addValueChangeListener(this); editor2.fillHorizontal(); + if (DisplayType.isDate(mField.getDisplayType())) { + // give space for the Date Range button + ZKUpdateUtil.setWidth((HtmlBasedComponent) editor.getComponent(), "44%"); + ZKUpdateUtil.setWidth((HtmlBasedComponent) editor2.getComponent(), "44%"); + } else { + ZKUpdateUtil.setWidth((HtmlBasedComponent) editor.getComponent(), "50%"); + ZKUpdateUtil.setWidth((HtmlBasedComponent) editor2.getComponent(), "50%"); + } } } Label label = editor.getLabel(); @@ -1985,16 +2020,13 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL editors2.add(editor2); if (infoColumn.isQueryAfterChange()) { queryAfterChangeEditors.add(editor); + if (editor2 != null) + queryAfterChangeEditors.add(editor2); } editor.showMenu(); - //if MLookup is available, use display columns of MLookup instead of InfoColumn's IsIdentifier flag - if (lookupIdentifiers != null && lookupIdentifiers.size() > 0) { - if (lookupIdentifiers.contains(infoColumn.getColumnName()) ) { - identifiers.add(editor); - } - } else if (infoColumn.isIdentifier()) { + if (infoColumn.isIdentifier()) { identifiers.add(editor); } @@ -2034,17 +2066,8 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL else { panel = (Row) parameterGrid.getRows().getLastChild(); - if (panel.getChildren().size() == getNoOfParameterColumns()) + if (getRowSize(panel) == getNoOfParameterColumns()) { - if (parameterGrid.getRows().getChildren().size() == 1) - { - createAndCheckbox(); - panel.appendChild(checkAND); - } - else - { - panel.appendChild(new Space()); - } panel = new Row(); parameterGrid.getRows().appendChild(panel); } @@ -2067,21 +2090,18 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL outerParent.setStyle("display: flex;"); outerParent.appendChild(fieldEditor); if(fieldEditor2 != null) { - Label dash = new Label("-"); - dash.setStyle("padding-left:3px;padding-right:3px;display:flex;align-items:center;"); - outerParent.appendChild(dash); + outerParent.setStyle("display: flex; flex-wrap: wrap;"); outerParent.appendChild(fieldEditor2); if(editor.getGridField() != null && DisplayType.isDate(editor.getGridField().getDisplayType())) { DateRangeButton drb = (new DateRangeButton(editor, editor2)); outerParent.appendChild(drb); - drb.setDateButtonVisible(false); - } - if (fieldEditor instanceof InputElement && fieldEditor2 instanceof InputElement) { - ((InputElement)fieldEditor).setPlaceholder(Msg.getMsg(Env.getCtx(), "From")); - ((InputElement)fieldEditor2).setPlaceholder(Msg.getMsg(Env.getCtx(), "To")); - } else if (fieldEditor instanceof NumberBox && fieldEditor2 instanceof NumberBox) { - ((NumberBox)fieldEditor).getDecimalbox().setPlaceholder(Msg.getMsg(Env.getCtx(), "From")); - ((NumberBox)fieldEditor2).getDecimalbox().setPlaceholder(Msg.getMsg(Env.getCtx(), "To")); + } + if (fieldEditor instanceof InputElement && fieldEditor2 instanceof InputElement) { + ((InputElement)fieldEditor).setPlaceholder(Msg.getMsg(Env.getCtx(), "From")); + ((InputElement)fieldEditor2).setPlaceholder(Msg.getMsg(Env.getCtx(), "To")); + } else if (fieldEditor instanceof NumberBox && fieldEditor2 instanceof NumberBox) { + ((NumberBox)fieldEditor).getDecimalbox().setPlaceholder(Msg.getMsg(Env.getCtx(), "From")); + ((NumberBox)fieldEditor2).getDecimalbox().setPlaceholder(Msg.getMsg(Env.getCtx(), "To")); } } panel.appendChild(outerParent); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/RelatedInfoWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/RelatedInfoWindow.java index 79737ef57e..3da23c2a82 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/RelatedInfoWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/RelatedInfoWindow.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.logging.Level; +import org.adempiere.exceptions.AdempiereException; import org.adempiere.webui.component.ListModelTable; import org.adempiere.webui.component.WListItemRenderer; import org.adempiere.webui.component.WListbox; @@ -355,7 +356,7 @@ public class RelatedInfoWindow implements EventListener, Sortable if (!Util.isEmpty(m_sqlUserOrder)) { dataSql = dataSql + m_sqlUserOrder; if(!Util.isEmpty(orderByClause)) - dataSql = dataSql + orderByClause; + dataSql = dataSql + ", " + orderByClause; } else if(!Util.isEmpty(orderByClause)) { dataSql = dataSql + " ORDER BY " + orderByClause; @@ -422,7 +423,7 @@ public class RelatedInfoWindow implements EventListener, Sortable catch (SQLException e) { - log.log(Level.SEVERE, dataSql, e); + throw new AdempiereException(e); } finally diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/AbstractMenuPanel.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/AbstractMenuPanel.java index 96e4ac5123..16bf448a69 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/AbstractMenuPanel.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/AbstractMenuPanel.java @@ -20,18 +20,12 @@ import java.util.Collection; import java.util.Enumeration; import java.util.Properties; -import org.adempiere.util.Callback; -import org.adempiere.webui.adwindow.ADTabpanel; -import org.adempiere.webui.adwindow.ADWindow; import org.adempiere.webui.apps.MenuSearchController; -import org.adempiere.webui.desktop.AbstractDesktop; -import org.adempiere.webui.desktop.IDesktop; import org.adempiere.webui.exception.ApplicationException; import org.adempiere.webui.session.SessionManager; import org.adempiere.webui.theme.ThemeManager; import org.adempiere.webui.util.ZKUpdateUtil; import org.compiere.model.MMenu; -import org.compiere.model.MQuery; import org.compiere.model.MToolBarButtonRestrict; import org.compiere.model.MTree; import org.compiere.model.MTreeNode; @@ -291,7 +285,8 @@ public abstract class AbstractMenuPanel extends Panel implements EventListener + * The event from global search and application menu tree will be routed to here. * @param comp * @param eventData */ @@ -361,31 +356,11 @@ public abstract class AbstractMenuPanel extends Panel implements EventListener() { - @Override - public void onCallback(ADWindow result) { - if(result == null) - return; - - result.getADWindowContent().onNew(); - ADTabpanel adtabpanel = (ADTabpanel) result.getADWindowContent().getADTab().getSelectedTabpanel(); - adtabpanel.focusToFirstEditor(false); - } - }); + SessionManager.getAppDesktop().onNewRecord(menuId); } catch (Exception e) { diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/InfoGeneralPanel.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/InfoGeneralPanel.java index 486266db4c..8440b08b37 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/InfoGeneralPanel.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/InfoGeneralPanel.java @@ -582,7 +582,7 @@ public class InfoGeneralPanel extends InfoPanel implements EventListener else if (DisplayType.isDate(displayType)) colClass = Timestamp.class; // ignore Binary, Button, ID, RowID - else if (displayType == DisplayType.List) + else if (displayType == DisplayType.List || displayType == DisplayType.RadiogroupList || displayType == DisplayType.Payment) { if (Env.isBaseLanguage(Env.getCtx(), "AD_Ref_List")) colSql = new StringBuffer("(SELECT l.Name FROM AD_Ref_List l WHERE l.AD_Reference_ID=") @@ -602,7 +602,8 @@ public class InfoGeneralPanel extends InfoPanel implements EventListener list.add(new ColumnInfo(Msg.translate(Env.getCtx(), columnName), colSql.toString(), colClass, true, columnName )); if (log.isLoggable(Level.FINEST)) log.finest("Added Column=" + columnName); } - else if (isDisplayed && DisplayType.isLookup(displayType)) + else if (isDisplayed && DisplayType.isLookup(displayType) + && !(displayType == DisplayType.ChosenMultipleSelectionTable || displayType == DisplayType.ChosenMultipleSelectionSearch || displayType == DisplayType.ChosenMultipleSelectionList)) { ColumnInfo colInfo = createLookupColumnInfo(Msg.translate(Env.getCtx(), columnName), columnName, displayType, AD_Reference_Value_ID, AD_Column_ID, colSql.toString()); if (colInfo != null) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/InfoPanel.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/InfoPanel.java index 97a97d02ba..20cfc08f9b 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/InfoPanel.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/InfoPanel.java @@ -2785,9 +2785,9 @@ public abstract class InfoPanel extends Window implements EventListener, else if (data instanceof UUIDColumn) { UUIDColumn id = (UUIDColumn) data; - parameters.add(null); parameters.add(id.getRecord_UU()); parameters.add(null); + parameters.add(null); } else if (data instanceof String) { diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/WAttachment.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/WAttachment.java index 565fd4545e..c92a45edc6 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/WAttachment.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/WAttachment.java @@ -93,9 +93,9 @@ import org.zkoss.zul.impl.XulElement; public class WAttachment extends Window implements EventListener { /** - * generated serial id + * */ - private static final long serialVersionUID = -8534334828539841412L; + private static final long serialVersionUID = 1041937899860394478L; private static final CLogger log = CLogger.getCLogger(WAttachment.class); @@ -252,14 +252,21 @@ public class WAttachment extends Window implements EventListener { } - String maxUploadSize = ""; - int size = MSysConfig.getIntValue(MSysConfig.ZK_MAX_UPLOAD_SIZE, 0); - if (size > 0) - maxUploadSize = "" + size; + if (m_attachment.isReadOnly()) { + toolBar.removeChild(bLoad); + toolBar.removeChild(bDelete); + confirmPanel.removeChild(bDeleteAll); + text.setReadonly(true); + } else { + String maxUploadSize = ""; + int size = MSysConfig.getIntValue(MSysConfig.ZK_MAX_UPLOAD_SIZE, 0); + if (size > 0) + maxUploadSize = "" + size; - Clients.evalJavaScript("idempiere.dropToAttachFiles('" + this.getUuid() + "','" + mainPanel.getUuid() + "','" - + this.getDesktop().getId() + "','" + progress.getUuid() + "','" + sizeLabel.getUuid() + "','" - + maxUploadSize + "');"); + Clients.evalJavaScript("idempiere.dropToAttachFiles('" + this.getUuid() + "','" + mainPanel.getUuid() + "','" + + this.getDesktop().getId() + "','" + progress.getUuid() + "','" + sizeLabel.getUuid() + "','" + + maxUploadSize + "');"); + } } // WAttachment @@ -703,10 +710,7 @@ public class WAttachment extends Window implements EventListener if (newText.length() > 0 || m_attachment.getEntryCount() > 0) { if (m_change) { - m_attachment.setBinaryData(new byte[0]); // ATTENTION! HEAVY HACK HERE... Else it will not save :( - m_attachment.setTextMsg(text.getText()); - m_attachment.saveEx(); - m_change = false; + saveAttachment(); } } else { m_attachment.delete(true); @@ -730,17 +734,27 @@ public class WAttachment extends Window implements EventListener autoPreview (cbContent.getSelectedIndex(), false); } else if (e.getTarget() == bSave) { // Open Attachment - saveAttachmentToFile(); + exportAttachmentToFile(); } else if (e.getTarget() == bPreview) { displayData(cbContent.getSelectedIndex(), true); } else if (e.getTarget() == bSaveAllAsZip) { - saveAllAsZip(); + exportAllAsZip(); } else if(e.getTarget()==bEmail){ sendMail(); } } // onEvent + /** + * Save the attachment to database + */ + private void saveAttachment() { + m_attachment.setBinaryData(new byte[0]); // ATTENTION! HEAVY HACK HERE... Else it will not save :( + m_attachment.setTextMsg(text.getText()); + m_attachment.saveEx(); + m_change = false; + } + /** * Handle onCancel event */ @@ -876,21 +890,22 @@ public class WAttachment extends Window implements EventListener if (result) { if (m_attachment.deleteEntry(index)) { + // must save the attachment immediately, on external storage providers the file doesn't exist at this point + saveAttachment(); cbContent.removeItemAt(index); clearPreview(); autoPreview (cbContent.getSelectedIndex(), true); } - m_change = true; } } }); } // deleteAttachment /** - * Save current Attachment entry to File + * Export current Attachment entry to File */ - private void saveAttachmentToFile() + private void exportAttachmentToFile() { int index = cbContent.getSelectedIndex(); if (log.isLoggable(Level.INFO)) @@ -931,9 +946,9 @@ public class WAttachment extends Window implements EventListener } /** - * Save all attachment items as zip file + * Export all attachment items as zip file */ - private void saveAllAsZip() { + private void exportAllAsZip() { File zipFile = m_attachment.saveAsZip(); if (zipFile != null) { diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/action/ExportAction.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/action/ExportAction.java index 4c0e9865dc..0842d581e2 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/action/ExportAction.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/action/ExportAction.java @@ -15,12 +15,12 @@ package org.adempiere.webui.panel.action; import java.io.File; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import java.util.TreeMap; import org.adempiere.base.IGridTabExporter; import org.adempiere.base.equinox.EquinoxExtensionLocator; @@ -99,8 +99,8 @@ public class ExportAction implements EventListener */ public void export() { - exporterMap = new HashMap(); - extensionMap = new HashMap(); + exporterMap = new TreeMap(); + extensionMap = new TreeMap(); List exporterList = EquinoxExtensionLocator.instance().list(IGridTabExporter.class).getExtensions(); MRole role = MRole.getDefault(); for(IGridTabExporter exporter : exporterList) @@ -130,11 +130,8 @@ public class ExportAction implements EventListener cboType.setMold("select"); cboType.getItems().clear(); - List keys = new ArrayList<>(extensionMap.keySet()); - Collections.sort(keys); - for(String key : keys) - { - cboType.appendItem(key, key); + for (Entry extension : extensionMap.entrySet()) { + cboType.appendItem(extension.getKey(), extension.getValue()); } cboType.setSelectedIndex(0); @@ -352,12 +349,12 @@ public class ExportAction implements EventListener */ protected IGridTabExporter getExporter() { ListItem li = cboType.getSelectedItem(); - if(li == null || li.getValue() == null) + if(li == null || li.getLabel() == null) { return null; } - String ext = li.getValue().toString(); + String ext = li.getLabel().toString(); IGridTabExporter exporter = exporterMap.get(ext); return exporter; } diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/action/ReportAction.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/action/ReportAction.java index 42c00a9a87..4479a2711e 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/action/ReportAction.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/action/ReportAction.java @@ -291,8 +291,8 @@ public class ReportAction implements EventListener boolean currentRowOnly = chkCurrentRowOnly.isChecked(); int Record_ID = 0; String Record_UU = null; - List RecordIDs = null; - List RecordUUs = null; + List jasperRecordIDs = null; + List jasperRecordUUs = null; MQuery query = new MQuery(gridTab.getTableName()); MTable table = MTable.get(gridTab.getAD_Table_ID()); StringBuilder whereClause = new StringBuilder(""); @@ -309,15 +309,17 @@ public class ReportAction implements EventListener else { whereClause.append(gridTab.getTableModel().getSelectWhereClause()); - if (table.isUUIDKeyTable()) { - RecordUUs = new ArrayList(); - for(int i = 0; i < gridTab.getRowCount(); i++) { - RecordUUs.add(gridTab.getKeyUUID(i)); - } - } else { - RecordIDs = new ArrayList(); - for(int i = 0; i < gridTab.getRowCount(); i++) { - RecordIDs.add(gridTab.getKeyID(i)); + if (pf != null && pf.getJasperProcess_ID() > 0) { + if (table.isUUIDKeyTable()) { + jasperRecordUUs = new ArrayList(); + for(int i = 0; i < gridTab.getRowCount(); i++) { + jasperRecordUUs.add(gridTab.getKeyUUID(i)); + } + } else { + jasperRecordIDs = new ArrayList(); + for(int i = 0; i < gridTab.getRowCount(); i++) { + jasperRecordIDs.add(gridTab.getKeyID(i)); + } } } } @@ -358,8 +360,8 @@ public class ReportAction implements EventListener { // It's a report using the JasperReports engine ProcessInfo pi = new ProcessInfo ("", pf.getJasperProcess_ID(), pf.getAD_Table_ID(), Record_ID, Record_UU); - pi.setRecord_IDs(RecordIDs); - pi.setRecord_UUs(RecordUUs); + pi.setRecord_IDs(jasperRecordIDs); + pi.setRecord_UUs(jasperRecordUUs); //pi.setIsBatch(true); if (export) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/part/WindowContainer.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/part/WindowContainer.java index f7f6859b78..1669cad051 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/part/WindowContainer.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/part/WindowContainer.java @@ -446,7 +446,7 @@ public class WindowContainer extends AbstractUIPart implements EventListener { /** - * generated serial id + * */ - private static final long serialVersionUID = -4235323239552159150L; + private static final long serialVersionUID = -5590393631865037228L; /** Logger */ private static final CLogger log = CLogger.getCLogger(AboutWindow.class); @@ -104,6 +104,7 @@ public class AboutWindow extends Window implements EventListener { protected Button btnAdempiereLog; protected Button btnReloadLogProps; + protected Button btnGC; private Listbox levelListBox; @@ -120,9 +121,6 @@ public class AboutWindow extends Window implements EventListener { */ private void init() { - System.runFinalization(); - System.gc(); - this.setPosition("center"); this.setTitle(ThemeManager.getBrowserTitle()); this.setSclass("popup-dialog about-window"); @@ -251,11 +249,11 @@ public class AboutWindow extends Window implements EventListener { } } + MUser user = MUser.get(Env.getCtx()); levelListBox.setEnabled(false); - if (Env.getAD_Client_ID(Env.getCtx()) == 0) + if (user.isAdministrator()) { - MUser user = MUser.get(Env.getCtx()); - if (user.isAdministrator()) + if (Env.getAD_Client_ID(Env.getCtx()) == 0) { levelListBox.setEnabled(true); levelListBox.setTooltiptext("Set trace level. Warning: this will effect all session not just the current session"); @@ -268,6 +266,13 @@ public class AboutWindow extends Window implements EventListener { hbox.appendChild(new Space()); hbox.appendChild(btnAdempiereLog); + ZKUpdateUtil.setHflex(hbox, "1"); + ZKUpdateUtil.setVflex(hbox, "0"); + vbox.appendChild(hbox); + hbox = new Hbox(); + hbox.setAlign("center"); + hbox.setPack("start"); + btnReloadLogProps = new Button("Reload Log Props"); btnReloadLogProps.setTooltiptext("Reload the configuration of log levels from idempiere.properties file"); LayoutUtils.addSclass("txt-btn", btnReloadLogProps); @@ -275,6 +280,12 @@ public class AboutWindow extends Window implements EventListener { hbox.appendChild(new Space()); hbox.appendChild(btnReloadLogProps); } + btnGC = new Button("Garbage Collect"); + btnGC.setTooltiptext("Perform a Garbage Collection on the JVM"); + LayoutUtils.addSclass("txt-btn", btnGC); + btnGC.addEventListener(Events.ON_CLICK, this); + hbox.appendChild(new Space()); + hbox.appendChild(btnGC); } ZKUpdateUtil.setHflex(hbox, "1"); @@ -500,6 +511,8 @@ public class AboutWindow extends Window implements EventListener { downloadAdempiereLogFile(); else if (event.getTarget() == btnReloadLogProps) reloadLogProps(); + else if (event.getTarget() == btnGC) + garbageCollection(); else if (event.getTarget() == levelListBox) setTraceLevel(); else if (Events.ON_CLICK.equals(event.getName())) @@ -556,6 +569,29 @@ public class AboutWindow extends Window implements EventListener { } } + /** + * Call JVM GC + */ + private void garbageCollection() { + Runtime runtime = Runtime.getRuntime(); + long usedMemoryBefore = runtime.totalMemory() - runtime.freeMemory(); + System.runFinalization(); + System.gc(); + try {Thread.sleep(1000);} catch (InterruptedException e) {} // Wait 1 second for GC to complete + long usedMemoryAfter = runtime.totalMemory() - runtime.freeMemory(); + long freedMemory = usedMemoryAfter - usedMemoryBefore; + String msg = String.format("Memory: total %,d, before gc: %,d, after gc %,d, freed by gc %,d bytes%n", runtime.totalMemory(), usedMemoryBefore, usedMemoryAfter, freedMemory); + log.warning(msg); + msg = String.format("Memory in bytes:
    " + + "
  • Total = %,d
  • " + + "
  • Used before gc = %,d
  • " + + "
  • Used after gc = %,d
  • " + + "
  • Freed by gc = %,d
  • " + + "
", + runtime.totalMemory(), usedMemoryBefore, usedMemoryAfter, freedMemory); + Dialog.info(0, "", msg, "JVM Garbage Collection"); + } + /** * Change trace/log level */ diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/DateRangeButton.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/DateRangeButton.java index 05d5c57e00..cc081ce0fc 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/DateRangeButton.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/DateRangeButton.java @@ -28,10 +28,12 @@ import java.util.Properties; import org.adempiere.webui.LayoutUtils; import org.adempiere.webui.component.ToolBarButton; +import org.adempiere.webui.component.Window; import org.adempiere.webui.editor.WDateEditor; import org.adempiere.webui.editor.WEditor; import org.adempiere.webui.theme.ThemeManager; import org.compiere.util.Env; +import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.event.Events; /** @@ -74,9 +76,24 @@ public class DateRangeButton extends ToolBarButton implements WEditor.DynamicDis setImage(ThemeManager.getThemeResource(IMAGES_CONTEXT_HISTORY_PNG)); DateRangePicker popup = new DateRangePicker(editor, editor2); - this.setTooltip(popup); this.addEventListener(Events.ON_CLICK, event -> { - popup.setPage(this.getPage()); + Window window = null; + Component component = this.getParent(); + while(component != null) { + if (component instanceof Window w) { + window = w; + break; + } else { + component = component.getParent(); + } + } + // Popup must be a child of highlighted parent, otherwise combobox wouldn't work + // https://tracker.zkoss.org/browse/ZK-5740 + if (window != null && "highlighted".equals(window.getMode())) { + window.appendChild(popup); + } else { + popup.setPage(this.getPage()); + } popup.open(this, "after_center"); LayoutUtils.autoDetachOnClose(popup); }); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/FindWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/FindWindow.java index 0fa9af9f01..ab6bdf1dcf 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/FindWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/FindWindow.java @@ -949,7 +949,14 @@ public class FindWindow extends Window implements EventListener, ValueCha Collections.sort(gridFieldList, new Comparator() { @Override public int compare(GridField o1, GridField o2) { - return o1.getSeqNoSelection()-o2.getSeqNoSelection(); + // order by SeqNoSelection, sending the zeroes to the end + int sel1 = o1.getSeqNoSelection(); + if (sel1 == 0) + sel1 = Integer.MAX_VALUE; + int sel2 = o2.getSeqNoSelection(); + if (sel2 == 0) + sel2 = Integer.MAX_VALUE; + return sel1-sel2; } }); @@ -1064,17 +1071,6 @@ public class FindWindow extends Window implements EventListener, ValueCha ListItem listItem = new ListItem(); listItem.setId("Row"+ rowCount++); - int id = 0; - - if(advancedPanel.getItemCount()>0){ - String previousID = advancedPanel.getItems().get(advancedPanel.getItemCount()-1).getId(); - previousID = previousID.substring(3, previousID.length()); - id = Integer.valueOf(previousID); - id++; - } - - listItem.setId("Row"+id); - Combobox listTable = new Combobox(); listTable.setId("listTable"+listItem.getId()); listTable.setName("listTable"); @@ -3020,14 +3016,10 @@ public class FindWindow extends Window implements EventListener, ValueCha // Test for no records if (getNoOfRecords(m_query, true) != 0) { - if (m_total == COUNTING_RECORDS_TIMED_OUT) { - Dialog.error(m_targetWindowNo, "InfoQueryTimeOutError"); - } else { - if (advancedPanel != null) { - advancedPanel.getItems().clear(); - } - dispose(); + if (advancedPanel != null) { + advancedPanel.getItems().clear(); } + dispose(); } } // cmd_ok_Simple @@ -3098,11 +3090,7 @@ public class FindWindow extends Window implements EventListener, ValueCha } if (getNoOfRecords(m_query, true) != 0) { - if (m_total == COUNTING_RECORDS_TIMED_OUT) { - Dialog.error(m_targetWindowNo, "InfoQueryTimeOutError"); - } else { - dispose(); - } + dispose(); } } // cmd_ok_Advanced @@ -3150,8 +3138,8 @@ public class FindWindow extends Window implements EventListener, ValueCha Env.setContext(Env.getCtx(), m_targetWindowNo, TABNO, GridTab.CTX_FindSQL, finalSQL); // Execute Query - int timeout = MSysConfig.getIntValue(MSysConfig.GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, - GridTable.DEFAULT_GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, Env.getAD_Client_ID(Env.getCtx())); + int timeout = MSysConfig.getIntValue(MSysConfig.GRIDTABLE_INITIAL_COUNT_TIMEOUT_IN_SECONDS, + GridTable.DEFAULT_GRIDTABLE_COUNT_TIMEOUT_IN_SECONDS, Env.getAD_Client_ID(Env.getCtx())); m_total = 999999; Statement stmt = null; ResultSet rs = null; @@ -3187,12 +3175,12 @@ public class FindWindow extends Window implements EventListener, ValueCha // No Records if (m_total == 0 && alertRecords) Dialog.warn(m_targetWindowNo, "FindZeroRecords", null); - // More then allowed + // Load not more than max allow if (m_gridTab != null && alertRecords && m_total != COUNTING_RECORDS_TIMED_OUT && m_gridTab.isQueryMax(m_total)) { - Dialog.error(m_targetWindowNo, "FindOverMax", - m_total + " > " + m_gridTab.getMaxQueryRecords()); - m_total = 0; // return 0 if more then allowed - teo_sarca [ 1708717 ] + Dialog.info(m_targetWindowNo, "FindOverMax", + m_total + " > " + m_gridTab.getMaxQueryRecords()); + m_total = m_gridTab.getMaxQueryRecords(); } else if (log.isLoggable(Level.CONFIG)) log.config("#" + m_total); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/WEMailDialog.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/WEMailDialog.java index 4d0db2b020..89cffe78a6 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/WEMailDialog.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/WEMailDialog.java @@ -996,8 +996,8 @@ public class WEMailDialog extends Window implements EventListener, ValueC * @param newUserCc */ public void setUserCc(int newUserCc) { - ValueChangeEvent vce = new ValueChangeEvent(fCcUser, fCcUser.getColumnName(), fCc.getValue(), newUserCc); - fUser.valueChange(vce); + ValueChangeEvent vce = new ValueChangeEvent(fCcUser, fCcUser.getColumnName(), fCcUser.getValue(), newUserCc); + fCcUser.valueChange(vce); } /** diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/WPAttributeDialog.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/WPAttributeDialog.java index d54e3f2f3d..387c383775 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/WPAttributeDialog.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/WPAttributeDialog.java @@ -412,10 +412,11 @@ public class WPAttributeDialog extends Window implements EventListener + "FROM M_Lot l " + "WHERE EXISTS (SELECT M_Product_ID FROM M_Product p " + "WHERE p.M_AttributeSet_ID=" + m_masi.getM_AttributeSet_ID() - + " AND p.M_Product_ID=l.M_Product_ID)"; + + " AND p.M_Product_ID=l.M_Product_ID) " + + " AND l.M_Product_ID = ? "; fieldLot = new Listbox(); fieldLot.setMold("select"); - KeyNamePair[] keyNamePairs = DB.getKeyNamePairs(sql, true); + KeyNamePair[] keyNamePairs = DB.getKeyNamePairs(sql, true, m_M_Product_ID); for (KeyNamePair pair : keyNamePairs) { fieldLot.appendItem(pair.getName(), pair.getKey()); } diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/WRecordInfo.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/WRecordInfo.java index 2fbbadcb44..4da661e45c 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/WRecordInfo.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/WRecordInfo.java @@ -171,6 +171,7 @@ public class WRecordInfo extends Window implements EventListener /** Number Format */ private DecimalFormat m_intFormat = DisplayType.getNumberFormat (DisplayType.Integer, Env.getLanguage(Env.getCtx())); + private int windowNo; /** * Layout dialog @@ -281,7 +282,7 @@ public class WRecordInfo extends Window implements EventListener // Info MUser user = MUser.get(Env.getCtx(), dse.CreatedBy.intValue()); m_info.append(" ") - .append(Msg.translate(Env.getCtx(), "CreatedBy")) + .append(Msg.getElement(Env.getCtx(), "CreatedBy")) .append(": ").append(user.getName()) .append(" - ").append(m_dateTimeFormat.format(dse.Created)).append("\n"); @@ -291,7 +292,7 @@ public class WRecordInfo extends Window implements EventListener if (!dse.CreatedBy.equals(dse.UpdatedBy)) user = MUser.get(Env.getCtx(), dse.UpdatedBy.intValue()); m_info.append(" ") - .append(Msg.translate(Env.getCtx(), "UpdatedBy")) + .append(Msg.getElement(Env.getCtx(), "UpdatedBy")) .append(": ").append(user.getName()) .append(" - ").append(m_dateTimeFormat.format(dse.Updated)).append("\n"); } @@ -304,19 +305,25 @@ public class WRecordInfo extends Window implements EventListener if (gridTab != null) { gridTable = gridTab.getTableModel(); + windowNo = gridTab.getWindowNo(); } else if (dse.getSource() instanceof GridTab) { gridTab = (GridTab) dse.getSource(); gridTable = gridTab.getTableModel(); tabName = gridTab.getName(); + windowNo = gridTab.getWindowNo(); } else if (dse.getSource() instanceof GridTable) { gridTable = (GridTable) dse.getSource(); GridField firstField = gridTable.getField(0); - if (firstField != null && firstField.getGridTab() != null) - tabName = firstField.getGridTab().getName(); + if (firstField != null) { + windowNo = firstField.getWindowNo(); + if (firstField.getGridTab() != null) { + tabName = firstField.getGridTab().getName(); + } + } } int Record_ID = -1; @@ -339,24 +346,21 @@ public class WRecordInfo extends Window implements EventListener if (! m_info.toString().contains(uuinfo)) m_info.append("\n ").append(uuinfo); } - if (po.get_KeyColumns().length == 1) { - String ticketURL; - if (Record_ID <= 0) - ticketURL = AEnv.getZoomUrlTableUU(po); - else - ticketURL = AEnv.getZoomUrlTableID(po); - m_permalink.addEventListener(Events.ON_CLICK, new EventListener() { - public void onEvent(Event event) throws Exception { - StringBuffer sb = new StringBuffer("navigator.clipboard.writeText(\"") + String ticketURL; + if (Record_ID <= 0) + ticketURL = AEnv.getZoomUrlTableUU(po); + else + ticketURL = AEnv.getZoomUrlTableID(po); + m_permalink.addEventListener(Events.ON_CLICK, new EventListener() { + public void onEvent(Event event) throws Exception { + StringBuffer sb = new StringBuffer("navigator.clipboard.writeText(\"") .append(ticketURL) .append("\");"); - Clients.evalJavaScript(sb.toString()); - Notification.show(Msg.getMsg(Env.getCtx(), "Copied"), Notification.TYPE_INFO, m_permalink, "end_before", 1000); - } - }); - } - m_permalink.setVisible(po.get_KeyColumns().length == 1); - final String whereClause = po.get_WhereClause(true, Record_UU); + Clients.evalJavaScript(sb.toString()); + Notification.show(Msg.getMsg(Env.getCtx(), "Copied"), Notification.TYPE_INFO, m_permalink, "end_before", 1000); + } + }); + final String whereClause = po.get_WhereClause(true); m_copySelect.addEventListener(Events.ON_CLICK, new EventListener() { public void onEvent(Event event) throws Exception { StringBuffer query = new StringBuffer("navigator.clipboard.writeText(\"SELECT * FROM ") @@ -423,12 +427,12 @@ public class WRecordInfo extends Window implements EventListener // ArrayList columnNames = new ArrayList(); - columnNames.add(Msg.translate(Env.getCtx(), "Name")); - columnNames.add(Msg.translate(Env.getCtx(), "NewValue")); - columnNames.add(Msg.translate(Env.getCtx(), "OldValue")); - columnNames.add(Msg.translate(Env.getCtx(), "UpdatedBy")); - columnNames.add(Msg.translate(Env.getCtx(), "Updated")); - columnNames.add(Msg.translate(Env.getCtx(), "AD_Column_ID")); + columnNames.add(Msg.getElement(Env.getCtx(), "Name")); + columnNames.add(Msg.getElement(Env.getCtx(), "NewValue")); + columnNames.add(Msg.getElement(Env.getCtx(), "OldValue")); + columnNames.add(Msg.getElement(Env.getCtx(), "UpdatedBy")); + columnNames.add(Msg.getElement(Env.getCtx(), "Updated")); + columnNames.add(Msg.getElement(Env.getCtx(), "AD_Column_ID")); Listhead listhead = new Listhead(); listhead.setSizable(true); @@ -461,7 +465,7 @@ public class WRecordInfo extends Window implements EventListener Vector line = new Vector(); // Column MColumn column = MColumn.get (Env.getCtx(), AD_Column_ID); - line.add(Msg.translate(Env.getCtx(), column.getColumnName())); + line.add(Msg.getElement(Env.getCtx(), column.getColumnName(), Env.isSOTrx(Env.getCtx(), windowNo))); // if (OldValue != null && OldValue.equals(MChangeLog.NULL)) OldValue = null; diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/WTask.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/WTask.java index b511428839..ce9a49075b 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/WTask.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/WTask.java @@ -125,10 +125,12 @@ public class WTask extends Window implements EventListener, IHelpContext Executions.activate(desktop, 500); try { StringBuilder sb = new StringBuilder(); - sb.append(osTask.getOut()) - .append("
-----------
") + sb.append("
")
+							.append(osTask.getOut())
+							.append("-----------

") .append(osTask.getErr()) - .append("
-----------"); + .append("

-----------
") + .append("
"); info.setContent(sb.toString().replace("\n", "
")); if (!osTask.isAlive()) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/ZkJRViewer.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/ZkJRViewer.java index db8be74326..9bc5902cc1 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/ZkJRViewer.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/ZkJRViewer.java @@ -427,7 +427,7 @@ public class ZkJRViewer extends Window implements EventListener, ITabOnCl mediaSuppliers.put(toMediaType(PDF_MIME_TYPE, PDF_FILE_EXT), () -> { try { attachment = getPDF(); - return new AMedia(m_title+"."+PDF_FILE_EXT, PDF_FILE_EXT, PDF_MIME_TYPE, attachment, true); + return new AMedia(attachment.getName(), PDF_FILE_EXT, PDF_MIME_TYPE, attachment, true); } catch (Exception e) { if (e instanceof RuntimeException) throw (RuntimeException)e; diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/CSVReportViewerRenderer.java b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/CSVReportViewerRenderer.java index a00f763ace..3ec43ea24c 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/CSVReportViewerRenderer.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/CSVReportViewerRenderer.java @@ -25,7 +25,6 @@ import java.io.File; import java.util.logging.Level; import org.adempiere.base.Core; -import org.adempiere.webui.apps.AEnv; import org.adempiere.webui.window.ZkReportViewer; import org.compiere.print.ReportEngine; import org.compiere.tools.FileUtil; @@ -87,14 +86,11 @@ public class CSVReportViewerRenderer implements IReportViewerRenderer { try { String path = System.getProperty("java.io.tmpdir"); String prefix = makePrefix(reportEngine.getName()); - if (log.isLoggable(Level.FINE)) - { - log.log(Level.FINE, "Path="+path + " Prefix="+prefix); - } + if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "Path="+path + " Prefix="+prefix); File file = FileUtil.createTempFile(prefix, "."+getFileExtension(), new File(path)); IReportRenderer renderer = Core.getReportRenderer(getId()); CSVReportRendererConfiguration config = new CSVReportRendererConfiguration() - .setLanguage(AEnv.getLanguage(Env.getCtx())) + .setLanguage(reportEngine.getPrintFormat().getLanguage()) .setOutputFile(file); renderer.renderReport(reportEngine, config); return new AMedia(file.getName(), getFileExtension(), getContentType(), file, false); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/HTMLReportViewerRenderer.java b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/HTMLReportViewerRenderer.java index 58060493d9..9563304f06 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/HTMLReportViewerRenderer.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/HTMLReportViewerRenderer.java @@ -88,8 +88,6 @@ public class HTMLReportViewerRenderer implements IReportViewerRenderer { try { String path = System.getProperty("java.io.tmpdir"); String prefix = makePrefix(reportEngine.getName()); - if (prefix.length() < 3) - prefix += "_".repeat(3-prefix.length()); if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "Path="+path + " Prefix="+prefix); File file = FileUtil.createTempFile(prefix, "."+getFileExtension(), new File(path)); String contextPath = Executions.getCurrent().getContextPath(); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/PDFReportViewerRenderer.java b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/PDFReportViewerRenderer.java index 354c0228ea..535036221f 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/PDFReportViewerRenderer.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/PDFReportViewerRenderer.java @@ -86,8 +86,6 @@ public class PDFReportViewerRenderer implements IReportViewerRenderer { try { String path = System.getProperty("java.io.tmpdir"); String prefix = makePrefix(reportEngine.getName()); - if (prefix.length() < 3) - prefix += "_".repeat(3-prefix.length()); if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "Path="+path + " Prefix="+prefix); File file = FileUtil.createTempFile(prefix, "."+getFileExtension(), new File(path)); IReportRenderer renderer = Core.getReportRenderer(getId()); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/XLSReportViewerRenderer.java b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/XLSReportViewerRenderer.java index 56e1464964..4e50f2f83d 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/XLSReportViewerRenderer.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/XLSReportViewerRenderer.java @@ -86,8 +86,6 @@ public class XLSReportViewerRenderer implements IReportViewerRenderer { try { String path = System.getProperty("java.io.tmpdir"); String prefix = makePrefix(reportEngine.getName()); - if (prefix.length() < 3) - prefix += "_".repeat(3-prefix.length()); if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "Path="+path + " Prefix="+prefix); File file = FileUtil.createTempFile(prefix, "."+getFileExtension(), new File(path)); IReportRenderer renderer = Core.getReportRenderer(getId()); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/XLSXReportViewerRenderer.java b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/XLSXReportViewerRenderer.java index 9254e2e855..510e950d93 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/XLSXReportViewerRenderer.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/idempiere/ui/zk/report/XLSXReportViewerRenderer.java @@ -86,10 +86,7 @@ public class XLSXReportViewerRenderer implements IReportViewerRenderer { try { String path = System.getProperty("java.io.tmpdir"); String prefix = makePrefix(reportEngine.getName()); - if (log.isLoggable(Level.FINE)) - { - log.log(Level.FINE, "Path=" + path + " Prefix=" + prefix); - } + if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "Path=" + path + " Prefix=" + prefix); File file = FileUtil.createTempFile(prefix, "."+getFileExtension(), new File(path)); IReportRenderer renderer = Core.getReportRenderer(getId()); XLSXReportRendererConfiguration config = new XLSXReportRendererConfiguration() diff --git a/org.adempiere.ui.zk/WEB-INF/src/web/theme/default/css/fragment/parameter-process.css.dsp b/org.adempiere.ui.zk/WEB-INF/src/web/theme/default/css/fragment/parameter-process.css.dsp index 75f20d8916..1848678028 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/web/theme/default/css/fragment/parameter-process.css.dsp +++ b/org.adempiere.ui.zk/WEB-INF/src/web/theme/default/css/fragment/parameter-process.css.dsp @@ -31,7 +31,14 @@ when detect side effect, fix to only apply for parameter window*/ } .report-option-container { - overflow-x: auto; + display: flex; + flex-wrap: wrap; + flex-direction: row; + align-items: center; +} + +.report-option-container div { + padding: 2px; } /* Chromium based browsers + Safari */ diff --git a/org.adempiere.ui/src/org/compiere/apps/AbstractProcessCtl.java b/org.adempiere.ui/src/org/compiere/apps/AbstractProcessCtl.java index 758748d6be..29c7ca07a6 100644 --- a/org.adempiere.ui/src/org/compiere/apps/AbstractProcessCtl.java +++ b/org.adempiere.ui/src/org/compiere/apps/AbstractProcessCtl.java @@ -175,6 +175,7 @@ public abstract class AbstractProcessCtl implements Runnable String errmsg = m_pi.getSummary(); pinstance.setResult(!m_pi.isError()); pinstance.setErrorMsg(errmsg); + pinstance.setJsonData(m_pi.getJsonData()); pinstance.saveEx(); unlock(); } diff --git a/org.adempiere.ui/src/org/compiere/apps/form/PayPrint.java b/org.adempiere.ui/src/org/compiere/apps/form/PayPrint.java index 58aebdcee0..f11e640eaa 100644 --- a/org.adempiere.ui/src/org/compiere/apps/form/PayPrint.java +++ b/org.adempiere.ui/src/org/compiere/apps/form/PayPrint.java @@ -346,8 +346,9 @@ public class PayPrint { if (pdfFile != null) { // increase the check document no by the number of pages of the generated pdf file - PdfReader document = new PdfReader(pdfFile.getAbsolutePath()); - lastDocumentNo += document.getNumberOfPages(); + try (PdfReader document = new PdfReader(pdfFile.getAbsolutePath())) { + lastDocumentNo += document.getNumberOfPages(); + } pdfList.add(pdfFile); } } diff --git a/org.adempiere.ui/src/org/compiere/print/DrillReportCtl.java b/org.adempiere.ui/src/org/compiere/print/DrillReportCtl.java index 1ae0cabd7f..e3289580f9 100644 --- a/org.adempiere.ui/src/org/compiere/print/DrillReportCtl.java +++ b/org.adempiere.ui/src/org/compiere/print/DrillReportCtl.java @@ -514,6 +514,10 @@ public class DrillReportCtl { MProcessDrillRulePara sPara = sParams[p]; if(processPara.getColumnName().equals(m_ColumnName)) { + if(m_Value == null) { + if (log.isLoggable(Level.FINE)) log.fine(sPara.getColumnName() + " - empty"); + continue; + } iPara.setParameter(DisplayType.isID(sPara.getDisplayType()) ? new BigDecimal(String.valueOf(m_Value)) : String.valueOf(m_Value)); iPara.setInfo(!Util.isEmpty(m_DisplayValue) ? m_DisplayValue : String.valueOf(m_Value)); iParams.add(iPara); diff --git a/org.adempiere.ui/src/org/compiere/print/ReportCtl.java b/org.adempiere.ui/src/org/compiere/print/ReportCtl.java index 2718753393..272cfc54de 100644 --- a/org.adempiere.ui/src/org/compiere/print/ReportCtl.java +++ b/org.adempiere.ui/src/org/compiere/print/ReportCtl.java @@ -476,8 +476,12 @@ public class ReportCtl // ============================== if(format.getJasperProcess_ID() > 0) { - ServerReportCtl.runJasperProcess(Record_ID, re, IsDirectPrint, printerName); - if (IsDirectPrint) { + int jasperRecordId = Record_ID; + if (re.getPrintInfo() != null && re.getPrintInfo().getRecord_ID() > 0) + jasperRecordId = re.getPrintInfo().getRecord_ID(); + boolean result = ServerReportCtl.runJasperProcess(jasperRecordId, re, IsDirectPrint, printerName); + if (result && IsDirectPrint) + { ReportEngine.printConfirm(type, Record_ID); } } diff --git a/org.compiere.db.oracle.provider/src/org/compiere/db/DB_Oracle.java b/org.compiere.db.oracle.provider/src/org/compiere/db/DB_Oracle.java index 9dddfb4670..76c419f507 100644 --- a/org.compiere.db.oracle.provider/src/org/compiere/db/DB_Oracle.java +++ b/org.compiere.db.oracle.provider/src/org/compiere/db/DB_Oracle.java @@ -536,7 +536,23 @@ public class DB_Oracle implements AdempiereDatabase } return result.toString(); } // TO_NUMBER + + /** + * @return string with right casting for JSON inserts + */ + public String getJSONCast () { + return "?"; + } + /** + * Return string as JSON object for INSERT statements + * @param value + * @return value as json + */ + public String TO_JSON (String value) + { + return value; + } /** * Get SQL Commands. @@ -1026,6 +1042,11 @@ public class DB_Oracle implements AdempiereDatabase public String getClobDataType() { return "CLOB"; } + + @Override + public String getJsonDataType() { + return getClobDataType(); + } @Override public String getTimestampDataType() { @@ -1072,6 +1093,8 @@ public class DB_Oracle implements AdempiereDatabase // Inline Constraint if (column.getAD_Reference_ID() == DisplayType.YesNo) sql.append(" CHECK (").append(column.getColumnName()).append(" IN ('Y','N'))"); + else if (column.getAD_Reference_ID() == DisplayType.JSON) + sql.append(" CONSTRAINT ").append(column.getAD_Table().getTableName()).append("_").append(column.getColumnName()).append("_isjson CHECK (").append(column.getColumnName()).append(" IS JSON)"); // Null if (column.isMandatory()) @@ -1146,6 +1169,8 @@ public class DB_Oracle implements AdempiereDatabase sql.append(sqlDefault); // Constraint + if (column.getAD_Reference_ID() == DisplayType.JSON) + sql.append(" CONSTRAINT ").append(column.getAD_Table().getTableName()).append("_").append(column.getColumnName()).append("_isjson CHECK (").append(column.getColumnName()).append(" IS JSON)"); // Null Values if (column.isMandatory() && defaultValue != null && defaultValue.length() > 0) diff --git a/org.compiere.db.postgresql.provider/src/org/adempiere/db/postgresql/partition/TablePartitionService.java b/org.compiere.db.postgresql.provider/src/org/adempiere/db/postgresql/partition/TablePartitionService.java index 8b7c1799cf..f3a20ab9e5 100644 --- a/org.compiere.db.postgresql.provider/src/org/adempiere/db/postgresql/partition/TablePartitionService.java +++ b/org.compiere.db.postgresql.provider/src/org/adempiere/db/postgresql/partition/TablePartitionService.java @@ -63,10 +63,24 @@ public class TablePartitionService implements ITablePartitionService { """; return DB.getSQLValueEx(trxName, sql, table.getTableName()) == 1; } - - private String getDefaultPartitionName(MTable table) + + /** + * Get default partition name for table + * @param table + * @return String default partition name for table + */ + public String getDefaultPartitionName(MTable table) { - return table.getTableName() + "_default_partition"; + return getDefaultPartitionName(table.getTableName()); + } + + /** + * Get default partition name for table + * @param tableName + * @return String default partition name for table + */ + public String getDefaultPartitionName(String tableName) { + return tableName + "_default_partition"; } /** diff --git a/org.compiere.db.postgresql.provider/src/org/compiere/db/DB_PostgreSQL.java b/org.compiere.db.postgresql.provider/src/org/compiere/db/DB_PostgreSQL.java index e41300713a..ff9c055054 100755 --- a/org.compiere.db.postgresql.provider/src/org/compiere/db/DB_PostgreSQL.java +++ b/org.compiere.db.postgresql.provider/src/org/compiere/db/DB_PostgreSQL.java @@ -539,8 +539,31 @@ public class DB_PostgreSQL implements AdempiereDatabase } return result.toString(); } // TO_NUMBER + + /** + * @return string with right casting for JSON inserts + */ + public String getJSONCast () { + return "CAST (? AS jsonb)"; + } + + /** + * Return string as JSON object for INSERT statements + * @param value + * @return value as json + */ + public String TO_JSON (String value) + { + if (value == null) + return "NULL"; - + StringBuilder retValue = null; + retValue = new StringBuilder("CAST ("); + retValue.append(value); + retValue.append(" AS jsonb)"); + return retValue.toString(); + } + /** * Get SQL Commands * @param cmdType CMD_* @@ -1211,6 +1234,11 @@ public class DB_PostgreSQL implements AdempiereDatabase public String getClobDataType() { return "TEXT"; } + + @Override + public String getJsonDataType() { + return "JSONB"; + } @Override public String getTimestampDataType() { diff --git a/org.compiere.db.postgresql.provider/src/org/compiere/dbPort/Convert_PostgreSQL.java b/org.compiere.db.postgresql.provider/src/org/compiere/dbPort/Convert_PostgreSQL.java index d40ff07f88..5531f1bd72 100644 --- a/org.compiere.db.postgresql.provider/src/org/compiere/dbPort/Convert_PostgreSQL.java +++ b/org.compiere.db.postgresql.provider/src/org/compiere/dbPort/Convert_PostgreSQL.java @@ -103,6 +103,7 @@ public class Convert_PostgreSQL extends Convert_SQL92 { } else { + statement = convertAddJson(statement); statement = convertWithConvertMap(statement); statement = convertSimilarTo(statement); statement = DB_PostgreSQL.removeNativeKeyworkMarker(statement); @@ -148,9 +149,9 @@ public class Convert_PostgreSQL extends Convert_SQL92 { boolean print = true; if (filterPgDebug != null) print = statement.matches(filterPgDebug); - // log.warning("Oracle -> " + oraStatement); if (print) { - log.warning("Oracle -> " + sqlStatement); + if (SystemProperties.isDBDebugConvert()) + log.warning("Oracle -> " + sqlStatement); log.warning("PgSQL -> " + statement); } } @@ -171,11 +172,16 @@ public class Convert_PostgreSQL extends Convert_SQL92 { return retValue; } + /** + * Convert LIKE to SIMILAR TO depending on the user preference P|IsUseSimilarTo - applies just to SELECT queries + * @param statement + * @return + */ private String convertSimilarTo(String statement) { String retValue = statement; boolean useSimilarTo = isUseSimilarTo(); - if (useSimilarTo) { - String replacement = "SIMILAR TO"; + if (useSimilarTo && statement.matches("(?i)^\\s*SELECT\\b.*")) { + final String replacement = "SIMILAR TO"; try { Matcher m = likePattern.matcher(retValue); retValue = m.replaceAll(replacement); @@ -188,6 +194,10 @@ public class Convert_PostgreSQL extends Convert_SQL92 { return retValue; } + /** + * True if the user preference IsUseSimilarTo is set to Y + * @return + */ private boolean isUseSimilarTo() { return "Y".equals(Env.getContext(Env.getCtx(), "P|IsUseSimilarTo")); } @@ -1096,10 +1106,16 @@ public class Convert_PostgreSQL extends Convert_SQL92 { begin_default = rest.toUpperCase().indexOf( " DEFAULT ") + 9; defaultvalue = rest.substring(begin_default); - int nextspace = defaultvalue.indexOf(' '); - if (nextspace > -1) { - rest = defaultvalue.substring(nextspace); - defaultvalue = defaultvalue.substring(0, defaultvalue.indexOf(' ')); + String endDefaultChar = " "; + int shift = 0; + if (defaultvalue.startsWith("'")) { + endDefaultChar = "'"; + shift = 1; + } + int endDefault = defaultvalue.substring(shift).indexOf(endDefaultChar) + shift; + if (endDefault > -1+shift) { + rest = defaultvalue.substring(endDefault+shift); + defaultvalue = defaultvalue.substring(0, endDefault+shift); } else { rest = ""; } @@ -1158,10 +1174,16 @@ public class Convert_PostgreSQL extends Convert_SQL92 { begin_default = rest.toUpperCase().indexOf( " DEFAULT ") + 9; defaultvalue = rest.substring(begin_default); - int nextspace = defaultvalue.indexOf(' '); - if (nextspace > -1) { - rest = defaultvalue.substring(nextspace); - defaultvalue = defaultvalue.substring(0, defaultvalue.indexOf(' ')); + String endDefaultChar = " "; + int shift = 0; + if (defaultvalue.startsWith("'")) { + endDefaultChar = "'"; + shift = 1; + } + int endDefault = defaultvalue.substring(shift).indexOf(endDefaultChar) + shift; + if (endDefault > -1+shift) { + rest = defaultvalue.substring(endDefault+shift); + defaultvalue = defaultvalue.substring(0, endDefault+shift); } else { rest = ""; } @@ -1211,4 +1233,21 @@ public class Convert_PostgreSQL extends Convert_SQL92 { return sqlStatement; } + + /** + * For JSON columns Oracle uses CLOB ... CONSTRAINT ... CHECK IS JSON + * while oracle uses JSONB, no constraint + * @param statement + * @return + */ + private String convertAddJson(String statement) { + if (statement.toUpperCase().matches(".*\\bCLOB\\b.*\\bCONSTRAINT\\b.*CHECK\\b.*\\bIS JSON\\).*")) { + // remove the CONSTRAINT ... IS JSON part + statement = statement.replaceAll("(?i)\\bCONSTRAINT\\b.*CHECK\\b.*\\(.*\\bIS JSON\\)", ""); + // change type CLOB to JSONB + statement = statement.replaceAll("(?i)\\bCLOB\\b", "JSONB"); + } + return statement; + } + } // Convert diff --git a/org.idempiere.keikai/src/io/keikai/ui/au/in/TextHeightCommand.java b/org.idempiere.keikai/src/io/keikai/ui/au/in/TextHeightCommand.java new file mode 100644 index 0000000000..93f3a7eafb --- /dev/null +++ b/org.idempiere.keikai/src/io/keikai/ui/au/in/TextHeightCommand.java @@ -0,0 +1,73 @@ +/* CellFetchCommand.java + +{{IS_NOTE + Purpose: + + Description: + + History: + January 10, 2008 03:10:40 PM , Created by Dennis.Chen +}}IS_NOTE + +Copyright (C) 2007 Potix Corporation. All Rights Reserved. + +{{IS_RIGHT + This program is distributed under Lesser GPL Version 2.1 in the hope that + it will be useful, but WITHOUT ANY WARRANTY. +}}IS_RIGHT +*/ +package io.keikai.ui.au.in; + + +import java.util.Map; + +import io.keikai.api.model.Sheet; +import io.keikai.model.impl.AbstractCellAdv; +import io.keikai.ui.Spreadsheet; +import org.zkoss.lang.Objects; +import org.zkoss.zk.au.AuRequest; +import org.zkoss.zk.mesg.MZk; +import org.zkoss.zk.ui.Component; +import org.zkoss.zk.ui.UiException; + +/** + * A Command (client to server) for fetch data back + * @author Dennis.Chen + * + */ +public class TextHeightCommand extends AbstractCommand implements Command { + public final static String Command = "onZSSTextHeight"; + + //-- super --// + public void process(AuRequest request) { + final Component comp = request.getComponent(); + if (comp == null) + throw new UiException(MZk.ILLEGAL_REQUEST_COMPONENT_REQUIRED, TextHeightCommand.class.getCanonicalName()); + + final Map data = (Map) request.getData(); + if (data == null || data.size() != 4) + throw new UiException(MZk.ILLEGAL_REQUEST_WRONG_DATA, new Object[] {Objects.toString(data), TextHeightCommand.class.getCanonicalName() }); + + String sheetId = (String) data.get("sheetId"); + Sheet sheet = ((Spreadsheet) comp).getSelectedSheet(); + if (!getSheetUuid(sheet).equals(sheetId)) + return; + + int row = 0; + Object obj = data.get("row"); + if (obj instanceof Number) + row = ((Number) obj).intValue(); + + int col = 0; + obj = data.get("col"); + if (obj instanceof Number) + col = ((Number) obj).intValue(); + + int height = 0; + obj = data.get("height"); + if (obj instanceof Number) + height = ((Number) obj).intValue(); + + ((AbstractCellAdv)sheet.getInternalSheet().getCell(row, col)).setTextHeight(height); + } +} \ No newline at end of file diff --git a/org.idempiere.p2.targetplatform/maven.locations.xml b/org.idempiere.p2.targetplatform/maven.locations.xml index 70dbca1536..70630219ae 100644 --- a/org.idempiere.p2.targetplatform/maven.locations.xml +++ b/org.idempiere.p2.targetplatform/maven.locations.xml @@ -360,13 +360,13 @@ Export-Package: *;-noimport:=true org.bouncycastle bcpg-jdk18on - 1.72.2 + 1.77 jar org.bouncycastle bcmail-jdk18on - 1.72 + 1.77 jar diff --git a/org.idempiere.p2.targetplatform/org.idempiere.p2.targetplatform.target b/org.idempiere.p2.targetplatform/org.idempiere.p2.targetplatform.target index 3c4a55cf1c..6159f9367a 100644 --- a/org.idempiere.p2.targetplatform/org.idempiere.p2.targetplatform.target +++ b/org.idempiere.p2.targetplatform/org.idempiere.p2.targetplatform.target @@ -1,7 +1,7 @@ - + @@ -502,13 +502,13 @@ Export-Package: *;-noimport:=true org.bouncycastle bcpg-jdk18on - 1.72.2 + 1.77 jar org.bouncycastle bcmail-jdk18on - 1.72 + 1.77 jar diff --git a/org.idempiere.test/META-INF/MANIFEST.MF b/org.idempiere.test/META-INF/MANIFEST.MF index b076e9dc5b..ec389ceadb 100644 --- a/org.idempiere.test/META-INF/MANIFEST.MF +++ b/org.idempiere.test/META-INF/MANIFEST.MF @@ -5,7 +5,9 @@ Bundle-SymbolicName: org.idempiere.test Bundle-Version: 11.0.0.qualifier Bundle-Vendor: iDempiere Automatic-Module-Name: org.idempiere.test -Import-Package: org.assertj.core.api;version="3.22.0", +Import-Package: net.sf.jasperreports.export, + org.adempiere.report.jasper, + org.assertj.core.api;version="3.22.0", org.assertj.core.api.junit.jupiter;version="3.22.0", org.junit.jupiter.api;version="5.9.0", org.junit.jupiter.api.condition;version="5.9.0", @@ -43,4 +45,4 @@ Bundle-Activator: org.idempiere.test.TestActivator Bundle-RequiredExecutionEnvironment: JavaSE-17 Export-Package: org.idempiere.test Bundle-ClassPath: . -Service-Component: OSGI-INF/org.idempiere.test.event.MyComponent.xml +Service-Component: OSGI-INF/*.xml diff --git a/org.idempiere.test/OSGI-INF/org.idempiere.test.jasper.HandleSetupConfigurationPdfExport.xml b/org.idempiere.test/OSGI-INF/org.idempiere.test.jasper.HandleSetupConfigurationPdfExport.xml new file mode 100644 index 0000000000..d65e934384 --- /dev/null +++ b/org.idempiere.test/OSGI-INF/org.idempiere.test.jasper.HandleSetupConfigurationPdfExport.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/org.idempiere.test/OSGI-INF/org.idempiere.test.model.MTaxTest_TaxLookup.xml b/org.idempiere.test/OSGI-INF/org.idempiere.test.model.MTaxTest_TaxLookup.xml new file mode 100644 index 0000000000..214fd2904f --- /dev/null +++ b/org.idempiere.test/OSGI-INF/org.idempiere.test.model.MTaxTest_TaxLookup.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/org.idempiere.test/pom.xml b/org.idempiere.test/pom.xml index 40f3818142..c447127b41 100644 --- a/org.idempiere.test/pom.xml +++ b/org.idempiere.test/pom.xml @@ -126,9 +126,14 @@ eclipse-plugin - org.idempiere.tablepartition - 0.0.0 - + org.idempiere.tablepartition + 0.0.0 + + + eclipse-plugin + wrapped.org.apache.xmlbeans.xmlbeans + 0.0.0 + diff --git a/org.idempiere.test/src/org/idempiere/test/AbstractTestCase.java b/org.idempiere.test/src/org/idempiere/test/AbstractTestCase.java index cde0642607..0c1fd98fd1 100644 --- a/org.idempiere.test/src/org/idempiere/test/AbstractTestCase.java +++ b/org.idempiere.test/src/org/idempiere/test/AbstractTestCase.java @@ -24,10 +24,15 @@ **********************************************************************/ package org.idempiere.test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import java.math.RoundingMode; import java.sql.SQLException; import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.Properties; @@ -35,6 +40,7 @@ import org.adempiere.util.ServerContext; import org.compiere.Adempiere; import org.compiere.model.MAcctSchema; import org.compiere.model.MClientInfo; +import org.compiere.model.MFactAcct; import org.compiere.model.MRole; import org.compiere.util.Env; import org.compiere.util.Language; @@ -239,4 +245,104 @@ public abstract class AbstractTestCase { Adempiere.startup(false); } } + + /** + * Match expectedList against a list of MFactAcct records + * @param factAccts + * @param expectedList + */ + protected void assertFactAcctEntries(List factAccts, List expectedList) { + List found = new ArrayList(); + List matches = new ArrayList(); + expectedList.forEach(fa -> { + //LineId and account id match + List accountMatches = new ArrayList(); + //find exact match + for(MFactAcct mfa : factAccts) { + if (fa.account().getAccount().get_ID() == mfa.getAccount_ID()) { + if (fa.lineId() > 0 && fa.lineId() != mfa.getLine_ID()) + continue; + accountMatches.add(mfa); + if (fa.qty() != null && (mfa.getQty() == null || !mfa.getQty().setScale(fa.rounding(), RoundingMode.HALF_UP).equals(fa.qty().setScale(fa.rounding(), RoundingMode.HALF_UP)))) + continue; + if (fa.debit()) { + if (fa.accountedAmount() != null && !fa.accountedAmount().setScale(fa.rounding(), RoundingMode.HALF_UP).equals(mfa.getAmtAcctDr().setScale(fa.rounding(), RoundingMode.HALF_UP))) + continue; + if (fa.sourceAmount() != null && !fa.sourceAmount().setScale(fa.rounding(), RoundingMode.HALF_UP).equals(mfa.getAmtSourceDr().setScale(fa.rounding(), RoundingMode.HALF_UP))) + continue; + found.add(fa); + matches.add(mfa); + break; + } else { + if (fa.accountedAmount() != null && !fa.accountedAmount().setScale(fa.rounding(), RoundingMode.HALF_UP).equals(mfa.getAmtAcctCr().setScale(fa.rounding(), RoundingMode.HALF_UP))) + continue; + if (fa.sourceAmount() != null && !fa.sourceAmount().setScale(fa.rounding(), RoundingMode.HALF_UP).equals(mfa.getAmtSourceCr().setScale(fa.rounding(), RoundingMode.HALF_UP))) + continue; + found.add(fa); + matches.add(mfa); + break; + } + } + } + //assert qty mismatch + if (!found.contains(fa) && !accountMatches.isEmpty()) { + for(MFactAcct mfa : accountMatches) { + if (fa.debit()) { + if (fa.accountedAmount() != null && !fa.accountedAmount().setScale(fa.rounding(), RoundingMode.HALF_UP).equals(mfa.getAmtAcctDr().setScale(fa.rounding(), RoundingMode.HALF_UP))) + continue; + if (fa.sourceAmount() != null && !fa.sourceAmount().setScale(fa.rounding(), RoundingMode.HALF_UP).equals(mfa.getAmtSourceDr().setScale(fa.rounding(), RoundingMode.HALF_UP))) + continue; + } else { + if (fa.accountedAmount() != null && !fa.accountedAmount().setScale(fa.rounding(), RoundingMode.HALF_UP).equals(mfa.getAmtAcctCr().setScale(fa.rounding(), RoundingMode.HALF_UP))) + continue; + if (fa.sourceAmount() != null && !fa.sourceAmount().setScale(fa.rounding(), RoundingMode.HALF_UP).equals(mfa.getAmtSourceCr().setScale(fa.rounding(), RoundingMode.HALF_UP))) + continue; + } + assertEquals(fa.qty().setScale(fa.rounding(), RoundingMode.HALF_UP), mfa.getQty().setScale(fa.rounding(), RoundingMode.HALF_UP), "Unexpected Qty for " + fa); + found.add(fa); + } + } + }); + + //assert amount mismatch + expectedList.forEach(fa -> { + if (!found.contains(fa)) { + for(MFactAcct mfa : factAccts) { + if (matches.contains(mfa)) + continue; + if (fa.account().getAccount().get_ID() != mfa.getAccount_ID()) + continue; + if (fa.lineId() > 0 && fa.lineId() != mfa.getLine_ID()) + continue; + if (fa.debit()) { + if (fa.accountedAmount() != null && fa.accountedAmount().signum() == mfa.getAmtAcctDr().signum()) + assertEquals(fa.accountedAmount().setScale(fa.rounding(), RoundingMode.HALF_UP), mfa.getAmtAcctDr().setScale(fa.rounding(), RoundingMode.HALF_UP), "Unexpected Accounted Dr amount for " + fa); + else if (fa.accountedAmount() != null) + continue; + if (fa.sourceAmount() != null && fa.sourceAmount().signum() == mfa.getAmtSourceDr().signum()) + assertEquals(fa.accountedAmount().setScale(fa.rounding(), RoundingMode.HALF_UP), mfa.getAmtSourceDr().setScale(fa.rounding(), RoundingMode.HALF_UP), "Unexpected Source Dr amount for " + fa); + else if (fa.sourceAmount() != null) + continue; + } else { + if (fa.accountedAmount() != null && fa.accountedAmount().signum() == mfa.getAmtAcctCr().signum()) + assertEquals(fa.accountedAmount().setScale(fa.rounding(), RoundingMode.HALF_UP), mfa.getAmtAcctCr().setScale(fa.rounding(), RoundingMode.HALF_UP), "Unexpected Accounted Cr amount for " + fa); + else if (fa.accountedAmount() != null) + continue; + if (fa.sourceAmount() != null && fa.sourceAmount().signum() == mfa.getAmtSourceCr().signum()) + assertEquals(fa.accountedAmount().setScale(fa.rounding(), RoundingMode.HALF_UP), mfa.getAmtSourceCr().setScale(fa.rounding(), RoundingMode.HALF_UP), "Unexpected Source Cr amount for " + fa); + else if (fa.sourceAmount() != null) + continue; + } + if (fa.qty() != null && mfa.getQty() != null) + assertEquals(fa.qty().setScale(fa.rounding(), RoundingMode.HALF_UP), mfa.getQty().setScale(fa.rounding(), RoundingMode.HALF_UP), "Unexpected Qty for " + fa); + found.add(fa); + } + } + }); + + //assert not found + for(FactAcct factAcct : expectedList) { + assertTrue(found.contains(factAcct), "No fact acct record found for " + factAcct); + } + } } diff --git a/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java b/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java index ce180f06d5..e0504b3fbe 100644 --- a/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java +++ b/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java @@ -96,7 +96,17 @@ public final class DictionaryIDs { this.id = id; } } - + + public enum AD_SysConfig { + TAX_LOOKUP_SERVICE(200198); + + public final int id; + + private AD_SysConfig(int id) { + this.id = id; + } + } + public enum AD_User { GARDEN_ADMIN(101), GARDEN_USER(102), @@ -295,7 +305,17 @@ public final class DictionaryIDs { this.id = id; } } - + + public enum C_Location { + ORG_WH_HQ(114); + + public final int id; + + private C_Location(int id) { + this.id = id; + } + } + public enum C_PaymentTerm { NET_30(100), NET_30_DAYS(107), @@ -345,6 +365,16 @@ public final class DictionaryIDs { } } + public enum C_Region { + CT(102); + + public final int id; + + private C_Region(int id) { + this.id = id; + } + } + public enum C_Tax { STANDARD(104), CT_SALES(105), @@ -480,7 +510,18 @@ public final class DictionaryIDs { this.id = id; } } - + + public enum M_PriceList_Version { + STANDARD_2003(104), + IMPORT_2003(200000); + + public final int id; + + private M_PriceList_Version(int id) { + this.id = id; + } + } + public enum M_Product { STANDARD(122,"c713192a-9ed3-4740-ad32-9583c30d0206"), OAK(123,"220b7a9a-4917-4bb2-b431-1426afacd7b8"), @@ -542,7 +583,19 @@ public final class DictionaryIDs { this.id = id; } } - + + public enum M_ProductPrice { + PATIOSET_STANDARD_2003(200084), + PATIOCHAIR_STANDARD_2003(200033), + PATIOCHAIR_IMPORT_2003(200095); + + public final int id; + + private M_ProductPrice(int id) { + this.id = id; + } + } + public enum M_RMAType { DAMAGE_ON_ARRIVAL(100); diff --git a/org.idempiere.test/src/org/idempiere/test/FactAcct.java b/org.idempiere.test/src/org/idempiere/test/FactAcct.java new file mode 100644 index 0000000000..c4d7db80f5 --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/FactAcct.java @@ -0,0 +1,113 @@ +/*********************************************************************** + * This file is part of iDempiere ERP Open Source * + * http://www.idempiere.org * + * * + * Copyright (C) Contributors * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301, USA. * + **********************************************************************/ +package org.idempiere.test; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +import org.compiere.model.MAccount; + +/** + * @param account + * @param accountedAmount + * @param sourceAmount + * @param rounding + * @param debit + * @param lineId + * @param qty + */ +public record FactAcct(MAccount account, BigDecimal accountedAmount, BigDecimal sourceAmount, int rounding, boolean debit, int lineId, BigDecimal qty) { + /** + * @param account + * @param accountedAmount + * @param sourceAmount + * @param rounding + * @param debit + * @param lineId + */ + public FactAcct(MAccount account, BigDecimal accountedAmount, BigDecimal sourceAmount, int rounding, boolean debit, int lineId) { + this(account, accountedAmount, sourceAmount, rounding, debit, lineId, (BigDecimal)null); + } + + /** + * @param account + * @param accountedAmount + * @param sourceAmount + * @param rounding + * @param debit + * @param qty + */ + public FactAcct(MAccount account, BigDecimal accountedAmount, BigDecimal sourceAmount, int rounding, boolean debit, BigDecimal qty) { + this(account, accountedAmount, sourceAmount, rounding, debit, 0, qty); + } + + /** + * @param account + * @param accountedAmount + * @param rounding + * @param debit + * @param qty + */ + public FactAcct(MAccount account, BigDecimal accountedAmount, int rounding, boolean debit, BigDecimal qty) { + this(account, accountedAmount, null, rounding, debit, 0, qty); + } + + /** + * @param account + * @param accountedAmount + * @param rounding + * @param debit + */ + public FactAcct(MAccount account, BigDecimal accountedAmount, int rounding, boolean debit) { + this(account, accountedAmount, null, rounding, debit, 0); + } + + /** + * @param account + * @param accountedAmount + * @param sourceAmount + * @param rounding + * @param debit + */ + public FactAcct(MAccount account, BigDecimal accountedAmount, BigDecimal sourceAmount, int rounding, boolean debit) { + this(account, accountedAmount, sourceAmount, rounding, debit, 0); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("Account=").append(account.getAccount_ID()).append(" | ") + .append(account.getAccount()); + builder.append(", ").append(debit ? "Dr" : "Cr").append("["); + if (accountedAmount != null) + builder.append("Accounted=").append(accountedAmount.setScale(rounding, RoundingMode.HALF_UP).toPlainString()); + if (sourceAmount != null) { + if (accountedAmount != null) builder.append(", "); + builder.append("Source=").append(sourceAmount.setScale(rounding, RoundingMode.HALF_UP).toPlainString()); + } + builder.append("]"); + if (lineId > 0) + builder.append(", LineId=").append(lineId); + if (qty != null) + builder.append(", Qty=").append(qty.setScale(rounding, RoundingMode.HALF_UP).toPlainString()); + return builder.toString(); + } +} diff --git a/org.idempiere.test/src/org/idempiere/test/base/Convert_PostgreSQLTest.java b/org.idempiere.test/src/org/idempiere/test/base/Convert_PostgreSQLTest.java index 9d647f516d..ae576f06cf 100644 --- a/org.idempiere.test/src/org/idempiere/test/base/Convert_PostgreSQLTest.java +++ b/org.idempiere.test/src/org/idempiere/test/base/Convert_PostgreSQLTest.java @@ -201,6 +201,32 @@ public final class Convert_PostgreSQLTest extends AbstractTestCase { sqe = "ALTER TABLE C_InvoiceTax ADD COLUMN Created TIMESTAMP DEFAULT statement_timestamp() NOT NULL"; r = DB.getDatabase().convertStatement(sql); assertEquals(sqe, r.trim()); + + sql = "ALTER TABLE M_FreightCategory MODIFY Description VARCHAR2(255 CHAR) DEFAULT 'Test Default with Spaces'"; + sqe = "INSERT INTO t_alter_column values('m_freightcategory','Description','VARCHAR(255)',null,'Test Default with Spaces')"; + r = DB.getDatabase().convertStatement(sql); + assertEquals(sqe, r.trim()); + + sql = "ALTER TABLE M_FreightCategory ADD Description VARCHAR2(255 CHAR) DEFAULT 'Test Default with Spaces'"; + sqe = "ALTER TABLE M_FreightCategory ADD COLUMN Description VARCHAR(255) DEFAULT 'Test Default with Spaces'"; + r = DB.getDatabase().convertStatement(sql); + assertEquals(sqe, r.trim()); + + sql = "ALTER TABLE M_FreightCategory ADD JsonData CLOB DEFAULT NULL CONSTRAINT M_FreightCategory_JsonData_isjson CHECK (JsonData IS JSON)"; + sqe = "ALTER TABLE M_FreightCategory ADD COLUMN JsonData JSONB DEFAULT NULL"; + r = DB.getDatabase().convertStatement(sql); + assertEquals(sqe, r.trim()); + + sql = "ALTER TABLE M_FreightCategory ADD JsonData CLOB DEFAULT '{ \"color\": \"red\" }' CONSTRAINT M_FreightCategory_JsonData_isjson CHECK (JsonData IS JSON) NOT NULL"; + sqe = "ALTER TABLE M_FreightCategory ADD COLUMN JsonData JSONB DEFAULT '{ \"color\": \"red\" }' NOT NULL"; + r = DB.getDatabase().convertStatement(sql); + assertEquals(sqe, r.trim()); + + sql = "ALTER TABLE M_FreightCategory MODIFY JsonData CLOB DEFAULT '{ \"color\": \"red\" }' CONSTRAINT M_FreightCategory_JsonData_isjson CHECK (JsonData IS JSON)"; + sqe = "INSERT INTO t_alter_column values('m_freightcategory','JsonData','JSONB',null,'{ \"color\": \"red\" }')"; + r = DB.getDatabase().convertStatement(sql); + assertEquals(sqe, r.trim()); + } finally { Ini.setProperty(P_POSTGRE_SQL_NATIVE, originalNative); } diff --git a/org.idempiere.test/src/org/idempiere/test/base/GetBOMFunctionsTest.java b/org.idempiere.test/src/org/idempiere/test/base/GetBOMFunctionsTest.java new file mode 100644 index 0000000000..150580230f --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/base/GetBOMFunctionsTest.java @@ -0,0 +1,131 @@ +/****************************************************************************** + * Product: Adempiere ERP & CRM Smart Business Solution * + * Copyright (C) 2008 SC ARHIPAC SERVICE SRL. All Rights Reserved. * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. This program is distributed in the hope * + * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * See the GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.idempiere.test.base; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.math.BigDecimal; + +import org.compiere.model.MProductPrice; +import org.compiere.util.DB; +import org.compiere.util.Env; +import org.idempiere.test.AbstractTestCase; +import org.idempiere.test.DictionaryIDs; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; + +/** + * Test for the getBOM SQL function classes + */ +@Isolated +public class GetBOMFunctionsTest extends AbstractTestCase +{ + + @Test + public void test_getBOMFunctions() throws Exception + { + BigDecimal price; + String trxName = getTrxName(); + + // Prices of PatioSet in Standard 2003 Price List Version + + price = DB.getSQLValueBDEx(trxName, "SELECT bompricestd("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_PriceList_Version.STANDARD_2003.id+") FROM Dual"); + assertTrue(BigDecimal.valueOf(500.0).compareTo(price) == 0, "Unexpected direct bompricestd"); + + price = DB.getSQLValueBDEx(trxName, "SELECT bompricelist("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_PriceList_Version.STANDARD_2003.id+") FROM Dual"); + assertTrue(BigDecimal.valueOf(520.0).compareTo(price) == 0, "Unexpected direct bompricelist"); + + price = DB.getSQLValueBDEx(trxName, "SELECT bompricelimit("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_PriceList_Version.STANDARD_2003.id+") FROM Dual"); + assertTrue(BigDecimal.valueOf(496.0).compareTo(price) == 0, "Unexpected direct bompricelimit"); + + MProductPrice productPrice = new MProductPrice(Env.getCtx(), DictionaryIDs.M_ProductPrice.PATIOSET_STANDARD_2003.id, trxName); + productPrice.setIsActive(false); + productPrice.saveEx(); + + price = DB.getSQLValueBDEx(trxName, "SELECT bompricestd("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_PriceList_Version.STANDARD_2003.id+") FROM Dual"); + assertTrue(BigDecimal.valueOf(225.0).compareTo(price) == 0, "Unexpected indirect bompricestd"); + + price = DB.getSQLValueBDEx(trxName, "SELECT bompricelist("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_PriceList_Version.STANDARD_2003.id+") FROM Dual"); + assertTrue(BigDecimal.valueOf(220.0).compareTo(price) == 0, "Unexpected indirect bompricelist"); + + price = DB.getSQLValueBDEx(trxName, "SELECT bompricelimit("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_PriceList_Version.STANDARD_2003.id+") FROM Dual"); + assertTrue(BigDecimal.valueOf(180.0).compareTo(price) == 0, "Unexpected indirect bompricelimit"); + + productPrice = new MProductPrice(Env.getCtx(), DictionaryIDs.M_ProductPrice.PATIOCHAIR_STANDARD_2003.id, trxName); + productPrice.setIsActive(false); + productPrice.saveEx(); + + price = DB.getSQLValueBDEx(trxName, "SELECT bompricestd("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_PriceList_Version.STANDARD_2003.id+") FROM Dual"); + assertTrue(BigDecimal.valueOf(90.0).compareTo(price) == 0, "Unexpected double indirect bompricestd"); + + price = DB.getSQLValueBDEx(trxName, "SELECT bompricelist("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_PriceList_Version.STANDARD_2003.id+") FROM Dual"); + assertTrue(BigDecimal.valueOf(88.0).compareTo(price) == 0, "Unexpected double indirect bompricelist"); + + price = DB.getSQLValueBDEx(trxName, "SELECT bompricelimit("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_PriceList_Version.STANDARD_2003.id+") FROM Dual"); + assertTrue(BigDecimal.valueOf(72.0).compareTo(price) == 0, "Unexpected double indirect bompricelimit"); + + // Prices of PatioSet in Import 2003 Price List Version + + price = DB.getSQLValueBDEx(trxName, "SELECT bompricestd("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_PriceList_Version.IMPORT_2003.id+") FROM Dual"); + assertTrue(BigDecimal.valueOf(153.0).compareTo(price) == 0, "Unexpected indirect bompricestd"); + + price = DB.getSQLValueBDEx(trxName, "SELECT bompricelist("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_PriceList_Version.IMPORT_2003.id+") FROM Dual"); + assertTrue(BigDecimal.valueOf(170.0).compareTo(price) == 0, "Unexpected indirect bompricelist"); + + price = DB.getSQLValueBDEx(trxName, "SELECT bompricelimit("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_PriceList_Version.IMPORT_2003.id+") FROM Dual"); + assertTrue(BigDecimal.valueOf(144.52).compareTo(price) == 0, "Unexpected indirect bompricelimit"); + + productPrice = new MProductPrice(Env.getCtx(), DictionaryIDs.M_ProductPrice.PATIOCHAIR_IMPORT_2003.id, trxName); + productPrice.setIsActive(false); + productPrice.saveEx(); + + price = DB.getSQLValueBDEx(trxName, "SELECT bompricestd("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_PriceList_Version.IMPORT_2003.id+") FROM Dual"); + assertTrue(BigDecimal.valueOf(84.8).compareTo(price) == 0, "Unexpected double indirect bompricestd"); + + price = DB.getSQLValueBDEx(trxName, "SELECT bompricelist("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_PriceList_Version.IMPORT_2003.id+") FROM Dual"); + assertTrue(BigDecimal.valueOf(107.36).compareTo(price) == 0, "Unexpected double indirect bompricelist"); + + price = DB.getSQLValueBDEx(trxName, "SELECT bompricelimit("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_PriceList_Version.IMPORT_2003.id+") FROM Dual"); + assertTrue(BigDecimal.valueOf(77.52).compareTo(price) == 0, "Unexpected double indirect bompricelimit"); + + // Quantities of PatioSet in Standard 2003 Price List Version + + BigDecimal qty; + qty = DB.getSQLValueBDEx(trxName, "SELECT bomqtyavailable("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_Warehouse.HQ.id+",0) FROM Dual"); + assertTrue(BigDecimal.valueOf(0.0).compareTo(qty) == 0, "Unexpected direct bomqtyavailable"); + + qty = DB.getSQLValueBDEx(trxName, "SELECT bomqtyonhand("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_Warehouse.HQ.id+",0) FROM Dual"); + assertTrue(BigDecimal.valueOf(0.0).compareTo(qty) == 0, "Unexpected direct bomqtyonhand"); + + // Set PatioSet as non-stocked + DB.executeUpdateEx("UPDATE M_Product SET IsStocked='N' WHERE M_Product_ID=?", new Object[] {DictionaryIDs.M_Product.PATIOSET.id}, trxName); + + qty = DB.getSQLValueBDEx(trxName, "SELECT bomqtyavailable("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_Warehouse.HQ.id+",0) FROM Dual"); + assertTrue(BigDecimal.valueOf(7.0).compareTo(qty) == 0, "Unexpected indirect bomqtyavailable"); + + qty = DB.getSQLValueBDEx(trxName, "SELECT bomqtyonhand("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_Warehouse.HQ.id+",0) FROM Dual"); + assertTrue(BigDecimal.valueOf(7.0).compareTo(qty) == 0, "Unexpected indirect bomqtyonhand"); + + // Set PatioChair as non-stocked, directly to avoid error about existing stock + DB.executeUpdateEx("UPDATE M_Product SET IsStocked='N' WHERE M_Product_ID=?", new Object[] {DictionaryIDs.M_Product.P_CHAIR.id}, trxName); + + qty = DB.getSQLValueBDEx(trxName, "SELECT bomqtyavailable("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_Warehouse.HQ.id+",0) FROM Dual"); + assertTrue(BigDecimal.valueOf(0.0).compareTo(qty) == 0, "Unexpected double indirect bomqtyavailable"); + + qty = DB.getSQLValueBDEx(trxName, "SELECT bomqtyonhand("+DictionaryIDs.M_Product.PATIOSET.id+","+DictionaryIDs.M_Warehouse.HQ.id+",0) FROM Dual"); + assertTrue(BigDecimal.valueOf(0.0).compareTo(qty) == 0, "Unexpected double indirect bomqtyonhand"); + + } + +} diff --git a/org.idempiere.test/src/org/idempiere/test/base/InOutTest.java b/org.idempiere.test/src/org/idempiere/test/base/InOutTest.java index ea63d3a786..f27af152d8 100644 --- a/org.idempiere.test/src/org/idempiere/test/base/InOutTest.java +++ b/org.idempiere.test/src/org/idempiere/test/base/InOutTest.java @@ -31,6 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; import java.math.RoundingMode; import java.sql.Timestamp; +import java.util.Arrays; import java.util.List; import org.compiere.acct.Doc; @@ -70,6 +71,7 @@ import org.compiere.wf.MWorkflow; import org.idempiere.test.AbstractTestCase; import org.idempiere.test.ConversionRateHelper; import org.idempiere.test.DictionaryIDs; +import org.idempiere.test.FactAcct; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.ResourceLock; @@ -146,23 +148,14 @@ public class InOutTest extends AbstractTestCase { doc.setC_BPartner_ID(receipt.getC_BPartner_ID()); MAccount acctNIR = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); - String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MInOut.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + receipt.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID(); - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if (acctNIR.getAccount_ID() == fa.getAccount_ID()) { - if (receiptLine.get_ID() == fa.getLine_ID()) { - BigDecimal acctSource = orderLine.getPriceActual().multiply(receiptLine.getMovementQty()) - .setScale(as.getC_Currency().getStdPrecision(), RoundingMode.HALF_UP); - BigDecimal acctAmount = acctSource.multiply(rate) - .setScale(as.getC_Currency().getStdPrecision(), RoundingMode.HALF_UP); - assertTrue(fa.getAmtSourceCr().compareTo(acctSource) == 0, fa.getAmtSourceCr().toPlainString() + " != " + acctSource.toPlainString()); - assertTrue(fa.getAmtAcctCr().compareTo(acctAmount) == 0, fa.getAmtAcctCr().toPlainString() + " != " + acctAmount.toPlainString()); - } - } - } + BigDecimal acctSource = orderLine.getPriceActual().multiply(receiptLine.getMovementQty()) + .setScale(as.getC_Currency().getStdPrecision(), RoundingMode.HALF_UP); + BigDecimal acctAmount = acctSource.multiply(rate) + .setScale(as.getC_Currency().getStdPrecision(), RoundingMode.HALF_UP); + Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, receipt.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctNIR, acctAmount, acctSource, as.getC_Currency().getStdPrecision(), false, receiptLine.get_ID())); + assertFactAcctEntries(factAccts, expected); } order = createPurchaseOrder(bpartner, currentDate, priceList.getM_PriceList_ID(), Spot_ConversionType_ID); @@ -185,23 +178,14 @@ public class InOutTest extends AbstractTestCase { doc.setC_BPartner_ID(receipt.getC_BPartner_ID()); MAccount acctNIR = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); - String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MInOut.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + receipt.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID(); - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if (acctNIR.getAccount_ID() == fa.getAccount_ID()) { - if (receiptLine.get_ID() == fa.getLine_ID()) { - BigDecimal acctSource = orderLine.getPriceActual().multiply(receiptLine.getMovementQty()) + BigDecimal acctSource = orderLine.getPriceActual().multiply(receiptLine.getMovementQty()) .setScale(as.getC_Currency().getStdPrecision(), RoundingMode.HALF_UP); - BigDecimal acctAmount = acctSource.multiply(rate) - .setScale(as.getC_Currency().getStdPrecision(), RoundingMode.HALF_UP); - assertTrue(fa.getAmtSourceCr().compareTo(acctSource) == 0, fa.getAmtSourceCr().toPlainString() + " != " + acctSource.toPlainString()); - assertTrue(fa.getAmtAcctCr().compareTo(acctAmount) == 0, fa.getAmtAcctCr().toPlainString() + " != " + acctAmount.toPlainString()); - } - } - } + BigDecimal acctAmount = acctSource.multiply(rate) + .setScale(as.getC_Currency().getStdPrecision(), RoundingMode.HALF_UP); + Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, receipt.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctNIR, acctAmount, acctSource, as.getC_Currency().getStdPrecision(), false, receiptLine.get_ID())); + assertFactAcctEntries(factAccts, expected); } } finally { rollback(); @@ -276,20 +260,14 @@ public class InOutTest extends AbstractTestCase { doc.setC_BPartner_ID(receipt.getC_BPartner_ID()); MAccount acctNIR = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); + BigDecimal acctSource = orderLine.getPriceActual().multiply(receiptLine.getMovementQty()) + .setScale(as.getC_Currency().getStdPrecision(), RoundingMode.HALF_UP); + BigDecimal acctAmount = acctSource.multiply(rate) + .setScale(as.getC_Currency().getStdPrecision(), RoundingMode.HALF_UP); Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, receipt.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); List fas = query.list(); - for (MFactAcct fa : fas) { - if (acctNIR.getAccount_ID() == fa.getAccount_ID()) { - if (receiptLine.get_ID() == fa.getLine_ID()) { - BigDecimal acctSource = orderLine.getPriceActual().multiply(receiptLine.getMovementQty()) - .setScale(as.getC_Currency().getStdPrecision(), RoundingMode.HALF_UP); - BigDecimal acctAmount = acctSource.multiply(rate) - .setScale(as.getC_Currency().getStdPrecision(), RoundingMode.HALF_UP); - assertTrue(fa.getAmtSourceCr().compareTo(acctSource) == 0, fa.getAmtSourceCr().toPlainString() + " != " + acctSource.toPlainString()); - assertTrue(fa.getAmtAcctCr().compareTo(acctAmount) == 0, fa.getAmtAcctCr().toPlainString() + " != " + acctAmount.toPlainString()); - } - } - } + List expected = Arrays.asList(new FactAcct(acctNIR, acctAmount, acctSource, 2, false, receiptLine.get_ID())); + assertFactAcctEntries(fas, expected); } MRMA rma = new MRMA(Env.getCtx(), 0, getTrxName()); @@ -343,19 +321,14 @@ public class InOutTest extends AbstractTestCase { doc.setC_BPartner_ID(delivery.getC_BPartner_ID()); MAccount acctNIR = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); + BigDecimal acctSource = orderLine.getPriceActual().multiply(deliveryLine.getMovementQty()) + .setScale(as.getC_Currency().getStdPrecision(), RoundingMode.HALF_UP); + BigDecimal acctAmount = acctSource.multiply(rate) + .setScale(as.getC_Currency().getStdPrecision(), RoundingMode.HALF_UP); Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, delivery.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); List fas = query.list(); - for (MFactAcct fa : fas) { - if (acctNIR.getAccount_ID() == fa.getAccount_ID()) { - if (deliveryLine.get_ID() == fa.getLine_ID()) { - BigDecimal acctSource = orderLine.getPriceActual().multiply(deliveryLine.getMovementQty()) - .setScale(as.getC_Currency().getStdPrecision(), RoundingMode.HALF_UP); - BigDecimal acctAmount = acctSource.multiply(rate) - .setScale(as.getC_Currency().getStdPrecision(), RoundingMode.HALF_UP); - assertTrue(fa.getAmtAcctDr().compareTo(acctAmount) == 0, fa.getAmtAcctDr().toPlainString() + " != " + acctAmount.toPlainString()); - } - } - } + List expected = Arrays.asList(new FactAcct(acctNIR, acctAmount, null, 2, true, deliveryLine.get_ID())); + assertFactAcctEntries(fas, expected); } } finally { rollback(); diff --git a/org.idempiere.test/src/org/idempiere/test/base/JsonFieldTest.java b/org.idempiere.test/src/org/idempiere/test/base/JsonFieldTest.java new file mode 100644 index 0000000000..fa04940332 --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/base/JsonFieldTest.java @@ -0,0 +1,77 @@ +package org.idempiere.test.base; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; + +import org.compiere.dbPort.Convert; +import org.compiere.model.I_AD_UserPreference; +import org.compiere.model.MTest; +import org.compiere.util.Env; +import org.compiere.util.Ini; +import org.idempiere.test.AbstractTestCase; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; + +/** + * Tests for JSON data type + * Run Isolated because of migration script file management + */ +@Isolated +public class JsonFieldTest extends AbstractTestCase { + + /** + * + */ + public JsonFieldTest() { + } + + @Test + public void testSavingJSONValue() { + MTest testPO = new MTest(Env.getCtx(), getClass().getName(), 1, getTrxName()); + boolean updated; + testPO.setJsonData("Testing if JSON allows to save regular strings"); + updated = testPO.save(); + assertFalse(updated); + + testPO = new MTest(Env.getCtx(), getClass().getName(), 1, getTrxName()); + String validJsonString = "{ \"name\": \"iDempiere\", \"id\": 100 }"; + testPO.setJsonData(validJsonString); + updated = testPO.save(); + assertTrue(updated); + + String validJsonArray= "[ {\"type\": \"mobile\", \"phone\": \"001001\"} , {\"type\": \"fix\", \"phone\": \"002002\"} ]"; + testPO.setJsonData(validJsonArray); + updated = testPO.save(); + assertTrue(updated); + + testPO.setJsonData(null); + updated = testPO.save(); + assertTrue(updated); + + //Test inserting/updating with Values + Env.getCtx().setProperty(Ini.P_LOGMIGRATIONSCRIPT, "Y"); + Env.setContext(Env.getCtx(), I_AD_UserPreference.COLUMNNAME_MigrationScriptComment, "IDEMPIERE-02981 JsonFieldTest"); + testPO.setJsonData(validJsonString); + updated = testPO.save(); + assertTrue(updated); + + testPO.setJsonData(validJsonArray); + updated = testPO.save(); + assertTrue(updated); + + Env.getCtx().setProperty(Ini.P_LOGMIGRATIONSCRIPT, ""); + + String fileName = Convert.getGeneratedMigrationScriptFileName(); + String folderPg = Convert.getMigrationScriptFolder("postgresql"); + String folderOr = Convert.getMigrationScriptFolder("oracle"); + Convert.closeLogMigrationScript(); + File file = new File(folderPg + fileName); + assertTrue(file.exists(), "Not found: " + folderPg + fileName); + file.delete(); + file = new File(folderOr + fileName); + assertTrue(file.exists(), "Not found: " + folderOr + fileName); + file.delete(); + } +} diff --git a/org.idempiere.test/src/org/idempiere/test/base/MatchInv2ndAcctSchemaTest.java b/org.idempiere.test/src/org/idempiere/test/base/MatchInv2ndAcctSchemaTest.java index 360fb087c4..ceb22ca0cb 100644 --- a/org.idempiere.test/src/org/idempiere/test/base/MatchInv2ndAcctSchemaTest.java +++ b/org.idempiere.test/src/org/idempiere/test/base/MatchInv2ndAcctSchemaTest.java @@ -34,6 +34,7 @@ import java.sql.Timestamp; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; +import java.util.List; import org.compiere.acct.Doc; import org.compiere.acct.DocManager; @@ -59,6 +60,7 @@ import org.compiere.model.MProductPrice; import org.compiere.model.MWarehouse; import org.compiere.model.PO; import org.compiere.model.ProductCost; +import org.compiere.model.Query; import org.compiere.process.DocAction; import org.compiere.process.DocumentEngine; import org.compiere.process.ProcessInfo; @@ -1840,12 +1842,9 @@ public class MatchInv2ndAcctSchemaTest extends AbstractTestCase { BigDecimal totalNIRAmtAcct = Env.ZERO; BigDecimal totalInvClrAmtAcct = Env.ZERO; - String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MMatchInv.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + mi.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID(); - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); + Query query = MFactAcct.createRecordIdQuery(MMatchInv.Table_ID, mi.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + for (MFactAcct fa : factAccts) { if (acctNIR.getAccount_ID() == fa.getAccount_ID()) totalNIRAmtAcct = totalNIRAmtAcct.add(fa.getAmtAcctDr()).subtract(fa.getAmtAcctCr()); else if (acctInvClr.getAccount_ID() == fa.getAccount_ID()) diff --git a/org.idempiere.test/src/org/idempiere/test/base/MatchInvTest.java b/org.idempiere.test/src/org/idempiere/test/base/MatchInvTest.java index 6cb12b5b0f..84e31de68f 100644 --- a/org.idempiere.test/src/org/idempiere/test/base/MatchInvTest.java +++ b/org.idempiere.test/src/org/idempiere/test/base/MatchInvTest.java @@ -31,6 +31,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; import java.math.RoundingMode; import java.sql.Timestamp; +import java.util.Arrays; +import java.util.List; import org.compiere.acct.Doc; import org.compiere.acct.DocManager; @@ -63,9 +65,11 @@ import org.compiere.process.DocAction; import org.compiere.process.DocumentEngine; import org.compiere.process.ProcessInfo; import org.compiere.util.Env; +import org.compiere.util.TimeUtil; import org.compiere.wf.MWorkflow; import org.idempiere.test.AbstractTestCase; import org.idempiere.test.DictionaryIDs; +import org.idempiere.test.FactAcct; import org.junit.jupiter.api.Test; /** @@ -223,17 +227,10 @@ public class MatchInvTest extends AbstractTestCase { ProductCost pc = new ProductCost (Env.getCtx(), mi.getM_Product_ID(), mi.getM_AttributeSetInstance_ID(), getTrxName()); MAccount acctInvClr = pc.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); - String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MMatchInv.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + mi.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID(); - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if (fa.getAccount_ID() == acctNIR.getAccount_ID()) - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), credMatchAmt.setScale(2, RoundingMode.HALF_UP), "MatchInv incorrect amount posted "+fa.getAmtAcctCr().toPlainString()); - else if (fa.getAccount_ID() == acctInvClr.getAccount_ID()) - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), credMatchAmt.setScale(2, RoundingMode.HALF_UP), "MatchInv incorrect amount posted "+fa.getAmtAcctDr().toPlainString()); - } + Query query = MFactAcct.createRecordIdQuery(MMatchInv.Table_ID, mi.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctNIR, credMatchAmt, 2, false), new FactAcct(acctInvClr, credMatchAmt, 2, true)); + assertFactAcctEntries(factAccts, expected); } rollback(); @@ -334,17 +331,10 @@ public class MatchInvTest extends AbstractTestCase { ProductCost pc = new ProductCost (Env.getCtx(), mi.getM_Product_ID(), mi.getM_AttributeSetInstance_ID(), getTrxName()); MAccount acctInvClr = pc.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); - String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MMatchInv.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + mi.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID(); - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if (fa.getAccount_ID() == acctNIR.getAccount_ID()) - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), invMatchAmt.setScale(2, RoundingMode.HALF_UP), "MatchInv incorrect amount posted "+fa.getAmtAcctCr().toPlainString()); - else if (fa.getAccount_ID() == acctInvClr.getAccount_ID()) - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), invMatchAmt.setScale(2, RoundingMode.HALF_UP), "MatchInv incorrect amount posted "+fa.getAmtAcctCr().toPlainString()); - } + Query query = MFactAcct.createRecordIdQuery(MMatchInv.Table_ID, mi.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctNIR, invMatchAmt, 2, true), new FactAcct(acctInvClr, invMatchAmt, 2, false)); + assertFactAcctEntries(factAccts, expected); } rollback(); @@ -452,21 +442,11 @@ public class MatchInvTest extends AbstractTestCase { ProductCost pc = new ProductCost (Env.getCtx(), mi.getM_Product_ID(), mi.getM_AttributeSetInstance_ID(), getTrxName()); MAccount acctInvClr = pc.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); - String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MMatchInv.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + mi.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID(); - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if (fa.getAccount_ID() == acctNIR.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), invMatchAmt.setScale(2, RoundingMode.HALF_UP), "MatchInv incorrect amount posted "+fa.getAmtAcctDr().toPlainString()); - assertEquals(mi.getQty(), fa.getQty(), "Accounting fact quantity incorrect"); - } - else if (fa.getAccount_ID() == acctInvClr.getAccount_ID()) { - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), invMatchAmt.setScale(2, RoundingMode.HALF_UP), "MatchInv incorrect amount posted "+fa.getAmtAcctCr().toPlainString()); - assertEquals(mi.getQty().negate().setScale(2, RoundingMode.HALF_UP), fa.getQty().setScale(2, RoundingMode.HALF_UP), "Accounting fact quantity incorrect"); - } - } + Query query = MFactAcct.createRecordIdQuery(MMatchInv.Table_ID, mi.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctInvClr, invMatchAmt, 2, false, mi.getQty().negate()), + new FactAcct(acctNIR, invMatchAmt, 2, true, mi.getQty())); + assertFactAcctEntries(factAccts, expected); } MInvoice creditMemo = new MInvoice(Env.getCtx(), 0, getTrxName()); @@ -513,26 +493,11 @@ public class MatchInvTest extends AbstractTestCase { ProductCost pc = new ProductCost (Env.getCtx(), mi.getM_Product_ID(), mi.getM_AttributeSetInstance_ID(), getTrxName()); MAccount acctInvClr = pc.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); - BigDecimal amtAcctDrInvClr = BigDecimal.ZERO; - BigDecimal amtAcctCrInvClr = BigDecimal.ZERO; - String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MMatchInv.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + mi.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID(); - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if (fa.getAccount_ID() == acctInvClr.getAccount_ID() && fa.getQty().compareTo(BigDecimal.ZERO) < 0) { - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), credMatchAmt.setScale(2, RoundingMode.HALF_UP), "MatchInv incorrect amount posted "+fa.getAmtAcctCr().toPlainString()); - amtAcctCrInvClr = amtAcctCrInvClr.add(fa.getAmtAcctCr()); - assertEquals(mi.getQty(), fa.getQty(), "Accounting fact quantity incorrect"); - } - else if (fa.getAccount_ID() == acctInvClr.getAccount_ID() && fa.getQty().compareTo(BigDecimal.ZERO) > 0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), credMatchAmt.setScale(2, RoundingMode.HALF_UP), "MatchInv incorrect amount posted "+fa.getAmtAcctDr().toPlainString()); - amtAcctDrInvClr = amtAcctDrInvClr.add(fa.getAmtAcctDr()); - assertEquals(mi.getQty().negate(), fa.getQty(), "Accounting fact quantity incorrect"); - } - } - assertTrue(amtAcctDrInvClr.compareTo(amtAcctCrInvClr) == 0); + Query query = MFactAcct.createRecordIdQuery(MMatchInv.Table_ID, mi.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctInvClr, credMatchAmt, 2, false, mi.getQty()), + new FactAcct(acctInvClr, credMatchAmt, 2, true, mi.getQty().negate())); + assertFactAcctEntries(factAccts, expected); } rollback(); @@ -679,25 +644,14 @@ public class MatchInvTest extends AbstractTestCase { ProductCost pc = new ProductCost (Env.getCtx(), mi.getM_Product_ID(), mi.getM_AttributeSetInstance_ID(), getTrxName()); MAccount acctInvClr = pc.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); - String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MMatchInv.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + mi.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID(); - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if (fa.getAccount_ID() == acctNIR.getAccount_ID()) { - assertTrue(fa.getAmtAcctDr().compareTo(Env.ZERO) >= 0); - assertEquals(acctAmount, fa.getAmtAcctDr(), fa.getAmtAcctDr().toPlainString() + " != " + acctAmount.toPlainString()); - // verify source amt and currency - assertTrue(fa.getC_Currency_ID() == japaneseYen.getC_Currency_ID()); - assertEquals(acctSource, fa.getAmtSourceDr(), fa.getAmtSourceDr().toPlainString() + " != " + acctSource.toPlainString()); - } else if (fa.getAccount_ID() == acctInvClr.getAccount_ID()) { - assertTrue(fa.getAmtAcctCr().compareTo(Env.ZERO) >= 0); - assertEquals(acctAmount, fa.getAmtAcctCr(), fa.getAmtAcctCr().toPlainString() + " != " + acctAmount.toPlainString()); - // verify source amt and currency - assertTrue(fa.getC_Currency_ID() == japaneseYen.getC_Currency_ID()); - assertEquals(acctSource, fa.getAmtSourceCr(), fa.getAmtSourceCr().toPlainString() + " != " + acctSource.toPlainString()); - } + Query query = MFactAcct.createRecordIdQuery(MMatchInv.Table_ID, mi.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctNIR, acctAmount, acctSource, 2, true), + new FactAcct(acctInvClr, acctAmount, acctSource, 2, false)); + assertFactAcctEntries(factAccts, expected); + assertTrue(acctAmount.compareTo(Env.ZERO) >= 0); + for (MFactAcct fa : factAccts) { + assertTrue(fa.getC_Currency_ID() == japaneseYen.getC_Currency_ID()); } } } finally { @@ -1042,26 +996,205 @@ public class MatchInvTest extends AbstractTestCase { ProductCost pc = new ProductCost (Env.getCtx(), mi.getM_Product_ID(), mi.getM_AttributeSetInstance_ID(), getTrxName()); MAccount acctInvClr = pc.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); - String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MMatchInv.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + mi.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID(); - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if (fa.getAccount_ID() == acctNIR.getAccount_ID()) { - if (mi.isReversal()) - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), invMatchAmt.setScale(2, RoundingMode.HALF_UP), "MatchInv incorrect amount posted "); - else - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), invMatchAmt.setScale(2, RoundingMode.HALF_UP), "MatchInv incorrect amount posted "); - assertEquals(mi.getQty(), fa.getQty(), "Accounting fact quantity incorrect"); + Query query = MFactAcct.createRecordIdQuery(MMatchInv.Table_ID, mi.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctNIR, invMatchAmt, 2, !mi.isReversal(), mi.getQty()), + new FactAcct(acctInvClr, invMatchAmt, 2, mi.isReversal(), mi.getQty().negate())); + assertFactAcctEntries(factAccts, expected); + } + } + + @Test + public void testReversalPostingWithZeroOnHand() { + MBPartner bpartner = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.TREE_FARM.id); // Tree Farm Inc. + + MProduct product = null; + MClient client = MClient.get(Env.getCtx()); + MAcctSchema as = client.getAcctSchema(); + Timestamp today = TimeUtil.getDay(null); + try { + product = new MProduct(Env.getCtx(), 0, null); + product.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + product.setName("testReversalPostingWithZeroOnHand"); + product.setProductType(MProduct.PRODUCTTYPE_Item); + product.setIsStocked(true); + product.setIsSold(true); + product.setIsPurchased(true); + product.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + product.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + product.saveEx(); + + MPriceListVersion plv = MPriceList.get(DictionaryIDs.M_PriceList.PURCHASE.id).getPriceListVersion(null); + MProductPrice pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(product.get_ID()); + BigDecimal orderPrice = new BigDecimal("2.00"); + pp.setPriceStd(orderPrice); + pp.setPriceList(orderPrice); + pp.saveEx(); + + int purchaseId = DictionaryIDs.M_PriceList.PURCHASE.id; // Purchase Price List + MOrder order = new MOrder(Env.getCtx(), 0, getTrxName()); + order.setBPartner(bpartner); + order.setIsSOTrx(false); + order.setC_DocTypeTarget_ID(); + order.setM_PriceList_ID(purchaseId); + order.setDocStatus(DocAction.STATUS_Drafted); + order.setDocAction(DocAction.ACTION_Complete); + order.saveEx(); + + MOrderLine orderLine = new MOrderLine(order); + orderLine.setLine(10); + orderLine.setProduct(product); + orderLine.setQty(BigDecimal.TEN); + orderLine.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete); + order.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, order.getDocStatus()); + + MInOut receipt = new MInOut(order, DictionaryIDs.C_DocType.MM_RECEIPT.id, order.getDateOrdered()); // MM Receipt + receipt.saveEx(); + + MInOutLine receiptLine = new MInOutLine(receipt); + receiptLine.setC_OrderLine_ID(orderLine.get_ID()); + receiptLine.setLine(10); + receiptLine.setProduct(product); + receiptLine.setQty(orderLine.getQtyOrdered()); + MWarehouse wh = MWarehouse.get(Env.getCtx(), receipt.getM_Warehouse_ID()); + int M_Locator_ID = wh.getDefaultLocator().getM_Locator_ID(); + receiptLine.setM_Locator_ID(M_Locator_ID); + receiptLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete); + receipt.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus()); + + if (!receipt.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), receipt.getAD_Client_ID(), MInOut.Table_ID, receipt.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + receipt.load(getTrxName()); + assertTrue(receipt.isPosted()); + + //customer shipment + MOrder salesOrder = new MOrder(Env.getCtx(), 0, getTrxName()); + salesOrder.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id)); + salesOrder.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard); + salesOrder.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder); + salesOrder.setDocStatus(DocAction.STATUS_Drafted); + salesOrder.setDocAction(DocAction.ACTION_Complete); + salesOrder.setDatePromised(today); + salesOrder.saveEx(); + + MOrderLine salesLine1 = new MOrderLine(salesOrder); + salesLine1.setLine(10); + salesLine1.setProduct(product); + salesLine1.setQty(orderLine.getQtyOrdered()); + salesLine1.setDatePromised(today); + salesLine1.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(salesOrder, DocAction.ACTION_Complete); + salesOrder.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, salesOrder.getDocStatus(), "Unexpected Document Status"); + + MInOut shipment = new MInOut(salesOrder, DictionaryIDs.C_DocType.MM_SHIPMENT.id, salesOrder.getDateOrdered()); + shipment.setDocStatus(DocAction.STATUS_Drafted); + shipment.setDocAction(DocAction.ACTION_Complete); + shipment.saveEx(); + + MInOutLine shipmentLine = new MInOutLine(shipment); + shipmentLine.setOrderLine(salesLine1, 0, orderLine.getQtyOrdered()); + shipmentLine.setQty(orderLine.getQtyOrdered()); + shipmentLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(shipment, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + shipment.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, shipment.getDocStatus(), "Unexpected Document Status"); + + MInvoice invoice = new MInvoice(receipt, receipt.getMovementDate()); + invoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice); + invoice.setDocStatus(DocAction.STATUS_Drafted); + invoice.setDocAction(DocAction.ACTION_Complete); + invoice.saveEx(); + + //MR invoice + MInvoiceLine invoiceLine = new MInvoiceLine(invoice); + invoiceLine.setM_InOutLine_ID(receiptLine.get_ID()); + invoiceLine.setLine(10); + invoiceLine.setProduct(product); + invoiceLine.setQty(orderLine.getQtyOrdered()); + invoiceLine.setPrice(orderPrice.add(BigDecimal.ONE)); + invoiceLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete); + invoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus()); + + if (!invoice.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), invoice.getAD_Client_ID(), MInvoice.Table_ID, invoice.get_ID(), false, getTrxName()); + assertTrue(error == null, error); + } + BigDecimal invMatchAmt = invoiceLine.getMatchedQty().multiply(invoiceLine.getPriceActual()).setScale(as.getStdPrecision(), RoundingMode.HALF_UP); + BigDecimal poMatchAmt = invoiceLine.getMatchedQty().multiply(orderLine.getPriceActual()).setScale(as.getStdPrecision(), RoundingMode.HALF_UP); + + invoice.load(getTrxName()); + assertTrue(invoice.isPosted()); + + //reverse MR invoice + info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Reverse_Correct); + invoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Reversed, invoice.getDocStatus()); + + MInvoice reversalInvoice = new MInvoice(Env.getCtx(), invoice.getReversal_ID(), getTrxName()); + if (!reversalInvoice.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), reversalInvoice.getAD_Client_ID(), MInvoice.Table_ID, invoice.get_ID(), false, getTrxName()); + assertTrue(error == null, error); + } + + MMatchInv[] miList = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine.get_ID(), getTrxName()); + assertEquals(2, miList.length); + for (MMatchInv mi : miList) { + if (!mi.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), mi.getAD_Client_ID(), MMatchInv.Table_ID, mi.get_ID(), false, getTrxName()); + assertTrue(error == null, error); } - else if (fa.getAccount_ID() == acctInvClr.getAccount_ID()) { - if (mi.isReversal()) - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), invMatchAmt.setScale(2, RoundingMode.HALF_UP), "MatchInv incorrect amount posted "); - else - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), invMatchAmt.setScale(2, RoundingMode.HALF_UP), "MatchInv incorrect amount posted "); - assertEquals(mi.getQty().negate(), fa.getQty(), "Accounting fact quantity incorrect"); + mi.load(getTrxName()); + assertTrue(mi.isPosted()); + MMatchInv mir = new MMatchInv(Env.getCtx(), mi.getReversal_ID(), getTrxName()); + if (!mir.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), mir.getAD_Client_ID(), MMatchInv.Table_ID, mir.get_ID(), false, getTrxName()); + assertTrue(error == null, error); } + mir.load(getTrxName()); + assertTrue(mir.isPosted()); + + Doc doc = DocManager.getDocument(as, MMatchInv.Table_ID, mi.get_ID(), getTrxName()); + doc.setC_BPartner_ID(mi.getC_InvoiceLine().getC_Invoice().getC_BPartner_ID()); + MAccount acctNIR = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); + + ProductCost pc = new ProductCost (Env.getCtx(), mi.getM_Product_ID(), mi.getM_AttributeSetInstance_ID(), getTrxName()); + MAccount acctInvClr = pc.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); + MAccount acctIPV = pc.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as); + Query query = MFactAcct.createRecordIdQuery(MMatchInv.Table_ID, mi.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctNIR, poMatchAmt, 2, !mi.isReversal(), mi.getQty()), + new FactAcct(acctInvClr, invMatchAmt, 2, mi.isReversal(), mi.getQty().negate()), + new FactAcct(acctIPV, invMatchAmt.subtract(poMatchAmt), 2, !mi.isReversal())); + assertFactAcctEntries(factAccts, expected); + } + } finally { + rollback(); + + if (product != null) { + product.set_TrxName(null); + product.deleteEx(true); } } } diff --git a/org.idempiere.test/src/org/idempiere/test/base/MatchInvTestIsolated.java b/org.idempiere.test/src/org/idempiere/test/base/MatchInvTestIsolated.java index b7c59daa34..210e51ed8c 100644 --- a/org.idempiere.test/src/org/idempiere/test/base/MatchInvTestIsolated.java +++ b/org.idempiere.test/src/org/idempiere/test/base/MatchInvTestIsolated.java @@ -27,12 +27,17 @@ package org.idempiere.test.base; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; import java.math.RoundingMode; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Optional; import org.compiere.acct.Doc; import org.compiere.acct.DocManager; @@ -42,6 +47,8 @@ import org.compiere.model.MBPartner; import org.compiere.model.MClient; import org.compiere.model.MConversionRate; import org.compiere.model.MCost; +import org.compiere.model.MCostElement; +import org.compiere.model.MCurrency; import org.compiere.model.MDocType; import org.compiere.model.MFactAcct; import org.compiere.model.MInOut; @@ -53,19 +60,26 @@ import org.compiere.model.MInvoiceLine; import org.compiere.model.MMatchInv; import org.compiere.model.MOrder; import org.compiere.model.MOrderLine; +import org.compiere.model.MPriceList; +import org.compiere.model.MPriceListVersion; import org.compiere.model.MProduct; import org.compiere.model.MProductCategory; import org.compiere.model.MProductCategoryAcct; +import org.compiere.model.MProductPrice; import org.compiere.model.MWarehouse; import org.compiere.model.ProductCost; import org.compiere.model.Query; import org.compiere.process.DocAction; import org.compiere.process.DocumentEngine; import org.compiere.process.ProcessInfo; +import org.compiere.util.CacheMgt; import org.compiere.util.Env; +import org.compiere.util.TimeUtil; import org.compiere.wf.MWorkflow; import org.idempiere.test.AbstractTestCase; +import org.idempiere.test.ConversionRateHelper; import org.idempiere.test.DictionaryIDs; +import org.idempiere.test.FactAcct; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Isolated; @@ -240,21 +254,13 @@ public class MatchInvTestIsolated extends AbstractTestCase { MAccount acctIPV = pc.getAccount(ProductCost.ACCTTYPE_P_IPV, as); int C_AcctSchema_ID = as.getC_AcctSchema_ID(); - String whereClause2 = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MMatchInv.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + mi.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + C_AcctSchema_ID; - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause2, getTrxName()); + Query query = MFactAcct.createRecordIdQuery(MMatchInv.Table_ID, mi.get_ID(), C_AcctSchema_ID, getTrxName()); + List factAccts = query.list(); BigDecimal invMatchAmt = invoiceLine.getMatchedQty().multiply(invoiceLine.getPriceActual()).setScale(as.getStdPrecision(), RoundingMode.HALF_UP); mulchCost = mulchCost.setScale(as.getStdPrecision(), RoundingMode.HALF_UP); - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if (fa.getAccount_ID() == acctNIR.getAccount_ID()) - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), mulchCost.setScale(2, RoundingMode.HALF_UP), ""); - else if (fa.getAccount_ID() == acctInvClr.getAccount_ID()) - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), invMatchAmt.setScale(2, RoundingMode.HALF_UP), ""); - else if (fa.getAccount_ID() == acctIPV.getAccount_ID()) - assertEquals(fa.getAmtAcctDr().subtract(fa.getAmtAcctCr()).setScale(2, RoundingMode.HALF_UP), invMatchAmt.subtract(mulchCost).setScale(2, RoundingMode.HALF_UP), ""); - } + List expected = Arrays.asList(new FactAcct(acctNIR, mulchCost, 2, true), new FactAcct(acctInvClr, invMatchAmt, 2, false), + new FactAcct(acctIPV, invMatchAmt.subtract(mulchCost), 2, true)); + assertFactAcctEntries(factAccts, expected); } } finally { getTrx().rollback(); @@ -263,4 +269,617 @@ public class MatchInvTestIsolated extends AbstractTestCase { category.deleteEx(true); } } + + /** + * Test Average PO Cost and Invoice Price Variance posting + */ + @Test + public void testAverageCostingIPV() { + MProduct product = null; + MClient client = MClient.get(Env.getCtx()); + MAcctSchema as = client.getAcctSchema(); + assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO"); + + try { + product = new MProduct(Env.getCtx(), 0, null); + product.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + product.setName("testAverageCostingIPV"); + product.setProductType(MProduct.PRODUCTTYPE_Item); + product.setIsStocked(true); + product.setIsSold(true); + product.setIsPurchased(true); + product.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + product.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + product.saveEx(); + + MPriceListVersion plv = MPriceList.get(DictionaryIDs.M_PriceList.PURCHASE.id).getPriceListVersion(null); + MProductPrice pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(product.get_ID()); + BigDecimal orderPrice = new BigDecimal("2.00"); + pp.setPriceStd(orderPrice); + pp.setPriceList(orderPrice); + pp.saveEx(); + + int purchaseId = DictionaryIDs.M_PriceList.PURCHASE.id; // Purchase Price List + MBPartner bpartner = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.SEED_FARM.id); + + MOrder order = new MOrder(Env.getCtx(), 0, getTrxName()); + order.setBPartner(bpartner); + order.setIsSOTrx(false); + order.setC_DocTypeTarget_ID(); + order.setM_PriceList_ID(purchaseId); + order.setDocStatus(DocAction.STATUS_Drafted); + order.setDocAction(DocAction.ACTION_Complete); + order.saveEx(); + + MOrderLine orderLine = new MOrderLine(order); + orderLine.setLine(10); + orderLine.setProduct(product); + orderLine.setQty(BigDecimal.TEN); + orderLine.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete); + order.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, order.getDocStatus()); + + MInOut receipt = new MInOut(order, 122, order.getDateOrdered()); // MM Receipt + receipt.saveEx(); + + MInOutLine receiptLine = new MInOutLine(receipt); + receiptLine.setC_OrderLine_ID(orderLine.get_ID()); + receiptLine.setLine(10); + receiptLine.setProduct(product); + receiptLine.setQty(BigDecimal.TEN); + MWarehouse wh = MWarehouse.get(Env.getCtx(), receipt.getM_Warehouse_ID()); + int M_Locator_ID = wh.getDefaultLocator().getM_Locator_ID(); + receiptLine.setM_Locator_ID(M_Locator_ID); + receiptLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete); + receipt.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus()); + + if (!receipt.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), receipt.getAD_Client_ID(), MInOut.Table_ID, receipt.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + receipt.load(getTrxName()); + assertTrue(receipt.isPosted()); + + product.set_TrxName(getTrxName()); + MCost cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(cost, "No MCost record found"); + assertEquals(orderPrice, cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + MInvoice invoice = new MInvoice(receipt, receipt.getMovementDate()); + invoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice); + invoice.setDocStatus(DocAction.STATUS_Drafted); + invoice.setDocAction(DocAction.ACTION_Complete); + invoice.saveEx(); + + MInvoiceLine invoiceLine = new MInvoiceLine(invoice); + invoiceLine.setM_InOutLine_ID(receiptLine.get_ID()); + invoiceLine.setLine(10); + invoiceLine.setProduct(product); + invoiceLine.setQty(BigDecimal.TEN); + BigDecimal invoicePrice = new BigDecimal("2.50"); + invoiceLine.setPrice(invoicePrice); + invoiceLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete); + invoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus()); + + if (!invoice.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), invoice.getAD_Client_ID(), MInvoice.Table_ID, invoice.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + invoice.load(getTrxName()); + assertTrue(invoice.isPosted()); + + MMatchInv[] miList = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine.get_ID(), getTrxName()); + for (MMatchInv mi : miList) { + if (!mi.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), mi.getAD_Client_ID(), MMatchInv.Table_ID, mi.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + mi.load(getTrxName()); + assertTrue(mi.isPosted()); + + cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(cost, "No MCost record found"); + assertEquals(invoicePrice, cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + ProductCost pc = new ProductCost (Env.getCtx(), mi.getM_Product_ID(), mi.getM_AttributeSetInstance_ID(), getTrxName()); + MAccount acctInvClr = pc.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); + MAccount acctAsset = pc.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + Doc doc = DocManager.getDocument(as, MInvoice.Table_ID, invoice.get_ID(), getTrxName()); + MAccount nirAccount = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); + int C_AcctSchema_ID = as.getC_AcctSchema_ID(); + + Query query = MFactAcct.createRecordIdQuery(MMatchInv.Table_ID, mi.get_ID(), C_AcctSchema_ID, getTrxName()); + List factAccts = query.list(); + BigDecimal ipvAmt = invoicePrice.subtract(orderPrice).multiply(BigDecimal.TEN); + List expected = Arrays.asList(new FactAcct(acctAsset, ipvAmt, 2, true), + new FactAcct(nirAccount, orderPrice.multiply(BigDecimal.TEN), 2, true), + new FactAcct(acctInvClr, invoicePrice.multiply(BigDecimal.TEN), 2, false)); + assertFactAcctEntries(factAccts, expected); + } + } finally { + rollback(); + + if (product != null) { + product.set_TrxName(null); + product.deleteEx(true); + } + } + } + + /** + * Test Average PO Cost and Invoice Price Variance posting (after customer shipment) + */ + @Test + public void testAverageCostingIPVAfterShipment() { + MProduct product = null; + MClient client = MClient.get(Env.getCtx()); + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + List allowNegatives = new ArrayList(); + Arrays.stream(ass).forEach(e -> { + MAcctSchema copy = MAcctSchema.getCopy(Env.getCtx(), e.getC_AcctSchema_ID(), null); + if (copy.isAllowNegativePosting()) + { + copy.setIsAllowNegativePosting(false); + copy.saveEx(); + allowNegatives.add(copy); + } + }); + if (allowNegatives.size() > 0) + CacheMgt.get().reset(MAcctSchema.Table_Name); + ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + MAcctSchema as = client.getAcctSchema(); + assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO"); + + MCurrency usd = MCurrency.get(DictionaryIDs.C_Currency.USD.id); + MCurrency euro = MCurrency.get(DictionaryIDs.C_Currency.EUR.id); + int C_ConversionType_ID = DictionaryIDs.C_ConversionType.SPOT.id; + Timestamp today = TimeUtil.getDay(null); + Timestamp tomorrow = TimeUtil.addDays(today, 1); + MConversionRate cr1 = ConversionRateHelper.createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, today, new BigDecimal("0.91"), true); + MConversionRate cr2 = ConversionRateHelper.createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, tomorrow, new BigDecimal("0.85"), true); + try { + product = new MProduct(Env.getCtx(), 0, null); + product.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + product.setName("testAverageCostingIPVAfterShipment"); + product.setProductType(MProduct.PRODUCTTYPE_Item); + product.setIsStocked(true); + product.setIsSold(true); + product.setIsPurchased(true); + product.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + product.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + product.saveEx(); + + MPriceListVersion plv = MPriceList.get(DictionaryIDs.M_PriceList.PURCHASE.id).getPriceListVersion(null); + MProductPrice pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(product.get_ID()); + BigDecimal orderPrice = new BigDecimal("2.00"); + pp.setPriceStd(orderPrice); + pp.setPriceList(orderPrice); + pp.saveEx(); + + //PO and MR + int purchaseId = DictionaryIDs.M_PriceList.PURCHASE.id; // Purchase Price List + MBPartner bpartner = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.SEED_FARM.id); + + MOrder order = new MOrder(Env.getCtx(), 0, getTrxName()); + order.setBPartner(bpartner); + order.setIsSOTrx(false); + order.setC_DocTypeTarget_ID(); + order.setM_PriceList_ID(purchaseId); + order.setDocStatus(DocAction.STATUS_Drafted); + order.setDocAction(DocAction.ACTION_Complete); + order.saveEx(); + + MOrderLine orderLine = new MOrderLine(order); + orderLine.setLine(10); + orderLine.setProduct(product); + orderLine.setQty(BigDecimal.TEN); + orderLine.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete); + order.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, order.getDocStatus()); + + MInOut receipt = new MInOut(order, 122, order.getDateOrdered()); // MM Receipt + receipt.saveEx(); + + MInOutLine receiptLine = new MInOutLine(receipt); + receiptLine.setC_OrderLine_ID(orderLine.get_ID()); + receiptLine.setLine(10); + receiptLine.setProduct(product); + receiptLine.setQty(BigDecimal.TEN); + MWarehouse wh = MWarehouse.get(Env.getCtx(), receipt.getM_Warehouse_ID()); + int M_Locator_ID = wh.getDefaultLocator().getM_Locator_ID(); + receiptLine.setM_Locator_ID(M_Locator_ID); + receiptLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete); + receipt.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus()); + + if (!receipt.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), receipt.getAD_Client_ID(), MInOut.Table_ID, receipt.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + receipt.load(getTrxName()); + assertTrue(receipt.isPosted()); + + product.set_TrxName(getTrxName()); + MCost cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(cost, "No MCost record found"); + assertEquals(orderPrice, cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + //customer shipment + MOrder salesOrder = new MOrder(Env.getCtx(), 0, getTrxName()); + salesOrder.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id)); + salesOrder.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard); + salesOrder.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder); + salesOrder.setDocStatus(DocAction.STATUS_Drafted); + salesOrder.setDocAction(DocAction.ACTION_Complete); + salesOrder.setDatePromised(today); + salesOrder.saveEx(); + + BigDecimal salesQty = new BigDecimal("5"); + MOrderLine salesLine1 = new MOrderLine(salesOrder); + salesLine1.setLine(10); + salesLine1.setProduct(product); + salesLine1.setQty(salesQty); + salesLine1.setDatePromised(today); + salesLine1.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(salesOrder, DocAction.ACTION_Complete); + salesOrder.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, salesOrder.getDocStatus(), "Unexpected Document Status"); + + MInOut shipment = new MInOut(salesOrder, DictionaryIDs.C_DocType.MM_SHIPMENT.id, salesOrder.getDateOrdered()); + shipment.setDocStatus(DocAction.STATUS_Drafted); + shipment.setDocAction(DocAction.ACTION_Complete); + shipment.saveEx(); + + MInOutLine shipmentLine = new MInOutLine(shipment); + shipmentLine.setOrderLine(salesLine1, 0, salesQty); + shipmentLine.setQty(salesQty); + shipmentLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(shipment, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + shipment.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, shipment.getDocStatus(), "Unexpected Document Status"); + + //MR invoice + MInvoice invoice = new MInvoice(receipt, receipt.getMovementDate()); + invoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice); + invoice.setDocStatus(DocAction.STATUS_Drafted); + invoice.setDocAction(DocAction.ACTION_Complete); + invoice.saveEx(); + + MInvoiceLine invoiceLine = new MInvoiceLine(invoice); + invoiceLine.setM_InOutLine_ID(receiptLine.get_ID()); + invoiceLine.setLine(10); + invoiceLine.setProduct(product); + invoiceLine.setQty(BigDecimal.TEN); + BigDecimal invoicePrice = new BigDecimal("2.50"); + invoiceLine.setPrice(invoicePrice); + invoiceLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete); + invoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus()); + + if (!invoice.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), invoice.getAD_Client_ID(), MInvoice.Table_ID, invoice.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + invoice.load(getTrxName()); + assertTrue(invoice.isPosted()); + + MMatchInv[] miList = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine.get_ID(), getTrxName()); + for (MMatchInv mi : miList) { + if (!mi.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), mi.getAD_Client_ID(), MMatchInv.Table_ID, mi.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + mi.load(getTrxName()); + assertTrue(mi.isPosted()); + + cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(cost, "No MCost record found"); + assertEquals(invoicePrice, cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + ProductCost pc = new ProductCost (Env.getCtx(), mi.getM_Product_ID(), mi.getM_AttributeSetInstance_ID(), getTrxName()); + MAccount acctInvClr = pc.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); + MAccount acctAsset = pc.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + MAccount varianceAccount = pc.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as); + Doc doc = DocManager.getDocument(as, MInvoice.Table_ID, invoice.get_ID(), getTrxName()); + MAccount nirAccount = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); + int C_AcctSchema_ID = as.getC_AcctSchema_ID(); + + Query query = MFactAcct.createRecordIdQuery(MMatchInv.Table_ID, mi.get_ID(), C_AcctSchema_ID, getTrxName()); + List factAccts = query.list(); + BigDecimal stockBalance = BigDecimal.TEN.subtract(salesQty); + BigDecimal assetAmt = invoicePrice.subtract(orderPrice).multiply(stockBalance); + List expected = Arrays.asList(new FactAcct(acctAsset, assetAmt, 2, true), + new FactAcct(varianceAccount, invoicePrice.subtract(orderPrice).multiply(BigDecimal.TEN.subtract(stockBalance)), 2, true), + new FactAcct(nirAccount, orderPrice.multiply(BigDecimal.TEN), 2, true), + new FactAcct(acctInvClr, invoicePrice.multiply(BigDecimal.TEN), 2, false)); + assertFactAcctEntries(factAccts, expected); + } + + //test reversal posting + Env.setContext(Env.getCtx(), Env.DATE, tomorrow); + info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Reverse_Accrual); + invoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Reversed, invoice.getDocStatus()); + assertTrue(invoice.getReversal_ID() > 0, "No reversal invoice id"); + + MInvoice reversalInvoice = new MInvoice(Env.getCtx(), invoice.getReversal_ID(), getTrxName()); + assertEquals(invoice.getReversal_ID(), reversalInvoice.get_ID(), "Failed to load reversal invoice"); + if (!reversalInvoice.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), reversalInvoice.getAD_Client_ID(), MInvoice.Table_ID, reversalInvoice.get_ID(), false, getTrxName()); + assertTrue(error == null, error); + } + reversalInvoice.load(getTrxName()); + assertTrue(reversalInvoice.isPosted()); + + for (MMatchInv mi : miList) { + mi.load(getTrxName()); + Query query = MFactAcct.createRecordIdQuery(MMatchInv.Table_ID, mi.get_ID(), as.get_ID(), getTrxName()); + List factAccts = query.list(); + query = MFactAcct.createRecordIdQuery(MMatchInv.Table_ID, mi.getReversal_ID(), as.get_ID(), getTrxName()); + List rFactAccts = query.list(); + ArrayList expected = new ArrayList(); + for(MFactAcct factAcct : factAccts) { + MAccount acct = MAccount.get(factAcct, getTrxName()); + if (factAcct.getAmtAcctDr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr(), 2, false)); + } else if (factAcct.getAmtAcctCr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr(), 2, true)); + } + } + assertFactAcctEntries(rFactAccts, expected); + + Optional optional = Arrays.stream(ass).filter(e -> e.getC_AcctSchema_ID() != as.get_ID()).findFirst(); + if (optional.isPresent()) { + MAcctSchema as2 = optional.get(); + query = MFactAcct.createRecordIdQuery(MMatchInv.Table_ID, mi.get_ID(), as2.get_ID(), getTrxName()); + factAccts = query.list(); + query = MFactAcct.createRecordIdQuery(MMatchInv.Table_ID, mi.getReversal_ID(), as2.get_ID(), getTrxName()); + rFactAccts = query.list(); + expected = new ArrayList(); + for(MFactAcct factAcct : factAccts) { + MAccount acct = MAccount.get(factAcct, getTrxName()); + if (factAcct.getAmtAcctDr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr(), 2, false)); + } else if (factAcct.getAmtAcctCr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr(), 2, true)); + } + } + assertFactAcctEntries(rFactAccts, expected); + } + } + + //assert reversal invoice posting + Query query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, invoice.get_ID(), as.get_ID(), getTrxName()); + List factAccts = query.list(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, invoice.getReversal_ID(), as.get_ID(), getTrxName()); + List rFactAccts = query.list(); + ArrayList expected = new ArrayList(); + for(MFactAcct factAcct : factAccts) { + MAccount acct = MAccount.get(factAcct, getTrxName()); + if (factAcct.getAmtAcctDr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr(), 2, false)); + } else if (factAcct.getAmtAcctCr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr(), 2, true)); + } + } + assertFactAcctEntries(rFactAccts, expected); + + Optional optional = Arrays.stream(ass).filter(e -> e.getC_AcctSchema_ID() != as.get_ID()).findFirst(); + if (optional.isPresent()) { + MAcctSchema as2 = optional.get(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, invoice.get_ID(), as2.get_ID(), getTrxName()); + factAccts = query.list(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, invoice.getReversal_ID(), as2.get_ID(), getTrxName()); + rFactAccts = query.list(); + expected = new ArrayList(); + for(MFactAcct factAcct : factAccts) { + MAccount acct = MAccount.get(factAcct, getTrxName()); + if (factAcct.getAmtAcctDr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr(), 2, false)); + } else if (factAcct.getAmtAcctCr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr(), 2, true)); + } + } + assertFactAcctEntries(rFactAccts, expected); + } + } finally { + rollback(); + + if (product != null) { + product.set_TrxName(null); + product.deleteEx(true); + } + ConversionRateHelper.deleteConversionRate(cr1); + ConversionRateHelper.deleteConversionRate(cr2); + + if (allowNegatives.size() > 0) { + allowNegatives.forEach(e -> { + e.setIsAllowNegativePosting(true); + e.saveEx(); + }); + } + + } + } + + /** + * Test Average PO Cost and Invoice Price Variance posting for partial MR + */ + @Test + public void testAverageCostingIPVPartialMR() { + MProduct product = null; + MClient client = MClient.get(Env.getCtx()); + MAcctSchema as = client.getAcctSchema(); + assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO"); + + try { + product = new MProduct(Env.getCtx(), 0, null); + product.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + product.setName("testAverageCostingIPVPartialMR"); + product.setProductType(MProduct.PRODUCTTYPE_Item); + product.setIsStocked(true); + product.setIsSold(true); + product.setIsPurchased(true); + product.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + product.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + product.saveEx(); + + MPriceListVersion plv = MPriceList.get(DictionaryIDs.M_PriceList.PURCHASE.id).getPriceListVersion(null); + MProductPrice pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(product.get_ID()); + BigDecimal orderPrice = new BigDecimal("2.00"); + pp.setPriceStd(orderPrice); + pp.setPriceList(orderPrice); + pp.saveEx(); + + int purchaseId = DictionaryIDs.M_PriceList.PURCHASE.id; // Purchase Price List + MBPartner bpartner = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.SEED_FARM.id); + + MOrder order = new MOrder(Env.getCtx(), 0, getTrxName()); + order.setBPartner(bpartner); + order.setIsSOTrx(false); + order.setC_DocTypeTarget_ID(); + order.setM_PriceList_ID(purchaseId); + order.setDocStatus(DocAction.STATUS_Drafted); + order.setDocAction(DocAction.ACTION_Complete); + order.saveEx(); + + MOrderLine orderLine = new MOrderLine(order); + orderLine.setLine(10); + orderLine.setProduct(product); + orderLine.setQty(BigDecimal.TEN); + orderLine.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete); + order.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, order.getDocStatus()); + + //partial MR + MInOut receipt = new MInOut(order, 122, order.getDateOrdered()); // MM Receipt + receipt.saveEx(); + + BigDecimal mrQty = new BigDecimal("5"); + MInOutLine receiptLine = new MInOutLine(receipt); + receiptLine.setC_OrderLine_ID(orderLine.get_ID()); + receiptLine.setLine(10); + receiptLine.setProduct(product); + receiptLine.setQty(mrQty); + MWarehouse wh = MWarehouse.get(Env.getCtx(), receipt.getM_Warehouse_ID()); + int M_Locator_ID = wh.getDefaultLocator().getM_Locator_ID(); + receiptLine.setM_Locator_ID(M_Locator_ID); + receiptLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete); + receipt.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus()); + + if (!receipt.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), receipt.getAD_Client_ID(), MInOut.Table_ID, receipt.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + receipt.load(getTrxName()); + assertTrue(receipt.isPosted()); + + product.set_TrxName(getTrxName()); + MCost cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(cost, "No MCost record found"); + assertEquals(orderPrice, cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + //ap invoce, full + MInvoice invoice = new MInvoice(receipt, receipt.getMovementDate()); + invoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice); + invoice.setDocStatus(DocAction.STATUS_Drafted); + invoice.setDocAction(DocAction.ACTION_Complete); + invoice.saveEx(); + + MInvoiceLine invoiceLine = new MInvoiceLine(invoice); + invoiceLine.setM_InOutLine_ID(receiptLine.get_ID()); + invoiceLine.setLine(10); + invoiceLine.setProduct(product); + invoiceLine.setQty(BigDecimal.TEN); + BigDecimal invoicePrice = new BigDecimal("4.00"); + invoiceLine.setPrice(invoicePrice); + invoiceLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete); + invoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus()); + + if (!invoice.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), invoice.getAD_Client_ID(), MInvoice.Table_ID, invoice.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + invoice.load(getTrxName()); + assertTrue(invoice.isPosted()); + + MMatchInv[] miList = MMatchInv.getInvoiceLine(Env.getCtx(), invoiceLine.get_ID(), getTrxName()); + for (MMatchInv mi : miList) { + if (!mi.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), mi.getAD_Client_ID(), MMatchInv.Table_ID, mi.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + mi.load(getTrxName()); + assertTrue(mi.isPosted()); + + cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(cost, "No MCost record found"); + assertEquals(invoicePrice, cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + ProductCost pc = new ProductCost (Env.getCtx(), mi.getM_Product_ID(), mi.getM_AttributeSetInstance_ID(), getTrxName()); + MAccount acctInvClr = pc.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); + MAccount acctAsset = pc.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + Doc doc = DocManager.getDocument(as, MInvoice.Table_ID, invoice.get_ID(), getTrxName()); + MAccount nirAccount = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); + int C_AcctSchema_ID = as.getC_AcctSchema_ID(); + + Query query = MFactAcct.createRecordIdQuery(MMatchInv.Table_ID, mi.get_ID(), C_AcctSchema_ID, getTrxName()); + List factAccts = query.list(); + BigDecimal assetAmt = invoicePrice.subtract(orderPrice).multiply(mrQty); + List expected = Arrays.asList(new FactAcct(acctAsset, assetAmt, 2, true), + new FactAcct(nirAccount, orderPrice.multiply(mrQty), 2, true), + new FactAcct(acctInvClr, invoicePrice.multiply(mrQty), 2, false)); + assertFactAcctEntries(factAccts, expected); + } + } finally { + rollback(); + + if (product != null) { + product.set_TrxName(null); + product.deleteEx(true); + } + } + } } diff --git a/org.idempiere.test/src/org/idempiere/test/base/POTest.java b/org.idempiere.test/src/org/idempiere/test/base/POTest.java index 5387072a34..d856dace80 100644 --- a/org.idempiere.test/src/org/idempiere/test/base/POTest.java +++ b/org.idempiere.test/src/org/idempiere/test/base/POTest.java @@ -58,12 +58,16 @@ import org.compiere.util.Trx; import org.idempiere.test.AbstractTestCase; import org.idempiere.test.DictionaryIDs; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; /** * Tests for {@link org.compiere.model.PO} class. * @author Teo Sarca, SC ARHIPAC SERVICE SRL * @author hengsin + * + * Run Isolated because of migration script file management */ +@Isolated public class POTest extends AbstractTestCase { public static class MyTestPO extends MTest @@ -512,9 +516,6 @@ public class POTest extends AbstractTestCase Env.getCtx().setProperty(Ini.P_LOGMIGRATIONSCRIPT, "Y"); Env.setContext(Env.getCtx(), I_AD_UserPreference.COLUMNNAME_MigrationScriptComment, "testLogMigrationScript"); assertTrue(Env.isLogMigrationScript(MProduct.Table_Name), "Unexpected Log Migration Script Y/N value for MProduct"); - String fileName = Convert.getMigrationScriptFileName("testLogMigrationScript"); - String folderPg = Convert.getMigrationScriptFolder("postgresql"); - String folderOr = Convert.getMigrationScriptFolder("oracle"); MProductCategory lotLevel = new MProductCategory(Env.getCtx(), 0, null); lotLevel.setName("testLogMigrationScript"); @@ -548,6 +549,10 @@ public class POTest extends AbstractTestCase lotLevel.deleteEx(true); } + String fileName = Convert.getGeneratedMigrationScriptFileName(); + String folderPg = Convert.getMigrationScriptFolder("postgresql"); + String folderOr = Convert.getMigrationScriptFolder("oracle"); + Convert.closeLogMigrationScript(); File file = new File(folderPg + fileName); assertTrue(file.exists(), "Not found: " + folderPg + fileName); file.delete(); diff --git a/org.idempiere.test/src/org/idempiere/test/base/ReportTest.java b/org.idempiere.test/src/org/idempiere/test/base/ReportTest.java new file mode 100644 index 0000000000..9d806bd423 --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/base/ReportTest.java @@ -0,0 +1,75 @@ +/*********************************************************************** + * This file is part of iDempiere ERP Open Source * + * http://www.idempiere.org * + * * + * Copyright (C) Contributors * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301, USA. * + * * + * Contributors: * + * - Carlos Ruiz - globalqss * + **********************************************************************/ +package org.idempiere.test.base; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.File; + +import org.compiere.model.MOrder; +import org.compiere.model.MPInstance; +import org.compiere.model.MProcess; +import org.compiere.model.SystemIDs; +import org.compiere.process.ProcessInfo; +import org.compiere.process.ServerProcessCtl; +import org.compiere.util.Env; +import org.idempiere.test.AbstractTestCase; +import org.junit.jupiter.api.Test; + +/** + * @author Diego Ruiz - BX Service GmbH + */ +public class ReportTest extends AbstractTestCase { + + public ReportTest() { + } + + /** + * https://idempiere.atlassian.net/browse/IDEMPIERE-6165 + */ + @Test + public void testPDFFileName() { + MProcess orderReport = MProcess.get(Env.getCtx(), SystemIDs.PROCESS_RPT_C_ORDER); + MOrder order = new MOrder(Env.getCtx(), 108, getTrxName()); // Garden Order 60000 + + String fileName = order.getDocumentNo() + ".pdf"; + + ProcessInfo pi = new ProcessInfo(orderReport.getName(), orderReport.getAD_Process_ID()); + pi.setRecord_ID(order.getC_Order_ID()); + pi.setAD_Client_ID(Env.getAD_Client_ID(Env.getCtx())); + pi.setTable_ID(order.get_Table_ID()); + pi.setPrintPreview(true); + pi.setIsBatch(true); + pi.setPDFFileName(fileName); + pi.setReportType("PDF"); + MPInstance instance = new MPInstance(orderReport, order.get_Table_ID(), order.getC_Order_ID(), order.getC_Order_UU()); + instance.saveEx(); + ServerProcessCtl.process(pi, null); + File file = pi.getPDFReport(); + + assertEquals(file.getName(), fileName); + + } +} diff --git a/org.idempiere.test/src/org/idempiere/test/costing/AveragePOCostingTest.java b/org.idempiere.test/src/org/idempiere/test/costing/AveragePOCostingTest.java index 5d3fc2fb0b..99b6c11f78 100644 --- a/org.idempiere.test/src/org/idempiere/test/costing/AveragePOCostingTest.java +++ b/org.idempiere.test/src/org/idempiere/test/costing/AveragePOCostingTest.java @@ -29,30 +29,42 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.math.BigDecimal; import java.math.RoundingMode; import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Optional; import org.compiere.acct.Doc; import org.compiere.acct.DocManager; import org.compiere.model.MAccount; import org.compiere.model.MAcctSchema; +import org.compiere.model.MAllocationHdr; import org.compiere.model.MAttributeSet; import org.compiere.model.MAttributeSetExclude; import org.compiere.model.MAttributeSetInstance; import org.compiere.model.MBPartner; import org.compiere.model.MClient; +import org.compiere.model.MConversionRate; import org.compiere.model.MCost; import org.compiere.model.MCostDetail; import org.compiere.model.MCostElement; +import org.compiere.model.MCurrency; +import org.compiere.model.MDocType; import org.compiere.model.MFactAcct; import org.compiere.model.MInOut; import org.compiere.model.MInOutLine; import org.compiere.model.MInOutLineMA; import org.compiere.model.MInventory; import org.compiere.model.MInventoryLine; +import org.compiere.model.MInvoice; +import org.compiere.model.MInvoiceLine; +import org.compiere.model.MLandedCost; +import org.compiere.model.MLandedCostAllocation; import org.compiere.model.MOrder; import org.compiere.model.MOrderLandedCost; import org.compiere.model.MOrderLine; @@ -77,11 +89,14 @@ import org.compiere.process.ServerProcessCtl; import org.compiere.util.DB; import org.compiere.util.Env; import org.compiere.util.TimeUtil; +import org.compiere.util.Util; import org.compiere.wf.MWorkflow; import org.eevolution.model.MPPProductBOM; import org.eevolution.model.MPPProductBOMLine; import org.idempiere.test.AbstractTestCase; +import org.idempiere.test.ConversionRateHelper; import org.idempiere.test.DictionaryIDs; +import org.idempiere.test.FactAcct; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Isolated; @@ -958,6 +973,18 @@ public class AveragePOCostingTest extends AbstractTestCase { assertNotNull(cost, "No MCost record found"); assertEquals(new BigDecimal("2.30"), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + //check posting + ProductCost productCost = new ProductCost(Env.getCtx(), product.get_ID(), 0, getTrxName()); + MAccount assetAccount = productCost.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + Doc doc = DocManager.getDocument(as, MInOut.Table_ID, receipt1.get_ID(), getTrxName()); + MAccount nivReceiptAccount = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); + MAccount landedCostAccount = productCost.getAccount(ProductCost.ACCTTYPE_P_LandedCostClearing, as); + Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, receipt1.get_ID(), as.get_ID(), getTrxName()); + List list = query.list(); + List expected = Arrays.asList(new FactAcct(assetAccount, new BigDecimal("2.30"), 2, true), + new FactAcct(nivReceiptAccount, new BigDecimal("2.00"), 2, false), new FactAcct(landedCostAccount, new BigDecimal("0.30"), 2, false)); + assertFactAcctEntries(list, expected); + //reverse receipt info = MWorkflow.runDocumentActionWorkflow(receipt1, DocAction.ACTION_Reverse_Accrual); assertFalse(info.isError(), info.getSummary()); @@ -975,6 +1002,13 @@ public class AveragePOCostingTest extends AbstractTestCase { assertEquals(new BigDecimal("0").setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt"); } } + + //check posting for reversal document + query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, receipt1.getReversal_ID(), as.get_ID(), getTrxName()); + list = query.list(); + expected = Arrays.asList(new FactAcct(assetAccount, new BigDecimal("2.30"), 2, false), + new FactAcct(nivReceiptAccount, new BigDecimal("2.00"), 2, true), new FactAcct(landedCostAccount, new BigDecimal("0.30"), 2, true)); + assertFactAcctEntries(list, expected); } finally { rollback(); @@ -985,6 +1019,3509 @@ public class AveragePOCostingTest extends AbstractTestCase { } } + @Test + public void testLandedCostForPOAndInvoice() { + MClient client = MClient.get(Env.getCtx()); + MAcctSchema as = client.getAcctSchema(); + assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO"); + + MProduct product = null; + try { + product = new MProduct(Env.getCtx(), 0, null); + product.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + product.setName("testLandedCostForPOAndInvoice"); + product.setProductType(MProduct.PRODUCTTYPE_Item); + product.setIsStocked(true); + product.setIsSold(true); + product.setIsPurchased(true); + product.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + product.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + product.saveEx(); + + MPriceListVersion plv = MPriceList.get(DictionaryIDs.M_PriceList.PURCHASE.id).getPriceListVersion(null); + MProductPrice pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(product.get_ID()); + pp.setPriceStd(new BigDecimal("2")); + pp.setPriceList(new BigDecimal("2")); + pp.saveEx(); + + //create order + MOrder order = new MOrder(Env.getCtx(), 0, getTrxName()); + order.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id)); + order.setC_DocTypeTarget_ID(DictionaryIDs.C_DocType.PURCHASE_ORDER.id); + order.setIsSOTrx(false); + order.setSalesRep_ID(DictionaryIDs.AD_User.GARDEN_ADMIN.id); + order.setDocStatus(DocAction.STATUS_Drafted); + order.setDocAction(DocAction.ACTION_Complete); + Timestamp today = TimeUtil.getDay(System.currentTimeMillis()); + order.setDateOrdered(today); + order.setDatePromised(today); + order.saveEx(); + + MOrderLine line1 = new MOrderLine(order); + line1.setLine(10); + line1.setProduct(new MProduct(Env.getCtx(), product.get_ID(), getTrxName())); + line1.setQty(new BigDecimal("1")); + line1.setDatePromised(today); + line1.setPrice(new BigDecimal("2")); + line1.saveEx(); + + MOrderLandedCost landedCost = new MOrderLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_Order_ID(order.get_ID()); + landedCost.setLandedCostDistribution(MOrderLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setAmt(new BigDecimal("0.30")); + landedCost.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + order.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, order.getDocStatus()); + + MInOut receipt1 = new MInOut(order, DictionaryIDs.C_DocType.MM_RECEIPT.id, order.getDateOrdered()); + receipt1.setDocStatus(DocAction.STATUS_Drafted); + receipt1.setDocAction(DocAction.ACTION_Complete); + receipt1.saveEx(); + + MInOutLine receiptLine1 = new MInOutLine(receipt1); + receiptLine1.setOrderLine(line1, 0, new BigDecimal("1")); + receiptLine1.setQty(new BigDecimal("1")); + receiptLine1.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt1, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + receipt1.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, receipt1.getDocStatus()); + if (!receipt1.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), receipt1.getAD_Client_ID(), receipt1.get_Table_ID(), receipt1.get_ID(), false, getTrxName()); + assertNull(error, error); + } + + List cds = MCostDetail.list(Env.getCtx(), "C_OrderLine_ID=?", line1.getC_OrderLine_ID(), 0, as.get_ID(), getTrxName()); + assertTrue(cds.size() == 2, "MCostDetail not found for order line1"); + for(MCostDetail cd : cds) { + if (cd.getM_CostElement_ID() == 0) { + assertEquals(1, cd.getQty().intValue(), "Unexpected MCostDetail Qty"); + assertEquals(new BigDecimal("2.00").setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt"); + } else if (cd.getM_CostElement_ID() == DictionaryIDs.M_CostElement.FREIGHT.id ) { + assertEquals(1, cd.getQty().intValue(), "Unexpected MCostDetail Qty"); + assertEquals(new BigDecimal("0.30").setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt"); + } + } + + product.set_TrxName(getTrxName()); + MCost cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(cost, "No MCost record found"); + assertEquals(new BigDecimal("2.30"), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + ProductCost productCost = new ProductCost(Env.getCtx(), product.get_ID(), 0, getTrxName()); + MAccount assetAccount = productCost.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + Doc doc = DocManager.getDocument(as, MInOut.Table_ID, receipt1.get_ID(), getTrxName()); + MAccount nivReceiptAccount = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); + MAccount landedCostAccount = productCost.getAccount(ProductCost.ACCTTYPE_P_LandedCostClearing, as); + Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, receipt1.get_ID(), as.get_ID(), getTrxName()); + List list = query.list(); + List expected = Arrays.asList(new FactAcct(assetAccount, new BigDecimal("2.30"), 2, true), + new FactAcct(nivReceiptAccount, new BigDecimal("2.00"), 2, false), new FactAcct(landedCostAccount, new BigDecimal("0.30"), 2, false)); + assertFactAcctEntries(list, expected); + + //invoice for MR + MInvoice invoice = new MInvoice(receipt1, receipt1.getMovementDate()); + invoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice); + invoice.setDocStatus(DocAction.STATUS_Drafted); + invoice.setDocAction(DocAction.ACTION_Complete); + invoice.saveEx(); + + MInvoiceLine invoiceLine = new MInvoiceLine(invoice); + invoiceLine.setM_InOutLine_ID(receiptLine1.get_ID()); + invoiceLine.setLine(10); + invoiceLine.setProduct(product); + invoiceLine.setQty(BigDecimal.ONE); + invoiceLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete); + invoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus()); + + if (!invoice.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), invoice.getAD_Client_ID(), MInvoice.Table_ID, invoice.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + invoice.load(getTrxName()); + assertTrue(invoice.isPosted()); + + Doc invoiceDoc = DocManager.getDocument(as, MInvoice.Table_ID, invoice.get_ID(), getTrxName()); + MAccount liabilityAccount = invoiceDoc.getAccount(Doc.ACCTTYPE_V_Liability, as); + MAccount inventoryClearingAccount = productCost.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, invoice.get_ID(), as.get_ID(), getTrxName()); + list = query.list(); + expected = Arrays.asList(new FactAcct(inventoryClearingAccount, new BigDecimal("2.00"), 2, true), + new FactAcct(liabilityAccount, new BigDecimal("2.00"), 2, false)); + assertFactAcctEntries(list, expected); + + //invoice for landed cost + MBPartner bp = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id); + invoice = new MInvoice(Env.getCtx(), 0, getTrxName()); + invoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice); + invoice.setBPartner(bp); + invoice.setDocStatus(DocAction.STATUS_Drafted); + invoice.setDocAction(DocAction.ACTION_Complete); + invoice.saveEx(); + + invoiceLine = new MInvoiceLine(invoice); + invoiceLine.setLine(10); + invoiceLine.setC_Charge_ID(DictionaryIDs.C_Charge.FREIGHT.id); + invoiceLine.setQty(BigDecimal.ONE); + invoiceLine.setPrice(new BigDecimal("0.40")); + invoiceLine.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + invoiceLine.saveEx(); + + MLandedCost invoiceLandedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + invoiceLandedCost.setC_InvoiceLine_ID(invoiceLine.get_ID()); + invoiceLandedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + invoiceLandedCost.setM_InOut_ID(receipt1.get_ID()); + invoiceLandedCost.setM_InOutLine_ID(receiptLine1.get_ID()); + invoiceLandedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Quantity); + invoiceLandedCost.saveEx(); + String error = invoiceLandedCost.allocateCosts(); + assertTrue(Util.isEmpty(error, true), error); + + info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete); + invoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus()); + + if (!invoice.isPosted()) { + error = DocumentEngine.postImmediate(Env.getCtx(), invoice.getAD_Client_ID(), MInvoice.Table_ID, invoice.get_ID(), false, getTrxName()); + assertTrue(error == null, error); + } + invoice.load(getTrxName()); + assertTrue(invoice.isPosted()); + + doc = DocManager.getDocument(as, MInvoice.Table_ID, invoice.get_ID(), getTrxName()); + MAccount apAccount = doc.getAccount(Doc.ACCTTYPE_V_Liability, as); + MAccount landedCostClearingAccount = productCost.getAccount(ProductCost.ACCTTYPE_P_LandedCostClearing, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, invoice.get_ID(), as.get_ID(), getTrxName()); + list = query.list(); + expected = Arrays.asList(new FactAcct(assetAccount, new BigDecimal("0.10"), 2, true), + new FactAcct(landedCostClearingAccount, new BigDecimal("0.30"), 2, true), + new FactAcct(apAccount, new BigDecimal("0.40"), 2, false)); + assertFactAcctEntries(list, expected); + cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertEquals(new BigDecimal("2.40"), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + } finally { + rollback(); + + if (product != null) { + product.set_TrxName(null); + product.deleteEx(true); + } + } + } + + @Test + public void testLandedCostWtihNoEstimateForPOAndInvoice() { + MClient client = MClient.get(Env.getCtx()); + MAcctSchema as = client.getAcctSchema(); + assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO"); + + MProduct product = null; + try { + product = new MProduct(Env.getCtx(), 0, null); + product.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + product.setName("testLandedCostWtihNoEstimateForPOAndInvoice"); + product.setProductType(MProduct.PRODUCTTYPE_Item); + product.setIsStocked(true); + product.setIsSold(true); + product.setIsPurchased(true); + product.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + product.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + product.saveEx(); + + MPriceListVersion plv = MPriceList.get(DictionaryIDs.M_PriceList.PURCHASE.id).getPriceListVersion(null); + MProductPrice pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(product.get_ID()); + pp.setPriceStd(new BigDecimal("2")); + pp.setPriceList(new BigDecimal("2")); + pp.saveEx(); + + //create order + MOrder order = new MOrder(Env.getCtx(), 0, getTrxName()); + order.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id)); + order.setC_DocTypeTarget_ID(DictionaryIDs.C_DocType.PURCHASE_ORDER.id); + order.setIsSOTrx(false); + order.setSalesRep_ID(DictionaryIDs.AD_User.GARDEN_ADMIN.id); + order.setDocStatus(DocAction.STATUS_Drafted); + order.setDocAction(DocAction.ACTION_Complete); + Timestamp today = TimeUtil.getDay(System.currentTimeMillis()); + order.setDateOrdered(today); + order.setDatePromised(today); + order.saveEx(); + + MOrderLine line1 = new MOrderLine(order); + line1.setLine(10); + line1.setProduct(new MProduct(Env.getCtx(), product.get_ID(), getTrxName())); + line1.setQty(new BigDecimal("1")); + line1.setDatePromised(today); + line1.setPrice(new BigDecimal("2")); + line1.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + order.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, order.getDocStatus()); + + MInOut receipt1 = new MInOut(order, DictionaryIDs.C_DocType.MM_RECEIPT.id, order.getDateOrdered()); + receipt1.setDocStatus(DocAction.STATUS_Drafted); + receipt1.setDocAction(DocAction.ACTION_Complete); + receipt1.saveEx(); + + MInOutLine receiptLine1 = new MInOutLine(receipt1); + receiptLine1.setOrderLine(line1, 0, new BigDecimal("1")); + receiptLine1.setQty(new BigDecimal("1")); + receiptLine1.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt1, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + receipt1.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, receipt1.getDocStatus()); + if (!receipt1.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), receipt1.getAD_Client_ID(), receipt1.get_Table_ID(), receipt1.get_ID(), false, getTrxName()); + assertNull(error, error); + } + + List cds = MCostDetail.list(Env.getCtx(), "C_OrderLine_ID=?", line1.getC_OrderLine_ID(), 0, as.get_ID(), getTrxName()); + assertTrue(cds.size() == 1, "MCostDetail not found for order line1"); + for(MCostDetail cd : cds) { + if (cd.getM_CostElement_ID() == 0) { + assertEquals(1, cd.getQty().intValue(), "Unexpected MCostDetail Qty"); + assertEquals(new BigDecimal("2.00").setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt"); + } + } + + product.set_TrxName(getTrxName()); + MCost cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(cost, "No MCost record found"); + assertEquals(new BigDecimal("2.00"), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + ProductCost productCost = new ProductCost(Env.getCtx(), product.get_ID(), 0, getTrxName()); + MAccount assetAccount = productCost.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + Doc doc = DocManager.getDocument(as, MInOut.Table_ID, receipt1.get_ID(), getTrxName()); + MAccount nivReceiptAccount = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); + Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, receipt1.get_ID(), as.get_ID(), getTrxName()); + List list = query.list(); + List expected = Arrays.asList(new FactAcct(assetAccount, new BigDecimal("2.00"), 2, true), + new FactAcct(nivReceiptAccount, new BigDecimal("2.00"), 2, false)); + assertFactAcctEntries(list, expected); + + MInvoice invoice = new MInvoice(receipt1, receipt1.getMovementDate()); + invoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice); + invoice.setDocStatus(DocAction.STATUS_Drafted); + invoice.setDocAction(DocAction.ACTION_Complete); + invoice.saveEx(); + + MInvoiceLine invoiceLine = new MInvoiceLine(invoice); + invoiceLine.setM_InOutLine_ID(receiptLine1.get_ID()); + invoiceLine.setLine(10); + invoiceLine.setProduct(product); + invoiceLine.setQty(BigDecimal.ONE); + invoiceLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete); + invoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus()); + + if (!invoice.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), invoice.getAD_Client_ID(), MInvoice.Table_ID, invoice.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + invoice.load(getTrxName()); + assertTrue(invoice.isPosted()); + + Doc invoiceDoc = DocManager.getDocument(as, MInvoice.Table_ID, invoice.get_ID(), getTrxName()); + MAccount liabilityAccount = invoiceDoc.getAccount(Doc.ACCTTYPE_V_Liability, as); + MAccount inventoryClearingAccount = productCost.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, invoice.get_ID(), as.get_ID(), getTrxName()); + list = query.list(); + expected = Arrays.asList(new FactAcct(inventoryClearingAccount, new BigDecimal("2.00"), 2, true), + new FactAcct(liabilityAccount, new BigDecimal("2.00"), 2, false)); + assertFactAcctEntries(list, expected); + + MBPartner bp = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id); + invoice = new MInvoice(Env.getCtx(), 0, getTrxName()); + invoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice); + invoice.setBPartner(bp); + invoice.setDocStatus(DocAction.STATUS_Drafted); + invoice.setDocAction(DocAction.ACTION_Complete); + invoice.saveEx(); + + invoiceLine = new MInvoiceLine(invoice); + invoiceLine.setLine(10); + invoiceLine.setC_Charge_ID(DictionaryIDs.C_Charge.FREIGHT.id); + invoiceLine.setQty(BigDecimal.ONE); + invoiceLine.setPrice(new BigDecimal("0.30")); + invoiceLine.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + invoiceLine.saveEx(); + + MLandedCost landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(invoiceLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt1.get_ID()); + landedCost.setM_InOutLine_ID(receiptLine1.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Quantity); + landedCost.saveEx(); + String error = landedCost.allocateCosts(); + assertTrue(Util.isEmpty(error, true), error); + + info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete); + invoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus()); + + if (!invoice.isPosted()) { + error = DocumentEngine.postImmediate(Env.getCtx(), invoice.getAD_Client_ID(), MInvoice.Table_ID, invoice.get_ID(), false, getTrxName()); + assertTrue(error == null, error); + } + invoice.load(getTrxName()); + assertTrue(invoice.isPosted()); + + doc = DocManager.getDocument(as, MInvoice.Table_ID, invoice.get_ID(), getTrxName()); + MAccount apAccount = doc.getAccount(Doc.ACCTTYPE_V_Liability, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, invoice.get_ID(), as.get_ID(), getTrxName()); + list = query.list(); + expected = Arrays.asList(new FactAcct(assetAccount, new BigDecimal("0.30"), 2, true), + new FactAcct(apAccount, new BigDecimal("0.30"), 2, false)); + assertFactAcctEntries(list, expected); + + cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertEquals(new BigDecimal("2.30"), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Reverse_Correct); + invoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Reversed, invoice.getDocStatus()); + + MInvoice reversal = new MInvoice(Env.getCtx(), invoice.getReversal_ID(), getTrxName()); + assertEquals(invoice.getReversal_ID(), reversal.get_ID(), "Failed to load reversal invoice"); + if (!reversal.isPosted()) { + error = DocumentEngine.postImmediate(Env.getCtx(), reversal.getAD_Client_ID(), MInvoice.Table_ID, reversal.get_ID(), false, getTrxName()); + assertTrue(error == null, error); + reversal.load(getTrxName()); + } + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, reversal.get_ID(), as.get_ID(), getTrxName()); + list = query.list(); + expected = Arrays.asList(new FactAcct(assetAccount, new BigDecimal("0.30"), 2, false), + new FactAcct(apAccount, new BigDecimal("0.30"), 2, true)); + assertFactAcctEntries(list, expected); + + cost = product.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertEquals(new BigDecimal("2.00"), cost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + } finally { + rollback(); + + if (product != null) { + product.set_TrxName(null); + product.deleteEx(true); + } + } + } + + @Test + public void testUnplannedLandedCostWtihMultipleMRAndShipment() { + MClient client = MClient.get(Env.getCtx()); + MAcctSchema as = client.getAcctSchema(); + assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO"); + + MProduct p1 = null; + MProduct p2 = null; + try { + p1 = new MProduct(Env.getCtx(), 0, null); + p1.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + p1.setName("testUnplannedLandedCostWtihMultipleMRAndShipment1"); + p1.setProductType(MProduct.PRODUCTTYPE_Item); + p1.setIsStocked(true); + p1.setIsSold(true); + p1.setIsPurchased(true); + p1.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + p1.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + p1.saveEx(); + + MPriceListVersion plv = MPriceList.get(DictionaryIDs.M_PriceList.PURCHASE.id).getPriceListVersion(null); + MProductPrice pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(p1.get_ID()); + BigDecimal p1price = new BigDecimal("36.00"); + pp.setPriceStd(p1price); + pp.setPriceList(p1price); + pp.saveEx(); + + p2 = new MProduct(Env.getCtx(), 0, null); + p2.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + p2.setName("testUnplannedLandedCostWtihMultipleMRAndShipment2"); + p2.setProductType(MProduct.PRODUCTTYPE_Item); + p2.setIsStocked(true); + p2.setIsSold(true); + p2.setIsPurchased(true); + p2.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + p2.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + p2.saveEx(); + + pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(p2.get_ID()); + BigDecimal p2price = new BigDecimal("50.00"); + pp.setPriceStd(p2price); + pp.setPriceList(p2price); + pp.saveEx(); + + //create purchase order + MOrder purchaseOrder = new MOrder(Env.getCtx(), 0, getTrxName()); + purchaseOrder.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id)); + purchaseOrder.setC_DocTypeTarget_ID(DictionaryIDs.C_DocType.PURCHASE_ORDER.id); + purchaseOrder.setIsSOTrx(false); + purchaseOrder.setSalesRep_ID(DictionaryIDs.AD_User.GARDEN_ADMIN.id); + purchaseOrder.setDocStatus(DocAction.STATUS_Drafted); + purchaseOrder.setDocAction(DocAction.ACTION_Complete); + Timestamp today = TimeUtil.getDay(System.currentTimeMillis()); + purchaseOrder.setDateOrdered(today); + purchaseOrder.setDatePromised(today); + purchaseOrder.saveEx(); + + MOrderLine poLine1 = new MOrderLine(purchaseOrder); + poLine1.setLine(10); + poLine1.setProduct(new MProduct(Env.getCtx(), p1.get_ID(), getTrxName())); + BigDecimal orderQty = new BigDecimal("100"); + poLine1.setQty(orderQty); + poLine1.setDatePromised(today); + poLine1.setPrice(p1price); + poLine1.saveEx(); + + MOrderLine poLine2 = new MOrderLine(purchaseOrder); + poLine2.setLine(10); + poLine2.setProduct(new MProduct(Env.getCtx(), p2.get_ID(), getTrxName())); + poLine2.setQty(orderQty); + poLine2.setDatePromised(today); + poLine2.setPrice(p2price); + poLine2.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(purchaseOrder, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + purchaseOrder.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, purchaseOrder.getDocStatus()); + + //mr1 for 10 each + MInOut receipt1 = new MInOut(purchaseOrder, DictionaryIDs.C_DocType.MM_RECEIPT.id, purchaseOrder.getDateOrdered()); + receipt1.setDocStatus(DocAction.STATUS_Drafted); + receipt1.setDocAction(DocAction.ACTION_Complete); + receipt1.saveEx(); + + MInOutLine receipt1Line1 = new MInOutLine(receipt1); + BigDecimal mr1Qty = new BigDecimal("10"); + receipt1Line1.setOrderLine(poLine1, 0, mr1Qty); + receipt1Line1.setQty(mr1Qty); + receipt1Line1.saveEx(); + + MInOutLine receipt1Line2 = new MInOutLine(receipt1); + receipt1Line2.setOrderLine(poLine2, 0, mr1Qty); + receipt1Line2.setQty(mr1Qty); + receipt1Line2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt1, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + receipt1.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, receipt1.getDocStatus()); + if (!receipt1.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), receipt1.getAD_Client_ID(), receipt1.get_Table_ID(), receipt1.get_ID(), false, getTrxName()); + assertNull(error, error); + } + + //assert p1 cost and posting + List cds = MCostDetail.list(Env.getCtx(), "C_OrderLine_ID=?", poLine1.getC_OrderLine_ID(), 0, as.get_ID(), getTrxName()); + assertTrue(cds.size() == 1, "MCostDetail not found for order line1"); + for(MCostDetail cd : cds) { + if (cd.getM_CostElement_ID() == 0) { + assertEquals(10, cd.getQty().intValue(), "Unexpected MCostDetail Qty"); + assertEquals(p1price.multiply(mr1Qty).setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt"); + } + } + + p1.set_TrxName(getTrxName()); + MCost p1mcost = p1.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(p1mcost, "No MCost record found"); + assertEquals(p1price, p1mcost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + ProductCost p1ProductCost = new ProductCost(Env.getCtx(), p1.get_ID(), 0, getTrxName()); + MAccount assetAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + Doc doc = DocManager.getDocument(as, MInOut.Table_ID, receipt1.get_ID(), getTrxName()); + MAccount nivReceiptAccount = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); + Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, receipt1.get_ID(), as.get_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(assetAccount, p1price.multiply(mr1Qty), 2, true), + new FactAcct(nivReceiptAccount, p1price.multiply(mr1Qty), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //assert p2 cost and posting + cds = MCostDetail.list(Env.getCtx(), "C_OrderLine_ID=?", poLine2.getC_OrderLine_ID(), 0, as.get_ID(), getTrxName()); + assertTrue(cds.size() == 1, "MCostDetail not found for order line2"); + for(MCostDetail cd : cds) { + if (cd.getM_CostElement_ID() == 0) { + assertEquals(10, cd.getQty().intValue(), "Unexpected MCostDetail Qty"); + assertEquals(p2price.multiply(mr1Qty).setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt"); + } + } + + p2.set_TrxName(getTrxName()); + MCost p2mcost = p2.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(p2mcost, "No MCost record found"); + assertEquals(p2price, p2mcost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + ProductCost p2ProductCost = new ProductCost(Env.getCtx(), p2.get_ID(), 0, getTrxName()); + assetAccount = p2ProductCost.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + expected = Arrays.asList(new FactAcct(assetAccount, p2price.multiply(mr1Qty), 2, true), + new FactAcct(nivReceiptAccount, p2price.multiply(mr1Qty), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //mr2 for 90 each + MInOut receipt2 = new MInOut(purchaseOrder, DictionaryIDs.C_DocType.MM_RECEIPT.id, purchaseOrder.getDateOrdered()); + receipt2.setDocStatus(DocAction.STATUS_Drafted); + receipt2.setDocAction(DocAction.ACTION_Complete); + receipt2.saveEx(); + + MInOutLine receipt2Line1 = new MInOutLine(receipt2); + BigDecimal mr2Qty = new BigDecimal("90"); + receipt2Line1.setOrderLine(poLine1, 0, mr2Qty); + receipt2Line1.setQty(mr2Qty); + receipt2Line1.saveEx(); + + MInOutLine receipt2Line2 = new MInOutLine(receipt2); + receipt2Line2.setOrderLine(poLine2, 0, mr2Qty); + receipt2Line2.setQty(mr2Qty); + receipt2Line2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt2, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + receipt2.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, receipt2.getDocStatus()); + if (!receipt2.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), receipt2.getAD_Client_ID(), receipt2.get_Table_ID(), receipt2.get_ID(), false, getTrxName()); + assertNull(error, error); + } + + //full po invoice + MInvoice purchaseInvoice = new MInvoice(purchaseOrder, DictionaryIDs.C_DocType.AP_INVOICE.id, purchaseOrder.getDateOrdered()); + purchaseInvoice.setDocStatus(DocAction.STATUS_Drafted); + purchaseInvoice.setDocAction(DocAction.ACTION_Complete); + purchaseInvoice.saveEx(); + + MInvoiceLine piLine1 = new MInvoiceLine(purchaseInvoice); + piLine1.setOrderLine(poLine1); + piLine1.setLine(10); + piLine1.setProduct(p1); + piLine1.setQty(poLine1.getQtyOrdered()); + piLine1.saveEx(); + + MInvoiceLine piLine2 = new MInvoiceLine(purchaseInvoice); + piLine2.setOrderLine(poLine2); + piLine2.setLine(10); + piLine2.setProduct(p2); + piLine2.setQty(poLine2.getQtyOrdered()); + piLine2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(purchaseInvoice, DocAction.ACTION_Complete); + purchaseInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, purchaseInvoice.getDocStatus()); + + if (!purchaseInvoice.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), purchaseInvoice.getAD_Client_ID(), MInvoice.Table_ID, purchaseInvoice.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + purchaseInvoice.load(getTrxName()); + assertTrue(purchaseInvoice.isPosted()); + + Doc invoiceDoc = DocManager.getDocument(as, MInvoice.Table_ID, purchaseInvoice.get_ID(), getTrxName()); + MAccount liabilityAccount = invoiceDoc.getAccount(Doc.ACCTTYPE_V_Liability, as); + MAccount inventoryClearingAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, purchaseInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + expected = Arrays.asList(new FactAcct(inventoryClearingAccount, p1price.multiply(orderQty), 2, true), + new FactAcct(inventoryClearingAccount, p2price.multiply(orderQty), 2, true), + new FactAcct(liabilityAccount, p1price.multiply(orderQty).add(p2price.multiply(orderQty)), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //so and shipment + MBPartner customer = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id); + MOrder salesOrder = new MOrder(Env.getCtx(), 0, getTrxName()); + salesOrder.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard); + salesOrder.setBPartner(customer); + salesOrder.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder); + salesOrder.setDocStatus(DocAction.STATUS_Drafted); + salesOrder.setDocAction(DocAction.ACTION_Complete); + salesOrder.setDatePromised(today); + salesOrder.saveEx(); + + MOrderLine soLine1 = new MOrderLine(salesOrder); + soLine1.setLine(10); + soLine1.setProduct(p1); + BigDecimal p1ShipQty = new BigDecimal("76"); + soLine1.setQty(p1ShipQty); + soLine1.setDatePromised(today); + soLine1.setPrice(new BigDecimal("50")); + soLine1.saveEx(); + + MOrderLine soLine2 = new MOrderLine(salesOrder); + soLine2.setLine(20); + soLine2.setProduct(p2); + BigDecimal p2ShipQty = new BigDecimal("82"); + soLine2.setQty(p2ShipQty); + soLine2.setDatePromised(today); + soLine2.setPrice(new BigDecimal("70")); + soLine2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(salesOrder, DocAction.ACTION_Complete); + salesOrder.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, salesOrder.getDocStatus()); + + MInOut shipment = new MInOut(salesOrder, DictionaryIDs.C_DocType.MM_SHIPMENT.id, salesOrder.getDateOrdered()); + shipment.setDocStatus(DocAction.STATUS_Drafted); + shipment.setDocAction(DocAction.ACTION_Complete); + shipment.saveEx(); + + MInOutLine shipmentLine1 = new MInOutLine(shipment); + shipmentLine1.setOrderLine(soLine1, 0, soLine1.getQtyOrdered()); + shipmentLine1.setQty(soLine1.getQtyOrdered()); + shipmentLine1.saveEx(); + + MInOutLine shipmentLine2 = new MInOutLine(shipment); + shipmentLine2.setOrderLine(soLine2, 0, soLine2.getQtyOrdered()); + shipmentLine2.setQty(soLine2.getQtyOrdered()); + shipmentLine2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(shipment, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + shipment.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, shipment.getDocStatus()); + + //landed cost invoice + MBPartner freightBP = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id); + MInvoice freightInvoice = new MInvoice(Env.getCtx(), 0, getTrxName()); + freightInvoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice); + freightInvoice.setBPartner(freightBP); + freightInvoice.setDocStatus(DocAction.STATUS_Drafted); + freightInvoice.setDocAction(DocAction.ACTION_Complete); + freightInvoice.saveEx(); + + MInvoiceLine fiLine = new MInvoiceLine(freightInvoice); + fiLine.setLine(10); + fiLine.setC_Charge_ID(DictionaryIDs.C_Charge.FREIGHT.id); + fiLine.setQty(BigDecimal.ONE); + BigDecimal freightPrice = new BigDecimal("1000.00"); + fiLine.setPrice(freightPrice); + fiLine.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + fiLine.saveEx(); + + MLandedCost landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(fiLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt1.get_ID()); + landedCost.setM_InOutLine_ID(receipt1Line1.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.saveEx(); + + landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(fiLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt1.get_ID()); + landedCost.setM_InOutLine_ID(receipt1Line2.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.saveEx(); + + landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(fiLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt2.get_ID()); + landedCost.setM_InOutLine_ID(receipt2Line1.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.saveEx(); + + landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(fiLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt2.get_ID()); + landedCost.setM_InOutLine_ID(receipt2Line2.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.saveEx(); + + String error = landedCost.allocateCosts(); + assertTrue(Util.isEmpty(error, true), error); + + BigDecimal totalBase = purchaseInvoice.getGrandTotal(); + BigDecimal p1a1 = p1price.multiply(mr1Qty).multiply(fiLine.getLineNetAmt()).divide(totalBase, 6, RoundingMode.HALF_UP); + BigDecimal p1a2 = p1price.multiply(mr2Qty).multiply(fiLine.getLineNetAmt()).divide(totalBase, 6, RoundingMode.HALF_UP); + BigDecimal p2a1 = p2price.multiply(mr1Qty).multiply(fiLine.getLineNetAmt()).divide(totalBase, 6, RoundingMode.HALF_UP); + BigDecimal p2a2 = p2price.multiply(mr2Qty).multiply(fiLine.getLineNetAmt()).divide(totalBase, 6, RoundingMode.HALF_UP); + + MLandedCostAllocation[] allocations = MLandedCostAllocation.getOfInvoiceLine(Env.getCtx(), fiLine.get_ID(), getTrxName()); + assertEquals(4, allocations.length, "Unexpected number of landed cost allocation line"); + for (MLandedCostAllocation allocation : allocations) { + if (allocation.getM_Product_ID() == p1.get_ID() && allocation.getQty().intValue() == 10) { + assertEquals(p1a1.setScale(2, RoundingMode.HALF_UP), allocation.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected landed cost allocation amount"); + } else if (allocation.getM_Product_ID() == p1.get_ID() && allocation.getQty().intValue() == 90) { + assertEquals(p1a2.setScale(2, RoundingMode.HALF_UP), allocation.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected landed cost allocation amount"); + } else if (allocation.getM_Product_ID() == p2.get_ID() && allocation.getQty().intValue() == 10) { + assertEquals(p2a1.setScale(2, RoundingMode.HALF_UP), allocation.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected landed cost allocation amount"); + } else if (allocation.getM_Product_ID() == p2.get_ID() && allocation.getQty().intValue() == 90) { + assertEquals(p2a2.setScale(2, RoundingMode.HALF_UP), allocation.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected landed cost allocation amount"); + } else { + fail("Unknown landed cost allocation line: " + allocation); + } + } + + info = MWorkflow.runDocumentActionWorkflow(freightInvoice, DocAction.ACTION_Complete); + freightInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, freightInvoice.getDocStatus()); + + if (!freightInvoice.isPosted()) { + error = DocumentEngine.postImmediate(Env.getCtx(), freightInvoice.getAD_Client_ID(), MInvoice.Table_ID, freightInvoice.get_ID(), false, getTrxName()); + assertTrue(error == null, error); + } + freightInvoice.load(getTrxName()); + assertTrue(freightInvoice.isPosted()); + + //assert freight invoice posting + doc = DocManager.getDocument(as, MInvoice.Table_ID, freightInvoice.get_ID(), getTrxName()); + MAccount apAccount = doc.getAccount(Doc.ACCTTYPE_V_Liability, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + BigDecimal p1QtyOnHand = orderQty.subtract(p1ShipQty); + BigDecimal p2QtyOnHand = orderQty.subtract(p2ShipQty); + BigDecimal p1a1Qty = mr1Qty; + BigDecimal p1a2Qty = p1QtyOnHand.subtract(p1a1Qty); + BigDecimal p2a1Qty = mr1Qty; + BigDecimal p2a2Qty = p2QtyOnHand.subtract(p2a1Qty); + MAccount varianceAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as); + BigDecimal p1a2Asset = p1a2.divide(mr2Qty, RoundingMode.HALF_UP).multiply(p1a2Qty); + BigDecimal p2a2Asset = p2a2.divide(mr2Qty, RoundingMode.HALF_UP).multiply(p2a2Qty); + expected = Arrays.asList(new FactAcct(assetAccount, p1a1, 2, true), + new FactAcct(assetAccount, p2a1, 2, true), + new FactAcct(assetAccount, p1a2Asset, 2, true), + new FactAcct(varianceAccount, p1a2.subtract(p1a2Asset), 0, true), + new FactAcct(assetAccount, p2a2Asset, 2, true), + new FactAcct(varianceAccount, p2a2.subtract(p2a2Asset), 0, true), + new FactAcct(apAccount, freightInvoice.getGrandTotal(), 2, false)); + assertFactAcctEntries(factAccts, expected); + + p1mcost = p1.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertEquals(p1price.add(p1a1.divide(p1QtyOnHand, 2, RoundingMode.HALF_UP)) + .add(p1a2Asset.divide(p1QtyOnHand, 2, RoundingMode.HALF_UP)) + .setScale(1, RoundingMode.HALF_UP), p1mcost.getCurrentCostPrice().setScale(1, RoundingMode.HALF_UP), "Unexpected current cost price"); + + p1mcost = p2.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertEquals(p2price.add(p2a1.divide(p2QtyOnHand, 2, RoundingMode.HALF_UP)) + .add(p2a2Asset.divide(p2QtyOnHand, 2, RoundingMode.HALF_UP)) + .setScale(1, RoundingMode.HALF_UP), p1mcost.getCurrentCostPrice().setScale(1, RoundingMode.HALF_UP), "Unexpected current cost price"); + } finally { + rollback(); + + if (p1 != null) { + p1.set_TrxName(null); + p1.deleteEx(true); + } + + if (p2 != null) { + p2.set_TrxName(null); + p2.deleteEx(true); + } + } + } + + @Test + public void testUnplannedLandedCostReversal() { + MClient client = MClient.get(Env.getCtx()); + MAcctSchema as = client.getAcctSchema(); + assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO"); + + MProduct p1 = null; + MProduct p2 = null; + try { + p1 = new MProduct(Env.getCtx(), 0, null); + p1.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + p1.setName("testUnplannedLandedCostReversal1"); + p1.setProductType(MProduct.PRODUCTTYPE_Item); + p1.setIsStocked(true); + p1.setIsSold(true); + p1.setIsPurchased(true); + p1.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + p1.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + p1.saveEx(); + + MPriceListVersion plv = MPriceList.get(DictionaryIDs.M_PriceList.PURCHASE.id).getPriceListVersion(null); + MProductPrice pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(p1.get_ID()); + BigDecimal p1price = new BigDecimal("30.00"); + pp.setPriceStd(p1price); + pp.setPriceList(p1price); + pp.saveEx(); + + p2 = new MProduct(Env.getCtx(), 0, null); + p2.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + p2.setName("testUnplannedLandedCostReversal2"); + p2.setProductType(MProduct.PRODUCTTYPE_Item); + p2.setIsStocked(true); + p2.setIsSold(true); + p2.setIsPurchased(true); + p2.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + p2.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + p2.saveEx(); + + pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(p2.get_ID()); + BigDecimal p2price = new BigDecimal("50.00"); + pp.setPriceStd(p2price); + pp.setPriceList(p2price); + pp.saveEx(); + + //create purchase order + MOrder purchaseOrder = new MOrder(Env.getCtx(), 0, getTrxName()); + purchaseOrder.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id)); + purchaseOrder.setC_DocTypeTarget_ID(DictionaryIDs.C_DocType.PURCHASE_ORDER.id); + purchaseOrder.setIsSOTrx(false); + purchaseOrder.setSalesRep_ID(DictionaryIDs.AD_User.GARDEN_ADMIN.id); + purchaseOrder.setDocStatus(DocAction.STATUS_Drafted); + purchaseOrder.setDocAction(DocAction.ACTION_Complete); + Timestamp today = TimeUtil.getDay(System.currentTimeMillis()); + purchaseOrder.setDateOrdered(today); + purchaseOrder.setDatePromised(today); + purchaseOrder.saveEx(); + + MOrderLine poLine1 = new MOrderLine(purchaseOrder); + poLine1.setLine(10); + poLine1.setProduct(new MProduct(Env.getCtx(), p1.get_ID(), getTrxName())); + BigDecimal orderQty = new BigDecimal("10"); + poLine1.setQty(orderQty); + poLine1.setDatePromised(today); + poLine1.setPrice(p1price); + poLine1.saveEx(); + + MOrderLine poLine2 = new MOrderLine(purchaseOrder); + poLine2.setLine(10); + poLine2.setProduct(new MProduct(Env.getCtx(), p2.get_ID(), getTrxName())); + poLine2.setQty(orderQty); + poLine2.setDatePromised(today); + poLine2.setPrice(p2price); + poLine2.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(purchaseOrder, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + purchaseOrder.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, purchaseOrder.getDocStatus()); + + //mr1 for 10 each + MInOut receipt1 = new MInOut(purchaseOrder, DictionaryIDs.C_DocType.MM_RECEIPT.id, purchaseOrder.getDateOrdered()); + receipt1.setDocStatus(DocAction.STATUS_Drafted); + receipt1.setDocAction(DocAction.ACTION_Complete); + receipt1.saveEx(); + + MInOutLine receipt1Line1 = new MInOutLine(receipt1); + BigDecimal mr1Qty = new BigDecimal("10"); + receipt1Line1.setOrderLine(poLine1, 0, mr1Qty); + receipt1Line1.setQty(mr1Qty); + receipt1Line1.saveEx(); + + MInOutLine receipt1Line2 = new MInOutLine(receipt1); + receipt1Line2.setOrderLine(poLine2, 0, mr1Qty); + receipt1Line2.setQty(mr1Qty); + receipt1Line2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt1, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + receipt1.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, receipt1.getDocStatus()); + if (!receipt1.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), receipt1.getAD_Client_ID(), receipt1.get_Table_ID(), receipt1.get_ID(), false, getTrxName()); + assertNull(error, error); + } + + //assert p1 cost and posting + List cds = MCostDetail.list(Env.getCtx(), "C_OrderLine_ID=?", poLine1.getC_OrderLine_ID(), 0, as.get_ID(), getTrxName()); + assertTrue(cds.size() == 1, "MCostDetail not found for order line1"); + for(MCostDetail cd : cds) { + if (cd.getM_CostElement_ID() == 0) { + assertEquals(10, cd.getQty().intValue(), "Unexpected MCostDetail Qty"); + assertEquals(p1price.multiply(mr1Qty).setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt"); + } + } + + p1.set_TrxName(getTrxName()); + MCost p1mcost = p1.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(p1mcost, "No MCost record found"); + assertEquals(p1price, p1mcost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + ProductCost p1ProductCost = new ProductCost(Env.getCtx(), p1.get_ID(), 0, getTrxName()); + MAccount assetAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + Doc doc = DocManager.getDocument(as, MInOut.Table_ID, receipt1.get_ID(), getTrxName()); + MAccount nivReceiptAccount = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); + Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, receipt1.get_ID(), as.get_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(assetAccount, p1price.multiply(mr1Qty), 2, true), + new FactAcct(nivReceiptAccount, p1price.multiply(mr1Qty), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //assert p2 cost and posting + cds = MCostDetail.list(Env.getCtx(), "C_OrderLine_ID=?", poLine2.getC_OrderLine_ID(), 0, as.get_ID(), getTrxName()); + assertTrue(cds.size() == 1, "MCostDetail not found for order line2"); + for(MCostDetail cd : cds) { + if (cd.getM_CostElement_ID() == 0) { + assertEquals(10, cd.getQty().intValue(), "Unexpected MCostDetail Qty"); + assertEquals(p2price.multiply(mr1Qty).setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt"); + } + } + + p2.set_TrxName(getTrxName()); + MCost p2mcost = p2.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(p2mcost, "No MCost record found"); + assertEquals(p2price, p2mcost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + ProductCost p2ProductCost = new ProductCost(Env.getCtx(), p2.get_ID(), 0, getTrxName()); + assetAccount = p2ProductCost.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + expected = Arrays.asList(new FactAcct(assetAccount, p2price.multiply(mr1Qty), 2, true), + new FactAcct(nivReceiptAccount, p2price.multiply(mr1Qty), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //full po invoice + MInvoice purchaseInvoice = new MInvoice(purchaseOrder, DictionaryIDs.C_DocType.AP_INVOICE.id, purchaseOrder.getDateOrdered()); + purchaseInvoice.setDocStatus(DocAction.STATUS_Drafted); + purchaseInvoice.setDocAction(DocAction.ACTION_Complete); + purchaseInvoice.saveEx(); + + MInvoiceLine piLine1 = new MInvoiceLine(purchaseInvoice); + piLine1.setOrderLine(poLine1); + piLine1.setLine(10); + piLine1.setProduct(p1); + piLine1.setQty(poLine1.getQtyOrdered()); + piLine1.saveEx(); + + MInvoiceLine piLine2 = new MInvoiceLine(purchaseInvoice); + piLine2.setOrderLine(poLine2); + piLine2.setLine(10); + piLine2.setProduct(p2); + piLine2.setQty(poLine2.getQtyOrdered()); + piLine2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(purchaseInvoice, DocAction.ACTION_Complete); + purchaseInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, purchaseInvoice.getDocStatus()); + + if (!purchaseInvoice.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), purchaseInvoice.getAD_Client_ID(), MInvoice.Table_ID, purchaseInvoice.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + purchaseInvoice.load(getTrxName()); + assertTrue(purchaseInvoice.isPosted()); + + Doc invoiceDoc = DocManager.getDocument(as, MInvoice.Table_ID, purchaseInvoice.get_ID(), getTrxName()); + MAccount liabilityAccount = invoiceDoc.getAccount(Doc.ACCTTYPE_V_Liability, as); + MAccount inventoryClearingAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, purchaseInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + expected = Arrays.asList(new FactAcct(inventoryClearingAccount, p1price.multiply(orderQty), 2, true), + new FactAcct(inventoryClearingAccount, p2price.multiply(orderQty), 2, true), + new FactAcct(liabilityAccount, p1price.multiply(orderQty).add(p2price.multiply(orderQty)), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //landed cost invoice + MBPartner freightBP = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id); + MInvoice freightInvoice = new MInvoice(Env.getCtx(), 0, getTrxName()); + freightInvoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice); + freightInvoice.setBPartner(freightBP); + freightInvoice.setDocStatus(DocAction.STATUS_Drafted); + freightInvoice.setDocAction(DocAction.ACTION_Complete); + freightInvoice.saveEx(); + + MInvoiceLine fiLine = new MInvoiceLine(freightInvoice); + fiLine.setLine(10); + fiLine.setC_Charge_ID(DictionaryIDs.C_Charge.FREIGHT.id); + fiLine.setQty(BigDecimal.ONE); + BigDecimal freightPrice = new BigDecimal("200.00"); + fiLine.setPrice(freightPrice); + fiLine.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + fiLine.saveEx(); + + MLandedCost landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(fiLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt1.get_ID()); + landedCost.setM_InOutLine_ID(receipt1Line1.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.saveEx(); + + landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(fiLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt1.get_ID()); + landedCost.setM_InOutLine_ID(receipt1Line2.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.saveEx(); + + String error = landedCost.allocateCosts(); + assertTrue(Util.isEmpty(error, true), error); + + BigDecimal totalBase = purchaseInvoice.getGrandTotal(); + BigDecimal p1a1 = p1price.multiply(mr1Qty).multiply(fiLine.getLineNetAmt()).divide(totalBase, 6, RoundingMode.HALF_UP); + BigDecimal p2a1 = p2price.multiply(mr1Qty).multiply(fiLine.getLineNetAmt()).divide(totalBase, 6, RoundingMode.HALF_UP); + + MLandedCostAllocation[] allocations = MLandedCostAllocation.getOfInvoiceLine(Env.getCtx(), fiLine.get_ID(), getTrxName()); + assertEquals(2, allocations.length, "Unexpected number of landed cost allocation line"); + for (MLandedCostAllocation allocation : allocations) { + if (allocation.getM_Product_ID() == p1.get_ID() && allocation.getQty().intValue() == 10) { + assertEquals(p1a1.setScale(2, RoundingMode.HALF_UP), allocation.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected landed cost allocation amount"); + } else if (allocation.getM_Product_ID() == p2.get_ID() && allocation.getQty().intValue() == 10) { + assertEquals(p2a1.setScale(2, RoundingMode.HALF_UP), allocation.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected landed cost allocation amount"); + } else { + fail("Unknown landed cost allocation line: " + allocation); + } + } + + info = MWorkflow.runDocumentActionWorkflow(freightInvoice, DocAction.ACTION_Complete); + freightInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, freightInvoice.getDocStatus()); + + if (!freightInvoice.isPosted()) { + error = DocumentEngine.postImmediate(Env.getCtx(), freightInvoice.getAD_Client_ID(), MInvoice.Table_ID, freightInvoice.get_ID(), false, getTrxName()); + assertTrue(error == null, error); + } + freightInvoice.load(getTrxName()); + assertTrue(freightInvoice.isPosted()); + + //assert freight invoice posting + doc = DocManager.getDocument(as, MInvoice.Table_ID, freightInvoice.get_ID(), getTrxName()); + MAccount apAccount = doc.getAccount(Doc.ACCTTYPE_V_Liability, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + BigDecimal p1QtyOnHand = mr1Qty; + BigDecimal p2QtyOnHand = mr1Qty; + expected = Arrays.asList(new FactAcct(assetAccount, p1a1, 2, true), + new FactAcct(assetAccount, p2a1, 2, true), + new FactAcct(apAccount, freightInvoice.getGrandTotal(), 2, false)); + assertFactAcctEntries(factAccts, expected); + + p1mcost = p1.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertEquals(p1price.add(p1a1.divide(p1QtyOnHand, 2, RoundingMode.HALF_UP)) + .setScale(1, RoundingMode.HALF_UP), p1mcost.getCurrentCostPrice().setScale(1, RoundingMode.HALF_UP), "Unexpected current cost price"); + + p1mcost = p2.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertEquals(p2price.add(p2a1.divide(p2QtyOnHand, 2, RoundingMode.HALF_UP)) + .setScale(1, RoundingMode.HALF_UP), p1mcost.getCurrentCostPrice().setScale(1, RoundingMode.HALF_UP), "Unexpected current cost price"); + + //reverse freight invoice + info = MWorkflow.runDocumentActionWorkflow(freightInvoice, DocAction.ACTION_Reverse_Correct); + freightInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Reversed, freightInvoice.getDocStatus()); + assertTrue(freightInvoice.getReversal_ID() > 0, "Unexpected reversal id"); + MInvoice reversal = new MInvoice(Env.getCtx(), freightInvoice.getReversal_ID(), getTrxName()); + assertEquals(freightInvoice.getReversal_ID(), reversal.get_ID()); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, reversal.get_ID(), as.get_ID(), getTrxName()); + List rFactAccts = query.list(); + expected = new ArrayList(); + for(MFactAcct factAcct : factAccts) { + MAccount acct = MAccount.get(factAcct, getTrxName()); + if (factAcct.getAmtAcctDr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr(), 2, false)); + } else if (factAcct.getAmtAcctCr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr(), 2, true)); + } + } + assertFactAcctEntries(rFactAccts, expected); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + Optional optional = Arrays.stream(ass).filter(e -> e.getC_AcctSchema_ID() != as.get_ID()).findFirst(); + if (optional.isPresent()) { + MAcctSchema as2 = optional.get(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as2.get_ID(), getTrxName()); + factAccts = query.list(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, reversal.get_ID(), as2.get_ID(), getTrxName()); + rFactAccts = query.list(); + expected = new ArrayList(); + for(MFactAcct factAcct : factAccts) { + MAccount acct = MAccount.get(factAcct, getTrxName()); + if (factAcct.getAmtAcctDr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr(), 2, false)); + } else if (factAcct.getAmtAcctCr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr(), 2, true)); + } + } + assertFactAcctEntries(rFactAccts, expected); + } + } finally { + rollback(); + + if (p1 != null) { + p1.set_TrxName(null); + p1.deleteEx(true); + } + + if (p2 != null) { + p2.set_TrxName(null); + p2.deleteEx(true); + } + } + } + + @Test + public void testUnplannedLandedCostReversalAfterShipment1() { + MClient client = MClient.get(Env.getCtx()); + MAcctSchema as = client.getAcctSchema(); + assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO"); + + MProduct p1 = null; + MProduct p2 = null; + try { + p1 = new MProduct(Env.getCtx(), 0, null); + p1.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + p1.setName("testUnplannedLandedCostReversalAfterShipment1.1"); + p1.setProductType(MProduct.PRODUCTTYPE_Item); + p1.setIsStocked(true); + p1.setIsSold(true); + p1.setIsPurchased(true); + p1.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + p1.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + p1.saveEx(); + + MPriceListVersion plv = MPriceList.get(DictionaryIDs.M_PriceList.PURCHASE.id).getPriceListVersion(null); + MProductPrice pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(p1.get_ID()); + BigDecimal p1price = new BigDecimal("30.00"); + pp.setPriceStd(p1price); + pp.setPriceList(p1price); + pp.saveEx(); + + p2 = new MProduct(Env.getCtx(), 0, null); + p2.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + p2.setName("testUnplannedLandedCostReversalAfterShipment1.2"); + p2.setProductType(MProduct.PRODUCTTYPE_Item); + p2.setIsStocked(true); + p2.setIsSold(true); + p2.setIsPurchased(true); + p2.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + p2.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + p2.saveEx(); + + pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(p2.get_ID()); + BigDecimal p2price = new BigDecimal("50.00"); + pp.setPriceStd(p2price); + pp.setPriceList(p2price); + pp.saveEx(); + + //create purchase order + MOrder purchaseOrder = new MOrder(Env.getCtx(), 0, getTrxName()); + purchaseOrder.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id)); + purchaseOrder.setC_DocTypeTarget_ID(DictionaryIDs.C_DocType.PURCHASE_ORDER.id); + purchaseOrder.setIsSOTrx(false); + purchaseOrder.setSalesRep_ID(DictionaryIDs.AD_User.GARDEN_ADMIN.id); + purchaseOrder.setDocStatus(DocAction.STATUS_Drafted); + purchaseOrder.setDocAction(DocAction.ACTION_Complete); + Timestamp today = TimeUtil.getDay(System.currentTimeMillis()); + purchaseOrder.setDateOrdered(today); + purchaseOrder.setDatePromised(today); + purchaseOrder.saveEx(); + + MOrderLine poLine1 = new MOrderLine(purchaseOrder); + poLine1.setLine(10); + poLine1.setProduct(new MProduct(Env.getCtx(), p1.get_ID(), getTrxName())); + BigDecimal orderQty = new BigDecimal("10"); + poLine1.setQty(orderQty); + poLine1.setDatePromised(today); + poLine1.setPrice(p1price); + poLine1.saveEx(); + + MOrderLine poLine2 = new MOrderLine(purchaseOrder); + poLine2.setLine(10); + poLine2.setProduct(new MProduct(Env.getCtx(), p2.get_ID(), getTrxName())); + poLine2.setQty(orderQty); + poLine2.setDatePromised(today); + poLine2.setPrice(p2price); + poLine2.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(purchaseOrder, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + purchaseOrder.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, purchaseOrder.getDocStatus()); + + //mr1 for 10 each + MInOut receipt1 = new MInOut(purchaseOrder, DictionaryIDs.C_DocType.MM_RECEIPT.id, purchaseOrder.getDateOrdered()); + receipt1.setDocStatus(DocAction.STATUS_Drafted); + receipt1.setDocAction(DocAction.ACTION_Complete); + receipt1.saveEx(); + + MInOutLine receipt1Line1 = new MInOutLine(receipt1); + BigDecimal mr1Qty = new BigDecimal("10"); + receipt1Line1.setOrderLine(poLine1, 0, mr1Qty); + receipt1Line1.setQty(mr1Qty); + receipt1Line1.saveEx(); + + MInOutLine receipt1Line2 = new MInOutLine(receipt1); + receipt1Line2.setOrderLine(poLine2, 0, mr1Qty); + receipt1Line2.setQty(mr1Qty); + receipt1Line2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt1, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + receipt1.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, receipt1.getDocStatus()); + if (!receipt1.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), receipt1.getAD_Client_ID(), receipt1.get_Table_ID(), receipt1.get_ID(), false, getTrxName()); + assertNull(error, error); + } + + //assert p1 cost and posting + List cds = MCostDetail.list(Env.getCtx(), "C_OrderLine_ID=?", poLine1.getC_OrderLine_ID(), 0, as.get_ID(), getTrxName()); + assertTrue(cds.size() == 1, "MCostDetail not found for order line1"); + for(MCostDetail cd : cds) { + if (cd.getM_CostElement_ID() == 0) { + assertEquals(10, cd.getQty().intValue(), "Unexpected MCostDetail Qty"); + assertEquals(p1price.multiply(mr1Qty).setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt"); + } + } + + p1.set_TrxName(getTrxName()); + MCost p1mcost = p1.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(p1mcost, "No MCost record found"); + assertEquals(p1price, p1mcost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + ProductCost p1ProductCost = new ProductCost(Env.getCtx(), p1.get_ID(), 0, getTrxName()); + MAccount assetAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + Doc doc = DocManager.getDocument(as, MInOut.Table_ID, receipt1.get_ID(), getTrxName()); + MAccount nivReceiptAccount = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); + Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, receipt1.get_ID(), as.get_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(assetAccount, p1price.multiply(mr1Qty), 2, true), + new FactAcct(nivReceiptAccount, p1price.multiply(mr1Qty), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //assert p2 cost and posting + cds = MCostDetail.list(Env.getCtx(), "C_OrderLine_ID=?", poLine2.getC_OrderLine_ID(), 0, as.get_ID(), getTrxName()); + assertTrue(cds.size() == 1, "MCostDetail not found for order line2"); + for(MCostDetail cd : cds) { + if (cd.getM_CostElement_ID() == 0) { + assertEquals(10, cd.getQty().intValue(), "Unexpected MCostDetail Qty"); + assertEquals(p2price.multiply(mr1Qty).setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt"); + } + } + + p2.set_TrxName(getTrxName()); + MCost p2mcost = p2.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(p2mcost, "No MCost record found"); + assertEquals(p2price, p2mcost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + ProductCost p2ProductCost = new ProductCost(Env.getCtx(), p2.get_ID(), 0, getTrxName()); + assetAccount = p2ProductCost.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + expected = Arrays.asList(new FactAcct(assetAccount, p2price.multiply(mr1Qty), 2, true), + new FactAcct(nivReceiptAccount, p2price.multiply(mr1Qty), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //full po invoice + MInvoice purchaseInvoice = new MInvoice(purchaseOrder, DictionaryIDs.C_DocType.AP_INVOICE.id, purchaseOrder.getDateOrdered()); + purchaseInvoice.setDocStatus(DocAction.STATUS_Drafted); + purchaseInvoice.setDocAction(DocAction.ACTION_Complete); + purchaseInvoice.saveEx(); + + MInvoiceLine piLine1 = new MInvoiceLine(purchaseInvoice); + piLine1.setOrderLine(poLine1); + piLine1.setLine(10); + piLine1.setProduct(p1); + piLine1.setQty(poLine1.getQtyOrdered()); + piLine1.saveEx(); + + MInvoiceLine piLine2 = new MInvoiceLine(purchaseInvoice); + piLine2.setOrderLine(poLine2); + piLine2.setLine(10); + piLine2.setProduct(p2); + piLine2.setQty(poLine2.getQtyOrdered()); + piLine2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(purchaseInvoice, DocAction.ACTION_Complete); + purchaseInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, purchaseInvoice.getDocStatus()); + + if (!purchaseInvoice.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), purchaseInvoice.getAD_Client_ID(), MInvoice.Table_ID, purchaseInvoice.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + purchaseInvoice.load(getTrxName()); + assertTrue(purchaseInvoice.isPosted()); + + Doc invoiceDoc = DocManager.getDocument(as, MInvoice.Table_ID, purchaseInvoice.get_ID(), getTrxName()); + MAccount liabilityAccount = invoiceDoc.getAccount(Doc.ACCTTYPE_V_Liability, as); + MAccount inventoryClearingAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, purchaseInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + expected = Arrays.asList(new FactAcct(inventoryClearingAccount, p1price.multiply(orderQty), 2, true), + new FactAcct(inventoryClearingAccount, p2price.multiply(orderQty), 2, true), + new FactAcct(liabilityAccount, p1price.multiply(orderQty).add(p2price.multiply(orderQty)), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //landed cost invoice + MBPartner freightBP = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id); + MInvoice freightInvoice = new MInvoice(Env.getCtx(), 0, getTrxName()); + freightInvoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice); + freightInvoice.setBPartner(freightBP); + freightInvoice.setDocStatus(DocAction.STATUS_Drafted); + freightInvoice.setDocAction(DocAction.ACTION_Complete); + freightInvoice.saveEx(); + + MInvoiceLine fiLine = new MInvoiceLine(freightInvoice); + fiLine.setLine(10); + fiLine.setC_Charge_ID(DictionaryIDs.C_Charge.FREIGHT.id); + fiLine.setQty(BigDecimal.ONE); + BigDecimal freightPrice = new BigDecimal("200.00"); + fiLine.setPrice(freightPrice); + fiLine.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + fiLine.saveEx(); + + MLandedCost landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(fiLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt1.get_ID()); + landedCost.setM_InOutLine_ID(receipt1Line1.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.saveEx(); + + landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(fiLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt1.get_ID()); + landedCost.setM_InOutLine_ID(receipt1Line2.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.saveEx(); + + String error = landedCost.allocateCosts(); + assertTrue(Util.isEmpty(error, true), error); + + BigDecimal totalBase = purchaseInvoice.getGrandTotal(); + BigDecimal p1a1 = p1price.multiply(mr1Qty).multiply(fiLine.getLineNetAmt()).divide(totalBase, 6, RoundingMode.HALF_UP); + BigDecimal p2a1 = p2price.multiply(mr1Qty).multiply(fiLine.getLineNetAmt()).divide(totalBase, 6, RoundingMode.HALF_UP); + + MLandedCostAllocation[] allocations = MLandedCostAllocation.getOfInvoiceLine(Env.getCtx(), fiLine.get_ID(), getTrxName()); + assertEquals(2, allocations.length, "Unexpected number of landed cost allocation line"); + for (MLandedCostAllocation allocation : allocations) { + if (allocation.getM_Product_ID() == p1.get_ID() && allocation.getQty().intValue() == 10) { + assertEquals(p1a1.setScale(2, RoundingMode.HALF_UP), allocation.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected landed cost allocation amount"); + } else if (allocation.getM_Product_ID() == p2.get_ID() && allocation.getQty().intValue() == 10) { + assertEquals(p2a1.setScale(2, RoundingMode.HALF_UP), allocation.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected landed cost allocation amount"); + } else { + fail("Unknown landed cost allocation line: " + allocation); + } + } + + info = MWorkflow.runDocumentActionWorkflow(freightInvoice, DocAction.ACTION_Complete); + freightInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, freightInvoice.getDocStatus()); + + if (!freightInvoice.isPosted()) { + error = DocumentEngine.postImmediate(Env.getCtx(), freightInvoice.getAD_Client_ID(), MInvoice.Table_ID, freightInvoice.get_ID(), false, getTrxName()); + assertTrue(error == null, error); + } + freightInvoice.load(getTrxName()); + assertTrue(freightInvoice.isPosted()); + + //assert freight invoice posting + doc = DocManager.getDocument(as, MInvoice.Table_ID, freightInvoice.get_ID(), getTrxName()); + MAccount apAccount = doc.getAccount(Doc.ACCTTYPE_V_Liability, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + BigDecimal p1QtyOnHand = mr1Qty; + BigDecimal p2QtyOnHand = mr1Qty; + expected = Arrays.asList(new FactAcct(assetAccount, p1a1, 2, true), + new FactAcct(assetAccount, p2a1, 2, true), + new FactAcct(apAccount, freightInvoice.getGrandTotal(), 2, false)); + assertFactAcctEntries(factAccts, expected); + + p1mcost = p1.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertEquals(p1price.add(p1a1.divide(p1QtyOnHand, 2, RoundingMode.HALF_UP)) + .setScale(1, RoundingMode.HALF_UP), p1mcost.getCurrentCostPrice().setScale(1, RoundingMode.HALF_UP), "Unexpected current cost price"); + + p1mcost = p2.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertEquals(p2price.add(p2a1.divide(p2QtyOnHand, 2, RoundingMode.HALF_UP)) + .setScale(1, RoundingMode.HALF_UP), p1mcost.getCurrentCostPrice().setScale(1, RoundingMode.HALF_UP), "Unexpected current cost price"); + + //so and shipment + MBPartner customer = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id); + MOrder salesOrder = new MOrder(Env.getCtx(), 0, getTrxName()); + salesOrder.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard); + salesOrder.setBPartner(customer); + salesOrder.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder); + salesOrder.setDocStatus(DocAction.STATUS_Drafted); + salesOrder.setDocAction(DocAction.ACTION_Complete); + salesOrder.setDatePromised(today); + salesOrder.saveEx(); + + MOrderLine soLine1 = new MOrderLine(salesOrder); + soLine1.setLine(10); + soLine1.setProduct(p1); + BigDecimal p1ShipQty = new BigDecimal("10"); + soLine1.setQty(p1ShipQty); + soLine1.setDatePromised(today); + soLine1.setPrice(new BigDecimal("50")); + soLine1.saveEx(); + + MOrderLine soLine2 = new MOrderLine(salesOrder); + soLine2.setLine(20); + soLine2.setProduct(p2); + BigDecimal p2ShipQty = new BigDecimal("5"); + soLine2.setQty(p2ShipQty); + soLine2.setDatePromised(today); + soLine2.setPrice(new BigDecimal("70")); + soLine2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(salesOrder, DocAction.ACTION_Complete); + salesOrder.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, salesOrder.getDocStatus()); + + MInOut shipment = new MInOut(salesOrder, DictionaryIDs.C_DocType.MM_SHIPMENT.id, salesOrder.getDateOrdered()); + shipment.setDocStatus(DocAction.STATUS_Drafted); + shipment.setDocAction(DocAction.ACTION_Complete); + shipment.saveEx(); + + MInOutLine shipmentLine1 = new MInOutLine(shipment); + shipmentLine1.setOrderLine(soLine1, 0, soLine1.getQtyOrdered()); + shipmentLine1.setQty(soLine1.getQtyOrdered()); + shipmentLine1.saveEx(); + + MInOutLine shipmentLine2 = new MInOutLine(shipment); + shipmentLine2.setOrderLine(soLine2, 0, soLine2.getQtyOrdered()); + shipmentLine2.setQty(soLine2.getQtyOrdered()); + shipmentLine2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(shipment, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + shipment.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, shipment.getDocStatus()); + + //reverse freight invoice + info = MWorkflow.runDocumentActionWorkflow(freightInvoice, DocAction.ACTION_Reverse_Correct); + freightInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Reversed, freightInvoice.getDocStatus()); + assertTrue(freightInvoice.getReversal_ID() > 0, "Unexpected reversal id"); + MInvoice reversal = new MInvoice(Env.getCtx(), freightInvoice.getReversal_ID(), getTrxName()); + assertEquals(freightInvoice.getReversal_ID(), reversal.get_ID()); + if (!reversal.isPosted()) { + String msg = DocumentEngine.postImmediate(Env.getCtx(), getAD_Client_ID(), MInvoice.Table_ID, reversal.get_ID(), false, getTrxName()); + assertNull(msg, msg); + } + + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, reversal.get_ID(), as.get_ID(), getTrxName()); + List rFactAccts = query.list(); + MAccount varianceAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as); + expected = Arrays.asList(new FactAcct(varianceAccount, p1a1, 2, false), + new FactAcct(assetAccount, p2a1.divide(new BigDecimal(2), RoundingMode.HALF_UP), 2, false), + new FactAcct(apAccount, freightInvoice.getGrandTotal(), 2, true)); + assertFactAcctEntries(rFactAccts, expected); + + } finally { + rollback(); + + if (p1 != null) { + p1.set_TrxName(null); + p1.deleteEx(true); + } + + if (p2 != null) { + p2.set_TrxName(null); + p2.deleteEx(true); + } + } + } + + @Test + public void testUnplannedLandedCostReversalAfterShipment3() { + MClient client = MClient.get(Env.getCtx()); + MAcctSchema as = client.getAcctSchema(); + assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO"); + + MProduct p1 = null; + MCurrency usd = MCurrency.get(DictionaryIDs.C_Currency.USD.id); + MCurrency euro = MCurrency.get(DictionaryIDs.C_Currency.EUR.id); + int C_ConversionType_ID = DictionaryIDs.C_ConversionType.SPOT.id; + Timestamp today = TimeUtil.getDay(null); + Timestamp tomorrow = TimeUtil.addDays(today, 1); + BigDecimal crate1 = new BigDecimal("1.05"); + BigDecimal crate2 = new BigDecimal("1.12"); + MConversionRate cr1 = ConversionRateHelper.createConversionRate(euro.getC_Currency_ID(), usd.getC_Currency_ID(), C_ConversionType_ID, today, crate1, true); + MConversionRate cr2 = ConversionRateHelper.createConversionRate(euro.getC_Currency_ID(), usd.getC_Currency_ID(), C_ConversionType_ID, tomorrow, crate2, true); + try { + p1 = new MProduct(Env.getCtx(), 0, null); + p1.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + p1.setName("testUnplannedLandedCostReversalAfterShipment3"); + p1.setProductType(MProduct.PRODUCTTYPE_Item); + p1.setIsStocked(true); + p1.setIsSold(true); + p1.setIsPurchased(true); + p1.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + p1.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + p1.saveEx(); + + MPriceListVersion plv = MPriceList.get(DictionaryIDs.M_PriceList.IMPORT.id).getPriceListVersion(null); + MProductPrice pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(p1.get_ID()); + BigDecimal p1price = new BigDecimal("30.00"); + pp.setPriceStd(p1price); + pp.setPriceList(p1price); + pp.saveEx(); + + //create purchase order + MOrder purchaseOrder = new MOrder(Env.getCtx(), 0, getTrxName()); + purchaseOrder.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id)); + purchaseOrder.setC_DocTypeTarget_ID(DictionaryIDs.C_DocType.PURCHASE_ORDER.id); + purchaseOrder.setIsSOTrx(false); + purchaseOrder.setSalesRep_ID(DictionaryIDs.AD_User.GARDEN_ADMIN.id); + purchaseOrder.setDocStatus(DocAction.STATUS_Drafted); + purchaseOrder.setDocAction(DocAction.ACTION_Complete); + purchaseOrder.setDateOrdered(today); + purchaseOrder.setDatePromised(today); + purchaseOrder.setM_PriceList_ID(plv.getM_PriceList_ID()); + purchaseOrder.saveEx(); + + MOrderLine poLine1 = new MOrderLine(purchaseOrder); + poLine1.setLine(10); + poLine1.setProduct(new MProduct(Env.getCtx(), p1.get_ID(), getTrxName())); + BigDecimal orderQty = new BigDecimal("100"); + poLine1.setQty(orderQty); + poLine1.setDatePromised(today); + poLine1.setPrice(p1price); + poLine1.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(purchaseOrder, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + purchaseOrder.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, purchaseOrder.getDocStatus()); + + //material receipt + MInOut receipt1 = new MInOut(purchaseOrder, DictionaryIDs.C_DocType.MM_RECEIPT.id, purchaseOrder.getDateOrdered()); + receipt1.setDocStatus(DocAction.STATUS_Drafted); + receipt1.setDocAction(DocAction.ACTION_Complete); + receipt1.saveEx(); + + MInOutLine receipt1Line1 = new MInOutLine(receipt1); + BigDecimal mr1Qty = new BigDecimal("100"); + receipt1Line1.setOrderLine(poLine1, 0, mr1Qty); + receipt1Line1.setQty(mr1Qty); + receipt1Line1.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt1, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + receipt1.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, receipt1.getDocStatus()); + if (!receipt1.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), receipt1.getAD_Client_ID(), receipt1.get_Table_ID(), receipt1.get_ID(), false, getTrxName()); + assertNull(error, error); + } + + //assert p1 cost and posting + List cds = MCostDetail.list(Env.getCtx(), "C_OrderLine_ID=?", poLine1.getC_OrderLine_ID(), 0, as.get_ID(), getTrxName()); + assertTrue(cds.size() == 1, "MCostDetail not found for order line1"); + for(MCostDetail cd : cds) { + if (cd.getM_CostElement_ID() == 0) { + assertEquals(mr1Qty.intValue(), cd.getQty().intValue(), "Unexpected MCostDetail Qty"); + assertEquals(p1price.multiply(mr1Qty).multiply(crate1).setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt"); + } + } + + p1.set_TrxName(getTrxName()); + MCost p1mcost = p1.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(p1mcost, "No MCost record found"); + assertEquals(p1price.multiply(crate1).setScale(2, RoundingMode.HALF_UP), p1mcost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + ProductCost p1ProductCost = new ProductCost(Env.getCtx(), p1.get_ID(), 0, getTrxName()); + MAccount assetAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + MAccount varianceAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as); + Doc doc = DocManager.getDocument(as, MInOut.Table_ID, receipt1.get_ID(), getTrxName()); + MAccount nivReceiptAccount = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); + Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, receipt1.get_ID(), as.get_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(assetAccount, p1price.multiply(mr1Qty).multiply(crate1).setScale(2, RoundingMode.HALF_UP), p1price.multiply(mr1Qty), 2, true), + new FactAcct(nivReceiptAccount, p1price.multiply(mr1Qty).multiply(crate1).setScale(2, RoundingMode.HALF_UP), p1price.multiply(mr1Qty), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //PO invoice + MInvoice purchaseInvoice = new MInvoice(purchaseOrder, DictionaryIDs.C_DocType.AP_INVOICE.id, purchaseOrder.getDateOrdered()); + purchaseInvoice.setDocStatus(DocAction.STATUS_Drafted); + purchaseInvoice.setDocAction(DocAction.ACTION_Complete); + purchaseInvoice.saveEx(); + + MInvoiceLine piLine1 = new MInvoiceLine(purchaseInvoice); + piLine1.setOrderLine(poLine1); + piLine1.setLine(10); + piLine1.setProduct(p1); + piLine1.setQty(poLine1.getQtyOrdered()); + piLine1.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(purchaseInvoice, DocAction.ACTION_Complete); + purchaseInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, purchaseInvoice.getDocStatus()); + + if (!purchaseInvoice.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), purchaseInvoice.getAD_Client_ID(), MInvoice.Table_ID, purchaseInvoice.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + purchaseInvoice.load(getTrxName()); + assertTrue(purchaseInvoice.isPosted()); + + Doc invoiceDoc = DocManager.getDocument(as, MInvoice.Table_ID, purchaseInvoice.get_ID(), getTrxName()); + MAccount liabilityAccount = invoiceDoc.getAccount(Doc.ACCTTYPE_V_Liability, as); + MAccount inventoryClearingAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, purchaseInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + expected = Arrays.asList(new FactAcct(inventoryClearingAccount, p1price.multiply(orderQty).multiply(crate1), p1price.multiply(orderQty), 2, true), + new FactAcct(liabilityAccount, p1price.multiply(orderQty).multiply(crate1), p1price.multiply(orderQty), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //so and shipment + plv = MPriceList.get(DictionaryIDs.M_PriceList.EXPORT.id).getPriceListVersion(null); + pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(p1.get_ID()); + BigDecimal orderPrice = new BigDecimal("100.00"); + pp.setPriceStd(orderPrice); + pp.setPriceList(orderPrice); + pp.saveEx(); + + MBPartner customer = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id); + MOrder salesOrder = new MOrder(Env.getCtx(), 0, getTrxName()); + salesOrder.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard); + salesOrder.setBPartner(customer); + salesOrder.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder); + salesOrder.setDocStatus(DocAction.STATUS_Drafted); + salesOrder.setDocAction(DocAction.ACTION_Complete); + salesOrder.setDatePromised(today); + salesOrder.setM_PriceList_ID(DictionaryIDs.M_PriceList.EXPORT.id); + salesOrder.saveEx(); + + MOrderLine soLine1 = new MOrderLine(salesOrder); + soLine1.setLine(10); + soLine1.setProduct(p1); + BigDecimal p1ShipQty = new BigDecimal("80"); + soLine1.setQty(p1ShipQty); + soLine1.setDatePromised(today); + soLine1.setPrice(orderPrice); + soLine1.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(salesOrder, DocAction.ACTION_Complete); + salesOrder.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, salesOrder.getDocStatus()); + + MInOut shipment = new MInOut(salesOrder, DictionaryIDs.C_DocType.MM_SHIPMENT.id, salesOrder.getDateOrdered()); + shipment.setDocStatus(DocAction.STATUS_Drafted); + shipment.setDocAction(DocAction.ACTION_Complete); + shipment.saveEx(); + + MInOutLine shipmentLine1 = new MInOutLine(shipment); + shipmentLine1.setOrderLine(soLine1, 0, soLine1.getQtyOrdered()); + shipmentLine1.setQty(soLine1.getQtyOrdered()); + shipmentLine1.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(shipment, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + shipment.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, shipment.getDocStatus()); + + //landed cost invoice + MBPartner freightBP = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id); + MInvoice freightInvoice = new MInvoice(Env.getCtx(), 0, getTrxName()); + freightInvoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice); + freightInvoice.setBPartner(freightBP); + freightInvoice.setDocStatus(DocAction.STATUS_Drafted); + freightInvoice.setDocAction(DocAction.ACTION_Complete); + freightInvoice.setM_PriceList_ID(DictionaryIDs.M_PriceList.EXPORT.id); + freightInvoice.saveEx(); + + MInvoiceLine fiLine = new MInvoiceLine(freightInvoice); + fiLine.setLine(10); + fiLine.setC_Charge_ID(DictionaryIDs.C_Charge.FREIGHT.id); + fiLine.setQty(BigDecimal.ONE); + BigDecimal freightPrice = new BigDecimal("200.00"); + fiLine.setPrice(freightPrice); + fiLine.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + fiLine.saveEx(); + + MLandedCost landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(fiLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt1.get_ID()); + landedCost.setM_InOutLine_ID(receipt1Line1.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.saveEx(); + + String error = landedCost.allocateCosts(); + assertTrue(Util.isEmpty(error, true), error); + + BigDecimal p1a1 = fiLine.getLineTotalAmt(); + + MLandedCostAllocation[] allocations = MLandedCostAllocation.getOfInvoiceLine(Env.getCtx(), fiLine.get_ID(), getTrxName()); + assertEquals(1, allocations.length, "Unexpected number of landed cost allocation line"); + for (MLandedCostAllocation allocation : allocations) { + if (allocation.getM_Product_ID() == p1.get_ID() && allocation.getQty().intValue() == orderQty.intValue()) { + assertEquals(p1a1.setScale(2, RoundingMode.HALF_UP), allocation.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected landed cost allocation amount"); + } else { + fail("Unknown landed cost allocation line: " + allocation); + } + } + + info = MWorkflow.runDocumentActionWorkflow(freightInvoice, DocAction.ACTION_Complete); + freightInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, freightInvoice.getDocStatus()); + + if (!freightInvoice.isPosted()) { + error = DocumentEngine.postImmediate(Env.getCtx(), freightInvoice.getAD_Client_ID(), MInvoice.Table_ID, freightInvoice.get_ID(), false, getTrxName()); + assertTrue(error == null, error); + } + freightInvoice.load(getTrxName()); + assertTrue(freightInvoice.isPosted()); + + //assert freight invoice posting + doc = DocManager.getDocument(as, MInvoice.Table_ID, freightInvoice.get_ID(), getTrxName()); + MAccount apAccount = doc.getAccount(Doc.ACCTTYPE_V_Liability, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + BigDecimal assetAmt = p1a1.divide(orderQty, RoundingMode.HALF_UP).multiply(orderQty.subtract(p1ShipQty)); + BigDecimal varianceAmt = p1a1.subtract(assetAmt); + expected = Arrays.asList(new FactAcct(varianceAccount, varianceAmt.multiply(crate1), varianceAmt, 2, true), + new FactAcct(assetAccount, assetAmt.multiply(crate1), assetAmt, 2, true), + new FactAcct(apAccount, freightInvoice.getGrandTotal().multiply(crate1), freightInvoice.getGrandTotal(), 2, false)); + assertFactAcctEntries(factAccts, expected); + + p1mcost = p1.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertEquals(p1price.multiply(crate1).add(assetAmt.multiply(crate1).divide(orderQty.subtract(p1ShipQty), RoundingMode.HALF_UP)).setScale(2, RoundingMode.HALF_UP), + p1mcost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + //reverse freight invoice + Env.setContext(Env.getCtx(), Env.DATE, tomorrow); + info = MWorkflow.runDocumentActionWorkflow(freightInvoice, DocAction.ACTION_Reverse_Correct); + freightInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Reversed, freightInvoice.getDocStatus()); + assertTrue(freightInvoice.getReversal_ID() > 0, "Unexpected reversal id"); + MInvoice reversal = new MInvoice(Env.getCtx(), freightInvoice.getReversal_ID(), getTrxName()); + assertEquals(freightInvoice.getReversal_ID(), reversal.get_ID()); + if (!reversal.isPosted()) { + String msg = DocumentEngine.postImmediate(Env.getCtx(), getAD_Client_ID(), MInvoice.Table_ID, reversal.get_ID(), false, getTrxName()); + assertNull(msg, msg); + } + + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, reversal.get_ID(), as.get_ID(), getTrxName()); + List rFactAccts = query.list(); + expected = new ArrayList(); + for(MFactAcct factAcct : factAccts) { + MAccount acct = MAccount.get(factAcct, getTrxName()); + if (factAcct.getAmtAcctDr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr(), 1, false)); + } else if (factAcct.getAmtAcctCr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr(), 1, true)); + } + } + assertFactAcctEntries(rFactAccts, expected); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + Optional optional = Arrays.stream(ass).filter(e -> e.getC_AcctSchema_ID() != as.get_ID()).findFirst(); + if (optional.isPresent()) { + MAcctSchema as2 = optional.get(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as2.get_ID(), getTrxName()); + factAccts = query.list(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.getReversal_ID(), as2.get_ID(), getTrxName()); + rFactAccts = query.list(); + expected = new ArrayList(); + for(MFactAcct factAcct : factAccts) { + MAccount acct = MAccount.get(factAcct, getTrxName()); + if (factAcct.getAmtAcctDr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr(), 1, false)); + } else if (factAcct.getAmtAcctCr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr(), 1, true)); + } + } + assertFactAcctEntries(rFactAccts, expected); + } + } finally { + rollback(); + + if (p1 != null) { + p1.set_TrxName(null); + p1.deleteEx(true); + } + + ConversionRateHelper.deleteConversionRate(cr1); + ConversionRateHelper.deleteConversionRate(cr2); + } + } + + @Test + public void testUnplannedLandedCostReversalAfterShipment2() { + MClient client = MClient.get(Env.getCtx()); + MAcctSchema as = client.getAcctSchema(); + assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO"); + + MProduct p1 = null; + MProduct p2 = null; + MCurrency usd = MCurrency.get(DictionaryIDs.C_Currency.USD.id); + MCurrency euro = MCurrency.get(DictionaryIDs.C_Currency.EUR.id); + int C_ConversionType_ID = DictionaryIDs.C_ConversionType.SPOT.id; + Timestamp today = TimeUtil.getDay(null); + Timestamp tomorrow = TimeUtil.addDays(today, 1); + MConversionRate cr1 = ConversionRateHelper.createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, today, new BigDecimal("0.91"), true); + MConversionRate cr2 = ConversionRateHelper.createConversionRate(usd.getC_Currency_ID(), euro.getC_Currency_ID(), C_ConversionType_ID, tomorrow, new BigDecimal("0.85"), true); + try { + p1 = new MProduct(Env.getCtx(), 0, null); + p1.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + p1.setName("testUnplannedLandedCostReversalAfterShipment2.1"); + p1.setProductType(MProduct.PRODUCTTYPE_Item); + p1.setIsStocked(true); + p1.setIsSold(true); + p1.setIsPurchased(true); + p1.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + p1.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + p1.saveEx(); + + MPriceListVersion plv = MPriceList.get(DictionaryIDs.M_PriceList.PURCHASE.id).getPriceListVersion(null); + MProductPrice pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(p1.get_ID()); + BigDecimal p1price = new BigDecimal("30.00"); + pp.setPriceStd(p1price); + pp.setPriceList(p1price); + pp.saveEx(); + + p2 = new MProduct(Env.getCtx(), 0, null); + p2.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + p2.setName("testUnplannedLandedCostReversalAfterShipment2.2"); + p2.setProductType(MProduct.PRODUCTTYPE_Item); + p2.setIsStocked(true); + p2.setIsSold(true); + p2.setIsPurchased(true); + p2.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + p2.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + p2.saveEx(); + + pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(p2.get_ID()); + BigDecimal p2price = new BigDecimal("50.00"); + pp.setPriceStd(p2price); + pp.setPriceList(p2price); + pp.saveEx(); + + //create purchase order + MOrder purchaseOrder = new MOrder(Env.getCtx(), 0, getTrxName()); + purchaseOrder.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id)); + purchaseOrder.setC_DocTypeTarget_ID(DictionaryIDs.C_DocType.PURCHASE_ORDER.id); + purchaseOrder.setIsSOTrx(false); + purchaseOrder.setSalesRep_ID(DictionaryIDs.AD_User.GARDEN_ADMIN.id); + purchaseOrder.setDocStatus(DocAction.STATUS_Drafted); + purchaseOrder.setDocAction(DocAction.ACTION_Complete); + purchaseOrder.setDateOrdered(today); + purchaseOrder.setDatePromised(today); + purchaseOrder.saveEx(); + + MOrderLine poLine1 = new MOrderLine(purchaseOrder); + poLine1.setLine(10); + poLine1.setProduct(new MProduct(Env.getCtx(), p1.get_ID(), getTrxName())); + BigDecimal orderQty = new BigDecimal("10"); + poLine1.setQty(orderQty); + poLine1.setDatePromised(today); + poLine1.setPrice(p1price); + poLine1.saveEx(); + + MOrderLine poLine2 = new MOrderLine(purchaseOrder); + poLine2.setLine(10); + poLine2.setProduct(new MProduct(Env.getCtx(), p2.get_ID(), getTrxName())); + poLine2.setQty(orderQty); + poLine2.setDatePromised(today); + poLine2.setPrice(p2price); + poLine2.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(purchaseOrder, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + purchaseOrder.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, purchaseOrder.getDocStatus()); + + //mr1 for 10 each + MInOut receipt1 = new MInOut(purchaseOrder, DictionaryIDs.C_DocType.MM_RECEIPT.id, purchaseOrder.getDateOrdered()); + receipt1.setDocStatus(DocAction.STATUS_Drafted); + receipt1.setDocAction(DocAction.ACTION_Complete); + receipt1.saveEx(); + + MInOutLine receipt1Line1 = new MInOutLine(receipt1); + BigDecimal mr1Qty = new BigDecimal("10"); + receipt1Line1.setOrderLine(poLine1, 0, mr1Qty); + receipt1Line1.setQty(mr1Qty); + receipt1Line1.saveEx(); + + MInOutLine receipt1Line2 = new MInOutLine(receipt1); + receipt1Line2.setOrderLine(poLine2, 0, mr1Qty); + receipt1Line2.setQty(mr1Qty); + receipt1Line2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt1, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + receipt1.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, receipt1.getDocStatus()); + if (!receipt1.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), receipt1.getAD_Client_ID(), receipt1.get_Table_ID(), receipt1.get_ID(), false, getTrxName()); + assertNull(error, error); + } + + //assert p1 cost and posting + List cds = MCostDetail.list(Env.getCtx(), "C_OrderLine_ID=?", poLine1.getC_OrderLine_ID(), 0, as.get_ID(), getTrxName()); + assertTrue(cds.size() == 1, "MCostDetail not found for order line1"); + for(MCostDetail cd : cds) { + if (cd.getM_CostElement_ID() == 0) { + assertEquals(10, cd.getQty().intValue(), "Unexpected MCostDetail Qty"); + assertEquals(p1price.multiply(mr1Qty).setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt"); + } + } + + p1.set_TrxName(getTrxName()); + MCost p1mcost = p1.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(p1mcost, "No MCost record found"); + assertEquals(p1price, p1mcost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + ProductCost p1ProductCost = new ProductCost(Env.getCtx(), p1.get_ID(), 0, getTrxName()); + MAccount assetAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + MAccount varianceAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as); + Doc doc = DocManager.getDocument(as, MInOut.Table_ID, receipt1.get_ID(), getTrxName()); + MAccount nivReceiptAccount = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); + Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, receipt1.get_ID(), as.get_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(assetAccount, p1price.multiply(mr1Qty), 2, true), + new FactAcct(nivReceiptAccount, p1price.multiply(mr1Qty), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //assert p2 cost and posting + cds = MCostDetail.list(Env.getCtx(), "C_OrderLine_ID=?", poLine2.getC_OrderLine_ID(), 0, as.get_ID(), getTrxName()); + assertTrue(cds.size() == 1, "MCostDetail not found for order line2"); + for(MCostDetail cd : cds) { + if (cd.getM_CostElement_ID() == 0) { + assertEquals(10, cd.getQty().intValue(), "Unexpected MCostDetail Qty"); + assertEquals(p2price.multiply(mr1Qty).setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt"); + } + } + + p2.set_TrxName(getTrxName()); + MCost p2mcost = p2.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(p2mcost, "No MCost record found"); + assertEquals(p2price, p2mcost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + ProductCost p2ProductCost = new ProductCost(Env.getCtx(), p2.get_ID(), 0, getTrxName()); + assetAccount = p2ProductCost.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + expected = Arrays.asList(new FactAcct(assetAccount, p2price.multiply(mr1Qty), 2, true), + new FactAcct(nivReceiptAccount, p2price.multiply(mr1Qty), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //full po invoice + MInvoice purchaseInvoice = new MInvoice(purchaseOrder, DictionaryIDs.C_DocType.AP_INVOICE.id, purchaseOrder.getDateOrdered()); + purchaseInvoice.setDocStatus(DocAction.STATUS_Drafted); + purchaseInvoice.setDocAction(DocAction.ACTION_Complete); + purchaseInvoice.saveEx(); + + MInvoiceLine piLine1 = new MInvoiceLine(purchaseInvoice); + piLine1.setOrderLine(poLine1); + piLine1.setLine(10); + piLine1.setProduct(p1); + piLine1.setQty(poLine1.getQtyOrdered()); + piLine1.saveEx(); + + MInvoiceLine piLine2 = new MInvoiceLine(purchaseInvoice); + piLine2.setOrderLine(poLine2); + piLine2.setLine(10); + piLine2.setProduct(p2); + piLine2.setQty(poLine2.getQtyOrdered()); + piLine2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(purchaseInvoice, DocAction.ACTION_Complete); + purchaseInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, purchaseInvoice.getDocStatus()); + + if (!purchaseInvoice.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), purchaseInvoice.getAD_Client_ID(), MInvoice.Table_ID, purchaseInvoice.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + purchaseInvoice.load(getTrxName()); + assertTrue(purchaseInvoice.isPosted()); + + Doc invoiceDoc = DocManager.getDocument(as, MInvoice.Table_ID, purchaseInvoice.get_ID(), getTrxName()); + MAccount liabilityAccount = invoiceDoc.getAccount(Doc.ACCTTYPE_V_Liability, as); + MAccount inventoryClearingAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, purchaseInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + expected = Arrays.asList(new FactAcct(inventoryClearingAccount, p1price.multiply(orderQty), 2, true), + new FactAcct(inventoryClearingAccount, p2price.multiply(orderQty), 2, true), + new FactAcct(liabilityAccount, p1price.multiply(orderQty).add(p2price.multiply(orderQty)), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //so and shipment + MBPartner customer = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id); + MOrder salesOrder = new MOrder(Env.getCtx(), 0, getTrxName()); + salesOrder.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard); + salesOrder.setBPartner(customer); + salesOrder.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder); + salesOrder.setDocStatus(DocAction.STATUS_Drafted); + salesOrder.setDocAction(DocAction.ACTION_Complete); + salesOrder.setDatePromised(today); + salesOrder.saveEx(); + + MOrderLine soLine1 = new MOrderLine(salesOrder); + soLine1.setLine(10); + soLine1.setProduct(p1); + BigDecimal p1ShipQty = new BigDecimal("10"); + soLine1.setQty(p1ShipQty); + soLine1.setDatePromised(today); + soLine1.setPrice(new BigDecimal("50")); + soLine1.saveEx(); + + MOrderLine soLine2 = new MOrderLine(salesOrder); + soLine2.setLine(20); + soLine2.setProduct(p2); + BigDecimal p2ShipQty = new BigDecimal("5"); + soLine2.setQty(p2ShipQty); + soLine2.setDatePromised(today); + soLine2.setPrice(new BigDecimal("70")); + soLine2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(salesOrder, DocAction.ACTION_Complete); + salesOrder.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, salesOrder.getDocStatus()); + + MInOut shipment = new MInOut(salesOrder, DictionaryIDs.C_DocType.MM_SHIPMENT.id, salesOrder.getDateOrdered()); + shipment.setDocStatus(DocAction.STATUS_Drafted); + shipment.setDocAction(DocAction.ACTION_Complete); + shipment.saveEx(); + + MInOutLine shipmentLine1 = new MInOutLine(shipment); + shipmentLine1.setOrderLine(soLine1, 0, soLine1.getQtyOrdered()); + shipmentLine1.setQty(soLine1.getQtyOrdered()); + shipmentLine1.saveEx(); + + MInOutLine shipmentLine2 = new MInOutLine(shipment); + shipmentLine2.setOrderLine(soLine2, 0, soLine2.getQtyOrdered()); + shipmentLine2.setQty(soLine2.getQtyOrdered()); + shipmentLine2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(shipment, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + shipment.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, shipment.getDocStatus()); + + //landed cost invoice + MBPartner freightBP = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id); + MInvoice freightInvoice = new MInvoice(Env.getCtx(), 0, getTrxName()); + freightInvoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice); + freightInvoice.setBPartner(freightBP); + freightInvoice.setDocStatus(DocAction.STATUS_Drafted); + freightInvoice.setDocAction(DocAction.ACTION_Complete); + freightInvoice.saveEx(); + + MInvoiceLine fiLine = new MInvoiceLine(freightInvoice); + fiLine.setLine(10); + fiLine.setC_Charge_ID(DictionaryIDs.C_Charge.FREIGHT.id); + fiLine.setQty(BigDecimal.ONE); + BigDecimal freightPrice = new BigDecimal("200.00"); + fiLine.setPrice(freightPrice); + fiLine.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + fiLine.saveEx(); + + MLandedCost landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(fiLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt1.get_ID()); + landedCost.setM_InOutLine_ID(receipt1Line1.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.saveEx(); + + landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(fiLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt1.get_ID()); + landedCost.setM_InOutLine_ID(receipt1Line2.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.saveEx(); + + String error = landedCost.allocateCosts(); + assertTrue(Util.isEmpty(error, true), error); + + BigDecimal totalBase = purchaseInvoice.getGrandTotal(); + BigDecimal p1a1 = p1price.multiply(mr1Qty).multiply(fiLine.getLineNetAmt()).divide(totalBase, 6, RoundingMode.HALF_UP); + BigDecimal p2a1 = p2price.multiply(mr1Qty).multiply(fiLine.getLineNetAmt()).divide(totalBase, 6, RoundingMode.HALF_UP); + + MLandedCostAllocation[] allocations = MLandedCostAllocation.getOfInvoiceLine(Env.getCtx(), fiLine.get_ID(), getTrxName()); + assertEquals(2, allocations.length, "Unexpected number of landed cost allocation line"); + for (MLandedCostAllocation allocation : allocations) { + if (allocation.getM_Product_ID() == p1.get_ID() && allocation.getQty().intValue() == 10) { + assertEquals(p1a1.setScale(2, RoundingMode.HALF_UP), allocation.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected landed cost allocation amount"); + } else if (allocation.getM_Product_ID() == p2.get_ID() && allocation.getQty().intValue() == 10) { + assertEquals(p2a1.setScale(2, RoundingMode.HALF_UP), allocation.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected landed cost allocation amount"); + } else { + fail("Unknown landed cost allocation line: " + allocation); + } + } + + info = MWorkflow.runDocumentActionWorkflow(freightInvoice, DocAction.ACTION_Complete); + freightInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, freightInvoice.getDocStatus()); + + if (!freightInvoice.isPosted()) { + error = DocumentEngine.postImmediate(Env.getCtx(), freightInvoice.getAD_Client_ID(), MInvoice.Table_ID, freightInvoice.get_ID(), false, getTrxName()); + assertTrue(error == null, error); + } + freightInvoice.load(getTrxName()); + assertTrue(freightInvoice.isPosted()); + + //assert freight invoice posting + doc = DocManager.getDocument(as, MInvoice.Table_ID, freightInvoice.get_ID(), getTrxName()); + MAccount apAccount = doc.getAccount(Doc.ACCTTYPE_V_Liability, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + expected = Arrays.asList(new FactAcct(varianceAccount, p1a1, 2, true), + new FactAcct(assetAccount, p2a1.divide(new BigDecimal("2"), RoundingMode.HALF_UP), 2, true), + new FactAcct(apAccount, freightInvoice.getGrandTotal(), 2, false)); + assertFactAcctEntries(factAccts, expected); + + p1mcost = p1.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertEquals(p1price.setScale(1, RoundingMode.HALF_UP), + p1mcost.getCurrentCostPrice().setScale(1, RoundingMode.HALF_UP), "Unexpected current cost price"); + + p1mcost = p2.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertEquals(p2price.add(p2a1.divide(new BigDecimal("2"), 2, RoundingMode.HALF_UP).divide(new BigDecimal("5"), RoundingMode.HALF_UP)) + .setScale(1, RoundingMode.HALF_UP), p1mcost.getCurrentCostPrice().setScale(1, RoundingMode.HALF_UP), "Unexpected current cost price"); + + //reverse freight invoice + Env.setContext(Env.getCtx(), Env.DATE, tomorrow); + info = MWorkflow.runDocumentActionWorkflow(freightInvoice, DocAction.ACTION_Reverse_Correct); + freightInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Reversed, freightInvoice.getDocStatus()); + assertTrue(freightInvoice.getReversal_ID() > 0, "Unexpected reversal id"); + MInvoice reversal = new MInvoice(Env.getCtx(), freightInvoice.getReversal_ID(), getTrxName()); + assertEquals(freightInvoice.getReversal_ID(), reversal.get_ID()); + if (!reversal.isPosted()) { + String msg = DocumentEngine.postImmediate(Env.getCtx(), getAD_Client_ID(), MInvoice.Table_ID, reversal.get_ID(), false, getTrxName()); + assertNull(msg, msg); + } + + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, reversal.get_ID(), as.get_ID(), getTrxName()); + List rFactAccts = query.list(); + expected = Arrays.asList(new FactAcct(varianceAccount, p1a1, 2, false), + new FactAcct(assetAccount, p2a1.divide(new BigDecimal(2), RoundingMode.HALF_UP), 2, false), + new FactAcct(apAccount, freightInvoice.getGrandTotal(), 2, true)); + assertFactAcctEntries(rFactAccts, expected); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + Optional optional = Arrays.stream(ass).filter(e -> e.getC_AcctSchema_ID() != as.get_ID()).findFirst(); + if (optional.isPresent()) { + MAcctSchema as2 = optional.get(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as2.get_ID(), getTrxName()); + factAccts = query.list(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.getReversal_ID(), as2.get_ID(), getTrxName()); + rFactAccts = query.list(); + expected = new ArrayList(); + for(MFactAcct factAcct : factAccts) { + MAccount acct = MAccount.get(factAcct, getTrxName()); + if (factAcct.getAmtAcctDr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr(), 1, false)); + } else if (factAcct.getAmtAcctCr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr(), 1, true)); + } + } + assertFactAcctEntries(rFactAccts, expected); + } + } finally { + rollback(); + + if (p1 != null) { + p1.set_TrxName(null); + p1.deleteEx(true); + } + + if (p2 != null) { + p2.set_TrxName(null); + p2.deleteEx(true); + } + + ConversionRateHelper.deleteConversionRate(cr1); + ConversionRateHelper.deleteConversionRate(cr2); + } + } + + @Test + public void testUnplannedLandedCostReversalAfterInventoryUseASI() { + + MClient client = MClient.get(Env.getCtx()); + MAcctSchema as = client.getAcctSchema(); + assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO"); + + MProduct p1 = null; + MProduct p2 = null; + MCurrency usd = MCurrency.get(DictionaryIDs.C_Currency.USD.id); + MCurrency euro = MCurrency.get(DictionaryIDs.C_Currency.EUR.id); + int C_ConversionType_ID = DictionaryIDs.C_ConversionType.SPOT.id; + Timestamp today = TimeUtil.getDay(null); + Timestamp tomorrow = TimeUtil.addDays(today, 1); + BigDecimal crate1 = new BigDecimal("1.05"); + BigDecimal crate2 = new BigDecimal("1.12"); + MConversionRate cr1 = ConversionRateHelper.createConversionRate(euro.getC_Currency_ID(), usd.getC_Currency_ID(), C_ConversionType_ID, today, crate1, true); + MConversionRate cr2 = ConversionRateHelper.createConversionRate(euro.getC_Currency_ID(), usd.getC_Currency_ID(), C_ConversionType_ID, tomorrow, crate2, true); + try { + p1 = new MProduct(Env.getCtx(), 0, null); + p1.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + p1.setName("testUnplannedLandedCostReversalAfterInventoryUseASI.1"); + p1.setProductType(MProduct.PRODUCTTYPE_Item); + p1.setIsStocked(true); + p1.setIsSold(true); + p1.setIsPurchased(true); + p1.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + p1.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + p1.setM_AttributeSet_ID(DictionaryIDs.M_AttributeSet.FERTILIZER_LOT.id); + p1.saveEx(); + + MPriceListVersion plv = MPriceList.get(DictionaryIDs.M_PriceList.IMPORT.id).getPriceListVersion(null); + MProductPrice pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(p1.get_ID()); + BigDecimal p1price = new BigDecimal("30.00"); + pp.setPriceStd(p1price); + pp.setPriceList(p1price); + pp.saveEx(); + + p2 = new MProduct(Env.getCtx(), 0, null); + p2.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + p2.setName("testUnplannedLandedCostReversalAfterInventoryUseASI.2"); + p2.setProductType(MProduct.PRODUCTTYPE_Item); + p2.setIsStocked(true); + p2.setIsSold(true); + p2.setIsPurchased(true); + p2.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + p2.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + p2.setM_AttributeSet_ID(DictionaryIDs.M_AttributeSet.FERTILIZER_LOT.id); + p2.saveEx(); + + pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(p2.get_ID()); + BigDecimal p2price = new BigDecimal("50.00"); + pp.setPriceStd(p2price); + pp.setPriceList(p2price); + pp.saveEx(); + + //create purchase order + MOrder purchaseOrder = new MOrder(Env.getCtx(), 0, getTrxName()); + purchaseOrder.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id)); + purchaseOrder.setC_DocTypeTarget_ID(DictionaryIDs.C_DocType.PURCHASE_ORDER.id); + purchaseOrder.setIsSOTrx(false); + purchaseOrder.setSalesRep_ID(DictionaryIDs.AD_User.GARDEN_ADMIN.id); + purchaseOrder.setDocStatus(DocAction.STATUS_Drafted); + purchaseOrder.setDocAction(DocAction.ACTION_Complete); + purchaseOrder.setDateOrdered(today); + purchaseOrder.setDatePromised(today); + purchaseOrder.setM_PriceList_ID(plv.getM_PriceList_ID()); + purchaseOrder.saveEx(); + + MOrderLine poLine1 = new MOrderLine(purchaseOrder); + poLine1.setLine(10); + poLine1.setProduct(new MProduct(Env.getCtx(), p1.get_ID(), getTrxName())); + BigDecimal orderQty = new BigDecimal("100"); + poLine1.setQty(orderQty); + poLine1.setDatePromised(today); + poLine1.setPrice(p1price); + poLine1.saveEx(); + + MOrderLine poLine2 = new MOrderLine(purchaseOrder); + poLine2.setLine(10); + poLine2.setProduct(new MProduct(Env.getCtx(), p2.get_ID(), getTrxName())); + poLine2.setQty(orderQty); + poLine2.setDatePromised(today); + poLine2.setPrice(p2price); + poLine2.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(purchaseOrder, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + purchaseOrder.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, purchaseOrder.getDocStatus()); + + //material receipt 1 + MInOut receipt1 = new MInOut(purchaseOrder, DictionaryIDs.C_DocType.MM_RECEIPT.id, purchaseOrder.getDateOrdered()); + receipt1.setDocStatus(DocAction.STATUS_Drafted); + receipt1.setDocAction(DocAction.ACTION_Complete); + receipt1.saveEx(); + + MInOutLine mr1Line1 = new MInOutLine(receipt1); + BigDecimal mr1l1Qty = new BigDecimal("25"); + mr1Line1.setOrderLine(poLine1, 0, mr1l1Qty); + mr1Line1.setQty(mr1l1Qty); + MAttributeSetInstance mr1l1asi = new MAttributeSetInstance(Env.getCtx(), 0, getTrxName()); + mr1l1asi.setM_AttributeSet_ID(p1.getM_AttributeSet_ID()); + mr1l1asi.setLot("mr1l1asi"); + mr1l1asi.setDescription(); + mr1l1asi.saveEx(); + mr1Line1.setM_AttributeSetInstance_ID(mr1l1asi.get_ID()); + mr1Line1.saveEx(); + + MInOutLine mr1Line2 = new MInOutLine(receipt1); + BigDecimal mr1l2Qty = new BigDecimal("100"); + mr1Line2.setOrderLine(poLine2, 0, mr1l2Qty); + mr1Line2.setQty(mr1l2Qty); + MAttributeSetInstance mr1l2asi = new MAttributeSetInstance(Env.getCtx(), 0, getTrxName()); + mr1l2asi.setM_AttributeSet_ID(p2.getM_AttributeSet_ID()); + mr1l2asi.setLot("mr1l2asi"); + mr1l2asi.setDescription(); + mr1l2asi.saveEx(); + mr1Line2.setM_AttributeSetInstance_ID(mr1l2asi.get_ID()); + mr1Line2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt1, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + receipt1.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, receipt1.getDocStatus()); + if (!receipt1.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), receipt1.getAD_Client_ID(), receipt1.get_Table_ID(), receipt1.get_ID(), false, getTrxName()); + assertNull(error, error); + } + + //material receipt 2 + MInOut receipt2 = new MInOut(purchaseOrder, DictionaryIDs.C_DocType.MM_RECEIPT.id, purchaseOrder.getDateOrdered()); + receipt2.setDocStatus(DocAction.STATUS_Drafted); + receipt2.setDocAction(DocAction.ACTION_Complete); + receipt2.saveEx(); + + MInOutLine mr2Line1 = new MInOutLine(receipt2); + BigDecimal mr2l1Qty = new BigDecimal("75"); + mr2Line1.setOrderLine(poLine1, 0, mr2l1Qty); + mr2Line1.setQty(mr2l1Qty); + MAttributeSetInstance mr2l1asi = new MAttributeSetInstance(Env.getCtx(), 0, getTrxName()); + mr2l1asi.setM_AttributeSet_ID(p1.getM_AttributeSet_ID()); + mr2l1asi.setLot("mr2l1asi"); + mr2l1asi.setDescription(); + mr2l1asi.saveEx(); + mr2Line1.setM_AttributeSetInstance_ID(mr2l1asi.get_ID()); + mr2Line1.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt2, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + receipt2.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, receipt2.getDocStatus()); + if (!receipt2.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), receipt2.getAD_Client_ID(), receipt2.get_Table_ID(), receipt2.get_ID(), false, getTrxName()); + assertNull(error, error); + } + + //assert p1 cost and posting + List cds = MCostDetail.list(Env.getCtx(), "C_OrderLine_ID=?", poLine1.getC_OrderLine_ID(), mr1l1asi.get_ID(), as.get_ID(), getTrxName()); + assertEquals(1, cds.size(), "Unexpected number of MCostDetail records for order line1"); + for(MCostDetail cd : cds) { + if (cd.getM_CostElement_ID() == 0) { + assertEquals(mr1Line1.getMovementQty().intValue(), cd.getQty().intValue(), "Unexpected MCostDetail Qty"); + assertEquals(p1price.multiply(mr1Line1.getMovementQty()).multiply(crate1).setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt"); + } + } + + //assert p1 mcost + p1.set_TrxName(getTrxName()); + MCost p1mcost = p1.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(p1mcost, "No MCost record found"); + assertEquals(p1price.multiply(crate1).setScale(2, RoundingMode.HALF_UP), p1mcost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + ProductCost p1ProductCost = new ProductCost(Env.getCtx(), p1.get_ID(), 0, getTrxName()); + MAccount assetAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + MAccount varianceAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as); + Doc doc = DocManager.getDocument(as, MInOut.Table_ID, receipt1.get_ID(), getTrxName()); + MAccount nivReceiptAccount = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); + Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, receipt1.get_ID(), as.get_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(assetAccount, p1price.multiply(mr1l1Qty).multiply(crate1).setScale(2, RoundingMode.HALF_UP), p1price.multiply(mr1l1Qty), 2, true), + new FactAcct(nivReceiptAccount, p1price.multiply(mr1l1Qty).multiply(crate1).setScale(2, RoundingMode.HALF_UP), p1price.multiply(mr1l1Qty), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //PO invoice + MInvoice purchaseInvoice = new MInvoice(purchaseOrder, DictionaryIDs.C_DocType.AP_INVOICE.id, purchaseOrder.getDateOrdered()); + purchaseInvoice.setDocStatus(DocAction.STATUS_Drafted); + purchaseInvoice.setDocAction(DocAction.ACTION_Complete); + purchaseInvoice.saveEx(); + + MInvoiceLine piLine1 = new MInvoiceLine(purchaseInvoice); + piLine1.setOrderLine(poLine1); + piLine1.setLine(10); + piLine1.setProduct(p1); + piLine1.setQty(poLine1.getQtyOrdered()); + piLine1.saveEx(); + + MInvoiceLine piLine2 = new MInvoiceLine(purchaseInvoice); + piLine2.setOrderLine(poLine2); + piLine2.setLine(20); + piLine2.setProduct(p2); + piLine2.setQty(poLine2.getQtyOrdered()); + piLine2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(purchaseInvoice, DocAction.ACTION_Complete); + purchaseInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, purchaseInvoice.getDocStatus()); + + if (!purchaseInvoice.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), purchaseInvoice.getAD_Client_ID(), MInvoice.Table_ID, purchaseInvoice.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + purchaseInvoice.load(getTrxName()); + assertTrue(purchaseInvoice.isPosted()); + + Doc invoiceDoc = DocManager.getDocument(as, MInvoice.Table_ID, purchaseInvoice.get_ID(), getTrxName()); + MAccount liabilityAccount = invoiceDoc.getAccount(Doc.ACCTTYPE_V_Liability, as); + MAccount inventoryClearingAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, purchaseInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + expected = Arrays.asList(new FactAcct(inventoryClearingAccount, p1price.multiply(orderQty).multiply(crate1), p1price.multiply(orderQty), 2, true), + new FactAcct(liabilityAccount, p1price.multiply(orderQty).multiply(crate1).add(p2price.multiply(orderQty).multiply(crate1)), + p1price.multiply(orderQty).add(p2price.multiply(orderQty)), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //inventory decrease + MInventory inventory = new MInventory(Env.getCtx(), 0, getTrxName()); + inventory.setC_DocType_ID(DictionaryIDs.C_DocType.INTERNAL_USE_INVENTORY.id); + inventory.setM_Warehouse_ID(DictionaryIDs.M_Warehouse.HQ.id); + inventory.setMovementDate(today); + inventory.saveEx(); + + MInventoryLine inventoryLine1 = new MInventoryLine(inventory, DictionaryIDs.M_Locator.HQ.id, p1.get_ID(), 0, null, null, new BigDecimal("25")); + inventoryLine1.setC_Charge_ID(DictionaryIDs.C_Charge.FREIGHT.id); + inventoryLine1.setM_AttributeSetInstance_ID(mr2l1asi.get_ID()); + inventoryLine1.saveEx(); + MInventoryLine inventoryLine2 = new MInventoryLine(inventory, DictionaryIDs.M_Locator.HQ.id, p2.get_ID(), 0, null, null, new BigDecimal("100")); + inventoryLine2.setC_Charge_ID(DictionaryIDs.C_Charge.FREIGHT.id); + inventoryLine2.setM_AttributeSetInstance_ID(mr1l2asi.get_ID()); + inventoryLine2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(inventory, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + inventory.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, inventory.getDocStatus()); + if (!inventory.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), inventory.getAD_Client_ID(), inventory.get_Table_ID(), inventory.get_ID(), false, getTrxName()); + assertNull(error, error); + } + + //landed cost invoice + MBPartner freightBP = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id); + MInvoice freightInvoice = new MInvoice(Env.getCtx(), 0, getTrxName()); + freightInvoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice); + freightInvoice.setBPartner(freightBP); + freightInvoice.setDocStatus(DocAction.STATUS_Drafted); + freightInvoice.setDocAction(DocAction.ACTION_Complete); + freightInvoice.setM_PriceList_ID(DictionaryIDs.M_PriceList.STANDARD.id); + freightInvoice.saveEx(); + + MInvoiceLine fiLine = new MInvoiceLine(freightInvoice); + fiLine.setLine(10); + fiLine.setC_Charge_ID(DictionaryIDs.C_Charge.FREIGHT.id); + fiLine.setQty(BigDecimal.ONE); + BigDecimal freightPrice = new BigDecimal("1000.00"); + fiLine.setPrice(freightPrice); + fiLine.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + fiLine.saveEx(); + + MLandedCost landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(fiLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt1.get_ID()); + landedCost.setM_InOutLine_ID(mr1Line1.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.saveEx(); + + landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(fiLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt1.get_ID()); + landedCost.setM_InOutLine_ID(mr1Line2.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.saveEx(); + + landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(fiLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt1.get_ID()); + landedCost.setM_InOutLine_ID(mr2Line1.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.saveEx(); + + String error = landedCost.allocateCosts(); + assertTrue(Util.isEmpty(error, true), error); + + BigDecimal totalBase = purchaseOrder.getGrandTotal(); + BigDecimal p1a1 = p1price.multiply(mr1l1Qty).multiply(fiLine.getLineNetAmt()).divide(totalBase, 6, RoundingMode.HALF_UP); + BigDecimal p1a2 = p1price.multiply(mr2l1Qty).multiply(fiLine.getLineNetAmt()).divide(totalBase, 6, RoundingMode.HALF_UP); + BigDecimal p2a1 = p2price.multiply(mr1l2Qty).multiply(fiLine.getLineNetAmt()).divide(totalBase, 6, RoundingMode.HALF_UP); + + MLandedCostAllocation[] allocations = MLandedCostAllocation.getOfInvoiceLine(Env.getCtx(), fiLine.get_ID(), getTrxName()); + assertEquals(3, allocations.length, "Unexpected number of landed cost allocation line"); + for (MLandedCostAllocation allocation : allocations) { + if (allocation.getM_Product_ID() == p1.get_ID() && allocation.getQty().intValue() == mr1l1Qty.intValue()) { + assertEquals(p1a1.setScale(2, RoundingMode.HALF_UP), allocation.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected landed cost allocation amount"); + } else if (allocation.getM_Product_ID() == p1.get_ID() && allocation.getQty().intValue() == mr2l1Qty.intValue()) { + assertEquals(p1a2.setScale(2, RoundingMode.HALF_UP), allocation.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected landed cost allocation amount"); + } else if (allocation.getM_Product_ID() == p2.get_ID() && allocation.getQty().intValue() == mr1l2Qty.intValue()) { + assertEquals(p2a1.setScale(2, RoundingMode.HALF_UP), allocation.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected landed cost allocation amount"); + } else { + fail("Unknown landed cost allocation line: " + allocation); + } + } + + info = MWorkflow.runDocumentActionWorkflow(freightInvoice, DocAction.ACTION_Complete); + freightInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, freightInvoice.getDocStatus()); + + if (!freightInvoice.isPosted()) { + error = DocumentEngine.postImmediate(Env.getCtx(), freightInvoice.getAD_Client_ID(), MInvoice.Table_ID, freightInvoice.get_ID(), false, getTrxName()); + assertTrue(error == null, error); + } + freightInvoice.load(getTrxName()); + assertTrue(freightInvoice.isPosted()); + + //assert freight invoice posting + doc = DocManager.getDocument(as, MInvoice.Table_ID, freightInvoice.get_ID(), getTrxName()); + MAccount apAccount = doc.getAccount(Doc.ACCTTYPE_V_Liability, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + BigDecimal p1OnHand = orderQty.add(inventoryLine1.getMovementQty()); + BigDecimal p1a1assetAmt = p1a1; + BigDecimal p1a2assetAmt = p1a2.divide(mr2l1Qty, RoundingMode.HALF_UP).multiply(p1OnHand.subtract(mr1l1Qty)); + BigDecimal p1a2varianceAmt = p1a2.subtract(p1a2assetAmt); + BigDecimal p2varianceAmt = p2a1; + expected = Arrays.asList(new FactAcct(varianceAccount, p1a2varianceAmt, p1a2varianceAmt, 2, true), + new FactAcct(assetAccount, p1a1assetAmt, p1a1assetAmt, 2, true), + new FactAcct(assetAccount, p1a2assetAmt, p1a2assetAmt, 2, true), + new FactAcct(varianceAccount, p2varianceAmt, p2varianceAmt, 2, true), + new FactAcct(varianceAccount, p1a2varianceAmt, p1a2varianceAmt, 2, true), + new FactAcct(apAccount, freightInvoice.getGrandTotal(), freightInvoice.getGrandTotal(), 2, false)); + assertFactAcctEntries(factAccts, expected); + + BigDecimal p1assetAmt = p1a1assetAmt.add(p1a2assetAmt); + p1mcost = p1.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertEquals(p1price.multiply(crate1).add(p1assetAmt.divide(p1OnHand, RoundingMode.HALF_UP)).setScale(2, RoundingMode.HALF_UP), + p1mcost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + //reverse freight invoice + Env.setContext(Env.getCtx(), Env.DATE, tomorrow); + info = MWorkflow.runDocumentActionWorkflow(freightInvoice, DocAction.ACTION_Reverse_Correct); + freightInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Reversed, freightInvoice.getDocStatus()); + assertTrue(freightInvoice.getReversal_ID() > 0, "Unexpected reversal id"); + MInvoice reversal = new MInvoice(Env.getCtx(), freightInvoice.getReversal_ID(), getTrxName()); + assertEquals(freightInvoice.getReversal_ID(), reversal.get_ID()); + if (!reversal.isPosted()) { + String msg = DocumentEngine.postImmediate(Env.getCtx(), getAD_Client_ID(), MInvoice.Table_ID, reversal.get_ID(), false, getTrxName()); + assertNull(msg, msg); + } + + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, reversal.get_ID(), as.get_ID(), getTrxName()); + List rFactAccts = query.list(); + expected = new ArrayList(); + for(MFactAcct factAcct : factAccts) { + MAccount acct = MAccount.get(factAcct, getTrxName()); + if (factAcct.getAmtAcctDr().signum() != 0) { + FactAcct fa = null; + for (FactAcct t : expected) { + if (t.account().getAccount_ID() == acct.getAccount_ID() && + t.account().getM_Product_ID() == acct.getM_Product_ID() && + t.debit() == false) { + fa = t; + break; + } + } + if (fa == null) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr(), 1, false)); + } else { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr().add(fa.accountedAmount()), 1, false)); + expected.remove(fa); + } + } else if (factAcct.getAmtAcctCr().signum() != 0) { + FactAcct fa = null; + for (FactAcct t : expected) { + if (t.account().getAccount_ID() == acct.getAccount_ID() && + t.account().getM_Product_ID() == acct.getM_Product_ID() && + t.debit() == true) { + fa = t; + break; + } + } + if (fa == null) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr(), 1, true)); + } else { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr().add(fa.accountedAmount()), 1, true)); + expected.remove(fa); + } + } + + //assert reversal allocation generate no entries + MAllocationHdr[] allocationHdrs = MAllocationHdr.getOfInvoice(Env.getCtx(), freightInvoice.getC_Invoice_ID(), getTrxName()); + assertEquals(1, allocationHdrs.length, "Unexpected number of allocations for freight invoice"); + if (!allocationHdrs[0].isPosted()) { + String msg = DocumentEngine.postImmediate(Env.getCtx(), getAD_Client_ID(), MAllocationHdr.Table_ID, allocationHdrs[0].get_ID(), false, getTrxName()); + assertNull(msg, msg); + } + assertTrue(allocationHdrs[0].isPosted(), "Allocation of freight invoice not posted"); + query = MFactAcct.createRecordIdQuery(MAllocationHdr.Table_ID, allocationHdrs[0].get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + assertEquals(0, factAccts.size(), "Unexpected number of fact entries generated by invoice reversal allocation"); + } + assertFactAcctEntries(rFactAccts, expected); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + Optional optional = Arrays.stream(ass).filter(e -> e.getC_AcctSchema_ID() != as.get_ID()).findFirst(); + if (optional.isPresent()) { + MAcctSchema as2 = optional.get(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as2.get_ID(), getTrxName()); + factAccts = query.list(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.getReversal_ID(), as2.get_ID(), getTrxName()); + rFactAccts = query.list(); + expected = new ArrayList(); + for(MFactAcct factAcct : factAccts) { + MAccount acct = MAccount.get(factAcct, getTrxName()); + if (factAcct.getAmtAcctDr().signum() != 0) { + FactAcct fa = null; + for (FactAcct t : expected) { + if (t.account().getAccount_ID() == acct.getAccount_ID() && + t.account().getM_Product_ID() == acct.getM_Product_ID() && + t.debit() == false) { + fa = t; + break; + } + } + if (fa == null) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr(), 1, false)); + } else { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr().add(fa.accountedAmount()), 1, false)); + expected.remove(fa); + } + } else if (factAcct.getAmtAcctCr().signum() != 0) { + FactAcct fa = null; + for (FactAcct t : expected) { + if (t.account().getAccount_ID() == acct.getAccount_ID() && + t.account().getM_Product_ID() == acct.getM_Product_ID() && + t.debit() == true) { + fa = t; + break; + } + } + if (fa == null) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr(), 1, true)); + } else { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr().add(fa.accountedAmount()), 1, true)); + expected.remove(fa); + } + } + } + assertFactAcctEntries(rFactAccts, expected); + } + } finally { + rollback(); + + if (p1 != null) { + p1.set_TrxName(null); + p1.deleteEx(true); + } + + ConversionRateHelper.deleteConversionRate(cr1); + ConversionRateHelper.deleteConversionRate(cr2); + } + + } + + @Test + public void testUnplannedLandedCostReversalAfterInventoryUse() { + MClient client = MClient.get(Env.getCtx()); + MAcctSchema as = client.getAcctSchema(); + assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO"); + + MProduct p1 = null; + MProduct p2 = null; + MCurrency usd = MCurrency.get(DictionaryIDs.C_Currency.USD.id); + MCurrency euro = MCurrency.get(DictionaryIDs.C_Currency.EUR.id); + int C_ConversionType_ID = DictionaryIDs.C_ConversionType.SPOT.id; + Timestamp today = TimeUtil.getDay(null); + Timestamp tomorrow = TimeUtil.addDays(today, 1); + BigDecimal crate1 = new BigDecimal("1.05"); + BigDecimal crate2 = new BigDecimal("1.12"); + MConversionRate cr1 = ConversionRateHelper.createConversionRate(euro.getC_Currency_ID(), usd.getC_Currency_ID(), C_ConversionType_ID, today, crate1, true); + MConversionRate cr2 = ConversionRateHelper.createConversionRate(euro.getC_Currency_ID(), usd.getC_Currency_ID(), C_ConversionType_ID, tomorrow, crate2, true); + try { + p1 = new MProduct(Env.getCtx(), 0, null); + p1.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + p1.setName("testUnplannedLandedCostReversalAfterInventoryUse.1"); + p1.setProductType(MProduct.PRODUCTTYPE_Item); + p1.setIsStocked(true); + p1.setIsSold(true); + p1.setIsPurchased(true); + p1.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + p1.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + p1.saveEx(); + + MPriceListVersion plv = MPriceList.get(DictionaryIDs.M_PriceList.IMPORT.id).getPriceListVersion(null); + MProductPrice pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(p1.get_ID()); + BigDecimal p1price = new BigDecimal("30.00"); + pp.setPriceStd(p1price); + pp.setPriceList(p1price); + pp.saveEx(); + + p2 = new MProduct(Env.getCtx(), 0, null); + p2.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + p2.setName("testUnplannedLandedCostReversalAfterInventoryUse.2"); + p2.setProductType(MProduct.PRODUCTTYPE_Item); + p2.setIsStocked(true); + p2.setIsSold(true); + p2.setIsPurchased(true); + p2.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + p2.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + p2.saveEx(); + + pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(p2.get_ID()); + BigDecimal p2price = new BigDecimal("50.00"); + pp.setPriceStd(p2price); + pp.setPriceList(p2price); + pp.saveEx(); + + //create purchase order + MOrder purchaseOrder = new MOrder(Env.getCtx(), 0, getTrxName()); + purchaseOrder.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id)); + purchaseOrder.setC_DocTypeTarget_ID(DictionaryIDs.C_DocType.PURCHASE_ORDER.id); + purchaseOrder.setIsSOTrx(false); + purchaseOrder.setSalesRep_ID(DictionaryIDs.AD_User.GARDEN_ADMIN.id); + purchaseOrder.setDocStatus(DocAction.STATUS_Drafted); + purchaseOrder.setDocAction(DocAction.ACTION_Complete); + purchaseOrder.setDateOrdered(today); + purchaseOrder.setDatePromised(today); + purchaseOrder.setM_PriceList_ID(plv.getM_PriceList_ID()); + purchaseOrder.saveEx(); + + MOrderLine poLine1 = new MOrderLine(purchaseOrder); + poLine1.setLine(10); + poLine1.setProduct(new MProduct(Env.getCtx(), p1.get_ID(), getTrxName())); + BigDecimal orderQty = new BigDecimal("100"); + poLine1.setQty(orderQty); + poLine1.setDatePromised(today); + poLine1.setPrice(p1price); + poLine1.saveEx(); + + MOrderLine poLine2 = new MOrderLine(purchaseOrder); + poLine2.setLine(10); + poLine2.setProduct(new MProduct(Env.getCtx(), p2.get_ID(), getTrxName())); + poLine2.setQty(orderQty); + poLine2.setDatePromised(today); + poLine2.setPrice(p2price); + poLine2.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(purchaseOrder, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + purchaseOrder.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, purchaseOrder.getDocStatus()); + + //material receipt 1 + MInOut receipt1 = new MInOut(purchaseOrder, DictionaryIDs.C_DocType.MM_RECEIPT.id, purchaseOrder.getDateOrdered()); + receipt1.setDocStatus(DocAction.STATUS_Drafted); + receipt1.setDocAction(DocAction.ACTION_Complete); + receipt1.saveEx(); + + MInOutLine mr1Line1 = new MInOutLine(receipt1); + BigDecimal mr1l1Qty = new BigDecimal("25"); + mr1Line1.setOrderLine(poLine1, 0, mr1l1Qty); + mr1Line1.setQty(mr1l1Qty); + mr1Line1.saveEx(); + + MInOutLine mr1Line2 = new MInOutLine(receipt1); + BigDecimal mr1l2Qty = new BigDecimal("100"); + mr1Line2.setOrderLine(poLine2, 0, mr1l2Qty); + mr1Line2.setQty(mr1l2Qty); + mr1Line2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt1, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + receipt1.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, receipt1.getDocStatus()); + if (!receipt1.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), receipt1.getAD_Client_ID(), receipt1.get_Table_ID(), receipt1.get_ID(), false, getTrxName()); + assertNull(error, error); + } + + //material receipt 2 + MInOut receipt2 = new MInOut(purchaseOrder, DictionaryIDs.C_DocType.MM_RECEIPT.id, purchaseOrder.getDateOrdered()); + receipt2.setDocStatus(DocAction.STATUS_Drafted); + receipt2.setDocAction(DocAction.ACTION_Complete); + receipt2.saveEx(); + + MInOutLine mr2Line1 = new MInOutLine(receipt2); + BigDecimal mr2l1Qty = new BigDecimal("75"); + mr2Line1.setOrderLine(poLine1, 0, mr2l1Qty); + mr2Line1.setQty(mr2l1Qty); + mr2Line1.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt2, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + receipt2.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, receipt2.getDocStatus()); + if (!receipt2.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), receipt2.getAD_Client_ID(), receipt2.get_Table_ID(), receipt2.get_ID(), false, getTrxName()); + assertNull(error, error); + } + + //assert p1 cost and posting + List cds = MCostDetail.list(Env.getCtx(), "C_OrderLine_ID=?", poLine1.getC_OrderLine_ID(), 0, as.get_ID(), getTrxName()); + assertTrue(cds.size() == 1, "MCostDetail not found for order line1"); + for(MCostDetail cd : cds) { + if (cd.getM_CostElement_ID() == 0) { + assertEquals(poLine1.getQtyOrdered().intValue(), cd.getQty().intValue(), "Unexpected MCostDetail Qty"); + assertEquals(p1price.multiply(orderQty).multiply(crate1).setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt"); + } + } + + //assert p1 mcost + p1.set_TrxName(getTrxName()); + MCost p1mcost = p1.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(p1mcost, "No MCost record found"); + assertEquals(p1price.multiply(crate1).setScale(2, RoundingMode.HALF_UP), p1mcost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + ProductCost p1ProductCost = new ProductCost(Env.getCtx(), p1.get_ID(), 0, getTrxName()); + MAccount assetAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + MAccount varianceAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as); + Doc doc = DocManager.getDocument(as, MInOut.Table_ID, receipt1.get_ID(), getTrxName()); + MAccount nivReceiptAccount = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); + Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, receipt1.get_ID(), as.get_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(assetAccount, p1price.multiply(mr1l1Qty).multiply(crate1).setScale(2, RoundingMode.HALF_UP), p1price.multiply(mr1l1Qty), 2, true), + new FactAcct(nivReceiptAccount, p1price.multiply(mr1l1Qty).multiply(crate1).setScale(2, RoundingMode.HALF_UP), p1price.multiply(mr1l1Qty), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //PO invoice + MInvoice purchaseInvoice = new MInvoice(purchaseOrder, DictionaryIDs.C_DocType.AP_INVOICE.id, purchaseOrder.getDateOrdered()); + purchaseInvoice.setDocStatus(DocAction.STATUS_Drafted); + purchaseInvoice.setDocAction(DocAction.ACTION_Complete); + purchaseInvoice.saveEx(); + + MInvoiceLine piLine1 = new MInvoiceLine(purchaseInvoice); + piLine1.setOrderLine(poLine1); + piLine1.setLine(10); + piLine1.setProduct(p1); + piLine1.setQty(poLine1.getQtyOrdered()); + piLine1.saveEx(); + + MInvoiceLine piLine2 = new MInvoiceLine(purchaseInvoice); + piLine2.setOrderLine(poLine2); + piLine2.setLine(20); + piLine2.setProduct(p2); + piLine2.setQty(poLine2.getQtyOrdered()); + piLine2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(purchaseInvoice, DocAction.ACTION_Complete); + purchaseInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, purchaseInvoice.getDocStatus()); + + if (!purchaseInvoice.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), purchaseInvoice.getAD_Client_ID(), MInvoice.Table_ID, purchaseInvoice.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + purchaseInvoice.load(getTrxName()); + assertTrue(purchaseInvoice.isPosted()); + + Doc invoiceDoc = DocManager.getDocument(as, MInvoice.Table_ID, purchaseInvoice.get_ID(), getTrxName()); + MAccount liabilityAccount = invoiceDoc.getAccount(Doc.ACCTTYPE_V_Liability, as); + MAccount inventoryClearingAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, purchaseInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + expected = Arrays.asList(new FactAcct(inventoryClearingAccount, p1price.multiply(orderQty).multiply(crate1), p1price.multiply(orderQty), 2, true), + new FactAcct(liabilityAccount, p1price.multiply(orderQty).multiply(crate1).add(p2price.multiply(orderQty).multiply(crate1)), + p1price.multiply(orderQty).add(p2price.multiply(orderQty)), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //inventory decrease + MInventory inventory = new MInventory(Env.getCtx(), 0, getTrxName()); + inventory.setC_DocType_ID(DictionaryIDs.C_DocType.INTERNAL_USE_INVENTORY.id); + inventory.setM_Warehouse_ID(DictionaryIDs.M_Warehouse.HQ.id); + inventory.setMovementDate(today); + inventory.saveEx(); + + MInventoryLine inventoryLine1 = new MInventoryLine(inventory, DictionaryIDs.M_Locator.HQ.id, p1.get_ID(), 0, null, null, new BigDecimal("25")); + inventoryLine1.setC_Charge_ID(DictionaryIDs.C_Charge.FREIGHT.id); + inventoryLine1.saveEx(); + MInventoryLine inventoryLine2 = new MInventoryLine(inventory, DictionaryIDs.M_Locator.HQ.id, p2.get_ID(), 0, null, null, new BigDecimal("100")); + inventoryLine2.setC_Charge_ID(DictionaryIDs.C_Charge.FREIGHT.id); + inventoryLine2.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(inventory, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + inventory.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, inventory.getDocStatus()); + if (!inventory.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), inventory.getAD_Client_ID(), inventory.get_Table_ID(), inventory.get_ID(), false, getTrxName()); + assertNull(error, error); + } + + //landed cost invoice + MBPartner freightBP = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id); + MInvoice freightInvoice = new MInvoice(Env.getCtx(), 0, getTrxName()); + freightInvoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice); + freightInvoice.setBPartner(freightBP); + freightInvoice.setDocStatus(DocAction.STATUS_Drafted); + freightInvoice.setDocAction(DocAction.ACTION_Complete); + freightInvoice.setM_PriceList_ID(DictionaryIDs.M_PriceList.STANDARD.id); + freightInvoice.saveEx(); + + MInvoiceLine fiLine = new MInvoiceLine(freightInvoice); + fiLine.setLine(10); + fiLine.setC_Charge_ID(DictionaryIDs.C_Charge.FREIGHT.id); + fiLine.setQty(BigDecimal.ONE); + BigDecimal freightPrice = new BigDecimal("1000.00"); + fiLine.setPrice(freightPrice); + fiLine.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + fiLine.saveEx(); + + MLandedCost landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(fiLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt1.get_ID()); + landedCost.setM_InOutLine_ID(mr1Line1.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.saveEx(); + + landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(fiLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt1.get_ID()); + landedCost.setM_InOutLine_ID(mr1Line2.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.saveEx(); + + landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(fiLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt1.get_ID()); + landedCost.setM_InOutLine_ID(mr2Line1.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.saveEx(); + + String error = landedCost.allocateCosts(); + assertTrue(Util.isEmpty(error, true), error); + + BigDecimal totalBase = purchaseOrder.getGrandTotal(); + BigDecimal p1a1 = p1price.multiply(mr1l1Qty).multiply(fiLine.getLineNetAmt()).divide(totalBase, 6, RoundingMode.HALF_UP); + BigDecimal p1a2 = p1price.multiply(mr2l1Qty).multiply(fiLine.getLineNetAmt()).divide(totalBase, 6, RoundingMode.HALF_UP); + BigDecimal p2a1 = p2price.multiply(mr1l2Qty).multiply(fiLine.getLineNetAmt()).divide(totalBase, 6, RoundingMode.HALF_UP); + + MLandedCostAllocation[] allocations = MLandedCostAllocation.getOfInvoiceLine(Env.getCtx(), fiLine.get_ID(), getTrxName()); + assertEquals(3, allocations.length, "Unexpected number of landed cost allocation line"); + for (MLandedCostAllocation allocation : allocations) { + if (allocation.getM_Product_ID() == p1.get_ID() && allocation.getQty().intValue() == mr1l1Qty.intValue()) { + assertEquals(p1a1.setScale(2, RoundingMode.HALF_UP), allocation.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected landed cost allocation amount"); + } else if (allocation.getM_Product_ID() == p1.get_ID() && allocation.getQty().intValue() == mr2l1Qty.intValue()) { + assertEquals(p1a2.setScale(2, RoundingMode.HALF_UP), allocation.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected landed cost allocation amount"); + } else if (allocation.getM_Product_ID() == p2.get_ID() && allocation.getQty().intValue() == mr1l2Qty.intValue()) { + assertEquals(p2a1.setScale(2, RoundingMode.HALF_UP), allocation.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected landed cost allocation amount"); + } else { + fail("Unknown landed cost allocation line: " + allocation); + } + } + + info = MWorkflow.runDocumentActionWorkflow(freightInvoice, DocAction.ACTION_Complete); + freightInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, freightInvoice.getDocStatus()); + + if (!freightInvoice.isPosted()) { + error = DocumentEngine.postImmediate(Env.getCtx(), freightInvoice.getAD_Client_ID(), MInvoice.Table_ID, freightInvoice.get_ID(), false, getTrxName()); + assertTrue(error == null, error); + } + freightInvoice.load(getTrxName()); + assertTrue(freightInvoice.isPosted()); + + //assert freight invoice posting + doc = DocManager.getDocument(as, MInvoice.Table_ID, freightInvoice.get_ID(), getTrxName()); + MAccount apAccount = doc.getAccount(Doc.ACCTTYPE_V_Liability, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + BigDecimal p1OnHand = orderQty.add(inventoryLine1.getMovementQty()); + BigDecimal p1a1assetAmt = p1a1; + BigDecimal p1a2assetAmt = p1a2.divide(mr2l1Qty, RoundingMode.HALF_UP).multiply(p1OnHand.subtract(mr1l1Qty)); + BigDecimal p1a2varianceAmt = p1a2.subtract(p1a2assetAmt); + BigDecimal p2varianceAmt = p2a1; + expected = Arrays.asList(new FactAcct(varianceAccount, p1a2varianceAmt, p1a2varianceAmt, 2, true), + new FactAcct(assetAccount, p1a1assetAmt, p1a1assetAmt, 2, true), + new FactAcct(assetAccount, p1a2assetAmt, p1a2assetAmt, 2, true), + new FactAcct(varianceAccount, p2varianceAmt, p2varianceAmt, 2, true), + new FactAcct(varianceAccount, p1a2varianceAmt, p1a2varianceAmt, 2, true), + new FactAcct(apAccount, freightInvoice.getGrandTotal(), freightInvoice.getGrandTotal(), 2, false)); + assertFactAcctEntries(factAccts, expected); + + BigDecimal p1assetAmt = p1a1assetAmt.add(p1a2assetAmt); + p1mcost = p1.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertEquals(p1price.multiply(crate1).add(p1assetAmt.divide(p1OnHand, RoundingMode.HALF_UP)).setScale(2, RoundingMode.HALF_UP), + p1mcost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + //reverse freight invoice + Env.setContext(Env.getCtx(), Env.DATE, tomorrow); + info = MWorkflow.runDocumentActionWorkflow(freightInvoice, DocAction.ACTION_Reverse_Correct); + freightInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Reversed, freightInvoice.getDocStatus()); + assertTrue(freightInvoice.getReversal_ID() > 0, "Unexpected reversal id"); + MInvoice reversal = new MInvoice(Env.getCtx(), freightInvoice.getReversal_ID(), getTrxName()); + assertEquals(freightInvoice.getReversal_ID(), reversal.get_ID()); + if (!reversal.isPosted()) { + String msg = DocumentEngine.postImmediate(Env.getCtx(), getAD_Client_ID(), MInvoice.Table_ID, reversal.get_ID(), false, getTrxName()); + assertNull(msg, msg); + } + + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, reversal.get_ID(), as.get_ID(), getTrxName()); + List rFactAccts = query.list(); + expected = new ArrayList(); + for(MFactAcct factAcct : factAccts) { + MAccount acct = MAccount.get(factAcct, getTrxName()); + if (factAcct.getAmtAcctDr().signum() != 0) { + FactAcct fa = null; + for (FactAcct t : expected) { + if (t.account().getAccount_ID() == acct.getAccount_ID() && + t.account().getM_Product_ID() == acct.getM_Product_ID() && + t.debit() == false) { + fa = t; + break; + } + } + if (fa == null) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr(), 1, false)); + } else { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr().add(fa.accountedAmount()), 1, false)); + expected.remove(fa); + } + } else if (factAcct.getAmtAcctCr().signum() != 0) { + FactAcct fa = null; + for (FactAcct t : expected) { + if (t.account().getAccount_ID() == acct.getAccount_ID() && + t.account().getM_Product_ID() == acct.getM_Product_ID() && + t.debit() == true) { + fa = t; + break; + } + } + if (fa == null) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr(), 1, true)); + } else { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr().add(fa.accountedAmount()), 1, true)); + expected.remove(fa); + } + } + + //assert reversal allocation generate no entries + MAllocationHdr[] allocationHdrs = MAllocationHdr.getOfInvoice(Env.getCtx(), freightInvoice.getC_Invoice_ID(), getTrxName()); + assertEquals(1, allocationHdrs.length, "Unexpected number of allocations for freight invoice"); + if (!allocationHdrs[0].isPosted()) { + String msg = DocumentEngine.postImmediate(Env.getCtx(), getAD_Client_ID(), MAllocationHdr.Table_ID, allocationHdrs[0].get_ID(), false, getTrxName()); + assertNull(msg, msg); + } + assertTrue(allocationHdrs[0].isPosted(), "Allocation of freight invoice not posted"); + query = MFactAcct.createRecordIdQuery(MAllocationHdr.Table_ID, allocationHdrs[0].get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + assertEquals(0, factAccts.size(), "Unexpected number of fact entries generated by invoice reversal allocation"); + } + assertFactAcctEntries(rFactAccts, expected); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + Optional optional = Arrays.stream(ass).filter(e -> e.getC_AcctSchema_ID() != as.get_ID()).findFirst(); + if (optional.isPresent()) { + MAcctSchema as2 = optional.get(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as2.get_ID(), getTrxName()); + factAccts = query.list(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.getReversal_ID(), as2.get_ID(), getTrxName()); + rFactAccts = query.list(); + expected = new ArrayList(); + for(MFactAcct factAcct : factAccts) { + MAccount acct = MAccount.get(factAcct, getTrxName()); + if (factAcct.getAmtAcctDr().signum() != 0) { + FactAcct fa = null; + for (FactAcct t : expected) { + if (t.account().getAccount_ID() == acct.getAccount_ID() && + t.account().getM_Product_ID() == acct.getM_Product_ID() && + t.debit() == false) { + fa = t; + break; + } + } + if (fa == null) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr(), 1, false)); + } else { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr().add(fa.accountedAmount()), 1, false)); + expected.remove(fa); + } + } else if (factAcct.getAmtAcctCr().signum() != 0) { + FactAcct fa = null; + for (FactAcct t : expected) { + if (t.account().getAccount_ID() == acct.getAccount_ID() && + t.account().getM_Product_ID() == acct.getM_Product_ID() && + t.debit() == true) { + fa = t; + break; + } + } + if (fa == null) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr(), 1, true)); + } else { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr().add(fa.accountedAmount()), 1, true)); + expected.remove(fa); + } + } + } + assertFactAcctEntries(rFactAccts, expected); + } + } finally { + rollback(); + + if (p1 != null) { + p1.set_TrxName(null); + p1.deleteEx(true); + } + + ConversionRateHelper.deleteConversionRate(cr1); + ConversionRateHelper.deleteConversionRate(cr2); + } + } + + @Test + public void testUnplannedLandedCostReversalWithZeroOnHand() { + + MClient client = MClient.get(Env.getCtx()); + MAcctSchema as = client.getAcctSchema(); + assertEquals(as.getCostingMethod(), MCostElement.COSTINGMETHOD_AveragePO, "Default costing method not Average PO"); + + MProduct p1 = null; + try { + p1 = new MProduct(Env.getCtx(), 0, null); + p1.setM_Product_Category_ID(DictionaryIDs.M_Product_Category.STANDARD.id); + p1.setName("testUnplannedLandedCostReversalWithZeroOnHand"); + p1.setProductType(MProduct.PRODUCTTYPE_Item); + p1.setIsStocked(true); + p1.setIsSold(true); + p1.setIsPurchased(true); + p1.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + p1.setC_TaxCategory_ID(DictionaryIDs.C_TaxCategory.STANDARD.id); + p1.saveEx(); + + MPriceListVersion plv = MPriceList.get(DictionaryIDs.M_PriceList.PURCHASE.id).getPriceListVersion(null); + MProductPrice pp = new MProductPrice(Env.getCtx(), 0, getTrxName()); + pp.setM_PriceList_Version_ID(plv.getM_PriceList_Version_ID()); + pp.setM_Product_ID(p1.get_ID()); + BigDecimal p1price = new BigDecimal("30.00"); + pp.setPriceStd(p1price); + pp.setPriceList(p1price); + pp.saveEx(); + + //create purchase order + MOrder purchaseOrder = new MOrder(Env.getCtx(), 0, getTrxName()); + purchaseOrder.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id)); + purchaseOrder.setC_DocTypeTarget_ID(DictionaryIDs.C_DocType.PURCHASE_ORDER.id); + purchaseOrder.setIsSOTrx(false); + purchaseOrder.setSalesRep_ID(DictionaryIDs.AD_User.GARDEN_ADMIN.id); + purchaseOrder.setDocStatus(DocAction.STATUS_Drafted); + purchaseOrder.setDocAction(DocAction.ACTION_Complete); + Timestamp today = TimeUtil.getDay(System.currentTimeMillis()); + purchaseOrder.setDateOrdered(today); + purchaseOrder.setDatePromised(today); + purchaseOrder.saveEx(); + + MOrderLine poLine1 = new MOrderLine(purchaseOrder); + poLine1.setLine(10); + poLine1.setProduct(new MProduct(Env.getCtx(), p1.get_ID(), getTrxName())); + BigDecimal orderQty = new BigDecimal("10"); + poLine1.setQty(orderQty); + poLine1.setDatePromised(today); + poLine1.setPrice(p1price); + poLine1.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(purchaseOrder, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + purchaseOrder.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, purchaseOrder.getDocStatus()); + + //mr1 for 10 each + MInOut receipt1 = new MInOut(purchaseOrder, DictionaryIDs.C_DocType.MM_RECEIPT.id, purchaseOrder.getDateOrdered()); + receipt1.setDocStatus(DocAction.STATUS_Drafted); + receipt1.setDocAction(DocAction.ACTION_Complete); + receipt1.saveEx(); + + MInOutLine receipt1Line1 = new MInOutLine(receipt1); + BigDecimal mr1Qty = new BigDecimal("10"); + receipt1Line1.setOrderLine(poLine1, 0, mr1Qty); + receipt1Line1.setQty(mr1Qty); + receipt1Line1.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt1, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + receipt1.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, receipt1.getDocStatus()); + if (!receipt1.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), receipt1.getAD_Client_ID(), receipt1.get_Table_ID(), receipt1.get_ID(), false, getTrxName()); + assertNull(error, error); + } + + //assert p1 cost and posting + List cds = MCostDetail.list(Env.getCtx(), "C_OrderLine_ID=?", poLine1.getC_OrderLine_ID(), 0, as.get_ID(), getTrxName()); + assertTrue(cds.size() == 1, "MCostDetail not found for order line1"); + for(MCostDetail cd : cds) { + if (cd.getM_CostElement_ID() == 0) { + assertEquals(10, cd.getQty().intValue(), "Unexpected MCostDetail Qty"); + assertEquals(p1price.multiply(mr1Qty).setScale(2, RoundingMode.HALF_UP), cd.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected MCostDetail Amt"); + } + } + + p1.set_TrxName(getTrxName()); + MCost p1mcost = p1.getCostingRecord(as, getAD_Org_ID(), 0, as.getCostingMethod()); + assertNotNull(p1mcost, "No MCost record found"); + assertEquals(p1price, p1mcost.getCurrentCostPrice().setScale(2, RoundingMode.HALF_UP), "Unexpected current cost price"); + + ProductCost p1ProductCost = new ProductCost(Env.getCtx(), p1.get_ID(), 0, getTrxName()); + MAccount assetAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + MAccount varianceAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_AverageCostVariance, as); + Doc doc = DocManager.getDocument(as, MInOut.Table_ID, receipt1.get_ID(), getTrxName()); + MAccount nivReceiptAccount = doc.getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as); + Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, receipt1.get_ID(), as.get_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(assetAccount, p1price.multiply(mr1Qty), 2, true), + new FactAcct(nivReceiptAccount, p1price.multiply(mr1Qty), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //full po invoice + MInvoice purchaseInvoice = new MInvoice(purchaseOrder, DictionaryIDs.C_DocType.AP_INVOICE.id, purchaseOrder.getDateOrdered()); + purchaseInvoice.setDocStatus(DocAction.STATUS_Drafted); + purchaseInvoice.setDocAction(DocAction.ACTION_Complete); + purchaseInvoice.saveEx(); + + MInvoiceLine piLine1 = new MInvoiceLine(purchaseInvoice); + piLine1.setOrderLine(poLine1); + piLine1.setLine(10); + piLine1.setProduct(p1); + piLine1.setQty(poLine1.getQtyOrdered()); + piLine1.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(purchaseInvoice, DocAction.ACTION_Complete); + purchaseInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, purchaseInvoice.getDocStatus()); + + if (!purchaseInvoice.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), purchaseInvoice.getAD_Client_ID(), MInvoice.Table_ID, purchaseInvoice.get_ID(), false, getTrxName()); + assertTrue(error == null); + } + purchaseInvoice.load(getTrxName()); + assertTrue(purchaseInvoice.isPosted()); + + Doc invoiceDoc = DocManager.getDocument(as, MInvoice.Table_ID, purchaseInvoice.get_ID(), getTrxName()); + MAccount liabilityAccount = invoiceDoc.getAccount(Doc.ACCTTYPE_V_Liability, as); + MAccount inventoryClearingAccount = p1ProductCost.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, purchaseInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + expected = Arrays.asList(new FactAcct(inventoryClearingAccount, p1price.multiply(orderQty), 2, true), + new FactAcct(liabilityAccount, p1price.multiply(orderQty), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //so and shipment + MBPartner customer = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id); + MOrder salesOrder = new MOrder(Env.getCtx(), 0, getTrxName()); + salesOrder.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard); + salesOrder.setBPartner(customer); + salesOrder.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder); + salesOrder.setDocStatus(DocAction.STATUS_Drafted); + salesOrder.setDocAction(DocAction.ACTION_Complete); + salesOrder.setDatePromised(today); + salesOrder.saveEx(); + + MOrderLine soLine1 = new MOrderLine(salesOrder); + soLine1.setLine(10); + soLine1.setProduct(p1); + BigDecimal p1ShipQty = new BigDecimal("10"); + soLine1.setQty(p1ShipQty); + soLine1.setDatePromised(today); + soLine1.setPrice(new BigDecimal("50")); + soLine1.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(salesOrder, DocAction.ACTION_Complete); + salesOrder.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, salesOrder.getDocStatus()); + + MInOut shipment = new MInOut(salesOrder, DictionaryIDs.C_DocType.MM_SHIPMENT.id, salesOrder.getDateOrdered()); + shipment.setDocStatus(DocAction.STATUS_Drafted); + shipment.setDocAction(DocAction.ACTION_Complete); + shipment.saveEx(); + + MInOutLine shipmentLine1 = new MInOutLine(shipment); + shipmentLine1.setOrderLine(soLine1, 0, soLine1.getQtyOrdered()); + shipmentLine1.setQty(soLine1.getQtyOrdered()); + shipmentLine1.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(shipment, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + shipment.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, shipment.getDocStatus()); + + //landed cost invoice + MBPartner freightBP = MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id); + MInvoice freightInvoice = new MInvoice(Env.getCtx(), 0, getTrxName()); + freightInvoice.setC_DocTypeTarget_ID(MDocType.DOCBASETYPE_APInvoice); + freightInvoice.setBPartner(freightBP); + freightInvoice.setDocStatus(DocAction.STATUS_Drafted); + freightInvoice.setDocAction(DocAction.ACTION_Complete); + freightInvoice.saveEx(); + + MInvoiceLine fiLine = new MInvoiceLine(freightInvoice); + fiLine.setLine(10); + fiLine.setC_Charge_ID(DictionaryIDs.C_Charge.FREIGHT.id); + fiLine.setQty(BigDecimal.ONE); + BigDecimal freightPrice = new BigDecimal("200.00"); + fiLine.setPrice(freightPrice); + fiLine.setC_UOM_ID(DictionaryIDs.C_UOM.EACH.id); + fiLine.saveEx(); + + MLandedCost landedCost = new MLandedCost(Env.getCtx(), 0, getTrxName()); + landedCost.setC_InvoiceLine_ID(fiLine.get_ID()); + landedCost.setM_CostElement_ID(DictionaryIDs.M_CostElement.FREIGHT.id); + landedCost.setM_InOut_ID(receipt1.get_ID()); + landedCost.setM_InOutLine_ID(receipt1Line1.get_ID()); + landedCost.setLandedCostDistribution(MLandedCost.LANDEDCOSTDISTRIBUTION_Costs); + landedCost.saveEx(); + + String error = landedCost.allocateCosts(); + assertTrue(Util.isEmpty(error, true), error); + + BigDecimal totalBase = purchaseInvoice.getGrandTotal(); + BigDecimal p1a1 = p1price.multiply(mr1Qty).multiply(fiLine.getLineNetAmt()).divide(totalBase, 6, RoundingMode.HALF_UP); + + MLandedCostAllocation[] allocations = MLandedCostAllocation.getOfInvoiceLine(Env.getCtx(), fiLine.get_ID(), getTrxName()); + assertEquals(1, allocations.length, "Unexpected number of landed cost allocation line"); + for (MLandedCostAllocation allocation : allocations) { + if (allocation.getM_Product_ID() == p1.get_ID() && allocation.getQty().intValue() == 10) { + assertEquals(p1a1.setScale(2, RoundingMode.HALF_UP), allocation.getAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected landed cost allocation amount"); + } else { + fail("Unknown landed cost allocation line: " + allocation); + } + } + + info = MWorkflow.runDocumentActionWorkflow(freightInvoice, DocAction.ACTION_Complete); + freightInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Completed, freightInvoice.getDocStatus()); + + if (!freightInvoice.isPosted()) { + error = DocumentEngine.postImmediate(Env.getCtx(), freightInvoice.getAD_Client_ID(), MInvoice.Table_ID, freightInvoice.get_ID(), false, getTrxName()); + assertTrue(error == null, error); + } + freightInvoice.load(getTrxName()); + assertTrue(freightInvoice.isPosted()); + + //assert freight invoice posting + doc = DocManager.getDocument(as, MInvoice.Table_ID, freightInvoice.get_ID(), getTrxName()); + MAccount apAccount = doc.getAccount(Doc.ACCTTYPE_V_Liability, as); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + expected = Arrays.asList(new FactAcct(varianceAccount, p1a1, 2, true), + new FactAcct(apAccount, freightInvoice.getGrandTotal(), 2, false)); + assertFactAcctEntries(factAccts, expected); + + //reverse freight invoice + info = MWorkflow.runDocumentActionWorkflow(freightInvoice, DocAction.ACTION_Reverse_Correct); + freightInvoice.load(getTrxName()); + assertFalse(info.isError(), info.getSummary()); + assertEquals(DocAction.STATUS_Reversed, freightInvoice.getDocStatus()); + assertTrue(freightInvoice.getReversal_ID() > 0, "Unexpected reversal id"); + MInvoice reversal = new MInvoice(Env.getCtx(), freightInvoice.getReversal_ID(), getTrxName()); + assertEquals(freightInvoice.getReversal_ID(), reversal.get_ID()); + if (!reversal.isPosted()) { + String msg = DocumentEngine.postImmediate(Env.getCtx(), getAD_Client_ID(), MInvoice.Table_ID, reversal.get_ID(), false, getTrxName()); + assertNull(msg, msg); + } + + //assert reversal invoice posting + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as.get_ID(), getTrxName()); + factAccts = query.list(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.getReversal_ID(), as.get_ID(), getTrxName()); + List rFactAccts = query.list(); + expected = new ArrayList(); + for(MFactAcct factAcct : factAccts) { + MAccount acct = MAccount.get(factAcct, getTrxName()); + if (factAcct.getAmtAcctDr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr(), 2, false)); + } else if (factAcct.getAmtAcctCr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr(), 2, true)); + } + } + assertFactAcctEntries(rFactAccts, expected); + + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(Env.getCtx(), Env.getAD_Client_ID(Env.getCtx())); + Optional optional = Arrays.stream(ass).filter(e -> e.getC_AcctSchema_ID() != as.get_ID()).findFirst(); + if (optional.isPresent()) { + MAcctSchema as2 = optional.get(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.get_ID(), as2.get_ID(), getTrxName()); + factAccts = query.list(); + query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, freightInvoice.getReversal_ID(), as2.get_ID(), getTrxName()); + rFactAccts = query.list(); + expected = new ArrayList(); + for(MFactAcct factAcct : factAccts) { + MAccount acct = MAccount.get(factAcct, getTrxName()); + if (factAcct.getAmtAcctDr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctDr(), 2, false)); + } else if (factAcct.getAmtAcctCr().signum() != 0) { + expected.add(new FactAcct(acct, factAcct.getAmtAcctCr(), 2, true)); + } + } + assertFactAcctEntries(rFactAccts, expected); + } + + } finally { + rollback(); + + if (p1 != null) { + p1.set_TrxName(null); + p1.deleteEx(true); + } + } + } + private MInOutLine createPOAndMRForProduct(int productId, MAttributeSetInstance asi, BigDecimal price) { MOrder order = new MOrder(Env.getCtx(), 0, getTrxName()); order.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.PATIO.id)); diff --git a/org.idempiere.test/src/org/idempiere/test/costing/NonStockedExpTypeAvgPOCostingTest.java b/org.idempiere.test/src/org/idempiere/test/costing/NonStockedExpTypeAvgPOCostingTest.java index 6ffa53b4a9..000d158ed2 100644 --- a/org.idempiere.test/src/org/idempiere/test/costing/NonStockedExpTypeAvgPOCostingTest.java +++ b/org.idempiere.test/src/org/idempiere/test/costing/NonStockedExpTypeAvgPOCostingTest.java @@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.fail; import java.math.BigDecimal; import java.math.RoundingMode; import java.sql.Timestamp; +import java.util.Arrays; import java.util.List; import org.compiere.acct.Doc; @@ -67,6 +68,7 @@ import org.eevolution.model.MPPProductBOM; import org.eevolution.model.MPPProductBOMLine; import org.idempiere.test.AbstractTestCase; import org.idempiere.test.DictionaryIDs; +import org.idempiere.test.FactAcct; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Isolated; @@ -133,38 +135,11 @@ public class NonStockedExpTypeAvgPOCostingTest extends AbstractTestCase ProductCost pc = new ProductCost(Env.getCtx(), rLine.getM_Product_ID(), 0, getTrxName()); MAccount productExpense = pc.getAccount(ProductCost.ACCTTYPE_P_Expense, as); - StringBuilder whereClause = new StringBuilder(); - whereClause - .append(MFactAcct.COLUMNNAME_AD_Table_ID) - .append("=") - .append(MInOut.Table_ID) - .append(" AND ") - .append(MFactAcct.COLUMNNAME_Record_ID) - .append("=") - .append(rLine.getM_InOut_ID()) - .append(" AND ") - .append(MFactAcct.COLUMNNAME_C_AcctSchema_ID) - .append("=") - .append(as.getC_AcctSchema_ID()); - - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause.toString(), getTrxName()); - - for (int id : ids) - { - // Test Accounting for MR - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if (fa.getAccount_ID() == acctNIR.getAccount_ID()) - { - assertEquals(BD_20, fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), ""); - assertEquals(new BigDecimal("-10.00"), fa.getQty().setScale(2, RoundingMode.HALF_UP), ""); - } - else if (fa.getAccount_ID() == productExpense.getAccount_ID()) - { - assertEquals(BD_20, fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), ""); - assertEquals(BigDecimal.TEN.setScale(2, RoundingMode.HALF_UP), fa.getQty().setScale(2, RoundingMode.HALF_UP), ""); - } - } - + Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, rLine.getM_InOut_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctNIR, BD_20, 2, false, new BigDecimal("-10.00")), + new FactAcct(productExpense, BD_20, 2, true, BigDecimal.TEN)); + assertFactAcctEntries(factAccts, expected); } finally { @@ -379,27 +354,11 @@ public class NonStockedExpTypeAvgPOCostingTest extends AbstractTestCase // get AccPayable of the Invoice MAccount acctPT = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getV_Liability_Acct(), getTrxName()); - String whereClause = "AD_Table_ID = " + MInvoice.Table_ID; - whereClause += " AND Record_ID = " + iLine.getC_Invoice_ID(); - whereClause += " AND C_AcctSchema_ID = " + as.getC_AcctSchema_ID(); - - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause.toString(), getTrxName()); - - for (int id : ids) - { - // Test Accounting for Invoice - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if (fa.getAccount_ID() == acctPT.getAccount_ID()) - { - assertEquals(BD_20, fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), ""); - assertEquals(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP), fa.getQty().setScale(2, RoundingMode.HALF_UP), ""); - } - else if (fa.getAccount_ID() == productExpense.getAccount_ID()) - { - assertEquals(BD_20, fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), ""); - assertEquals(BigDecimal.TEN.setScale(2, RoundingMode.HALF_UP), fa.getQty().setScale(2, RoundingMode.HALF_UP), ""); - } - } + Query query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, iLine.getC_Invoice_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctPT, BD_20, 2, false, BigDecimal.ZERO), + new FactAcct(productExpense, BD_20, 2, true, BigDecimal.TEN)); + assertFactAcctEntries(factAccts, expected); // Testing Accounting For MatchInv MMatchInv[] matchInvoices = MMatchInv.getInOut(Env.getCtx(), rLine.getM_InOut_ID(), getTrxName()); @@ -416,29 +375,11 @@ public class NonStockedExpTypeAvgPOCostingTest extends AbstractTestCase // get Product Expense for the MatchInv pc = new ProductCost(Env.getCtx(), matchInvoices[0].getM_Product_ID(), 0, getTrxName()); productExpense = pc.getAccount(ProductCost.ACCTTYPE_P_Expense, as); - whereClause = null; - whereClause = "AD_Table_ID = " + MMatchInv.Table_ID; - whereClause += " AND Record_ID = " + matchInvoices[0].get_ID(); - whereClause += " AND C_AcctSchema_ID = " + as.getC_AcctSchema_ID(); - - int[] idsMatchInv = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause.toString(), getTrxName()); - - for (int id : idsMatchInv) - { - // Test Accounting for MatchINV - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if (fa.getAccount_ID() == acctNIR.getAccount_ID()) - { - assertEquals(BD_20, fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), ""); - assertEquals(BigDecimal.TEN.setScale(2, RoundingMode.HALF_UP), fa.getQty().setScale(2, RoundingMode.HALF_UP), ""); - } - else if (fa.getAccount_ID() == productExpense.getAccount_ID()) - { - assertEquals(BD_20, fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), ""); - assertEquals(new BigDecimal("-10.00"), fa.getQty().setScale(2, RoundingMode.HALF_UP), ""); - } - } - + query = MFactAcct.createRecordIdQuery(MMatchInv.Table_ID, matchInvoices[0].get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + factAccts = query.list(); + expected = Arrays.asList(new FactAcct(acctNIR, BD_20, 2, true, BigDecimal.TEN), + new FactAcct(productExpense, BD_20, 2, false, BigDecimal.TEN.negate())); + assertFactAcctEntries(factAccts, expected); } finally { diff --git a/org.idempiere.test/src/org/idempiere/test/costing/NonStockedExpTypeStdCostingTest.java b/org.idempiere.test/src/org/idempiere/test/costing/NonStockedExpTypeStdCostingTest.java index 9b7149432b..00deac0a1a 100644 --- a/org.idempiere.test/src/org/idempiere/test/costing/NonStockedExpTypeStdCostingTest.java +++ b/org.idempiere.test/src/org/idempiere/test/costing/NonStockedExpTypeStdCostingTest.java @@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.fail; import java.math.BigDecimal; import java.math.RoundingMode; import java.sql.Timestamp; +import java.util.Arrays; import java.util.List; import org.compiere.acct.Doc; @@ -68,6 +69,7 @@ import org.eevolution.model.MPPProductBOM; import org.eevolution.model.MPPProductBOMLine; import org.idempiere.test.AbstractTestCase; import org.idempiere.test.DictionaryIDs; +import org.idempiere.test.FactAcct; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Isolated; @@ -129,38 +131,11 @@ public class NonStockedExpTypeStdCostingTest extends AbstractTestCase ProductCost pc = new ProductCost(Env.getCtx(), rLine.getM_Product_ID(), 0, getTrxName()); MAccount productExpense = pc.getAccount(ProductCost.ACCTTYPE_P_Expense, as); - StringBuilder whereClause = new StringBuilder(); - whereClause - .append(MFactAcct.COLUMNNAME_AD_Table_ID) - .append("=") - .append(MInOut.Table_ID) - .append(" AND ") - .append(MFactAcct.COLUMNNAME_Record_ID) - .append("=") - .append(rLine.getM_InOut_ID()) - .append(" AND ") - .append(MFactAcct.COLUMNNAME_C_AcctSchema_ID) - .append("=") - .append(as.getC_AcctSchema_ID()); - - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause.toString(), getTrxName()); - - for (int id : ids) - { - // Test Accounting for MR - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if (fa.getAccount_ID() == acctNIR.getAccount_ID()) - { - assertEquals(BD_20, fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), ""); - assertEquals(new BigDecimal("-10.00"), fa.getQty().setScale(2, RoundingMode.HALF_UP), ""); - } - else if (fa.getAccount_ID() == productExpense.getAccount_ID()) - { - assertEquals(BD_20, fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), ""); - assertEquals(BigDecimal.TEN.setScale(2, RoundingMode.HALF_UP), fa.getQty().setScale(2, RoundingMode.HALF_UP), ""); - } - } - + Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, rLine.getM_InOut_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctNIR, BD_20, 2, false, BigDecimal.TEN.negate()), + new FactAcct(productExpense, BD_20, 2, true, BigDecimal.TEN)); + assertFactAcctEntries(factAccts, expected); } finally { @@ -377,38 +352,11 @@ public class NonStockedExpTypeStdCostingTest extends AbstractTestCase // get AccPayable of the Invoice MAccount acctPT = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getV_Liability_Acct(), getTrxName()); - StringBuilder whereClause = new StringBuilder(); - - whereClause - .append(MFactAcct.COLUMNNAME_AD_Table_ID) - .append("=") - .append(MInvoice.Table_ID) - .append(" AND ") - .append(MFactAcct.COLUMNNAME_Record_ID) - .append("=") - .append(iLine.getC_Invoice_ID()) - .append(" AND ") - .append(MFactAcct.COLUMNNAME_C_AcctSchema_ID) - .append("=") - .append(as.getC_AcctSchema_ID()); - - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause.toString(), getTrxName()); - - for (int id : ids) - { - // Test Accounting for Invoice - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if (fa.getAccount_ID() == acctPT.getAccount_ID()) - { - assertEquals(BD_20, fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), ""); - assertEquals(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP), fa.getQty().setScale(2, RoundingMode.HALF_UP), ""); - } - else if (fa.getAccount_ID() == productExpense.getAccount_ID()) - { - assertEquals(BD_20, fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), ""); - assertEquals(BigDecimal.TEN.setScale(2, RoundingMode.HALF_UP), fa.getQty().setScale(2, RoundingMode.HALF_UP), ""); - } - } + Query query = MFactAcct.createRecordIdQuery(MInvoice.Table_ID, iLine.getC_Invoice_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctPT, BD_20, 2, false, BigDecimal.ZERO), + new FactAcct(productExpense, BD_20, 2, true, BigDecimal.TEN)); + assertFactAcctEntries(factAccts, expected); //Accounting MatchInv MMatchInv[] matchInvoices = MMatchInv.getInOut(Env.getCtx(), rLine.getM_InOut_ID(), getTrxName()); @@ -426,39 +374,11 @@ public class NonStockedExpTypeStdCostingTest extends AbstractTestCase pc = new ProductCost(Env.getCtx(), matchInvoices[0].getM_Product_ID(), 0, getTrxName()); productExpense = pc.getAccount(ProductCost.ACCTTYPE_P_Expense, as); - whereClause = null; - whereClause = new StringBuilder(); - whereClause - .append(MFactAcct.COLUMNNAME_AD_Table_ID) - .append("=") - .append(MMatchInv.Table_ID) - .append(" AND ") - .append(MFactAcct.COLUMNNAME_Record_ID) - .append("=") - .append(matchInvoices[0].get_ID()) - .append(" AND ") - .append(MFactAcct.COLUMNNAME_C_AcctSchema_ID) - .append("=") - .append(as.getC_AcctSchema_ID()); - - int[] idsMatchInv = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause.toString(), getTrxName()); - - for (int id : idsMatchInv) - { - // Test Accounting for MatchINV - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if (fa.getAccount_ID() == acctNIR.getAccount_ID()) - { - assertEquals(BD_20, fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), ""); - assertEquals(BigDecimal.TEN.setScale(2, RoundingMode.HALF_UP), fa.getQty().setScale(2, RoundingMode.HALF_UP), ""); - } - else if (fa.getAccount_ID() == productExpense.getAccount_ID()) - { - assertEquals(BD_20, fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), ""); - assertEquals(new BigDecimal("-10.00"), fa.getQty().setScale(2, RoundingMode.HALF_UP), ""); - } - } - + query = MFactAcct.createRecordIdQuery(MMatchInv.Table_ID, matchInvoices[0].get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + factAccts = query.list(); + expected = Arrays.asList(new FactAcct(acctNIR, BD_20, 2, true, BigDecimal.TEN), + new FactAcct(productExpense, BD_20, 2, false, BigDecimal.TEN.negate())); + assertFactAcctEntries(factAccts, expected); } finally { diff --git a/org.idempiere.test/src/org/idempiere/test/jasper/HandleSetupConfigurationPdfExport.java b/org.idempiere.test/src/org/idempiere/test/jasper/HandleSetupConfigurationPdfExport.java new file mode 100644 index 0000000000..396142fb84 --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/jasper/HandleSetupConfigurationPdfExport.java @@ -0,0 +1,87 @@ +/*********************************************************************** + * This file is part of iDempiere ERP Open Source * + * http://www.idempiere.org * + * * + * Copyright (C) Contributors * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301, USA. * + **********************************************************************/ + +package org.idempiere.test.jasper; + +import net.sf.jasperreports.export.SimplePdfExporterConfiguration; +import org.adempiere.base.event.AbstractEventHandler; +import org.adempiere.base.event.IEventManager; +import org.adempiere.report.jasper.JREventManage; +import org.compiere.process.ProcessInfo; +import org.compiere.process.ProcessInfoParameter; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.event.Event; + +@Component( +reference = @Reference( + name = "IEventManager", bind = "bindEventManager", unbind="unbindEventManager", + policy = ReferencePolicy.STATIC, cardinality =ReferenceCardinality.MANDATORY, service = IEventManager.class) +) +public class HandleSetupConfigurationPdfExport extends AbstractEventHandler { + + @Override + protected void initialize() { + registerEvent(JREventManage.JP_PDF_EXPORT_CONFIG_EVENT, null); + + } + + @Override + protected void doHandleEvent(Event event) { + // just handle pdf export event + if (!JREventManage.JP_PDF_EXPORT_CONFIG_EVENT.equals(event.getTopic())) + return; + + // get common object from event info + SimplePdfExporterConfiguration pdfExporterConfig = (SimplePdfExporterConfiguration)event.getProperty(JREventManage.JP_KEY_PDF_EXPORT_CONFIG); + //JRPdfExporter pdfExporter = (JRPdfExporter)event.getProperty(JP_KEY_PDF_EXPORT_EXPORTER); + ProcessInfo pi = (ProcessInfo)event.getProperty(JREventManage.JP_KEY_PROCESS_INFO); + + // can apply passowrd or not up to parameter + boolean isEncrypted = false; + String readPassword = ""; + String createPassword = ""; + + // control logic by parameter + for (ProcessInfoParameter parameter : pi.getParameter()) { + if ("isEncrypted".equals(parameter.getParameterName())) { + isEncrypted = parameter.getParameterAsBoolean(); + }else if ("readPassword".equals(parameter.getParameterName())) { + readPassword = parameter.getParameterAsString(); + }else if ("createPassword".equals(parameter.getParameterName())) { + createPassword = parameter.getParameterAsString(); + } + } + + if (isEncrypted) { + pdfExporterConfig.setEncrypted(true); + pdfExporterConfig.set128BitKey(true); + pdfExporterConfig.setUserPassword(readPassword); + pdfExporterConfig.setOwnerPassword(createPassword); + + } + + } + +} diff --git a/org.idempiere.test/src/org/idempiere/test/jasper/PrintWithinProcess.java b/org.idempiere.test/src/org/idempiere/test/jasper/PrintWithinProcess.java index 8bcf2fd5cc..910c924fba 100644 --- a/org.idempiere.test/src/org/idempiere/test/jasper/PrintWithinProcess.java +++ b/org.idempiere.test/src/org/idempiere/test/jasper/PrintWithinProcess.java @@ -40,6 +40,7 @@ import org.compiere.model.MProcess; import org.compiere.model.PO; import org.compiere.model.Query; import org.compiere.process.ProcessInfo; +import org.compiere.process.ProcessInfoParameter; import org.compiere.util.Env; import org.compiere.util.Trx; import org.idempiere.test.AbstractTestCase; @@ -56,6 +57,28 @@ public class PrintWithinProcess extends AbstractTestCase { public PrintWithinProcess() { } + private MProcess setupProcess (Properties ctx, String trxName, String jasperReport) { + MProcess process = new MProcess(ctx, 0, trxName); + process.set_ValueNoCheck("AD_Client_ID", 0); + process.setAD_Org_ID(0); + process.setJasperReport(jasperReport); + process.setName("Test Invoice Jasper"); + process.setValue("Test_Invoice_Jasper"); + process.saveCrossTenantSafeEx(); + commit(); + return process; + } + + private ProcessInfo setupProcessInfo(MProcess process) { + ProcessInfo pi = new ProcessInfo (process.getName(), process.getAD_Process_ID()); + pi.setClassName(ProcessUtil.JASPER_STARTER_CLASS); + pi.setAD_User_ID(getAD_User_ID()); + pi.setAD_Client_ID(getAD_Client_ID()); + pi.setPrintPreview(false); + pi.setIsBatch(true); + return pi; + } + @Test public void testPrintWithLocalFile() { Properties ctx = Env.getCtx(); @@ -64,14 +87,7 @@ public class PrintWithinProcess extends AbstractTestCase { MProcess process = null; try { String fileName = putResourceInTempFolder("org/idempiere/test/jasper/AR_Invoice.jrxml"); - process = new MProcess(ctx, 0, trxName); - process.set_ValueNoCheck("AD_Client_ID", 0); - process.setAD_Org_ID(0); - process.setJasperReport("file://" + fileName); - process.setName("Test Invoice Jasper"); - process.setValue("Test_Invoice_Jasper"); - process.saveCrossTenantSafeEx(); - commit(); + process = setupProcess(ctx, trxName, "file://" + fileName); List invoices = new Query(ctx, MInvoice.Table_Name, "C_Invoice_ID IN (?,?)", trxName) .setClient_ID() @@ -83,12 +99,7 @@ public class PrintWithinProcess extends AbstractTestCase { invoice.saveEx(); } - ProcessInfo pi = new ProcessInfo (process.getName(), process.getAD_Process_ID()); - pi.setClassName(ProcessUtil.JASPER_STARTER_CLASS); - pi.setAD_User_ID(getAD_User_ID()); - pi.setAD_Client_ID(getAD_Client_ID()); - pi.setPrintPreview(false); - pi.setIsBatch(true); + ProcessInfo pi = setupProcessInfo(process); Trx trx = Trx.get(trxName, false); List pdfList = new ArrayList(); @@ -144,7 +155,7 @@ public class PrintWithinProcess extends AbstractTestCase { } throw new AdempiereException("Resource " + resource + " not found"); } - + @Test public void testPrintWithBundleResource() { Properties ctx = Env.getCtx(); @@ -152,14 +163,7 @@ public class PrintWithinProcess extends AbstractTestCase { MProcess process = null; try { - process = new MProcess(ctx, 0, trxName); - process.set_ValueNoCheck("AD_Client_ID", 0); - process.setAD_Org_ID(0); - process.setJasperReport("bundle:org.idempiere.test:/AR_Invoice_Bundle.jrxml"); - process.setName("Test Invoice Jasper"); - process.setValue("Test_Invoice_Jasper"); - process.saveCrossTenantSafeEx(); - commit(); + process = setupProcess(ctx, trxName, "bundle:org.idempiere.test:/AR_Invoice_Bundle.jrxml"); List invoices = new Query(ctx, MInvoice.Table_Name, "C_Invoice_ID IN (?,?)", trxName) .setClient_ID() @@ -171,12 +175,7 @@ public class PrintWithinProcess extends AbstractTestCase { invoice.saveEx(); } - ProcessInfo pi = new ProcessInfo (process.getName(), process.getAD_Process_ID()); - pi.setClassName(ProcessUtil.JASPER_STARTER_CLASS); - pi.setAD_User_ID(getAD_User_ID()); - pi.setAD_Client_ID(getAD_Client_ID()); - pi.setPrintPreview(false); - pi.setIsBatch(true); + ProcessInfo pi = setupProcessInfo(process); Trx trx = Trx.get(trxName, false); List pdfList = new ArrayList(); @@ -212,14 +211,7 @@ public class PrintWithinProcess extends AbstractTestCase { MProcess process = null; try { - process = new MProcess(ctx, 0, trxName); - process.set_ValueNoCheck("AD_Client_ID", 0); - process.setAD_Org_ID(0); - process.setJasperReport("resource:org.idempiere.test:org/idempiere/test/jasper/AR_Invoice.jrxml"); - process.setName("Test Invoice Jasper"); - process.setValue("Test_Invoice_Jasper"); - process.saveCrossTenantSafeEx(); - commit(); + process = setupProcess(ctx, trxName, "resource:org.idempiere.test:org/idempiere/test/jasper/AR_Invoice.jrxml"); List invoices = new Query(ctx, MInvoice.Table_Name, "C_Invoice_ID IN (?,?)", trxName) .setClient_ID() @@ -231,12 +223,7 @@ public class PrintWithinProcess extends AbstractTestCase { invoice.saveEx(); } - ProcessInfo pi = new ProcessInfo (process.getName(), process.getAD_Process_ID()); - pi.setClassName(ProcessUtil.JASPER_STARTER_CLASS); - pi.setAD_User_ID(getAD_User_ID()); - pi.setAD_Client_ID(getAD_Client_ID()); - pi.setPrintPreview(false); - pi.setIsBatch(true); + ProcessInfo pi = setupProcessInfo(process); Trx trx = Trx.get(trxName, false); List pdfList = new ArrayList(); @@ -264,4 +251,58 @@ public class PrintWithinProcess extends AbstractTestCase { commit(); } } + + @Test + public void testEncryptReport() { + Properties ctx = Env.getCtx(); + String trxName = getTrxName(); + + MProcess process = null; + try { + process = setupProcess(ctx, trxName, "bundle:org.idempiere.test:/AR_Invoice_Bundle.jrxml"); + List invoices = new Query(ctx, MInvoice.Table_Name, "C_Invoice_ID IN (?,?)", trxName) + .setClient_ID() + .setOnlyActiveRecords(true) + .setParameters(103, 109) + .list(); + for (MInvoice invoice : invoices) { + invoice.setDescription("Test Printing within a Process"); + invoice.saveEx(); + } + + ProcessInfo pi = setupProcessInfo(process); + pi.setExport(true); + Trx trx = Trx.get(trxName, false); + ProcessInfoParameter [] parameter = new ProcessInfoParameter [] + {new ProcessInfoParameter("isEncrypted", true, null, null, null), + new ProcessInfoParameter("readPassword", "readPassword", null, null, null), + new ProcessInfoParameter("createPassword", "createPassword", null, null, null)}; + + pi.setParameter(parameter); + + List pdfList = new ArrayList(); + for (MInvoice invoice : invoices) { + pi.setRecord_ID(invoice.getC_Invoice_ID()); + ProcessUtil.startJavaProcess(Env.getCtx(), pi, trx, false); + assertFalse(pi.isError(), pi.getSummary()); + assertFalse(pi.getExportFile() == null); + pdfList.add(pi.getExportFile()); + } + assertFalse(pdfList.isEmpty()); + } finally { + rollback(); + if (process != null) { + int oldRole = Env.getAD_Role_ID(ctx); + try { + PO.setCrossTenantSafe(); + Env.setContext(ctx, Env.AD_ROLE_ID, 0); // to allow deleting process + process.deleteEx(true); + } finally { + Env.setContext(ctx, Env.AD_ROLE_ID, oldRole); + PO.clearCrossTenantSafe(); + } + } + commit(); + } + } } diff --git a/org.idempiere.test/src/org/idempiere/test/model/Allocation2ndAcctSchemaTest.java b/org.idempiere.test/src/org/idempiere/test/model/Allocation2ndAcctSchemaTest.java index 54ce6232a8..7efc543af0 100644 --- a/org.idempiere.test/src/org/idempiere/test/model/Allocation2ndAcctSchemaTest.java +++ b/org.idempiere.test/src/org/idempiere/test/model/Allocation2ndAcctSchemaTest.java @@ -34,6 +34,7 @@ import java.sql.Timestamp; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; +import java.util.List; import org.compiere.acct.Doc; import org.compiere.acct.DocManager; @@ -1453,11 +1454,11 @@ public class Allocation2ndAcctSchemaTest extends AbstractTestCase { BigDecimal allocAmount = new BigDecimal(1000); ArrayList tradeLineList = new ArrayList(); ArrayList gainLossLineList = new ArrayList(); - BigDecimal accountedDrAmt = getAccountedAmount(usd, allocAmount, cr2.getMultiplyRate()); + BigDecimal accountedDrAmt = getAccountedAmount(usd, allocAmount, cr1.getMultiplyRate()); BigDecimal accountedCrAmt = getAccountedAmount(usd, allocAmount, cr1.getMultiplyRate()); tradeLineList.add(new PostingLine(usd, accountedDrAmt, Env.ZERO)); tradeLineList.add(new PostingLine(usd, Env.ZERO, accountedCrAmt)); - BigDecimal gainLossAmt = new BigDecimal(1000).setScale(usd.getStdPrecision(), RoundingMode.HALF_UP); + BigDecimal gainLossAmt = BigDecimal.ZERO; gainLossLineList.add(new PostingLine(usd, Env.ZERO, gainLossAmt)); testAllocationPosting(ass, allocList, null, tradeLineList, gainLossLineList, null); @@ -2181,12 +2182,9 @@ public class Allocation2ndAcctSchemaTest extends AbstractTestCase { BigDecimal totalTradeAmtAcct = Env.ZERO; BigDecimal totalGainLossAmtAcct = Env.ZERO; BigDecimal totalCurrBalAmtAcct = Env.ZERO; - String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MAllocationHdr.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + alloc.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID(); - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); + Query query = MFactAcct.createRecordIdQuery(MAllocationHdr.Table_ID, alloc.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + for (MFactAcct fa : factAccts) { if (C_Charge_ID != 0 && acctCharge != null && acctCharge.getAccount_ID() == fa.getAccount_ID()) totalPaymentAmtAcct = totalPaymentAmtAcct.add(fa.getAmtAcctDr()).subtract(fa.getAmtAcctCr()); else if (C_Charge_ID == 0 && acctUnallocatedCash != null && acctUnallocatedCash.getAccount_ID() == fa.getAccount_ID()) diff --git a/org.idempiere.test/src/org/idempiere/test/model/AllocationTest.java b/org.idempiere.test/src/org/idempiere/test/model/AllocationTest.java index 3a9e597889..f9fa8c1ce5 100644 --- a/org.idempiere.test/src/org/idempiere/test/model/AllocationTest.java +++ b/org.idempiere.test/src/org/idempiere/test/model/AllocationTest.java @@ -31,7 +31,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; import java.math.RoundingMode; import java.sql.Timestamp; +import java.util.Arrays; import java.util.Calendar; +import java.util.List; import java.util.Properties; import java.util.logging.LogRecord; @@ -64,6 +66,7 @@ import org.compiere.wf.MWorkflow; import org.idempiere.test.AbstractTestCase; import org.idempiere.test.ConversionRateHelper; import org.idempiere.test.DictionaryIDs; +import org.idempiere.test.FactAcct; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.api.parallel.ResourceLock; @@ -459,12 +462,9 @@ public class AllocationTest extends AbstractTestCase { BigDecimal ucAmtAcctCr = new BigDecimal(31000); BigDecimal lossAmtAcct = new BigDecimal(1000); - whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MAllocationHdr.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + allocation.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID(); - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); + Query query = MFactAcct.createRecordIdQuery(MAllocationHdr.Table_ID, allocation.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + for (MFactAcct fa : factAccts) { if (acctUC.getAccount_ID() == fa.getAccount_ID()) { if (fa.getAmtAcctDr().signum() > 0) assertTrue(fa.getAmtAcctDr().compareTo(ucAmtAcctDr) == 0, fa.getAmtAcctDr().toPlainString() + "!=" + ucAmtAcctDr.toPlainString()); @@ -605,12 +605,9 @@ public class AllocationTest extends AbstractTestCase { MAccount acctLiability = doc.getAccount(Doc.ACCTTYPE_V_Liability, as); BigDecimal tradeAmtAcct = new BigDecimal(2.13).setScale(usd.getStdPrecision(), RoundingMode.HALF_UP);; - String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MAllocationHdr.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + allocation.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID(); - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); + Query query = MFactAcct.createRecordIdQuery(MAllocationHdr.Table_ID, allocation.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + for (MFactAcct fa : factAccts) { if (acctLiability.getAccount_ID() == fa.getAccount_ID()) { if (fa.getAmtAcctDr().signum() > 0) assertTrue(fa.getAmtAcctDr().compareTo(tradeAmtAcct) == 0, fa.getAmtAcctDr().toPlainString() + "!=" + tradeAmtAcct.toPlainString()); @@ -706,12 +703,9 @@ public class AllocationTest extends AbstractTestCase { BigDecimal ucAmtAcctCr = new BigDecimal(31000); BigDecimal lossAmtAcct = new BigDecimal(1000); - whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MAllocationHdr.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + allocation.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID(); - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); + Query query = MFactAcct.createRecordIdQuery(MAllocationHdr.Table_ID, allocation.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + for (MFactAcct fa : factAccts) { if (acctUC.getAccount_ID() == fa.getAccount_ID()) { if (fa.getAmtAcctDr().signum() > 0) assertTrue(fa.getAmtAcctDr().compareTo(ucAmtAcctDr) == 0, fa.getAmtAcctDr().toPlainString() + "!=" + ucAmtAcctDr.toPlainString()); @@ -897,43 +891,14 @@ public class AllocationTest extends AbstractTestCase { MAccount acctART = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getC_Receivable_Acct(), getTrxName()); MAccount acctTD = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getT_Due_Acct(), getTrxName()); - whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MAllocationHdr.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + alloc.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID() - + " ORDER BY Created"; - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if(fa.getAccount_ID() == acctUC.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("102.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else if(fa.getAccount_ID() == acctDEP.getAccount_ID()) { - if(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)>0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - } - } else if(fa.getAccount_ID() == acctWO.getAccount_ID()) { - if(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)>0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - } - - } else if(fa.getAccount_ID() == acctART.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("106.00")); - } else if(fa.getAccount_ID() == acctTD.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } - - } + Query query = MFactAcct.createRecordIdQuery(MAllocationHdr.Table_ID, alloc.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctUC, new BigDecimal("102.00"), 2, true), + new FactAcct(acctDEP, new BigDecimal("2.00"), 2, true), new FactAcct(acctDEP, new BigDecimal("0.11"), 2, false), + new FactAcct(acctWO, new BigDecimal("2.00"), 2, true), new FactAcct(acctWO, new BigDecimal("0.11"), 2, false), + new FactAcct(acctART, new BigDecimal("106.00"), 2, false), + new FactAcct(acctTD, new BigDecimal("0.11"), 2, true)); + assertFactAcctEntries(factAccts, expected); } } finally { @@ -1028,42 +993,14 @@ public class AllocationTest extends AbstractTestCase { MAccount acctART = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getC_Receivable_Acct(), getTrxName()); MAccount acctTD = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getT_Due_Acct(), getTrxName()); - whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MAllocationHdr.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + alloc.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID() - + " ORDER BY Created"; - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if(fa.getAccount_ID() == acctPS.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("102.00").negate()); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else if(fa.getAccount_ID() == acctDEP.getAccount_ID()) { - if(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)<0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00").negate()); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } - } else if(fa.getAccount_ID() == acctWO.getAccount_ID()) { - if(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)<0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00").negate()); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } - } else if(fa.getAccount_ID() == acctART.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("106.00").negate()); - } else if(fa.getAccount_ID() == acctTD.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - } - - } + Query query = MFactAcct.createRecordIdQuery(MAllocationHdr.Table_ID, alloc.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctPS, new BigDecimal("-102.00"), 2, true), + new FactAcct(acctDEP, new BigDecimal("2.00").negate(), 2, true), new FactAcct(acctDEP, new BigDecimal("0.11"), 2, true), + new FactAcct(acctWO, new BigDecimal("2.00").negate(), 2, true), new FactAcct(acctWO, new BigDecimal("0.11"), 2, true), + new FactAcct(acctART, new BigDecimal("106.00").negate(), 2, false), + new FactAcct(acctTD, new BigDecimal("0.11"), 2, false)); + assertFactAcctEntries(factAccts, expected); } } finally { @@ -1158,42 +1095,14 @@ public class AllocationTest extends AbstractTestCase { MAccount acctPS = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getB_PaymentSelect_Acct(), getTrxName()); MAccount acctTD = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getT_Credit_Acct(), getTrxName()); - whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MAllocationHdr.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + alloc.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID() - + " ORDER BY Created"; - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if(fa.getAccount_ID() == acctPT.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("106.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else if(fa.getAccount_ID() == acctDRE.getAccount_ID()) { - if(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)>0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00")); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } - } else if(fa.getAccount_ID() == acctWO.getAccount_ID()) { - if(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)>0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00")); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } - } else if(fa.getAccount_ID() == acctPS.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("102.00")); - } else if(fa.getAccount_ID() == acctTD.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - } - - } + Query query = MFactAcct.createRecordIdQuery(MAllocationHdr.Table_ID, alloc.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctPT, new BigDecimal("106.00"), 2, true), + new FactAcct(acctDRE, new BigDecimal("2.00"), 2, false), new FactAcct(acctDRE, new BigDecimal("0.11"), 2, true), + new FactAcct(acctWO, new BigDecimal("2.00"), 2, false), new FactAcct(acctWO, new BigDecimal("0.11"), 2, true), + new FactAcct(acctPS, new BigDecimal("102.00"), 2, false), + new FactAcct(acctTD, new BigDecimal("0.11"), 2, false)); + assertFactAcctEntries(factAccts, expected); } } finally { @@ -1288,44 +1197,15 @@ public class AllocationTest extends AbstractTestCase { MAccount acctUC = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getB_UnallocatedCash_Acct(), getTrxName()); MAccount acctTD = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getT_Credit_Acct(), getTrxName()); - whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MAllocationHdr.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + alloc.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID() - + " ORDER BY Created"; - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if(fa.getAccount_ID() == acctPT.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("106.00").negate()); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else if(fa.getAccount_ID() == acctDRE.getAccount_ID()) { - if(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)<0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00").negate()); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - } - } else if(fa.getAccount_ID() == acctWO.getAccount_ID()) { - if(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)<0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00").negate()); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - } - } else if(fa.getAccount_ID() == acctUC.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("102.00").negate()); - } else if(fa.getAccount_ID() == acctTD.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } - - } + Query query = MFactAcct.createRecordIdQuery(MAllocationHdr.Table_ID, alloc.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctPT, new BigDecimal("106.00").negate(), 2, true), + new FactAcct(acctDRE, new BigDecimal("2.00").negate(), 2, false), new FactAcct(acctDRE, new BigDecimal("0.11"), 2, false), + new FactAcct(acctWO, new BigDecimal("2.00").negate(), 2, false), new FactAcct(acctWO, new BigDecimal("0.11"), 2, false), + new FactAcct(acctUC, new BigDecimal("102.00").negate(), 2, false), + new FactAcct(acctTD, new BigDecimal("0.11"), 2, true)); + assertFactAcctEntries(factAccts, expected); } - } finally { rollback(); @@ -1419,43 +1299,14 @@ public class AllocationTest extends AbstractTestCase { MAccount acctART = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getC_Receivable_Acct(), getTrxName()); MAccount acctTD = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getT_Due_Acct(), getTrxName()); - whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MAllocationHdr.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + allocationa[0].get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID() - + " ORDER BY Created"; - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if(fa.getAccount_ID() == acctUC.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("102.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else if(fa.getAccount_ID() == acctDEP.getAccount_ID()) { - if(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)>0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - } - } else if(fa.getAccount_ID() == acctWO.getAccount_ID()) { - if(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)>0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - } - - } else if(fa.getAccount_ID() == acctART.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("106.00")); - } else if(fa.getAccount_ID() == acctTD.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } - - } + Query query = MFactAcct.createRecordIdQuery(MAllocationHdr.Table_ID, allocationa[0].get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctUC, new BigDecimal("102.00"), 2, true), + new FactAcct(acctDEP, new BigDecimal("2.00"), 2, true), new FactAcct(acctDEP, new BigDecimal("0.11"), 2, false), + new FactAcct(acctWO, new BigDecimal("2.00"), 2, true), new FactAcct(acctWO, new BigDecimal("0.11"), 2, false), + new FactAcct(acctART, new BigDecimal("106.00"), 2, false), + new FactAcct(acctTD, new BigDecimal("0.11"), 2, true)); + assertFactAcctEntries(factAccts, expected); } } finally { @@ -1545,48 +1396,20 @@ public class AllocationTest extends AbstractTestCase { // 78100_Bad Debts Write-off | 0.11 | 0.00 // 21610 Tax due | 0.00 | 0.11 // -------------------------------------------------------------------- - MAccount acctPS = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getB_PaymentSelect_Acct(), getTrxName()); + MAccount acctPS = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getB_UnallocatedCash_Acct(), getTrxName()); MAccount acctDEP = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getPayDiscount_Exp_Acct(), getTrxName()); MAccount acctWO = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getWriteOff_Acct(), getTrxName()); MAccount acctART = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getC_Receivable_Acct(), getTrxName()); MAccount acctTD = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getT_Due_Acct(), getTrxName()); - whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MAllocationHdr.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + allocationa[0].get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID() - + " ORDER BY Created"; - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if(fa.getAccount_ID() == acctPS.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("102.00").negate()); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else if(fa.getAccount_ID() == acctDEP.getAccount_ID()) { - if(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)<0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00").negate()); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } - } else if(fa.getAccount_ID() == acctWO.getAccount_ID()) { - if(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)<0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00").negate()); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } - } else if(fa.getAccount_ID() == acctART.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("106.00").negate()); - } else if(fa.getAccount_ID() == acctTD.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - } - - } + Query query = MFactAcct.createRecordIdQuery(MAllocationHdr.Table_ID, allocationa[0].get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctPS, new BigDecimal("102.00").negate(), 2, true), + new FactAcct(acctDEP, new BigDecimal("2.00").negate(), 2, true), new FactAcct(acctDEP, new BigDecimal("0.11"), 2, true), + new FactAcct(acctWO, new BigDecimal("2.00").negate(), 2, true), new FactAcct(acctWO, new BigDecimal("0.11"), 2, true), + new FactAcct(acctART, new BigDecimal("106.00").negate(), 2, false), + new FactAcct(acctTD, new BigDecimal("0.11"), 2, false)); + assertFactAcctEntries(factAccts, expected); } } finally { @@ -1682,42 +1505,14 @@ public class AllocationTest extends AbstractTestCase { MAccount acctPS = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getB_PaymentSelect_Acct(), getTrxName()); MAccount acctTD = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getT_Credit_Acct(), getTrxName()); - whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MAllocationHdr.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + allocationa[0].get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID() - + " ORDER BY Created"; - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if(fa.getAccount_ID() == acctPT.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("106.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else if(fa.getAccount_ID() == acctDRE.getAccount_ID()) { - if(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)>0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00")); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } - } else if(fa.getAccount_ID() == acctWO.getAccount_ID()) { - if(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)>0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00")); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } - } else if(fa.getAccount_ID() == acctPS.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("102.00")); - } else if(fa.getAccount_ID() == acctTD.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - } - - } + Query query = MFactAcct.createRecordIdQuery(MAllocationHdr.Table_ID, allocationa[0].get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctPT, new BigDecimal("106.00"), 2, true), + new FactAcct(acctDRE, new BigDecimal("2.00"), 2, false), new FactAcct(acctDRE, new BigDecimal("0.11"), 2, true), + new FactAcct(acctWO, new BigDecimal("2.00"), 2, false), new FactAcct(acctWO, new BigDecimal("0.11"), 2, true), + new FactAcct(acctPS, new BigDecimal("102.00"), 2, false), + new FactAcct(acctTD, new BigDecimal("0.11"), 2, false)); + assertFactAcctEntries(factAccts, expected); } } finally { @@ -1810,45 +1605,17 @@ public class AllocationTest extends AbstractTestCase { MAccount acctPT = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getV_Liability_Acct(), getTrxName()); MAccount acctDRE = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getPayDiscount_Rev_Acct(), getTrxName()); MAccount acctWO = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getWriteOff_Acct(), getTrxName()); - MAccount acctUC = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getB_UnallocatedCash_Acct(), getTrxName()); + MAccount acctUC = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getB_PaymentSelect_Acct(), getTrxName()); MAccount acctTD = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getT_Credit_Acct(), getTrxName()); - whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MAllocationHdr.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + allocationa[0].get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID() - + " ORDER BY Created"; - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if(fa.getAccount_ID() == acctPT.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("106.00").negate()); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else if(fa.getAccount_ID() == acctDRE.getAccount_ID()) { - if(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)<0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00").negate()); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - } - } else if(fa.getAccount_ID() == acctWO.getAccount_ID()) { - if(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)<0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00").negate()); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - } - } else if(fa.getAccount_ID() == acctUC.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("104.00").negate()); - } else if(fa.getAccount_ID() == acctTD.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } - - } + Query query = MFactAcct.createRecordIdQuery(MAllocationHdr.Table_ID, allocationa[0].get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctPT, new BigDecimal("106.00").negate(), 2, true), + new FactAcct(acctDRE, new BigDecimal("2.00").negate(), 2, false), new FactAcct(acctDRE, new BigDecimal("0.11"), 2, false), + new FactAcct(acctWO, new BigDecimal("2.00").negate(), 2, false), new FactAcct(acctWO, new BigDecimal("0.11"), 2, false), + new FactAcct(acctUC, new BigDecimal("102.00").negate(), 2, false), + new FactAcct(acctTD, new BigDecimal("0.11"), 2, true)); + assertFactAcctEntries(factAccts, expected); } } finally { @@ -1932,60 +1699,29 @@ public class AllocationTest extends AbstractTestCase { // Account | Acct Debit | Acct Credit // -------------------------------------------------------------------- - // 59201_Payment discount revenue | 2.00 | 0.00 + // 59201_Payment discount expense | 2.00 | 0.00 // 78100_Bad Debts Write-off | 2.00 | 0.00 // 12110 Accounts Receivable - Trade | 0.00 | 106.00 // 21610 Tax due | 0.11 | 0.00 - // 59201_Payment discount revenue | 0.00 | 0.11 + // 59201_Payment discount expense | 0.00 | 0.11 // 21610 Tax due | 0.11 | 0.00 // 78100_Bad Debts Write-off | 0.00 | 0.11 // 12110_Accounts Receivable - Trade | 0.00 | -102.00 // -------------------------------------------------------------------- - // ToDo: set Account - MAccount acctDRE = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getPayDiscount_Rev_Acct(), getTrxName()); - MAccount acctWO = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getWriteOff_Acct(), getTrxName()); - MAccount acctART = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getC_Receivable_Acct(), getTrxName()); + doc.setC_BPartner_ID(bpartner.getC_BPartner_ID()); + MAccount acctDRE = doc.getAccount(Doc.ACCTTYPE_DiscountExp, as); + MAccount acctWO = doc.getAccount(Doc.ACCTTYPE_WriteOff, as); + MAccount acctART = doc.getAccount(Doc.ACCTTYPE_C_Receivable, as); MAccount acctTD = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getT_Due_Acct(), getTrxName()); - whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MAllocationHdr.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + alloc.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID() - + " ORDER BY Created"; - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if(fa.getAccount_ID() == acctDRE.getAccount_ID()) { - if(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)>0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - } - } else if(fa.getAccount_ID() == acctWO.getAccount_ID()) { - if(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)>0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - } - - } else if(fa.getAccount_ID() == acctART.getAccount_ID()) { - if(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)>0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("106.00")); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("-102.00")); - } - } else if(fa.getAccount_ID() == acctTD.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } - - } + Query query = MFactAcct.createRecordIdQuery(MAllocationHdr.Table_ID, alloc.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctDRE, new BigDecimal("2.00"), 2, true), + new FactAcct(acctDRE, new BigDecimal("0.11"), 2, false), + new FactAcct(acctWO, new BigDecimal("2.00"), 2, true), new FactAcct(acctWO, new BigDecimal("0.11"), 2, false), + new FactAcct(acctART, new BigDecimal("106.00"), 2, false), new FactAcct(acctART, new BigDecimal("-102.00"), 2, false), + new FactAcct(acctTD, new BigDecimal("0.11"), 2, true)); + assertFactAcctEntries(factAccts, expected); } } finally { @@ -2085,43 +1821,14 @@ public class AllocationTest extends AbstractTestCase { MAccount acctWO = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getWriteOff_Acct(), getTrxName()); MAccount acctTD = new MAccount(Env.getCtx(), as.getAcctSchemaDefault().getT_Credit_Acct(), getTrxName()); - whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MAllocationHdr.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + alloc.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID() - + " ORDER BY Created"; - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if(fa.getAccount_ID() == acctPT.getAccount_ID()) { - if(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)>0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("106.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("-102.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } - } else if(fa.getAccount_ID() == acctDRE.getAccount_ID()) { - if(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)>0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00")); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } - } else if(fa.getAccount_ID() == acctWO.getAccount_ID()) { - if(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP).compareTo(Env.ZERO)>0) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("2.00")); - } else { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - } - } else if(fa.getAccount_ID() == acctTD.getAccount_ID()) { - assertEquals(fa.getAmtAcctDr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.00")); - assertEquals(fa.getAmtAcctCr().setScale(2, RoundingMode.HALF_UP), new BigDecimal("0.11")); - } - } + Query query = MFactAcct.createRecordIdQuery(MAllocationHdr.Table_ID, alloc.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctPT, new BigDecimal("106.00"), 2, true), + new FactAcct(acctPT, new BigDecimal("-102.00"), 2, true), + new FactAcct(acctDRE, new BigDecimal("2.00"), 2, false), new FactAcct(acctDRE, new BigDecimal("0.11"), 2, true), + new FactAcct(acctWO, new BigDecimal("2.00"), 2, false), new FactAcct(acctWO, new BigDecimal("0.11"), 2, true), + new FactAcct(acctTD, new BigDecimal("0.11"), 2, false)); + assertFactAcctEntries(factAccts, expected); } } finally { @@ -2242,18 +1949,10 @@ public class AllocationTest extends AbstractTestCase { MAccount acctUC = doc.getAccount(Doc.ACCTTYPE_UnallocatedCash, as); BigDecimal ucAmtAcctDr = new BigDecimal(370.88).setScale(2, RoundingMode.HALF_UP); - whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MAllocationHdr.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + allocation.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID() - + " ORDER BY Created"; - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - if (acctUC.getAccount_ID() == fa.getAccount_ID()) { - if (fa.getAmtAcctDr().signum() > 0) - assertTrue(fa.getAmtAcctDr().compareTo(ucAmtAcctDr) == 0, fa.getAmtAcctDr().toPlainString() + "!=" + ucAmtAcctDr.toPlainString()); - } - } + Query query = MFactAcct.createRecordIdQuery(MAllocationHdr.Table_ID, allocation.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + List expected = Arrays.asList(new FactAcct(acctUC, ucAmtAcctDr, 2, true)); + assertFactAcctEntries(factAccts, expected); } MInvoice invoice2 = new MInvoice(Env.getCtx(), 0, getTrxName()); @@ -2313,13 +2012,9 @@ public class AllocationTest extends AbstractTestCase { BigDecimal ucAmtAcctDr = new BigDecimal(175.67).setScale(2, RoundingMode.HALF_UP); BigDecimal ucAmtAcctCr = new BigDecimal(0.01).setScale(2, RoundingMode.HALF_UP); - whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MAllocationHdr.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + allocation.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID() - + " ORDER BY Created"; - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); + Query query = MFactAcct.createRecordIdQuery(MAllocationHdr.Table_ID, allocation.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); + for (MFactAcct fa : factAccts) { if (acctUC.getAccount_ID() == fa.getAccount_ID()) { if (fa.getAmtAcctDr().signum() > 0) assertTrue(fa.getAmtAcctDr().compareTo(ucAmtAcctDr) == 0, fa.getAmtAcctDr().toPlainString() + "!=" + ucAmtAcctDr.toPlainString()); diff --git a/org.idempiere.test/src/org/idempiere/test/model/InvoiceCustomerTest.java b/org.idempiere.test/src/org/idempiere/test/model/InvoiceCustomerTest.java index 4eb94a5bba..0ffa767228 100644 --- a/org.idempiere.test/src/org/idempiere/test/model/InvoiceCustomerTest.java +++ b/org.idempiere.test/src/org/idempiere/test/model/InvoiceCustomerTest.java @@ -29,6 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; +import java.math.RoundingMode; import java.sql.Timestamp; import java.util.Calendar; import java.util.List; @@ -37,7 +38,9 @@ import java.util.logging.LogRecord; import org.compiere.model.MBPartner; import org.compiere.model.MDocType; import org.compiere.model.MInOut; +import org.compiere.model.MInOutConfirm; import org.compiere.model.MInOutLine; +import org.compiere.model.MInOutLineConfirm; import org.compiere.model.MInvoice; import org.compiere.model.MInvoiceLine; import org.compiere.model.MInvoiceTax; @@ -50,6 +53,7 @@ import org.compiere.model.MProduct; import org.compiere.model.MRMA; import org.compiere.model.MRMALine; import org.compiere.model.MTax; +import org.compiere.model.MWarehouse; import org.compiere.model.PO; import org.compiere.model.SystemIDs; import org.compiere.model.X_C_BP_Relation; @@ -251,6 +255,127 @@ public class InvoiceCustomerTest extends AbstractTestCase { assertEquals(1, line1.getQtyInvoiced().intValue()); } + @Test + public void testGenerateInvoiceManualForPartialConfirm() { + MDocType shipmentDocType = MDocType.get(DictionaryIDs.C_DocType.MM_SHIPMENT.id); + MDocType docType = new MDocType(Env.getCtx(), 0, null); + PO.copyValues(shipmentDocType, docType); + docType.setName(shipmentDocType.getName() + " " + System.currentTimeMillis()); + docType.setIsShipConfirm(true); + docType.saveEx(); + try { + MOrder order = new MOrder(Env.getCtx(), 0, getTrxName()); + order.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id)); + order.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard); + order.setDeliveryRule(MOrder.DELIVERYRULE_Availability); + order.setInvoiceRule(MOrder.INVOICERULE_AfterDelivery); + order.setDocStatus(DocAction.STATUS_Drafted); + order.setDocAction(DocAction.ACTION_Complete); + Timestamp today = TimeUtil.getDay(System.currentTimeMillis()); + order.setDateOrdered(today); + order.setDatePromised(today); + order.saveEx(); + + MOrderLine orderLine = new MOrderLine(order); + orderLine.setLine(10); + orderLine.setProduct(MProduct.get(Env.getCtx(), DictionaryIDs.M_Product.MULCH.id)); + BigDecimal orderQty = new BigDecimal("3"); + orderLine.setQty(orderQty); + orderLine.setDatePromised(today); + orderLine.saveEx(); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + order.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, order.getDocStatus()); + orderLine.load(getTrxName()); + assertEquals(orderQty.intValue(), orderLine.getQtyReserved().intValue()); + assertEquals(0, orderLine.getQtyInvoiced().intValue()); + + MInOut shipment = new MInOut(order, docType.get_ID(), today); // MM Shipment + shipment.saveEx(); + + MInOutLine shipmentLine = new MInOutLine(shipment); + shipmentLine.setC_OrderLine_ID(orderLine.get_ID()); + shipmentLine.setLine(orderLine.getLine()); + shipmentLine.setProduct(orderLine.getProduct()); + shipmentLine.setQty(orderLine.getQtyEntered()); + MWarehouse wh = MWarehouse.get(Env.getCtx(), shipment.getM_Warehouse_ID()); + int M_Locator_ID = wh.getDefaultLocator().getM_Locator_ID(); + shipmentLine.setM_Locator_ID(M_Locator_ID); + shipmentLine.saveEx(); + + // status should be in progress due to pending confirmation + info = MWorkflow.runDocumentActionWorkflow(shipment, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + shipment.load(getTrxName()); + assertEquals(DocAction.STATUS_InProgress, shipment.getDocStatus()); + + // complete confirmation + MInOutConfirm[] confirmations = shipment.getConfirmations(true); + assertEquals(1, confirmations.length, "Unexpected number of shipment confirmation records"); + MInOutLineConfirm[] confirmationLines = confirmations[0].getLines(true); + assertEquals(1, confirmationLines.length, "Unexpected number of shipment confirmation lines"); + BigDecimal confirmedQty = new BigDecimal("1"); + confirmationLines[0].setConfirmedQty(confirmedQty); + confirmationLines[0].saveEx(); + assertEquals(2, confirmationLines[0].getDifferenceQty().intValue(), "Unexpected confirmation line difference quantity"); + info = MWorkflow.runDocumentActionWorkflow(confirmations[0], DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + confirmations[0].load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, confirmations[0].getDocStatus()); + + //complete shipment after confirmation + shipment.load(getTrxName()); + info = MWorkflow.runDocumentActionWorkflow(shipment, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + shipment.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, shipment.getDocStatus()); + shipmentLine.load(getTrxName()); + assertEquals(confirmedQty.intValue(), shipmentLine.getMovementQty().intValue(), "Unexpected shipment line movement quantity"); + assertEquals(orderQty.intValue(), shipmentLine.getTargetQty().intValue(), "Unexpected shipment line target quantity"); + assertEquals(orderQty.intValue(), shipmentLine.getQtyEntered().intValue(), "Unexpected shipment line entered quantity"); + + // create invoice + int AD_Process_ID = SystemIDs.PROCESS_C_INVOICE_GENERATE_MANUAL; + MPInstance instance = new MPInstance(Env.getCtx(), AD_Process_ID, 0, 0, null); + instance.saveEx(); + String insert = "INSERT INTO T_SELECTION(AD_PINSTANCE_ID, T_SELECTION_ID) Values (?, ?)"; + DB.executeUpdateEx(insert, new Object[] {instance.getAD_PInstance_ID(), order.getC_Order_ID()}, null); + + //call process + ProcessInfo pi = new ProcessInfo ("InvoiceGenerateManual", AD_Process_ID); + pi.setAD_PInstance_ID (instance.getAD_PInstance_ID()); + + // Add Parameter - Selection=Y + MPInstancePara ip = new MPInstancePara(instance, 10); + ip.setParameter("Selection","Y"); + ip.saveEx(); + //Add Document action parameter + ip = new MPInstancePara(instance, 20); + ip.setParameter("DocAction", "CO"); + ip.saveEx(); + + ServerProcessCtl processCtl = new ServerProcessCtl(pi, getTrx()); + processCtl.setManagedTrxForJavaProcess(false); + processCtl.run(); + + assertFalse(pi.isError(), pi.getSummary()); + orderLine.load(getTrxName()); + assertEquals(1, orderLine.getQtyInvoiced().intValue()); + + MInvoiceLine invoiceLine = MInvoiceLine.getOfInOutLine(shipmentLine); + assertTrue(invoiceLine != null && invoiceLine.get_ID() > 0, "No invoice line created for shipment line"); + assertEquals(confirmedQty.intValue(), invoiceLine.getQtyEntered().intValue(), "Unexpected invoice line quantity entered"); + assertEquals(confirmedQty.intValue(), invoiceLine.getQtyInvoiced().intValue(), "Unexpected invoice line quantity invoiced"); + assertEquals(orderLine.getPriceActual().setScale(2, RoundingMode.HALF_UP), invoiceLine.getLineNetAmt().setScale(2, RoundingMode.HALF_UP), "Unexpected invoice line total amount"); + } finally { + rollback(); + if (docType.get_ID() > 0) + docType.deleteEx(true); + } + } + @Test public void testInvoiceGenerateRMAManual() { MOrder order = new MOrder(Env.getCtx(), 0, getTrxName()); diff --git a/org.idempiere.test/src/org/idempiere/test/model/MTaxTest.java b/org.idempiere.test/src/org/idempiere/test/model/MTaxTest.java index 1fb4095a12..bfca4adac1 100644 --- a/org.idempiere.test/src/org/idempiere/test/model/MTaxTest.java +++ b/org.idempiere.test/src/org/idempiere/test/model/MTaxTest.java @@ -31,6 +31,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.List; +import java.util.Properties; import org.adempiere.base.Core; import org.compiere.model.MAccount; @@ -44,6 +46,7 @@ import org.compiere.model.MInOut; import org.compiere.model.MInOutLine; import org.compiere.model.MInvoice; import org.compiere.model.MInvoiceLine; +import org.compiere.model.MLocation; import org.compiere.model.MMatchPO; import org.compiere.model.MOrder; import org.compiere.model.MOrderLine; @@ -51,11 +54,14 @@ import org.compiere.model.MPriceList; import org.compiere.model.MPriceListVersion; import org.compiere.model.MProduct; import org.compiere.model.MProductPrice; +import org.compiere.model.MSysConfig; import org.compiere.model.MTax; import org.compiere.model.MTaxCategory; import org.compiere.model.MWarehouse; import org.compiere.model.ProductCost; +import org.compiere.model.Query; import org.compiere.model.Tax; +import org.compiere.model.X_C_Order; import org.compiere.process.DocAction; import org.compiere.process.DocumentEngine; import org.compiere.process.ProcessInfo; @@ -66,12 +72,14 @@ import org.compiere.wf.MWorkflow; import org.idempiere.test.AbstractTestCase; import org.idempiere.test.DictionaryIDs; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; /** * * @author hengsin * */ +@Isolated public class MTaxTest extends AbstractTestCase { public MTaxTest() { @@ -95,26 +103,110 @@ public class MTaxTest extends AbstractTestCase { @Test public void testTaxLookup() { - int taxExemptId = Tax.getExemptTax(Env.getCtx(), getAD_Org_ID(), getTrxName()); + Properties ctx = Env.getCtx(); + String trxName = getTrxName(); + + int taxExemptId = Tax.getExemptTax(ctx, getAD_Org_ID(), trxName); assertTrue(taxExemptId>0, "Fail to get tax exempt Id"); - - MBPartner bp = new MBPartner(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id, getTrxName()); - bp.setIsTaxExempt(true); - bp.saveEx(); - - int id = Core.getTaxLookup().get(Env.getCtx(), DictionaryIDs.M_Product.AZALEA_BUSH.id, 0, getLoginDate(), getLoginDate(), getAD_Org_ID(), getM_Warehouse_ID(), - bp.getPrimaryC_BPartner_Location_ID(), bp.getPrimaryC_BPartner_Location_ID(), true, null, getTrxName()); - assertEquals(taxExemptId, id, "Unexpected tax id"); - - bp.setIsTaxExempt(false); - bp.saveEx(); - - id = Core.getTaxLookup().get(Env.getCtx(), DictionaryIDs.M_Product.AZALEA_BUSH.id, 0, getLoginDate(), getLoginDate(), getAD_Org_ID(), getM_Warehouse_ID(), - bp.getPrimaryC_BPartner_Location_ID(), bp.getPrimaryC_BPartner_Location_ID(), true, null, getTrxName()); - assertTrue(id != taxExemptId, "Unexpected tax id: " + id); - assertEquals(DictionaryIDs.C_Tax.STANDARD.id, id, "Unexpected tax id"); + + MBPartner bpTree = new MBPartner(ctx, DictionaryIDs.C_BPartner.TREE_FARM.id, trxName); + MBPartner bpPatio = new MBPartner(ctx, DictionaryIDs.C_BPartner.PATIO.id, trxName); + MBPartner bpCW = new MBPartner(ctx, DictionaryIDs.C_BPartner.C_AND_W.id, trxName); + MBPartner bpSeed = new MBPartner(ctx, DictionaryIDs.C_BPartner.SEED_FARM.id, trxName); + MBPartner bpJoe = new MBPartner(ctx, DictionaryIDs.C_BPartner.JOE_BLOCK.id, trxName); + bpJoe.setIsTaxExempt(true); + bpJoe.saveEx(); + + expectedTaxLookup(DictionaryIDs.M_Product.AZALEA_BUSH.id, 0, + getAD_Org_ID(), getM_Warehouse_ID(), + bpJoe.getPrimaryC_BPartner_Location_ID(), bpJoe.getPrimaryC_BPartner_Location_ID(), -1, + true, null, taxExemptId); + + bpJoe.setIsTaxExempt(false); + bpJoe.saveEx(); + + expectedTaxLookup(DictionaryIDs.M_Product.AZALEA_BUSH.id, 0, + getAD_Org_ID(), getM_Warehouse_ID(), + bpJoe.getPrimaryC_BPartner_Location_ID(), bpJoe.getPrimaryC_BPartner_Location_ID(), -1, + true, null, DictionaryIDs.C_Tax.STANDARD.id); + + // Sales charge from Germany (Org Fertilizer) to USA (Joe Block) -> expected Exempt + expectedTaxLookup(0, DictionaryIDs.C_Charge.FREIGHT.id, + DictionaryIDs.AD_Org.FERTILIZER.id, DictionaryIDs.M_Warehouse.FERTILIZER.id, + bpJoe.getPrimaryC_BPartner_Location_ID(), bpJoe.getPrimaryC_BPartner_Location_ID(), -1, + true, null, taxExemptId); + + // Purchase charge from USA New York (Patio BP) to USA Oregon (HQ Warehouse) -> expected Standard + expectedTaxLookup(0, DictionaryIDs.C_Charge.FREIGHT.id, + DictionaryIDs.AD_Org.HQ.id, DictionaryIDs.M_Warehouse.HQ.id, + bpPatio.getPrimaryC_BPartner_Location_ID(), bpPatio.getPrimaryC_BPartner_Location_ID(), -1, + false, null, DictionaryIDs.C_Tax.STANDARD.id); + + // Change location of Org HQ to Connecticut CT + MLocation location = new MLocation(ctx, DictionaryIDs.C_Location.ORG_WH_HQ.id, trxName); + location.setC_Region_ID(DictionaryIDs.C_Region.CT.id); + location.saveEx(); + // Sales charge from USA Connecticut (Org HQ modified) to USA Connecticut (C&W) -> expected CT Sales + expectedTaxLookup(0, DictionaryIDs.C_Charge.FREIGHT.id, + DictionaryIDs.AD_Org.HQ.id, DictionaryIDs.M_Warehouse.HQ.id, + bpCW.getPrimaryC_BPartner_Location_ID(), bpCW.getPrimaryC_BPartner_Location_ID(), -1, + true, null, DictionaryIDs.C_Tax.CT_SALES.id); + + // Sales product from USA Connecticut (Org HQ modified) to USA New Jersey (TreeFarm) -> expected Standard + expectedTaxLookup(DictionaryIDs.M_Product.AZALEA_BUSH.id, 0, + DictionaryIDs.AD_Org.HQ.id, DictionaryIDs.M_Warehouse.HQ.id, + bpTree.getPrimaryC_BPartner_Location_ID(), bpTree.getPrimaryC_BPartner_Location_ID(), -1, + true, null, DictionaryIDs.C_Tax.STANDARD.id); + + // Sales product from USA Connecticut (Org HQ modified) to USA New Jersey (TreeFarm) - pick rule -> expected CT Sales + expectedTaxLookup(DictionaryIDs.M_Product.AZALEA_BUSH.id, 0, + DictionaryIDs.AD_Org.HQ.id, DictionaryIDs.M_Warehouse.HQ.id, + bpTree.getPrimaryC_BPartner_Location_ID(), bpTree.getPrimaryC_BPartner_Location_ID(), -1, + true, X_C_Order.DELIVERYVIARULE_Pickup, DictionaryIDs.C_Tax.CT_SALES.id); + + // Purchase product from USA New York (Patio BP) to Germany (HQ Fertilizer) -> expected Exempt + expectedTaxLookup(DictionaryIDs.M_Product.AZALEA_BUSH.id, 0, + DictionaryIDs.AD_Org.FERTILIZER.id, DictionaryIDs.M_Warehouse.FERTILIZER.id, + bpPatio.getPrimaryC_BPartner_Location_ID(), bpPatio.getPrimaryC_BPartner_Location_ID(), -1, + false, null, taxExemptId); + + // Purchase product from USA Connecticut (Seed Farm BP) to Germany (HQ Fertilizer) drop ship to BP C&W -> expected Exempt (core) + expectedTaxLookup(DictionaryIDs.M_Product.AZALEA_BUSH.id, 0, + DictionaryIDs.AD_Org.FERTILIZER.id, DictionaryIDs.M_Warehouse.FERTILIZER.id, + bpSeed.getPrimaryC_BPartner_Location_ID(), bpSeed.getPrimaryC_BPartner_Location_ID(), bpCW.getPrimaryC_BPartner_Location_ID(), + false, null, taxExemptId); + + MSysConfig sysCfgTaxLookup = new MSysConfig(ctx, DictionaryIDs.AD_SysConfig.TAX_LOOKUP_SERVICE.id, null); + String oldValue = sysCfgTaxLookup.getValue(); + try { + sysCfgTaxLookup.setValue(MTaxTest_TaxLookup.class.getName()); + sysCfgTaxLookup.saveCrossTenantSafeEx(); + CacheMgt.get().reset(MSysConfig.Table_Name); + + // Drop Ship Case with custom tax lookup + // Purchase product from USA Connecticut (Seed Farm BP) to Germany (HQ Fertilizer) drop ship to BP C&W -> expected CT Sales (custom tax lookup) + expectedTaxLookup(DictionaryIDs.M_Product.AZALEA_BUSH.id, 0, + DictionaryIDs.AD_Org.FERTILIZER.id, DictionaryIDs.M_Warehouse.FERTILIZER.id, + bpSeed.getPrimaryC_BPartner_Location_ID(), bpSeed.getPrimaryC_BPartner_Location_ID(), bpCW.getPrimaryC_BPartner_Location_ID(), + false, null, DictionaryIDs.C_Tax.CT_SALES.id); + } finally { + sysCfgTaxLookup.setValue(oldValue); + sysCfgTaxLookup.saveCrossTenantSafeEx(); + } + } - + + private void expectedTaxLookup(int prodId, int chargeId, int orgId, int warehouseId, int billLocationId, int shipLocationId, int dropshipLocation, boolean isSOTrx, String deliveryViaRule, int expectedTaxId) { + Properties ctx = Env.getCtx(); + String trxName = getTrxName(); + + int id = Core.getTaxLookup().get(ctx, prodId, chargeId, getLoginDate(), getLoginDate(), orgId, warehouseId, + billLocationId, shipLocationId, + dropshipLocation, + isSOTrx, deliveryViaRule, trxName); + assertEquals(expectedTaxId, id, "Unexpected tax id"); + } + @Test public void testDistributeTaxToProductCost() { MProduct product = null; @@ -235,13 +327,10 @@ public class MTaxTest extends AbstractTestCase { assertEquals(expectedCost, averageCost, "Un-expected average cost"); MAccount acctAsset = productCost.getAccount(ProductCost.ACCTTYPE_P_Asset, schema); - String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MInOut.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + receipt.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + schema.getC_AcctSchema_ID(); - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); + Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, receipt.get_ID(), schema.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); BigDecimal totalDebit = new BigDecimal("0.00"); - for(int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); + for(MFactAcct fa : factAccts) { if (fa.getAccount_ID() == acctAsset.getAccount_ID()) { totalDebit = totalDebit.add(fa.getAmtAcctDr()); } @@ -380,13 +469,10 @@ public class MTaxTest extends AbstractTestCase { assertEquals(expectedCost, averageCost, "Un-expected average cost"); MAccount acctAsset = productCost.getAccount(ProductCost.ACCTTYPE_P_Asset, schema); - String whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MInOut.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + receipt.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + schema.getC_AcctSchema_ID(); - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); + Query query = MFactAcct.createRecordIdQuery(MInOut.Table_ID, receipt.get_ID(), schema.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); BigDecimal totalDebit = new BigDecimal("0.00"); - for(int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); + for(MFactAcct fa : factAccts) { if (fa.getAccount_ID() == acctAsset.getAccount_ID()) { totalDebit = totalDebit.add(fa.getAmtAcctDr()); } @@ -402,4 +488,5 @@ public class MTaxTest extends AbstractTestCase { category.deleteEx(true); } } + } diff --git a/org.idempiere.test/src/org/idempiere/test/model/MTaxTest_TaxLookup.java b/org.idempiere.test/src/org/idempiere/test/model/MTaxTest_TaxLookup.java new file mode 100644 index 0000000000..b9af633822 --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/model/MTaxTest_TaxLookup.java @@ -0,0 +1,53 @@ +/*********************************************************************** + * This file is part of iDempiere ERP Open Source * + * http://www.idempiere.org * + * * + * Copyright (C) Contributors * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301, USA. * + * * + * Contributors: * + * - Diego Ruiz - BX Service * + **********************************************************************/ +package org.idempiere.test.model; + +import java.sql.Timestamp; +import java.util.Properties; + +import org.adempiere.base.DefaultTaxLookup; +import org.adempiere.base.ITaxLookup; +import org.osgi.service.component.annotations.Component; + +// test case copied from bxservice/de.bxservice.europeantaxprovider plugin +@Component(immediate = true, service = {ITaxLookup.class}) +public class MTaxTest_TaxLookup extends DefaultTaxLookup { + + @Override + public int get(Properties ctx, int C_TaxCategory_ID, boolean IsSOTrx, Timestamp shipDate, int shipFromC_Location_ID, + int shipToC_Location_ID, int dropshipToC_Location_ID, Timestamp billDate, int billFromC_Location_ID, int billToC_Location_ID, + String trxName) { + if (IsSOTrx) { + billToC_Location_ID = shipToC_Location_ID; + } else { + if (dropshipToC_Location_ID > 0 && dropshipToC_Location_ID != shipToC_Location_ID) + billToC_Location_ID = dropshipToC_Location_ID; + billFromC_Location_ID = shipFromC_Location_ID; + } + + return super.get(ctx, C_TaxCategory_ID, IsSOTrx, shipDate, shipFromC_Location_ID, shipToC_Location_ID, billDate, billFromC_Location_ID, billToC_Location_ID, trxName); + } + +} diff --git a/org.idempiere.test/src/org/idempiere/test/model/ProductionTestIsolated.java b/org.idempiere.test/src/org/idempiere/test/model/ProductionTestIsolated.java index b00751b05d..81fa73832f 100644 --- a/org.idempiere.test/src/org/idempiere/test/model/ProductionTestIsolated.java +++ b/org.idempiere.test/src/org/idempiere/test/model/ProductionTestIsolated.java @@ -33,6 +33,7 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.sql.Timestamp; import java.util.List; +import java.util.Optional; import org.compiere.model.MAccount; import org.compiere.model.MAcctSchema; @@ -312,17 +313,12 @@ public class ProductionTestIsolated extends AbstractTestCase { ProductCost pc = new ProductCost (Env.getCtx(), mulchX.getM_Product_ID(), 0, getTrxName()); MAccount acctVariance = pc.getAccount(ProductCost.ACCTTYPE_P_RateVariance, as); - whereClause = MFactAcct.COLUMNNAME_AD_Table_ID + "=" + MProduction.Table_ID - + " AND " + MFactAcct.COLUMNNAME_Record_ID + "=" + production.get_ID() - + " AND " + MFactAcct.COLUMNNAME_C_AcctSchema_ID + "=" + as.getC_AcctSchema_ID() - + " AND " + MFactAcct.COLUMNNAME_Account_ID + "=" + acctVariance.getAccount_ID(); - int[] ids = MFactAcct.getAllIDs(MFactAcct.Table_Name, whereClause, getTrxName()); + Query query = MFactAcct.createRecordIdQuery(MProduction.Table_ID, production.get_ID(), as.getC_AcctSchema_ID(), getTrxName()); + List factAccts = query.list(); BigDecimal variance = BigDecimal.ZERO; - for (int id : ids) { - MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); - variance = fa.getAmtAcctDr().subtract(fa.getAmtAcctCr()); - break; - } + Optional optional = factAccts.stream().filter(e -> e.getAccount_ID() == acctVariance.getAccount_ID()).findFirst(); + if (optional.isPresent()) + variance = optional.get().getAmtAcctDr().subtract(optional.get().getAmtAcctCr()); BigDecimal varianceExpected = componentCost.subtract(endProductCost).setScale(as.getStdPrecision(), RoundingMode.HALF_UP); assertEquals(varianceExpected.setScale(2, RoundingMode.HALF_UP), variance.setScale(2, RoundingMode.HALF_UP), "Variance not posted correctly."); } finally { diff --git a/org.idempiere.ui.sso.oidc/src/org/idempiere/ui/sso/oidc/service/OIDCPrincipalService.java b/org.idempiere.ui.sso.oidc/src/org/idempiere/ui/sso/oidc/service/OIDCPrincipalService.java index e2e3704df2..9d12aaa68e 100644 --- a/org.idempiere.ui.sso.oidc/src/org/idempiere/ui/sso/oidc/service/OIDCPrincipalService.java +++ b/org.idempiere.ui.sso.oidc/src/org/idempiere/ui/sso/oidc/service/OIDCPrincipalService.java @@ -133,11 +133,13 @@ public class OIDCPrincipalService implements ISSOPrincipalService { // Check the returned state parameter, must match the original State state = (State) request.getSession().getAttribute(OIDC_STATE); - if (!state.equals(authResponse.getState())) { + if (state != null && !state.equals(authResponse.getState())) { // Unexpected or tampered response, stop!!! request.getSession().removeAttribute(OIDC_STATE); response.sendRedirect(getRedirectURL(principalConfig, redirectMode)); return; + } else if (state == null) { + request.getSession().setAttribute(OIDC_STATE, authResponse.getState()); } if (!authResponse.indicatesSuccess()) { @@ -276,7 +278,6 @@ public class OIDCPrincipalService implements ISSOPrincipalService { throws IOException { AuthenticationRequest authRequest = null; try { - String url = getMetaData().getAuthorizationEndpointURI().toString(); authRequest = new AuthenticationRequest.Builder( new ResponseType(AUTHENTICATION_CODE_PARAMETER), new Scope("openid", "profile", "email"), @@ -285,7 +286,7 @@ public class OIDCPrincipalService implements ISSOPrincipalService { .state(new State()) .nonce(new Nonce()) .prompt(new Prompt(Prompt.Type.LOGIN)) - .endpointURI(new URI(url)) + .endpointURI(getMetaData().getAuthorizationEndpointURI()) .build(); } catch (URISyntaxException e) { throw new RuntimeException(e); @@ -297,7 +298,7 @@ public class OIDCPrincipalService implements ISSOPrincipalService { request.getSession().setAttribute(OIDC_STATE, authRequest.getState()); URI redirectURI = authRequest.toURI(); - response.sendRedirect(redirectURI.toURL().toString()); + response.sendRedirect(redirectURI.toString()); } @Override @@ -305,4 +306,42 @@ public class OIDCPrincipalService implements ISSOPrincipalService { httpRequest.getSession().removeAttribute(ISSOPrincipalService.SSO_PRINCIPAL_SESSION_TOKEN); httpRequest.getSession().removeAttribute(OIDC_STATE); } + + @Override + public String getLogoutURL() { + if (metaData != null) { + if (metaData.getEndSessionEndpointURI() != null) { + + StringBuilder url = new StringBuilder(metaData.getEndSessionEndpointURI().toString()); + url.append("?response_type=code") + .append("&client_id=") + .append(principalConfig.getSSO_ApplicationClientID()); + //redirect url: the oidc spec say post_logout_redirect_uri but amazon cognito is using redirect_uri + if (url.indexOf("amazonaws.com") >= 0 || url.indexOf("amazoncognito.com") >= 0) + url.append("&redirect_uri=").append(principalConfig.getSSO_ApplicationRedirectURIs()); + else + url.append("&post_logout_redirect_uri=").append(principalConfig.getSSO_ApplicationRedirectURIs()); + return url.toString(); + } else { + //For provider that doesn’t support end_session_endpoint (for e.g Google Identity), + //We fall back to authorization_endpoint with SELECT_ACCOUNT prompt type + try { + AuthenticationRequest authRequest = new AuthenticationRequest.Builder( + new ResponseType(AUTHENTICATION_CODE_PARAMETER), + new Scope("openid", "profile", "email"), + new ClientID(principalConfig.getSSO_ApplicationClientID()), + new URI(getRedirectURL(principalConfig, SSOUtils.SSO_MODE_WEBUI))) + .prompt(new Prompt(Prompt.Type.SELECT_ACCOUNT)) + .endpointURI(metaData.getAuthorizationEndpointURI()) + .state(new State()) + .build(); + return authRequest.toURI().toString(); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + } + } + + return null; + } } diff --git a/org.idempiere.zk.billboard/src/metainfo/zk/lang-addon.xml b/org.idempiere.zk.billboard/src/metainfo/zk/lang-addon.xml index f75d108997..521d0c8666 100644 --- a/org.idempiere.zk.billboard/src/metainfo/zk/lang-addon.xml +++ b/org.idempiere.zk.billboard/src/metainfo/zk/lang-addon.xml @@ -6,7 +6,7 @@ xul/html org.idempiere.zk.billboard.Version - 3.10.3.20231118 + 3.10.3.20240411 billboard @@ -19,7 +19,7 @@ - + - + diff --git a/org.idempiere.zk.billboard/src/org/idempiere/zk/billboard/Version.java b/org.idempiere.zk.billboard/src/org/idempiere/zk/billboard/Version.java index 265c5d6bae..5d658e628f 100644 --- a/org.idempiere.zk.billboard/src/org/idempiere/zk/billboard/Version.java +++ b/org.idempiere.zk.billboard/src/org/idempiere/zk/billboard/Version.java @@ -34,5 +34,5 @@ public class Version { * Returns the version UID.
* Must match with version-uid value in lang-addon.xml */ - public static final String UID = "3.10.3.20231118"; + public static final String UID = "3.10.3.20240411"; } diff --git a/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.gauge.js b/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.gauge.js index 1e31e4e53f..8e9d09ce5a 100644 --- a/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.gauge.js +++ b/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.gauge.js @@ -13,7 +13,7 @@ billboard.GaugeRenderer = class { if (rendererOptions) { if (rendererOptions["showNeedle"] && rendererOptions["showNeedle"] == true) { showNeedle = true; - gauge.title = "\n{=NEEDLE_VALUE}%"; + gauge.title = "\n"+wgt.getSeriesData()[1]+"%"; gauge.width = 20; gauge.label = { format: function(_value, _ratio, id) { return id; } @@ -69,6 +69,11 @@ billboard.GaugeRenderer = class { value: wgt.getSeriesData()[1][0] } }; + if (rendererOptions["intervals"]) { + if (model.arc.needle.value > rendererOptions["intervals"][rendererOptions["intervals"].length-1]) { + model.arc.needle.value = rendererOptions["intervals"][rendererOptions["intervals"].length-1]+3; + } + } model.interaction = { enabled: false }; diff --git a/org.idempiere.zk.datatable/README.md b/org.idempiere.zk.datatable/README.md new file mode 100644 index 0000000000..f06e96a0a1 --- /dev/null +++ b/org.idempiere.zk.datatable/README.md @@ -0,0 +1,15 @@ +* Steps to get datatables css and js from https://datatables.net/download/index + * Step 1. Choose a styling framework + * Datatables + * Step 2. Select packages + * jQuery3 + * DataTables + * Extensions + * Buttons > Column visibility + * ColReorder + * DateTime + * FixedColumns + * Responsive + * RowGroup + * Step 3. Pick a download method + * Download > Download files diff --git a/org.idempiere.zk.datatable/src/metainfo/zk/lang-addon.xml b/org.idempiere.zk.datatable/src/metainfo/zk/lang-addon.xml index 6eb17fa654..d4bf0b341e 100644 --- a/org.idempiere.zk.datatable/src/metainfo/zk/lang-addon.xml +++ b/org.idempiere.zk.datatable/src/metainfo/zk/lang-addon.xml @@ -3,5 +3,5 @@ datatables xul/html - + diff --git a/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DataTableOptions.java b/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DataTableOptions.java index 6d55b204fa..12ca0f9e99 100644 --- a/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DataTableOptions.java +++ b/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DataTableOptions.java @@ -32,6 +32,7 @@ import org.compiere.util.Util; import org.idempiere.zk.datatable.DatatableReportRenderer.FunctionTypes; import org.json.JSONArray; import org.json.JSONString; +import org.zkoss.zk.ui.Executions; public class DataTableOptions { @@ -68,30 +69,54 @@ public class DataTableOptions { /** * Get datatables settings - * @param path * @return json datatables settings */ - public String getDataTableOptions(String path) { + public String getDataTableOptions() { + String localePath = geti18nURL(); + String i18nPath = localePath != null && Executions.getCurrent() != null ? Executions.encodeURL(localePath) : null; StringBuilder dataOptions = new StringBuilder(); dataOptions.append(" { "); dataOptions.append(" pageLength: ").append(250); dataOptions.append(", lengthMenu: [ [250, 500, 1000, -1], [250, 500, 1000,\"").append(Msg.getMsg( Language.getAD_Language(locale), "All")).append("\" ] ] "); - dataOptions.append(", colReorder: ").append(true); - dataOptions.append(", responsive: ").append(true); - dataOptions.append(", ordering: ").append(true); - dataOptions.append(", search: { return: ").append(true).append(" }"); - dataOptions.append(", language: { url: '").append(path).append("DataTables/i18n/").append(this.locale).append(".json' }"); - dataOptions.append(", dom: \"lfrtip\" "); + dataOptions.append(", colReorder: true"); + dataOptions.append(", responsive: false"); + dataOptions.append(", ordering: true"); + if (i18nPath != null) + dataOptions.append(", language: { url: '").append(i18nPath).append("' }"); + dataOptions.append(", layout: {topStart:['buttons'], topEnd:['pageLength'], bottomStart: ['info'], bottomEnd: ['paging']} "); + dataOptions.append(""" + , buttons: [{extend: 'colvis', collectionLayout: 'fixed columns'}, + { text: 'Responsive', + action: function ( e, dt, node, config ) { + let option = dt.init(); + option.responsive = !option.responsive; + option.buttons[1].text = option.responsive ? 'Responsive ✓' : 'Responsive'; + dt.destroy(); + $('#JS_DataTable').DataTable(option); + } + }]"""); - dataOptions.append(", initComplete:").append(" function () " - + "{ this.api().columns().every( function () " - + "{ var that = this; $('input', this.footer()).on('keyup change clear', function () " - + "{ if (that.search() !== this.value) " - + "{ that.search(this.value).draw();}});});}"); + dataOptions.append(", initComplete:").append(""" + function () { + let tbl = this; + this.api().columns().every(function () { + let that = this; + let selector = 'th[data-dt-column="' + this.index() + '"]'; + let headerCell = tbl.find(selector); + if (headerCell.length) { + let input = headerCell.find('input'); + input.on('keyup change clear', + function () { + if (that.search() !== this.value) { + that.search(this.value).draw(); + } + }); + } + }); + }"""); String orderBy = getOrderBy(); - if(orderBy != null) dataOptions.append(", order: ").append(orderBy); @@ -116,6 +141,23 @@ public class DataTableOptions { return dataOptions.toString(); } + + /** + * Get i18n json URL for datatables + * @return i18n json URL + */ + public String geti18nURL() { + String localePath = "~./js/datatables/i18n/"+this.locale+".json"; + if (getClass().getResource("/web"+localePath.substring(2)) == null) { + if (this.locale.contains("-")) { + localePath = "~./js/datatables/i18n/"+this.locale.substring(0, this.locale.indexOf("-")) +".json"; + if (getClass().getResource("/web"+localePath.substring(2)) == null) { + localePath = null; + } + } + } + return localePath; + } /** * Get group rendering function diff --git a/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DatatableReportRenderer.java b/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DatatableReportRenderer.java index 89e83abc40..f1294cdb1e 100644 --- a/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DatatableReportRenderer.java +++ b/org.idempiere.zk.datatable/src/org/idempiere/zk/datatable/DatatableReportRenderer.java @@ -47,6 +47,7 @@ import java.util.logging.Level; import org.adempiere.exceptions.AdempiereException; import org.apache.ecs.XhtmlDocument; import org.apache.ecs.xhtml.a; +import org.apache.ecs.xhtml.input; import org.apache.ecs.xhtml.link; import org.apache.ecs.xhtml.script; import org.apache.ecs.xhtml.span; @@ -173,7 +174,7 @@ public class DatatableReportRenderer implements IReportRenderer> mapCssInfo = new HashMap<>(); try { - DataTableOptions dataTableOptions = new DataTableOptions(language.getLanguageCode()); + DataTableOptions dataTableOptions = new DataTableOptions(language.getLocale().toLanguageTag()); //collect column to print List columns = new ArrayList<>(); List asiElements = new ArrayList<>(); @@ -280,14 +281,10 @@ public class DatatableReportRenderer implements IReportRenderer"); - doc.appendHead(""); - if (extension != null && !isExport){ extension.setWebAttribute(doc.getBody()); } @@ -401,27 +398,8 @@ public class DatatableReportRenderer implements IReportRenderer"); - tr tfoot = new tr(); - - for (int col = 0; col < printFormat.getItemCount(); col++) - { - MPrintFormatItem item = printFormat.getItem(col); - if (item.isPrinted()) - { - var printName = item.getPrintName(language); - if (!Util.isEmpty(printName)) - { - th th = new th(); - tfoot.addElement(th); - th.setTagText(printName); - } - } - } - tfoot.output(w); - w.print(""); - thead thead = new thead(); + thead.setClass("sticky"); tbody tbody = new tbody(); tbody.setNeedClosingTag(false); @@ -434,10 +412,35 @@ public class DatatableReportRenderer implements IReportRenderer suppressMap = new HashMap<>(); + //search input at header + { + tr tr = new tr(); + for (int col = 0; col < printFormat.getItemCount(); col++) + { + MPrintFormatItem item = printFormat.getItem(col); + if (item.isPrinted()) + { + var printName = item.getPrintName(language); + + th th = new th(); + th.addAttribute("data-dt-order", "disable"); + tr.addElement(th); + input searchInput = new input(); + + if (!Util.isEmpty(printName)) + searchInput.addAttribute("placeholder", "Search "+printName); + + th.addElement(searchInput); + + } + } + thead.addElement(tr); + } + // for all rows (-1 = header row) for (int row = -1; row < printData.getRowCount(); row++) { - tr tr = new tr(); + tr tr = new tr(); if (row != -1) { printData.setRowIndex(row); @@ -687,7 +690,7 @@ public class DatatableReportRenderer implements IReportRenderer"); + w.print(""); w.print(""); if (suppressMap.size() > 0) { @@ -707,7 +710,7 @@ public class DatatableReportRenderer implements IReportRenderer"); w.print(""); - String dataTableOptionString = dataTableOptions.getDataTableOptions("~./web/js"); + String dataTableOptionString = dataTableOptions.getDataTableOptions(); if( dataTableOptionString != null ) { w.print(""); @@ -782,8 +785,7 @@ public class DatatableReportRenderer implements IReportRenderer urls = Arrays.asList("~./js/datatables/jquery.min.js","~./js/datatables/jquery.floatThead.min.js" - ,"~./js/datatables/datatables.js","~./js/datatables/jquery.dataTables.min.js","~./js/datatables/dataTables.rowGroup.min.js"); + List urls = Arrays.asList("~./js/datatables/datatables.min.js"); if (isExport){ // embed script by content for (String extraScriptPath : urls){ @@ -802,11 +804,12 @@ public class DatatableReportRenderer implements IReportRenderer urls = Arrays.asList("~./js/datatables/datatables.css"); + private void appendStyles (XhtmlDocument doc, boolean isExport, String i18nURL) throws IOException, URISyntaxException{ + List urls = Arrays.asList("~./js/datatables/datatables.min.css", "~./js/datatables/customization.css"); if (isExport){ // embed css by content for (String extraStylePath : urls){ @@ -817,6 +820,13 @@ public class DatatableReportRenderer implements IReportRenderer").append(b("").attr("colspan",this._colspan()).attr("scope","row").append(a))).addClass(this.c.className).addClass(f).addClass("dtrg-level-"+h)}});k.defaults={className:"dtrg-group",dataSrc:0,emptyDataGroup:"No group",enable:!0,endClassName:"dtrg-end",endRender:null,startClassName:"dtrg-start",startRender:function(a,f){return f}};k.version="1.2.0";b.fn.dataTable.RowGroup= -k;b.fn.DataTable.RowGroup=k;e.Api.register("rowGroup()",function(){return this});e.Api.register("rowGroup().disable()",function(){return this.iterator("table",function(a){a.rowGroup&&a.rowGroup.enable(!1)})});e.Api.register("rowGroup().enable()",function(a){return this.iterator("table",function(f){f.rowGroup&&f.rowGroup.enable(a===g?!0:a)})});e.Api.register("rowGroup().enabled()",function(){var a=this.context;return a.length&&a[0].rowGroup?a[0].rowGroup.enabled():!1});e.Api.register("rowGroup().dataSrc()", -function(a){return a===g?this.context[0].rowGroup.dataSrc():this.iterator("table",function(f){f.rowGroup&&f.rowGroup.dataSrc(a)})});b(d).on("preInit.dt.dtrg",function(a,f,h){"dt"===a.namespace&&(a=f.oInit.rowGroup,h=e.defaults.rowGroup,a||h)&&(h=b.extend({},h,a),!1!==a&&new k(f,h))});return k}); diff --git a/org.idempiere.zk.datatable/src/web/js/datatables/datatables.css b/org.idempiere.zk.datatable/src/web/js/datatables/datatables.css index 401040caee..3fc9c3b75b 100644 --- a/org.idempiere.zk.datatable/src/web/js/datatables/datatables.css +++ b/org.idempiere.zk.datatable/src/web/js/datatables/datatables.css @@ -4,93 +4,150 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#dt/dt-1.12.1/b-2.2.3/b-colvis-2.2.3/cr-1.5.6/fh-3.2.4/sp-2.0.2 + * https://datatables.net/download/#dt/jq-3.7.0/dt-2.0.8/b-3.0.2/b-colvis-3.0.2/cr-2.0.3/date-1.5.2/fc-5.0.1/r-3.0.2/rg-1.5.0 * * Included libraries: - * DataTables 1.12.1, Buttons 2.2.3, Column visibility 2.2.3, ColReorder 1.5.6, FixedHeader 3.2.4, SearchPanes 2.0.2 + * jQuery 3 3.7.0, DataTables 2.0.8, Buttons 3.0.2, Column visibility 3.0.2, ColReorder 2.0.3, DateTime 1.5.2, FixedColumns 5.0.1, Responsive 3.0.2, RowGroup 1.5.0 */ @charset "UTF-8"; +:root { + --dt-row-selected: 13, 110, 253; + --dt-row-selected-text: 255, 255, 255; + --dt-row-selected-link: 9, 10, 11; + --dt-row-stripe: 0, 0, 0; + --dt-row-hover: 0, 0, 0; + --dt-column-ordering: 0, 0, 0; + --dt-html-background: white; +} +:root.dark { + --dt-html-background: rgb(33, 37, 41); +} + table.dataTable td.dt-control { text-align: center; cursor: pointer; } table.dataTable td.dt-control:before { - height: 1em; - width: 1em; - margin-top: -9px; display: inline-block; - color: white; - border: 0.15em solid white; - border-radius: 1em; - box-shadow: 0 0 0.2em #444; - box-sizing: content-box; - text-align: center; - text-indent: 0 !important; - font-family: "Courier New", Courier, monospace; - line-height: 1em; - content: "+"; - background-color: #31b131; + box-sizing: border-box; + content: ""; + border-top: 5px solid transparent; + border-left: 10px solid rgba(0, 0, 0, 0.5); + border-bottom: 5px solid transparent; + border-right: 0px solid transparent; } table.dataTable tr.dt-hasChild td.dt-control:before { - content: "-"; - background-color: #d33333; + border-top: 10px solid rgba(0, 0, 0, 0.5); + border-left: 5px solid transparent; + border-bottom: 0px solid transparent; + border-right: 5px solid transparent; } -table.dataTable thead > tr > th.sorting, table.dataTable thead > tr > th.sorting_asc, table.dataTable thead > tr > th.sorting_desc, table.dataTable thead > tr > th.sorting_asc_disabled, table.dataTable thead > tr > th.sorting_desc_disabled, -table.dataTable thead > tr > td.sorting, -table.dataTable thead > tr > td.sorting_asc, -table.dataTable thead > tr > td.sorting_desc, -table.dataTable thead > tr > td.sorting_asc_disabled, -table.dataTable thead > tr > td.sorting_desc_disabled { - cursor: pointer; - position: relative; - padding-right: 26px; +html.dark table.dataTable td.dt-control:before, +:root[data-bs-theme=dark] table.dataTable td.dt-control:before { + border-left-color: rgba(255, 255, 255, 0.5); } -table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:after, -table.dataTable thead > tr > td.sorting:before, -table.dataTable thead > tr > td.sorting:after, -table.dataTable thead > tr > td.sorting_asc:before, -table.dataTable thead > tr > td.sorting_asc:after, -table.dataTable thead > tr > td.sorting_desc:before, -table.dataTable thead > tr > td.sorting_desc:after, -table.dataTable thead > tr > td.sorting_asc_disabled:before, -table.dataTable thead > tr > td.sorting_asc_disabled:after, -table.dataTable thead > tr > td.sorting_desc_disabled:before, -table.dataTable thead > tr > td.sorting_desc_disabled:after { +html.dark table.dataTable tr.dt-hasChild td.dt-control:before, +:root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before { + border-top-color: rgba(255, 255, 255, 0.5); + border-left-color: transparent; +} + +div.dt-scroll-body thead tr, +div.dt-scroll-body tfoot tr { + height: 0; +} +div.dt-scroll-body thead tr th, div.dt-scroll-body thead tr td, +div.dt-scroll-body tfoot tr th, +div.dt-scroll-body tfoot tr td { + height: 0 !important; + padding-top: 0px !important; + padding-bottom: 0px !important; + border-top-width: 0px !important; + border-bottom-width: 0px !important; +} +div.dt-scroll-body thead tr th div.dt-scroll-sizing, div.dt-scroll-body thead tr td div.dt-scroll-sizing, +div.dt-scroll-body tfoot tr th div.dt-scroll-sizing, +div.dt-scroll-body tfoot tr td div.dt-scroll-sizing { + height: 0 !important; + overflow: hidden !important; +} + +table.dataTable thead > tr > th:active, +table.dataTable thead > tr > td:active { + outline: none; +} +table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before { position: absolute; display: block; - opacity: 0.125; - right: 10px; - line-height: 9px; - font-size: 0.9em; -} -table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:before, -table.dataTable thead > tr > td.sorting:before, -table.dataTable thead > tr > td.sorting_asc:before, -table.dataTable thead > tr > td.sorting_desc:before, -table.dataTable thead > tr > td.sorting_asc_disabled:before, -table.dataTable thead > tr > td.sorting_desc_disabled:before { bottom: 50%; - content: "▴"; + content: "▲"; + content: "▲"/""; } -table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:after, -table.dataTable thead > tr > td.sorting:after, -table.dataTable thead > tr > td.sorting_asc:after, -table.dataTable thead > tr > td.sorting_desc:after, -table.dataTable thead > tr > td.sorting_asc_disabled:after, -table.dataTable thead > tr > td.sorting_desc_disabled:after { +table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { + position: absolute; + display: block; top: 50%; - content: "▾"; + content: "▼"; + content: "▼"/""; } -table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:after, -table.dataTable thead > tr > td.sorting_asc:before, -table.dataTable thead > tr > td.sorting_desc:after { +table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, table.dataTable thead > tr > th.dt-ordering-asc, table.dataTable thead > tr > th.dt-ordering-desc, +table.dataTable thead > tr > td.dt-orderable-asc, +table.dataTable thead > tr > td.dt-orderable-desc, +table.dataTable thead > tr > td.dt-ordering-asc, +table.dataTable thead > tr > td.dt-ordering-desc { + position: relative; + padding-right: 30px; +} +table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order, +table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order, +table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order { + position: absolute; + right: 12px; + top: 0; + bottom: 0; + width: 12px; +} +table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { + left: 0; + opacity: 0.125; + line-height: 9px; + font-size: 0.8em; +} +table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, +table.dataTable thead > tr > td.dt-orderable-asc, +table.dataTable thead > tr > td.dt-orderable-desc { + cursor: pointer; +} +table.dataTable thead > tr > th.dt-orderable-asc:hover, table.dataTable thead > tr > th.dt-orderable-desc:hover, +table.dataTable thead > tr > td.dt-orderable-asc:hover, +table.dataTable thead > tr > td.dt-orderable-desc:hover { + outline: 2px solid rgba(0, 0, 0, 0.05); + outline-offset: -2px; +} +table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after, +table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before, +table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after { opacity: 0.6; } -table.dataTable thead > tr > th.sorting_desc_disabled:after, table.dataTable thead > tr > th.sorting_asc_disabled:before, -table.dataTable thead > tr > td.sorting_desc_disabled:after, -table.dataTable thead > tr > td.sorting_asc_disabled:before { +table.dataTable thead > tr > th.sorting_desc_disabled span.dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled span.dt-column-order:before, +table.dataTable thead > tr > td.sorting_desc_disabled span.dt-column-order:after, +table.dataTable thead > tr > td.sorting_asc_disabled span.dt-column-order:before { display: none; } table.dataTable thead > tr > th:active, @@ -98,50 +155,61 @@ table.dataTable thead > tr > td:active { outline: none; } -div.dataTables_scrollBody table.dataTable thead > tr > th:before, div.dataTables_scrollBody table.dataTable thead > tr > th:after, -div.dataTables_scrollBody table.dataTable thead > tr > td:before, -div.dataTables_scrollBody table.dataTable thead > tr > td:after { - display: none; +div.dt-scroll-body > table.dataTable > thead > tr > th, +div.dt-scroll-body > table.dataTable > thead > tr > td { + overflow: hidden; } -div.dataTables_processing { +:root.dark table.dataTable thead > tr > th.dt-orderable-asc:hover, :root.dark table.dataTable thead > tr > th.dt-orderable-desc:hover, +:root.dark table.dataTable thead > tr > td.dt-orderable-asc:hover, +:root.dark table.dataTable thead > tr > td.dt-orderable-desc:hover, +:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-asc:hover, +:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-desc:hover, +:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-asc:hover, +:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-desc:hover { + outline: 2px solid rgba(255, 255, 255, 0.05); +} + +div.dt-processing { position: absolute; top: 50%; left: 50%; width: 200px; margin-left: -100px; - margin-top: -26px; + margin-top: -22px; text-align: center; padding: 2px; + z-index: 10; } -div.dataTables_processing > div:last-child { +div.dt-processing > div:last-child { position: relative; width: 80px; height: 15px; margin: 1em auto; } -div.dataTables_processing > div:last-child > div { +div.dt-processing > div:last-child > div { position: absolute; top: 0; width: 13px; height: 13px; border-radius: 50%; - background: rgba(13, 110, 253, 0.9); + background: rgb(13, 110, 253); + background: rgb(var(--dt-row-selected)); animation-timing-function: cubic-bezier(0, 1, 1, 0); } -div.dataTables_processing > div:last-child > div:nth-child(1) { +div.dt-processing > div:last-child > div:nth-child(1) { left: 8px; animation: datatables-loader-1 0.6s infinite; } -div.dataTables_processing > div:last-child > div:nth-child(2) { +div.dt-processing > div:last-child > div:nth-child(2) { left: 8px; animation: datatables-loader-2 0.6s infinite; } -div.dataTables_processing > div:last-child > div:nth-child(3) { +div.dt-processing > div:last-child > div:nth-child(3) { left: 32px; animation: datatables-loader-2 0.6s infinite; } -div.dataTables_processing > div:last-child > div:nth-child(4) { +div.dt-processing > div:last-child > div:nth-child(4) { left: 56px; animation: datatables-loader-3 0.6s infinite; } @@ -173,13 +241,16 @@ div.dataTables_processing > div:last-child > div:nth-child(4) { table.dataTable.nowrap th, table.dataTable.nowrap td { white-space: nowrap; } +table.dataTable th, +table.dataTable td { + box-sizing: border-box; +} table.dataTable th.dt-left, table.dataTable td.dt-left { text-align: left; } table.dataTable th.dt-center, -table.dataTable td.dt-center, -table.dataTable td.dataTables_empty { +table.dataTable td.dt-center { text-align: center; } table.dataTable th.dt-right, @@ -194,6 +265,16 @@ table.dataTable th.dt-nowrap, table.dataTable td.dt-nowrap { white-space: nowrap; } +table.dataTable th.dt-empty, +table.dataTable td.dt-empty { + text-align: center; + vertical-align: top; +} +table.dataTable th.dt-type-numeric, table.dataTable th.dt-type-date, +table.dataTable td.dt-type-numeric, +table.dataTable td.dt-type-date { + text-align: right; +} table.dataTable thead th, table.dataTable thead td, table.dataTable tfoot th, @@ -257,8 +338,6 @@ table.dataTable tbody td.dt-body-nowrap { table.dataTable { width: 100%; margin: 0 auto; - clear: both; - border-collapse: separate; border-spacing: 0; /* * Header and footer styles @@ -271,62 +350,78 @@ table.dataTable thead th, table.dataTable tfoot th { font-weight: bold; } -table.dataTable thead th, -table.dataTable thead td { +table.dataTable > thead > tr > th, +table.dataTable > thead > tr > td { padding: 10px; border-bottom: 1px solid rgba(0, 0, 0, 0.3); } -table.dataTable thead th:active, -table.dataTable thead td:active { +table.dataTable > thead > tr > th:active, +table.dataTable > thead > tr > td:active { outline: none; } -table.dataTable tfoot th, -table.dataTable tfoot td { - padding: 10px 10px 6px 10px; +table.dataTable > tfoot > tr > th, +table.dataTable > tfoot > tr > td { border-top: 1px solid rgba(0, 0, 0, 0.3); + padding: 10px 10px 6px 10px; } -table.dataTable tbody tr { +table.dataTable > tbody > tr { background-color: transparent; } -table.dataTable tbody tr.selected > * { - box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.9); - color: white; -} -table.dataTable tbody th, -table.dataTable tbody td { - padding: 8px 10px; -} -table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td { - border-top: 1px solid rgba(0, 0, 0, 0.15); -} -table.dataTable.row-border tbody tr:first-child th, -table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th, -table.dataTable.display tbody tr:first-child td { +table.dataTable > tbody > tr:first-child > * { border-top: none; } -table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td { +table.dataTable > tbody > tr:last-child > * { + border-bottom: none; +} +table.dataTable > tbody > tr.selected > * { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.9); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.9); + color: rgb(255, 255, 255); + color: rgb(var(--dt-row-selected-text)); +} +table.dataTable > tbody > tr.selected a { + color: rgb(9, 10, 11); + color: rgb(var(--dt-row-selected-link)); +} +table.dataTable > tbody > tr > th, +table.dataTable > tbody > tr > td { + padding: 8px 10px; +} +table.dataTable.row-border > tbody > tr > *, table.dataTable.display > tbody > tr > * { + border-top: 1px solid rgba(0, 0, 0, 0.15); +} +table.dataTable.row-border > tbody > tr:first-child > *, table.dataTable.display > tbody > tr:first-child > * { + border-top: none; +} +table.dataTable.row-border > tbody > tr.selected + tr.selected > td, table.dataTable.display > tbody > tr.selected + tr.selected > td { + border-top-color: rgba(13, 110, 253, 0.65); + border-top-color: rgba(var(--dt-row-selected), 0.65); +} +table.dataTable.cell-border > tbody > tr > * { border-top: 1px solid rgba(0, 0, 0, 0.15); border-right: 1px solid rgba(0, 0, 0, 0.15); } -table.dataTable.cell-border tbody tr th:first-child, -table.dataTable.cell-border tbody tr td:first-child { +table.dataTable.cell-border > tbody > tr > *:first-child { border-left: 1px solid rgba(0, 0, 0, 0.15); } -table.dataTable.cell-border tbody tr:first-child th, -table.dataTable.cell-border tbody tr:first-child td { - border-top: none; +table.dataTable.cell-border > tbody > tr:first-child > * { + border-top: 1px solid rgba(0, 0, 0, 0.3); } -table.dataTable.stripe > tbody > tr.odd > *, table.dataTable.display > tbody > tr.odd > * { +table.dataTable.stripe > tbody > tr:nth-child(odd) > *, table.dataTable.display > tbody > tr:nth-child(odd) > * { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.023); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.023); } -table.dataTable.stripe > tbody > tr.odd.selected > *, table.dataTable.display > tbody > tr.odd.selected > * { +table.dataTable.stripe > tbody > tr:nth-child(odd).selected > *, table.dataTable.display > tbody > tr:nth-child(odd).selected > * { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.923); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.923); } table.dataTable.hover > tbody > tr:hover > *, table.dataTable.display > tbody > tr:hover > * { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.035); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.035); } table.dataTable.hover > tbody > tr.selected:hover > *, table.dataTable.display > tbody > tr.selected:hover > * { - box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.935); + box-shadow: inset 0 0 0 9999px #0d6efd !important; + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 1) !important; } table.dataTable.order-column > tbody tr > .sorting_1, table.dataTable.order-column > tbody tr > .sorting_2, @@ -334,6 +429,7 @@ table.dataTable.order-column > tbody tr > .sorting_3, table.dataTable.display > table.dataTable.display > tbody tr > .sorting_2, table.dataTable.display > tbody tr > .sorting_3 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.019); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.019); } table.dataTable.order-column > tbody tr.selected > .sorting_1, table.dataTable.order-column > tbody tr.selected > .sorting_2, @@ -341,121 +437,143 @@ table.dataTable.order-column > tbody tr.selected > .sorting_3, table.dataTable.d table.dataTable.display > tbody tr.selected > .sorting_2, table.dataTable.display > tbody tr.selected > .sorting_3 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.919); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.919); } -table.dataTable.display > tbody > tr.odd > .sorting_1, table.dataTable.order-column.stripe > tbody > tr.odd > .sorting_1 { +table.dataTable.display > tbody > tr:nth-child(odd) > .sorting_1, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd) > .sorting_1 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.054); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.054); } -table.dataTable.display > tbody > tr.odd > .sorting_2, table.dataTable.order-column.stripe > tbody > tr.odd > .sorting_2 { +table.dataTable.display > tbody > tr:nth-child(odd) > .sorting_2, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd) > .sorting_2 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.047); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.047); } -table.dataTable.display > tbody > tr.odd > .sorting_3, table.dataTable.order-column.stripe > tbody > tr.odd > .sorting_3 { +table.dataTable.display > tbody > tr:nth-child(odd) > .sorting_3, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd) > .sorting_3 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.039); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.039); } -table.dataTable.display > tbody > tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe > tbody > tr.odd.selected > .sorting_1 { +table.dataTable.display > tbody > tr:nth-child(odd).selected > .sorting_1, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd).selected > .sorting_1 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.954); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.954); } -table.dataTable.display > tbody > tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe > tbody > tr.odd.selected > .sorting_2 { +table.dataTable.display > tbody > tr:nth-child(odd).selected > .sorting_2, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd).selected > .sorting_2 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.947); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.947); } -table.dataTable.display > tbody > tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe > tbody > tr.odd.selected > .sorting_3 { +table.dataTable.display > tbody > tr:nth-child(odd).selected > .sorting_3, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd).selected > .sorting_3 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.939); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.939); } table.dataTable.display > tbody > tr.even > .sorting_1, table.dataTable.order-column.stripe > tbody > tr.even > .sorting_1 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.019); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.019); } table.dataTable.display > tbody > tr.even > .sorting_2, table.dataTable.order-column.stripe > tbody > tr.even > .sorting_2 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.011); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.011); } table.dataTable.display > tbody > tr.even > .sorting_3, table.dataTable.order-column.stripe > tbody > tr.even > .sorting_3 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.003); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.003); } table.dataTable.display > tbody > tr.even.selected > .sorting_1, table.dataTable.order-column.stripe > tbody > tr.even.selected > .sorting_1 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.919); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.919); } table.dataTable.display > tbody > tr.even.selected > .sorting_2, table.dataTable.order-column.stripe > tbody > tr.even.selected > .sorting_2 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.911); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.911); } table.dataTable.display > tbody > tr.even.selected > .sorting_3, table.dataTable.order-column.stripe > tbody > tr.even.selected > .sorting_3 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.903); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.903); } table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.082); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.082); } table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.074); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.074); } table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.062); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.062); } table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.982); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.982); } table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.974); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.974); } table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.962); -} -table.dataTable.no-footer { - border-bottom: 1px solid rgba(0, 0, 0, 0.3); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.962); } table.dataTable.compact thead th, -table.dataTable.compact thead td { - padding: 4px 17px; -} +table.dataTable.compact thead td, table.dataTable.compact tfoot th, -table.dataTable.compact tfoot td { - padding: 4px; -} +table.dataTable.compact tfoot td, table.dataTable.compact tbody th, table.dataTable.compact tbody td { padding: 4px; } -table.dataTable th, -table.dataTable td { - box-sizing: content-box; -} - /* * Control feature layout */ -.dataTables_wrapper { +div.dt-container { position: relative; clear: both; } -.dataTables_wrapper { - float: left; +div.dt-container div.dt-layout-row { + display: table; + clear: both; + width: 100%; } -.dataTables_wrapper .dataTables_length select { - border: 1px solid #aaa; - border-radius: 3px; - padding: 5px; - background-color: transparent; - padding: 4px; +div.dt-container div.dt-layout-row.dt-layout-table { + display: block; } -.dataTables_wrapper .dataTables_filter { - float: right; +div.dt-container div.dt-layout-row.dt-layout-table div.dt-layout-cell { + display: block; +} +div.dt-container div.dt-layout-cell { + display: table-cell; + vertical-align: middle; + padding: 5px 0; +} +div.dt-container div.dt-layout-cell.dt-full { + text-align: center; +} +div.dt-container div.dt-layout-cell.dt-start { + text-align: left; +} +div.dt-container div.dt-layout-cell.dt-end { text-align: right; } -.dataTables_wrapper .dataTables_filter input { +div.dt-container div.dt-layout-cell:empty { + display: none; +} +div.dt-container .dt-search input { border: 1px solid #aaa; border-radius: 3px; padding: 5px; background-color: transparent; + color: inherit; margin-left: 3px; } -.dataTables_wrapper .dataTables_info { - clear: both; - float: left; - padding-top: 0.755em; +div.dt-container .dt-input { + border: 1px solid #aaa; + border-radius: 3px; + padding: 5px; + background-color: transparent; + color: inherit; } -.dataTables_wrapper .dataTables_paginate { - float: right; - text-align: right; - padding-top: 0.25em; +div.dt-container select.dt-input { + padding: 4px; } -.dataTables_wrapper .dataTables_paginate .paginate_button { +div.dt-container .dt-paging .dt-paging-button { box-sizing: border-box; display: inline-block; min-width: 1.5em; @@ -464,130 +582,191 @@ table.dataTable td { text-align: center; text-decoration: none !important; cursor: pointer; - color: #333 !important; + color: inherit !important; border: 1px solid transparent; border-radius: 2px; + background: transparent; } -.dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { - color: #333 !important; +div.dt-container .dt-paging .dt-paging-button.current, div.dt-container .dt-paging .dt-paging-button.current:hover { + color: inherit !important; border: 1px solid rgba(0, 0, 0, 0.3); - background-color: rgba(230, 230, 230, 0.1); - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(230, 230, 230, 0.1)), color-stop(100%, rgba(0, 0, 0, 0.1))); - /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); - /* Chrome10+,Safari5.1+ */ - background: -moz-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); - /* FF3.6+ */ - background: -ms-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); - /* IE10+ */ - background: -o-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); - /* Opera 11.10+ */ - background: linear-gradient(to bottom, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); - /* W3C */ + background-color: rgba(0, 0, 0, 0.05); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(230, 230, 230, 0.05)), color-stop(100%, rgba(0, 0, 0, 0.05))); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* FF3.6+ */ + background: -ms-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* IE10+ */ + background: -o-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* Opera 11.10+ */ + background: linear-gradient(to bottom, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* W3C */ } -.dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active { +div.dt-container .dt-paging .dt-paging-button.disabled, div.dt-container .dt-paging .dt-paging-button.disabled:hover, div.dt-container .dt-paging .dt-paging-button.disabled:active { cursor: default; - color: #666 !important; + color: rgba(0, 0, 0, 0.5) !important; border: 1px solid transparent; background: transparent; box-shadow: none; } -.dataTables_wrapper .dataTables_paginate .paginate_button:hover { +div.dt-container .dt-paging .dt-paging-button:hover { color: white !important; border: 1px solid #111; - background-color: #585858; - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111)); - /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #585858 0%, #111 100%); - /* Chrome10+,Safari5.1+ */ - background: -moz-linear-gradient(top, #585858 0%, #111 100%); - /* FF3.6+ */ - background: -ms-linear-gradient(top, #585858 0%, #111 100%); - /* IE10+ */ - background: -o-linear-gradient(top, #585858 0%, #111 100%); - /* Opera 11.10+ */ - background: linear-gradient(to bottom, #585858 0%, #111 100%); - /* W3C */ + background-color: #111; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #585858 0%, #111 100%); /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, #585858 0%, #111 100%); /* FF3.6+ */ + background: -ms-linear-gradient(top, #585858 0%, #111 100%); /* IE10+ */ + background: -o-linear-gradient(top, #585858 0%, #111 100%); /* Opera 11.10+ */ + background: linear-gradient(to bottom, #585858 0%, #111 100%); /* W3C */ } -.dataTables_wrapper .dataTables_paginate .paginate_button:active { +div.dt-container .dt-paging .dt-paging-button:active { outline: none; - background-color: #2b2b2b; - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c)); - /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); - /* Chrome10+,Safari5.1+ */ - background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); - /* FF3.6+ */ - background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); - /* IE10+ */ - background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); - /* Opera 11.10+ */ - background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%); - /* W3C */ + background-color: #0c0c0c; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* FF3.6+ */ + background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* IE10+ */ + background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* Opera 11.10+ */ + background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%); /* W3C */ box-shadow: inset 0 0 3px #111; } -.dataTables_wrapper .dataTables_paginate .ellipsis { +div.dt-container .dt-paging .ellipsis { padding: 0 1em; } -.dataTables_wrapper .dataTables_length, -.dataTables_wrapper .dataTables_filter, -.dataTables_wrapper .dataTables_info, -.dataTables_wrapper .dataTables_processing, -.dataTables_wrapper .dataTables_paginate { - color: #333; +div.dt-container .dt-length, +div.dt-container .dt-search, +div.dt-container .dt-info, +div.dt-container .dt-processing, +div.dt-container .dt-paging { + color: inherit; } -.dataTables_wrapper .dataTables_scroll { +div.dt-container .dataTables_scroll { clear: both; } -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody { +div.dt-container .dataTables_scroll div.dt-scroll-body { -webkit-overflow-scrolling: touch; } -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td { +div.dt-container .dataTables_scroll div.dt-scroll-body > table > thead > tr > th, div.dt-container .dataTables_scroll div.dt-scroll-body > table > thead > tr > td, div.dt-container .dataTables_scroll div.dt-scroll-body > table > tbody > tr > th, div.dt-container .dataTables_scroll div.dt-scroll-body > table > tbody > tr > td { vertical-align: middle; } -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th > div.dataTables_sizing, -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td > div.dataTables_sizing, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th > div.dataTables_sizing, -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td > div.dataTables_sizing { +div.dt-container .dataTables_scroll div.dt-scroll-body > table > thead > tr > th > div.dataTables_sizing, +div.dt-container .dataTables_scroll div.dt-scroll-body > table > thead > tr > td > div.dataTables_sizing, div.dt-container .dataTables_scroll div.dt-scroll-body > table > tbody > tr > th > div.dataTables_sizing, +div.dt-container .dataTables_scroll div.dt-scroll-body > table > tbody > tr > td > div.dataTables_sizing { height: 0; overflow: hidden; margin: 0 !important; padding: 0 !important; } -.dataTables_wrapper.no-footer .dataTables_scrollBody { +div.dt-container.dt-empty-footer tbody > tr:last-child > * { border-bottom: 1px solid rgba(0, 0, 0, 0.3); } -.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable, -.dataTables_wrapper.no-footer div.dataTables_scrollBody > table { - border-bottom: none; +div.dt-container.dt-empty-footer .dt-scroll-body { + border-bottom: 1px solid rgba(0, 0, 0, 0.3); } -.dataTables_wrapper:after { - visibility: hidden; - display: block; - content: ""; - clear: both; - height: 0; +div.dt-container.dt-empty-footer .dt-scroll-body tbody > tr:last-child > * { + border-bottom: none; } @media screen and (max-width: 767px) { - .dataTables_wrapper .dataTables_info, -.dataTables_wrapper .dataTables_paginate { - float: none; - text-align: center; + div.dt-container div.dt-layout-row { + display: block; } - .dataTables_wrapper .dataTables_paginate { - margin-top: 0.5em; + div.dt-container div.dt-layout-cell { + display: block; + } + div.dt-container div.dt-layout-cell.dt-full, div.dt-container div.dt-layout-cell.dt-start, div.dt-container div.dt-layout-cell.dt-end { + text-align: center; } } @media screen and (max-width: 640px) { - .dataTables_wrapper .dataTables_length, -.dataTables_wrapper .dataTables_filter { + .dt-container .dt-length, + .dt-container .dt-search { float: none; text-align: center; } - .dataTables_wrapper .dataTables_filter { + .dt-container .dt-search { margin-top: 0.5em; } } +html.dark { + --dt-row-hover: 255, 255, 255; + --dt-row-stripe: 255, 255, 255; + --dt-column-ordering: 255, 255, 255; +} +html.dark table.dataTable > thead > tr > th, +html.dark table.dataTable > thead > tr > td { + border-bottom: 1px solid rgb(89, 91, 94); +} +html.dark table.dataTable > thead > tr > th:active, +html.dark table.dataTable > thead > tr > td:active { + outline: none; +} +html.dark table.dataTable > tfoot > tr > th, +html.dark table.dataTable > tfoot > tr > td { + border-top: 1px solid rgb(89, 91, 94); +} +html.dark table.dataTable.row-border > tbody > tr > *, html.dark table.dataTable.display > tbody > tr > * { + border-top: 1px solid rgb(64, 67, 70); +} +html.dark table.dataTable.row-border > tbody > tr:first-child > *, html.dark table.dataTable.display > tbody > tr:first-child > * { + border-top: none; +} +html.dark table.dataTable.row-border > tbody > tr.selected + tr.selected > td, html.dark table.dataTable.display > tbody > tr.selected + tr.selected > td { + border-top-color: rgba(13, 110, 253, 0.65); + border-top-color: rgba(var(--dt-row-selected), 0.65); +} +html.dark table.dataTable.cell-border > tbody > tr > th, +html.dark table.dataTable.cell-border > tbody > tr > td { + border-top: 1px solid rgb(64, 67, 70); + border-right: 1px solid rgb(64, 67, 70); +} +html.dark table.dataTable.cell-border > tbody > tr > th:first-child, +html.dark table.dataTable.cell-border > tbody > tr > td:first-child { + border-left: 1px solid rgb(64, 67, 70); +} +html.dark .dt-container.dt-empty-footer table.dataTable { + border-bottom: 1px solid rgb(89, 91, 94); +} +html.dark .dt-container .dt-search input, +html.dark .dt-container .dt-length select { + border: 1px solid rgba(255, 255, 255, 0.2); + background-color: var(--dt-html-background); +} +html.dark .dt-container .dt-paging .dt-paging-button.current, html.dark .dt-container .dt-paging .dt-paging-button.current:hover { + border: 1px solid rgb(89, 91, 94); + background: rgba(255, 255, 255, 0.15); +} +html.dark .dt-container .dt-paging .dt-paging-button.disabled, html.dark .dt-container .dt-paging .dt-paging-button.disabled:hover, html.dark .dt-container .dt-paging .dt-paging-button.disabled:active { + color: #666 !important; +} +html.dark .dt-container .dt-paging .dt-paging-button:hover { + border: 1px solid rgb(53, 53, 53); + background: rgb(53, 53, 53); +} +html.dark .dt-container .dt-paging .dt-paging-button:active { + background: #3a3a3a; +} + +/* + * Overrides for RTL support + */ +*[dir=rtl] table.dataTable thead th, +*[dir=rtl] table.dataTable thead td, +*[dir=rtl] table.dataTable tfoot th, +*[dir=rtl] table.dataTable tfoot td { + text-align: right; +} +*[dir=rtl] table.dataTable th.dt-type-numeric, *[dir=rtl] table.dataTable th.dt-type-date, +*[dir=rtl] table.dataTable td.dt-type-numeric, +*[dir=rtl] table.dataTable td.dt-type-date { + text-align: left; +} +*[dir=rtl] div.dt-container div.dt-layout-cell.dt-start { + text-align: right; +} +*[dir=rtl] div.dt-container div.dt-layout-cell.dt-end { + text-align: left; +} +*[dir=rtl] div.dt-container div.dt-search input { + margin: 0 3px 0 0; +} @keyframes dtb-spinner { @@ -626,6 +805,11 @@ div.dataTables_wrapper { div.dt-buttons { position: initial; } +div.dt-buttons .dt-button { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} div.dt-button-info { position: fixed; @@ -635,31 +819,27 @@ div.dt-button-info { margin-top: -100px; margin-left: -200px; background-color: white; - border: 2px solid #111; - box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.3); - border-radius: 3px; + border-radius: 0.75em; + box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.8); text-align: center; - z-index: 21; + z-index: 2003; + overflow: hidden; } div.dt-button-info h2 { - padding: 0.5em; + padding: 2rem 2rem 1rem 2rem; margin: 0; font-weight: normal; - border-bottom: 1px solid #ddd; - background-color: #f3f3f3; } div.dt-button-info > div { - padding: 1em; + padding: 1em 2em 2em 2em; } div.dtb-popover-close { position: absolute; - top: 10px; - right: 10px; + top: 6px; + right: 6px; width: 22px; height: 22px; - border: 1px solid #eaeaea; - background-color: #f9f9f9; text-align: center; border-radius: 3px; cursor: pointer; @@ -672,10 +852,13 @@ button.dtb-hide-drop { div.dt-button-collection-title { text-align: center; - padding: 0.3em 0 0.5em; + padding: 0.3em 0.5em 0.5em; margin-left: 0.5em; margin-right: 0.5em; font-size: 0.9em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } div.dt-button-collection-title:empty { @@ -698,6 +881,46 @@ span.dt-button-spacer.bar:empty { padding-left: 0; } +div.dt-button-collection .dt-button-active { + padding-right: 3em; +} +div.dt-button-collection .dt-button-active:after { + position: absolute; + top: 50%; + margin-top: -10px; + right: 1em; + display: inline-block; + content: "✓"; + color: inherit; +} +div.dt-button-collection .dt-button-active.dt-button-split { + padding-right: 0; +} +div.dt-button-collection .dt-button-active.dt-button-split:after { + display: none; +} +div.dt-button-collection .dt-button-active.dt-button-split > *:first-child { + padding-right: 3em; +} +div.dt-button-collection .dt-button-active.dt-button-split > *:first-child:after { + position: absolute; + top: 50%; + margin-top: -10px; + right: 1em; + display: inline-block; + content: "✓"; + color: inherit; +} +div.dt-button-collection .dt-button-active-a a { + padding-right: 3em; +} +div.dt-button-collection .dt-button-active-a a:after { + position: absolute; + right: 1em; + display: inline-block; + content: "✓"; + color: inherit; +} div.dt-button-collection span.dt-button-spacer { width: 100%; font-size: 0.9em; @@ -710,14 +933,22 @@ div.dt-button-collection span.dt-button-spacer:empty { } div.dt-button-collection span.dt-button-spacer.bar { border-left: none; - border-bottom: 1px solid rgba(0, 0, 0, 0.3); + border-bottom: 1px solid rgba(0, 0, 0, 0.1); padding-left: 0; } -button.dt-button, -div.dt-button, -a.dt-button, -input.dt-button { +@media print { + table.dataTable tr > * { + box-shadow: none !important; + } +} +html.dark div.dt-button-info { + background-color: var(--dt-html-background); + border: 1px solid rgba(255, 255, 255, 0.15); +} + +div.dt-buttons > .dt-button, +div.dt-buttons > div.dt-button-split .dt-button { position: relative; display: inline-block; box-sizing: border-box; @@ -730,11 +961,10 @@ input.dt-button { cursor: pointer; font-size: 0.88em; line-height: 1.6em; - color: black; + color: inherit; white-space: nowrap; overflow: hidden; - background-color: rgba(0, 0, 0, 0.1); - /* Fallback */ + background-color: rgba(0, 0, 0, 0.1); /* Fallback */ background: linear-gradient(to bottom, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="rgba(230, 230, 230, 0.1)", EndColorStr="rgba(0, 0, 0, 0.1)"); -webkit-user-select: none; @@ -745,106 +975,87 @@ input.dt-button { outline: none; text-overflow: ellipsis; } -button.dt-button:first-child, -div.dt-button:first-child, -a.dt-button:first-child, -input.dt-button:first-child { +div.dt-buttons > .dt-button:first-child, +div.dt-buttons > div.dt-button-split .dt-button:first-child { margin-left: 0; } -button.dt-button.disabled, -div.dt-button.disabled, -a.dt-button.disabled, -input.dt-button.disabled { +div.dt-buttons > .dt-button.disabled, +div.dt-buttons > div.dt-button-split .dt-button.disabled { cursor: default; opacity: 0.4; } -button.dt-button:active:not(.disabled), -div.dt-button:active:not(.disabled), -a.dt-button:active:not(.disabled), -input.dt-button:active:not(.disabled) { - background-color: rgba(0, 0, 0, 0.1); - /* Fallback */ +div.dt-buttons > .dt-button.dt-button-active:not(.disabled), +div.dt-buttons > div.dt-button-split .dt-button.dt-button-active:not(.disabled) { + background-color: rgba(0, 0, 0, 0.1); /* Fallback */ background: linear-gradient(to bottom, rgba(179, 179, 179, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="rgba(179, 179, 179, 0.1)", EndColorStr="rgba(0, 0, 0, 0.1)"); box-shadow: inset 1px 1px 3px #999999; } -button.dt-button:active:not(.disabled):hover:not(.disabled), -div.dt-button:active:not(.disabled):hover:not(.disabled), -a.dt-button:active:not(.disabled):hover:not(.disabled), -input.dt-button:active:not(.disabled):hover:not(.disabled) { +div.dt-buttons > .dt-button.dt-button-active:not(.disabled):hover:not(.disabled), +div.dt-buttons > div.dt-button-split .dt-button.dt-button-active:not(.disabled):hover:not(.disabled) { box-shadow: inset 1px 1px 3px #999999; - background-color: rgba(0, 0, 0, 0.1); - /* Fallback */ + background-color: rgba(0, 0, 0, 0.1); /* Fallback */ background: linear-gradient(to bottom, rgba(128, 128, 128, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="rgba(128, 128, 128, 0.1)", EndColorStr="rgba(0, 0, 0, 0.1)"); } -button.dt-button:hover, -div.dt-button:hover, -a.dt-button:hover, -input.dt-button:hover { +div.dt-buttons > .dt-button:hover, +div.dt-buttons > div.dt-button-split .dt-button:hover { text-decoration: none; } -button.dt-button:hover:not(.disabled), -div.dt-button:hover:not(.disabled), -a.dt-button:hover:not(.disabled), -input.dt-button:hover:not(.disabled) { +div.dt-buttons > .dt-button:hover:not(.disabled), +div.dt-buttons > div.dt-button-split .dt-button:hover:not(.disabled) { border: 1px solid #666; - background-color: rgba(0, 0, 0, 0.1); - /* Fallback */ + background-color: rgba(0, 0, 0, 0.1); /* Fallback */ background: linear-gradient(to bottom, rgba(153, 153, 153, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="rgba(153, 153, 153, 0.1)", EndColorStr="rgba(0, 0, 0, 0.1)"); } -button.dt-button:focus:not(.disabled), -div.dt-button:focus:not(.disabled), -a.dt-button:focus:not(.disabled), -input.dt-button:focus:not(.disabled) { - border: 1px solid #426c9e; - text-shadow: 0 1px 0 #c4def1; +div.dt-buttons > .dt-button:focus:not(.disabled), +div.dt-buttons > div.dt-button-split .dt-button:focus:not(.disabled) { + outline: 2px solid rgb(53, 132, 228); +} +div.dt-buttons > .dt-button embed, +div.dt-buttons > div.dt-button-split .dt-button embed { outline: none; - background-color: #79ace9; - /* Fallback */ - background: linear-gradient(to bottom, #d1e2f7 0%, #79ace9 100%); - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="#d1e2f7", EndColorStr="#79ace9"); } -button.dt-button.active:focus:not(.disabled), -div.dt-button.active:focus:not(.disabled), -a.dt-button.active:focus:not(.disabled), -input.dt-button.active:focus:not(.disabled) { - background: linear-gradient(to bottom, #d1e2f7 0%, #79ace9 100%) !important; +div.dt-buttons > div.dt-button-split .dt-button:first-child { + border-right: 1px solid rgba(0, 0, 0, 0.15); + border-top-right-radius: 0; + border-bottom-right-radius: 0; } -button.dt-button span.dt-down-arrow, -div.dt-button span.dt-down-arrow, -a.dt-button span.dt-down-arrow, -input.dt-button span.dt-down-arrow { +div.dt-buttons > div.dt-button-split .dt-button:first-child:hover { + border-right: 1px solid #666; +} +div.dt-buttons > div.dt-button-split .dt-button:last-child { + border-left: 1px solid transparent; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +div.dt-buttons > div.dt-button-split .dt-button:last-child:hover { + border-left: 1px solid #666; +} +div.dt-buttons span.dt-button-down-arrow { position: relative; top: -2px; - color: rgba(70, 70, 70, 0.75); - font-size: 8px; + font-size: 10px; padding-left: 10px; line-height: 1em; + opacity: 0.6; } - -.dt-button embed { - outline: none; -} - -div.dt-buttons { - float: left; -} -div.dt-buttons.buttons-right { - float: right; -} - -div.dataTables_layout_cell div.dt-buttons { - float: none; -} -div.dataTables_layout_cell div.dt-buttons.buttons-right { - float: none; -} - -div.dt-btn-split-wrapper { +div.dt-buttons div.dt-button-split { display: inline-block; } +div.dt-buttons div.dt-button-split .dt-button:first-child { + margin-right: 0; +} +div.dt-buttons div.dt-button-split .dt-button:last-child { + margin-left: -1px; + padding-left: 0.75em; + padding-right: 0.75em; + z-index: 2; +} +div.dt-buttons div.dt-button-split .dt-button:last-child span { + padding-left: 0; +} div.dt-button-collection { position: absolute; @@ -853,8 +1064,7 @@ div.dt-button-collection { width: 200px; margin-top: 3px; margin-bottom: 3px; - padding: 4px 4px 2px 4px; - border: 1px solid #ccc; + padding: 0.75em 0; border: 1px solid rgba(0, 0, 0, 0.4); background-color: white; overflow: hidden; @@ -863,98 +1073,57 @@ div.dt-button-collection { box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.3); box-sizing: border-box; } -div.dt-button-collection button.dt-button, -div.dt-button-collection div.dt-button, -div.dt-button-collection a.dt-button { +div.dt-button-collection .dt-button { position: relative; left: 0; right: 0; width: 100%; display: block; float: none; - margin: 4px 0 2px 0; + background: none; + margin: 0; + padding: 0.5em 1em; + border: none; + text-align: left; + cursor: pointer; + color: inherit; } -div.dt-button-collection button.dt-button:active:not(.disabled), div.dt-button-collection button.dt-button.active:not(.disabled), -div.dt-button-collection div.dt-button:active:not(.disabled), -div.dt-button-collection div.dt-button.active:not(.disabled), -div.dt-button-collection a.dt-button:active:not(.disabled), -div.dt-button-collection a.dt-button.active:not(.disabled) { - background-color: #dadada; - /* Fallback */ - background: linear-gradient(to bottom, #f0f0f0 0%, #dadada 100%); - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="#f0f0f0", EndColorStr="#dadada"); - box-shadow: inset 1px 1px 3px #666; +div.dt-button-collection .dt-button.dt-button-active { + background: none; + box-shadow: none; } -div.dt-button-collection button.dt-button:first-child, -div.dt-button-collection div.dt-button:first-child, -div.dt-button-collection a.dt-button:first-child { - margin-top: 0; - border-top-left-radius: 3px; - border-top-right-radius: 3px; +div.dt-button-collection .dt-button.disabled { + cursor: default; + opacity: 0.4; } -div.dt-button-collection button.dt-button:last-child, -div.dt-button-collection div.dt-button:last-child, -div.dt-button-collection a.dt-button:last-child { - border-bottom-left-radius: 3px; - border-bottom-right-radius: 3px; +div.dt-button-collection .dt-button:hover:not(.disabled) { + border: none; + background: rgba(153, 153, 153, 0.1); + box-shadow: none; } -div.dt-button-collection div.dt-btn-split-wrapper { +div.dt-button-collection div.dt-button-split { display: flex; flex-direction: row; flex-wrap: wrap; justify-content: flex-start; align-content: flex-start; align-items: stretch; - margin: 4px 0 2px 0; } -div.dt-button-collection div.dt-btn-split-wrapper button.dt-button { +div.dt-button-collection div.dt-button-split button.dt-button { margin: 0; display: inline-block; width: 0; flex-grow: 1; flex-shrink: 0; flex-basis: 50px; - border-radius: 0; } -div.dt-button-collection div.dt-btn-split-wrapper button.dt-btn-split-drop { - min-width: 20px; - flex-grow: 0; - flex-shrink: 0; - flex-basis: 0; +div.dt-button-collection div.dt-button-split button.dt-button-split-drop { + min-width: 33px; + flex: 0; } -div.dt-button-collection div.dt-btn-split-wrapper:first-child { - margin-top: 0; -} -div.dt-button-collection div.dt-btn-split-wrapper:first-child button.dt-button { - border-top-left-radius: 3px; -} -div.dt-button-collection div.dt-btn-split-wrapper:first-child button.dt-btn-split-drop { - border-top-right-radius: 3px; -} -div.dt-button-collection div.dt-btn-split-wrapper:last-child button.dt-button { - border-bottom-left-radius: 3px; -} -div.dt-button-collection div.dt-btn-split-wrapper:last-child button.dt-btn-split-drop { - border-bottom-right-radius: 3px; -} -div.dt-button-collection div.dt-btn-split-wrapper:active:not(.disabled) button.dt-button, div.dt-button-collection div.dt-btn-split-wrapper.active:not(.disabled) button.dt-button { - background-color: #dadada; - /* Fallback */ - background: linear-gradient(to bottom, #f0f0f0 0%, #dadada 100%); - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="#f0f0f0", EndColorStr="#dadada"); - box-shadow: inset 0px 0px 4px #666; -} -div.dt-button-collection div.dt-btn-split-wrapper:active:not(.disabled) button.dt-btn-split-drop, div.dt-button-collection div.dt-btn-split-wrapper.active:not(.disabled) button.dt-btn-split-drop { - box-shadow: none; -} -div.dt-button-collection.fixed .dt-button:first-child { - margin-top: 0; - border-top-left-radius: 0; - border-top-right-radius: 0; -} -div.dt-button-collection.fixed .dt-button:last-child { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; +div.dt-button-collection.fixed .dt-button { + border-radius: 0.25em; + background: rgba(255, 255, 255, 0.1); } div.dt-button-collection.fixed { position: fixed; @@ -964,6 +1133,7 @@ div.dt-button-collection.fixed { margin-left: -75px; border-radius: 5px; background-color: white; + padding: 0.5em; } div.dt-button-collection.fixed.two-column { margin-left: -200px; @@ -1086,27 +1256,15 @@ div.dt-button-background { left: 0; width: 100%; height: 100%; - background: rgba(0, 0, 0, 0.7); - /* Fallback */ - background: radial-gradient(ellipse farthest-corner at center, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.7) 100%); - /* W3C Markup, IE10 Release Preview */ + background: rgba(0, 0, 0, 0.7); /* Fallback */ + background: radial-gradient(ellipse farthest-corner at center, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.7) 100%); /* W3C Markup, IE10 Release Preview */ z-index: 2001; } -@media screen and (max-width: 640px) { - div.dt-buttons { - float: none !important; - text-align: center; - } -} -button.dt-button.processing, -div.dt-button.processing, -a.dt-button.processing { +.dt-button.processing { color: rgba(0, 0, 0, 0.2); } -button.dt-button.processing:after, -div.dt-button.processing:after, -a.dt-button.processing:after { +.dt-button.processing:after { position: absolute; top: 50%; left: 50%; @@ -1116,7 +1274,7 @@ a.dt-button.processing:after { box-sizing: border-box; display: block; content: " "; - border: 2px solid #282828; + border: 2px solid rgb(40, 40, 40); border-radius: 50%; border-left-color: transparent; border-right-color: transparent; @@ -1127,453 +1285,697 @@ a.dt-button.processing:after { -moz-animation: dtb-spinner 1500ms infinite linear; } -button.dt-btn-split-drop { - margin-left: calc(-1px - 0.333em); - padding-bottom: calc(0.5em - 1px); - border-radius: 0px 1px 1px 0px; - color: rgba(70, 70, 70, 0.9); - border-left: none; +@media screen and (max-width: 640px) { + div.dt-buttons { + float: none !important; + text-align: center; + } } -button.dt-btn-split-drop span.dt-btn-split-drop-arrow { - position: relative; - top: -1px; - left: -2px; - font-size: 8px; +html.dark div.dt-buttons > .dt-button, +html.dark div.dt-buttons > div.dt-button-split .dt-button { + border: 1px solid rgb(89, 91, 94); + background: rgba(255, 255, 255, 0.15); } -button.dt-btn-split-drop:hover { - z-index: 2; +html.dark div.dt-buttons > .dt-button.dt-button-active:not(.disabled), +html.dark div.dt-buttons > div.dt-button-split .dt-button.dt-button-active:not(.disabled) { + background: rgba(179, 179, 179, 0.15); + box-shadow: inset 1px 1px 2px black; } - -button.buttons-split { - border-right: 1px solid rgba(70, 70, 70, 0); - border-radius: 1px 0px 0px 1px; +html.dark div.dt-buttons > .dt-button.dt-button-active:not(.disabled):hover:not(.disabled), +html.dark div.dt-buttons > div.dt-button-split .dt-button.dt-button-active:not(.disabled):hover:not(.disabled) { + background: rgba(128, 128, 128, 0.15); + box-shadow: inset 1px 1px 3px black; } - -button.dt-btn-split-drop-button { - background-color: white; +html.dark div.dt-buttons > .dt-button:hover:not(.disabled), +html.dark div.dt-buttons > div.dt-button-split .dt-button:hover:not(.disabled) { + background: rgba(179, 179, 179, 0.15); } -button.dt-btn-split-drop-button:hover { - background-color: white; +html.dark div.dt-buttons > .dt-button:focus:not(.disabled), +html.dark div.dt-buttons > div.dt-button-split .dt-button:focus:not(.disabled) { + outline: 2px solid rgb(110, 168, 254); +} +html.dark div.dt-buttons > div.dt-button-split .dt-button:first-child { + border-right: 1px solid rgba(255, 255, 255, 0.1); +} +html.dark div.dt-buttons > div.dt-button-split .dt-button:first-child:hover { + border-right: 1px solid rgb(89, 91, 94); +} +html.dark div.dt-buttons > div.dt-button-split .dt-button:last-child:hover { + border-left: 1px solid rgb(89, 91, 94); +} +html.dark div.dt-button-collection { + border: 1px solid rgba(255, 255, 255, 0.15); + background-color: rgb(33, 37, 41); + box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.8); } -table.DTCR_clonedTable.dataTable { +body.dtcr-dragging { + overflow-x: hidden; +} + +table.dtcr-cloned.dataTable { position: absolute !important; background-color: rgba(255, 255, 255, 0.7); z-index: 202; + border-radius: 4px; } -div.DTCR_pointer { - width: 1px; - background-color: #0259C4; - z-index: 201; +table.dataTable tbody tr td.dtcr-moving { + background-color: rgba(127, 127, 127, 0.15); +} +table.dataTable tbody tr td.dtcr-moving-first { + border-left: 1px solid #0259C4; +} +table.dataTable tbody tr td.dtcr-moving-last { + border-right: 1px solid #0259C4; +} + +html.dark table.dtcr-cloned.dataTable { + background-color: rgba(33, 33, 33, 0.9); } -table.fixedHeader-floating { - background-color: white; -} - -table.fixedHeader-floating.no-footer { - border-bottom-width: 0; -} - -table.fixedHeader-locked { - position: absolute !important; - background-color: white; -} - -@media print { - table.fixedHeader-floating { - display: none; - } -} - - -div.dtsp-topRow { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - border: 2px solid rgba(0, 0, 0, 0); - border-radius: 3px; - justify-content: space-around; - align-content: flex-start; - align-items: flex-start; - min-height: 37px; -} -div.dtsp-topRow input.dtsp-search { - text-overflow: ellipsis; - min-width: 50px; - flex-basis: 90px; - max-width: none; -} -div.dtsp-topRow input.dtsp-search::placeholder { - color: black; -} -div.dtsp-topRow div.dtsp-subRow1 { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - flex: 1 1 auto; -} -div.dtsp-topRow div.dtsp-subRow1 div.dtsp-searchCont { - position: relative; - width: 100%; -} -div.dtsp-topRow div.dtsp-subRow1 div.dtsp-searchCont input.dtsp-disabledButton { - padding-top: 10px; - padding-bottom: 10px; - background-color: transparent; -} -div.dtsp-topRow div.dtsp-subRow1 input { - padding-right: 2em; - width: 100% !important; - box-sizing: border-box; - font-size: 1em; -} -div.dtsp-topRow div.dtsp-subRow1 button.dtsp-searchIcon { +div.dt-datetime { position: absolute; - top: 0; - right: 0; - bottom: 0; - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAABbmlDQ1BpY2MAACiRdZE7SwNBFIU/EyWikRRaiFhsoWKhEBREO42FTZAQFYza7G5eQhKX3QQJtoKNhWAh2vgq/AfaCrYKgqAIIhb+Al+NhPVOEkiQZJbZ+3FmzmXmDHjCGTPrtAYhm8vb0bmQthxb0Xzv+PHRxRSabjrWTCQSpun4eaRF1YdR1av5voajM55wTGhpF54wLTsvPC0c3sxbineFe8y0Hhc+ER6x5YDCt0o3KvymOFXhL8X2YnQWPKqnlqpjo47NtJ0VHhYeyGYKZvU86ib+RG5pQWqfzH4coswRQsOgwDoZ8oxKzUlmjX3Bsm+eDfGY8rcoYosjRVq8I6IWpGtCalL0hHwZiir3/3k6yfGxSnd/CNpeXfdzEHz7UNpz3d9T1y2dgfcFrnM1/4bkNPkt+l5NGziGwDZc3tQ04wCudqD32dJtvSx5ZXqSSfi4gK4YdN9Dx2olq+o650+wuCVPdAeHRzAk+wNrfw8JaBFXEnV+AAAACXBIWXMAAA9hAAAPYQGoP6dpAAABMUlEQVQoU6XRr0vDQRjH8akoM4iIjqGoOIZ5oIjB5XWxajaYDGLSIhhNYjcPRDSJwbQNw+L+BNGgYYo/5pT5/shz8vDlBgMPXux7z3N3z+25VOofYyCyd4ZYCavI4gXPsRp9LqiDdrEMH+8wv8Vh8gBfWclFPOEUN3hAHjlMoRa7wTzBS5xgKLFglPkZLjDic6HyDsEMNvGR2Nxifoci3tEI+X770JU0XmPXIlax+LTPh83fFox1X6kxyzdjm9UcdXi9S+Vti6svfyNULhNR9TVsYNhW6Ff9KKCNR7/Zv6eeaQ+6+qcdpu9BqGlp1HFgud+FYdzzUcUExu0Q/cdzHGEFetIlXKPjK/sbqYoOftMiS+j9jzEJPd1Wt+5+kdR/9EM9ucIC5jCbyPc01Q32kfsBppYz3hYFcCwAAAAASUVORK5CYII=") !important; - background-repeat: no-repeat; - background-position: center; - background-size: 12px; + background-color: white; + z-index: 2050; + border: 1px solid #ccc; + box-shadow: 0 5px 15px -5px rgba(0, 0, 0, 0.5); + padding: 6px 20px; + width: 275px; + border-radius: 5px; } -div.dtsp-topRow div.dtsp-subRow2 { - white-space: nowrap; - flex: 0 0 auto; -} -div.dtsp-topRow button.dtsp-nameButton { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAABcGlDQ1BpY2MAACiRdZHNSwJBGMYftTDS8FCHkA57sOigIAXRMQzyYh3UIKvL7rirwe66zK6IdA26dBA6RF36OvQf1DXoWhAERRAR9B/0dQnZ3nEFJXSG2ffHs/O8zDwD+DM6M+yBJGCYDs+mU9JaYV0KviNMM4QoEjKzreXcUh59x88jfKI+JESv/vt6jlBRtRngGyKeYxZ3iBeIMzXHErxHPMbKcpH4hDjO6YDEt0JXPH4TXPL4SzDPZxcBv+gplbpY6WJW5gbxNHHM0KusfR5xk7BqruaoRmlNwEYWaaQgQUEVW9DhIEHVpMx6+5It3woq5GH0tVAHJ0cJZfLGSa1SV5WqRrpKU0dd5P4/T1ubnfG6h1PA4Kvrfk4CwX2g2XDd31PXbZ4BgRfg2uz4K5TT/DfpjY4WOwYiO8DlTUdTDoCrXWD82ZK53JICtPyaBnxcACMFYPQeGN7wsmr/x/kTkN+mJ7oDDo+AKdof2fwDCBRoDkL8UccAAAAJcEhZcwAAD2EAAA9hAag/p2kAAAK2SURBVFgJ7ZY9j41BFICvryCExrJBQ6HyEYVEIREaUZDQIRoR2ViJKCioxV+gkVXYTVZEQiEUhG2EQnxUCh0FKolY4ut5XnM2cyfva3Pt5m7EPcmzZ2bemTNnzjkzd1utnvQi0IvAfxiBy5z5FoxO89kPY+8mbMjtzs47RXs5/WVpbAG6bWExt5PuIibvhVkwmC+ck3eK9ln6/fAddFojYzBVuYSBpcnIEvRaqOw2RcaN18FPuJH0JvRUxbT3wWf4ltiKPgfVidWlbGZgPozDFfgAC+EA/K2EI4cwcAJ+gPaeQ+VQU2SOMMGcPgPl/m/V2p50rrbRsRgt9Iv5h6xtpP22Bz7Ce1C+gFFxfKzOmShcU+Qmyh2w3w8rIJfddHTck66EukL/xPhj+JM8rHNmFys0Pg4v0up3aFNlwR9NYyodd3OL/C64zpsymcTFcf6ElM4YzjAWKYrJkaq8kE/yUYNP4BoYvS1QRo+hNtF5xfkTUjoTheukSFFMjlTFm6PjceOca/SMpKfeCR1L6Uzk/y2WIkVhNFJlJAZhP+hYns7b9D3IPuhY5mYrIv8OrQJvR5NYyNaW4jsU8pSGNySiVx4o5tXq3JkoXE/mg5R/M8dGJCJpKhaDcjBRdbI/Rm8g69c122om33BHmj2CHoV5qa9jUXBraJ+G1fAVjIBO1klc87ro1K4JZ/K35SWW3TwcyDd6TecqnAEd8cGq2+w84xvBm1n3vS0izKkkwh5XNC/GmFPqqAtPF89AOScKuemaNzoTV1SD5dtSbmLf1/RV+tC0WTgcj6R7HEtrVGWaqu/lYDZ/2pvxQ/kIyw/gFByHC9AHw910hv1aUUumyd8yy0QfhmEkfiNod0Xusct68J1qc8Tdux0Z97Q+hsDb+AYGYEbF/4Guw2Q/qDPqZG/zXgT+3Qj8AtKnfWhFwmuAAAAAAElFTkSuQmCC") !important; - background-repeat: no-repeat; - background-position: center; - background-size: 23px; - vertical-align: bottom; -} -div.dtsp-topRow button.dtsp-countButton { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAABcGlDQ1BpY2MAACiRdZHNSwJBGMYftTDS8FCHkA57sOigIAXRMQzyYh3UIKvL7rirwe66zK6IdA26dBA6RF36OvQf1DXoWhAERRAR9B/0dQnZ3nEFJXSG2ffHs/O8zDwD+DM6M+yBJGCYDs+mU9JaYV0KviNMM4QoEjKzreXcUh59x88jfKI+JESv/vt6jlBRtRngGyKeYxZ3iBeIMzXHErxHPMbKcpH4hDjO6YDEt0JXPH4TXPL4SzDPZxcBv+gplbpY6WJW5gbxNHHM0KusfR5xk7BqruaoRmlNwEYWaaQgQUEVW9DhIEHVpMx6+5It3woq5GH0tVAHJ0cJZfLGSa1SV5WqRrpKU0dd5P4/T1ubnfG6h1PA4Kvrfk4CwX2g2XDd31PXbZ4BgRfg2uz4K5TT/DfpjY4WOwYiO8DlTUdTDoCrXWD82ZK53JICtPyaBnxcACMFYPQeGN7wsmr/x/kTkN+mJ7oDDo+AKdof2fwDCBRoDkL8UccAAAAJcEhZcwAAD2EAAA9hAag/p2kAAAG5SURBVEgN3VU9LwVBFF0fiYhofUSlEQkKhU7z/oBCQkIiGr9BgUbhVzy9BAnhFyjV/AYFiU5ICM7ZN+c5Zud5dm3lJmfmzrkz9+7cu3c3y/6jjOBSF8CxXS7FmTkbwqIJjDpJvTcmsJ4K3KPZUpyZsx0sxoB9J6mnAkyC7wGuuCFIipNtEcpcWExgXpOBc78vgj6N+QO4NVsjwdFM59tUIDxDrHMBOeIQ34C5ZDregXuAQm4YcI68nN9B3wr2PcwPAIPkN2EqtJH6b+QZm1ajjTx7BqwAr26Lb+C2Kvpbt0Mb2HAJ7NrGFGfmXO3DeA4UshDfQAVmH0gaUFg852TTTDvlxwBlCtxy9zXyBhQFaq0wMmIdRebrfgosA3zb2hKnqG0oqchp4QbuR8X0TjzABhbdOT8jnQ/atcgqpnfwOA7yqZyTU587ZkIGdesLTt2EkynOnbreMUUKMI/dA4B/QVOcO13CQh+5wWCgDwo/75u59odB/wjmfhbgvACcAOyZPHihMWAoIwxyCLgf1oxfgjzVbgBXSTzIN+f0pg6s5DkcesLMRpsBrgE2XO3CN64JFP7JtUeKHX4CKtRRXFZ+7dEAAAAASUVORK5CYII=") !important; - background-repeat: no-repeat; - background-position: center; - background-size: 18px; - vertical-align: bottom; -} -div.dtsp-topRow button.dtsp-collapseButton span.dtsp-caret { +div.dt-datetime.inline { position: relative; - top: 2px; + box-shadow: none; +} +div.dt-datetime div.dt-datetime-title { + text-align: center; + padding: 5px 0px 3px; +} +div.dt-datetime div.dt-datetime-buttons { + text-align: center; +} +div.dt-datetime div.dt-datetime-buttons a { display: inline-block; + padding: 0 0.5em 0.5em 0.5em; + margin: 0; + font-size: 0.9em; } -div.dtsp-topRow button.dtsp-collapseButton.dtsp-rotated { - transform: rotate(180deg); +div.dt-datetime div.dt-datetime-buttons a:hover { + text-decoration: underline; } - -div.dtsp-topRow.dtsp-bordered { - border: 2px solid #f0f0f0; - border-radius: 3px; -} - -div.dtsp-topRow.dtsp-bordered:hover { - background-color: #f0f0f0; - opacity: 0.6; - border: 2px solid #cfcfcf; - border-radius: 3px; - cursor: pointer !important; -} - -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane table thead th, -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane table thead td { - width: 100% !important; -} - -div.dt-button-collection { - z-index: 2002; -} - -div.dt-button-collection.dtb-collection-closeable div.dtsp-titleRow { - padding-right: 25px; -} - -div.dtsp-columns-1 { - max-width: 100%; - min-width: 100%; - margin: 0px !important; -} - -div.dtsp-columns-2 { - max-width: 49%; - min-width: 49%; - margin: 0px !important; -} - -div.dtsp-columns-3 { - max-width: 32%; - min-width: 32%; - margin: 0px !important; -} - -div.dtsp-columns-4 { - max-width: 24%; - min-width: 24%; - margin: 0px !important; -} - -div.dtsp-columns-5 { - max-width: 19%; - min-width: 19%; - margin: 0px !important; -} - -div.dtsp-columns-6 { - max-width: 16%; - min-width: 16%; - margin: 0px !important; -} - -div.dtsp-columns-7 { - max-width: 14%; - min-width: 14%; - margin: 0px !important; -} - -div.dtsp-columns-8 { - max-width: 12%; - min-width: 12%; - margin: 0px !important; -} - -div.dtsp-columns-9 { - max-width: 10.5%; - min-width: 10.5%; - margin: 0px !important; -} - -div.dtsp-narrow { - flex-direction: column !important; -} -div.dtsp-narrow div.dtsp-subRow1, -div.dtsp-narrow div.dtsp-subRow2 { +div.dt-datetime table { + border-spacing: 0; + margin: 12px 0; width: 100%; } -div.dtsp-narrow div.dtsp-subRow2 button { - margin: 0 !important; - width: 25% !important; +div.dt-datetime table.dt-datetime-table-nospace { + margin-top: -12px; } - -div.dt-button-collection { - float: none; +div.dt-datetime table th { + font-size: 0.8em; + color: #777; + font-weight: normal; + width: 14.285714286%; + padding: 0 0 4px 0; + text-align: center; } - -div.dtsp-panesContainer { - margin-bottom: 1em; -} -div.dtsp-panesContainer div.dataTables_wrapper { - width: 100%; -} -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_layout_cell { +div.dt-datetime table td { + font-size: 0.9em; + color: #444; padding: 0; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollHead { - display: none !important; +div.dt-datetime table td.selectable { + text-align: center; + background: #f5f5f5; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody { - background: white !important; - border-bottom: none; +div.dt-datetime table td.selectable.disabled { + color: #aaa; + background: white; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody thead { - display: none; +div.dt-datetime table td.selectable.disabled button:hover { + color: #aaa; + background: white; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody table { - table-layout: fixed; +div.dt-datetime table td.selectable.now { + background-color: #ddd; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody table tr > th, -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody table tr > td { - padding: 5px 10px; +div.dt-datetime table td.selectable.now button { + font-weight: bold; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody td.dtsp-nameColumn { - width: 100% !important; +div.dt-datetime table td.selectable.selected button { + background: #4E6CA3; + color: white; + border-radius: 2px; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont { +div.dt-datetime table td.selectable button:hover { + background: #ff8000; + color: white; + border-radius: 2px; +} +div.dt-datetime table td.dt-datetime-week { + font-size: 0.7em; +} +div.dt-datetime table button { width: 100%; - display: flex; - flex-direction: row; - justify-content: flex-start; - align-content: flex-start; - align-items: flex-start; + box-sizing: border-box; + border: none; + background: transparent; + font-size: inherit; + color: inherit; + text-align: center; + padding: 4px 0; + cursor: pointer; + margin: 0; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-name, -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-pill { - cursor: default; +div.dt-datetime table button span { + display: inline-block; + min-width: 14px; + text-align: right; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-name { - text-overflow: ellipsis; - overflow: hidden; +div.dt-datetime table.weekNumber th { + width: 12.5%; +} +div.dt-datetime div.dt-datetime-calendar table { + margin-top: 0; +} +div.dt-datetime div.dt-datetime-label { + position: relative; + display: inline-block; + height: 30px; + padding: 5px 6px; + border: 1px solid transparent; + box-sizing: border-box; + cursor: pointer; +} +div.dt-datetime div.dt-datetime-label:hover { + border: 1px solid #ddd; + border-radius: 2px; + background-color: #f5f5f5; +} +div.dt-datetime div.dt-datetime-label select { + position: absolute; + top: 6px; + left: 0; + cursor: pointer; + opacity: 0; +} +div.dt-datetime.horizontal { + width: 550px; +} +div.dt-datetime.horizontal div.dt-datetime-date, +div.dt-datetime.horizontal div.dt-datetime-time { + width: 48%; +} +div.dt-datetime.horizontal div.dt-datetime-time { + margin-left: 4%; +} +div.dt-datetime div.dt-datetime-date { + position: relative; + float: left; + width: 100%; +} +div.dt-datetime div.dt-datetime-time { + position: relative; + float: left; + width: 100%; + text-align: center; +} +div.dt-datetime div.dt-datetime-time > span { + vertical-align: middle; +} +div.dt-datetime div.dt-datetime-time th { + text-align: left; +} +div.dt-datetime div.dt-datetime-time div.dt-datetime-timeblock { display: inline-block; vertical-align: middle; - white-space: nowrap; - flex-grow: 1; - text-align: left; } -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-pill { - display: inline-block; - background-color: #cfcfcf; - text-align: center; - border: 1px solid #cfcfcf; - border-radius: 10px; - width: auto; - min-width: 30px; - color: black; - font-size: 0.9em; - padding: 0 4px; -} -div.dtsp-panesContainer div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-pill:empty { - display: none; -} - -div.dtsp-panesContainer { - clear: both; - padding-left: 0; - padding-right: 0; - text-align: center; -} -div.dtsp-panesContainer div.dtsp-searchPanes { - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: space-between; - align-content: flex-start; - align-items: stretch; - clear: both; - text-align: left; -} -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane { - flex-grow: 1; - flex-shrink: 0; - font-size: 0.9em; - margin-top: 15px !important; -} -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dataTables_wrapper { - flex: 1; +div.dt-datetime div.dt-datetime-iconLeft, +div.dt-datetime div.dt-datetime-iconRight { + width: 30px; + height: 30px; + background-position: center; + background-repeat: no-repeat; + opacity: 0.3; + overflow: hidden; box-sizing: border-box; + border: 1px solid transparent; } -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dataTables_wrapper div.dataTables_filter { - display: none; -} -div.dtsp-panesContainer div.dtsp-title { - float: left; - padding: 10px 0; -} -div.dtsp-panesContainer button.dtsp-clearAll, -div.dtsp-panesContainer button.dtsp-collapseAll, -div.dtsp-panesContainer button.dtsp-showAll { - float: right; - padding: 10px; -} - -div.dtsp-hidden { - display: none !important; -} - -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dataTables_wrapper { - border: 2px solid #f0f0f0; - border-radius: 4px; -} -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dataTables_wrapper:hover { - border: 2px solid #cfcfcf; -} -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dtsp-selected { - border: 2px solid #3276b1; - border-radius: 4px; -} -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dtsp-selected:hover { - border: 2px solid #286092; -} -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dtsp-topRow div.dtsp-searchCont input.dtsp-search { - border: none; - padding-left: 3px; -} -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane input.dtsp-paneInputButton, -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane button.dtsp-paneButton { - height: 35px; - width: 35px; - min-width: 0; - display: inline-block; - margin: 2px; - border: 0px solid transparent; - background-color: transparent; - margin-bottom: 0px; -} -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane input.dtsp-paneInputButton:hover, -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane button.dtsp-paneButton:hover { - background-color: #f0f0f0; +div.dt-datetime div.dt-datetime-iconLeft:hover, +div.dt-datetime div.dt-datetime-iconRight:hover { + border: 1px solid #ccc; border-radius: 2px; - cursor: pointer; -} -div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane button.dtsp-paneButton { + background-color: #f0f0f0; opacity: 0.6; } -div.dtsp-panesContainer button.dtsp-clearAll, -div.dtsp-panesContainer button.dtsp-collapseAll, -div.dtsp-panesContainer button.dtsp-showAll { - border: 1px solid transparent; - background-color: transparent; -} -div.dtsp-panesContainer button.dtsp-clearAll:hover, -div.dtsp-panesContainer button.dtsp-collapseAll:hover, -div.dtsp-panesContainer button.dtsp-showAll:hover { - background-color: #f0f0f0; - border-radius: 2px; +div.dt-datetime div.dt-datetime-iconLeft button, +div.dt-datetime div.dt-datetime-iconRight button { + border: none; + background: transparent; + text-indent: 30px; + height: 100%; + width: 100%; cursor: pointer; } -div.dtsp-panesContainer button.dtsp-disabledButton { - cursor: default !important; - color: #7c7c7c; +div.dt-datetime div.dt-datetime-iconLeft { + position: absolute; + top: 5px; + left: 5px; } -div.dtsp-panesContainer button.dtsp-disabledButton:hover { - background-color: transparent; +div.dt-datetime div.dt-datetime-iconLeft button { + position: relative; + z-index: 1; } -div.dtsp-panesContainer button.dtsp-disabledButton:focus { - outline: none; +div.dt-datetime div.dt-datetime-iconLeft:after { + position: absolute; + top: 7px; + left: 10px; + display: block; + content: ""; + border-top: 7px solid transparent; + border-right: 7px solid black; + border-bottom: 7px solid transparent; +} +div.dt-datetime div.dt-datetime-iconRight { + position: absolute; + top: 5px; + right: 5px; +} +div.dt-datetime div.dt-datetime-iconRight button { + position: relative; + z-index: 1; +} +div.dt-datetime div.dt-datetime-iconRight:after { + position: absolute; + top: 7px; + left: 12px; + display: block; + content: ""; + border-top: 7px solid transparent; + border-left: 7px solid black; + border-bottom: 7px solid transparent; } -div.dtsp-topRow.dtsp-bordered:hover button.dtsp-disabledButton { - cursor: pointer !important; +div.dt-datetime-error { + clear: both; + padding: 0 1em; + max-width: 240px; + font-size: 11px; + line-height: 1.25em; + text-align: center; + color: #b11f1f; +} + +html.dark input.dt-datetime { + color-scheme: dark; +} +html.dark div.dt-datetime { + border: 1px solid #595b5e; + background-color: #212529; + box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.8); +} +html.dark div.dt-datetime table th { + color: #ccc; +} +html.dark div.dt-datetime table td { + color: #eee; +} +html.dark div.dt-datetime table td.selectable { + background: #373c41; +} +html.dark div.dt-datetime table td.selectable.disabled { + color: #aaa; + background: #171b1f; +} +html.dark div.dt-datetime table td.selectable.disabled button:hover { + color: #aaa; + background: #171b1f; +} +html.dark div.dt-datetime table td.selectable.now { + background: #4b5055; +} +html.dark div.dt-datetime table td.selectable.selected button { + background: #6ea8fe; + color: black; +} +html.dark div.dt-datetime table td.selectable button:hover { + background: #ff8000; + color: black; +} +html.dark div.dt-datetime div.dt-datetime-label:hover { + border: 1px solid transparent; + background-color: rgba(255, 255, 255, 0.1); +} +html.dark div.dt-datetime div.dt-datetime-iconLeft:hover, +html.dark div.dt-datetime div.dt-datetime-iconRight:hover, +html.dark div.dt-datetime div.dt-datetime-iconUp:hover, +html.dark div.dt-datetime div.dt-datetime-iconDown:hover { + border: 1px solid transparent; + background-color: rgba(255, 255, 255, 0.1); +} +html.dark div.dt-datetime div.dt-datetime-iconLeft:after { + border-right-color: white; +} +html.dark div.dt-datetime div.dt-datetime-iconRight:after { + border-left-color: white; +} +html.dark div.dt-datetime select { + color-scheme: dark; +} +html.dark div.dt-datetime-error { + color: #b11f1f; +} + +table.dataTable thead tr > .dtfc-fixed-start, +table.dataTable thead tr > .dtfc-fixed-end, +table.dataTable tfoot tr > .dtfc-fixed-start, +table.dataTable tfoot tr > .dtfc-fixed-end { + top: 0; + bottom: 0; + z-index: 3; + background-color: white; +} +table.dataTable tbody tr > .dtfc-fixed-start, +table.dataTable tbody tr > .dtfc-fixed-end { + z-index: 1; + background-color: white; +} +table.dataTable tr > .dtfc-fixed-left::after, +table.dataTable tr > .dtfc-fixed-right::after { + position: absolute; + top: 0; + bottom: 0; + width: 10px; + transition: box-shadow 0.3s; + content: ""; pointer-events: none; } -div.dtsp-topRow.dtsp-bordered:hover input.dtsp-paneInputButton { - pointer-events: none; +table.dataTable tr > .dtfc-fixed-left::after { + right: 0; + transform: translateX(100%); +} +table.dataTable tr > .dtfc-fixed-right::after { + left: 0; + transform: translateX(-80%); +} +table.dataTable.dtfc-scrolling-left tr > .dtfc-fixed-left::after { + box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, 0.2); +} +table.dataTable.dtfc-scrolling-right tr > .dtfc-fixed-right::after { + box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, 0.2); +} +table.dataTable.dtfc-scrolling-right tr > .dtfc-fixed-right + .dtfc-fixed-right::after { + box-shadow: none; +} + +div.dt-scroll, +div.dtfh-floatingparent { + position: relative; +} +div.dt-scroll div.dtfc-top-blocker, +div.dt-scroll div.dtfc-bottom-blocker, +div.dtfh-floatingparent div.dtfc-top-blocker, +div.dtfh-floatingparent div.dtfc-bottom-blocker { + position: absolute; + background-color: white; +} + +html.dark table.dataTable thead tr > .dtfc-fixed-start, +html.dark table.dataTable thead tr > .dtfc-fixed-end, +html.dark table.dataTable tfoot tr > .dtfc-fixed-start, +html.dark table.dataTable tfoot tr > .dtfc-fixed-end { + background-color: var(--dt-html-background); +} +html.dark table.dataTable tbody tr > .dtfc-fixed-start, +html.dark table.dataTable tbody tr > .dtfc-fixed-end { + background-color: var(--dt-html-background); +} +html.dark table.dataTable.dtfc-scrolling-left tbody > tr > .dtfc-fixed-left::after { + box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, 0.3); +} +html.dark table.dataTable.dtfc-scrolling-right tbody > tr > .dtfc-fixed-right::after { + box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, 0.3); +} +html.dark table.dataTable.dtfc-scrolling-right tbody > tr > .dtfc-fixed-right + .dtfc-fixed-right::after { + box-shadow: none; +} +html.dark div.dtfc-top-blocker, +html.dark div.dtfc-bottom-blocker { + background-color: var(--dt-html-background); +} + + +table.dataTable.dtr-inline.collapsed > tbody > tr > td.child, +table.dataTable.dtr-inline.collapsed > tbody > tr > th.child, +table.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty { + cursor: default !important; +} +table.dataTable.dtr-inline.collapsed > tbody > tr > td.child:before, +table.dataTable.dtr-inline.collapsed > tbody > tr > th.child:before, +table.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty:before { + display: none !important; +} +table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control, +table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control { + cursor: pointer; +} +table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control:before, +table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control:before { + margin-right: 0.5em; + display: inline-block; + box-sizing: border-box; + content: ""; + border-top: 5px solid transparent; + border-left: 10px solid rgba(0, 0, 0, 0.5); + border-bottom: 5px solid transparent; + border-right: 0px solid transparent; +} +table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control.arrow-right::before, +table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control.arrow-right::before { + border-top: 5px solid transparent; + border-left: 0px solid transparent; + border-bottom: 5px solid transparent; + border-right: 10px solid rgba(0, 0, 0, 0.5); +} +table.dataTable.dtr-inline.collapsed > tbody > tr.dtr-expanded > td.dtr-control:before, +table.dataTable.dtr-inline.collapsed > tbody > tr.dtr-expanded > th.dtr-control:before { + border-top: 10px solid rgba(0, 0, 0, 0.5); + border-left: 5px solid transparent; + border-bottom: 0px solid transparent; + border-right: 5px solid transparent; +} +table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td.dtr-control, +table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th.dtr-control { + padding-left: 0.333em; +} +table.dataTable.dtr-column > tbody > tr > td.dtr-control, +table.dataTable.dtr-column > tbody > tr > th.dtr-control, +table.dataTable.dtr-column > tbody > tr > td.control, +table.dataTable.dtr-column > tbody > tr > th.control { + cursor: pointer; +} +table.dataTable.dtr-column > tbody > tr > td.dtr-control:before, +table.dataTable.dtr-column > tbody > tr > th.dtr-control:before, +table.dataTable.dtr-column > tbody > tr > td.control:before, +table.dataTable.dtr-column > tbody > tr > th.control:before { + display: inline-block; + box-sizing: border-box; + content: ""; + border-top: 5px solid transparent; + border-left: 10px solid rgba(0, 0, 0, 0.5); + border-bottom: 5px solid transparent; + border-right: 0px solid transparent; +} +table.dataTable.dtr-column > tbody > tr > td.dtr-control.arrow-right::before, +table.dataTable.dtr-column > tbody > tr > th.dtr-control.arrow-right::before, +table.dataTable.dtr-column > tbody > tr > td.control.arrow-right::before, +table.dataTable.dtr-column > tbody > tr > th.control.arrow-right::before { + border-top: 5px solid transparent; + border-left: 0px solid transparent; + border-bottom: 5px solid transparent; + border-right: 10px solid rgba(0, 0, 0, 0.5); +} +table.dataTable.dtr-column > tbody > tr.dtr-expanded td.dtr-control:before, +table.dataTable.dtr-column > tbody > tr.dtr-expanded th.dtr-control:before, +table.dataTable.dtr-column > tbody > tr.dtr-expanded td.control:before, +table.dataTable.dtr-column > tbody > tr.dtr-expanded th.control:before { + border-top: 10px solid rgba(0, 0, 0, 0.5); + border-left: 5px solid transparent; + border-bottom: 0px solid transparent; + border-right: 5px solid transparent; +} +table.dataTable > tbody > tr.child { + padding: 0.5em 1em; +} +table.dataTable > tbody > tr.child:hover { + background: transparent !important; +} +table.dataTable > tbody > tr.child ul.dtr-details { + display: inline-block; + list-style-type: none; + margin: 0; + padding: 0; +} +table.dataTable > tbody > tr.child ul.dtr-details > li { + border-bottom: 1px solid #efefef; + padding: 0.5em 0; +} +table.dataTable > tbody > tr.child ul.dtr-details > li:first-child { + padding-top: 0; +} +table.dataTable > tbody > tr.child ul.dtr-details > li:last-child { + padding-bottom: 0; + border-bottom: none; +} +table.dataTable > tbody > tr.child span.dtr-title { + display: inline-block; + min-width: 75px; + font-weight: bold; +} + +div.dtr-modal { + position: fixed; + box-sizing: border-box; + top: 0; + left: 0; + height: 100%; + width: 100%; + z-index: 100; + padding: 10em 1em; +} +div.dtr-modal div.dtr-modal-display { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + width: 50%; + height: fit-content; + max-height: 75%; + overflow: auto; + margin: auto; + z-index: 102; + overflow: auto; + background-color: #f5f5f7; + border: 1px solid black; + border-radius: 0.5em; + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6); +} +div.dtr-modal div.dtr-modal-content { + position: relative; + padding: 2.5em; +} +div.dtr-modal div.dtr-modal-content h2 { + margin-top: 0; +} +div.dtr-modal div.dtr-modal-close { + position: absolute; + top: 6px; + right: 6px; + width: 22px; + height: 22px; + text-align: center; + border-radius: 3px; + cursor: pointer; + z-index: 12; +} +div.dtr-modal div.dtr-modal-background { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 101; + background: rgba(0, 0, 0, 0.6); } @media screen and (max-width: 767px) { - div.dtsp-columns-4, -div.dtsp-columns-5, -div.dtsp-columns-6 { - max-width: 31% !important; - min-width: 31% !important; + div.dtr-modal div.dtr-modal-display { + width: 95%; } } -@media screen and (max-width: 640px) { - div.dtsp-searchPanes { - flex-direction: column !important; - } - - div.dtsp-searchPane { - max-width: 98% !important; - min-width: 98% !important; - } +html.dark table.dataTable > tbody > tr > td.dtr-control:before, +html[data-bs-theme=dark] table.dataTable > tbody > tr > td.dtr-control:before { + border-left-color: rgba(255, 255, 255, 0.5) !important; +} +html.dark table.dataTable > tbody > tr > td.dtr-control.arrow-right::before, +html[data-bs-theme=dark] table.dataTable > tbody > tr > td.dtr-control.arrow-right::before { + border-right-color: rgba(255, 255, 255, 0.5) !important; +} +html.dark table.dataTable > tbody > tr.dtr-expanded > td.dtr-control:before, +html.dark table.dataTable > tbody > tr.dtr-expanded > th.dtr-control:before, +html[data-bs-theme=dark] table.dataTable > tbody > tr.dtr-expanded > td.dtr-control:before, +html[data-bs-theme=dark] table.dataTable > tbody > tr.dtr-expanded > th.dtr-control:before { + border-top-color: rgba(255, 255, 255, 0.5) !important; + border-left-color: transparent !important; + border-right-color: transparent !important; +} +html.dark table.dataTable > tbody > tr.child ul.dtr-details > li, +html[data-bs-theme=dark] table.dataTable > tbody > tr.child ul.dtr-details > li { + border-bottom-color: rgb(64, 67, 70); +} +html.dark div.dtr-modal div.dtr-modal-display, +html[data-bs-theme=dark] div.dtr-modal div.dtr-modal-display { + background-color: rgb(33, 37, 41); + border: 1px solid rgba(255, 255, 255, 0.15); } -tfoot input { - width: 100%; - padding: 3px; - box-sizing: border-box; - } -tfoot { - display: table-header-group; + +table.dataTable tr.dtrg-group th { + background-color: rgba(0, 0, 0, 0.1); + text-align: left; } -.dataTables_length { -float: right; -text-align: right; +table.dataTable tr.dtrg-group.dtrg-level-0 th { + font-weight: bold; } -.dataTables_filter, .dataTables_info { display: none; } + +table.dataTable tr.dtrg-group.dtrg-level-1 th, +table.dataTable tr.dtrg-group.dtrg-level-2 th, +table.dataTable tr.dtrg-group.dtrg-level-3 th, +table.dataTable tr.dtrg-group.dtrg-level-4 th, +table.dataTable tr.dtrg-group.dtrg-level-5 th { + background-color: rgba(0, 0, 0, 0.05); + padding-top: 0.25em; + padding-bottom: 0.25em; + padding-left: 2em; + font-size: 0.9em; +} + +table.dataTable tr.dtrg-group.dtrg-level-2 th { + background-color: rgba(0, 0, 0, 0.01); + padding-left: 2.5em; +} + +table.dataTable tr.dtrg-group.dtrg-level-3 th { + background-color: rgba(0, 0, 0, 0.01); + padding-left: 3em; +} + +table.dataTable tr.dtrg-group.dtrg-level-4 th { + background-color: rgba(0, 0, 0, 0.01); + padding-left: 3.5em; +} + +table.dataTable tr.dtrg-group.dtrg-level-5 th { + background-color: rgba(0, 0, 0, 0.01); + padding-left: 4em; +} + +html.dark table.dataTable tr.dtrg-group th { + background-color: rgba(255, 255, 255, 0.1); +} +html.dark table.dataTable tr.dtrg-group.dtrg-level-1 th { + background-color: rgba(255, 255, 255, 0.05); +} +html.dark table.dataTable tr.dtrg-group.dtrg-level-2 th, +html.dark table.dataTable tr.dtrg-group.dtrg-level-3 th, +html.dark table.dataTable tr.dtrg-group.dtrg-level-4 th, +html.dark table.dataTable tr.dtrg-group.dtrg-level-5 th { + background-color: rgba(255, 255, 255, 0.01); +} + + diff --git a/org.idempiere.zk.datatable/src/web/js/datatables/datatables.js b/org.idempiere.zk.datatable/src/web/js/datatables/datatables.js index 73aa34b030..cdde37b5f3 100644 --- a/org.idempiere.zk.datatable/src/web/js/datatables/datatables.js +++ b/org.idempiere.zk.datatable/src/web/js/datatables/datatables.js @@ -4,37 +4,10740 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#dt/dt-1.12.1/b-2.2.3/b-colvis-2.2.3/cr-1.5.6/fh-3.2.4/sp-2.0.2 + * https://datatables.net/download/#dt/jq-3.7.0/dt-2.0.8/b-3.0.2/b-colvis-3.0.2/cr-2.0.3/date-1.5.2/fc-5.0.1/r-3.0.2/rg-1.5.0 * * Included libraries: - * DataTables 1.12.1, Buttons 2.2.3, Column visibility 2.2.3, ColReorder 1.5.6, FixedHeader 3.2.4, SearchPanes 2.0.2 + * jQuery 3 3.7.0, DataTables 2.0.8, Buttons 3.0.2, Column visibility 3.0.2, ColReorder 2.0.3, DateTime 1.5.2, FixedColumns 5.0.1, Responsive 3.0.2, RowGroup 1.5.0 */ -/*! DataTables 1.12.1 - * ©2008-2022 SpryMedia Ltd - datatables.net/license +/*! + * jQuery JavaScript Library v3.7.0 + * https://jquery.com/ + * + * Copyright OpenJS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2023-05-11T18:29Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket trac-14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + // Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5 + // Plus for old WebKit, typeof returns "function" for HTML collections + // (e.g., `typeof document.getElementsByTagName("div") === "function"`). (gh-4756) + return typeof obj === "function" && typeof obj.nodeType !== "number" && + typeof obj.item !== "function"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var version = "3.7.0", + + rhtmlSuffix = /HTML$/i, + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + + // Retrieve the text value of an array of DOM nodes + text: function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += jQuery.text( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + return elem.textContent; + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + isXMLDoc: function( elem ) { + var namespace = elem && elem.namespaceURI, + docElem = elem && ( elem.ownerDocument || elem ).documentElement; + + // Assume HTML when documentElement doesn't yet exist, such as inside + // document fragments. + return !rhtmlSuffix.test( namespace || docElem && docElem.nodeName || "HTML" ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), + function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); + } ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +} +var pop = arr.pop; + + +var sort = arr.sort; + + +var splice = arr.splice; + + +var whitespace = "[\\x20\\t\\r\\n\\f]"; + + +var rtrimCSS = new RegExp( + "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", + "g" +); + + + + +// Note: an element does not contain itself +jQuery.contains = function( a, b ) { + var bup = b && b.parentNode; + + return a === bup || !!( bup && bup.nodeType === 1 && ( + + // Support: IE 9 - 11+ + // IE doesn't have `contains` on SVG. + a.contains ? + a.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); +}; + + + + +// CSS string/identifier serialization +// https://drafts.csswg.org/cssom/#common-serializing-idioms +var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; + +function fcssescape( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; +} + +jQuery.escapeSelector = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + + + + +var preferredDoc = document, + pushNative = push; + +( function() { + +var i, + Expr, + outermostContext, + sortInput, + hasDuplicate, + push = pushNative, + + // Local document vars + document, + documentElement, + documentIsHTML, + rbuggyQSA, + matches, + + // Instance-specific data + expando = jQuery.expando, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|" + + "loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: https://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rleadingCombinator = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + + whitespace + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + ID: new RegExp( "^#(" + identifier + ")" ), + CLASS: new RegExp( "^\\.(" + identifier + ")" ), + TAG: new RegExp( "^(" + identifier + "|[*])" ), + ATTR: new RegExp( "^" + attributes ), + PSEUDO: new RegExp( "^" + pseudos ), + CHILD: new RegExp( + "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + bool: new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + needsContext: new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // https://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + if ( nonHex ) { + + // Strip the backslash prefix from a non-hex escape sequence + return nonHex; + } + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + return high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // Used for iframes; see `setDocument`. + // Support: IE 9 - 11+, Edge 12 - 18+ + // Removing the function wrapper causes a "Permission Denied" + // error in IE/Edge. + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && nodeName( elem, "fieldset" ); + }, + { dir: "parentNode", next: "legend" } + ); + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android <=4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { + apply: function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + }, + call: function( target ) { + pushNative.apply( target, slice.call( arguments, 1 ) ); + } + }; +} + +function find( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE 9 only + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + push.call( results, elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE 9 only + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + find.contains( context, elem ) && + elem.id === m ) { + + push.call( results, elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && context.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when + // strict-comparing two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( newContext != context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = jQuery.escapeSelector( nid ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrimCSS, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties + // (see https://github.com/jquery/sizzle/issues/157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by jQuery selector module + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + return nodeName( elem, "input" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + return ( nodeName( elem, "input" ) || nodeName( elem, "button" ) ) && + elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11+ + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a jQuery selector context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [node] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +function setDocument( node ) { + var subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + documentElement = document.documentElement; + documentIsHTML = !jQuery.isXMLDoc( document ); + + // Support: iOS 7 only, IE 9 - 11+ + // Older browsers didn't support unprefixed `matches`. + matches = documentElement.matches || + documentElement.webkitMatchesSelector || + documentElement.msMatchesSelector; + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (see trac-13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 9 - 11+, Edge 12 - 18+ + subWindow.addEventListener( "unload", unloadHandler ); + } + + // Support: IE <10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + documentElement.appendChild( el ).id = jQuery.expando; + return !document.getElementsByName || + !document.getElementsByName( jQuery.expando ).length; + } ); + + // Support: IE 9 only + // Check to see if it's possible to do matchesSelector + // on a disconnected node. + support.disconnectedMatch = assert( function( el ) { + return matches.call( el, "*" ); + } ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // IE/Edge don't support the :scope pseudo-class. + support.scope = assert( function() { + return document.querySelectorAll( ":scope" ); + } ); + + // Support: Chrome 105 - 111 only, Safari 15.4 - 16.3 only + // Make sure the `:has()` argument is parsed unforgivingly. + // We include `*` in the test to detect buggy implementations that are + // _selectively_ forgiving (specifically when the list includes at least + // one valid selector). + // Note that we treat complete lack of support for `:has()` as if it were + // spec-compliant support, which is fine because use of `:has()` in such + // environments will fail in the qSA path and fall back to jQuery traversal + // anyway. + support.cssHas = assert( function() { + try { + document.querySelector( ":has(*,:jqfake)" ); + return false; + } catch ( e ) { + return true; + } + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter.ID = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find.ID = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter.ID = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find.ID = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find.TAG = function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else { + return context.querySelectorAll( tag ); + } + }; + + // Class + Expr.find.CLASS = function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + rbuggyQSA = []; + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + documentElement.appendChild( el ).innerHTML = + "" + + ""; + + // Support: iOS <=7 - 8 only + // Boolean attributes and "value" are not treated correctly in some XML documents + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: iOS <=7 - 8 only + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: iOS 8 only + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+ + // In some of the document kinds, these selectors wouldn't work natively. + // This is probably OK but for backwards compatibility we want to maintain + // handling them through jQuery traversal in jQuery 3.x. + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE 9 - 11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+ + // In some of the document kinds, these selectors wouldn't work natively. + // This is probably OK but for backwards compatibility we want to maintain + // handling them through jQuery traversal in jQuery 3.x. + documentElement.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + } ); + + if ( !support.cssHas ) { + + // Support: Chrome 105 - 110+, Safari 15.4 - 16.3+ + // Our regular `try-catch` mechanism fails to detect natively-unsupported + // pseudo-classes inside `:has()` (such as `:has(:contains("Foo"))`) + // in browsers that parse the `:has()` argument as a forgiving selector list. + // https://drafts.csswg.org/selectors/#relational now requires the argument + // to be parsed unforgivingly, but browsers have not yet fully adjusted. + rbuggyQSA.push( ":has" ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a === document || a.ownerDocument == preferredDoc && + find.contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b === document || b.ownerDocument == preferredDoc && + find.contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + }; + + return document; +} + +find.matches = function( expr, elements ) { + return find( expr, null, null, elements ); +}; + +find.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return find( expr, document, null, [ elem ] ).length > 0; +}; + +find.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return jQuery.contains( context, elem ); +}; + + +find.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (see trac-13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + if ( val !== undefined ) { + return val; + } + + return elem.getAttribute( name ); +}; + +find.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +jQuery.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + // + // Support: Android <=4.0+ + // Testing for detecting duplicates is unpredictable so instead assume we can't + // depend on duplicate detection in all browsers without a stable sort. + hasDuplicate = !support.sortStable; + sortInput = !support.sortStable && slice.call( results, 0 ); + sort.call( results, sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + splice.call( results, duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +jQuery.fn.uniqueSort = function() { + return this.pushStack( jQuery.uniqueSort( slice.apply( this ) ) ); +}; + +Expr = jQuery.expr = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + ATTR: function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || match[ 5 ] || "" ) + .replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + CHILD: function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + find.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) + ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + find.error( match[ 0 ] ); + } + + return match; + }, + + PSEUDO: function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr.CHILD.test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + TAG: function( nodeNameSelector ) { + var expectedNodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return nodeName( elem, expectedNodeName ); + }; + }, + + CLASS: function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + ")" + className + + "(" + whitespace + "|$)" ) ) && + classCache( className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + ATTR: function( name, operator, check ) { + return function( elem ) { + var result = find.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + if ( operator === "=" ) { + return result === check; + } + if ( operator === "!=" ) { + return result !== check; + } + if ( operator === "^=" ) { + return check && result.indexOf( check ) === 0; + } + if ( operator === "*=" ) { + return check && result.indexOf( check ) > -1; + } + if ( operator === "$=" ) { + return check && result.slice( -check.length ) === check; + } + if ( operator === "~=" ) { + return ( " " + result.replace( rwhitespace, " " ) + " " ) + .indexOf( check ) > -1; + } + if ( operator === "|=" ) { + return result === check || result.slice( 0, check.length + 1 ) === check + "-"; + } + + return false; + }; + }, + + CHILD: function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + nodeName( node, name ) : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || ( parent[ expando ] = {} ); + cache = outerCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + cache = outerCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + nodeName( node, name ) : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + outerCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + PSEUDO: function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // https://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + find.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as jQuery does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + not: markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrimCSS, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element + // (see https://github.com/jquery/sizzle/issues/299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + has: markFunction( function( selector ) { + return function( elem ) { + return find( selector, elem ).length > 0; + }; + } ), + + contains: markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || jQuery.text( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // https://www.w3.org/TR/selectors/#lang-pseudo + lang: markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + find.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + target: function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + root: function( elem ) { + return elem === documentElement; + }, + + focus: function( elem ) { + return elem === safeActiveElement() && + document.hasFocus() && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + enabled: createDisabledPseudo( false ), + disabled: createDisabledPseudo( true ), + + checked: function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // https://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + return ( nodeName( elem, "input" ) && !!elem.checked ) || + ( nodeName( elem, "option" ) && !!elem.selected ); + }, + + selected: function( elem ) { + + // Support: IE <=11+ + // Accessing the selectedIndex property + // forces the browser to treat the default option as + // selected when in an optgroup. + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + empty: function( elem ) { + + // https://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + parent: function( elem ) { + return !Expr.pseudos.empty( elem ); + }, + + // Element/input types + header: function( elem ) { + return rheader.test( elem.nodeName ); + }, + + input: function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + button: function( elem ) { + return nodeName( elem, "input" ) && elem.type === "button" || + nodeName( elem, "button" ); + }, + + text: function( elem ) { + var attr; + return nodeName( elem, "input" ) && elem.type === "text" && + + // Support: IE <10 only + // New HTML5 attribute values (e.g., "search") appear + // with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + first: createPositionalPseudo( function() { + return [ 0 ]; + } ), + + last: createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + eq: createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + even: createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + odd: createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + lt: createPositionalPseudo( function( matchIndexes, length, argument ) { + var i; + + if ( argument < 0 ) { + i = argument + length; + } else if ( argument > length ) { + i = length; + } else { + i = argument; + } + + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + gt: createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos.nth = Expr.pseudos.eq; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rleadingCombinator.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrimCSS, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + if ( parseOnly ) { + return soFar.length; + } + + return soFar ? + find.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + if ( skip && nodeName( elem, skip ) ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = outerCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + outerCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + find( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, matcherOut, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || + multipleContexts( selector || "*", + context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems; + + if ( matcher ) { + + // If we have a postFinder, or filtered seed, or non-seed postFilter + // or preexisting results, + matcherOut = postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results; + + // Find primary matches + matcher( matcherIn, matcherOut, context, xml ); + } else { + matcherOut = matcherIn; + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf.call( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + var ret = ( !leadingRelative && ( xml || context != outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element + // (see https://github.com/jquery/sizzle/issues/299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrimCSS, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find.TAG( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: iOS <=7 - 9 only + // Tolerate NodeList properties (IE: "length"; Safari: ) matching + // elements by id. (see trac-14142) + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + push.call( results, elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + jQuery.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +function compile( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +} + +/** + * A low-level selection function that works with jQuery's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with jQuery selector compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find.ID( + token.matches[ 0 ].replace( runescape, funescape ), + context + ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr.needsContext.test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && + testContext( context.parentNode ) || context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +} + +// One-time assignments + +// Support: Android <=4.0 - 4.1+ +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Initialize against the default document +setDocument(); + +// Support: Android <=4.0 - 4.1+ +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +jQuery.find = find; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.unique = jQuery.uniqueSort; + +// These have always been private, but they used to be documented +// as part of Sizzle so let's maintain them in the 3.x line +// for backwards compatibility purposes. +find.compile = compile; +find.select = select; +find.setDocument = setDocument; + +find.escape = jQuery.escapeSelector; +find.getText = jQuery.text; +find.isXML = jQuery.isXMLDoc; +find.selectors = jQuery.expr; +find.support = jQuery.support; +find.uniqueSort = jQuery.uniqueSort; + + /* eslint-enable */ + +} )(); + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (trac-9521) + // Strict HTML recognition (trac-11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to jQuery#find + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.error ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the error, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getErrorHook ) { + process.error = jQuery.Deferred.getErrorHook(); + + // The deprecated alias of the above. While the name suggests + // returning the stack, not an error instance, jQuery just passes + // it directly to `console.warn` so both will work; an instance + // just better cooperates with source maps. + } else if ( jQuery.Deferred.getStackHook ) { + process.error = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the primary Deferred + primary = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + primary.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( primary.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return primary.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject ); + } + + return primary.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +// If `jQuery.Deferred.getErrorHook` is defined, `asyncError` is an error +// captured before the async barrier to get the original error cause +// which may otherwise be hidden. +jQuery.Deferred.exceptionHook = function( error, asyncError ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, + error.stack, asyncError ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See trac-6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (trac-9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see trac-8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (trac-14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (trac-11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (trac-14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (trac-13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (trac-15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (trac-12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (trac-13208) + // Don't process clicks on disabled elements (trac-6911, trac-8165, trac-11382, trac-11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (trac-13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", true ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, isSetup ) { + + // Missing `isSetup` indicates a trigger call, which must force setup through jQuery.event.add + if ( !isSetup ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + if ( !saved ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + this[ type ](); + result = dataPriv.get( this, type ); + dataPriv.set( this, type, false ); + + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + + return result; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering + // the native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved ) { + + // ...and capture the result + dataPriv.set( this, type, jQuery.event.trigger( + saved[ 0 ], + saved.slice( 1 ), + this + ) ); + + // Abort handling of the native event by all jQuery handlers while allowing + // native handlers on the same element to run. On target, this is achieved + // by stopping immediate propagation just on the jQuery event. However, + // the native event is re-wrapped by a jQuery one on each level of the + // propagation so the only way to stop it for jQuery is to stop it for + // everyone via native `stopPropagation()`. This is not a problem for + // focus/blur which don't bubble, but it does also stop click on checkboxes + // and radios. We accept this limitation. + event.stopPropagation(); + event.isImmediatePropagationStopped = returnTrue; + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (trac-504, trac-13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + which: true +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + + function focusMappedHandler( nativeEvent ) { + if ( document.documentMode ) { + + // Support: IE 11+ + // Attach a single focusin/focusout handler on the document while someone wants + // focus/blur. This is because the former are synchronous in IE while the latter + // are async. In other browsers, all those handlers are invoked synchronously. + + // `handle` from private data would already wrap the event, but we need + // to change the `type` here. + var handle = dataPriv.get( this, "handle" ), + event = jQuery.event.fix( nativeEvent ); + event.type = nativeEvent.type === "focusin" ? "focus" : "blur"; + event.isSimulated = true; + + // First, handle focusin/focusout + handle( nativeEvent ); + + // ...then, handle focus/blur + // + // focus/blur don't bubble while focusin/focusout do; simulate the former by only + // invoking the handler at the lower level. + if ( event.target === event.currentTarget ) { + + // The setup part calls `leverageNative`, which, in turn, calls + // `jQuery.event.add`, so event handle will already have been set + // by this point. + handle( event ); + } + } else { + + // For non-IE browsers, attach a single capturing handler on the document + // while someone wants focusin/focusout. + jQuery.event.simulate( delegateType, nativeEvent.target, + jQuery.event.fix( nativeEvent ) ); + } + } + + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + var attaches; + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, true ); + + if ( document.documentMode ) { + + // Support: IE 9 - 11+ + // We use the same native handler for focusin & focus (and focusout & blur) + // so we need to coordinate setup & teardown parts between those events. + // Use `delegateType` as the key as `type` is already used by `leverageNative`. + attaches = dataPriv.get( this, delegateType ); + if ( !attaches ) { + this.addEventListener( delegateType, focusMappedHandler ); + } + dataPriv.set( this, delegateType, ( attaches || 0 ) + 1 ); + } else { + + // Return false to allow normal processing in the caller + return false; + } + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + teardown: function() { + var attaches; + + if ( document.documentMode ) { + attaches = dataPriv.get( this, delegateType ) - 1; + if ( !attaches ) { + this.removeEventListener( delegateType, focusMappedHandler ); + dataPriv.remove( this, delegateType ); + } else { + dataPriv.set( this, delegateType, attaches ); + } + } else { + + // Return false to indicate standard teardown should be applied + return false; + } + }, + + // Suppress native focus or blur if we're currently inside + // a leveraged native-event stack + _default: function( event ) { + return dataPriv.get( event.target, type ); + }, + + delegateType: delegateType + }; + + // Support: Firefox <=44 + // Firefox doesn't have focus(in | out) events + // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 + // + // Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 + // focus(in | out) events fire after focus & blur events, + // which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order + // Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 + // + // Support: IE 9 - 11+ + // To preserve relative focusin/focus & focusout/blur event order guaranteed on the 3.x branch, + // attach a single handler for both events in IE. + jQuery.event.special[ delegateType ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + dataHolder = document.documentMode ? this : doc, + attaches = dataPriv.get( dataHolder, delegateType ); + + // Support: IE 9 - 11+ + // We use the same native handler for focusin & focus (and focusout & blur) + // so we need to coordinate setup & teardown parts between those events. + // Use `delegateType` as the key as `type` is already used by `leverageNative`. + if ( !attaches ) { + if ( document.documentMode ) { + this.addEventListener( delegateType, focusMappedHandler ); + } else { + doc.addEventListener( type, focusMappedHandler, true ); + } + } + dataPriv.set( dataHolder, delegateType, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + dataHolder = document.documentMode ? this : doc, + attaches = dataPriv.get( dataHolder, delegateType ) - 1; + + if ( !attaches ) { + if ( document.documentMode ) { + this.removeEventListener( delegateType, focusMappedHandler ); + } else { + doc.removeEventListener( type, focusMappedHandler, true ); + } + dataPriv.remove( dataHolder, delegateType ); + } else { + dataPriv.set( dataHolder, delegateType, attaches ); + } + } + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (trac-8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + + // Unwrap a CDATA section containing script contents. This shouldn't be + // needed as in XML documents they're already not visible when + // inspecting element contents and in HTML documents they have no + // meaning but we're preserving that logic for backwards compatibility. + // This will be removed completely in 4.0. See gh-4904. + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew jQuery#find here for performance reasons: + // https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var rcustomProp = /^--/; + + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (trac-15098, trac-14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (trac-8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + // + // Support: Firefox 70+ + // Only Firefox includes border widths + // in computed dimensions. (gh-4529) + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate"; + tr.style.cssText = "border:1px solid"; + + // Support: Chrome 86+ + // Height set through cssText does not get applied. + // Computed height then comes back as 0. + tr.style.height = "1px"; + trChild.style.height = "9px"; + + // Support: Android 8 Chrome 86+ + // In our bodyBackground.html iframe, + // display for all div elements is set to "inline", + // which causes a problem only in Android 8 Chrome 86. + // Ensuring the div is display: block + // gets around this issue. + trChild.style.display = "block"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = ( parseInt( trStyle.height, 10 ) + + parseInt( trStyle.borderTopWidth, 10 ) + + parseInt( trStyle.borderBottomWidth, 10 ) ) === tr.offsetHeight; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + isCustomProp = rcustomProp.test( name ), + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, trac-12537) + // .css('--customProperty) (gh-3144) + if ( computed ) { + + // Support: IE <=9 - 11+ + // IE only supports `"float"` in `getPropertyValue`; in computed styles + // it's only available as `"cssFloat"`. We no longer modify properties + // sent to `.css()` apart from camelCasing, so we need to check both. + // Normally, this would create difference in behavior: if + // `getPropertyValue` returns an empty string, the value returned + // by `.css()` would be `undefined`. This is usually the case for + // disconnected elements. However, in IE even disconnected elements + // with no styles return `"none"` for `getPropertyValue( "float" )` + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( isCustomProp && ret ) { + + // Support: Firefox 105+, Chrome <=105+ + // Spec requires trimming whitespace for custom properties (gh-4926). + // Firefox only trims leading whitespace. Chrome just collapses + // both leading & trailing whitespace to a single space. + // + // Fall back to `undefined` if empty string returned. + // This collapses a missing definition with property defined + // and set to an empty string but there's no standard API + // allowing us to differentiate them without a performance penalty + // and returning `undefined` aligns with older jQuery. + // + // rtrimCSS treats U+000D CARRIAGE RETURN and U+000C FORM FEED + // as whitespace while CSS does not, but this is not a problem + // because CSS preprocessing replaces them with U+000A LINE FEED + // (which *is* CSS whitespace) + // https://www.w3.org/TR/css-syntax-3/#input-preprocessing + ret = ret.replace( rtrimCSS, "$1" ) || undefined; + } + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0, + marginDelta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + // Count margin delta separately to only add it after scroll gutter adjustment. + // This is needed to make negative margins work with `outerHeight( true )` (gh-3982). + if ( box === "margin" ) { + marginDelta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta + marginDelta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + animationIterationCount: true, + aspectRatio: true, + borderImageSlice: true, + columnCount: true, + flexGrow: true, + flexShrink: true, + fontWeight: true, + gridArea: true, + gridColumn: true, + gridColumnEnd: true, + gridColumnStart: true, + gridRow: true, + gridRowEnd: true, + gridRowStart: true, + lineHeight: true, + opacity: true, + order: true, + orphans: true, + scale: true, + widows: true, + zIndex: true, + zoom: true, + + // SVG-related + fillOpacity: true, + floodOpacity: true, + stopOpacity: true, + strokeMiterlimit: true, + strokeOpacity: true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (trac-7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug trac-9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (trac-7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (trac-12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // Use proper attribute retrieval (trac-12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classNames, cur, curValue, className, i, finalValue; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classNames = classesToArray( value ); + + if ( classNames.length ) { + return this.each( function() { + curValue = getClass( this ); + cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; + if ( cur.indexOf( " " + className + " " ) < 0 ) { + cur += className + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + this.setAttribute( "class", finalValue ); + } + } + } ); + } + + return this; + }, + + removeClass: function( value ) { + var classNames, cur, curValue, className, i, finalValue; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classNames = classesToArray( value ); + + if ( classNames.length ) { + return this.each( function() { + curValue = getClass( this ); + + // This expression is here for better compressibility (see addClass) + cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; + + // Remove *all* instances + while ( cur.indexOf( " " + className + " " ) > -1 ) { + cur = cur.replace( " " + className + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + this.setAttribute( "class", finalValue ); + } + } + } ); + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var classNames, className, i, self, + type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + classNames = classesToArray( value ); + + return this.each( function() { + if ( isValidValue ) { + + // Toggle individual class names + self = jQuery( this ); + + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (trac-14686, trac-14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (trac-2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml, parserErrorElem; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) {} + + parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ]; + if ( !xml || parserErrorElem ) { + jQuery.error( "Invalid XML: " + ( + parserErrorElem ? + jQuery.map( parserErrorElem.childNodes, function( el ) { + return el.textContent; + } ).join( "\n" ) : + data + ) ); + } + return xml; +}; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (trac-9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (trac-9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (trac-6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ).filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ).map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // trac-7653, trac-8125, trac-8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (trac-10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + +originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes trac-9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (trac-10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket trac-12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (trac-15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // trac-9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script but not if jsonp + if ( !isSuccess && + jQuery.inArray( "script", s.dataTypes ) > -1 && + jQuery.inArray( "json", s.dataTypes ) < 0 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (trac-11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // trac-1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see trac-8605, trac-14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // trac-14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( "