// actions.ts "use server"; import { cache } from 'react'; import { serverFetchJson } from "@/app/utils/fetchUtil"; // 改为 serverFetchJson import { BASE_API_URL } from "@/config/api"; //import { stockTakeDebugLog } from "@/components/StockTakeManagement/stockTakeDebugLog"; export interface RecordsRes { records: T[]; total: number; } export interface InventoryLotDetailResponse { id: number; inventoryLotId: number; itemId: number; itemCode: string; itemName: string; lotNo: string; expiryDate: string; productionDate: string; stockInDate: string; inQty: number; outQty: number; holdQty: number; availableQty: number; uom: string; warehouseCode: string; warehouseName: string; warehouseSlot: string; warehouseArea: string; warehouse: string; varianceQty: number | null; status: string; remarks: string | null; stockTakeRecordStatus: string; stockTakeRecordId: number | null; firstStockTakeQty: number | null; secondStockTakeQty: number | null; firstBadQty: number | null; secondBadQty: number | null; approverQty: number | null; approverBadQty: number | null; finalQty: number | null; bookQty: number | null; lastSelect?: number | null; stockTakeSection?: string | null; stockTakeSectionDescription?: string | null; stockTakerName?: string | null; /** ISO string or backend LocalDateTime array */ stockTakeEndTime?: string | string[] | null; /** ISO string or backend LocalDateTime array */ approverTime?: string | string[] | null; } /** * `approverInventoryLotDetailsAll*`: * - `total` = 全域 `inventory_lot_line` 中 `status = available` 筆數(與 DB COUNT 一致) * - `filteredRecordCount` = 目前 tab/篩選後筆數(分頁用) */ export interface ApproverInventoryLotDetailsRecordsRes extends RecordsRes { filteredRecordCount?: number; totalWaitingForApprover?: number; totalApproved?: number; } function normalizeApproverInventoryLotDetailsRes( raw: ApproverInventoryLotDetailsRecordsRes ): ApproverInventoryLotDetailsRecordsRes { const waiting = Number(raw.totalWaitingForApprover ?? 0) || 0; const approved = Number(raw.totalApproved ?? 0) || 0; return { records: Array.isArray(raw.records) ? raw.records : [], total: Number(raw.total ?? 0) || 0, filteredRecordCount: Number(raw.filteredRecordCount ?? 0) || 0, totalWaitingForApprover: waiting, totalApproved: approved, }; } export const getInventoryLotDetailsBySection = async ( stockTakeSection: string, stockTakeId?: number | null, pageNum?: number, pageSize?: number, stockTakeRoundId?: number | null ) => { console.log('🌐 [API] getInventoryLotDetailsBySection called with:', { stockTakeSection, stockTakeId, stockTakeRoundId, pageNum, pageSize }); const encodedSection = encodeURIComponent(stockTakeSection); let url = `${BASE_API_URL}/stockTakeRecord/inventoryLotDetailsBySection?stockTakeSection=${encodedSection}&pageNum=${pageNum}&pageSize=${pageSize}`; if (stockTakeId != null && stockTakeId > 0) { url += `&stockTakeId=${stockTakeId}`; } if (stockTakeRoundId != null && stockTakeRoundId > 0) { url += `&stockTakeRoundId=${stockTakeRoundId}`; } console.log(' [API] Full URL:', url); const response = await serverFetchJson>( url, { method: "GET", }, ); console.log('[API] Response received:', response); return response; } export interface SaveStockTakeRecordRequest { stockTakeRecordId?: number | null; inventoryLotLineId: number; qty: number; badQty: number; //stockTakerName: string; remark?: string | null; } export interface AllPickedStockTakeListReponse { id: number; stockTakeSession: string; lastStockTakeDate: string | null; status: string|null; approverName: string | null; currentStockTakeItemNumber: number; totalInventoryLotNumber: number; stockTakeId: number; stockTakeRoundId: number | null; stockTakerName: string | null; totalItemNumber: number; startTime: string | null; endTime: string | null; planStartDate: string | null; stockTakeSectionDescription: string | null; reStockTakeTrueFalse: boolean; } /** 與 Picker 列表一致:區域描述、盤點區域、貨品編號/名稱(逗號多關鍵字由後端解析) */ export type ApproverInventoryLotDetailsQuery = { itemKeyword?: string | null; //itemKeyword?: string | null; sectionDescription?: string | null; stockTakeSections?: string | null; warehouseKeyword?: string | null; }; function appendApproverInventoryLotQueryParams( params: URLSearchParams, query?: ApproverInventoryLotDetailsQuery | null ) { if (!query) return; if (query.itemKeyword != null && query.itemKeyword.trim() !== "") { params.append("itemKeyword", query.itemKeyword.trim()); } if (query.warehouseKeyword != null && query.warehouseKeyword.trim() !== "") { params.append("warehouseKeyword", query.warehouseKeyword.trim()); } if ( query.sectionDescription != null && query.sectionDescription !== "" && query.sectionDescription !== "All" ) { params.append("sectionDescription", query.sectionDescription.trim()); } if (query.stockTakeSections != null && query.stockTakeSections.trim() !== "") { params.append("stockTakeSections", query.stockTakeSections.trim()); } } export const getApproverInventoryLotDetailsAll = async ( stockTakeId?: number | null, pageNum: number = 0, pageSize: number = 100, query?: ApproverInventoryLotDetailsQuery | null ) => { const params = new URLSearchParams(); params.append("pageNum", String(pageNum)); params.append("pageSize", String(pageSize)); if (stockTakeId != null && stockTakeId > 0) { params.append("stockTakeId", String(stockTakeId)); } appendApproverInventoryLotQueryParams(params, query); const url = `${BASE_API_URL}/stockTakeRecord/approverInventoryLotDetailsAll?${params.toString()}`; const response = await serverFetchJson( url, { method: "GET", }, ); return normalizeApproverInventoryLotDetailsRes(response); } export const getApproverInventoryLotDetailsAllPending = async ( stockTakeId?: number | null, pageNum: number = 0, pageSize: number = 50, query?: ApproverInventoryLotDetailsQuery | null ) => { const params = new URLSearchParams(); params.append("pageNum", String(pageNum)); params.append("pageSize", String(pageSize)); if (stockTakeId != null && stockTakeId > 0) { params.append("stockTakeId", String(stockTakeId)); } appendApproverInventoryLotQueryParams(params, query); const url = `${BASE_API_URL}/stockTakeRecord/approverInventoryLotDetailsAllPending?${params.toString()}`; const response = await serverFetchJson(url, { method: "GET" }); return normalizeApproverInventoryLotDetailsRes(response); } export const getApproverInventoryLotDetailsAllApproved = async ( stockTakeId?: number | null, pageNum: number = 0, pageSize: number = 50, query?: ApproverInventoryLotDetailsQuery | null ) => { const params = new URLSearchParams(); params.append("pageNum", String(pageNum)); params.append("pageSize", String(pageSize)); if (stockTakeId != null && stockTakeId > 0) { params.append("stockTakeId", String(stockTakeId)); } appendApproverInventoryLotQueryParams(params, query); const url = `${BASE_API_URL}/stockTakeRecord/approverInventoryLotDetailsAllApproved?${params.toString()}`; const response = await serverFetchJson(url, { method: "GET" }); return normalizeApproverInventoryLotDetailsRes(response); } export const importStockTake = async (data: FormData) => { const importStockTake = await serverFetchJson( `${BASE_API_URL}/stockTake/import`, { method: "POST", body: data, }, ); return importStockTake; } export const getStockTakeRecords = async () => { const stockTakeRecords = await serverFetchJson( // 改为 serverFetchJson `${BASE_API_URL}/stockTakeRecord/AllPickedStockOutRecordList`, { method: "GET", }, ); return stockTakeRecords; } export const getStockTakeRecordsPaged = async ( pageNum: number, pageSize: number, params?: { sectionDescription?: string; stockTakeSections?: string } ) => { const searchParams = new URLSearchParams(); searchParams.set("pageNum", String(pageNum)); searchParams.set("pageSize", String(pageSize)); if (params?.sectionDescription && params.sectionDescription !== "All") { searchParams.set("sectionDescription", params.sectionDescription); } if (params?.stockTakeSections?.trim()) { searchParams.set("stockTakeSections", params.stockTakeSections.trim()); } const url = `${BASE_API_URL}/stockTakeRecord/AllPickedStockOutRecordList?${searchParams.toString()}`; const res = await serverFetchJson>(url, { method: "GET" }); return res; }; export const getApproverStockTakeRecords = async () => { const stockTakeRecords = await serverFetchJson( // 改为 serverFetchJson `${BASE_API_URL}/stockTakeRecord/AllApproverStockTakeList`, { method: "GET", }, ); return stockTakeRecords; } export const getLatestApproverStockTakeHeader = async () => { return serverFetchJson( `${BASE_API_URL}/stockTakeRecord/LatestApproverStockTakeHeader`, { method: "GET" } ); } export const createStockTakeForSections = async () => { const createStockTakeForSections = await serverFetchJson>( `${BASE_API_URL}/stockTake/createForSections`, { method: "POST", }, ); return createStockTakeForSections; } export const saveStockTakeRecord = async ( request: SaveStockTakeRecordRequest, stockTakeId: number, stockTakerId: number ) => { try { const result = await serverFetchJson( `${BASE_API_URL}/stockTakeRecord/saveStockTakeRecord?stockTakeId=${stockTakeId}&stockTakerId=${stockTakerId}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(request), }, ); console.log('saveStockTakeRecord: request:', request); console.log('saveStockTakeRecord: stockTakeId:', stockTakeId); console.log('saveStockTakeRecord: stockTakerId:', stockTakerId); return result; } catch (error: any) { // 尝试从错误响应中提取消息 if (error?.response) { try { const errorData = await error.response.json(); const errorWithMessage = new Error(errorData.message || errorData.error || "Failed to save stock take record"); (errorWithMessage as any).response = error.response; throw errorWithMessage; } catch { throw error; } } throw error; } } export interface BatchSaveStockTakeRecordRequest { stockTakeId: number; stockTakeSection: string; stockTakerId: number; //stockTakerName: string; } export interface BatchSaveStockTakeRecordResponse { successCount: number; errorCount: number; errors: string[]; } export const batchSaveStockTakeRecords = cache(async (data: BatchSaveStockTakeRecordRequest) => { const r = await serverFetchJson(`${BASE_API_URL}/stockTakeRecord/batchSaveStockTakeRecords`, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }) return r }) // Add these interfaces and functions export interface SaveApproverStockTakeRecordRequest { stockTakeRecordId?: number | null; qty: number; badQty: number; approverId?: number | null; approverQty?: number | null; approverBadQty?: number | null; lastSelect?: number | null; } export interface BatchSaveApproverStockTakeRecordRequest { stockTakeId: number; stockTakeSection: string; approverId: number; variancePercentTolerance?: number | null; } export interface BatchSaveApproverStockTakeRecordResponse { successCount: number; errorCount: number; errors: string[]; } /* export interface BatchSaveApproverStockTakeAllRequest { stockTakeId: number; approverId: number; variancePercentTolerance?: number | null; } */ export interface BatchSaveApproverStockTakeAllRequest { stockTakeId: number; approverId: number; // UI 用,batch 不應該用它來 skip variancePercentTolerance?: number | null; // 新增:讓 batch 只處理搜尋結果那批 itemKeyword?: string | null; warehouseKeyword?: string | null; sectionDescription?: string | null; stockTakeSections?: string | null; // 逗號字串 } export const saveApproverStockTakeRecord = async ( request: SaveApproverStockTakeRecordRequest, stockTakeId: number ) => { try { const result = await serverFetchJson( `${BASE_API_URL}/stockTakeRecord/saveApproverStockTakeRecord?stockTakeId=${stockTakeId}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(request), }, ); return result; } catch (error: any) { if (error?.response) { try { const errorData = await error.response.json(); const errorWithMessage = new Error(errorData.message || errorData.error || "Failed to save approver stock take record"); (errorWithMessage as any).response = error.response; throw errorWithMessage; } catch { throw error; } } throw error; } } export const batchSaveApproverStockTakeRecords = cache(async (data: BatchSaveApproverStockTakeRecordRequest) => { return serverFetchJson( `${BASE_API_URL}/stockTakeRecord/batchSaveApproverStockTakeRecords`, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, } ) } ) export const batchSaveApproverStockTakeRecordsAll = cache(async (data: BatchSaveApproverStockTakeAllRequest) => { const r = await serverFetchJson( `${BASE_API_URL}/stockTakeRecord/batchSaveApproverStockTakeRecordsAll`, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, } ) return r }) export const updateStockTakeRecordStatusToNotMatch = async ( stockTakeRecordId: number ) => { try { const result = await serverFetchJson( `${BASE_API_URL}/stockTakeRecord/updateStockTakeRecordStatusToNotMatch?stockTakeRecordId=${stockTakeRecordId}`, { method: "POST", }, ); return result; } catch (error: any) { if (error?.response) { try { const errorData = await error.response.json(); const errorWithMessage = new Error(errorData.message || errorData.error || "Failed to update stock take record status"); (errorWithMessage as any).response = error.response; throw errorWithMessage; } catch { throw error; } } throw error; } } export const getInventoryLotDetailsBySectionNotMatch = async ( stockTakeSection: string, stockTakeId?: number | null, pageNum: number = 0, pageSize: number = 10, stockTakeRoundId?: number | null ) => { const encodedSection = encodeURIComponent(stockTakeSection); let url = `${BASE_API_URL}/stockTakeRecord/inventoryLotDetailsBySectionNotMatch?stockTakeSection=${encodedSection}&pageNum=${pageNum}`; // Only add pageSize if it's not "all" (which would be a large number) if (pageSize < 100000) { url += `&pageSize=${pageSize}`; } // If pageSize is large (meaning "all"), don't send it - backend will return all if (stockTakeId != null && stockTakeId > 0) { url += `&stockTakeId=${stockTakeId}`; } if (stockTakeRoundId != null && stockTakeRoundId > 0) { url += `&stockTakeRoundId=${stockTakeRoundId}`; } const response = await serverFetchJson>( url, { method: "GET", }, ); return response; } export interface SearchStockTransactionResult { records: StockTransactionResponse[]; total: number; } export interface SearchStockTransactionRequest { startDate: string | null; endDate: string | null; itemCode: string | null; itemName: string | null; type: string | null; pageNum: number; pageSize: number; } export interface StockTransactionResponse { id: number; transactionType: string; itemId: number; itemCode: string | null; itemName: string | null; uomId?: number | null; uomDesc?: string | null; balanceQty: number | null; qty: number; type: string | null; status: string; transactionDate: string | null; date: string | null; // 添加这个字段 lotNo: string | null; stockInId: number | null; stockOutId: number | null; remarks: string | null; } export interface StockTransactionListResponse { records: RecordsRes; } export const searchStockTransactions = cache(async (request: SearchStockTransactionRequest) => { const params = new URLSearchParams(); if (request.itemCode) params.append("itemCode", request.itemCode); if (request.itemName) params.append("itemName", request.itemName); if (request.type) params.append("type", request.type); if (request.startDate) params.append("startDate", request.startDate); if (request.endDate) params.append("endDate", request.endDate); params.append("pageNum", String(request.pageNum || 0)); params.append("pageSize", String(request.pageSize || 100)); const queryString = params.toString(); const url = `${BASE_API_URL}/stockTakeRecord/searchStockTransactions${queryString ? `?${queryString}` : ''}`; const response = await serverFetchJson>( url, { method: "GET", next: { tags: ["Stock Transaction List"] }, } ); // 回傳 records 與 total,供分頁正確顯示 return { records: response?.records || [], total: response?.total ?? 0, }; });