Tokiva-backend-Node / src /services /diskon.service.ts
anoderb
feat: initial commit of Tokiva POS Node backend for Hugging Face
ef874e6
Raw
History Blame Contribute Delete
7.11 kB
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,
};
}
}