front/src/api/channel/salesProduct.ts
zzh f1d219ae48 feat: ship billing UI for additional services, conditions, and fee insights
Add product/channel billing condition editors, place-order additional services, billing simulator, forecast-to-inbound fee delta and margin warnings, insurance-aware claim tips, OMS preset dict seeds, and related AI skills/system pages.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 18:09:22 +08:00

213 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { axiosInstance } from '@/api/rbac'
import { assertBusinessOk, omsUrl, parseListPayload, unwrapData } from '@/api/oms/common'
/** 从 HTTP 响应体取出详情对象:标准 { code, data } 或根级直接返回实体 */
function extractSalesProductDetailFromBody(body: any): any {
if (body == null || typeof body !== 'object') return undefined
const nested = body.data ?? body.Data
if (nested != null && typeof nested === 'object') return nested
const looksLikeProduct =
body.productCode != null ||
body.ProductCode != null ||
(body.id != null && (body.productName != null || body.ProductName != null))
if (looksLikeProduct) return body
return undefined
}
/** 兼容 detail 返回 data 为空、或多层 { data: { ... } } 包裹 */
function normalizeSalesProductDetailPayload(raw: unknown): Record<string, any> | undefined {
if (raw == null || typeof raw !== 'object') return undefined
let cur: any = raw
for (let i = 0; i < 5; i++) {
if (
cur.productCode != null ||
cur.ProductCode != null ||
(cur.id != null && (cur.productName != null || cur.ProductName != null))
) {
return cur as Record<string, any>
}
const next = cur.data ?? cur.Data ?? cur.result ?? cur.Result ?? cur.item ?? cur.Item
if (next == null || typeof next !== 'object') break
cur = next
}
return typeof cur === 'object' ? (cur as Record<string, any>) : undefined
}
export async function getSalesProductList(params: {
page?: number
pageSize?: number
productGroup?: string
keyword?: string
isActive?: number
}) {
const res = await axiosInstance.get(omsUrl('/channel/sales-product/list'), { params })
return parseListPayload(res.data)
}
export async function getSalesProductDetail(id: number | string) {
const res = await axiosInstance.get(omsUrl('/channel/sales-product/detail'), { params: { id } })
const body = res.data
assertBusinessOk(body)
const raw = extractSalesProductDetailFromBody(body)
return normalizeSalesProductDetailPayload(raw) ?? (raw && typeof raw === 'object' ? raw : undefined)
}
export async function createSalesProduct(body: Record<string, any>) {
const res = await axiosInstance.post(omsUrl('/channel/sales-product/create'), body)
assertBusinessOk(res.data)
}
export async function updateSalesProduct(body: Record<string, any> & { id: number | string }) {
const res = await axiosInstance.post(omsUrl('/channel/sales-product/update'), body)
assertBusinessOk(res.data)
}
export async function deleteSalesProduct(id: number | string) {
const res = await axiosInstance.post(omsUrl('/channel/sales-product/delete'), { id })
assertBusinessOk(res.data)
}
/** 对客说明 */
export async function saveSalesProductCustomerGuide(body: {
salesProductId: number | string
customerGuideMarkdown?: string
}) {
const res = await axiosInstance.post(omsUrl('/channel/sales-product/customer-guide/save'), body)
assertBusinessOk(res.data)
}
/** 卖价 */
export async function getSalesProductPriceList(salesProductId: number | string) {
const res = await axiosInstance.get(omsUrl('/channel/sales-product/price/list'), {
params: { salesProductId },
})
const { items } = parseListPayload(res.data)
return items ?? []
}
export async function createSalesProductPrice(body: Record<string, any>) {
const res = await axiosInstance.post(omsUrl('/channel/sales-product/price/create'), body)
assertBusinessOk(res.data)
return unwrapData<any>(res.data)
}
export async function updateSalesProductPrice(body: Record<string, any> & { id: number | string }) {
const res = await axiosInstance.post(omsUrl('/channel/sales-product/price/update'), body)
assertBusinessOk(res.data)
}
export async function deleteSalesProductPrice(id: number | string) {
const res = await axiosInstance.post(omsUrl('/channel/sales-product/price/delete'), { id })
assertBusinessOk(res.data)
}
export async function toggleSalesProductPriceStatus(id: number | string, status: number) {
const res = await axiosInstance.post(omsUrl('/channel/sales-product/price/toggleStatus'), { id, status })
assertBusinessOk(res.data)
}
/** allocation/get:兼容 { code, data: { scheme, providers } }、或与 code 同级的根字段、或无 code 仅实体 */
function extractSalesProductAllocationFromBody(body: any): Record<string, any> | undefined {
if (body == null || typeof body !== 'object') return undefined
const nested = body.data ?? body.Data
const looksLikeAllocation = (o: any) =>
o != null &&
typeof o === 'object' &&
!Array.isArray(o) &&
(Array.isArray(o.providers) ||
Array.isArray(o.Providers) ||
o.scheme != null ||
o.Scheme != null ||
o.salesProductId != null ||
o.SalesProductId != null)
if (looksLikeAllocation(nested)) return nested as Record<string, any>
if (looksLikeAllocation(body)) return body as Record<string, any>
return undefined
}
/** 排货 */
export async function getSalesProductAllocation(salesProductId: number | string) {
const res = await axiosInstance.get(omsUrl('/channel/sales-product/allocation/get'), {
params: { salesProductId },
})
const body = res.data
assertBusinessOk(body)
return extractSalesProductAllocationFromBody(body)
}
export async function saveSalesProductAllocation(body: Record<string, any>) {
const res = await axiosInstance.post(omsUrl('/channel/sales-product/allocation/save'), body)
assertBusinessOk(res.data)
}
/** 发布 */
export async function getSalesProductPublishPriceList(params: {
salesProductId: number | string
page?: number
pageSize?: number
}) {
const res = await axiosInstance.get(omsUrl('/channel/sales-product/publish/price/list'), { params })
return parseListPayload(res.data)
}
export async function getSalesProductPublishRulesList(params: {
salesProductId: number | string
page?: number
pageSize?: number
}) {
const res = await axiosInstance.get(omsUrl('/channel/sales-product/publish/rules/list'), { params })
return parseListPayload(res.data)
}
/** 预览即将发布的快照(不落库),用于发布弹窗与发布记录核对 */
export async function getSalesProductPublishPreview(params: {
salesProductId: number | string
operatorType: 'rules' | 'price' | string
}) {
const res = await axiosInstance.get(omsUrl('/channel/sales-product/publish/preview'), { params })
return unwrapData<Record<string, unknown>>(res.data)
}
export async function publishSalesProduct(body: {
salesProductId: number | string
operatorType: string
description?: string
/** 价格发布必填:AI 风险分析报告原文 */
aiAnalysisReport?: string
}) {
const res = await axiosInstance.post(omsUrl('/channel/sales-product/publish'), body)
assertBusinessOk(res.data)
}
export async function rollbackSalesProductPublish(body: {
salesProductId: number | string
recordId: number | string
operatorType: string
}) {
const res = await axiosInstance.post(omsUrl('/channel/sales-product/publish/rollback'), body)
assertBusinessOk(res.data)
}
/** 销售产品计费模拟(基于当前库内卖价) */
export async function postSalesProductBillingSimulate(body: {
salesProductId: number | string
countryCode: string
province?: string
city?: string
zipCode?: string
length: number
width: number
height: number
actualWeight: number
cargoAttribute?: string
warehouseId?: number | null
additionalServices?: string[]
declaredSum?: number | null
declaredCurrency?: string
isRemote?: boolean | null
priceTierKey?: string
}) {
const res = await axiosInstance.post(omsUrl('/channel/sales-product/billing/simulate'), body)
return unwrapData<Record<string, any>>(res.data)
}