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>
438 lines
12 KiB
TypeScript
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}` : ""}`,
|
|
);
|
|
},
|
|
});
|