Initial import of AbelBirdNest Stock

This commit is contained in:
2026-05-16 18:25:51 +07:00
commit 14bb9bf744
472 changed files with 70671 additions and 0 deletions

View File

@ -0,0 +1,234 @@
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
const OPENING_EVENT_TYPES = new Set(["OPENING_COST"]);
const SALE_EVENT_TYPES = new Set(["SALE_REVENUE", "CONSIGNMENT_REVENUE"]);
const RETURN_EVENT_TYPES = new Set(["SALE_RETURN", "CONSIGNMENT_RETURN"]);
const COST_ADDITIONAL_EVENT_TYPES = new Set([
"WASHING_COST",
"WASHING_SHRINKAGE",
"TRANSFORMATION_SHRINKAGE",
"SALE_SHRINKAGE",
"CONSIGNMENT_SHRINKAGE",
"STOCK_ADJUSTMENT_LOSS",
"MANUAL_ADJUSTMENT"
]);
const roundQty = (value) => Number(value.toFixed(3));
const roundAmount = (value) => Number(value.toFixed(2));
async function recalcSummary(purchaseId, agentSharePercent) {
const entries = await prisma.purchaseRealizationEntry.findMany({
where: { purchaseId }
});
const aggregate = entries.reduce(
(state, entry) => {
const qtyIn = entry.qtyIn.toNumber();
const qtyOut = entry.qtyOut.toNumber();
const qtyShrinkage = entry.qtyShrinkage.toNumber();
const amountCost = entry.amountCost.toNumber();
const amountRevenue = entry.amountRevenue.toNumber();
const amountExpense = entry.amountExpense.toNumber();
const agentAmount = entry.agentAmount.toNumber();
state.qtyRemaining += qtyIn - qtyOut - qtyShrinkage;
state.profitLossTotal += amountRevenue - amountCost - amountExpense;
state.agentProfitTotal += agentAmount;
if (OPENING_EVENT_TYPES.has(entry.eventType)) {
state.qtyOpening += qtyIn;
state.costOpeningTotal += amountCost;
}
if (SALE_EVENT_TYPES.has(entry.eventType)) {
state.qtySold += qtyOut;
state.revenueTotal += amountRevenue;
}
if (RETURN_EVENT_TYPES.has(entry.eventType)) {
state.qtyReturned += qtyIn;
}
if (COST_ADDITIONAL_EVENT_TYPES.has(entry.eventType)) {
state.costAdditionalTotal += amountCost + amountExpense;
}
state.qtyShrinkage += qtyShrinkage;
return state;
},
{
qtyOpening: 0,
qtyRemaining: 0,
qtySold: 0,
qtyReturned: 0,
qtyShrinkage: 0,
costOpeningTotal: 0,
costAdditionalTotal: 0,
revenueTotal: 0,
profitLossTotal: 0,
agentProfitTotal: 0
}
);
const status =
aggregate.qtyOpening <= 0
? "OPEN"
: aggregate.qtyRemaining <= 0
? "READY_TO_CLOSE"
: aggregate.qtySold > 0 || aggregate.qtyShrinkage > 0
? "PARTIAL"
: "OPEN";
await prisma.purchaseRealizationSummary.upsert({
where: { purchaseId },
update: {
status,
qtyOpening: roundQty(aggregate.qtyOpening),
qtyRemaining: roundQty(aggregate.qtyRemaining),
qtySold: roundQty(aggregate.qtySold),
qtyReturned: roundQty(aggregate.qtyReturned),
qtyShrinkage: roundQty(aggregate.qtyShrinkage),
costOpeningTotal: roundAmount(aggregate.costOpeningTotal),
costAdditionalTotal: roundAmount(aggregate.costAdditionalTotal),
revenueTotal: roundAmount(aggregate.revenueTotal),
profitLossTotal: roundAmount(aggregate.profitLossTotal),
agentSharePercent,
agentProfitTotal: roundAmount(aggregate.agentProfitTotal),
closedAt: null
},
create: {
purchaseId,
status,
qtyOpening: roundQty(aggregate.qtyOpening),
qtyRemaining: roundQty(aggregate.qtyRemaining),
qtySold: roundQty(aggregate.qtySold),
qtyReturned: roundQty(aggregate.qtyReturned),
qtyShrinkage: roundQty(aggregate.qtyShrinkage),
costOpeningTotal: roundAmount(aggregate.costOpeningTotal),
costAdditionalTotal: roundAmount(aggregate.costAdditionalTotal),
revenueTotal: roundAmount(aggregate.revenueTotal),
profitLossTotal: roundAmount(aggregate.profitLossTotal),
agentSharePercent,
agentProfitTotal: roundAmount(aggregate.agentProfitTotal),
closedAt: null
}
});
}
async function main() {
const purchases = await prisma.purchase.findMany({
where: {
status: "SUBMITTED",
purchaseType: {
in: ["REGULAR", "OFFICE_BUYOUT"]
}
},
include: {
profitShareScheme: {
select: {
shareAgent: true
}
},
lots: true
},
orderBy: { id: "asc" }
});
let allocationsCreated = 0;
let entriesCreated = 0;
for (const purchase of purchases) {
for (const lot of purchase.lots) {
const allocationCount = await prisma.lotPurchaseAllocation.count({
where: {
lotId: lot.id,
purchaseId: purchase.id
}
});
if (allocationCount === 0) {
await prisma.lotPurchaseAllocation.create({
data: {
lotId: lot.id,
purchaseId: purchase.id,
purchaseLineId: lot.purchaseLineId,
sourceType: lot.sourceType,
sourceRefId: lot.sourceRefId,
agentIdSnapshot: purchase.agentId,
profitShareSchemeIdSnapshot: purchase.profitShareSchemeId,
qtyAllocated: lot.availableQty,
costTotalAllocated: roundAmount(lot.availableQty.toNumber() * lot.unitCost.toNumber()),
unitCostSnapshot: lot.unitCost,
notes: "Backfill opening allocation"
}
});
allocationsCreated += 1;
}
const openingCount = await prisma.purchaseRealizationEntry.count({
where: {
purchaseId: purchase.id,
lotId: lot.id,
eventType: "OPENING_COST"
}
});
if (openingCount === 0) {
const allocation = await prisma.lotPurchaseAllocation.findFirst({
where: {
lotId: lot.id,
purchaseId: purchase.id
},
orderBy: { id: "asc" }
});
await prisma.purchaseRealizationEntry.create({
data: {
purchaseId: purchase.id,
lotId: lot.id,
allocationId: allocation?.id ?? null,
eventType: "OPENING_COST",
referenceType: "PURCHASE",
referenceId: purchase.id,
occurredAt: lot.receivedAt,
qtyIn: lot.originalQty,
qtyOut: 0,
qtyShrinkage: 0,
amountCost: roundAmount(lot.originalQty.toNumber() * lot.unitCost.toNumber()),
amountRevenue: 0,
amountExpense: 0,
amountProfit: 0,
agentSharePercentSnapshot: purchase.profitShareScheme?.shareAgent ?? null,
agentAmount: 0,
notes: "Backfill opening realization"
}
});
entriesCreated += 1;
}
}
await recalcSummary(
purchase.id,
purchase.profitShareScheme?.shareAgent?.toNumber() ?? null
);
}
console.log(
JSON.stringify(
{
purchases: purchases.length,
allocations_created: allocationsCreated,
entries_created: entriesCreated
},
null,
2
)
);
}
main()
.catch((error) => {
console.error(error);
process.exitCode = 1;
})
.finally(async () => {
await prisma.$disconnect();
});

View File

@ -0,0 +1,222 @@
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
const banks = [
["002", "Bank Rakyat Indonesia (Persero) Tbk"],
["008", "Bank Mandiri (Persero) Tbk"],
["009", "Bank Negara Indonesia (Persero) Tbk"],
["014", "Bank Central Asia Tbk"],
["019", "Bank Panin Tbk"],
["022", "Bank CIMB Niaga Tbk"],
["023", "Bank UOB Indonesia"],
["026", "Bank Lippo"],
["028", "Bank OCBC NISP Tbk"],
["030", "American Express Bank"],
["031", "Citibank N.A."],
["032", "JP. Morgan Chase Bank NA"],
["033", "Bank of America N.A."],
["034", "ING Indonesia Bank"],
["036", "Bank Ekspor Indonesia"],
["037", "Bank Artha Graha Int'l"],
["039", "Bank Credit Agricole Indosuez"],
["040", "The Bank of Hongkong & Shanghai B.C. (Hongkong)"],
["041", "The Bank of Tokyo Mitsubishi UFJ"],
["042", "Bank Sumitomo Mitsui Indonesia"],
["045", "Bank Sumitomo Mitsui Indonesia"],
["046", "Bank DBS Indonesia"],
["047", "Bank Resona Perdania"],
["048", "Bank Mizuho Indonesia"],
["050", "Standard Chartered Bank"],
["052", "Bank ABN Amro"],
["053", "Bank Keppel Tatlee Buana"],
["054", "Bank Capital Indonesia"],
["057", "Bank BNP Paribas Indonesia"],
["059", "Korea Exchange Bank Danamon"],
["061", "ANZ Indonesia"],
["067", "Deutsche Bank AG"],
["068", "Bank Woori Indonesia"],
["069", "Bank of China"],
["076", "Bank Bumi Arta"],
["089", "Bank IFI"],
["093", "Bank Jtrust"],
["097", "Bank Haja"],
["110", "Bank Jabar Banten (BJB)"],
["111", "Bank DKI"],
["112", "Bank Pembangunan Daerah Daerah Istimewa Yogyakarta"],
["113", "Bank Jateng"],
["114", "Bank Jatim"],
["115", "Bank Jambi"],
["116", "Bank Aceh"],
["117", "Bank Sumut"],
["118", "Bank Nagari"],
["119", "Bank Riau Kepri"],
["120", "Bank Sumsel Babel"],
["121", "Bank Lampung"],
["122", "Bank Kalsel"],
["123", "Bank Kaltimtara"],
["124", "Bank Kaltim & Utara"],
["125", "Bank Kalteng"],
["126", "Bank Sulselbar"],
["127", "Bank SulutGo"],
["128", "Bank NTB Syariah"],
["129", "Bank Bali"],
["130", "Bank NTT"],
["131", "Bank Maluku Malut"],
["132", "Bank Papua"],
["133", "Bank Bengkulu"],
["134", "Bank Sulselbar"],
["135", "Bank Sultra"],
["136", "Bank Sulawesi Tenggara"],
["137", "Bank Sulawesi Tengah"],
["138", "Bank Sulawesi Selatan"],
["139", "Bank Sulawesi Utara"],
["141", "Bank Kalteng (Kaltimtara)"],
["147", "Bank Muamalat Indonesia"],
["145", "Bank Nusantara Parahyangan"],
["146", "Bank of India Indonesia"],
["151", "Bank Mestika Dharma"],
["152", "Shinhan Bank Indonesia (Metro Express)"],
["153", "Bank Sinarmas"],
["157", "Bank Maspion"],
["159", "Bank Hagakita"],
["161", "Bank Ganesha"],
["162", "Bank Windu Kentjana"],
["166", "Bank ICBC Indonesia (Halim Indonesia Bank)"],
["167", "Bank QNB Indonesia (QNB Kesawan)"],
["171", "Bank of Tokyo Mitsubishi UFJ Indonesia"],
["176", "Bank QNB Indonesia"],
["186", "Bank Agris"],
["213", "Bank Tabungan Pensiunan Nasional (BTPN)"],
["200", "Bank Tabungan Negara (BTN)"],
["203", "Bank Bumi Arta"],
["213", "BTPN/Jenius BTPN"],
["216", "Bank Artha Graha International"],
["240", "Bank Victoria International"],
["244", "Bank Index Selindo"],
["245", "Bank Kesejahteraan Ekonomi"],
["246", "Bank Harfa"],
["247", "Bank Artos Indonesia"],
["251", "Prima Master Bank"],
["252", "Bank Persyarikatan Indonesia"],
["253", "Liman International Bank"],
["254", "Bank Dipo International (Sahabat Sampoerna)"],
["255", "Bank Fama Internasional"],
["256", "Bank Kesehatan Bumi Arta"],
["257", "Bank Mayora Indonesia"],
["258", "Bank Royal Indonesia"],
["259", "Centratama Nasional Bank"],
["261", "Bank Indonesia"],
["262", "Bank Multika"],
["263", "Bank Permata"],
["267", "Bank Raya Indonesia"],
["273", "Bank Nusantara Parahyangan Syariah"],
["274", "Bank Jasa Jakarta"],
["275", "Bank Alfindo"],
["282", "Bank Yudha Bhakti"],
["283", "Bank MNC"],
["285", "Bank Bintang Manunggal"],
["286", "Bank Haga"],
["287", "Bank Mega"],
["294", "Bank Bisnis Internasional"],
["295", "Bank Sri Partha"],
["422", "BRI Syariah (migrasi ke BSI)"],
["423", "BCA Syariah"],
["425", "Bank BJB Syariah"],
["426", "Bank Mega"],
["427", "Bank Syariah Mandiri / BNI Syariah"],
["431", "Bank BTPN Syariah"],
["451", "Bank Syariah Indonesia"],
["453", "Bank BPD Kaltim"],
["454", "Bank Jatim Syariah"],
["456", "Bank Nusantara Parahyangan Syariah"],
["503", "Bank Agris"],
["506", "Bank Sinarmas Syariah"],
["510", "Prima Master Bank"],
["513", "Bank Ina Perdana"],
["517", "Bank Harfa"],
["521", "Bank Akita"],
["526", "Liman International Bank"],
["531", "Anglomas Internasional Bank"],
["535", "Bank Kesejahteraan"],
["536", "BCA Syariah"],
["542", "Artos Indonesia Bank"],
["547", "Bank Purba Danarta"],
["548", "Bank Multi Arta Sentosa"],
["550", "Bank Andara"],
["553", "Mayora Bank"],
["555", "Bank Victoria International"],
["562", "Bank Fama International"],
["564", "Bank Mandiri Taspen Pos"],
["566", "Bank Victoria International"],
["567", "Bank Harda"],
["688", "BPR KS"],
["761", "Bank Rakyat Indonesia"],
["789", "Indosat Dompetku"],
["811", "BPR Bank Indonesia"],
["836", "Bank OCBC NISP"],
["911", "Link Aja"],
["949", "Bank CTBC (China Trust) Indonesia"],
["950", "Bank Commonwealth"],
["956", "Bank Merincorp"],
["957", "Bank Diners Club"],
["985", "The Royal Bank"],
["988", "Bank Swaguna"],
["989", "Bank QNB Indonesia"],
["990", "Bank Swaguna"],
["992", "Bank Himpunan Saudara"],
["993", "Bank Jasa Jakarta"],
["994", "Bank Swaguna"]
];
function toRecords(list) {
const byCode = new Map();
const byName = new Set();
for (const [code, name] of list) {
const trimmedCode = String(code).trim().padStart(3, "0");
const trimmedName = String(name).trim();
if (!trimmedCode || !trimmedName) continue;
const normalizedName = trimmedName.toUpperCase();
if (!byCode.has(trimmedCode) && !byName.has(normalizedName)) {
byCode.set(trimmedCode, trimmedName);
byName.add(normalizedName);
}
}
return [...byCode.entries()].map(([code, name]) => ({ code, name, address: "Indonesia" }));
}
async function main() {
const records = toRecords(banks);
let created = 0;
let updated = 0;
for (const record of records) {
const current = await prisma.bank.findUnique({ where: { code: record.code } });
if (current) {
await prisma.bank.update({
where: { code: record.code },
data: { name: record.name, address: record.address, status: "ACTIVE" }
});
updated += 1;
} else {
await prisma.bank.create({
data: { code: record.code, name: record.name, address: record.address, status: "ACTIVE" }
});
created += 1;
}
}
const total = await prisma.bank.count();
console.log(`Seeded banks: created=${created}, updated=${updated}, total=${total}`);
}
main()
.catch((error) => {
console.error(error);
process.exitCode = 1;
})
.finally(async () => {
await prisma.$disconnect();
});

View File

@ -0,0 +1,77 @@
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
const globalCurrencies = [
["IDR", "Indonesian Rupiah", "Mata uang default sistem."],
["USD", "US Dollar", "Mata uang internasional umum."],
["EUR", "Euro", "Mata uang utama kawasan Euro."],
["GBP", "British Pound Sterling", "Mata uang utama Inggris."],
["JPY", "Japanese Yen", "Mata uang utama Jepang."],
["CNY", "Chinese Yuan Renminbi", "Mata uang utama Tiongkok."],
["HKD", "Hong Kong Dollar", "Mata uang utama Hong Kong."],
["SGD", "Singapore Dollar", "Mata uang regional umum."],
["AUD", "Australian Dollar", "Mata uang utama Australia."],
["NZD", "New Zealand Dollar", "Mata uang utama Selandia Baru."],
["CAD", "Canadian Dollar", "Mata uang utama Kanada."],
["CHF", "Swiss Franc", "Mata uang utama Swiss."],
["SEK", "Swedish Krona", "Mata uang utama Swedia."],
["NOK", "Norwegian Krone", "Mata uang utama Norwegia."],
["DKK", "Danish Krone", "Mata uang utama Denmark."],
["PLN", "Polish Zloty", "Mata uang utama Polandia."],
["CZK", "Czech Koruna", "Mata uang utama Ceko."],
["HUF", "Hungarian Forint", "Mata uang utama Hungaria."],
["AED", "UAE Dirham", "Mata uang utama Uni Emirat Arab."],
["SAR", "Saudi Riyal", "Mata uang utama Arab Saudi."],
["QAR", "Qatari Riyal", "Mata uang utama Qatar."],
["KWD", "Kuwaiti Dinar", "Mata uang utama Kuwait."],
["BHD", "Bahraini Dinar", "Mata uang utama Bahrain."],
["OMR", "Omani Rial", "Mata uang utama Oman."],
["INR", "Indian Rupee", "Mata uang utama India."],
["PKR", "Pakistani Rupee", "Mata uang utama Pakistan."],
["BDT", "Bangladeshi Taka", "Mata uang utama Bangladesh."],
["KRW", "South Korean Won", "Mata uang utama Korea Selatan."],
["TWD", "New Taiwan Dollar", "Mata uang utama Taiwan."],
["THB", "Thai Baht", "Mata uang utama Thailand."],
["MYR", "Malaysian Ringgit", "Mata uang utama Malaysia."],
["PHP", "Philippine Peso", "Mata uang utama Filipina."],
["VND", "Vietnamese Dong", "Mata uang utama Vietnam."],
["KHR", "Cambodian Riel", "Mata uang utama Kamboja."],
["LAK", "Lao Kip", "Mata uang utama Laos."],
["MMK", "Myanmar Kyat", "Mata uang utama Myanmar."],
["ZAR", "South African Rand", "Mata uang utama Afrika Selatan."],
["NGN", "Nigerian Naira", "Mata uang utama Nigeria."],
["EGP", "Egyptian Pound", "Mata uang utama Mesir."],
["KES", "Kenyan Shilling", "Mata uang utama Kenya."],
["TRY", "Turkish Lira", "Mata uang utama Turki."],
["ILS", "Israeli New Shekel", "Mata uang utama Israel."],
["MXN", "Mexican Peso", "Mata uang utama Meksiko."],
["BRL", "Brazilian Real", "Mata uang utama Brasil."],
["ARS", "Argentine Peso", "Mata uang utama Argentina."],
["CLP", "Chilean Peso", "Mata uang utama Chili."],
["COP", "Colombian Peso", "Mata uang utama Kolombia."],
["PEN", "Peruvian Sol", "Mata uang utama Peru."]
];
async function main() {
const result = await prisma.currency.createMany({
data: globalCurrencies.map(([code, name, description]) => ({
code,
name,
description,
status: "ACTIVE"
})),
skipDuplicates: true
});
console.log(JSON.stringify({ inserted: result.count, total_candidates: globalCurrencies.length }, null, 2));
}
main()
.catch((error) => {
console.error(error);
process.exitCode = 1;
})
.finally(async () => {
await prisma.$disconnect();
});

View File

@ -0,0 +1,67 @@
import path from "node:path";
import XLSX from "xlsx";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
const sourcePath =
process.argv[2] ?? "/Users/wirabasalamah/work/abelbirdnest/data gudang/Grade.xls";
function formatCode(prefix, sequence) {
return `${prefix}${String(sequence).padStart(5, "0")}`;
}
async function main() {
const workbook = XLSX.readFile(sourcePath);
const sheetName = workbook.SheetNames[0];
const rows = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName], { defval: "" });
let mangkokSequence = 1;
let nonMangkokSequence = 1;
for (const row of rows) {
const legacyCode = String(row.Code || "").trim();
const name = String(row.Name || "").trim();
const description = String(row.Description || "").trim();
const isMangkok = String(row.Mangkok || "").trim().toLowerCase() === "yes";
if (!name) continue;
const prefix = isMangkok ? "MGK" : "GRD";
const code = isMangkok
? formatCode(prefix, mangkokSequence++)
: formatCode(prefix, nonMangkokSequence++);
await prisma.grade.upsert({
where: { legacyCode: legacyCode || "__missing__" },
update: {
code,
isMangkok,
name,
description: description || null,
status: "ACTIVE"
},
create: {
code,
legacyCode: legacyCode || null,
isMangkok,
name,
description: description || null,
status: "ACTIVE"
}
});
}
const count = await prisma.grade.count();
console.log(`Seeded grades: ${count} from ${path.basename(sourcePath)}`);
}
main()
.catch((error) => {
console.error(error);
process.exitCode = 1;
})
.finally(async () => {
await prisma.$disconnect();
});