...
The result is mapped into the Bright-facing BrightInvoice model.
SonWin is a BALANCE-FORWARD ledger: payments are not matched per-invoice.
The "remaining amount" and the payment status are both derived from the running account balance (løbende saldo) as of each invoice line, computed in SQL.
DEV info
When running on local machine bypassing the Swagger UI:
Visibility/filtering is configuration-driven. Each filter toggle is resolved as "local appsettings config wins, otherwise defer to the company's BCHEC flag" (Sonlinc.BCHED, cached in memory).
The SQL is assembled from pure-SQL fragments so only the active clauses appear in the executed statement.
...
Resolved in BrightInvoiceRepository.GetByCustomerAsync as "local appsettings config (Settings.Invoices:*) ?? company BCHEC flag".
The threeUDSMARK three UDSMARK visibility variants are independent and compose. Each active flag appends its own SQL clause; inactive flags add nothing (no runtime OR-gates).
- ExcludeParked ExcludeParked = Settings.InvoiceExcludeParked InvoiceExcludeParked ?? BCHEC VISEJPARKEREDE -> AND (UDSMARK IS NULL OR UDSMARK <= 25) (hide UDSMARK > 25)
- CheckUdsmark CheckUdsmark = Settings.InvoiceCheckUdsmark InvoiceCheckUdsmark ?? BCHEC CHECKUDSMARK -> AND (UDSMARK IS NULL OR = 0 OR BETWEEN 2 AND 25) (also drops UDSMARK = 1)
- ShowOnlyDelivered = Settings.InvoiceShowOnlyDelivered ?? BCHEC VISKUNUDSKREVNE -> UDSMARK <= 25 + SNEX/DSEND delivery check (DELIVERYSTATE IN (5,9))
- ExcludeFutureDated = Settings.InvoiceExcludeFutureDated InvoiceExcludeFutureDated ?? BCHEC EJFREMTID EJFREMTID -> AND BILAGSDATO <= @Today
- ExcludeAfregnTypes = Settings.InvoiceExcludeAfregnTypes (non-empty) ?? BCHEC W11_W12EJAFRTYP W12EJAFRTYP -> AND AFREGNTYPE NOT IN @ExcludeAfregnTypes
- OnlyRendered OnlyRendered = !Settings.InvoiceShowAlsoInvoicesWithoutPdf (default true) -> AND EXISTS (AKONDDOC -> BDOC, DOCTYPE=1, DATALENGTH(PAYLOAD)>0, MIMETYPE MIMETYPE LIKE @PdfMimeType) → @PdfMimeType defaults to application/pdf
Always-applied (Base query) filters, independent of the toggles above:
...
The endpoint returns a JSON array of invoice objects (no wrapper). Each object contains a nested `Info` array of supplementary key/value (or title/value) entries.
| Code Block |
|---|
[ { "Id": <value>, "<InstNr>-<ForbnNr>-<UdebNr>-<Id>", // Data: composite ofComposite: $"{InstNr}-{ForbnNr}-{UdebNr}-{Id}" = AKOND.INSTNR + FORBNR + UDEBNR + REGNINGNR (REGNINGNR cast to varchar AS Id) -FORBNR-UDEBNR-REGNINGNR "ServiceId": "consumption_trade"<value>, // HARDCODED NULL. Previous: // Data: AUDEBFORS.FORSYNINGSART (+ AKOND.AFREGNTYPE for KØLAUDEBFORS.FORSYNINGSART (+AFREGNTYPE) via ForsyningsartMapper;. null for unmapped/unknown/null supply types "DueDate": "2026-01-31T00:00:00+00:00", El-only join => in practice "consumption_trade" or null (a supply type, not an identifier — omitted; see limitations). "InvoiceDate": <value>, // Data: AKOND.FORFDATOBILAGSDATO (DateTimeOffset, UTC offset 0); | null if NULL "InvoiceDateDueDate": "2026-01-01T00:00:00+00:00", //<value>, // Data: AKOND.BILAGSDATOFORFDATO (DateTimeOffset, UTC offset 0); | null if NULL "Period": "1month"<value>, // Derived from StartDate/EndDate month-span: "1month"/"2month"/"3month"/"6month"/"12month" (defaults to "1month" when dates missing or other span) "StartDate": <value>, // Data: derived from AKOND.DATOFRA/DATOTIL month span (1/2/3/6/12month); defaults "1month" if either date null or span unmatched (DateTimeOffset, UTC offset 0) | null "StartDateEndDate": "2026-01-01T00:00:00+00:00"<value>, // Data: AKOND.DATOFRADATOTIL (DateTimeOffset, UTC offset 0); | null if NULL "EndDateRemainingAmount": <value>,// Data: running account saldo (SUM of "2026-01-31T00:00:00+00:00", // Data: AKOND.DATOTIL (DateTimeOffset, UTC offset 0); null if NULL "RemainingAmount": 0.00, AKOND.KR up to this line) - NOT a per-invoice remaining figure. "TotalAmount": <value>, // Data: AKOND.KR (decimal) | null "InvoiceStatus": <value>, // Derived (see status logic below): one of cancelled / credited / paid / overdue / unpaid // Data: AKOND.KR (TotalAmount) - PBSI PaidAmount; null unless BOTH present "TotalAmount": 1953.12, "InvoiceType": <value>, // Derived: "pdf" when a rendered PDF (non-empty BDOC.PAYLOAD with matching MIMETYPE) exists, else "missing" // Flat supplementary list; mostly DUPLICATES the structured fields above, as strings/dates. // Data: AKOND.KR "InvoiceStatus": "paid", Every entry carries BOTH Key AND Title (EGCommon emits null for whichever is unset). "Info": [ { "Key": "invoiceNumber", "Title": "Invoice number", "Value": <Id> }, // Data: derivedAKOND.REGNINGNR (see MapInvoiceStatus); one of unpaid/paid/partly_paid/overdue/cancelled/credited only string) { "InvoiceTypeKey": "invoiceDate", "pdf "Title": "Invoice date", "Value": <InvoiceDate> },// Data: AKOND.BILAGSDATO (DateTimeOffset|null) { "Key": "dueDate", // Data: derived from HasPdf EXISTS check -> "pdf" or "missing" ("html" never produced) "Info": ["Title": "Due date", "Value": <DueDate> }, // Data: AKOND.FORFDATO (DateTimeOffset|null) { "Key": "invoiceNumbertotalAmount", "ValueTitle": "<REGNINGNR>" }, Total amount", "Value": <TotalAmount> },// Data: AKOND.REGNINGNRKR .ToString(as) Id) — NOTE: raw value, NOT the composite top-level Id | null { "Key": "invoiceDateperiod", "ValueTitle": "<offset>Period" }, // Data: AKOND.BILAGSDATO (DateTimeOffset) or null "Value": <Period> }, { "Key": "dueDate", // Derived period string { "Key": "remainingAmount", "ValueTitle": "Remaining amount", "<offset>Value": <Saldo> }, // Data: running saldo AKOND.FORFDATO ToString(DateTimeOffset) or| null { "Key": "totalAmountstatus", "ValueTitle": "1953.12Status" }, // Data: AKOND.KR (.ToString(), so a STRING here vs decimal at top level) "Value": <InvoiceStatus> },// Derived status string { "Key": "perioddescription", "Title": "Description", "Value": "1month"<Tekst> }, // Data: same derivation as top-level Period AKOND.TEKST { "Key": "remainingAmount", "Value": "0.00" }tekst", // Data"Title": TotalAmount"Tekst", - PaidAmount (.ToString(); null unless both present) { "KeyValue": "status",<Tekst> } // "Value": "paid" }, Data: AKOND.TEKST (duplicate of description) // DataNOTE: same"ocr" derivationkey asis top-level InvoiceStatus { "Key": "description",NOT emitted (source field not yet identified - TODO) "Value": "<Tekst>" }, // Data: AKOND.TEKST { "Title": "Tekst", "Value": "<Tekst>" } // Data: AKOND.TEKST (DUPLICATE of description; uses Title (untranslated) instead of Key) // "ocr" -> Not mapped (TODO: source field in SonWin not yet identified) ] } ] |
Field origin detail (top-level BrightInvoice):
- Id <- composite "{InstNr}-{ForbnNr}-{UdebNr}-{Id}" (Id = REGNINGNR)
- ServiceId <- ForsyningsartMapper.MapToBrightServiceType(ForsyningsArt, AfregnType)
- DueDate <- AKOND.FORFDATO
- InvoiceDate <- AKOND.BILAGSDATO
- Period <- MapPeriod(DATOFRA, DATOTIL)
- StartDate <- AKOND.DATOFRA
- EndDate <- AKOND.DATOTIL
- RemainingAmount <- AKOND.KR - PBSI PaidAmount (null unless both present)
- TotalAmount <- AKOND.KR
- InvoiceStatus <- MapInvoiceStatus(row).GetDisplayName()
- InvoiceType <- MapInvoiceType(row) ("pdf" if HasPdf else "missing")
- Info <- list assembled in the mapper (see above)
]
}
] |
Field origin detail (top-level BrightInvoice):
- Id <- composite "{InstNr}-{ForbnNr}-{UdebNr}-{Id}" (Id = REGNINGNR)
- ServiceId <- ForsyningsartMapper.MapToBrightServiceType(ForsyningsArt, AfregnType)
- DueDate <- AKOND.FORFDATO
- InvoiceDate <- AKOND.BILAGSDATO
- Period <- MapPeriod(DATOFRA, DATOTIL)
- StartDate <- AKOND.DATOFRA
- EndDate <- AKOND.DATOTIL
- RemainingAmount <- AKOND.KR - PBSI PaidAmount (null unless both present)
- TotalAmount <- AKOND.KR
- InvoiceStatus <- MapInvoiceStatus(row).GetDisplayName()
- InvoiceType <- MapInvoiceType(row) ("pdf" if HasPdf else "missing")
- Info <- list assembled in the mapper (see above)
INVOICE STATUS LOGIC (MapInvoiceStatus, first match wins):
1. KORTSTATUS = 99 InvoiceStatus derivation (MapInvoiceStatus), in order:
1. KORTSTATUS == 99 -> "cancelled"
2. TotalAmount (KR) < 0 -> "credited"
3. SettlementDate (UDLIGNDATO) set -> "paid" (regardless of PaidAmount)
4. PaidAmount >= TotalAmount -> "paid"
5. DueDate < today (and not fully paid)-> "overdue"
6. PaidAmount > 0 -> "partly_paid"
7. otherwise -> "unpaidcancelled"
(collection / reminder / deferred_* / investigation / paid_out are never returned — TODO.)
PaidAmount source: OUTER APPLY over Sonlinc.AKOND where TARIFART='PBSI' for the same INSTNR+FORBNR+UDEBNR+REGNINGNR+FIRMANR; SUM(KR*ANTAL)*-1
(PBSI rows are negative), ISNULL(...,0). Not exposed as its own response field — only feeds RemainingAmount and the status derivation.
confirmed cancelled)
2. TotalAmount (KR) < 0 -> "credited" (credit note)
3. UDLIGNDATO set OR running saldo <= 0 -> "paid" (account square through this line; balance-forward)
4. DueDate (FORFDATO) in the past -> "overdue"
5. otherwise -> "unpaid"
('partly_paid' is intentionally never produced.)
PERIOD LOGIC (MapPeriod): month span = (EndDate - StartDate) in whole months;
- 2→"2month"
- 3→"3month"
- 6→"6month"
- 12→"12month"
- anything else (incl. missing dates or 1-month) -> "1month".
(PBSI rows are negative), ISNULL(...,0). Not exposed as its own response field — only feeds RemainingAmount and the status derivation.DbInvoice columns selected but NOT surfaced as their own response field:
- AfregnType (AKOND.AFREGNTYPE) used only by ServiceId (KØL -> cooling)
- KortStatus (AKOND.KORTSTATUS) used only by status (== 99 -> cancelled)
- KortType (AKOND.KORTTYPE) selected, currently unused in mapping
- SettlementDate (AKOND.UDLIGNDATO) used only by status
- CollectiveBillNr (AKOND.SAMLREGNINGNR) selected; also used as a WHERE filter
- HasPdf (EXISTS check) used only by InvoiceType
- PaidAmount (PBSI sum) used by RemainingAmount + status