ADempiere Business Suite ERP/CRM/MFG/SCM/POS done the Bazaar way in an open and unabated fashion. Focus is on the Community that includes Subject Matter Specialists, Implementors and End-Users.
yamelsenih on develop
#3266 Error in calculating quan… (compare)
e-Evolution on develop
Adding a clear method to empty … Adding a clear method to empty … Merge pull request #3240 from O… (compare)
@e-Evolution @yamelsenih I found an issue with the ProcessBuilder. As a static method, I can "hijack" the process if I'm running more than one process at a time. In my case, I have one process, call it Process1, that I have to run multiple times. I have another process, Process2, that I'm going to run in between the runs of Process1. I can reduce code duplication by creating a processorForProcess1
using the ProcessBuilder where I set the parameters and then call processorForProcess1.execute()
each time I want to run it. It works the for multiple runs as expected. But when I use the ProcessBuilder to execute Process2, the next time I call processorForProcess1.execute()
again, it reruns Process2.
I think the problem is the static constructor used by the ProcessBuilder and the internal static processBuilder field. I'd suggest we deprecate these and use a non-static constructor instead.
ProcessBuilder processor = ProcessBuilder.with...;
// should be
ProcessBuilder processor = new ProcessBuilder().with...;
What do you think?
@mckayERP Hi Mike, I did not understand your problem, but currently there is code that from a process is called another process that works correctly, it is important to handle the transaction, you can handle two approaches:
1.- Execute all the processes with the same transaction, it is recommended for processes that are executed in a short time and that must be completely atomic.
In this case, the parameters are important to indicate that it comes from a parent process, that the second process does not close the transaction at the end, and which transaction will be used:
.withParentProcess (this)
.withoutTransactionClose ()
.execute (get_TrxName ());
Example of executing two processes with the same transaction:
2.- Execute each process in a different transaction, it is recommended for very long processes and where the control of the processed records is handled individually.
Use Trx.run (trxName -> {if you need to run a process in its own transaction.
@DisplayName("Given the GardenWorld context")
class ProcessBuilderTest extends CommonGWSetup {
private static final String CLIENT_ACCOUNTING_IMMEDIATE = "I";
ProcessBuilder clientAcctProcessor;
ProcessBuilder resetAccounting;
@BeforeEach
void givenClientAccountingEnabled() {
enableClientAccounting();
}
@Nested
@DisplayName("When the first process is created")
class WhenASingleProcessIsCreated {
@BeforeEach
void whenClientAcctProcessorIsCreated() {
clientAcctProcessor = ProcessBuilder.create(ctx)
.process(org.adempiere.process.ClientAcctProcessor.class)
.withTitle("ClientAcctProcessorTest");
}
@Test
@DisplayName("When the first process is run, "
+ "then the process succeeds")
void whenTheClientAcctProcessorIsRun_itSucceeds() {
ProcessInfo info = clientAcctProcessor.execute(trxName);
assertEquals("OK", info.getSummary());
}
@Nested
@DisplayName("When a second process is created")
class WhenASecondProcessIsCreated {
@BeforeEach
void whenFactResetProcessorIsCreated() {
resetAccounting = ProcessBuilder.create(ctx)
.process(org.compiere.process.FactAcctReset.class)
.withTitle("FactAcctReset")
.withParameter(FactAcctReset.AD_CLIENT_ID,
AD_CLIENT_ID)
.withParameter(FactAcctReset.DELETEPOSTING, true)
.withParameter(FactAcctReset.DATEACCT,
TimeUtil.getDay(1999, 01, 01), today);
}
@Test
@DisplayName("When the second process is executed, "
+ "then it succeeds")
void whenTheFactResetProcessorIsRun_itSucceeds() {
ProcessInfo info = resetAccounting.execute(trxName);
assertTrue(info.getSummary().startsWith("Updated"));
}
@Test
@DisplayName("When the first process is executed again, "
+ "then it should succeed")
void butWhenTheClientAcctProcessorIsRun_itShouldSucceed() {
ProcessInfo info = clientAcctProcessor.execute(trxName); // This throws a NPE
assertEquals("OK", info.getSummary());
}
}
}
private void enableClientAccounting() {
MSysConfig gwAccounting = getOrCreateClientAcctConfig();
gwAccounting.setValue(CLIENT_ACCOUNTING_IMMEDIATE);
gwAccounting.saveEx();
}
private MSysConfig getOrCreateClientAcctConfig() {
return clientAccountingConfigs(AD_CLIENT_ID)
.filter(config -> config.getAD_Client_ID() == AD_CLIENT_ID)
.findFirst()
.orElseGet(() -> {
MSysConfig config = new MSysConfig(ctx, 0, null);
config.setName("CLIENT_ACCOUNTING");
config.saveEx();
return config;
});
}
private Stream<MSysConfig> clientAccountingConfigs(int clientId) {
String where = "Name=? AND AD_Client_ID IN (0, ?)";
return new Query(ctx, I_AD_SysConfig.Table_Name, where, null)
.setOnlyActiveRecords(true)
.setParameters("CLIENT_ACCOUNTING", clientId)
.setOrderBy("AD_Client_ID DESC, AD_Org_ID DESC")
.list(MSysConfig.class)
.stream();
}
}
@ProcessRunError@ @Error@ Client Accounting is not enabled
In order to run this process you need to enable client accounting, this can be done in window System Configurator, setting the parameter CLIENT_ACCOUNTING to [I]mmediate or [Q]ueue
+ "throw an exception")
final void whenClientAcctDisabled_throwsException() {
assertThrows(AdempiereException.class, () -> {
process.execute(trxName);
});
}
ProcessInfo info = process.execute(trxName);
if (info.isError())
throw new AdempiereException(info.getThrowable().toString());
private void assertProcessWasSuccessful(ProcessInfo info) {
assertFalse(info.isError());
assertEquals("OK", info.getSummary());
}
@Test
@DisplayName("When passed no parameters, then the process should "
+ "succeed")
final void whenNoParameters_doItReturnsOk() throws Exception {
ProcessInfo info = process.execute(trxName);
assertProcessWasSuccessful(info);
}
Other proiblem that I found was that report to @yamelsenih Yamel , the table FM_Batch have not DateAcct then the return Client Accounting Processor return an NPE for this reason
Here is the fix adempiere/adempiere#3226