oprava datumu: prijem data bez zony jako UTC
SDK validuje Post/Patch modely pred odeslanim a u kazde polozky DateTime vyzaduje Kind == Utc. Datum bez zony, napriklad "2026-08-25", nacetl Newtonsoft jako Unspecified, takze POST /issued-invoices koncil chybou "DateTime must be in UTC format" jeste pred volanim iDokladu. - DateTimeZoneHandling.Utc: hodnota bez zony dostane Kind Utc bez posunu, hodnota s offsetem se prepocita do UTC - ExceptionHandlingMiddleware zachytava IdokladValidationException a vraci 400 se seznamem vadnych vlastnosti misto prazdne 500 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2f3afb6af0
commit
73f01d0228
@@ -52,6 +52,16 @@ public sealed class ExceptionHandlingMiddleware
|
|||||||
["idokladError"] = ex.AuthenticationError?.Error,
|
["idokladError"] = ex.AuthenticationError?.Error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
catch (IdokladValidationException ex)
|
||||||
|
{
|
||||||
|
// The SDK validates Post/Patch models before sending them. This does not derive from
|
||||||
|
// IdokladBaseException, so without this catch the request ended as an empty 500.
|
||||||
|
_logger.LogWarning("iDoklad model validation failed: {Message}", ex.Message);
|
||||||
|
await WriteProblem(context, HttpStatusCode.BadRequest, ex.Message, new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["invalidProperties"] = ex.InvalidProperties?.Keys.ToArray(),
|
||||||
|
});
|
||||||
|
}
|
||||||
catch (IdokladBaseException ex)
|
catch (IdokladBaseException ex)
|
||||||
{
|
{
|
||||||
// Other SDK-level failures (malformed responses, batch errors, etc.).
|
// Other SDK-level failures (malformed responses, batch errors, etc.).
|
||||||
|
|||||||
@@ -49,6 +49,13 @@ builder.Services
|
|||||||
{
|
{
|
||||||
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
|
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
|
||||||
|
|
||||||
|
// The SDK validates every DateTime member of a Post/Patch model with a UTC check and
|
||||||
|
// rejects the model when Kind is not Utc. Newtonsoft's default (RoundtripKind) leaves a
|
||||||
|
// date without a zone, e.g. "2026-08-25", as Unspecified, so such a request failed
|
||||||
|
// validation before it ever reached iDoklad. Utc marks the value as UTC without shifting
|
||||||
|
// it and converts values that do carry an offset.
|
||||||
|
options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
|
||||||
|
|
||||||
// Some IdokladSdk converters attached to model members only support reading (their
|
// Some IdokladSdk converters attached to model members only support reading (their
|
||||||
// WriteJson throws NotImplementedException), which makes responses carrying such models
|
// WriteJson throws NotImplementedException), which makes responses carrying such models
|
||||||
// fail. The resolver bypasses them on the write path; the existing naming strategy is
|
// fail. The resolver bypasses them on the write path; the existing naming strategy is
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# Datumy v requestech a UTC kontrola SDK
|
||||||
|
|
||||||
|
## Problém
|
||||||
|
|
||||||
|
`POST /issued-invoices` (a stejně tak další Post/Patch endpointy) končil chybou HTTP 500
|
||||||
|
s prázdným tělem. V logu kontejneru:
|
||||||
|
|
||||||
|
```text
|
||||||
|
IdokladSdk.Exceptions.IdokladValidationException: Model is not valid.
|
||||||
|
DateTime must be in UTC format.
|
||||||
|
DateTime must be in UTC format.
|
||||||
|
DateTime must be in UTC format.
|
||||||
|
DateTime must be in UTC format
|
||||||
|
at IdokladSdk.Clients.BaseClient.ValidateModel[T](T model)
|
||||||
|
at IdokladSdk.Clients.BaseClient.PostAsync[TPostModel,TGetModel](String resource, TPostModel model, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
Chyby byly čtyři, protože model faktury má čtyři datumy: `DateOfIssue`, `DateOfMaturity`,
|
||||||
|
`DateOfTaxing` a `DateOfVatApplication`.
|
||||||
|
|
||||||
|
Příčina:
|
||||||
|
|
||||||
|
- Klient posílá datum bez časové zóny, například `"DateOfIssue":"2026-08-25"`.
|
||||||
|
- Newtonsoft s výchozím `DateTimeZoneHandling.RoundtripKind` takovou hodnotu načte jako
|
||||||
|
`DateTimeKind.Unspecified`.
|
||||||
|
- SDK před odesláním requestu validuje model a u každé položky `DateTime` vyžaduje `Kind == Utc`.
|
||||||
|
Validace tedy selhala ještě předtím, než se cokoli odeslalo do iDokladu.
|
||||||
|
- `IdokladValidationException` dědí z `System.ComponentModel.DataAnnotations.ValidationException`,
|
||||||
|
ne z `IdokladBaseException`. `ExceptionHandlingMiddleware` ji proto nezachytil a request skončil
|
||||||
|
jako neošetřená 500 bez těla. Z odpovědi nebylo poznat, co je špatně.
|
||||||
|
|
||||||
|
Ověřeno, že s tím nesouvisí změna serializace z [serializace-odpovedi.md](serializace-odpovedi.md).
|
||||||
|
Deserializace stejného JSONu dává `Kind=Unspecified` shodně s původním `DefaultContractResolver`
|
||||||
|
i s novým `SdkContractResolver`.
|
||||||
|
|
||||||
|
## Řešení
|
||||||
|
|
||||||
|
| Soubor | Změna |
|
||||||
|
| --- | --- |
|
||||||
|
| `Program.cs` | `SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc` |
|
||||||
|
| `Infrastructure/ExceptionHandlingMiddleware.cs` | nový `catch (IdokladValidationException)` mapovaný na HTTP 400 |
|
||||||
|
|
||||||
|
Detaily:
|
||||||
|
|
||||||
|
- `DateTimeZoneHandling.Utc` u hodnoty bez zóny pouze nastaví `Kind` na `Utc`, hodnotu neposouvá.
|
||||||
|
Z `"2026-08-25"` vznikne `2026-08-25T00:00:00Z`, takže datum zůstává stejné.
|
||||||
|
- Hodnota, která zónu nese (například `"2026-08-25T10:00:00+02:00"`), se přepočítá do UTC.
|
||||||
|
To je požadované chování, iDoklad pracuje s UTC.
|
||||||
|
- Nastavení platí pro celý serializer, tedy pro všechny agendy, ne jen pro vydané faktury.
|
||||||
|
- Nový `catch` vrací `problem+json` se statusem 400, zprávou ze SDK a seznamem vadných vlastností
|
||||||
|
v poli `invalidProperties`. Validační chyba už neskončí jako prázdná 500.
|
||||||
|
|
||||||
|
## Ověření
|
||||||
|
|
||||||
|
1. `dotnet build -c Release` bez chyb.
|
||||||
|
2. Deserializace původního JSONu z logu klienta:
|
||||||
|
- před změnou všechny čtyři datumy `Kind=Unspecified`
|
||||||
|
- po změně všechny čtyři `Kind=Utc` a shodná hodnota data
|
||||||
|
3. Běh služby lokálně, `POST /issued-invoices` se stejným tělem, jaké dřív padalo:
|
||||||
|
- dříve: HTTP 500, prázdné tělo, v logu `DateTime must be in UTC format`
|
||||||
|
- nyní: HTTP 401 `invalid_client`, tedy request prošel validací a došel až k přihlášení
|
||||||
|
do iDokladu (test běžel s neplatnými credentials)
|
||||||
|
4. Model s chybějícími povinnými poli vrací HTTP 400 se seznamem chyb.
|
||||||
|
|
||||||
|
Po nasazení musí projít:
|
||||||
|
|
||||||
|
```text
|
||||||
|
POST https://services.csbot.cz/apps/idoklad/issued-invoices
|
||||||
|
```
|
||||||
|
|
||||||
|
s datumy ve tvaru `2026-08-25`.
|
||||||
@@ -87,6 +87,10 @@ Očekávaný výsledek je HTTP 200 a v logu žádná `NotImplementedException`.
|
|||||||
## Poznámka mimo rozsah opravy
|
## Poznámka mimo rozsah opravy
|
||||||
|
|
||||||
Pokud iDoklad vrátí odpověď, kterou SDK neumí zpracovat, vyhodí `IdokladValidationException`.
|
Pokud iDoklad vrátí odpověď, kterou SDK neumí zpracovat, vyhodí `IdokladValidationException`.
|
||||||
Ta není potomkem `IdokladBaseException`, takže ji `ExceptionHandlingMiddleware` nezachytí
|
Ta není potomkem `IdokladBaseException`, takže ji `ExceptionHandlingMiddleware` nezachytil
|
||||||
a request skončí jako neošetřená 500 místo 502. Zjištěno při testu proti falešnému API,
|
a request skončil jako neošetřená 500 místo 502. Zjištěno při testu proti falešnému API,
|
||||||
neopravováno, protože to nesouvisí se serializací odpovědí.
|
tehdy neopravováno, protože to nesouvisí se serializací odpovědí.
|
||||||
|
|
||||||
|
Doplněno později: tato výjimka se ve skutečnosti vyhazuje hlavně při validaci Post/Patch modelu
|
||||||
|
před odesláním a způsobovala prázdné 500. Middleware ji nyní zachytává a vrací 400,
|
||||||
|
viz [datumy-utc.md](datumy-utc.md).
|
||||||
|
|||||||
Reference in New Issue
Block a user