Playwright Tests / test (push) Canceled after 0s
Lets a back-office user correct/define which invoice line belongs to which "set" and how deep it is nested, instead of relying only on the implicit "everything after a Set header until the next Set" grouping fds__prepInvoice derives. Sets may contain sets; every change applies immediately to the cached draft session and is reflected in the editor and PDF (ADR 0006/0008). Backend: - InvoiceSetStructure: pure parent-link forest (assign, change level, create, place/move-sibling, depth-first linearise + level recompute). - InvoiceDraftSetStructureDeltas: new inv/dpatch targets set.assign, set.level, set.create, set.move. - InvoiceSetPricing.Build is now order-preserving and nesting-aware (governed by the nearest converted ancestor); the set-item switch sums a header's transitive descendants so nested conversions never double-count. - FuchsPdf indents nested set members per level, mirroring the editor. Frontend: - New fis.inv_sets.js: "Set-Struktur bearbeiten" dialog — per-block table of all lines with drag & drop reorder (before/after/into), ▲▼ order and ◀▶ level buttons, fold/unfold per set (view-only), and a create bar for new Set/Text lines. Posts one delta per action; the dialog is a pure view of the server state (ADR 0006). - _Layout.cshtml / fis_main.js: cache-bust the dynamically imported per-module bundles (web/fis.<mdl>.<locale>.js/.css) with an asset version derived from the newest bundle write time, so a gulp rebuild is picked up without a stale cached module. Docs: ADR 0016, live-draft-editing concept, INVOICE_SET_PRICING.md, USER_GUIDE.md, ARCHITECTURE.md updated. Tests: InvoiceSetStructureTests (new) + additions to InvoiceDraftServiceTests/InvoiceSetPricingTests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
149 lines
5.9 KiB
Plaintext
149 lines
5.9 KiB
Plaintext
@using System.Security.Claims
|
|
@using Microsoft.Data.SqlClient
|
|
@using Newtonsoft.Json
|
|
@inject IConfiguration Configuration
|
|
@inject Microsoft.AspNetCore.Hosting.IWebHostEnvironment HostEnvironment
|
|
@{
|
|
bool isAuth = User.Identity?.IsAuthenticated ?? false;
|
|
|
|
var authinfo = new Dictionary<string, object>();
|
|
if (isAuth)
|
|
{
|
|
authinfo["useraccount_id"] = User.FindFirstValue("fuchs:useraccount_id") ?? "";
|
|
authinfo["email"] = User.FindFirstValue("fuchs:email") ?? "";
|
|
authinfo["authorization"] = User.FindFirstValue("fuchs:authorization") ?? "0";
|
|
}
|
|
authinfo["requestedaccount"] = "";
|
|
authinfo["accountrequired"] = false;
|
|
// Cache-buster for the per-module bundles ($ocms.getScript loads web/fis.<mdl>.<locale>.js and
|
|
// web/fis.<mdl>.css dynamically, so asp-append-version cannot cover them): the newest write
|
|
// time of any module bundle, appended as ?v=… by fis_main.js, so a gulp rebuild is picked up
|
|
// on the next page load instead of a stale cached module.
|
|
try
|
|
{
|
|
var webDir = new System.IO.DirectoryInfo(System.IO.Path.Combine(HostEnvironment.WebRootPath, "web"));
|
|
authinfo["assetversion"] = webDir.Exists
|
|
? webDir.GetFiles("fis.*").Select(f => f.LastWriteTimeUtc.Ticks).DefaultIfEmpty(0L).Max().ToString()
|
|
: "0";
|
|
}
|
|
catch { authinfo["assetversion"] = "0"; }
|
|
|
|
string appName = ViewData["AppName"] as string ?? "Fuchs Intranet";
|
|
string fullName = ViewData["FullName"] as string ?? "";
|
|
string pageTitle = ViewData["Title"] as string ?? "Intranet";
|
|
// When the deployment is flagged as a test/staging instance (Fuchs:IsTestDeployment = true),
|
|
// a distinctive green background is injected below so a test instance is never mistaken for
|
|
// production. Defaults to false (production) in appsettings.json.
|
|
bool isTestDeployment = Configuration.GetValue<bool>("Fuchs:IsTestDeployment");
|
|
string? debugDbTarget = null;
|
|
if (HostEnvironment.IsDevelopment())
|
|
{
|
|
var connectionString = Configuration.GetConnectionString("fuchs_fds_ConnectionString");
|
|
if (!string.IsNullOrWhiteSpace(connectionString))
|
|
{
|
|
var builder = new SqlConnectionStringBuilder(connectionString);
|
|
debugDbTarget = $"{builder.DataSource} / {builder.InitialCatalog}";
|
|
}
|
|
}
|
|
}
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
@if (!string.IsNullOrWhiteSpace(debugDbTarget))
|
|
{
|
|
<meta name="fuchs-debug-database" content="@debugDbTarget" />
|
|
}
|
|
<title>@pageTitle</title>
|
|
<script src="~/web/tools.js" asp-append-version="true"></script>
|
|
|
|
@if (isAuth)
|
|
{
|
|
<script src="~/lib/tinymce/tinymce.min.js"></script>
|
|
<link rel="stylesheet" href="~/web/fis.min.css" asp-append-version="true" />
|
|
<script src="~/web/fis.min.js" asp-append-version="true"></script>
|
|
}
|
|
else
|
|
{
|
|
<link rel="stylesheet" href="~/web/fisb.min.css" asp-append-version="true" />
|
|
<script src="~/web/fisb.min.js" asp-append-version="true"></script>
|
|
}
|
|
|
|
@await RenderSectionAsync("CustomHeader", required: false)
|
|
|
|
@if (isTestDeployment)
|
|
{
|
|
<style>
|
|
body, html, body > header > nav, body > main > #topbar > nav {
|
|
background-color: #13a143 !important;
|
|
}
|
|
</style>
|
|
}
|
|
|
|
<script type="text/javascript">
|
|
$ocms.auth = @Html.Raw(JsonConvert.SerializeObject(authinfo));
|
|
</script>
|
|
</head>
|
|
<body class="ldng">
|
|
<div id="bgimg"></div>
|
|
@if (isAuth)
|
|
{
|
|
<div class="pgb inactive">
|
|
<div class="pgb-progress">
|
|
<div class="inner"></div>
|
|
</div>
|
|
</div>
|
|
<header>
|
|
<nav id="mainmenu">
|
|
<div class="nav-header">
|
|
<div id="logo"> </div>
|
|
<button id="mmmb" class="nav-btn" data-toggle="vis" data-target="#mainmenu">
|
|
<span class="glyphicon glyphicon-menu-hamburger"></span>
|
|
</button>
|
|
<div id="brand" class="brand">@appName</div>
|
|
<div id="activemodule" class="activemodule">Übersicht</div>
|
|
</div>
|
|
<ul class="nav-right">
|
|
<li class="dropdown">
|
|
<a class="dds dropdown-toggle" role="button" aria-expanded="false">
|
|
<span class="glyphicon glyphicon-cog" aria-hidden="true"></span>
|
|
<span class="caret dd"></span>
|
|
</a>
|
|
<ul id="vm_menu_auth" class="dropdown-menu right" role="menu">
|
|
<li class="dropdown submenu">
|
|
<a class="dds dropdown-toggle" role="button" aria-expanded="false">
|
|
@fullName<span class="caret dd"></span>
|
|
</a>
|
|
<ul class="dropdown-menu" role="menu">
|
|
<li><a onclick="$ocms.logout.call(this);">abmelden</a></li>
|
|
<li><a onclick="$fis.resetPass.call(this);">Passwort ändern</a></li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
@await RenderSectionAsync("BodyHeader", required: false)
|
|
</header>
|
|
<main>
|
|
<div id="topbar"></div>
|
|
<div id="listframe"></div>
|
|
<div id="contentframe">
|
|
@RenderBody()
|
|
</div>
|
|
</main>
|
|
<footer>
|
|
<div id="notification_frame"></div>
|
|
@await RenderSectionAsync("BodyFooter", required: false)
|
|
</footer>
|
|
}
|
|
else
|
|
{
|
|
@await Html.PartialAsync("~/Views/Partials/vpart__ocms_login.cshtml")
|
|
@RenderBody()
|
|
}
|
|
<div class="timer"></div>
|
|
</body>
|
|
</html>
|