Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba52486f53 | ||
|
|
6cd41ee5a3 | ||
|
|
3c0e30bf35 | ||
|
|
d4eb3f2102 |
@@ -1,4 +1,4 @@
|
|||||||
---
|
---
|
||||||
name: render-dxf
|
name: render-dxf
|
||||||
description: Specialista na kreslicí a DXF vrstvu Rozpisek (Rozpisky/Rendering/, Rozpisky/Dxf/). Použij pro změny v renderování (Skia, PNG, PDF, DXF výstup), ve čtečce DXF šablony, v mapování hodnot do polí, v kódech příloh a v práci s logy. Nepoužívej pro UI, ViewModely ani COM (CAD/Excel).
|
description: Specialista na kreslicí a DXF vrstvu Rozpisek (Rozpisky/Rendering/, Rozpisky/Dxf/). Použij pro změny v renderování (Skia, PNG, PDF, DXF výstup), ve čtečce DXF šablony, v mapování hodnot do polí, v kódech příloh a v práci s logy. Nepoužívej pro UI, ViewModely ani COM (CAD/Excel).
|
||||||
tools: Read, Edit, Write, Grep, Glob, Bash, PowerShell
|
tools: Read, Edit, Write, Grep, Glob, Bash, PowerShell
|
||||||
@@ -24,7 +24,7 @@ teče na obrazovku, do PNG, do PDF i do DXF. Nový výstup = **nová implementac
|
|||||||
duplikovaný kreslicí kód. `DxfTemplate` jen čte DXF a přehrává ho do rozhraní — logika výstupu
|
duplikovaný kreslicí kód. `DxfTemplate` jen čte DXF a přehrává ho do rozhraní — logika výstupu
|
||||||
do ní nepatří.
|
do ní nepatří.
|
||||||
|
|
||||||
## Tvrdá pravidla DXF (z ROZPISKY.md, ověř si je tam)
|
## Tvrdá pravidla DXF (z docs/ROZPISKY.md, ověř si je tam)
|
||||||
|
|
||||||
- Tloušťka čáry = `Layer.LineWeight`, enum v 1/100 mm, **jen** povolené hodnoty:
|
- Tloušťka čáry = `Layer.LineWeight`, enum v 1/100 mm, **jen** povolené hodnoty:
|
||||||
0, 5, 9, 13, 15, 18, 20, 25, 30, 35, 40, 50, 53, 60, 70, 80, 90, 100, 106, 120, 140, 158, 200, 211.
|
0, 5, 9, 13, 15, 18, 20, 25, 30, 35, 40, 50, 53, 60, 70, 80, 90, 100, 106, 120, 140, 158, 200, 211.
|
||||||
@@ -40,15 +40,16 @@ do ní nepatří.
|
|||||||
## Konvence
|
## Konvence
|
||||||
|
|
||||||
Kód, komentáře i názvy typů česky. `Nullable` je zapnutý. Nepřidávej NuGet balíčky.
|
Kód, komentáře i názvy typů česky. `Nullable` je zapnutý. Nepřidávej NuGet balíčky.
|
||||||
Needituj produkční data: `Rozpiska.dxf`, `schema.dxf`, `Rozpisky/loga/*.dxf`.
|
Needituj produkční data: `Rozpisky/Podklady/Rozpiska.dxf`, `vzorky/schema.dxf`,
|
||||||
|
`Rozpisky/Podklady/loga/*.dxf`.
|
||||||
|
|
||||||
## Postup
|
## Postup
|
||||||
|
|
||||||
1. Přečti si relevantní kapitolu `ROZPISKY.md` a dotčené soubory, než začneš editovat.
|
1. Přečti si relevantní kapitolu `docs/ROZPISKY.md` a dotčené soubory, než začneš editovat.
|
||||||
2. Změň kód co nejmenším zásahem, ve stylu okolního kódu.
|
2. Změň kód co nejmenším zásahem, ve stylu okolního kódu.
|
||||||
3. Ověř:
|
3. Ověř:
|
||||||
`dotnet build "Rozpisky.sln" -nologo -v q -clp:ErrorsOnly` a
|
`dotnet build "Rozpisky.sln" -nologo -v q -clp:ErrorsOnly` a
|
||||||
`dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q`
|
`dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q`
|
||||||
(baseline je 36 zelených testů — nesmí ubýt).
|
(baseline je 50 zelených testů — nesmí ubýt).
|
||||||
4. V odpovědi vrať: co jsi změnil (soubor:řádek), proč, a výsledek buildu a testů. Když něco
|
4. V odpovědi vrať: co jsi změnil (soubor:řádek), proč, a výsledek buildu a testů. Když něco
|
||||||
neprošlo, řekni to rovnou i s chybovou hláškou — nezakrývej to.
|
neprošlo, řekni to rovnou i s chybovou hláškou — nezakrývej to.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
---
|
---
|
||||||
name: verifikator
|
name: verifikator
|
||||||
description: Read-only ověřovatel — spustí build a testy Rozpisek a vrátí strukturovaný verdikt. Použij po každé nezanedbatelné změně kódu, nebo když je potřeba nezávisle potvrdit, že projekt je v pořádku. Nic needituje.
|
description: Read-only ověřovatel — spustí build a testy Rozpisek a vrátí strukturovaný verdikt. Použij po každé nezanedbatelné změně kódu, nebo když je potřeba nezávisle potvrdit, že projekt je v pořádku. Nic needituje.
|
||||||
tools: Read, Grep, Glob, Bash, PowerShell
|
tools: Read, Grep, Glob, Bash, PowerShell
|
||||||
@@ -17,7 +17,7 @@ dotnet test "C:\Users\marek\_Osobní\C sharp\EXE\Rozpisky\Rozpisky.Tests\Rozpis
|
|||||||
Testy spusť i tehdy, když build projde s varováními. Když build spadne, testy nespouštěj
|
Testy spusť i tehdy, když build projde s varováními. Když build spadne, testy nespouštěj
|
||||||
a rovnou hlas chyby buildu.
|
a rovnou hlas chyby buildu.
|
||||||
|
|
||||||
Baseline, se kterou porovnáváš: **build bez chyb a bez varování, 36 testů zelených, 0 přeskočených.**
|
Baseline, se kterou porovnáváš: **build bez chyb a bez varování, 50 testů zelených, 0 přeskočených.**
|
||||||
|
|
||||||
Volitelně (jen když o to volající požádá) ověř, že aplikace naběhne:
|
Volitelně (jen když o to volající požádá) ověř, že aplikace naběhne:
|
||||||
spusť `Rozpisky\bin\Debug\net10.0-windows\Rozpisky.exe`, počkej ~8 s, zkontroluj, že proces
|
spusť `Rozpisky\bin\Debug\net10.0-windows\Rozpisky.exe`, počkej ~8 s, zkontroluj, že proces
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
---
|
---
|
||||||
name: wpf-ui
|
name: wpf-ui
|
||||||
description: Specialista na WPF prezentační vrstvu Rozpisek (ViewModels/, Views/, Themes/, Behaviors/, Converters/, Theming/). Použij pro změny obrazovek, XAML, bindingů, příkazů, chování gridů a vzhledu. Nepoužívej pro kreslicí/DXF jádro ani pro COM vrstvu.
|
description: Specialista na WPF prezentační vrstvu Rozpisek (ViewModels/, Views/, Themes/, Behaviors/, Converters/, Theming/). Použij pro změny obrazovek, XAML, bindingů, příkazů, chování gridů a vzhledu. Nepoužívej pro kreslicí/DXF jádro ani pro COM vrstvu.
|
||||||
tools: Read, Edit, Write, Grep, Glob, Bash, PowerShell
|
tools: Read, Edit, Write, Grep, Glob, Bash, PowerShell
|
||||||
@@ -31,11 +31,11 @@ nebo `Rozpisky/Xlsx/` jen popiš v odpovědi a nech je na volajícím.
|
|||||||
|
|
||||||
## Postup
|
## Postup
|
||||||
|
|
||||||
1. `ROZHRANI.md` popisuje obrazovky, pole a datový model — přečti relevantní kapitolu, než
|
1. `docs/ROZHRANI.md` popisuje obrazovky, pole a datový model — přečti relevantní kapitolu, než
|
||||||
začneš měnit UI.
|
začneš měnit UI.
|
||||||
2. `MainViewModel.cs` je velký; hledej v něm grepem, nečti ho celý zbytečně.
|
2. `MainViewModel.cs` je velký; hledej v něm grepem, nečti ho celý zbytečně.
|
||||||
3. Ověř: `dotnet build "Rozpisky.sln" -nologo -v q -clp:ErrorsOnly` a
|
3. Ověř: `dotnet build "Rozpisky.sln" -nologo -v q -clp:ErrorsOnly` a
|
||||||
`dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q` (baseline 36 zelených).
|
`dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q` (baseline 50 zelených).
|
||||||
Build musí být **bez varování** — chyby v XAML se často projeví jen jako varování.
|
Build musí být **bez varování** — chyby v XAML se často projeví jen jako varování.
|
||||||
4. WPF chyby v bindingu build nezachytí. Pokud změna může binding rozbít, řekni to explicitně
|
4. WPF chyby v bindingu build nezachytí. Pokud změna může binding rozbít, řekni to explicitně
|
||||||
a doporuč ruční spuštění aplikace.
|
a doporuč ruční spuštění aplikace.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Rozpisky — pokyny pro Claude Code
|
# Rozpisky — pokyny pro Claude Code
|
||||||
|
|
||||||
WPF desktopová aplikace (.NET 10, `net10.0-windows`, AnyCPU) — generátor výkresových rohových
|
WPF desktopová aplikace (.NET 10, `net10.0-windows`, AnyCPU) — generátor výkresových rohových
|
||||||
razítek („rozpisek") pro Správu železnic. Náhrada původního excelového nástroje „Rozpisky 1.0.8".
|
razítek („rozpisek") pro Správu železnic. Náhrada původního excelového nástroje „Rozpisky 1.0.8".
|
||||||
@@ -11,7 +11,7 @@ dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q
|
|||||||
dotnet run --project "Rozpisky\Rozpisky.csproj"
|
dotnet run --project "Rozpisky\Rozpisky.csproj"
|
||||||
```
|
```
|
||||||
|
|
||||||
Baseline: build bez chyb a varování, **36 testů zelených**. Před hlášením hotovo obojí spustit
|
Baseline: build bez chyb a varování, **50 testů zelených**. Před hlášením hotovo obojí spustit
|
||||||
(nebo použít `/overit`).
|
(nebo použít `/overit`).
|
||||||
|
|
||||||
## Architektura — jediné pravidlo, které se nesmí porušit
|
## Architektura — jediné pravidlo, které se nesmí porušit
|
||||||
@@ -21,7 +21,7 @@ Tatáž kresba teče na obrazovku, do PNG, do PDF i do DXF. **Nový výstupní f
|
|||||||
`IProfileRenderer`, nikdy nový kreslicí kód.** Čtečka [`DxfTemplate`](Rozpisky/Dxf/DxfTemplate.cs)
|
`IProfileRenderer`, nikdy nový kreslicí kód.** Čtečka [`DxfTemplate`](Rozpisky/Dxf/DxfTemplate.cs)
|
||||||
jen čte DXF šablonu a „přehrává" ji do tohoto rozhraní.
|
jen čte DXF šablonu a „přehrává" ji do tohoto rozhraní.
|
||||||
|
|
||||||
Podrobnosti (vrstvy, NuGet API, pravidla zápisu DXF) — [`ROZPISKY.md`](ROZPISKY.md). Nečíst celý,
|
Podrobnosti (vrstvy, NuGet API, pravidla zápisu DXF) — [`ROZPISKY.md`](docs/ROZPISKY.md). Nečíst celý,
|
||||||
jen relevantní kapitolu.
|
jen relevantní kapitolu.
|
||||||
|
|
||||||
## Konvence
|
## Konvence
|
||||||
@@ -35,13 +35,32 @@ jen relevantní kapitolu.
|
|||||||
(WPF-DarkTheme-Kit). Žádné hardcoded `#RRGGBB` ani natvrdo zadané odsazení.
|
(WPF-DarkTheme-Kit). Žádné hardcoded `#RRGGBB` ani natvrdo zadané odsazení.
|
||||||
- `Nullable` a `ImplicitUsings` jsou zapnuté v obou projektech.
|
- `Nullable` a `ImplicitUsings` jsou zapnuté v obou projektech.
|
||||||
- COM (CAD, MS Excel) jde přes pozdní vazbu (`dynamic`) — nepřidávat interop assembly.
|
- COM (CAD, MS Excel) jde přes pozdní vazbu (`dynamic`) — nepřidávat interop assembly.
|
||||||
|
- Uživatelská data (číselníky, výběr CADu, logy) jdou přes
|
||||||
|
[`UzivatelskaData`](Rozpisky/Data/UzivatelskaData.cs) do `%APPDATA%\Rozpisky`. Do složky u `.exe`
|
||||||
|
se **nezapisuje** — může být jen pro čtení a přeinstalace ji přepíše. Číselníky navíc žijí ve dvou
|
||||||
|
vrstvách ([`CiselnikyStore`](Rozpisky/Data/CiselnikyStore.cs)): dodávaná + uložený rozdíl
|
||||||
|
uživatele. Změnu slučování vždy pokrýt testem — chyba tam se projeví tichou ztrátou dat.
|
||||||
|
|
||||||
|
## Rozvržení složky — tři druhy podkladů
|
||||||
|
|
||||||
|
| Kde | Co to je |
|
||||||
|
|---|---|
|
||||||
|
| `docs/` | Podklady **jen pro tvorbu programu** — architektura, datový model, normativní manuál SŽ v `.md`. Nedodává se uživateli. |
|
||||||
|
| `Rozpisky/Podklady/` | Podklady **nutné k běhu** — kopírují se vedle `.exe` (plochá struktura + `loga\`, řídí `Link` v `.csproj`). Aplikace je **jen čte**. |
|
||||||
|
| `%APPDATA%\Rozpisky` | Podklady **vznikající za běhu** — vše, co píše uživatel. V repu nejsou a nemají tam co dělat. |
|
||||||
|
| `vzorky/` | Cizí vstupy pro ruční zkoušení (`schema.dxf`). Nedodává se. |
|
||||||
|
|
||||||
|
Nové dodávané datum = do `Rozpisky/Podklady/` **a** řádek v `.csproj` s `Link`, ať výstup
|
||||||
|
zůstane plochý. Nový zapisovaný soubor = přes `UzivatelskaData.Cesta(...)`, nikdy
|
||||||
|
`AppContext.BaseDirectory`.
|
||||||
|
|
||||||
## Kam nesahat
|
## Kam nesahat
|
||||||
|
|
||||||
- `Rozpiska.dxf`, `schema.dxf` — produkční DXF šablony.
|
- `Rozpisky/Podklady/Rozpiska.dxf`, `vzorky/schema.dxf` — produkční DXF šablony.
|
||||||
- `Rozpisky/loga/*.dxf` — dodávaná loga (9 ks, testy je renderují).
|
- `Rozpisky/Podklady/loga/*.dxf` — dodávaná loga (9 ks, testy je renderují).
|
||||||
- `SEZNAM.xlsx` — mustr „Seznam příloh".
|
- `Rozpisky/Podklady/SEZNAM.xlsx` — mustr „Seznam příloh".
|
||||||
- `SZ_SM011_P10_Manual_v6.md/.pdf` — normativní podklad SŽ, jen ke čtení.
|
- `docs/SZ_SM011_P10_Manual_v6.md`, `Rozpisky/Podklady/SZ_SM011_P10_Manual_v6.pdf`
|
||||||
|
— normativní podklad SŽ, jen ke čtení.
|
||||||
|
|
||||||
## Delegování
|
## Delegování
|
||||||
|
|
||||||
@@ -51,5 +70,5 @@ celý do jedné z těchto domén je použít; drobnosti napříč vrstvami řeš
|
|||||||
|
|
||||||
## Další dokumentace
|
## Další dokumentace
|
||||||
|
|
||||||
[`ROZHRANI.md`](ROZHRANI.md) — obrazovky, pole a datový model.
|
[`ROZHRANI.md`](docs/ROZHRANI.md) — obrazovky, pole a datový model.
|
||||||
[`PODKLAD_CAD_Layout_Tisk.md`](PODKLAD_CAD_Layout_Tisk.md) — CAD, layouty a tisk přes COM.
|
[`PODKLAD_CAD_Layout_Tisk.md`](docs/PODKLAD_CAD_Layout_Tisk.md) — CAD, layouty a tisk přes COM.
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using Rozpisky.Data;
|
||||||
|
using Rozpisky.Models;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Rozpisky.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Slučování dodávané a uživatelské vrstvy číselníků a dopočet rozdílu
|
||||||
|
/// (<see cref="CiselnikyStore.Sluc{T}"/> / <see cref="CiselnikyStore.Rozdil{T}"/>).
|
||||||
|
/// Na téhle logice stojí, jestli uživatel po aktualizaci programu najde svoje záznamy –
|
||||||
|
/// chyba tady se projeví tichou ztrátou dat, kterou nikdo nenahlásí.
|
||||||
|
/// </summary>
|
||||||
|
public class CiselnikyVrstvyTesty
|
||||||
|
{
|
||||||
|
private static List<Jmeno> Vychozi(params string[] jmena)
|
||||||
|
=> jmena.Select(j => new Jmeno { Hodnota = j }).ToList();
|
||||||
|
|
||||||
|
private static ObservableCollection<Jmeno> Sluc(
|
||||||
|
List<Jmeno> vychozi, CiselnikyStore.Vrstva<Jmeno>? uziv)
|
||||||
|
{
|
||||||
|
var cil = new ObservableCollection<Jmeno>();
|
||||||
|
CiselnikyStore.Sluc(cil, vychozi, uziv);
|
||||||
|
return cil;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BezUzivatelskeVrstvyZustaneDodavanaBezeZmeny()
|
||||||
|
{
|
||||||
|
var vysledek = Sluc(Vychozi("Novák", "Svoboda"), null);
|
||||||
|
Assert.Equal(new[] { "Novák", "Svoboda" }, vysledek.Select(j => j.Hodnota));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void VlastniPolozkySeRadiZaDodavane()
|
||||||
|
{
|
||||||
|
var uziv = new CiselnikyStore.Vrstva<Jmeno> { Vlastni = Vychozi("Dvořák") };
|
||||||
|
var vysledek = Sluc(Vychozi("Novák", "Svoboda"), uziv);
|
||||||
|
Assert.Equal(new[] { "Novák", "Svoboda", "Dvořák" }, vysledek.Select(j => j.Hodnota));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SkrytaDodavanaPolozkaSeNeobjevi()
|
||||||
|
{
|
||||||
|
var uziv = new CiselnikyStore.Vrstva<Jmeno> { Skryte = { "Svoboda" } };
|
||||||
|
var vysledek = Sluc(Vychozi("Novák", "Svoboda"), uziv);
|
||||||
|
Assert.Equal(new[] { "Novák" }, vysledek.Select(j => j.Hodnota));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UpravenaDodavanaPolozkaSePrepiseNaSvemMiste()
|
||||||
|
{
|
||||||
|
// Uživatel opravil adresu dodávané organizace – musí zůstat na původní pozici,
|
||||||
|
// jinak by se mu po každé aktualizaci přeházelo pořadí v našeptávači.
|
||||||
|
var vychozi = new List<Organizace>
|
||||||
|
{
|
||||||
|
new() { Nazev = "SŽ Praha", Adresa = "stará" },
|
||||||
|
new() { Nazev = "SŽ Brno", Adresa = "Kounicova 26" },
|
||||||
|
};
|
||||||
|
var uziv = new CiselnikyStore.Vrstva<Organizace>
|
||||||
|
{
|
||||||
|
Vlastni = { new Organizace { Nazev = "SŽ Praha", Adresa = "Dlážděná 1003/7" } },
|
||||||
|
};
|
||||||
|
|
||||||
|
var cil = new ObservableCollection<Organizace>();
|
||||||
|
CiselnikyStore.Sluc(cil, vychozi, uziv);
|
||||||
|
|
||||||
|
Assert.Equal(new[] { "SŽ Praha", "SŽ Brno" }, cil.Select(o => o.Nazev));
|
||||||
|
Assert.Equal("Dlážděná 1003/7", cil[0].Adresa);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ParovaniIgnorujeVelikostPismen()
|
||||||
|
{
|
||||||
|
var uziv = new CiselnikyStore.Vrstva<Jmeno> { Vlastni = Vychozi("NOVÁK") };
|
||||||
|
var vysledek = Sluc(Vychozi("Novák"), uziv);
|
||||||
|
Assert.Single(vysledek);
|
||||||
|
Assert.Equal("NOVÁK", vysledek[0].Hodnota);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RozepsaneRadkySeNeslijouDoJednoho()
|
||||||
|
{
|
||||||
|
// Dva prázdné řádky přidané tlačítkem „Přidat záznam“ mají stejný (prázdný) klíč –
|
||||||
|
// nesmí se spárovat, jinak by uživateli jeden zmizel dřív, než ho stihne vyplnit.
|
||||||
|
var uziv = new CiselnikyStore.Vrstva<Jmeno> { Vlastni = Vychozi("", "") };
|
||||||
|
var vysledek = Sluc(Vychozi("Novák"), uziv);
|
||||||
|
Assert.Equal(3, vysledek.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RozdilZachytiPridaniUpravuISkryti()
|
||||||
|
{
|
||||||
|
var vychozi = Vychozi("Novák", "Svoboda", "Černý");
|
||||||
|
// Uživatel: Svobodu smazal, přidal Dvořáka, Novák zůstal beze změny.
|
||||||
|
var stav = Vychozi("Novák", "Černý", "Dvořák");
|
||||||
|
|
||||||
|
var rozdil = CiselnikyStore.Rozdil(stav, vychozi);
|
||||||
|
|
||||||
|
Assert.Equal(new[] { "Dvořák" }, rozdil.Vlastni.Select(j => j.Hodnota));
|
||||||
|
Assert.Equal(new[] { "Svoboda" }, rozdil.Skryte);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RozdilNepovazujeNezmenenouPolozkuZaVlastni()
|
||||||
|
{
|
||||||
|
var vychozi = Vychozi("Novák", "Svoboda");
|
||||||
|
var rozdil = CiselnikyStore.Rozdil(Vychozi("Novák", "Svoboda"), vychozi);
|
||||||
|
|
||||||
|
Assert.Empty(rozdil.Vlastni);
|
||||||
|
Assert.Empty(rozdil.Skryte);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RozdilZachytiZmenuJinehoNezKlicovehoPole()
|
||||||
|
{
|
||||||
|
var vychozi = new List<Firma>
|
||||||
|
{
|
||||||
|
new() { Nazev = "EXprojekt s.r.o.", Telefon = "+420 533 312 000" },
|
||||||
|
};
|
||||||
|
var stav = new List<Firma>
|
||||||
|
{
|
||||||
|
new() { Nazev = "EXprojekt s.r.o.", Telefon = "+420 111 222 333" },
|
||||||
|
};
|
||||||
|
|
||||||
|
var rozdil = CiselnikyStore.Rozdil(stav, vychozi);
|
||||||
|
|
||||||
|
Assert.Single(rozdil.Vlastni);
|
||||||
|
Assert.Equal("+420 111 222 333", rozdil.Vlastni[0].Telefon);
|
||||||
|
Assert.Empty(rozdil.Skryte); // klíč zůstal, nejde o skrytí
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PrejmenovaniDodavanePolozkySeUloziJakoSkrytiAPridani()
|
||||||
|
{
|
||||||
|
var vychozi = Vychozi("Nowak");
|
||||||
|
var rozdil = CiselnikyStore.Rozdil(Vychozi("Novák"), vychozi);
|
||||||
|
|
||||||
|
Assert.Equal(new[] { "Novák" }, rozdil.Vlastni.Select(j => j.Hodnota));
|
||||||
|
Assert.Equal(new[] { "Nowak" }, rozdil.Skryte);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RozdilASlouceniDavajiZpetPuvodniStav()
|
||||||
|
{
|
||||||
|
// Kolečko uložit → načíst nesmí nic ztratit ani přidat, ať se s číselníkem stalo cokoli.
|
||||||
|
var vychozi = Vychozi("Novák", "Svoboda", "Černý");
|
||||||
|
var stav = Vychozi("Novák", "Černý", "Dvořák", "Horák");
|
||||||
|
|
||||||
|
var vysledek = Sluc(vychozi, CiselnikyStore.Rozdil(stav, vychozi));
|
||||||
|
|
||||||
|
Assert.Equal(stav.Select(j => j.Hodnota), vysledek.Select(j => j.Hodnota));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NovaDodavanaPolozkaSePoAktualizaciObjeviAVlastniZustanou()
|
||||||
|
{
|
||||||
|
// Jádro celé změny: program vydá novou verzi s doplněným číselníkem, uživatelův
|
||||||
|
// rozdíl je pořád ten starý – nová položka musí přibýt, vlastní nesmí zmizet.
|
||||||
|
var stareVychozi = Vychozi("Novák");
|
||||||
|
var rozdilUzivatele = CiselnikyStore.Rozdil(Vychozi("Novák", "Dvořák"), stareVychozi);
|
||||||
|
|
||||||
|
var noveVychozi = Vychozi("Novák", "Svoboda");
|
||||||
|
var vysledek = Sluc(noveVychozi, rozdilUzivatele);
|
||||||
|
|
||||||
|
Assert.Equal(new[] { "Novák", "Svoboda", "Dvořák" }, vysledek.Select(j => j.Hodnota));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SkrytiPrezijeAktualizaciDodavaneVrstvy()
|
||||||
|
{
|
||||||
|
var stareVychozi = Vychozi("Novák", "Svoboda");
|
||||||
|
var rozdilUzivatele = CiselnikyStore.Rozdil(Vychozi("Novák"), stareVychozi);
|
||||||
|
|
||||||
|
// Nová verze programu Svobodu pořád dodává – uživatel ho ale jednou odklidil.
|
||||||
|
var vysledek = Sluc(Vychozi("Novák", "Svoboda", "Černý"), rozdilUzivatele);
|
||||||
|
|
||||||
|
Assert.Equal(new[] { "Novák", "Černý" }, vysledek.Select(j => j.Hodnota));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void KlicSeBerePodleTypuPolozky()
|
||||||
|
{
|
||||||
|
Assert.Equal("SŽ", CiselnikyStore.Klic(new Organizace { Nazev = "SŽ" }));
|
||||||
|
Assert.Equal("EXprojekt", CiselnikyStore.Klic(new Firma { Nazev = "EXprojekt" }));
|
||||||
|
Assert.Equal("Novák", CiselnikyStore.Klic(new Jmeno { Hodnota = "Novák" }));
|
||||||
|
Assert.Equal("DPS", CiselnikyStore.Klic(new VerzeDokumentace { Hodnota = "DPS" }));
|
||||||
|
Assert.Equal("D", CiselnikyStore.Klic(new Cast { Oznaceni = "D", Nazev = "Mosty" }));
|
||||||
|
Assert.Equal("Jihomoravský", CiselnikyStore.Klic(new Kraj { Nazev = "Jihomoravský" }));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace Rozpisky.Tests;
|
namespace Rozpisky.Tests;
|
||||||
|
|
||||||
@@ -8,8 +8,8 @@ public static class TestData
|
|||||||
/// <summary>Kořen repozitáře – nalezen od bin složky testů směrem nahoru podle Rozpisky.sln.</summary>
|
/// <summary>Kořen repozitáře – nalezen od bin složky testů směrem nahoru podle Rozpisky.sln.</summary>
|
||||||
public static string RepoRoot { get; } = NajdiKoren();
|
public static string RepoRoot { get; } = NajdiKoren();
|
||||||
|
|
||||||
/// <summary>Plná cesta k logu ve zdrojové složce <c>Rozpisky\loga</c>.</summary>
|
/// <summary>Plná cesta k logu ve zdrojové složce <c>Rozpisky\Podklady\loga</c>.</summary>
|
||||||
public static string Logo(string fileName) => Path.Combine(RepoRoot, "Rozpisky", "loga", fileName);
|
public static string Logo(string fileName) => Path.Combine(RepoRoot, "Rozpisky", "Podklady", "loga", fileName);
|
||||||
|
|
||||||
/// <summary>Všech 9 log dodávaných s programem.</summary>
|
/// <summary>Všech 9 log dodávaných s programem.</summary>
|
||||||
public static readonly string[] VsechnaLoga =
|
public static readonly string[] VsechnaLoga =
|
||||||
|
|||||||
+11
-6
@@ -1,4 +1,4 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
@@ -7,8 +7,9 @@ namespace Rozpisky
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Globální záchytná síť pro neošetřené výjimky – aplikace nesmí spadnout bez stopy.
|
/// Globální záchytná síť pro neošetřené výjimky – aplikace nesmí spadnout bez stopy.
|
||||||
/// Každá výjimka se zapíše do <c>error.log</c> vedle .exe (stejný portable princip jako
|
/// Každá výjimka se zapíše do <c>error.log</c> v uživatelské složce
|
||||||
/// <c>cad.json</c>/<c>layout.json</c>) a uživateli se ukáže MessageBox. Výjimky na UI
|
/// (<see cref="Data.UzivatelskaData"/>, stejně jako <c>cad.json</c>/<c>layout.json</c>)
|
||||||
|
/// a uživateli se ukáže MessageBox. Výjimky na UI
|
||||||
/// (Dispatcher) vlákně se označí jako zpracované, takže aplikace běží dál – rozdělaný
|
/// (Dispatcher) vlákně se označí jako zpracované, takže aplikace běží dál – rozdělaný
|
||||||
/// projekt zůstane otevřený a jde uložit.
|
/// projekt zůstane otevřený a jde uložit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -18,6 +19,10 @@ namespace Rozpisky
|
|||||||
{
|
{
|
||||||
base.OnStartup(e);
|
base.OnStartup(e);
|
||||||
|
|
||||||
|
// Data starších verzí ležela vedle .exe – přenést do uživatelské složky dřív,
|
||||||
|
// než si na ně sáhne kterýkoli store (ten už čte jen nové umístění).
|
||||||
|
Data.Migrace.PrenesStaraData();
|
||||||
|
|
||||||
// Úklid diagnostického logu tisku, ať po měsících nenaroste do enormních rozměrů.
|
// Úklid diagnostického logu tisku, ať po měsících nenaroste do enormních rozměrů.
|
||||||
Cad.CadTiskService.UklidLog();
|
Cad.CadTiskService.UklidLog();
|
||||||
|
|
||||||
@@ -46,14 +51,14 @@ namespace Rozpisky
|
|||||||
{
|
{
|
||||||
string radek = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {zdroj}: {ex?.ToString() ?? "(neznámá výjimka)"}"
|
string radek = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {zdroj}: {ex?.ToString() ?? "(neznámá výjimka)"}"
|
||||||
+ Environment.NewLine + Environment.NewLine;
|
+ Environment.NewLine + Environment.NewLine;
|
||||||
File.AppendAllText(Path.Combine(AppContext.BaseDirectory, "error.log"), radek, Encoding.UTF8);
|
File.AppendAllText(Data.UzivatelskaData.Cesta("error.log"), radek, Encoding.UTF8);
|
||||||
}
|
}
|
||||||
catch { /* read-only složka apod. – MessageBox se ukáže i tak */ }
|
catch { /* nezapisovatelná složka apod. – MessageBox se ukáže i tak */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UkazChybu(Exception ex) => MessageBox.Show(
|
private static void UkazChybu(Exception ex) => MessageBox.Show(
|
||||||
"Nastala neočekávaná chyba, aplikace ale běží dál – doporučujeme uložit projekt.\n\n" +
|
"Nastala neočekávaná chyba, aplikace ale běží dál – doporučujeme uložit projekt.\n\n" +
|
||||||
$"{ex.Message}\n\nPodrobnosti jsou v souboru error.log vedle aplikace.",
|
$"{ex.Message}\n\nPodrobnosti jsou v souboru error.log ve složce\n{Data.UzivatelskaData.Slozka}",
|
||||||
"Neočekávaná chyba", MessageBoxButton.OK, MessageBoxImage.Error);
|
"Neočekávaná chyba", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Rozpisky.Data;
|
using Rozpisky.Data;
|
||||||
@@ -7,9 +7,9 @@ namespace Rozpisky.Cad;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Načítání/ukládání seznamu podporovaných CAD systémů + naposledy vybraného enginu do JSON souboru
|
/// Načítání/ukládání seznamu podporovaných CAD systémů + naposledy vybraného enginu do JSON souboru
|
||||||
/// <b>vedle .exe</b> (portable). Stejný princip jako <see cref="Data.CiselnikyStore"/> /
|
/// v <b>uživatelské složce</b> (<see cref="Data.UzivatelskaData"/>). CAD je vlastnost stroje
|
||||||
/// <see cref="Dxf.MapovaniStore"/>: CAD je vlastnost stroje (ne zakázky), takže výběr žije mimo
|
/// (ne zakázky), takže výběr žije mimo projekt a je společný všem projektům – a mimo instalační
|
||||||
/// projekt a je globální pro celou instalaci.
|
/// složku, aby o něj uživatel při přeinstalaci nepřišel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class CadEnginesStore
|
public static class CadEnginesStore
|
||||||
{
|
{
|
||||||
@@ -21,8 +21,8 @@ public static class CadEnginesStore
|
|||||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>Výchozí umístění: soubor vedle .exe (portable).</summary>
|
/// <summary>Výchozí umístění: uživatelská složka (viz <see cref="UzivatelskaData"/>).</summary>
|
||||||
public static string DefaultPath => Path.Combine(AppContext.BaseDirectory, SouborNazev);
|
public static string DefaultPath => UzivatelskaData.Cesta(SouborNazev);
|
||||||
|
|
||||||
/// <summary>Uložený výběr CADu: buď předdefinovaný engine (<see cref="VybranyProgId"/>), nebo
|
/// <summary>Uložený výběr CADu: buď předdefinovaný engine (<see cref="VybranyProgId"/>), nebo
|
||||||
/// ruční režim „Manuálně" (<see cref="RucniRezim"/> + <see cref="RucniProgId"/>). Režim a ruční
|
/// ruční režim „Manuálně" (<see cref="RucniRezim"/> + <see cref="RucniProgId"/>). Režim a ruční
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -196,14 +196,15 @@ public static class CadTiskService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var cesta = Path.Combine(AppContext.BaseDirectory, "tisk_diag.log");
|
var cesta = Data.UzivatelskaData.Cesta("tisk_diag.log");
|
||||||
if (File.Exists(cesta) && new FileInfo(cesta).Length > MaxLogBajtu)
|
if (File.Exists(cesta) && new FileInfo(cesta).Length > MaxLogBajtu)
|
||||||
File.Delete(cesta);
|
File.Delete(cesta);
|
||||||
}
|
}
|
||||||
catch { /* read-only složka apod. – úklid nesmí přerušit start */ }
|
catch { /* read-only složka apod. – úklid nesmí přerušit start */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Diagnostický „chlebníček“ – zapíše krok tisku do <c>tisk_diag.log</c> vedle .exe.
|
/// <summary>Diagnostický „chlebníček“ – zapíše krok tisku do <c>tisk_diag.log</c>
|
||||||
|
/// v uživatelské složce.
|
||||||
/// Slouží k lokalizaci tvrdého pádu CADu (nativní chyba v <c>PlotToFile</c>), který nezanechá
|
/// Slouží k lokalizaci tvrdého pádu CADu (nativní chyba v <c>PlotToFile</c>), který nezanechá
|
||||||
/// stopu v error.log ani v log okně (obojí zmizí s procesem). Zapisuje se synchronně (flush ihned),
|
/// stopu v error.log ani v log okně (obojí zmizí s procesem). Zapisuje se synchronně (flush ihned),
|
||||||
/// takže poslední řádek přežije i pád procesu. Diagnostika sama nesmí nic shodit.</summary>
|
/// takže poslední řádek přežije i pád procesu. Diagnostika sama nesmí nic shodit.</summary>
|
||||||
@@ -211,7 +212,7 @@ public static class CadTiskService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
File.AppendAllText(Path.Combine(AppContext.BaseDirectory, "tisk_diag.log"),
|
File.AppendAllText(Data.UzivatelskaData.Cesta("tisk_diag.log"),
|
||||||
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {krok}{Environment.NewLine}");
|
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {krok}{Environment.NewLine}");
|
||||||
}
|
}
|
||||||
catch { /* read-only složka apod. – nesmí přerušit tisk */ }
|
catch { /* read-only složka apod. – nesmí přerušit tisk */ }
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Rozpisky.Data;
|
using Rozpisky.Data;
|
||||||
@@ -6,8 +6,8 @@ using Rozpisky.Data;
|
|||||||
namespace Rozpisky.Cad;
|
namespace Rozpisky.Cad;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Načítání/ukládání <see cref="LayoutConfig"/> do JSON souboru <b>vedle .exe</b> (portable). Stejný
|
/// Načítání/ukládání <see cref="LayoutConfig"/> do JSON souboru v <b>uživatelské složce</b>
|
||||||
/// princip jako <see cref="CadEnginesStore"/> – konfigurace layoutů/tisku je vlastnost stroje/instalace
|
/// (<see cref="Data.UzivatelskaData"/>). Stejný princip jako <see cref="CadEnginesStore"/> – konfigurace layoutů/tisku je vlastnost stroje/instalace
|
||||||
/// (bloky a hladiny šablony, dostupné tiskárny), needituje se přes UI, jen ručně v souboru.
|
/// (bloky a hladiny šablony, dostupné tiskárny), needituje se přes UI, jen ručně v souboru.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class LayoutConfigStore
|
public static class LayoutConfigStore
|
||||||
@@ -20,8 +20,8 @@ public static class LayoutConfigStore
|
|||||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>Výchozí umístění: soubor vedle .exe (portable).</summary>
|
/// <summary>Výchozí umístění: uživatelská složka (viz <see cref="UzivatelskaData"/>).</summary>
|
||||||
public static string DefaultPath => Path.Combine(AppContext.BaseDirectory, SouborNazev);
|
public static string DefaultPath => UzivatelskaData.Cesta(SouborNazev);
|
||||||
|
|
||||||
/// <summary>Načte konfiguraci. Když soubor chybí/je poškozený, vrátí vestavěné výchozí hodnoty
|
/// <summary>Načte konfiguraci. Když soubor chybí/je poškozený, vrátí vestavěné výchozí hodnoty
|
||||||
/// (a rovnou je uloží, aby šel soubor dál ručně upravovat).</summary>
|
/// (a rovnou je uloží, aby šel soubor dál ručně upravovat).</summary>
|
||||||
|
|||||||
+224
-41
@@ -1,4 +1,4 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@@ -7,64 +7,213 @@ using Rozpisky.Models;
|
|||||||
namespace Rozpisky.Data;
|
namespace Rozpisky.Data;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Načítání/ukládání sdílených číselníků (organizace, firmy, jména, verze, části)
|
/// Sdílené číselníky (organizace, firmy, jména, verze, části, kraje) ve <b>dvou vrstvách</b>:
|
||||||
/// do JSON souboru <b>vedle .exe</b> (portable). Číselníky jsou společné všem projektům
|
/// <list type="bullet">
|
||||||
/// (na rozdíl od <c>.rzp</c> přes <see cref="ProjektStore"/>), takže žijí mimo projekt.
|
/// <item><b>dodávaná</b> – <c>ciselniky.vychozi.json</c> vedle .exe, jen ke čtení; každá instalace
|
||||||
/// Bez NuGet závislostí – System.Text.Json je součást .NET.
|
/// ji obnoví, takže smí s novou verzí programu růst a měnit se;</item>
|
||||||
|
/// <item><b>uživatelská</b> – <c>ciselniky.user.json</c> v uživatelské složce
|
||||||
|
/// (<see cref="UzivatelskaData"/>), kde je uložený jen <b>rozdíl</b> proti dodávané vrstvě.</item>
|
||||||
|
/// </list>
|
||||||
|
/// Důvod rozdělení: číselníky uživatel průběžně doplňuje (ručně i „samoučením“ jmen z projektů).
|
||||||
|
/// Kdyby žily v jednom souboru vedle .exe, přeinstalace novou verzí by je přepsala a uživatel by
|
||||||
|
/// o svoje záznamy přišel. Uložením rozdílu se obojí smíří – uživatel si nese své, program své.
|
||||||
|
/// <para>Bez NuGet závislostí – System.Text.Json je součást .NET.</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class CiselnikyStore
|
public static class CiselnikyStore
|
||||||
{
|
{
|
||||||
public const string SouborNazev = "ciselniky.json";
|
/// <summary>Dodávaná vrstva vedle .exe (jen ke čtení).</summary>
|
||||||
|
public const string VychoziNazev = "ciselniky.vychozi.json";
|
||||||
|
|
||||||
|
/// <summary>Uživatelská vrstva (rozdíl proti dodávané) v uživatelské složce.</summary>
|
||||||
|
public const string UzivatelskyNazev = "ciselniky.user.json";
|
||||||
|
|
||||||
|
/// <summary>Jednovrstvý soubor starších verzí vedle .exe – zdroj pro jednorázovou migraci.</summary>
|
||||||
|
public const string StaryNazev = "ciselniky.json";
|
||||||
|
|
||||||
private static readonly JsonSerializerOptions Opts = new()
|
private static readonly JsonSerializerOptions Opts = new()
|
||||||
{
|
{
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
// Diakritika čitelně (ě š č…), ne jako \uXXXX.
|
// Diakritika čitelně (ě š č…), ne jako escape sekvence.
|
||||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>Výchozí umístění: soubor vedle .exe (portable). Lze přepsat parametrem cesty.</summary>
|
public static string VychoziPath => Path.Combine(AppContext.BaseDirectory, VychoziNazev);
|
||||||
public static string DefaultPath => Path.Combine(AppContext.BaseDirectory, SouborNazev);
|
public static string UzivatelskyPath => UzivatelskaData.Cesta(UzivatelskyNazev);
|
||||||
|
public static string StaryPath => Path.Combine(AppContext.BaseDirectory, StaryNazev);
|
||||||
|
|
||||||
/// <summary>Naplní cílové číselníky obsahem z JSON. Vrací false, když soubor neexistuje
|
/// <summary>Dodávaná vrstva načtená při startu. Drží se po celý běh, protože je referenčním
|
||||||
/// nebo je poškozený – volající pak nasází výchozí obsah (a uloží nový soubor).</summary>
|
/// bodem pro dopočet rozdílu při každém uložení – kdyby se načítala znovu, změna souboru
|
||||||
public static bool LoadInto(Ciselniky target, string? path = null)
|
/// za běhu by rozdíl rozhodila.</summary>
|
||||||
|
private static Dto? _vychozi;
|
||||||
|
|
||||||
|
/// <summary>Načte číselníky: dodávaná vrstva + rozdíl uživatele. Když dodávaná vrstva chybí
|
||||||
|
/// (poškozená instalace), použije se vestavěný <see cref="DemoData.SeedCiselniky"/>, ať má
|
||||||
|
/// uživatel s čím pracovat. Před prvním načtením proběhne migrace ze starého jednovrstvého
|
||||||
|
/// souboru.</summary>
|
||||||
|
public static void Nacti(Ciselniky cil)
|
||||||
{
|
{
|
||||||
path ??= DefaultPath;
|
_vychozi = NactiVychozi();
|
||||||
if (!File.Exists(path)) return false;
|
MigrujStarySoubor();
|
||||||
|
|
||||||
|
var uziv = NactiUzivatelskou();
|
||||||
|
Sluc(cil.Organizace, _vychozi.Organizace, uziv?.Organizace);
|
||||||
|
Sluc(cil.Firmy, _vychozi.Firmy, uziv?.Firmy);
|
||||||
|
Sluc(cil.Jmena, _vychozi.Jmena, uziv?.Jmena);
|
||||||
|
Sluc(cil.VerzeDokumentace, _vychozi.VerzeDokumentace, uziv?.VerzeDokumentace);
|
||||||
|
Sluc(cil.Casti, _vychozi.Casti, uziv?.Casti);
|
||||||
|
Sluc(cil.Kraje, _vychozi.Kraje, uziv?.Kraje);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Uloží <b>rozdíl</b> aktuálního stavu proti dodávané vrstvě. Chyby zápisu propustí
|
||||||
|
/// volajícímu, ať je ošetří dle kontextu (autosave je spolkne, ruční uložení zahlásí).</summary>
|
||||||
|
public static void UlozRozdil(Ciselniky c)
|
||||||
|
{
|
||||||
|
var v = _vychozi ??= NactiVychozi();
|
||||||
|
var dto = new UzivatelskyDto
|
||||||
|
{
|
||||||
|
Organizace = Rozdil(c.Organizace, v.Organizace),
|
||||||
|
Firmy = Rozdil(c.Firmy, v.Firmy),
|
||||||
|
Jmena = Rozdil(c.Jmena, v.Jmena),
|
||||||
|
VerzeDokumentace = Rozdil(c.VerzeDokumentace, v.VerzeDokumentace),
|
||||||
|
Casti = Rozdil(c.Casti, v.Casti),
|
||||||
|
Kraje = Rozdil(c.Kraje, v.Kraje),
|
||||||
|
};
|
||||||
|
JsonConfigFile.ZapisAtomicky(UzivatelskyPath, JsonSerializer.Serialize(dto, Opts));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Vrstvy ----
|
||||||
|
|
||||||
|
private static Dto NactiVychozi()
|
||||||
|
{
|
||||||
|
if (File.Exists(VychoziPath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = JsonSerializer.Deserialize<Dto>(File.ReadAllText(VychoziPath), Opts);
|
||||||
|
if (data is not null) return data;
|
||||||
|
JsonConfigFile.ZalohujAOhlasPoskozeny(VychoziPath);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is IOException or JsonException or UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
JsonConfigFile.ZalohujAOhlasPoskozeny(VychoziPath, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Chybějící/poškozená dodávaná vrstva se nezapisuje zpátky (je součástí instalace) –
|
||||||
|
// jen se nahradí vestavěným obsahem, ať aplikace není bez číselníků.
|
||||||
|
var nahrada = new Ciselniky();
|
||||||
|
DemoData.SeedCiselniky(nahrada);
|
||||||
|
return Dto.Z(nahrada);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UzivatelskyDto? NactiUzivatelskou()
|
||||||
|
{
|
||||||
|
if (!File.Exists(UzivatelskyPath)) return null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Čteme do DTO se settable List<T>: System.Text.Json neplní get-only ObservableCollection.
|
return JsonSerializer.Deserialize<UzivatelskyDto>(File.ReadAllText(UzivatelskyPath), Opts);
|
||||||
var data = JsonSerializer.Deserialize<Dto>(File.ReadAllText(path), Opts);
|
|
||||||
if (data is null)
|
|
||||||
{
|
|
||||||
// Soubor existuje, ale nenese data („null") → stejné zacházení jako s rozbitým JSON.
|
|
||||||
JsonConfigFile.ZalohujAOhlasPoskozeny(path);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Replace(target.Organizace, data.Organizace);
|
|
||||||
Replace(target.Firmy, data.Firmy);
|
|
||||||
Replace(target.Jmena, data.Jmena);
|
|
||||||
Replace(target.VerzeDokumentace, data.VerzeDokumentace);
|
|
||||||
Replace(target.Casti, data.Casti);
|
|
||||||
Replace(target.Kraje, data.Kraje);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (ex is IOException or JsonException or UnauthorizedAccessException)
|
catch (Exception ex) when (ex is IOException or JsonException or UnauthorizedAccessException)
|
||||||
{
|
{
|
||||||
// Poškozený / nečitelný soubor → fallback na seed. Volající pak soubor přepíše výchozím
|
// Ruční editace s překlepem nesmí uživatelovy záznamy tiše zahodit – zazálohovat a ohlásit.
|
||||||
// obsahem, proto se originál nejdřív zazálohuje a uživatel dozví (jinak by o své
|
JsonConfigFile.ZalohujAOhlasPoskozeny(UzivatelskyPath, ex);
|
||||||
// číselníky přišel tiše).
|
return null;
|
||||||
JsonConfigFile.ZalohujAOhlasPoskozeny(path, ex);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Uloží číselníky do JSON (atomicky přes .tmp – pád uprostřed zápisu nesmí soubor
|
/// <summary>Jednorázový převod ze starého jednovrstvého <c>ciselniky.json</c> vedle .exe:
|
||||||
/// poškodit). Chyby zápisu (např. read-only složka u .exe v Program Files) propustí volajícímu,
|
/// jeho obsah je „aktuální stav“ uživatele, takže se z něj dopočítá rozdíl proti dodávané
|
||||||
/// ať je může ošetřit dle kontextu (autosave je spolkne, ruční uložení zahlásí).</summary>
|
/// vrstvě a uloží jako uživatelská vrstva. Originál se jen přejmenuje, nikdy nemaže.</summary>
|
||||||
public static void Save(Ciselniky c, string? path = null)
|
private static void MigrujStarySoubor()
|
||||||
=> JsonConfigFile.ZapisAtomicky(path ?? DefaultPath, JsonSerializer.Serialize(c, Opts));
|
{
|
||||||
|
if (File.Exists(UzivatelskyPath) || !File.Exists(StaryPath)) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var stary = JsonSerializer.Deserialize<Dto>(File.ReadAllText(StaryPath), Opts);
|
||||||
|
if (stary is null) return;
|
||||||
|
|
||||||
|
var stav = new Ciselniky();
|
||||||
|
Replace(stav.Organizace, stary.Organizace);
|
||||||
|
Replace(stav.Firmy, stary.Firmy);
|
||||||
|
Replace(stav.Jmena, stary.Jmena);
|
||||||
|
Replace(stav.VerzeDokumentace, stary.VerzeDokumentace);
|
||||||
|
Replace(stav.Casti, stary.Casti);
|
||||||
|
Replace(stav.Kraje, stary.Kraje);
|
||||||
|
|
||||||
|
UlozRozdil(stav);
|
||||||
|
Migrace.OznacJakoPrenesene(StaryPath);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is IOException or JsonException or UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
// Nepovedená migrace nesmí zabránit startu; starý soubor zůstane a zkusí se příště.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Slučování a rozdíl ----
|
||||||
|
|
||||||
|
/// <summary>Klíč položky – podle něj se poznává „tatáž“ položka napříč vrstvami.
|
||||||
|
/// Porovnává se bez ohledu na velikost písmen, stejně jako to dělá samoučení jmen.</summary>
|
||||||
|
internal static string Klic(object polozka) => polozka switch
|
||||||
|
{
|
||||||
|
Organizace o => o.Nazev,
|
||||||
|
Firma f => f.Nazev,
|
||||||
|
Jmeno j => j.Hodnota,
|
||||||
|
VerzeDokumentace v => v.Hodnota,
|
||||||
|
Cast c => c.Oznaceni,
|
||||||
|
Kraj k => k.Nazev,
|
||||||
|
_ => "",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>Dodávaná vrstva → odebrat skryté → přepsat/doplnit vlastními. Pořadí dodávaných
|
||||||
|
/// položek zůstane zachované, vlastní se řadí za ně.</summary>
|
||||||
|
internal static void Sluc<T>(ObservableCollection<T> cil, List<T> vychozi, Vrstva<T>? uziv)
|
||||||
|
{
|
||||||
|
var skryte = new HashSet<string>(uziv?.Skryte ?? new List<string>(), StringComparer.OrdinalIgnoreCase);
|
||||||
|
var vysledek = vychozi.Where(x => !skryte.Contains(Klic(x!))).ToList();
|
||||||
|
|
||||||
|
foreach (var vlastni in uziv?.Vlastni ?? new List<T>())
|
||||||
|
{
|
||||||
|
var klic = Klic(vlastni!);
|
||||||
|
// Prázdný klíč = rozepsaný řádek; ten se nikdy nespáruje, jen připojí (jinak by se
|
||||||
|
// dva rozepsané řádky slily do jednoho).
|
||||||
|
int i = string.IsNullOrEmpty(klic)
|
||||||
|
? -1
|
||||||
|
: vysledek.FindIndex(x => string.Equals(Klic(x!), klic, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (i >= 0) vysledek[i] = vlastni; else vysledek.Add(vlastni);
|
||||||
|
}
|
||||||
|
|
||||||
|
Replace(cil, vysledek);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Rozdíl aktuálního stavu proti dodávané vrstvě: co v ní není nebo se od ní liší,
|
||||||
|
/// jde do <c>Vlastni</c>; dodávaná položka, která ve stavu chybí, jde do <c>Skryte</c>.</summary>
|
||||||
|
internal static Vrstva<T> Rozdil<T>(IEnumerable<T> aktualni, List<T> vychozi)
|
||||||
|
{
|
||||||
|
var stav = aktualni.ToList();
|
||||||
|
var vychoziMapa = vychozi
|
||||||
|
.GroupBy(x => Klic(x!), StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
|
||||||
|
var klicStavu = new HashSet<string>(stav.Select(x => Klic(x!)), StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var vrstva = new Vrstva<T>();
|
||||||
|
foreach (var polozka in stav)
|
||||||
|
{
|
||||||
|
var klic = Klic(polozka!);
|
||||||
|
if (string.IsNullOrEmpty(klic) ||
|
||||||
|
!vychoziMapa.TryGetValue(klic, out var vzor) ||
|
||||||
|
!StejnyObsah(polozka, vzor))
|
||||||
|
vrstva.Vlastni.Add(polozka);
|
||||||
|
}
|
||||||
|
foreach (var klic in vychoziMapa.Keys)
|
||||||
|
if (!string.IsNullOrEmpty(klic) && !klicStavu.Contains(klic))
|
||||||
|
vrstva.Skryte.Add(klic);
|
||||||
|
|
||||||
|
return vrstva;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Porovnání obsahu přes serializaci – jedna metoda místo šesti implementací rovnosti
|
||||||
|
/// a automaticky pokryje i pole, která k položkám teprve přibudou.</summary>
|
||||||
|
private static bool StejnyObsah<T>(T a, T b)
|
||||||
|
=> JsonSerializer.Serialize(a, Opts) == JsonSerializer.Serialize(b, Opts);
|
||||||
|
|
||||||
private static void Replace<T>(ObservableCollection<T> target, IEnumerable<T> src)
|
private static void Replace<T>(ObservableCollection<T> target, IEnumerable<T> src)
|
||||||
{
|
{
|
||||||
@@ -72,8 +221,11 @@ public static class CiselnikyStore
|
|||||||
foreach (var x in src) target.Add(x);
|
foreach (var x in src) target.Add(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Přechodný tvar pro čtení – settable List<T> (na rozdíl od get-only kolekcí v
|
// ---- Přenosové tvary ----
|
||||||
/// <see cref="Ciselniky"/>). Názvy vlastností musí odpovídat <see cref="Ciselniky"/> i zápisu.</summary>
|
|
||||||
|
/// <summary>Tvar dodávané vrstvy (i starého jednovrstvého souboru) – settable List<T>
|
||||||
|
/// na rozdíl od get-only kolekcí v <see cref="Ciselniky"/>, které System.Text.Json neplní.
|
||||||
|
/// Názvy vlastností musí odpovídat <see cref="Ciselniky"/>.</summary>
|
||||||
private sealed class Dto
|
private sealed class Dto
|
||||||
{
|
{
|
||||||
public List<Organizace> Organizace { get; set; } = new();
|
public List<Organizace> Organizace { get; set; } = new();
|
||||||
@@ -82,5 +234,36 @@ public static class CiselnikyStore
|
|||||||
public List<VerzeDokumentace> VerzeDokumentace { get; set; } = new();
|
public List<VerzeDokumentace> VerzeDokumentace { get; set; } = new();
|
||||||
public List<Cast> Casti { get; set; } = new();
|
public List<Cast> Casti { get; set; } = new();
|
||||||
public List<Kraj> Kraje { get; set; } = new();
|
public List<Kraj> Kraje { get; set; } = new();
|
||||||
|
|
||||||
|
public static Dto Z(Ciselniky c) => new()
|
||||||
|
{
|
||||||
|
Organizace = c.Organizace.ToList(),
|
||||||
|
Firmy = c.Firmy.ToList(),
|
||||||
|
Jmena = c.Jmena.ToList(),
|
||||||
|
VerzeDokumentace = c.VerzeDokumentace.ToList(),
|
||||||
|
Casti = c.Casti.ToList(),
|
||||||
|
Kraje = c.Kraje.ToList(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Jeden číselník v uživatelské vrstvě: co uživatel přidal nebo upravil
|
||||||
|
/// (<see cref="Vlastni"/>) a které dodávané položky si odklidil (<see cref="Skryte"/> – klíče).</summary>
|
||||||
|
internal sealed class Vrstva<T>
|
||||||
|
{
|
||||||
|
public List<T> Vlastni { get; set; } = new();
|
||||||
|
public List<string> Skryte { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Tvar uživatelské vrstvy. <see cref="Verze"/> je rezerva pro budoucí změnu formátu –
|
||||||
|
/// bez ní by starší soubor nešlo odlišit od nového.</summary>
|
||||||
|
private sealed class UzivatelskyDto
|
||||||
|
{
|
||||||
|
public int Verze { get; set; } = 1;
|
||||||
|
public Vrstva<Organizace> Organizace { get; set; } = new();
|
||||||
|
public Vrstva<Firma> Firmy { get; set; } = new();
|
||||||
|
public Vrstva<Jmeno> Jmena { get; set; } = new();
|
||||||
|
public Vrstva<VerzeDokumentace> VerzeDokumentace { get; set; } = new();
|
||||||
|
public Vrstva<Cast> Casti { get; set; } = new();
|
||||||
|
public Vrstva<Kraj> Kraje { get; set; } = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
|
||||||
namespace Rozpisky.Data;
|
namespace Rozpisky.Data;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Společné zacházení s JSON konfiguračními soubory vedle .exe (<c>ciselniky.json</c>,
|
/// Společné zacházení s JSON konfiguračními soubory (<c>ciselniky.user.json</c>, <c>cad.json</c>,
|
||||||
/// <c>cad.json</c>, <c>layout.json</c>, <c>mapovani.json</c>). Řeší dvě slabiny přímého
|
/// <c>layout.json</c> v uživatelské složce, <c>mapovani.json</c> vedle .exe). Řeší dvě slabiny přímého
|
||||||
/// <c>File.WriteAllText</c>: pád uprostřed zápisu nesmí poškodit původní soubor (atomický zápis
|
/// <c>File.WriteAllText</c>: pád uprostřed zápisu nesmí poškodit původní soubor (atomický zápis
|
||||||
/// přes .tmp) a poškozený soubor se nesmí tiše zahodit – ručně editovaný config s překlepem by se
|
/// přes .tmp) a poškozený soubor se nesmí tiše zahodit – ručně editovaný config s překlepem by se
|
||||||
/// jinak bez varování přepsal výchozími hodnotami a uživatel by přišel o vlastní data.
|
/// jinak bez varování přepsal výchozími hodnotami a uživatel by přišel o vlastní data.
|
||||||
@@ -41,7 +41,7 @@ public static class JsonConfigFile
|
|||||||
(zaloha is null
|
(zaloha is null
|
||||||
? "Zálohu původního souboru se nepodařilo vytvořit."
|
? "Zálohu původního souboru se nepodařilo vytvořit."
|
||||||
: $"Původní obsah je zálohovaný v:\n{zaloha}") +
|
: $"Původní obsah je zálohovaný v:\n{zaloha}") +
|
||||||
"\n\nPodrobnosti jsou v souboru error.log vedle aplikace.";
|
$"\n\nPodrobnosti jsou v souboru error.log ve složce\n{UzivatelskaData.Slozka}";
|
||||||
// BeginInvoke: hláška se ukáže až po rozběhnutí UI smyčky (config se čte před oknem).
|
// BeginInvoke: hláška se ukáže až po rozběhnutí UI smyčky (config se čte před oknem).
|
||||||
Application.Current?.Dispatcher.BeginInvoke(() =>
|
Application.Current?.Dispatcher.BeginInvoke(() =>
|
||||||
MessageBox.Show(zprava, "Poškozený konfigurační soubor",
|
MessageBox.Show(zprava, "Poškozený konfigurační soubor",
|
||||||
@@ -54,7 +54,7 @@ public static class JsonConfigFile
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
File.AppendAllText(Path.Combine(AppContext.BaseDirectory, "error.log"),
|
File.AppendAllText(UzivatelskaData.Cesta("error.log"),
|
||||||
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] Config: {text}{Environment.NewLine}{Environment.NewLine}",
|
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] Config: {text}{Environment.NewLine}{Environment.NewLine}",
|
||||||
Encoding.UTF8);
|
Encoding.UTF8);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Rozpisky.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Jednorázový přesun dat starších verzí, které zapisovaly vedle .exe, do uživatelské složky
|
||||||
|
/// (<see cref="UzivatelskaData"/>). Bez toho by uživatel po aktualizaci našel prázdné číselníky
|
||||||
|
/// a přenastavený CAD. Volá se jednou při startu, ještě než si na data sáhne kterýkoli store.
|
||||||
|
/// <para>
|
||||||
|
/// Zásada: nikdy nic nemazat a nikdy nepřepsat novější data. Soubor se přenese jen tehdy, když
|
||||||
|
/// v cíli ještě není; originál zůstane ležet jako <c>*.migrovano</c> (jediná kopie uživatelových dat).
|
||||||
|
/// Selhání nesmí shodit start – horší než nepřenesená data je aplikace, která nenaběhne.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public static class Migrace
|
||||||
|
{
|
||||||
|
/// <summary>Přípona, kterou dostane původní soubor po přenesení.</summary>
|
||||||
|
public const string PriponaHotovo = ".migrovano";
|
||||||
|
|
||||||
|
/// <summary>Přenese <c>cad.json</c> a <c>layout.json</c> ze složky aplikace do uživatelské.
|
||||||
|
/// Číselníky mají vlastní cestu (rozpad na vrstvy) – viz <see cref="CiselnikyStore"/>.</summary>
|
||||||
|
public static void PrenesStaraData()
|
||||||
|
{
|
||||||
|
Prenes(CadEnginesStoreNazev);
|
||||||
|
Prenes(LayoutConfigStoreNazev);
|
||||||
|
}
|
||||||
|
|
||||||
|
private const string CadEnginesStoreNazev = "cad.json";
|
||||||
|
private const string LayoutConfigStoreNazev = "layout.json";
|
||||||
|
|
||||||
|
/// <summary>Přesune soubor ze složky aplikace do uživatelské, pokud tam ještě není.
|
||||||
|
/// V portable režimu jsou obě složky totožné a neudělá se nic.</summary>
|
||||||
|
private static void Prenes(string nazevSouboru)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var zdroj = Path.Combine(AppContext.BaseDirectory, nazevSouboru);
|
||||||
|
var cil = UzivatelskaData.Cesta(nazevSouboru);
|
||||||
|
if (string.Equals(zdroj, cil, StringComparison.OrdinalIgnoreCase)) return;
|
||||||
|
if (!File.Exists(zdroj) || File.Exists(cil)) return;
|
||||||
|
|
||||||
|
File.Copy(zdroj, cil);
|
||||||
|
OznacJakoPrenesene(zdroj);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
// Nezapisovatelná instalační složka apod. – data zůstanou tam, kde jsou, a zkusí se
|
||||||
|
// přenést zase příště. Start aplikace to zastavit nesmí.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Přejmenuje původní soubor na <c>*.migrovano</c>, aby se přenos neopakoval a aby
|
||||||
|
/// starší verze programu (kdyby ji uživatel vrátil) nezmátla už neaktuální data. Nedaří-li se
|
||||||
|
/// to (soubor jen pro čtení), nevadí – kopie v cíli už existuje a další běh přenos přeskočí.</summary>
|
||||||
|
internal static void OznacJakoPrenesene(string cesta)
|
||||||
|
{
|
||||||
|
try { File.Move(cesta, cesta + PriponaHotovo, overwrite: true); }
|
||||||
|
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Rozpisky.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Jediné místo pro data, která píše <b>uživatel</b> (číselníky, výběr CADu, konfigurace layoutů,
|
||||||
|
/// logy). Leží v profilu uživatele (<c>%APPDATA%\Rozpisky</c>), ne vedle .exe – přeinstalace novou
|
||||||
|
/// verzí ani .exe v <c>Program Files</c> (jen pro čtení) je tak nemůžou zničit.
|
||||||
|
/// <para>
|
||||||
|
/// Podklady <b>dodávané</b> s programem (šablona, mustr, loga, výchozí číselníky, mapování) zůstávají
|
||||||
|
/// vedle .exe a čtou se přes <see cref="AppContext.BaseDirectory"/> – ty se naopak při každé instalaci
|
||||||
|
/// obnovují. Hranice mezi tím, co se instaluje, a tím, co patří uživateli, vede přesně tudy.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// <b>Portable režim:</b> leží-li vedle .exe soubor <c>portable.txt</c>, vrátí se složka aplikace.
|
||||||
|
/// Slouží k běhu z USB a k vývoji, kdy je pohodlnější mít všechno v jedné složce s buildem.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public static class UzivatelskaData
|
||||||
|
{
|
||||||
|
/// <summary>Značka portable režimu vedle .exe (obsah souboru se neřeší, stačí že existuje).</summary>
|
||||||
|
public const string PortableZnacka = "portable.txt";
|
||||||
|
|
||||||
|
private const string NazevSlozky = "Rozpisky";
|
||||||
|
|
||||||
|
private static string? _slozka;
|
||||||
|
|
||||||
|
/// <summary>Složka pro zapisovaná data. Počítá se jednou za běh a při prvním použití se vytvoří.
|
||||||
|
/// Když se vytvořit nepodaří (výjimečné – profil uživatele je zapisovatelný), spadne se zpátky
|
||||||
|
/// na složku aplikace, ať se program aspoň spustí.</summary>
|
||||||
|
public static string Slozka => _slozka ??= UrciSlozku();
|
||||||
|
|
||||||
|
/// <summary>Plná cesta k souboru se zapisovanými daty.</summary>
|
||||||
|
public static string Cesta(string nazevSouboru) => Path.Combine(Slozka, nazevSouboru);
|
||||||
|
|
||||||
|
private static string UrciSlozku()
|
||||||
|
{
|
||||||
|
var vedleExe = AppContext.BaseDirectory;
|
||||||
|
if (File.Exists(Path.Combine(vedleExe, PortableZnacka))) return vedleExe;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var slozka = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), NazevSlozky);
|
||||||
|
Directory.CreateDirectory(slozka);
|
||||||
|
return slozka;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
return vedleExe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-12
@@ -23,20 +23,28 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- DXF šablona rozpisky (leží v kořeni solution) – kopíruje se vedle .exe. -->
|
<!-- Slučování vrstev číselníků (CiselnikyStore.Sluc/Rozdil) je čistá logika, na které závisí,
|
||||||
<None Include="..\Rozpiska.dxf" Link="Rozpiska.dxf" CopyToOutputDirectory="PreserveNewest" />
|
jestli uživatel po aktualizaci najde svoje záznamy – testuje se přímo, bez souborů. -->
|
||||||
|
<InternalsVisibleTo Include="Rozpisky.Tests" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Podklady dodávané s programem (Podklady\) – kopírují se vedle .exe do ploché struktury
|
||||||
|
(+ podsložka loga\). Link určuje umístění ve výstupu; zdrojový strom je tím oddělený
|
||||||
|
od rozvržení instalace. Aplikace je jen čte, nikdy do nich nezapisuje. -->
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- DXF šablona rozpisky. -->
|
||||||
|
<None Update="Podklady\Rozpiska.dxf" Link="Rozpiska.dxf" CopyToOutputDirectory="PreserveNewest" />
|
||||||
<!-- Mustr „Seznam příloh“ (Otevřená verze .xlsx) – šablona vyplňovaná při exportu seznamu. -->
|
<!-- Mustr „Seznam příloh“ (Otevřená verze .xlsx) – šablona vyplňovaná při exportu seznamu. -->
|
||||||
<None Include="..\SEZNAM.xlsx" Link="SEZNAM.xlsx" CopyToOutputDirectory="PreserveNewest" />
|
<None Update="Podklady\SEZNAM.xlsx" Link="SEZNAM.xlsx" CopyToOutputDirectory="PreserveNewest" />
|
||||||
<!-- Závazný manuál SŽ (PDF) – kopíruje se vedle .exe pro otevření z nabídky Nápověda. -->
|
<!-- Závazný manuál SŽ (PDF) – otevírá se z nabídky Nápověda. -->
|
||||||
<None Include="..\SZ_SM011_P10_Manual_v6.pdf" Link="SZ_SM011_P10_Manual_v6.pdf" CopyToOutputDirectory="PreserveNewest" />
|
<None Update="Podklady\SZ_SM011_P10_Manual_v6.pdf" Link="SZ_SM011_P10_Manual_v6.pdf" CopyToOutputDirectory="PreserveNewest" />
|
||||||
<!-- Výchozí číselníky (data z původního Excelu) – dodávají se vedle .exe.
|
<!-- Dodávaná vrstva číselníků (data z původního Excelu). Aplikace ji jen čte – uživatelovy
|
||||||
Update (ne Include): .json je už implicitní None item. PreserveNewest = uživatelovy
|
záznamy žijí odděleně v ciselniky.user.json v jeho profilu, viz CiselnikyStore. -->
|
||||||
úpravy v output složce se při rebuildu nepřepíšou (přepíše jen novější zdroj). -->
|
<None Update="Podklady\ciselniky.vychozi.json" Link="ciselniky.vychozi.json" CopyToOutputDirectory="PreserveNewest" />
|
||||||
<None Update="ciselniky.json" CopyToOutputDirectory="PreserveNewest" />
|
|
||||||
<!-- Mapování „pole programu → DXF atribut“ (RozpiskaValues přes reflexi). Editovatelné vedle .exe. -->
|
<!-- Mapování „pole programu → DXF atribut“ (RozpiskaValues přes reflexi). Editovatelné vedle .exe. -->
|
||||||
<None Update="mapovani.json" CopyToOutputDirectory="PreserveNewest" />
|
<None Update="Podklady\mapovani.json" Link="mapovani.json" CopyToOutputDirectory="PreserveNewest" />
|
||||||
<!-- Loga dodávaná s programem (DXF) – kopírují se do složky loga\ vedle .exe. -->
|
<!-- Loga dodávaná s programem (DXF) – do podsložky loga\ vedle .exe. -->
|
||||||
<None Include="loga\**\*" CopyToOutputDirectory="PreserveNewest" LinkBase="loga" />
|
<None Update="Podklady\loga\*" Link="loga\%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@@ -511,33 +511,24 @@ public sealed class MainViewModel : ObservableObject
|
|||||||
|
|
||||||
// ---- Sdílené číselníky (mimo projekt, vedle .exe) ----
|
// ---- Sdílené číselníky (mimo projekt, vedle .exe) ----
|
||||||
|
|
||||||
/// <summary>Načte číselníky z <c>ciselniky.json</c> vedle .exe; když chybí, nasází výchozí obsah
|
/// <summary>Načte číselníky: dodávaná vrstva vedle .exe + rozdíl uživatele z jeho složky
|
||||||
/// a soubor vytvoří. Pak zapne autosave při přidání/odebrání položky.</summary>
|
/// (<see cref="CiselnikyStore"/>). Pak zapne autosave při přidání/odebrání položky.</summary>
|
||||||
private void NactiCiselniky()
|
private void NactiCiselniky()
|
||||||
{
|
{
|
||||||
if (!CiselnikyStore.LoadInto(Ciselniky))
|
CiselnikyStore.Nacti(Ciselniky);
|
||||||
{
|
|
||||||
DemoData.SeedCiselniky(Ciselniky);
|
|
||||||
UlozCiselniky(); // vytvoř výchozí soubor
|
|
||||||
}
|
|
||||||
else if (Ciselniky.Kraje.Count == 0)
|
|
||||||
{
|
|
||||||
DemoData.SeedKraje(Ciselniky); // kraje přibyly později → doplň i do starších ciselniky.json
|
|
||||||
UlozCiselniky();
|
|
||||||
}
|
|
||||||
// Autosave na přidání/odebrání položky; úpravy v buňkách se uloží při zavření okna.
|
// Autosave na přidání/odebrání položky; úpravy v buňkách se uloží při zavření okna.
|
||||||
foreach (var col in new INotifyCollectionChanged[]
|
foreach (var col in new INotifyCollectionChanged[]
|
||||||
{ Ciselniky.Organizace, Ciselniky.Firmy, Ciselniky.Jmena, Ciselniky.VerzeDokumentace, Ciselniky.Casti, Ciselniky.Kraje })
|
{ Ciselniky.Organizace, Ciselniky.Firmy, Ciselniky.Jmena, Ciselniky.VerzeDokumentace, Ciselniky.Casti, Ciselniky.Kraje })
|
||||||
col.CollectionChanged += (_, _) => UlozCiselniky();
|
col.CollectionChanged += (_, _) => UlozCiselniky();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Uloží číselníky vedle .exe. Při autosave záměrně bez dialogu (read-only složka u .exe
|
/// <summary>Uloží rozdíl číselníků proti dodávané vrstvě do složky uživatele. Při autosave
|
||||||
/// v Program Files nesmí spamovat MessageBoxy) – selhání se ale ukáže ve stavovém řádku,
|
/// záměrně bez dialogu (nesmí spamovat MessageBoxy) – selhání se ale ukáže ve stavovém řádku,
|
||||||
/// ať uživatel ví, že se změny číselníků neukládají. Volá se i při zavření okna.</summary>
|
/// ať uživatel ví, že se změny číselníků neukládají. Volá se i při zavření okna.</summary>
|
||||||
public void UlozCiselniky()
|
public void UlozCiselniky()
|
||||||
{
|
{
|
||||||
try { CiselnikyStore.Save(Ciselniky); }
|
try { CiselnikyStore.UlozRozdil(Ciselniky); }
|
||||||
catch { StavovaZprava = "Číselníky se nepodařilo uložit (složka aplikace je jen pro čtení?)."; }
|
catch { StavovaZprava = $"Číselníky se nepodařilo uložit do složky {UzivatelskaData.Slozka}."; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>„Samoučení" našeptávače: jména použitá v projektu/přílohách, která v číselníku
|
/// <summary>„Samoučení" našeptávače: jména použitá v projektu/přílohách, která v číselníku
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Rozpisky – rozhraní aplikace, datový model a budoucí funkcionality
|
# Rozpisky – rozhraní aplikace, datový model a budoucí funkcionality
|
||||||
|
|
||||||
Tento dokument popisuje **standalone aplikaci Rozpisky** z pohledu uživatele a dat: jaké má
|
Tento dokument popisuje **standalone aplikaci Rozpisky** z pohledu uživatele a dat: jaké má
|
||||||
obrazovky, co se v nich vyplňuje, jaké akce nabízí a jak je vnitřně uspořádaný datový model.
|
obrazovky, co se v nich vyplňuje, jaké akce nabízí a jak je vnitřně uspořádaný datový model.
|
||||||
@@ -131,12 +131,22 @@ Na revize se odkazují řádkové revizní sloupce přílohy (R1–R4) a revizn
|
|||||||
| **Verze dokumentace** | verze | stupeň dokumentace |
|
| **Verze dokumentace** | verze | stupeň dokumentace |
|
||||||
| **Části** | označení, název části | části/objekty v seznamu |
|
| **Části** | označení, název části | části/objekty v seznamu |
|
||||||
|
|
||||||
> **Číselníky jsou sdílená data napříč projekty a žijí v samostatné databázi vedle `.exe`.**
|
> **Číselníky jsou sdílená data napříč projekty a žijí mimo projekt.** V původním Excelu šlo
|
||||||
> V původním Excelu šlo o samostatné listy s daty (označení dokumentace, data firem apod.).
|
> o samostatné listy s daty (označení dokumentace, data firem apod.). Ve standalone aplikaci jsou
|
||||||
> Ve standalone aplikaci se z nich stane **externí databázový soubor uložený vedle spustitelného
|
> uložené ve **dvou vrstvách JSON** (na rozdíl od revizí, které jsou interní k zakázce –
|
||||||
> souboru** (např. `cisleniky.db` / `.json` ve složce aplikace), aby ho šlo **průběžně doplňovat
|
> viz [3.3](#33-revize-původní-list-revize)):
|
||||||
> a sdílet** mezi projekty bez zásahu do aplikace. (Na rozdíl od revizí, které jsou interní
|
>
|
||||||
> k zakázce – viz [3.3](#33-revize-původní-list-revize).)
|
> | Vrstva | Soubor | Role |
|
||||||
|
> |---|---|---|
|
||||||
|
> | dodávaná | `ciselniky.vychozi.json` vedle `.exe` | výchozí obsah dodaný s programem, jen ke čtení; každá instalace ho obnoví |
|
||||||
|
> | uživatelská | `ciselniky.user.json` v `%APPDATA%\Rozpisky` | jen **rozdíl** proti dodávané: `Vlastni` (přidané a upravené položky) + `Skryte` (klíče odklizených dodávaných položek) |
|
||||||
|
>
|
||||||
|
> Při načtení se vrstvy slučují: dodávané pořadí zůstává, upravená položka se přepíše na svém
|
||||||
|
> místě, vlastní se řadí za ně. Při uložení se rozdíl dopočítá znovu. Smysl rozdělení: uživatel
|
||||||
|
> číselníky průběžně doplňuje (ručně i samoučením jmen z projektů) a **přeinstalace novou verzí
|
||||||
|
> mu je nesmí přepsat** – zároveň smí nová verze dodávaný číselník libovolně měnit.
|
||||||
|
> Klíč položky: `Nazev` (organizace, firmy, kraje), `Hodnota` (jména, verze), `Oznaceni` (části);
|
||||||
|
> porovnává se bez ohledu na velikost písmen.
|
||||||
|
|
||||||
### 3.5 Konfigurace
|
### 3.5 Konfigurace
|
||||||
|
|
||||||
@@ -153,7 +163,7 @@ Na revize se odkazují řádkové revizní sloupce přílohy (R1–R4) a revizn
|
|||||||
|
|
||||||
### 3.6 Mapování `pole aplikace ↔ DXF ATTDEF tag`
|
### 3.6 Mapování `pole aplikace ↔ DXF ATTDEF tag`
|
||||||
|
|
||||||
Šablona [`Rozpiska.dxf`](Rozpiska.dxf) obsahuje **101 polí (`ATTDEF`)**. Tag pole = klíč do
|
Šablona [`Rozpiska.dxf`](../Rozpisky/Podklady/Rozpiska.dxf) obsahuje **101 polí (`ATTDEF`)**. Tag pole = klíč do
|
||||||
`values` při vyplnění (viz `DxfTemplate.Replay(g, values)` v `ROZPISKY.md`). Mapování na zdroj dat:
|
`values` při vyplnění (viz `DxfTemplate.Replay(g, values)` v `ROZPISKY.md`). Mapování na zdroj dat:
|
||||||
|
|
||||||
**Projektová pole (z údajů projektu):**
|
**Projektová pole (z údajů projektu):**
|
||||||
@@ -396,8 +406,8 @@ Orientační fázování (priorita shora dolů):
|
|||||||
- **Formát uložení** – rozdělené úložiště:
|
- **Formát uložení** – rozdělené úložiště:
|
||||||
- **Projekt** (údaje + seznam příloh + **revize**) = interní data jedné zakázky → formát k rozhodnutí
|
- **Projekt** (údaje + seznam příloh + **revize**) = interní data jedné zakázky → formát k rozhodnutí
|
||||||
(JSON / SQLite / vlastní).
|
(JSON / SQLite / vlastní).
|
||||||
- **Číselníky** (organizace, firmy, jména, profese, verze, části) = **externí databáze vedle `.exe`**,
|
- ~~**Číselníky** – formát a umístění.~~ **Vyřešeno:** dvě vrstvy JSON – dodávaná vedle `.exe`,
|
||||||
sdílená a doplňovatelná napříč projekty ([3.4](#34-číselníky)) → formát k rozhodnutí.
|
uživatelská (rozdíl) v `%APPDATA%\Rozpisky` ([3.4](#34-číselníky)).
|
||||||
- **Generátor 47-pozičního kódu** – doimplementovat dle manuálu kap. 3.2.1–3.2.8 vč. okrajových případů
|
- **Generátor 47-pozičního kódu** – doimplementovat dle manuálu kap. 3.2.1–3.2.8 vč. okrajových případů
|
||||||
(sdružené objekty `SK`/`PK`, kap. 3.3; pracovní vs. definitivní verze).
|
(sdružené objekty `SK`/`PK`, kap. 3.3; pracovní vs. definitivní verze).
|
||||||
- **Orientační schéma** – jak se obrázek schématu získá/přiřadí k příloze (vstup uživatele vs. generování).
|
- **Orientační schéma** – jak se obrázek schématu získá/přiřadí k příloze (vstup uživatele vs. generování).
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Rozpisky – generátor výkresových rohových razítek (C# / .NET)
|
# Rozpisky – generátor výkresových rohových razítek (C# / .NET)
|
||||||
|
|
||||||
Standalone WPF aplikace (náhrada původního excelového nástroje „Rozpisky 1.0.8"). Spravuje projekt,
|
Standalone WPF aplikace (náhrada původního excelového nástroje „Rozpisky 1.0.8"). Spravuje projekt,
|
||||||
seznam příloh, revize a číselníky, a z **DXF šablony rozpisky** generuje **náhled**, **PDF na A4**,
|
seznam příloh, revize a číselníky, a z **DXF šablony rozpisky** generuje **náhled**, **PDF na A4**,
|
||||||
@@ -6,9 +6,9 @@ seznam příloh, revize a číselníky, a z **DXF šablony rozpisky** generuje *
|
|||||||
i **tisk rozvržení**.
|
i **tisk rozvržení**.
|
||||||
|
|
||||||
Celé kreslení stojí na jedné myšlence: existuje jedno **kreslicí rozhraní**
|
Celé kreslení stojí na jedné myšlence: existuje jedno **kreslicí rozhraní**
|
||||||
([`IProfileRenderer`](Rozpisky/Rendering/IProfileRenderer.cs) – „kam se kreslí") a několik jeho
|
([`IProfileRenderer`](../Rozpisky/Rendering/IProfileRenderer.cs) – „kam se kreslí") a několik jeho
|
||||||
implementací. Tatáž kresba teče na obrazovku (náhled), do PNG, do PDF i do DXF. Čtečka šablony
|
implementací. Tatáž kresba teče na obrazovku (náhled), do PNG, do PDF i do DXF. Čtečka šablony
|
||||||
([`DxfTemplate`](Rozpisky/Dxf/DxfTemplate.cs)) jen čte DXF a „přehrává" ho do tohoto rozhraní.
|
([`DxfTemplate`](../Rozpisky/Dxf/DxfTemplate.cs)) jen čte DXF a „přehrává" ho do tohoto rozhraní.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ zvláštní `PlatformTarget` se nenastavuje.
|
|||||||
- Pole, která byla v šabloně `ATTDEF`, se při vyplnění zapisují jako obyčejné `TEXT`/`MTEXT` entity
|
- Pole, která byla v šabloně `ATTDEF`, se při vyplnění zapisují jako obyčejné `TEXT`/`MTEXT` entity
|
||||||
(žádné atributy bloku).
|
(žádné atributy bloku).
|
||||||
- Objekty v netisknutelných hladinách (`PlotFlag=0`) se **nevykreslují**, ale ví se o nich kvůli
|
- Objekty v netisknutelných hladinách (`PlotFlag=0`) se **nevykreslují**, ale ví se o nich kvůli
|
||||||
místům pro loga (viz [`DxfTemplate.PlaceholderHladiny`](Rozpisky/Dxf/DxfTemplate.cs)).
|
místům pro loga (viz [`DxfTemplate.PlaceholderHladiny`](../Rozpisky/Dxf/DxfTemplate.cs)).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -73,8 +73,17 @@ Rozpisky/
|
|||||||
└─ MainWindow.xaml(.cs) ← hlavní okno
|
└─ MainWindow.xaml(.cs) ← hlavní okno
|
||||||
```
|
```
|
||||||
|
|
||||||
Vedle .exe se dodávají editovatelné soubory: `Rozpiska.dxf` (šablona), `SEZNAM.xlsx` (mustr seznamu),
|
Datové soubory jsou rozdělené podle toho, kdo je vlastní:
|
||||||
`ciselniky.json`, `mapovani.json`, `cad.json`, `layout.json`, `loga/` a manuál SŽ (PDF).
|
|
||||||
|
| Kde | Co | Kdo zapisuje |
|
||||||
|
|---|---|---|
|
||||||
|
| vedle `.exe` (zdroj `Rozpisky/Podklady/`) | `Rozpiska.dxf`, `SEZNAM.xlsx`, `ciselniky.vychozi.json`, `mapovani.json`, `loga/`, manuál SŽ (PDF) | nikdo – dodává se s programem, každá instalace přepíše |
|
||||||
|
| `%APPDATA%\Rozpisky` ([`UzivatelskaData`](../Rozpisky/Data/UzivatelskaData.cs)) | `ciselniky.user.json`, `cad.json`, `layout.json`, `error.log`, `tisk_diag.log` | aplikace za běhu |
|
||||||
|
| kdekoli u uživatele | projekty `.rzp` | aplikace na vyžádání |
|
||||||
|
|
||||||
|
Rozdělení existuje proto, aby přeinstalace novou verzí (nebo `.exe` v `Program Files`, kam se nesmí
|
||||||
|
psát) nesmazala nic, co si uživatel nastřádal. Portable režim se zapíná souborem `portable.txt`
|
||||||
|
vedle `.exe` – pak se všechno vrátí do složky aplikace.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -122,7 +131,7 @@ Implementace:
|
|||||||
|
|
||||||
## 4. Čtečka šablony a fasáda pro UI
|
## 4. Čtečka šablony a fasáda pro UI
|
||||||
|
|
||||||
Nízkoúrovňová čtečka [`DxfTemplate`](Rozpisky/Dxf/DxfTemplate.cs):
|
Nízkoúrovňová čtečka [`DxfTemplate`](../Rozpisky/Dxf/DxfTemplate.cs):
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
var tpl = new DxfTemplate("Rozpiska.dxf");
|
var tpl = new DxfTemplate("Rozpiska.dxf");
|
||||||
@@ -136,7 +145,7 @@ tpl.Replay(g, values, includeNoPrint); // + volitelně A4 rámeček !!NOPRINT (p
|
|||||||
vystředění), `Placeholders` vrací rámečky pro loga/schéma podle vyhrazených hladin, `Bounds` bbox
|
vystředění), `Placeholders` vrací rámečky pro loga/schéma podle vyhrazených hladin, `Bounds` bbox
|
||||||
kresby (pro fit vkládaného fragmentu).
|
kresby (pro fit vkládaného fragmentu).
|
||||||
|
|
||||||
Fasáda pro UI [`RozpiskaRenderer`](Rozpisky/Dxf/RozpiskaRenderer.cs) spojuje šablonu, mapování hodnot
|
Fasáda pro UI [`RozpiskaRenderer`](../Rozpisky/Dxf/RozpiskaRenderer.cs) spojuje šablonu, mapování hodnot
|
||||||
a exportéry na jedno místo (šablona se cachuje):
|
a exportéry na jedno místo (šablona se cachuje):
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
@@ -147,16 +156,16 @@ r.ExportPdf(projekt, priloha, revize, ciselniky, "rozpiska.pdf", loga); // A4
|
|||||||
r.ExportPng(projekt, priloha, revize, ciselniky, "nahled.png", loga); // A4 PNG
|
r.ExportPng(projekt, priloha, revize, ciselniky, "nahled.png", loga); // A4 PNG
|
||||||
```
|
```
|
||||||
|
|
||||||
Hodnoty polí sestavuje [`RozpiskaValues`](Rozpisky/Dxf/RozpiskaValues.cs): mapování „pole programu →
|
Hodnoty polí sestavuje [`RozpiskaValues`](../Rozpisky/Dxf/RozpiskaValues.cs): mapování „pole programu →
|
||||||
DXF atribut" **není v kódu**, čte se z `mapovani.json` a hodnoty se dotahují reflexí. K tomu se
|
DXF atribut" **není v kódu**, čte se z `mapovani.json` a hodnoty se dotahují reflexí. K tomu se
|
||||||
algoritmicky doplní 47-poziční strojový kód (`K1`–`K47`, viz
|
algoritmicky doplní 47-poziční strojový kód (`K1`–`K47`, viz
|
||||||
[`KodPrilohy`](Rozpisky/Dxf/KodPrilohy.cs)).
|
[`KodPrilohy`](../Rozpisky/Dxf/KodPrilohy.cs)).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Export na A4 (`Exporters`)
|
## 5. Export na A4 (`Exporters`)
|
||||||
|
|
||||||
[`Exporters`](Rozpisky/Dxf/Exporters.cs) vykreslí **libovolný** `Action<IProfileRenderer>`:
|
[`Exporters`](../Rozpisky/Dxf/Exporters.cs) vykreslí **libovolný** `Action<IProfileRenderer>`:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
Exporters.PngA4(draw, "rozpiska.png", dpi: 200, frame: sheetFrame); // A4 raster
|
Exporters.PngA4(draw, "rozpiska.png", dpi: 200, frame: sheetFrame); // A4 raster
|
||||||
@@ -172,7 +181,7 @@ přes `SKDocument.CreatePdf` (1 mm = 72/25,4 bodu; text vektorový, tloušťky v
|
|||||||
|
|
||||||
## 6. Demo data (`DemoData`)
|
## 6. Demo data (`DemoData`)
|
||||||
|
|
||||||
[`DemoData`](Rozpisky/Data/DemoData.cs) naplní úvodní stav při startu:
|
[`DemoData`](../Rozpisky/Data/DemoData.cs) naplní úvodní stav při startu:
|
||||||
`SeedProjekt(vm)` (ukázkový projekt + přílohy), `SeedCiselniky(c)` a `SeedKraje(c)` (výchozí
|
`SeedProjekt(vm)` (ukázkový projekt + přílohy), `SeedCiselniky(c)` a `SeedKraje(c)` (výchozí
|
||||||
číselníky, pokud `ciselniky.json` chybí).
|
číselníky, pokud `ciselniky.json` chybí).
|
||||||
|
|
||||||
@@ -190,18 +199,25 @@ přes `SKDocument.CreatePdf` (1 mm = 72/25,4 bodu; text vektorový, tloušťky v
|
|||||||
| Aktualizace/tisk rozvržení | přílohy | přejmenování/tisk v CADu | `Cad/CadLayoutService`, `Cad/CadTiskService` (COM) |
|
| Aktualizace/tisk rozvržení | přílohy | přejmenování/tisk v CADu | `Cad/CadLayoutService`, `Cad/CadTiskService` (COM) |
|
||||||
|
|
||||||
Aplikace **nemá CLI režim** – vše jede z GUI. Neočekávané výjimky zachytává globální síť v
|
Aplikace **nemá CLI režim** – vše jede z GUI. Neočekávané výjimky zachytává globální síť v
|
||||||
[`App.xaml.cs`](Rozpisky/App.xaml.cs) a zapisuje je do `error.log` vedle .exe.
|
[`App.xaml.cs`](../Rozpisky/App.xaml.cs) a zapisuje je do `error.log` vedle .exe.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Persistence a robustnost
|
## 8. Persistence a robustnost
|
||||||
|
|
||||||
- **Projekt `.rzp`** ([`ProjektStore`](Rozpisky/Data/ProjektStore.cs)) – JSON s exkluzivním zámkem
|
- **Projekt `.rzp`** ([`ProjektStore`](../Rozpisky/Data/ProjektStore.cs)) – JSON s exkluzivním zámkem
|
||||||
drženým po dobu otevření (ostatní procesy smí jen číst). Zápis serializuje **před** useknutím
|
drženým po dobu otevření (ostatní procesy smí jen číst). Zápis serializuje **před** useknutím
|
||||||
souboru, aby pád nezanechal prázdný soubor.
|
souboru, aby pád nezanechal prázdný soubor.
|
||||||
- **Configy vedle .exe** (`ciselniky.json`, `cad.json`, `layout.json`, `mapovani.json`) jdou přes
|
- **JSON configy** (`ciselniky.user.json`, `cad.json`, `layout.json` v uživatelské složce,
|
||||||
[`JsonConfigFile`](Rozpisky/Data/JsonConfigFile.cs): **atomický zápis** (přes `.tmp`) a při
|
`mapovani.json` vedle .exe) jdou přes [`JsonConfigFile`](../Rozpisky/Data/JsonConfigFile.cs):
|
||||||
poškozeném souboru **záloha do `.bak` + upozornění** (místo tichého přepsání výchozími daty).
|
**atomický zápis** (přes `.tmp`) a při poškozeném souboru **záloha do `.bak` + upozornění**
|
||||||
|
(místo tichého přepsání výchozími daty).
|
||||||
|
- **Číselníky ve dvou vrstvách** ([`CiselnikyStore`](../Rozpisky/Data/CiselnikyStore.cs)) – dodávaná
|
||||||
|
`ciselniky.vychozi.json` (jen ke čtení) a uživatelská `ciselniky.user.json`, kde je uložený jen
|
||||||
|
**rozdíl** proti ní (přidané/upravené položky + klíče skrytých). Nová verze programu tak smí
|
||||||
|
dodávaný číselník měnit, aniž by uživatel přišel o svoje záznamy.
|
||||||
|
- **Migrace starých poloh** ([`Migrace`](../Rozpisky/Data/Migrace.cs)) – data zapisovaná dřív vedle
|
||||||
|
.exe se při startu přenesou do uživatelské složky; originál se jen přejmenuje na `.migrovano`.
|
||||||
- **Zamčený cíl exportu** – před zápisem PDF/XLSX/DXF se ověří, zda soubor nedrží jiný program
|
- **Zamčený cíl exportu** – před zápisem PDF/XLSX/DXF se ověří, zda soubor nedrží jiný program
|
||||||
(Excel, prohlížeč PDF), a nabídne Pokračovat/Přerušit.
|
(Excel, prohlížeč PDF), a nabídne Pokračovat/Přerušit.
|
||||||
|
|
||||||
Reference in New Issue
Block a user