完善配送订单创建联动
This commit is contained in:
64
frontend/delivery_admin/src/api/order-creation.ts
Normal file
64
frontend/delivery_admin/src/api/order-creation.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 功能描述:计算配送订单创建页的只读金额预估并执行前端金额校验。
|
||||
* 版本:v1.0.0。
|
||||
*/
|
||||
import type { ResourceRow } from './resource-record-form';
|
||||
|
||||
export type OrderAmountPreview = {
|
||||
productAmount: number;
|
||||
deliveryFee: number;
|
||||
discountAmount: number;
|
||||
payableAmount: number;
|
||||
ready: boolean;
|
||||
};
|
||||
|
||||
/** 金额均以分计算,页面只展示预估,最终结果仍由服务端重新计算。 */
|
||||
export function orderAmountPreview(
|
||||
form: Record<string, any>,
|
||||
relationOptions: Record<string, ResourceRow[]>,
|
||||
): OrderAmountPreview {
|
||||
const contractIdentity = String(form.gasorder_contract_identity ?? '');
|
||||
const contract = (relationOptions['/gasorder_contract'] ?? []).find(
|
||||
(row) => String(row.identity ?? '') === contractIdentity,
|
||||
);
|
||||
const selected = new Set(
|
||||
(Array.isArray(form.gasorder_contract_product_identities)
|
||||
? form.gasorder_contract_product_identities
|
||||
: []
|
||||
)
|
||||
.map((value: unknown) => String(value ?? ''))
|
||||
.filter(Boolean),
|
||||
);
|
||||
const products = (relationOptions['/gasorder_contract_product'] ?? []).filter(
|
||||
(row) => selected.has(String(row.identity ?? '')),
|
||||
);
|
||||
const productAmount = products.reduce(
|
||||
(total, row) => total + Math.max(0, Number(row.unit_price ?? 0)),
|
||||
0,
|
||||
);
|
||||
const deliveryFee = Math.max(0, Number(contract?.default_delivery_fee ?? 0));
|
||||
const discountAmount = Math.round(Number(form.discount_amount ?? 0) * 100);
|
||||
return {
|
||||
productAmount,
|
||||
deliveryFee,
|
||||
discountAmount,
|
||||
payableAmount: productAmount + deliveryFee - discountAmount,
|
||||
ready: Boolean(
|
||||
contract && selected.size > 0 && products.length === selected.size,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/** 返回订单金额的前端校验结果;服务端仍执行相同约束和溢出保护。 */
|
||||
export function orderAmountValidation(preview: OrderAmountPreview) {
|
||||
if (!Number.isFinite(preview.discountAmount) || preview.discountAmount < 0)
|
||||
return '优惠金额不能为负数';
|
||||
if (preview.ready && preview.payableAmount <= 0)
|
||||
return '优惠金额必须小于商品金额与配送费之和';
|
||||
return '';
|
||||
}
|
||||
|
||||
/** 将分格式化为人民币展示文本。 */
|
||||
export function formatOrderMoney(value: number) {
|
||||
return `¥${(value / 100).toFixed(2)}`;
|
||||
}
|
||||
@@ -9,12 +9,28 @@ export type ResourceRow = Record<string, unknown>;
|
||||
|
||||
const editableFieldKeys: Record<string, Set<string>> = {
|
||||
staff_account: new Set(['name', 'phone', 'avatar', 'work_status']),
|
||||
staff_credential: new Set(['staff_account_identity', 'credential_type', 'credential_no', 'expired_at']),
|
||||
staff_credential: new Set([
|
||||
'staff_account_identity',
|
||||
'credential_type',
|
||||
'credential_no',
|
||||
'expired_at',
|
||||
]),
|
||||
user_account: new Set(['name', 'phone', 'avatar', 'real_name']),
|
||||
user_address: new Set(['user_account_identity', 'address', 'longitude', 'latitude', 'is_default']),
|
||||
user_address: new Set([
|
||||
'user_account_identity',
|
||||
'address',
|
||||
'longitude',
|
||||
'latitude',
|
||||
'is_default',
|
||||
]),
|
||||
gasorder_contract: new Set([
|
||||
'title', 'terms', 'file_uri', 'default_delivery_fee',
|
||||
'signed_at', 'effective_at', 'expired_at',
|
||||
'title',
|
||||
'terms',
|
||||
'file_uri',
|
||||
'default_delivery_fee',
|
||||
'signed_at',
|
||||
'effective_at',
|
||||
'expired_at',
|
||||
]),
|
||||
};
|
||||
|
||||
@@ -60,13 +76,19 @@ export function validateResourceRecordForm(
|
||||
mode: 'create' | 'edit',
|
||||
) {
|
||||
const required = fields.filter(
|
||||
(field) => field.required && !(mode === 'edit' && field.type === 'password'),
|
||||
(field) =>
|
||||
field.required && !(mode === 'edit' && field.type === 'password'),
|
||||
);
|
||||
if (required.some((field) => isMissingField(form[field.key]))) return '请填写必填字段';
|
||||
if (required.some((field) => isMissingField(form[field.key])))
|
||||
return '请填写必填字段';
|
||||
const password = fields.find(
|
||||
(field) => field.type === 'password' && !isMissingField(form[field.key]),
|
||||
);
|
||||
if (password && Array.from(String(form[password.key])).length < 6)
|
||||
return '密码长度不能少于 6 个字符';
|
||||
const belowMinimum = fields.find(
|
||||
(field) => field.min != null && Number(form[field.key]) < field.min,
|
||||
);
|
||||
if (belowMinimum) return `${belowMinimum.label}不能小于 ${belowMinimum.min}`;
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -25,6 +25,14 @@ export type ResourceFieldType =
|
||||
| 'select'
|
||||
| 'contract-file';
|
||||
|
||||
/** 父子关联候选配置;筛选仍由服务端按当前配送点范围执行。 */
|
||||
export type ResourceRelationLinkage = {
|
||||
parentKey: string;
|
||||
filterKey: string;
|
||||
requiresParent?: boolean;
|
||||
parentChangeMessage?: string;
|
||||
};
|
||||
|
||||
export type ResourceField = {
|
||||
key: string;
|
||||
label: string;
|
||||
@@ -39,6 +47,20 @@ export type ResourceField = {
|
||||
readonlyRelationText?: boolean;
|
||||
/** 列表中以可悬浮、可复制的紧凑文本展示普通字段。 */
|
||||
listCopyable?: boolean;
|
||||
/** 关联选项优先展示的业务字段。 */
|
||||
relationOptionLabelKey?: string;
|
||||
/** 合同气瓶等关系的组合展示方式。 */
|
||||
relationOptionDisplay?: 'product-name-type';
|
||||
/** 为默认候选追加中文标识,并在加载完成后自动选择。 */
|
||||
relationOptionDefaultKey?: string;
|
||||
relationAutoSelectKey?: string;
|
||||
/** 关联候选为空时的业务提示。 */
|
||||
relationEmptyText?: string;
|
||||
relationFilters?: Record<string, string>;
|
||||
relationStrictFilter?: boolean;
|
||||
relationInvalidMessage?: string;
|
||||
relationLinkage?: ResourceRelationLinkage;
|
||||
min?: number;
|
||||
options?: Array<{ label: string; value: string | number; disabled?: boolean }>;
|
||||
defaultValue?: string | number | boolean;
|
||||
readonly?: boolean;
|
||||
@@ -530,7 +552,50 @@ const deliveryOverrides: ResourceUiDefinition[] = [
|
||||
]),
|
||||
define('gasorder_contract_revision', '合同修订记录', 'readonly', []),
|
||||
define('product_info', '合同可选气瓶', 'readonly', []),
|
||||
define('gasorder_basic', '配送订单', 'append_only', [f('request_no', { required: true }), relation('gasorder_contract_identity', '/gasorder_contract', true), relation('user_address_identity', '/user_address', true), f('gasorder_contract_product_identities', { required: true, type: 'identity-list', relation: '/gasorder_contract_product' }), f('contact_name', { required: true }), f('contact_phone', { required: true }), f('discount_amount'), f('remark')], 'list', [
|
||||
define('gasorder_basic', '配送订单', 'append_only', [
|
||||
f('request_no', { required: true }),
|
||||
relation('gasorder_contract_identity', '/gasorder_contract', true, {
|
||||
label: '配送合同',
|
||||
placeholder: '请选择当前可履约的生效合同',
|
||||
relationFilters: { candidate: 'order' },
|
||||
relationStrictFilter: true,
|
||||
relationInvalidMessage: '所选配送合同已不可履约,已清空,请重新选择',
|
||||
relationEmptyText: '暂无可下单的生效合同,请先启用或续签配送合同',
|
||||
}),
|
||||
relation('user_address_identity', '/user_address', true, {
|
||||
label: '收货地址',
|
||||
placeholder: '请先选择配送合同,再选择该合同用户的收货地址',
|
||||
relationOptionLabelKey: 'address',
|
||||
relationOptionDefaultKey: 'is_default',
|
||||
relationAutoSelectKey: 'is_default',
|
||||
relationEmptyText: '该合同用户暂无收货地址,请先维护用户地址',
|
||||
relationLinkage: {
|
||||
parentKey: 'gasorder_contract_identity',
|
||||
filterKey: 'gasorder_contract_identity',
|
||||
requiresParent: true,
|
||||
parentChangeMessage: '配送合同已变更,请重新选择该合同用户的收货地址',
|
||||
},
|
||||
}),
|
||||
f('gasorder_contract_product_identities', {
|
||||
label: '合同气瓶',
|
||||
required: true,
|
||||
type: 'identity-list',
|
||||
relation: '/gasorder_contract_product',
|
||||
placeholder: '请输入智能气阀名称、气瓶类型或气瓶编码搜索',
|
||||
relationOptionDisplay: 'product-name-type',
|
||||
relationEmptyText: '该合同暂无可用气瓶,请先为合同绑定气瓶',
|
||||
relationLinkage: {
|
||||
parentKey: 'gasorder_contract_identity',
|
||||
filterKey: 'contract_identity',
|
||||
requiresParent: true,
|
||||
parentChangeMessage: '配送合同已变更,请重新选择该合同可用的气瓶',
|
||||
},
|
||||
}),
|
||||
f('contact_name', { required: true }),
|
||||
f('contact_phone', { required: true }),
|
||||
f('discount_amount', { defaultValue: 0, min: 0 }),
|
||||
f('remark'),
|
||||
], 'list', [
|
||||
{ name: '分配/改派', resource: '/gasorder_basic/:identity/assign', fields: [relation('staff_account_identity', '/staff_account', true), ...reason], visibleFor: { field: 'order_status', values: [16, 18] } },
|
||||
{ name: '回收任务', resource: '/gasorder_basic/:identity/reclaim', danger: true, fields: reason, visibleFor: { field: 'order_status', values: [18] } },
|
||||
{ name: '调整金额', resource: '/gasorder_basic/:identity/adjust-amount', fields: [f('delivery_fee', { required: true }), f('discount_amount', { required: true }), ...reason], visibleFor: { field: 'order_status', values: [16, 18] } },
|
||||
|
||||
Reference in New Issue
Block a user