完善接单平台上传与物品规则

This commit is contained in:
yml2213
2026-07-25 15:56:18 +08:00
parent f6d3f00f21
commit a2587eda01
40 changed files with 4978 additions and 1008 deletions
@@ -2,6 +2,8 @@ import crypto from 'node:crypto'
import { runtimeConfig } from '../../config/runtime.js'
import { WORK_ORDER_STATUS } from '../../domain/work-order-status.js'
import { listOrderItemsByOrderId } from '../../repositories/order-item-repo.js'
import { findLatestOrderByAnyPlatformOrderId, getOrderById } from '../../repositories/order-repo.js'
import {
acceptWorkOrderAndSettle,
addWorkerWalletCredit,
@@ -12,32 +14,35 @@ import {
findPendingMaterialWorkOrderByPlatformOrderId,
getWorkCategoryByKey,
getWorkOrderById,
getWorkOrderByOrderItemId,
getWorkerLevelById,
getWorkerLevelByKey,
getWorkerUserById,
getWorkerUserByUsername,
listAllWorkCategories,
grabWorkOrder,
listWorkCategories,
listWorkOrders,
listWorkProductRules,
listWorkerLevels,
listWorkerUsers,
updateWorkOrder,
updateWorkerUser,
upsertWorkCategory,
upsertWorkProductRule,
upsertWorkerLevel,
type WorkCategoryRow,
type WorkOrderRow,
type WorkProductRuleRow,
type WorkerLevelRow,
type WorkerUserRow,
} from '../../repositories/worker-platform-repo.js'
import type { JsonObject } from '../../types/json.js'
import type { OrderItemRow, OrderRow } from '../../types/repository/rows.js'
import { createHttpError } from '../../utils/http.js'
import { randomId } from '../../utils/random.js'
import { nowIso, addHours } from '../../utils/time.js'
import {
normalizePage,
normalizePageSize,
safeParseJson,
} from '../admin/admin-query-utils.js'
import { normalizePage, normalizePageSize, safeParseJson } from '../admin/admin-query-utils.js'
export type WorkerSession = {
sessionId: string
@@ -340,7 +345,9 @@ export async function submitWorkerOrderAcceptance(
errorCode: 'work_order_owner_required',
})
}
if (![WORK_ORDER_STATUS.IN_PROGRESS, WORK_ORDER_STATUS.PROBLEM].includes(workOrder.status as never)) {
if (
![WORK_ORDER_STATUS.IN_PROGRESS, WORK_ORDER_STATUS.PROBLEM].includes(workOrder.status as never)
) {
throw createHttpError('当前状态不能提交验收', {
statusCode: 409,
errorCode: 'work_order_submit_status_invalid',
@@ -348,9 +355,21 @@ export async function submitWorkerOrderAcceptance(
}
const now = nowIso()
const files = normalizeUploadedFiles(payload.files)
const imageUrls = [
...files.map((file) => file.url || file.mediumUrl || file.thumbnailUrl).filter(Boolean),
...normalizeStringArray(payload.imageUrls),
]
if (imageUrls.length === 0) {
throw createHttpError('请上传验收图片', {
statusCode: 400,
errorCode: 'work_order_acceptance_image_required',
})
}
const acceptance = {
note: String(payload.note || '').trim(),
imageUrls: normalizeStringArray(payload.imageUrls),
files,
imageUrls: [...new Set(imageUrls)],
submittedAt: now,
}
const updated = await updateWorkOrder(workOrder.id, {
@@ -369,7 +388,12 @@ export async function submitWorkerOrderAcceptance(
payloadJson: JSON.stringify(acceptance),
now,
})
return { order: mapWorkOrderForWorker(updated || workOrder, resolveWorkerPermissions(await getRequiredWorker(session.workerId))) }
return {
order: mapWorkOrderForWorker(
updated || workOrder,
resolveWorkerPermissions(await getRequiredWorker(session.workerId)),
),
}
}
export async function collectLookupWorkOrder(payload: JsonObject = {}) {
@@ -411,7 +435,9 @@ export async function collectSubmitWorkOrder(payload: JsonObject = {}) {
const fields = resolveRequirementFields(workOrder)
const submittedFields = normalizeSubmittedFields(payload.fields || payload.material || payload)
const complete = fields.every((field) => !field.required || String(submittedFields[field.key] || '').trim())
const complete = fields.every(
(field) => !field.required || String(submittedFields[field.key] || '').trim(),
)
const now = nowIso()
const material = {
...safeParseJson(workOrder.material_json),
@@ -448,12 +474,37 @@ export async function listAdminWorkerLevels() {
return { items: (await listWorkerLevels()).map(mapWorkerLevel) }
}
export async function listAdminWorkCategories() {
await ensureWorkerPlatformDefaults()
return { items: (await listAllWorkCategories()).map(mapWorkCategory) }
}
export async function saveAdminWorkCategory(payload: JsonObject = {}) {
const now = nowIso()
const categoryKey = normalizeSlugKey(
payload.categoryKey || payload.category_key || payload.name,
DEFAULT_CATEGORY_KEY,
)
const name = String(payload.name || '').trim() || '默认分类'
const category = await upsertWorkCategory({
categoryKey,
name,
sortOrder: normalizeInteger(payload.sortOrder, 100),
status: normalizeEnabledStatus(payload.status),
now,
})
return { category: mapWorkCategory(category) }
}
export async function saveAdminWorkerLevel(payload: JsonObject = {}) {
const now = nowIso()
const levelKey = String(payload.levelKey || payload.level_key || '').trim() || DEFAULT_LEVEL_KEY
const name = String(payload.name || '').trim() || '普通接单员'
const permissions = {
depositFreeAmount: normalizeAmountFen(payload.depositFreeAmount ?? payload.depositFreeAmountYuan, 0),
depositFreeAmount: normalizeAmountFen(
payload.depositFreeAmount ?? payload.depositFreeAmountYuan,
0,
),
maxActiveOrders: normalizePositiveInteger(payload.maxActiveOrders, 3),
}
const level = await upsertWorkerLevel({
@@ -467,6 +518,78 @@ export async function saveAdminWorkerLevel(payload: JsonObject = {}) {
return { level: mapWorkerLevel(level) }
}
export async function listAdminWorkProductRules(query: JsonObject = {}) {
await ensureWorkerPlatformDefaults()
const enabledValue = String(query.enabled ?? '').trim()
const enabled = enabledValue
? ['true', '1', 'enabled', 'active'].includes(enabledValue.toLowerCase())
: null
const items = await listWorkProductRules({
enabled,
keyword: String(query.keyword || '').trim(),
})
return { items: items.map(mapWorkProductRule) }
}
export async function saveAdminWorkProductRule(payload: JsonObject = {}) {
const defaults = await ensureWorkerPlatformDefaults()
const productName = String(
payload.productName || payload.product_name || payload.skuName || '',
).trim()
const skuCode = String(payload.skuCode || payload.sku_code || '').trim()
if (!productName && !skuCode) {
throw createHttpError('请填写商品名或 SKU', {
statusCode: 400,
errorCode: 'work_product_rule_target_required',
})
}
const rewardAmount = normalizeAmountFen(payload.rewardAmount ?? payload.rewardAmountYuan, 0)
if (rewardAmount <= 0) {
throw createHttpError('接单金额必须大于 0', {
statusCode: 400,
errorCode: 'work_product_rule_reward_invalid',
})
}
const depositThresholdAmount = normalizeAmountFen(
payload.depositThresholdAmount ?? payload.depositThresholdAmountYuan,
DEFAULT_DEPOSIT_THRESHOLD_AMOUNT,
)
const requiredDepositAmount =
payload.requiredDepositAmount === undefined && payload.requiredDepositAmountYuan === undefined
? rewardAmount > depositThresholdAmount
? rewardAmount
: 0
: normalizeAmountFen(payload.requiredDepositAmount ?? payload.requiredDepositAmountYuan, 0)
const fields = normalizeRequirementFieldsFromPayload(payload)
const rule = await upsertWorkProductRule({
ruleKey: normalizeSlugKey(
payload.ruleKey || payload.rule_key || skuCode || productName,
`rule-${Date.now()}`,
),
provider: String(payload.provider || '').trim(),
platform: String(payload.platform || '').trim(),
shopId: String(payload.shopId || payload.shop_id || '').trim(),
skuCode,
productName,
matchType: normalizeMatchType(payload.matchType || payload.match_type),
categoryId:
normalizeOptionalId(payload.categoryId || payload.category_id) ||
defaults.category?.id ||
null,
enabled: normalizeBoolean(payload.enabled, true),
autoCreate: normalizeBoolean(payload.autoCreate ?? payload.auto_create, false),
rewardAmount,
requiredDepositAmount,
depositThresholdAmount,
requirementJson: JSON.stringify({ fields }),
sortOrder: normalizeInteger(payload.sortOrder, 100),
now: nowIso(),
})
return { rule: mapWorkProductRule(rule) }
}
export async function listAdminWorkerUsers(query: JsonObject = {}) {
const page = normalizePage(query.page)
const pageSize = normalizePageSize(query.pageSize)
@@ -485,7 +608,11 @@ export async function listAdminWorkerUsers(query: JsonObject = {}) {
export async function reviewAdminWorkerUser(workerId: number | string, payload: JsonObject = {}) {
const worker = await getRequiredWorker(workerId)
const status = normalizeReviewStatus(payload.status)
const levelId = normalizeOptionalId(payload.levelId) || worker.level_id || (await ensureWorkerPlatformDefaults()).level?.id || null
const levelId =
normalizeOptionalId(payload.levelId) ||
worker.level_id ||
(await ensureWorkerPlatformDefaults()).level?.id ||
null
const now = nowIso()
const updated = await updateWorkerUser(worker.id, {
status,
@@ -532,6 +659,66 @@ export async function listAdminWorkOrders(query: JsonObject = {}) {
}
}
export async function submitAdminWorkOrderMaterial(
workOrderId: number | string,
payload: JsonObject = {},
) {
const workOrder = await getRequiredWorkOrder(workOrderId)
if (
![WORK_ORDER_STATUS.PENDING_MATERIAL, WORK_ORDER_STATUS.UNASSIGNED].includes(
workOrder.status as never,
)
) {
throw createHttpError('当前状态不能补充资料', {
statusCode: 409,
errorCode: 'work_order_material_status_invalid',
})
}
const fields = resolveRequirementFields(workOrder)
const submittedFields = normalizeSubmittedFields(payload.fields || payload.material || payload)
const complete = fields.every(
(field) => !field.required || String(submittedFields[field.key] || '').trim(),
)
const now = nowIso()
const material = {
...safeParseJson(workOrder.material_json),
collect: {
fields: submittedFields,
submittedAt: now,
complete,
source: 'admin',
},
}
const nextStatus = complete ? WORK_ORDER_STATUS.UNASSIGNED : WORK_ORDER_STATUS.PENDING_MATERIAL
const updated = await updateWorkOrder(workOrder.id, {
status: nextStatus,
material_json: material,
updated_at: now,
})
await createWorkOrderEvent({
workOrderId: workOrder.id,
actorType: 'admin',
actorId: '',
eventType: 'material_completed',
fromStatus: workOrder.status,
toStatus: nextStatus,
payloadJson: JSON.stringify({ fields: submittedFields, complete }),
now,
})
return { order: mapWorkOrderAdmin(updated || workOrder), complete }
}
export async function syncAdminWorkerOrdersFromSource(payload: JsonObject = {}) {
const order = await resolveSourceOrder(payload)
const orderItems = await listOrderItemsByOrderId(order.id)
const result = await syncWorkerOrdersForSourceOrder(order, orderItems, {
source: 'admin_manual_sync',
autoOnly: false,
})
return result
}
export async function createAdminMockWorkOrder(payload: JsonObject = {}) {
const defaults = await ensureWorkerPlatformDefaults()
const now = nowIso()
@@ -540,12 +727,17 @@ export async function createAdminMockWorkOrder(payload: JsonObject = {}) {
payload.depositThresholdAmount ?? payload.depositThresholdAmountYuan,
DEFAULT_DEPOSIT_THRESHOLD_AMOUNT,
)
const requiredDepositAmount = payload.requiredDepositAmount === undefined
? (rewardAmount > depositThresholdAmount ? rewardAmount : 0)
: normalizeAmountFen(payload.requiredDepositAmount, 0)
const requiredDepositAmount =
payload.requiredDepositAmount === undefined
? rewardAmount > depositThresholdAmount
? rewardAmount
: 0
: normalizeAmountFen(payload.requiredDepositAmount, 0)
const materialComplete = payload.materialComplete === true || payload.materialComplete === 'true'
const fields = normalizeRequirementFields(payload.fields)
const platformOrderId = String(payload.platformOrderId || payload.orderNo || `MOCKWORK${Date.now()}`).trim()
const platformOrderId = String(
payload.platformOrderId || payload.orderNo || `MOCKWORK${Date.now()}`,
).trim()
const productName = String(payload.productName || '指挥官秘钥1个').trim()
const material = materialComplete
? {
@@ -617,9 +809,16 @@ export async function publishAdminWorkOrder(workOrderId: number | string) {
return { order: mapWorkOrderAdmin(updated || workOrder) }
}
export async function markAdminWorkOrderProblem(workOrderId: number | string, payload: JsonObject = {}) {
export async function markAdminWorkOrderProblem(
workOrderId: number | string,
payload: JsonObject = {},
) {
const workOrder = await getRequiredWorkOrder(workOrderId)
if (![WORK_ORDER_STATUS.IN_PROGRESS, WORK_ORDER_STATUS.PENDING_ACCEPTANCE].includes(workOrder.status as never)) {
if (
![WORK_ORDER_STATUS.IN_PROGRESS, WORK_ORDER_STATUS.PENDING_ACCEPTANCE].includes(
workOrder.status as never,
)
) {
throw createHttpError('当前状态不能标记问题单', {
statusCode: 409,
errorCode: 'work_order_problem_status_invalid',
@@ -679,6 +878,96 @@ export async function getAdminWorkerPlatformSummary() {
}
}
export async function syncWorkerOrdersForSourceOrder(
order: OrderRow,
orderItems: OrderItemRow[],
options: { source?: string; autoOnly?: boolean } = {},
) {
const rules = await listWorkProductRules({ enabled: true })
const created: WorkOrderRow[] = []
const skipped: Array<{ orderItemId: number; reason: string }> = []
for (const item of orderItems) {
const rule = resolveMatchingProductRule(order, item, rules)
if (!rule) {
skipped.push({ orderItemId: Number(item.id), reason: 'rule_not_matched' })
continue
}
if (options.autoOnly && !rule.auto_create) {
skipped.push({ orderItemId: Number(item.id), reason: 'auto_create_disabled' })
continue
}
if (await getWorkOrderByOrderItemId(item.id)) {
skipped.push({ orderItemId: Number(item.id), reason: 'already_exists' })
continue
}
const fields = normalizeRequirementFields(safeParseJson(rule.requirement_json).fields)
const rewardAmount = Number(rule.reward_amount || 0)
const depositThresholdAmount = Number(
rule.deposit_threshold_amount || DEFAULT_DEPOSIT_THRESHOLD_AMOUNT,
)
const requiredDepositAmount =
Number(rule.required_deposit_amount || 0) ||
(rewardAmount > depositThresholdAmount ? rewardAmount : 0)
const now = nowIso()
const materialComplete = fields.every((field) => !field.required)
const workOrder = await createWorkOrder({
workOrderNo: randomId('WO'),
orderId: Number(order.id),
orderItemId: Number(item.id),
taskId: null,
platformOrderId: order.platform_order_id,
productName: item.sku_name || item.sku_code,
categoryId: rule.category_id,
status: materialComplete ? WORK_ORDER_STATUS.UNASSIGNED : WORK_ORDER_STATUS.PENDING_MATERIAL,
rewardAmount,
requiredDepositAmount,
depositThresholdAmount,
materialJson: JSON.stringify({
source: {
orderId: Number(order.id),
orderItemId: Number(item.id),
provider: order.provider,
platform: order.platform,
shopId: order.shop_id,
skuCode: item.sku_code,
skuName: item.sku_name,
quantity: Number(item.quantity || 1),
syncSource: options.source || 'source_order',
},
}),
requirementJson: JSON.stringify({ fields }),
now,
})
if (workOrder) {
created.push(workOrder)
await createWorkOrderEvent({
workOrderId: workOrder.id,
actorType: 'system',
actorId: options.source || 'source_order',
eventType: 'source_synced',
fromStatus: '',
toStatus: workOrder.status,
payloadJson: JSON.stringify({
ruleKey: rule.rule_key,
orderId: order.id,
orderItemId: item.id,
}),
now,
})
}
}
return {
created: created.map(mapWorkOrderAdmin),
skipped,
createdCount: created.length,
skippedCount: skipped.length,
}
}
function createWorkerSession(worker: WorkerUserRow) {
const issuedAt = nowIso()
const expiresAt = addHours(issuedAt, Number(runtimeConfig.admin?.sessionTtlHours || 12))
@@ -759,6 +1048,37 @@ async function getRequiredWorkOrder(workOrderId: number | string): Promise<WorkO
return workOrder
}
async function resolveSourceOrder(payload: JsonObject): Promise<OrderRow> {
const orderId = normalizeOptionalId(payload.orderId || payload.sourceOrderId)
if (orderId) {
const order = await getOrderById(orderId)
if (order) {
return order
}
throw createHttpError('源订单不存在', {
statusCode: 404,
errorCode: 'source_order_not_found',
})
}
const platformOrderId = String(payload.platformOrderId || payload.orderNo || '').trim()
if (!platformOrderId) {
throw createHttpError('请填写源订单号', {
statusCode: 400,
errorCode: 'source_order_no_required',
})
}
const order = await findLatestOrderByAnyPlatformOrderId(platformOrderId)
if (!order) {
throw createHttpError('未找到源订单,请确认订单号是否正确', {
statusCode: 404,
errorCode: 'source_order_not_found',
})
}
return order
}
function resolveWorkerPermissions(worker: WorkerUserRow) {
const permission = safeParseJson(worker.level_permission_json)
return {
@@ -767,8 +1087,14 @@ function resolveWorkerPermissions(worker: WorkerUserRow) {
}
}
function resolveFreezeDepositAmount(workOrder: WorkOrderRow, permissions: { depositFreeAmount: number }) {
return Math.max(0, Number(workOrder.required_deposit_amount || 0) - Number(permissions.depositFreeAmount || 0))
function resolveFreezeDepositAmount(
workOrder: WorkOrderRow,
permissions: { depositFreeAmount: number },
) {
return Math.max(
0,
Number(workOrder.required_deposit_amount || 0) - Number(permissions.depositFreeAmount || 0),
)
}
function mapWorkerLevel(level: WorkerLevelRow | null | undefined) {
@@ -787,6 +1113,49 @@ function mapWorkerLevel(level: WorkerLevelRow | null | undefined) {
}
}
function mapWorkCategory(category: WorkCategoryRow | null | undefined) {
if (!category) return null
return {
categoryId: Number(category.id),
categoryKey: category.category_key,
name: category.name,
sortOrder: Number(category.sort_order || 100),
status: category.status,
createdAt: category.created_at,
updatedAt: category.updated_at,
}
}
function mapWorkProductRule(rule: WorkProductRuleRow | null | undefined) {
if (!rule) return null
const requirement = safeParseJson(rule.requirement_json)
return {
ruleId: Number(rule.id),
ruleKey: rule.rule_key,
provider: rule.provider || '',
platform: rule.platform || '',
shopId: rule.shop_id || '',
skuCode: rule.sku_code || '',
productName: rule.product_name || '',
matchType: rule.match_type || 'contains',
categoryId: rule.category_id ? Number(rule.category_id) : null,
categoryName: rule.category_name || '',
enabled: Boolean(rule.enabled),
autoCreate: Boolean(rule.auto_create),
rewardAmount: Number(rule.reward_amount || 0),
requiredDepositAmount: Number(rule.required_deposit_amount || 0),
depositThresholdAmount: Number(
rule.deposit_threshold_amount || DEFAULT_DEPOSIT_THRESHOLD_AMOUNT,
),
requirement: {
fields: normalizeRequirementFields(requirement.fields),
},
sortOrder: Number(rule.sort_order || 100),
createdAt: rule.created_at,
updatedAt: rule.updated_at,
}
}
function mapWorkerUser(worker: WorkerUserRow) {
return {
workerId: Number(worker.id),
@@ -823,7 +1192,9 @@ function mapWorkOrderAdmin(workOrder: WorkOrderRow) {
status: workOrder.status,
rewardAmount: Number(workOrder.reward_amount || 0),
requiredDepositAmount: Number(workOrder.required_deposit_amount || 0),
depositThresholdAmount: Number(workOrder.deposit_threshold_amount || DEFAULT_DEPOSIT_THRESHOLD_AMOUNT),
depositThresholdAmount: Number(
workOrder.deposit_threshold_amount || DEFAULT_DEPOSIT_THRESHOLD_AMOUNT,
),
material: safeParseJson(workOrder.material_json),
requirement: safeParseJson(workOrder.requirement_json),
acceptance: safeParseJson(workOrder.acceptance_json),
@@ -844,7 +1215,10 @@ function mapWorkOrderAdmin(workOrder: WorkOrderRow) {
}
}
function mapWorkOrderForWorker(workOrder: WorkOrderRow, permissions: { depositFreeAmount: number }) {
function mapWorkOrderForWorker(
workOrder: WorkOrderRow,
permissions: { depositFreeAmount: number },
) {
return {
...mapWorkOrderAdmin(workOrder),
freezeDepositAmount: resolveFreezeDepositAmount(workOrder, permissions),
@@ -878,7 +1252,8 @@ function normalizeRequirementFields(value: unknown) {
const rawFields = Array.isArray(value) ? value : []
const fields = rawFields
.map((item) => {
const source = item && typeof item === 'object' && !Array.isArray(item) ? item as JsonObject : {}
const source =
item && typeof item === 'object' && !Array.isArray(item) ? (item as JsonObject) : {}
const key = String(source.key || '').trim()
if (!key) return null
return {
@@ -888,7 +1263,9 @@ function normalizeRequirementFields(value: unknown) {
mockValue: String(source.mockValue || '').trim(),
}
})
.filter((item): item is { key: string; label: string; required: boolean; mockValue: string } => Boolean(item))
.filter((item): item is { key: string; label: string; required: boolean; mockValue: string } =>
Boolean(item),
)
return fields.length > 0
? fields
@@ -899,8 +1276,113 @@ function normalizeRequirementFields(value: unknown) {
]
}
function normalizeRequirementFieldsFromPayload(payload: JsonObject) {
const directFields = Array.isArray(payload.fields) ? payload.fields : null
const requirement =
payload.requirement &&
typeof payload.requirement === 'object' &&
!Array.isArray(payload.requirement)
? (payload.requirement as JsonObject)
: {}
if (directFields) {
return normalizeRequirementFields(directFields)
}
if (Array.isArray(requirement.fields)) {
return normalizeRequirementFields(requirement.fields)
}
const textFields = normalizeRequirementFieldsText(
payload.fieldsText || payload.requiredFieldsText,
)
return textFields.length > 0 ? textFields : normalizeRequirementFields([])
}
function normalizeRequirementFieldsText(value: unknown) {
return String(value || '')
.split(/\r?\n/)
.map((line, index) => {
const text = line.trim()
if (!text) return null
const delimiter = text.includes('') ? '' : ':'
if (text.includes(delimiter)) {
const [rawKey, ...labelParts] = text.split(delimiter)
const key = String(rawKey || '').trim()
if (!key) return null
return {
key,
label: labelParts.join(delimiter).trim() || key,
required: true,
mockValue: '',
}
}
return {
key: `field${index + 1}`,
label: text,
required: true,
mockValue: '',
}
})
.filter((item): item is { key: string; label: string; required: boolean; mockValue: string } =>
Boolean(item),
)
}
function resolveMatchingProductRule(
order: OrderRow,
item: OrderItemRow,
rules: WorkProductRuleRow[],
) {
return (
rules.find((rule) => {
if (!matchesOptionalText(rule.provider, order.provider)) return false
if (!matchesOptionalText(rule.platform, order.platform)) return false
if (!matchesOptionalText(rule.shop_id, order.shop_id)) return false
const skuCode = String(item.sku_code || '')
.trim()
.toLowerCase()
const skuName = String(item.sku_name || '')
.trim()
.toLowerCase()
const ruleSkuCode = String(rule.sku_code || '')
.trim()
.toLowerCase()
const ruleProductName = String(rule.product_name || '')
.trim()
.toLowerCase()
if (ruleSkuCode && ruleSkuCode !== skuCode) {
return false
}
if (!ruleProductName) {
return Boolean(ruleSkuCode)
}
if (rule.match_type === 'exact') {
return ruleProductName === skuName || ruleProductName === skuCode
}
return skuName.includes(ruleProductName) || skuCode.includes(ruleProductName)
}) || null
)
}
function matchesOptionalText(ruleValue: unknown, sourceValue: unknown) {
const ruleText = String(ruleValue || '')
.trim()
.toLowerCase()
if (!ruleText) {
return true
}
return (
ruleText ===
String(sourceValue || '')
.trim()
.toLowerCase()
)
}
function normalizeSubmittedFields(value: unknown) {
const source = value && typeof value === 'object' && !Array.isArray(value) ? value as JsonObject : {}
const source =
value && typeof value === 'object' && !Array.isArray(value) ? (value as JsonObject) : {}
return Object.fromEntries(
Object.entries(source)
.filter(([key]) => !['orderNo', 'platformOrderId', 'fields', 'material'].includes(key))
@@ -913,14 +1395,80 @@ function normalizeStringArray(value: unknown): string[] {
return value.map((item) => String(item || '').trim()).filter(Boolean)
}
function normalizeUploadedFiles(value: unknown) {
if (!Array.isArray(value)) return []
return value
.map((item) => {
const source =
item && typeof item === 'object' && !Array.isArray(item) ? (item as JsonObject) : {}
const objectKey = String(source.objectKey || source.object_key || '').trim()
const url = String(source.url || '').trim()
if (!objectKey && !url) return null
return {
objectKey,
url,
thumbnailUrl: String(source.thumbnailUrl || source.thumbnail_url || url).trim(),
mediumUrl: String(source.mediumUrl || source.medium_url || url).trim(),
filename: String(source.filename || '').trim(),
contentType: String(source.contentType || source.content_type || '').trim(),
size: normalizeInteger(source.size, 0),
}
})
.filter(
(
item,
): item is {
objectKey: string
url: string
thumbnailUrl: string
mediumUrl: string
filename: string
contentType: string
size: number
} => Boolean(item),
)
}
function normalizeReviewStatus(value: unknown) {
const status = String(value || '').trim()
if (['active', 'rejected', 'disabled', 'pending_review'].includes(status)) return status
return 'pending_review'
}
function normalizeEnabledStatus(value: unknown) {
return String(value || 'active').trim() === 'disabled' ? 'disabled' : 'active'
}
function normalizeMatchType(value: unknown) {
const matchType = String(value || '').trim()
if (matchType === 'exact') return 'exact'
return 'contains'
}
function normalizeBoolean(value: unknown, fallback: boolean) {
if (typeof value === 'boolean') return value
const text = String(value ?? '')
.trim()
.toLowerCase()
if (!text) return fallback
if (['true', '1', 'yes', 'enabled', 'active', 'on'].includes(text)) return true
if (['false', '0', 'no', 'disabled', 'off'].includes(text)) return false
return fallback
}
function normalizeSlugKey(value: unknown, fallback: string) {
const key = String(value || '')
.trim()
.toLowerCase()
.replace(/[^a-z0-9._-]+/g, '-')
.replace(/^-+|-+$/g, '')
return key || fallback
}
function normalizeUsername(value: unknown): string {
return String(value || '').trim().toLowerCase()
return String(value || '')
.trim()
.toLowerCase()
}
function normalizePassword(value: unknown): string {