Poprawki w scan-xxxx
This commit is contained in:
@@ -45,15 +45,25 @@ Algorytm:
|
|||||||
w l.mn. („Dokumenty handlowe"), bo opisują tabelę. Fallback: jeśli klasy `*Table` brak
|
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
|
lub nie ma atrybutu, czytane są te same atrybuty z klasy `*Row`. Wartością jest pierwszy
|
||||||
parametr `string` konstruktora atrybutu.
|
parametr `string` konstruktora atrybutu.
|
||||||
- `Guided` = `tak`, gdy klasa `*Table` dziedziczy (bezpośrednio lub pośrednio) z `GuidedTable`
|
- `Guided` — rozróżnia trzy stany:
|
||||||
albo `ExportedTable`. Tabele oznaczone `Guided=tak` są **rootami drzewa obiektów** —
|
- `root` — klasa `*Table` dziedziczy (bezpośrednio lub pośrednio) z `GuidedTable`
|
||||||
stanowią korzeń paczki danych (`Datapack`/`GuidedRow`/`ExportedRow`) i to one są obsługiwane
|
albo `ExportedTable`. Tabele te są **korzeniami drzewa obiektów** — stanowią root
|
||||||
przez mechanizm synchronizacji i eksportu/importu. Tabele bez tej flagi to elementy
|
paczki danych (`Datapack`/`GuidedRow`/`ExportedRow`) i to one są obsługiwane
|
||||||
szczegółowe (subrowy, info-rowy), które są częścią paczki danej tabeli-korzenia, ale nie
|
przez mechanizm synchronizacji i eksportu/importu.
|
||||||
stanowią samodzielnego rootu.
|
- `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.
|
- 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
|
6. Wypisz markdown: sekcja `##` per moduł (z jego `Caption`/`Description` jeśli są), w każdej
|
||||||
sekcji tabela `RowType | TableType | Tytuł | Opis`.
|
sekcji tabela `RowType | TableType | Guided | Konfig | Interfaces | Tytuł | Opis`.
|
||||||
|
|
||||||
## Wymagania
|
## Wymagania
|
||||||
|
|
||||||
@@ -89,14 +99,14 @@ Znaleziono modułów: 37
|
|||||||
- Opis: Moduł handlowy obsługujący dokumenty sprzedaży, zakupu, zamówień i innych operacji handlowych...
|
- Opis: Moduł handlowy obsługujący dokumenty sprzedaży, zakupu, zamówień i innych operacji handlowych...
|
||||||
- Tabel: 62
|
- Tabel: 62
|
||||||
|
|
||||||
| RowType | TableType | Guided | Tytuł | Opis |
|
| RowType | TableType | Guided | Konfig | Interfaces | Tytuł | Opis |
|
||||||
|---------|-----------|--------|-------|------|
|
|---------|-----------|--------|--------|------------|-------|------|
|
||||||
| DefDokHandlowego | DefDokHandlowych | tak | Definicje dokumentów handlowych | Konfigurowalna definicja (szablon) dokumentu handlowego... |
|
| DefDokHandlowego | DefDokHandlowych | root | konfig | | Definicje dokumentów handlowych | Konfigurowalna definicja (szablon) dokumentu handlowego... |
|
||||||
| DefRelacjiHandlowej | DefRelHandlowych | tak | Definicje relacji handlowych | Konfigurowalna definicja relacji między dokumentami handlowymi... |
|
| DefRelacjiHandlowej | DefRelHandlowych | root | konfig | | 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.)... |
|
| DokumentHandlowy | DokHandlowe | root | | IDokument, IKontrahentRef | 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... |
|
| DokumentHandlowyKoszt | DokHandloweKoszt | child: Dokument→DokumentHandlowy | | | Koszty dodatkowe | Koszt dodatkowy przypisany do dokumentu handlowego... |
|
||||||
| DrukarkaFiskalna | DrukarkiFiskalne | tak | Lista drukarek fiskalnych | Konfiguracja drukarki fiskalnej... |
|
| DrukarkaFiskalna | DrukarkiFiskalne | root | konfig | | Lista drukarek fiskalnych | Konfiguracja drukarki fiskalnej... |
|
||||||
| ... | ... | ... | ... | ... |
|
| ... | ... | ... | ... | ... | ... | ... |
|
||||||
|
|
||||||
_Łącznie tabel: 1196_
|
_Łącznie tabel: 1196_
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -41,7 +41,24 @@ Algorytm:
|
|||||||
- znajdź klasę biznesową (`DefinicjaNumeracji`) oraz typ `*Module+DefinicjaNumeracjiRow` (mogą być w innym module — np. `CoreModule`);
|
- 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`, …).
|
- 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.
|
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`).
|
10. **Metadane tabeli** — dodatkowo do nagłówka trafiają:
|
||||||
|
- `Tabela konfiguracyjna: Tak/Nie` — czytane z `[TableInfo(IsConfig=true)]` na zagnieżdżonej
|
||||||
|
klasie `*Module.*Table` (atrybut siedzi tam, nie na top-levelowym typie zwracanym przez
|
||||||
|
property `Table` w `*Row`).
|
||||||
|
- `Guided: root` — gdy `*Table` dziedziczy z `GuidedTable`/`ExportedTable`.
|
||||||
|
- `Guided: child — nadrzędna przez pole \`X\` → \`Y\`` — gdy w rekordzie istnieje pole
|
||||||
|
z `[ColumnInfo(GuidedRelation=…)]` wskazujące tabelę nadrzędną w drzewie obiektów.
|
||||||
|
- `Implementuje interfejsy: …` — lista interfejsów z `[TableInfo(Interfaces=…)]` tej tabeli.
|
||||||
|
11. **Relacje interfejsowe** — skrypt buduje globalny indeks `interfejs → lista tabel implementujących`
|
||||||
|
(iteracja po wszystkich `*Module.*Table` we wszystkich referencjach). Dla każdego pola, którego
|
||||||
|
typ jest interfejsem występującym w tym indeksie (heurystyka: nazwa zaczyna się od `I` + wielka
|
||||||
|
litera), kolumna `Rodzaj` dostaje znacznik `iface-ref`, a po głównej tabeli pól wypisywana
|
||||||
|
jest sekcja `## Relacje interfejsowe` z listą `Pole | Interfejs | Tabele implementujące`.
|
||||||
|
Pozwala to od razu zobaczyć alternatywy, do których pole może wskazywać.
|
||||||
|
12. **Znacznik `guided-parent`** — pole rekordu z atrybutem `[ColumnInfo(GuidedRelation=…)]`
|
||||||
|
dostaje w kolumnie `Rodzaj` dodatkowy tag `guided-parent`, sygnalizując, że to ono trzyma
|
||||||
|
referencję do rootu drzewa.
|
||||||
|
13. Wypisz tabelę markdown na stdout (kolumny: `Pole | Typ | Rodzaj | Tytuł | Opis`).
|
||||||
|
|
||||||
## Wymagania
|
## Wymagania
|
||||||
|
|
||||||
@@ -70,6 +87,9 @@ dotnet script ~/.claude/skills/soneta-programming/scripts/scan-props.csx \
|
|||||||
```markdown
|
```markdown
|
||||||
# Pola i właściwości klasy biznesowej: `Soneta.Handel.DokumentHandlowy`
|
# Pola i właściwości klasy biznesowej: `Soneta.Handel.DokumentHandlowy`
|
||||||
Nazwa tabeli: `DokHandlowe`
|
Nazwa tabeli: `DokHandlowe`
|
||||||
|
Tabela konfiguracyjna: Nie
|
||||||
|
Guided: root
|
||||||
|
Implementuje interfejsy: `IDokument`, `IKontrahentRef`
|
||||||
|
|
||||||
- pola bazodanowe: 128
|
- pola bazodanowe: 128
|
||||||
- pola kalkulowane (z klas biznesowych): 388
|
- pola kalkulowane (z klas biznesowych): 388
|
||||||
@@ -78,14 +98,28 @@ Nazwa tabeli: `DokHandlowe`
|
|||||||
|------|-----|--------|-------|------|
|
|------|-----|--------|-------|------|
|
||||||
| Brutto | `decimal` | bazodanowe | Brutto | Wartość brutto dokumentu |
|
| Brutto | `decimal` | bazodanowe | Brutto | Wartość brutto dokumentu |
|
||||||
| DataDokumentu | `System.DateTime` | bazodanowe | Data dokumentu | |
|
| DataDokumentu | `System.DateTime` | bazodanowe | Data dokumentu | |
|
||||||
| Kontrahent | `Soneta.Kontrahenci.Kontrahent` | bazodanowe | Kontrahent | |
|
| Kontrahent | `Soneta.Kontrahenci.Kontrahent` | bazodanowe, iface-ref | Kontrahent | |
|
||||||
| Netto | `decimal` | bazodanowe | Netto | |
|
| Netto | `decimal` | bazodanowe | Netto | |
|
||||||
| Numer | `string` | bazodanowe | Numer | |
|
| Numer | `string` | bazodanowe | Numer | |
|
||||||
| SaldoWaluta | `decimal` | | Saldo w walucie | |
|
| SaldoWaluta | `decimal` | | Saldo w walucie | |
|
||||||
| ... | ... | ... | ... | ... |
|
| ... | ... | ... | ... | ... |
|
||||||
|
|
||||||
|
## Relacje interfejsowe
|
||||||
|
|
||||||
|
Pola, których typ jest interfejsem zadeklarowanym w `[TableInfo(Interfaces=...)]` innych tabel.
|
||||||
|
Pole może wskazywać na rekord dowolnej z poniższych tabel.
|
||||||
|
|
||||||
|
| Pole | Interfejs | Tabele implementujące |
|
||||||
|
|------|-----------|------------------------|
|
||||||
|
| Kontrahent | `IKontrahent` | `Kontrahent`, `Pracownik`, `Urzad` |
|
||||||
```
|
```
|
||||||
|
|
||||||
Kolumna `Rodzaj` ma wartość `bazodanowe` dla pól rekordu lub jest pusta dla właściwości kalkulowanych.
|
Kolumna `Rodzaj` jest kombinacją znaczników rozdzielonych przecinkami:
|
||||||
|
- `bazodanowe` — pole rekordu (`*Record`); brak znacznika = property kalkulowana klasy biznesowej.
|
||||||
|
- `guided-parent` — pole z `[ColumnInfo(GuidedRelation=…)]` trzymające referencję do nadrzędnej
|
||||||
|
tabeli w drzewie obiektów guided.
|
||||||
|
- `iface-ref` — typ pola jest interfejsem zadeklarowanym w `[TableInfo(Interfaces=…)]` innej tabeli;
|
||||||
|
konkretne tabele docelowe są wymienione w sekcji `## Relacje interfejsowe` pod tabelą pól.
|
||||||
|
|
||||||
## Kody wyjścia
|
## Kody wyjścia
|
||||||
|
|
||||||
|
|||||||
@@ -112,8 +112,8 @@ foreach (var module in modules)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine("| RowType | TableType | Guided | Tytuł | Opis |");
|
Console.WriteLine("| RowType | TableType | Guided | Konfig | Interfaces | Tytuł | Opis |");
|
||||||
Console.WriteLine("|---------|-----------|--------|-------|------|");
|
Console.WriteLine("|---------|-----------|--------|--------|------------|-------|------|");
|
||||||
foreach (var row in rowClasses)
|
foreach (var row in rowClasses)
|
||||||
{
|
{
|
||||||
var rowType = row.Name.EndsWith("Row")
|
var rowType = row.Name.EndsWith("Row")
|
||||||
@@ -136,11 +136,19 @@ foreach (var module in modules)
|
|||||||
if (string.IsNullOrEmpty(description))
|
if (string.IsNullOrEmpty(description))
|
||||||
description = GetAttributeFirstString(row, "DescriptionAttribute");
|
description = GetAttributeFirstString(row, "DescriptionAttribute");
|
||||||
|
|
||||||
var guided = tableCls != null && InheritsFromGuidedOrExportedTable(tableCls)
|
var isGuidedRoot = tableCls != null && InheritsFromGuidedOrExportedTable(tableCls);
|
||||||
? "tak"
|
var guided = isGuidedRoot ? "root" : "";
|
||||||
: "";
|
if (!isGuidedRoot)
|
||||||
|
{
|
||||||
|
var recordCls = module.GetTypeMembers(rowType + "Record").FirstOrDefault();
|
||||||
|
var parent = FindGuidedParent(recordCls, row);
|
||||||
|
if (!string.IsNullOrEmpty(parent)) guided = "child: " + parent;
|
||||||
|
}
|
||||||
|
|
||||||
Console.WriteLine($"| {rowType} | {tableType} | {guided} | {EscapeCell(caption)} | {EscapeCell(description)} |");
|
var konfig = IsConfigTable(tableCls) ? "konfig" : "";
|
||||||
|
var interfaces = string.Join(", ", GetTableInterfaces(tableCls));
|
||||||
|
|
||||||
|
Console.WriteLine($"| {rowType} | {tableType} | {guided} | {konfig} | {EscapeCell(interfaces)} | {EscapeCell(caption)} | {EscapeCell(description)} |");
|
||||||
totalRows++;
|
totalRows++;
|
||||||
}
|
}
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
@@ -185,6 +193,75 @@ static ISymbol FindMemberInherited(INamedTypeSymbol type, string name)
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zwraca opis nadrzędnej tabeli w strukturze guided dla tabel guided-child.
|
||||||
|
// Pole rekordu oznaczone [ColumnInfo(GuidedRelation=RelationGuidedType.GuidedParent)] wskazuje
|
||||||
|
// kierunek relacji; konkretny typ Row pobieramy z property o tej samej nazwie w klasie *Row
|
||||||
|
// (w *Record pole ma zwykle typ IRow, więc bez Row nie da się ustalić konkretu).
|
||||||
|
static string FindGuidedParent(INamedTypeSymbol recordCls, INamedTypeSymbol rowCls)
|
||||||
|
{
|
||||||
|
if (recordCls == null) return "";
|
||||||
|
foreach (var f in recordCls.GetMembers().OfType<IFieldSymbol>())
|
||||||
|
{
|
||||||
|
foreach (var a in f.GetAttributes())
|
||||||
|
{
|
||||||
|
var an = a.AttributeClass?.Name;
|
||||||
|
if (an != "ColumnInfoAttribute" && an != "ColumnInfo") continue;
|
||||||
|
var hasGuided = a.NamedArguments.Any(na => na.Key == "GuidedRelation"
|
||||||
|
&& na.Value.Kind == TypedConstantKind.Enum
|
||||||
|
&& na.Value.Value is int v && v != 0);
|
||||||
|
if (!hasGuided) continue;
|
||||||
|
var propType = "?";
|
||||||
|
if (rowCls != null)
|
||||||
|
{
|
||||||
|
for (var rc = rowCls; rc != null && rc.SpecialType != SpecialType.System_Object; rc = rc.BaseType)
|
||||||
|
{
|
||||||
|
var p = rc.GetMembers(f.Name).OfType<IPropertySymbol>().FirstOrDefault();
|
||||||
|
if (p != null) { propType = p.Type.Name; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f.Name + "→" + propType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lista interfejsów biznesowych z [TableInfo(Interfaces = new[] { "I1", "I2", ... })].
|
||||||
|
// Soneta używa ich jako "relacji interfejsowych" — pole typu IXxx może referować dowolny
|
||||||
|
// rekord z tabeli, która deklaruje IXxx w swoim TableInfo.
|
||||||
|
static System.Collections.Generic.IEnumerable<string> GetTableInterfaces(INamedTypeSymbol tableCls)
|
||||||
|
{
|
||||||
|
if (tableCls == null) yield break;
|
||||||
|
foreach (var a in tableCls.GetAttributes())
|
||||||
|
{
|
||||||
|
if (a.AttributeClass?.Name != "TableInfoAttribute" && a.AttributeClass?.Name != "TableInfo")
|
||||||
|
continue;
|
||||||
|
foreach (var na in a.NamedArguments)
|
||||||
|
{
|
||||||
|
if (na.Key != "Interfaces" || na.Value.Kind != TypedConstantKind.Array) continue;
|
||||||
|
foreach (var el in na.Value.Values)
|
||||||
|
{
|
||||||
|
if (el.Value is string s && !string.IsNullOrEmpty(s)) yield return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsConfigTable(INamedTypeSymbol tableCls)
|
||||||
|
{
|
||||||
|
if (tableCls == null) return false;
|
||||||
|
foreach (var a in tableCls.GetAttributes())
|
||||||
|
{
|
||||||
|
if (a.AttributeClass?.Name != "TableInfoAttribute" && a.AttributeClass?.Name != "TableInfo")
|
||||||
|
continue;
|
||||||
|
foreach (var na in a.NamedArguments)
|
||||||
|
{
|
||||||
|
if (na.Key == "IsConfig" && na.Value.Value is bool b)
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static string GetAttributeFirstString(ISymbol symbol, string attributeTypeName)
|
static string GetAttributeFirstString(ISymbol symbol, string attributeTypeName)
|
||||||
{
|
{
|
||||||
if (symbol == null) return "";
|
if (symbol == null) return "";
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ topLevelClasses.TryGetValue(recordBaseName, out mainBusinessClass);
|
|||||||
|
|
||||||
// Nazwa tabeli wyciągana z typu zwracanego przez property `Table` w klasie XxxxRow.
|
// Nazwa tabeli wyciągana z typu zwracanego przez property `Table` w klasie XxxxRow.
|
||||||
string tableTypeName = null;
|
string tableTypeName = null;
|
||||||
|
bool isConfigTable = false;
|
||||||
var rowClass = enclosing?.GetTypeMembers(recordBaseName + "Row").FirstOrDefault();
|
var rowClass = enclosing?.GetTypeMembers(recordBaseName + "Row").FirstOrDefault();
|
||||||
if (rowClass != null)
|
if (rowClass != null)
|
||||||
{
|
{
|
||||||
@@ -118,6 +119,20 @@ if (rowClass != null)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Atrybut [TableInfo(IsConfig=true)] siedzi na klasie zagnieżdżonej XxxxModule.XxxxTable
|
||||||
|
// (nie na top-level typie tabeli zwracanym przez property `Table` w *Row).
|
||||||
|
var nestedTableCls = enclosing?.GetTypeMembers(recordBaseName + "Table").FirstOrDefault();
|
||||||
|
if (nestedTableCls != null)
|
||||||
|
isConfigTable = IsConfigTable(nestedTableCls);
|
||||||
|
|
||||||
|
// Wyznacz status guided: root (dziedziczy po GuidedTable/ExportedTable) lub child→ParentRow
|
||||||
|
// (pole rekordu z [ColumnInfo(GuidedRelation=...)]). Pole zapamiętujemy też w guidedParentField,
|
||||||
|
// żeby oznaczyć je później w tabeli pól.
|
||||||
|
var isGuidedRoot = nestedTableCls != null && InheritsFromGuidedOrExportedTable(nestedTableCls);
|
||||||
|
string guidedParentField = null;
|
||||||
|
string guidedParentType = null;
|
||||||
|
if (!isGuidedRoot)
|
||||||
|
(guidedParentField, guidedParentType) = FindGuidedParent(foundRecord, rowClass);
|
||||||
|
|
||||||
// Klucz: nazwa pola z notacją kropkową dla subrowów; Wartość: (typ, czyBazodanowe, tytuł, opis)
|
// Klucz: nazwa pola z notacją kropkową dla subrowów; Wartość: (typ, czyBazodanowe, tytuł, opis)
|
||||||
var merged = new SortedDictionary<string, (string Type, bool IsDb, string Caption, string Description)>(StringComparer.Ordinal);
|
var merged = new SortedDictionary<string, (string Type, bool IsDb, string Caption, string Description)>(StringComparer.Ordinal);
|
||||||
@@ -138,6 +153,38 @@ else
|
|||||||
if (!string.IsNullOrEmpty(tableTypeName))
|
if (!string.IsNullOrEmpty(tableTypeName))
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Nazwa tabeli: `{tableTypeName}`");
|
Console.WriteLine($"Nazwa tabeli: `{tableTypeName}`");
|
||||||
|
Console.WriteLine($"Tabela konfiguracyjna: {(isConfigTable ? "Tak" : "Nie")}");
|
||||||
|
if (isGuidedRoot)
|
||||||
|
Console.WriteLine("Guided: root");
|
||||||
|
else if (guidedParentField != null)
|
||||||
|
Console.WriteLine($"Guided: child — nadrzędna przez pole `{guidedParentField}` → `{guidedParentType}`");
|
||||||
|
var thisInterfaces = nestedTableCls != null ? GetTableInterfaces(nestedTableCls).ToList() : new System.Collections.Generic.List<string>();
|
||||||
|
if (thisInterfaces.Count > 0)
|
||||||
|
Console.WriteLine($"Implementuje interfejsy: {string.Join(", ", thisInterfaces.Select(i => "`" + i + "`"))}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indeks interfejs → lista tabel implementujących, na potrzeby pokazania alternatyw
|
||||||
|
// dla pól o typie interfejsowym (relacje interfejsowe Soneta). Klasy *Table są zagnieżdżone
|
||||||
|
// w *Module — iterujemy po top-level *Module i pobieramy ich nested types.
|
||||||
|
var interfaceImpls = new SortedDictionary<string, System.Collections.Generic.List<string>>(StringComparer.Ordinal);
|
||||||
|
foreach (var asmRef in compilation.References)
|
||||||
|
{
|
||||||
|
if (compilation.GetAssemblyOrModuleSymbol(asmRef) is not IAssemblySymbol asm) continue;
|
||||||
|
foreach (var top in EnumerateAllTypes(asm.GlobalNamespace))
|
||||||
|
{
|
||||||
|
if (top.ContainingType != null || !top.Name.EndsWith("Module")) continue;
|
||||||
|
foreach (var t in top.GetTypeMembers())
|
||||||
|
{
|
||||||
|
if (!t.Name.EndsWith("Table")) continue;
|
||||||
|
foreach (var iface in GetTableInterfaces(t))
|
||||||
|
{
|
||||||
|
if (!interfaceImpls.TryGetValue(iface, out var list))
|
||||||
|
interfaceImpls[iface] = list = new System.Collections.Generic.List<string>();
|
||||||
|
var rowName = t.Name.Substring(0, t.Name.Length - "Table".Length);
|
||||||
|
list.Add(rowName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
var dbCount = merged.Values.Count(v => v.IsDb);
|
var dbCount = merged.Values.Count(v => v.IsDb);
|
||||||
@@ -147,13 +194,48 @@ Console.WriteLine($"- pola kalkulowane (z klas biznesowych): {calcCount}");
|
|||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
Console.WriteLine("| Pole | Typ | Rodzaj | Tytuł | Opis |");
|
Console.WriteLine("| Pole | Typ | Rodzaj | Tytuł | Opis |");
|
||||||
Console.WriteLine("|------|-----|--------|-------|------|");
|
Console.WriteLine("|------|-----|--------|-------|------|");
|
||||||
|
var interfaceFields = new System.Collections.Generic.List<(string Field, string IfaceShort, System.Collections.Generic.List<string> Impls)>();
|
||||||
foreach (var kv in merged)
|
foreach (var kv in merged)
|
||||||
{
|
{
|
||||||
var rodzaj = kv.Value.IsDb ? "bazodanowe" : "";
|
var rodzaj = kv.Value.IsDb ? "bazodanowe" : "";
|
||||||
|
if (guidedParentField != null && kv.Key == guidedParentField)
|
||||||
|
rodzaj = string.IsNullOrEmpty(rodzaj) ? "guided-parent" : rodzaj + ", guided-parent";
|
||||||
|
var shortType = ShortTypeName(kv.Value.Type);
|
||||||
|
if (shortType.StartsWith("I") && shortType.Length > 1 && char.IsUpper(shortType[1])
|
||||||
|
&& interfaceImpls.TryGetValue(shortType, out var impls))
|
||||||
|
{
|
||||||
|
rodzaj = string.IsNullOrEmpty(rodzaj) ? "iface-ref" : rodzaj + ", iface-ref";
|
||||||
|
interfaceFields.Add((kv.Key, shortType, impls));
|
||||||
|
}
|
||||||
Console.WriteLine($"| {kv.Key} | `{kv.Value.Type}` | {rodzaj} | {EscapeCell(kv.Value.Caption)} | {EscapeCell(kv.Value.Description)} |");
|
Console.WriteLine($"| {kv.Key} | `{kv.Value.Type}` | {rodzaj} | {EscapeCell(kv.Value.Caption)} | {EscapeCell(kv.Value.Description)} |");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (interfaceFields.Count > 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine("## Relacje interfejsowe");
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine("Pola, których typ jest interfejsem zadeklarowanym w `[TableInfo(Interfaces=...)]` innych tabel.");
|
||||||
|
Console.WriteLine("Pole może wskazywać na rekord dowolnej z poniższych tabel.");
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine("| Pole | Interfejs | Tabele implementujące |");
|
||||||
|
Console.WriteLine("|------|-----------|------------------------|");
|
||||||
|
foreach (var f in interfaceFields)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"| {f.Field} | `{f.IfaceShort}` | {string.Join(", ", f.Impls.Select(i => "`" + i + "`"))} |");
|
||||||
|
}
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
static string ShortTypeName(string fullName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(fullName)) return "";
|
||||||
|
var lt = fullName.IndexOf('<');
|
||||||
|
if (lt >= 0) fullName = fullName.Substring(0, lt);
|
||||||
|
var dot = fullName.LastIndexOf('.');
|
||||||
|
return dot >= 0 ? fullName.Substring(dot + 1) : fullName;
|
||||||
|
}
|
||||||
|
|
||||||
static void ScanRecord(
|
static void ScanRecord(
|
||||||
INamedTypeSymbol record,
|
INamedTypeSymbol record,
|
||||||
string prefix,
|
string prefix,
|
||||||
@@ -267,6 +349,77 @@ static IEnumerable<IPropertySymbol> EnumerateInheritedProperties(INamedTypeSymbo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool InheritsFromGuidedOrExportedTable(INamedTypeSymbol type)
|
||||||
|
{
|
||||||
|
for (var t = type.BaseType; t != null && t.SpecialType != SpecialType.System_Object; t = t.BaseType)
|
||||||
|
{
|
||||||
|
if (t.Name == "GuidedTable" || t.Name == "ExportedTable") return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static (string field, string parentType) FindGuidedParent(INamedTypeSymbol recordCls, INamedTypeSymbol rowCls)
|
||||||
|
{
|
||||||
|
if (recordCls == null) return (null, null);
|
||||||
|
foreach (var f in recordCls.GetMembers().OfType<IFieldSymbol>())
|
||||||
|
{
|
||||||
|
foreach (var a in f.GetAttributes())
|
||||||
|
{
|
||||||
|
var an = a.AttributeClass?.Name;
|
||||||
|
if (an != "ColumnInfoAttribute" && an != "ColumnInfo") continue;
|
||||||
|
var hasGuided = a.NamedArguments.Any(na => na.Key == "GuidedRelation"
|
||||||
|
&& na.Value.Kind == TypedConstantKind.Enum
|
||||||
|
&& na.Value.Value is int v && v != 0);
|
||||||
|
if (!hasGuided) continue;
|
||||||
|
var propType = "?";
|
||||||
|
if (rowCls != null)
|
||||||
|
{
|
||||||
|
for (var rc = rowCls; rc != null && rc.SpecialType != SpecialType.System_Object; rc = rc.BaseType)
|
||||||
|
{
|
||||||
|
var p = rc.GetMembers(f.Name).OfType<IPropertySymbol>().FirstOrDefault();
|
||||||
|
if (p != null) { propType = p.Type.Name; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (f.Name, propType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static System.Collections.Generic.IEnumerable<string> GetTableInterfaces(INamedTypeSymbol tableCls)
|
||||||
|
{
|
||||||
|
if (tableCls == null) yield break;
|
||||||
|
foreach (var a in tableCls.GetAttributes())
|
||||||
|
{
|
||||||
|
if (a.AttributeClass?.Name != "TableInfoAttribute" && a.AttributeClass?.Name != "TableInfo")
|
||||||
|
continue;
|
||||||
|
foreach (var na in a.NamedArguments)
|
||||||
|
{
|
||||||
|
if (na.Key != "Interfaces" || na.Value.Kind != TypedConstantKind.Array) continue;
|
||||||
|
foreach (var el in na.Value.Values)
|
||||||
|
{
|
||||||
|
if (el.Value is string s && !string.IsNullOrEmpty(s)) yield return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsConfigTable(INamedTypeSymbol tableCls)
|
||||||
|
{
|
||||||
|
if (tableCls == null) return false;
|
||||||
|
foreach (var a in tableCls.GetAttributes())
|
||||||
|
{
|
||||||
|
if (a.AttributeClass?.Name != "TableInfoAttribute" && a.AttributeClass?.Name != "TableInfo")
|
||||||
|
continue;
|
||||||
|
foreach (var na in a.NamedArguments)
|
||||||
|
{
|
||||||
|
if (na.Key == "IsConfig" && na.Value.Value is bool b)
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static string GetAttributeFirstString(ISymbol symbol, string attributeTypeName)
|
static string GetAttributeFirstString(ISymbol symbol, string attributeTypeName)
|
||||||
{
|
{
|
||||||
if (symbol == null) return "";
|
if (symbol == null) return "";
|
||||||
|
|||||||
Reference in New Issue
Block a user