diff --git a/pages/api/invoices/index.ts b/pages/api/invoices/index.ts new file mode 100644 index 000000000..64c2f9743 --- /dev/null +++ b/pages/api/invoices/index.ts @@ -0,0 +1,32 @@ +import { setSession } from 'utils/setSession' +import { parseCreateInvoicePOSTRequest } from 'utils/validators' +import { RESPONSE_MESSAGES } from 'constants/index' +import { createInvoice } from 'services/invoiceService' + +export default async ( + req: any, + res: any +): Promise => { + await setSession(req, res, true) + const session = req.session + if (req.method === 'POST') { + try { + const parsedValues = parseCreateInvoicePOSTRequest({ + ...req.body, + userId: session.userId + }) + const invoice = await createInvoice(parsedValues) + res.status(200).json({ + invoice + }) + } catch (err: any) { + switch (err.message) { + case RESPONSE_MESSAGES.USER_ID_NOT_PROVIDED_400.message: + res.status(400).json(RESPONSE_MESSAGES.USER_ID_NOT_PROVIDED_400) + break + default: + res.status(500).json({ statusCode: 500, message: err.message }) + } + } + } +} diff --git a/prisma/migrations/20250512173910_add_invoice/migration.sql b/prisma/migrations/20250512173910_add_invoice/migration.sql new file mode 100644 index 000000000..aa6afcb51 --- /dev/null +++ b/prisma/migrations/20250512173910_add_invoice/migration.sql @@ -0,0 +1,24 @@ +-- CreateTable +CREATE TABLE `Invoice` ( + `id` VARCHAR(191) NOT NULL, + `userId` VARCHAR(191) NOT NULL, + `invoiceNumber` VARCHAR(191) NOT NULL, + `transactionId` VARCHAR(191) NOT NULL, + `amount` DECIMAL(65, 30) NOT NULL, + `description` VARCHAR(191) NOT NULL, + `recipientName` VARCHAR(191) NOT NULL, + `recipientAddress` VARCHAR(191) NOT NULL, + `customerName` VARCHAR(191) NOT NULL, + `customerAddress` VARCHAR(191) NOT NULL, + `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updatedAt` DATETIME(3) NOT NULL, + + UNIQUE INDEX `Invoice_invoiceNumber_userId_unique_constraint`(`invoiceNumber`, `userId`), + PRIMARY KEY (`id`, `userId`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- AddForeignKey +ALTER TABLE `Invoice` ADD CONSTRAINT `Invoice_transactionId_fkey` FOREIGN KEY (`transactionId`) REFERENCES `Transaction`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `Invoice` ADD CONSTRAINT `Invoice_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `UserProfile`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d102eb0e1..a3e9fa868 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -64,6 +64,7 @@ model Network { prices Price[] } + model Transaction { id String @id @default(dbgenerated("(uuid())")) hash String @db.VarChar(255) @@ -74,6 +75,8 @@ model Transaction { opReturn String @db.LongText @default("") address Address @relation(fields: [addressId], references: [id], onDelete: Cascade, onUpdate: Cascade) prices PricesOnTransactions[] + invoices Invoice[] + createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -166,6 +169,7 @@ model UserProfile { lastSentVerificationEmailAt DateTime? wallets WalletsOnUserProfile[] addresses AddressesOnUserProfiles[] + invoices Invoice[] preferredCurrencyId Int @default(1) preferredTimezone String @db.VarChar(255)@default("") @@ -241,3 +245,25 @@ model OrganizationInvite { organizationId String organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) } + +model Invoice { + id String @default(uuid()) + userId String + invoiceNumber String + transactionId String + transaction Transaction @relation(fields: [transactionId], references: [id], onUpdate: Cascade, onDelete: Restrict) + userProfile UserProfile @relation(fields: [userId], references: [id], onUpdate: Cascade, onDelete: Restrict) + + amount Decimal + description String + recipientName String + recipientAddress String + customerName String + customerAddress String + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@id([id, userId]) + @@unique([invoiceNumber, userId], map: "Invoice_invoiceNumber_userId_unique_constraint") +} \ No newline at end of file diff --git a/services/invoiceService.ts b/services/invoiceService.ts new file mode 100644 index 000000000..157e3450d --- /dev/null +++ b/services/invoiceService.ts @@ -0,0 +1,37 @@ +import prisma from 'prisma/clientInstance' +import { Invoice } from '@prisma/client' +interface CreateInvoiceParams { + userId: string + transactionId: string + amount: number + description: string + recipientName: string + recipientAddress: string + customerName: string + customerAddress: string +} + +export async function createInvoice (params: CreateInvoiceParams): Promise { + const year = new Date().getFullYear() + + const latestInvoiceNumber = await prisma.invoice.findFirst({ + where: { + invoiceNumber: { + startsWith: `${year}-` + } + }, + orderBy: { + invoiceNumber: 'desc' + } + }) + const nextNumber = (latestInvoiceNumber != null) ? parseInt(latestInvoiceNumber.invoiceNumber.split('-')[1]) + 1 : 1 + + const invoiceNumber = `${year}-${String(nextNumber).padStart(3, '0')}` + + return await prisma.invoice.create({ + data: { + invoiceNumber, + ...params + } + }) +} diff --git a/utils/validators.ts b/utils/validators.ts index 27f20eb0e..8f29ca6c6 100644 --- a/utils/validators.ts +++ b/utils/validators.ts @@ -550,3 +550,24 @@ export const parseUpdateUserTimezonePUTRequest = function (params: UpdateUserTim return { timezone: params.timezone } } + +export interface CreateinvoicePOSTParameters { + userId: string + transactionId: string + amount: number + description: string + recipientName: string + recipientAddress: string + customerName: string + customerAddress: string +} + +export const parseCreateInvoicePOSTRequest = function (params: CreateinvoicePOSTParameters): CreateinvoicePOSTParameters { + let description = params.description + if (params.userId === '' || params.userId === undefined) throw new Error(RESPONSE_MESSAGES.USER_ID_NOT_PROVIDED_400.message) + if (description === undefined) description = '' + return { + ...params, + description + } +}