How to crud 3 levels

RequirementsDatabaseClasses createdRepresentationScripts usedValidation and deletion

This example explains how to create a persistent entity based on a header, lines and sub-lines, and how to manage the CRUD related operations.
Some data types, which are references to other Sage X3 tables are used.
The naming rules are used for vertical elements (names starting with 'Y').
A customized development made for a unique customer should be coded with 'Z'.

Description of requirements

The following requirements need to be managed:

The unit price is entered at the sub-detail level, cumulated on the line, and finally at the header level. In the user interface, the user displays the cumulated price when a modification has been sent to the server. As it is done, the line price must not be modified; it is recomputed by cumulating the sub-lines.

Database structure

The database structure used for the persistence of the entity is as follows:

TableDescriptionColumnData typeExplanation
YORDERH [YOH]Specific sales orderYORDNUMA 10 (character string)Sales order number
YBPCBPC (customer code)Order customer
YREPREP (sales rep code)Order sales rep
YORDDATDateSales order date
YTOTALDCB 9.2(Decimal)Total amount of the order
YCURCUR (currency code)Order currency
YSTATUSLocal menu (6520)Order status
YORDERLINE [YOD]Sales order detailYORDNUMYOD (sales order reference)Order number
YORDLC 4(short integer)Line order number (for join)
YSORTL 8(long integer)Line order number (order in which the lines appear on the document)
YITMITM (item reference)Ordered item
YPRICEDCB 9.2 (decimal)Line price
YORDERSUBLIN [YOL]Sales order line sub-detailYORDNUMYOD (line order reference)Order number
YORDLC 4 (short integer)Order Line number
YORDSLC 4 (short integer)Order sub-line number
YUNITUOM (unit of measure)Unit for quantity
YQTYQTY (quantity)Sub-line quantity
YUNITPRICEDCB 9.2 (Decimal)Unit price for sub-line

Every table also has the usual technical columns (CREDATTIM, UPDDATTIM, CREUSR, UPDUSR, AUUID).

The indexes needed on the tables are as follows:

TableDescriptionIndexDefinitionDescription
YORDERH [YOH]Specific sales orderYOH0YORDNUM (no duplicates)Main index used to access the sales order by its reference
YOH1YBPC+YORDNUMIndex per customer
YOH2YREP+YORDNUMIndex per sales rep
YOH1YORDDAT+YORDNUMIndex per date
YORDERLINE [YOD]Sales order detailYOD0YORDNUM+YORDL (no duplicates)Main index used to access the sales order line
YOH1YORDNUM+YSORTIndex used to display lines in the right order
YORDERSUBLIN [YOL]Sales order sub-lineYOD0YORDNUM+YORDL+YORDSL (no duplicates)Main index used to access the sales order sub-line

The local menu 6520 used has two values: Entered and Validated.

Description of the created classes

Three classes are needed:

The 'YORDER' persistent class also has the following characteristics:

The Mapping tab includes the following join description grid:

ReferenceClassReference tableOrigin tableMain indexSorting indexTypeJoin expression
YODYORDERLINEYORDERLINEYORDERYOD0YOD11,n[F:YOH]YORDNUM
YOD.YOLYORDERSUBLINYORDERSUBLINYORDERLINEYOL01,n[F:YOD]YORDNUM;[F:YOD]YORDL

Description of the 'YORDER' representation

This is the representation used for the CRUD management of the 'YORDER' class. It has the following characteristics:

The sections and blocks are organized as follows:

Section definitionBlocks definition
CodeDescriptionOrderCodeDescriptionSectionOrder
YHEADHeader10YMAINMain infoYHEAD10
YSTATStatusYHEAD20
YLINESLines20YLINESLinesYLINES30
YFOOTFooter30YFOOTFooterYFOOT40

The following properties are available in the representation:

AliasPropertyCollectionDescriptionFacets
YORDNUMYOH.YORDNUMOrder NumberAll
YBPCYOH.YBPCCustomerQuery, Detail, Edit, Lookup
YREPYOH.YREPSales repQuery, Detail, Edit
YORDDATYOH.YORDDATOrder DateQuery, Detail, Edit
YCURYOH.YCURCurrencyDetail, Edit
YSTATUSYOH.YSTATUSStatusDetail, Edit (Not inputted)
YITMYOH.YOD.YITMYOH.YODItem RefDetail, Edit
YPRICEYOH.YOD.YPRICEYOH.YODLine PriceDetail, Edit
YUNITYOH.YOD.YOL.YUNITYOH.YOD.YOLUnitDetail, Edit
YQTYYOH.YOD.YOL.YQTYYOH.YOD.YOLQuantituDetail, Edit
YUNITPRICEYOH.YOD.YOL.YUNITPRICEYOH.YOD.YOLUnit PriceDetail, Edit
YTOTALYOH.YTOTALOrder totalDetail, Edit (not inputted)

The script associated with the representation is called 'YORDER_RSTD'.

The different scripts used

All scripts are starting with '$METHODS' or '$PROPERTIES' label. To know the exact context, the following variables need to be checked:

The scripts used on every class or representation have the following pattern:

$METHODSCase CURPTHWhen "" : # Methods called for the instance of the class associated with the scriptCase ACTION... here are the methodsEndcaseWhen ... : # Methods called for child instances of the class associated with the script Case ACTION... here are the methodsEndcaseEndcaseReturn$PROPERTIESCase CURPROWhen ... : # The property is defined hereCase ACTION# Here ACTION can have the values CONTROL, INIT, PROPAGATE...EndcaseEndcaseReturn

In the following example code, we will skip these lines and mention where the code is written.

At sub-line level

This script associated with 'YORDSUBLIN' is used at the sub-line level to manage the events, control, and default values only. By default, this script defined as specific is called 'YORDERSUBLIN_CSPE'.

As nothing is required, there is no action to be done in this script.

If the properties in the sub-lines have to be filled, no code is necessary, the Mandatory check box will be activated in the class definition.

At line level

This script associated with 'YORDLINE' is called 'YORDLINE_CSPE'. The only code written is used when a line is modified to propagate to the sub-lines some of the modifications that have been done.

Two things happen:

EventACTION valueCURPTHCURPRO"this" valueActions to be done
When a sub-line is added to a lineADD_LINE_AFTER"""YOL"YORDELINE instance1. Propagate YORDNUM and YORDL from line to the sub-line
2. Compute YORDSL with a new value greater than the existing line values
When a key property (YORDNUM or YORDL) is modified in the line"PROPAGATE""""YORDNUM"YORDELINE instance1. Propagate YORDNUM on all the sub-lines
"""YORDL"YORDELINE instance2. Propagate YORDL on all the sub-lines
The first case gives the corresponding code (after the '$METHOD' label):
$METHODSCase CURPTHWhen "YOL"Case ACTIONWhen "ADDLINE_AFTER" : # A line has been addedthis.YORDNUM = this.APARENT.YORDNUM : # "this" is the sub-line, this.APARENT is the linethis.YORDL = this.APARENT.YORDL# To assign YORDSL to a new value, compute the maximum of the YORDSL property# for all the YOL child instance of the line, and add 1 to the resultthis.YORDSL = 1+max(this.APARENT.YOL(1..maxtab(this.APARENT.YOL)).YORDSL)EndcaseEndcaseReturn
The second case gives the corresponding code (after the $PROPERTIES label):
$PROPERTIESCase CURPTHWhen "" : # We are here on the properties of the instance associated with the current class (line)Case CURPROWhen "YORDNUM" : # Order number at line levelCase ACTIONWhen "PROPAGATE" : # The value has been modifiedLocal Integer I# Loop on all existing sub-lines of the line : "this" is the line modifiedFor I=1 To maxtab(this.YOL)If this.YOL(I)<>null : this.YOL(I).YORDNUM=this.YORDNUM : EndifNext IEndcaseWhen "YORDL" : # Line number at line levelCase ACTIONWhen "PROPAGATE" : # The value has been modifiedLocal Integer I# Loop on all existing sub-lines of the line : "this" is the line modifiedFor I=1 To maxtab(this.YOL)If this.YOL(I)<>null : this.YOL(I).YORDL=this.YORDL : EndifNext IEndcaseEndcaseEndcaseReturn

At header level

This script associated with YORDER is called YORDER_CSPE. There is more code to write because several cases need to be managed:

On properties, the propagation code is the following:

$PROPERTIESCase CURPROWhen "YORDNUM"Case ACTIONWhen "PROPAGATE"# Loop on all the lines present to assign YORDNUM# The propagation rule present in YORDLINE_CSPE script will update the sub-line valuesLocal Integer IFor I=1 To maxtab(this.YOD)If this.YOD(I)<>null : this.YOD(I).YORDNUM=this.YORDNUM : EndifNext IEndcaseEndcaseReturn

On methods, the code is the following:
$METHODSCase CURPTHWhen "" : # Method on the order classCase ACTIONWhen "YTOTAMOUNT" : # Total amount computation : ARET_VALUE returns the result, I is a loop variableLocal Integer IARET_VALUE=0# Loop done on all the YOD values if the pointer is not null and if the line is not marked as deletedFor I=1 To maxtab(this.YOD)If this.YOD(I)<>nullIf find (this.YOD(I).ASTALIN,[V]CST_ADEL, [V]CST_ANEWDEL)=0this.YOD(I).YPRICE=fmet this.YOD(I).LINEPRICEARET_VALUE+=this.YOD(I).YPRICEEndifEndifNext I# When an insertion or an update method is called, call the previous computation method before database persistenceWhen "AINSERT_CONTROL_BEFORE", "AUPDATE_CONTROL_BEFORE"this.YTOTAL= fmet this.YTOTAMOUNTEndcaseWhen "YOD" : # method on the lineCase ACTIONWhen "LINEPRICE" : # Total line computation : ARET_VALUE returns the result, I is a loop variableLocal Integer IARET_VALUE=0# Loop done on all the YOL values if the pointer is not null and if the line is not marked as deletedFor I=1 To maxtab(this.YOL)If this.YOL(I)<>nullIf find (this.YOL(I).ASTALIN,[V]CST_ADEL, [V]CST_ANEWDEL)=0ARET_VALUE+=this.YOL(I).YUNITPRICE*this.YOL(I).YQTYEndifEndifNext IWhen "ADDLINE_AFTER" : # A line is addedGosub ADD_LINE_YOD : # We need to manage the numbering of the line addedEndcaseEndcaseReturn

The last part of the code is now the 'ADD_LINE_YOD' sub-program. This code assigns two properties on the YOD collection:

For example, we start with an empty order. Lines A, B, and C are added first. Then line D is inserted between A and B, line E is inserted between lines B and C, line F is inserted between A and D, line G is inserted before A, and line H is added at the end. The final result will be the following, as it appears on the page when all updates have been done:

line insertedAORDER propertyYORDSL valueYSORT value
G1750
A21100
F36125
D44150
B52200
E65250
C73300
H88400

As you can see, 'YORDSL' gives the input order, 'AORDER' handles the index for displaying the lines, and 'YSORT' allows you to sort the lines without having to reassign other number lines when a limited number of insertions have been done.

The following algorithm manages this operation:
$ADD_LINE_YOD# Increment value for dichotomy algorithmLocal Integer MY_STEPMY_STEP = 100# Manage key identifiersthis.YORDNUM = this.APARENT.YORDNUMthis.YORDL = 1 + max(this.APARENT.YOD(1..maxtab(this.APARENT.YOD)).YORDL)# Manage the SORT propertyLocal Integer I, BEF_LINE, AFT_LINE, OFFSET_LINE : # Positions in collection (they have a 0 value here)# Where are the previous and next lines ? Loop on YOD values# "this" is here the current line, we need to loop on the YOD in the header instance (APARENT)For I = 1 To maxtab(this.APARENT.YOD)If this.APARENT.YOD(I) <> nullIf this.APARENT.YOD(I).AORDER = this.AORDER - 1BEF_LINE=IElsif this.APARENT.YOD(I).AORDER = this.AORDER + 1AFT_LINE=IEndifEndifNext ILocal Integer BEF_LINE_SORT, AFT_LINE_SORT: # Positions in the sort order# If the new line is not added at the end of the collectionIf AFT_LINE <> 0# Assign the SORT properties (BEF_LINE_SORT is 0 if insertion at the beginning of the collection)AFT_LINE_SORT = this.APARENT.YOD(AFT_LINE).YSORTIf BEF_LINE <> 0 : BEF_LINE_SORT = this.APARENT.YOD(BEF_LINE).YSORT : Endif# If there is enough space then we insert using a dichotomy methodIf BEF_LINE_SORT <= AFT_LINE_SORT - 2this.YSORT = int((BEF_LINE_SORT + AFT_LINE_SORT)/2)Else# If not enough space, we reassign the SORT property of all lines from the new inserted lineFor I=1 To maxtab(this.APARENT.YOD)If this.APARENT.YOD(I) <> nullOFFSET_LINE=this.APARENT.YOD(I).AORDER-this.AORDERIf OFFSET_LINE>=0# Assign the SORT propertythis.APARENT.YOD(I).YSORT = BEF_LINE_SORT + MY_STEP + (OFFSET_LINE * MY_STEP)EndifEndifNext IEndif# If the new inserted line is at the last position in the arrayElse #If AFT_LINE = 0# If a previous line exist, we know the last value used, otherwise it is 0If BEF_LINE <> 0 : BEF_LINE_SORT = this.APARENT.YOD(BEF_LINE).YSORT : Endif# Assign the SORT propertythis.SORT = BEF_LINE_SORT + MY_STEPEndifReturn

At representation level

At representation level, the only code that has been added is the one that triggers a computation of the total every time a unit price is modified or a line is added. This code does not have to be inserted in the class code; otherwise, when running in service mode, the computation is repeated several times.

There is no guarantee that the total price will be displayed every time a modification is made on the price due to a bad web connection, especially with mobile devices, or data sent by group. Every time an exchange is made between the client and the server, the client returns the modifications made on prices since the last time, and the server sends back the computed total price accordingly.

The implemented code executes the following rules:

The computation of the price is done by calling the 'YTOAMOUNT' method on the order header instance, which provides the following code:
$METHODSCase CURPTHWhen "YOH.YOD" : # Here we are at the line level, "this" is the line, "this.APARENT" is the headerCase ACTIONWhen "PROPAGATE"this.APARENT.YTOTAL= fmet this.APARENT.YTOTAMOUNTEndcaseWhen "YOH.YOD.YOL" : # Here we are at the sub-line level, "this" is the sub-line, "this.APARENT.APARENT" is the headerCase ACTIONWhen "PROPAGATE"this.APARENT.APARENT.YTOTAL= fmet this.APARENT.APARENT.YTOTAMOUNTEndcaseEndcaseReturn$PROPERTIESCase CURPROWhen "YOH.YOD.YPRICE"Case ACTIONWhen "PROPAGATE"this.APARENT.YTOTAL= fmet this.APARENT.YTOTAMOUNTEndcaseWhen "YOH.YOD.YOL.YQTY", "YOH.YOD.YOL.YUNITPRICE"Case ACTIONWhen "PROPAGATE"this.APARENT.APARENT.YTOTAL= fmet this.APARENT.APARENT.YTOTAMOUNTEndcaseEndcaseReturn

Validation and deletion management

In a second step of this development, a validation operation has been added. Being an operation, it is considered as stateless. When the event is caught, 'this.YORDNUM' (the key) is filled but the instance is not. A complex operation needs to perform an 'AREAD' method first. In this case, the only operation to perform is to update the 'YSTATUS' value to 2 if it is not already done. If it is already the case, a warning is returned.

An important point is that for the moment, using error handling classes and methods is useless in an operation context because the instance in which the operation runs is volatile and will disappear with the corresponding 'AERROR' child class. The only solution is to return a single status, which is done through the 'ARET_VALUE' parameter.

The code of the operation has been defined in the 'YORDER_CSPE' script, just after the '$METHODS' label. If 'CURPTH' is empty, the code should be added after the lines managing the "AINSERT_CONTROL_BEFORE" and "AUPDATE_CONTROL_BEFORE" methods.

The code is as follows:

When "YVALIDATE"Local File YORDER# Execute the update transactionTrbegin [YOH]Update [YOH] Where YORDNUM=[L]YORDNUM and YSTATUS=1 With YSTATUS=2Commit# If no record updated, it can be because the order does not exist or because already validatedIf adxuprec=0Read [YOH]YOH0=[L]YORDNUM# Send a warning status if order already validated, and error status if order doesn't existIf fstat=[V]CST_AOKARET_VALUE=[V]CST_AWARNINGElseARET_VALUE=[V]CST_AERROREndifEndifEndcase

When a deletion is requested, the system also controls that the 'YSTATUS' is not equal to 2; otherwise, the deletion is rejected. This will be added in sequence to the previous lines. When 'CURPTH' is empty it is also done at the header level in the '$METHODS' section.

The code is the following:

# The order can only be deleted if YSTATUS is 1When "ADELETE_BEFORE"If this.YSTATUS<>1VAL_OK=fmet this.ASETERROR('','Order validated ',[V]CST_AERROR)Endif