↧
Merry Christmas and Happy New Year!
↧
Multiple Column Report. For Example - Payment Remittance.
Here is a method of adding fixed number of columns into a report. In my case Additional Remittance Form report, which is often printed after or when check is issued.
PayNb PayDoc CompanyID RowNumber InvRefNbr1 InvRefNbr2 InvRefNbr3
--------------- ------ ----------- ----------- --------------- --------------- ---------------
000001 CHK 2 1 038630
000002 CHK 2 1 038631
000003 CHK 2 1 038632
000005 CHK 2 2 038652
000005 CHK 2 1 038648
000005 CHK 2 1 038649
000005 CHK 2 1 038650
000004 QCK 2 1 000004
Sometimes we need to list all the Bills, the check is paid for.
In certain format. In my case in 3 (Three) columns.
For example for check 00100002 we have 4 Bills attached:
In certain format. In my case in 3 (Three) columns.
For example for check 00100002 we have 4 Bills attached:
Well, I know, it will be good to show how much exactly each bill is paid for, but this is minor.
Lets achieve first multiple column printing.
First of all, I would need a View in a database, that will return 3 Bill Numbers in a row, in a sequential order.
If I used standard select
select p.RefNbr,i.CompanyID, i.DocType, i.RefNbr
from APPayment p inner join APAdjust a on p.CompanyID = a.CompanyID and
p.DocType = a.AdjgDocType and p.RefNbr = a.AdjgRefNbr
inner join APInvoice i on p.CompanyID = i.CompanyID and a.AdjdDocType = i.DocType and
i.RefNbr = a.AdjdRefNbr
It will just list me all the Payments from AP with corresponding Bills:
RefNbr CompanyID DocType RefNbr
--------------- ----------- ------- ---------------
000001 2 INV 038630
000002 2 INV 038631
000003 2 INV 038632
000005 2 ACR 038652
000005 2 INV 038648
000005 2 INV 038649
000005 2 INV 038650
000004 2 QCK 000004
Well, it shows for Check 000005 we have 3 Bills and 1 Credit Adjustment. This relates to my example above with what we actually want to achieve.
Second step would be - we need to add a numeric counter, to count the order of those rows. In my case from 1 to 4 for highlighted rows. Will also be good to add a type of Check, as we may have different.
Lets rework our view:
select p.RefNbr as PayNbr, p.DocType, i.CompanyID as CompanyID, i.DocType as InvDocType, i.RefNbr as InvRefNbr
from APPayment p inner join APAdjust a on p.CompanyID = a.CompanyID and
p.DocType = a.AdjgDocType and p.RefNbr = a.AdjgRefNbr
inner join APInvoice i on p.CompanyID = i.CompanyID and a.AdjdDocType = i.DocType and
i.RefNbr = a.AdjdRefNbr
order by i.RefNbr
PayNbr DocType CompanyID InvDocType InvRefNbr
--------------- ------- ----------- ---------- ---------------
000004 QCK 2 QCK 000004
000001 CHK 2 INV 038630
000002 CHK 2 INV 038631
000003 CHK 2 INV 038632
000005 CHK 2 INV 038648
000005 CHK 2 INV 038649
000005 CHK 2 INV 038650
000005 CHK 2 ACR 038652
Ok, now lets add a counter, generic rule will be to create a special RowNumber field:
SELECT Bill Number, other fields, Check Number
(SELECT COUNT(*) FROM table t2 WHERE t2.Bill Number <= t.Bill Number and t2.Check Number = t.Check Number) AS RowNumber
FROM Table t
ORDER BY Bill Number
Here is what our view will become:
select p.RefNbr as PayNbr, p.DocType, i.CompanyID as CompanyID, i.DocType as InvDocType, i.RefNbr as InvRefNbr,
(SELECT COUNT(*) FROM APPayment p2 inner join APAdjust a2 on p2.CompanyID = a2.CompanyID and
p2.DocType = a2.AdjgDocType and p2.RefNbr = a2.AdjgRefNbr
inner join APInvoice i2 on p2.CompanyID = i2.CompanyID and a2.AdjdDocType = i2.DocType and
i2.RefNbr = a2.AdjdRefNbr WHERE i2.RefNbr <= i.RefNbr AND p2.RefNbr = p.RefNbr and a2.AdjgDocType = a.AdjgDocType) AS rownumber
from APPayment p inner join APAdjust a on p.CompanyID = a.CompanyID and
p.DocType = a.AdjgDocType and p.RefNbr = a.AdjgRefNbr
inner join APInvoice i on p.CompanyID = i.CompanyID and a.AdjdDocType = i.DocType and
i.RefNbr = a.AdjdRefNbr
order by i.RefNbr
PayNbr Type CompanyID InvDocType InvRefNbr rownumber
--------------- ------- ----------- ---------- --------------- -----------
000004 QCK 2 QCK 000004 1
000001 CHK 2 INV 038630 1
000002 CHK 2 INV 038631 1
000003 CHK 2 INV 038632 1
000005 CHK 2 INV 038648 1
000005 CHK 2 INV 038649 2
000005 CHK 2 INV 038650 3
000005 CHK 2 ACR 038652 4
Here we have a number 1 to 4. Now we have more complicated task - to arrange these Invoice Numbers into 3 columns, within a single row. To make it simpler, I assume that no check will pay for more than 3000 invoices. This is realistic example. Formula to describe all the sequential numbers that will fall into respective columns will be
1. Column One: N1 = a*3-2. Where N1 are numbers to fall into column 1, and a - is the number of row.
For example for row 1 number should be 1, for row 2 number would be 4, because number 2 will go for column 2, and number 3 will go for column 3. Same way for columns 2 and 3.
2. Column Two: N2 = a*3-1
3. Column Three: N3 = a*3
Based on above, invoice 038648 should go to row 1 column 1, 038649 to row 1 column 2, 038650 to row 1 column 3 and last 038652 should jump to row 2 and column 1. The other words, sequential number 1 corresponds col 1 row 1, 2 = col2 row 1, 3= col3 row 1 and 4 = col1 row 2.
To help me to arrange this logic within a view, I will create 3 tables, each with sequential numbers. And each table will have only numbers that corresponds to columns 1, 2 and 3 respectively.
CREATE TABLE [dbo].[XSW_Column1_Of3](
[Counting] [int] NOT NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[XSW_Column2_Of3](
[Counting] [int] NOT NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[XSW_Column3_Of3](
[Counting] [int] NOT NULL
) ON [PRIMARY]
GO
Now I will populate them using this script, again, its in assumption that we will have up to 3,000 bills per check:
declare @i as int
set @i = 1
while @i <= 1000
BEGIN
insert XSW_Column1_Of3
SELECT @i*3-2
insert XSW_Column2_Of3
select @i*3-1
insert XSW_Column3_Of3
select @i*3
SELECT @i = @i +1
END
Now lets create a view with Raw data for all 3 columns:
Create VIEW XSW_APRemitRaw
AS
SELECT r.PayNbr, r.PayDoc, r.CompanyID, ROUND((r.rownumber+2)/3,0) as RowNumber,
InvRefNbr1 = CASE WHEN r.rownumber in (select Counting from XSW_Column1_Of3) THEN r.InvRefNbr ELSE '' END,
InvRefNbr2 = CASE WHEN r.rownumber in (select Counting from XSW_Column2_Of3) THEN r.InvRefNbr ELSE '' END,
InvRefNbr3 = CASE WHEN r.rownumber in (select Counting from XSW_Column3_Of3) THEN r.InvRefNbr ELSE '' END
FROM (select p.RefNbr as PayNbr, p.DocType as PayDoc, i.CompanyID as CompanyID, i.DocType as InvDocType, i.RefNbr as InvRefNbr,
(SELECT COUNT(*) FROM APPayment p2 inner join APAdjust a2 on p2.CompanyID = a2.CompanyID and
p2.DocType = a2.AdjgDocType and p2.RefNbr = a2.AdjgRefNbr
inner join APInvoice i2 on p2.CompanyID = i2.CompanyID and a2.AdjdDocType = i2.DocType and
i2.RefNbr = a2.AdjdRefNbr WHERE i2.RefNbr <= i.RefNbr AND p2.RefNbr = p.RefNbr and a2.AdjgDocType = a.AdjgDocType) AS rownumber
from APPayment p inner join APAdjust a on p.CompanyID = a.CompanyID and
p.DocType = a.AdjgDocType and p.RefNbr = a.AdjgRefNbr
inner join APInvoice i on p.CompanyID = i.CompanyID and a.AdjdDocType = i.DocType and
i.RefNbr = a.AdjdRefNbr) r
Create VIEW XSW_APRemitRaw
AS
SELECT r.PayNbr, r.PayDoc, r.CompanyID, ROUND((r.rownumber+2)/3,0) as RowNumber,
InvRefNbr1 = CASE WHEN r.rownumber in (select Counting from XSW_Column1_Of3) THEN r.InvRefNbr ELSE '' END,
InvRefNbr2 = CASE WHEN r.rownumber in (select Counting from XSW_Column2_Of3) THEN r.InvRefNbr ELSE '' END,
InvRefNbr3 = CASE WHEN r.rownumber in (select Counting from XSW_Column3_Of3) THEN r.InvRefNbr ELSE '' END
FROM (select p.RefNbr as PayNbr, p.DocType as PayDoc, i.CompanyID as CompanyID, i.DocType as InvDocType, i.RefNbr as InvRefNbr,
(SELECT COUNT(*) FROM APPayment p2 inner join APAdjust a2 on p2.CompanyID = a2.CompanyID and
p2.DocType = a2.AdjgDocType and p2.RefNbr = a2.AdjgRefNbr
inner join APInvoice i2 on p2.CompanyID = i2.CompanyID and a2.AdjdDocType = i2.DocType and
i2.RefNbr = a2.AdjdRefNbr WHERE i2.RefNbr <= i.RefNbr AND p2.RefNbr = p.RefNbr and a2.AdjgDocType = a.AdjgDocType) AS rownumber
from APPayment p inner join APAdjust a on p.CompanyID = a.CompanyID and
p.DocType = a.AdjgDocType and p.RefNbr = a.AdjgRefNbr
inner join APInvoice i on p.CompanyID = i.CompanyID and a.AdjdDocType = i.DocType and
i.RefNbr = a.AdjdRefNbr) r
PayNb PayDoc CompanyID RowNumber InvRefNbr1 InvRefNbr2 InvRefNbr3
--------------- ------ ----------- ----------- --------------- --------------- ---------------
000001 CHK 2 1 038630
000002 CHK 2 1 038631
000003 CHK 2 1 038632
000005 CHK 2 2 038652
000005 CHK 2 1 038648
000005 CHK 2 1 038649
000005 CHK 2 1 038650
000004 QCK 2 1 000004
Well I modified a RowNumber a little bit to show not Sequential number but Actual Row by adding ROUND((r.rownumber+2)/3,0) as RowNumber statement. I need it for aggregation.
Now final step, lets aggregate the view into single row.
Create VIEW XSW_APRemittance
AS
select PayNbr, PayDoc, CompanyID, RowNumber, MAX(InvRefNbr1) as Inv1, MAX(InvRefNbr2) as Inv2, MAX(InvRefNbr3) as Inv3
from XSW_APRemitRaw
group by PayNbr, PayDoc, CompanyID, RowNumber
go
PayNbr PayDoc CompanyID RowNumber Inv1 Inv2 Inv3
--------------- ------ ----------- ----------- --------------- --------------- ---------------
000001 CHK 2 1 038630
000002 CHK 2 1 038631
000003 CHK 2 1 038632
000004 QCK 2 1 000004
000005 CHK 2 1 038648 038649 038650
000005 CHK 2 2 038652
This is exactly what we need to add to report. Well I will skip adding a view to report, please refer to my earlier blogs for that. Just mention, Key fields should be PayNbr, PayDoc and RowNumber.
Added a group
And then added 3 fields to report
That is all,
Best Regards,
Sergey.
↧
↧
Disclosure Controls and Internal Audit. Acumatica and SOX.
Hi Everyone,
Starting from version 4.0 we are implementing Audit History function, that is designed to help in Assessment of internal control, as per Sarbanes–Oxley Section 404.
As per SOX 404 TDRA:
1. - We have extended structure of accounts/subaccounts/business accounts, as well as company entities.
2. - This part is usually done during system implementation and security elements should be tuned according to the risks.
3. - Entity level controls are as well secured in version 3.0. We can specify to restrict or allow access on entity level as well as on reporting element level.
4. - This is achieved by transactional level security.
So almost all the items were available in version 3.0. Except - 5. There was no clear evidence of specific transactions modification. Yes we had time/date when transaction was created or last modified. But there was no historical data kept for the modifications made.
Starting version 4.0 we added Audit History capability, that tracks ALL the changes made to any record in the system. This function require a setup before activation.
All the best,
Sergey
Starting from version 4.0 we are implementing Audit History function, that is designed to help in Assessment of internal control, as per Sarbanes–Oxley Section 404.
As per SOX 404 TDRA:
Key steps to implement such control include:
- Identifying significant financial reporting elements (accounts or disclosures)
- Identifying material financial statement risks within these accounts or disclosures
- Determining which entity-level controls would address these risks with sufficient precision
- Determining which transaction-level controls would address these risks in the absence of precise entity-level controls
- Determining the nature, extent, and timing of evidence gathered to complete the assessment of in-scope controls
1. - We have extended structure of accounts/subaccounts/business accounts, as well as company entities.
2. - This part is usually done during system implementation and security elements should be tuned according to the risks.
3. - Entity level controls are as well secured in version 3.0. We can specify to restrict or allow access on entity level as well as on reporting element level.
4. - This is achieved by transactional level security.
So almost all the items were available in version 3.0. Except - 5. There was no clear evidence of specific transactions modification. Yes we had time/date when transaction was created or last modified. But there was no historical data kept for the modifications made.
Starting version 4.0 we added Audit History capability, that tracks ALL the changes made to any record in the system. This function require a setup before activation.
All the best,
Sergey
↧
Issues with Google Chrome embedded PDF viewer. Use workaround till Chromium team fixes it.
Hi Everyone,
Recently some of our customer started reporting an issue with Google Chrome, while printing reports it cuts the page and does not display details. Also it may hide images embedded into PDF report format. IE and Mozilla FireFox works perfectly fine.
We have checked with Chromium team, found that this issue is a known bug and they work on it. But until the fix is deployed you can use a workaround.
Main sense is to disable built in PDF Viewer and replace it with external plug-in from Adobe. System will still open PDF inside the browser, but without any glitches.
Chromium team, please hurry up :)
Ok, the workaround is to install Adobe PDF Reader on your PC.
Then, once you confirmed you are running the latest Chrome and Adobe Reader, and if issue is still there please try this:
1. Open Chrome and type this into address bar: chrome://plugins/
It will open plugin page.
2. You should see a list of all the Plug-ins installed for Chrome, please find one called Chrome PDF Viewer plugin, it may look like this:
3. Please make this plugin grayed out (disabled) by clicking on Disable link:
4. After that it should show you this
5. It will now save you a file locally on your PC for viewing by ordinary PDF reader.
If saving a file locally is not preferred option you may do it another way.
1. Install Adobe PDF Reader. Open Chrome and type this into address bar: chrome://plugins/
2. Find the Adobe Reader plugin.
3. And make it Enabled
4. Please note that Built In Chrome PDF Viewer should be disabled in this configuration:
All the best,
Sergey.
Recently some of our customer started reporting an issue with Google Chrome, while printing reports it cuts the page and does not display details. Also it may hide images embedded into PDF report format. IE and Mozilla FireFox works perfectly fine.
We have checked with Chromium team, found that this issue is a known bug and they work on it. But until the fix is deployed you can use a workaround.
Main sense is to disable built in PDF Viewer and replace it with external plug-in from Adobe. System will still open PDF inside the browser, but without any glitches.
Chromium team, please hurry up :)
Ok, the workaround is to install Adobe PDF Reader on your PC.
Then, once you confirmed you are running the latest Chrome and Adobe Reader, and if issue is still there please try this:
1. Open Chrome and type this into address bar: chrome://plugins/
It will open plugin page.
2. You should see a list of all the Plug-ins installed for Chrome, please find one called Chrome PDF Viewer plugin, it may look like this:
3. Please make this plugin grayed out (disabled) by clicking on Disable link:
4. After that it should show you this
5. It will now save you a file locally on your PC for viewing by ordinary PDF reader.
If saving a file locally is not preferred option you may do it another way.
1. Install Adobe PDF Reader. Open Chrome and type this into address bar: chrome://plugins/
2. Find the Adobe Reader plugin.
3. And make it Enabled
4. Please note that Built In Chrome PDF Viewer should be disabled in this configuration:
All the best,
Sergey.
↧
Updating One Field from Another. Easy Customization.
Hi Everyone.
One of the most often called customization requests is to default (or update) a Field A, based on what was entered in a Field B. And now its time for me to post a memo article on standard technique for this customization.
Lets define a scope of work:
1. When we issue an invoice to a customer.
2. And then select a Project used by this customer.
3. We have to default a Customer Ref Number field with a Project Purchase Order Number.
4. Which is, in its own case, an Attribute of a Project, and has to be retrieved from there.
5. PO Number is defined in Project Module "Attributes" screen and entered into a "Project" Maintenance screen.
Something like this:
In the video below I comment on customization done. Please find the code for the customization below.
Here is the code:
using System;
using System.Collections;
using System.Collections.Generic;
using PX.Data;
using PX.Objects.GL;
using PX.Objects.CM;
using PX.Objects.CS;
using PX.Objects.CR;
using PX.Objects.TX;
using PX.Objects.IN;
using PX.Objects.BQLConstants;
using PX.Objects.EP;
using PX.TM;
using SOInvoice = PX.Objects.SO.SOInvoice;
using SOInvoiceEntry = PX.Objects.SO.SOInvoiceEntry;
using PX.Objects.SO;
using PX.Objects.DR;
using PX.Objects;
using PX.Objects.AR;
using PX.Objects.PM;
namespace PX.Objects.AR
{
[PXCustomization]
public class Cst_ARInvoiceEntry: ARInvoiceEntry
{
#region Event Handlers
protected override void ARInvoice_ProjectID_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
base.ARInvoice_ProjectID_FieldUpdated(sender, e);
var row = (ARInvoice)e.Row;
var ProjectID = row.ProjectID;
var CustRefNbr = row.InvoiceNbr;
string EntityType = "PR";
string Attribute = "PONUMBER";
//CustRefNbr = CustRefNbr + 1;
PMProject pm = PXSelect<PMProject,
Where<PMProject.baseType, Equal<PMProject.ProjectBaseType>, And<PMProject.nonProject, Equal<False>, And<PMProject.contractID, Equal<Current<ARInvoice.projectID>>, And<PMProject.isTemplate, NotEqual<True>>>>>>.SelectSingleBound(this, new object [] { e.Row });
if (pm != null)
{
//CustRefNbr = pm.ContractCD;
PXResultset<CSAnswers> res = PXSelectJoin<CSAnswers,
InnerJoin<CSAttribute, On<CSAttribute.attributeID, Equal<CSAnswers.attributeID>>>,
Where<CSAnswers.entityType, Equal<Required<CSAnswers.entityType>>,
And<CSAnswers.attributeID, Equal<Required<CSAnswers.attributeID>>,
And<CSAnswers.entityID, Equal<Required<CSAnswers.entityID>>>>>>.Select(this, EntityType, Attribute, pm.ContractID);
CSAnswers ans = res;
if (ans.Value != null)
{
//CustRefNbr = ans.Value;
ARInvoice line = sender.CreateCopy(row) as ARInvoice;
line.InvoiceNbr = ans.Value;
sender.Update(line);
//string msg = CustRefNbr;
//throw new PXSetPropertyException(msg);
//row.InvoiceNbr = CustRefNbr;
}}}
#endregion
}
}
All the best,
Sergey.
One of the most often called customization requests is to default (or update) a Field A, based on what was entered in a Field B. And now its time for me to post a memo article on standard technique for this customization.
Lets define a scope of work:
1. When we issue an invoice to a customer.
2. And then select a Project used by this customer.
3. We have to default a Customer Ref Number field with a Project Purchase Order Number.
4. Which is, in its own case, an Attribute of a Project, and has to be retrieved from there.
5. PO Number is defined in Project Module "Attributes" screen and entered into a "Project" Maintenance screen.
Something like this:
In the video below I comment on customization done. Please find the code for the customization below.
Here is the code:
using System;
using System.Collections;
using System.Collections.Generic;
using PX.Data;
using PX.Objects.GL;
using PX.Objects.CM;
using PX.Objects.CS;
using PX.Objects.CR;
using PX.Objects.TX;
using PX.Objects.IN;
using PX.Objects.BQLConstants;
using PX.Objects.EP;
using PX.TM;
using SOInvoice = PX.Objects.SO.SOInvoice;
using SOInvoiceEntry = PX.Objects.SO.SOInvoiceEntry;
using PX.Objects.SO;
using PX.Objects.DR;
using PX.Objects;
using PX.Objects.AR;
using PX.Objects.PM;
namespace PX.Objects.AR
{
[PXCustomization]
public class Cst_ARInvoiceEntry: ARInvoiceEntry
{
#region Event Handlers
protected override void ARInvoice_ProjectID_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
base.ARInvoice_ProjectID_FieldUpdated(sender, e);
var row = (ARInvoice)e.Row;
var ProjectID = row.ProjectID;
var CustRefNbr = row.InvoiceNbr;
string EntityType = "PR";
string Attribute = "PONUMBER";
//CustRefNbr = CustRefNbr + 1;
PMProject pm = PXSelect<PMProject,
Where<PMProject.baseType, Equal<PMProject.ProjectBaseType>, And<PMProject.nonProject, Equal<False>, And<PMProject.contractID, Equal<Current<ARInvoice.projectID>>, And<PMProject.isTemplate, NotEqual<True>>>>>>.SelectSingleBound(this, new object [] { e.Row });
if (pm != null)
{
//CustRefNbr = pm.ContractCD;
PXResultset<CSAnswers> res = PXSelectJoin<CSAnswers,
InnerJoin<CSAttribute, On<CSAttribute.attributeID, Equal<CSAnswers.attributeID>>>,
Where<CSAnswers.entityType, Equal<Required<CSAnswers.entityType>>,
And<CSAnswers.attributeID, Equal<Required<CSAnswers.attributeID>>,
And<CSAnswers.entityID, Equal<Required<CSAnswers.entityID>>>>>>.Select(this, EntityType, Attribute, pm.ContractID);
CSAnswers ans = res;
if (ans.Value != null)
{
//CustRefNbr = ans.Value;
ARInvoice line = sender.CreateCopy(row) as ARInvoice;
line.InvoiceNbr = ans.Value;
sender.Update(line);
//string msg = CustRefNbr;
//throw new PXSetPropertyException(msg);
//row.InvoiceNbr = CustRefNbr;
}}}
#endregion
}
}
All the best,
Sergey.
↧
↧
Adding Missing Columns to the grid Upload Data Import Function.
Hi Everyone,
We can upload data directly from Excel into Acumatica screens, as long as screen detail area (or grid) has Action Button "Upload". Like, for example, Journal Entry screen from GL module.
But not all the fields will be presented in the Screen Fields choice:
Like above, Transaction Date, that is present in the screen is missing in the Property Name choice.
In order to add it, a slight customization will be needed. This field is hidden on the form by the Business Logic code. And we have to re-enable it.
Adding the following code to Batch_RowSelected event will help to make the column visible.
Under normal circumstances you need to add an event in your Customization Manager if have not yet made so:
protected override void Batch_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
base.Batch_RowSelected(sender, e);
var row = (Batch)e.Row;
PXUIFieldAttribute.SetVisible<GLTran.tranDate>(GLTranModuleBatNbr.Cache, null, true);
}
Here you can see that we made Transaction Date visible by changing it's property to True.
All the best,
Sergey.
We can upload data directly from Excel into Acumatica screens, as long as screen detail area (or grid) has Action Button "Upload". Like, for example, Journal Entry screen from GL module.
But not all the fields will be presented in the Screen Fields choice:
Like above, Transaction Date, that is present in the screen is missing in the Property Name choice.
In order to add it, a slight customization will be needed. This field is hidden on the form by the Business Logic code. And we have to re-enable it.
Adding the following code to Batch_RowSelected event will help to make the column visible.
Under normal circumstances you need to add an event in your Customization Manager if have not yet made so:
protected override void Batch_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
base.Batch_RowSelected(sender, e);
var row = (Batch)e.Row;
PXUIFieldAttribute.SetVisible<GLTran.tranDate>(GLTranModuleBatNbr.Cache, null, true);
}
Here you can see that we made Transaction Date visible by changing it's property to True.
All the best,
Sergey.
↧
Removig Artificial Intelligence from GL Journal Entry Screen.
Hi Guys,
GL Journal Entry screen has one controversial feature - auto balancing of Debit to Credit for the line entered in the grid.
Its like when you enter Debit 100$ then you move to the next line, system auto defaults Credit to be 100$. Well its nice to have but...would be great to be able to switch it off...when needed.
So after some browsing throught the code, just realised, the calculation is hidden into a Method PopulateSubDescr
So, if you do not like DR to be == to CR :) for each line, then just override that method in your customization code with this statment. Well what I did - just removed AI piece of code. So just leave this:
protected override void PopulateSubDescr(PXCache sender, GLTran Row, bool ExternalCall)
{
GLTran prevTran = null;
foreach (GLTran tran in GLTranModuleBatNbr.Select())
{
if (Row == tran)
{
break;
}
prevTran = tran;
}
if (prevTran != null)
{
if (prevTran.SubID != null)
{
Sub sub = (Sub)PXSelectorAttribute.Select<GLTran.subID>(sender, prevTran);
if (sub != null)
{
sender.SetValueExt<GLTran.subID>(Row, sub.SubCD);
PXUIFieldAttribute.SetError<GLTran.subID>(sender, Row, null);
}
}
Row.TranDesc = prevTran.TranDesc;
Row.RefNbr = prevTran.RefNbr;
}
else
{
Row.TranDesc = BatchModule.Current.Description;
}
Account account = (Account)PXSelectorAttribute.Select<GLTran.accountID>(sender, Row);
if (account != null && account.CashSubID != null)
{
Row.SubID = account.CashSubID;
}
if (account != null && (bool)account.NoSubDetail && glsetup.Current.DefaultSubID != null)
{
Row.SubID = glsetup.Current.DefaultSubID;
}
}
Have a nice Day!
Sergey. :)
GL Journal Entry screen has one controversial feature - auto balancing of Debit to Credit for the line entered in the grid.
Its like when you enter Debit 100$ then you move to the next line, system auto defaults Credit to be 100$. Well its nice to have but...would be great to be able to switch it off...when needed.
So after some browsing throught the code, just realised, the calculation is hidden into a Method PopulateSubDescr
So, if you do not like DR to be == to CR :) for each line, then just override that method in your customization code with this statment. Well what I did - just removed AI piece of code. So just leave this:
protected override void PopulateSubDescr(PXCache sender, GLTran Row, bool ExternalCall)
{
GLTran prevTran = null;
foreach (GLTran tran in GLTranModuleBatNbr.Select())
{
if (Row == tran)
{
break;
}
prevTran = tran;
}
if (prevTran != null)
{
if (prevTran.SubID != null)
{
Sub sub = (Sub)PXSelectorAttribute.Select<GLTran.subID>(sender, prevTran);
if (sub != null)
{
sender.SetValueExt<GLTran.subID>(Row, sub.SubCD);
PXUIFieldAttribute.SetError<GLTran.subID>(sender, Row, null);
}
}
Row.TranDesc = prevTran.TranDesc;
Row.RefNbr = prevTran.RefNbr;
}
else
{
Row.TranDesc = BatchModule.Current.Description;
}
Account account = (Account)PXSelectorAttribute.Select<GLTran.accountID>(sender, Row);
if (account != null && account.CashSubID != null)
{
Row.SubID = account.CashSubID;
}
if (account != null && (bool)account.NoSubDetail && glsetup.Current.DefaultSubID != null)
{
Row.SubID = glsetup.Current.DefaultSubID;
}
}
Have a nice Day!
Sergey. :)
↧
Changing Fields Default Values. Auto populating with what we want.
Hi Everyone,
This will be the last post for tonight :)
Ok, when we need to change default behavior for a control (a field), for instance in AR Invoice and Memo screen I would like a Subaccount and a Task to get defaulted from a Project, entered in the header area. Illustrated below:
To achieve it, I will use FieldDefaulting event for Subaccount and Project Task fields in the grid area.
So what I did is added an event for both fields, it had created me a code then I added necessary lines to it.
For the Subaccount:
protected override void ARTran_SubID_FieldDefaulting(PXCache sender, PXFieldDefaultingEventArgs e)
{
base.ARTran_SubID_FieldDefaulting(sender, e);
var row = (ARTran)e.Row;
var SubID = row.SubID;
PMProject pm = PXSelect<PMProject,
Where<PMProject.baseType, Equal<PMProject.ProjectBaseType>, And<PMProject.nonProject, Equal<False>, And<PMProject.contractID, Equal<Current<ARInvoice.projectID>>, And<PMProject.isTemplate, NotEqual<True>>>>>>.SelectSingleBound(this, new object [] { e.Row });
if (pm != null)
{
//row.SubID = pm.DefaultSubID;
e.NewValue = pm.DefaultSubID;
}
}
For the Task:
protected void ARTran_TaskID_FieldDefaulting(PXCache sender, PXFieldDefaultingEventArgs e)
{
var row = (ARTran)e.Row;
var TaskID = row.TaskID;
string DefTask = "00";
PMProject pm = PXSelect<PMProject,
Where<PMProject.baseType, Equal<PMProject.ProjectBaseType>, And<PMProject.nonProject, Equal<False>, And<PMProject.contractID, Equal<Current<ARInvoice.projectID>>, And<PMProject.isTemplate, NotEqual<True>>>>>>.SelectSingleBound(this, new object [] { e.Row });
if (pm != null)
{
e.NewValue = DefTask;
}
}
Please take note, I am updating task with 00 value, which is default task in my case.
e.NewValue structure is the correct way of settling new Default value for the control.
As a result:
It is defaulted to what I want on a New Line click :)
All the best,
Sergey.
This will be the last post for tonight :)
Ok, when we need to change default behavior for a control (a field), for instance in AR Invoice and Memo screen I would like a Subaccount and a Task to get defaulted from a Project, entered in the header area. Illustrated below:
To achieve it, I will use FieldDefaulting event for Subaccount and Project Task fields in the grid area.
So what I did is added an event for both fields, it had created me a code then I added necessary lines to it.
For the Subaccount:
protected override void ARTran_SubID_FieldDefaulting(PXCache sender, PXFieldDefaultingEventArgs e)
{
base.ARTran_SubID_FieldDefaulting(sender, e);
var row = (ARTran)e.Row;
var SubID = row.SubID;
PMProject pm = PXSelect<PMProject,
Where<PMProject.baseType, Equal<PMProject.ProjectBaseType>, And<PMProject.nonProject, Equal<False>, And<PMProject.contractID, Equal<Current<ARInvoice.projectID>>, And<PMProject.isTemplate, NotEqual<True>>>>>>.SelectSingleBound(this, new object [] { e.Row });
if (pm != null)
{
//row.SubID = pm.DefaultSubID;
e.NewValue = pm.DefaultSubID;
}
}
For the Task:
protected void ARTran_TaskID_FieldDefaulting(PXCache sender, PXFieldDefaultingEventArgs e)
{
var row = (ARTran)e.Row;
var TaskID = row.TaskID;
string DefTask = "00";
PMProject pm = PXSelect<PMProject,
Where<PMProject.baseType, Equal<PMProject.ProjectBaseType>, And<PMProject.nonProject, Equal<False>, And<PMProject.contractID, Equal<Current<ARInvoice.projectID>>, And<PMProject.isTemplate, NotEqual<True>>>>>>.SelectSingleBound(this, new object [] { e.Row });
if (pm != null)
{
e.NewValue = DefTask;
}
}
Please take note, I am updating task with 00 value, which is default task in my case.
e.NewValue structure is the correct way of settling new Default value for the control.
As a result:
It is defaulted to what I want on a New Line click :)
All the best,
Sergey.
↧
Cloud Fixed Assets. Split, Transfer, Change of Useful Life, Accelerated Depreciation and more.
Hi Everyone.
In Acumatica version 4 FA module was greatly reworked and improved. Original goal was to make it better than JDE. You are the one to judge.
The module is used by KFC/Pizza Hut for nearly 20,000 assets around 200 locations island-wide.
Here are some of the features that makes us distinct and unique among other ERP:
1. Asset Split.
It is when we purchase assets in bulk, for example chairs for the restaurant, then entered it into our system as a Single Asset. Well you may not wish spent time labeling each chair individually, but rather put them in as a "lot number". It works fine until we need to shift part of these chairs to another location or even another shop...
One step solution for that - Asset Split process.
2.Asset Transfer.
There could be a transfer of asset without changing the ownership, when sales purchase of asset is not needed. It can be between branches or Departments
Inter Departmental Transfer:
Second type is when we have to Sell the Asset in location A and re-purchase it at cost on location B.
Inter Brand transfer:
You may notice Disposal Method, that will be used to write off the asset during the transfer. Its because second method require sender company to get rid of asset in their books completely.
3. Change of Useful Life (UL).
There are two ways of changing the useful life of an asset. Either re-depreciate it from the beginning of life of the asset, and then, posting difference to the period of UL change.
Or use so called "accelerated depreciation" mode to spread remaining Net Book Value of the asset over remaining life of the asset proportionally. Depreciating in a nicer and more flat manner.
In Acumatica you are free to choose the method of changing UL for your assets:
4. Cost Additions and Deductions.
It is clear that some assets may get additions. Like for a PC we can buy a new HDD or RAM upgrade.
This is usually managed by changing the asset cost at certain point of parent asset life.
In Acumatica we have a dedicated transaction type and screen to process asset cost additions OR deductions.
5. Reports.
Here I just provide a list of reports we have:
In Acumatica version 4 FA module was greatly reworked and improved. Original goal was to make it better than JDE. You are the one to judge.
The module is used by KFC/Pizza Hut for nearly 20,000 assets around 200 locations island-wide.
Here are some of the features that makes us distinct and unique among other ERP:
1. Asset Split.
It is when we purchase assets in bulk, for example chairs for the restaurant, then entered it into our system as a Single Asset. Well you may not wish spent time labeling each chair individually, but rather put them in as a "lot number". It works fine until we need to shift part of these chairs to another location or even another shop...
One step solution for that - Asset Split process.
2.Asset Transfer.
There could be a transfer of asset without changing the ownership, when sales purchase of asset is not needed. It can be between branches or Departments
Inter Departmental Transfer:
Second type is when we have to Sell the Asset in location A and re-purchase it at cost on location B.
Inter Brand transfer:
You may notice Disposal Method, that will be used to write off the asset during the transfer. Its because second method require sender company to get rid of asset in their books completely.
3. Change of Useful Life (UL).
There are two ways of changing the useful life of an asset. Either re-depreciate it from the beginning of life of the asset, and then, posting difference to the period of UL change.
Or use so called "accelerated depreciation" mode to spread remaining Net Book Value of the asset over remaining life of the asset proportionally. Depreciating in a nicer and more flat manner.
In Acumatica you are free to choose the method of changing UL for your assets:
4. Cost Additions and Deductions.
It is clear that some assets may get additions. Like for a PC we can buy a new HDD or RAM upgrade.
This is usually managed by changing the asset cost at certain point of parent asset life.
In Acumatica we have a dedicated transaction type and screen to process asset cost additions OR deductions.
5. Reports.
Here I just provide a list of reports we have:
All the Best,
Sergey.
↧
↧
Importing Invoices. Multiple Lines. Multiple Times. Using Search Function.
Hi Everyone,
One of our customers importing Invoices from ageing Oracle based system into Acumatica.
I will skip here technical aspects of this integration, just mention it is done via MS SQL Integration Services, data from Oracle DB are placed on to our Azure SQL server.
Main issue is not Oracle->SQL transport but the interface dynamics.
Customer has multiple lines in the invoice with each line having its unique number per invoice.
Also customer wants to import into Acumatica multiple times, and that the SAME Invoice will dot be imported twice or lines will not get duplicated.
This can be achieved by adding a user field into Invoice Transaction area, and then modifying Import Scenario to look into that field before inserting a NEW transaction line.
Here is additional field added to Transaction Line, you may refer to my earlier posts on how to add a field to grid and map it to the database.
And then, at the Interface Import Scenario we should indicate Search parameter to be Our newly added field. Then next line we should indicate that this parameter should be tally to importing data file.
@@UserLineRef will be a search variable, that is used to compare content of LineRef customized field and JobLineNbr field from the importing data.
As a result we can import the same file multiple times without the risk of duplicating the line data.
All the best,
Sergey.
One of our customers importing Invoices from ageing Oracle based system into Acumatica.
I will skip here technical aspects of this integration, just mention it is done via MS SQL Integration Services, data from Oracle DB are placed on to our Azure SQL server.
Main issue is not Oracle->SQL transport but the interface dynamics.
Customer has multiple lines in the invoice with each line having its unique number per invoice.
Also customer wants to import into Acumatica multiple times, and that the SAME Invoice will dot be imported twice or lines will not get duplicated.
This can be achieved by adding a user field into Invoice Transaction area, and then modifying Import Scenario to look into that field before inserting a NEW transaction line.
Here is additional field added to Transaction Line, you may refer to my earlier posts on how to add a field to grid and map it to the database.
@@UserLineRef will be a search variable, that is used to compare content of LineRef customized field and JobLineNbr field from the importing data.
As a result we can import the same file multiple times without the risk of duplicating the line data.
All the best,
Sergey.
↧
Customization in Multi-Company Environment
Hello Everyone,
Would like to discuss customization used in Acumatica Cloud ERP, specifically when more than one company are in a single database.
As you already know, we can setup multiple companies in a single database, by using Configuration Tool, based on Example from DEMO40 Instance:
I will navigate to Company Maintenance:
In my case there are 2 companies available for the end users: ID=2 and ID=3
So what if I need to customize Co3 and Co2 independently?
We should look at it from our system design prospective. We are using a single database, so if we say add a field to a table, and this table is say Customer. Then any company that are using this table will see this new column, it is basically because of the SQL Table design concept. IIS will pick up this column and show up in the Application. Tables are shared among all the database users, so here is:
Rule number One:
When Adding a new fields to a table, be prepared that all the companies from your Acumatica instance (sharing single DB) will see this field.
And immediately after it,
Rule number Two:
We can always use Access Rights on the selected Companies, to restrict the access to the field, if we want the only specific company to see it.
Another question - can we share or split functional customization?
Again, looking into design, we share the same Web Site for all these companies. We also share the same Application Pool. So, naturally, whatever I publish will be seen by all.
Rule number Three:
When Publishing customization, it will be seen to all Companies. Make sure all your companies are ready to use the same customization set.
But if you still wish to split functions, there is a workaround:
Rule number Four:
Please add hard coded logic into your customization to execute certain code only if CompanyID = <Desired Company>. To make sure you apply customization to specific company only. Something like this:
if (company1).PXUIFieldAttribute.SetVisible
All the best,
Sergey.
Would like to discuss customization used in Acumatica Cloud ERP, specifically when more than one company are in a single database.
As you already know, we can setup multiple companies in a single database, by using Configuration Tool, based on Example from DEMO40 Instance:
I will navigate to Company Maintenance:
In my case there are 2 companies available for the end users: ID=2 and ID=3
So what if I need to customize Co3 and Co2 independently?
We should look at it from our system design prospective. We are using a single database, so if we say add a field to a table, and this table is say Customer. Then any company that are using this table will see this new column, it is basically because of the SQL Table design concept. IIS will pick up this column and show up in the Application. Tables are shared among all the database users, so here is:
Rule number One:
When Adding a new fields to a table, be prepared that all the companies from your Acumatica instance (sharing single DB) will see this field.
And immediately after it,
Rule number Two:
We can always use Access Rights on the selected Companies, to restrict the access to the field, if we want the only specific company to see it.
Another question - can we share or split functional customization?
Again, looking into design, we share the same Web Site for all these companies. We also share the same Application Pool. So, naturally, whatever I publish will be seen by all.
Rule number Three:
When Publishing customization, it will be seen to all Companies. Make sure all your companies are ready to use the same customization set.
But if you still wish to split functions, there is a workaround:
Rule number Four:
Please add hard coded logic into your customization to execute certain code only if CompanyID = <Desired Company>. To make sure you apply customization to specific company only. Something like this:
if (company1).PXUIFieldAttribute.SetVisible
All the best,
Sergey.
↧
Understanding Company Setup Options
Hi Everyone,
Lets discuss companies setup using Configuration Setup application.
Company Setup in Acumatica designed to be using the same tables by multiple entities.
And when the record is shared, we are not replicating it but truly share.
To isolate one company from another within a table we have Masks as a column, they are processed automatically by Server application, to ensure we see a correct company data.
In order to understand the design, we need to put few considerations:
1. There is a single top level company and we use it as a system storage for kernel data. This company is in Read Only mode for the end user. No data can be amended or inserted into that company and it stays at the top level node of the hierarchy tree.
What it means to us? Effectively, this top company is a break-point for any updates coming from below nodes. No data can be passed through this node to another nodes. Its a true dead end for any data coming from its children.
This company has fixed ID and it is always equal to 1 [One]. Consider it as Master Parent company.
2. We can create unlimited number of "normal" Parent Companies. But the trick is - if company is Parent, it can not be used for the users to log into it. It looks like a nonsense but you will soon understand what is the purpose of such Parent node. Parent company is the one that have child companies. Parent company can be a child of another, higher Parent company. Parent Companies data can be modified, updated or inserted. And because we can not directly log in into Parent Company, these updates/insertions can only be made either by it's child companies, it's parent companies or directly inserting data into it from a Template, using Configuration Tool.
3. We can create unlimited number of child companies, which can not be parent to any other companies. Forever young, in a sense :) It means we call a child a company that has parent but does not have its own children. Data in those companies are modifiable as well. And because we can log in into them we can modify data directly, or modification can be done by it's parent.
4. We have a data sharing mode or setup for all companies on a PER TABLE basis. Say, we define sharing data or data isolation (whatever you like) once only in a single place, for each table in the database. And it applied to all and any company immediately. There is no individual sharing/isolation mechanisms per company. But soon you will get that it is not necessary to setup isolation per company, it must be global setting :), per table. I will discuss how it works later, but let me mention: there could be Separate, Split or Share data isolation modes.
Lets start from a simple configuration with 2 child companies, 1 parent and of course 1 master parent.
In this example companies 3 and 4 will be used for log in, they are "true companies". The end user will think that there are only companies 3 and 4. Company 2 will be invisible. And company 1 is not only invisible, but it is Read Only :).
So you may ask a question, why do we need company 2?
The answer is - we need it to transfer data for the tables in SHARING DATA MODE from company 3 to company 4, because we can not transfer via company 1, remember, its Read Only one.
Secondly - we can use company 2 to propagate template data to child companies 3 and 4 for the tables in SPLIT DATA MODE.
So what if we need to share GL Accounts between companies 3 and 4, at the same time we would like to pre-populate the list of Customers for companies 3 and 4 with some list, but we would like to let end users to modify customers independently in companies 3 and 4.
Basically, for Account table we will use SHARE data isolation mode. While for Customer table (BAccount in fact) we will use SPLIT data isolation mode.
Exactly, when we use SHARE, data will be shared between companies 3 and 4, whatever update you made in co 3 will be propagated to co 4 and vice verse. But when we use SPLIT, data from Parent company are VISIBLE in child companies, and the end user will think that these data are actually company 3 or 4 data.
But at the moment you will update/delete/insert data in any of the child companies 3 or 4, data will become independent and for that particular data row from table Customer, system will SPLIT the row between company 3 and 4, creating 2 independent rows.
Well, all other tables we will keep in SEPARATE data isolation mode, which means they are absolutely isolated and independent in companies 3 and 4.
I predict some of you will say - we can use data import instead, why do we need this strange SPLIT mode at all! Well here are two reasons for it:
1. In split mode, before you made any modification to a data row, you save significant amount of database space, by not creating a row of data per company, instead, using a SINGLE row for ALL companies.
2. There are cases when we need to setup exactly the same list of entities for every new company. And running Data Import every time may not be a good option. As we might need to spent significant amount of time for it.
Anyway, if you do not like SPLIT mode, just use Data Import. And concentrate on SHARE and SEPARATE modes only :)
So, lets get to practice. Setup from above should look like this:
Take note, for Parent companies we must NOT check Visible option.
Then we have to setup companies first before going to data isolation configurations.
Now we have to add template data to company 2, I will use Demo company data.
Later it supposed to propagate Chart of Account to both companies 3 and 4, as well as pre-define customers and vendors in companies 3 and 4. Of course companies 3 and 4 will need to get setup for gl/ar/ap etc, which I will do manually but skip here.
Once setup is processed lets assign that Account we share and BAccount (Customer,Vendor,Employee) we split.
Then press Next and process the changes into a database.
Please take note, we should insert template data first then change isolation mode.
Now lets take a look at companies 3 and 4. COA is in place and shared and Customers are propagated as well.
And if I now change the customer in company 3, it will not affect company 4. Where the same list of customers if pre-created.
I only hope that concept of Companies and their data sharing modes became a bit clearer. Also, there is nothing wrong in setting companies structures like below:
Here we have Parent->Parent->Parent relationship between companies 1, 2 and 5.
And even if we switched on SHARE mode data from left company group (2,3,5,8,9) will never go to either company 4 or group of companies (6,7). Simply because company 1 is in Read Only mode and data can not pass through it.
So three blocks (2,3,5,8,9) ; (4) ; (6,7) are completely isolated from one another and can exchange data only within itself.
Have a great time configuring your companies :)
All the best,
Sergey.
Lets discuss companies setup using Configuration Setup application.
Company Setup in Acumatica designed to be using the same tables by multiple entities.
And when the record is shared, we are not replicating it but truly share.
To isolate one company from another within a table we have Masks as a column, they are processed automatically by Server application, to ensure we see a correct company data.
In order to understand the design, we need to put few considerations:
1. There is a single top level company and we use it as a system storage for kernel data. This company is in Read Only mode for the end user. No data can be amended or inserted into that company and it stays at the top level node of the hierarchy tree.
What it means to us? Effectively, this top company is a break-point for any updates coming from below nodes. No data can be passed through this node to another nodes. Its a true dead end for any data coming from its children.
This company has fixed ID and it is always equal to 1 [One]. Consider it as Master Parent company.
2. We can create unlimited number of "normal" Parent Companies. But the trick is - if company is Parent, it can not be used for the users to log into it. It looks like a nonsense but you will soon understand what is the purpose of such Parent node. Parent company is the one that have child companies. Parent company can be a child of another, higher Parent company. Parent Companies data can be modified, updated or inserted. And because we can not directly log in into Parent Company, these updates/insertions can only be made either by it's child companies, it's parent companies or directly inserting data into it from a Template, using Configuration Tool.
3. We can create unlimited number of child companies, which can not be parent to any other companies. Forever young, in a sense :) It means we call a child a company that has parent but does not have its own children. Data in those companies are modifiable as well. And because we can log in into them we can modify data directly, or modification can be done by it's parent.
4. We have a data sharing mode or setup for all companies on a PER TABLE basis. Say, we define sharing data or data isolation (whatever you like) once only in a single place, for each table in the database. And it applied to all and any company immediately. There is no individual sharing/isolation mechanisms per company. But soon you will get that it is not necessary to setup isolation per company, it must be global setting :), per table. I will discuss how it works later, but let me mention: there could be Separate, Split or Share data isolation modes.
Lets start from a simple configuration with 2 child companies, 1 parent and of course 1 master parent.
In this example companies 3 and 4 will be used for log in, they are "true companies". The end user will think that there are only companies 3 and 4. Company 2 will be invisible. And company 1 is not only invisible, but it is Read Only :).
So you may ask a question, why do we need company 2?
The answer is - we need it to transfer data for the tables in SHARING DATA MODE from company 3 to company 4, because we can not transfer via company 1, remember, its Read Only one.
Secondly - we can use company 2 to propagate template data to child companies 3 and 4 for the tables in SPLIT DATA MODE.
So what if we need to share GL Accounts between companies 3 and 4, at the same time we would like to pre-populate the list of Customers for companies 3 and 4 with some list, but we would like to let end users to modify customers independently in companies 3 and 4.
Basically, for Account table we will use SHARE data isolation mode. While for Customer table (BAccount in fact) we will use SPLIT data isolation mode.
Exactly, when we use SHARE, data will be shared between companies 3 and 4, whatever update you made in co 3 will be propagated to co 4 and vice verse. But when we use SPLIT, data from Parent company are VISIBLE in child companies, and the end user will think that these data are actually company 3 or 4 data.
But at the moment you will update/delete/insert data in any of the child companies 3 or 4, data will become independent and for that particular data row from table Customer, system will SPLIT the row between company 3 and 4, creating 2 independent rows.
Well, all other tables we will keep in SEPARATE data isolation mode, which means they are absolutely isolated and independent in companies 3 and 4.
I predict some of you will say - we can use data import instead, why do we need this strange SPLIT mode at all! Well here are two reasons for it:
1. In split mode, before you made any modification to a data row, you save significant amount of database space, by not creating a row of data per company, instead, using a SINGLE row for ALL companies.
2. There are cases when we need to setup exactly the same list of entities for every new company. And running Data Import every time may not be a good option. As we might need to spent significant amount of time for it.
Anyway, if you do not like SPLIT mode, just use Data Import. And concentrate on SHARE and SEPARATE modes only :)
So, lets get to practice. Setup from above should look like this:
Take note, for Parent companies we must NOT check Visible option.
Then we have to setup companies first before going to data isolation configurations.
Now we have to add template data to company 2, I will use Demo company data.
Later it supposed to propagate Chart of Account to both companies 3 and 4, as well as pre-define customers and vendors in companies 3 and 4. Of course companies 3 and 4 will need to get setup for gl/ar/ap etc, which I will do manually but skip here.
Once setup is processed lets assign that Account we share and BAccount (Customer,Vendor,Employee) we split.
Then press Next and process the changes into a database.
Please take note, we should insert template data first then change isolation mode.
Now lets take a look at companies 3 and 4. COA is in place and shared and Customers are propagated as well.
And if I now change the customer in company 3, it will not affect company 4. Where the same list of customers if pre-created.
I only hope that concept of Companies and their data sharing modes became a bit clearer. Also, there is nothing wrong in setting companies structures like below:
And even if we switched on SHARE mode data from left company group (2,3,5,8,9) will never go to either company 4 or group of companies (6,7). Simply because company 1 is in Read Only mode and data can not pass through it.
So three blocks (2,3,5,8,9) ; (4) ; (6,7) are completely isolated from one another and can exchange data only within itself.
Have a great time configuring your companies :)
All the best,
Sergey.
↧
Securing Web Configuration
Hi Everyone,
One of the questions from the audit team could be why do we keep some sensitive data in our configuration files. Luckily .NET provides fast and effective way of encrypting content of the web.config or machine.config files.
Of course if we use Windows Authentication, no data will be exposed in Web.Config, but just in case we need SQL based authentication to be used. Please use a simple script to encrypt content of the <connectionStrings> section of your web.config file.
1. Get the ID of your web site from the IIS Administration panel:
2. Open Command Prompt. Navigate to a folder where .NET resides and execute the following statement:
aspnet_regiis -pe connectionStrings -app / -site 9
Where connectionStrings is the area what we plan to encrypt, -app / means we have our site at root, and
-site 9 is the site ID.
Web.Config before the "surgery":
And After:
The only drawback of this - Configuration Tool, that is using local executable application, will not be able to find a database. But this I believe for good, you can control the attempt of changing configurations for the system.
If you need to decrypt the web.config, just run this statement:
aspnet_regiis -pd connectionStrings -app / -site 9
You Enjoy :) ,
Sergey.
One of the questions from the audit team could be why do we keep some sensitive data in our configuration files. Luckily .NET provides fast and effective way of encrypting content of the web.config or machine.config files.
Of course if we use Windows Authentication, no data will be exposed in Web.Config, but just in case we need SQL based authentication to be used. Please use a simple script to encrypt content of the <connectionStrings> section of your web.config file.
1. Get the ID of your web site from the IIS Administration panel:
2. Open Command Prompt. Navigate to a folder where .NET resides and execute the following statement:
aspnet_regiis -pe connectionStrings -app / -site 9
Where connectionStrings is the area what we plan to encrypt, -app / means we have our site at root, and
-site 9 is the site ID.
Web.Config before the "surgery":
And After:
The only drawback of this - Configuration Tool, that is using local executable application, will not be able to find a database. But this I believe for good, you can control the attempt of changing configurations for the system.
If you need to decrypt the web.config, just run this statement:
aspnet_regiis -pd connectionStrings -app / -site 9
You Enjoy :) ,
Sergey.
↧
↧
What is Automation, Work Flow and how to Reporduce it.
Hi All,
Well, I am not saying you do not know what is a work flow, sure you do.
Just wanted to point - in Acumatica it is called Automation Steps and located System->Automation menu.
We can create work flow nodes for any screen in Acumatica, as many steps per screen as we want. In the above example I show standard Automation Step for Purchase Order screen, Step called NL Open. Based on the author's idea it supposed to mean Normal Order Open State ;)
Plain English - When the Purchase Order of type Normal is on Open Status, we should exercise this step. We are on this step. Whatever you like to call it.
Q: So, once we are here, what will define position of this step in a work flow?
A: Tab called Conditions, there you can see:
1. Approved Checkbox must be checked.
2. Hold checkbox must not be checked.
etc.
Q: Once we are here, what is configured under this step?
A: We can define Actions, Reports and Inquiries seen from the screen.
For example, action Approve is disabled, which is reasonable, since we approved the order already. Option to place order on Hold is available, so we have it active.
Last thing, we can define some fields on the screen here.
So basically its just a step in a long tree, so where is this hierarchy stored?
Here it is:
In order to see actual tree in XML form we should press the button Show Populated:
And here is the best part: ALL STEPS, FIELDS AND BRANCHES are in this XML code.
And we CAN COPY this code to another company. Meaning - WE CAN replicate our automation steps for another customer :)
In order to do it, just copy the XML text into a clipboard.
Then open another company, then Add definition , then Press "Show populated".
It will be blank, then just copy the text into it, then save the definition.
Then Press Activate Definition.
This way you can copy tailor made work flows to a newly created companies.
All the best,
Sergey.
Well, I am not saying you do not know what is a work flow, sure you do.
Just wanted to point - in Acumatica it is called Automation Steps and located System->Automation menu.
We can create work flow nodes for any screen in Acumatica, as many steps per screen as we want. In the above example I show standard Automation Step for Purchase Order screen, Step called NL Open. Based on the author's idea it supposed to mean Normal Order Open State ;)
Plain English - When the Purchase Order of type Normal is on Open Status, we should exercise this step. We are on this step. Whatever you like to call it.
Q: So, once we are here, what will define position of this step in a work flow?
A: Tab called Conditions, there you can see:
1. Approved Checkbox must be checked.
2. Hold checkbox must not be checked.
etc.
Q: Once we are here, what is configured under this step?
A: We can define Actions, Reports and Inquiries seen from the screen.
For example, action Approve is disabled, which is reasonable, since we approved the order already. Option to place order on Hold is available, so we have it active.
Last thing, we can define some fields on the screen here.
So basically its just a step in a long tree, so where is this hierarchy stored?
Here it is:
In order to see actual tree in XML form we should press the button Show Populated:
And here is the best part: ALL STEPS, FIELDS AND BRANCHES are in this XML code.
And we CAN COPY this code to another company. Meaning - WE CAN replicate our automation steps for another customer :)
In order to do it, just copy the XML text into a clipboard.
Then open another company, then Add definition , then Press "Show populated".
It will be blank, then just copy the text into it, then save the definition.
Then Press Activate Definition.
This way you can copy tailor made work flows to a newly created companies.
All the best,
Sergey.
↧
Adding new Field to DB Schema and Screen in 8 Minutes.
Hi Everyone,
Please refer to the video for adding new fields into Acumatica screens, by modifying DB schema directly from Acumatica Application.
I was using Mozilla Waterfox browser. Any browser will do.
All the best,
Sergey.
Please refer to the video for adding new fields into Acumatica screens, by modifying DB schema directly from Acumatica Application.
I was using Mozilla Waterfox browser. Any browser will do.
All the best,
Sergey.
↧
Bundle for Sales. Recognize the Margins by Item. Non Stock Kits.
Hi Everyone,
Got a question from one of the clients: "What if we want to bundle up few items together, then position it as a single set. But internally have to recognize costs and revenue per item."
Answer: This is possible by utilizing Non-Stock Kits. They will behave as a normal Item when we sell it, but when shipping it will explode into components. Revenue will also be recognized by item.
Below are simple steps to configure and process such items on a demo system.
Create a Kit add components to it. Please take note Non Stock option should be checked.
Receive components to Warehouse.
Set the price for the Kit
Update it from Pending to become Effective, here we can say for which month our Promo is active.
Create and Print a Sales Order.
Please note that in Sales order, system shows us a Bundle as a single product.
Now we generate shipment and after confirmation see the inventory part of the transaction, please note servers are serialized therefore displayed separately one by each line in GL transaction.
Now is time to Print the invoice and check the AR part. Which I made to be Deferred Revenue until customer pays us for the goods.
Now lets enter payment and recognize our revenue
In my case I use the same Sales Account for each item, but we can split it based on our company policy, and indicate at the Inventory Item master level.
All the best,
Sergey.
Got a question from one of the clients: "What if we want to bundle up few items together, then position it as a single set. But internally have to recognize costs and revenue per item."
Answer: This is possible by utilizing Non-Stock Kits. They will behave as a normal Item when we sell it, but when shipping it will explode into components. Revenue will also be recognized by item.
Below are simple steps to configure and process such items on a demo system.
Create a Kit add components to it. Please take note Non Stock option should be checked.
Receive components to Warehouse.
Please note that in Sales order, system shows us a Bundle as a single product.
Now we generate shipment and after confirmation see the inventory part of the transaction, please note servers are serialized therefore displayed separately one by each line in GL transaction.
Now is time to Print the invoice and check the AR part. Which I made to be Deferred Revenue until customer pays us for the goods.
Now lets enter payment and recognize our revenue
In my case I use the same Sales Account for each item, but we can split it based on our company policy, and indicate at the Inventory Item master level.
All the best,
Sergey.
↧
Updating one field from another. Easy Customization.
Hi Guys,
What if we need not just add a field to a database schema, but also to update it from another field.
Based on certain event. Below is the example of customization that takes content of Description field and pushes it into newly added field.
1. Add new field to a screen. I had explained earlier how to do it. This time I add text edit field, 30 characters long.
2. Added Data Event: Field Updated to the Description field, so on the change of content it will get triggered.
3. Under description field I added a code, that takes content of the field and updates UsrDesc2 field:
What if we need not just add a field to a database schema, but also to update it from another field.
Based on certain event. Below is the example of customization that takes content of Description field and pushes it into newly added field.
1. Add new field to a screen. I had explained earlier how to do it. This time I add text edit field, 30 characters long.
2. Added Data Event: Field Updated to the Description field, so on the change of content it will get triggered.
3. Under description field I added a code, that takes content of the field and updates UsrDesc2 field:
protected void Batch_Description_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
var row = (Batch)e.Row;
var Description = row.Description;
if (Description != null)
{
row.UsrDesc2 = Description;
}
}
{
var row = (Batch)e.Row;
var Description = row.Description;
if (Description != null)
{
row.UsrDesc2 = Description;
}
}
4. Last is to add property of the Description field to do commit, so every time we try to update it, it will push data to UsrDesc2 field as well.
Finally here is what we got:
I am sure, adding If's and Switches into the code is not a problem :)
All the best,
Sergey.
↧
↧
Enabling Partners is tough but a Pleasure.
↧
Automation Steps and Definitions Explained. Rapid Copy to Another Company.
Hi Everyone,
Well, I understand recently, thanks to your questions, that Automation Definition is a grey area.
Need to explain about what is what. And how exactly we can copy Automation Steps from one company to another.
A. Automation Steps. They are what we define as work flow. Nodes and actions.
Inside the steps we manually define, which reports or screens need to be used on each specific step of our workflow.
Please note, that Actual automation steps are UNIQUE per system and are stored directly in the database, attached to actual screens. Actual steps are not tied to anything else but to your company and screens.
B. Automation Definitions. They has nothing to do with ACTUAL steps from chapter A.
May be team used bad term. These are not definitions at all. These are just archives, where you can store certain or all steps.
You can see definition has list of screens, which you can include into it.
So most important thing to understand before we move on. Actual steps has nothing to do to Definitions. Definitions are not actually defining anything :) they are simply a storage rooms where we keep "notes" about how we CAN setup our steps.
I give you an example. I was creating Automation Steps for GL Journal Transactions screen. And I want to save my work, just in case someone else will update actual steps on GL Journal screen, I will always have a backup of what I have done.
So, I create Automation Definition, from clause B. And save my steps from the screen into it.
I give it a name - GLBACKUP. So next day I could restore my steps (override someone else steps actually) when needed.
So hope you understand that we have Actual screens, where Actual steps are sitting.
While definitions are just OUR notes about what were the steps at certain point in time.
Lets move on. There are few actions available on Definition screen.
Preload Details.
When you just created a new note, sorry, Definition. You might want to fill it up with all the available steps for all the screens from the system.
That is what this button does. It puts each and every step from all the screens into your Definition.
Like a total Backup :)
After you have done it, you may remove some unnecessary screens, if let say, you create definition for certain module only. Like AP Default should include AP screens right :)
Populate Definition.
Unlike the above total copy option, this one only copies Actual Steps for the screens that you indicated in your definition. Well it takes steps from Actual screens, then places them into your definition. Say after you removed unnecessary screens from definitions, then you can press this button to UPDATE your definition with MOST current steps from the ACTUAL system.
So, don't expect that Definition will auto update! its not actual steps, these are NOTES only. Would love my notebook write for me but... So, don't forget to press this button.
And Of course ALWAYS PRESS SAVE after you depressed either button in this screen.
Activate Definition.
This is kind of RESTORE button. It takes steps from definition and puts them ONTO ACTUAL screens. Well it takes your notes, and turns them into life.
Show Populated.
This button will open a screen, that will show you a content of the Definition. Basically its a textual form of the Definition. So you can copy/paste it anywhere you want.
======================================================================
Now the best part.
How to COPY STEPS from company X to company Y.
1. If we copy steps only for particular screens. Find the Definition, that has all the screens you would like to copy steps from. If you wish to copy ALL steps, create new definition.
2A. If you copy particular screen steps only, press POPULATE DEFINITION button. Then press SAVE.
2B. If you do a total copy. Press PRELOAD DETAILS. Then press SAVE.
3. Press SHOW POPULATED. Now copy into clipboard the content of the pop up screen.
4. Now lets change the company, so open another browser and login to another company - destination for our steps. Below steps will be done on DESTINATION company only.
5A. If you copy particular screen steps, open the similar Definition where screens you want to copy present. Then press POPULATE DEFINITION. Press SAVE.
5B. If you do total copy for all screens. Create new definition. Press PRELOAD DETAILS. Press SAVE.
6. Press SHOW POPULATED button. Select all the text there and press delete or backspace to erase it. DONT PRESS CUT :) as our clipboard will be needed. Just erase. Ok. Now press X button to close this pop up and PRESS SAVE.
Well what we have now is a skeleton of the definition without a content :)
7. Now press SHOW POPULATED again. Pop up screen should be empty this time. PASTE there all the content of the clipboard. Press X button to close the pop up and PRESS SAVE.
8. Now final part - PRESS ACTIVATE DEFINITION. It will copy the content of the definition into real screens, steps will appear.
Enjoy.
All the best,
Sergey.
Well, I understand recently, thanks to your questions, that Automation Definition is a grey area.
Need to explain about what is what. And how exactly we can copy Automation Steps from one company to another.
A. Automation Steps. They are what we define as work flow. Nodes and actions.
Inside the steps we manually define, which reports or screens need to be used on each specific step of our workflow.
Please note, that Actual automation steps are UNIQUE per system and are stored directly in the database, attached to actual screens. Actual steps are not tied to anything else but to your company and screens.
B. Automation Definitions. They has nothing to do with ACTUAL steps from chapter A.
May be team used bad term. These are not definitions at all. These are just archives, where you can store certain or all steps.
You can see definition has list of screens, which you can include into it.
So most important thing to understand before we move on. Actual steps has nothing to do to Definitions. Definitions are not actually defining anything :) they are simply a storage rooms where we keep "notes" about how we CAN setup our steps.
I give you an example. I was creating Automation Steps for GL Journal Transactions screen. And I want to save my work, just in case someone else will update actual steps on GL Journal screen, I will always have a backup of what I have done.
So, I create Automation Definition, from clause B. And save my steps from the screen into it.
I give it a name - GLBACKUP. So next day I could restore my steps (override someone else steps actually) when needed.
So hope you understand that we have Actual screens, where Actual steps are sitting.
While definitions are just OUR notes about what were the steps at certain point in time.
Lets move on. There are few actions available on Definition screen.
Preload Details.
When you just created a new note, sorry, Definition. You might want to fill it up with all the available steps for all the screens from the system.
That is what this button does. It puts each and every step from all the screens into your Definition.
Like a total Backup :)
After you have done it, you may remove some unnecessary screens, if let say, you create definition for certain module only. Like AP Default should include AP screens right :)
Populate Definition.
Unlike the above total copy option, this one only copies Actual Steps for the screens that you indicated in your definition. Well it takes steps from Actual screens, then places them into your definition. Say after you removed unnecessary screens from definitions, then you can press this button to UPDATE your definition with MOST current steps from the ACTUAL system.
So, don't expect that Definition will auto update! its not actual steps, these are NOTES only. Would love my notebook write for me but... So, don't forget to press this button.
And Of course ALWAYS PRESS SAVE after you depressed either button in this screen.
Activate Definition.
This is kind of RESTORE button. It takes steps from definition and puts them ONTO ACTUAL screens. Well it takes your notes, and turns them into life.
Show Populated.
This button will open a screen, that will show you a content of the Definition. Basically its a textual form of the Definition. So you can copy/paste it anywhere you want.
======================================================================
Now the best part.
How to COPY STEPS from company X to company Y.
1. If we copy steps only for particular screens. Find the Definition, that has all the screens you would like to copy steps from. If you wish to copy ALL steps, create new definition.
2A. If you copy particular screen steps only, press POPULATE DEFINITION button. Then press SAVE.
2B. If you do a total copy. Press PRELOAD DETAILS. Then press SAVE.
3. Press SHOW POPULATED. Now copy into clipboard the content of the pop up screen.
4. Now lets change the company, so open another browser and login to another company - destination for our steps. Below steps will be done on DESTINATION company only.
5A. If you copy particular screen steps, open the similar Definition where screens you want to copy present. Then press POPULATE DEFINITION. Press SAVE.
5B. If you do total copy for all screens. Create new definition. Press PRELOAD DETAILS. Press SAVE.
6. Press SHOW POPULATED button. Select all the text there and press delete or backspace to erase it. DONT PRESS CUT :) as our clipboard will be needed. Just erase. Ok. Now press X button to close this pop up and PRESS SAVE.
Well what we have now is a skeleton of the definition without a content :)
7. Now press SHOW POPULATED again. Pop up screen should be empty this time. PASTE there all the content of the clipboard. Press X button to close the pop up and PRESS SAVE.
8. Now final part - PRESS ACTIVATE DEFINITION. It will copy the content of the definition into real screens, steps will appear.
Enjoy.
All the best,
Sergey.
↧
Adding Report to AP or AR screens. Unhiding Automation Steps Links.
Hi Everyone,
While adding report to some of the screens might be an easy task, for AR or AP we have only choice of predefined hard coded reports.
So our goal for today to add report to get something like this:
Normally we do it via Automation Steps, but once you get there you will realise, we can't add any other report to the list, but only predefined existing reports.
I wanted to extend this list with newly created user report. So as usual, I added a report to a site map, it appeared in the reports section of the AP Module.
And because AR/AP modules are using predefined report list we will have to customize the Report method of the APDataEntryGraph
To do it, I called up Source Code Browser, found this graph, and copied Report method to my customization. Then I added new report to it:
[PXUIField(DisplayName = "Reports", MapEnableRights = PXCacheRights.Select)]
[PXButton]
protected override IEnumerable Report(PXAdapter adapter,
[PXString(8)]
[PXStringList(new string[] { "AP610500", "AP622000", "AP622500", "AP610720"}, new string[] { "AP Edit", "AP Register Detailed", "AP Payment Register", "AP Payment Voucher" })]
string reportID
)
{
APPayment doc = Document.Current;
if (doc!=null)
{
object FinPeriodID;
switch (reportID)
{
case "AP610500":
{
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters["DocType"] = doc.DocType;
parameters["RefNbr"] = doc.RefNbr;
throw new PXReportRequiredException(parameters, reportID, "Report");
}
case "AP622000":
{
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters["PeriodID"] = (FinPeriodID = this.Caches[typeof(APPayment)].GetValueExt<APRegister.finPeriodID>(doc)) is PXFieldState ? (string)((PXFieldState)FinPeriodID).Value : (string)FinPeriodID;
parameters["DocType"] = doc.DocType;
parameters["RefNbr"] = doc.RefNbr;
throw new PXReportRequiredException(parameters, reportID, "Report");
}
case "AP622500":
{
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters["DocType"] = doc.DocType;
parameters["RefNbr"] = doc.RefNbr;
parameters["PeriodID"] = (FinPeriodID = this.Caches[typeof(APPayment)].GetValueExt<APRegister.finPeriodID>(doc)) is PXFieldState ? (string)((PXFieldState)FinPeriodID).Value : (string)FinPeriodID;
throw new PXReportRequiredException(parameters, reportID, "Report");
}
case "AP610720":
{
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters["DocType"] = doc.DocType;
parameters["RefNbr"] = doc.RefNbr;
parameters["PeriodID"] = (FinPeriodID = this.Caches[typeof(APPayment)].GetValueExt<APRegister.finPeriodID>(doc)) is PXFieldState ? (string)((PXFieldState)FinPeriodID).Value : (string)FinPeriodID;
throw new PXReportRequiredException(parameters, reportID, "Report");
}
}
}
return adapter.Get();
}
In my case I added report AP610720.
Please note we need to add a button, its right on top of the code, also we have to place this code into our screen, in my case Checks and Payments.
Right after you added this code, option will become enabled to choose in Automation Steps newly added report.
Have a great weekend,
Sergey.
While adding report to some of the screens might be an easy task, for AR or AP we have only choice of predefined hard coded reports.
So our goal for today to add report to get something like this:
Normally we do it via Automation Steps, but once you get there you will realise, we can't add any other report to the list, but only predefined existing reports.
I wanted to extend this list with newly created user report. So as usual, I added a report to a site map, it appeared in the reports section of the AP Module.
And because AR/AP modules are using predefined report list we will have to customize the Report method of the APDataEntryGraph
To do it, I called up Source Code Browser, found this graph, and copied Report method to my customization. Then I added new report to it:
[PXUIField(DisplayName = "Reports", MapEnableRights = PXCacheRights.Select)]
[PXButton]
protected override IEnumerable Report(PXAdapter adapter,
[PXString(8)]
[PXStringList(new string[] { "AP610500", "AP622000", "AP622500", "AP610720"}, new string[] { "AP Edit", "AP Register Detailed", "AP Payment Register", "AP Payment Voucher" })]
string reportID
)
{
APPayment doc = Document.Current;
if (doc!=null)
{
object FinPeriodID;
switch (reportID)
{
case "AP610500":
{
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters["DocType"] = doc.DocType;
parameters["RefNbr"] = doc.RefNbr;
throw new PXReportRequiredException(parameters, reportID, "Report");
}
case "AP622000":
{
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters["PeriodID"] = (FinPeriodID = this.Caches[typeof(APPayment)].GetValueExt<APRegister.finPeriodID>(doc)) is PXFieldState ? (string)((PXFieldState)FinPeriodID).Value : (string)FinPeriodID;
parameters["DocType"] = doc.DocType;
parameters["RefNbr"] = doc.RefNbr;
throw new PXReportRequiredException(parameters, reportID, "Report");
}
case "AP622500":
{
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters["DocType"] = doc.DocType;
parameters["RefNbr"] = doc.RefNbr;
parameters["PeriodID"] = (FinPeriodID = this.Caches[typeof(APPayment)].GetValueExt<APRegister.finPeriodID>(doc)) is PXFieldState ? (string)((PXFieldState)FinPeriodID).Value : (string)FinPeriodID;
throw new PXReportRequiredException(parameters, reportID, "Report");
}
case "AP610720":
{
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters["DocType"] = doc.DocType;
parameters["RefNbr"] = doc.RefNbr;
parameters["PeriodID"] = (FinPeriodID = this.Caches[typeof(APPayment)].GetValueExt<APRegister.finPeriodID>(doc)) is PXFieldState ? (string)((PXFieldState)FinPeriodID).Value : (string)FinPeriodID;
throw new PXReportRequiredException(parameters, reportID, "Report");
}
}
}
return adapter.Get();
}
In my case I added report AP610720.
Please note we need to add a button, its right on top of the code, also we have to place this code into our screen, in my case Checks and Payments.
Right after you added this code, option will become enabled to choose in Automation Steps newly added report.
Have a great weekend,
Sergey.
↧