admin services
This commit is contained in:
@@ -14,6 +14,7 @@ import { paths } from "../../config/paths.js";
|
||||
import { logAuditEvent } from "../../lib/audit.js";
|
||||
import { hashPassword } from "../../lib/password.js";
|
||||
import { prisma } from "../../lib/prisma.js";
|
||||
import { getLatestStartupReport } from "../../lib/startup-state.js";
|
||||
|
||||
function mapAuditEvent(record: {
|
||||
id: string;
|
||||
@@ -464,6 +465,7 @@ export async function updateAdminUser(userId: string, payload: AdminUserInput, a
|
||||
}
|
||||
|
||||
export async function getAdminDiagnostics(): Promise<AdminDiagnosticsDto> {
|
||||
const startupReport = getLatestStartupReport();
|
||||
const [
|
||||
companyProfile,
|
||||
userCount,
|
||||
@@ -540,6 +542,8 @@ export async function getAdminDiagnostics(): Promise<AdminDiagnosticsDto> {
|
||||
shipmentCount,
|
||||
attachmentCount,
|
||||
auditEventCount,
|
||||
startupStatus: startupReport.status,
|
||||
startupChecks: startupReport.checks,
|
||||
recentAuditEvents: recentAuditEvents.map(mapAuditEvent),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ crmRouter.post("/customers", requirePermissions([permissions.crmWrite]), async (
|
||||
return fail(response, 400, "INVALID_INPUT", "Customer payload is invalid.");
|
||||
}
|
||||
|
||||
const customer = await createCustomer(parsed.data);
|
||||
const customer = await createCustomer(parsed.data, request.authUser?.id);
|
||||
if (!customer) {
|
||||
return fail(response, 400, "INVALID_INPUT", "Customer reseller relationship is invalid.");
|
||||
}
|
||||
@@ -143,7 +143,7 @@ crmRouter.put("/customers/:customerId", requirePermissions([permissions.crmWrite
|
||||
return fail(response, 404, "CRM_CUSTOMER_NOT_FOUND", "Customer record was not found.");
|
||||
}
|
||||
|
||||
const customer = await updateCustomer(customerId, parsed.data);
|
||||
const customer = await updateCustomer(customerId, parsed.data, request.authUser?.id);
|
||||
if (!customer) {
|
||||
return fail(response, 400, "INVALID_INPUT", "Customer reseller relationship is invalid.");
|
||||
}
|
||||
@@ -181,7 +181,7 @@ crmRouter.post("/customers/:customerId/contacts", requirePermissions([permission
|
||||
return fail(response, 400, "INVALID_INPUT", "CRM contact is invalid.");
|
||||
}
|
||||
|
||||
const contact = await createCustomerContact(customerId, parsed.data);
|
||||
const contact = await createCustomerContact(customerId, parsed.data, request.authUser?.id);
|
||||
if (!contact) {
|
||||
return fail(response, 404, "CRM_CUSTOMER_NOT_FOUND", "Customer record was not found.");
|
||||
}
|
||||
@@ -227,7 +227,7 @@ crmRouter.post("/vendors", requirePermissions([permissions.crmWrite]), async (re
|
||||
return fail(response, 400, "INVALID_INPUT", "Vendor payload is invalid.");
|
||||
}
|
||||
|
||||
return ok(response, await createVendor(parsed.data), 201);
|
||||
return ok(response, await createVendor(parsed.data, request.authUser?.id), 201);
|
||||
});
|
||||
|
||||
crmRouter.put("/vendors/:vendorId", requirePermissions([permissions.crmWrite]), async (request, response) => {
|
||||
@@ -241,7 +241,7 @@ crmRouter.put("/vendors/:vendorId", requirePermissions([permissions.crmWrite]),
|
||||
return fail(response, 400, "INVALID_INPUT", "Vendor payload is invalid.");
|
||||
}
|
||||
|
||||
const vendor = await updateVendor(vendorId, parsed.data);
|
||||
const vendor = await updateVendor(vendorId, parsed.data, request.authUser?.id);
|
||||
if (!vendor) {
|
||||
return fail(response, 404, "CRM_VENDOR_NOT_FOUND", "Vendor record was not found.");
|
||||
}
|
||||
@@ -279,7 +279,7 @@ crmRouter.post("/vendors/:vendorId/contacts", requirePermissions([permissions.cr
|
||||
return fail(response, 400, "INVALID_INPUT", "CRM contact is invalid.");
|
||||
}
|
||||
|
||||
const contact = await createVendorContact(vendorId, parsed.data);
|
||||
const contact = await createVendorContact(vendorId, parsed.data, request.authUser?.id);
|
||||
if (!contact) {
|
||||
return fail(response, 404, "CRM_VENDOR_NOT_FOUND", "Vendor record was not found.");
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
} from "@mrp/shared/dist/crm/types.js";
|
||||
import type { Customer, Vendor } from "@prisma/client";
|
||||
|
||||
import { logAuditEvent } from "../../lib/audit.js";
|
||||
import { prisma } from "../../lib/prisma.js";
|
||||
|
||||
function mapSummary(record: Customer | Vendor): CrmRecordSummaryDto {
|
||||
@@ -397,7 +398,7 @@ export async function getCustomerById(customerId: string) {
|
||||
return mapCustomerDetail(customer, attachmentCount);
|
||||
}
|
||||
|
||||
export async function createCustomer(payload: CrmRecordInput) {
|
||||
export async function createCustomer(payload: CrmRecordInput, actorId?: string | null) {
|
||||
if (payload.parentCustomerId) {
|
||||
const parentCustomer = await prisma.customer.findUnique({
|
||||
where: { id: payload.parentCustomerId },
|
||||
@@ -436,6 +437,20 @@ export async function createCustomer(payload: CrmRecordInput) {
|
||||
},
|
||||
});
|
||||
|
||||
await logAuditEvent({
|
||||
actorId,
|
||||
entityType: "crm-customer",
|
||||
entityId: customer.id,
|
||||
action: "created",
|
||||
summary: `Created customer ${customer.name}.`,
|
||||
metadata: {
|
||||
name: customer.name,
|
||||
status: customer.status,
|
||||
lifecycleStage: customer.lifecycleStage,
|
||||
isReseller: customer.isReseller,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
...mapDetail(customer),
|
||||
isReseller: customer.isReseller,
|
||||
@@ -463,7 +478,7 @@ export async function createCustomer(payload: CrmRecordInput) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function updateCustomer(customerId: string, payload: CrmRecordInput) {
|
||||
export async function updateCustomer(customerId: string, payload: CrmRecordInput, actorId?: string | null) {
|
||||
const existingCustomer = await prisma.customer.findUnique({
|
||||
where: { id: customerId },
|
||||
});
|
||||
@@ -515,6 +530,20 @@ export async function updateCustomer(customerId: string, payload: CrmRecordInput
|
||||
},
|
||||
});
|
||||
|
||||
await logAuditEvent({
|
||||
actorId,
|
||||
entityType: "crm-customer",
|
||||
entityId: customer.id,
|
||||
action: "updated",
|
||||
summary: `Updated customer ${customer.name}.`,
|
||||
metadata: {
|
||||
name: customer.name,
|
||||
status: customer.status,
|
||||
lifecycleStage: customer.lifecycleStage,
|
||||
isReseller: customer.isReseller,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
...mapDetail(customer),
|
||||
isReseller: customer.isReseller,
|
||||
@@ -630,7 +659,7 @@ export async function getVendorById(vendorId: string) {
|
||||
return mapVendorDetail(vendor, attachmentCount);
|
||||
}
|
||||
|
||||
export async function createVendor(payload: CrmRecordInput) {
|
||||
export async function createVendor(payload: CrmRecordInput, actorId?: string | null) {
|
||||
const vendor = await prisma.vendor.create({
|
||||
data: {
|
||||
name: payload.name,
|
||||
@@ -656,6 +685,19 @@ export async function createVendor(payload: CrmRecordInput) {
|
||||
},
|
||||
});
|
||||
|
||||
await logAuditEvent({
|
||||
actorId,
|
||||
entityType: "crm-vendor",
|
||||
entityId: vendor.id,
|
||||
action: "created",
|
||||
summary: `Created vendor ${vendor.name}.`,
|
||||
metadata: {
|
||||
name: vendor.name,
|
||||
status: vendor.status,
|
||||
lifecycleStage: vendor.lifecycleStage,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
...mapDetail(vendor),
|
||||
paymentTerms: vendor.paymentTerms,
|
||||
@@ -677,7 +719,7 @@ export async function createVendor(payload: CrmRecordInput) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function updateVendor(vendorId: string, payload: CrmRecordInput) {
|
||||
export async function updateVendor(vendorId: string, payload: CrmRecordInput, actorId?: string | null) {
|
||||
const existingVendor = await prisma.vendor.findUnique({
|
||||
where: { id: vendorId },
|
||||
});
|
||||
@@ -712,6 +754,19 @@ export async function updateVendor(vendorId: string, payload: CrmRecordInput) {
|
||||
},
|
||||
});
|
||||
|
||||
await logAuditEvent({
|
||||
actorId,
|
||||
entityType: "crm-vendor",
|
||||
entityId: vendor.id,
|
||||
action: "updated",
|
||||
summary: `Updated vendor ${vendor.name}.`,
|
||||
metadata: {
|
||||
name: vendor.name,
|
||||
status: vendor.status,
|
||||
lifecycleStage: vendor.lifecycleStage,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
...mapDetail(vendor),
|
||||
paymentTerms: vendor.paymentTerms,
|
||||
@@ -756,6 +811,19 @@ export async function createCustomerContactEntry(customerId: string, payload: Cr
|
||||
},
|
||||
});
|
||||
|
||||
await logAuditEvent({
|
||||
actorId: createdById,
|
||||
entityType: "crm-customer",
|
||||
entityId: customerId,
|
||||
action: "contact-entry.created",
|
||||
summary: `Added ${payload.type.toLowerCase()} contact history for customer ${existingCustomer.name}.`,
|
||||
metadata: {
|
||||
type: payload.type,
|
||||
summary: payload.summary,
|
||||
contactAt: payload.contactAt,
|
||||
},
|
||||
});
|
||||
|
||||
return mapContactEntry(entry);
|
||||
}
|
||||
|
||||
@@ -782,10 +850,23 @@ export async function createVendorContactEntry(vendorId: string, payload: CrmCon
|
||||
},
|
||||
});
|
||||
|
||||
await logAuditEvent({
|
||||
actorId: createdById,
|
||||
entityType: "crm-vendor",
|
||||
entityId: vendorId,
|
||||
action: "contact-entry.created",
|
||||
summary: `Added ${payload.type.toLowerCase()} contact history for vendor ${existingVendor.name}.`,
|
||||
metadata: {
|
||||
type: payload.type,
|
||||
summary: payload.summary,
|
||||
contactAt: payload.contactAt,
|
||||
},
|
||||
});
|
||||
|
||||
return mapContactEntry(entry);
|
||||
}
|
||||
|
||||
export async function createCustomerContact(customerId: string, payload: CrmContactInput) {
|
||||
export async function createCustomerContact(customerId: string, payload: CrmContactInput, actorId?: string | null) {
|
||||
const existingCustomer = await prisma.customer.findUnique({
|
||||
where: { id: customerId },
|
||||
});
|
||||
@@ -812,10 +893,24 @@ export async function createCustomerContact(customerId: string, payload: CrmCont
|
||||
},
|
||||
});
|
||||
|
||||
await logAuditEvent({
|
||||
actorId,
|
||||
entityType: "crm-customer",
|
||||
entityId: customerId,
|
||||
action: "contact.created",
|
||||
summary: `Added contact ${contact.fullName} to customer ${existingCustomer.name}.`,
|
||||
metadata: {
|
||||
fullName: contact.fullName,
|
||||
role: contact.role,
|
||||
email: contact.email,
|
||||
isPrimary: contact.isPrimary,
|
||||
},
|
||||
});
|
||||
|
||||
return mapCrmContact(contact);
|
||||
}
|
||||
|
||||
export async function createVendorContact(vendorId: string, payload: CrmContactInput) {
|
||||
export async function createVendorContact(vendorId: string, payload: CrmContactInput, actorId?: string | null) {
|
||||
const existingVendor = await prisma.vendor.findUnique({
|
||||
where: { id: vendorId },
|
||||
});
|
||||
@@ -842,5 +937,19 @@ export async function createVendorContact(vendorId: string, payload: CrmContactI
|
||||
},
|
||||
});
|
||||
|
||||
await logAuditEvent({
|
||||
actorId,
|
||||
entityType: "crm-vendor",
|
||||
entityId: vendorId,
|
||||
action: "contact.created",
|
||||
summary: `Added contact ${contact.fullName} to vendor ${existingVendor.name}.`,
|
||||
metadata: {
|
||||
fullName: contact.fullName,
|
||||
role: contact.role,
|
||||
email: contact.email,
|
||||
isPrimary: contact.isPrimary,
|
||||
},
|
||||
});
|
||||
|
||||
return mapCrmContact(contact);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ shippingRouter.post("/shipments", requirePermissions([permissions.shippingWrite]
|
||||
return fail(response, 400, "INVALID_INPUT", "Shipment payload is invalid.");
|
||||
}
|
||||
|
||||
const result = await createShipment(parsed.data);
|
||||
const result = await createShipment(parsed.data, request.authUser?.id);
|
||||
if (!result.ok) {
|
||||
return fail(response, 400, "INVALID_INPUT", result.reason);
|
||||
}
|
||||
@@ -86,7 +86,7 @@ shippingRouter.put("/shipments/:shipmentId", requirePermissions([permissions.shi
|
||||
return fail(response, 400, "INVALID_INPUT", "Shipment payload is invalid.");
|
||||
}
|
||||
|
||||
const result = await updateShipment(shipmentId, parsed.data);
|
||||
const result = await updateShipment(shipmentId, parsed.data, request.authUser?.id);
|
||||
if (!result.ok) {
|
||||
return fail(response, 400, "INVALID_INPUT", result.reason);
|
||||
}
|
||||
@@ -105,7 +105,7 @@ shippingRouter.patch("/shipments/:shipmentId/status", requirePermissions([permis
|
||||
return fail(response, 400, "INVALID_INPUT", "Shipment status payload is invalid.");
|
||||
}
|
||||
|
||||
const result = await updateShipmentStatus(shipmentId, parsed.data.status);
|
||||
const result = await updateShipmentStatus(shipmentId, parsed.data.status, request.authUser?.id);
|
||||
if (!result.ok) {
|
||||
return fail(response, 400, "INVALID_INPUT", result.reason);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
ShipmentSummaryDto,
|
||||
} from "@mrp/shared/dist/shipping/types.js";
|
||||
|
||||
import { logAuditEvent } from "../../lib/audit.js";
|
||||
import { prisma } from "../../lib/prisma.js";
|
||||
|
||||
export interface ShipmentPackingSlipData {
|
||||
@@ -168,7 +169,7 @@ export async function getShipmentById(shipmentId: string) {
|
||||
return shipment ? mapShipment(shipment) : null;
|
||||
}
|
||||
|
||||
export async function createShipment(payload: ShipmentInput) {
|
||||
export async function createShipment(payload: ShipmentInput, actorId?: string | null) {
|
||||
const order = await prisma.salesOrder.findUnique({
|
||||
where: { id: payload.salesOrderId },
|
||||
select: { id: true },
|
||||
@@ -195,10 +196,25 @@ export async function createShipment(payload: ShipmentInput) {
|
||||
});
|
||||
|
||||
const detail = await getShipmentById(shipment.id);
|
||||
if (detail) {
|
||||
await logAuditEvent({
|
||||
actorId,
|
||||
entityType: "shipment",
|
||||
entityId: shipment.id,
|
||||
action: "created",
|
||||
summary: `Created shipment ${detail.shipmentNumber}.`,
|
||||
metadata: {
|
||||
shipmentNumber: detail.shipmentNumber,
|
||||
salesOrderId: detail.salesOrderId,
|
||||
status: detail.status,
|
||||
carrier: detail.carrier,
|
||||
},
|
||||
});
|
||||
}
|
||||
return detail ? { ok: true as const, shipment: detail } : { ok: false as const, reason: "Unable to load saved shipment." };
|
||||
}
|
||||
|
||||
export async function updateShipment(shipmentId: string, payload: ShipmentInput) {
|
||||
export async function updateShipment(shipmentId: string, payload: ShipmentInput, actorId?: string | null) {
|
||||
const existing = await prisma.shipment.findUnique({
|
||||
where: { id: shipmentId },
|
||||
select: { id: true },
|
||||
@@ -233,10 +249,25 @@ export async function updateShipment(shipmentId: string, payload: ShipmentInput)
|
||||
});
|
||||
|
||||
const detail = await getShipmentById(shipmentId);
|
||||
if (detail) {
|
||||
await logAuditEvent({
|
||||
actorId,
|
||||
entityType: "shipment",
|
||||
entityId: shipmentId,
|
||||
action: "updated",
|
||||
summary: `Updated shipment ${detail.shipmentNumber}.`,
|
||||
metadata: {
|
||||
shipmentNumber: detail.shipmentNumber,
|
||||
salesOrderId: detail.salesOrderId,
|
||||
status: detail.status,
|
||||
carrier: detail.carrier,
|
||||
},
|
||||
});
|
||||
}
|
||||
return detail ? { ok: true as const, shipment: detail } : { ok: false as const, reason: "Unable to load saved shipment." };
|
||||
}
|
||||
|
||||
export async function updateShipmentStatus(shipmentId: string, status: ShipmentStatus) {
|
||||
export async function updateShipmentStatus(shipmentId: string, status: ShipmentStatus, actorId?: string | null) {
|
||||
const existing = await prisma.shipment.findUnique({
|
||||
where: { id: shipmentId },
|
||||
select: { id: true },
|
||||
@@ -253,6 +284,19 @@ export async function updateShipmentStatus(shipmentId: string, status: ShipmentS
|
||||
});
|
||||
|
||||
const detail = await getShipmentById(shipmentId);
|
||||
if (detail) {
|
||||
await logAuditEvent({
|
||||
actorId,
|
||||
entityType: "shipment",
|
||||
entityId: shipmentId,
|
||||
action: "status.updated",
|
||||
summary: `Updated shipment ${detail.shipmentNumber} to ${status}.`,
|
||||
metadata: {
|
||||
shipmentNumber: detail.shipmentNumber,
|
||||
status,
|
||||
},
|
||||
});
|
||||
}
|
||||
return detail ? { ok: true as const, shipment: detail } : { ok: false as const, reason: "Unable to load updated shipment." };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user