fix: rename data -> models

This commit is contained in:
Vasily Zubarev
2025-03-22 10:39:47 +01:00
parent ac8f848d94
commit 9ca7d9c712
50 changed files with 103 additions and 98 deletions

40
models/categories.ts Normal file
View File

@@ -0,0 +1,40 @@
import { prisma } from "@/lib/db"
import { codeFromName } from "@/lib/utils"
import { Prisma } from "@prisma/client"
import { cache } from "react"
export const getCategories = cache(async () => {
return await prisma.category.findMany({
orderBy: {
name: "asc",
},
})
})
export const getCategoryByCode = cache(async (code: string) => {
return await prisma.category.findUnique({
where: { code },
})
})
export const createCategory = async (category: Prisma.CategoryCreateInput) => {
if (!category.code) {
category.code = codeFromName(category.name as string)
}
return await prisma.category.create({
data: category,
})
}
export const updateCategory = async (code: string, category: Prisma.CategoryUpdateInput) => {
return await prisma.category.update({
where: { code },
data: category,
})
}
export const deleteCategory = async (code: string) => {
return await prisma.category.delete({
where: { code },
})
}

30
models/currencies.ts Normal file
View File

@@ -0,0 +1,30 @@
import { prisma } from "@/lib/db"
import { Prisma } from "@prisma/client"
import { cache } from "react"
export const getCurrencies = cache(async () => {
return await prisma.currency.findMany({
orderBy: {
code: "asc",
},
})
})
export const createCurrency = async (currency: Prisma.CurrencyCreateInput) => {
return await prisma.currency.create({
data: currency,
})
}
export const updateCurrency = async (code: string, currency: Prisma.CurrencyUpdateInput) => {
return await prisma.currency.update({
where: { code },
data: currency,
})
}
export const deleteCurrency = async (code: string) => {
return await prisma.currency.delete({
where: { code },
})
}

155
models/export_and_import.ts Normal file
View File

@@ -0,0 +1,155 @@
import { prisma } from "@/lib/db"
import { codeFromName } from "@/lib/utils"
import { formatDate } from "date-fns"
import { createCategory, getCategoryByCode } from "./categories"
import { createProject, getProjectByCode } from "./projects"
import { TransactionFilters } from "./transactions"
export type ExportFilters = TransactionFilters
export type ExportFields = string[]
export const exportImportFields = [
{
code: "name",
type: "string",
},
{
code: "description",
type: "string",
},
{
code: "merchant",
type: "string",
},
{
code: "total",
type: "number",
export: async function (value: number) {
return value / 100
},
import: async function (value: string) {
const num = parseFloat(value)
return isNaN(num) ? 0.0 : num * 100
},
},
{
code: "currencyCode",
type: "string",
},
{
code: "convertedTotal",
type: "number",
export: async function (value: number | null) {
if (!value) {
return null
}
return value / 100
},
import: async function (value: string) {
const num = parseFloat(value)
return isNaN(num) ? 0.0 : num * 100
},
},
{
code: "convertedCurrencyCode",
type: "string",
},
{
code: "type",
type: "string",
},
{
code: "note",
type: "string",
},
{
code: "categoryCode",
type: "string",
export: async function (value: string | null) {
if (!value) {
return null
}
const category = await getCategoryByCode(value)
return category?.name
},
import: async function (value: string) {
const category = await importCategory(value)
return category?.code
},
},
{
code: "projectCode",
type: "string",
export: async function (value: string | null) {
if (!value) {
return null
}
const project = await getProjectByCode(value)
return project?.name
},
import: async function (value: string) {
const project = await importProject(value)
return project?.code
},
},
{
code: "issuedAt",
type: "date",
export: async function (value: Date | null) {
if (!value || isNaN(value.getTime())) {
return null
}
try {
return formatDate(value, "yyyy-MM-dd")
} catch (error) {
return null
}
},
import: async function (value: string) {
try {
return new Date(value)
} catch (error) {
return null
}
},
},
]
export const exportImportFieldsMapping = exportImportFields.reduce((acc, field) => {
acc[field.code] = field
return acc
}, {} as Record<string, any>)
export const importProject = async (name: string) => {
const code = codeFromName(name)
const existingProject = await prisma.project.findFirst({
where: {
OR: [{ code }, { name }],
},
})
if (existingProject) {
return existingProject
}
return await createProject({ code, name })
}
export const importCategory = async (name: string) => {
const code = codeFromName(name)
const existingCategory = await prisma.category.findFirst({
where: {
OR: [{ code }, { name }],
},
})
if (existingCategory) {
return existingCategory
}
return await createCategory({ code, name })
}

30
models/fields.ts Normal file
View File

@@ -0,0 +1,30 @@
import { prisma } from "@/lib/db"
import { codeFromName } from "@/lib/utils"
import { Prisma } from "@prisma/client"
import { cache } from "react"
export const getFields = cache(async () => {
return await prisma.field.findMany()
})
export const createField = async (field: Prisma.FieldCreateInput) => {
if (!field.code) {
field.code = codeFromName(field.name as string)
}
return await prisma.field.create({
data: field,
})
}
export const updateField = async (code: string, field: Prisma.FieldUpdateInput) => {
return await prisma.field.update({
where: { code },
data: field,
})
}
export const deleteField = async (code: string) => {
return await prisma.field.delete({
where: { code },
})
}

79
models/files.ts Normal file
View File

@@ -0,0 +1,79 @@
"use server"
import { prisma } from "@/lib/db"
import { unlink } from "fs/promises"
import path from "path"
import { cache } from "react"
import { getTransactionById } from "./transactions"
export const getUnsortedFiles = cache(async () => {
return await prisma.file.findMany({
where: {
isReviewed: false,
},
orderBy: {
createdAt: "desc",
},
})
})
export const getUnsortedFilesCount = cache(async () => {
return await prisma.file.count({
where: {
isReviewed: false,
},
})
})
export const getFileById = cache(async (id: string) => {
return await prisma.file.findFirst({
where: { id },
})
})
export const getFilesByTransactionId = cache(async (id: string) => {
const transaction = await getTransactionById(id)
if (transaction && transaction.files) {
return await prisma.file.findMany({
where: {
id: {
in: transaction.files as string[],
},
},
orderBy: {
createdAt: "asc",
},
})
}
return []
})
export const createFile = async (data: any) => {
return await prisma.file.create({
data,
})
}
export const updateFile = async (id: string, data: any) => {
return await prisma.file.update({
where: { id },
data,
})
}
export const deleteFile = async (id: string) => {
const file = await getFileById(id)
if (!file) {
return
}
try {
await unlink(path.resolve(file.path))
} catch (error) {
console.error("Error deleting file:", error)
}
return await prisma.file.delete({
where: { id },
})
}

40
models/projects.ts Normal file
View File

@@ -0,0 +1,40 @@
import { prisma } from "@/lib/db"
import { codeFromName } from "@/lib/utils"
import { Prisma } from "@prisma/client"
import { cache } from "react"
export const getProjects = cache(async () => {
return await prisma.project.findMany({
orderBy: {
name: "asc",
},
})
})
export const getProjectByCode = cache(async (code: string) => {
return await prisma.project.findUnique({
where: { code },
})
})
export const createProject = async (project: Prisma.ProjectCreateInput) => {
if (!project.code) {
project.code = codeFromName(project.name as string)
}
return await prisma.project.create({
data: project,
})
}
export const updateProject = async (code: string, project: Prisma.ProjectUpdateInput) => {
return await prisma.project.update({
where: { code },
data: project,
})
}
export const deleteProject = async (code: string) => {
return await prisma.project.delete({
where: { code },
})
}

25
models/settings.ts Normal file
View File

@@ -0,0 +1,25 @@
import { prisma } from "@/lib/db"
import { cache } from "react"
export type SettingsMap = Record<string, string>
export const getSettings = cache(async (): Promise<SettingsMap> => {
const settings = await prisma.setting.findMany()
return settings.reduce((acc, setting) => {
acc[setting.code] = setting.value || ""
return acc
}, {} as SettingsMap)
})
export const updateSettings = cache(async (code: string, value?: any) => {
console.log("updateSettings", code, value)
return await prisma.setting.upsert({
where: { code },
update: { value },
create: {
code,
value,
name: code,
},
})
})

79
models/stats.ts Normal file
View File

@@ -0,0 +1,79 @@
import { prisma } from "@/lib/db"
import { calcTotalPerCurrency } from "@/lib/stats"
import { Prisma } from "@prisma/client"
import { cache } from "react"
import { TransactionFilters } from "./transactions"
export type DashboardStats = {
totalIncomePerCurrency: Record<string, number>
totalExpensesPerCurrency: Record<string, number>
profitPerCurrency: Record<string, number>
invoicesProcessed: number
}
export const getDashboardStats = cache(async (filters: TransactionFilters = {}): Promise<DashboardStats> => {
const where: Prisma.TransactionWhereInput = {}
if (filters.dateFrom || filters.dateTo) {
where.issuedAt = {
gte: filters.dateFrom ? new Date(filters.dateFrom) : undefined,
lte: filters.dateTo ? new Date(filters.dateTo) : undefined,
}
}
const transactions = await prisma.transaction.findMany({ where })
const totalIncomePerCurrency = calcTotalPerCurrency(transactions.filter((t) => t.type === "income"))
const totalExpensesPerCurrency = calcTotalPerCurrency(transactions.filter((t) => t.type === "expense"))
const profitPerCurrency = Object.fromEntries(
Object.keys(totalIncomePerCurrency).map((currency) => [
currency,
totalIncomePerCurrency[currency] - totalExpensesPerCurrency[currency],
])
)
const invoicesProcessed = transactions.length
return {
totalIncomePerCurrency,
totalExpensesPerCurrency,
profitPerCurrency,
invoicesProcessed,
}
})
export type ProjectStats = {
totalIncomePerCurrency: Record<string, number>
totalExpensesPerCurrency: Record<string, number>
profitPerCurrency: Record<string, number>
invoicesProcessed: number
}
export const getProjectStats = cache(async (projectId: string, filters: TransactionFilters = {}) => {
const where: Prisma.TransactionWhereInput = {
projectCode: projectId,
}
if (filters.dateFrom || filters.dateTo) {
where.issuedAt = {
gte: filters.dateFrom ? new Date(filters.dateFrom) : undefined,
lte: filters.dateTo ? new Date(filters.dateTo) : undefined,
}
}
const transactions = await prisma.transaction.findMany({ where })
const totalIncomePerCurrency = calcTotalPerCurrency(transactions.filter((t) => t.type === "income"))
const totalExpensesPerCurrency = calcTotalPerCurrency(transactions.filter((t) => t.type === "expense"))
const profitPerCurrency = Object.fromEntries(
Object.keys(totalIncomePerCurrency).map((currency) => [
currency,
totalIncomePerCurrency[currency] - totalExpensesPerCurrency[currency],
])
)
const invoicesProcessed = transactions.length
return {
totalIncomePerCurrency,
totalExpensesPerCurrency,
profitPerCurrency,
invoicesProcessed,
}
})

153
models/transactions.ts Normal file
View File

@@ -0,0 +1,153 @@
import { prisma } from "@/lib/db"
import { Field, Prisma, Transaction } from "@prisma/client"
import { cache } from "react"
import { getFields } from "./fields"
import { deleteFile } from "./files"
export type TransactionData = {
[key: string]: unknown
}
export type TransactionFilters = {
search?: string
dateFrom?: string
dateTo?: string
ordering?: string
categoryCode?: string
projectCode?: string
}
export const getTransactions = cache(async (filters?: TransactionFilters): Promise<Transaction[]> => {
const where: Prisma.TransactionWhereInput = {}
let orderBy: Prisma.TransactionOrderByWithRelationInput = { issuedAt: "desc" }
if (filters) {
if (filters.search) {
where.OR = [
{ name: { contains: filters.search } },
{ merchant: { contains: filters.search } },
{ description: { contains: filters.search } },
{ note: { contains: filters.search } },
{ text: { contains: filters.search } },
]
}
if (filters.dateFrom || filters.dateTo) {
where.issuedAt = {
gte: filters.dateFrom ? new Date(filters.dateFrom) : undefined,
lte: filters.dateTo ? new Date(filters.dateTo) : undefined,
}
}
if (filters.categoryCode) {
where.categoryCode = filters.categoryCode
}
if (filters.projectCode) {
where.projectCode = filters.projectCode
}
if (filters.ordering) {
const isDesc = filters.ordering.startsWith("-")
const field = isDesc ? filters.ordering.slice(1) : filters.ordering
orderBy = { [field]: isDesc ? "desc" : "asc" }
}
}
return await prisma.transaction.findMany({
where,
include: {
category: true,
project: true,
},
orderBy,
})
})
export const getTransactionById = cache(async (id: string): Promise<Transaction | null> => {
return await prisma.transaction.findUnique({
where: { id },
include: {
category: true,
project: true,
},
})
})
export const createTransaction = async (data: TransactionData): Promise<Transaction> => {
const { standard, extra } = await splitTransactionDataExtraFields(data)
return await prisma.transaction.create({
data: {
...standard,
extra: extra,
},
})
}
export const updateTransaction = async (id: string, data: TransactionData): Promise<Transaction> => {
const { standard, extra } = await splitTransactionDataExtraFields(data)
return await prisma.transaction.update({
where: { id },
data: {
...standard,
extra: extra,
},
})
}
export const updateTransactionFiles = async (id: string, files: string[]): Promise<Transaction> => {
return await prisma.transaction.update({
where: { id },
data: { files },
})
}
export const deleteTransaction = async (id: string): Promise<Transaction | undefined> => {
const transaction = await getTransactionById(id)
if (transaction) {
const files = Array.isArray(transaction.files) ? transaction.files : []
for (const fileId of files as string[]) {
await deleteFile(fileId)
}
return await prisma.transaction.delete({
where: { id },
})
}
}
export const bulkDeleteTransactions = async (ids: string[]) => {
return await prisma.transaction.deleteMany({
where: { id: { in: ids } },
})
}
const splitTransactionDataExtraFields = async (
data: TransactionData
): Promise<{ standard: TransactionData; extra: Prisma.InputJsonValue }> => {
const fields = await getFields()
const fieldMap = fields.reduce((acc, field) => {
acc[field.code] = field
return acc
}, {} as Record<string, Field>)
const standard: Omit<Partial<Transaction>, "extra"> = {}
const extra: Record<string, unknown> = {}
Object.entries(data).forEach(([key, value]) => {
const fieldDef = fieldMap[key]
if (fieldDef) {
if (fieldDef.isExtra) {
extra[key] = value
} else {
standard[key as keyof Omit<Transaction, "extra">] = value as any
}
}
})
return { standard, extra: extra as Prisma.InputJsonValue }
}