Loga se dosud hledala jen v loga\ vedle .exe. Kdo si do číselníku přidal firmu s vlastním logem, musel .dxf nakopírovat do instalační složky – tu ale přeinstalace přepíše a v Program Files do ní nejde psát. Číselník firmu přežil, logo ne. Nově se hledá nejdřív v %APPDATA%\Rozpisky\loga a teprve pak mezi dodávanými; nabídka v Údajích projektu je sjednocením obou složek (stejné jméno se nezdvojuje). Tlačítko v Nastavení složku založí a otevře. Hierarchie je stejná jako u číselníků a je to úmyslné: při konfliktu vyhrává uživatelská vrstva. Kdyby vyhrávala dodávaná, uživatelova úprava by se tiše neprojevila – on ji vidí uloženou, program pracuje s něčím jiným. Výjimkou zůstávají data držící správnost výstupu (mapovani.json, Rozpiska.dxf), kde vyhrává program. Subagenti dostali explicitní model: render-dxf a wpf-ui sonnet, verifikator haiku (jen spouští build a testy). Build 0 chyb / 0 varování, 55 testů zelených (5 nových na slučování nabídky). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
114 lines
5.6 KiB
C#
114 lines
5.6 KiB
C#
using System.IO;
|
||
using Rozpisky.Data;
|
||
using Rozpisky.Models;
|
||
|
||
namespace Rozpisky.Dxf;
|
||
|
||
/// <summary>
|
||
/// Sestaví mapu „hladina placeholderu → absolutní cesta k DXF" pro jednu rozpisku.
|
||
/// Pravidla (kap. 4.3 manuálu SŽ):
|
||
/// <list type="bullet">
|
||
/// <item><c>Rozpiska_logo1/2</c> – financující instituce (dotační vlevo / státní vpravo),
|
||
/// projektové, soubor ze složky <c>loga\</c>,</item>
|
||
/// <item><c>Rozpiska_logo3</c> – logo investora (číselník Organizace dle <c>Projekt.Investor</c>),</item>
|
||
/// <item><c>Rozpiska_logo4</c> – logo zhotovitele díla (číselník Firmy dle <c>Projekt.ZhotovitelSOD</c>),</item>
|
||
/// <item><c>Rozpiska_logo5</c> – logo zhotovitele objektu (číselník Firmy dle <c>Priloha.ZhotovitelCasti</c>),</item>
|
||
/// <item><c>Rozpiska_Orientační schéma</c> – DXF u přílohy (<c>Priloha.SchemaSoubor</c>), relativně k .rzp.</item>
|
||
/// </list>
|
||
/// Položky bez vyplněného / neexistujícího souboru se vynechají.
|
||
/// <para>
|
||
/// Loga se hledají ve <b>dvou složkách</b> se stejnou logikou jako číselníky: nejdřív
|
||
/// <c>loga\</c> v uživatelské složce (viz <see cref="UzivatelskaData"/>), pak <c>loga\</c> vedle
|
||
/// .exe. Uživatelské logo stejného jména přebíjí dodávané. Bez toho by si uživatel, který přidá
|
||
/// do číselníku firmu s vlastním logem, musel soubor kopírovat do instalační složky – tu ale
|
||
/// přeinstalace přepíše a v <c>Program Files</c> do ní nejde psát.
|
||
/// </para>
|
||
/// </summary>
|
||
public static class LogaResolver
|
||
{
|
||
/// <summary>Složka s logy dodávanými s programem (vedle .exe). Jen ke čtení – instalace ji přepisuje.</summary>
|
||
public static string LogaDir => Path.Combine(AppContext.BaseDirectory, "loga");
|
||
|
||
/// <summary>Složka pro loga, která si přidal uživatel. Přeinstalace na ni nesahá.</summary>
|
||
public static string UzivatelskaLogaDir => UzivatelskaData.Cesta("loga");
|
||
|
||
/// <summary>Najde soubor loga: absolutní cesta se bere, jak je, jinak se hledá nejdřív mezi
|
||
/// uživatelskými logy a pak mezi dodávanými. Vrací null, když soubor nikde není.</summary>
|
||
public static string? Najdi(string? soubor)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(soubor)) return null;
|
||
if (Path.IsPathRooted(soubor)) return File.Exists(soubor) ? soubor : null;
|
||
|
||
var uzivatelske = Path.Combine(UzivatelskaLogaDir, soubor);
|
||
if (File.Exists(uzivatelske)) return uzivatelske;
|
||
|
||
var dodavane = Path.Combine(LogaDir, soubor);
|
||
return File.Exists(dodavane) ? dodavane : null;
|
||
}
|
||
|
||
/// <summary>Jména dostupných log z obou složek dohromady, abecedně. Uživatelské logo stejného
|
||
/// jména dodávané nezdvojuje – v nabídce je jednou a použije se to uživatelovo.</summary>
|
||
public static IReadOnlyList<string> DostupnaLoga() => SlozNabidku(UzivatelskaLogaDir, LogaDir);
|
||
|
||
/// <summary>Vlastní slučování nabídky – s cestami v parametru, ať jde otestovat bez profilu uživatele.</summary>
|
||
internal static List<string> SlozNabidku(string uzivatelskaDir, string dodavanaDir)
|
||
{
|
||
var jmena = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
foreach (var dir in new[] { dodavanaDir, uzivatelskaDir })
|
||
{
|
||
try
|
||
{
|
||
if (!Directory.Exists(dir)) continue;
|
||
foreach (var soubor in Directory.GetFiles(dir, "*.dxf"))
|
||
jmena.Add(Path.GetFileName(soubor));
|
||
}
|
||
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
|
||
{
|
||
// Nedostupná složka (odpojený síťový disk apod.) nesmí shodit nabídku.
|
||
}
|
||
}
|
||
return jmena.ToList();
|
||
}
|
||
|
||
/// <param name="rzpDir">Složka aktuálního souboru .rzp (pro relativní cestu k schématu); null = neuloženo.</param>
|
||
public static IReadOnlyDictionary<string, string> Resolve(Projekt p, Priloha r, Ciselniky c, string? rzpDir)
|
||
{
|
||
var d = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||
|
||
PridejZeSlozky(d, "Rozpiska_logo1", p.Logo1Soubor);
|
||
PridejZeSlozky(d, "Rozpiska_logo2", p.Logo2Soubor);
|
||
PridejZeSlozky(d, "Rozpiska_logo3", OrganizaceLogo(c, p.Investor));
|
||
PridejZeSlozky(d, "Rozpiska_logo4", FirmaLogo(c, p.ZhotovitelSOD));
|
||
PridejZeSlozky(d, "Rozpiska_logo5", FirmaLogo(c, r.ZhotovitelCasti));
|
||
|
||
var schema = ResolveSchema(r.SchemaSoubor, rzpDir);
|
||
if (schema is not null) d["Rozpiska_Orientační schéma"] = schema;
|
||
|
||
return d;
|
||
}
|
||
|
||
private static void PridejZeSlozky(IDictionary<string, string> d, string hladina, string? soubor)
|
||
{
|
||
var cesta = Najdi(soubor);
|
||
if (cesta is not null) d[hladina] = cesta;
|
||
}
|
||
|
||
/// <summary>Cesta ke schématu: absolutní beze změny, jinak relativně ke složce .rzp.</summary>
|
||
private static string? ResolveSchema(string? soubor, string? rzpDir)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(soubor)) return null;
|
||
if (Path.IsPathRooted(soubor)) return File.Exists(soubor) ? soubor : null;
|
||
if (string.IsNullOrEmpty(rzpDir)) return null;
|
||
var cesta = Path.Combine(rzpDir, soubor);
|
||
return File.Exists(cesta) ? cesta : null;
|
||
}
|
||
|
||
private static string? FirmaLogo(Ciselniky c, string? nazev)
|
||
=> string.IsNullOrWhiteSpace(nazev) ? null
|
||
: c.Firmy.FirstOrDefault(f => string.Equals(f.Nazev, nazev, StringComparison.OrdinalIgnoreCase))?.Logo;
|
||
|
||
private static string? OrganizaceLogo(Ciselniky c, string? nazev)
|
||
=> string.IsNullOrWhiteSpace(nazev) ? null
|
||
: c.Organizace.FirstOrDefault(o => string.Equals(o.Nazev, nazev, StringComparison.OrdinalIgnoreCase))?.Logo;
|
||
}
|