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

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;
}
}