Files
idoklad/documentation/datumy-utc.md
T
JiriUhlirandClaude Opus 5 225161e4ca revize endpointu: cteni NullableProperty, filtr v UTC, attachments
Kontrola vsech 180 operaci proti falesnemu iDoklad API a round trip
331 modelu SDK v obou smerech.

- SdkNullablePropertyConverter doplnuje cteni NullableProperty<T>.
  Konvertor SDK umi jen zapis, takze PATCH s takovou polozkou koncil
  prazdnou 500 uz pri cteni tela. Tykalo se 29 modelu. Zapis zustava
  na konvertoru SDK, odchozi payload se nemeni.
- Datum uvnitr NullableProperty se normalizuje na UTC stejne jako
  zbytek serializace.
- ListModifiers parsuje datum ve filtru s AdjustToUniversal a
  AssumeUniversal. Filtr se zonou se drive posouval o offset.
- POST /attachments kontroluje FileName a FileBytes, SDK na ne sahalo
  bez kontroly na null a vracelo neosetrenou 500.
- ExceptionHandlingMiddleware ma posledni zachyt, zadny request uz
  nekonci prazdnou 500.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 11:13:21 +02:00

3.3 KiB

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:

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. 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:

POST https://services.csbot.cz/apps/idoklad/issued-invoices

s datumy ve tvaru 2026-08-25.

Navazující revize všech ostatních endpointů: revize-endpointu.md.