diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WFactReconcile.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WFactReconcile.java index 82765033b4..0f68ea977d 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WFactReconcile.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WFactReconcile.java @@ -1,10 +1,39 @@ +/*********************************************************************** + * 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: * + * - adaxa * + * - anozimada * + * - hengsin * + * - carlosruiz * + * - druiz * + * - nmicoud * + **********************************************************************/ package org.adempiere.webui.apps.form; -import java.math.BigDecimal; import java.sql.Timestamp; import java.text.DecimalFormat; -import java.text.SimpleDateFormat; -import java.util.Calendar; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Vector; import java.util.logging.Level; @@ -60,6 +89,11 @@ import org.zkoss.zul.North; import org.zkoss.zul.South; @org.idempiere.ui.zk.annotation.Form(name = "org.compiere.apps.form.VFactReconcile") +/** + * + * https://wiki.idempiere.org/en/NF1.0_GL_Reconciliation + * + */ public class WFactReconcile extends FactReconcile implements IFormController, EventListener, WTableModelListener, ValueChangeListener{ @@ -67,8 +101,6 @@ implements IFormController, EventListener, WTableModelListener, ValueChan /** Format */ private DecimalFormat m_format = DisplayType.getNumberFormat(DisplayType.Amount); - /** Number of selected rows */ - private int m_noSelected = 0; /** Logger */ private static final CLogger log = CLogger.getCLogger(WFactReconcile.class); @@ -79,19 +111,15 @@ implements IFormController, EventListener, WTableModelListener, ValueChan { Env.setContext(Env.getCtx(), form.getWindowNo(), "IsSOTrx", "Y"); // defaults to no try - { - super.dynInit(); + { dynInit(); zkInit(); - //calculate(); - //southPanel.appendChild(new Separator()); - //southPanel.appendChild(statusBar); } catch(Exception e) { - log.log(Level.SEVERE, "", e); + log.log(Level.SEVERE, e.getMessage(), e); } - } // init + } private Borderlayout mainLayout = new Borderlayout(); private Panel parameterPanel = new Panel(); @@ -136,7 +164,7 @@ implements IFormController, EventListener, WTableModelListener, ValueChan private boolean checkAllSelected = true; /** - * Static Init + * init UI * @throws Exception */ private void zkInit() throws Exception @@ -150,7 +178,6 @@ implements IFormController, EventListener, WTableModelListener, ValueChan bZoomDoc.addActionListener(this); bGenerate.setEnabled(false); bReset.setEnabled(false); - //bRefresh.setText(Msg.getMsg(Env.getCtx(), "Query")); bGenerate.setLabel(Msg.getMsg(Env.getCtx(),"Process")); bReset.setLabel(Msg.getMsg(Env.getCtx(),"Reset")); bZoom.setLabel(Msg.translate(Env.getCtx(), "Fact_Acct_ID")); @@ -179,11 +206,7 @@ implements IFormController, EventListener, WTableModelListener, ValueChan differenceLabel.setText(Msg.getMsg(Env.getCtx(), "Difference")); - //differenceField.setBackground(AdempierePLAF.getFieldBackground_Inactive()); - //differenceField.setEditable(false); differenceField.setText("0"); - //differenceField.setColumns(8); - //differenceField.setHorizontalAlignment(SwingConstants.RIGHT); // bGenerate.addActionListener(this); bCancel.addActionListener(this); @@ -244,7 +267,6 @@ implements IFormController, EventListener, WTableModelListener, ValueChan mainLayout.appendChild(center); center.appendChild(miniTable); ZKUpdateUtil.setWidth(miniTable, "100%"); - //ZKUpdateUtil.setHeight(miniTable, "99%"); center.setStyle("border: none"); // Command Panel @@ -297,10 +319,10 @@ implements IFormController, EventListener, WTableModelListener, ValueChan * Dynamic Init (prepare dynamic fields) * @throws Exception if Lookups cannot be initialized */ + @Override public void dynInit() throws Exception { - - m_AD_Client_ID = Env.getAD_Client_ID(Env.getCtx()); + super.dynInit(); // AcctSchema int AD_Column_ID = FactReconcile.col_C_AcctSchema_ID; // Fact_Acct.C_AcctSchema_ID @@ -341,7 +363,7 @@ implements IFormController, EventListener, WTableModelListener, ValueChan fieldAccount = new WTableDirEditor("C_ElementValue_ID", true, false, true, lookupAccount); } - public void loadData(){ + private void loadData(){ if(fieldAccount.isNullOrEmpty()) throw new WrongValueException(fieldAccount.getComponent(), Msg.getMsg(Env.getCtx(), "FillMandatory")); @@ -400,32 +422,18 @@ implements IFormController, EventListener, WTableModelListener, ValueChan * Calculate selected rows. * - add up selected rows */ - public void calculateSelection() + private void calculateSelection() { - m_noSelected = 0; - BigDecimal selectedAmt = Env.ZERO; - - int rows = miniTable.getRowCount(); - for (int i = 0; i < rows; i++) - { - boolean isSelected = (Boolean)miniTable.getModel().getValueAt(i, selectedColIndex); - if (isSelected) - { - BigDecimal amt = (BigDecimal)miniTable.getModel().getValueAt(i, amtColIndex); - if (amt != null) - selectedAmt = selectedAmt.add(amt); - m_noSelected++; - } - } + calculateSelection(miniTable); // Information StringBuilder info = new StringBuilder(); info.append(m_noSelected).append(" ").append(Msg.getMsg(Env.getCtx(), "Selected")).append(" / ").append(miniTable.getRowCount()); - differenceField.setText(m_format.format(selectedAmt)); + differenceField.setText(m_format.format(m_selectedAmt)); dataStatus.setText(info.toString()); // - bGenerate.setEnabled(m_noSelected != 0 && Env.ZERO.compareTo(selectedAmt) == 0 && !isReconciled.isSelected()); + bGenerate.setEnabled(m_noSelected != 0 && Env.ZERO.compareTo(m_selectedAmt) == 0 && !isReconciled.isSelected()); bReset.setEnabled(m_noSelected > 0 && isReconciled.isSelected()); } // calculateSelection @@ -475,7 +483,6 @@ implements IFormController, EventListener, WTableModelListener, ValueChan @Override public void onEvent(Event event) throws Exception { - log.config(""); if (event.getTarget().equals(bGenerate)) generateReconciliation(); @@ -499,36 +506,18 @@ implements IFormController, EventListener, WTableModelListener, ValueChan } private void generateReconciliation() { - log.info(""); - // - //miniTable.stopEditor(true); if (miniTable.getRowCount() == 0) return; - //miniTable.setRowSelectionInterval(0,0); calculateSelection(); if (m_noSelected == 0) return; - String format = "yyyy-MM-dd HH:mm:ss.SSS"; - Calendar cal = Calendar.getInstance(); - SimpleDateFormat sdf = new SimpleDateFormat(format); - String time = sdf.format(cal.getTime()); - - for ( int r = 0; r < miniTable.getModel().getRowCount(); r++ ) - { - boolean isSelected = (Boolean)miniTable.getModel().getValueAt(r, selectedColIndex); - - if (isSelected) - { - KeyNamePair pp = (KeyNamePair)miniTable.getModel().getValueAt(r, idColIndex); - - int factId = pp.getKey(); - - boolean result = generate(factId, time); - if(!result) - continue; - - ((ListModelTable) miniTable.getModel()).remove(r--); + List generated = new ArrayList<>(); + generate(miniTable, generated); + if (generated.size() > 0) { + Collections.reverse(generated); + for(int i : generated) { + miniTable.getModel().remove(i); } } @@ -541,8 +530,6 @@ implements IFormController, EventListener, WTableModelListener, ValueChan */ protected void zoom (int tableID) { - log.info(""); - int selected = miniTable.getSelectedRow(); if ( selected == -1 ) @@ -561,31 +548,21 @@ implements IFormController, EventListener, WTableModelListener, ValueChan } // zoom private void resetReconciliation() { - log.info(""); if (miniTable.getRowCount() == 0) return; calculateSelection(); if (m_noSelected == 0) return; - for ( int r = 0; r < miniTable.getModel().getRowCount(); r++ ) - { - boolean isSelected = (Boolean)miniTable.getModel().getValueAt(r, selectedColIndex); - - if (isSelected) - { - KeyNamePair pp = (KeyNamePair)miniTable.getModel().getValueAt(r, idColIndex); - - int factId = pp.getKey(); - - boolean result = reset(factId); - if(!result) - continue; - - ((ListModelTable) miniTable.getModel()).remove(r--); + List resets = new ArrayList<>(); + reset(miniTable, resets); + if (resets.size() > 0) { + Collections.reverse(resets); + for(int i : resets) { + miniTable.getModel().remove(i); } } - + bSelect.setPressed(false); } @@ -594,7 +571,7 @@ implements IFormController, EventListener, WTableModelListener, ValueChan return form; } - void onbSelect() { + private void onbSelect() { ListModelTable model = miniTable.getModel(); int rows = model.getSize(); Boolean selectAll = bSelect.isPressed() ? Boolean.FALSE : Boolean.TRUE; @@ -605,6 +582,6 @@ implements IFormController, EventListener, WTableModelListener, ValueChan checkAllSelected = true; miniTable.setModel(model); calculateSelection(); - } + } } diff --git a/org.adempiere.ui/src/org/compiere/apps/form/FactReconcile.java b/org.adempiere.ui/src/org/compiere/apps/form/FactReconcile.java index 8a1bcc8703..7bd0f7c708 100644 --- a/org.adempiere.ui/src/org/compiere/apps/form/FactReconcile.java +++ b/org.adempiere.ui/src/org/compiere/apps/form/FactReconcile.java @@ -1,3 +1,32 @@ +/*********************************************************************** + * 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: * + * - adaxa * + * - anozimada * + * - hengsin * + * - carlosruiz * + * - druiz * + * - nmicoud * + **********************************************************************/ package org.compiere.apps.form; import java.math.BigDecimal; @@ -5,6 +34,9 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.List; import java.util.Vector; import java.util.logging.Level; @@ -22,17 +54,17 @@ import org.compiere.util.Msg; public class FactReconcile { /** Logger */ - public static final CLogger log = CLogger.getCLogger(FactReconcile.class); + protected static final CLogger log = CLogger.getCLogger(FactReconcile.class); - public int m_AD_Client_ID = 0; - public int m_AD_Org_ID = 0; - public int m_Account_ID = 0; - public int m_C_AcctSchema_ID = 0; - public boolean m_isReconciled = false; - public int m_C_BPartner_ID = 0; - public int m_M_Product_ID = 0; - public Timestamp m_DateAcct = null; - public Timestamp m_DateAcct2 = null; + protected int m_AD_Client_ID = 0; + protected int m_AD_Org_ID = 0; + protected int m_Account_ID = 0; + protected int m_C_AcctSchema_ID = 0; + protected boolean m_isReconciled = false; + protected int m_C_BPartner_ID = 0; + protected int m_M_Product_ID = 0; + protected Timestamp m_DateAcct = null; + protected Timestamp m_DateAcct2 = null; public int selectedColIndex = 2; public int idColIndex = 8; @@ -43,18 +75,31 @@ public class FactReconcile { static protected int col_C_BPartner_ID = COLUMN_C_INVOICE_C_BPARTNER_ID; // C_Invoice.C_BPartner_ID static protected int col_M_Product_ID = COLUMN_FACT_ACCT_M_PRODUCT_ID; // Fact_Acct.M_Product_ID + //Optional trx name + protected String m_trxName; + /** Number of selected rows */ + protected int m_noSelected; + /** Total selected amount */ + protected BigDecimal m_selectedAmt; + /** + * dynamic initialization + * @throws Exception + */ public void dynInit() throws Exception { m_AD_Client_ID = Env.getAD_Client_ID(Env.getCtx()); } + /** + * + * @return column header labels + */ public Vector getColumnNames() { // Header Info Vector columnNames = new Vector(); columnNames.add(Msg.translate(Env.getCtx(), "Amt")); - //columnNames.add(Msg.translate(Env.getCtx(), "AmtAcct")); columnNames.add(Msg.translate(Env.getCtx(), "DR/CR")); columnNames.add(Msg.translate(Env.getCtx(), "Selected")); columnNames.add(Msg.translate(Env.getCtx(), "C_BPartner_ID")); @@ -70,6 +115,10 @@ public class FactReconcile { return columnNames; } + /** + * + * @return list of Fact_Acct records + */ public Vector> getData() { Vector> data = new Vector>(); @@ -130,7 +179,7 @@ public class FactReconcile { ResultSet rs = null; try { - pstmt = DB.prepareStatement(sql.toString(), null); + pstmt = DB.prepareStatement(sql.toString(), m_trxName); int i = 1; pstmt.setInt(i++, m_AD_Client_ID); @@ -159,20 +208,19 @@ public class FactReconcile { while (rs.next()) { Vector line = new Vector(); - //line.add(rs.getBigDecimal(1)); // 1-Amt - line.add(rs.getBigDecimal(2)); // 2-AmtAcct - line.add(rs.getString(3)); // 3-DR/CR - line.add(Boolean.FALSE); // 4-Fact_Acct_ID - line.add(rs.getString(5)); // 5-BP - line.add(rs.getTimestamp(6)); // 6-DateAcct - line.add(rs.getString(7)); // 7-GL Category - line.add(rs.getString(8)); // 8-Product - line.add(rs.getBigDecimal(9)); // 9-Qty - KeyNamePair pp = new KeyNamePair(rs.getInt(4), rs.getString(10)); // 10 Fact_Acct_ID - description + line.add(rs.getBigDecimal(2)); // 1-AmtAcct + line.add(rs.getString(3)); // 2-DR/CR + line.add(Boolean.FALSE); // 3-Fact_Acct_ID + line.add(rs.getString(5)); // 4-BP + line.add(rs.getTimestamp(6)); // 5-DateAcct + line.add(rs.getString(7)); // 6-GL Category + line.add(rs.getString(8)); // 7-Product + line.add(rs.getBigDecimal(9)); // 8-Qty + KeyNamePair pp = new KeyNamePair(rs.getInt(4), rs.getString(10)); // 9 Fact_Acct_ID - description line.add(pp); - line.add(rs.getString(11)); // 11-MatchCode - line.add(rs.getTimestamp(12)); // 12-DateTrx - line.add(rs.getString(13)); // 13-Org + line.add(rs.getString(11)); // 10-MatchCode + line.add(rs.getTimestamp(12)); // 11-DateTrx + line.add(rs.getString(13)); // 12-Org // data.add(line); } @@ -191,42 +239,97 @@ public class FactReconcile { return data; } + /** + * set class type of column + * @param miniTable + */ public void setColumnClass(IMiniTable miniTable) { int i = 0; - //miniTable.setColumnClass(i++, BigDecimal.class, true); // 1-Amt - miniTable.setColumnClass(i++, BigDecimal.class, true); // 2-AmtAcct - miniTable.setColumnClass(i++, String.class, true); // 3-DR/CR - miniTable.setColumnClass(i++, Boolean.class, false); // 4-Selected - miniTable.setColumnClass(i++, String.class, true); // 5-BP - miniTable.setColumnClass(i++, Timestamp.class, true); // 6-DateAcct - miniTable.setColumnClass(i++, String.class, true); // 7-GL Category - miniTable.setColumnClass(i++, String.class, true); // 8-Product - miniTable.setColumnClass(i++, BigDecimal.class, true); // 9-Qty - miniTable.setColumnClass(i++, String.class, true); // 10-Description - miniTable.setColumnClass(i++, String.class, true); // 11-MatchCode - miniTable.setColumnClass(i++, Timestamp.class, true); // 12-DateTrx - miniTable.setColumnClass(i++, String.class, true); // 13-Org + miniTable.setColumnClass(i++, BigDecimal.class, true); // 1-AmtAcct + miniTable.setColumnClass(i++, String.class, true); // 2-DR/CR + miniTable.setColumnClass(i++, Boolean.class, false); // 3-Selected + miniTable.setColumnClass(i++, String.class, true); // 4-BP + miniTable.setColumnClass(i++, Timestamp.class, true); // 5-DateAcct + miniTable.setColumnClass(i++, String.class, true); // 6-GL Category + miniTable.setColumnClass(i++, String.class, true); // 7-Product + miniTable.setColumnClass(i++, BigDecimal.class, true); // 8-Qty + miniTable.setColumnClass(i++, String.class, true); // 9-Description + miniTable.setColumnClass(i++, String.class, true); // 10-MatchCode + miniTable.setColumnClass(i++, Timestamp.class, true); // 11-DateTrx + miniTable.setColumnClass(i++, String.class, true); // 12-Org // Table UI miniTable.autoSize(); } /** - * Generate Reconciliation record - * @return + * Calculate selected rows. + * - add up selected rows + */ + public void calculateSelection(IMiniTable miniTable) + { + m_noSelected = 0; + m_selectedAmt = Env.ZERO; + + int rows = miniTable.getRowCount(); + for (int i = 0; i < rows; i++) + { + boolean isSelected = (Boolean)miniTable.getValueAt(i, selectedColIndex); + if (isSelected) + { + BigDecimal amt = (BigDecimal)miniTable.getValueAt(i, amtColIndex); + if (amt != null) + m_selectedAmt = m_selectedAmt.add(amt); + m_noSelected++; + } + } + } + + /** + * Generate {@link MFactReconciliation} record from selected row in miniTable + * @param miniTable + * @param generatedIndexes list of rows that {@link MFactReconciliation} have been successfully created from + */ + public void generate(IMiniTable miniTable, List generatedIndexes) { + String format = "yyyy-MM-dd HH:mm:ss.SSS"; + Calendar cal = Calendar.getInstance(); + SimpleDateFormat sdf = new SimpleDateFormat(format); + String time = sdf.format(cal.getTime()); + + for ( int r = 0; r < miniTable.getRowCount(); r++ ) + { + boolean isSelected = (Boolean)miniTable.getValueAt(r, selectedColIndex); + + if (isSelected) + { + KeyNamePair pp = (KeyNamePair)miniTable.getValueAt(r, idColIndex); + + int factId = pp.getKey(); + + boolean result = generate(factId, time); + if(!result) + continue; + + if (generatedIndexes != null) + generatedIndexes.add(r); + } + } + } + + /** + * Generate Reconciliation record + * @return true if save successfully */ public boolean generate(int factId, String time) { - log.info(""); - String matchcode = "Manual: " + Env.getContext(Env.getCtx(), Env.AD_USER_NAME) + " " + time; - MFactReconciliation rec = new Query(Env.getCtx(), MFactReconciliation.Table_Name, "Fact_Acct_ID = ?", null) + MFactReconciliation rec = new Query(Env.getCtx(), MFactReconciliation.Table_Name, "Fact_Acct_ID = ?", m_trxName) .setParameters(new Object[] {factId}).first(); if ( rec == null ) { - rec = new MFactReconciliation(Env.getCtx(), 0, null); + rec = new MFactReconciliation(Env.getCtx(), 0, m_trxName); rec.setFact_Acct_ID(factId); } @@ -235,14 +338,38 @@ public class FactReconcile { } /** - * Generate Reconciliation record - * @return + * Reset/Delete {@link MFactReconciliation} record from selected row in miniTable + * @param miniTable + * @param resetedIndexes list of rows that {@link MFactReconciliation} have been successfully deleted + */ + public void reset(IMiniTable miniTable, List resetedIndexes) { + for ( int r = 0; r < miniTable.getRowCount(); r++ ) + { + boolean isSelected = (Boolean)miniTable.getValueAt(r, selectedColIndex); + + if (isSelected) + { + KeyNamePair pp = (KeyNamePair)miniTable.getValueAt(r, idColIndex); + + int factId = pp.getKey(); + + boolean result = reset(factId); + if(!result) + continue; + + if (resetedIndexes != null) + resetedIndexes.add(r); + } + } + } + + /** + * Reset/delete Reconciliation record + * @return true if reset/delete successfully */ public boolean reset(int factId) { - log.info(""); - - MFactReconciliation rec = new Query(Env.getCtx(), MFactReconciliation.Table_Name, "Fact_Acct_ID = ?", null) + MFactReconciliation rec = new Query(Env.getCtx(), MFactReconciliation.Table_Name, "Fact_Acct_ID = ?", m_trxName) .setParameters(new Object[] {factId}).first(); if ( rec == null ) @@ -253,6 +380,10 @@ public class FactReconcile { return rec.delete(false); } + /** + * + * @return list of account element records + */ protected Vector getAccount(){ Vector vector = new Vector(); String sql = MRole.getDefault().addAccessSQL( @@ -268,7 +399,7 @@ public class FactReconcile { ResultSet rs = null; try { - pstmt = DB.prepareStatement(sql, null); + pstmt = DB.prepareStatement(sql, m_trxName); rs = pstmt.executeQuery(); while (rs.next()) { diff --git a/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java b/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java index 42ed000d41..b9fbaa3826 100644 --- a/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java +++ b/org.idempiere.test/src/org/idempiere/test/DictionaryIDs.java @@ -199,6 +199,16 @@ public final class DictionaryIDs { } } + public enum C_ElementValue { + CHECKING_IN_TRANSFER(509); + + public final int id; + + private C_ElementValue(int id) { + this.id = id; + } + } + public enum C_PaymentTerm { NET_30(100), IMMEDIATE(105), diff --git a/org.idempiere.test/src/org/idempiere/test/form/FactReconcileFormTest.java b/org.idempiere.test/src/org/idempiere/test/form/FactReconcileFormTest.java new file mode 100644 index 0000000000..59a90451a4 --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/form/FactReconcileFormTest.java @@ -0,0 +1,241 @@ +/*********************************************************************** + * 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: * + * - hengsin * + **********************************************************************/ +package org.idempiere.test.form; + +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.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.Vector; + +import org.compiere.apps.form.FactReconcile; +import org.compiere.model.MBankStatement; +import org.compiere.model.MBankStatementLine; +import org.compiere.model.MClient; +import org.compiere.model.MFactAcct; +import org.compiere.model.MFactReconciliation; +import org.compiere.model.MPayment; +import org.compiere.model.Query; +import org.compiere.process.DocAction; +import org.compiere.process.DocumentEngine; +import org.compiere.process.ProcessInfo; +import org.compiere.util.Env; +import org.compiere.util.KeyNamePair; +import org.compiere.util.Util; +import org.compiere.wf.MWorkflow; +import org.idempiere.test.AbstractTestCase; +import org.idempiere.test.DictionaryIDs; +import org.idempiere.test.ui.MiniTableImpl; +import org.junit.jupiter.api.Test; + +/** + * + * @author hengsin + * + */ +public class FactReconcileFormTest extends AbstractTestCase { + + public FactReconcileFormTest() { + } + + @Test + public void testReconcileForm() { + MPayment payment = new MPayment(Env.getCtx(), 0, getTrxName()); + payment.setC_BankAccount_ID(DictionaryIDs.C_BankAccount.ACCOUNT_1234.id); + payment.setIsReceipt(true); + payment.setPayAmt(new BigDecimal("9.99")); + payment.setC_BPartner_ID(DictionaryIDs.C_BPartner.SEED_FARM.id); + payment.setC_DocType_ID(true); + payment.setC_Currency_ID(DictionaryIDs.C_Currency.USD.id); + payment.saveEx(); + + ProcessInfo pi = MWorkflow.runDocumentActionWorkflow(payment, DocAction.ACTION_Complete); + assertFalse(pi.isError(), pi.getSummary()); + + payment.load(getTrxName()); + if (!payment.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), payment.getAD_Client_ID(), MPayment.Table_ID, payment.get_ID(), true, getTrxName()); + assertTrue(Util.isEmpty(error, true), error); + } + + Query query = new Query(Env.getCtx(), MFactAcct.Table_Name, "AD_Table_ID=? AND Record_ID=? AND Account_ID=?", getTrxName()); + MFactAcct paymentFact = query.setParameters(MPayment.Table_ID, payment.get_ID(), DictionaryIDs.C_ElementValue.CHECKING_IN_TRANSFER.id).first(); + assertNotNull(paymentFact, "Faild to retrieve MFactAcct checking in transfer record for payment"); + + MBankStatement stmt = new MBankStatement(Env.getCtx(), 0, getTrxName()); + stmt.setC_BankAccount_ID(DictionaryIDs.C_BankAccount.ACCOUNT_1234.id); + stmt.setName("testReconcileForm"); + stmt.setDateAcct(payment.getDateAcct()); + stmt.saveEx(); + + MBankStatementLine line = new MBankStatementLine(stmt); + line.setC_Payment_ID(payment.get_ID()); + line.setStmtAmt(payment.getPayAmt()); + line.setTrxAmt(line.getStmtAmt()); + line.setC_Currency_ID(payment.getC_Currency_ID()); + line.saveEx(); + + pi = MWorkflow.runDocumentActionWorkflow(stmt, DocAction.ACTION_Complete); + assertFalse(pi.isError(), pi.getSummary()); + + stmt.load(getTrxName()); + if (!stmt.isPosted()) { + String error = DocumentEngine.postImmediate(Env.getCtx(), stmt.getAD_Client_ID(), MBankStatement.Table_ID, stmt.get_ID(), true, getTrxName()); + assertTrue(Util.isEmpty(error, true), error); + } + MFactAcct statementFact = query.setParameters(MBankStatement.Table_ID, stmt.get_ID(), DictionaryIDs.C_ElementValue.CHECKING_IN_TRANSFER.id).first(); + assertNotNull(statementFact, "Faild to retrieve MFactAcct checking in transfer record for bank statement"); + + FactReconcileImpl fri = new FactReconcileImpl(); + Vector accounts = fri.getAccount(); + assertTrue(accounts.size() > 0, "Failed to retrieve account elements"); + KeyNamePair checkingInTransfer = null; + for(KeyNamePair account : accounts) { + if (account.getKey() == DictionaryIDs.C_ElementValue.CHECKING_IN_TRANSFER.id) { + checkingInTransfer = account; + break; + } + } + assertNotNull(checkingInTransfer, "Can't find Checking In Transfer account element"); + + //load not reconciled fact records + fri.setParameters(payment.getC_BPartner_ID(), payment.getDateAcct(), checkingInTransfer, false); + fri.loadData(); + assertTrue(fri.miniTable.getRowCount() >= 2, "Failed to load not reconciled fact account records"); + for(int i = 0; i < fri.miniTable.getRowCount(); i++) { + KeyNamePair knp = (KeyNamePair) fri.miniTable.getValueAt(i, fri.getIdColumnIndex()); + if (knp.getKey() == paymentFact.get_ID() || knp.getKey() == statementFact.get_ID()) { + fri.miniTable.setValueAt(Boolean.TRUE, i, fri.getSelectedColumnIndex()); + } + } + + //select and generate reconciliation + fri.calculateSelection(fri.miniTable); + assertEquals(2, fri.getSelectedCount(), "Failed to locate not reconciled payment and bank statement fact records in mini table"); + List generated = new ArrayList<>(); + fri.generate(fri.miniTable, generated); + assertEquals(2, generated.size(), "Failed to generate " + MFactReconciliation.Table_Name + " records"); + + //reload not reconciled fact records after generate + fri.setParameters(payment.getC_BPartner_ID(), payment.getDateAcct(), checkingInTransfer, false); + fri.loadData(); + for(int i = 0; i < fri.miniTable.getRowCount(); i++) { + KeyNamePair knp = (KeyNamePair) fri.miniTable.getValueAt(i, fri.getIdColumnIndex()); + if (knp.getKey() == paymentFact.get_ID() || knp.getKey() == statementFact.get_ID()) { + fri.miniTable.setValueAt(Boolean.TRUE, i, fri.getSelectedColumnIndex()); + } + } + //should select 0 after generate + fri.calculateSelection(fri.miniTable); + assertEquals(0, fri.getSelectedCount(), "Payment and bank statement line not reconcilled"); + + //load reconciled fact records + fri.setParameters(payment.getC_BPartner_ID(), payment.getDateAcct(), checkingInTransfer, true); + fri.loadData(); + for(int i = 0; i < fri.miniTable.getRowCount(); i++) { + KeyNamePair knp = (KeyNamePair) fri.miniTable.getValueAt(i, fri.getIdColumnIndex()); + if (knp.getKey() == paymentFact.get_ID() || knp.getKey() == statementFact.get_ID()) { + fri.miniTable.setValueAt(Boolean.TRUE, i, fri.getSelectedColumnIndex()); + } + } + fri.calculateSelection(fri.miniTable); + assertEquals(2, fri.getSelectedCount(), "Failed to locate reconcilled payment and statement fact record in mini table"); + //select and reset + List reseted = new ArrayList<>(); + fri.reset(fri.miniTable, reseted); + assertEquals(2, reseted.size(), "Failed to reset " + MFactReconciliation.Table_Name + " records"); + + //reload not reconciled fact records after reset + fri.setParameters(payment.getC_BPartner_ID(), payment.getDateAcct(), checkingInTransfer, false); + fri.loadData(); + assertTrue(fri.miniTable.getRowCount() >= 2, "Failed to load fact account records"); + for(int i = 0; i < fri.miniTable.getRowCount(); i++) { + KeyNamePair knp = (KeyNamePair) fri.miniTable.getValueAt(i, fri.getIdColumnIndex()); + if (knp.getKey() == paymentFact.get_ID() || knp.getKey() == statementFact.get_ID()) { + fri.miniTable.setValueAt(Boolean.TRUE, i, fri.getSelectedColumnIndex()); + } + } + //should select 2 again after reset + fri.calculateSelection(fri.miniTable); + assertEquals(2, fri.getSelectedCount(), "Failed to locate payment and statement fact record in mini table"); + } + + private class FactReconcileImpl extends FactReconcile { + protected MiniTableImpl miniTable = null; + + protected FactReconcileImpl() { + m_trxName = getTrxName(); + m_C_AcctSchema_ID = MClient.get(Env.getCtx()).getAcctSchema().get_ID(); + m_AD_Client_ID = Env.getAD_Client_ID(Env.getCtx()); + } + + public void loadData() { + miniTable = new MiniTableImpl(); + Vector headers = getColumnNames(); + for(String header : headers) { + miniTable.addColumn(header); + } + setColumnClass(miniTable); + + Vector> datas = super.getData(); + for (Vector data : datas) { + int row = miniTable.getRowCount(); + miniTable.setRowCount(row+1); + for(int column = 0; column < data.size(); column++) { + Object value = data.get(column); + miniTable.setValueAt(value, row, column); + } + } + } + + protected void setParameters(int C_BPartner_ID, Timestamp dateAcct, KeyNamePair checkingInTransfer, boolean isReconciled) { + m_C_BPartner_ID = C_BPartner_ID; + m_DateAcct = dateAcct; + m_Account_ID = checkingInTransfer.getKey(); + m_isReconciled = isReconciled; + } + + protected Vector getAccount() { + return super.getAccount(); + } + + protected int getSelectedCount() { + return m_noSelected; + } + + protected int getIdColumnIndex() { + return idColIndex; + } + + protected int getSelectedColumnIndex() { + return selectedColIndex; + } + } +}