2025-12-30 15:03:19 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 格式化相關工具函式
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 格式化數字為千分位格式
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const formatNumber = (num: number): string => {
|
|
|
|
|
|
return num.toLocaleString();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 格式化貨幣(NT$)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const formatCurrency = (num: number): string => {
|
|
|
|
|
|
return `NT$ ${num.toLocaleString()}`;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 格式化日期
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const formatDate = (date: string): string => {
|
|
|
|
|
|
if (!date) return "-";
|
2026-01-20 11:06:31 +08:00
|
|
|
|
// Assume date format is YYYY-MM-DD or YYYY-MM-DD HH:mm:ss
|
2026-01-20 10:57:39 +08:00
|
|
|
|
const datePart = date.split("T")[0].split(" ")[0];
|
2026-01-20 11:06:31 +08:00
|
|
|
|
// Directly return the parsed string components to guarantee no timezone shift
|
|
|
|
|
|
const parts = datePart.split("-");
|
|
|
|
|
|
if (parts.length === 3) {
|
|
|
|
|
|
return `${parts[0]}/${parts[1]}/${parts[2]}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Fallback for unexpected formats
|
|
|
|
|
|
return datePart.replace(/-/g, "/");
|
2025-12-30 15:03:19 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-20 10:41:35 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 格式化日期並包含星期
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const formatDateWithDayOfWeek = (date: string): string => {
|
|
|
|
|
|
if (!date) return "-";
|
2026-01-20 10:57:39 +08:00
|
|
|
|
const datePart = date.split("T")[0].split(" ")[0];
|
2026-01-20 11:06:31 +08:00
|
|
|
|
const parts = datePart.split("-");
|
|
|
|
|
|
|
|
|
|
|
|
if (parts.length === 3) {
|
|
|
|
|
|
const [y, m, d] = parts.map(Number);
|
|
|
|
|
|
// Use noon to safely calculate the day of week
|
|
|
|
|
|
const dt = new Date(y, m - 1, d, 12, 0, 0);
|
|
|
|
|
|
const weekDay = dt.toLocaleDateString("zh-TW", { weekday: "short" });
|
2026-01-20 10:57:39 +08:00
|
|
|
|
|
2026-01-20 11:06:31 +08:00
|
|
|
|
// Return original string parts + calculated weekday
|
|
|
|
|
|
return `${parts[0]}/${parts[1]}/${parts[2]} (${weekDay})`;
|
|
|
|
|
|
}
|
2026-01-20 10:57:39 +08:00
|
|
|
|
|
2026-01-20 11:06:31 +08:00
|
|
|
|
return datePart.replace(/-/g, "/");
|
2026-01-20 10:41:35 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 格式化發票號碼
|
|
|
|
|
|
* 例如:AB12345678 -> AB-12345678
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const formatInvoiceNumber = (invoice: string | null | undefined): string => {
|
|
|
|
|
|
if (!invoice) return "-";
|
|
|
|
|
|
const cleanInvoice = invoice.replace(/-/g, "");
|
|
|
|
|
|
if (/^[a-zA-Z]{2}\d+$/.test(cleanInvoice)) {
|
|
|
|
|
|
return `${cleanInvoice.slice(0, 2).toUpperCase()}-${cleanInvoice.slice(2)}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
return invoice;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-12-30 15:03:19 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 獲取當前日期(YYYY-MM-DD 格式)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const getCurrentDate = (): string => {
|
2026-01-20 10:57:39 +08:00
|
|
|
|
const now = new Date();
|
|
|
|
|
|
const year = now.getFullYear();
|
|
|
|
|
|
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
|
|
|
|
const day = String(now.getDate()).padStart(2, "0");
|
|
|
|
|
|
return `${year}-${month}-${day}`;
|
2025-12-30 15:03:19 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成唯一 ID
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const generateId = (): string => {
|
|
|
|
|
|
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成撥補單號
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const generateOrderNumber = (): string => {
|
|
|
|
|
|
return `TO${Date.now().toString().slice(-8)}`;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成批號
|
|
|
|
|
|
* 格式:{倉庫代碼}-{日期YYYYMMDD}-{流水號}
|
|
|
|
|
|
* 例如:WH1-20251128-001
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const generateBatchNumber = (
|
|
|
|
|
|
warehouseId: string,
|
|
|
|
|
|
date?: string,
|
|
|
|
|
|
sequence?: number
|
|
|
|
|
|
): string => {
|
|
|
|
|
|
const targetDate = date || getCurrentDate();
|
|
|
|
|
|
const dateStr = targetDate.replace(/-/g, "");
|
|
|
|
|
|
const seq = sequence || Math.floor(Math.random() * 1000);
|
|
|
|
|
|
const seqStr = seq.toString().padStart(3, "0");
|
|
|
|
|
|
return `WH${warehouseId}-${dateStr}-${seqStr}`;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 獲取當前日期時間(YYYY-MM-DDTHH:mm 格式,用於 datetime-local input)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const getCurrentDateTime = (): string => {
|
|
|
|
|
|
const now = new Date();
|
|
|
|
|
|
const year = now.getFullYear();
|
|
|
|
|
|
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
|
|
|
|
const day = String(now.getDate()).padStart(2, "0");
|
|
|
|
|
|
const hours = String(now.getHours()).padStart(2, "0");
|
|
|
|
|
|
const minutes = String(now.getMinutes()).padStart(2, "0");
|
|
|
|
|
|
return `${year}-${month}-${day}T${hours}:${minutes}`;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 格式化日期時間顯示
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const formatDateTime = (datetime: string): string => {
|
|
|
|
|
|
if (!datetime) return "-";
|
|
|
|
|
|
return new Date(datetime).toLocaleString("zh-TW", {
|
|
|
|
|
|
year: "numeric",
|
|
|
|
|
|
month: "2-digit",
|
|
|
|
|
|
day: "2-digit",
|
|
|
|
|
|
hour: "2-digit",
|
|
|
|
|
|
minute: "2-digit",
|
|
|
|
|
|
hour12: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|