Compare commits
3 Commits
d892621e9b
...
85441c0dfe
| Author | SHA1 | Date | |
|---|---|---|---|
| 85441c0dfe | |||
| 85223bc3b2 | |||
| 193bce0cc9 |
@@ -1,4 +1,5 @@
|
|||||||
import { fail } from '@sveltejs/kit';
|
import { fail } from '@sveltejs/kit';
|
||||||
|
import type { BatchItem } from 'drizzle-orm/batch';
|
||||||
import { and, asc, desc, eq, gte, inArray, isNull, like, lte, ne } from 'drizzle-orm';
|
import { and, asc, desc, eq, gte, inArray, isNull, like, lte, ne } from 'drizzle-orm';
|
||||||
import { createSearchParamsSchema, validateSearchParams } from 'runed/kit';
|
import { createSearchParamsSchema, validateSearchParams } from 'runed/kit';
|
||||||
import { db } from '$lib/server/db';
|
import { db } from '$lib/server/db';
|
||||||
@@ -458,14 +459,17 @@ export const actions: Actions = {
|
|||||||
const invoicePrefix = invoiceNumberPrefix(invoiceDate);
|
const invoicePrefix = invoiceNumberPrefix(invoiceDate);
|
||||||
const latestSequence = await latestInvoiceSequence(activeOrganizationId, invoicePrefix);
|
const latestSequence = await latestInvoiceSequence(activeOrganizationId, invoicePrefix);
|
||||||
|
|
||||||
await db.transaction(async (tx) => {
|
const now = new Date();
|
||||||
|
const queries: BatchItem<'sqlite'>[] = [];
|
||||||
let invoiceOffset = 0;
|
let invoiceOffset = 0;
|
||||||
|
|
||||||
for (const client of groupedClients) {
|
for (const client of groupedClients) {
|
||||||
const invoiceId = crypto.randomUUID();
|
const invoiceId = crypto.randomUUID();
|
||||||
const invoiceNumber = `${invoicePrefix}-${String(latestSequence + invoiceOffset + 1).padStart(3, '0')}`;
|
const invoiceNumber = `${invoicePrefix}-${String(latestSequence + invoiceOffset + 1).padStart(3, '0')}`;
|
||||||
invoiceOffset += 1;
|
invoiceOffset += 1;
|
||||||
|
|
||||||
await tx.insert(invoices).values({
|
queries.push(
|
||||||
|
db.insert(invoices).values({
|
||||||
id: invoiceId,
|
id: invoiceId,
|
||||||
organizationId: activeOrganizationId,
|
organizationId: activeOrganizationId,
|
||||||
clientId: client.clientId,
|
clientId: client.clientId,
|
||||||
@@ -476,11 +480,13 @@ export const actions: Actions = {
|
|||||||
taxGbp: 0,
|
taxGbp: 0,
|
||||||
totalGbp: client.totalGbp,
|
totalGbp: client.totalGbp,
|
||||||
notes: `Generated from billing run. Contract billing through ${preview.contractsCutoff}; services through ${preview.servicesCutoff}.`,
|
notes: `Generated from billing run. Contract billing through ${preview.contractsCutoff}; services through ${preview.servicesCutoff}.`,
|
||||||
createdAt: new Date(),
|
createdAt: now,
|
||||||
updatedAt: new Date()
|
updatedAt: now
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
await tx.insert(invoiceLines).values(
|
queries.push(
|
||||||
|
db.insert(invoiceLines).values(
|
||||||
client.lines.map((line) => ({
|
client.lines.map((line) => ({
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
organizationId: activeOrganizationId,
|
organizationId: activeOrganizationId,
|
||||||
@@ -494,9 +500,10 @@ export const actions: Actions = {
|
|||||||
quantity: line.quantity,
|
quantity: line.quantity,
|
||||||
unitPriceGbp: line.unitPriceGbp,
|
unitPriceGbp: line.unitPriceGbp,
|
||||||
totalGbp: line.totalGbp,
|
totalGbp: line.totalGbp,
|
||||||
createdAt: new Date(),
|
createdAt: now,
|
||||||
updatedAt: new Date()
|
updatedAt: now
|
||||||
}))
|
}))
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,19 +511,24 @@ export const actions: Actions = {
|
|||||||
for (const line of includedLines) {
|
for (const line of includedLines) {
|
||||||
if (line.sourceType !== 'contract') continue;
|
if (line.sourceType !== 'contract') continue;
|
||||||
const current = contractBilledTo.get(line.sourceId);
|
const current = contractBilledTo.get(line.sourceId);
|
||||||
if (!current || line.periodEnd > current)
|
if (!current || line.periodEnd > current) contractBilledTo.set(line.sourceId, line.periodEnd);
|
||||||
contractBilledTo.set(line.sourceId, line.periodEnd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [contractId, billedTo] of contractBilledTo) {
|
for (const [contractId, billedTo] of contractBilledTo) {
|
||||||
await tx
|
queries.push(
|
||||||
|
db
|
||||||
.update(contracts)
|
.update(contracts)
|
||||||
.set({ billedTo, updatedAt: new Date() })
|
.set({ billedTo, updatedAt: now })
|
||||||
.where(
|
.where(
|
||||||
and(eq(contracts.id, contractId), eq(contracts.organizationId, activeOrganizationId))
|
and(eq(contracts.id, contractId), eq(contracts.organizationId, activeOrganizationId))
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
const [firstQuery, ...remainingQueries] = queries;
|
||||||
|
if (firstQuery) {
|
||||||
|
await db.batch([firstQuery, ...remainingQueries]);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: `Billing run complete. Created ${groupedClients.length} invoice${groupedClients.length === 1 ? '' : 's'}.`
|
message: `Billing run complete. Created ${groupedClients.length} invoice${groupedClients.length === 1 ? '' : 's'}.`
|
||||||
|
|||||||
@@ -157,8 +157,8 @@ export const actions: Actions = {
|
|||||||
if (contact.isPrimary) return message(form, 'Contact is already primary.');
|
if (contact.isPrimary) return message(form, 'Contact is already primary.');
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
await db.transaction(async (tx) => {
|
await db.batch([
|
||||||
await tx
|
db
|
||||||
.update(contacts)
|
.update(contacts)
|
||||||
.set({ isPrimary: false, updatedAt: now })
|
.set({ isPrimary: false, updatedAt: now })
|
||||||
.where(
|
.where(
|
||||||
@@ -168,9 +168,9 @@ export const actions: Actions = {
|
|||||||
eq(contacts.isPrimary, true),
|
eq(contacts.isPrimary, true),
|
||||||
isNull(contacts.archivedAt)
|
isNull(contacts.archivedAt)
|
||||||
)
|
)
|
||||||
);
|
),
|
||||||
|
|
||||||
await tx
|
db
|
||||||
.update(contacts)
|
.update(contacts)
|
||||||
.set({ isPrimary: true, updatedAt: now })
|
.set({ isPrimary: true, updatedAt: now })
|
||||||
.where(
|
.where(
|
||||||
@@ -180,8 +180,8 @@ export const actions: Actions = {
|
|||||||
eq(contacts.organizationId, activeOrganizationId),
|
eq(contacts.organizationId, activeOrganizationId),
|
||||||
isNull(contacts.archivedAt)
|
isNull(contacts.archivedAt)
|
||||||
)
|
)
|
||||||
);
|
)
|
||||||
});
|
]);
|
||||||
|
|
||||||
return message(form, 'Primary contact updated.');
|
return message(form, 'Primary contact updated.');
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -213,8 +213,8 @@ export const actions: Actions = {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const contractId = crypto.randomUUID();
|
const contractId = crypto.randomUUID();
|
||||||
await db.transaction(async (tx) => {
|
const now = new Date();
|
||||||
await tx.insert(contracts).values({
|
const createContract = db.insert(contracts).values({
|
||||||
id: contractId,
|
id: contractId,
|
||||||
organizationId: activeOrganizationId,
|
organizationId: activeOrganizationId,
|
||||||
clientId: form.data.clientId,
|
clientId: form.data.clientId,
|
||||||
@@ -224,21 +224,26 @@ export const actions: Actions = {
|
|||||||
startDate: form.data.startDate,
|
startDate: form.data.startDate,
|
||||||
endDate: form.data.endDate,
|
endDate: form.data.endDate,
|
||||||
status: 'draft',
|
status: 'draft',
|
||||||
updatedAt: new Date(),
|
updatedAt: now,
|
||||||
createdAt: new Date()
|
createdAt: now
|
||||||
});
|
});
|
||||||
|
|
||||||
const uniqueRoomIds = [...new Set(form.data.roomIds)];
|
const uniqueRoomIds = [...new Set(form.data.roomIds)];
|
||||||
if (uniqueRoomIds.length > 0) {
|
if (uniqueRoomIds.length > 0) {
|
||||||
await tx.insert(contractRooms).values(
|
await db.batch([
|
||||||
|
createContract,
|
||||||
|
db.insert(contractRooms).values(
|
||||||
uniqueRoomIds.map((roomId) => ({
|
uniqueRoomIds.map((roomId) => ({
|
||||||
contractId,
|
contractId,
|
||||||
roomId
|
roomId
|
||||||
}))
|
}))
|
||||||
);
|
)
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
await createContract;
|
||||||
}
|
}
|
||||||
});
|
} catch (cause) {
|
||||||
} catch {
|
console.error('Unable to create contract', cause);
|
||||||
return message(form, 'Unable to create contract.', { status: 400 });
|
return message(form, 'Unable to create contract.', { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,8 +300,8 @@ export const actions: Actions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.transaction(async (tx) => {
|
const now = new Date();
|
||||||
await tx
|
const updateContract = db
|
||||||
.update(contracts)
|
.update(contracts)
|
||||||
.set({
|
.set({
|
||||||
clientId: form.data.clientId,
|
clientId: form.data.clientId,
|
||||||
@@ -305,7 +310,7 @@ export const actions: Actions = {
|
|||||||
depositGbp: form.data.depositGbp,
|
depositGbp: form.data.depositGbp,
|
||||||
startDate: form.data.startDate,
|
startDate: form.data.startDate,
|
||||||
endDate: form.data.endDate,
|
endDate: form.data.endDate,
|
||||||
updatedAt: new Date()
|
updatedAt: now
|
||||||
})
|
})
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
@@ -315,19 +320,27 @@ export const actions: Actions = {
|
|||||||
isNull(contracts.archivedAt)
|
isNull(contracts.archivedAt)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
const deleteRoomLinks = db
|
||||||
await tx.delete(contractRooms).where(eq(contractRooms.contractId, form.data.id));
|
.delete(contractRooms)
|
||||||
|
.where(eq(contractRooms.contractId, form.data.id));
|
||||||
const uniqueRoomIds = [...new Set(form.data.roomIds)];
|
const uniqueRoomIds = [...new Set(form.data.roomIds)];
|
||||||
|
|
||||||
if (uniqueRoomIds.length > 0) {
|
if (uniqueRoomIds.length > 0) {
|
||||||
await tx.insert(contractRooms).values(
|
await db.batch([
|
||||||
|
updateContract,
|
||||||
|
deleteRoomLinks,
|
||||||
|
db.insert(contractRooms).values(
|
||||||
uniqueRoomIds.map((roomId) => ({
|
uniqueRoomIds.map((roomId) => ({
|
||||||
contractId: form.data.id,
|
contractId: form.data.id,
|
||||||
roomId
|
roomId
|
||||||
}))
|
}))
|
||||||
);
|
)
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
await db.batch([updateContract, deleteRoomLinks]);
|
||||||
}
|
}
|
||||||
});
|
} catch (cause) {
|
||||||
} catch {
|
console.error('Unable to update contract', cause);
|
||||||
return message(form, 'Unable to update contract.', { status: 400 });
|
return message(form, 'Unable to update contract.', { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ export const POST: RequestHandler = async ({ locals, request }) => {
|
|||||||
const id = crypto.randomUUID();
|
const id = crypto.randomUUID();
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
await db.transaction(async (tx) => {
|
await db.batch([
|
||||||
await tx.insert(organizations).values({
|
db.insert(organizations).values({
|
||||||
id,
|
id,
|
||||||
name: parsed.data.name,
|
name: parsed.data.name,
|
||||||
addressLine1: parsed.data.addressLine1,
|
addressLine1: parsed.data.addressLine1,
|
||||||
@@ -43,16 +43,16 @@ export const POST: RequestHandler = async ({ locals, request }) => {
|
|||||||
bankSwift: parsed.data.bankSwift || null,
|
bankSwift: parsed.data.bankSwift || null,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now
|
updatedAt: now
|
||||||
});
|
}),
|
||||||
|
|
||||||
await tx.insert(organizationMemberships).values({
|
db.insert(organizationMemberships).values({
|
||||||
organizationId: id,
|
organizationId: id,
|
||||||
userId: locals.user.id,
|
userId: locals.user.id,
|
||||||
role: 'admin',
|
role: 'admin',
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now
|
updatedAt: now
|
||||||
});
|
})
|
||||||
});
|
]);
|
||||||
|
|
||||||
await setActiveOrganization(locals, id);
|
await setActiveOrganization(locals, id);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user