309 lines
9.7 KiB
JavaScript
309 lines
9.7 KiB
JavaScript
import useActiveCart from "~/components/pos/composables/useActiveCart";
|
|
import useOrderInfo from "~/components/pos/composables/useOrderInfo";
|
|
|
|
export default function usePlaceOrder({ onSuccess }) {
|
|
const { $dayjs, $id, $getdata, $insertapi, $patchapi, $deleteapi, $snackbar } = useNuxtApp();
|
|
|
|
const store = useStore();
|
|
const pendingInvoiceStatus = store.Invoice_Status.find(({ code }) => code === "DRAFT");
|
|
const pendingPaymentStatus = store.Payment_Status.find(({ code }) => code === "PENDING");
|
|
const pendingDeliveryStatus = store.Delivery_Status.find(({ code }) => code === "PENDING");
|
|
|
|
const posStore = usePosStore();
|
|
const { invalidCartItems, orderInfo } = storeToRefs(posStore);
|
|
const { activeCart, activeCartItems, subtotal } = useActiveCart();
|
|
const { fullAddress, total } = useOrderInfo();
|
|
|
|
const isPending = ref(false);
|
|
const confirmedInvoiceStatus = ref();
|
|
const paidPaymentStatus = ref();
|
|
const invoice = ref();
|
|
const invoice_details = ref();
|
|
const payment_record = ref();
|
|
const showVietQRModal = ref();
|
|
|
|
async function checkImeiAvai() {
|
|
const imeisSold = await $getdata("IMEI_Sold");
|
|
const invalidImeis = [];
|
|
|
|
for (const cartItem of activeCartItems.value) {
|
|
if (imeisSold.find((sold) => sold.imei === cartItem.imei__imei)) {
|
|
invalidImeis.push(cartItem);
|
|
}
|
|
}
|
|
|
|
return {
|
|
allAvailable: invalidImeis.length === 0,
|
|
invalidImeis,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a record in `invoice` as `pending`
|
|
*/
|
|
async function createOrder() {
|
|
try {
|
|
isPending.value = true;
|
|
|
|
const invoicePayload = {
|
|
customer: activeCart.value.customer,
|
|
customer_name: activeCart.value.customer__fullname,
|
|
customer_phone: activeCart.value.customer__phone,
|
|
customer_email: activeCart.value.customer__email,
|
|
shipping_address: fullAddress.value,
|
|
product_amount: activeCartItems.value.length,
|
|
shipping_fee: orderInfo.value.shipping_fee,
|
|
total_amount: subtotal.value,
|
|
discount_amount: 0,
|
|
final_amount: total.value,
|
|
order_type: "pos",
|
|
invoice_status: pendingInvoiceStatus.id,
|
|
payment_status: pendingPaymentStatus.id,
|
|
staff: 1,
|
|
ordered_at: new Date(),
|
|
paid_at: null,
|
|
delivery_method: orderInfo.value.deliveryMethod.id,
|
|
};
|
|
|
|
const invoiceRecord = await $insertapi("Invoice", {
|
|
data: invoicePayload,
|
|
notify: false,
|
|
});
|
|
|
|
invoice.value = invoiceRecord;
|
|
return invoiceRecord;
|
|
} catch (error) {
|
|
console.error(error);
|
|
$snackbar("Tạo đơn hàng không thành công", "Error");
|
|
} finally {
|
|
isPending.value = false;
|
|
}
|
|
}
|
|
|
|
async function printReceipt() {
|
|
return await $fetch("/api/print/receipt", {
|
|
method: "POST",
|
|
body: {
|
|
invoice: invoice.value,
|
|
invoice_details: invoice_details.value,
|
|
payment_record: payment_record.value,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Prints receipt, displays snackbar, resets cart, closes modal
|
|
*/
|
|
async function onOrderSuccess() {
|
|
$snackbar("Khởi tạo đơn hàng thành công", "Success");
|
|
printReceipt();
|
|
resetCart();
|
|
onSuccess();
|
|
}
|
|
|
|
/**
|
|
* Mark sold IMEIs, create invoice details, delivery info, payment records, reset cart.
|
|
* Runs when order payment is confirmed.
|
|
* @param invoiceId id of created invoice
|
|
*/
|
|
async function finalizeOrder(invoiceId) {
|
|
if (!invoiceId) {
|
|
console.error("invoiceId is required");
|
|
return;
|
|
}
|
|
|
|
const imeisSold = await markImeisSold();
|
|
invoice_details.value = await createInvoiceDetails(invoiceId, imeisSold);
|
|
await createPaymentRecord(invoiceId);
|
|
|
|
if (orderInfo.value.deliveryMethod.code === "HOME_DELIVERY") {
|
|
await createDeliveryInfo(invoiceId);
|
|
}
|
|
|
|
// update invoice's statuses
|
|
await $patchapi("Invoice", {
|
|
data: {
|
|
id: invoiceId,
|
|
invoice_status: confirmedInvoiceStatus.value.id,
|
|
payment_status: paidPaymentStatus.value.id,
|
|
},
|
|
});
|
|
await onOrderSuccess();
|
|
}
|
|
|
|
async function markImeisSold() {
|
|
const imeisSoldPayload = activeCartItems.value.map((cartItem) => ({
|
|
imei: cartItem.imei__imei,
|
|
variant: cartItem.imei__variant,
|
|
sold_date: $dayjs().format("YYYY-MM-DD"),
|
|
}));
|
|
|
|
const imeisSold = await $insertapi("IMEI_Sold", {
|
|
data: imeisSoldPayload,
|
|
notify: false,
|
|
});
|
|
|
|
return imeisSold;
|
|
}
|
|
|
|
async function createInvoiceDetails(invoiceId, imeisSold) {
|
|
const invoiceDetailPayload = activeCartItems.value.map((cartItem) => {
|
|
const imeiSold = imeisSold.find((imeiSold) => imeiSold.imei === cartItem.imei__imei);
|
|
return {
|
|
invoice: invoiceId,
|
|
variant: cartItem.imei__variant,
|
|
imei_sold: imeiSold.id,
|
|
price: cartItem.imei__variant__price,
|
|
};
|
|
});
|
|
|
|
const invoiceDetails = await $insertapi("Invoice_Detail", {
|
|
data: invoiceDetailPayload,
|
|
notify: false,
|
|
values:
|
|
"id,code,invoice,variant,variant__code,variant__product,variant__product__name,variant__color,variant__color__name,variant__ram__code,variant__ram__capacity,variant__internal_storage__code,variant__internal_storage__capacity,variant__price,variant__image__path,imei_sold,price,status,note,deleted,create_time,update_time",
|
|
});
|
|
|
|
return invoiceDetails;
|
|
}
|
|
|
|
async function createDeliveryInfo(invoiceId) {
|
|
const deliveryInfoPayload = {
|
|
invoice: invoiceId,
|
|
receiver_name: orderInfo.value.receiver_name,
|
|
receiver_phone: orderInfo.value.receiver_phone,
|
|
city: orderInfo.value.address.city,
|
|
district: orderInfo.value.address.district,
|
|
ward: orderInfo.value.address.ward,
|
|
address: orderInfo.value.address.address_detail,
|
|
shipping_fee: orderInfo.value.shipping_fee,
|
|
carrier: orderInfo.value.deliveryMethod.id,
|
|
tracking_code: $id(),
|
|
delivery_status: pendingDeliveryStatus.id,
|
|
staff: 1,
|
|
};
|
|
|
|
const deliveryInfo = await $insertapi("Delivery_Info", {
|
|
data: deliveryInfoPayload,
|
|
notify: false,
|
|
});
|
|
|
|
return deliveryInfo;
|
|
}
|
|
|
|
async function createPaymentRecord(invoiceId) {
|
|
const paymentRecord = await $insertapi("Payment_Record", {
|
|
data: {
|
|
invoice: invoiceId,
|
|
payment_method: orderInfo.value.paymentMethod.id,
|
|
transfer_amount: orderInfo.value.paymentMethod.code === "CASH" ? undefined : total.value,
|
|
cash_amount: orderInfo.value.paymentMethod.code === "CASH" ? total.value : undefined,
|
|
},
|
|
notify: false,
|
|
});
|
|
|
|
payment_record.value = paymentRecord;
|
|
return paymentRecord;
|
|
}
|
|
|
|
async function resetCart() {
|
|
await Promise.all([
|
|
$deleteapi(
|
|
"Cart_Item",
|
|
activeCartItems.value.map((c) => c.id),
|
|
),
|
|
$patchapi("Cart", {
|
|
data: {
|
|
id: activeCartItems.value[0].cart,
|
|
customer: null,
|
|
},
|
|
}),
|
|
]);
|
|
posStore.getCarts();
|
|
}
|
|
|
|
async function placeOrder() {
|
|
const { allAvailable, invalidImeis } = await checkImeiAvai();
|
|
if (!allAvailable) {
|
|
invalidCartItems.value = invalidImeis;
|
|
console.error("IMEIs not available", invalidImeis);
|
|
$snackbar("Một số IMEI đã được bán, vui lòng kiểm tra lại", "Error");
|
|
return;
|
|
}
|
|
|
|
await createOrder();
|
|
|
|
if (orderInfo.value.paymentMethod.code === "CASH") {
|
|
finalizeOrder(invoice.value.id);
|
|
} else if (orderInfo.value.paymentMethod.code === "VIETQR") {
|
|
showVietQRModal.value = {
|
|
component: "pos/VietQR",
|
|
title: "Thanh toán qua VietQR",
|
|
width: "700px",
|
|
vbind: { invoiceId: invoice.value.id, finalizeOrder }, // verify thủ công?
|
|
};
|
|
} else if (orderInfo.value.paymentMethod.code === "VNPAY") {
|
|
try {
|
|
const { paymentUrl } = await $fetch("/api/vnpay/create-payment", {
|
|
method: "POST",
|
|
body: {
|
|
amount: invoice.value.total_amount,
|
|
invoiceCode: invoice.value.code,
|
|
},
|
|
});
|
|
|
|
window.open(paymentUrl, "vnpaypopup", "popup=true,width=900,height=700");
|
|
listenToVnpayEvents();
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
}
|
|
|
|
function vnpayListener(e) {
|
|
console.log("main window received e.data from popup", e.data);
|
|
|
|
const { type, message } = e.data;
|
|
if (type === "vnpay-success") {
|
|
finalizeOrder(invoice.value.id);
|
|
}
|
|
if (type === "vnpay-error") {
|
|
$snackbar(`Thanh toán VNPAY thất bại: ${message}`, "Error");
|
|
}
|
|
}
|
|
|
|
function listenToVnpayEvents() {
|
|
window.removeEventListener("message", vnpayListener);
|
|
window.addEventListener("message", vnpayListener);
|
|
}
|
|
|
|
function testReturnPage() {
|
|
listenToVnpayEvents();
|
|
|
|
const vnp_ResponseCode = "01";
|
|
window.open(
|
|
`http://localhost:3000/vnpay-return?vnp_Amount=1000000&vnp_BankCode=NCB&vnp_BankTranNo=VNP15593296&vnp_CardType=ATM&vnp_OrderInfo=Thanh+to%C3%A1n+%C4%91%C6%A1n+h%C3%A0ng+HD000001&vnp_PayDate=20260622142046&vnp_ResponseCode=${vnp_ResponseCode}&vnp_TmnCode=SCG4UUS6&vnp_TransactionNo=15593296&vnp_TransactionStatus=00&vnp_TxnRef=HD000001-2026-06-22T07:20:14.735Z&vnp_SecureHash=9726fe2db0da9741fc14fc42ced098cabf204f21ab12f633e8ac1cb7c9567a3b7a379d790f29a1a8b60fa8edb24acb0faca83feca0f7757865b0bf853b55ea01`,
|
|
"return",
|
|
"popup=true,width=800,height=600",
|
|
);
|
|
}
|
|
|
|
onMounted(async () => {
|
|
const [confirmedInvoiceStatusData, paidPaymentStatusData] = [
|
|
$getdata("Invoice_Status", {
|
|
filter: { code: "CONFIRMED" },
|
|
first: true,
|
|
}),
|
|
$getdata("Payment_Status", {
|
|
filter: { code: "PAID" },
|
|
first: true,
|
|
}),
|
|
];
|
|
|
|
confirmedInvoiceStatus.value = confirmedInvoiceStatusData;
|
|
paidPaymentStatus.value = paidPaymentStatusData;
|
|
});
|
|
|
|
return { isPending, placeOrder, testReturnPage, showVietQRModal };
|
|
}
|