415 lines
12 KiB
TypeScript
415 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
|
|
};
|
|
} |