scan-modules.csx + scan-props.csx
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
# Skanowanie modułów i tabel z DLL (Roslyn MetadataReference)
|
||||
|
||||
Narzędzie do wylistowania wszystkich modułów (`*Module`) platformy enova365/Soneta oraz tabel
|
||||
(`*Row` / `*Table`) zdefiniowanych w każdym z nich. Czyta metadane skompilowanych bibliotek dodatku,
|
||||
nie wymaga źródeł.
|
||||
|
||||
## Cel
|
||||
|
||||
W modelu Soneta każda baza danych jest opisana zbiorem modułów (`HandelModule`, `KadryModule`,
|
||||
`CoreModule`, …), a każdy moduł zawiera zagnieżdżone klasy `*Row` definiujące pojedyncze tabele.
|
||||
Skrypt pozwala szybko zinwentaryzować całą strukturę: jakie moduły są obecne w bibliotekach,
|
||||
jakie tabele zawierają i jak nazywa się klasa `*Table` używana w sesji (`Session.Tables.*`).
|
||||
|
||||
Używaj tego narzędzia, gdy:
|
||||
- eksplorujesz nieznany zestaw bibliotek i chcesz zobaczyć pełną listę modułów/tabel;
|
||||
- chcesz znaleźć właściwą nazwę `RowType` lub `TableType` przed użyciem skryptu
|
||||
[scan-props](./scan-props.md);
|
||||
- przygotowujesz dodatek/raport, który potrzebuje pełnego mapowania klasa biznesowa ↔ nazwa tabeli;
|
||||
- weryfikujesz, że nowy dodatek został poprawnie zarejestrowany (jego `*Module` pojawia się na liście).
|
||||
|
||||
## Mechanizm
|
||||
|
||||
Skrypt używa **Roslyn** (`Microsoft.CodeAnalysis.CSharp`) i `MetadataReference.CreateFromFile`,
|
||||
czyli metadane są czytane bez ładowania IL do CLR — bezpiecznie, bez ryzyka konfliktów wersji,
|
||||
x86/x64 itp.
|
||||
|
||||
Algorytm:
|
||||
1. Zbierz wszystkie `*.dll` z podanego katalogu i zarejestruj jako `MetadataReference`. Dodatkowo
|
||||
dołącz biblioteki runtime'u .NET z listy TPA
|
||||
(`AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")`) — bez tego Roslyn nie rozwiązuje
|
||||
`CaptionAttribute` / `DescriptionAttribute` i `ConstructorArguments` zwraca pustą tablicę,
|
||||
przez co `Tytuł`/`Opis` zostają puste.
|
||||
2. Zbuduj `CSharpCompilation` z tymi referencjami.
|
||||
3. Przejdź rekurencyjnie po `IAssemblySymbol.GlobalNamespace` każdej referencji.
|
||||
4. Wybierz wszystkie publiczne klasy top-level o nazwie kończącej się na `Module`,
|
||||
które dziedziczą z `Soneta.Business.Module` (sprawdzane po `BaseType`).
|
||||
Filtr eliminuje "śmieci" w stylu `System.Reflection.RuntimeModule`.
|
||||
5. Dla każdego modułu:
|
||||
- znajdź zagnieżdżone klasy o nazwie kończącej się na `Row` (`module.GetTypeMembers()`);
|
||||
- `RowType` = nazwa klasy bez sufiksu `Row` (np. `DokumentHandlowyRow` → `DokumentHandlowy`);
|
||||
- `TableType` = nazwa typu property `Table` w klasie `*Row` (przeszukiwany wraz z dziedziczeniem
|
||||
przez `FindMemberInherited`);
|
||||
- `Tytuł` = `CaptionAttribute`, `Opis` = `DescriptionAttribute` z klasy `*Table` zagnieżdżonej
|
||||
w tym samym module (np. `HandelModule.DokumentHandlowyTable`). Atrybuty są deklarowane
|
||||
w l.mn. („Dokumenty handlowe"), bo opisują tabelę. Fallback: jeśli klasy `*Table` brak
|
||||
lub nie ma atrybutu, czytane są te same atrybuty z klasy `*Row`. Wartością jest pierwszy
|
||||
parametr `string` konstruktora atrybutu.
|
||||
- `Guided` = `tak`, gdy klasa `*Table` dziedziczy (bezpośrednio lub pośrednio) z `GuidedTable`
|
||||
albo `ExportedTable`. Tabele oznaczone `Guided=tak` są **rootami drzewa obiektów** —
|
||||
stanowią korzeń paczki danych (`Datapack`/`GuidedRow`/`ExportedRow`) i to one są obsługiwane
|
||||
przez mechanizm synchronizacji i eksportu/importu. Tabele bez tej flagi to elementy
|
||||
szczegółowe (subrowy, info-rowy), które są częścią paczki danej tabeli-korzenia, ale nie
|
||||
stanowią samodzielnego rootu.
|
||||
- Dla samego modułu (`*Module`) Tytuł/Opis czytane są analogicznie z atrybutów na klasie modułu.
|
||||
6. Wypisz markdown: sekcja `##` per moduł (z jego `Caption`/`Description` jeśli są), w każdej
|
||||
sekcji tabela `RowType | TableType | Tytuł | Opis`.
|
||||
|
||||
## Wymagania
|
||||
|
||||
- .NET SDK (8.0+)
|
||||
- `dotnet-script`:
|
||||
```bash
|
||||
dotnet tool install -g dotnet-script
|
||||
```
|
||||
|
||||
## Uruchomienie
|
||||
|
||||
```bash
|
||||
dotnet script ~/.claude/skills/soneta-programming/scripts/scan-modules.csx \
|
||||
-- <KatalogDll>
|
||||
```
|
||||
|
||||
### Przykład
|
||||
|
||||
```bash
|
||||
dotnet script ~/.claude/skills/soneta-programming/scripts/scan-modules.csx \
|
||||
-- ./bin/Debug/net8.0
|
||||
```
|
||||
|
||||
### Przykładowe wyjście
|
||||
|
||||
```markdown
|
||||
# Moduły i tabele (Soneta)
|
||||
|
||||
Znaleziono modułów: 37
|
||||
|
||||
## `Soneta.Handel.HandelModule`
|
||||
|
||||
- Opis: Moduł handlowy obsługujący dokumenty sprzedaży, zakupu, zamówień i innych operacji handlowych...
|
||||
- Tabel: 62
|
||||
|
||||
| RowType | TableType | Guided | Tytuł | Opis |
|
||||
|---------|-----------|--------|-------|------|
|
||||
| DefDokHandlowego | DefDokHandlowych | tak | Definicje dokumentów handlowych | Konfigurowalna definicja (szablon) dokumentu handlowego... |
|
||||
| DefRelacjiHandlowej | DefRelHandlowych | tak | Definicje relacji handlowych | Konfigurowalna definicja relacji między dokumentami handlowymi... |
|
||||
| DokumentHandlowy | DokHandlowe | tak | Dokumenty handlowe | Główna tabela dokumentów handlowych (faktury, paragony, zamówienia, korekty, umowy itp.)... |
|
||||
| DokumentHandlowyKoszt | DokHandloweKoszt | | Koszty dodatkowe | Koszt dodatkowy przypisany do dokumentu handlowego... |
|
||||
| DrukarkaFiskalna | DrukarkiFiskalne | tak | Lista drukarek fiskalnych | Konfiguracja drukarki fiskalnej... |
|
||||
| ... | ... | ... | ... | ... |
|
||||
|
||||
_Łącznie tabel: 1196_
|
||||
```
|
||||
|
||||
## Kody wyjścia
|
||||
|
||||
| Kod | Znaczenie |
|
||||
|-----|-----------|
|
||||
| `0` | OK — wypisano listę modułów i tabel |
|
||||
| `1` | Błąd argumentów / nie istnieje katalog / brak DLL |
|
||||
|
||||
## Ograniczenia
|
||||
|
||||
- Skanuje tylko górny poziom katalogu (`SearchOption.TopDirectoryOnly`) — jeśli DLL są
|
||||
rozproszone, skopiuj je do jednego katalogu.
|
||||
- `TableType` dla abstrakcyjnych klas `*Row` (subrowy, klasy bazowe) jest często równy `Table` —
|
||||
to znaczy, że property `Table` pochodzi z klasy bazowej `Soneta.Business.Row` i zwraca ogólny
|
||||
typ `Soneta.Business.Table`, a klasa `*Row` nie ma własnej, dedykowanej tabeli.
|
||||
- Pierwsze uruchomienie pobiera pakiet NuGet `Microsoft.CodeAnalysis.CSharp` — wymaga
|
||||
połączenia internetowego (kolejne odpalenia działają offline).
|
||||
|
||||
## Typowy workflow
|
||||
|
||||
1. **Wstępna inwentaryzacja** — uruchom `scan-modules.csx`, żeby zobaczyć pełną listę
|
||||
`RowType`/`TableType`.
|
||||
2. **Drążenie szczegółów** — dla wybranego `RowType` (np. `DokumentHandlowy`) uruchom
|
||||
[scan-props.csx](./scan-props.md) i odczytaj listę pól bazodanowych oraz właściwości
|
||||
kalkulowanych klasy biznesowej.
|
||||
3. **Generowanie kodu / form.xml / warunków** — użyj odczytanych nazw i typów do budowania
|
||||
wyrażeń bindujących, warunków filtrujących, kodu workerów lub Datapacków.
|
||||
|
||||
## Powiązania
|
||||
|
||||
- [scan-props.md](./scan-props.md) — drugi skrypt skanujący, dla pojedynczego rekordu wypisuje
|
||||
pełną listę pól (bazodanowych + kalkulowanych) wraz z `Tytuł`/`Opis` i rekurencyjnym
|
||||
rozwinięciem subrowów.
|
||||
- Patrz skill `soneta-business-xml` — definicje schematu z których `BusinessGenerator`
|
||||
produkuje klasy `*Module`, `*Row`, `*Table` i `*Record`.
|
||||
@@ -0,0 +1,109 @@
|
||||
# Skanowanie pól klasy biznesowych z DLL (Roslyn MetadataReference)
|
||||
|
||||
Narzędzie do odczytu rzeczywistych pól bazodanowych obiektu biznesowego ze skompilowanych bibliotek dodatku
|
||||
enova365/Soneta.
|
||||
Pozwala to na budowanie kodu opierającego się na tych klasach biznesowych, wyrażeń bindujących form.xml oraz
|
||||
(Warunki filtrujące)[./rowconditions.md].
|
||||
|
||||
## Cel
|
||||
|
||||
W modelu Soneta klasa `Row` (np. `DokumentHandlowy`) udostępnia właściwości publiczne, które można wykorzystać w
|
||||
generowanych kodzie biznesowym, oraz do budowania wyrażeń bindujących form.xml. Natomiast w
|
||||
(warunkach filtrujących)[./rowconditions.md], można używać TYLKO pól bazodananowych udostępnianych przez to narzędzie.
|
||||
|
||||
Używaj tego narzędzia, gdy:
|
||||
- piszesz kod operujący bezpośrednio na polach rekordu (np. w extenderze, workerze, datapack);
|
||||
- chcesz zweryfikować rzeczywisty typ pola (np. `decimal?` vs `decimal`) bez czytania wygenerowanego kodu;
|
||||
- pracujesz na dodatku innej osoby i nie masz dostępu do źródeł, tylko do DLL.
|
||||
- generujesz warunki filtrujące (serwerowe - LINQ)
|
||||
- Przygotowujesz formularze form.xml.
|
||||
|
||||
## Mechanizm
|
||||
|
||||
Skrypt używa **Roslyn** (`Microsoft.CodeAnalysis.CSharp`) i `MetadataReference.CreateFromFile`, co oznacza, że **metadane są czytane bez ładowania IL** do CLR — bezpiecznie, bez ryzyka konfliktów wersji, x86/x64 itp.
|
||||
|
||||
Algorytm:
|
||||
1. Zbierz wszystkie `*.dll` z podanego katalogu i zarejestruj jako `MetadataReference`. Dodatkowo dołącz wszystkie biblioteki runtime'u .NET z listy TPA (`AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")`) — bez tego Roslyn nie rozwiązuje typu `System.ComponentModel.DescriptionAttribute` (i podobnych) i `ConstructorArguments` atrybutów zwraca pustą tablicę, przez co `Tytuł`/`Opis` zostają puste.
|
||||
2. Zbuduj `CSharpCompilation` z tymi referencjami.
|
||||
3. Przejdź rekurencyjnie po `IAssemblySymbol.GlobalNamespace` każdej referencji.
|
||||
4. Znajdź pierwszy typ kończący się na `Module`, który zawiera typ zagnieżdżony o nazwie `{NazwaRekordu}Record`.
|
||||
5. Odczytaj publiczne pola (`IFieldSymbol`, `DeclaredAccessibility == Public`) i ich typy → oznacz jako **bazodanowe**.
|
||||
6. Znajdź publiczną klasę najwyższego poziomu o nazwie `{NazwaRekordu}` (klasę biznesową, np. `DokumentHandlowy`) i wczytaj jej publiczne, instancyjne `IPropertySymbol` (wraz z dziedziczonymi).
|
||||
7. Scal listy:
|
||||
- property o nazwie unikalnej (brak takiego pola w rekordzie) → oznacz jako **kalkulowane**;
|
||||
- property o nazwie pokrywającej się z polem rekordu → zachowaj znacznik **bazodanowe**, ale podmień typ na ten z property (bo property zwykle precyzuje typ, np. zwraca konkretny enum lub `Row` zamiast `Guid`/`int`).
|
||||
8. Dla każdego wpisu odczytaj `Tytuł` i `Opis` — pierwszy parametr `string` konstruktora atrybutu. Matching nazwy atrybutu jest dopasowywany do `Caption`/`CaptionAttribute` oraz `Description`/`DescriptionAttribute` (np. `System.ComponentModel.DescriptionAttribute` używany w generowanym kodzie Soneta). Kolejność źródeł:
|
||||
1. property klasy biznesowej (`{NazwaRekordu}`) — z uwzględnieniem dziedziczenia (atrybut może być na property bazowej klasy, np. `{NazwaRekordu}Row`);
|
||||
2. pole rekordu (`{NazwaModulu}+{NazwaRekordu}Record`);
|
||||
3. odpowiadający member w typie zagnieżdżonym `{NazwaModulu}+{NazwaRekordu}Row` (fallback — przeszukiwany wraz z klasami bazowymi). To tam Soneta zwykle deklaruje `[Description("...")]` na publicznych property delegujących do pola rekordu.
|
||||
9. **Rekurencja po subrowach** — jeśli któreś z pól rekordu ma typ kończący się na `Record` (np. `CoreModule.DefinicjaNumeracjiRecord Numeracja`), traktuj je jako subrow:
|
||||
- na bazie nazwy typu wylicz nazwę bazową (`DefinicjaNumeracjiRecord` → `DefinicjaNumeracji`);
|
||||
- znajdź klasę biznesową (`DefinicjaNumeracji`) oraz typ `*Module+DefinicjaNumeracjiRow` (mogą być w innym module — np. `CoreModule`);
|
||||
- powtórz całą procedurę (kroki 5–8) dla tego rekordu, używając prefiksu `Numeracja.` w kluczach wyników (`Numeracja.Pole1`, `Numeracja.Pole2`, …).
|
||||
Rekurencja działa dowolnie głęboko (subrow w subrowie). Pętle (rekord zawierający siebie pośrednio) są zabezpieczone przez zbiór odwiedzonych typów.
|
||||
10. Wypisz tabelę markdown na stdout (kolumny: `Pole | Typ | Rodzaj | Tytuł | Opis`).
|
||||
|
||||
## Wymagania
|
||||
|
||||
- .NET SDK (8.0+)
|
||||
- `dotnet-script`:
|
||||
```bash
|
||||
dotnet tool install -g dotnet-script
|
||||
```
|
||||
|
||||
## Uruchomienie
|
||||
|
||||
```bash
|
||||
dotnet script ~/.claude/skills/soneta-programming/scripts/scan-props.csx \
|
||||
-- <NazwaRow> <KatalogDll>
|
||||
```
|
||||
|
||||
### Przykład
|
||||
|
||||
```bash
|
||||
dotnet script ~/.claude/skills/soneta-programming/scripts/scan-props.csx \
|
||||
-- DokumentHandlowy ./bin/Debug/net8.0
|
||||
```
|
||||
|
||||
### Przykładowe wyjście
|
||||
|
||||
```markdown
|
||||
# Pola i właściwości klasy biznesowej: `Soneta.Handel.DokumentHandlowy`
|
||||
Nazwa tabeli: `DokHandlowe`
|
||||
|
||||
- pola bazodanowe: 128
|
||||
- pola kalkulowane (z klas biznesowych): 388
|
||||
|
||||
| Pole | Typ | Rodzaj | Tytuł | Opis |
|
||||
|------|-----|--------|-------|------|
|
||||
| Brutto | `decimal` | bazodanowe | Brutto | Wartość brutto dokumentu |
|
||||
| DataDokumentu | `System.DateTime` | bazodanowe | Data dokumentu | |
|
||||
| Kontrahent | `Soneta.Kontrahenci.Kontrahent` | bazodanowe | Kontrahent | |
|
||||
| Netto | `decimal` | bazodanowe | Netto | |
|
||||
| Numer | `string` | bazodanowe | Numer | |
|
||||
| SaldoWaluta | `decimal` | | Saldo w walucie | |
|
||||
| ... | ... | ... | ... | ... |
|
||||
```
|
||||
|
||||
Kolumna `Rodzaj` ma wartość `bazodanowe` dla pól rekordu lub jest pusta dla właściwości kalkulowanych.
|
||||
|
||||
## Kody wyjścia
|
||||
|
||||
| Kod | Znaczenie |
|
||||
|-----|-----------|
|
||||
| `0` | OK — wypisano tabelę pól |
|
||||
| `1` | Błąd argumentów / nie istnieje katalog / brak DLL |
|
||||
| `2` | Nie znaleziono typu `*Module+{NazwaRekordu}Record` w referencjach |
|
||||
|
||||
## Ograniczenia
|
||||
|
||||
- Skanuje tylko górny poziom katalogu (`SearchOption.TopDirectoryOnly`) — jeśli DLL są rozproszone, skopiuj je do jednego katalogu.
|
||||
- Zwraca pierwszy znaleziony typ pasujący do wzorca `*Module+{Nazwa}Record` — jeśli dwa moduły mają taki sam zagnieżdżony rekord, dostaniesz tylko jeden (niedeterministycznie wg kolejności assembly).
|
||||
- Zwraca **publiczne pola** rekordu (`IFieldSymbol`) oraz **publiczne, instancyjne właściwości** klasy biznesowej (`IPropertySymbol`, łącznie z dziedziczonymi). Pola rekordu = źródło prawdy o schemacie DB (rodzaj `bazodanowe`); właściwości spoza rekordu = wyliczane w kodzie (rodzaj `kalkulowane`).
|
||||
- Jeśli klasa biznesowa o nazwie `{NazwaRekordu}` nie zostanie znaleziona w referencjach, skrypt zwraca tylko listę pól bazodanowych (z odpowiednią adnotacją w nagłówku) i kończy się kodem `0`.
|
||||
- Pierwsze uruchomienie pobiera pakiet NuGet `Microsoft.CodeAnalysis.CSharp` — wymaga połączenia internetowego (kolejne odpalenia działają offline).
|
||||
|
||||
## Powiązania
|
||||
|
||||
- Patrz [datapack-guidedrow.md](datapack-guidedrow.md) — struktury `GuidedRow` / `ExportedRow` i mechanizm Datapack operujący na polach rekordu.
|
||||
- Patrz skill `soneta-business-xml` — definicja schematu, z którego `BusinessGenerator` produkuje klasę `XxxRecord`.
|
||||
Reference in New Issue
Block a user