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.
228 lines
8.5 KiB
JavaScript
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 })
|
|
}
|