// composables/usePaymentCalculator.js import { ref, computed } from 'vue'; export function usePaymentCalculator() { // Inputs cơ bản const originPrice = ref(0); const discounts = ref([]); // [{ id, name, code, type: 1 (percent) | 2 (fixed), value: number }] const paymentPlan = ref([]); // [{cycle, value, type: 1 (percent) | other (fixed), days, ...}] const contractAllocationPercentage = ref(100); const startDate = ref(new Date()); // Thanh toán sớm const earlyPaymentCycles = ref(0); const earlyDiscountRate = ref(0.0191780821918); // Số tiền đã thanh toán thực tế const paidAmount = ref(0); // Tiền gốc const originalPrice = computed(() => originPrice.value); // Chi tiết chiết khấu const detailedDiscounts = computed(() => { const details = []; let currentBalance = originPrice.value || 0; discounts.value.forEach(discountData => { const d = discountData; let amount = 0; if (d.type === 1) { // percent amount = (currentBalance * Number(d.value)) / 100; } else { // fixed amount = Number(d.value); } if (currentBalance - amount < 0) { amount = currentBalance; } currentBalance -= amount; details.push({ id: d.id, name: d.name, code: d.code, customValue: d.value, customType: d.type, amount: amount, remaining: currentBalance }); }); return details; }); // Tổng tiền giảm giá từ discounts const totalDiscount = computed(() => { return detailedDiscounts.value.reduce((sum, d) => sum + d.amount, 0); }); // Giá sau chiết khấu const salePrice = computed(() => { const lastDiscount = detailedDiscounts.value[detailedDiscounts.value.length - 1]; return lastDiscount ? lastDiscount.remaining : originPrice.value; }); const allocatedAmount = computed(() => { const amount = salePrice.value * (contractAllocationPercentage.value / 100); return Math.round(amount / 1000) * 1000; }); const finalTotal = computed(() => allocatedAmount.value); // Lịch thanh toán gốc (hỗ trợ cả percent và fixed cho paymentPlan.type) const originalPaymentSchedule = computed(() => { const schedule = []; let remaining = allocatedAmount.value; const sortedPlan = [...paymentPlan.value].sort((a, b) => a.cycle - b.cycle); let lastToDateForPeriodCalc = new Date(startDate.value); // Used to calculate the duration of each installment period. sortedPlan.forEach(plan => { let amount = 0; // Xử lý linh hoạt theo type của Payment_Plan if (plan.type === 1) { // percent amount = allocatedAmount.value * (plan.value / 100); } else { // fixed (type 2 hoặc bất kỳ giá trị khác) amount = plan.value || 0; } const adjustedAmount = Math.min(amount, remaining); remaining -= adjustedAmount; const fromDate = new Date(startDate.value); // Per user request, all installments start from the same date. const toDate = new Date(startDate.value); toDate.setDate(toDate.getDate() + (plan.days || 0)); // The duration of the installment period is the difference between this installment's due date and the previous one. const daysInPeriod = Math.round((toDate.getTime() - lastToDateForPeriodCalc.getTime()) / (1000 * 60 * 60 * 24)); schedule.push({ cycle: plan.cycle, from_date: fromDate, to_date: toDate, amount: adjustedAmount, paid_amount: 0, remain_amount: adjustedAmount, days: daysInPeriod > 0 ? daysInPeriod : 0, // Use calculated period duration; it cannot be negative. status: 1, type: plan.type, value: plan.value, payment_note: `Thanh toán theo kế hoạch đợt số ${plan.cycle}` }); lastToDateForPeriodCalc = new Date(toDate); // Update the last due date for the next period's duration calculation. }); // Cộng phần dư (nếu có) vào đợt cuối if (remaining > 0 && schedule.length > 0) { const last = schedule[schedule.length - 1]; last.amount += remaining; last.remain_amount += remaining; } return schedule; }); // Chiết khấu thanh toán sớm const earlyDiscounts = computed(() => { if (earlyPaymentCycles.value <= 0) return []; const schedule = originalPaymentSchedule.value; const numEarly = Math.min(earlyPaymentCycles.value, schedule.length); const actualPaymentDate = new Date(startDate.value); const actualPaymentDateOnly = new Date(actualPaymentDate.getFullYear(), actualPaymentDate.getMonth(), actualPaymentDate.getDate()); return schedule.slice(0, numEarly).map(item => { const originalDate = new Date(item.to_date); const originalDateOnly = new Date(originalDate.getFullYear(), originalDate.getMonth(), originalDate.getDate()); const earlyDays = Math.round((originalDateOnly.getTime() - actualPaymentDateOnly.getTime()) / (1000 * 60 * 60 * 24)); if (earlyDays <= 0) { return 0; } return item.amount * earlyDays * (earlyDiscountRate.value / 100); }); }); const totalEarlyDiscount = computed(() => { const rawTotal = earlyDiscounts.value.reduce((sum, disc) => sum + disc, 0); return parseInt(rawTotal); }); // Chi tiết chiết khấu thanh toán sớm const earlyDiscountDetails = computed(() => { if (earlyPaymentCycles.value <= 0) return []; const schedule = originalPaymentSchedule.value; const numEarly = Math.min(earlyPaymentCycles.value, schedule.length); const actualPaymentDate = new Date(startDate.value); const actualPaymentDateOnly = new Date(actualPaymentDate.getFullYear(), actualPaymentDate.getMonth(), actualPaymentDate.getDate()); return schedule.slice(0, numEarly).map((item, index) => { const originalDate = new Date(item.to_date); const originalDateOnly = new Date(originalDate.getFullYear(), originalDate.getMonth(), originalDate.getDate()); const earlyDays = Math.round((originalDateOnly.getTime() - actualPaymentDateOnly.getTime()) / (1000 * 60 * 60 * 24)); return { cycle: item.cycle, original_payment_date: originalDate, actual_payment_date: actualPaymentDate, early_days: earlyDays > 0 ? earlyDays : 0, original_amount: item.amount, discount_rate: earlyDiscountRate.value, discount_amount: earlyDiscounts.value[index] || 0, }; }); }); // Lịch sau thanh toán sớm (gộp đợt) const scheduleAfterEarly = computed(() => { const base = originalPaymentSchedule.value; const numEarly = Math.min(earlyPaymentCycles.value, base.length); if (numEarly <= 0) return base; const earlyItems = base.slice(0, numEarly); const totalEarlyAmount = earlyItems.reduce((sum, i) => sum + i.amount, 0); const mergedAmount = totalEarlyAmount - totalEarlyDiscount.value; const originalCycles = earlyItems.map(i => i.cycle); const mergedItem = { cycle: 1, from_date: earlyItems[0].from_date, to_date: earlyItems[0].from_date, // Due date is the start date for the merged installment amount: Math.max(0, mergedAmount), paid_amount: 0, remain_amount: Math.max(0, mergedAmount), payment_note: `Thanh toán gộp đợt ${originalCycles.join(', ')} (đã trừ số tiền chiết khấu thanh toán sớm là ${totalEarlyDiscount.value})`, days: 0, status: 1, is_merged: true, original_cycles: originalCycles }; const remainingItems = base.slice(numEarly).map((item, idx) => ({ ...item, cycle: idx + 2, })); return [mergedItem, ...remainingItems]; }); // Lịch thanh toán cuối cùng const finalPaymentSchedule = computed(() => { const schedule = scheduleAfterEarly.value.map(item => ({ ...item })); let remainingPaid = paidAmount.value || 0; for (let i = 0; i < schedule.length && remainingPaid > 0; i++) { const item = schedule[i]; const canPay = Math.min(remainingPaid, item.remain_amount); item.paid_amount += canPay; item.remain_amount -= canPay; remainingPaid -= canPay; if (item.remain_amount <= 0) { item.status = 2; item.remain_amount = 0; } } return schedule; }); const totalRemaining = computed(() => finalPaymentSchedule.value.reduce((sum, item) => sum + item.remain_amount, 0) ); return { originPrice, discounts, paymentPlan, contractAllocationPercentage, startDate, earlyPaymentCycles, earlyDiscountRate, paidAmount, originalPrice, detailedDiscounts, totalDiscount, salePrice, allocatedAmount, finalTotal, finalPaymentSchedule, originalPaymentSchedule, earlyDiscountDetails, totalEarlyDiscount, totalRemaining, }; }