import prisma from '../utils/db'; import { APIError } from '../utils/errors'; export class DiskonService { static async getActiveDiscounts(isMember: boolean) { const now = new Date(); // Filter diskon yang aktif dan periode tanggalnya cocok const discounts = await prisma.diskon.findMany({ where: { is_aktif: 1, tgl_mulai: { lte: now }, tgl_selesai: { gte: now }, ...(isMember ? {} : { member_only: 0 }), }, }); const currentTimeString = now.toTimeString().split(' ')[0]; // "HH:MM:SS" const activeDiscounts = discounts.filter((d) => { if (d.tipe === 'flash_sale') { if (d.jam_mulai && d.jam_selesai) { const jamMulai = new Date(d.jam_mulai).toTimeString().split(' ')[0]; const jamSelesai = new Date(d.jam_selesai).toTimeString().split(' ')[0]; return currentTimeString >= jamMulai && currentTimeString <= jamSelesai; } return true; } return true; }); return activeDiscounts; } static async hitungDiskonDanHarga(items: Array<{ produk_id: number; qty: number }>, memberId?: number | null) { const isMember = memberId !== undefined && memberId !== null; const activePromos = await this.getActiveDiscounts(isMember); // 1. Hitung harga dasar bertingkat per item (volume pricing) const cartItems: any[] = []; let subtotalDasar = 0; for (const item of items) { const produkId = item.produk_id; const qty = item.qty; const produk = await prisma.produk.findUnique({ where: { id: produkId }, include: { harga_tingkat: true }, }); if (!produk) { throw new APIError(`Produk ID ${produkId} tidak ditemukan.`, 404); } // Tentukan harga dasar berdasarkan volume pricing (harga_tingkat) let hargaSatuan = Number(produk.harga_jual); // Default ecer if (produk.harga_tingkat && produk.harga_tingkat.length > 0) { // Urutkan berdasarkan min_qty desc untuk mencari batas tertinggi yang terpenuhi const sortedTingkat = [...produk.harga_tingkat].sort((a, b) => Number(b.min_qty) - Number(a.min_qty)); for (const ht of sortedTingkat) { if (qty >= Number(ht.min_qty)) { hargaSatuan = Number(ht.harga); break; } } } const itemSubtotal = qty * hargaSatuan; subtotalDasar += itemSubtotal; cartItems.push({ produk, qty, harga_original: hargaSatuan, diskon_item: 0, diskon_id: null, subtotal: itemSubtotal, }); } // 2. Terapkan diskon per item (per_item_persen, per_item_nominal, flash_sale) for (const item of cartItems) { const produk = item.produk; const qty = item.qty; const hargaOriginal = item.harga_original; let bestDiscount = 0; let bestDiskonId: number | null = null; for (const promo of activePromos) { // Cek apakah promo berlaku untuk produk ini let isApplicable = false; if (!promo.produk_ids) { // Berlaku untuk semua produk isApplicable = true; } else { try { let pIds: number[] = []; if (typeof promo.produk_ids === 'string') { pIds = JSON.parse(promo.produk_ids); } else if (Array.isArray(promo.produk_ids)) { pIds = promo.produk_ids as number[]; } if (pIds.includes(produk.id)) { isApplicable = true; } } catch (e) { isApplicable = false; } } if (!isApplicable) continue; // Hitung nilai diskon let currentDiscount = 0; if (promo.tipe === 'per_item_persen' || promo.tipe === 'flash_sale') { currentDiscount = hargaOriginal * (Number(promo.nilai) / 100) * qty; } else if (promo.tipe === 'per_item_nominal') { currentDiscount = Number(promo.nilai) * qty; } if (currentDiscount > bestDiscount) { bestDiscount = currentDiscount; bestDiskonId = promo.id; } } if (bestDiscount > 0) { item.diskon_item = bestDiscount; item.diskon_id = bestDiskonId; item.subtotal = (hargaOriginal * qty) - bestDiscount; } } // Kalkulasi subtotal setelah item-level discounts let subtotalSetelahItem = cartItems.reduce((acc, item) => acc + item.subtotal, 0); const totalDiskonItem = cartItems.reduce((acc, item) => acc + item.diskon_item, 0); // 3. Terapkan diskon bundling (paket promo) let totalDiskonBundling = 0; const bundlingPromos = activePromos.filter((p) => p.tipe === 'bundling'); for (const promo of bundlingPromos) { const bundleItems = await prisma.diskon_bundling.findMany({ where: { diskon_id: promo.id }, }); if (bundleItems.length === 0) continue; // Hitung berapa banyak paket bundle yang bisa dibuat dari keranjang let maxPossibleBundles = 999999; for (const bItem of bundleItems) { const matchingCartItem = cartItems.find((item) => item.produk.id === bItem.produk_id); if (!matchingCartItem) { maxPossibleBundles = 0; break; } const availableMultiples = Math.floor(matchingCartItem.qty / Number(bItem.qty)); if (availableMultiples < maxPossibleBundles) { maxPossibleBundles = availableMultiples; } } if (maxPossibleBundles > 0) { const diskonBundleTotal = Number(promo.nilai) * maxPossibleBundles; totalDiskonBundling += diskonBundleTotal; subtotalSetelahItem -= diskonBundleTotal; } } // 4. Terapkan diskon tingkat transaksi (total_persen, total_nominal, member) const transactionPromos = activePromos.filter((p) => ['total_persen', 'total_nominal', 'member'].includes(p.tipe) ); let bestTransDiscount = 0; for (const promo of transactionPromos) { // Cek minimal belanja if (subtotalSetelahItem < Number(promo.min_belanja)) { continue; } let currentTransDiscount = 0; if (promo.tipe === 'total_persen' || promo.tipe === 'member') { currentTransDiscount = subtotalSetelahItem * (Number(promo.nilai) / 100); } else if (promo.tipe === 'total_nominal') { currentTransDiscount = Number(promo.nilai); } if (currentTransDiscount > bestTransDiscount) { bestTransDiscount = currentTransDiscount; } } const totalDiskonTransaksi = bestTransDiscount; // Hitung total akhir let totalAkhir = subtotalSetelahItem - totalDiskonTransaksi; if (totalAkhir < 0) { totalAkhir = 0; } return { cart_items: cartItems, subtotal_dasar: subtotalDasar, total_diskon_item: totalDiskonItem, total_diskon_bundling: totalDiskonBundling, total_diskon_transaksi: totalDiskonTransaksi, total_diskon_keseluruhan: totalDiskonItem + totalDiskonBundling + totalDiskonTransaksi, total_akhir: totalAkhir, }; } }