Files
app/src/admin/lib/queries/warehouse.ts
BOHA db7a5c3d15 feat(suppliers)!: full customers model - structured address + custom fields
The dodavatele modal is now an exact mirror of the customers modal
(minus the PDF field-order picker): Nazev+ARES, Ulice, Mesto+PSC, Zeme,
ICO+ARES, DIC, and the same "Vlastni pole" editor (maxWidth md).

Two migrations on sklad_suppliers:
- structured street/city/postal_code/country replace the free-text
  address blob (best-effort split: street / PSC regex / city)
- custom_fields LONGTEXT replaces contact_person/email/phone/notes;
  existing values are preserved as labeled custom fields

The encode/decode of the custom_fields blob is shared with customers
via the new utils/custom-fields.ts. The PO PDF supplier block renders
the structured address + custom-field lines like the customer block;
the supplier picker/detail selects and types follow. Legacy clients
sending the dropped keys are silently stripped (tested). Suppliers
list shows Ulice/Mesto instead of the dropped contact columns; search
covers name/ico/city.

BREAKING CHANGE: sklad_suppliers.address, contact_person, email,
phone, notes columns dropped (data migrated); deploy must run
prisma migrate deploy + generate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 20:11:22 +02:00

438 lines
12 KiB
TypeScript

import { queryOptions } from "@tanstack/react-query";
import { jsonQuery, paginatedJsonQuery } from "../apiAdapter";
// --- Types ---
export interface WarehouseItem {
id: number;
item_number: string | null;
name: string;
description: string | null;
category_id: number | null;
unit: string;
min_quantity: number | null;
is_active: boolean;
notes: string | null;
category?: { id: number; name: string } | null;
total_quantity?: number;
available_quantity?: number;
stock_value?: number;
below_minimum?: boolean;
}
export interface WarehouseReceipt {
id: number;
receipt_number: string | null;
supplier_id: number | null;
delivery_note_number: string | null;
delivery_note_date: string | null;
received_by: number | null;
notes: string | null;
status: "DRAFT" | "CONFIRMED" | "CANCELLED";
created_at: string;
modified_at: string | null;
supplier?: { id: number; name: string } | null;
received_by_user?: {
id: number;
first_name: string;
last_name: string;
} | null;
_count?: { items: number };
items?: WarehouseReceiptItem[];
attachments?: WarehouseReceiptAttachment[];
}
export interface WarehouseReceiptItem {
id: number;
receipt_id: number;
item_id: number;
quantity: number;
unit_price: number;
location_id: number | null;
notes: string | null;
item?: WarehouseItem;
location?: { id: number; code: string; name: string } | null;
}
export interface WarehouseReceiptAttachment {
id: number;
receipt_id: number;
file_name: string;
file_mime: string;
file_size: number;
file_path: string;
created_at: string;
}
export interface WarehouseIssue {
id: number;
issue_number: string | null;
project_id: number;
issued_by: number | null;
notes: string | null;
status: "DRAFT" | "CONFIRMED" | "CANCELLED";
created_at: string;
modified_at: string | null;
project?: { id: number; name: string; project_number: string } | null;
issued_by_user?: { id: number; first_name: string; last_name: string } | null;
_count?: { items: number };
items?: WarehouseIssueItem[];
}
export interface WarehouseIssueItem {
id: number;
issue_id: number;
item_id: number;
batch_id: number;
quantity: number;
location_id: number | null;
reservation_id: number | null;
notes: string | null;
item?: WarehouseItem;
batch?: {
id: number;
quantity: number;
unit_price: number;
received_at: string;
} | null;
location?: { id: number; code: string; name: string } | null;
reservation?: { id: number; quantity: number; remaining_qty: number } | null;
}
export interface WarehouseReservation {
id: number;
item_id: number;
project_id: number;
quantity: number;
remaining_qty: number;
reserved_by: number | null;
notes: string | null;
status: "ACTIVE" | "FULFILLED" | "CANCELLED";
created_at: string;
modified_at: string | null;
item?: WarehouseItem;
project?: { id: number; name: string } | null;
reserved_by_user?: {
id: number;
first_name: string;
last_name: string;
} | null;
}
export interface WarehouseInventorySession {
id: number;
session_number: string | null;
notes: string | null;
status: "DRAFT" | "CONFIRMED";
created_at: string;
modified_at: string | null;
items?: WarehouseInventoryItem[];
}
export interface WarehouseInventoryItem {
id: number;
session_id: number;
item_id: number;
location_id: number | null;
system_qty: number;
actual_qty: number;
difference: number;
notes: string | null;
item?: WarehouseItem;
location?: { id: number; code: string; name: string } | null;
}
export interface WarehouseCategory {
id: number;
name: string;
description: string | null;
sort_order: number;
}
export interface WarehouseLocation {
id: number;
code: string;
name: string;
description: string | null;
is_active: boolean;
}
export interface WarehouseSupplierCustomField {
name: string;
value: string;
showLabel: boolean;
_key?: string;
}
export interface WarehouseSupplier {
id: number;
name: string;
ico: string | null;
dic: string | null;
street: string | null;
city: string | null;
postal_code: string | null;
country: string | null;
custom_fields?: WarehouseSupplierCustomField[];
is_active: boolean;
}
export interface MovementLogRow {
date: string;
type: string;
document_number: string;
item_name: string;
quantity: number;
unit_price: number;
supplier_or_project: string;
}
// --- Query Options ---
export const warehouseItemListOptions = (filters: {
search?: string;
page?: number;
perPage?: number;
sort?: string;
order?: string;
category_id?: number;
}) =>
queryOptions({
queryKey: ["warehouse", "items", "list", filters],
queryFn: () => {
const params = new URLSearchParams();
if (filters.search) params.set("search", filters.search);
if (filters.page) params.set("page", String(filters.page));
if (filters.perPage) params.set("per_page", String(filters.perPage));
if (filters.sort) params.set("sort", filters.sort);
if (filters.order) params.set("order", filters.order);
if (filters.category_id)
params.set("category_id", String(filters.category_id));
const qs = params.toString();
return paginatedJsonQuery<WarehouseItem>(
`/api/admin/warehouse/items${qs ? `?${qs}` : ""}`,
);
},
});
export const warehouseItemDetailOptions = (
id: string | undefined,
enabled?: boolean,
) =>
queryOptions({
queryKey: ["warehouse", "items", id],
queryFn: () => jsonQuery<WarehouseItem>(`/api/admin/warehouse/items/${id}`),
enabled: enabled ?? !!id,
});
export const warehouseCategoryListOptions = () =>
queryOptions({
queryKey: ["warehouse", "categories"],
queryFn: () =>
jsonQuery<WarehouseCategory[]>("/api/admin/warehouse/categories"),
});
export const warehouseSupplierListOptions = (filters: {
search?: string;
page?: number;
perPage?: number;
}) =>
queryOptions({
queryKey: ["warehouse", "suppliers", "list", filters],
queryFn: () => {
const params = new URLSearchParams();
if (filters.search) params.set("search", filters.search);
if (filters.page) params.set("page", String(filters.page));
if (filters.perPage) params.set("per_page", String(filters.perPage));
const qs = params.toString();
return paginatedJsonQuery<WarehouseSupplier>(
`/api/admin/warehouse/suppliers${qs ? `?${qs}` : ""}`,
);
},
});
export const warehouseLocationListOptions = (filters?: {
active?: "true" | "false" | "all";
}) =>
queryOptions({
queryKey: ["warehouse", "locations", filters ?? { active: "true" }],
queryFn: () => {
const active = filters?.active ?? "true";
return jsonQuery<WarehouseLocation[]>(
`/api/admin/warehouse/locations?active=${active}`,
);
},
});
export const warehouseReceiptListOptions = (filters: {
search?: string;
page?: number;
perPage?: number;
status?: string;
supplier_id?: number;
date_from?: string;
date_to?: string;
}) =>
queryOptions({
queryKey: ["warehouse", "receipts", "list", filters],
queryFn: () => {
const params = new URLSearchParams();
if (filters.search) params.set("search", filters.search);
if (filters.page) params.set("page", String(filters.page));
if (filters.perPage) params.set("per_page", String(filters.perPage));
if (filters.status) params.set("status", filters.status);
if (filters.supplier_id)
params.set("supplier_id", String(filters.supplier_id));
if (filters.date_from) params.set("date_from", filters.date_from);
if (filters.date_to) params.set("date_to", filters.date_to);
const qs = params.toString();
return paginatedJsonQuery<WarehouseReceipt>(
`/api/admin/warehouse/receipts${qs ? `?${qs}` : ""}`,
);
},
});
export const warehouseReceiptDetailOptions = (id: string | undefined) =>
queryOptions({
queryKey: ["warehouse", "receipts", id],
queryFn: () =>
jsonQuery<WarehouseReceipt>(`/api/admin/warehouse/receipts/${id}`),
enabled: !!id,
});
export const warehouseIssueListOptions = (filters: {
search?: string;
page?: number;
perPage?: number;
status?: string;
project_id?: number;
date_from?: string;
date_to?: string;
}) =>
queryOptions({
queryKey: ["warehouse", "issues", "list", filters],
queryFn: () => {
const params = new URLSearchParams();
if (filters.search) params.set("search", filters.search);
if (filters.page) params.set("page", String(filters.page));
if (filters.perPage) params.set("per_page", String(filters.perPage));
if (filters.status) params.set("status", filters.status);
if (filters.project_id)
params.set("project_id", String(filters.project_id));
if (filters.date_from) params.set("date_from", filters.date_from);
if (filters.date_to) params.set("date_to", filters.date_to);
const qs = params.toString();
return paginatedJsonQuery<WarehouseIssue>(
`/api/admin/warehouse/issues${qs ? `?${qs}` : ""}`,
);
},
});
export const warehouseIssueDetailOptions = (id: string | undefined) =>
queryOptions({
queryKey: ["warehouse", "issues", id],
queryFn: () =>
jsonQuery<WarehouseIssue>(`/api/admin/warehouse/issues/${id}`),
enabled: !!id,
});
export const warehouseReservationListOptions = (filters: {
page?: number;
perPage?: number;
item_id?: number;
project_id?: number;
status?: string;
}) =>
queryOptions({
queryKey: ["warehouse", "reservations", "list", filters],
queryFn: () => {
const params = new URLSearchParams();
if (filters.page) params.set("page", String(filters.page));
if (filters.perPage) params.set("per_page", String(filters.perPage));
if (filters.item_id) params.set("item_id", String(filters.item_id));
if (filters.project_id)
params.set("project_id", String(filters.project_id));
if (filters.status) params.set("status", filters.status);
const qs = params.toString();
return paginatedJsonQuery<WarehouseReservation>(
`/api/admin/warehouse/reservations${qs ? `?${qs}` : ""}`,
);
},
});
export const warehouseInventoryListOptions = (filters: {
page?: number;
perPage?: number;
status?: string;
}) =>
queryOptions({
queryKey: ["warehouse", "inventory", "list", filters],
queryFn: () => {
const params = new URLSearchParams();
if (filters.page) params.set("page", String(filters.page));
if (filters.perPage) params.set("per_page", String(filters.perPage));
if (filters.status) params.set("status", filters.status);
const qs = params.toString();
return paginatedJsonQuery<WarehouseInventorySession>(
`/api/admin/warehouse/inventory-sessions${qs ? `?${qs}` : ""}`,
);
},
});
export const warehouseInventoryDetailOptions = (id: string | undefined) =>
queryOptions({
queryKey: ["warehouse", "inventory", id],
queryFn: () =>
jsonQuery<WarehouseInventorySession>(
`/api/admin/warehouse/inventory-sessions/${id}`,
),
enabled: !!id,
});
export const warehouseStockStatusOptions = (filters?: {
category_id?: number;
}) =>
queryOptions({
queryKey: ["warehouse", "stock-status", filters],
queryFn: () => {
const params = new URLSearchParams();
if (filters?.category_id)
params.set("category_id", String(filters.category_id));
const qs = params.toString();
return jsonQuery<WarehouseItem[]>(
`/api/admin/warehouse/reports/stock-status${qs ? `?${qs}` : ""}`,
);
},
});
export const warehouseBelowMinimumOptions = () =>
queryOptions({
queryKey: ["warehouse", "below-minimum"],
queryFn: () =>
jsonQuery<WarehouseItem[]>("/api/admin/warehouse/reports/below-minimum"),
});
export const warehouseMovementLogOptions = (filters?: {
limit?: number;
type?: string;
project_id?: number;
date_from?: string;
date_to?: string;
}) =>
queryOptions({
queryKey: ["warehouse", "movement-log", filters],
queryFn: () => {
const params = new URLSearchParams();
if (filters?.limit) params.set("limit", String(filters.limit));
if (filters?.type) params.set("type", filters.type);
if (filters?.project_id)
params.set("project_id", String(filters.project_id));
if (filters?.date_from) params.set("date_from", filters.date_from);
if (filters?.date_to) params.set("date_to", filters.date_to);
const qs = params.toString();
return jsonQuery<MovementLogRow[]>(
`/api/admin/warehouse/reports/movement-log${qs ? `?${qs}` : ""}`,
);
},
});