crm4
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { permissions } from "@mrp/shared";
|
||||
import { crmContactEntryTypes, crmRecordStatuses } from "@mrp/shared/dist/crm/types.js";
|
||||
import { crmContactEntryTypes, crmContactRoles, crmRecordStatuses } from "@mrp/shared/dist/crm/types.js";
|
||||
import { Router } from "express";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -7,12 +7,15 @@ import { fail, ok } from "../../lib/http.js";
|
||||
import { requirePermissions } from "../../lib/rbac.js";
|
||||
import {
|
||||
createCustomerContactEntry,
|
||||
createCustomerContact,
|
||||
createCustomer,
|
||||
createVendorContactEntry,
|
||||
createVendorContact,
|
||||
createVendor,
|
||||
getCustomerById,
|
||||
getVendorById,
|
||||
listCustomers,
|
||||
listCustomerHierarchyOptions,
|
||||
listVendors,
|
||||
updateCustomer,
|
||||
updateVendor,
|
||||
@@ -30,6 +33,13 @@ const crmRecordSchema = z.object({
|
||||
country: z.string().trim().min(1),
|
||||
status: z.enum(crmRecordStatuses),
|
||||
notes: z.string(),
|
||||
isReseller: z.boolean().optional(),
|
||||
resellerDiscountPercent: z.number().min(0).max(100).nullable().optional(),
|
||||
parentCustomerId: z.string().nullable().optional(),
|
||||
paymentTerms: z.string().nullable().optional(),
|
||||
currencyCode: z.string().max(8).nullable().optional(),
|
||||
taxExempt: z.boolean().optional(),
|
||||
creditHold: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const crmListQuerySchema = z.object({
|
||||
@@ -45,7 +55,15 @@ const crmContactEntrySchema = z.object({
|
||||
contactAt: z.string().datetime(),
|
||||
});
|
||||
|
||||
function getRouteParam(value: string | string[] | undefined) {
|
||||
const crmContactSchema = z.object({
|
||||
fullName: z.string().trim().min(1).max(160),
|
||||
role: z.enum(crmContactRoles),
|
||||
email: z.string().trim().email(),
|
||||
phone: z.string().trim().min(1).max(64),
|
||||
isPrimary: z.boolean(),
|
||||
});
|
||||
|
||||
function getRouteParam(value: unknown) {
|
||||
return typeof value === "string" ? value : null;
|
||||
}
|
||||
|
||||
@@ -67,6 +85,11 @@ crmRouter.get("/customers", requirePermissions([permissions.crmRead]), async (_r
|
||||
);
|
||||
});
|
||||
|
||||
crmRouter.get("/customers/hierarchy-options", requirePermissions([permissions.crmRead]), async (request, response) => {
|
||||
const excludeCustomerId = getRouteParam(request.query.excludeCustomerId);
|
||||
return ok(response, await listCustomerHierarchyOptions(excludeCustomerId ?? undefined));
|
||||
});
|
||||
|
||||
crmRouter.get("/customers/:customerId", requirePermissions([permissions.crmRead]), async (request, response) => {
|
||||
const customerId = getRouteParam(request.params.customerId);
|
||||
if (!customerId) {
|
||||
@@ -87,7 +110,12 @@ crmRouter.post("/customers", requirePermissions([permissions.crmWrite]), async (
|
||||
return fail(response, 400, "INVALID_INPUT", "Customer payload is invalid.");
|
||||
}
|
||||
|
||||
return ok(response, await createCustomer(parsed.data), 201);
|
||||
const customer = await createCustomer(parsed.data);
|
||||
if (!customer) {
|
||||
return fail(response, 400, "INVALID_INPUT", "Customer reseller relationship is invalid.");
|
||||
}
|
||||
|
||||
return ok(response, customer, 201);
|
||||
});
|
||||
|
||||
crmRouter.put("/customers/:customerId", requirePermissions([permissions.crmWrite]), async (request, response) => {
|
||||
@@ -101,9 +129,14 @@ crmRouter.put("/customers/:customerId", requirePermissions([permissions.crmWrite
|
||||
return fail(response, 400, "INVALID_INPUT", "Customer payload is invalid.");
|
||||
}
|
||||
|
||||
const existingCustomer = await getCustomerById(customerId);
|
||||
if (!existingCustomer) {
|
||||
return fail(response, 404, "CRM_CUSTOMER_NOT_FOUND", "Customer record was not found.");
|
||||
}
|
||||
|
||||
const customer = await updateCustomer(customerId, parsed.data);
|
||||
if (!customer) {
|
||||
return fail(response, 404, "CRM_CUSTOMER_NOT_FOUND", "Customer record was not found.");
|
||||
return fail(response, 400, "INVALID_INPUT", "Customer reseller relationship is invalid.");
|
||||
}
|
||||
|
||||
return ok(response, customer);
|
||||
@@ -128,6 +161,25 @@ crmRouter.post("/customers/:customerId/contact-history", requirePermissions([per
|
||||
return ok(response, entry, 201);
|
||||
});
|
||||
|
||||
crmRouter.post("/customers/:customerId/contacts", requirePermissions([permissions.crmWrite]), async (request, response) => {
|
||||
const customerId = getRouteParam(request.params.customerId);
|
||||
if (!customerId) {
|
||||
return fail(response, 400, "INVALID_INPUT", "Customer id is invalid.");
|
||||
}
|
||||
|
||||
const parsed = crmContactSchema.safeParse(request.body);
|
||||
if (!parsed.success) {
|
||||
return fail(response, 400, "INVALID_INPUT", "CRM contact is invalid.");
|
||||
}
|
||||
|
||||
const contact = await createCustomerContact(customerId, parsed.data);
|
||||
if (!contact) {
|
||||
return fail(response, 404, "CRM_CUSTOMER_NOT_FOUND", "Customer record was not found.");
|
||||
}
|
||||
|
||||
return ok(response, contact, 201);
|
||||
});
|
||||
|
||||
crmRouter.get("/vendors", requirePermissions([permissions.crmRead]), async (_request, response) => {
|
||||
const parsed = crmListQuerySchema.safeParse(_request.query);
|
||||
if (!parsed.success) {
|
||||
@@ -204,3 +256,22 @@ crmRouter.post("/vendors/:vendorId/contact-history", requirePermissions([permiss
|
||||
|
||||
return ok(response, entry, 201);
|
||||
});
|
||||
|
||||
crmRouter.post("/vendors/:vendorId/contacts", requirePermissions([permissions.crmWrite]), async (request, response) => {
|
||||
const vendorId = getRouteParam(request.params.vendorId);
|
||||
if (!vendorId) {
|
||||
return fail(response, 400, "INVALID_INPUT", "Vendor id is invalid.");
|
||||
}
|
||||
|
||||
const parsed = crmContactSchema.safeParse(request.body);
|
||||
if (!parsed.success) {
|
||||
return fail(response, 400, "INVALID_INPUT", "CRM contact is invalid.");
|
||||
}
|
||||
|
||||
const contact = await createVendorContact(vendorId, parsed.data);
|
||||
if (!contact) {
|
||||
return fail(response, 404, "CRM_VENDOR_NOT_FOUND", "Vendor record was not found.");
|
||||
}
|
||||
|
||||
return ok(response, contact, 201);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user