Files
beenvoice-web/src/server/api/routers/invoiceTemplates.ts
T
soconnor 0e46fdafb2 feat: add administration page and account role management
- Implemented `AdministrationContent` component for managing account roles.
- Created `AdministrationPage` to serve as the main entry point for administration tasks.
- Added PDF preview functionality with `PdfPreviewFrame` component for invoice generation.
- Introduced `InputColor` component for advanced color selection with various formats.
- Established color conversion utilities in `color-converter.ts` for handling color formats.
- Defined appearance-related schemas and types in `appearance.ts` for consistent theme management.
2026-04-30 10:50:50 -04:00

127 lines
3.6 KiB
TypeScript

import { z } from "zod";
import { eq, and } from "drizzle-orm";
import { createTRPCRouter, protectedProcedure } from "../trpc";
import { invoiceTemplates } from "~/server/db/schema";
import { TRPCError } from "@trpc/server";
const createTemplateSchema = z.object({
name: z.string().min(1, "Name is required").max(255),
type: z.enum(["notes", "terms"]).default("notes"),
content: z.string().min(1, "Content is required"),
isDefault: z.boolean().default(false),
});
const updateTemplateSchema = createTemplateSchema.partial().extend({
id: z.string(),
});
export const invoiceTemplatesRouter = createTRPCRouter({
getAll: protectedProcedure.query(async ({ ctx }) => {
return await ctx.db.query.invoiceTemplates.findMany({
where: eq(invoiceTemplates.createdById, ctx.session.user.id),
orderBy: (t, { asc }) => [asc(t.type), asc(t.name)],
});
}),
getByType: protectedProcedure
.input(z.object({ type: z.enum(["notes", "terms"]) }))
.query(async ({ ctx, input }) => {
return await ctx.db.query.invoiceTemplates.findMany({
where: and(
eq(invoiceTemplates.createdById, ctx.session.user.id),
eq(invoiceTemplates.type, input.type),
),
orderBy: (t, { asc }) => [asc(t.name)],
});
}),
create: protectedProcedure
.input(createTemplateSchema)
.mutation(async ({ ctx, input }) => {
// If setting as default, unset others of same type
if (input.isDefault) {
await ctx.db
.update(invoiceTemplates)
.set({ isDefault: false })
.where(
and(
eq(invoiceTemplates.createdById, ctx.session.user.id),
eq(invoiceTemplates.type, input.type),
),
);
}
const [template] = await ctx.db
.insert(invoiceTemplates)
.values({ ...input, createdById: ctx.session.user.id })
.returning();
return template;
}),
update: protectedProcedure
.input(updateTemplateSchema)
.mutation(async ({ ctx, input }) => {
const { id, ...data } = input;
const existing = await ctx.db.query.invoiceTemplates.findFirst({
where: and(
eq(invoiceTemplates.id, id),
eq(invoiceTemplates.createdById, ctx.session.user.id),
),
});
if (!existing) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Template not found",
});
}
// If setting as default, unset others of same type
if (data.isDefault) {
const type = data.type ?? existing.type;
await ctx.db
.update(invoiceTemplates)
.set({ isDefault: false })
.where(
and(
eq(invoiceTemplates.createdById, ctx.session.user.id),
eq(invoiceTemplates.type, type),
),
);
}
await ctx.db
.update(invoiceTemplates)
.set({ ...data, updatedAt: new Date() })
.where(eq(invoiceTemplates.id, id));
return { success: true };
}),
delete: protectedProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ ctx, input }) => {
const existing = await ctx.db.query.invoiceTemplates.findFirst({
where: and(
eq(invoiceTemplates.id, input.id),
eq(invoiceTemplates.createdById, ctx.session.user.id),
),
});
if (!existing) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Template not found",
});
}
await ctx.db
.delete(invoiceTemplates)
.where(eq(invoiceTemplates.id, input.id));
return { success: true };
}),
});