|
- import { clientAuthFetch } from "@/app/utils/clientAuthFetch";
- import { NEXT_PUBLIC_API_URL } from "@/config/api";
-
- const BASE = `${NEXT_PUBLIC_API_URL}/chart`;
-
- function buildParams(params: Record<string, string | number | undefined>) {
- const p = new URLSearchParams();
- Object.entries(params).forEach(([k, v]) => {
- if (v !== undefined && v !== "") p.set(k, String(v));
- });
- return p.toString();
- }
-
- export interface StockTransactionsByDateRow {
- date: string;
- inQty: number;
- outQty: number;
- totalQty: number;
- }
-
- export interface DeliveryOrderByDateRow {
- date: string;
- orderCount: number;
- totalQty: number;
- }
-
- export interface PurchaseOrderByStatusRow {
- status: string;
- count: number;
- }
-
- /** Multi-select filters for purchase charts (repeated `supplierId` / `itemCode` / `purchaseOrderNo` query params). */
- export type PurchaseOrderChartFilters = {
- supplierIds?: number[];
- itemCodes?: string[];
- purchaseOrderNos?: string[];
- /** Single supplier code (drill when row has no supplier id); not used with `supplierIds`. */
- supplierCode?: string;
- };
-
- function appendPurchaseOrderListParams(p: URLSearchParams, filters?: PurchaseOrderChartFilters) {
- (filters?.supplierIds ?? []).forEach((id) => {
- if (Number.isFinite(id) && id > 0) p.append("supplierId", String(id));
- });
- (filters?.itemCodes ?? []).forEach((c) => {
- const t = String(c).trim();
- if (t) p.append("itemCode", t);
- });
- (filters?.purchaseOrderNos ?? []).forEach((n) => {
- const t = String(n).trim();
- if (t) p.append("purchaseOrderNo", t);
- });
- const sc = filters?.supplierCode?.trim();
- if (sc) p.set("supplierCode", sc);
- }
-
- export interface PoFilterSupplierOption {
- supplierId: number;
- code: string;
- name: string;
- }
-
- export interface PoFilterItemOption {
- itemCode: string;
- itemName: string;
- }
-
- export interface PoFilterPoNoOption {
- poNo: string;
- }
-
- export interface PurchaseOrderFilterOptions {
- suppliers: PoFilterSupplierOption[];
- items: PoFilterItemOption[];
- poNos: PoFilterPoNoOption[];
- }
-
- export interface PurchaseOrderEstimatedArrivalRow {
- bucket: string;
- count: number;
- }
-
- export interface PurchaseOrderDetailByStatusRow {
- purchaseOrderId: number;
- purchaseOrderNo: string;
- status: string;
- orderDate: string;
- estimatedArrivalDate: string;
- /** Shop / supplier FK; use for grouping when code is blank */
- supplierId: number | null;
- supplierCode: string;
- supplierName: string;
- itemCount: number;
- totalQty: number;
- }
-
- export interface PurchaseOrderItemRow {
- purchaseOrderLineId: number;
- itemCode: string;
- itemName: string;
- orderedQty: number;
- uom: string;
- receivedQty: number;
- pendingQty: number;
- }
-
- export interface StockInOutByDateRow {
- date: string;
- inQty: number;
- outQty: number;
- }
-
- export interface TopDeliveryItemsRow {
- itemCode: string;
- itemName: string;
- totalQty: number;
- }
-
- export interface StockBalanceTrendRow {
- date: string;
- balance: number;
- }
-
- export interface ConsumptionTrendByMonthRow {
- month: string;
- outQty: number;
- }
-
- export interface StaffDeliveryPerformanceRow {
- date: string;
- staffName: string;
- orderCount: number;
- totalMinutes: number;
- }
-
- export interface StaffOption {
- staffNo: string;
- name: string;
- }
-
- export async function fetchStaffDeliveryPerformanceHandlers(): Promise<StaffOption[]> {
- const res = await clientAuthFetch(`${BASE}/staff-delivery-performance-handlers`);
- if (!res.ok) throw new Error("Failed to fetch staff list");
- const data = await res.json();
- if (!Array.isArray(data)) return [];
- return (data as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
- staffNo: String(r.staffNo ?? ""),
- name: String(r.name ?? ""),
- }));
- }
-
- // Job order
- export interface JobOrderByStatusRow {
- status: string;
- count: number;
- }
-
- export interface JobOrderCountByDateRow {
- date: string;
- orderCount: number;
- }
-
- export interface JobOrderCreatedCompletedRow {
- date: string;
- createdCount: number;
- completedCount: number;
- }
-
- export interface ProductionScheduleByDateRow {
- date: string;
- scheduledItemCount: number;
- totalEstProdCount: number;
- }
-
- export interface PlannedDailyOutputRow {
- itemCode: string;
- itemName: string;
- dailyQty: number;
- }
-
- export async function fetchJobOrderByStatus(
- targetDate?: string
- ): Promise<JobOrderByStatusRow[]> {
- const q = targetDate ? buildParams({ targetDate }) : "";
- const res = await clientAuthFetch(
- q ? `${BASE}/job-order-by-status?${q}` : `${BASE}/job-order-by-status`
- );
- if (!res.ok) throw new Error("Failed to fetch job order by status");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
- status: String(r.status ?? ""),
- count: Number(r.count ?? 0),
- }));
- }
-
- export async function fetchJobOrderCountByDate(
- startDate?: string,
- endDate?: string
- ): Promise<JobOrderCountByDateRow[]> {
- const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
- const res = await clientAuthFetch(`${BASE}/job-order-count-by-date?${q}`);
- if (!res.ok) throw new Error("Failed to fetch job order count by date");
- const data = await res.json();
- return normalizeChartRows(data, "date", ["orderCount"]);
- }
-
- export async function fetchJobOrderCreatedCompletedByDate(
- startDate?: string,
- endDate?: string
- ): Promise<JobOrderCreatedCompletedRow[]> {
- const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
- const res = await clientAuthFetch(
- `${BASE}/job-order-created-completed-by-date?${q}`
- );
- if (!res.ok) throw new Error("Failed to fetch job order created/completed");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
- date: String(r.date ?? ""),
- createdCount: Number(r.createdCount ?? 0),
- completedCount: Number(r.completedCount ?? 0),
- }));
- }
-
- export interface JobMaterialPendingPickedRow {
- date: string;
- pendingCount: number;
- pickedCount: number;
- }
-
- export async function fetchJobMaterialPendingPickedByDate(
- startDate?: string,
- endDate?: string
- ): Promise<JobMaterialPendingPickedRow[]> {
- const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
- const res = await clientAuthFetch(`${BASE}/job-material-pending-picked-by-date?${q}`);
- if (!res.ok) throw new Error("Failed to fetch job material pending/picked");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
- date: String(r.date ?? ""),
- pendingCount: Number(r.pendingCount ?? 0),
- pickedCount: Number(r.pickedCount ?? 0),
- }));
- }
-
- export interface JobProcessPendingCompletedRow {
- date: string;
- pendingCount: number;
- completedCount: number;
- }
-
- export async function fetchJobProcessPendingCompletedByDate(
- startDate?: string,
- endDate?: string
- ): Promise<JobProcessPendingCompletedRow[]> {
- const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
- const res = await clientAuthFetch(`${BASE}/job-process-pending-completed-by-date?${q}`);
- if (!res.ok) throw new Error("Failed to fetch job process pending/completed");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
- date: String(r.date ?? ""),
- pendingCount: Number(r.pendingCount ?? 0),
- completedCount: Number(r.completedCount ?? 0),
- }));
- }
-
- export interface JobEquipmentWorkingWorkedRow {
- date: string;
- workingCount: number;
- workedCount: number;
- }
-
- export async function fetchJobEquipmentWorkingWorkedByDate(
- startDate?: string,
- endDate?: string
- ): Promise<JobEquipmentWorkingWorkedRow[]> {
- const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
- const res = await clientAuthFetch(`${BASE}/job-equipment-working-worked-by-date?${q}`);
- if (!res.ok) throw new Error("Failed to fetch job equipment working/worked");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
- date: String(r.date ?? ""),
- workingCount: Number(r.workingCount ?? 0),
- workedCount: Number(r.workedCount ?? 0),
- }));
- }
-
- export interface JobOrderBoardRow {
- jobOrderId: number;
- code: string;
- status: string;
- planStart: string;
- actualStart: string;
- planEnd: string;
- actualEnd: string;
- materialPendingCount: number;
- materialPickedCount: number;
- processTotalCount: number;
- processCompletedCount: number;
- currentProcessCode: string;
- currentProcessName: string;
- currentProcessStartTime: string;
- /** FG/WIP job stock-in: sum acceptedQty on all linked lines */
- stockInAcceptedQtyTotal: number;
- /** Lines QC-passed, waiting putaway (receiving / received) */
- fgReadyToStockInCount: number;
- fgReadyToStockInQty: number;
- fgInQcLineCount: number;
- fgInQcQty: number;
- fgStockedQty: number;
- /** Same sources as /jo/edit 工藝流程 summary (product process + lines) */
- itemCode: string;
- itemName: string;
- jobTypeName: string;
- reqQty: number;
- outputQtyUom: string;
- productionDate: string;
- /** Sum of line processingTime (matches ProcessSummaryHeader 預計所需時間) */
- planProcessingMinsTotal: number;
- /** Sum of setup + changeover minutes on all lines */
- planSetupChangeoverMinsTotal: number;
- productProcessStart: string;
- /** Σ line durations in decimal minutes (seconds÷60); sub-minute shown; Pass w/o endTime uses planned processing min */
- actualLineMinsTotal: number;
- }
-
- function numField(v: unknown): number {
- if (v == null || v === "") return 0;
- const n = Number(v);
- return Number.isFinite(n) ? n : 0;
- }
-
- function mapJobOrderBoardRow(r: Record<string, unknown>): JobOrderBoardRow {
- const id = r.jobOrderId ?? r.joborderid;
- return {
- jobOrderId: Number(id ?? 0),
- code: String(r.code ?? ""),
- status: String(r.status ?? ""),
- planStart: String(r.planStart ?? r.planstart ?? ""),
- actualStart: String(r.actualStart ?? r.actualstart ?? ""),
- planEnd: String(r.planEnd ?? r.planend ?? ""),
- actualEnd: String(r.actualEnd ?? r.actualend ?? ""),
- materialPendingCount: Number(r.materialPendingCount ?? r.materialpendingcount ?? 0),
- materialPickedCount: Number(r.materialPickedCount ?? r.materialpickedcount ?? 0),
- processTotalCount: Number(r.processTotalCount ?? r.processtotalcount ?? 0),
- processCompletedCount: Number(r.processCompletedCount ?? r.processcompletedcount ?? 0),
- currentProcessCode: String(r.currentProcessCode ?? r.currentprocesscode ?? ""),
- currentProcessName: String(r.currentProcessName ?? r.currentprocessname ?? ""),
- currentProcessStartTime: String(r.currentProcessStartTime ?? r.currentprocessstarttime ?? ""),
- stockInAcceptedQtyTotal: Number(r.stockInAcceptedQtyTotal ?? r.stockinacceptedqtytotal ?? 0),
- fgReadyToStockInCount: Number(r.fgReadyToStockInCount ?? r.fgreadytostockincount ?? 0),
- fgReadyToStockInQty: Number(r.fgReadyToStockInQty ?? r.fgreadytostockinqty ?? 0),
- fgInQcLineCount: Number(r.fgInQcLineCount ?? r.fginqclinecount ?? 0),
- fgInQcQty: Number(r.fgInQcQty ?? r.fginqcqty ?? 0),
- fgStockedQty: Number(r.fgStockedQty ?? r.fgstockedqty ?? 0),
- itemCode: String(r.itemCode ?? r.itemcode ?? ""),
- itemName: String(r.itemName ?? r.itemname ?? ""),
- jobTypeName: String(r.jobTypeName ?? r.jobtypename ?? ""),
- reqQty: numField(r.reqQty ?? r.reqqty),
- outputQtyUom: String(r.outputQtyUom ?? r.outputqtyuom ?? ""),
- productionDate: String(r.productionDate ?? r.productiondate ?? ""),
- planProcessingMinsTotal: numField(r.planProcessingMinsTotal ?? r.planprocessingminstotal),
- planSetupChangeoverMinsTotal: numField(r.planSetupChangeoverMinsTotal ?? r.plansetupchangeoverminstotal),
- productProcessStart: String(r.productProcessStart ?? r.productprocessstart ?? ""),
- actualLineMinsTotal: numField(r.actualLineMinsTotal ?? r.actuallineminstotal),
- };
- }
-
- /** Per-job board rows. With [incompleteOnly], excludes status completed (backend LOWER(status) <> 'completed'). */
- export async function fetchJobOrderBoard(
- targetDate?: string,
- opts?: { incompleteOnly?: boolean },
- ): Promise<JobOrderBoardRow[]> {
- const params: Record<string, string | number | undefined> = {};
- if (targetDate) params.targetDate = targetDate;
- if (opts?.incompleteOnly) params.incompleteOnly = "true";
- const q = buildParams(params);
- const res = await clientAuthFetch(q ? `${BASE}/job-order-board?${q}` : `${BASE}/job-order-board`);
- if (!res.ok) throw new Error("Failed to fetch job order board");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map(mapJobOrderBoardRow);
- }
-
- export interface ProcessBoardRow {
- jopId: number;
- jobOrderId: number;
- jobOrderCode: string;
- jobOrderStatus: string;
- processId: number;
- processCode: string;
- processName: string;
- seqNo: number;
- rowStatus: string;
- jobPlanStart: string;
- startTime: string;
- endTime: string;
- /** Derived: pending | in_progress | completed */
- boardStatus: string;
- /** 工藝流程步驟名稱(productprocessline.name;多筆以 | 分隔);無明細時為主檔工序名。 */
- lineStepName: string;
- /** 描述 */
- lineDescription: string;
- /** 設備類型-設備名稱-編號(與工單工藝流程一致) */
- lineEquipmentLabel: string;
- /** 操作員/員工顯示名 */
- lineOperatorInfo: string;
- itemCode: string;
- itemName: string;
- jobTypeName: string;
- reqQty: number;
- outputQtyUom: string;
- productionDate: string;
- planProcessingMinsTotal: number;
- planSetupChangeoverMinsTotal: number;
- productProcessStart: string;
- actualLineMinsTotal: number;
- /** This BOM step: sum(processing+setup+changeover) on matching lines */
- stepPlanMins: number;
- /** This BOM step: Σ line durations in decimal minutes (seconds÷60); Pass/Completed without endTime uses planned processing min as fallback */
- stepActualMins: number;
- }
-
- function mapProcessBoardRow(r: Record<string, unknown>): ProcessBoardRow {
- return {
- jopId: Number(r.jopId ?? r.jopid ?? 0),
- jobOrderId: Number(r.jobOrderId ?? r.joborderid ?? 0),
- jobOrderCode: String(r.jobOrderCode ?? r.jobordercode ?? ""),
- jobOrderStatus: String(r.jobOrderStatus ?? r.joborderstatus ?? ""),
- processId: Number(r.processId ?? r.processid ?? 0),
- processCode: String(r.processCode ?? r.processcode ?? ""),
- processName: String(r.processName ?? r.processname ?? ""),
- seqNo: Number(r.seqNo ?? r.seqno ?? 0),
- rowStatus: String(r.rowStatus ?? r.rowstatus ?? ""),
- jobPlanStart: String(r.jobPlanStart ?? r.jobplanstart ?? ""),
- startTime: String(r.startTime ?? r.starttime ?? ""),
- endTime: String(r.endTime ?? r.endtime ?? ""),
- boardStatus: String(r.boardStatus ?? r.boardstatus ?? "pending").toLowerCase(),
- lineStepName: String(r.lineStepName ?? r.linestepname ?? r.line_step_name ?? ""),
- lineDescription: String(r.lineDescription ?? r.linedescription ?? r.line_description ?? ""),
- lineEquipmentLabel: String(r.lineEquipmentLabel ?? r.lineequipmentlabel ?? r.line_equipment_label ?? ""),
- lineOperatorInfo: String(r.lineOperatorInfo ?? r.lineoperatorinfo ?? r.line_operator_info ?? ""),
- itemCode: String(r.itemCode ?? r.itemcode ?? ""),
- itemName: String(r.itemName ?? r.itemname ?? ""),
- jobTypeName: String(r.jobTypeName ?? r.jobtypename ?? ""),
- reqQty: numField(r.reqQty ?? r.reqqty),
- outputQtyUom: String(r.outputQtyUom ?? r.outputqtyuom ?? ""),
- productionDate: String(r.productionDate ?? r.productiondate ?? ""),
- planProcessingMinsTotal: numField(r.planProcessingMinsTotal ?? r.planprocessingminstotal),
- planSetupChangeoverMinsTotal: numField(r.planSetupChangeoverMinsTotal ?? r.plansetupchangeoverminstotal),
- productProcessStart: String(r.productProcessStart ?? r.productprocessstart ?? ""),
- actualLineMinsTotal: numField(r.actualLineMinsTotal ?? r.actuallineminstotal),
- stepPlanMins: numField(r.stepPlanMins ?? r.stepplanmins),
- stepActualMins: numField(r.stepActualMins ?? r.stepactualmins),
- };
- }
-
- /** Per job_order_process line; same filters as job-order board. */
- export async function fetchProcessBoard(
- targetDate?: string,
- opts?: { incompleteOnly?: boolean },
- ): Promise<ProcessBoardRow[]> {
- const params: Record<string, string | number | undefined> = {};
- if (targetDate) params.targetDate = targetDate;
- if (opts?.incompleteOnly) params.incompleteOnly = "true";
- const q = buildParams(params);
- const res = await clientAuthFetch(q ? `${BASE}/process-board?${q}` : `${BASE}/process-board`);
- if (!res.ok) throw new Error("Failed to fetch process board");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map(mapProcessBoardRow);
- }
-
- export interface EquipmentUsageBoardRow {
- jopdId: number;
- equipmentId: number;
- equipmentCode: string;
- equipmentName: string;
- jobOrderId: number;
- jobOrderCode: string;
- jobPlanStart: string;
- processCode: string;
- processName: string;
- operatingStart: string;
- operatingEnd: string;
- /** Estimated usage minutes (start–end diff, or 產線 processingTime when Pass/Completed without end). */
- usageMinutes: number;
- workingNow: number;
- operatorUsername: string;
- operatorName: string;
- }
-
- function mapEquipmentUsageBoardRow(r: Record<string, unknown>): EquipmentUsageBoardRow {
- return {
- jopdId: Number(r.jopdId ?? r.jopdid ?? 0),
- equipmentId: Number(r.equipmentId ?? r.equipmentid ?? 0),
- equipmentCode: String(r.equipmentCode ?? r.equipmentcode ?? ""),
- equipmentName: String(r.equipmentName ?? r.equipmentname ?? ""),
- jobOrderId: Number(r.jobOrderId ?? r.joborderid ?? 0),
- jobOrderCode: String(r.jobOrderCode ?? r.jobordercode ?? ""),
- jobPlanStart: String(r.jobPlanStart ?? r.jobplanstart ?? ""),
- processCode: String(r.processCode ?? r.processcode ?? ""),
- processName: String(r.processName ?? r.processname ?? ""),
- operatingStart: String(r.operatingStart ?? r.operatingstart ?? ""),
- operatingEnd: String(r.operatingEnd ?? r.operatingend ?? ""),
- usageMinutes: Number(r.usageMinutes ?? r.usageminutes ?? 0),
- workingNow: Number(r.workingNow ?? r.workingnow ?? 0),
- operatorUsername: String(r.operatorUsername ?? r.operatorusername ?? ""),
- operatorName: String(r.operatorName ?? r.operatorname ?? ""),
- };
- }
-
- /** Day = COALESCE(line/jopd times, jop.endTime, planStart). Includes productprocessline (工藝流程) and job_order_process_detail. Omit targetDate = server today. */
- export async function fetchEquipmentUsageBoard(targetDate?: string): Promise<EquipmentUsageBoardRow[]> {
- const q = buildParams({ targetDate: targetDate ?? "" });
- const res = await clientAuthFetch(q ? `${BASE}/equipment-usage-board?${q}` : `${BASE}/equipment-usage-board`);
- if (!res.ok) throw new Error("Failed to fetch equipment usage board");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map(mapEquipmentUsageBoardRow);
- }
-
- export async function fetchProductionScheduleByDate(
- startDate?: string,
- endDate?: string
- ): Promise<ProductionScheduleByDateRow[]> {
- const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
- const res = await clientAuthFetch(
- `${BASE}/production-schedule-by-date?${q}`
- );
- if (!res.ok) throw new Error("Failed to fetch production schedule by date");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
- date: String(r.date ?? ""),
- scheduledItemCount: Number(r.scheduledItemCount ?? r.scheduleCount ?? 0),
- totalEstProdCount: Number(r.totalEstProdCount ?? 0),
- }));
- }
-
- export async function fetchPlannedDailyOutputByItem(
- limit = 20
- ): Promise<PlannedDailyOutputRow[]> {
- const res = await clientAuthFetch(
- `${BASE}/planned-daily-output-by-item?limit=${limit}`
- );
- if (!res.ok) throw new Error("Failed to fetch planned daily output");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
- itemCode: String(r.itemCode ?? ""),
- itemName: String(r.itemName ?? ""),
- dailyQty: Number(r.dailyQty ?? 0),
- }));
- }
-
- /** Planned production by date and by item (production_schedule). */
- export interface PlannedOutputByDateAndItemRow {
- date: string;
- itemCode: string;
- itemName: string;
- qty: number;
- }
-
- export async function fetchPlannedOutputByDateAndItem(
- startDate?: string,
- endDate?: string
- ): Promise<PlannedOutputByDateAndItemRow[]> {
- const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
- const res = await clientAuthFetch(
- q ? `${BASE}/planned-output-by-date-and-item?${q}` : `${BASE}/planned-output-by-date-and-item`
- );
- if (!res.ok) throw new Error("Failed to fetch planned output by date and item");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
- date: String(r.date ?? ""),
- itemCode: String(r.itemCode ?? ""),
- itemName: String(r.itemName ?? ""),
- qty: Number(r.qty ?? 0),
- }));
- }
-
- export async function fetchStaffDeliveryPerformance(
- startDate?: string,
- endDate?: string,
- staffNos?: string[]
- ): Promise<StaffDeliveryPerformanceRow[]> {
- const p = new URLSearchParams();
- if (startDate) p.set("startDate", startDate);
- if (endDate) p.set("endDate", endDate);
- (staffNos ?? []).forEach((no) => p.append("staffNo", no));
- const q = p.toString();
- const res = await clientAuthFetch(
- q ? `${BASE}/staff-delivery-performance?${q}` : `${BASE}/staff-delivery-performance`
- );
- if (!res.ok) throw new Error("Failed to fetch staff delivery performance");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => {
- // Accept camelCase or lowercase keys (JDBC/DB may return different casing)
- const row = r as Record<string, unknown>;
- return {
- date: String(row.date ?? row.Date ?? ""),
- staffName: String(row.staffName ?? row.staffname ?? ""),
- orderCount: Number(row.orderCount ?? row.ordercount ?? 0),
- totalMinutes: Number(row.totalMinutes ?? row.totalminutes ?? 0),
- };
- });
- }
-
- export async function fetchStockTransactionsByDate(
- startDate?: string,
- endDate?: string
- ): Promise<StockTransactionsByDateRow[]> {
- const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
- const res = await clientAuthFetch(`${BASE}/stock-transactions-by-date?${q}`);
- if (!res.ok) throw new Error("Failed to fetch stock transactions by date");
- const data = await res.json();
- return normalizeChartRows(data, "date", ["inQty", "outQty", "totalQty"]);
- }
-
- export async function fetchDeliveryOrderByDate(
- startDate?: string,
- endDate?: string
- ): Promise<DeliveryOrderByDateRow[]> {
- const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
- const res = await clientAuthFetch(`${BASE}/delivery-order-by-date?${q}`);
- if (!res.ok) throw new Error("Failed to fetch delivery order by date");
- const data = await res.json();
- return normalizeChartRows(data, "date", ["orderCount", "totalQty"]);
- }
-
- export async function fetchPurchaseOrderByStatus(
- targetDate?: string,
- filters?: PurchaseOrderChartFilters
- ): Promise<PurchaseOrderByStatusRow[]> {
- const p = new URLSearchParams();
- if (targetDate) p.set("targetDate", targetDate);
- appendPurchaseOrderListParams(p, filters);
- const q = p.toString();
- const res = await clientAuthFetch(
- q ? `${BASE}/purchase-order-by-status?${q}` : `${BASE}/purchase-order-by-status`
- );
- if (!res.ok) throw new Error("Failed to fetch purchase order by status");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
- status: String(r.status ?? ""),
- count: Number(r.count ?? 0),
- }));
- }
-
- export async function fetchPurchaseOrderFilterOptions(
- targetDate?: string
- ): Promise<PurchaseOrderFilterOptions> {
- const p = new URLSearchParams();
- if (targetDate) p.set("targetDate", targetDate);
- const q = p.toString();
- const res = await clientAuthFetch(
- q ? `${BASE}/purchase-order-filter-options?${q}` : `${BASE}/purchase-order-filter-options`
- );
- if (!res.ok) throw new Error("Failed to fetch purchase order filter options");
- const data = await res.json();
- const row = (data ?? {}) as Record<string, unknown>;
- const suppliers = (Array.isArray(row.suppliers) ? row.suppliers : []) as Record<string, unknown>[];
- const items = (Array.isArray(row.items) ? row.items : []) as Record<string, unknown>[];
- const poNos = (Array.isArray(row.poNos) ? row.poNos : []) as Record<string, unknown>[];
- return {
- suppliers: suppliers.map((r) => ({
- supplierId: Number(r.supplierId ?? r.supplierid ?? 0),
- code: String(r.code ?? ""),
- name: String(r.name ?? ""),
- })),
- items: items.map((r) => ({
- itemCode: String(r.itemCode ?? r.itemcode ?? ""),
- itemName: String(r.itemName ?? r.itemname ?? ""),
- })),
- poNos: poNos.map((r) => ({
- poNo: String(r.poNo ?? r.pono ?? ""),
- })),
- };
- }
-
- export async function fetchPurchaseOrderEstimatedArrivalSummary(
- targetDate?: string,
- filters?: PurchaseOrderChartFilters
- ): Promise<PurchaseOrderEstimatedArrivalRow[]> {
- const p = new URLSearchParams();
- if (targetDate) p.set("targetDate", targetDate);
- appendPurchaseOrderListParams(p, filters);
- const q = p.toString();
- const res = await clientAuthFetch(
- q
- ? `${BASE}/purchase-order-estimated-arrival-summary?${q}`
- : `${BASE}/purchase-order-estimated-arrival-summary`
- );
- if (!res.ok) throw new Error("Failed to fetch estimated arrival summary");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
- bucket: String(r.bucket ?? ""),
- count: Number(r.count ?? 0),
- }));
- }
-
- export interface EstimatedArrivalBreakdownSupplierRow {
- supplierId: number | null;
- supplierCode: string;
- supplierName: string;
- poCount: number;
- }
-
- export interface EstimatedArrivalBreakdownItemRow {
- itemCode: string;
- itemName: string;
- poCount: number;
- totalQty: number;
- }
-
- export interface EstimatedArrivalBreakdownPoRow {
- purchaseOrderId: number;
- purchaseOrderNo: string;
- status: string;
- orderDate: string;
- supplierId: number | null;
- supplierCode: string;
- supplierName: string;
- }
-
- export interface PurchaseOrderEstimatedArrivalBreakdown {
- suppliers: EstimatedArrivalBreakdownSupplierRow[];
- items: EstimatedArrivalBreakdownItemRow[];
- purchaseOrders: EstimatedArrivalBreakdownPoRow[];
- }
-
- /** Related suppliers / items / POs for one 預計送貨 bucket (same bar filters as the donut). */
- export async function fetchPurchaseOrderEstimatedArrivalBreakdown(
- targetDate: string,
- estimatedArrivalBucket: string,
- filters?: PurchaseOrderChartFilters
- ): Promise<PurchaseOrderEstimatedArrivalBreakdown> {
- const p = new URLSearchParams();
- p.set("targetDate", targetDate);
- p.set("estimatedArrivalBucket", estimatedArrivalBucket.trim().toLowerCase());
- appendPurchaseOrderListParams(p, filters);
- const res = await clientAuthFetch(`${BASE}/purchase-order-estimated-arrival-breakdown?${p.toString()}`);
- if (!res.ok) throw new Error("Failed to fetch estimated arrival breakdown");
- const data = await res.json();
- const row = (data ?? {}) as Record<string, unknown>;
- const suppliers = (Array.isArray(row.suppliers) ? row.suppliers : []) as Record<string, unknown>[];
- const items = (Array.isArray(row.items) ? row.items : []) as Record<string, unknown>[];
- const purchaseOrders = (Array.isArray(row.purchaseOrders) ? row.purchaseOrders : []) as Record<string, unknown>[];
- return {
- suppliers: suppliers.map((r) => ({
- supplierId: (() => {
- const v = r.supplierId ?? r.supplierid;
- if (v == null || v === "") return null;
- const n = Number(v);
- return Number.isFinite(n) ? n : null;
- })(),
- supplierCode: String(r.supplierCode ?? r.suppliercode ?? ""),
- supplierName: String(r.supplierName ?? r.suppliername ?? ""),
- poCount: Number(r.poCount ?? r.pocount ?? 0),
- })),
- items: items.map((r) => ({
- itemCode: String(r.itemCode ?? r.itemcode ?? ""),
- itemName: String(r.itemName ?? r.itemname ?? ""),
- poCount: Number(r.poCount ?? r.pocount ?? 0),
- totalQty: Number(r.totalQty ?? r.totalqty ?? 0),
- })),
- purchaseOrders: purchaseOrders.map((r) => ({
- purchaseOrderId: Number(r.purchaseOrderId ?? r.purchaseorderid ?? 0),
- purchaseOrderNo: String(r.purchaseOrderNo ?? r.purchaseorderno ?? ""),
- status: String(r.status ?? ""),
- orderDate: String(r.orderDate ?? r.orderdate ?? ""),
- supplierId: (() => {
- const v = r.supplierId ?? r.supplierid;
- if (v == null || v === "") return null;
- const n = Number(v);
- return Number.isFinite(n) ? n : null;
- })(),
- supplierCode: String(r.supplierCode ?? r.suppliercode ?? ""),
- supplierName: String(r.supplierName ?? r.suppliername ?? ""),
- })),
- };
- }
-
- export type PurchaseOrderDrillQuery = PurchaseOrderChartFilters & {
- /** order = PO order date; complete = PO complete date (for received/completed on a day) */
- dateFilter?: "order" | "complete";
- /** delivered | not_delivered | cancelled | other — same as 預計送貨 donut buckets */
- estimatedArrivalBucket?: string;
- };
-
- export async function fetchPurchaseOrderDetailsByStatus(
- status: string,
- targetDate?: string,
- opts?: PurchaseOrderDrillQuery
- ): Promise<PurchaseOrderDetailByStatusRow[]> {
- const p = new URLSearchParams();
- p.set("status", status.trim().toLowerCase());
- if (targetDate) p.set("targetDate", targetDate);
- if (opts?.dateFilter) p.set("dateFilter", opts.dateFilter);
- if (opts?.estimatedArrivalBucket?.trim()) {
- p.set("estimatedArrivalBucket", opts.estimatedArrivalBucket.trim().toLowerCase());
- }
- appendPurchaseOrderListParams(p, opts);
- const q = p.toString();
- const res = await clientAuthFetch(`${BASE}/purchase-order-details-by-status?${q}`);
- if (!res.ok) throw new Error("Failed to fetch purchase order details by status");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
- purchaseOrderId: Number(r.purchaseOrderId ?? 0),
- purchaseOrderNo: String(r.purchaseOrderNo ?? ""),
- status: String(r.status ?? ""),
- orderDate: String(r.orderDate ?? ""),
- estimatedArrivalDate: String(r.estimatedArrivalDate ?? ""),
- supplierId: (() => {
- const v = r.supplierId;
- if (v == null || v === "") return null;
- const n = Number(v);
- return Number.isFinite(n) && n > 0 ? n : null;
- })(),
- supplierCode: String(r.supplierCode ?? ""),
- supplierName: String(r.supplierName ?? ""),
- itemCount: Number(r.itemCount ?? 0),
- totalQty: Number(r.totalQty ?? 0),
- }));
- }
-
- export async function fetchPurchaseOrderItems(
- purchaseOrderId: number
- ): Promise<PurchaseOrderItemRow[]> {
- const q = buildParams({ purchaseOrderId });
- const res = await clientAuthFetch(`${BASE}/purchase-order-items?${q}`);
- if (!res.ok) throw new Error("Failed to fetch purchase order items");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
- purchaseOrderLineId: Number(r.purchaseOrderLineId ?? 0),
- itemCode: String(r.itemCode ?? ""),
- itemName: String(r.itemName ?? ""),
- orderedQty: Number(r.orderedQty ?? 0),
- uom: String(r.uom ?? ""),
- receivedQty: Number(r.receivedQty ?? 0),
- pendingQty: Number(r.pendingQty ?? 0),
- }));
- }
-
- export async function fetchPurchaseOrderItemsByStatus(
- status: string,
- targetDate?: string,
- opts?: PurchaseOrderDrillQuery
- ): Promise<PurchaseOrderItemRow[]> {
- const p = new URLSearchParams();
- p.set("status", status.trim().toLowerCase());
- if (targetDate) p.set("targetDate", targetDate);
- if (opts?.dateFilter) p.set("dateFilter", opts.dateFilter);
- if (opts?.estimatedArrivalBucket?.trim()) {
- p.set("estimatedArrivalBucket", opts.estimatedArrivalBucket.trim().toLowerCase());
- }
- appendPurchaseOrderListParams(p, opts);
- const q = p.toString();
- const res = await clientAuthFetch(`${BASE}/purchase-order-items-by-status?${q}`);
- if (!res.ok) throw new Error("Failed to fetch purchase order items by status");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
- purchaseOrderLineId: 0,
- itemCode: String(r.itemCode ?? ""),
- itemName: String(r.itemName ?? ""),
- orderedQty: Number(r.orderedQty ?? 0),
- uom: String(r.uom ?? ""),
- receivedQty: Number(r.receivedQty ?? 0),
- pendingQty: Number(r.pendingQty ?? 0),
- }));
- }
-
- export async function fetchStockInOutByDate(
- startDate?: string,
- endDate?: string
- ): Promise<StockInOutByDateRow[]> {
- const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
- const res = await clientAuthFetch(`${BASE}/stock-in-out-by-date?${q}`);
- if (!res.ok) throw new Error("Failed to fetch stock in/out by date");
- const data = await res.json();
- return normalizeChartRows(data, "date", ["inQty", "outQty"]);
- }
-
- export interface TopDeliveryItemOption {
- itemCode: string;
- itemName: string;
- }
-
- export async function fetchTopDeliveryItemsItemOptions(
- startDate?: string,
- endDate?: string
- ): Promise<TopDeliveryItemOption[]> {
- const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
- const res = await clientAuthFetch(
- q ? `${BASE}/top-delivery-items-item-options?${q}` : `${BASE}/top-delivery-items-item-options`
- );
- if (!res.ok) throw new Error("Failed to fetch item options");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
- itemCode: String(r.itemCode ?? ""),
- itemName: String(r.itemName ?? ""),
- }));
- }
-
- export async function fetchTopDeliveryItems(
- startDate?: string,
- endDate?: string,
- limit = 10,
- itemCodes?: string[]
- ): Promise<TopDeliveryItemsRow[]> {
- const p = new URLSearchParams();
- if (startDate) p.set("startDate", startDate);
- if (endDate) p.set("endDate", endDate);
- p.set("limit", String(limit));
- (itemCodes ?? []).forEach((code) => p.append("itemCode", code));
- const q = p.toString();
- const res = await clientAuthFetch(`${BASE}/top-delivery-items?${q}`);
- if (!res.ok) throw new Error("Failed to fetch top delivery items");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
- itemCode: String(r.itemCode ?? ""),
- itemName: String(r.itemName ?? ""),
- totalQty: Number(r.totalQty ?? 0),
- }));
- }
-
- export async function fetchStockBalanceTrend(
- startDate?: string,
- endDate?: string,
- itemCode?: string
- ): Promise<StockBalanceTrendRow[]> {
- const q = buildParams({
- startDate: startDate ?? "",
- endDate: endDate ?? "",
- itemCode: itemCode ?? "",
- });
- const res = await clientAuthFetch(`${BASE}/stock-balance-trend?${q}`);
- if (!res.ok) throw new Error("Failed to fetch stock balance trend");
- const data = await res.json();
- return normalizeChartRows(data, "date", ["balance"]);
- }
-
- export async function fetchConsumptionTrendByMonth(
- year?: number,
- startDate?: string,
- endDate?: string,
- itemCode?: string
- ): Promise<ConsumptionTrendByMonthRow[]> {
- const q = buildParams({
- year: year ?? "",
- startDate: startDate ?? "",
- endDate: endDate ?? "",
- itemCode: itemCode ?? "",
- });
- const res = await clientAuthFetch(`${BASE}/consumption-trend-by-month?${q}`);
- if (!res.ok) throw new Error("Failed to fetch consumption trend");
- const data = await res.json();
- return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
- month: String(r.month ?? ""),
- outQty: Number(r.outQty ?? 0),
- }));
- }
-
- /** Normalize rows: ensure date key is string and numeric keys are numbers (backend may return BigDecimal/Long). */
- function normalizeChartRows<T>(
- rows: unknown[],
- dateKey: string,
- numberKeys: string[]
- ): T[] {
- if (!Array.isArray(rows)) return [];
- return rows.map((r: unknown) => {
- const row = r as Record<string, unknown>;
- const out: Record<string, unknown> = {};
- out[dateKey] = row[dateKey] != null ? String(row[dateKey]) : "";
- numberKeys.forEach((k) => {
- out[k] = Number(row[k]) || 0;
- });
- return out as T;
- });
- }
|