# 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` — rozróżnia trzy stany:
     - `root` — klasa `*Table` dziedziczy (bezpośrednio lub pośrednio) z `GuidedTable`
       albo `ExportedTable`. Tabele te są **korzeniami drzewa obiektów** — stanowią root
       paczki danych (`Datapack`/`GuidedRow`/`ExportedRow`) i to one są obsługiwane
       przez mechanizm synchronizacji i eksportu/importu.
     - `child: Pole→TypRow` — tabela jest częścią drzewa innego rootu; pole rekordu
       z atrybutem `[ColumnInfo(GuidedRelation=…)]` wskazuje na tabelę nadrzędną.
       `Pole` to nazwa pola w `*Record`, `TypRow` to konkretny typ `*Row` odczytany
       z odpowiadającej property w klasie `*Row` (w `*Record` pole ma zwykle typ `IRow`).
     - pusta wartość — tabela szczegółowa (subrow, info-row) niewchodząca w skład żadnego
       drzewa guided.
   - `Konfig` = `konfig`, gdy `*Table` ma `[TableInfo(IsConfig=true)]`. Tabele konfiguracyjne
     żyją w osobnej sesji (`ExecuteConfig`) i mają inne reguły zapisu niż tabele operacyjne.
   - `Interfaces` = lista nazw interfejsów zadeklarowanych w `[TableInfo(Interfaces = new[] { … })]`.
     Soneta używa ich jako **relacji interfejsowych** — pole typu `IXxx` może referować rekord
     z dowolnej tabeli deklarującej `IXxx` w swoim `TableInfo`.
   - 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 | Guided | Konfig | Interfaces | 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 | Konfig | Interfaces | Tytuł | Opis |
|---------|-----------|--------|--------|------------|-------|------|
| DefDokHandlowego | DefDokHandlowych | root | konfig |  | Definicje dokumentów handlowych | Konfigurowalna definicja (szablon) dokumentu handlowego... |
| DefRelacjiHandlowej | DefRelHandlowych | root | konfig |  | Definicje relacji handlowych | Konfigurowalna definicja relacji między dokumentami handlowymi... |
| DokumentHandlowy | DokHandlowe | root |  | IDokument, IKontrahentRef | Dokumenty handlowe | Główna tabela dokumentów handlowych (faktury, paragony, zamówienia, korekty, umowy itp.)... |
| DokumentHandlowyKoszt | DokHandloweKoszt | child: Dokument→DokumentHandlowy |  |  | Koszty dodatkowe | Koszt dodatkowy przypisany do dokumentu handlowego... |
| DrukarkaFiskalna | DrukarkiFiskalne | root | konfig |  | 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`.
