Wednesday, December 3, 2014

How to change the Enter key functionality

In Sage X3 screens, the Enter key usually has some default functionality - for example, it might be triggering the OK button. Regardless of what the actual default functionality is, here is how you can block it or replace it with something else.

When the Enter key is pressed, X3 assigns the value GSTARET to the variable REPONSE. You can check for that condition in the 'After Choice' action. Depending on the template you are using, this action might be APRES_CHOI or AP_CHOIX. For the examples below, we will use APRES_CHOI.

First of all, you need to add the action to the list of actions in the $ACTION label of your specific processing file like this:

$ACTION
Case ACTION
# ... Other actions ...
  When "APRES_CHOI" : Gosub APRES_CHOI
# ... Other actions ...
  When Default
Endcase
Return


Then, in the action handler, if you just want to stop the standard Enter key functionality from executing, you can do:

$APRES_CHOI
If REPONSE=GSTARET : REPONSE=0 : Endif
Return


And if you want to replace it with new functionality:

$APRES_CHOI
If REPONSE=GSTARET
    REPONSE=0
    Infbox("Custom functionality goes here!")
Endif
Return


Once you make sure that the Infbox statement is getting executed when you press Enter in your screen, you can put in its place something actually useful, depending on your needs.

Thursday, November 13, 2014

How to tab directly to a non-neighbour field in a grid

Tabbing through default values can get boring if you have to do it a lot. The good news is, with Sage X3 you don't have to do it at all!

Suppose, for example, that you enter Sales orders in the following screen (GESSOH function)


On each row, you enter the product code and then you tab through to the Ordered qty field, accepting the default values for Description, Ship site and Sales unit. This is three tabs too many. Wouldn't it be great if you could just hit Tab once in the Product field and get directly to the Ordered qty field, with the default values appearing as they normally should in the three fields in-between?

Well, here's how to do just that.

You need to create an After change action on the Product field. For the Sales orders, this is the [M:SOH4]ITMREF field. You must open the SOH4 screen in the GESAMK function and create the appropriate action like this:


In your specific processing file SPESOH, in the After change action handler (AM_ITMREF) you must assign a value to the zonsui variable, which contains the next field to be entered. To go to the Ordered qty field on the current line, this value must be QTY(nolign-1). So the AM_ITMREF handler should look like this:

Subprog AM_ITMREF(VALEUR)
Variable Char VALEUR()
zonsui="QTY(" + num$(nolign-1) + ")"
End

After you validate your screen and your entry transactions, you will have the desired effect of tabbing directly from Product to Ordered qty field when you enter Sales orders. And you can use this technique in any other screen you want.

Tuesday, November 4, 2014

How to prevent the printing of a report

To prevent the printing of a report, you can create a specific processing for the report and put the following code in the file:

Subprog IMPRIME(NBPAR, PARAMETRE)
Value Integer NBPAR
Variable Char PARAMETRE()()
If ... ... ... ...
    GOK=0
Endif
End


The principle is that when you set GOK to 0, the report will not be printed. Of course, you have to substitute the dots after 'If' with the appropriate condition. For example, suppose that you print a report from the Deliveries function (GESSDH) and you want to print it only if the delivery is validated. To prevent the printing otherwise, you might have:

Subprog IMPRIME(NBPAR, PARAMETRE)
Value Integer NBPAR
Variable Char PARAMETRE()()
If [M:SDH1]CFMFLG<>2
    GOK=0
Endif
End

Saturday, October 25, 2014

How to activate a custom menu item

In the GESAWI function, Buttons/menus tab, you can add custom menu items to any Sage X3 window like this:


After you validate the window (and the entry transactions, if it is an object managed with entry transactions) you will see your custom menu item when you open the window, but it will always be grayed out. That is the default. To activate it, you need a bit of custom programming.

In the specific processing file for your window you must handle the SETBOUT action and put the following code in the action handler:

$SETBOUT
CHMEN += "O"
Gosub SET_BOUT_SPE From GSAISIE
Return

The principle is this: You add the codes of the menu items that you want to activate to the CHMEN variable and then call the SET_BOUT_SPE standard function. You use only the second letter of the code - in the above screenshot you can see that the actual code of our custom menu item is DO, but we only add O to CHMEN.

You can add more than one code at once. For example, if you have a custom menu item with code DO (as shown above) and another one with code DP, you can add them by saying

CHMEN += "OP"


And, of course, you can have an If statement and only activate a menu item when certain conditions hold.

When adding menu item codes to the CHMEN variable, be careful to use += and not =.

Tuesday, October 21, 2014

How to lock out a user on unsuccessful login

To boost the security of Sage X3 and prevent brute-force attacks, you might want to lock out a user after three unsuccessful login attempts. Here is how to do it, using workflow and a helper table:

First we will create the helper table. We do this in the GESATB function:

Fields tab:

Index tab:


This table will hold the number of unsuccessful login attempts for each user.

Next we need an action that will be triggered by the workflow. We create it in the GESACT function:


Do not forget to mark the Workflow checkbox!

Now we go to the Workflow rules function (GESAWA) and create the following workflow:

General tab:

Recipients tab:
Only one row. All fields should be empty or have the value of "No", except for Type and Recipients.

Action tab:

And the last thing we need to do is create the standard processing file for our ZPASS action. So we go to the editor (function ADOTRT) and create the ZPASSSTD file with this code:

Subprog PASS()
If GERR=6
    If clalev([ZPS])=0 : Local File ZPASS [ZPS] : Endif

    Read [ZPS]ZPS0=GUSER
    If fstat=5
        Raz [ZPS]
        [F:ZPS]USER=GUSER
        [F:ZPS]COUNT=1

        Write [ZPS]
        If fstat
            Infbox("Error while writing [ZPS], fstat="+num$(fstat))
        Endif
    Elsif fstat
        Infbox("Error while reading [ZPS], fstat="+num$(fstat))
    Else
        If [F:ZPS]COUNT>=3
            If clalev([ZAUS])=0 : Local File AUTILIS [ZAUS] : Endif
            Update [ZAUS] Where USR=GUSER With ENAFLG=1

            Delete [ZPS] Where USER=GUSER       
        Else
            [F:ZPS]COUNT+=1

            Rewrite [ZPS]
            If fstat
                Infbox("Error while writing [ZPS], fstat="+num$(fstat))
            Endif
        Endif
    Endif
Elsif GERR=0
    If clalev([ZPS])=0 : Local File ZPASS [ZPS] : Endif
    Delete [ZPS] Where USER=GUSER       
Endif
End

And here is how it all works:

The workflow that we created is of type Miscellaneous and has the CON event code. This means that the workflow will be triggered on each connection to the application. The GERR global variable will be set to 6 if the provided connection password is incorrect (you might need to debug in order to see which is the right value of GERR - my Sage X3 documentation says that GERR is set to 1 when the password is incorrect, but in my case it turned out to be actually 6). The workflow runs the ZPASS action, which means that the code we placed in the ZPASSSTD file is executed. This code uses our ZPASS helper table to check if the user has three consecutive unsuccessful login attempts due to incorrect password, and if so, locks the user out by deactivating his record in the AUTILIS table (setting the ENAFLG field to 1).

Once locked out, the user cannot connect to X3 even with his correct password. An administrator will have to re-enable his record in the GESAUS function. And if the administrator locks himself out, he will have to change his record in the AUTILIS table (set the ENAFLG field to 2) directly in the database.

Tuesday, October 14, 2014

How to manually unlock a code file

When you open a code file in the Sage X3 internal editor (function ADOTRT), Sage X3 locks the file to prevent simultaneous modification. When you close the file, the lock is released. But if you experience an unexpected exit from the system while you have a file opened in the editor (for example, due to power failure), the file lock might not be released. In this case, when you open the file again, you will see a warning message and the file will be in read-only mode.

In such situations, to release the lock manually you have to delete a temporary file that X3 creates to mark the code file as locked. This temporary file has the same name as your code file and an extension of 'LCKsrc'. For example, if you open the SPEPOH code file, X3 will create a temporary file called 'SPEPOH.LCKsrc'.

The temporary file is created in the same directory as its corresponding code file. This is usually the TRT subdirectory of your X3 folder. To release the lock, all you have to do is delete the temporary file (be careful not to delete the .src and .adx files of the same name!)

Saturday, October 11, 2014

How to start a manual workflow programmatically

A manual workflow is usually started - you guessed it - manually, using the SAIWRKMAN function. But if you need to start a manual workflow programmatically, here is one way do it:

Local Char PARAM(GLONAWA)(1..3)
PARAM(1)="XXX"
PARAM(2)=""
PARAM(3)="1"

SAVGSERVEUR=GSERVEUR
GSERVEUR=2

Gosub INIT From SAIWRKMAN
Gosub CONTROLE From SAIWRKMAN
Gosub EXEC From SAIWRKMAN

GSERVEUR=SAVGSERVEUR

In the above code, XXX should be replaced by the name of the manual workflow that you want to run.

Keep in mind that depending on where you use it, this might change the values of some of your significant global variables and/or modify the masks and files by default. If such errors occur, you should program around them. For example, you can save the values of the affected global variables before running the above code and restore them afterwards.