================================================================================

 BrightInvoicesController - Summary & Response Structures

 File: SonWinCommonAPI/Controllers/BrightInvoicesController.cs

================================================================================


--------------------------------------------------------------------------------

 WHAT THE CONTROLLER DOES

--------------------------------------------------------------------------------

Exposes a single read-only endpoint that returns a customer's invoices within a date range.

The domain is SonWin billing: invoice header rows live in Sonlinc.AKOND, enriched with supply type (AUDEBFORS), PBSI payment rows (also in AKOND), and a "has a rendered PDF" existence check (AKONDDOC -> BDOC).

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.

--------------------------------------------------------------------------------

 METHODS

--------------------------------------------------------------------------------

1) GetInvoices

   Route:    GET accounts/{accountId}/invoices

   Auth:     [Authorize] (class-level); [ApiController]

   Params:  

   Body:     none (GET)

   Returns: 

   What it does:

     Validates the customer and the input range, resolves the company's invoice visibility filters, runs the list query, and maps each row to a BrightInvoice.

     Returns the (possibly empty) list with 200.

     Validation that affects the result (all failures THROW -> surface as HTTP 500):


     Validation that affects the result (all surface as HTTP 500 with a message):


     InvoicesValidator.ValidateGetInvoicesInput  (invoked as "GetInvoices"):

       (A missing/unparseable date query param binds to DateTime.MinValue, which is below SQL Server's datetime min; the range check catches this up front to avoid a cryptic SQL error.)

--------------------------------------------------------------------------------

 FILTER RESOLUTION (InvoiceListFilters)

--------------------------------------------------------------------------------

Resolved in BrightInvoiceRepository.GetByCustomerAsync as "local appsettings config (Settings.Invoices:*) ?? company BCHEC flag".

The three UDSMARK visibility variants are independent and compose. Each active flag appends its own SQL clause; inactive flags add nothing (no runtime OR-gates).

Always-applied (Base query) filters, independent of the toggles above:

--------------------------------------------------------------------------------

 RESPONSE STRUCTURE (IEnumerable<BrightInvoice>)

--------------------------------------------------------------------------------

The endpoint returns a JSON array of invoice objects. Each object contains a nested `Info` array of supplementary key/value (or title/value) entries.

[
    {
      "Id":          <value>,    // Composite: $"{InstNr}-{ForbnNr}-{UdebNr}-{Id}" = AKOND.INSTNR-FORBNR-UDEBNR-REGNINGNR
      "ServiceId":   <value>,    // HARDCODED NULL. Previous: AUDEBFORS.FORSYNINGSART (+AFREGNTYPE) via ForsyningsartMapper.
  El-only join => in practice "consumption_trade" or null (a supply type, not an identifier — omitted; see limitations).
      "InvoiceDate": <value>,    // Data: AKOND.BILAGSDATO  (DateTimeOffset, UTC offset 0) | null
      "DueDate":     <value>,    // Data: AKOND.FORFDATO    (DateTimeOffset, UTC offset 0) | null
      "Period":      <value>,    // Derived from StartDate/EndDate month-span: "1month"/"2month"/"3month"/"6month"/"12month"
  (defaults to "1month" when dates missing or other span)
      "StartDate":   <value>,    // Data: AKOND.DATOFRA     (DateTimeOffset, UTC offset 0) | null
      "EndDate":     <value>,    // Data: AKOND.DATOTIL     (DateTimeOffset, UTC offset 0) | null
      "RemainingAmount": <value>,// Data: running account saldo (SUM of 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
      "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.
      // Every entry carries BOTH Key AND Title (EGCommon emits null for whichever is unset).
      "Info": [
        { "Key": "invoiceNumber",   "Title": "Invoice number",   "Value": <Id> },         // Data: AKOND.REGNINGNR (string)
        { "Key": "invoiceDate",     "Title": "Invoice date",     "Value": <InvoiceDate> },// Data: AKOND.BILAGSDATO
  (DateTimeOffset|null)
        { "Key": "dueDate",         "Title": "Due date",         "Value": <DueDate> },    // Data: AKOND.FORFDATO
  (DateTimeOffset|null)
        { "Key": "totalAmount",     "Title": "Total amount",     "Value": <TotalAmount> },// Data: AKOND.KR .ToString() | null
        { "Key": "period",          "Title": "Period",           "Value": <Period> },     // Derived period string
        { "Key": "remainingAmount", "Title": "Remaining amount", "Value": <Saldo> },      // Data: running saldo .ToString() |
  null
        { "Key": "status",          "Title": "Status",           "Value": <InvoiceStatus> },// Derived status string
        { "Key": "description",     "Title": "Description",      "Value": <Tekst> },      // Data: AKOND.TEKST
        { "Key": "tekst",           "Title": "Tekst",            "Value": <Tekst> }       // Data: AKOND.TEKST (duplicate of
  description)
        // NOTE: "ocr" key is NOT emitted (source field not yet identified - TODO)
      ]
    }
  ]

Field origin detail (top-level BrightInvoice): 

INVOICE STATUS LOGIC (MapInvoiceStatus, first match wins):

  1. KORTSTATUS = 99                       -> "cancelled"  (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;

(PBSI rows are negative), ISNULL(...,0). Not exposed as its own response field — only feeds RemainingAmount and the status derivation.