Desktop application (WPF, .NET 8) for managing operators in Comarch ERP XL: creating operator cards through XL-API, setting passwords, linking them to employee cards, editing and archiving, plus printing warehouse badges with a QR code.
Replaces the old WinForms application (Drukowanie identyfikatorów magazyn), which kept its own
operators in dbo.KontrolaPakowaniaOperatorzy and printed through Crystal Reports.
There is no logon screen - the XL account and the database come from appsettings.json and the
connection is made at start-up.
| Feature | Where it goes |
|---|---|
| Operator list | CDN.OpeKarty plus position (CDN.StaKarty), branch (CDN.FrmStruktura), employee (CDN.PrcKarty) |
| Filtering | branch picked from a dropdown (defaults to NewOperator:BranchId) and a switch for archived operators; archived employees (Prc_Archiwalny <> 0) are never listed |
| Creating an operator | XL-API, XLNowyOperator |
| Editing an operator | acronym, password, employee and e-mail (SQL) |
| Archiving an operator | Ope_Zablokowane 1/0 - archive and restore (SQL) |
| Operator password | new operator: XL-API XLZmienHaslo; edit: hash written directly (SQL) |
| Employee link | CDN.OpeKarty.Ope_Prc* (SQL) |
| Employee e-mail | CDN.PrcKarty.Prc_EMail (SQL) - required, defaults to acronym@EmailDomain |
| Badge printing | label drawn by WPF, QR generated locally (QRCoder) |
New operators always land on the position and branch configured in appsettings.json
(PositionId 8 - "Magazynierzy", BranchId 15 - "Magazyn").
The operator-to-employee link is stored on CDN.OpeKarty only. One employee can serve any number
of operators, so the employee card is never rewritten apart from the e-mail address.
Picking an employee by hand copies the first name, the last name and - when the card has one - the e-mail address into the form.
The e-mail address is required. The form suggests acronym@gaska.com.pl and keeps it in step with
the acronym, but once the user types an address of their own the field stops following. Clearing
the field hands control back to the acronym. Because the address is stored on the employee card,
creating an operator requires an employee to be selected.
- Comarch ERP XL installed on the machine. Only the managed shim
(
cdn_api20251.net.dll) ships with this repository, inComarchOperatorManager/lib/. The nativecdn_api.dllcannot travel with it - it depends on three dozen other Comarch libraries and the Clarion runtime - so it is loaded from the installation itself. The folder is discovered from the registry, so an XL upgrade does not require any change here. - .NET 8 Desktop Runtime x86. The process has to be 32-bit because
cdn_api.dllis 32-bit, so the project pinsPlatformTarget=x86. - A Comarch operator account with XL-API access (a licence is taken at start-up).
- A SQL account for the XL database (reads, plus the columns XL-API does not expose).
Two things are deliberately kept out of source control:
| Path | What to do |
|---|---|
ComarchOperatorManager/lib/cdn_api20251.net.dll |
copy it from the Comarch ERP XL installation folder (vendor binary) |
ComarchOperatorManager/appsettings.json |
copy appsettings.example.json and fill in the credentials |
Without the shim the project will not compile; without the settings file the application stops at start-up with a message saying so.
When Sql.Server / Sql.Database are empty the application reads
HKLM\SOFTWARE\WOW6432Node\CDN\CDNXL\MSSQL\Bazy and expands the alias
(for example TESTOWA_2014 becomes CDNXL_TESTOWA_2014 on 192.168.0.105).
To switch to production, set Comarch.Database to GASKA.
The file holds passwords in clear text, which is why it is git-ignored - restrict access to the folder accordingly.
There is no XlPath setting by default. The Comarch installation folder is found through the
uninstall entry, the cdnxl.exe app path and finally C:\Program Files (x86)\Comarch ERP XL*,
picking the newest folder that actually contains cdn_api.dll. Add "XlPath": "..." under
Comarch only if an unusual installation defeats that.
For a new operator the password is set by XL-API, not by SQL. XLZmienHaslo always works
on the operator currently logged on to the API, so the session is briefly switched over:
- log our own session out (
XLLogout), - log on as the freshly created operator - his password is still empty,
- call
XLZmienHaslo(empty -> new), - log the temporary session out,
- log back on with the account from
appsettings.json.
Step 5 runs whether or not the password change succeeded, but deliberately not inside a finally
block: a failure there would replace the real error with a misleading one. If the password change
fails, that error is reported; if coming back fails as well, the application says so plainly,
because from that point it cannot talk to XL-API and has to be restarted.
A password may be at most 16 characters (XLZmianaHaslaInfo.NoweHaslo is CSTRING(17)) and must
be printable ASCII without spaces.
Comarch stores only a hash in Ope_Haslo, so a password can never be read back - the application
neither displays nor reconstructs one.
Edytuj opens a form for changing the acronym (Ope_Ident), setting a new password, linking or
changing the employee, and correcting the e-mail address. Only fields that actually differ are
written - opening the window and saving without touching anything does nothing at all. An acronym
that is already taken is refused with a warning.
Archiving sets Ope_Zablokowane = 1. Comarch has no archive flag on an operator card, so
blocking the account stands in for it. An archived operator disappears from the list until
"Pokaż zarchiwizowanych" is ticked; his acronym then carries an "arch." badge and the edit window
offers Przywróć z archiwum instead. Restoring writes 0, the value XL itself settles on the
first time an operator logs in.
XLZmienHaslo only ever works on the operator currently logged on to the API, and it insists on
his previous password. When creating an operator that password is empty and the application
uses exactly that; for an operator already in use nobody knows it. Changing the password of an
existing operator therefore writes the hash straight into Ope_Haslo. Verified: XL accepts a
logon with a password set this way and rejects a wrong one.
CDN.OpeKarty and CDN.PrcKarty carry Comarch triggers that flip SET NOCOUNT, so the row count
returned by ExecuteNonQuery cannot be trusted - it depends on which columns the UPDATE touched
and therefore which trigger fired. Every write counts its own rows with OUTPUT ... INTO.
Landscape layout filling the whole label: bold acronym on the left with the name centred beneath it, and a square QR code on the right using the full height.
The size comes from appsettings.json and is not reached by scaling a fixed design.
BadgeLayout works out every measurement for the label at hand: the code's side equals the inner
height, the text column takes the remaining width, and the acronym font size is measured
(FormattedText) so that the particular acronym fits without being trimmed. That keeps the QR
square on any label proportions - a stretched symbol is the most common reason a code will not
scan.
The QR payload is e-mail + TAB + the contents of Ope_Haslo (exactly the value the
database returns) + CR. The scanner emulates a keyboard, so the tab moves to the password box
and the CR confirms the form. Nothing has to be typed when printing - the print window is just a
printer, a number of copies and a preview.
The code is drawn as vector art, not a bitmap. A bitmap has a fixed module size that the printer has to rescale, and a non-integer ratio drops or doubles module columns until the code stops scanning. A drawing is rasterised by the driver at its own resolution.
Error correction level is M. Reads were verified with the ZXing decoder on labels rendered natively at 203, 300 and 600 dpi.
dotnet build ComarchOperatorManager.sln -c Releaselib/cdn_api20251.net.dll has to be in place first (see First checkout). A different XL
version needs that file swapped, the cdn_api20251.net reference name in the .csproj changed
to match, and Comarch.ApiVersion updated.
Code, names and comments are in English; Polish is used only for text the user sees.
Configuration/ AppSettings, XlDatabaseRegistry (database aliases), ComarchInstallation
(locating the XL folder)
Model/ XlOperator, Employee, BranchOption, request objects, XlPassword
Data/ ComarchRepository - every SQL statement; ComarchDate - Comarch date encoding
Xl/ XlSession (XLLogin/XLNowyOperator/XLZmienHaslo/XLLogout) on a dedicated STA thread
Services/ AppSession (application context), OperatorService (create and edit orchestration)
Printing/ QrGenerator, BadgeLayout, BadgeModel, PrintService, Millimeters
ViewModels/ MVVM (CommunityToolkit.Mvvm)
Views/ WPF windows, converters, CornerClip
Themes/ Palette.xaml, Controls.xaml, Converters.xaml
Assets/ app.ico - application icon
lib/ cdn_api20251.net.dll - Comarch managed shim (not in source control)
XL-API is a native, single-threaded library, so every call goes through one STA thread
(Xl/XlApiThread.cs) and the session opened by XLLogin is always closed from that same thread.
{ "Comarch": { "Database": "TESTOWA_2014", // database alias registered in Comarch "ApiVersion": 20251, // must match the shim in lib/ "ProgramId": "OperatorManager", // shown on the XL session list "Operator": "KURKRZ", // account the application logs on with "Password": "..." // XL-API accepts at most 8 characters }, "Sql": { "User": "Gaska", "Password": "...", "Server": null, // null = resolved from the Comarch registry "Database": null // null = resolved from the Comarch registry }, "NewOperator": { "PositionId": 8, "BranchId": 15, "EmailDomain": "gaska.com.pl" }, "Label": { "WidthMm": 61, "HeightMm": 34 } }