Files
zkxw/mobile-oa-h5/src/api/market.js
T
zlwu bd62416263 feat(project): add team attribution and business statistics
Add tenant-scoped teams and projects, order attribution across four businesses, conversion inheritance, scoped statistics and audited reassignment. Reuse existing Web and mobile UI patterns; team regions and hospitals remain informational.

Include incremental SQL and isolated regression coverage. Actual MySQL and browser/WebView integration validation remains pending; relates to issue #13.
2026-11-02 15:29:43 +08:00

228 lines
8.5 KiB
JavaScript

import { moduleResourceName, normalizeMarketContext } from '../market/context-contract.js'
import { getMarketModule } from '../market/module-registry.js'
function assertContext(context) {
return normalizeMarketContext(context)
}
export function normalizeMarketEntityId(id, { rejectPathSeparators = false } = {}) {
if (id == null) throw new Error('业务实体 ID 无效')
const normalized = String(id).trim()
if (
!normalized ||
normalized.length > 64 ||
normalized === '.' ||
normalized === '..' ||
/[\u0000-\u001f\u007f-\u009f]/.test(normalized) ||
(rejectPathSeparators && /[\\/]/.test(normalized))
) {
throw new Error('业务实体 ID 无效')
}
return normalized
}
export function buildMarketResourcePath(context, id) {
const normalizedContext = assertContext(context)
const base = `/mini/market/${normalizedContext.marketRole}/${moduleResourceName(normalizedContext.module)}`
if (arguments.length < 2) return base
const normalizedId = normalizeMarketEntityId(id, {
rejectPathSeparators: normalizedContext.module === 'presign'
})
return `${base}/${encodeURIComponent(normalizedId)}`
}
export function buildMarketFormOptionsPath(context) {
const normalizedContext = assertContext(context)
return `/mini/market/${normalizedContext.marketRole}/${moduleResourceName(normalizedContext.module)}/form-options`
}
export function buildMarketAttachmentPath(context) {
const normalizedContext = assertContext(context)
return `/mini/market/${normalizedContext.marketRole}/${normalizedContext.module}/attachments`
}
export function buildMarketAttachmentDownloadPath(context) {
return `${buildMarketAttachmentPath(context)}/download-url`
}
export function buildMarketActionPath(context, action, id, data = {}) {
const normalizedContext = assertContext(context)
const roleBase = `/mini/market/${normalizedContext.marketRole}`
const encodeId = () => encodeURIComponent(normalizeMarketEntityId(id, {
rejectPathSeparators: normalizedContext.module === 'presign'
}))
if (normalizedContext.module === 'presign') {
const encodedId = encodeId()
if (action === 'delete') return buildMarketResourcePath(context, id)
if (action === 'convert') return `${buildMarketResourcePath(context, id)}/convert`
if (action === 'create-transfer') {
return `${buildMarketResourcePath(context, id)}/transfers`
}
if (['edit-presign-transfer', 'delete-transfer'].includes(action)) {
return `${roleBase}/presign-transfers/${encodedId}`
}
if (['submit-transfer', 'withdraw-transfer'].includes(action)) {
const suffix = action === 'submit-transfer' ? 'submit' : 'withdraw'
return `${roleBase}/presign-transfers/${encodedId}/${suffix}`
}
}
if (normalizedContext.module === 'payment') {
if (action === 'register-transfer') return `${roleBase}/payment-transfers`
if (['edit-transfer', 'delete-payment-transfer', 'submit', 'withdraw'].includes(action)) {
const encodedId = encodeId()
const suffix = ['edit-transfer', 'delete-payment-transfer'].includes(action) ? '' : `/${action}`
return `${roleBase}/payment-transfers/${encodedId}${suffix}`
}
}
if (normalizedContext.module === 'protocol' && action === 'delete') {
encodeId()
const source = String(data?.source || '').trim()
if (!['cell_storage', 'research_donation'].includes(source)) {
throw new Error('协议业务来源无效')
}
return `${buildMarketResourcePath(context, id)}?source=${encodeURIComponent(source)}`
}
if (normalizedContext.module === 'freight') {
if (action === 'delete') return buildMarketResourcePath(context, id)
if (['link', 'unlink'].includes(action)) {
encodeId()
return `${buildMarketResourcePath(context, id)}/${action}`
}
}
throw new Error('不支持的市场业务操作')
}
export async function fetchMarketList(context, params, { signal } = {}) {
const { request } = await import('../utils/http.js')
return request({
url: buildMarketResourcePath(context),
method: 'GET',
data: params,
signal
})
}
export async function fetchMarketDetail(context, id, { signal, params } = {}) {
const { request } = await import('../utils/http.js')
return request({
url: buildMarketResourcePath(context, id),
method: 'GET',
data: params,
signal
})
}
export async function fetchMarketFormOptions(context, params, { signal } = {}) {
const { request } = await import('../utils/http.js')
return request({ url: buildMarketFormOptionsPath(context), method: 'GET', data: params, signal })
}
export async function fetchMarketProtocolCustomers(context, params, { signal } = {}) {
const { request } = await import('../utils/http.js')
const normalizedContext = assertContext(context)
if (normalizedContext.module !== 'protocol') {
throw new Error('仅协议模块支持客户档案检索')
}
return request({
url: `${buildMarketResourcePath(normalizedContext)}/customers`,
method: 'GET',
data: params,
signal
})
}
export async function saveMarketRecord(context, { id, data }, { signal } = {}) {
const { request } = await import('../utils/http.js')
const normalizedContext = assertContext(context)
const normalizedId = id == null || id === ''
? ''
: normalizeMarketEntityId(id, { rejectPathSeparators: normalizedContext.module === 'presign' })
return request({
url: normalizedId
? buildMarketResourcePath(context, normalizedId)
: buildMarketResourcePath(context),
method: normalizedId ? 'PUT' : 'POST',
data,
signal
})
}
export function marketActionMethod(action) {
if (['delete', 'delete-transfer', 'delete-payment-transfer'].includes(action)) return 'DELETE'
return ['edit-transfer', 'edit-presign-transfer'].includes(action)
? 'PUT'
: 'POST'
}
export async function performMarketAction(context, action, { id, data } = {}, { signal } = {}) {
const { request } = await import('../utils/http.js')
const requestData = context.module === 'protocol' && action === 'delete'
? Object.fromEntries(Object.entries(data || {}).filter(([key]) => key !== 'source'))
: data
const omitBody = action === 'delete' && ['protocol', 'freight', 'presign'].includes(context.module)
return request({
url: buildMarketActionPath(context, action, id, data),
method: marketActionMethod(action),
data: omitBody ? undefined : requestData,
signal
})
}
export const deletePresignOrder = (context, id, options) =>
performMarketAction(context, 'delete', { id }, options)
export const createPresignTransfer = (context, orderId, data, options) =>
performMarketAction(context, 'create-transfer', { id: orderId, data }, options)
export const updatePresignTransfer = (context, transferId, data, options) =>
performMarketAction(context, 'edit-presign-transfer', { id: transferId, data }, options)
export const deletePresignTransfer = (context, transferId, options) =>
performMarketAction(context, 'delete-transfer', { id: transferId }, options)
export const submitPresignTransfer = (context, transferId, options) =>
performMarketAction(context, 'submit-transfer', { id: transferId }, options)
export const withdrawPresignTransfer = (context, transferId, options) =>
performMarketAction(context, 'withdraw-transfer', { id: transferId }, options)
export async function uploadMarketAttachment(context, file, { signal } = {}) {
const { request } = await import('../utils/http.js')
const data = new FormData()
data.append('file', file)
return request({ url: buildMarketAttachmentPath(context), method: 'POST', data, signal })
}
export async function fetchMarketAttachmentDownloadUrl(context, data, { signal } = {}) {
const { request } = await import('../utils/http.js')
return request({
url: buildMarketAttachmentDownloadPath(context),
method: 'POST',
data,
signal
})
}
export async function fetchFreightSampleOptions(context, params, { signal } = {}) {
const { request } = await import('../utils/http.js')
return request({
url: `${buildMarketResourcePath(context)}/sample-options`,
method: 'GET',
data: params,
signal
})
}
export function marketModuleContract(context) {
const normalizedContext = assertContext(context)
return getMarketModule(normalizedContext.module)
}
export async function fetchProjectOptions(bizType, { signal, filter = false, context } = {}) {
const { request } = await import('../utils/http.js')
const url = filter
? `/mini/market/${assertContext(context).marketRole}/project-options`
: '/project-management/options'
return request({ url, method: 'GET', data: { bizType }, signal })
}