...
--------------------------------------------------------------------------------
BrightProductsController is meant to expose Exposes a single read endpoint that returns the price products and / tariffs for atied to
specific one customer and service + one service (metering point / EAN number) within a date range, .
The data is read from the SonWin /(Sonlinc) contract tables;
The query selects the contract "lines" for the requested EAN numbersystem, shaped into the "Bright" product contract. On any thrown exception logs the error and returns HTTP 500 with the exception message in the body.
--------------------------------------------------------------------------------
...
--------------------------------------------------------------------------------
1) GetProducts
Route: GET / bright/products
Auth: [Authorize] (controller-level)
Params:
- customerId ( required
- query)
- serviceId ( required
- query)
- DateFrom ( required
- query DateTime)
- DateTo ( required
- query DateTime) (CancellationToken)
- CancellationToken ct
Body: none
Returns:
- 200 OK -> IEnumerable<BrightProduct>
- 400 BadRequest
- BadRequest (declared ; see note below
- , never produced - see NOTE)
- 401 Unauthorized (from auth middleware, not the action)
What it does:
Return the list of price products/tariffs for a given customer
and service that are valid within the requested date range.
It first validates the customer exists , then asks
the repository for the matching DbProduct rows, groups them by ServiceId,
...
- Validates the customer
- validates the inputs
- queries active contracts for the customer + EAN within the date range
- validates that every returned row belongs to the requested serviceId
- Groups by AftageNr
- Maps each group to a BrightProduct with a
...
- list of BrightPrice lines.
Validation that affects the result (all ALL failures surface as HTTP 500 with a message:
Customer validation (AccountValidator.ValidateAccountGetOutput):
- Customer not found
- no customer row -> "No customer found with Id ' {id}
- <id>'." More
- more than one active customer
- row -> "Unique active customer could not be identified. Id: ' {id}'."
- <id>'."
- row has empty CustomerId -> "The customer returned did not have any CustomerId."
- returned id != requested -> "Returned customer '<x>' does not match requested '<id>'."
Input validation (ProductsValidator.ValidateGetProductsAsyncInput):
- customerId blank -> "Missing parameter 'CustomerId'."
- serviceId blank -> "Missing parameter 'ServiceId'."
- serviceId non-digit -> "Parameter 'ServiceId' must contain digits only."
- DateFrom == default -> "Missing parameter 'DateFrom'."
- DateTo == default -> "Missing parameter 'DateTo'."
- DateFrom >= DateTo -> "'DateFrom' must be before 'DateTo'."
Output validation (ProductsValidator.ValidateGetProductsAsyncOutput):
- Any row.AftageNr != serviceId -> "Products belong to wrong ServiceId(s):
- <ids>. Expected ServiceId: <id>.
- CustomerId <id>, daterange: <from>-<to>"
--------------------------------------------------------------------------------
RESPONSE STRUCTURE (IEnumerable<BrightProduct>)
--------------------------------------------------------------------------------
Top level is a JSON array of BrightProduct. One element per distinct AftageNr
(EAN). Because the query filters on a single @EanNumber, in practice there is
one group, and each contract row becomes one entry in that group's Prices list.
[
{
"ValidFrom": null, // Hardcoded null (real: g.First().ValidFrom, commented out)
"ValidTo": null, // Hardcoded null (real: g.First().ValidTo, commented out)
"ServiceId": "<serviceId>", // Echo of request serviceId param (real: g.Key/AftageNr, commented out)
"Prices": [
{
"Value": 0, // Hardcoded 0 (real: r.Value, commented out; also not selected by SQL)
"Type": "kWh", // Hardcoded "kWh" (real: r.Type, commented out; not selected by SQL)
"Note": null, // Hardcoded null (real: r.Note, commented out; not selected by SQL)
"Name": "kWhPrice", // Hardcoded "kWhPrice" (real: r.Name, commented out; not selected by SQL)
"ValidFrom": "<datetime>", // Data: AFORBKONTR.FRADATO (DbProduct.ContractActiveFrom)
"ValidTo": "<datetime>", // Data: AFORBKONTR.TILDATO (DbProduct.ContractActiveTo); null = open-ended
"ValidHours": [], // Not mapped (TODO: pending DB schema)
"ValidMonth": [], // Not mapped (TODO: pending DB schema)
"ValidPart": null, // Not mapped (TODO: pending DB schema)
"ValidFraction": null, // Not mapped (TODO: pending DB schema)
"SubtractMeasurement": null// Not mapped (TODO: pending DB schema)
}
]
}
]
Notes:
- Property names above are the C# names; no [JsonPropertyName] attributes are
present, so serialization uses the configured default casing.
- Only TWO fields carry real data: Price.ValidFrom and Price.ValidTo (the
contract's FRADATO/TILDATO). Everything else is hard-coded, null, or unmapped.
- Product.ServiceId is the request input, so the output ServiceId always equals
the requested serviceId by construction.
- Grouping is by DbProduct.AftageNr (EANNR). Since the WHERE clause pins a
single @EanNumber, expect a single product group in normal use.
- The output validator rejects rows whose AftageNr differs from the requested
serviceId; given the SQL filter (kontr.EANNR = @EanNumber, AftageNr = EANNR)
this should not normally trigger.
--------------------------------------------------------------------------------
DATA SOURCE (SQL: ProductSql.GetActiveProducts)
--------------------------------------------------------------------------------
Bound parameters (BrightProductRepository.GetActiveProductsAsync):
@CompanyId = Settings.CompanyId
@CustomerId = customerId (request)
@EanNumber = serviceId (request, "edielNumber")
@StartDate = DateFrom (request)
@EndDate = DateTo (request)
Tables / joins:
FROM Sonlinc.AFORD ford (consumer)
LEFT JOIN Sonlinc.AFORBKONTR kontr (contract link)
ON ford.FIRMANR = kontr.FIRMANR AND ford.FORBNR = kontr.FORBNR
INNER JOIN Sonlinc.BAKONTRAKT bKontr (actual contracts)
ON bKontr.FIRMANR = ford.FIRMANR AND bKontr.KONTRAKT = kontr.KONTRAKT
INNER JOIN Sonlinc.AUDEBFORS fors (supply type)
ON fors.FIRMANR/INSTNR/FORBNR = ford.*
INNER JOIN Sonlinc.audeb deb (debtor; electrical only)
ON deb.* = ford.* AND deb.UDEBNR = fors.UDEBNR
(A commented-out DGF/energikilde LEFT JOIN block is left in place for future use.)
WHERE:
ford.FIRMANR = @CompanyId
AND ford.KUNDENR = @CustomerId
AND kontr.EANNR = @EanNumber
AND ISNULL(ford.STATUS2N,0) < 1 (active consumer)
AND ISNULL(fors.FORSYNINGSART,0) = 0 (electrical supply)
AND (kontr.TILDATO IS NULL OR kontr.TILDATO >= @StartDate)
AND kontr.FRADATO <= @EndDate
ORDER BY ContractName
Selected columns -> DbProduct:
ford.KUNDENR -> CustomerId (unused by mapper)
ISNULL(kontr.kontrakt,'') -> ContractName (unused by mapper)
kontr.FRADATO -> ContractActiveFrom -> Price.ValidFrom
kontr.TILDATO -> ContractActiveTo -> Price.ValidTo
ISNULL(bkontr.TYP,'') -> ContractType (unused by mapper)
ISNULL(bkontr.LINTYPE,'') -> LineType (unused by mapper)
kontr.EANNR -> AftageNr (grouping key + output validation)
DbProduct columns NOT selected by the SQL (always null/default):
Value, Type, Name, Note, PriceValidFrom, PriceValidTo
================================================================================Run claude !