feat: add dayjs for date formatting and implement invoice partner functionality
Deploy Application / deploy (push) Successful in 39s
Details
Deploy Application / deploy (push) Successful in 39s
Details
This commit is contained in:
parent
d7031052ea
commit
faa30f3d7f
|
|
@ -13,6 +13,7 @@
|
|||
"axios": "^1.12.2",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"cors": "^2.8.5",
|
||||
"dayjs": "^1.11.19",
|
||||
"decimal.js": "^10.6.0",
|
||||
"express": "^4.17.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
|
|
@ -2321,6 +2322,12 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.19",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
|
||||
"integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
"axios": "^1.12.2",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"cors": "^2.8.5",
|
||||
"dayjs": "^1.11.19",
|
||||
"decimal.js": "^10.6.0",
|
||||
"express": "^4.17.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
|
|
|
|||
|
|
@ -262,6 +262,7 @@ model rv_openitem {
|
|||
db_id String? @db.Uuid
|
||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
is_pay String? @default("N") @db.Char(1)
|
||||
invoicePartners invoicePartner[]
|
||||
}
|
||||
|
||||
model transactions {
|
||||
|
|
@ -334,6 +335,23 @@ model bank_code {
|
|||
banks banks[]
|
||||
}
|
||||
|
||||
model invoicePartner {
|
||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
partnerServiceId String @db.VarChar(50)
|
||||
c_bpartner_id Decimal? @db.Decimal(10, 0)
|
||||
db_id String? @db.Uuid
|
||||
c_invoice_id Decimal? @db.Decimal(10, 0)
|
||||
invoiceAmount Decimal? @db.Decimal(20, 2)
|
||||
payedAmount Decimal? @db.Decimal(20, 2)
|
||||
amount Decimal? @db.Decimal(20, 2)
|
||||
is_active Boolean @default(true)
|
||||
is_pay Boolean @default(false)
|
||||
rv_openitem_id String? @db.Uuid
|
||||
created_at DateTime @default(now()) @db.Timestamptz(6)
|
||||
updated_at DateTime @default(now()) @db.Timestamptz(6)
|
||||
rv_openitem rv_openitem? @relation(fields: [rv_openitem_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
}
|
||||
|
||||
model invoice {
|
||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
user_id String? @db.Uuid
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ export async function createSignature({
|
|||
throw new Error("No private key file associated with the user");
|
||||
}
|
||||
const dir = path.join(__dirname, "..", "..", user.private_key_file);
|
||||
console.log(dir);
|
||||
if (!fs.existsSync(dir)) {
|
||||
throw new Error(`Private key file not found`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,17 @@ function mapRowToRvOpenitem(row: any) {
|
|||
|
||||
// Function to process a single database sync
|
||||
async function processDatabaseSync(database: any) {
|
||||
if (!database.db_id) {
|
||||
await db.activity_logs.create({
|
||||
data: {
|
||||
action: "sync_rv_openitem",
|
||||
status: "error",
|
||||
message: `Database ${database.name} has no db_id, skipping sync`,
|
||||
extra_json: database,
|
||||
},
|
||||
});
|
||||
return { inserted: 0, updated: 0 }; // Exit the function early if db_id is missing
|
||||
}
|
||||
await db.activity_logs.create({
|
||||
data: {
|
||||
action: "sync_rv_openitem",
|
||||
|
|
@ -118,6 +129,13 @@ async function processDatabaseSync(database: any) {
|
|||
const batchSize = 200;
|
||||
|
||||
// Process in batches to avoid memory issues
|
||||
|
||||
const existingRecords = await db.rv_openitem.findMany({
|
||||
where: {
|
||||
db_id: database.db_id,
|
||||
},
|
||||
select: { id: true, c_invoice_id: true },
|
||||
});
|
||||
for (let i = 0; i < rows.length; i += batchSize) {
|
||||
const batch = rows.slice(i, i + batchSize);
|
||||
const ops = batch.map((r: any) => mapRowToRvOpenitem(r));
|
||||
|
|
@ -133,12 +151,6 @@ async function processDatabaseSync(database: any) {
|
|||
);
|
||||
dbInserted += ops.length;
|
||||
} else {
|
||||
const existingRecords = await db.rv_openitem.findMany({
|
||||
where: {
|
||||
db_id: database.db_id,
|
||||
},
|
||||
select: { id: true, c_invoice_id: true },
|
||||
});
|
||||
await db.activity_logs.create({
|
||||
data: {
|
||||
action: "sync_rv_openitem",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import { get } from "lodash";
|
|||
import db from "../../../db";
|
||||
import lo from "../../lodash";
|
||||
import Decimal from "decimal.js";
|
||||
import invoicePartner from "./invoicePartner";
|
||||
import { formatDate, formatDateIndonesia } from "../../formatTimestamp";
|
||||
const twoDigits = (n: number) => String(n).padStart(2, "0");
|
||||
export const formatMoney = (v: any, decimal?: boolean) => {
|
||||
const d = new Decimal(v ?? 0);
|
||||
|
|
@ -54,16 +56,31 @@ export default async function ({
|
|||
message: "Try Get Outstanding Amount",
|
||||
},
|
||||
});
|
||||
const grandtotal = await db.rv_openitem.aggregate({
|
||||
_sum: {
|
||||
grandtotal: true,
|
||||
},
|
||||
|
||||
const rv = await db.rv_openitem.findMany({
|
||||
where: {
|
||||
c_bpartner_id: c_bpartner.c_bpartner_id,
|
||||
db_id: user.database.db_id,
|
||||
is_pay: "N",
|
||||
},
|
||||
});
|
||||
console.log({
|
||||
c_bpartner_id: c_bpartner.c_bpartner_id,
|
||||
db_id: user.database.db_id,
|
||||
is_pay: "N",
|
||||
});
|
||||
const invoiceBPartner = await invoicePartner({
|
||||
data: {
|
||||
partnerServiceId: customerNo,
|
||||
invoices: rv,
|
||||
db_id: user.database.db_id,
|
||||
},
|
||||
});
|
||||
const grandtotal = {
|
||||
_sum: {
|
||||
grandtotal: invoiceBPartner?.totalAmount,
|
||||
},
|
||||
};
|
||||
|
||||
await db.activity_logs.create({
|
||||
data: {
|
||||
|
|
@ -73,16 +90,6 @@ export default async function ({
|
|||
"Outstanding Amount Found: " + formatMoney(grandtotal._sum.grandtotal),
|
||||
},
|
||||
});
|
||||
// console.log({
|
||||
// _sum: {
|
||||
// grandtotal: true,
|
||||
// },
|
||||
// where: {
|
||||
// c_bpartner_id: c_bpartner.c_bpartner_id,
|
||||
// db_id: user.database.db_id,
|
||||
// is_pay: "N",
|
||||
// },
|
||||
// });
|
||||
if (lo.get(grandtotal, "_sum.grandtotal") === null) {
|
||||
throw new Error(
|
||||
"No outstanding amount found for the given Virtual Account No"
|
||||
|
|
@ -127,7 +134,35 @@ export default async function ({
|
|||
if (!invoice) throw new Error("Failed to create invoice record");
|
||||
const transactions: any[] = [];
|
||||
const billDetails: any[] = [];
|
||||
lines.map((item, index) => {
|
||||
const deskripsi: any[] = [];
|
||||
const keterangan: any[] = [];
|
||||
const jatuhTempo: any[] = [];
|
||||
const status: any[] = [];
|
||||
const refTransaksi: any[] = [];
|
||||
const linesInvoice = invoiceBPartner?.invoice || [];
|
||||
linesInvoice.map((inv, index) => {
|
||||
const item = get(inv, "rv_openitem");
|
||||
keterangan.push({
|
||||
english: get(item, "description"),
|
||||
indonesia: get(item, "description"),
|
||||
});
|
||||
deskripsi.push({
|
||||
english: `Invoice ${get(item, "documentno")}`,
|
||||
indonesia: `Faktur ${get(item, "documentno")}`,
|
||||
});
|
||||
jatuhTempo.push({
|
||||
english: formatDate(),
|
||||
indonesia: formatDateIndonesia(),
|
||||
});
|
||||
status.push({
|
||||
english: "Unpaid",
|
||||
indonesia: "Belum Lunas",
|
||||
});
|
||||
refTransaksi.push({
|
||||
english: get(item, "documentno"),
|
||||
indonesia: get(item, "documentno"),
|
||||
});
|
||||
|
||||
billDetails.push({
|
||||
billCode: twoDigits(index + 1),
|
||||
billName: get(item, "documentno"),
|
||||
|
|
@ -144,7 +179,7 @@ export default async function ({
|
|||
billcode: twoDigits(index + 1),
|
||||
line_no: index + 1,
|
||||
billname: get(item, "documentno"),
|
||||
amount: lo.get(item, "grandtotal", 0),
|
||||
amount: lo.get(item, "amount", 0),
|
||||
c_invoice_id: get(item, "c_invoice_id"),
|
||||
db_id: lo.get(user, "database.db_id"),
|
||||
bank_id: get(user, "bank_id"),
|
||||
|
|
@ -182,5 +217,10 @@ export default async function ({
|
|||
value: "0.00",
|
||||
currency: "IDR",
|
||||
},
|
||||
deskripsi,
|
||||
keterangan,
|
||||
jatuhTempo,
|
||||
status,
|
||||
refTransaksi,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
import db from "../../../db";
|
||||
|
||||
export default async function ({ data }: { data: any }) {
|
||||
const partnerServiceId = data.partnerServiceId;
|
||||
const db_id = data.db_id;
|
||||
const invoices: any[] = data.invoices || [];
|
||||
const invoiceExists = await db.invoicePartner.findMany({
|
||||
where: {
|
||||
partnerServiceId,
|
||||
db_id,
|
||||
is_pay: false,
|
||||
},
|
||||
});
|
||||
|
||||
const removeSpace = (string: string) => {
|
||||
return string
|
||||
.replace(
|
||||
/[\s\u00A0\u1680\u2000-\u200F\u2028\u2029\u202F\u205F\u3000\uFEFF]/g,
|
||||
" "
|
||||
)
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
};
|
||||
const invoicePartnerCreate = invoices.filter((invoice: any) => {
|
||||
return !invoiceExists.find(
|
||||
(inv) =>
|
||||
removeSpace(JSON.stringify(inv.c_invoice_id)) ===
|
||||
removeSpace(JSON.stringify(invoice.c_invoice_id))
|
||||
);
|
||||
});
|
||||
const transactions = [] as any[];
|
||||
if (invoicePartnerCreate.length > 0)
|
||||
for (const inv of invoicePartnerCreate) {
|
||||
transactions.push(
|
||||
db.invoicePartner.create({
|
||||
data: {
|
||||
partnerServiceId,
|
||||
db_id,
|
||||
c_bpartner_id: inv.c_bpartner_id,
|
||||
c_invoice_id: inv.c_invoice_id,
|
||||
amount: Number(inv.grandtotal) || 0,
|
||||
rv_openitem_id: inv.id,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
if (transactions.length > 0) {
|
||||
await db.$transaction(transactions);
|
||||
}
|
||||
const sumAmount = await db.invoicePartner.aggregate({
|
||||
_sum: {
|
||||
amount: true,
|
||||
},
|
||||
where: {
|
||||
db_id,
|
||||
partnerServiceId,
|
||||
is_pay: false,
|
||||
},
|
||||
});
|
||||
const invoice = await db.invoicePartner.findMany({
|
||||
where: {
|
||||
db_id,
|
||||
partnerServiceId,
|
||||
is_pay: false,
|
||||
},
|
||||
include: {
|
||||
rv_openitem: true,
|
||||
},
|
||||
});
|
||||
return {
|
||||
invoice,
|
||||
totalAmount: sumAmount._sum.amount || 0,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import db from "../../../db";
|
||||
|
||||
export default async function ({ data }: { data: any }) {
|
||||
const partnerServiceId = data.partnerServiceId;
|
||||
let paymentAmount = data.paymentAmount;
|
||||
const db_id = data.db_id;
|
||||
const invoices: any[] = data.invoices || [];
|
||||
const invoiceExists = await db.invoicePartner.findMany({
|
||||
where: {
|
||||
partnerServiceId,
|
||||
db_id,
|
||||
is_pay: false,
|
||||
amount: {
|
||||
gt: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
const transactions = [] as any[];
|
||||
if (paymentAmount > 0) {
|
||||
invoiceExists.map((inv) => {
|
||||
if (paymentAmount > 0) {
|
||||
let sisaInvoice = Number(inv.amount) || 0;
|
||||
sisaInvoice =
|
||||
paymentAmount > sisaInvoice ? 0 : sisaInvoice - paymentAmount;
|
||||
const amountInvoice = Number(inv.amount) || 0;
|
||||
transactions.push(
|
||||
db.invoicePartner.update({
|
||||
where: {
|
||||
id: inv.id,
|
||||
},
|
||||
data: {
|
||||
amount: sisaInvoice < 0 ? 0 : sisaInvoice,
|
||||
},
|
||||
})
|
||||
);
|
||||
if (paymentAmount >= amountInvoice) {
|
||||
paymentAmount -= amountInvoice;
|
||||
} else {
|
||||
paymentAmount = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
await db.$transaction(transactions);
|
||||
const sumAmount = await db.invoicePartner.aggregate({
|
||||
_sum: { amount: true },
|
||||
});
|
||||
if (!sumAmount._sum.amount) {
|
||||
await db.invoicePartner.updateMany({
|
||||
where: {
|
||||
partnerServiceId,
|
||||
},
|
||||
data: {
|
||||
is_pay: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
return {
|
||||
totalUnpaid: sumAmount._sum.amount || 0,
|
||||
};
|
||||
}
|
||||
|
|
@ -59,6 +59,7 @@ export default async function ({ data }: { data: any }) {
|
|||
throw new Error("No public key file associated with the user");
|
||||
}
|
||||
const fullPath = path.join(__dirname, "..", "..", "..", "..", publicKeyFile);
|
||||
console.log(fullPath);
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
throw new Error("Public key file not found");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import dayjs from "dayjs";
|
||||
|
||||
export function formatTimestamp(d = new Date()): string {
|
||||
const yyyy = d.getFullYear();
|
||||
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
||||
|
|
@ -12,3 +14,27 @@ export function formatTimestamp(d = new Date()): string {
|
|||
const om = String(abs % 60).padStart(2, "0");
|
||||
return `${yyyy}-${mm}-${dd}T${hh}:${min}:${ss}${sign}${oh}:${om}`;
|
||||
}
|
||||
|
||||
export const formatDateIndonesia = (d = new Date()) => {
|
||||
// 15 November 2025
|
||||
const bulanIndo = [
|
||||
"Januari",
|
||||
"Februari",
|
||||
"Maret",
|
||||
"April",
|
||||
"Mei",
|
||||
"Juni",
|
||||
"Juli",
|
||||
"Agustus",
|
||||
"September",
|
||||
"Oktober",
|
||||
"November",
|
||||
"Desember",
|
||||
];
|
||||
return `${d.getDate()} ${bulanIndo[d.getMonth()]} ${d.getFullYear()}`;
|
||||
};
|
||||
|
||||
export const formatDate = (d = new Date(), format = "DD MMMM YYYY") => {
|
||||
// YYYY-MMM-DD
|
||||
return dayjs(d).format(format);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue