Files
web/app/composables/useAdvancedWorkflow.ts
2026-05-05 11:06:49 +07:00

399 lines
12 KiB
TypeScript

import { ref } from "vue";
import { useNuxtApp } from "#app";
import dayjs from "dayjs";
// Interface cho các tham số từ UI
interface TransactionParams {
product: any;
customer: any;
policy: any;
phaseInfo: any;
priceAfterDiscount: number;
discountValue: number;
detailedDiscounts: any[];
paymentPlans: any[];
currentDate: string;
reservationDueDate: string;
reservationAmount: number;
depositReceived: number;
people?: number | null;
earlyDiscountAmount?: number;
gifts?: Array<{ id: number }>; // Thêm trường gifts
}
export function useAdvancedWorkflow() {
const { $insertapi, $snackbar, $store } = useNuxtApp();
const isLoading = ref(false);
const error = ref<any>(null);
/**
* Helper: Tìm kết quả của một step cụ thể trong mảng 'result' của backend trả về
*/
const findStepResult = (steps: any[], stepCode: string, filter?: string | ((res: any) => boolean)) => {
if (!Array.isArray(steps)) return null;
// 1. Tìm đúng step đang thực thi
const step = steps.find((s) => (s.step === stepCode || s.step?.startsWith(stepCode)) && s.executed);
if (!step || !Array.isArray(step.results) || step.results.length === 0) return null;
// 2. Nếu filter là một hàm (Callback)
if (typeof filter === "function") {
const matched = step.results.find(filter);
return matched ? matched.result : null;
}
// 3. Nếu filter là một chuỗi (Action Name)
if (typeof filter === "string") {
const matched = step.results.find((r) => r.action === filter);
return matched ? matched.result : null;
}
// 4. Mặc định: Trả về result của phần tử đầu tiên
return step.results[0]?.result;
};
/**
* Chuyển đổi danh sách chiết khấu sang format API
*/
const mapDiscountsList = (detailedDiscounts: any[]) => {
if (!detailedDiscounts) return [];
return detailedDiscounts
.filter((d) => d.id)
.map((d) => ({
discount: d.id,
value: d.customValue,
type: d.customType,
}));
};
/**
* Tạo lịch thanh toán cọc (Installments)
*/
const createInstallments = (depositReceived: number, reservationAmount: number) => {
const installments = [];
if (depositReceived > 0) {
installments.push({
amount: depositReceived,
due_days: 0,
description: "Thanh toán tiền đặt cọc đã nhận",
detail: { note: "Thanh toán tiền đặt cọc" },
});
}
const remaining = reservationAmount - depositReceived;
if (remaining > 0) {
installments.push({
amount: remaining,
due_days: 2,
description: "Phần cọc còn lại",
detail: { note: "Thanh toán phần cọc còn lại" },
});
}
return installments;
};
const createWorkflowTransaction = async (params: TransactionParams) => {
isLoading.value = true;
error.value = null;
try {
const userId = $store?.login?.id;
if (!userId) {
throw new Error("Không tìm thấy thông tin người dùng. Vui lòng đăng nhập lại.");
}
// 1. Cấu hình Body theo đúng workflow
const workflowPayload = {
workflow_code: "FULL_CONTRACT_CREATION",
trigger: "create",
data: {
phase_code: params.phaseInfo.code,
current_date: params.currentDate,
due_date: params.reservationDueDate,
customer_id: params.customer.id,
product_id: params.product.id,
policy_id: params.policy.id,
user_id: userId,
sale_price: params.product.origin_price,
origin_price: params.product.origin_price,
deposit_amount: params.reservationAmount,
discount_amount: 0,
amount_received: 0,
payment_plan: params.paymentPlans,
installments: createInstallments(params.depositReceived, params.reservationAmount),
people: params.people || null,
early_discount_amount: Math.trunc(params.earlyDiscountAmount || 0),
gifts: params.gifts || [], // Thêm danh sách quà tặng
},
};
// 2. Gọi API Workflow
const response = await $insertapi("workflow", workflowPayload, undefined, false);
// Kiểm tra thành công
if (response === "error" || !response?.success) {
throw new Error(response?.message || "Thực thi Workflow thất bại.");
}
// 3. Bóc tách dữ liệu từ mảng result trả về
const steps = response.result;
// Lấy Transaction từ step "create_transaction" với action "API_CALL"
const txnData = findStepResult(steps, "create_transaction", "API_CALL");
console.log("Transaction Data:", txnData);
$snackbar("Giao dịch đã được khởi tạo thành công!", "Thành công", "Success");
// 4. Return transaction data
return {
transaction: txnData ? { id: txnData.id, code: txnData.code } : null,
};
} catch (e: any) {
error.value = e;
console.error("Workflow Error:", e);
$snackbar(e.message || "Có lỗi xảy ra khi thực thi workflow.", "Lỗi", "Error");
return null;
} finally {
isLoading.value = false;
}
};
/**
* DUYỆT CÔNG NỢ (KẾ TOÁN) - SỬ DỤNG WORKFLOW
*/
const confirmPaymentSchedule = async (paymentId: number, payload?: object): Promise<boolean> => {
isLoading.value = true;
error.value = null;
try {
const userId = $store?.login?.id;
if (!userId) {
throw new Error("Không tìm thấy thông tin người dùng. Vui lòng đăng nhập lại.");
}
const workflowPayload = {
workflow_id: 9,
workflow_code: "APPROVE_PAYMENT",
trigger: "create",
data: {
payment_id: paymentId,
user_id: userId,
...payload,
},
};
const response = await $insertapi("workflow", workflowPayload, undefined, false);
if (response === "error" || !response?.success) {
throw new Error(response?.message || "Không thể xác nhận công nợ qua workflow.");
}
$snackbar("Công nợ đã được xác nhận thành công!", "Thành công", "Success");
return true;
} catch (e: any) {
error.value = e;
console.error("Workflow Error (confirmPaymentSchedule):", e);
$snackbar(e.message || "Có lỗi xảy ra khi xác nhận công nợ.", "Lỗi", "Error");
return false;
} finally {
isLoading.value = false;
}
};
const rollbackPayment = async (paymentId: number, entryCode: string, entryAmount: number) => {
isLoading.value = true;
error.value = null;
try {
const userId = $store?.login?.id;
if (!userId) {
throw new Error("Không tìm thấy thông tin người dùng. Vui lòng đăng nhập lại.");
}
const workflowPayload = {
workflow_code: "ROLLBACK_PAYMENT",
trigger: "create",
data: {
payment_id: paymentId,
entry_code: entryCode,
amount: entryAmount,
},
};
const response = await $insertapi("workflow", workflowPayload, undefined, false);
if (response === "error" || !response?.success) {
throw new Error(response?.message || "Không thể hủy bút toán qua workflow.");
}
$snackbar("Công nợ đã được xác nhận thành công!", "Thành công", "Success");
return true;
} catch (e: any) {
error.value = e;
console.error("Workflow Error (confirmPaymentSchedule):", e);
$snackbar(e.message || "Có lỗi xảy ra khi xác nhận công nợ.", "Lỗi", "Error");
return false;
} finally {
isLoading.value = false;
}
};
/**
* DUYỆT CHI TIẾT GIAO DỊCH (QUẢN LÝ) - SỬ DỤNG WORKFLOW
*/
const approveTransactionDetail = async (detailId: number, statusCode: string): Promise<any> => {
isLoading.value = true;
error.value = null;
try {
const userId = $store?.login?.id;
if (!userId) {
throw new Error("Không tìm thấy thông tin người dùng. Vui lòng đăng nhập lại.");
}
const workflowPayload = {
workflow_code: "APPROVE_TRANSACTION_DETAIL",
trigger: "create",
data: {
detail_id: detailId,
status_code: statusCode,
user_id: userId,
},
};
const response = await $insertapi("workflow", workflowPayload, undefined, false);
if (response === "error" || !response?.success) {
throw new Error(response?.message || "Không thể duyệt chi tiết giao dịch qua workflow.");
}
$snackbar("Chi tiết giao dịch đã được duyệt thành công!", "Thành công", "Success");
return response;
} catch (e: any) {
error.value = e;
console.error("Workflow Error (approveTransactionDetail):", e);
$snackbar(e.message || "Có lỗi xảy ra khi duyệt chi tiết giao dịch.", "Lỗi", "Error");
return null;
} finally {
isLoading.value = false;
}
};
/**
* CHUYỂN GIAI ĐOẠN GIAO DỊCH - SỬ DỤNG WORKFLOW
*/
const advanceTransactionPhase = async (detailId: number): Promise<any> => {
isLoading.value = true;
error.value = null;
try {
const userId = $store?.login?.id;
if (!userId) {
throw new Error("Không tìm thấy thông tin người dùng. Vui lòng đăng nhập lại.");
}
const workflowPayload = {
workflow_code: "ADVANCE_TRANSACTION_PHASE",
trigger: "create",
data: {
detail_id: detailId,
user_id: userId,
},
};
const response = await $insertapi("workflow", workflowPayload, undefined, false);
if (response === "error" || !response?.success) {
throw new Error(response?.message || "Không thể chuyển giai đoạn giao dịch qua workflow.");
}
const steps = response.result;
// 1. Tìm contract thông qua step bắt đầu bằng 'adv_from_phase_' với action là 'GENERATE_DOCUMENT'
const contract = findStepResult(steps, "adv_lookup_transaction", "LOOKUP_DATA");
console.log("contract", contract);
$snackbar("Chuyển giai đoạn giao dịch thành công!", contract, "Success");
return {
...response,
contract: contract,
};
} catch (e: any) {
error.value = e;
console.error("Workflow Error (advanceTransactionPhase):", e);
$snackbar(e.message || "Có lỗi xảy ra khi chuyển giai đoạn.", "Lỗi", "Error");
return null;
} finally {
isLoading.value = false;
}
};
/**
* CHUYỂN ĐỔI KHÁCH HÀNG - SỬ DỤNG WORKFLOW
*/
const updateTransactionCustomer = async (
transactionId: number,
newCustomerId: number,
contractDate?: string,
): Promise<any> => {
isLoading.value = true;
error.value = null;
try {
const userId = $store?.login?.id;
if (!userId) {
throw new Error("Không tìm thấy thông tin người dùng. Vui lòng đăng nhập lại.");
}
const workflowPayload = {
workflow_code: "UPDATE_TRANS_AND_CO_OP",
trigger: "create",
data: {
transaction: transactionId,
new_cus: newCustomerId,
user_id: userId,
date: contractDate || dayjs().format("YYYY-MM-DD"),
},
};
const response = await $insertapi("workflow", workflowPayload, undefined, false);
if (response === "error" || !response?.success) {
throw new Error(response?.message || "Không thể chuyển đổi khách hàng qua workflow.");
}
const steps = response.result;
// Find the contract from any executed step starting with 'GEN_'
const contract = findStepResult(steps, "GEN_", "API_CALL");
$snackbar("Chuyển đổi khách hàng thành công!", "Thành công", "Success");
return {
...response,
contract: contract,
};
} catch (e: any) {
error.value = e;
console.error("Workflow Error (updateTransactionCustomer):", e);
$snackbar(e.message || "Có lỗi xảy ra khi chuyển đổi khách hàng.", "Lỗi", "Error");
return null;
} finally {
isLoading.value = false;
}
};
return {
isLoading,
error,
createWorkflowTransaction,
confirmPaymentSchedule,
approveTransactionDetail,
advanceTransactionPhase,
updateTransactionCustomer,
rollbackPayment,
};
}