From a41aeb2205f4960b371db96a77ce03bbb1c10c40 Mon Sep 17 00:00:00 2001 From: hengsin Date: Thu, 12 May 2022 17:12:05 +0800 Subject: [PATCH] IDEMPIERE-5057 Implement Deductible and non deductible input tax for purchasing and costing (#1277) * IDEMPIERE-5057 Implement Deductible and non deductible input tax for purchasing and costing * IDEMPIERE-5057 Implement Deductible and non deductible input tax for purchasing and costing * IDEMPIERE-5057 Implement Deductible and non deductible input tax for purchasing and costing - Fix rounding error --- .../oracle/202203290700_IDEMPIERE-5057.sql | 89 +++++ .../oracle/202204011738_IDEMPIERE-5057.sql | 36 ++ .../202203290700_IDEMPIERE-5057.sql | 87 +++++ .../202204011738_IDEMPIERE-5057.sql | 33 ++ .../src/org/adempiere/model/ITaxProvider.java | 82 +++++ .../src/org/compiere/acct/Doc_InOut.java | 95 ++++- .../src/org/compiere/acct/Doc_Invoice.java | 81 ++++- .../src/org/compiere/acct/Doc_MatchInv.java | 52 +++ .../src/org/compiere/acct/Doc_MatchPO.java | 52 ++- .../src/org/compiere/model/I_C_Tax.java | 19 +- .../src/org/compiere/model/MInvoiceLine.java | 49 ++- .../src/org/compiere/model/MInvoiceTax.java | 70 ++++ .../src/org/compiere/model/MMatchPO.java | 12 + .../src/org/compiere/model/MOrderLine.java | 58 ++- .../src/org/compiere/model/MOrderTax.java | 95 +++++ .../src/org/compiere/model/MRMALine.java | 65 +++- .../src/org/compiere/model/MRMATax.java | 92 +++++ .../src/org/compiere/model/MTax.java | 8 + .../src/org/compiere/model/X_C_Tax.java | 26 +- .../org/idempiere/test/model/MTaxTest.java | 331 +++++++++++++++++- 20 files changed, 1365 insertions(+), 67 deletions(-) create mode 100644 migration/iD10/oracle/202203290700_IDEMPIERE-5057.sql create mode 100644 migration/iD10/oracle/202204011738_IDEMPIERE-5057.sql create mode 100644 migration/iD10/postgresql/202203290700_IDEMPIERE-5057.sql create mode 100644 migration/iD10/postgresql/202204011738_IDEMPIERE-5057.sql diff --git a/migration/iD10/oracle/202203290700_IDEMPIERE-5057.sql b/migration/iD10/oracle/202203290700_IDEMPIERE-5057.sql new file mode 100644 index 0000000000..4192d0c07c --- /dev/null +++ b/migration/iD10/oracle/202203290700_IDEMPIERE-5057.sql @@ -0,0 +1,89 @@ +SELECT register_migration_script('202203290700_IDEMPIERE-5057.sql') FROM dual +; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Mar 28, 2022 3:27:22 PM MYT +-- IDEMPIERE-5057 Implement Deductible and non deductible input tax for purchasing and costing +INSERT INTO AD_Element (AD_Element_ID,ColumnName,Updated,Name,PrintName,AD_Element_UU,IsActive,Created,CreatedBy,UpdatedBy,AD_Client_ID,EntityType,AD_Org_ID) VALUES (203283,'TaxPostingIndicator',TO_DATE('2022-03-28 15:27:21','YYYY-MM-DD HH24:MI:SS'),'Posting Indicator','Posting Indicator','6de750b3-2872-44ba-a78b-94fa6af96acf','Y',TO_DATE('2022-03-28 15:27:21','YYYY-MM-DD HH24:MI:SS'),100,100,0,'D',0) +; + +-- Mar 28, 2022 3:32:00 PM MYT +INSERT INTO AD_Reference (AD_Reference_ID,Name,AD_Reference_UU,IsOrderByValue,ValidationType,Updated,IsActive,CreatedBy,UpdatedBy,AD_Client_ID,Created,EntityType,AD_Org_ID) VALUES (200160,'C_Tax Posting Indicator','429065e5-80c4-458e-a7ae-d20761dcb5a8','N','L',TO_DATE('2022-03-28 15:31:59','YYYY-MM-DD HH24:MI:SS'),'Y',100,100,0,TO_DATE('2022-03-28 15:31:59','YYYY-MM-DD HH24:MI:SS'),'D',0) +; + +-- Mar 28, 2022 3:35:42 PM MYT +INSERT INTO AD_Ref_List (AD_Ref_List_ID,Description,AD_Ref_List_UU,Name,IsActive,CreatedBy,UpdatedBy,AD_Client_ID,Created,Updated,EntityType,AD_Reference_ID,Value,AD_Org_ID) VALUES (200446,'Tax is calculated on the full amount of the item and posted separately.','e84b618c-a8b3-47cf-89a6-dd674e52d3e4','Separate Tax Posting','Y',100,100,0,TO_DATE('2022-03-28 15:35:41','YYYY-MM-DD HH24:MI:SS'),TO_DATE('2022-03-28 15:35:41','YYYY-MM-DD HH24:MI:SS'),'D',200160,'0',0) +; + +-- Mar 28, 2022 3:37:59 PM MYT +INSERT INTO AD_Ref_List (AD_Ref_List_ID,Description,AD_Ref_List_UU,Name,IsActive,CreatedBy,UpdatedBy,AD_Client_ID,Created,Updated,EntityType,AD_Reference_ID,Value,AD_Org_ID) VALUES (200447,'Tax amount is added to the item amount during account posting time and for updating of Product Cost.','3e8e0d29-29ac-4c67-ae2c-92c0ee43d2e6','Distribute Tax with Relevant Expense','Y',100,100,0,TO_DATE('2022-03-28 15:37:58','YYYY-MM-DD HH24:MI:SS'),TO_DATE('2022-03-28 15:37:58','YYYY-MM-DD HH24:MI:SS'),'D',200160,'1',0) +; + +-- Mar 28, 2022 3:38:05 PM MYT +UPDATE AD_Reference SET IsOrderByValue='Y',Updated=TO_DATE('2022-03-28 15:38:05','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Reference_ID=200160 +; + +-- Mar 28, 2022 3:42:45 PM MYT +INSERT INTO AD_Column (AD_Column_ID,SeqNoSelection,IsSyncDatabase,Version,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsParent,FieldLength,IsSelectionColumn,IsKey,ReadOnlyLogic,IsAutocomplete,IsAllowLogging,MandatoryLogic,AD_Column_UU,Updated,IsUpdateable,ColumnName,DefaultValue,Name,IsAllowCopy,IsActive,CreatedBy,UpdatedBy,IsAlwaysUpdateable,AD_Client_ID,Created,EntityType,IsEncrypted,IsSecure,FKConstraintType,AD_Element_ID,AD_Reference_Value_ID,AD_Table_ID,AD_Reference_ID,IsToolbarButton,AD_Org_ID,IsHtml) VALUES (213805,0,'N',0,'N','N','N',0,'N',1,'N','N','@IsSummary@=Y','N','Y','@IsSummary@=N','e7a97d28-f550-4253-a77b-b6fab1e7b38a',TO_DATE('2022-03-28 15:42:44','YYYY-MM-DD HH24:MI:SS'),'Y','TaxPostingIndicator','0','Posting Indicator','Y','Y',100,100,'N',0,TO_DATE('2022-03-28 15:42:44','YYYY-MM-DD HH24:MI:SS'),'D','N','N','N',203283,200160,261,17,'N',0,'N') +; + +-- Mar 28, 2022 3:42:52 PM MYT +INSERT INTO AD_TreeNode (AD_Client_ID,AD_Org_ID, IsActive,Created,CreatedBy,Updated,UpdatedBy, AD_Tree_ID, Node_ID, Parent_ID, SeqNo, AD_TreeNode_UU) SELECT t.AD_Client_ID, 0, 'Y', SysDate, 100, SysDate, 100,t.AD_Tree_ID, 200012, 0, 999, Generate_UUID() FROM AD_Tree t WHERE t.AD_Client_ID=0 AND t.IsActive='Y' AND t.IsAllNodes='Y' AND t.TreeType='TL' AND t.AD_Table_ID=282 AND NOT EXISTS (SELECT * FROM AD_TreeNode e WHERE e.AD_Tree_ID=t.AD_Tree_ID AND Node_ID=200012) +; + +-- Mar 28, 2022 3:42:53 PM MYT +ALTER TABLE C_Tax ADD TaxPostingIndicator CHAR(1) DEFAULT '0' +; + +-- Mar 28, 2022 3:55:05 PM MYT +INSERT INTO AD_Field (SortNo,AD_Field_ID,IsEncrypted,DisplayLength,IsSameLine,IsHeading,SeqNo,IsCentrallyMaintained,IsReadOnly,DisplayLogic,Updated,Name,AD_Field_UU,IsDisplayed,IsFieldOnly,CreatedBy,UpdatedBy,IsActive,IsDisplayedGrid,SeqNoGrid,XPosition,IsQuickEntry,AD_Client_ID,Created,ColumnSpan,NumLines,IsAdvancedField,IsDefaultFocus,AD_Column_ID,EntityType,AD_Tab_ID,AD_Org_ID) VALUES (0,205862,'N',0,'N','N',1010,'Y','N','@IsDocumentLevel@=N',TO_DATE('2022-03-28 15:55:04','YYYY-MM-DD HH24:MI:SS'),'Posting Indicator','96a2af5a-4a6f-4d44-ab54-da4713d70fc5','Y','N',100,100,'Y','Y',1010,4,'N',0,TO_DATE('2022-03-28 15:55:04','YYYY-MM-DD HH24:MI:SS'),2,1,'N','N',213805,'D',174,0) +; + +-- Mar 28, 2022 3:55:33 PM MYT +UPDATE AD_Field SET SeqNo=180,SeqNoGrid=180,IsDisplayed='Y', Updated=Now(), UpdatedBy=100 WHERE AD_Field_ID=205862 +; + +-- Mar 28, 2022 3:55:33 PM MYT +UPDATE AD_Field SET SeqNo=190,SeqNoGrid=190,IsDisplayed='Y', Updated=Now(), UpdatedBy=100 WHERE AD_Field_ID=203325 +; + +-- Mar 28, 2022 3:55:33 PM MYT +UPDATE AD_Field SET SeqNo=200,SeqNoGrid=200,IsDisplayed='Y', Updated=Now(), UpdatedBy=100 WHERE AD_Field_ID=203326 +; + +-- Mar 28, 2022 3:55:33 PM MYT +UPDATE AD_Field SET SeqNo=210,SeqNoGrid=210,IsDisplayed='Y', Updated=Now(), UpdatedBy=100 WHERE AD_Field_ID=974 +; + +-- Mar 28, 2022 3:55:33 PM MYT +UPDATE AD_Field SET SeqNo=220,SeqNoGrid=220,IsDisplayed='Y', Updated=Now(), UpdatedBy=100 WHERE AD_Field_ID=976 +; + +-- Mar 28, 2022 3:55:33 PM MYT +UPDATE AD_Field SET SeqNo=230,SeqNoGrid=230,IsDisplayed='Y', Updated=Now(), UpdatedBy=100 WHERE AD_Field_ID=975 +; + +UPDATE AD_Field SET SeqNo=240,SeqNoGrid=240,IsDisplayed='Y', Updated=Now(), UpdatedBy=100 WHERE AD_Field_ID=977 +; + +UPDATE AD_Field SET SeqNo=250,SeqNoGrid=250,IsDisplayed='Y', Updated=Now(), UpdatedBy=100 WHERE AD_Field_ID=202402 +; + +-- Mar 28, 2022 3:56:15 PM MYT +UPDATE AD_Column SET ReadOnlyLogic='@IsDocumentLevel@=Y', MandatoryLogic='@IsDocumentLevel@=N',Updated=TO_DATE('2022-03-28 15:56:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=213805 +; + +-- Mar 28, 2022 6:43:36 PM MYT +UPDATE AD_Field SET DisplayLogic='@IsDocumentLevel@=N & @IsSummary@=N', AD_Val_Rule_ID=NULL, AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_DATE('2022-03-28 18:43:36','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=205862 +; + +-- Mar 28, 2022 8:10:21 PM MYT +UPDATE AD_Field SET DisplayLogic='@IsDocumentLevel@=N & @IsSummary@=N & @SOPOType@!S', AD_Val_Rule_ID=NULL, AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_DATE('2022-03-28 20:10:21','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=205862 +; + +-- Mar 28, 2022 8:10:34 PM MYT +INSERT INTO AD_TreeNode (AD_Client_ID,AD_Org_ID, IsActive,Created,CreatedBy,Updated,UpdatedBy, AD_Tree_ID, Node_ID, Parent_ID, SeqNo, AD_TreeNode_UU) SELECT t.AD_Client_ID, 0, 'Y', SysDate, 100, SysDate, 100,t.AD_Tree_ID, 200014, 0, 999, Generate_UUID() FROM AD_Tree t WHERE t.AD_Client_ID=0 AND t.IsActive='Y' AND t.IsAllNodes='Y' AND t.TreeType='TL' AND t.AD_Table_ID=282 AND NOT EXISTS (SELECT * FROM AD_TreeNode e WHERE e.AD_Tree_ID=t.AD_Tree_ID AND Node_ID=200014) +; + diff --git a/migration/iD10/oracle/202204011738_IDEMPIERE-5057.sql b/migration/iD10/oracle/202204011738_IDEMPIERE-5057.sql new file mode 100644 index 0000000000..336fea2f65 --- /dev/null +++ b/migration/iD10/oracle/202204011738_IDEMPIERE-5057.sql @@ -0,0 +1,36 @@ +-- IDEMPIERE-5057 Implement Deductible and non deductible input tax for purchasing and costing +SELECT register_migration_script('202204011738_IDEMPIERE-5057.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Apr 1, 2022, 5:38:03 PM MYT +UPDATE AD_Element SET Description='Type of input tax (deductible and non deductible)', Help='Separate Tax Posting: Tax is calculated on the full amount of the item and posted separately. +Distribute Tax with Relevant Expense: Tax amount is added to the item amount during account posting time and for updating of Product Cost.',Updated=TO_TIMESTAMP('2022-04-01 17:38:03','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Element_ID=203283 +; + +-- Apr 1, 2022, 5:38:03 PM MYT +UPDATE AD_Column SET ColumnName='TaxPostingIndicator', Name='Posting Indicator', Description='Type of input tax (deductible and non deductible)', Help='Separate Tax Posting: Tax is calculated on the full amount of the item and posted separately. +Distribute Tax with Relevant Expense: Tax amount is added to the item amount during account posting time and for updating of Product Cost.', Placeholder=NULL WHERE AD_Element_ID=203283 +; + +-- Apr 1, 2022, 5:38:03 PM MYT +UPDATE AD_Process_Para SET ColumnName='TaxPostingIndicator', Name='Posting Indicator', Description='Type of input tax (deductible and non deductible)', Help='Separate Tax Posting: Tax is calculated on the full amount of the item and posted separately. +Distribute Tax with Relevant Expense: Tax amount is added to the item amount during account posting time and for updating of Product Cost.', AD_Element_ID=203283 WHERE UPPER(ColumnName)='TAXPOSTINGINDICATOR' AND IsCentrallyMaintained='Y' AND AD_Element_ID IS NULL +; + +-- Apr 1, 2022, 5:38:03 PM MYT +UPDATE AD_Process_Para SET ColumnName='TaxPostingIndicator', Name='Posting Indicator', Description='Type of input tax (deductible and non deductible)', Help='Separate Tax Posting: Tax is calculated on the full amount of the item and posted separately. +Distribute Tax with Relevant Expense: Tax amount is added to the item amount during account posting time and for updating of Product Cost.', Placeholder=NULL WHERE AD_Element_ID=203283 AND IsCentrallyMaintained='Y' +; + +-- Apr 1, 2022, 5:38:03 PM MYT +UPDATE AD_InfoColumn SET ColumnName='TaxPostingIndicator', Name='Posting Indicator', Description='Type of input tax (deductible and non deductible)', Help='Separate Tax Posting: Tax is calculated on the full amount of the item and posted separately. +Distribute Tax with Relevant Expense: Tax amount is added to the item amount during account posting time and for updating of Product Cost.', Placeholder=NULL WHERE AD_Element_ID=203283 AND IsCentrallyMaintained='Y' +; + +-- Apr 1, 2022, 5:38:03 PM MYT +UPDATE AD_Field SET Name='Posting Indicator', Description='Type of input tax (deductible and non deductible)', Help='Separate Tax Posting: Tax is calculated on the full amount of the item and posted separately. +Distribute Tax with Relevant Expense: Tax amount is added to the item amount during account posting time and for updating of Product Cost.', Placeholder=NULL WHERE AD_Column_ID IN (SELECT AD_Column_ID FROM AD_Column WHERE AD_Element_ID=203283) AND IsCentrallyMaintained='Y' +; + diff --git a/migration/iD10/postgresql/202203290700_IDEMPIERE-5057.sql b/migration/iD10/postgresql/202203290700_IDEMPIERE-5057.sql new file mode 100644 index 0000000000..151e14194a --- /dev/null +++ b/migration/iD10/postgresql/202203290700_IDEMPIERE-5057.sql @@ -0,0 +1,87 @@ +SELECT register_migration_script('202203290700_IDEMPIERE-5057.sql') FROM dual +; + +-- Mar 28, 2022 3:27:22 PM MYT +-- IDEMPIERE-5057 Implement Deductible and non deductible input tax for purchasing and costing +INSERT INTO AD_Element (AD_Element_ID,ColumnName,Updated,Name,PrintName,AD_Element_UU,IsActive,Created,CreatedBy,UpdatedBy,AD_Client_ID,EntityType,AD_Org_ID) VALUES (203283,'TaxPostingIndicator',TO_TIMESTAMP('2022-03-28 15:27:21','YYYY-MM-DD HH24:MI:SS'),'Posting Indicator','Posting Indicator','6de750b3-2872-44ba-a78b-94fa6af96acf','Y',TO_TIMESTAMP('2022-03-28 15:27:21','YYYY-MM-DD HH24:MI:SS'),100,100,0,'D',0) +; + +-- Mar 28, 2022 3:32:00 PM MYT +INSERT INTO AD_Reference (AD_Reference_ID,Name,AD_Reference_UU,IsOrderByValue,ValidationType,Updated,IsActive,CreatedBy,UpdatedBy,AD_Client_ID,Created,EntityType,AD_Org_ID) VALUES (200160,'C_Tax Posting Indicator','429065e5-80c4-458e-a7ae-d20761dcb5a8','N','L',TO_TIMESTAMP('2022-03-28 15:31:59','YYYY-MM-DD HH24:MI:SS'),'Y',100,100,0,TO_TIMESTAMP('2022-03-28 15:31:59','YYYY-MM-DD HH24:MI:SS'),'D',0) +; + +-- Mar 28, 2022 3:35:42 PM MYT +INSERT INTO AD_Ref_List (AD_Ref_List_ID,Description,AD_Ref_List_UU,Name,IsActive,CreatedBy,UpdatedBy,AD_Client_ID,Created,Updated,EntityType,AD_Reference_ID,Value,AD_Org_ID) VALUES (200446,'Tax is calculated on the full amount of the item and posted separately.','e84b618c-a8b3-47cf-89a6-dd674e52d3e4','Separate Tax Posting','Y',100,100,0,TO_TIMESTAMP('2022-03-28 15:35:41','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2022-03-28 15:35:41','YYYY-MM-DD HH24:MI:SS'),'D',200160,'0',0) +; + +-- Mar 28, 2022 3:37:59 PM MYT +INSERT INTO AD_Ref_List (AD_Ref_List_ID,Description,AD_Ref_List_UU,Name,IsActive,CreatedBy,UpdatedBy,AD_Client_ID,Created,Updated,EntityType,AD_Reference_ID,Value,AD_Org_ID) VALUES (200447,'Tax amount is added to the item amount during account posting time and for updating of Product Cost.','3e8e0d29-29ac-4c67-ae2c-92c0ee43d2e6','Distribute Tax with Relevant Expense','Y',100,100,0,TO_TIMESTAMP('2022-03-28 15:37:58','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2022-03-28 15:37:58','YYYY-MM-DD HH24:MI:SS'),'D',200160,'1',0) +; + +-- Mar 28, 2022 3:38:05 PM MYT +UPDATE AD_Reference SET IsOrderByValue='Y',Updated=TO_TIMESTAMP('2022-03-28 15:38:05','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Reference_ID=200160 +; + +-- Mar 28, 2022 3:42:45 PM MYT +INSERT INTO AD_Column (AD_Column_ID,SeqNoSelection,IsSyncDatabase,Version,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsParent,FieldLength,IsSelectionColumn,IsKey,ReadOnlyLogic,IsAutocomplete,IsAllowLogging,MandatoryLogic,AD_Column_UU,Updated,IsUpdateable,ColumnName,DefaultValue,Name,IsAllowCopy,IsActive,CreatedBy,UpdatedBy,IsAlwaysUpdateable,AD_Client_ID,Created,EntityType,IsEncrypted,IsSecure,FKConstraintType,AD_Element_ID,AD_Reference_Value_ID,AD_Table_ID,AD_Reference_ID,IsToolbarButton,AD_Org_ID,IsHtml) VALUES (213805,0,'N',0,'N','N','N',0,'N',1,'N','N','@IsSummary@=Y','N','Y','@IsSummary@=N','e7a97d28-f550-4253-a77b-b6fab1e7b38a',TO_TIMESTAMP('2022-03-28 15:42:44','YYYY-MM-DD HH24:MI:SS'),'Y','TaxPostingIndicator','0','Posting Indicator','Y','Y',100,100,'N',0,TO_TIMESTAMP('2022-03-28 15:42:44','YYYY-MM-DD HH24:MI:SS'),'D','N','N','N',203283,200160,261,17,'N',0,'N') +; + +-- Mar 28, 2022 3:42:52 PM MYT +INSERT INTO AD_TreeNode (AD_Client_ID,AD_Org_ID, IsActive,Created,CreatedBy,Updated,UpdatedBy, AD_Tree_ID, Node_ID, Parent_ID, SeqNo, AD_TreeNode_UU) SELECT t.AD_Client_ID, 0, 'Y', statement_timestamp(), 100, statement_timestamp(), 100,t.AD_Tree_ID, 200012, 0, 999, Generate_UUID() FROM AD_Tree t WHERE t.AD_Client_ID=0 AND t.IsActive='Y' AND t.IsAllNodes='Y' AND t.TreeType='TL' AND t.AD_Table_ID=282 AND NOT EXISTS (SELECT * FROM AD_TreeNode e WHERE e.AD_Tree_ID=t.AD_Tree_ID AND Node_ID=200012) +; + +-- Mar 28, 2022 3:42:53 PM MYT +ALTER TABLE C_Tax ADD COLUMN TaxPostingIndicator CHAR(1) DEFAULT '0' +; + +-- Mar 28, 2022 3:55:05 PM MYT +INSERT INTO AD_Field (SortNo,AD_Field_ID,IsEncrypted,DisplayLength,IsSameLine,IsHeading,SeqNo,IsCentrallyMaintained,IsReadOnly,DisplayLogic,Updated,Name,AD_Field_UU,IsDisplayed,IsFieldOnly,CreatedBy,UpdatedBy,IsActive,IsDisplayedGrid,SeqNoGrid,XPosition,IsQuickEntry,AD_Client_ID,Created,ColumnSpan,NumLines,IsAdvancedField,IsDefaultFocus,AD_Column_ID,EntityType,AD_Tab_ID,AD_Org_ID) VALUES (0,205862,'N',0,'N','N',1010,'Y','N','@IsDocumentLevel@=N',TO_TIMESTAMP('2022-03-28 15:55:04','YYYY-MM-DD HH24:MI:SS'),'Posting Indicator','96a2af5a-4a6f-4d44-ab54-da4713d70fc5','Y','N',100,100,'Y','Y',1010,4,'N',0,TO_TIMESTAMP('2022-03-28 15:55:04','YYYY-MM-DD HH24:MI:SS'),2,1,'N','N',213805,'D',174,0) +; + +-- Mar 28, 2022 3:55:33 PM MYT +UPDATE AD_Field SET SeqNo=180,SeqNoGrid=180,IsDisplayed='Y', Updated=Now(), UpdatedBy=100 WHERE AD_Field_ID=205862 +; + +-- Mar 28, 2022 3:55:33 PM MYT +UPDATE AD_Field SET SeqNo=190,SeqNoGrid=190,IsDisplayed='Y', Updated=Now(), UpdatedBy=100 WHERE AD_Field_ID=203325 +; + +-- Mar 28, 2022 3:55:33 PM MYT +UPDATE AD_Field SET SeqNo=200,SeqNoGrid=200,IsDisplayed='Y', Updated=Now(), UpdatedBy=100 WHERE AD_Field_ID=203326 +; + +-- Mar 28, 2022 3:55:33 PM MYT +UPDATE AD_Field SET SeqNo=210,SeqNoGrid=210,IsDisplayed='Y', Updated=Now(), UpdatedBy=100 WHERE AD_Field_ID=974 +; + +-- Mar 28, 2022 3:55:33 PM MYT +UPDATE AD_Field SET SeqNo=220,SeqNoGrid=220,IsDisplayed='Y', Updated=Now(), UpdatedBy=100 WHERE AD_Field_ID=976 +; + +-- Mar 28, 2022 3:55:33 PM MYT +UPDATE AD_Field SET SeqNo=230,SeqNoGrid=230,IsDisplayed='Y', Updated=Now(), UpdatedBy=100 WHERE AD_Field_ID=975 +; + +UPDATE AD_Field SET SeqNo=240,SeqNoGrid=240,IsDisplayed='Y', Updated=Now(), UpdatedBy=100 WHERE AD_Field_ID=977 +; + +UPDATE AD_Field SET SeqNo=250,SeqNoGrid=250,IsDisplayed='Y', Updated=Now(), UpdatedBy=100 WHERE AD_Field_ID=202402 +; + +-- Mar 28, 2022 3:56:15 PM MYT +UPDATE AD_Column SET ReadOnlyLogic='@IsDocumentLevel@=Y', MandatoryLogic='@IsDocumentLevel@=N',Updated=TO_TIMESTAMP('2022-03-28 15:56:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=213805 +; + +-- Mar 28, 2022 6:43:36 PM MYT +UPDATE AD_Field SET DisplayLogic='@IsDocumentLevel@=N & @IsSummary@=N', AD_Val_Rule_ID=NULL, AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_TIMESTAMP('2022-03-28 18:43:36','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=205862 +; + +-- Mar 28, 2022 8:10:21 PM MYT +UPDATE AD_Field SET DisplayLogic='@IsDocumentLevel@=N & @IsSummary@=N & @SOPOType@!S', AD_Val_Rule_ID=NULL, AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_TIMESTAMP('2022-03-28 20:10:21','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=205862 +; + +-- Mar 28, 2022 8:10:34 PM MYT +INSERT INTO AD_TreeNode (AD_Client_ID,AD_Org_ID, IsActive,Created,CreatedBy,Updated,UpdatedBy, AD_Tree_ID, Node_ID, Parent_ID, SeqNo, AD_TreeNode_UU) SELECT t.AD_Client_ID, 0, 'Y', statement_timestamp(), 100, statement_timestamp(), 100,t.AD_Tree_ID, 200014, 0, 999, Generate_UUID() FROM AD_Tree t WHERE t.AD_Client_ID=0 AND t.IsActive='Y' AND t.IsAllNodes='Y' AND t.TreeType='TL' AND t.AD_Table_ID=282 AND NOT EXISTS (SELECT * FROM AD_TreeNode e WHERE e.AD_Tree_ID=t.AD_Tree_ID AND Node_ID=200014) +; + + diff --git a/migration/iD10/postgresql/202204011738_IDEMPIERE-5057.sql b/migration/iD10/postgresql/202204011738_IDEMPIERE-5057.sql new file mode 100644 index 0000000000..2c3a35d9bf --- /dev/null +++ b/migration/iD10/postgresql/202204011738_IDEMPIERE-5057.sql @@ -0,0 +1,33 @@ +-- IDEMPIERE-5057 Implement Deductible and non deductible input tax for purchasing and costing +SELECT register_migration_script('202204011738_IDEMPIERE-5057.sql') FROM dual; + +-- Apr 1, 2022, 5:38:03 PM MYT +UPDATE AD_Element SET Description='Type of input tax (deductible and non deductible)', Help='Separate Tax Posting: Tax is calculated on the full amount of the item and posted separately. +Distribute Tax with Relevant Expense: Tax amount is added to the item amount during account posting time and for updating of Product Cost.',Updated=TO_TIMESTAMP('2022-04-01 17:38:03','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Element_ID=203283 +; + +-- Apr 1, 2022, 5:38:03 PM MYT +UPDATE AD_Column SET ColumnName='TaxPostingIndicator', Name='Posting Indicator', Description='Type of input tax (deductible and non deductible)', Help='Separate Tax Posting: Tax is calculated on the full amount of the item and posted separately. +Distribute Tax with Relevant Expense: Tax amount is added to the item amount during account posting time and for updating of Product Cost.', Placeholder=NULL WHERE AD_Element_ID=203283 +; + +-- Apr 1, 2022, 5:38:03 PM MYT +UPDATE AD_Process_Para SET ColumnName='TaxPostingIndicator', Name='Posting Indicator', Description='Type of input tax (deductible and non deductible)', Help='Separate Tax Posting: Tax is calculated on the full amount of the item and posted separately. +Distribute Tax with Relevant Expense: Tax amount is added to the item amount during account posting time and for updating of Product Cost.', AD_Element_ID=203283 WHERE UPPER(ColumnName)='TAXPOSTINGINDICATOR' AND IsCentrallyMaintained='Y' AND AD_Element_ID IS NULL +; + +-- Apr 1, 2022, 5:38:03 PM MYT +UPDATE AD_Process_Para SET ColumnName='TaxPostingIndicator', Name='Posting Indicator', Description='Type of input tax (deductible and non deductible)', Help='Separate Tax Posting: Tax is calculated on the full amount of the item and posted separately. +Distribute Tax with Relevant Expense: Tax amount is added to the item amount during account posting time and for updating of Product Cost.', Placeholder=NULL WHERE AD_Element_ID=203283 AND IsCentrallyMaintained='Y' +; + +-- Apr 1, 2022, 5:38:03 PM MYT +UPDATE AD_InfoColumn SET ColumnName='TaxPostingIndicator', Name='Posting Indicator', Description='Type of input tax (deductible and non deductible)', Help='Separate Tax Posting: Tax is calculated on the full amount of the item and posted separately. +Distribute Tax with Relevant Expense: Tax amount is added to the item amount during account posting time and for updating of Product Cost.', Placeholder=NULL WHERE AD_Element_ID=203283 AND IsCentrallyMaintained='Y' +; + +-- Apr 1, 2022, 5:38:03 PM MYT +UPDATE AD_Field SET Name='Posting Indicator', Description='Type of input tax (deductible and non deductible)', Help='Separate Tax Posting: Tax is calculated on the full amount of the item and posted separately. +Distribute Tax with Relevant Expense: Tax amount is added to the item amount during account posting time and for updating of Product Cost.', Placeholder=NULL WHERE AD_Column_ID IN (SELECT AD_Column_ID FROM AD_Column WHERE AD_Element_ID=203283) AND IsCentrallyMaintained='Y' +; + diff --git a/org.adempiere.base/src/org/adempiere/model/ITaxProvider.java b/org.adempiere.base/src/org/adempiere/model/ITaxProvider.java index 2f7d9abc5a..9ebfedae85 100644 --- a/org.adempiere.base/src/org/adempiere/model/ITaxProvider.java +++ b/org.adempiere.base/src/org/adempiere/model/ITaxProvider.java @@ -31,30 +31,112 @@ import org.compiere.process.ProcessInfo; */ public interface ITaxProvider { + /** + * Calculate order tax + * @param provider + * @param order + * @return true if success, false otherwise + */ public boolean calculateOrderTaxTotal(MTaxProvider provider, MOrder order); + /** + * Update order tax for line + * @param provider + * @param line + * @return true if success, false otherwise + */ public boolean updateOrderTax(MTaxProvider provider, MOrderLine line); + /** + * Re-calculate order tax for line (if line tax id change) + * @param provider + * @param line + * @param newRecord + * @return true if success, false otherwise + */ public boolean recalculateTax(MTaxProvider provider, MOrderLine line, boolean newRecord); + /** + * Update order tax total + * @param provider + * @param line + * @return true if success, false otherwise + */ public boolean updateHeaderTax(MTaxProvider provider, MOrderLine line); + /** + * Calculate invoice tax total + * @param provider + * @param invoice + * @return true if success, false otherwise + */ public boolean calculateInvoiceTaxTotal(MTaxProvider provider, MInvoice invoice); + /** + * Update invoice tax for line + * @param provider + * @param line + * @return true if success, false otherwise + */ public boolean updateInvoiceTax(MTaxProvider provider, MInvoiceLine line); + /** + * Re-calculate invoice tax for line (if line tax id change) + * @param provider + * @param line + * @param newRecord + * @return true if success, false otherwise + */ public boolean recalculateTax(MTaxProvider provider, MInvoiceLine line, boolean newRecord); + /** + * Update invoice tax total + * @param provider + * @param line + * @return true if success, false otherwise + */ public boolean updateHeaderTax(MTaxProvider provider, MInvoiceLine line); + /** + * Calculate rma tax total + * @param provider + * @param rma + * @return true if success, false otherwise + */ public boolean calculateRMATaxTotal(MTaxProvider provider, MRMA rma); + /** + * Update rma tax for rma line + * @param provider + * @param line + * @return true if success, false otherwise + */ public boolean updateRMATax(MTaxProvider provider, MRMALine line); + /** + * Re-calculate rma tax for ram line (if line tax id change) + * @param provider + * @param line + * @param newRecord + * @return true if success, false otherwise + */ public boolean recalculateTax(MTaxProvider provider, MRMALine line, boolean newRecord); + /** + * Update rma header total + * @param provider + * @param line + * @return true if success, false otherwise + */ public boolean updateHeaderTax(MTaxProvider provider, MRMALine line); + /** + * + * @param provider + * @param pi + * @return error message + * @throws Exception + */ public String validateConnection(MTaxProvider provider, ProcessInfo pi) throws Exception; } \ No newline at end of file diff --git a/org.adempiere.base/src/org/compiere/acct/Doc_InOut.java b/org.adempiere.base/src/org/compiere/acct/Doc_InOut.java index 8e97b70296..2ff8e0df80 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc_InOut.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc_InOut.java @@ -20,6 +20,7 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.sql.ResultSet; import java.util.ArrayList; +import java.util.List; import java.util.logging.Level; import org.compiere.model.I_C_OrderLine; @@ -616,9 +617,57 @@ public class Doc_InOut extends Doc int stdPrecision = MCurrency.getStdPrecision(getCtx(), C_Currency_ID); BigDecimal costTax = tax.calculateTax(costs, true, stdPrecision); if (log.isLoggable(Level.FINE)) log.fine("Costs=" + costs + " - Tax=" + costTax); - costs = costs.subtract(costTax); + if(tax.isSummary()) + { + MTax[] cTaxes = tax.getChildTaxes(false); + List toSubtract = new ArrayList<>(); + for(MTax cTax : cTaxes) + { + if (!cTax.isDistributeTaxWithLineItem()) + toSubtract.add(cTax); + } + if (toSubtract.size() > 0) + { + BigDecimal base = costs.subtract(costTax); + for(MTax cTax : toSubtract) + { + BigDecimal ts = cTax.calculateTax(base, false, stdPrecision); + costs = costs.subtract(ts); + } + } + } + else if (!tax.isDistributeTaxWithLineItem()) + { + costs = costs.subtract(costTax); + } } } // correct included Tax + else if (C_Tax_ID != 0) + { + MTax tax = MTax.get(getCtx(), C_Tax_ID); + if(tax.isSummary()) + { + MTax[] cTaxes = tax.getChildTaxes(false); + BigDecimal base = costs; + for(MTax cTax : cTaxes) + { + if (cTax.isDistributeTaxWithLineItem()) + { + //do not round to stdprecision before multiply qty + BigDecimal costTax = cTax.calculateTax(base, false, 12); + if (log.isLoggable(Level.FINE)) log.fine("Costs=" + base + " - Tax=" + costTax); + costs = costs.add(costTax); + } + } + } + else if (tax.isDistributeTaxWithLineItem()) + { + //do not round to stdprecision before multiply qty + BigDecimal costTax = tax.calculateTax(costs, false, 12); + if (log.isLoggable(Level.FINE)) log.fine("Costs=" + costs + " - Tax=" + costTax); + costs = costs.add(costTax); + } + } } costs = costs.multiply(line.getQty()); } @@ -781,18 +830,50 @@ public class Doc_InOut extends Doc MOrderLine originalOrderLine = (MOrderLine) originalInOutLine.getC_OrderLine(); // Goodwill: Correct included Tax int C_Tax_ID = originalOrderLine.getC_Tax_ID(); + MTax tax = MTax.get(getCtx(), C_Tax_ID); + int stdPrecision = MCurrency.getStdPrecision(getCtx(), originalOrderLine.getC_Currency_ID()); if (originalOrderLine.isTaxIncluded() && C_Tax_ID != 0) { - MTax tax = MTax.get(getCtx(), C_Tax_ID); - if (!tax.isZeroTax()) + BigDecimal costTax = tax.calculateTax(costs, true, stdPrecision); + if (log.isLoggable(Level.FINE)) log.fine("Costs=" + costs + " - Tax=" + costTax); + if (tax.isSummary()) + { + costs = costs.subtract(costTax); + BigDecimal base = costs; + for(MTax cTax : tax.getChildTaxes(false)) + { + if (!cTax.isZeroTax() && cTax.isDistributeTaxWithLineItem()) + { + costTax = cTax.calculateTax(base, false, stdPrecision); + costs = costs.add(costTax); + } + } + } + else if (!tax.isZeroTax() && !tax.isDistributeTaxWithLineItem()) { - int stdPrecision = MCurrency.getStdPrecision(getCtx(), originalOrderLine.getC_Currency_ID()); - BigDecimal costTax = tax.calculateTax(costs, true, stdPrecision); - if (log.isLoggable(Level.FINE)) log.fine("Costs=" + costs + " - Tax=" + costTax); costs = costs.subtract(costTax); } } // correct included Tax - + else + { + if (tax.isSummary()) + { + BigDecimal base = costs; + for(MTax cTax : tax.getChildTaxes(false)) + { + if (!cTax.isZeroTax() && cTax.isDistributeTaxWithLineItem()) + { + BigDecimal costTax = cTax.calculateTax(base, false, stdPrecision); + costs = costs.add(costTax); + } + } + } + else if (tax.isDistributeTaxWithLineItem()) + { + BigDecimal costTax = tax.calculateTax(costs, false, stdPrecision); + costs = costs.add(costTax); + } + } // different currency if (C_Currency_ID != originalOrderLine.getC_Currency_ID()) { 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 30f82e8070..d4c4b74ce2 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc_Invoice.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc_Invoice.java @@ -74,6 +74,9 @@ public class Doc_Invoice extends Doc /** Contained Optional Tax Lines */ protected DocTax[] m_taxes = null; + /** Contained Optional Tax Lines Distributed to Line Item */ + @SuppressWarnings("unused") + private DocTax[] m_addToLineTaxes = null; /** Currency Precision */ protected int m_precision = -1; /** All lines are Service */ @@ -109,6 +112,7 @@ public class Doc_Invoice extends Doc private DocTax[] loadTaxes() { ArrayList list = new ArrayList(); + ArrayList distributeList = new ArrayList(); String sql = "SELECT it.C_Tax_ID, t.Name, t.Rate, it.TaxBaseAmt, it.TaxAmt, t.IsSalesTax " + "FROM C_Tax t, C_InvoiceTax it " + "WHERE t.C_Tax_ID=it.C_Tax_ID AND it.C_Invoice_ID=?"; @@ -129,10 +133,18 @@ public class Doc_Invoice extends Doc BigDecimal amount = rs.getBigDecimal(5); boolean salesTax = "Y".equals(rs.getString(6)); // + MTax tax = MTax.get(getCtx(), C_Tax_ID); DocTax taxLine = new DocTax(C_Tax_ID, name, rate, taxBaseAmt, amount, salesTax); if (log.isLoggable(Level.FINE)) log.fine(taxLine.toString()); - list.add(taxLine); + if (!tax.isDistributeTaxWithLineItem()) + { + list.add(taxLine); + } + else + { + distributeList.add(taxLine); + } } } catch (SQLException e) @@ -148,6 +160,9 @@ public class Doc_Invoice extends Doc // Return Array DocTax[] tl = new DocTax[list.size()]; list.toArray(tl); + // Distribute list + m_addToLineTaxes = distributeList.toArray(new DocTax[0]); + return tl; } // loadTaxes @@ -184,24 +199,33 @@ public class Doc_Invoice extends Doc { BigDecimal LineNetAmtTax = tax.calculateTax(LineNetAmt, true, getStdPrecision()); if (log.isLoggable(Level.FINE)) log.fine("LineNetAmt=" + LineNetAmt + " - Tax=" + LineNetAmtTax); - LineNetAmt = LineNetAmt.subtract(LineNetAmtTax); if (tax.isSummary()) { + LineNetAmt = LineNetAmt.subtract(LineNetAmtTax); + BigDecimal base = LineNetAmt; BigDecimal sumChildLineNetAmtTax = Env.ZERO; DocTax taxToApplyDiff = null; for (MTax childTax : tax.getChildTaxes(false)) { if (!childTax.isZeroTax()) { - BigDecimal childLineNetAmtTax = childTax.calculateTax(LineNetAmt, false, getStdPrecision()); - if (log.isLoggable(Level.FINE)) log.fine("LineNetAmt=" + LineNetAmt + " - Child Tax=" + childLineNetAmtTax); - for (int t = 0; t < m_taxes.length; t++) + BigDecimal childLineNetAmtTax = childTax.calculateTax(base, false, getStdPrecision()); + if (log.isLoggable(Level.FINE)) log.fine("LineNetAmt=" + base + " - Child Tax=" + childLineNetAmtTax); + if (childTax.isDistributeTaxWithLineItem()) + { + LineNetAmt = LineNetAmt.add(childLineNetAmtTax); + LineNetAmtTax = LineNetAmtTax.subtract(childLineNetAmtTax); + } + else { - if (m_taxes[t].getC_Tax_ID() == childTax.getC_Tax_ID()) + for (int t = 0; t < m_taxes.length; t++) { - m_taxes[t].addIncludedTax(childLineNetAmtTax); - taxToApplyDiff = m_taxes[t]; - sumChildLineNetAmtTax = sumChildLineNetAmtTax.add(childLineNetAmtTax); - break; + if (m_taxes[t].getC_Tax_ID() == childTax.getC_Tax_ID()) + { + m_taxes[t].addIncludedTax(childLineNetAmtTax); + taxToApplyDiff = m_taxes[t]; + sumChildLineNetAmtTax = sumChildLineNetAmtTax.add(childLineNetAmtTax); + break; + } } } } @@ -211,12 +235,16 @@ public class Doc_Invoice extends Doc taxToApplyDiff.addIncludedTax(diffChildVsSummary); } } else { - for (int t = 0; t < m_taxes.length; t++) - { - if (m_taxes[t].getC_Tax_ID() == C_Tax_ID) + if (!tax.isDistributeTaxWithLineItem()) + { + LineNetAmt = LineNetAmt.subtract(LineNetAmtTax); + for (int t = 0; t < m_taxes.length; t++) { - m_taxes[t].addIncludedTax(LineNetAmtTax); - break; + if (m_taxes[t].getC_Tax_ID() == C_Tax_ID) + { + m_taxes[t].addIncludedTax(LineNetAmtTax); + break; + } } } } @@ -225,6 +253,29 @@ public class Doc_Invoice extends Doc PriceList = PriceList.subtract(PriceListTax); } } // correct included Tax + else + { + int stdPrecision = MCurrency.getStdPrecision(getCtx(), invoice.getC_Currency_ID()); + MTax tax = MTax.get(getCtx(), C_Tax_ID); + if (tax.isSummary()) + { + MTax[] cTaxes = tax.getChildTaxes(false); + BigDecimal base = LineNetAmt; + for(MTax cTax : cTaxes) + { + if (cTax.isDistributeTaxWithLineItem()) + { + BigDecimal taxAmt = cTax.calculateTax(base, false, stdPrecision); + LineNetAmt = LineNetAmt.add(taxAmt); + } + } + } + else if (tax.isDistributeTaxWithLineItem()) + { + BigDecimal taxAmt = tax.calculateTax(LineNetAmt, false, stdPrecision); + LineNetAmt = LineNetAmt.add(taxAmt); + } + } docLine.setAmount (LineNetAmt, PriceList, Qty); // qty for discount calc if (docLine.isItem()) 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 74b4a05eec..e8bd28e918 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc_MatchInv.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc_MatchInv.java @@ -37,6 +37,7 @@ import org.compiere.model.MAcctSchema; import org.compiere.model.MAcctSchemaElement; import org.compiere.model.MConversionRate; import org.compiere.model.MCostDetail; +import org.compiere.model.MCurrency; import org.compiere.model.MFactAcct; import org.compiere.model.MInOut; import org.compiere.model.MInOutLine; @@ -44,6 +45,7 @@ import org.compiere.model.MInvoice; import org.compiere.model.MInvoiceLine; import org.compiere.model.MMatchInv; import org.compiere.model.MOrderLandedCostAllocation; +import org.compiere.model.MTax; import org.compiere.model.MUOM; import org.compiere.model.ProductCost; import org.compiere.model.Query; @@ -533,6 +535,56 @@ public class Doc_MatchInv extends Doc } } tAmt = tAmt.add(LineNetAmt); //Invoice Price + // adjust for tax + MTax tax = MTax.get(getCtx(), m_invoiceLine.getC_Tax_ID()); + int stdPrecision = MCurrency.getStdPrecision(getCtx(), m_invoiceLine.getParent().getC_Currency_ID()); + if (m_invoiceLine.isTaxIncluded()) + { + BigDecimal tAmtTax = tax.calculateTax(tAmt, true, stdPrecision); + if (tax.isSummary()) + { + tAmt = tAmt.subtract(tAmtTax); + BigDecimal base = tAmt; + for (MTax childTax : tax.getChildTaxes(false)) + { + if (!childTax.isZeroTax()) + { + if (childTax.isDistributeTaxWithLineItem()) + { + BigDecimal taxAmt = childTax.calculateTax(base, false, stdPrecision); + tAmt = tAmt.add(taxAmt); + } + } + } + } + else if (!tax.isDistributeTaxWithLineItem()) + { + tAmt = tAmt.subtract(tAmtTax); + } + } + else + { + if (tax.isSummary()) + { + BigDecimal base = tAmt; + for (MTax childTax : tax.getChildTaxes(false)) + { + if (!childTax.isZeroTax()) + { + if (childTax.isDistributeTaxWithLineItem()) + { + BigDecimal taxAmt = childTax.calculateTax(base, false, stdPrecision); + tAmt = tAmt.add(taxAmt); + } + } + } + } + else if (tax.isDistributeTaxWithLineItem()) + { + BigDecimal taxAmt = tax.calculateTax(tAmt, false, stdPrecision); + tAmt = tAmt.add(taxAmt); + } + } // Different currency MInvoice invoice = m_invoiceLine.getParent(); diff --git a/org.adempiere.base/src/org/compiere/acct/Doc_MatchPO.java b/org.adempiere.base/src/org/compiere/acct/Doc_MatchPO.java index 5da6c9066f..376fa0d4d0 100644 --- a/org.adempiere.base/src/org/compiere/acct/Doc_MatchPO.java +++ b/org.adempiere.base/src/org/compiere/acct/Doc_MatchPO.java @@ -286,17 +286,53 @@ public class Doc_MatchPO extends Doc poCost = m_oLine.getPriceActual(); // Goodwill: Correct included Tax int C_Tax_ID = m_oLine.getC_Tax_ID(); + MTax tax = MTax.get(getCtx(), C_Tax_ID); + int stdPrecision = MCurrency.getStdPrecision(getCtx(), m_oLine.getC_Currency_ID()); if (m_oLine.isTaxIncluded() && C_Tax_ID != 0) - { - MTax tax = MTax.get(getCtx(), C_Tax_ID); + { if (!tax.isZeroTax()) - { - int stdPrecision = MCurrency.getStdPrecision(getCtx(), m_oLine.getC_Currency_ID()); + { BigDecimal costTax = tax.calculateTax(poCost, true, stdPrecision); if (log.isLoggable(Level.FINE)) log.fine("Costs=" + poCost + " - Tax=" + costTax); - poCost = poCost.subtract(costTax); + if (tax.isSummary()) + { + poCost = poCost.subtract(costTax); + BigDecimal base = poCost; + for (MTax childTax : tax.getChildTaxes(false)) + { + if (!childTax.isZeroTax() && childTax.isDistributeTaxWithLineItem()) + { + BigDecimal taxAmt = childTax.calculateTax(base, false, stdPrecision); + poCost = poCost.add(taxAmt); + } + } + } + else if (!tax.isDistributeTaxWithLineItem()) + { + poCost = poCost.subtract(costTax); + } } } // correct included Tax + else + { + if (tax.isSummary()) + { + BigDecimal base = poCost; + for (MTax childTax : tax.getChildTaxes(false)) + { + if (childTax.isDistributeTaxWithLineItem()) + { + BigDecimal taxAmt = childTax.calculateTax(base, false, stdPrecision); + poCost = poCost.add(taxAmt); + } + } + } + else if (tax.isDistributeTaxWithLineItem()) + { + BigDecimal taxAmt = tax.calculateTax(poCost, false, stdPrecision); + poCost = poCost.add(taxAmt); + } + } } MInOutLine receiptLine = new MInOutLine (getCtx(), m_M_InOutLine_ID, getTrxName()); @@ -383,7 +419,7 @@ public class Doc_MatchPO extends Doc if (MAcctSchema.COSTINGMETHOD_StandardCosting.equals(costingMethod)) { - if (m_matchPO.getReversal_ID() > 0) + if (m_matchPO.isReversal()) { // Product PPV FactLine cr = fact.createLine(null, @@ -582,7 +618,7 @@ public class Doc_MatchPO extends Doc tAmt = tAmt.add(isReturnTrx ? poCost.negate() : poCost); tQty = tQty.add(isReturnTrx ? getQty().negate() : getQty()); - if (mMatchPO.getReversal_ID() > 0) + if (mMatchPO.isReversal()) { String error = createLandedCostAdjustments(as, landedCostMap, mMatchPO, tQty); if (!Util.isEmpty(error)) @@ -601,7 +637,7 @@ public class Doc_MatchPO extends Doc return "SaveError"; } - if (mMatchPO.getReversal_ID() <= 0) + if (!mMatchPO.isReversal()) { String error = createLandedCostAdjustments(as, landedCostMap, mMatchPO, tQty); if (!Util.isEmpty(error)) diff --git a/org.adempiere.base/src/org/compiere/model/I_C_Tax.java b/org.adempiere.base/src/org/compiere/model/I_C_Tax.java index 51337c7ead..ce37fac48e 100644 --- a/org.adempiere.base/src/org/compiere/model/I_C_Tax.java +++ b/org.adempiere.base/src/org/compiere/model/I_C_Tax.java @@ -22,7 +22,7 @@ import org.compiere.util.KeyNamePair; /** Generated Interface for C_Tax * @author iDempiere (generated) - * @version Release 9 + * @version Release 10 */ public interface I_C_Tax { @@ -44,8 +44,8 @@ public interface I_C_Tax /** Column name AD_Client_ID */ public static final String COLUMNNAME_AD_Client_ID = "AD_Client_ID"; - /** Get Client. - * Client/Tenant for this installation. + /** Get Tenant. + * Tenant for this installation. */ public int getAD_Client_ID(); @@ -53,12 +53,12 @@ public interface I_C_Tax public static final String COLUMNNAME_AD_Org_ID = "AD_Org_ID"; /** Set Organization. - * Organizational entity within client + * Organizational entity within tenant */ public void setAD_Org_ID (int AD_Org_ID); /** Get Organization. - * Organizational entity within client + * Organizational entity within tenant */ public int getAD_Org_ID(); @@ -358,6 +358,15 @@ public interface I_C_Tax */ public String getTaxIndicator(); + /** Column name TaxPostingIndicator */ + public static final String COLUMNNAME_TaxPostingIndicator = "TaxPostingIndicator"; + + /** Set Posting Indicator */ + public void setTaxPostingIndicator (String TaxPostingIndicator); + + /** Get Posting Indicator */ + public String getTaxPostingIndicator(); + /** Column name To_Country_ID */ public static final String COLUMNNAME_To_Country_ID = "To_Country_ID"; diff --git a/org.adempiere.base/src/org/compiere/model/MInvoiceLine.java b/org.adempiere.base/src/org/compiere/model/MInvoiceLine.java index ad77033d84..73a6bc9fbd 100644 --- a/org.adempiere.base/src/org/compiere/model/MInvoiceLine.java +++ b/org.adempiere.base/src/org/compiere/model/MInvoiceLine.java @@ -943,14 +943,49 @@ public class MInvoiceLine extends X_C_InvoiceLine * author teo_sarca [ 1583825 ] */ protected boolean updateInvoiceTax(boolean oldTax) { - MInvoiceTax tax = MInvoiceTax.get (this, getPrecision(), oldTax, get_TrxName()); - if (tax != null) { - if (!tax.calculateTaxFromLines()) - return false; + int C_Tax_ID = getC_Tax_ID(); + boolean isOldTax = oldTax && is_ValueChanged(MInvoiceTax.COLUMNNAME_C_Tax_ID); + if (isOldTax) + { + Object old = get_ValueOld(MInvoiceTax.COLUMNNAME_C_Tax_ID); + if (old == null) + { + return true; + } + C_Tax_ID = ((Integer)old).intValue(); + } + if (C_Tax_ID == 0) + { + return true; + } - // red1 - solving BUGS #[ 1701331 ] , #[ 1786103 ] - if (!tax.save(get_TrxName())) - return false; + MTax t = MTax.get(C_Tax_ID); + if (t.isSummary()) + { + MInvoiceTax[] invoiceTaxes = MInvoiceTax.getChildTaxes(this, getPrecision(), oldTax, get_TrxName()); + if (invoiceTaxes != null && invoiceTaxes.length > 0) + { + for(MInvoiceTax tax : invoiceTaxes) + { + if (!tax.calculateTaxFromLines()) + return false; + + if (!tax.save(get_TrxName())) + return false; + } + } + } + else + { + MInvoiceTax tax = MInvoiceTax.get (this, getPrecision(), oldTax, get_TrxName()); + if (tax != null) { + if (!tax.calculateTaxFromLines()) + return false; + + // red1 - solving BUGS #[ 1701331 ] , #[ 1786103 ] + if (!tax.save(get_TrxName())) + return false; + } } return true; } diff --git a/org.adempiere.base/src/org/compiere/model/MInvoiceTax.java b/org.adempiere.base/src/org/compiere/model/MInvoiceTax.java index 76f68da2c8..fb1c6ad0f4 100644 --- a/org.adempiere.base/src/org/compiere/model/MInvoiceTax.java +++ b/org.adempiere.base/src/org/compiere/model/MInvoiceTax.java @@ -20,6 +20,8 @@ import java.math.BigDecimal; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; import java.util.logging.Level; @@ -104,6 +106,74 @@ public class MInvoiceTax extends X_C_InvoiceTax return retValue; } // get + /** + * Get Child Tax Lines for Invoice Line + * @param line invoice line + * @param precision currency precision + * @param oldTax if true old tax is returned + * @param trxName transaction name + * @return existing or new tax + */ + public static MInvoiceTax[] getChildTaxes(MInvoiceLine line, int precision, + boolean oldTax, String trxName) + { + List invoiceTaxes = new ArrayList(); + + if (line == null || line.getC_Invoice_ID() == 0) + return invoiceTaxes.toArray(new MInvoiceTax[0]); + + int C_Tax_ID = line.getC_Tax_ID(); + if (oldTax) + { + Object old = line.get_ValueOld(MInvoiceLine.COLUMNNAME_C_Tax_ID); + if (old == null) + return invoiceTaxes.toArray(new MInvoiceTax[0]); + C_Tax_ID = ((Integer)old).intValue(); + } + if (C_Tax_ID == 0) + { + return invoiceTaxes.toArray(new MInvoiceTax[0]); + } + + MTax tax = MTax.get(C_Tax_ID); + if (!tax.isSummary()) + return invoiceTaxes.toArray(new MInvoiceTax[0]); + + MTax[] cTaxes = tax.getChildTaxes(false); + for(MTax cTax : cTaxes) { + MInvoiceTax invoiceTax = new Query(line.getCtx(), Table_Name, "C_Invoice_ID=? AND C_Tax_ID=?", trxName) + .setParameters(line.getC_Invoice_ID(), cTax.getC_Tax_ID()) + .firstOnly(); + if (invoiceTax != null) + { + invoiceTax.set_TrxName(trxName); + invoiceTax.setPrecision(precision); + invoiceTaxes.add(invoiceTax); + } + // If the old tax was required and there is no MInvoiceTax for that + // return null, and not create another MInvoiceTax - teo_sarca [ 1583825 ] + else + { + if (oldTax) + continue; + } + + if (invoiceTax == null) + { + // Create New + invoiceTax = new MInvoiceTax(line.getCtx(), 0, trxName); + invoiceTax.set_TrxName(trxName); + invoiceTax.setClientOrg(line); + invoiceTax.setC_Invoice_ID(line.getC_Invoice_ID()); + invoiceTax.setC_Tax_ID(line.getC_Tax_ID()); + invoiceTax.setPrecision(precision); + invoiceTax.setIsTaxIncluded(line.isTaxIncluded()); + invoiceTaxes.add(invoiceTax); + } + } + return invoiceTaxes.toArray(new MInvoiceTax[0]); + } + /** Static Logger */ private static CLogger s_log = CLogger.getCLogger (MInvoiceTax.class); diff --git a/org.adempiere.base/src/org/compiere/model/MMatchPO.java b/org.adempiere.base/src/org/compiere/model/MMatchPO.java index ec0fa30999..a6a8431678 100644 --- a/org.adempiere.base/src/org/compiere/model/MMatchPO.java +++ b/org.adempiere.base/src/org/compiere/model/MMatchPO.java @@ -1422,4 +1422,16 @@ public class MMatchPO extends X_M_MatchPO } return false; } + + /** + * @return true if this is created to reverse another match po document + */ + public boolean isReversal() { + if (getReversal_ID() > 0) { + MMatchPO reversal = new MMatchPO (getCtx(), getReversal_ID(), get_TrxName()); + if (reversal.getM_MatchPO_ID() < getM_MatchPO_ID()) + return true; + } + return false; + } } // MMatchPO diff --git a/org.adempiere.base/src/org/compiere/model/MOrderLine.java b/org.adempiere.base/src/org/compiere/model/MOrderLine.java index 49dadf7554..f96fb06ecc 100644 --- a/org.adempiere.base/src/org/compiere/model/MOrderLine.java +++ b/org.adempiere.base/src/org/compiere/model/MOrderLine.java @@ -980,17 +980,57 @@ public class MOrderLine extends X_C_OrderLine * author teo_sarca [ 1583825 ] */ public boolean updateOrderTax(boolean oldTax) { - MOrderTax tax = MOrderTax.get (this, getPrecision(), oldTax, get_TrxName()); - if (tax != null) { - if (!tax.calculateTaxFromLines()) - return false; - if (tax.getTaxAmt().signum() != 0) { - if (!tax.save(get_TrxName())) - return false; + int C_Tax_ID = getC_Tax_ID(); + boolean isOldTax = oldTax && is_ValueChanged(MOrderLine.COLUMNNAME_C_Tax_ID); + if (isOldTax) + { + Object old = get_ValueOld(MOrderLine.COLUMNNAME_C_Tax_ID); + if (old == null) + { + return true; } - else { - if (!tax.is_new() && !tax.delete(false, get_TrxName())) + C_Tax_ID = ((Integer)old).intValue(); + } + if (C_Tax_ID == 0) + { + return true; + } + + MTax t = MTax.get(C_Tax_ID); + if (t.isSummary()) + { + MOrderTax[] taxes = MOrderTax.getChildTaxes(this, getPrecision(), isOldTax, get_TrxName()); + if (taxes != null && taxes.length > 0) + { + for(MOrderTax tax : taxes) + { + if (!tax.calculateTaxFromLines()) + return false; + if (tax.getTaxAmt().signum() != 0) { + if (!tax.save(get_TrxName())) + return false; + } + else { + if (!tax.is_new() && !tax.delete(false, get_TrxName())) + return false; + } + } + } + } + else + { + MOrderTax tax = MOrderTax.get (this, getPrecision(), oldTax, get_TrxName()); + if (tax != null) { + if (!tax.calculateTaxFromLines()) return false; + if (tax.getTaxAmt().signum() != 0) { + if (!tax.save(get_TrxName())) + return false; + } + else { + if (!tax.is_new() && !tax.delete(false, get_TrxName())) + return false; + } } } return true; diff --git a/org.adempiere.base/src/org/compiere/model/MOrderTax.java b/org.adempiere.base/src/org/compiere/model/MOrderTax.java index 9a6f8fbb13..a99a843028 100644 --- a/org.adempiere.base/src/org/compiere/model/MOrderTax.java +++ b/org.adempiere.base/src/org/compiere/model/MOrderTax.java @@ -19,6 +19,8 @@ package org.compiere.model; import java.math.BigDecimal; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; import java.util.logging.Level; @@ -124,6 +126,99 @@ public class MOrderTax extends X_C_OrderTax return retValue; } // get + /** + * Get Child Tax Line for Order Line + * @param line Order line + * @param precision currency precision + * @param oldTax get old tax + * @param trxName transaction + * @return existing or new tax + */ + public static MOrderTax[] getChildTaxes(MOrderLine line, int precision, + boolean oldTax, String trxName) + { + List orderTaxes = new ArrayList(); + + if (line == null || line.getC_Order_ID() == 0) + { + return orderTaxes.toArray(new MOrderTax[0]); + } + + int C_Tax_ID = line.getC_Tax_ID(); + if (oldTax) + { + Object old = line.get_ValueOld(MOrderTax.COLUMNNAME_C_Tax_ID); + if (old == null) + { + return orderTaxes.toArray(new MOrderTax[0]); + } + C_Tax_ID = ((Integer)old).intValue(); + } + if (C_Tax_ID == 0) + { + return orderTaxes.toArray(new MOrderTax[0]); + } + + MTax tax = MTax.get(C_Tax_ID); + if (!tax.isSummary()) + return orderTaxes.toArray(new MOrderTax[0]); + + MTax[] cTaxes = tax.getChildTaxes(false); + for(MTax cTax : cTaxes) { + MOrderTax orderTax = null; + String sql = "SELECT * FROM C_OrderTax WHERE C_Order_ID=? AND C_Tax_ID=?"; + PreparedStatement pstmt = null; + ResultSet rs = null; + try + { + pstmt = DB.prepareStatement (sql, trxName); + pstmt.setInt (1, line.getC_Order_ID()); + pstmt.setInt (2, cTax.getC_Tax_ID()); + rs = pstmt.executeQuery (); + if (rs.next ()) + orderTax = new MOrderTax (line.getCtx(), rs, trxName); + } + catch (Exception e) + { + s_log.log(Level.SEVERE, sql, e); + } + finally + { + DB.close(rs, pstmt); + rs = null; + pstmt = null; + } + if (orderTax != null) + { + orderTax.setPrecision(precision); + orderTax.set_TrxName(trxName); + orderTaxes.add(orderTax); + } + // If the old tax was required and there is no MOrderTax for that + // return null, and not create another MOrderTax - teo_sarca [ 1583825 ] + else + { + if (oldTax) + continue; + } + + if (orderTax == null) + { + // Create New + orderTax = new MOrderTax(line.getCtx(), 0, trxName); + orderTax.set_TrxName(trxName); + orderTax.setClientOrg(line); + orderTax.setC_Order_ID(line.getC_Order_ID()); + orderTax.setC_Tax_ID(line.getC_Tax_ID()); + orderTax.setPrecision(precision); + orderTax.setIsTaxIncluded(line.isTaxIncluded()); + orderTaxes.add(orderTax); + } + } + + return orderTaxes.toArray(new MOrderTax[0]); + } + /** Static Logger */ private static CLogger s_log = CLogger.getCLogger (MOrderTax.class); diff --git a/org.adempiere.base/src/org/compiere/model/MRMALine.java b/org.adempiere.base/src/org/compiere/model/MRMALine.java index 993c70c379..438c19c1c0 100644 --- a/org.adempiere.base/src/org/compiere/model/MRMALine.java +++ b/org.adempiere.base/src/org/compiere/model/MRMALine.java @@ -378,22 +378,69 @@ public class MRMALine extends X_M_RMALine return true; } + /** + * + * @param oldTax true if the old C_Tax_ID should be used + * @return true if success, false otherwise + */ protected boolean updateOrderTax(boolean oldTax) { - MRMATax tax = MRMATax.get (this, getPrecision(), oldTax, get_TrxName()); - if (tax != null) + int C_Tax_ID = getC_Tax_ID(); + boolean isOldTax = oldTax && is_ValueChanged(MRMALine.COLUMNNAME_C_Tax_ID); + if (isOldTax) { - if (!tax.calculateTaxFromLines()) - return false; - if (tax.getTaxAmt().signum() != 0) + Object old = get_ValueOld(MRMALine.COLUMNNAME_C_Tax_ID); + if (old == null) { - if (!tax.save(get_TrxName())) - return false; + return true; } - else + C_Tax_ID = ((Integer)old).intValue(); + } + if (C_Tax_ID == 0) + { + return true; + } + + MTax t = MTax.get(C_Tax_ID); + if (t.isSummary()) + { + MRMATax[] taxes = MRMATax.getChildTaxes(this, getPrecision(), oldTax, get_TrxName()); + if (taxes != null && taxes.length > 0) { - if (!tax.is_new() && !tax.delete(false, get_TrxName())) + for(MRMATax tax : taxes) + { + if (!tax.calculateTaxFromLines()) + return false; + if (tax.getTaxAmt().signum() != 0) + { + if (!tax.save(get_TrxName())) + return false; + } + else + { + if (!tax.is_new() && !tax.delete(false, get_TrxName())) + return false; + } + } + } + } + else + { + MRMATax tax = MRMATax.get (this, getPrecision(), oldTax, get_TrxName()); + if (tax != null) + { + if (!tax.calculateTaxFromLines()) return false; + if (tax.getTaxAmt().signum() != 0) + { + if (!tax.save(get_TrxName())) + return false; + } + else + { + if (!tax.is_new() && !tax.delete(false, get_TrxName())) + return false; + } } } return true; diff --git a/org.adempiere.base/src/org/compiere/model/MRMATax.java b/org.adempiere.base/src/org/compiere/model/MRMATax.java index c0b6ab9958..2d21f7c455 100644 --- a/org.adempiere.base/src/org/compiere/model/MRMATax.java +++ b/org.adempiere.base/src/org/compiere/model/MRMATax.java @@ -16,6 +16,8 @@ package org.compiere.model; import java.math.BigDecimal; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; import java.util.logging.Level; @@ -118,6 +120,96 @@ public class MRMATax extends X_M_RMATax return retValue; } + /** + * Get Child Tax Lines for RMA Line + * @param line RMA line + * @param precision currency precision + * @param oldTax get old tax + * @param trxName transaction + * @return existing or new tax + */ + public static MRMATax[] getChildTaxes(MRMALine line, int precision, + boolean oldTax, String trxName) + { + List rmaTaxes = new ArrayList(); + if (line == null || line.getM_RMA_ID() == 0) + { + return rmaTaxes.toArray(new MRMATax[0]); + } + int C_Tax_ID = line.getC_Tax_ID(); + if (oldTax) + { + Object old = line.get_ValueOld(MRMATax.COLUMNNAME_C_Tax_ID); + if (old == null) + { + return rmaTaxes.toArray(new MRMATax[0]); + } + C_Tax_ID = ((Integer)old).intValue(); + } + if (C_Tax_ID == 0) + { + return rmaTaxes.toArray(new MRMATax[0]); + } + + MTax tax = MTax.get(C_Tax_ID); + if (!tax.isSummary()) + return rmaTaxes.toArray(new MRMATax[0]); + + MTax[] cTaxes = tax.getChildTaxes(false); + for(MTax cTax : cTaxes) { + MRMATax rmaTax = null; + String sql = "SELECT * FROM M_RMATax WHERE M_RMA_ID=? AND C_Tax_ID=?"; + PreparedStatement pstmt = null; + ResultSet rs = null; + try + { + pstmt = DB.prepareStatement (sql, trxName); + pstmt.setInt (1, line.getM_RMA_ID()); + pstmt.setInt (2, cTax.getC_Tax_ID()); + rs = pstmt.executeQuery (); + if (rs.next ()) + rmaTax = new MRMATax (line.getCtx(), rs, trxName); + } + catch (Exception e) + { + s_log.log(Level.SEVERE, sql, e); + } + finally + { + DB.close(rs, pstmt); + rs = null; + pstmt = null; + } + if (rmaTax != null) + { + rmaTax.setPrecision(precision); + rmaTax.set_TrxName(trxName); + rmaTaxes.add(rmaTax); + } + // If the old tax was required and there is no MOrderTax for that + // return null, and not create another MOrderTax - teo_sarca [ 1583825 ] + else + { + if (oldTax) + continue; + } + + if (rmaTax == null) + { + // Create New + rmaTax = new MRMATax(line.getCtx(), 0, trxName); + rmaTax.set_TrxName(trxName); + rmaTax.setClientOrg(line); + rmaTax.setM_RMA_ID(line.getM_RMA_ID()); + rmaTax.setC_Tax_ID(line.getC_Tax_ID()); + rmaTax.setPrecision(precision); + rmaTax.setIsTaxIncluded(line.getParent().isTaxIncluded()); + rmaTaxes.add(rmaTax); + } + } + return rmaTaxes.toArray(new MRMATax[0]); + } + /** Static Logger */ private static CLogger s_log = CLogger.getCLogger (MRMATax.class); diff --git a/org.adempiere.base/src/org/compiere/model/MTax.java b/org.adempiere.base/src/org/compiere/model/MTax.java index 63c94ca3d7..9bf1fab48b 100644 --- a/org.adempiere.base/src/org/compiere/model/MTax.java +++ b/org.adempiere.base/src/org/compiere/model/MTax.java @@ -411,4 +411,12 @@ public class MTax extends X_C_Tax implements ImmutablePOSupport return this; } + /** + * + * @return true if input tax is added to product cost + */ + public boolean isDistributeTaxWithLineItem() + { + return TAXPOSTINGINDICATOR_DistributeTaxWithRelevantExpense.equals(getTaxPostingIndicator()); + } } // MTax diff --git a/org.adempiere.base/src/org/compiere/model/X_C_Tax.java b/org.adempiere.base/src/org/compiere/model/X_C_Tax.java index 0fc92fac3b..eda9f06f57 100644 --- a/org.adempiere.base/src/org/compiere/model/X_C_Tax.java +++ b/org.adempiere.base/src/org/compiere/model/X_C_Tax.java @@ -26,7 +26,7 @@ import org.compiere.util.KeyNamePair; /** Generated Model for C_Tax * @author iDempiere (generated) - * @version Release 9 - $Id$ */ + * @version Release 10 - $Id$ */ @org.adempiere.base.Model(table="C_Tax") public class X_C_Tax extends PO implements I_C_Tax, I_Persistent { @@ -34,7 +34,7 @@ public class X_C_Tax extends PO implements I_C_Tax, I_Persistent /** * */ - private static final long serialVersionUID = 20220116L; + private static final long serialVersionUID = 20220329L; /** Standard Constructor */ public X_C_Tax (Properties ctx, int C_Tax_ID, String trxName) @@ -599,6 +599,28 @@ public class X_C_Tax extends PO implements I_C_Tax, I_Persistent return (String)get_Value(COLUMNNAME_TaxIndicator); } + /** TaxPostingIndicator AD_Reference_ID=200160 */ + public static final int TAXPOSTINGINDICATOR_AD_Reference_ID=200160; + /** Separate Tax Posting = 0 */ + public static final String TAXPOSTINGINDICATOR_SeparateTaxPosting = "0"; + /** Distribute Tax with Relevant Expense = 1 */ + public static final String TAXPOSTINGINDICATOR_DistributeTaxWithRelevantExpense = "1"; + /** Set Posting Indicator. + @param TaxPostingIndicator Posting Indicator + */ + public void setTaxPostingIndicator (String TaxPostingIndicator) + { + + set_Value (COLUMNNAME_TaxPostingIndicator, TaxPostingIndicator); + } + + /** Get Posting Indicator. + @return Posting Indicator */ + public String getTaxPostingIndicator() + { + return (String)get_Value(COLUMNNAME_TaxPostingIndicator); + } + /** Set To. @param To_Country_ID Receiving Country */ 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 620e11372f..73b4fb6564 100644 --- a/org.idempiere.test/src/org/idempiere/test/model/MTaxTest.java +++ b/org.idempiere.test/src/org/idempiere/test/model/MTaxTest.java @@ -25,14 +25,44 @@ package org.idempiere.test.model; 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.assertTrue; +import java.math.BigDecimal; +import java.math.RoundingMode; + import org.adempiere.base.Core; +import org.compiere.model.MAccount; +import org.compiere.model.MAcctSchema; import org.compiere.model.MBPartner; +import org.compiere.model.MClientInfo; +import org.compiere.model.MCost; +import org.compiere.model.MDocType; +import org.compiere.model.MFactAcct; +import org.compiere.model.MInOut; +import org.compiere.model.MInOutLine; +import org.compiere.model.MInvoice; +import org.compiere.model.MInvoiceLine; +import org.compiere.model.MMatchPO; +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.MProductPrice; import org.compiere.model.MTax; +import org.compiere.model.MTaxCategory; +import org.compiere.model.MWarehouse; +import org.compiere.model.ProductCost; import org.compiere.model.Tax; +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.junit.jupiter.api.Test; @@ -46,8 +76,11 @@ public class MTaxTest extends AbstractTestCase { private static final int STANDARD_TAX_ID = 104; private static final int STANDARD_TAX_CATEGORY_ID=107; - private final static int BP_JOE_BLOCK = 118; - private static final int PRODUCT_MULCH = 137; + private static final int BP_JOE_BLOCK_ID = 118; + private static final int PRODUCT_MULCH_ID = 137; + private static final int PURCHASE_PRICE_LIST_ID = 102; + private static final int BP_PATIO_ID = 121; + private static final int MM_RECEIPT_DOCTYPE_ID = 122; public MTaxTest() { } @@ -73,20 +106,308 @@ public class MTaxTest extends AbstractTestCase { int taxExemptId = Tax.getExemptTax(Env.getCtx(), getAD_Org_ID(), getTrxName()); assertTrue(taxExemptId>0, "Fail to get tax exempt Id"); - MBPartner bp = new MBPartner(Env.getCtx(), BP_JOE_BLOCK, getTrxName()); + MBPartner bp = new MBPartner(Env.getCtx(), BP_JOE_BLOCK_ID, getTrxName()); bp.setIsTaxExempt(true); bp.saveEx(); - int id = Core.getTaxLookup().get(Env.getCtx(), PRODUCT_MULCH, 0, getLoginDate(), getLoginDate(), getAD_Org_ID(), getM_Warehouse_ID(), + int id = Core.getTaxLookup().get(Env.getCtx(), PRODUCT_MULCH_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(), PRODUCT_MULCH, 0, getLoginDate(), getLoginDate(), getAD_Org_ID(), getM_Warehouse_ID(), + id = Core.getTaxLookup().get(Env.getCtx(), PRODUCT_MULCH_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(STANDARD_TAX_ID, id, "Unexpected tax id"); } + + @Test + public void testDistributeTaxToProductCost() { + MProduct product = null; + MTaxCategory category = null; + MTax tax = null; + try { + category = new MTaxCategory(Env.getCtx(), 0, null); + category.setName("testDistributeTaxToProductCost"); + category.saveEx(); + + //need to create tax without trx as tax is cache + tax = new MTax(Env.getCtx(), 0, null); + tax.setC_TaxCategory_ID(category.get_ID()); + tax.setIsDocumentLevel(false); + tax.setIsSummary(false); + tax.setRate(new BigDecimal("5.00")); + tax.setTaxPostingIndicator(MTax.TAXPOSTINGINDICATOR_DistributeTaxWithRelevantExpense); + tax.setSOPOType(MTax.SOPOTYPE_PurchaseTax); + tax.setName("testDistributeTaxToProductCost"); + tax.saveEx(); + CacheMgt.get().reset(); + + //need to create product with trx as order line get product from cache + MProduct p = MProduct.get(PRODUCT_MULCH_ID); + product = new MProduct(Env.getCtx(), 0, null); + product.setM_Product_Category_ID(p.getM_Product_Category_ID()); + product.setC_TaxCategory_ID(category.get_ID()); + product.setIsStocked(true); + product.setIsPurchased(true); + product.setIsSold(true); + product.setIsStocked(true); + product.setProductType(MProduct.PRODUCTTYPE_Item); + product.setName("testDistributeTaxToProductCost"); + product.setC_UOM_ID(p.getC_UOM_ID()); + product.saveEx(); + + MPriceList priceList = MPriceList.get(PURCHASE_PRICE_LIST_ID); + MPriceListVersion priceListVersion = priceList.getPriceListVersion(null); + MProductPrice productPrice = new MProductPrice(Env.getCtx(), 0, getTrxName()); + productPrice.setM_PriceList_Version_ID(priceListVersion.get_ID()); + productPrice.setM_Product_ID(product.getM_Product_ID()); + productPrice.setPrices(new BigDecimal("2.00"), new BigDecimal("2.00"), new BigDecimal("2.00")); + productPrice.saveEx(); + + //purchase price of 2 + 5% tax + BigDecimal expectedCost = new BigDecimal("2.00").add(new BigDecimal("2.00").multiply(new BigDecimal("0.05"))).setScale(2, RoundingMode.HALF_EVEN); + + MBPartner bpartner = MBPartner.get(Env.getCtx(), BP_PATIO_ID); + MOrder order = new MOrder(Env.getCtx(), 0, getTrxName()); + order.setBPartner(bpartner); + order.setIsSOTrx(false); + order.setC_DocTypeTarget_ID(); + 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(new BigDecimal("1")); + orderLine.setTax(); + orderLine.saveEx(); + + assertEquals(tax.get_ID(), orderLine.getC_Tax_ID(), "Un-expected tax id"); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + order.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, order.getDocStatus()); + assertEquals(expectedCost, order.getGrandTotal().setScale(2, RoundingMode.HALF_EVEN), "Un-expected order grand total"); + + MInOut receipt = new MInOut(order, MM_RECEIPT_DOCTYPE_ID, order.getDateOrdered()); // MM Receipt + receipt.saveEx(); + + MWarehouse wh = MWarehouse.get(Env.getCtx(), receipt.getM_Warehouse_ID()); + int M_Locator_ID = wh.getDefaultLocator().getM_Locator_ID(); + + MInOutLine receiptLine = new MInOutLine(receipt); + receiptLine.setOrderLine(orderLine, M_Locator_ID, new BigDecimal("1")); + receiptLine.setLine(10); + receiptLine.setQty(new BigDecimal("1")); + receiptLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + receipt.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus()); + MInvoice invoice = new MInvoice(order, MDocType.getOfDocBaseType(Env.getCtx(), MDocType.DOCBASETYPE_APInvoice)[0].getC_DocType_ID(), order.getDateAcct()); + invoice.setDocStatus(DocAction.STATUS_Drafted); + invoice.setDocAction(DocAction.ACTION_Complete); + invoice.saveEx(); + + MInvoiceLine invoiceLine = new MInvoiceLine(invoice); + invoiceLine.setShipLine(receiptLine); + invoiceLine.setLine(10); + invoiceLine.setProduct(product); + invoiceLine.setQty(new BigDecimal("1")); + invoiceLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + invoice.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus()); + + //ensure match po have been posted + MMatchPO[] matchPOs = MMatchPO.getOrderLine(Env.getCtx(), orderLine.get_ID(), getTrxName()); + assertNotNull(matchPOs, "Can't retrive match po for order line"); + assertEquals(1, matchPOs.length, "Un-expected number of match po record for order line"); + if (!matchPOs[0].isPosted()) + DocumentEngine.postImmediate(Env.getCtx(), getAD_Client_ID(), MMatchPO.Table_ID, matchPOs[0].get_ID(), true, getTrxName()); + ProductCost productCost = new ProductCost(Env.getCtx(), product.get_ID(), 0, getTrxName()); + productCost.setQty(new BigDecimal("1")); + MAcctSchema schema = MClientInfo.get().getMAcctSchema1(); + BigDecimal averageCost = productCost.getProductCosts(schema, getAD_Org_ID(), MCost.COSTINGMETHOD_AveragePO, 0, true); + if (averageCost == null) + averageCost = BigDecimal.ZERO; + averageCost = averageCost.setScale(2, RoundingMode.HALF_EVEN); + 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()); + BigDecimal totalDebit = new BigDecimal("0.00"); + for(int id : ids) { + MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); + if (fa.getAccount_ID() == acctAsset.getAccount_ID()) { + totalDebit = totalDebit.add(fa.getAmtAcctDr()); + } + } + assertEquals(expectedCost, totalDebit.setScale(2, RoundingMode.HALF_EVEN), "Un-expected product asset account posted amount"); + } finally { + rollback(); + if (product != null && product.get_ID() > 0) + product.deleteEx(true); + if (tax != null && tax.get_ID() > 0) + tax.deleteEx(true); + if (category != null && category.get_ID() > 0) + category.deleteEx(true); + } + } + + @Test + public void testSeparateTaxPosting() { + MProduct product = null; + MTaxCategory category = null; + MTax tax = null; + try { + category = new MTaxCategory(Env.getCtx(), 0, null); + category.setName("testSeparateTaxPosting"); + category.saveEx(); + + //need to create tax without trx as tax is cache + tax = new MTax(Env.getCtx(), 0, null); + tax.setC_TaxCategory_ID(category.get_ID()); + tax.setIsDocumentLevel(false); + tax.setIsSummary(false); + tax.setRate(new BigDecimal("5.00")); + tax.setTaxPostingIndicator(MTax.TAXPOSTINGINDICATOR_SeparateTaxPosting); + tax.setSOPOType(MTax.SOPOTYPE_PurchaseTax); + tax.setName("testSeparateTaxPosting"); + tax.saveEx(); + CacheMgt.get().reset(); + + //need to create product with trx as order line get product from cache + MProduct p = MProduct.get(PRODUCT_MULCH_ID); + product = new MProduct(Env.getCtx(), 0, null); + product.setM_Product_Category_ID(p.getM_Product_Category_ID()); + product.setC_TaxCategory_ID(category.get_ID()); + product.setIsStocked(true); + product.setIsPurchased(true); + product.setIsSold(true); + product.setIsStocked(true); + product.setProductType(MProduct.PRODUCTTYPE_Item); + product.setName("testSeparateTaxPosting"); + product.setC_UOM_ID(p.getC_UOM_ID()); + product.saveEx(); + + MPriceList priceList = MPriceList.get(PURCHASE_PRICE_LIST_ID); + MPriceListVersion priceListVersion = priceList.getPriceListVersion(null); + MProductPrice productPrice = new MProductPrice(Env.getCtx(), 0, getTrxName()); + productPrice.setM_PriceList_Version_ID(priceListVersion.get_ID()); + productPrice.setM_Product_ID(product.getM_Product_ID()); + productPrice.setPrices(new BigDecimal("2.00"), new BigDecimal("2.00"), new BigDecimal("2.00")); + productPrice.saveEx(); + + //purchase price of 2 + BigDecimal expectedCost = new BigDecimal("2.00").setScale(2, RoundingMode.HALF_EVEN); + //purchase price of 2 + 5% tax + BigDecimal expectedTotal = new BigDecimal("2.00").add(new BigDecimal("2.00").multiply(new BigDecimal("0.05"))).setScale(2, RoundingMode.HALF_EVEN); + + MBPartner bpartner = MBPartner.get(Env.getCtx(), BP_PATIO_ID); + MOrder order = new MOrder(Env.getCtx(), 0, getTrxName()); + order.setBPartner(bpartner); + order.setIsSOTrx(false); + order.setC_DocTypeTarget_ID(); + 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(new BigDecimal("1")); + orderLine.setTax(); + orderLine.saveEx(); + + assertEquals(tax.get_ID(), orderLine.getC_Tax_ID(), "Un-expected tax id"); + + ProcessInfo info = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + order.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, order.getDocStatus()); + assertEquals(expectedTotal, order.getGrandTotal().setScale(2, RoundingMode.HALF_EVEN), "Un-expected order grand total"); + + MInOut receipt = new MInOut(order, MM_RECEIPT_DOCTYPE_ID, order.getDateOrdered()); // MM Receipt + receipt.saveEx(); + + MWarehouse wh = MWarehouse.get(Env.getCtx(), receipt.getM_Warehouse_ID()); + int M_Locator_ID = wh.getDefaultLocator().getM_Locator_ID(); + + MInOutLine receiptLine = new MInOutLine(receipt); + receiptLine.setOrderLine(orderLine, M_Locator_ID, new BigDecimal("1")); + receiptLine.setLine(10); + receiptLine.setQty(new BigDecimal("1")); + receiptLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(receipt, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + receipt.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, receipt.getDocStatus()); + MInvoice invoice = new MInvoice(order, MDocType.getOfDocBaseType(Env.getCtx(), MDocType.DOCBASETYPE_APInvoice)[0].getC_DocType_ID(), order.getDateAcct()); + invoice.setDocStatus(DocAction.STATUS_Drafted); + invoice.setDocAction(DocAction.ACTION_Complete); + invoice.saveEx(); + + MInvoiceLine invoiceLine = new MInvoiceLine(invoice); + invoiceLine.setShipLine(receiptLine); + invoiceLine.setLine(10); + invoiceLine.setProduct(product); + invoiceLine.setQty(new BigDecimal("1")); + invoiceLine.saveEx(); + + info = MWorkflow.runDocumentActionWorkflow(invoice, DocAction.ACTION_Complete); + assertFalse(info.isError(), info.getSummary()); + invoice.load(getTrxName()); + assertEquals(DocAction.STATUS_Completed, invoice.getDocStatus()); + + //ensure match po have been posted + MMatchPO[] matchPOs = MMatchPO.getOrderLine(Env.getCtx(), orderLine.get_ID(), getTrxName()); + assertNotNull(matchPOs, "Can't retrive match po for order line"); + assertEquals(1, matchPOs.length, "Un-expected number of match po record for order line"); + if (!matchPOs[0].isPosted()) + DocumentEngine.postImmediate(Env.getCtx(), getAD_Client_ID(), MMatchPO.Table_ID, matchPOs[0].get_ID(), true, getTrxName()); + ProductCost productCost = new ProductCost(Env.getCtx(), product.get_ID(), 0, getTrxName()); + productCost.setQty(new BigDecimal("1")); + MAcctSchema schema = MClientInfo.get().getMAcctSchema1(); + BigDecimal averageCost = productCost.getProductCosts(schema, getAD_Org_ID(), MCost.COSTINGMETHOD_AveragePO, 0, true); + if (averageCost == null) + averageCost = BigDecimal.ZERO; + averageCost = averageCost.setScale(2, RoundingMode.HALF_EVEN); + 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()); + BigDecimal totalDebit = new BigDecimal("0.00"); + for(int id : ids) { + MFactAcct fa = new MFactAcct(Env.getCtx(), id, getTrxName()); + if (fa.getAccount_ID() == acctAsset.getAccount_ID()) { + totalDebit = totalDebit.add(fa.getAmtAcctDr()); + } + } + assertEquals(expectedCost, totalDebit.setScale(2, RoundingMode.HALF_EVEN), "Un-expected product asset account posted amount"); + } finally { + rollback(); + if (product != null && product.get_ID() > 0) + product.deleteEx(true); + if (tax != null && tax.get_ID() > 0) + tax.deleteEx(true); + if (category != null && category.get_ID() > 0) + category.deleteEx(true); + } + } }