|
- // 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<T> {
- 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<InventoryLotDetailResponse> {
- 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<RecordsRes<InventoryLotDetailResponse>>(
- 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<ApproverInventoryLotDetailsRecordsRes>(
- 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<ApproverInventoryLotDetailsRecordsRes>(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<ApproverInventoryLotDetailsRecordsRes>(url, { method: "GET" });
- return normalizeApproverInventoryLotDetailsRes(response);
- }
-
- export const importStockTake = async (data: FormData) => {
- const importStockTake = await serverFetchJson<string>(
- `${BASE_API_URL}/stockTake/import`,
- {
- method: "POST",
- body: data,
- },
- );
- return importStockTake;
- }
-
- export const getStockTakeRecords = async () => {
-
- const stockTakeRecords = await serverFetchJson<AllPickedStockTakeListReponse[]>( // 改为 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<RecordsRes<AllPickedStockTakeListReponse>>(url, { method: "GET" });
- return res;
- };
- export const getApproverStockTakeRecords = async () => {
- const stockTakeRecords = await serverFetchJson<AllPickedStockTakeListReponse[]>( // 改为 serverFetchJson
- `${BASE_API_URL}/stockTakeRecord/AllApproverStockTakeList`,
- {
- method: "GET",
- },
- );
- return stockTakeRecords;
- }
- export const getLatestApproverStockTakeHeader = async () => {
- return serverFetchJson<AllPickedStockTakeListReponse>(
- `${BASE_API_URL}/stockTakeRecord/LatestApproverStockTakeHeader`,
- { method: "GET" }
- );
- }
- export const createStockTakeForSections = async () => {
- const createStockTakeForSections = await serverFetchJson<Map<string, string>>(
- `${BASE_API_URL}/stockTake/createForSections`,
- {
- method: "POST",
- },
- );
- return createStockTakeForSections;
- }
- export const saveStockTakeRecord = async (
- request: SaveStockTakeRecordRequest,
- stockTakeId: number,
- stockTakerId: number
- ) => {
- try {
- const result = await serverFetchJson<any>(
-
- `${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<BatchSaveStockTakeRecordResponse>(`${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<any>(
- `${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<BatchSaveApproverStockTakeRecordResponse>(
- `${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<BatchSaveApproverStockTakeRecordResponse>(
- `${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<any>(
- `${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<RecordsRes<InventoryLotDetailResponse>>(
- 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<StockTransactionResponse>;
- }
-
- 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<RecordsRes<StockTransactionResponse>>(
- url,
- {
- method: "GET",
- next: { tags: ["Stock Transaction List"] },
- }
- );
- // 回傳 records 與 total,供分頁正確顯示
- return {
- records: response?.records || [],
- total: response?.total ?? 0,
- };
- });
|