mirror of
https://github.com/marcogll/TaxHacker_s23.git
synced 2026-01-13 13:25:18 +00:00
fix: rename data -> models
This commit is contained in:
40
models/categories.ts
Normal file
40
models/categories.ts
Normal 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
30
models/currencies.ts
Normal 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
155
models/export_and_import.ts
Normal 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
30
models/fields.ts
Normal 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
79
models/files.ts
Normal 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
40
models/projects.ts
Normal 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
25
models/settings.ts
Normal 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
79
models/stats.ts
Normal 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
153
models/transactions.ts
Normal 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 }
|
||||
}
|
||||
Reference in New Issue
Block a user