@@ -45,6 +45,8 @@ type CityOption = { id: string; name: string; code: string };
|
|||||||
type WarehouseOption = {
|
type WarehouseOption = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
cityId?: string;
|
||||||
|
cityName?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
contactName: string;
|
contactName: string;
|
||||||
contactPhone: string;
|
contactPhone: string;
|
||||||
@@ -192,16 +194,13 @@ export default function OrdersPage() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadWarehousesForOrder(order: OrderDetail) {
|
async function loadWarehouses() {
|
||||||
const cityId = order.city?.id || order.cityId;
|
const res = await request<Paginated<WarehouseOption>>(
|
||||||
if (!cityId) {
|
`/admin/city-warehouses?pageSize=${ADMIN_OPTIONS_PAGE_SIZE}&status=ACTIVE`,
|
||||||
setWarehouses([]);
|
).catch(() => null);
|
||||||
return [] as WarehouseOption[];
|
const rows = (res?.items ?? []).filter((w) => !w.status || w.status === 'ACTIVE');
|
||||||
}
|
setWarehouses(rows);
|
||||||
const rows = await request<WarehouseOption[]>(`/admin/cities/${cityId}/warehouses`).catch(() => []);
|
return rows;
|
||||||
const active = rows.filter((w) => !w.status || w.status === 'ACTIVE');
|
|
||||||
setWarehouses(active);
|
|
||||||
return active;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openDetail(id: string) {
|
async function openDetail(id: string) {
|
||||||
@@ -214,16 +213,25 @@ export default function OrdersPage() {
|
|||||||
const res = await request<OrderDetail>(`/admin/orders/${id}`);
|
const res = await request<OrderDetail>(`/admin/orders/${id}`);
|
||||||
setShipTarget(res);
|
setShipTarget(res);
|
||||||
setDetail(res);
|
setDetail(res);
|
||||||
logisticsForm.resetFields();
|
logisticsForm.setFieldsValue({
|
||||||
const rows = await loadWarehousesForOrder(res);
|
logisticsCompany: '',
|
||||||
const preferredWarehouseId = res.fulfillmentWarehouseId || res.fulfillmentWarehouse?.id || rows[0]?.id;
|
trackingNo: '',
|
||||||
const preferred = rows.find((w) => w.id === preferredWarehouseId) || rows[0];
|
manualQueryUrl: '',
|
||||||
|
});
|
||||||
|
const rows = await loadWarehouses();
|
||||||
|
const preferredWarehouseId = res.fulfillmentWarehouseId || res.fulfillmentWarehouse?.id;
|
||||||
|
const preferred =
|
||||||
|
rows.find((w) => w.id === preferredWarehouseId) ||
|
||||||
|
rows.find((w) => w.cityId === (res.city?.id || res.cityId)) ||
|
||||||
|
rows[0];
|
||||||
const defaults = preferred
|
const defaults = preferred
|
||||||
? warehouseToDefaults(preferred, shipDefaults)
|
? warehouseToDefaults(preferred, shipDefaults)
|
||||||
: (shipDefaults ?? await request<ShipDefaults>('/admin/orders/ship-defaults').catch(() => null));
|
: (shipDefaults ?? await request<ShipDefaults>('/admin/orders/ship-defaults').catch(() => null));
|
||||||
if (defaults) {
|
if (defaults) {
|
||||||
setShipDefaults(defaults);
|
setShipDefaults(defaults);
|
||||||
applyShipDefaults(defaults, preferred?.id);
|
applyShipDefaults(defaults, preferred?.id);
|
||||||
|
} else {
|
||||||
|
shipForm.setFieldsValue({ warehouseId: preferred?.id, provider: 'XFX' });
|
||||||
}
|
}
|
||||||
setShipMode(preferred ? 'WAREHOUSE' : 'EXPRESS');
|
setShipMode(preferred ? 'WAREHOUSE' : 'EXPRESS');
|
||||||
setShipModalOpen(true);
|
setShipModalOpen(true);
|
||||||
@@ -242,9 +250,21 @@ export default function OrdersPage() {
|
|||||||
try {
|
try {
|
||||||
const res = await request<OrderDetail>(`/admin/orders/${shipTarget.id}/ship`, {
|
const res = await request<OrderDetail>(`/admin/orders/${shipTarget.id}/ship`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(values),
|
body: JSON.stringify({
|
||||||
|
provider: values.provider || 'XFX',
|
||||||
|
warehouseId: values.warehouseId,
|
||||||
|
weight: values.weight,
|
||||||
|
payMode: values.payMode,
|
||||||
|
remark: values.remark,
|
||||||
|
fromName: values.fromName,
|
||||||
|
fromMobile: values.fromMobile,
|
||||||
|
fromAddress: values.fromAddress,
|
||||||
|
fromAddressDetail: values.fromAddressDetail,
|
||||||
|
fromLng: values.fromLng,
|
||||||
|
fromLat: values.fromLat,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
message.success('仓配发货成功');
|
message.success('选仓发货成功');
|
||||||
setShipTarget(res);
|
setShipTarget(res);
|
||||||
setDetail(res);
|
setDetail(res);
|
||||||
setShipModalOpen(false);
|
setShipModalOpen(false);
|
||||||
@@ -258,12 +278,22 @@ export default function OrdersPage() {
|
|||||||
|
|
||||||
async function submitLogisticsShip() {
|
async function submitLogisticsShip() {
|
||||||
if (!shipTarget) return;
|
if (!shipTarget) return;
|
||||||
const values = await logisticsForm.validateFields();
|
const values = await logisticsForm.validateFields(['logisticsCompany', 'trackingNo', 'manualQueryUrl']);
|
||||||
|
const logisticsCompany = String(values.logisticsCompany ?? '').trim();
|
||||||
|
const trackingNo = String(values.trackingNo ?? '').trim();
|
||||||
|
if (!logisticsCompany || !trackingNo) {
|
||||||
|
message.error('请填写快递公司与运单号');
|
||||||
|
return;
|
||||||
|
}
|
||||||
setLogisticsShipping(true);
|
setLogisticsShipping(true);
|
||||||
try {
|
try {
|
||||||
const res = await request<OrderDetail>(`/admin/orders/${shipTarget.id}/logistics-ship`, {
|
const res = await request<OrderDetail>(`/admin/orders/${shipTarget.id}/logistics-ship`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(values),
|
body: JSON.stringify({
|
||||||
|
logisticsCompany,
|
||||||
|
trackingNo,
|
||||||
|
manualQueryUrl: String(values.manualQueryUrl ?? '').trim() || undefined,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
message.success('快递单已录入');
|
message.success('快递单已录入');
|
||||||
setShipTarget(res);
|
setShipTarget(res);
|
||||||
@@ -593,13 +623,14 @@ export default function OrdersPage() {
|
|||||||
open={shipModalOpen}
|
open={shipModalOpen}
|
||||||
onCancel={() => setShipModalOpen(false)}
|
onCancel={() => setShipModalOpen(false)}
|
||||||
width={560}
|
width={560}
|
||||||
destroyOnClose
|
destroyOnClose={false}
|
||||||
|
forceRender
|
||||||
footer={
|
footer={
|
||||||
<Space>
|
<Space>
|
||||||
<Button onClick={() => setShipModalOpen(false)}>取消</Button>
|
<Button onClick={() => setShipModalOpen(false)}>取消</Button>
|
||||||
{shipMode === 'WAREHOUSE' ? (
|
{shipMode === 'WAREHOUSE' ? (
|
||||||
<Button type="primary" loading={shipping} onClick={() => void submitShip()}>
|
<Button type="primary" loading={shipping} onClick={() => void submitShip()}>
|
||||||
确认仓配发货
|
确认选仓发货
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button type="primary" loading={logisticsShipping} onClick={() => void submitLogisticsShip()}>
|
<Button type="primary" loading={logisticsShipping} onClick={() => void submitLogisticsShip()}>
|
||||||
@@ -629,21 +660,26 @@ export default function OrdersPage() {
|
|||||||
buttonStyle="solid"
|
buttonStyle="solid"
|
||||||
style={{ marginBottom: 16 }}
|
style={{ marginBottom: 16 }}
|
||||||
options={[
|
options={[
|
||||||
{ value: 'WAREHOUSE', label: '仓库配送(小飞侠)' },
|
{ value: 'WAREHOUSE', label: '选仓配送' },
|
||||||
{ value: 'EXPRESS', label: '填写快递单号' },
|
{ value: 'EXPRESS', label: '填写快递单号' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{shipMode === 'WAREHOUSE' ? (
|
<div style={{ display: shipMode === 'WAREHOUSE' ? 'block' : 'none' }}>
|
||||||
<Form form={shipForm} layout="vertical" size="small">
|
<Form form={shipForm} layout="vertical" size="small" preserve>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="warehouseId"
|
name="warehouseId"
|
||||||
label="履约仓库"
|
label="选择仓库"
|
||||||
rules={[{ required: true, message: '请选择仓库' }]}
|
rules={[{ required: true, message: '请选择仓库' }]}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
placeholder={warehouses.length ? '选择仓库' : '该城市暂无仓库'}
|
showSearch
|
||||||
options={warehouses.map((w) => ({ value: w.id, label: w.name }))}
|
optionFilterProp="label"
|
||||||
|
placeholder={warehouses.length ? '选择仓库' : '暂无可用仓库'}
|
||||||
|
options={warehouses.map((w) => ({
|
||||||
|
value: w.id,
|
||||||
|
label: w.cityName ? `${w.cityName} · ${w.name}` : w.name,
|
||||||
|
}))}
|
||||||
onChange={(id) => void onWarehouseChange(id)}
|
onChange={(id) => void onWarehouseChange(id)}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -696,22 +732,32 @@ export default function OrdersPage() {
|
|||||||
}]}
|
}]}
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
) : (
|
</div>
|
||||||
<Form form={logisticsForm} layout="vertical" size="small">
|
|
||||||
|
<div style={{ display: shipMode === 'EXPRESS' ? 'block' : 'none' }}>
|
||||||
|
<Form form={logisticsForm} layout="vertical" size="small" preserve>
|
||||||
<Typography.Paragraph type="secondary" style={{ fontSize: 12, marginTop: 0 }}>
|
<Typography.Paragraph type="secondary" style={{ fontSize: 12, marginTop: 0 }}>
|
||||||
与仓库端手动发货一致:填写快递公司与运单号即可完成发货
|
填写快递公司与运单号即可完成发货
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
<Form.Item name="logisticsCompany" label="快递公司" rules={[{ required: true, message: '请填写快递公司' }]}>
|
<Form.Item
|
||||||
|
name="logisticsCompany"
|
||||||
|
label="快递公司"
|
||||||
|
rules={[{ required: true, message: '请填写快递公司' }]}
|
||||||
|
>
|
||||||
<Input placeholder="如 顺丰速运、京东物流" />
|
<Input placeholder="如 顺丰速运、京东物流" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="trackingNo" label="运单号" rules={[{ required: true, message: '请填写运单号' }]}>
|
<Form.Item
|
||||||
<Input />
|
name="trackingNo"
|
||||||
|
label="运单号"
|
||||||
|
rules={[{ required: true, message: '请填写运单号' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入运单号" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="manualQueryUrl" label="物流查询链接(可选)">
|
<Form.Item name="manualQueryUrl" label="物流查询链接(可选)">
|
||||||
<Input placeholder="https://..." />
|
<Input placeholder="https://..." />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -135,10 +135,10 @@ export class AdminOrdersService {
|
|||||||
if (dto.warehouseId) {
|
if (dto.warehouseId) {
|
||||||
const warehouseId = BigInt(dto.warehouseId);
|
const warehouseId = BigInt(dto.warehouseId);
|
||||||
const warehouseRow = await this.prisma.cityWarehouse.findFirst({
|
const warehouseRow = await this.prisma.cityWarehouse.findFirst({
|
||||||
where: { id: warehouseId, cityId: order.cityId, status: 'ACTIVE' },
|
where: { id: warehouseId, status: 'ACTIVE' },
|
||||||
});
|
});
|
||||||
if (!warehouseRow) {
|
if (!warehouseRow) {
|
||||||
throw new BadRequestException('仓库不存在或不属于该订单开城城市');
|
throw new BadRequestException('仓库不存在或已停用');
|
||||||
}
|
}
|
||||||
if (order.fulfillmentWarehouseId !== warehouseId) {
|
if (order.fulfillmentWarehouseId !== warehouseId) {
|
||||||
await this.prisma.order.update({
|
await this.prisma.order.update({
|
||||||
|
|||||||
Reference in New Issue
Block a user