You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
150 lines
4.3 KiB
150 lines
4.3 KiB
import { parseServiceAuth } from "@/lib/api/auth-context";
|
|
import { fail, ok } from "@/lib/response";
|
|
import { AuthError } from "@/modules/auth/errors";
|
|
import {
|
|
createBusinessCustomer,
|
|
updateBusinessCustomer,
|
|
listBusinessCustomers,
|
|
} from "@/modules/customer/business-customer-service";
|
|
import { upsertBusinessCustomerUsers } from "@/modules/customer/business-customer-user-service";
|
|
import { upsertMarkupConfig } from "@/modules/pricing/markup-service";
|
|
import { ValidationError } from "@/modules/quote/types";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { z } from "zod";
|
|
|
|
const customerItemSchema = z.object({
|
|
business_customer_id: z.string().min(1),
|
|
name: z.string().min(1),
|
|
external_code: z.string().nullable().optional(),
|
|
remark: z.string().nullable().optional(),
|
|
markup_percent: z.number().min(0).max(30).optional(),
|
|
});
|
|
|
|
const userItemSchema = z.object({
|
|
business_customer_id: z.string().min(1),
|
|
external_user_id: z.string().min(1),
|
|
account: z.string().min(1),
|
|
display_name: z.string().nullable().optional(),
|
|
email: z.string().nullable().optional(),
|
|
mobile: z.string().nullable().optional(),
|
|
main_external_user_id: z.string().nullable().optional(),
|
|
status: z.enum(["active", "inactive"]).optional(),
|
|
});
|
|
|
|
const bodySchema = z.object({
|
|
customers: z.array(customerItemSchema).default([]),
|
|
users: z.array(userItemSchema).default([]),
|
|
});
|
|
|
|
/**
|
|
* 宿主推送:同步业务客户(代码/简称)+ 下属登录账号
|
|
* Authorization: Bearer <Service API Key>
|
|
* X-Customer-Id: CUST_004
|
|
*/
|
|
export async function POST(request: Request) {
|
|
let auth;
|
|
try {
|
|
auth = await parseServiceAuth(request);
|
|
} catch (error) {
|
|
if (error instanceof AuthError) {
|
|
return fail(error.code, error.message, error.httpStatus);
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
let body: unknown;
|
|
try {
|
|
body = await request.json();
|
|
} catch {
|
|
return fail("VALIDATION_FAILED", "请求体格式无效", 400);
|
|
}
|
|
|
|
const parsed = bodySchema.safeParse(body);
|
|
if (!parsed.success) {
|
|
return fail(
|
|
"VALIDATION_FAILED",
|
|
parsed.error.issues[0]?.message ?? "参数无效",
|
|
400,
|
|
);
|
|
}
|
|
|
|
const tenantId = auth.customerId;
|
|
let customerCreated = 0;
|
|
let customerUpdated = 0;
|
|
|
|
try {
|
|
for (const row of parsed.data.customers) {
|
|
const existing = await prisma.businessCustomer.findFirst({
|
|
where: {
|
|
customerId: tenantId,
|
|
businessCustomerId: row.business_customer_id,
|
|
isDeleted: false,
|
|
},
|
|
});
|
|
if (existing) {
|
|
await updateBusinessCustomer(tenantId, row.business_customer_id, {
|
|
name: row.name,
|
|
externalCode: row.external_code ?? null,
|
|
remark: row.remark ?? null,
|
|
status: "active",
|
|
});
|
|
customerUpdated += 1;
|
|
} else {
|
|
await createBusinessCustomer({
|
|
customerId: tenantId,
|
|
businessCustomerId: row.business_customer_id,
|
|
name: row.name,
|
|
externalCode: row.external_code ?? null,
|
|
remark: row.remark ?? null,
|
|
});
|
|
customerCreated += 1;
|
|
}
|
|
if (typeof row.markup_percent === "number") {
|
|
await upsertMarkupConfig(
|
|
tenantId,
|
|
row.business_customer_id,
|
|
{
|
|
markupType: "percent",
|
|
markupPercent: row.markup_percent,
|
|
markupFixedAmount: null,
|
|
},
|
|
"host-customers-sync",
|
|
"宿主同步加价",
|
|
);
|
|
}
|
|
}
|
|
|
|
const userResult = await upsertBusinessCustomerUsers(
|
|
tenantId,
|
|
parsed.data.users.map((u) => ({
|
|
businessCustomerId: u.business_customer_id,
|
|
externalUserId: u.external_user_id,
|
|
account: u.account,
|
|
displayName: u.display_name,
|
|
email: u.email,
|
|
mobile: u.mobile,
|
|
mainExternalUserId: u.main_external_user_id,
|
|
status: u.status,
|
|
})),
|
|
);
|
|
|
|
const customers = await listBusinessCustomers(tenantId);
|
|
|
|
return ok({
|
|
customer_id: tenantId,
|
|
customers: {
|
|
created: customerCreated,
|
|
updated: customerUpdated,
|
|
total: parsed.data.customers.length,
|
|
},
|
|
users: userResult,
|
|
business_customer_count: customers.length,
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof ValidationError) {
|
|
return fail("VALIDATION_FAILED", error.message, 400);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|