Commit b8e29c89f5acc2eb651ebfbdd22e42b81603eb72

Authored by 莫红玲
2 parents 35f1810a 1bb317d1

Merge remote-tracking branch 'origin/master' into fvm_ticket

Showing 28 changed files with 1236 additions and 355 deletions
src/common/api.ts
... ... @@ -213,6 +213,16 @@ export function queryShopBizType(): http.PromiseResp<CommonApi.ShopRpTypeVO[]> {
213 213 return request.get(`${OOP_HOST}/select/shopBizType`);
214 214 }
215 215  
  216 +export interface GetReimburseRpTypesReq {
  217 + businessTypes?: string
  218 +}
  219 +/**
  220 + * 报销款项列表
  221 + */
  222 +export function getReimburseRpTypes(params: GetReimburseRpTypesReq): http.PromiseResp<CommonApi.RPTypeVO[]> {
  223 + return request.get(`${FINANCE2_HOST}/common/reimburseRpTypes`, { params });
  224 +}
  225 +
216 226 /**
217 227 * 批量获取角色所在角色组信息
218 228 * @param params 角色编码,多个角色英文逗号分割
... ...
src/pages/approval/ApprovalSetting/subpages/components/DefaultFlowNewOrEdit.tsx
... ... @@ -85,7 +85,8 @@ export default function DefaultSettingNewOrEdit(props: Props) {
85 85 // idOrCode to idOrCode__bizType
86 86 newCondVals.forEach((n) => {
87 87 if (n.flowTriggerDto.type === TriggerType.门店) {
88   - const value = JSON.parse(n.value).map((i: FlowSetting.CondValItem) => ({
  88 + // 注意 || 和 ?? 的区别
  89 + const value = JSON.parse(n.value || '[]').map((i: FlowSetting.CondValItem) => ({
89 90 idOrCode: `${i.idOrCode}__${i.bizType}`,
90 91 name: i.name,
91 92 bizType: i.bizType,
... ... @@ -120,7 +121,7 @@ export default function DefaultSettingNewOrEdit(props: Props) {
120 121 const bizTypeSet = new Set<number>();
121 122 shopCondVals.forEach((s) => {
122 123 try {
123   - const options = JSON.parse(s.value);
  124 + const options = JSON.parse(s.value || '[]');
124 125 options.forEach((o: FlowSetting.CondValItem) => {
125 126 bizTypeSet.add(o.bizType!);
126 127 });
... ... @@ -135,7 +136,7 @@ export default function DefaultSettingNewOrEdit(props: Props) {
135 136 const bizTypeSet = new Set<number>();
136 137 shopCondVals.forEach((s) => {
137 138 try {
138   - const options = JSON.parse(s.value);
  139 + const options = JSON.parse(s.value || '[]');
139 140 options.forEach((o: FlowSetting.CondValItem) => {
140 141 bizTypeSet.add(o.bizType!);
141 142 });
... ... @@ -162,7 +163,7 @@ export default function DefaultSettingNewOrEdit(props: Props) {
162 163 // idOrCode__bizType to idOrCode
163 164 conditionVals.forEach((n) => {
164 165 if (n.flowTriggerDto.type === TriggerType.门店) {
165   - const value = JSON.parse(n.value).map((i: FlowSetting.CondValItem) => ({
  166 + const value = JSON.parse(n.value || '[]').map((i: FlowSetting.CondValItem) => ({
166 167 idOrCode: i.idOrCode.split('__')[0]!,
167 168 name: i.name,
168 169 bizType: i.bizType,
... ... @@ -347,7 +348,7 @@ export default function DefaultSettingNewOrEdit(props: Props) {
347 348 conditionVals.forEach((c) => {
348 349 const { flowTriggerDto: fd, value: v } = c;
349 350 const { type: t } = fd ?? {}; // 触发条件信息
350   - const oData = JSON.parse(v || '{}');
  351 + const oData = JSON.parse(v || '[]');
351 352 if (t === TriggerType.报销类型) {
352 353 rpTypes = oData.length > 0 ? oData.map((i: ApprovalSetting.CondValItem) => i.idOrCode) : [];
353 354 } else {
... ...
src/pages/approval/FlowSetting/subpages/ConditionSetting/components/CustomFlowNewOrEdit.tsx
... ... @@ -102,7 +102,8 @@ export default function CustomFlowNewOrEdit(props: Props) {
102 102 // idOrCode to idOrCode__bizType
103 103 newCondVals.forEach((n) => {
104 104 if (n.flowTriggerDto.type === TriggerType.门店) {
105   - const value = JSON.parse(n.value).map((i: FlowSetting.CondValItem) => ({
  105 + // 注意 || 和 ?? 的区别
  106 + const value = JSON.parse(n.value || '[]').map((i: FlowSetting.CondValItem) => ({
106 107 idOrCode: `${i.idOrCode}__${i.bizType}`,
107 108 name: i.name,
108 109 bizType: i.bizType,
... ... @@ -138,7 +139,7 @@ export default function CustomFlowNewOrEdit(props: Props) {
138 139 const bizTypeSet = new Set<number>();
139 140 shopCondVals.forEach((s) => {
140 141 try {
141   - const options = JSON.parse(s.value);
  142 + const options = JSON.parse(s.value || '[]');
142 143 options.forEach((o: FlowSetting.CondValItem) => {
143 144 bizTypeSet.add(o.bizType!);
144 145 });
... ... @@ -153,7 +154,7 @@ export default function CustomFlowNewOrEdit(props: Props) {
153 154 const bizTypeSet = new Set<number>();
154 155 shopCondVals.forEach((s) => {
155 156 try {
156   - const options = JSON.parse(s.value);
  157 + const options = JSON.parse(s.value || '[]');
157 158 options.forEach((o: FlowSetting.CondValItem) => {
158 159 bizTypeSet.add(o.bizType!);
159 160 });
... ... @@ -181,7 +182,7 @@ export default function CustomFlowNewOrEdit(props: Props) {
181 182 // idOrCode__bizType to idOrCode
182 183 conditionVals.forEach((n) => {
183 184 if (n.flowTriggerDto.type === TriggerType.门店) {
184   - const value = JSON.parse(n.value).map((i: FlowSetting.CondValItem) => ({
  185 + const value = JSON.parse(n.value || '[]').map((i: FlowSetting.CondValItem) => ({
185 186 idOrCode: i.idOrCode.split('__')[0]!,
186 187 name: i.name,
187 188 bizType: i.bizType,
... ... @@ -360,7 +361,7 @@ export default function CustomFlowNewOrEdit(props: Props) {
360 361 conditionVals.forEach((c) => {
361 362 const { flowTriggerDto: fd, value: v } = c;
362 363 const { type: t } = fd ?? {}; // 触发条件信息
363   - const oData = JSON.parse(v || '{}');
  364 + const oData = JSON.parse(v || '[]');
364 365 if (t === TriggerType.报销类型) {
365 366 rpTypes = oData.length > 0 ? oData.map((i: FlowSetting.CondValItem) => i.idOrCode) : [];
366 367 } else {
... ...
src/pages/cas/ClaimConfirmation/components/DetailModal.tsx
1 1 import React, { useEffect, useState } from 'react';
2 2 import { Descriptions, Form, Input, message, Modal, Popconfirm, Select, Spin, Table } from 'antd';
3 3 import moment from 'moment';
  4 +import {isEmpty} from "lodash";
4 5 import { ColumnsType } from 'antd/es/table';
5 6  
6 7 import { formatPartCnt } from '../../utils';
... ... @@ -119,7 +120,7 @@ export default function DetailMOdal({ current, visible, setVisible, setLoading:
119 120 dataIndex: 'totalPrice',
120 121 title: '材料费',
121 122 align: 'center',
122   - render: (totalPrice: number) => rmb.p(totalPrice),
  123 + render: (totalPrice: number) => isNaN(totalPrice) ? '--' : rmb.p(totalPrice ?? 0),
123 124 },
124 125 {
125 126 dataIndex: 'partName',
... ... @@ -357,7 +358,7 @@ export default function DetailMOdal({ current, visible, setVisible, setLoading:
357 358 {(detail && detail.consultantDesc) || '--'}
358 359 </DescriptionItem>
359 360 <DescriptionItem label="附件" span={3}>
360   - <ImageModal title="查看附件" fids={detail?.fids || []} />
  361 + <ImageModal title="查看附件" fids={isEmpty(detail?.fids) ? [] : detail?.fids} />
361 362 </DescriptionItem>
362 363 </Descriptions>
363 364  
... ...
src/pages/cas/workOrder/PartLackHandle/api.ts
1 1 import { http } from '@/typing/http';
2 2 import request from '@/utils/request';
3 3 import { CAS_HOST, PMS_HOST } from '@/utils/host';
  4 +import { AreaStockEnum, MethodEnum } from '@/pages/cas/workOrder/PartLackHandle/entity';
4 5  
5 6 type P<T> = http.PromiseResp<T>;
6 7 type Page<T> = http.PromisePageResp<T>;
7 8  
8   -interface ListParams {
9   - groupId?: number;
10   - shopId?: number;
11   - userId?: number;
12   - userName?: string;
13   - pageSize?: number;
14   - current?: number;
15   - keywords?: string;
16   -}
17   -
18   -export interface ListResult {
19   - dataId: number; // 缺件单id
20   - orderNo: number; // 工单号
21   - plateNo: number; // 车牌号
22   - ownerName: number; // 车主
23   - carName: number; // 车辆名称
24   - vin: number; // 车架号
25   - specCode: number; // 车型代码
26   - shopName: number; // 在修门店
27   - receiverName: number; // 服务顾问
28   - senderTime: number; // 进站时间
29   - serviceCatName: number; // 进站类型
30   -}
31   -
32   -export interface DetailResult {
33   - lackListId: number;
34   - quoteId: number;
35   - ownerName: string;
36   - phone: number;
37   - brandId: number;
38   - plateNo: string;
39   - carName: string;
40   - vin: string;
41   - specCode: string;
42   - senderTime: number;
43   - consultant: string;
44   - orderNo: string;
45   - orderId: number;
46   - shopId: number;
47   - shopName: string;
48   - groupId: number;
49   - lackStatus: number;
50   - items: Part[];
51   -}
52   -
53   -export interface TransferItem {
54   - outStorageId: number;
55   - outStorageName: string;
56   - outShopId: number;
57   - lackNum: number;
58   -}
59   -
60   -export interface Part {
61   - lackPartId: number;
62   - itemId: number;
63   - itemName: string;
64   - quotePartId: number;
  9 +/** 缺件处理配件必备基础信息 */
  10 +export interface LackPart {
65 11 partId: number;
66 12 partCode: string;
67 13 partName: string;
68 14 lackNum: number;
69   - processingMethod?: number; // 处理方式 1: 区域库内调件 2: 厂家采购 3:外部采购 4:区域库外调件 5:计划员处理
70   - processingMethodName?: string; // 处理方式名称
71   - // processingMethod为1时,有以下字段
72   - shopId?: number;
73   - outStorageId?: number;
74   - outStorageName?: string;
75   - outDividedBy?: number;
76   - outDivision?: number;
77   - inDividedBy?: number;
78   - inDivision?: number;
  15 + methods?: Method[];
  16 + oldPart?: LackPart;
79 17  
80   - // todo confirm to del
81   - expectedDate?: number; // 预计需要的入库天数
82   - transferItems?: TransferItem[]; // 顾问选择调件数据
83   - // @custom
84   - oldPart?: Part;
85   - methods?: MethodType[];
86   - // @custom 暂存区域库存信息:用于批量处理时展示
87   - isArea?: boolean; // 是否区域库内
88   - outStock?: number; // 区域库外库存
  18 + [key: string]: any;
89 19 }
90 20  
91   -export interface SavePart extends Part {
92   - method?: number;
  21 +/** 缺件处理方式 */
  22 +export interface Method {
  23 + method: MethodEnum;
  24 + num: number;
  25 + expectedDate?: number;
93 26 shopId?: number;
94 27 outStorageId?: number;
95 28 outStorageName?: string;
96 29 outDividedBy?: number;
97 30 outDivision?: number;
98   - oldPartId?: number;
99   - oldPartCode?: string;
100   - oldPartName?: string;
  31 + inDividedBy?: number;
  32 + inDivision?: number;
101 33 }
102 34  
103 35 export interface MethodType {
... ... @@ -119,35 +51,17 @@ export interface MethodType {
119 51 num: number;
120 52 }
121 53  
122   -export interface SaveParams {
123   - dataId: number;
124   - userId?: number;
125   - userName?: string;
126   - groupId?: number;
127   - purchase?: SavePart[];
128   - transfer?: SavePart[];
129   - book?: SavePart[];
130   -}
131   -
132   -// 列表
133   -export function listApi(params: ListParams): Page<ListResult> {
134   - return request.get(`${CAS_HOST}/app/part/lack/list/planner`, { params });
135   -}
136   -
137   -// 详情
138   -export function detailApi(dataId: number): P<DetailResult> {
139   - return request.get(`${CAS_HOST}/app/part/lack/list/detail/planner`, { params: { dataId } });
140   -}
141   -
142   -// 保存
143   -export function saveApi(params: SaveParams) {
144   - return request.post(`${CAS_HOST}/app/part/lack/confirm/planner`, params);
  54 +export interface TransferItem {
  55 + outStorageId: number;
  56 + outStorageName: string;
  57 + outShopId: number;
  58 + lackNum: number;
145 59 }
146 60  
147 61 export interface PartTransferParams {
148 62 bizShopId: number; //服务站
149 63 partId?: number; //配件ID
150   - type: number; //类型 1 区域库内 2 区域库外
  64 + type: AreaStockEnum; //类型 1 区域库内 2 区域库外
151 65 }
152 66  
153 67 /**
... ...
src/pages/cas/workOrder/PartLackHandle/components/BatchHandler.tsx
1 1 import React, { useEffect, useMemo, useState } from 'react';
2 2 import { Form, InputNumber, Modal, Select, Table } from 'antd';
3 3  
4   -import { BatchMethodData, MethodEnum } from '../entity';
5   -import type { MethodType, Part } from '../api';
6   -import { getPartsTransferStorageApi } from '../api';
  4 +import { BatchMethodData, MethodEnum } from '@/pages/cas/workOrder/PartLackHandle/entity';
  5 +import type { Method, LackPart } from '@/pages/cas/workOrder/PartLackHandle/api';
  6 +import { getPartsTransferStorageApi } from '@/pages/cas/workOrder/PartLackHandle/api';
7 7  
8 8 import st from '@/pages/cas/style.less';
9 9 import { ColumnsType } from 'antd/es/table';
... ... @@ -25,9 +25,9 @@ const formLayout = {
25 25 interface Props {
26 26 open: boolean;
27 27 shopId: number;
28   - parts: Part[];
  28 + parts: LackPart[];
29 29 onCancel: () => void;
30   - onConfirm: (selectParts: Part[]) => void;
  30 + onConfirm: (selectParts: LackPart[]) => void;
31 31 }
32 32  
33 33 /**
... ... @@ -37,9 +37,9 @@ export default function BatchHandler({ open, shopId, parts = [], onCancel, onCon
37 37 const [form] = Form.useForm();
38 38 const selectMethod = Form.useWatch('method', form);
39 39  
40   - const [selectParts, setSelectParts] = useState<Part[]>([]);
  40 + const [selectParts, setSelectParts] = useState<LackPart[]>([]);
41 41 // const [retainParts, setRetainParts] = useState<Part[]>([]);
42   - const [filteredList, setFilteredList] = useState<Part[]>([]);
  42 + const [filteredList, setFilteredList] = useState<LackPart[]>([]);
43 43  
44 44 const partIds = useMemo(() => parts.map((part) => part.partId), [parts]);
45 45 const isDayRequired = [MethodEnum.TRANSFER_OUTSIDE, MethodEnum.SUBSCRIBE].includes(Number(selectMethod));
... ... @@ -91,7 +91,7 @@ export default function BatchHandler({ open, shopId, parts = [], onCancel, onCon
91 91  
92 92 // const retainParts: Part[] = [];
93 93 // 需处理配件
94   - const needHandleParts: Part[] = [];
  94 + const needHandleParts: LackPart[] = [];
95 95 parts.forEach((part) => {
96 96 // @ts-ignore
97 97 if (!part.isArea && !part.methods) {
... ... @@ -109,7 +109,7 @@ export default function BatchHandler({ open, shopId, parts = [], onCancel, onCon
109 109 }
110 110 }, [partsStocks]);
111 111  
112   - const columns: ColumnsType<Part> = [
  112 + const columns: ColumnsType<LackPart> = [
113 113 {
114 114 title: '配件名称',
115 115 dataIndex: 'partName',
... ... @@ -141,7 +141,7 @@ export default function BatchHandler({ open, shopId, parts = [], onCancel, onCon
141 141  
142 142 const handleSubmit = (values: any) => {
143 143 const finalParts = selectParts.map((part) => {
144   - const item: MethodType = {
  144 + const item: Method = {
145 145 method: Number(values.method),
146 146 num: part.lackNum,
147 147 };
... ... @@ -203,7 +203,7 @@ export default function BatchHandler({ open, shopId, parts = [], onCancel, onCon
203 203 rowSelection={{
204 204 type: 'checkbox',
205 205 selectedRowKeys: selectParts.map((si) => si.partId) || [],
206   - onChange: (selectedRowKeys, selectedRows: Part[]) => {
  206 + onChange: (selectedRowKeys, selectedRows: LackPart[]) => {
207 207 setSelectParts(selectedRows);
208 208 },
209 209 }}
... ...
src/pages/cas/workOrder/PartLackHandle/components/EditMethod.tsx
... ... @@ -2,8 +2,8 @@ import React, { useEffect, useState } from &#39;react&#39;;
2 2 import { Form, InputNumber, Modal, Select } from 'antd';
3 3 import { isEmpty } from 'lodash';
4 4  
5   -import { MethodData, MethodEnum } from '../entity';
6   -import type { MethodType, Stock, StorageResult } from '../api';
  5 +import { MethodData, MethodEnum } from '@/pages/cas/workOrder/PartLackHandle/entity';
  6 +import type { Method, Stock, StorageResult } from '@/pages/cas/workOrder/PartLackHandle/api';
7 7  
8 8 const FormItem = Form.Item;
9 9 const Option = Select.Option;
... ... @@ -21,10 +21,10 @@ const formLayout = {
21 21 interface Props {
22 22 open: boolean;
23 23 lackNum: number; // 缺件数量
24   - method: MethodType; // 处理方案
  24 + method: Method; // 处理方案
25 25 warehouses: { inStorage: StorageResult; outStorage: StorageResult; inRatioThreshold: number }; // 库房信息
26 26 onCancel: () => void;
27   - onConfirm: (method: MethodType) => void;
  27 + onConfirm: (method: Method) => void;
28 28 }
29 29  
30 30 export default function EditMethod({ open, lackNum = 0, method, warehouses, onCancel, onConfirm }: Props) {
... ... @@ -46,7 +46,7 @@ export default function EditMethod({ open, lackNum = 0, method, warehouses, onCa
46 46 setNeedNum(lackNum);
47 47  
48 48 /**
49   - * 添加时
  49 + * 新增
50 50 * 默认选择条件:同时满足
51 51 * 1.区域库内有库存,且大于等于缺件数量
52 52 * 2.区域库内有库销比最高的仓库
... ... @@ -75,14 +75,21 @@ export default function EditMethod({ open, lackNum = 0, method, warehouses, onCa
75 75 }
76 76 }
77 77  
78   - // 编辑
  78 + // 编辑
79 79 if (!isEmpty(method)) {
  80 + // 初始化表单信息
80 81 form.setFieldsValue({
81 82 method: method.method ? Number(method.method) : undefined,
82 83 outStorageId: `${method.outStorageId || ''}`,
83 84 expectedDate: method.expectedDate || '',
84 85 num: method.num,
85 86 });
  87 + // 初始化选中仓库
  88 + if (method.outStorageId) {
  89 + const stocks = Number(method.method) === MethodEnum.TRANSFER_INSIDE ? inStocks : outStocks;
  90 + const selectStock = stocks.filter((item) => item.storageId === Number(method.outStorageId))[0];
  91 + setSelectStock({ ...selectStock });
  92 + }
86 93 }
87 94 }
88 95 }, [open, lackNum, method, inStocks]);
... ... @@ -100,11 +107,11 @@ export default function EditMethod({ open, lackNum = 0, method, warehouses, onCa
100 107 const params = { ...values };
101 108 // 区域库内/外调件,需要设置仓库信息
102 109 if (isTransferInside || isTransferOutside) {
103   - const storageType = isTransferInside ? 'inStorage' : 'outStorage';
  110 + const distStorage = warehouses[isTransferInside ? 'inStorage' : 'outStorage'];
104 111  
105   - params.inDivision = warehouses[storageType].inDivision;
106   - params.inDivisionBy = warehouses[storageType].inDividedBy;
107   - params.shopId = warehouses[storageType].shopId;
  112 + params.inDivision = distStorage.inDivision;
  113 + params.inDivisionBy = distStorage.inDividedBy;
  114 + params.shopId = distStorage.shopId;
108 115  
109 116 params.outDividedBy = selectStock.outDividedBy;
110 117 params.outDivision = selectStock.outDivision;
... ...
src/pages/cas/workOrder/PartLackHandle/components/PartSelector.tsx
... ... @@ -5,8 +5,8 @@ import { CheckCircleOutlined } from &#39;@ant-design/icons&#39;;
5 5 import { debounce } from 'lodash';
6 6  
7 7 import usePagination from '@/hooks/usePagination';
8   -import type { WorkItemPart } from '../api';
9   -import { getWorkItemPartsApi } from '../api';
  8 +import type { WorkItemPart } from '@/pages/cas/workOrder/PartLackHandle/api';
  9 +import { getWorkItemPartsApi } from '@/pages/cas/workOrder/PartLackHandle/api';
10 10  
11 11 import st from '@/pages/cas/style.less';
12 12  
... ...
src/pages/cas/workOrder/PartLackHandle/components/SingleHandler.tsx
... ... @@ -4,9 +4,9 @@ import { PlusOutlined } from &#39;@ant-design/icons&#39;;
4 4  
5 5 import useInitial from '@/hooks/useInitail';
6 6  
7   -import { MethodNameEnum } from '../entity';
8   -import type { MethodType, Part, Stock, StorageResult } from '../api';
9   -import { getPartAreaTransferThresholdApi, getPartTransferStorageApi } from '../api';
  7 +import { AreaStockEnum, MethodNameEnum } from '@/pages/cas/workOrder/PartLackHandle/entity';
  8 +import type { LackPart, Method, Stock, StorageResult } from '@/pages/cas/workOrder/PartLackHandle/api';
  9 +import { getPartAreaTransferThresholdApi, getPartTransferStorageApi } from '@/pages/cas/workOrder/PartLackHandle/api';
10 10  
11 11 import EditMethod from './EditMethod';
12 12  
... ... @@ -16,9 +16,9 @@ const Column = Table.Column;
16 16 interface Props {
17 17 open: boolean;
18 18 shopId: number;
19   - part: Part;
  19 + part: LackPart;
20 20 onCancel: () => void;
21   - onConfirm: (newPart: Part) => void;
  21 + onConfirm: (newPart: LackPart) => void;
22 22 }
23 23  
24 24 /**
... ... @@ -27,6 +27,7 @@ interface Props {
27 27 export default function SingleHandler({ open, shopId, part, onCancel, onConfirm }: Props) {
28 28 const [delay, setDelay] = useState(true);
29 29  
  30 + // 区域库内库存情况
30 31 const {
31 32 data: inStorage,
32 33 loading: inLoading,
... ... @@ -37,10 +38,11 @@ export default function SingleHandler({ open, shopId, part, onCancel, onConfirm
37 38 {
38 39 partId: part.partId,
39 40 bizShopId: shopId,
40   - type: 1,
  41 + type: AreaStockEnum.INSIDE,
41 42 },
42 43 delay,
43 44 );
  45 + // 区域库外库存情况
44 46 const {
45 47 data: outStorage,
46 48 loading: outLoading,
... ... @@ -51,15 +53,16 @@ export default function SingleHandler({ open, shopId, part, onCancel, onConfirm
51 53 {
52 54 partId: part.partId,
53 55 bizShopId: shopId,
54   - type: 2,
  56 + type: AreaStockEnum.OUTSIDE,
55 57 },
56 58 delay,
57 59 );
  60 + // 区域库内库销比调件阈值:区域库内有库存,且库销比大于该阈值,则必须选择区域库内调运
58 61 const { data: inRatioThreshold, setParams: setThresholdParams } = useInitial(getPartAreaTransferThresholdApi, 0, part.partId, delay);
59 62  
60 63 const [editOpen, setEditOpen] = useState(false);
61 64 const [currentIndex, setCurrentIndex] = useState(-1);
62   - const [currentMethod, setCurrentMethod] = useState({} as MethodType);
  65 + const [currentMethod, setCurrentMethod] = useState({} as Method);
63 66 const [methods, setMethods] = useState(part.methods || []);
64 67  
65 68 const totalNum = useMemo(() => methods.map((m) => m.num || 0).reduce((prev, curr) => prev + curr, 0), [methods]);
... ... @@ -101,17 +104,17 @@ export default function SingleHandler({ open, shopId, part, onCancel, onConfirm
101 104  
102 105 const handleAdd = () => {
103 106 setCurrentIndex(-1);
104   - setCurrentMethod({} as MethodType);
  107 + setCurrentMethod({} as Method);
105 108 setEditOpen(true);
106 109 };
107 110  
108   - const handleEdit = (method: MethodType, index: number) => {
  111 + const handleEdit = (method: Method, index: number) => {
109 112 setCurrentIndex(index);
110 113 setCurrentMethod(method);
111 114 setEditOpen(true);
112 115 };
113 116  
114   - const handleUpdate = (newMethod: MethodType) => {
  117 + const handleUpdate = (newMethod: Method) => {
115 118 if (currentIndex === -1) {
116 119 methods.push(newMethod); // 新增
117 120 } else {
... ... @@ -119,7 +122,7 @@ export default function SingleHandler({ open, shopId, part, onCancel, onConfirm
119 122 }
120 123 setMethods([...methods]);
121 124 setCurrentIndex(-1);
122   - setCurrentMethod({} as MethodType);
  125 + setCurrentMethod({} as Method);
123 126 setEditOpen(false);
124 127 };
125 128  
... ... @@ -172,7 +175,7 @@ export default function SingleHandler({ open, shopId, part, onCancel, onConfirm
172 175 <Column
173 176 title="处理方案"
174 177 dataIndex="index"
175   - render={(partId, record: MethodType) => (
  178 + render={(partId, record: Method) => (
176 179 <div
177 180 style={{
178 181 display: 'flex',
... ... @@ -184,7 +187,7 @@ export default function SingleHandler({ open, shopId, part, onCancel, onConfirm
184 187 >
185 188 <div style={{ flex: 1.5 }}>调运方式:{MethodNameEnum[record.method]}</div>
186 189 {!!record.outStorageId ? <div style={{ flex: 1.5 }}>调出仓库:{record.outStorageName}</div> : <div style={{ flex: 1.5 }} />}
187   - {!!record.expectedDate ? <div style={{ flex: 1.2 }}>预计入库天数:{record.expectedDate}天</div> : <div style={{ flex: 1 }} />}
  190 + {!!record.expectedDate ? <div style={{ flex: 1.2 }}>预计入库天数:{record.expectedDate}天</div> : <div style={{ flex: 1.2 }} />}
188 191 <div style={{ display: 'flex', flex: 1, justifyContent: 'center' }}>数量:{record.num}</div>
189 192 </div>
190 193 )}
... ... @@ -194,7 +197,7 @@ export default function SingleHandler({ open, shopId, part, onCancel, onConfirm
194 197 width={180}
195 198 align="center"
196 199 dataIndex="lackNum"
197   - render={(val, record: MethodType, index) => (
  200 + render={(val, record: Method, index) => (
198 201 <>
199 202 <Button type="link" onClick={() => handleEdit(record, index)}>
200 203 编辑
... ...
src/pages/cas/workOrder/PartLackHandle/entity.ts
... ... @@ -10,6 +10,8 @@ export enum MethodEnum {
10 10 PURCHASE_BY_THIRD_PARTY = 3,
11 11 /** 区域库外调件 */
12 12 TRANSFER_OUTSIDE = 4,
  13 + /** 计划员处理 */
  14 + PLANNER = 5,
13 15 }
14 16  
15 17 export enum MethodNameEnum {
... ... @@ -48,3 +50,10 @@ export const BatchMethodData = [
48 50 value: MethodEnum.PURCHASE_BY_THIRD_PARTY,
49 51 },
50 52 ];
  53 +
  54 +export enum AreaStockEnum {
  55 + /** 区域库内 */
  56 + INSIDE = 1,
  57 + /** 区域库外 */
  58 + OUTSIDE = 2,
  59 +}
... ...
src/pages/cas/workOrder/PartLackHandle/index.tsx
1   -import React, { useState } from 'react';
  1 +import React from 'react';
2 2 import { PageHeaderWrapper } from '@ant-design/pro-layout';
3   -import { Button, Card, Col, Input, Row, Table } from 'antd';
4   -import type { ColumnsType } from 'antd/lib/table';
5   -import { debounce } from 'lodash';
6   -import dayjs from 'dayjs';
  3 +import { Card, Tabs } from 'antd';
7 4  
8   -import usePagination from '@/hooks/usePagination';
9   -
10   -import { listApi, type ListResult } from './api';
11   -
12   -import PartListModal from './components/PartListModal';
13   -import HandleModal from './components/HandleModal';
14   -
15   -const Search = Input.Search;
  5 +import IndexOrder from './subpages/Order';
  6 +import IndexBook from './subpages/Book';
16 7  
17 8 /**
18   - * 计划员:工单缺件处理
  9 + * 计划员:工单、订件缺件处理
19 10 */
20 11 export default function PartLackHandle() {
21   - const [dataId, setDataId] = useState(0);
22   - const [openParts, setOpenParts] = useState(false);
23   - const [openHandle, setOpenHandle] = useState(false);
24   -
25   - const { list, loading, setParams, setLoading, paginationConfig } = usePagination(listApi, {});
26   -
27   - const handleSearch = debounce((keywords: string) => {
28   - setParams({ keywords }, true);
29   - }, 500);
30   -
31   - const showPartsModal = (dataId: number) => {
32   - setDataId(dataId);
33   - setOpenParts(true);
34   - };
35   -
36   - const showHandleModal = (dataId: number) => {
37   - setDataId(dataId);
38   - setOpenHandle(true);
39   - };
40   -
41   - const columns: ColumnsType<ListResult> = [
42   - {
43   - title: '工单号',
44   - dataIndex: 'orderNo',
45   - fixed: 'left',
46   - width: 100,
47   - },
48   - {
49   - title: '车牌号',
50   - dataIndex: 'plateNo',
51   - fixed: 'left',
52   - width: 70,
53   - },
54   - {
55   - title: '车主',
56   - dataIndex: 'ownerName',
57   - width: 60,
58   - },
59   - {
60   - title: '车辆',
61   - dataIndex: 'carName',
62   - width: 150,
63   - },
  12 + const tabs = [
64 13 {
65   - title: 'VIN',
66   - dataIndex: 'vin',
67   - width: 120,
  14 + label: '工单缺件',
  15 + key: 'order',
  16 + children: <IndexOrder />,
68 17 },
69 18 {
70   - title: '车型代码',
71   - dataIndex: 'specCode',
72   - width: 140,
73   - },
74   - {
75   - title: '在修门店',
76   - dataIndex: 'shopName',
77   - width: 140,
78   - },
79   - {
80   - title: '服务顾问',
81   - dataIndex: 'receiverName',
82   - width: 60,
83   - },
84   - {
85   - title: '进站类型',
86   - dataIndex: 'serviceCatName',
87   - width: 70,
88   - },
89   - {
90   - title: '进站时间',
91   - dataIndex: 'senderTime',
92   - width: 70,
93   - render: (senderTime: string) => dayjs(senderTime).format('YYYY-MM-DD HH:mm:ss'),
94   - },
95   - {
96   - title: '缺件清单',
97   - dataIndex: 'dataId',
98   - fixed: 'right',
99   - width: 70,
100   - align: 'center',
101   - render: (dataId: number) => (
102   - <Button type="link" onClick={() => showPartsModal(dataId)}>
103   - 查看
104   - </Button>
105   - ),
106   - },
107   - {
108   - title: '操作',
109   - dataIndex: 'dataId',
110   - fixed: 'right',
111   - width: 70,
112   - align: 'center',
113   - render: (dataId: number) => (
114   - <Button type="link" onClick={() => showHandleModal(dataId)}>
115   - 处理
116   - </Button>
117   - ),
  19 + label: '非工单缺件',
  20 + key: 'book',
  21 + children: <IndexBook />,
118 22 },
119 23 ];
120 24  
121 25 return (
122   - <PageHeaderWrapper title="工单缺件处理">
  26 + <PageHeaderWrapper title="缺件处理">
123 27 <Card>
124   - <Row justify="space-between" style={{ marginBottom: 16 }}>
125   - <Col>
126   - <Search
127   - style={{ width: 280 }}
128   - placeholder="搜索工单号、车牌、VIN、车主"
129   - allowClear
130   - onChange={(e) => handleSearch(e.target.value)}
131   - onSearch={handleSearch}
132   - />
133   - </Col>
134   - </Row>
135   -
136   - <Table dataSource={list} pagination={paginationConfig} rowKey="orderNo" loading={loading} columns={columns} bordered scroll={{ x: 1900 }} />
  28 + <Tabs items={tabs} />
137 29 </Card>
138   -
139   - <PartListModal
140   - open={openParts}
141   - dataId={dataId}
142   - onCancel={() => {
143   - setDataId(0);
144   - setOpenParts(false);
145   - }}
146   - />
147   -
148   - <HandleModal
149   - open={openHandle}
150   - dataId={dataId}
151   - onCancel={() => {
152   - setDataId(0);
153   - setOpenHandle(false);
154   - }}
155   - onConfirm={() => {
156   - setLoading(true);
157   - setDataId(0);
158   - setOpenHandle(false);
159   - }}
160   - />
161 30 </PageHeaderWrapper>
162 31 );
163 32 }
... ...
src/pages/cas/workOrder/PartLackHandle/subpages/Book/HandleModal.tsx 0 → 100644
  1 +import React, { useEffect, useState } from 'react';
  2 +import { Button, Col, Descriptions, Divider, List, message, Modal, Row, Skeleton, Table } from 'antd';
  3 +import type { ColumnsType } from 'antd/lib/table';
  4 +import { ArrowUpOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
  5 +import moment from 'moment';
  6 +import { cloneDeep, differenceBy, isEmpty } from 'lodash';
  7 +
  8 +import useInitail from '@/hooks/useInitail';
  9 +
  10 +import { MethodEnum, MethodNameEnum } from '@/pages/cas/workOrder/PartLackHandle/entity';
  11 +import type { LackPart, Method, WorkItemPart } from '@/pages/cas/workOrder/PartLackHandle/api';
  12 +import type { BookPart, DetailResult, SaveParams, Type, GetBearTypeInfoReq } from './api';
  13 +import { detailApi, saveApi, getBearTypeInfo } from './api';
  14 +
  15 +import SingleHandler from '@/pages/cas/workOrder/PartLackHandle/components/SingleHandler';
  16 +import BatchHandler from '@/pages/cas/workOrder/PartLackHandle/components/BatchHandler';
  17 +import PartSelector from '@/pages/cas/workOrder/PartLackHandle/components/PartSelector';
  18 +
  19 +import st from '@/pages/cas/style.less';
  20 +
  21 +const DescriptionItem = Descriptions.Item;
  22 +
  23 +interface Props {
  24 + open: boolean;
  25 + dataId: number;
  26 + onCancel: () => any;
  27 + onConfirm: () => any;
  28 +}
  29 +
  30 +export default function HandleModal({ open, dataId, onCancel, onConfirm }: Props) {
  31 + const [delay, setDelay] = useState(true);
  32 + const { data, loading, setParams } = useInitail(detailApi, {} as DetailResult, dataId, delay);
  33 +
  34 + const [parts, setParts] = useState<LackPart[]>([]);
  35 + const [batchParts, setBatchParts] = useState([] as LackPart[]);
  36 + const [currentIndex, setCurrentIndex] = useState(0);
  37 + const [currentRecord, setCurrentRecord] = useState({} as LackPart);
  38 + const [singleOpen, setSingleOpen] = useState(false);
  39 + const [batchOpen, setBatchOpen] = useState(false);
  40 + const [partSelectorOpen, setPartSelectorOpen] = useState(false);
  41 + const [submitting, setSubmitting] = useState(false);
  42 +
  43 + useEffect(() => {
  44 + if (open && dataId) {
  45 + setDelay(false);
  46 + setParams(dataId, true);
  47 + }
  48 + }, [open, dataId]);
  49 +
  50 + useEffect(() => {
  51 + if (data.parts) {
  52 + const initParts: LackPart[] = [];
  53 + data.parts.forEach((part) => {
  54 + // 顾问选择的区域库内调件,初始化为 method 信息,否则不添加 methods 字段,需要计划员新添加处理方式
  55 + if (part.del || isEmpty(part.types)) {
  56 + return;
  57 + }
  58 +
  59 + // 提取基础数据,用于缺件处理
  60 + const lackPart: LackPart = {
  61 + partId: part.partId,
  62 + partCode: part.partCode,
  63 + partName: part.partName,
  64 + lackNum: part.partCnt,
  65 + };
  66 +
  67 + // 需计划员处理:不初始化 methods 字段,手动添加处理方式
  68 + if (part.types.some((item) => item.type === MethodEnum.PLANNER)) {
  69 + initParts.push(lackPart);
  70 + return;
  71 + }
  72 + // 顾问已处理:初始化,计划员可修改
  73 + lackPart.methods = part.types.map((item) => ({
  74 + ...item,
  75 + method: item.type,
  76 + num: item.quantity,
  77 + }));
  78 + initParts.push(lackPart);
  79 + });
  80 + setParts([...initParts]);
  81 + }
  82 + }, [data]);
  83 +
  84 + // 批量处理
  85 + const showBatchHandleModal = () => {
  86 + // todo 区域库内有件的配件,不能批量处理,web是否继续保持该规则,需要时再次过滤
  87 + const unhandledParts = parts.filter((part) => isEmpty(part.methods));
  88 + setBatchParts(unhandledParts);
  89 + setBatchOpen(true);
  90 + };
  91 +
  92 + // 批量更新处理完的配件
  93 +
  94 + const handleBatchUpdate = (newBatchParts: LackPart[]) => {
  95 + const notBatchParts = differenceBy(parts, newBatchParts, 'partId');
  96 + setParts([...notBatchParts, ...newBatchParts]);
  97 + setBatchOpen(false);
  98 + };
  99 + // 单个编辑
  100 +
  101 + const handlePartEdit = (record: LackPart, index: number) => {
  102 + setCurrentIndex(index);
  103 + setCurrentRecord(record);
  104 + setSingleOpen(true);
  105 + };
  106 + // 单个更新
  107 +
  108 + const handlePartUpdate = (newPart: LackPart, index: number) => {
  109 + parts[index] = newPart;
  110 + setParts([...parts]);
  111 + setSingleOpen(false);
  112 + };
  113 + // 显示变更配件
  114 +
  115 + const handlePartChange = (record: LackPart, index: number) => {
  116 + setCurrentIndex(index);
  117 + setCurrentRecord(record);
  118 + setPartSelectorOpen(true);
  119 + };
  120 +
  121 + // 变更配件
  122 + const handlePartReplace = (newPart: WorkItemPart, index: number) => {
  123 + const oldPart = cloneDeep(parts[index]);
  124 + // 使用新配件替换原配件
  125 + parts[index] = {
  126 + // new part info
  127 + partId: newPart.partId,
  128 + partName: newPart.partName,
  129 + partCode: newPart.partCode,
  130 + // old part info
  131 + lackNum: oldPart.lackNum,
  132 + oldPart, // retain old part info for rollback
  133 + };
  134 + setParts([...parts]);
  135 + setPartSelectorOpen(false);
  136 + };
  137 +
  138 + const checkPartPrice = (newPart: WorkItemPart, index: number) => {
  139 + const params: GetBearTypeInfoReq = {
  140 + vin: data.vin,
  141 + shopId: data.shopId,
  142 + draftId: data.id,
  143 + parts: [{ partId: newPart.partId, partCnt: newPart.partCnt }],
  144 + }
  145 + getBearTypeInfo(params).then((res) => {
  146 + const partInfo = res.data?.parts[0];
  147 + if (!partInfo?.bookPrice || partInfo?.bookPrice <= 0) {
  148 + Modal.confirm({
  149 + title: '提示',
  150 + icon: <ExclamationCircleOutlined />,
  151 + content: '该配件没有售价,请先导入加权成本价或配置指定价格',
  152 + okText: '确认',
  153 + cancelText: '取消',
  154 + });
  155 + } else {
  156 + // 执行替换配件
  157 + handlePartReplace(newPart, index);
  158 + }
  159 + });
  160 + };
  161 +
  162 + // 撤销变更
  163 + const handlePartRollback = (index: number) => {
  164 + const oldPart = cloneDeep(parts[index].oldPart);
  165 + parts.splice(index, 1, oldPart!);
  166 + setParts([...parts]);
  167 + };
  168 +
  169 + function resetPageData() {
  170 + setParts([]);
  171 + setBatchParts([]);
  172 + }
  173 +
  174 + function assembleParts() {
  175 + // 配件对应处理方式数据
  176 + const partMethodMap: { [key: string]: Type[] } = {};
  177 + parts.forEach((part) => {
  178 + // 组装数据
  179 + partMethodMap[part.partId] =
  180 + part.methods?.map((m) => {
  181 + const temp = {
  182 + ...m,
  183 + type: m.method,
  184 + quantity: m.num,
  185 + };
  186 + // @ts-ignore
  187 + delete temp.method;
  188 + // @ts-ignore
  189 + delete temp.num;
  190 + return temp;
  191 + }) || [];
  192 + });
  193 +
  194 + const finalParts: BookPart[] = [];
  195 + data.parts.forEach((part) => {
  196 + // 无需处理的数据,需要回传
  197 + if (part.del || isEmpty(part.types)) {
  198 + finalParts.push(part);
  199 + return;
  200 + }
  201 + part.types = partMethodMap[part.partId];
  202 + finalParts.push(part);
  203 + });
  204 + return finalParts;
  205 + }
  206 +
  207 + function handleSubmit() {
  208 + if (parts.some((e) => !e.methods)) {
  209 + message.warn('请添加缺件处理方式');
  210 + return;
  211 + }
  212 +
  213 + const params: SaveParams = {
  214 + type: 2,
  215 + parts: assembleParts(),
  216 + };
  217 + setSubmitting(true);
  218 + saveApi(dataId, params)
  219 + .then(() => {
  220 + message.success('操作成功', 1, onConfirm);
  221 + resetPageData();
  222 + })
  223 + .catch((err) => {
  224 + Modal.confirm({
  225 + title: '提示',
  226 + icon: <ExclamationCircleOutlined />,
  227 + content: err.message || '操作失败',
  228 + keyboard: false,
  229 + okText: '确认',
  230 + cancelText: '取消',
  231 + });
  232 + })
  233 + .finally(() => {
  234 + setSubmitting(false);
  235 + });
  236 + }
  237 +
  238 + function renderCompareText(prevText: string, text: string) {
  239 + return (
  240 + <div className={st.rowColumnLeft}>
  241 + <p className={st.colorDanger}>{text}</p>
  242 + <ArrowUpOutlined size={12} style={{ marginTop: '1em', marginBottom: '1em', paddingLeft: '1em' }} />
  243 + <p>{prevText}</p>
  244 + </div>
  245 + );
  246 + }
  247 +
  248 + const columns: ColumnsType<LackPart> = [
  249 + {
  250 + title: '配件名称',
  251 + dataIndex: 'partName',
  252 + width: '30%',
  253 + render: (partName, record) => (record.oldPart ? renderCompareText(record.oldPart.partName, partName) : partName),
  254 + },
  255 + {
  256 + title: '配件编码',
  257 + dataIndex: 'partCode',
  258 + width: '15%',
  259 + render: (partCode, record) => (record.oldPart ? renderCompareText(record.oldPart.partCode, partCode) : partCode),
  260 + },
  261 + {
  262 + title: '数量',
  263 + dataIndex: 'lackNum',
  264 + width: '15%',
  265 + },
  266 + {
  267 + title: '处理方案',
  268 + dataIndex: 'partId',
  269 + width: '20%',
  270 + render: (partId, record) => (
  271 + <List
  272 + itemLayout="horizontal"
  273 + locale={{ emptyText: '暂无处理方案' }}
  274 + dataSource={record.methods || []}
  275 + renderItem={(item: Method) => (
  276 + <List.Item>
  277 + <List.Item.Meta
  278 + title={MethodNameEnum[item.method]}
  279 + description={
  280 + <>
  281 + {item.outStorageName && <p style={{ marginBottom: 0 }}>调出仓库:{item.outStorageName}</p>}
  282 + {item.expectedDate && <p style={{ marginBottom: 0 }}>预计入库天数:{item.expectedDate}</p>}
  283 + <p style={{ marginBottom: 0 }}>
  284 + 数量:<span style={{ color: '#FF921C' }}>{item.num}</span>
  285 + </p>
  286 + </>
  287 + }
  288 + />
  289 + </List.Item>
  290 + )}
  291 + />
  292 + ),
  293 + },
  294 + {
  295 + title: '操作',
  296 + dataIndex: 'partId',
  297 + width: 200,
  298 + align: 'center',
  299 + render: (partId, record: LackPart, index) => (
  300 + <>
  301 + {record.oldPart ? (
  302 + <Button type="link" danger onClick={() => handlePartRollback(index)}>
  303 + 撤销变更
  304 + </Button>
  305 + ) : (
  306 + <Button type="link" onClick={() => handlePartChange(record, index)}>
  307 + 变更配件
  308 + </Button>
  309 + )}
  310 + <Divider type="vertical" />
  311 + {record.methods ? (
  312 + <Button type="link" onClick={() => handlePartEdit(record, index)}>
  313 + 修改
  314 + </Button>
  315 + ) : (
  316 + <Button type="link" onClick={() => handlePartEdit(record, index)}>
  317 + 缺件处理
  318 + </Button>
  319 + )}
  320 + </>
  321 + ),
  322 + },
  323 + ];
  324 +
  325 + return (
  326 + <Modal
  327 + title="缺件处理"
  328 + open={open}
  329 + maskClosable={false}
  330 + width={1200}
  331 + confirmLoading={submitting}
  332 + okButtonProps={{ disabled: loading }}
  333 + cancelButtonProps={{ disabled: submitting }}
  334 + okText="提交"
  335 + onCancel={() => onCancel()}
  336 + onOk={handleSubmit}
  337 + >
  338 + <Skeleton loading={loading} active title={false} paragraph={{ rows: 3, width: '100%' }}>
  339 + <Descriptions>
  340 + <DescriptionItem label="车牌号">{data.plateNo}</DescriptionItem>
  341 + <DescriptionItem label="车主">{data.ownerName}</DescriptionItem>
  342 + <DescriptionItem label="车辆">{data.carName}</DescriptionItem>
  343 + <DescriptionItem label="VIN">{data.vin}</DescriptionItem>
  344 + <DescriptionItem label="车型代码">{data.specCode}</DescriptionItem>
  345 + <DescriptionItem label="服务顾问">{data.username}</DescriptionItem>
  346 + <DescriptionItem label="订件时间">{moment(data.createTime).format('YYYY-MM-DD HH:mm')}</DescriptionItem>
  347 + </Descriptions>
  348 + </Skeleton>
  349 +
  350 + <Divider />
  351 + <Row justify="end" style={{ marginBottom: 20 }}>
  352 + <Col>
  353 + <Button type="primary" disabled={loading} onClick={showBatchHandleModal}>
  354 + 批量处理
  355 + </Button>
  356 + </Col>
  357 + </Row>
  358 +
  359 + <Skeleton loading={loading} active title={false} paragraph={{ rows: 4, width: '100%' }}>
  360 + <Table loading={loading} dataSource={parts} pagination={false} rowKey="partId" columns={columns} scroll={{ y: 600 }} />
  361 + </Skeleton>
  362 +
  363 + <SingleHandler
  364 + open={singleOpen}
  365 + shopId={data.shopId}
  366 + part={currentRecord}
  367 + onCancel={() => setSingleOpen(false)}
  368 + onConfirm={(newPart) => handlePartUpdate(newPart, currentIndex)}
  369 + />
  370 +
  371 + <BatchHandler
  372 + open={batchOpen}
  373 + shopId={data.shopId}
  374 + parts={batchParts}
  375 + onCancel={() => {
  376 + setBatchOpen(false);
  377 + setBatchParts([]);
  378 + }}
  379 + onConfirm={handleBatchUpdate}
  380 + />
  381 +
  382 + <PartSelector
  383 + open={partSelectorOpen}
  384 + base={{ shopId: data.shopId, vin: data.vin, specCode: data.specCode }}
  385 + selectIds={parts.map((item) => item.partId)}
  386 + onCancel={() => setPartSelectorOpen(false)}
  387 + onConfirm={(newPart) => checkPartPrice(newPart, currentIndex)}
  388 + />
  389 + </Modal>
  390 + );
  391 +}
... ...
src/pages/cas/workOrder/PartLackHandle/subpages/Book/PartListModal.tsx 0 → 100644
  1 +import React from 'react';
  2 +import { Modal, Table } from 'antd';
  3 +import type { ColumnsType } from 'antd/lib/table';
  4 +import { isEmpty } from 'lodash';
  5 +
  6 +// import useInitail from '@/hooks/useInitail';
  7 +import type { BookPart } from './api';
  8 +
  9 +// import { detailApi } from './api';
  10 +
  11 +interface Props {
  12 + open: boolean;
  13 + dataId: number;
  14 + data: BookPart[];
  15 + onCancel: () => any;
  16 +}
  17 +
  18 +/**
  19 + * 配件清单
  20 + * @desc 订件配件列表,包含所有配件,缺件清单及处理时,需要顾虑掉 del=true 或者 types=[] 的配件
  21 + */
  22 +export default function PartListModal({ open, data, onCancel }: Props) {
  23 + // const [delay, setDelay] = useState(true);
  24 +
  25 + // const { data, loading, setParams } = useInitail(detailApi, {} as DetailResult, dataId, delay);
  26 + const needHandleParts =
  27 + data.map((item) => {
  28 + if (item.del || isEmpty(item.types)) {
  29 + return;
  30 + }
  31 + return item;
  32 + }) || ([] as BookPart[]);
  33 +
  34 + // useEffect(() => {
  35 + // if (open && dataId) {
  36 + // setDelay(false);
  37 + // setParams(dataId, true);
  38 + // }
  39 + // }, [open]);
  40 +
  41 + const columns: ColumnsType<BookPart> = [
  42 + {
  43 + title: '配件名称',
  44 + dataIndex: 'partName',
  45 + },
  46 + {
  47 + title: '配件编码',
  48 + dataIndex: 'partCode',
  49 + },
  50 + {
  51 + title: '数量',
  52 + dataIndex: 'partCnt',
  53 + },
  54 + ];
  55 +
  56 + return (
  57 + <Modal title="配件清单" open={open} width={700} footer={null} onCancel={onCancel}>
  58 + <Table
  59 + // loading={loading}
  60 + dataSource={needHandleParts.filter((item) => item)}
  61 + pagination={{ pageSize: 10, showSizeChanger: false, showTotal: (total) => `共 ${total} 条` }}
  62 + rowKey="partId"
  63 + columns={columns}
  64 + />
  65 + </Modal>
  66 + );
  67 +}
... ...
src/pages/cas/workOrder/PartLackHandle/subpages/Book/api.ts 0 → 100644
  1 +import { http } from '@/typing/http';
  2 +import request from '@/utils/request';
  3 +import { CAS_HOST } from '@/utils/host';
  4 +
  5 +type P<T> = http.PromiseResp<T>;
  6 +type Page<T> = http.PromisePageResp<T>;
  7 +
  8 +interface ListParams {
  9 + groupId?: number;
  10 + shopId?: number;
  11 + userId?: number;
  12 + userName?: string;
  13 + pageSize?: number;
  14 + current?: number;
  15 + keywords?: string;
  16 +}
  17 +
  18 +export interface ListResult {
  19 + dataId: number; // 缺件单id
  20 + plateNo: number; // 车牌号
  21 + ownerName: number; // 车主
  22 + carName: number; // 车辆名称
  23 + vin: number; // 车架号
  24 + specCode: number; // 车型代码
  25 + adviserName: number; // 服务顾问
  26 + createTime: number; // 订件时间
  27 + parts: BookPart[], // 配件:包含不缺件配件和已删除配件
  28 +}
  29 +
  30 +export interface DetailResult {
  31 + id: number; // 订件草稿id
  32 + vin: string; // 车架号
  33 + plateNo: string; // 车牌号
  34 + carName: string; // 车辆名称
  35 + ownerName: string; // 车主
  36 + parts: BookPart[]; // 配件列表,只展示订件数量大于库存且del为false的配件
  37 + totalAmount: number; // 填写的总金额
  38 + createTime: number; // 订件时间
  39 + status: number; // 状态
  40 + expectAmount: number; // 预付订金合计
  41 + customerId: number;
  42 + changeable: boolean; // 是否可以变更或退订
  43 + shopName: string; // 门店名称
  44 + username: string; // 服务顾问
  45 + partBookId: number; // 订件id
  46 + statusName: string; // 状态名称
  47 + specCode: string; // 配置代码
  48 + orderId: number; // 工单id
  49 + orderNo: string; // 工单号
  50 + shopId: number; // 门店id
  51 + adjustReason: string; // 调整原因
  52 +}
  53 +
  54 +export interface Type {
  55 + type: number;
  56 + quantity: number;
  57 + expectedDate?: number;
  58 + shopId?: number;
  59 + outStorageId?: number;
  60 + outStorageName?: string;
  61 + outDividedBy?: number;
  62 + outDivision?: number;
  63 + inDividedBy?: number;
  64 + inDivision?: number;
  65 +}
  66 +
  67 +export interface BookPart {
  68 + partId: number;
  69 + partCode: string;
  70 + partName: string;
  71 + bearType: number;
  72 + partCnt: number;
  73 + stockCnt?: number;
  74 + retailPrice?: number;
  75 + bookPrice?: number;
  76 + types: Type[];
  77 + planConfirmed?: boolean;
  78 + del?: boolean;
  79 + confirmCode?: string;
  80 + lockCnt?: number;
  81 +}
  82 +
  83 +// 列表-订件
  84 +export function listApi(params: ListParams): Page<ListResult> {
  85 + return request.get(`${CAS_HOST}/erp/customer/book`, { params });
  86 +}
  87 +
  88 +// 详情-订件
  89 +export function detailApi(draftId: number): P<DetailResult> {
  90 + return request.get(`${CAS_HOST}/620/part/book/${draftId}`);
  91 +}
  92 +
  93 +// 保存-订件
  94 +export interface SaveParams {
  95 + parts: BookPart[];
  96 + type: 1 | 2; // 1: 驳回,2:确认
  97 + planRemark?: string;
  98 +}
  99 +
  100 +// 订件缺件确认
  101 +export function saveApi(draftId: number, params: SaveParams): P<any> {
  102 + return request.put(`${CAS_HOST}/620/part/book/process/${draftId}`, { ...params });
  103 +}
  104 +
  105 +export interface GetBearTypeInfoItem {
  106 + partId: number;
  107 + partCnt: number;
  108 +}
  109 +
  110 +export interface GetBearTypeInfoReq {
  111 + vin?: string;
  112 + shopId: number;
  113 + draftId?: number;
  114 + parts: GetBearTypeInfoItem[];
  115 +}
  116 +
  117 +export interface BearTypePartItem {
  118 + partId: number;
  119 + bearType: number;
  120 + retailPrice: number; // 售价
  121 + bookPrice: number; // 订件价格
  122 + partCnt: number;
  123 +}
  124 +export interface GetBearTypeInfoRes {
  125 + parts: BearTypePartItem[];
  126 + expectAmount: number;
  127 + paid?: number;
  128 +}
  129 +
  130 +// 查询订件价格
  131 +export function getBearTypeInfo(params: GetBearTypeInfoReq): P<GetBearTypeInfoRes> {
  132 + return request.post(`${CAS_HOST}/620/part/book/bear`, { ...params });
  133 +}
... ...
src/pages/cas/workOrder/PartLackHandle/subpages/Book/index.tsx 0 → 100644
  1 +import React, { useState } from 'react';
  2 +import { Button, Col, Input, Row, Table } from 'antd';
  3 +import type { ColumnsType } from 'antd/lib/table';
  4 +import { debounce } from 'lodash';
  5 +import dayjs from 'dayjs';
  6 +
  7 +import usePagination from '@/hooks/usePagination';
  8 +
  9 +import { listApi, type ListResult, type BookPart } from './api';
  10 +
  11 +import PartListModal from './PartListModal';
  12 +import HandleModal from './HandleModal';
  13 +
  14 +const Search = Input.Search;
  15 +
  16 +/**
  17 + * 计划员:订件缺件处理
  18 + */
  19 +export default function PartLackHandle() {
  20 + const [dataId, setDataId] = useState(0);
  21 + const [openParts, setOpenParts] = useState(false);
  22 + const [openHandle, setOpenHandle] = useState(false);
  23 + // 缺件配件列表
  24 + const [lackParts, setLackParts] = useState<BookPart[]>([]);
  25 +
  26 + const { list, loading, setParams, setLoading, paginationConfig } = usePagination(listApi, {});
  27 +
  28 + const handleSearch = debounce((keywords: string) => {
  29 + setParams({ keywords }, true);
  30 + }, 500);
  31 +
  32 + const showPartsModal = (dataId: number) => {
  33 + setDataId(dataId);
  34 + setOpenParts(true);
  35 + };
  36 +
  37 + const showHandleModal = (dataId: number) => {
  38 + setDataId(dataId);
  39 + setOpenHandle(true);
  40 + };
  41 +
  42 + const columns: ColumnsType<ListResult> = [
  43 + {
  44 + title: '车牌号',
  45 + dataIndex: 'plateNo',
  46 + fixed: 'left',
  47 + width: 70,
  48 + },
  49 + {
  50 + title: '车主',
  51 + dataIndex: 'ownerName',
  52 + width: 60,
  53 + },
  54 + {
  55 + title: '车辆',
  56 + dataIndex: 'carName',
  57 + width: 150,
  58 + },
  59 + {
  60 + title: 'VIN',
  61 + dataIndex: 'vin',
  62 + width: 120,
  63 + },
  64 + {
  65 + title: '车型代码',
  66 + dataIndex: 'specCode',
  67 + width: 140,
  68 + },
  69 + {
  70 + title: '服务顾问',
  71 + dataIndex: 'adviserName',
  72 + width: 60,
  73 + },
  74 + {
  75 + title: '订件时间',
  76 + dataIndex: 'createTime',
  77 + width: 70,
  78 + render: (val: number) => dayjs(val).format('YYYY-MM-DD HH:mm:ss'),
  79 + },
  80 + {
  81 + title: '缺件清单',
  82 + dataIndex: 'dataId',
  83 + fixed: 'right',
  84 + width: 70,
  85 + align: 'center',
  86 + render: (dataId: number, record) => (
  87 + <Button
  88 + type="link"
  89 + onClick={() => {
  90 + setLackParts(record.parts);
  91 + showPartsModal(dataId);
  92 + }}
  93 + >
  94 + 查看
  95 + </Button>
  96 + ),
  97 + },
  98 + {
  99 + title: '操作',
  100 + dataIndex: 'dataId',
  101 + fixed: 'right',
  102 + width: 70,
  103 + align: 'center',
  104 + render: (dataId: number) => (
  105 + <Button type="link" onClick={() => showHandleModal(dataId)}>
  106 + 处理
  107 + </Button>
  108 + ),
  109 + },
  110 + ];
  111 +
  112 + return (
  113 + <div>
  114 + <Row justify="space-between" style={{ marginBottom: 16 }}>
  115 + <Col>
  116 + <Search
  117 + style={{ width: 280 }}
  118 + placeholder="搜索车牌、VIN、车主"
  119 + allowClear
  120 + onChange={(e) => handleSearch(e.target.value)}
  121 + onSearch={handleSearch}
  122 + />
  123 + </Col>
  124 + </Row>
  125 +
  126 + <Table dataSource={list} pagination={paginationConfig} rowKey="orderNo" loading={loading} columns={columns} bordered scroll={{ x: 1900 }} />
  127 +
  128 + <PartListModal
  129 + open={openParts}
  130 + dataId={dataId}
  131 + data={lackParts}
  132 + onCancel={() => {
  133 + setDataId(0);
  134 + setOpenParts(false);
  135 + setLackParts([]);
  136 + }}
  137 + />
  138 +
  139 + <HandleModal
  140 + open={openHandle}
  141 + dataId={dataId}
  142 + onCancel={() => {
  143 + setDataId(0);
  144 + setOpenHandle(false);
  145 + }}
  146 + onConfirm={() => {
  147 + setLoading(true);
  148 + setDataId(0);
  149 + setOpenHandle(false);
  150 + }}
  151 + />
  152 + </div>
  153 + );
  154 +}
... ...
src/pages/cas/workOrder/PartLackHandle/components/HandleModal.tsx renamed to src/pages/cas/workOrder/PartLackHandle/subpages/Order/HandleModal.tsx
... ... @@ -8,12 +8,13 @@ import { cloneDeep, differenceBy, isEmpty } from &#39;lodash&#39;;
8 8 import useInitail from '@/hooks/useInitail';
9 9  
10 10 import { MethodEnum, MethodNameEnum } from '@/pages/cas/workOrder/PartLackHandle/entity';
11   -import type { DetailResult, MethodType, Part, SaveParams, WorkItemPart } from '../api';
12   -import { detailApi, saveApi, SavePart } from '../api';
  11 +import type { WorkItemPart } from '@/pages/cas/workOrder/PartLackHandle/api';
  12 +import type { DetailResult, MethodType, Part, SaveParams } from './api';
  13 +import { detailApi, saveApi, SavePart } from './api';
13 14  
14   -import SingleHandler from './SingleHandler';
15   -import BatchHandler from './BatchHandler';
16   -import PartSelector from './PartSelector';
  15 +import SingleHandler from '@/pages/cas/workOrder/PartLackHandle/components/SingleHandler';
  16 +import BatchHandler from '@/pages/cas/workOrder/PartLackHandle/components/BatchHandler';
  17 +import PartSelector from '@/pages/cas/workOrder/PartLackHandle/components/PartSelector';
17 18  
18 19 import st from '@/pages/cas/style.less';
19 20  
... ...
src/pages/cas/workOrder/PartLackHandle/components/PartListModal.tsx renamed to src/pages/cas/workOrder/PartLackHandle/subpages/Order/PartListModal.tsx
... ... @@ -3,8 +3,8 @@ import { Modal, Table } from &#39;antd&#39;;
3 3 import type { ColumnsType } from 'antd/lib/table';
4 4  
5 5 import useInitail from '@/hooks/useInitail';
6   -import type { DetailResult, Part } from '../api';
7   -import { detailApi } from '../api';
  6 +import type { DetailResult, Part } from './api';
  7 +import { detailApi } from './api';
8 8  
9 9 interface Props {
10 10 open: boolean;
... ...
src/pages/cas/workOrder/PartLackHandle/subpages/Order/api.ts 0 → 100644
  1 +import { http } from '@/typing/http';
  2 +import request from '@/utils/request';
  3 +import { CAS_HOST } from '@/utils/host';
  4 +import { TransferItem } from '@/pages/cas/workOrder/PartLackHandle/api';
  5 +
  6 +type P<T> = http.PromiseResp<T>;
  7 +type Page<T> = http.PromisePageResp<T>;
  8 +
  9 +interface ListParams {
  10 + groupId?: number;
  11 + shopId?: number;
  12 + userId?: number;
  13 + userName?: string;
  14 + pageSize?: number;
  15 + current?: number;
  16 + keywords?: string;
  17 +}
  18 +
  19 +export interface ListResult {
  20 + dataId: number; // 缺件单id
  21 + orderNo: number; // 工单号
  22 + plateNo: number; // 车牌号
  23 + ownerName: number; // 车主
  24 + carName: number; // 车辆名称
  25 + vin: number; // 车架号
  26 + specCode: number; // 车型代码
  27 + shopName: number; // 在修门店
  28 + receiverName: number; // 服务顾问
  29 + senderTime: number; // 进站时间
  30 + serviceCatName: number; // 进站类型
  31 +}
  32 +
  33 +export interface DetailResult {
  34 + lackListId: number;
  35 + quoteId: number;
  36 + ownerName: string;
  37 + phone: number;
  38 + brandId: number;
  39 + plateNo: string;
  40 + carName: string;
  41 + vin: string;
  42 + specCode: string;
  43 + senderTime: number;
  44 + consultant: string;
  45 + orderNo: string;
  46 + orderId: number;
  47 + shopId: number;
  48 + shopName: string;
  49 + groupId: number;
  50 + lackStatus: number;
  51 + items: Part[];
  52 +}
  53 +
  54 +export interface Part {
  55 + lackPartId: number;
  56 + itemId: number;
  57 + itemName: string;
  58 + quotePartId: number;
  59 + partId: number;
  60 + partCode: string;
  61 + partName: string;
  62 + lackNum: number;
  63 + processingMethod?: number; // 处理方式 1: 区域库内调件 2: 厂家采购 3:外部采购 4:区域库外调件 5:计划员处理
  64 + processingMethodName?: string; // 处理方式名称
  65 + // processingMethod为1时,有以下字段
  66 + shopId?: number;
  67 + outStorageId?: number;
  68 + outStorageName?: string;
  69 + outDividedBy?: number;
  70 + outDivision?: number;
  71 + inDividedBy?: number;
  72 + inDivision?: number;
  73 +
  74 + // todo confirm to del
  75 + expectedDate?: number; // 预计需要的入库天数
  76 + transferItems?: TransferItem[]; // 顾问选择调件数据
  77 + // @custom
  78 + oldPart?: Part;
  79 + methods?: MethodType[];
  80 + // @custom 暂存区域库存信息:用于批量处理时展示
  81 + isArea?: boolean; // 是否区域库内
  82 + outStock?: number; // 区域库外库存
  83 +}
  84 +
  85 +export interface SavePart extends Part {
  86 + method?: number;
  87 + shopId?: number;
  88 + outStorageId?: number;
  89 + outStorageName?: string;
  90 + outDividedBy?: number;
  91 + outDivision?: number;
  92 + oldPartId?: number;
  93 + oldPartCode?: string;
  94 + oldPartName?: string;
  95 +}
  96 +
  97 +export interface MethodType {
  98 + method: number;
  99 + lackNum?: number;
  100 + expectedDate?: number;
  101 + partId?: number;
  102 + partCode?: string;
  103 + partName?: string;
  104 + itemId?: number;
  105 + shopId?: number;
  106 + outStorageId?: number;
  107 + outStorageName?: string;
  108 + outDividedBy?: number;
  109 + outDivision?: number;
  110 + inDividedBy?: number;
  111 + inDivision?: number;
  112 + // @custom
  113 + num: number;
  114 +}
  115 +
  116 +export interface SaveParams {
  117 + dataId: number;
  118 + userId?: number;
  119 + userName?: string;
  120 + groupId?: number;
  121 + purchase?: SavePart[];
  122 + transfer?: SavePart[];
  123 + book?: SavePart[];
  124 +}
  125 +
  126 +// 列表
  127 +export function listApi(params: ListParams): Page<ListResult> {
  128 + return request.get(`${CAS_HOST}/app/part/lack/list/planner`, { params });
  129 +}
  130 +
  131 +// 详情
  132 +export function detailApi(dataId: number): P<DetailResult> {
  133 + return request.get(`${CAS_HOST}/app/part/lack/list/detail/planner`, { params: { dataId } });
  134 +}
  135 +
  136 +// 保存
  137 +export function saveApi(params: SaveParams) {
  138 + return request.post(`${CAS_HOST}/app/part/lack/confirm/planner`, params);
  139 +}
... ...
src/pages/cas/workOrder/PartLackHandle/subpages/Order/index.tsx 0 → 100644
  1 +import React, { useState } from 'react';
  2 +import { Button, Col, Input, Row, Table } from 'antd';
  3 +import type { ColumnsType } from 'antd/lib/table';
  4 +import { debounce } from 'lodash';
  5 +import dayjs from 'dayjs';
  6 +
  7 +import usePagination from '@/hooks/usePagination';
  8 +
  9 +import { listApi, type ListResult } from './api';
  10 +
  11 +import PartListModal from './PartListModal';
  12 +import HandleModal from './HandleModal';
  13 +
  14 +const Search = Input.Search;
  15 +
  16 +/**
  17 + * 计划员:工单缺件处理
  18 + */
  19 +export default function PartLackHandle() {
  20 + const [dataId, setDataId] = useState(0);
  21 + const [openParts, setOpenParts] = useState(false);
  22 + const [openHandle, setOpenHandle] = useState(false);
  23 +
  24 + const { list, loading, setParams, setLoading, paginationConfig } = usePagination(listApi, {});
  25 +
  26 + const handleSearch = debounce((keywords: string) => {
  27 + setParams({ keywords }, true);
  28 + }, 500);
  29 +
  30 + const showPartsModal = (dataId: number) => {
  31 + setDataId(dataId);
  32 + setOpenParts(true);
  33 + };
  34 +
  35 + const showHandleModal = (dataId: number) => {
  36 + setDataId(dataId);
  37 + setOpenHandle(true);
  38 + };
  39 +
  40 + const columns: ColumnsType<ListResult> = [
  41 + {
  42 + title: '工单号',
  43 + dataIndex: 'orderNo',
  44 + fixed: 'left',
  45 + width: 100,
  46 + },
  47 + {
  48 + title: '车牌号',
  49 + dataIndex: 'plateNo',
  50 + fixed: 'left',
  51 + width: 70,
  52 + },
  53 + {
  54 + title: '车主',
  55 + dataIndex: 'ownerName',
  56 + width: 60,
  57 + },
  58 + {
  59 + title: '车辆',
  60 + dataIndex: 'carName',
  61 + width: 150,
  62 + },
  63 + {
  64 + title: 'VIN',
  65 + dataIndex: 'vin',
  66 + width: 120,
  67 + },
  68 + {
  69 + title: '车型代码',
  70 + dataIndex: 'specCode',
  71 + width: 140,
  72 + },
  73 + {
  74 + title: '在修门店',
  75 + dataIndex: 'shopName',
  76 + width: 140,
  77 + },
  78 + {
  79 + title: '服务顾问',
  80 + dataIndex: 'receiverName',
  81 + width: 60,
  82 + },
  83 + {
  84 + title: '进站类型',
  85 + dataIndex: 'serviceCatName',
  86 + width: 70,
  87 + },
  88 + {
  89 + title: '进站时间',
  90 + dataIndex: 'senderTime',
  91 + width: 70,
  92 + render: (senderTime: string) => dayjs(senderTime).format('YYYY-MM-DD HH:mm:ss'),
  93 + },
  94 + {
  95 + title: '缺件清单',
  96 + dataIndex: 'dataId',
  97 + fixed: 'right',
  98 + width: 70,
  99 + align: 'center',
  100 + render: (dataId: number) => (
  101 + <Button type="link" onClick={() => showPartsModal(dataId)}>
  102 + 查看
  103 + </Button>
  104 + ),
  105 + },
  106 + {
  107 + title: '操作',
  108 + dataIndex: 'dataId',
  109 + fixed: 'right',
  110 + width: 70,
  111 + align: 'center',
  112 + render: (dataId: number) => (
  113 + <Button type="link" onClick={() => showHandleModal(dataId)}>
  114 + 处理
  115 + </Button>
  116 + ),
  117 + },
  118 + ];
  119 +
  120 + return (
  121 + <div>
  122 + <Row justify="space-between" style={{ marginBottom: 16 }}>
  123 + <Col>
  124 + <Search
  125 + style={{ width: 280 }}
  126 + placeholder="搜索工单号、车牌、VIN、车主"
  127 + allowClear
  128 + onChange={(e) => handleSearch(e.target.value)}
  129 + onSearch={handleSearch}
  130 + />
  131 + </Col>
  132 + </Row>
  133 +
  134 + <Table dataSource={list} pagination={paginationConfig} rowKey="orderNo" loading={loading} columns={columns} bordered scroll={{ x: 1900 }} />
  135 +
  136 + <PartListModal
  137 + open={openParts}
  138 + dataId={dataId}
  139 + onCancel={() => {
  140 + setDataId(0);
  141 + setOpenParts(false);
  142 + }}
  143 + />
  144 +
  145 + <HandleModal
  146 + open={openHandle}
  147 + dataId={dataId}
  148 + onCancel={() => {
  149 + setDataId(0);
  150 + setOpenHandle(false);
  151 + }}
  152 + onConfirm={() => {
  153 + setLoading(true);
  154 + setDataId(0);
  155 + setOpenHandle(false);
  156 + }}
  157 + />
  158 + </div>
  159 + );
  160 +}
... ...
src/pages/coupon/CouponConfig/components/FullReduce.tsx
... ... @@ -155,7 +155,7 @@ export default function FullReduce({ info, readonly, form, getCouponType, confNo
155 155 )}
156 156 {/* 全部默认不叠加false,立减券允许修改类型 */}
157 157 <Form.Item label="可与其他同类型优惠券叠加使用" name="superimposed" >
158   - <Radio.Group disabled={readonly || !!confNo || info.discountsType != 1}>
  158 + <Radio.Group disabled={readonly || info.discountsType != 1}>
159 159 <Radio key={1} value={true}>可叠加</Radio>
160 160 <Radio key={0} value={false}>不可叠加</Radio>
161 161 </Radio.Group>
... ...
src/pages/mkt/ActivityCreate/ExternalPromotion/components/ActivityFlow/Coupons.tsx
... ... @@ -223,7 +223,7 @@ export default function Coupons({ awardWay, mulLottery, value, onChange, remove,
223 223 onClick={() => {
224 224 remove && remove(name);
225 225 }}
226   - // disabled={readOnly}
  226 + // disabled={readOnly}
227 227 />
228 228 )}
229 229  
... ... @@ -251,7 +251,7 @@ export default function Coupons({ awardWay, mulLottery, value, onChange, remove,
251 251 setVisible(true);
252 252 setNoRewad(false);
253 253 }}
254   - // disabled={readOnly}
  254 + // disabled={readOnly}
255 255 >
256 256 新增
257 257 </Button>
... ... @@ -275,12 +275,12 @@ export default function Coupons({ awardWay, mulLottery, value, onChange, remove,
275 275 onClick={
276 276 changeEnable
277 277 ? () => {
278   - // setVisible(true);
279   - setNoRewad(false);
280   - SetCurrentItem(record);
281   - setItemIndex(index);
282   - setVisible(true);
283   - }
  278 + // setVisible(true);
  279 + setNoRewad(false);
  280 + SetCurrentItem(record);
  281 + setItemIndex(index);
  282 + setVisible(true);
  283 + }
284 284 : undefined
285 285 }
286 286 >
... ... @@ -325,7 +325,7 @@ export default function Coupons({ awardWay, mulLottery, value, onChange, remove,
325 325 {itemIndex + 1}.{item.aliasName}
326 326 </span>
327 327 </Button>
328   - {((item.aliasName && !readOnly) || changeEnable) && (
  328 + {(item.aliasName && !readOnly) && (
329 329 <Popconfirm
330 330 title="删除将丢失优惠券信息,确定删除?"
331 331 okText="确定"
... ... @@ -432,15 +432,15 @@ export default function Coupons({ awardWay, mulLottery, value, onChange, remove,
432 432 wrapperCol={{ span: 16 }}
433 433 onFinish={_onOk}
434 434 autoComplete="off"
435   - // initialValues={{
436   - // awardName: (awardWay === 2 && noReward) ? "谢谢惠顾" : "",
437   - // }}
  435 + // initialValues={{
  436 + // awardName: (awardWay === 2 && noReward) ? "谢谢惠顾" : "",
  437 + // }}
438 438 >
439 439 <Form.Item
440 440 label="奖项名称:"
441 441 name="awardName"
442 442 rules={[{ required: true, message: '请输入奖项名称' }]}
443   - // initialValue={(awardWay === 2 && noReward) ? "谢谢惠顾" : ""}
  443 + // initialValue={(awardWay === 2 && noReward) ? "谢谢惠顾" : ""}
444 444 >
445 445 <Input
446 446 // disabled={(awardWay === 2 && noReward) || !!currentItem.awardName}
... ...
src/pages/mkt/ActivityCreate/ExternalPromotion/components/ActivityFlow/SignIn.tsx
... ... @@ -10,11 +10,11 @@ import moment from &quot;moment&quot;;
10 10 const { RangePicker } = DatePicker;
11 11 const { Option } = Select;
12 12  
13   -export default function index() {
  13 +export default function Index() {
14 14 const { baseInfo, readOnly, getFlowConfig, externalInfo } = useStore();
15 15 const { activityNo, changeEnable } = baseInfo;
16 16 const { flowDisable } = externalInfo;
17   - const { data, errMsg, loading, params, setParams } = useInitial(getSignInDetail, {}, {activityNo, change: changeEnable});
  17 + const { data, errMsg, loading, params, setParams } = useInitial(getSignInDetail, {}, { activityNo, change: changeEnable });
18 18 const [form] = Form.useForm();
19 19 const [saveLoading, setSaveLoading] = useState<boolean>(false);
20 20  
... ...
src/pages/mkt/ActivityCreate/ExternalPromotion/components/ActivityFlow/SignUp.tsx
1 1 import React, { useState, useEffect } from 'react';
2   -import { Button, Form, InputNumber, message, Space, Spin, Divider, Radio, Row, Alert } from 'antd';
  2 +import { Button, Form, InputNumber, message, Space, Spin, Divider, Radio, Row } from 'antd';
3 3 import useInitial from '@/hooks/useInitail';
4 4 import { PlusOutlined, MinusCircleOutlined } from '@ant-design/icons';
5 5 import { saveSignUpApi, getSignUpDetail, saveChangeSignUpApi } from '../../api'; //保存报名有礼
... ... @@ -163,25 +163,6 @@ export default function Index() {
163 163 <Radio value={1}>按报名顺序不同赠送不同优惠券</Radio>
164 164 </Radio.Group>
165 165 </Form.Item>
166   - {changeEnable ? (
167   - <Alert
168   - message="提示"
169   - type="warning"
170   - showIcon
171   - closable
172   - style={{ marginBottom: 10 }}
173   - description={
174   - <div>
175   - <p style={{ margin: 0, fontSize: 12, color: '#999' }}>
176   - 1.优惠券编辑变更:范围只能扩大不能缩小,变更前所发优惠券与变更后所发优惠券的限制范围一致
177   - </p>
178   - <p style={{ margin: 0, fontSize: 12, color: '#999' }}>
179   - 2.优惠券删除-新增变更:不限制优惠券变更范围,变更前所发优惠券使用范围不变,变更后所发优惠券限制范围变更
180   - </p>
181   - </div>
182   - }
183   - />
184   - ) : null}
185 166 <Form.Item noStyle shouldUpdate={(prevValues, currentValues) => prevValues.ladderReward !== currentValues.ladderReward}>
186 167 {({ getFieldValue }) => {
187 168 return (
... ...
src/pages/mkt/ActivityCreate/ExternalPromotion/components/ActivityFlow/SignupGifts.tsx
... ... @@ -167,16 +167,18 @@ function SignupGifts({ value, onChange, disabled }: Props) {
167 167 >
168 168 编辑
169 169 </Button>
170   - <Popconfirm
171   - title="删除将丢失优惠券信息,确定删除?"
172   - okText="确定"
173   - cancelText="取消"
174   - onConfirm={() => deleteQestion(index)}
175   - >
176   - <Button type="link" danger>
177   - 删除
178   - </Button>
179   - </Popconfirm>
  170 + {!changeEnable && (
  171 + <Popconfirm
  172 + title="删除将丢失优惠券信息,确定删除?"
  173 + okText="确定"
  174 + cancelText="取消"
  175 + onConfirm={() => deleteQestion(index)}
  176 + >
  177 + <Button type="link" danger>
  178 + 删除
  179 + </Button>
  180 + </Popconfirm>
  181 + )}
180 182 </Space>
181 183 )
182 184 )}
... ...
src/pages/performance/DataImport/components/ExcelTable.tsx
... ... @@ -5,6 +5,7 @@ import { useRequest } from &#39;ahooks&#39;;
5 5 import { ErrorType } from '../entity';
6 6  
7 7 import moment from 'moment';
  8 +
8 9 interface Props {
9 10 fid?: string;
10 11 show?: boolean;
... ... @@ -19,7 +20,7 @@ export default function ExcelTable({ fid, show, indicator, setExcelvisible, refr
19 20 message.error(e.message);
20 21 },
21 22 });
22   -
  23 +
23 24 useEffect(() => {
24 25 if (show) {
25 26 run(fid || '', indicator?.code || '');
... ... @@ -96,6 +97,7 @@ export default function ExcelTable({ fid, show, indicator, setExcelvisible, refr
96 97 <Modal
97 98 open={show}
98 99 destroyOnClose
  100 + okButtonProps={{ disabled: (data?.successNum || 0) <= 0 }}
99 101 onCancel={() => {
100 102 setExcelvisible?.(false);
101 103 }}
... ...
src/pages/performance/DataImport/components/ImportList.tsx
... ... @@ -16,6 +16,7 @@ import type { ApprovalProgressModalRef } from &#39;@/components/ApprovalProgressModa
16 16 import dowloader from '@/utils/downloader';
17 17 import { IMGURL } from '@/utils';
18 18 import ExcelTable from './ExcelTable';
  19 +import { ErrorType } from '../entity';
19 20  
20 21 type Props = {
21 22 indicator?: MDataImport.IndicatorList;
... ... @@ -160,7 +161,23 @@ export default function TableList({ indicator }: Props) {
160 161 dataIndex: 'graderStaffName',
161 162 render: (name) => <span>{name || '--'}</span>,
162 163 },
  164 + {
  165 + title: '是否识别',
  166 + width: 100,
  167 + dataIndex: 'errorType',
  168 + align: 'center',
  169 + render: (_: any, record: any) => (record.errorType ? <div>未识别</div> : <div>已识别</div>),
  170 + },
  171 + {
  172 + title: '未识别原因',
  173 + width: 150,
  174 + dataIndex: 'errorType',
  175 + align: 'center',
  176 + render: (_: any, record: any) => (record.errorType ? ErrorType[record.errorType] : '--'),
  177 + },
163 178 ];
  179 +
  180 +
164 181 const columns: ColumnsType<MDataImport.DetailedList> = [
165 182 {
166 183 title: '导入时间',
... ... @@ -311,6 +328,10 @@ export default function TableList({ indicator }: Props) {
311 328 onClick={() => {
312 329 DownloadApi(indicator?.code)
313 330 .then((res) => {
  331 + if(!res){
  332 + message.error('模版不存在!')
  333 + return
  334 + }
314 335 dowloader.downloadFile({ fileUrl: IMGURL.showImage(res) });
315 336 })
316 337 .catch((e) => {
... ...
src/pages/performance/KpiSetting/components/EditModal.tsx
... ... @@ -44,8 +44,9 @@ export default function EditModal({ onClose, setItem, item, roleList }: Props) {
44 44 useEffect(() => {
45 45 if (visible && currentItem) {
46 46 const result = transformFormData(currentItem, roleList, list);
  47 + console.log(currentItem,'323')
47 48 console.log({...result},'1111dwewwwwwew')
48   - form.setFieldsValue({ ...result });
  49 + form.setFieldsValue({ ...result});
49 50 }
50 51 }, [visible]);
51 52 function handleSave(values: any) {
... ... @@ -230,6 +231,18 @@ export default function EditModal({ onClose, setItem, item, roleList }: Props) {
230 231 ) : null;
231 232 }}
232 233 </Form.Item>
  234 + <Form.Item name="businessOriginName" label="原始指标名称" rules={[{ required: true, message: '请输入原始指标名称' }]}>
  235 + <Input placeholder="请输入指标名称" />
  236 + </Form.Item>
  237 + <Form.Item name="businessOriginUnit" label="原始指标单位" rules={[{ required: true }]}>
  238 + <Select disabled={isOriginIndicatorCode} placeholder="请选择原始指标单位">
  239 + {UnitType.map((item) => (
  240 + <Option value={item.value} key={item.value}>
  241 + {item.label}
  242 + </Option>
  243 + ))}
  244 + </Select>
  245 + </Form.Item>
233 246 </Form>
234 247 </Modal>
235 248 );
... ...
src/pages/performance/KpiSetting/interface.d.ts
... ... @@ -27,6 +27,8 @@ declare namespace KpiSetteing {
27 27 groupId: number; // 集团i
28 28 id: number;
29 29 originIndicatorCode?: string;
  30 + businessOriginName?: string;
  31 + businessOriginUnit?: string;
30 32 }
31 33  
32 34 /**
... ...