mirror of
https://github.com/marcogll/TaxHacker_s23.git
synced 2026-01-13 21:35:19 +00:00
feat: invoice generator
This commit is contained in:
126
app/(app)/apps/invoices/actions.ts
Normal file
126
app/(app)/apps/invoices/actions.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
"use server"
|
||||
|
||||
import { getCurrentUser, isSubscriptionExpired } from "@/lib/auth"
|
||||
import {
|
||||
getTransactionFileUploadPath,
|
||||
getUserUploadsDirectory,
|
||||
isEnoughStorageToUploadFile,
|
||||
safePathJoin,
|
||||
} from "@/lib/files"
|
||||
import { getAppData, setAppData } from "@/models/apps"
|
||||
import { createFile } from "@/models/files"
|
||||
import { createTransaction, updateTransactionFiles } from "@/models/transactions"
|
||||
import { Transaction, User } from "@/prisma/client"
|
||||
import { renderToBuffer } from "@react-pdf/renderer"
|
||||
import { randomUUID } from "crypto"
|
||||
import { mkdir, writeFile } from "fs/promises"
|
||||
import { revalidatePath } from "next/cache"
|
||||
import path from "path"
|
||||
import { createElement } from "react"
|
||||
import { InvoiceFormData } from "./components/invoice-page"
|
||||
import { InvoicePDF } from "./components/invoice-pdf"
|
||||
import { InvoiceTemplate } from "./default-templates"
|
||||
import { InvoiceAppData } from "./page"
|
||||
|
||||
export async function generateInvoicePDF(data: InvoiceFormData): Promise<Uint8Array> {
|
||||
const pdfElement = createElement(InvoicePDF, { data })
|
||||
const buffer = await renderToBuffer(pdfElement as any)
|
||||
return new Uint8Array(buffer)
|
||||
}
|
||||
|
||||
export async function addNewTemplateAction(user: User, template: InvoiceTemplate) {
|
||||
const appData = (await getAppData(user, "invoices")) as InvoiceAppData | null
|
||||
const updatedTemplates = [...(appData?.templates || []), template]
|
||||
const appDataResult = await setAppData(user, "invoices", { ...appData, templates: updatedTemplates })
|
||||
return { success: true, data: appDataResult }
|
||||
}
|
||||
|
||||
export async function deleteTemplateAction(user: User, templateId: string) {
|
||||
const appData = (await getAppData(user, "invoices")) as InvoiceAppData | null
|
||||
if (!appData) return { success: false, error: "No app data found" }
|
||||
|
||||
const updatedTemplates = appData.templates.filter((t) => t.id !== templateId)
|
||||
const appDataResult = await setAppData(user, "invoices", { ...appData, templates: updatedTemplates })
|
||||
return { success: true, data: appDataResult }
|
||||
}
|
||||
|
||||
export async function saveInvoiceAsTransactionAction(
|
||||
formData: InvoiceFormData
|
||||
): Promise<{ success: boolean; error?: string; data?: Transaction }> {
|
||||
try {
|
||||
const user = await getCurrentUser()
|
||||
|
||||
// Generate PDF
|
||||
const pdfBuffer = await generateInvoicePDF(formData)
|
||||
|
||||
// Calculate total amount from items
|
||||
const subtotal = formData.items.reduce((sum, item) => sum + item.subtotal, 0)
|
||||
const taxes = formData.additionalTaxes.reduce((sum, tax) => sum + tax.amount, 0)
|
||||
const fees = formData.additionalFees.reduce((sum, fee) => sum + fee.amount, 0)
|
||||
const totalAmount = (formData.taxIncluded ? subtotal : subtotal + taxes) + fees
|
||||
|
||||
// Create transaction
|
||||
const transaction = await createTransaction(user.id, {
|
||||
name: `Invoice #${formData.invoiceNumber || "unknown"}`,
|
||||
merchant: `${formData.billTo.split("\n")[0]}`,
|
||||
total: totalAmount * 100,
|
||||
currencyCode: formData.currency,
|
||||
issuedAt: new Date(formData.date),
|
||||
categoryCode: null,
|
||||
projectCode: null,
|
||||
type: "income",
|
||||
status: "pending",
|
||||
})
|
||||
|
||||
// Check storage limits
|
||||
if (!isEnoughStorageToUploadFile(user, pdfBuffer.length)) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Insufficient storage to save invoice PDF",
|
||||
}
|
||||
}
|
||||
|
||||
if (isSubscriptionExpired(user)) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Your subscription has expired, please upgrade your account or buy new subscription plan",
|
||||
}
|
||||
}
|
||||
|
||||
// Save PDF file
|
||||
const fileUuid = randomUUID()
|
||||
const fileName = `invoice-${formData.invoiceNumber}.pdf`
|
||||
const relativeFilePath = getTransactionFileUploadPath(fileUuid, fileName, transaction)
|
||||
const userUploadsDirectory = getUserUploadsDirectory(user)
|
||||
const fullFilePath = safePathJoin(userUploadsDirectory, relativeFilePath)
|
||||
|
||||
await mkdir(path.dirname(fullFilePath), { recursive: true })
|
||||
await writeFile(fullFilePath, pdfBuffer)
|
||||
|
||||
// Create file record in database
|
||||
const fileRecord = await createFile(user.id, {
|
||||
id: fileUuid,
|
||||
filename: fileName,
|
||||
path: relativeFilePath,
|
||||
mimetype: "application/pdf",
|
||||
isReviewed: true,
|
||||
metadata: {
|
||||
size: pdfBuffer.length,
|
||||
lastModified: Date.now(),
|
||||
},
|
||||
})
|
||||
|
||||
// Update transaction with the file ID
|
||||
await updateTransactionFiles(transaction.id, user.id, [fileRecord.id])
|
||||
|
||||
revalidatePath("/transactions")
|
||||
|
||||
return { success: true, data: transaction }
|
||||
} catch (error) {
|
||||
console.error("Failed to save invoice as transaction:", error)
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to save invoice as transaction: ${error}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user