import { ApiError, apiFetch } from "@/lib/auth/api-client"; import { CreateOptions, idempotencyHeaders } from "@/lib/offline/idempotency"; import { invalidColonyIdsOfBody } from "./bulk-input"; import type { CreateTreatmentInput, Treatment } from "@/lib/treatments/types"; import type { CreateFeedingInput, Feeding } from "@/lib/feedings/types"; import type { CreateVarroaMeasurementInput, VarroaMeasurement } from "@/lib/varroa/types"; /** Mirrors ApiCore-Backend/src/common/bulk/bulk-limits.ts - keep both in step. */ export const BULK_MAX_COLONIES = 100; export interface BulkCreateResult { bulkId: string; records: T[]; } export interface BulkCreateTreatmentInput extends Omit { colonyIds: string[]; } export interface BulkCreateFeedingInput extends Omit { colonyIds: string[]; } /** What each colony has of its own in a bulk count. */ export interface BulkVarroaEntry { colonyId: string; miteCount?: number | null; calculatedValue?: number | null; assessment?: string | null; } export interface BulkCreateVarroaInput extends Omit< CreateVarroaMeasurementInput, "colonyId" | "miteCount" | "calculatedValue" | "assessment" > { entries: BulkVarroaEntry[]; } function postBulk( path: string, input: unknown, options: CreateOptions, ): Promise> { return apiFetch>(path, { method: "POST", headers: idempotencyHeaders(options), body: JSON.stringify(input), }); } export function createTreatmentsBulk( input: BulkCreateTreatmentInput, options: CreateOptions = {}, ): Promise> { return postBulk("/treatments/bulk", input, options); } export function createFeedingsBulk( input: BulkCreateFeedingInput, options: CreateOptions = {}, ): Promise> { return postBulk("/feedings/bulk", input, options); } export function createVarroaBulk( input: BulkCreateVarroaInput, options: CreateOptions = {}, ): Promise> { return postBulk("/varroa-measurements/bulk", input, options); } /** * The colonies a refused bulk entry could not be recorded for (400 * `invalidColonyIds`: unknown or ended since the pack was loaded). Empty for * every other error. */ export function invalidColonyIdsOf(error: unknown): string[] { return error instanceof ApiError ? invalidColonyIdsOfBody(error.body) : []; } /** * The two 409 texts of the idempotency interceptor that mean „later“. The * server sends no error code for them; api.spec.ts pins both to its source. */ export const RETRY_LATER_MESSAGES = [ "Diese Anfrage wird bereits verarbeitet - bitte kurz warten", "Diese Anfrage wurde gerade abgebrochen - bitte erneut senden", ] as const; /** * 409 „wird bereits verarbeitet“ (the same key is still running) and „gerade * abgebrochen“ (it was just released): nothing is wrong with the entry, the * answer is „später erneut“. Other 409s (key used for another action) are real. */ export function isRetryLaterError(error: unknown): boolean { return ( error instanceof ApiError && error.status === 409 && RETRY_LATER_MESSAGES.some((message) => error.message === message) ); }