Soneta Business XML Generator
Skill do generowania plików business.xml dla platform firmy Soneta:
- Soneta - system ERP dla firm (enova365, Soneta Enterprise)
- Soneta Enterprise - platforma enterprise
Pliki te definiują obiekty biznesowe (encje ORM), które platforma automatycznie mapuje na tabele w bazie danych i generuje klasy C#.
⚠ Krytyczne zasady — łamią kompilację
descriptionicaptionzawsze w JEDNEJ linii XML. Generator wstawia wartość dosłownie do stałej C#[Description("…")]— zawinięcie na kilka linii dajeerror CS1010: Newline in constanti kaskadę błędów. Skróć treść zamiast łamać linię (references/table-reference.md).- Ostatni segment
namespace≠nameżadnej tabeli — inaczejerror CS0118: 'X' is a namespace but is used like a type. Wzorzec platformy: namespace w liczbie mnogiej, encje w pojedynczej (Soneta.Oceny+OcenaRealizacja,Soneta.Towary+Towar). - business.xml + klasy Row/Table = jeden nierozłączny krok. Sam XML się nie kompiluje — wygenerowany
*.business.cswymaga klas konkretnych, bez nicherror CS0246(references/generated-classes.md). <module>bezdescription=#warning 'Description for module X is not defined'w generowanym kodzie — zawsze podawaj opis modułu.
⚠ Krytyczne zasady — łamią RUNTIME (build przechodzi bez ostrzeżeń)
Te trzy błędy nie dają żadnego sygnału przy kompilacji — build jest zielony, a program pada dopiero przy pierwszym użyciu danej ścieżki kodu. Sprawdzaj je świadomie przy każdej nowej tabeli, bo nic Cię przed nimi nie ostrzeże automatycznie.
selector="true"na zwykłym polu enum (nie dyskryminatorze typu) — objaw:Nierozpoznany typ wiersza. Selektor N w tabeli X nieznaleziony.przy każdym odczycie listy.selector="true"stosuj wyłącznie, gdy ta sama tabela SQL przechowuje różne typy biznesowe, każdy jako osobna klasa C# (abstractbaza + podtypy rejestrowane[assembly: BusinessRow(...)]) — patrz references/generated-classes.md. Zwykłe pole statusu/kategorii/typu służące tylko do filtrowania i wyświetlania (RecipientType,TypWpisu...) nigdy nie dostajeselector="true"— to zwykły enum bez dodatkowego atrybutu. Gdy selector jest zasadny: enum dyskryminatora numeruj od 1 — wartość0działa jak „puste" (mechanizm z pułapki 2 niżej), a wiersz z selectorem0(np. z importu XML bez elementu selectora) zatruwa całą tabelę — każdy odczyt kończy sięUnrecognizedRowException, naprawa tylko przezDELETEw SQL (references/table-reference.md).required="true"na polu, którego domyślna wartość typu jest poprawnym stanem. Generator utożsamiadefault(T)z „polem pustym" dla każdego typu wartościowego, nie tylkoint:0(int/double/decimal/currency),Guid.Empty, wartość enum o numerze0,Date.MinValue/DateTime.MinValue,Time.Zero,Quantity.Zero— dla wszystkich tych typówrequired="true"generujeif (value==default) throw RequiredException(...). Objaw:Wymagane jest wprowadzenie wartości pola 'X'przy inicjalizacji obiektu wartością domyślną (np. licznikAttemptCount = 0wOnAdded()). Zasada:required="true"tylko gdy wartość domyślna typu jest semantycznie niedopuszczalna w domenie pola (np.MaxAttempts— 0 maksymalnych prób nie ma sensu;Lp— pozycja 0 nie istnieje). Dla liczników/postępów zaczynających się od zera (AttemptCount,Retries,Kolejnoscliczona od 0) używajrequired="false"— kolumna SQL zostaje taka sama (nienullowalna dla typu wartościowego), zmienia się tylko walidacja C#. Jedyny typ z sensowną semantyką required =string(IsNullOrEmpty) i referencja do wiersza (== null) — patrz references/table-reference.md.tablenamedłuższy niż 16 znaków. Nie łamie builda — łamie każdą transakcję zapisu na tej tabeli w runtime:String or binary data would be truncated, bo kolumnaChangeInfos.SourceTable(mechanizm audytu zmian, uruchamiany dla praktycznie każdej tabeliguided="Root"/guided="Exported") malength="16". To ten sam limit co kolumny referencyjne do nazw tabel w Cechach,LockInfos.RecordTable,Attachments— więc dotyczy każdej tabeli, nie tylko tych z relacjami interface'owymi. Żaden etap kompilacji tego nie sprawdza dla dodatków budowanych przez Soneta SDK — limit trzeba pilnować ręcznie, zawsze ≤16 znaków, z zachowaniem liczby mnogiej (DokumentyHandlowe→DokHandlowe) — patrz references/table-reference.md.- Brak wpisu nowej tabeli w
*.rightstree.xml. Build zielony, program działa, ale prawa do tabeli lądują w gałęziDodatki/<Moduł>drzewa uprawnień zamiast tam, gdzie operator ich szuka. Wpis dostaje tylko korzeń drzewa praw — tabela bez relacji nadrzędnej praw (relguided,relright="Primary",relright="true"do zwykłego obiektu). Uwaga na wyjątek: relacjarelright="true"do obiektu będącego źródłem praw (IRightsSource) nie czyni tabeli dzieckiem — taka tabela nadal potrzebuje wpisu. Ten plik jest drugą połową każdej zmiany struktury danych: dodanie tabeli, jej usunięcie, zmiana nazwy klasy i zmiana relacji praw wymagają jego aktualizacji — references/rights-tree.md.
Struktura pliku business.xml
<?xml version="1.0" encoding="utf-8" ?>
<module xmlns="http://www.enova.pl/schema/business_struct.xsd"
name="NazwaModulu"
namespace="Firma.NazwyModulow"
versionName="soneta"
description="Krótki opis przeznaczenia modułu.">
<using>Soneta.Core</using>
<!-- Definicje enum, subrow, interface, table -->
</module>
Atrybuty modułu
| Atrybut | Wymagany | Opis |
|---|---|---|
name |
✓ | Nazwa modułu (np. "Handel", "Kadry") |
namespace |
✓ | Namespace C# (np. "Soneta.Handel"); ostatni segment ≠ name żadnej tabeli (patrz wyżej) |
versionName |
✓ | Zazwyczaj "soneta" |
description |
zalecany | Opis modułu; brak = #warning 'Description for module X is not defined' w generowanym kodzie |
versionNumber |
Numer wersji (int) | |
internal |
true dla modułów wewnętrznych |
Namespace ≠ nazwa projektu/assembly.
namespacew business.xml może różnić się od nazwy projektu — moduł identyfikuje klasa modułu ([assembly: ModuleType]), nie nazwa DLL.Szablon
dotnet new soneta-item-businessxmlustawianamespace="<Projekt>.<NazwaModulu>"iversionName= nazwa modułu — po wygenerowaniu popraw oba w nagłówku<module>do docelowego namespace modułu (iversionName="soneta", jeśli tak przyjęto w projekcie).
Atrybuty table
| Atrybut | Wymagany | Opis |
|---|---|---|
name |
✓ | Nazwa klasy C# (PascalCase, l.poj.) |
tablename |
✓ | Nazwa tabeli w bazie danych (PascalCase, l.mn.). Maks. 16 znaków |
description |
Krótki (2–3 zdania) opis zastosowania tabeli — patrz niżej | |
guided |
Root = główna tabela programu (dokument, kartoteka) |
|
config |
true = tabela konfiguracyjna (tworzona podczas wdrożenia) |
|
caption |
Etykieta pojedynczego rekordu | |
tablecaption |
Etykieta listy rekordów |
tablename≤ 16 znaków — w relacjach interfejsowych identyfikatorem tabeli jest 16-znakowe pole bazodanowe, dłuższe nazwy trzeba skracać z zachowaniem liczby mnogiej (np.DokumentyHandlowe→DokHandlowe).name(klasa C#) nie ma tego limitu.
description— 2–3 zdania o przeznaczeniu tabeli, do szybkiej orientacji w strukturze programu (również dla modeli językowych). Opisuje, po co tabela powstała, nie pojedyncze pola.
Rodzaje tabel
Tabele główne (guided="Root"):
- Główne obiekty biznesowe: dokumenty, kartoteki (towar, pracownik, kontrahent)
- Dostępne z menu głównego programu
- Stanowią bazę definicji obiektów biznesowych
Tabele eksportowalne (guided="Exported"):
- Jak
Root, ale dodatkowo mogą być eksportowane do innych systemów - Najważniejsze tabele transakcyjne: DokumentHandlowy, Platnosc, DokEwidencja
- Używaj dla dokumentów wymagających integracji z systemami zewnętrznymi
Tabele szczegółów (bez guided):
- Opisują szczegóły obiektów głównych: pozycje dokumentu, kody towaru, adresy
- Muszą mieć dokładnie jedną relację z
relguided="inner"wskazującą na obiekt główny
Tabele konfiguracyjne (config="true"):
- Określają sposób działania programu
- Konfiguracja algorytmów, formularzy, wydruków, słowników
- Dane tworzone podczas wdrożenia systemu
- Przykłady: definicje dokumentów, jednostki miary, stawki VAT
Tabele operacyjne (bez config):
- Dane zbierane podczas codziennej pracy
- Dokumenty, kartoteki, transakcje
Elementy wewnętrzne
1. Import i using
<using>Soneta.Core</using>
<using>Soneta.CRM</using>
- using - namespace C# dla obiektów używanych w tym business.xml (potrzebne gdy referujesz typy z innych modułów)
- import - ścieżka do katalogu z innymi plikami business.xml (np.
<import>../..</import>)
W dodatkach opartych o Soneta SDK
<import>jest zbędny — referencje międzymodułowe rozwiązują się przez<using>+ biblioteki z SDK. Przykłady z<import>../..</import>pochodzą ze świata budowania platformy ze źródeł.
2. Enum - definicja typu wyliczeniowego
<enum name="TypTowaru"/>
<enum name="StatusDokumentu"/>
Enum musi być zdefiniowany w osobnym pliku C# - tutaj tylko deklaracja.
Jawne wartości liczbowe. Enum używany w kolumnie bazy danych powinien mieć w C# explicite przypisane numery (
Reklamacja = 1, Naprawa = 2). Wartość trafia do bazy, więc nowe pozycje dopisuje się na końcu — bez zmiany istniejących, by uniknąć renumeracji i rozjazdu z zapisanymi danymi. Dotyczy to zwłaszcza pól selector'a.
3. Interface - relacje polimorficzne
Interface może być implementowany przez wiele tabel. Deklaracja samego interfejsu (jego metody/właściwości) jest w kodzie C#. W business.xml deklarujemy tylko nazwę interfejsu, aby móc tworzyć relacje interface'owe.
<interface name="IKontrahent"/>
<interface name="IPodmiotKasowy"/>
IRightsSource— obiekt jako źródło praw. Dodanie do tabeli<interface>IRightsSource</interface>czyni obiekt (zwykle konfiguracyjny, np. magazyn) źródłem uprawnień: operatorowi przypisuje się prawa do tego obiektu, co steruje dostępem do danych operacyjnych referujących do niego (np. dokumentów z danego magazynu). System sam dba o widoczność i filtrowanie list. Mechanizm po stronie kodu (AccessRight,Login.GetObjectRight) — patrz skill/soneta-programming(rights-source.md).
Relacja interface'owa - kolumna typu interface może wskazywać na obiekt z dowolnej tabeli implementującej ten interface. W bazie danych zapisywana jest para: (nazwa_tabeli, ID).
<!-- Relacja interface'owa - może wskazywać na Osobę, Firmę lub inny obiekt implementujący IKontrahent -->
<col name="Kontrahent" type="IKontrahent" required="true"/>
4. Subrow - typ złożony (value object)
Subrow to zagnieżdżony obiekt bez własnej tabeli - przechowywany jako kolumny w tabeli rodzica.
<subrow name="Adres">
<col name="Ulica" type="string" length="100"/>
<col name="Miasto" type="string" length="50"/>
<col name="KodPocztowy" type="string" length="10"/>
</subrow>
<subrow name="NumerDokumentu">
<key name="WgSymbolu" keyunique="true">
<keycol name="Symbol"/>
<keycol name="Numer"/>
</key>
<col name="Symbol" type="string" length="50"/>
<col name="Numer" type="int"/>
<col name="Pelny" type="string" length="40"/>
</subrow>
5. Table - główna definicja obiektu biznesowego
Pełna dokumentacja: references/table-reference.md
<table name="Towar" tablename="Towary" guided="Root" caption="Towar" tablecaption="Towary">
<interface>IElementSlownika</interface>
<key name="WgKodu" keyunique="true" keyprimary="true">
<keycol name="Kod"/>
</key>
<col name="Kod" type="string" length="100" required="true" important="true"/>
<col name="Nazwa" type="string" length="200" required="true"/>
<col name="Cena" type="currency"/>
<col name="Aktywny" type="boolean"/>
</table>
Klasy biznesowe (generowane obok)
Plik business.xml to połowa definicji — druga to klasy C# tworzone równolegle.
Z każdej <table name="X" tablename="Xs"> generator tworzy bazy XRow/XTable, a programista
dopisuje klasy konkretne: rekord class X : <Moduł>Module.XRow i tabelę
class Xs : <Moduł>Module.XTable. Klasy te nie muszą być partial ani sealed
(partial stosuje się tylko, gdy faktycznie dzielisz klasę na kilka plików).
Sam business.xml nie skompiluje się bez tych klas — wygenerowany *.business.cs odwołuje
się do klas konkretnych (fabryki CreateRow, TableInfo.Create<…>); bez nich error CS0246.
Kontrakt nazw: klasa obiektu biznesowego = name tabeli (l. poj.), klasa tabeli = tablename
(l. mn., ≤16 znaków). Po pierwszym buildzie warto przeczytać wygenerowany *.business.cs
jako źródło prawdy o kontrakcie (konstruktory, settery, akcesory Wg…, session.Get<Moduł>()).
- Pola
readonly(w tym selector) wymagają konstruktora inicjującego oraz konstruktora(RowCreator creator)dla ORM; bez pól readonly wystarcza konstruktor domyślny. - Selector (
selector="true", poleint/enum) pozwala przechowywać wiele typów obiektów w jednej tabeli: klasa obiektu biznesowego jestabstract, a warianty to podtypy rejestrowane atrybutem[BusinessRow]. - Atrybut
[NewRow]wyznacza pozycje menu „Nowy..."; jego brak blokuje dodawanie obiektu z UI.
Pełny wzorzec (XML ↔ klasy, selector'y, konstruktory, [BusinessRow], [NewRow]):
references/generated-classes.md. Implementację klas po stronie
kodu opisuje skill /soneta-programming (row-types.md).
Standardy kodu C# (.NET 10)
Klasy pisane ręcznie stosuj z najnowszymi konstrukcjami języka C# (.NET 10):
- File-scoped namespace —
namespace Firma.NazwaModulu;bez klamer obejmujących cały plik (nienamespace X { ... }). - Primary constructors — gdy klasa ma jeden konstruktor, deklaruj parametry przy nazwie klasy (
class Kod(RowCreator creator) : XRow(creator)). Przy kilku konstruktorach (np. inicjujący polareadonly+(RowCreator creator)) primary constructor nie ma zastosowania — użyj zwykłych konstruktorów. - Pozostałe nowoczesne konstrukcje:
is null/ wyrażeniaswitch, kolekcje[...],nameof, wyrażenia lambda/=>w metodach jednolinijkowych.
Jedna klasa — jeden plik
Klasę obiektu biznesowego i klasę tabeli umieszczaj w osobnych plikach (Zgloszenie.cs i Zgloszenia.cs), nie w jednym pliku. Ta sama zasada dotyczy enumów, weryfikatorów i innych typów — jeden typ najwyższego poziomu na plik. Wyjątkiem są typy zagnieżdżone (np. podtypy selector'a wewnątrz klasy abstract), które z definicji należą do jednego pliku klasy nadrzędnej.
Typy danych kolumn
Typy proste
| Typ | Opis | Dodatkowe atrybuty |
|---|---|---|
string |
Tekst (wczytywany z rekordem) | length - wymagane, lub "max" dla nieograniczonego |
text |
Długi tekst (wczytywany na żądanie, osobne SQL) | nie może być kluczem |
binary |
Dane binarne | nie może być kluczem |
int |
Liczba całkowita | - |
double |
Liczba zmiennoprzecinkowa | - |
decimal |
Liczba z dokładnością do 2 miejsc (kwota bez waluty) | - |
currency |
Kwota z walutą (para: kwota + waluta) | - |
doublecy |
Liczba z walutą (para: liczba + waluta) | - |
percent |
Procent | - |
boolean |
Tak/Nie | - |
date |
Data | - |
time |
Czas | - |
datetime |
Data i czas | - |
FromTo |
Okres dat (para: from + to) | - |
guid |
Unikalny identyfikator | - |
Typy relacyjne
| Typ | Opis | Dodatkowe atrybuty |
|---|---|---|
NazwaTabeli |
Relacja do innej tabeli | children, delete, relname, relguided |
NazwaInterface |
Relacja interface'owa (polimorficzna) | children, delete, relname |
NazwaEnum |
Typ wyliczeniowy | - |
NazwaSubrow |
Typ złożony (value object) | - |
Uwagi do typów
stringvstext: Używajstringdla krótszych tekstów (wczytywane z rekordem). Używajtextdla długich opisów (wczytywane osobnym zapytaniem SQL).string length="max": Tekst bez ograniczenia rozmiaru, ale wczytywany razem z rekordem.textibinary: Nie mogą być używane jako klucze (keyprimary,keyunique).
textvsstring length="max"w tabelach konfiguracyjnych (cache). Tabele konfiguracyjne są cache'owane — z wyjątkiem kolumntext, których dane nie trafiają do cache i są wczytywane osobnym zapytaniem SQL na żądanie. Dlatego:
type="text"ma sens tylko dla danych bardzo dużych i odczytywanych sporadycznie / jednokrotnie (np. duży załącznik, log).- dla pól odczytywanych wielokrotnie (nawet jeśli bywają długie) użyj
type="string" length="max"— też bez limitu rozmiaru, ale wczytywane razem z rekordem (są w cache), co ogranicza liczbę zapytań SQL. Dotyczy to np. szablonów, instrukcji, treści wstrzykiwanych przy każdym użyciu.
Workflow tworzenia business.xml
- Analiza wymagań - określ jakie obiekty i relacje są potrzebne
- Zdefiniuj enum'y - typy wyliczeniowe używane w kolumnach
- Zdefiniuj interfejsy - dla relacji polimorficznych (gdy kolumna może wskazywać na różne typy obiektów)
- Zdefiniuj subrow - typy złożone (adresy, numery dokumentów)
- Zdefiniuj tabele - główne obiekty biznesowe
- Dodaj relacje - powiązania między tabelami (zwykłe i interface'owe)
- Dodaj indeksy - klucze dla wyszukiwania
- Zaktualizuj
*.rightstree.xml- wpis dla każdej nowej tabeli będącej korzeniem drzewa praw (bez relacjirelright="true"i bezrelguided); usunięte tabele wykreśl (references/rights-tree.md) - Utwórz klasy biznesowe obok (nierozłączny krok — bez nich build nie przejdzie) - dla każdej
tabeli klasa obiektu biznesowego i klasa tabeli; przy polach
readonlykonstruktory; dla tabel z selector'em -abstractbaza, podtypy z[BusinessRow]i[DefaultConstructor], pozycje[NewRow](patrz references/generated-classes.md) - Waliduj - sprawdź zgodność ze schematem XSD; po pierwszym buildzie przeczytaj wygenerowany
*.business.cs(kontrakt konstruktorów, setterów, akcesorówWg…)
Szczegółowa dokumentacja
- references/modules-catalog.md - katalog 34 modułów Soneta, tabele i interfejsy do relacji
- references/table-reference.md - kompletna dokumentacja atrybutów table i col
- references/generated-classes.md - klasy biznesowe tworzone obok XML (Row/Table, konstruktory, selector,
[BusinessRow],[NewRow]) - references/relations-guide.md - tworzenie relacji między obiektami
- references/rights-tree.md - pliki
*.rightstree.xml: miejsce tabel w drzewie uprawnień, reguła korzenia praw, osadzanie jako zasób - references/examples.md - przykłady z rzeczywistych modułów Soneta
Po zdefiniowaniu struktury danych w business.xml kolumny prezentuje się w formularzach (elementy Field, Grid) — patrz skill
/soneta-form-xml(ELEMENTS.md).
Konwencje nazewnicze Soneta
- Nazwa tabeli (name): PascalCase, liczba pojedyncza (np.
Towar,DokumentHandlowy) - Nazwa w bazie (tablename): PascalCase, liczba mnoga, maks. 16 znaków, globalnie unikalna
w bazie — uwaga na kolizje z tabelami modułów platformy (np.
Towary,DokHandlowe) - Nazwa kolumny: PascalCase (np.
KodPocztowy,DataWystawienia) - Klucz:
Wg+ nazwa kolumny (np.WgKodu,WgNazwy) - Namespace:
Firma.NazwaModuluw liczbie mnogiej; ostatni segment nie może równać sięnameżadnej tabeli — inaczejCS0118(wzorzec:Soneta.Towary+ encjaTowar)
Język nazewnictwa
- Obiekty biznesowe (domenowe) - język polski:
Towar,Faktura,Kontrahent,Pracownik - Obiekty systemowe (techniczne) - język angielski:
Session,Config,Cache,Runtime
Typowe wzorce
Słownik (tabela konfiguracyjna)
Tabele config="true" zawierają dane konfiguracyjne tworzone podczas wdrożenia.
<table name="Jednostka" tablename="Jednostki" guided="Root" config="true"
caption="Jednostka" tablecaption="Jednostki">
<key name="WgKodu" keyunique="true" keyprimary="true">
<keycol name="Kod"/>
</key>
<col name="Kod" type="string" length="10" required="true"
description="Symbol jednostki używany przy wprowadzaniu ilości."/>
<col name="Nazwa" type="string" length="80"
description="Pełna nazwa jednostki miary."/>
<col name="Blokada" type="boolean"
description="Jednostka nie będzie wyświetlana na listach wyboru."/>
</table>
Dokument z pozycjami (master-detail)
Tabela szczegółów (bez guided) musi mieć dokładnie jedną relację relguided="inner".
<!-- TABELA GŁÓWNA (guided="Root") -->
<table name="Dokument" tablename="Dokumenty" guided="Root"
caption="Dokument" tablecaption="Dokumenty">
<key name="WgNumeru" keyunique="true" keyprimary="true">
<keycol name="Numer"/>
</key>
<col name="Numer" type="string" length="30" required="true"
description="Numer dokumentu."/>
<col name="Data" type="date" required="true"
description="Data wystawienia dokumentu."/>
<col name="Kontrahent" type="Kontrahent" required="true"
description="Kontrahent, dla którego wystawiono dokument."/>
</table>
<!-- TABELA SZCZEGÓŁÓW (bez guided, jedna relacja relguided="inner") -->
<table name="PozycjaDokumentu" tablename="PozycjeDok"
caption="Pozycja" tablecaption="Pozycje dokumentu">
<col name="Dokument" type="Dokument"
required="true" readonly="true" keyprimary="true"
children="Pozycje" delete="cascade" relguided="inner"
description="Dokument, do którego należy pozycja."/>
<col name="Lp" type="int" required="true" batchfield="false"
description="Numer kolejny pozycji."/>
<col name="Towar" type="Towar" required="true"
description="Towar na pozycji."/>
<col name="Ilosc" type="double" required="true"
description="Ilość towaru."/>
<col name="Cena" type="decimal"
description="Cena jednostkowa."/>
</table>
Historia zmian (wersjonowanie)
Typ FromTo przechowuje okres dat (from + to).
<table name="CenaHistoria" tablename="CenyHistoria"
caption="Historia ceny" tablecaption="Historia cen">
<col name="Towar" type="Towar"
keyprimary="true" keyclass="History" keyclasscol="Okres"
children="HistoriaCen" delete="cascade" relguided="inner"
description="Towar, którego dotyczy historia cen."/>
<col name="Okres" type="FromTo" required="true"
description="Okres obowiązywania ceny."/>
<col name="CenaNetto" type="decimal"
description="Cena netto w okresie."/>
<col name="CenaBrutto" type="decimal"
description="Cena brutto w okresie."/>
</table>