Make invoice/reminder live-draft edits concurrency-safe with an explicit
DraftFlushResult, add the fds__setReminder stored procedure, and harden the
date helper functions. Includes concurrency and SQL contract tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Stornorechnung saves still failed: the VAT amount was formatted invariantly
("-615.3757999999999") and re-parsed under the server's de-DE culture as
-6153757999999999, overflowing numeric(10,3) in fds__setInvoice.
- FdsInvoiceData.BuildInvoiceParams: amount params are decimal(10,x) via
AmountParam/ParseAmount (culture-independent, rounded to cents); amounts
beyond +/-9,999,999.99 throw InvoiceAmountOutOfRangeException.
- InvoiceService.RegisterInvoiceAsync: refuses such an invoice with a logged
error (EventId InvoiceAmountRejected) instead of storing a wrong amount.
- Invoice editor: vat_val is cent-rounded decimal, no float noise.
- Tests: InvoiceAmountParamTests replaces the float-based Storno test.
- Bump Azure.Storage.Blobs 12.30.0; OCORE/OCORE_web submodules (AngleSharp
1.8.3, Azure.Storage.Files.Shares 12.28.0).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Customer log: /do/inv/dsave failed with "Error converting data type float to
numeric" followed by "Cannot insert the value NULL into column 'InvId'".
- Bump OCORE: SQL_Float/SQL_Decimal string overloads parsed negatives via the
de-DE server culture ("-19.95" -> -1995); float noise overflowed
numeric(10,3), so fds__setInvoice never ran.
- InvoiceService: the header save batch now stops with a clear THROW when
fds__createInvoice/fds__setInvoice yields no @Id, instead of failing again
in fds__createInvoice_Details and burying the real error. Batch SQL
extracted to BuildRegisterInvoiceSql for testing.
- Tests: signed/culture parsing under de-DE, Storno param build against the
proc numeric limits, and the batch guard ordering.
- hlp: read-only script to find invoice/reminder amounts stored scaled up
by the bug.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Added a new SQL script to address the "String or binary data would be truncated" error in mfr sync by widening the Note fields in service objects and companies to NVARCHAR(MAX).
- Updated the table types and associated stored procedures to accommodate the changes, ensuring compatibility with existing data.
- Queued a re-sync for previously rejected rows due to truncation issues.
- Created application logins for fuchs_fds and fuchs_web with placeholder passwords for initial setup.
- Introduced functions for invoice backdating and invoice date retrieval to streamline invoice processing.
- Developed a password generation function with customizable parameters for length and character types.
- Added a random number generation function to support password creation.
Pull OCORE, OCORE_web and OCORE_web_pdf to their latest main commits.
OCORE bumped MimeKit to 4.18.1 and Microsoft.Data.SqlClient to 7.1.0,
which made restore fail with NU1605 package-downgrade errors in Fuchs
and Fuchs_DataService; raise both pins to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Updated the stored procedure `fds__admin_backupAndClearInvoiceFile.sql` to back up not only the invoice file but also JSON snapshots of related data (invoices, items, details, service requests) to ensure consistency and traceability.
- Modified the table structure of `fds__invoice_file_backups` to accommodate new JSON columns for storing these snapshots.
- Added an ADR (0030) document to outline the decision and rationale behind these changes, emphasizing the importance of maintaining a consistent set of data alongside the backed-up file.
- Added MailerAttachmentPolicy for validating email attachments against server rules.
- Introduced fis.specialties.js for managing hidden maintenance actions via an admin interface.
- Created fis.specialties.scss for styling the specialties management UI.
- Added German localization for specialties management in fis.specialties_txt_de.js.
- Developed CSS for specialties management in fis.specialties.css.
- Implemented server-side stored procedure fds__admin_backupAndClearInvoiceFile for backing up and clearing invoice files.
- Created fds__invoice_file_backups table to store backup records of invoice files.
- Introduced FdsDocumentNames class to centralize the logic for sanitizing document names used in downloads.
- Updated FdsMfr to utilize the new SafeFileName method for generating safe filenames.
- Enhanced documentation for FdsDocumentNames and its methods to clarify usage and purpose.
- Added global.json file to configure testing framework settings.
Follow-up to 99bf533 from code review:
- The AffectedRows == 0 guard in Store{Invoice,Reminder}DocumentFileAsync
was dead: fds__setInvoiceFile / fds__setReminderFile run SET NOCOUNT ON,
so ExecuteNonQuery returns -1 whether or not the UPDATE ... WHERE [file]
IS NULL matched. Both procs already return the updated Id through their
OUTPUT clause, so read that result set (getSQLDatatable_async) instead:
empty rowset = no-op store, logged, and the blob upload / FileCreated
event are skipped rather than reporting unpersisted bytes as success.
- inv/rdoc's regenerate path built the download name from the raw
DocumentName; a user-typed title with '/' truncated FileInfo.Name.
SafeDocumentFileName strips editor markup and every invalid filename
char, and the path no longer goes through FileInfo at all.
- Reminder counterpart tests for the store-file failure branches.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An invoice's finalisation-time PDF render/store (fds__setInvoiceFile) can
fail silently; the row stays finalised with [file] = NULL and inv/rdoc
(the invoice list's preview icon) 404'd forever with no trace in any log.
- inv/rdoc and rem/idoc now regenerate and persist a missing document on
read, mirroring the legacy VB "idoc" fallback (Fuchs_Database ADR 0025),
gated to finalised + non-external invoices only — a draft render must
never be persisted, since fds__setInvoiceFile/fds__setReminderFile only
write WHERE [file] IS NULL and finalisation would then silently no-op.
- StoreInvoiceDocumentFileAsync/StoreReminderDocumentFileAsync now log
every distinct failure (render threw, render produced 0 bytes, DB write
threw, DB write matched 0 rows) instead of collapsing them into a single
silent empty-array return. The 0-rows case needed switching from
setSQLValue_async (reports success once the command completes, whatever
the row count) to setSQLValue_async_result, whose AffectedRows makes a
no-op UPDATE distinguishable from an actual write for the first time.
- HandleRequestIdoc/HandleReminderIdoc no longer treat
StoreInvoiceDocumentFileAsync's Array.Empty<byte>() failure signal as
success (a bare `ct == null` check never caught it), which would have
served a 0-byte "PDF".
- rdoc/HandleRequestIdoc reject a missing 'id' form field instead of doing
wasted work with an empty id.
- _BackupAndClearInvoiceFile.sql is annotated as a manual-only maintenance
utility, never called from app code, so it's clear why running it leaves
no application log trail.
- Client-side: $inv.pdfPreviewTimeout was 120 (ms) instead of 120000 — the
preview dialog was timing out client-side after 120ms on every call.
- Fixed a genuine process-wide test flake: PdfPipelineTests and
ERechnungMapperTests both install PdfSharp's global font resolver, and
whichever ran first would win, making the other's tests fail
deterministically once eRechnungLib's SystemFontResolver got there
first. FuchsPdf.EnsureFontResolver is now also race-safe under
concurrent first use (a check-then-set without a lock). Also removed
the InvoiceService.PublishAsync failure logging added mid-session after
code review found it duplicated every call site's own, more detailed
logging against ADR 0024's single-write-path rule.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CLAUDE.md and CODEX.md already documented that CA1416 is suppressed
solution-wide via the root .editorconfig; copilot-instructions.md was
missing that note, breaking the three-file sync rule the header of each
file requires. Also adds the .editorconfig itself, which the note
references and which was untracked.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Retires Fuchs_intranet.debug_log() and every direct EXECUTE [dbo].[fds__admin_logdebug]
call in favor of a single path: ILogger. A new SQL log sink (OCORE.MultiLogger.SqlLoggerProvider,
gated by Fuchs:Logging:SqlSink:Enabled, default on) mirrors every _logger.LogWarning/-Error/
-Critical call into [dbo].[fds__debuglog] via a new fds__admin_logdebug_ext proc, at Warning+.
- Program.cs registers the sink and RequestLogScopeMiddleware (request id/path/authenticated
user, pushed after UseAuthentication) so every log line in a request — including ones written
deep inside OCORE's SQL helpers — is attributed automatically.
- FIS_SQLOptions, IntranetController.Do's catch-all, PdfPreviewAsync, and the banking-upload
handlers now log once, with an explicit EventId preserving the exact CodeReference text the
retired debug_log() calls used to write, so existing dashboards/queries over fds__debuglog
keep working unchanged.
- Fuchs_DataService/FdsMfr.cs's FdsSqlOptions had the identical double-write hazard (it's hosted
in the same process per ADR 0010) — the original 2026-09-16 log review's mfr__getSchema/
fds__getUpdateableRequests deadlock rows came from exactly this class. Fixed the same way.
- fds__debuglog gains LogLevel/Machine/Category/Account/AuthUser/IsDebug columns; the legacy
fds__admin_logdebug proc is left in place unused, not dropped.
- FdsDebug.DebugLog (Fuchs_DataService's other ~9 call sites) deliberately left on its direct
DB-write path — no ILogger call sits alongside it there, so no duplication risk today.
See ADR 0024 (supersedes 0023) and Docs/Concepts/error-logging-pipeline.md.
New tests: ErrorLoggingTests (FIS_SQLOptions), FdsSqlOptionsTests, RequestLogScopeMiddlewareTests.
694/694 pass (the 4 PdfPipelineTests failures seen under full-suite parallel runs are a
pre-existing, unrelated PdfSharp global-font-resolver test-isolation flake — they pass both
individually and in most full-suite runs).
Bumps the OCORE submodule to 3137b56 (SQL sink hardening: redaction matches short fragments
as whole identifier segments, per-entry-tolerant serialization, lazy dataSerialized, opt-in
SqlLoggerOptions.IncludeCategory, LogEntry.Category/AuthUser capture).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Implemented a new method `ShapeTableNames` to gather entity and navigation table names, ensuring no blanks or duplicates are included.
- Updated `FdsMfrClient` to handle cases where schema queries return no tables, logging appropriate warnings and preventing misleading errors.
- Introduced `PdfPreviewAsync` in `IntranetController` to manage PDF preview rendering, capturing detailed logs for failures and notifying users with readable error messages.
- Added comprehensive unit tests for error logging and table name shaping to ensure robustness and correctness.
- Documented decision to persist user action failures to the `fds__admin_logdebug` table, enhancing support diagnostics.
Refactored refreshRow in fis.usr.de.js to look up the row node by id inside the AJAX response handler, ensuring correct replacement even with overlapping calls (e.g., rapid checkbox toggles). This prevents errors when a previous call detaches the node before a later response arrives. Enhanced fallback to full reload for robustness. Updated fis.usr.de.min.js accordingly. Reformatted tools.js with no content changes.
- Improved user group deletion logic to accurately report success or failure.
- Updated user group renaming checks to prevent unintended inserts or updates.
- Added documentation on transaction blocking risks during full resets in stored procedures.
- Removed unnecessary login scripts from database project.
- Introduced a new module for managing user groups (`usrgrp`) in the administration interface.
- Implemented CRUD operations for user groups: list, create, rename, and delete.
- Added authorization checks to ensure only users with appropriate permissions can manage groups.
- Created corresponding frontend JavaScript and SCSS files for the new module.
- Updated documentation to reflect the new group management functionality and its authorization model.
- Added SQL stored procedures for group management operations.
- Established a new login script for database access, ensuring proper security practices.
The Admin status pill previously turned green whenever the mailer was
enabled in config, without proving the account/token actually work.
Add IComService.VerifyCredentialsAsync (GET /mailer/fc?fn=verify, also
passing serverid when configured so the API confirms this server
belongs to the account) and wire it into a new "email" system-status
probe, so the pill only goes green once the Mailer API itself confirms
the credentials.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Bump Microsoft.NET.Test.Sdk, xunit.v3, MailKit, MimeKit, SixLabors.ImageSharp, Microsoft.Data.SqlClient, and Microsoft.Identity.Client.Extensions.Msal to latest patch versions
- Publish ZIP filename now includes environment name (Development/Production) in Fuchs.csproj
- Add FolderProfile-Dev.pubxml (Development) and FolderProfile.pubxml (Production) with EnvironmentName property
- Fix connection string keys in appsettings.Development.json to match production; set IsTestDeployment=true for development
- Set production DB and Azure Storage container names in appsettings.json; IsTestDeployment=false for production
- Mark OCORE, OCORE_web, and eRechnungLib submodules as dirty (local changes)
Removes the per-row indentation (span.ind) in favor of a small "belongs to
set" tag shown above each row's title, so membership is visible without
tracing indentation depth even while sets are expanded. Description text
is now clamped to 2 lines with ellipsis by default and expands to a
scrollable ~12-line view on click.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wire up two previously-unwired stored procedures for the usr module:
- fis_admin_createUserAccount gained the missing @authaccount +
intranet_admin auth gate, a duplicate-email check, and now returns
the (possibly auto-generated) password so it can be emailed to the
new user, mirroring fis_admin_resetUserPassword's pattern.
- fis_admin_delUserAccount gained the same missing auth gate plus a
guard against an admin deleting their own account.
New /do/usr/create and /do/usr/delete endpoints in
IntranetController.AdminUsers.cs, a "Neuer Benutzer" button and a
"Benutzer löschen" ribbon action in fis.usr.js, and unit tests for the
new welcome-email content (BuildWelcomeEmail).
Also restyle the user list (fis.usr.scss) from a plain bordered table
to a card-style row grid with avatar-initial badges and status pills,
matching the Admin dashboard's visual language.
Documented as ADR 0020 and in the admin-user-management concept doc.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fis_admin_getUserAccount always inserted a result row, even when no
matching user was found, so fis_admin_authenticate never truly
returned "no result" on wrong credentials — the login handler then
signed the caller in with an empty useraccount_id. The authenticated
shell rendered (IsAuthenticated only checks the claim exists), while
every data endpoint 401'd (FuchsUserIdentity.IsAuthenticated also
checks the id is non-empty), producing an empty portal.
Also treat an empty useraccount_id in HandleLogin as a failed login
(defense in depth) and fix a column-name mismatch (auth vs
authorization) that was silently zeroing the authorization level.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Pull latest origin/main for OCORE and OCORE_web. OCORE now depends on
Microsoft.Data.SqlClient 7.0.3, so bump Fuchs and Fuchs_DataService's
own pinned reference to match and avoid an NU1605 downgrade error.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
<img> is a nominally inline HTML element, but TinyMCE content places images
between paragraphs of text; without a forced line break before/after, an
image could render packed against adjacent text in the PDF. Insert a line
break ahead of the image (unless one is already there) and always after it.
Also fixes the img handler's early-return path to still apply the inline
size cap (MaxInlineImageWidth/Height) when only one of width/height was
resized, matching the aspect-ratio-locked branch.
Fuchs/wwwroot/web/tools.js: cosmetic gulp rebuild diff (leading blank line),
no behavior change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
"Set mit Preis" / "Nur Set mit Preis" collapsed every block into an inserted
"Gesamtumfang pauschal" row, nulled/removed all lines and cleared the user-defined
set structure. Both modes now convert every still-unconverted top-level set:
the set line takes the sum of its subtree, its members render without a price
(setprice) or are removed from itm + items (setonly); lines outside a set stay
individually priced and several sets stay separate lines. Nested sets are priced
once via their top-level set. One conversion primitive (ConvertSetHeader) now
backs all three triggers; item.setprice is a no-op on an already-converted set
(a repeated delta used to reset the set price to 0). The header's price_net is
mirrored too (flat PDF path unit price), and Build treats a null total_net as
"no price" like the flat path. The editor checks for a set before offering the
modes and the menu entry stays available for sets added later.
ADR 0009 is superseded by 0019; INVOICE_SET_PRICING.md rewritten accordingly.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- InvoiceService.ResolveInlineImagesAsync now fetches an invoice's
distinct whitelisted images concurrently (SemaphoreSlim-capped at 4)
instead of one at a time. It ran inline on every invoice render,
including repeated draft-preview renders, so N sequential mfr-portal
round-trips could otherwise add N x latency to a user-facing request.
- Add fuchs.invoice.inlineimages.fetches (counter, tagged by outcome:
success/http_error/bad_content_type/oversized/exception) and
fuchs.invoice.inlineimages.resolve.duration (histogram) to
FuchsTelemetry, matching the project's observability convention for
outbound-HTTP operations.
- MigraDocExtensions.HtmlConverter's pre-resolved-image parameter is
renamed inlineImages -> resolvedImages with generic docs: the
converter fetches nothing and knows nothing about invoices — which
images to resolve, and any whitelist/network policy, is entirely the
caller's concern (InvoiceService, ADR 0018 today; any future caller
tomorrow).
- Add tests asserting fetches actually overlap (not just faster) and
that the new counter/histogram are recorded.
- Update ADR 0018 to describe the concurrent fetch, telemetry, and the
generalized converter parameter.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- MigraDocExtensions: fix a bug where capping an oversized explicit
width+height image re-derived the other dimension from the image's
native pixel ratio instead of scaling both sides uniformly, silently
distorting a deliberately non-native-ratio (e.g. stretched) image.
- InvoiceService: derive the mfr-portal image whitelist prefix from the
existing Fds:MFR_host config setting (via MFRClientConfig.RestRoot)
instead of a second hardcoded literal, so it can't drift from the
actual configured host; parse <img> sources with HtmlAgilityPack
(already used by the PDF's own HTML converter) instead of a regex, so
a decoy attribute like data-src can't be picked up in place of the
real src; dedup fetch URLs with a HashSet instead of O(n^2) List
scanning; reuse FuchsPdf.MigraDocFilenameFromByteArray instead of
re-spelling the "base64:" prefix.
- Fix a stale log message and comment left over from the
GenerateInvoicePdf -> GenerateInvoicePdfAsync rename, and update
INVOICE_LIFECYCLE.md's references to the old sync method name.
- Add tests for the config-derived whitelist, a decoy-attribute src,
and the 20-image-per-invoice fetch cap boundary.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Fuchs/gulpfile.js: pass encoding:false to gulp.src() in the copy task —
vinyl-fs otherwise UTF-8-decodes binary files by default, which was
silently corrupting the glyphicons webfont (and any other binary asset
copyconfig.json matches) on every `gulp copy`/`gulp all` run.
- Invoice/reminder editor: replace the row's flat action icons (edit/
delete/insert/set-price) with a single context button opening a
dropdown menu, reusing the existing $ocms.menu toolbar mechanism.
- Fix "-1" leaking into the Pos. column for Text/Title rows: $inv.rrw's
`dta.position || dta.SortOrder` fallback treated the numbering's
intentional empty string as unset and fell back to SortOrder (-1 for
such rows); now only a genuinely uncomputed position falls back.
- Zusatzempfänger (CC) is now edited together with the main invoice
email in one combined dialog ($inv.emailFrm/$inv.eEmail), wired into
all three entry points (create, continue, storno) instead of a
separate hover-revealed row with its own edit target.
- Add tooltips to every row/header edit control, including several that
had none before (header field pencils, the request/section title
edit button), phrased as object+verb ($rct.frmTT).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The invoice line editor already shows <img> pictures pulled from the mfr
portal, but the PDF pipeline's HTML-to-MigraDoc converter had no <img>
handler, so they never appeared on the actual invoice.
InvoiceService now fetches only https://portal.mobilefieldreport.com/mfr/*
image sources before rendering (size/type/count capped, failures logged and
skipped) and hands the resolved images down through ApplyInvoice into the
HTML converter, which renders them inline at their original HTML position
and size (px converted to cm, capped against layout overflow). See ADR 0018.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previous gulp-copy refresh (3111347) mangled the binary
eot/ttf/woff/woff2 files through a text-mode path, replacing invalid
byte sequences with U+FFFD and desyncing each file's internal length
header from its actual size — hence the browser's "Failed to decode
downloaded font" error in production. Reverted the four files to the
last known-good (pre-corruption) bytes and marked font extensions as
binary in .gitattributes so text=auto can never normalize them again.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Picks up the currently installed glyphicons font from node_modules,
which now includes the arrow-up/arrow-down/triangle-right/
triangle-bottom glyphs used by the set-structure dialog's reorder and
fold controls (ADR 0016) — the previously copied font predates those
icons. Also adds an empty scratch file the user had open in
Fuchs/tmp/.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds the InvoiceLineNumbering class that InvoiceDraftCalculator.
RecomputePositions, InvoiceSetPricing.AssignPositionLabels and their
callers already depend on (this dependency was pushed in ecc74cf
without the class itself, breaking the build) — position labels are
now hierarchical ("1", "1.1", "1.2", …) matching a line's ADR 0016
nesting level, computed by one pure, shared algorithm for both the
online editor and the PDF so they can never disagree (ADR 0008).
Also fixes an InvoiceDraftServiceTests assertion that still expected
the old flat integer position field; it is a string label now.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lets an invoice carry an additional Cc recipient alongside the primary
SendToEmail, sent via the ProcessWeb mailer API. Honors the
Fuchs:Email:OverrideRecipient dev safety net: when the override is
active, Cc is dropped entirely (only the single override inbox is
ever reached), never leaked to it.
- IComService.SendEmailAsync / ProcessWebComService: new optional
`cc` parameter, included in the mailer payload only when non-empty.
- FdsInvoiceData.RawInvoiceEmailCc + BuildInvoiceParams: reads
`new.invoiceemailcc`, posts @SendToEmailCc.
- IntranetController.Requests: reads SendToEmailCc from the loaded
invoice row and passes it through on send/resend.
- Fuchs_Database: SendToEmailCc column on fds__invoices, threaded
through fds__createInvoice/fds__setInvoice/fds__getInvoice and the
fds__tt__invoice_base/_core table types.
- fis.req_txt_de.js: "Zusatzempfänger (CC)" field label.
- Tests: ProcessWebComServiceTests (Cc included/omitted/dropped under
override); SystemStatusServiceTests mocks updated for the new
IComService.SendEmailAsync parameter.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>