cleverence-mslx
Read, understand and safely edit Cleverence Mobile SMARTS (Склад 15 / Магазин 15) configuration stored as .mslx files - the XML algorithm graphs that drive handheld terminal (ТСД) workflows in 1C integration projects. Use this skill whenever the task touches Cleverence / Mobile S
Install
npx skills add https://github.com/Desko77/claude-code-skills-1c/tree/main/skills/cleverence-mslx
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install desko77-claude-code-skills-1c@llmmart
git clone https://github.com/Desko77/claude-code-skills-1c.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole desko77/claude-code-skills-1c collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Cleverence Mobile SMARTS (.mslx) configuration
Cleverence "Склад 15" / "Магазин 15" runs handheld-terminal (ТСД) logic as algorithms:
a directed graph of actions. The Mobile SMARTS Panel stores each document type and
each operation as a single-line XML file with the .mslx extension. Editing these by eye
is error-prone; the failures are almost always broken transitions or wrong variable /
field names, not XML syntax. This skill encodes how the graph works and how to change it
without breaking it.
Mental model
- DocumentType
.mslx(подDocuments/DocumentTypes/...) - a whole terminal workflow (e.g. "Сборка контейнера"). Its root is<DocumentType>. - Operation
.mslx(подDocuments/Operations/...) - a reusable sub-routine called by documents or other operations. Root is<Operation>. - Both contain
<Actions>- the ordered list of graph nodes. - The Panel and the MS Server work off a server database, not necessarily the git working copy. Editing a file on disk changes nothing until the config is transferred to the server (Сервис -> Сравнение конфигураций, or copying into the live DB + restart). If the Panel shows old behaviour after your edit, suspect a stale/never-transferred file before suspecting your change.
The single most important rule: CL_ copies of vendor operations
Vendor operations (typical Cleverence обработчики, usually under Operations/EN/...,
Operations/Основные/... etc.) get overwritten on Cleverence updates. Editing them
directly is like editing a vendor 1C extension - your change disappears on the next update.
So:
| What | Rule |
|---|---|
| Our own document type (e.g. "Сборка контейнера") | Edit in place. Do not clone. |
Our own operation (in the project root Operations/) |
Edit in place. |
| A vendor/typical operation we need to change | Make a copy CL_<Name> (placement below), edit the copy, and in the caller switch operationName to CL_<Name>. |
| A new operation | Prefix CL_ (latin). |
Copy only the operations you actually change. If the change is inside a nested vendor
operation, copy that one and repoint its CL_ parent at it - do not clone the whole chain.
Where to put a CL_ copy. Put it in your own branch of the operations tree -
Documents/Operations/ (the root) - not inside the vendor subfolder it was copied from
(Documents/Operations/EN/...). The Panel resolves a call by the operation's name, not by
its file path, so a CL_<Name> in Operations/ fully serves callers that use
operationName="CL_<Name>". Keeping the copy in the vendor subfolder (a) puts it among files
that get overwritten on update, and (b) risks two files with the same name if the Panel
later re-exports the operation into the root - a duplicate that conflicts on import. So: after
creating/moving the copy to Operations/, delete any stale same-name file left in the
vendor subfolder.
How the action graph flows
Every action decides where control goes next. Read references/graph-mechanics.md for the
full rules; the essentials:
- Sequential fall-through: an empty
nextDirection=""means "go to the next action in document order". This is why the physical order of<Actions>matters - inserting an action in the wrong place silently changes the flow. - Named transitions point to an action's
name. Resolution is case-insensitive (a button direction"Просмотр факт"resolves to actionname="Просмотр Факт"). So if a transition "is not found", the cause is almost never letter-case - it is a genuinely missing action (often a stale file on the server). - ConditionAction branches with
yesDirection(TRUE) andnoDirection(FALSE); empty means fall through to the next action. - QuestionYesNoAction branches with
yesDirection/noDirection. - QuestionAction (a menu) has three parallel arrays -
Buttons,ButtonTexts,ButtonDirections- matched by index. They must stay the same length; a button whoseButtonDirectionnames a non-existent action throws "действие для перехода не найдено". - Built-in targets that are not action names:
back,return,abort,exit,home,process zero, and"". indent= visual nesting in the Panel tree, not control flow. Each action carries an integerindent. Logically subordinate actions (aprocess zerobranch under the scan window, the body executed inside a condition) must haveindentgreater than their parent - they appear shifted right in the Panel algorithm tree. Flow is driven bynextDirection/named transitions, not byindent; but leaving a flatindent=0on nested actions renders the structure wrong/flat in the Panel and is a real defect to fix. When you copy or hand-edit a.mslx, preserve/setindenton the nested actions (it is easy to lose it on a copy).mslx_inspect.pyindents its dump by this attribute, so a flat structure shows up at a glance.
Calling another operation: parameter mapping
OperationAction invokes a sub-operation by operationName and maps variables explicitly:
InKeys/InValues- the sub-operation's variable name / the caller's expression. Expressions are wrapped in braces:InValues = {GO.GetBarcodeData(ScannedBarcode)}.OutKeys/OutValues- the sub-operation's result variable / the caller's variable.
Many shared variables (BarcodeData, IdentificationCode, CurrentItem, ...) are global
to the session, so some calls pass nothing and rely on globals. When in doubt, look at how
the operation is already called elsewhere (grep for its operationName) and copy that
contract verbatim - guessing variable names is the #1 cause of "scan does nothing".
Calling 1C online from the terminal
To run a 1C function live during a scan, use an InvokeMethodAction against the 1C
connector. The function name you pass must be a method of the integration data processor
(вендорская ИнтеграционнаяОбработка_*), and it must return a ТаблицаЗначений (not JSON).
The full mechanism, the thin-wrapper pattern (with vendor markers), and what is not an
online call (the "Список произвольных кодов" is field mapping, not a call) are in
references/online-1c-call.md.
Marking codes (КМ / КИЗ) and document lines
The marking code on a document line lives in Item.МаркаИСМП (and Item.ГрупповаяУпаковка
for aggregates) - not Item.Марка. You match against the recognised IdentificationCode,
which is produced by GetIdentificationCode from BarcodeData, not against the raw scanned
string. Reuse the ready operations (FindMCInDocument, DeleteCurrentItemFromDocument,
IsMarkingCode) instead of hand-writing queries. See
references/km-and-document-operations.md.
Editing methodology (do this, in this order)
- Inspect first. Run the bundled tool to see the real graph and current links:
It dumps every action (type, name, operationName, directions, In/Out mapping) and then validates: XML well-formedness, that every transition resolves, and that menu button arrays are balanced.PYTHONIOENCODING=utf-8 python scripts/mslx_inspect.py "<file>.mslx" - Find the proven pattern. Before writing new graph logic, grep the existing operations for one that already does it and copy its action structure and variable contract. The codebase almost always has a working precedent (scanning, finding a line, deleting a line, confirming, calling 1C). Reusing it beats inventing.
- Make point edits.
.mslxis one long line; use exact-string Edit, keep ids stable where you can, and keep the action order consistent with the fall-through you intend. - Re-inspect. Run
mslx_inspect.py ... --validateagain. Zero dangling transitions and balanced button arrays is the bar before you hand it back. - Remember the transfer step. A validated file on disk is not yet live - it must reach
the MS Server. Tell the user to transfer it and to confirm by checking that a changed
operationName(e.g.CL_...) shows up in the Panel.
When a scan "does nothing" on the TSD
Before suspecting the graph, rule out the input itself - the graph is usually innocent:
Ctrl+Vinto the debugger window is NOT a scan event. A scan window catches input via aKeyToAction barcode="{any}"KeyJump, which fires on a scan event. Pasting text into the field withCtrl+Vis keyboard input - it does not trigger thebarcodeKeyJump, so "nothing happens". Use the debugger's emulate-barcode function (a dedicated barcode input), or a real scanner.- DataMatrix loses its GS separators on copy-paste. A ЧЗ marking code (DataMatrix) contains
the GS control char (ASCII 29). Clipboard/Notepad strips it, so even if the input lands,
GO.GetBarcodeData()won't recognise the КМ structure. Only a real scanner (or the emulator) preserves GS. - The config may not be on the server. The debugger runs the server copy; an edited file in the repo is not live until transferred (Сервис -> Сравнение конфигураций).
- Only after these: check the graph. Confirm the scan window has
KeyToAction barcode="{any}"pointing at the scan-handling action, and (with a breakpoint in the debugger) whether that action is even reached. If it is reached but bails out, inspectBarcodeData- the code was read but not recognised.
Linking a TSD document back to its 1C document (dedup on completion)
When a TSD document must, on completion, update an existing 1C document instead of creating
a new one, the back-link is a Panel setting, not graph logic: in the БП open "Настройка
загрузки полей шапки документа" and the rule whose target (приемник) is the Ссылка field,
match mode Поиск по GUID. The source attribute of that rule is a classic trap:
Идентификатор- the MS document's own Id. This is the correct source for dedup. It is stable for one TSD document and is exactly what the vendor completion code resolves the 1C reference from (ШапкаДокумента.Ид). Use this.Идентификатор исходных документов(ИдИсходныхДокументов) - wrong for the back-link of a TSD-created document. If an online step creates/re-creates the 1C document and writes its GUID here, a fresh GUID can arrive on every completion -> the GUID search never matches -> a new 1C document is created each time = duplicates (often without a warehouse). This attribute is for the "document assembled from several 1C documents" case, not the back-link.
Symptom this fixes: every завершение on the TSD spawns a second 1C document. Root cause is
almost always the Ссылка rule pointing at Идентификатор исходных документов instead of
Идентификатор. The vendor completion path only ever resolves the 1C ref from
ШапкаДокумента.Ид - writing a GUID into ИдИсходныхДокументов does nothing for dedup.
Caveat for TSD-created docs: their MS Id is new_<guid>. A hand-rolled
Документы[Тип].ПолучитьСсылку(Новый УникальныйИдентификатор(Ид)) throws on the new_ prefix
(invalid GUID) and, inside a bare Попытка, silently falls through to creating a duplicate; the
Поиск по GUID rule handles the prefix correctly.
Reference files
references/graph-mechanics.md- action types, every transition attribute, fall-through, button arrays, KeyJumps, common caveats. Read when editing graph flow.references/online-1c-call.md- the InvokeMethodAction -> integration-processor -> ТаблицаЗначений mechanism, the wrapper pattern, what is not an online call.references/km-and-document-operations.md- КМ line fields, IdentificationCode, the ready find/delete/recognise operations and how to chain them.
Constraints in 1C-integration projects
Vendor extensions and 1C extensions are not edited directly - mark project insertions and prefer copies/wrappers. In this family of projects code and docs avoid the letter "е" and long dashes. Confirm destructive or shared-state actions (config transfer to Test/Prod) before doing them.
Files (claude-code-skills-1c)
-
references
-
graph-mechanics.md 7.1 KB
# .mslx graph mechanics How a Cleverence Mobile SMARTS algorithm actually executes, and the rules you must respect when inserting, removing, or repointing actions. ## File shape A `.mslx` is one physical line of XML. Root is `<DocumentType ...>` or `<Operation ...>`. The graph lives in `<Actions> ... </Actions>`. Each child of `<Actions>` is one **action node**. After `<Actions>` an Operation also has `<Parameters/>` and `<Returns/>`; a DocumentType has more metadata. Do not reorder or drop these container elements. Always edit with exact-string replacement and re-validate with `scripts/mslx_inspect.py`. Because it is a single line, a careless replace can swallow a sibling element - the validator catches the resulting parse error or dangling link. ## How control moves between actions Two mechanisms, used together: 1. **Sequential fall-through.** If an action's `nextDirection` is empty (`""`), control goes to the **next action in document order**. This is why the order of nodes inside `<Actions>` is semantically meaningful. When you insert a node, put it where you want fall-through to reach it. 2. **Named transition.** A direction attribute holds the `name` of a target action. Example: `nextDirection="Главное меню"` jumps to the action `name="Главное меню"`. **Name resolution is case-insensitive.** A button whose direction is `"Просмотр факт"` resolves to the action `name="Просмотр Факт"`. Practical consequence: when the Panel reports "действие для перехода не найдено", it is **not** a letter-case problem - the target action is genuinely absent (most often because a stale file was loaded onto the server, or a button was added without its target action). ### Built-in direction targets These are resolved by the platform, not by an action name. Treat them as always valid: `""` (fall through) · `back` · `return` · `abort` · `exit` · `home` · `cancel` · `process zero` · `break` · `continue` ## Direction attributes by action type | Action | Branch attributes | Meaning | |--------|-------------------|---------| | any action | `nextDirection` | where to go next (empty = fall through) | | any action | `abortDirection` | where to go if the action aborts / hardware Back inside it | | `ConditionAction` | `yesDirection` (TRUE), `noDirection` (FALSE) | empty branch = fall through | | `QuestionYesNoAction` | `yesDirection` (Да), `noDirection` (Нет) | empty branch = fall through | | `FieldEditAction`, `InvokeMethodAction` | `timeoutDirection`, `errorDirection` | timeout / error escape | | `OperationAction` | `onAsyncDirection` | for async calls | | `RemoveDocumentLineAction` | `quantityErrorDirection` | quantity-edit error | `ConditionAction` example: `expression="CurrentItem == null"` with `yesDirection=""` (fall to the next action when TRUE) and `noDirection="подтвердить"` (jump when FALSE). To read it: "if CurrentItem is null, continue to the next node; otherwise go to "подтвердить"". ## Menus: QuestionAction and its three parallel arrays A `QuestionAction` renders a menu. It carries three child elements, each a list of `<String>`: - `Buttons` - internal keys (often empty/None, matched by index) - `ButtonTexts` - what the user sees (may contain markup like `<b>...</b>`, `{Document.Barcode}`) - `ButtonDirections` - the transition target for each button (action name or built-in) They are **index-aligned and must stay the same length**. Removing a button means removing the same index from all three. Adding a button means appending to all three. A `ButtonDirection` that names a non-existent action is the classic `Cleverence.Warehouse.QuestionAction ... действие для перехода не найдено ... поле: ButtonDirections`. `scripts/mslx_inspect.py` checks both the balance and the resolution. ## KeyJumps (scan / key triggers inside an input action) `FieldEditAction` (and similar) carry `<KeyJumps>` with `<KeyToAction>` children: ```xml <KeyToAction action="найти" barcode="{any}" key="None" .../> <KeyToAction action="back" barcode="" key="Escape" viewType="InMenu" /> ``` - `barcode="{any}"` fires the jump on **any scan** - this is how "scan -> next step" is wired. The scanned value lands in the action's `fieldName` variable first. - `key="Escape"` maps the hardware Back button. - `action=` is a transition target (action name or built-in), same resolution rules as above. If a scan "does nothing": confirm there is a `KeyToAction barcode="{any}"` pointing at a real action, and that the target action's logic (often an expression or a sub-operation call) is correct - see `km-and-document-operations.md` for the usual culprit (wrong field/variable). ## OperationAction: calling a sub-operation with parameter mapping ```xml <OperationAction operationName="FindMCInDocument" nextDirection="" abortDirection=""> <InKeys><String>BarcodeData</String></InKeys> <InValues><String>{GO.GetBarcodeData(ScannedBarcode)}</String></InValues> <OutKeys><String>Result</String></OutKeys> <OutValues><String>CurrentItem</String></OutValues> </OperationAction> ``` - `InKeys[i]` = the variable name **inside** the called operation; `InValues[i]` = the **caller's** expression to put there. Brace form `{...}` is an expression. - `OutKeys[i]` = the result variable **inside** the called operation; `OutValues[i]` = the **caller's** variable to receive it. - Empty In/Out lists are common: many operations communicate through **global session variables** (`BarcodeData`, `IdentificationCode`, `CurrentItem`, `SelectedProduct`, ...), so the caller just relies on those being set. **Always copy the contract from an existing caller.** Grep the config for the target `operationName` and reuse the exact In/Out mapping that already works, rather than guessing variable names - guessing is the top cause of silent failures. ## Common action types you will meet - `FieldEditAction` - input/scan screen; `fieldName` is where the entered/scanned value goes. - `AssignAction` - `expression="X = ..."`; supports a query DSL (`select first (*) from Document.CurrentItems where ...`). - `ConditionAction` - boolean branch. - `QuestionAction` - menu (the three arrays). - `QuestionYesNoAction` - yes/no dialog. - `BaloonAction` - toast; `isError="True"` plays the error sound; `text`, `seconds`. - `OperationAction` - call a sub-operation. - `InvokeMethodAction` - call an external connector method (e.g. 1C online; see `online-1c-call.md`). - `RemoveDocumentLineAction` / `DeleteCurrentItemFromDocument` (operation) - remove a line. ## Editing checklist (mirror of the SKILL workflow) 1. Dump the file: `python scripts/mslx_inspect.py <file>` - know the real graph first. 2. Find a working precedent for what you want to add; copy its node structure and variable contract. 3. Make the smallest exact-string edit; keep node order consistent with intended fall-through; keep `id`s stable. 4. `python scripts/mslx_inspect.py <file> --validate` - zero dangling links, balanced menus. 5. Hand back with a reminder that the file must be transferred to the MS Server to take effect. -
km-and-document-operations.md 5 KB
# Marking codes (КМ / КИЗ) and document-line operations How marking codes are stored on Mobile SMARTS document lines and how to find / delete / process them correctly. Getting the field or the comparison value wrong is the most common reason a КМ scan "does nothing" on the terminal. ## Where the marking code lives on a line - **`Item.МаркаИСМП`** - the ИС МП marking code (Честный знак) on a document line. This is the field to match a scanned КМ against. - **`Item.ГрупповаяУпаковка`** - group packaging / aggregate (SSCC-style) code. - **`Item.Марка`** exists too, but it is **not** the ЧЗ marking-code field - do not match КМ against it (a frequent mistake). - Lines are addressed in queries via `Document.CurrentItems` (the collected/fact lines). ## Match against IdentificationCode, not the raw scan You never compare `Item.МаркаИСМП` to the raw scanned string. You compare it to the recognised **`IdentificationCode`** - a canonical form derived from the barcode. `IdentificationCode` is produced by the operation **`GetIdentificationCode`** from the global **`BarcodeData`** (handling GS1 `(01)(21)(8005)`, tobacco, SGTIN-medicine, fur, etc.). `BarcodeData` itself is set explicitly from a scanned string: `BarcodeData = GO.GetBarcodeData(<scanned string>)`. The standard input variable a scan field writes to is `ScannedBarcode`. So the canonical recognise-then-match pipeline is: ``` scanned string -> BarcodeData (GO.GetBarcodeData) -> IdentificationCode (GetIdentificationCode) -> match Item.МаркаИСМП / Item.ГрупповаяУпаковка == IdentificationCode ``` ## Ready-made operations - reuse, do not hand-write | Operation | Folder | Reads (global / InKeys) | Produces | |-----------|--------|-------------------------|----------| | `GetIdentificationCode` | `EN/Barcodes` | `BarcodeData` | `IdentificationCode` | | `FindMCInDocument` | `EN/Search` | `BarcodeData` (calls `GetIdentificationCode` internally) | `Result` = the found line | | `FindMCInStock` | `EN/Search` | - | `ProductLine` | | `IsMarkingCode` | `EN/Barcodes` | `BarcodeData` (must be non-null) | `IsMarkingCode` (bool) | | `DeleteCurrentItemFromDocument` | `EN/Delete` | global `CurrentItem` | removes the line (+ cleans SSCC / binding) | `FindMCInDocument` internally runs: `Result = select first (*) from Document.CurrentItems where Item.МаркаИСМП == IdentificationCode || Item.ГрупповаяУпаковка == IdentificationCode`. That is the authoritative "find a КМ in this document" query - reuse the operation rather than re-deriving the query. ## Worked example: a "delete a КМ" operation Goal: operator scans a КМ, the matching line is found and removed; if not present, say so. This reuses `FindMCInDocument` (find) + `DeleteCurrentItemFromDocument` (remove): 1. `FieldEditAction` `fieldName="ScannedBarcode"`; KeyJumps: `barcode="{any}"` -> action "найти", `Escape` -> `back`. 2. `OperationAction` `operationName="FindMCInDocument"`, `InKeys=[BarcodeData]`, `InValues=[{GO.GetBarcodeData(ScannedBarcode)}]`, `OutKeys=[Result]`, `OutValues=[CurrentItem]`. Fall through to step 3. 3. `ConditionAction` `expression="CurrentItem == null"`: `yesDirection=""` (fall to step 4, the "not found" toast), `noDirection="подтвердить"`. 4. `BaloonAction` `isError="True" text="Марка не найдена в контейнере"` -> back to the scan field. 5. `QuestionYesNoAction` name `подтвердить`, `message="Удалить марку?\r\n{CurrentItem.CurrentItemLabel}"`: `yesDirection=""` (fall to step 6), `noDirection=` scan field. 6. `OperationAction` `operationName="DeleteCurrentItemFromDocument"` (no params - it acts on the global `CurrentItem`). Fall through to step 7. 7. `BaloonAction text="Марка удалена"` -> back to the scan field. Key points that make it work: `fieldName` is `ScannedBarcode` (the standard scan variable); the find is delegated to `FindMCInDocument` with `BarcodeData` built from the scan; its `Result` is mapped to the global `CurrentItem` so `DeleteCurrentItemFromDocument` can act on it; the display label is `{CurrentItem.CurrentItemLabel}`. ## Diagnosing "scan does nothing" on a КМ screen Check in this order: 1. Is there a `KeyToAction barcode="{any}"` on the scan field pointing at a real action? (No jump -> nothing happens on scan.) 2. Is the field/variable right? Matching `Item.Марка` (wrong field) or the raw scan string (instead of `IdentificationCode`) yields no match - and if the expression references a field that does not resolve, the step can silently abort, looking like "nothing happened". 3. Are you reusing `FindMCInDocument` (which sets up `IdentificationCode` itself), or hand-writing a query that skips `GetIdentificationCode`? Prefer the operation. 4. Did the edited file actually reach the MS Server? A correct file on disk that was never transferred behaves like the old version. Run `scripts/mslx_inspect.py <file>` to confirm the graph and links before blaming logic. -
online-1c-call.md 5.8 KB
# Calling 1C online from the terminal How a Mobile SMARTS algorithm runs a 1C function live (during a scan) and gets a result back. This is the mechanism to use when the terminal must ask 1C something in real time - e.g. "does this container exist / what is its status" at the moment the operator scans it. ## The action ```xml <InvokeMethodAction connectorId="OneC_Connector" methodName="ВызовПроизвольнойФункции" sessionVariable="Result" waitingTime="10" errorDirection="<action>" timeoutDirection="<action>" timeoutMessage="..."> <Bindings/> <Parameters> <InvokeParameter name="ИмяФункции" type="String" value="ИмяМетодаИнтеграционнойОбработки"/> <InvokeParameter name="ТипВозвращаемогоЗначения" type="String" value="Cleverence.Warehouse.RowCollection, Cleverence.MobileSMARTS"/> <InvokeParameter name="Параметр1" type="String" value="{ВыражениеMS}"/> <!-- Параметр2..Параметр10 as needed --> </Parameters> </InvokeMethodAction> ``` - `methodName="ВызовПроизвольнойФункции"` is the generic 1C-connector entry point. - `ИмяФункции` names the 1C function to run. - `Параметр1..Параметр10` are passed **positionally** to that function. The `InvokeParameter` names MUST be exactly `Параметр1`, `Параметр2`, ... - the connector maps them by name onto the `ВызовПроизвольнойФункции(ИмяФункции, ТипВозвращаемогоЗначения, Параметр1..Параметр10)` signature. A semantic name (`Код`, `Штрихкод`) does NOT map - the corresponding `ПараметрN` stays `Неопределено`, the target 1C function is then called with missing arguments, throws, and the action takes its `errorDirection` (often shown as a generic "no connection"). This is a frequent and confusing failure: the call "doesn't reach 1C" only because the argument never bound. - `sessionVariable` receives the result on the terminal side. - Wire `errorDirection` / `timeoutDirection` to a "no connection" branch so a server hiccup does not dead-end the operator. ## How ИмяФункции is resolved (the critical constraint) `КлеверенсТСД_ОсновнаяОбработка.ВызовПроизвольнойФункции` (vendor `CleverenceMainExtension`) builds and evaluates: ``` СтрокаВызова = "Параметры.ИнтеграционнаяОбработка." + ИмяФункции + "(" + СтрокаПараметров + ")"; Результат = ГлЯдро_ВычислитьВБезопасномРежиме(СтрокаВызова, СтруктураПараметров); Если ТипЗнч(Результат) = Тип("ТаблицаЗначений") Тогда Результат = REST_API_ТаблицаЗначенийВМассивСтруктур(Результат); КонецЕсли; ``` So: - **`ИмяФункции` must be a method of the integration data processor** (`ИнтеграционнаяОбработка_*`, vendor `CleverenceIntegrationExtension`). Not a common module, not the "Список произвольных кодов". - The function must **return a `ТаблицаЗначений`** (table of values). The connector converts it to a `RowCollection`; on the terminal you read it as `Result[0].ИмяКолонки`, `Result.Count`. Do **not** return JSON. - `ГлЯдро_ВычислитьВБезопасномРежиме` may, depending on the build, be a plain `Вычислить()` (safe mode disabled) - then `УстановитьПривилегированныйРежим` inside your function works normally and you do not need extra rights setup. Verify in the specific project before assuming. ## Pattern: call your own logic without touching vendor code Your business logic should live in your own common module. To expose it to the terminal, add a **thin wrapper method** to the integration data processor that just forwards the call. Mark the insertion (project convention for vendor extensions). Example: ```bsl // ++ <Author>, <date> Функция Клв_ПроверитьИлиОбеспечитьКонтейнер(Код, UserId = "") Экспорт Возврат Клв_ИнтеграцияCleverence.ПроверитьИлиОбеспечитьКонтейнер(Код, UserId); КонецФункции // -- <Author>, <date> ``` Your common-module function returns a `ТаблицаЗначений` (one or N rows). On the terminal, `ИмяФункции="Клв_ПроверитьИлиОбеспечитьКонтейнер"` and `Параметр1` etc. map to its parameters by position. ## What is NOT an online call (common confusions) - **"Список произвольных кодов"** (1С: Клеверенс -> Расширенные настройки -> Выбор произвольного кода) is **field mapping at document exchange** (Загрузка/Выгрузка/Настройка печати), binding document/header/line fields. It is not a live function call. - The **"Произвольный код"** action in an MS algorithm evaluates an expression `Приемник = Источник`; it does not call 1C. - **"Расширение API через коннектор"** is a C# plugin (`IApiExtenderPlugin`) - heavyweight, not needed for this pattern. ## Reading the result on the terminal The returned `ТаблицаЗначений` becomes a row collection: - `Result.Count` - number of rows. - `Result[0].СтатусПоле` - a column value of the first row. Design your 1C function to return a small, fixed set of columns (status, allowed-flag, message, ids) so the algorithm can branch on `Result[0].<column>`.
-
-
scripts
-
mslx_inspect.py 7 KB
#!/usr/bin/env python3 """ mslx_inspect.py - dump and validate a Cleverence Mobile SMARTS .mslx file. .mslx are single-line XML graphs of Actions (DocumentType) or a single Operation. Reading them by eye is hard; this tool gives a structured view (actions are indented by their `indent` attribute, so the Panel nesting is visible at a glance) and catches the mistakes that actually break the algorithm on the TSD: - dangling transitions (a direction points at an action that does not exist) - unbalanced QuestionAction button arrays (Buttons / ButtonTexts / ButtonDirections) - malformed XML - flat structure: logically subordinate actions left at indent=0 (renders wrong in the Panel; the indented dump makes this obvious) Usage: python mslx_inspect.py <file.mslx> # dump + validate (default) python mslx_inspect.py <file.mslx> --dump # only structured dump python mslx_inspect.py <file.mslx> --validate # only validation, exit 1 on problems python mslx_inspect.py <file.mslx> --names # just list action names Set PYTHONIOENCODING=utf-8 on Windows to avoid console encoding errors with Cyrillic. """ import sys import xml.etree.ElementTree as ET # Directions that are resolved by the platform, not by an action name. BUILTINS = { "", "back", "return", "abort", "exit", "next", "home", "cancel", "break", "process zero", "continue", "undo", } # Attributes on actions that hold a transition target (an action name or builtin). DIRECTION_ATTRS = ( "nextDirection", "yesDirection", "noDirection", "abortDirection", "timeoutDirection", "errorDirection", "onAsyncDirection", "quantityErrorDirection", ) ACTION_SUFFIX = "Action" def local(tag): return tag.split("}")[-1] # Elements whose tag ends with "Action" but which are NOT graph nodes. NOT_NODES = {"KeyToAction"} def is_action(el): t = local(el.tag) return t.endswith(ACTION_SUFFIX) and t not in NOT_NODES def collect_action_names(root): """Names that a transition may legally point to (case-insensitive set).""" names = set() for el in root.iter(): if is_action(el): n = el.get("name") if n: names.add(n) return names def gather_references(root): """Yield (source_action, attr_or_kind, target) for every transition target.""" for el in root.iter(): t = local(el.tag) src = el.get("name") or "(unnamed %s)" % t if is_action(el): for attr in DIRECTION_ATTRS: v = el.get(attr) if v is not None: yield (src, attr, v) if t == "KeyToAction": v = el.get("action") if v: yield ("(KeyJumps)", "KeyToAction.action", v) # ButtonDirections live as <String> children of a ButtonDirections element if t == "ButtonDirections": for child in el: yield (src, "ButtonDirections", child.text or "") def resolve_ok(target, names_lower): return target.lower() in names_lower or target.lower() in {b.lower() for b in BUILTINS} def dump(root): print("ROOT <%s> name=%r" % (local(root.tag), root.get("name"))) interesting = { "name", "operationName", "nextDirection", "yesDirection", "noDirection", "abortDirection", "expression", "fieldName", "text", "message", "sessionVariable", } for el in root.iter(): if not is_action(el): continue t = local(el.tag) attrs = {local(k): v for k, v in el.attrib.items()} shown = {k: attrs[k] for k in attrs if k in interesting} # Trim long expressions/messages for readability. for k in ("expression", "message", "text"): if k in shown and len(shown[k]) > 90: shown[k] = shown[k][:90] + "..." # In/Out mapping for OperationAction. io = {} for grp in ("InKeys", "InValues", "OutKeys", "OutValues"): vals = [c.text for e in el for c in e if local(e.tag) == grp] if vals: io[grp] = vals # indent = visual nesting depth in the Panel algorithm tree. Subordinate actions # must sit deeper than their parent (indent > parent); a flat indent=0 on logically # nested actions renders the structure wrong in the Panel. We indent the dump by it # so a flat/broken structure is visible at a glance. try: depth = int(attrs.get("indent", "0") or "0") except ValueError: depth = 0 line = " %s[%s]" % (" " * depth, t) for k in ("name", "operationName", "fieldName"): if shown.get(k): line += " %s=%r" % (k, shown[k]) for k in ("nextDirection", "yesDirection", "noDirection", "abortDirection"): if shown.get(k): line += " %s=%r" % (k, shown[k]) for k in ("expression", "message", "text", "sessionVariable"): if shown.get(k): line += " %s=%r" % (k, shown[k]) if depth: line += " indent=%d" % depth if io: line += " " + " ".join("%s=%s" % (k, v) for k, v in io.items()) print(line) def validate(root): problems = [] names = collect_action_names(root) names_lower = {n.lower() for n in names} # 1. dangling transitions for src, kind, target in gather_references(root): if not resolve_ok(target, names_lower): problems.append("DANGLING: %s.%s -> %r (no such action)" % (src, kind, target)) # 2. QuestionAction button-array balance for el in root.iter(): if local(el.tag) != "QuestionAction": continue arrays = {} for child in el: tag = local(child.tag) if tag in ("Buttons", "ButtonTexts", "ButtonDirections"): arrays[tag] = len(list(child)) if arrays and len(set(arrays.values())) > 1: problems.append( "BUTTON IMBALANCE in QuestionAction %r: %s" % (el.get("name"), arrays) ) return problems def main(): args = [a for a in sys.argv[1:] if not a.startswith("--")] flags = {a for a in sys.argv[1:] if a.startswith("--")} if not args: print(__doc__) sys.exit(2) path = args[0] try: tree = ET.parse(path) except ET.ParseError as e: print("XML PARSE ERROR: %s" % e) sys.exit(1) root = tree.getroot() if "--names" in flags: for n in sorted(n for n in collect_action_names(root) if n): print(n) return do_dump = "--validate" not in flags or "--dump" in flags do_validate = "--dump" not in flags or "--validate" in flags if do_dump: dump(root) if do_validate: problems = validate(root) print("\n=== VALIDATION ===") if not problems: print("OK: XML valid, all transitions resolve, button arrays balanced.") else: for p in problems: print(" " + p) sys.exit(1) if __name__ == "__main__": main()
-
-
SKILL.md 13.4 KB
--- name: cleverence-mslx description: >- Read, understand and safely edit Cleverence Mobile SMARTS (Склад 15 / Магазин 15) configuration stored as .mslx files - the XML algorithm graphs that drive handheld terminal (ТСД) workflows in 1C integration projects. Use this skill whenever the task touches Cleverence / Mobile SMARTS / "Клеверенс" / ТСД algorithms, .mslx files, document types or operations (обработчики) on the terminal, menu/scan/КМ logic on a scanner, calling 1C online from the TSD, or "почему скан не срабатывает / действие не найдено / кнопка не работает" on a handheld - even if the user does not say ".mslx" explicitly. Covers the action graph, transition chaining, OperationAction parameter mapping, the online 1C call mechanism, marking-code (КМ) line operations, and the CL_-copy rule for not breaking vendor operations on update. --- # Cleverence Mobile SMARTS (.mslx) configuration Cleverence "Склад 15" / "Магазин 15" runs handheld-terminal (ТСД) logic as **algorithms**: a directed graph of **actions**. The Mobile SMARTS Panel stores each document type and each operation as a single-line XML file with the `.mslx` extension. Editing these by eye is error-prone; the failures are almost always *broken transitions* or *wrong variable / field names*, not XML syntax. This skill encodes how the graph works and how to change it without breaking it. ## Mental model - **DocumentType** `.mslx` (под `Documents/DocumentTypes/...`) - a whole terminal workflow (e.g. "Сборка контейнера"). Its root is `<DocumentType>`. - **Operation** `.mslx` (под `Documents/Operations/...`) - a reusable sub-routine called by documents or other operations. Root is `<Operation>`. - Both contain `<Actions>` - the ordered list of graph nodes. - The Panel and the MS Server work off a **server database**, not necessarily the git working copy. Editing a file on disk changes nothing until the config is transferred to the server (Сервис -> Сравнение конфигураций, or copying into the live DB + restart). **If the Panel shows old behaviour after your edit, suspect a stale/never-transferred file before suspecting your change.** ## The single most important rule: CL_ copies of vendor operations Vendor operations (typical Cleverence обработчики, usually under `Operations/EN/...`, `Operations/Основные/...` etc.) get **overwritten on Cleverence updates**. Editing them directly is like editing a vendor 1C extension - your change disappears on the next update. So: | What | Rule | |------|------| | Our own document type (e.g. "Сборка контейнера") | Edit **in place**. Do not clone. | | Our own operation (in the project root `Operations/`) | Edit **in place**. | | A **vendor/typical** operation we need to change | Make a copy `CL_<Name>` (placement below), edit the copy, and in the caller switch `operationName` to `CL_<Name>`. | | A new operation | Prefix `CL_` (latin). | Copy only the operations you actually change. If the change is inside a nested vendor operation, copy that one and repoint its `CL_` parent at it - do not clone the whole chain. **Where to put a CL_ copy.** Put it in your own branch of the operations tree - `Documents/Operations/` (the root) - **not** inside the vendor subfolder it was copied from (`Documents/Operations/EN/...`). The Panel resolves a call by the operation's `name`, not by its file path, so a `CL_<Name>` in `Operations/` fully serves callers that use `operationName="CL_<Name>"`. Keeping the copy in the vendor subfolder (a) puts it among files that get overwritten on update, and (b) risks **two files with the same `name`** if the Panel later re-exports the operation into the root - a duplicate that conflicts on import. So: after creating/moving the copy to `Operations/`, delete any stale same-`name` file left in the vendor subfolder. ## How the action graph flows Every action decides where control goes next. Read `references/graph-mechanics.md` for the full rules; the essentials: - **Sequential fall-through**: an empty `nextDirection=""` means "go to the next action in document order". This is why the **physical order of `<Actions>` matters** - inserting an action in the wrong place silently changes the flow. - **Named transitions** point to an action's `name`. Resolution is **case-insensitive** (a button direction `"Просмотр факт"` resolves to action `name="Просмотр Факт"`). So if a transition "is not found", the cause is almost never letter-case - it is a genuinely missing action (often a stale file on the server). - **ConditionAction** branches with `yesDirection` (TRUE) and `noDirection` (FALSE); empty means fall through to the next action. - **QuestionYesNoAction** branches with `yesDirection` / `noDirection`. - **QuestionAction** (a menu) has three parallel arrays - `Buttons`, `ButtonTexts`, `ButtonDirections` - matched by index. They **must stay the same length**; a button whose `ButtonDirection` names a non-existent action throws "действие для перехода не найдено". - Built-in targets that are not action names: `back`, `return`, `abort`, `exit`, `home`, `process zero`, and `""`. - **`indent` = visual nesting in the Panel tree, not control flow.** Each action carries an integer `indent`. Logically subordinate actions (a `process zero` branch under the scan window, the body executed inside a condition) must have `indent` **greater** than their parent - they appear *shifted right* in the Panel algorithm tree. Flow is driven by `nextDirection`/named transitions, **not** by `indent`; but leaving a flat `indent=0` on nested actions renders the structure wrong/flat in the Panel and is a real defect to fix. When you copy or hand-edit a `.mslx`, **preserve/set `indent`** on the nested actions (it is easy to lose it on a copy). `mslx_inspect.py` indents its dump by this attribute, so a flat structure shows up at a glance. ## Calling another operation: parameter mapping `OperationAction` invokes a sub-operation by `operationName` and maps variables explicitly: - `InKeys` / `InValues` - the sub-operation's variable name / the caller's expression. Expressions are wrapped in braces: `InValues = {GO.GetBarcodeData(ScannedBarcode)}`. - `OutKeys` / `OutValues` - the sub-operation's result variable / the caller's variable. Many shared variables (`BarcodeData`, `IdentificationCode`, `CurrentItem`, ...) are global to the session, so some calls pass nothing and rely on globals. When in doubt, look at how the operation is already called elsewhere (grep for its `operationName`) and copy that contract verbatim - guessing variable names is the #1 cause of "scan does nothing". ## Calling 1C online from the terminal To run a 1C function live during a scan, use an `InvokeMethodAction` against the 1C connector. The function name you pass must be a **method of the integration data processor** (вендорская `ИнтеграционнаяОбработка_*`), and it must return a `ТаблицаЗначений` (not JSON). The full mechanism, the thin-wrapper pattern (with vendor markers), and what is *not* an online call (the "Список произвольных кодов" is field mapping, not a call) are in `references/online-1c-call.md`. ## Marking codes (КМ / КИЗ) and document lines The marking code on a document line lives in `Item.МаркаИСМП` (and `Item.ГрупповаяУпаковка` for aggregates) - **not** `Item.Марка`. You match against the recognised `IdentificationCode`, which is produced by `GetIdentificationCode` from `BarcodeData`, not against the raw scanned string. Reuse the ready operations (`FindMCInDocument`, `DeleteCurrentItemFromDocument`, `IsMarkingCode`) instead of hand-writing queries. See `references/km-and-document-operations.md`. ## Editing methodology (do this, in this order) 1. **Inspect first.** Run the bundled tool to see the real graph and current links: ```bash PYTHONIOENCODING=utf-8 python scripts/mslx_inspect.py "<file>.mslx" ``` It dumps every action (type, name, operationName, directions, In/Out mapping) and then validates: XML well-formedness, that every transition resolves, and that menu button arrays are balanced. 2. **Find the proven pattern.** Before writing new graph logic, grep the existing operations for one that already does it and copy its action structure and variable contract. The codebase almost always has a working precedent (scanning, finding a line, deleting a line, confirming, calling 1C). Reusing it beats inventing. 3. **Make point edits.** `.mslx` is one long line; use exact-string Edit, keep ids stable where you can, and keep the action order consistent with the fall-through you intend. 4. **Re-inspect.** Run `mslx_inspect.py ... --validate` again. Zero dangling transitions and balanced button arrays is the bar before you hand it back. 5. **Remember the transfer step.** A validated file on disk is not yet live - it must reach the MS Server. Tell the user to transfer it and to confirm by checking that a changed `operationName` (e.g. `CL_...`) shows up in the Panel. ## When a scan "does nothing" on the TSD Before suspecting the graph, rule out the input itself - the graph is usually innocent: - **`Ctrl+V` into the debugger window is NOT a scan event.** A scan window catches input via a `KeyToAction barcode="{any}"` KeyJump, which fires on a *scan event*. Pasting text into the field with `Ctrl+V` is keyboard input - it does not trigger the `barcode` KeyJump, so "nothing happens". Use the debugger's *emulate-barcode* function (a dedicated barcode input), or a real scanner. - **DataMatrix loses its GS separators on copy-paste.** A ЧЗ marking code (DataMatrix) contains the GS control char (ASCII 29). Clipboard/Notepad strips it, so even if the input lands, `GO.GetBarcodeData()` won't recognise the КМ structure. Only a real scanner (or the emulator) preserves GS. - **The config may not be on the server.** The debugger runs the server copy; an edited file in the repo is not live until transferred (Сервис -> Сравнение конфигураций). - Only after these: check the graph. Confirm the scan window has `KeyToAction barcode="{any}"` pointing at the scan-handling action, and (with a breakpoint in the debugger) whether that action is even reached. If it is reached but bails out, inspect `BarcodeData` - the code was read but not recognised. ## Linking a TSD document back to its 1C document (dedup on completion) When a TSD document must, on completion, update an **existing** 1C document instead of creating a new one, the back-link is a Panel **setting**, not graph logic: in the БП open *"Настройка загрузки полей шапки документа"* and the rule whose target (приемник) is the `Ссылка` field, match mode **Поиск по GUID**. The **source attribute** of that rule is a classic trap: - **`Идентификатор`** - the MS document's own Id. This is the **correct** source for dedup. It is stable for one TSD document and is exactly what the vendor completion code resolves the 1C reference from (`ШапкаДокумента.Ид`). Use this. - **`Идентификатор исходных документов`** (`ИдИсходныхДокументов`) - **wrong** for the back-link of a TSD-created document. If an online step creates/re-creates the 1C document and writes its GUID here, a **fresh GUID can arrive on every completion** -> the GUID search never matches -> a new 1C document is created each time = **duplicates** (often without a warehouse). This attribute is for the "document assembled from several 1C documents" case, not the back-link. Symptom this fixes: every *завершение* on the TSD spawns a second 1C document. Root cause is almost always the `Ссылка` rule pointing at *Идентификатор исходных документов* instead of *Идентификатор*. The vendor completion path only ever resolves the 1C ref from `ШапкаДокумента.Ид` - writing a GUID into `ИдИсходныхДокументов` does nothing for dedup. Caveat for TSD-created docs: their MS Id is `new_<guid>`. A hand-rolled `Документы[Тип].ПолучитьСсылку(Новый УникальныйИдентификатор(Ид))` throws on the `new_` prefix (invalid GUID) and, inside a bare `Попытка`, silently falls through to creating a duplicate; the *Поиск по GUID* rule handles the prefix correctly. ## Reference files - `references/graph-mechanics.md` - action types, every transition attribute, fall-through, button arrays, KeyJumps, common caveats. Read when editing graph flow. - `references/online-1c-call.md` - the InvokeMethodAction -> integration-processor -> ТаблицаЗначений mechanism, the wrapper pattern, what is not an online call. - `references/km-and-document-operations.md` - КМ line fields, IdentificationCode, the ready find/delete/recognise operations and how to chain them. ## Constraints in 1C-integration projects Vendor extensions and 1C extensions are not edited directly - mark project insertions and prefer copies/wrappers. In this family of projects code and docs avoid the letter "е" and long dashes. Confirm destructive or shared-state actions (config transfer to Test/Prod) before doing them.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.