edit.vue 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891
  1. <template>
  2. <view class="page maintenance">
  3. <view class="segmented-header">
  4. <uni-segmented-control
  5. :current="currentTab"
  6. :values="tabTitles"
  7. :style-type="styleType"
  8. :active-color="activeColor"
  9. @clickItem="onClickTabItem"
  10. />
  11. </view>
  12. <scroll-view scroll-y="true" class="segmented-content">
  13. <!-- 工单信息 -->
  14. <view class="work-order-info" v-show="currentTab === 0">
  15. <work-order-form
  16. ref="workOrderFormRef"
  17. :form-data="mainWorkOrder"
  18. :form-disabled="false"
  19. />
  20. </view>
  21. <!-- 保养项列表 -->
  22. <view class="work-order-bom-list" v-show="currentTab === 1">
  23. <view class="list-content all">
  24. <work-order-boms
  25. :boms-data="mainWorkOrderBom"
  26. :work-order-type="mainWorkOrder.type"
  27. :current-bom="currentBomData"
  28. @onSelectBom="onSelectBom"
  29. @updateStatistics="updateBomStatisticsAndCost"
  30. ></work-order-boms>
  31. </view>
  32. </view>
  33. <!--物料列表 -->
  34. <view class="material-list" v-show="currentTab === 2">
  35. <!-- 物料列表操作栏 -->
  36. <uni-row
  37. class="list-header flex-row align-center justify-start"
  38. :gutter="10"
  39. >
  40. <!-- 选择物料 -->
  41. <uni-col :span="8">
  42. <button
  43. class="list-header-item flex-row align-center justify-center"
  44. type="default"
  45. :disabled="
  46. !currentBomData ||
  47. currentBomData.rule == '1' ||
  48. currentBomData.status == '1'
  49. "
  50. @click="onMaterialChoose(currentBomData)"
  51. >
  52. <image
  53. src="~@/static/workOrder/xuanzhewuliao.png"
  54. mode="aspectFill"
  55. ></image>
  56. <text>{{ t("workOrder.selectMaterial") }}</text>
  57. </button>
  58. </uni-col>
  59. <!-- 所有物料/当前保养项物料 -->
  60. <uni-col :span="8">
  61. <button
  62. class="list-header-item flex-row align-center justify-center"
  63. type="default"
  64. @click="changeMaterialList"
  65. >
  66. <image
  67. src="~@/static/workOrder/suoyoueuliao.png"
  68. mode="aspectFill"
  69. ></image>
  70. <text v-if="allMaterialShow">
  71. {{ t("workOrder.currentMaterial") }}
  72. </text>
  73. <text v-else>{{ t("workOrder.allMaterial") }}</text>
  74. </button>
  75. </uni-col>
  76. <!-- 新增物料 -->
  77. <uni-col :span="8">
  78. <button
  79. class="list-header-item flex-row align-center justify-center"
  80. type="default"
  81. :disabled="
  82. !currentBomData ||
  83. currentBomData.rule == '1' ||
  84. currentBomData.status == '1'
  85. "
  86. @click="addMaterial"
  87. >
  88. <image
  89. src="~@/static/workOrder/xinzengwuliao.png"
  90. mode="aspectFill"
  91. ></image>
  92. <text>{{ t("workOrder.addMaterial") }}</text>
  93. </button>
  94. </uni-col>
  95. </uni-row>
  96. <view class="list-content">
  97. <work-order-materials
  98. :materials-data="materialList"
  99. :material-disabled="false"
  100. @deleteMaterial="onDeleteMaterial"
  101. @materialChanged="updateBomStatisticsAndCost"
  102. ></work-order-materials>
  103. </view>
  104. </view>
  105. </scroll-view>
  106. <!-- 底部总览 及 操作按钮 -->
  107. <view class="segmented-footer">
  108. <view
  109. class="footer-total flex-row align-center justify-between"
  110. v-if="currentTab < 2"
  111. >
  112. <view class="total-item flex-row align-center justify-around">
  113. <!-- 共xx项 -->
  114. <view class="total-item-text">
  115. <text>{{ t("workOrder.total") }}</text>
  116. <text class="number">{{ bomNumbers.total }}</text>
  117. <text>{{ t("workOrder.item") }}</text>
  118. </view>
  119. <!-- 已保养xx项 -->
  120. <view class="total-item-text">
  121. <text>{{ t("maintenanceWorkOrder.maintained") }}</text>
  122. <text class="number">{{ bomNumbers.maintained }}</text>
  123. <text>{{ t("workOrder.item") }}</text>
  124. </view>
  125. <!-- 未保养xx项 -->
  126. <view class="total-item-text color-red">
  127. <text>{{ t("maintenanceWorkOrder.notMaintained") }}</text>
  128. <text class="number">{{ bomNumbers.notMaintained }}</text>
  129. <text>{{ t("workOrder.item") }}</text>
  130. </view>
  131. </view>
  132. <view class="total-btn">
  133. <button class="mini-btn" type="primary" @click="submitWorkOrder">
  134. {{ t("operation.submit") }}
  135. </button>
  136. </view>
  137. </view>
  138. <view class="footer-btn" v-else>
  139. <button class="mini-btn" type="primary" @click="saveMartialList">
  140. {{ t("operation.save") }}{{ t("workOrder.currentMaterialList") }}
  141. </button>
  142. </view>
  143. </view>
  144. </view>
  145. <!-- 选择物料弹窗 -->
  146. <materials-choose
  147. ref="materialsChooseRef"
  148. :deptId="mainWorkOrder?.deptId"
  149. :deviceId="currentBomData?.deviceId"
  150. :bomNodeId="currentBomData?.bomNodeId"
  151. :noAdd="true"
  152. @material-submit="onMaterialChooseSubmit"
  153. />
  154. <!-- 全局提示信息弹窗 -->
  155. <global-message-popup
  156. class="edit-message-popup"
  157. ref="messageGlobalRef"
  158. :msgType="msgType"
  159. :messageText="messageText"
  160. />
  161. </template>
  162. <script setup>
  163. import { onLoad, onReady, onBackPress } from "@dcloudio/uni-app";
  164. import {
  165. reactive,
  166. ref,
  167. onMounted,
  168. onBeforeUnmount,
  169. nextTick,
  170. getCurrentInstance,
  171. watch,
  172. } from "vue";
  173. import dayjs from "dayjs";
  174. // --------------------------引入用户信息-------------------------------
  175. import { getDeptId, getUserId, reloginByUserId } from "@/utils/auth";
  176. // --------------------------引用api接口-----start------------------------
  177. import {
  178. fillWorkOrder,
  179. getMaintenanceDetail,
  180. getWorkOrderBOMs,
  181. getBomMaterialsByWorkOrderId,
  182. } from "@/api/maintenance";
  183. // --------------------------引用api接口-----end--------------------------
  184. // --------------------------引用组件-----start---------------------------
  185. import workOrderForm from "./components/work-order-form.vue";
  186. import workOrderBoms from "./components/work-order-boms.vue";
  187. import workOrderMaterials from "./components/work-order-materials.vue";
  188. import materialsChoose from "@/components/materials/choose.vue";
  189. // --------------------------引用组件-----end-----------------------------
  190. // --------------------------引用全局变量$t-------------------------------
  191. const { appContext } = getCurrentInstance();
  192. const t = appContext.config.globalProperties.$t;
  193. // ----------------------------选项卡----------------------------------
  194. // 选项卡标题
  195. const tabTitles = ref([
  196. t("workOrder.workOrderInfo"),
  197. t("workOrder.maintenanceWorkOrderList"),
  198. t("workOrder.materialList"),
  199. ]);
  200. const currentTab = ref(0);
  201. const styleType = ref("text");
  202. const activeColor = ref("#004098");
  203. const onClickTabItem = (e) => {
  204. // 如果要切换的选项卡是“物料列表”,则先判断当前选中的保养项是否关闭消耗物料(rule=1),如果是,则不允许切换到“物料列表”,并提示
  205. if (!onBeforeTabChange(e.currentIndex, currentBomData.value)) {
  206. return;
  207. }
  208. currentTab.value = e.currentIndex;
  209. };
  210. // 关闭物料消耗(rule=1)时,不允许切换到物料列表tab
  211. const onBeforeTabChange = (index, bom) => {
  212. if (index === 2 && bom?.rule == 1) {
  213. uni.showToast({
  214. title: t("maintenanceWorkOrder.notConsumeMaterial"),
  215. icon: "none",
  216. });
  217. return false;
  218. }
  219. return true;
  220. };
  221. // ----------------------------提示信息弹窗----------------------------------
  222. const messageGlobalRef = ref(null);
  223. // 提示信息弹窗类型
  224. const msgType = ref("success");
  225. // 提示信息弹窗内容
  226. const messageText = ref("");
  227. // 提示信息弹窗方法
  228. const showMessage = (type, message) => {
  229. msgType.value = type;
  230. messageText.value = message;
  231. messageGlobalRef.value.openMessage();
  232. };
  233. // -----------------------------表单----------------------------------
  234. const mainWorkOrder = ref({
  235. name: "", // 工单名称
  236. outsourcingFlag: "0", // 保养类型(是否委外 0否 1是)
  237. actualStartTime: "", // 实际开始时间
  238. actualEndTime: "", // 实际结束时间
  239. remark: "", // 备注
  240. cost: "", // 保养费用
  241. otherCost: "", // 其他费用
  242. type: 2, //工单类型(1计划生成 2临时新建),
  243. deptId: null, // 部门id"
  244. responsiblePerson: getUserId(), // 负责人
  245. });
  246. // 获取子组件实例
  247. const workOrderFormRef = ref(null);
  248. // 提交方法中添加校验逻辑
  249. const validateForm = async () => {
  250. // 调用子组件的校验方法
  251. const isValid = await workOrderFormRef.value.validate();
  252. if (isValid) {
  253. // 校验通过,执行提交逻辑
  254. console.log("子组件表单校验通过,校验通过的表单数据:", mainWorkOrder.value);
  255. // 判断mainWorkOrder.value中的实际保养结束时间是否大于实际保养开始时间
  256. if (
  257. mainWorkOrder.value.actualEndTime &&
  258. mainWorkOrder.value.actualStartTime &&
  259. mainWorkOrder.value.actualEndTime < mainWorkOrder.value.actualStartTime
  260. ) {
  261. uni.showToast({
  262. title: t("maintenanceWorkOrder.timeNotBeEarlier"),
  263. icon: "none",
  264. });
  265. mainWorkOrder.value.actualEndTime = "";
  266. return false;
  267. }
  268. return true;
  269. } else {
  270. // 校验失败,不执行提交
  271. console.log("表单校验失败");
  272. return false;
  273. }
  274. };
  275. // 计算保养费用
  276. const calculateMaintenanceCost = () => {
  277. let totalCost = 0;
  278. mainWorkOrderBom.value.forEach((bom) => {
  279. // 条件1:关闭是否消耗物料(rule=1)
  280. const isNotConsumeMaterial = bom.rule == 1;
  281. // 检查是否设置了延保相关信息
  282. const hasDelaySetting = isBomDelaySet(bom);
  283. // 条件2:开启是否消耗物料(rule=0)且未设置延保信息
  284. if (!isNotConsumeMaterial && !hasDelaySetting) {
  285. // 遍历物料计算费用
  286. bom.deviceBomMaterials?.forEach((material) => {
  287. const quantity = Number(material.quantity) || 0;
  288. const unitPrice = Number(material.unitPrice) || 0;
  289. // 只计算消耗数量大于0的物料
  290. if (quantity > 0) {
  291. totalCost += quantity * unitPrice;
  292. }
  293. });
  294. }
  295. });
  296. // 更新工单保养费用(保留两位小数)
  297. mainWorkOrder.value.cost = totalCost.toFixed(2);
  298. };
  299. // ----------------------------------保养项-------------------------------
  300. // 保养项列表
  301. const mainWorkOrderBom = ref([]);
  302. // 保养项列表原数据(提交用:不修改deviceBomMaterials)
  303. const mainWorkOrderBomOriginal = ref([]);
  304. // 保养项统计
  305. const bomNumbers = reactive({
  306. total: 0, // 保养项总数
  307. maintained: 0, // 已保养项数
  308. notMaintained: 0, // 未保养项数
  309. });
  310. // 当前所选保养项
  311. const currentBomData = ref(null);
  312. // 切换当前保养项
  313. const onSelectBom = (bom) => {
  314. console.log("onSelectBom", bom);
  315. // 检查选择的保养项是否关闭消耗物料(rule=1),若关闭则提示且不跳转
  316. if (!onBeforeTabChange(2, bom)) {
  317. return;
  318. }
  319. // 更新当前所选保养项数据
  320. currentBomData.value = bom;
  321. // 更新展示的物料列表
  322. // 更新传递给子组件的物料列表
  323. materialList.value = bom.deviceBomMaterials || [];
  324. // 切换到物料列表tab
  325. currentTab.value = 2;
  326. };
  327. // 更新保养项统计数量(已保养和未保养)
  328. const updateBomStatistics = () => {
  329. let total = mainWorkOrderBom.value.length;
  330. let maintained = 0;
  331. mainWorkOrderBom.value.forEach((bom) => {
  332. // 计算物料总数
  333. calculateMaterialCount(bom);
  334. // 判断是否为已保养项
  335. const isMaintained = checkIfMaintained(bom);
  336. if (isMaintained) {
  337. // 已保养项数增加
  338. maintained++;
  339. // 添加已保养标识
  340. bom.maintainedFlag = true;
  341. } else {
  342. // 添加未保养标识
  343. bom.maintainedFlag = false;
  344. }
  345. });
  346. bomNumbers.total = total;
  347. bomNumbers.maintained = maintained;
  348. bomNumbers.notMaintained = total - maintained;
  349. };
  350. /**
  351. * 检查保养项是否满足已保养条件
  352. * @param {Object} bom - 保养项数据
  353. * @returns {boolean} - 是否满足已保养条件
  354. * @description
  355. * 1.保养状态(status 0保养中 1保养完成)为保养完成时:
  356. * 1). 关闭是否消耗物料(rule=1)
  357. * 2). 开启是否消耗物料(rule=0):存在物料列表,且所有物料的消耗数量均大于0
  358. * 2. 延保相关信息
  359. * 2.1 检查延迟保养规则是否启用
  360. * 2.2 若没有启用的规则,或启用规则但对应延迟值均不大于0
  361. *
  362. */
  363. const checkIfMaintained = (bom) => {
  364. // 检查保养状态是否为保养完成
  365. const bomStatusCompleted = isBomStatusCompleted(bom);
  366. if (bomStatusCompleted) {
  367. // 检查是否关闭消耗物料
  368. const isNotConsumeMaterial = isBomNotConsumeMaterial(bom);
  369. // 检查物料消耗数量是否均大于0
  370. const allMaterialsValid = checkAllMaterialsValid(bom);
  371. }
  372. /**
  373. * 检查是否设置了延保相关信息
  374. * 1. 检查延迟保养规则是否启用:mileageRule,naturalDateRule,runningTimeRule(0启用 1停用)
  375. * 2. 若启用规则但对应延迟值均不大于0
  376. */
  377. const hasDelaySetting = isBomDelaySet(bom);
  378. // 满足任一条件即视为已保养
  379. return bomStatusCompleted || hasDelaySetting;
  380. };
  381. // 保养项 保养状态 是否完成
  382. const isBomStatusCompleted = (bom) => {
  383. const isCompleted = bom.status == 1;
  384. // -----------添加物料保养状态标识------------
  385. bom.isStatusClosed = isCompleted;
  386. // 如果存在物料列表,将isStatusClosed存入每个物料项中
  387. if (bom.deviceBomMaterials.length > 0) {
  388. bom.deviceBomMaterials.forEach((material) => {
  389. material.isStatusClosed = isCompleted;
  390. });
  391. }
  392. return isCompleted;
  393. };
  394. // 保养项是否消耗物料
  395. const isBomNotConsumeMaterial = (bom) => {
  396. const isNotConsumeMaterial = bom.rule == "1";
  397. bom.isNotConsumeMaterial = isNotConsumeMaterial;
  398. // 如果存在物料列表,将isNotConsumeMaterial存入每个物料项中
  399. if (bom.deviceBomMaterials.length > 0) {
  400. bom.deviceBomMaterials.forEach((material) => {
  401. material.isNotConsumeMaterial = isNotConsumeMaterial;
  402. });
  403. }
  404. return isNotConsumeMaterial;
  405. };
  406. // 保养项是否设置了延保信息
  407. const isBomDelaySet = (bom) => {
  408. /**
  409. * 检查是否设置了延保相关信息
  410. * 1. 检查延迟保养规则是否启用:mileageRule,naturalDateRule,runningTimeRule(0启用 1停用)
  411. * 2. 若启用规则但对应延迟值均不大于0
  412. */
  413. const isAnyRuleActive =
  414. bom.mileageRule === 0 ||
  415. bom.naturalDateRule === 0 ||
  416. bom.runningTimeRule === 0;
  417. const hasDelaySetting =
  418. !isAnyRuleActive ||
  419. (isAnyRuleActive &&
  420. (bom.delayKilometers > 0 ||
  421. bom.delayNaturalDate > 0 ||
  422. bom.delayDuration > 0));
  423. // console.log("🚀 是否设置了延保相关信息:", hasDelaySetting);
  424. // 设置保养项是否设置了延保信息
  425. bom.isDelay = hasDelaySetting;
  426. // 如果存在物料列表,将isDelay存入每个物料项中
  427. if (bom.deviceBomMaterials.length > 0) {
  428. bom.deviceBomMaterials.forEach((material) => {
  429. // 添加物料是否延迟标志
  430. material.isDelay = hasDelaySetting;
  431. });
  432. }
  433. return hasDelaySetting;
  434. };
  435. // 检查所有物料消耗数量是否均大于0
  436. const checkAllMaterialsValid = (bom) => {
  437. if (!bom.deviceBomMaterials || bom.deviceBomMaterials.length === 0) {
  438. return false;
  439. }
  440. return bom.deviceBomMaterials.every((material) => {
  441. const quantity = Number(material.quantity);
  442. return !isNaN(quantity) && quantity > 0;
  443. });
  444. };
  445. // 计算保养项中所有物料的消耗数量累计
  446. const calculateMaterialCount = (bom) => {
  447. if (!bom.deviceBomMaterials || bom.deviceBomMaterials.length === 0) {
  448. bom.materialCount = 0;
  449. return;
  450. }
  451. // 物料数量为保养项下所有物料 消耗数量累计
  452. const total = bom.deviceBomMaterials.reduce((sum, material) => {
  453. const quantity = Number(material.quantity) || 0;
  454. return sum + quantity;
  455. }, 0);
  456. bom.materialCount = total;
  457. };
  458. // 更新保养项数量统计及保养费用
  459. const updateBomStatisticsAndCost = () => {
  460. // 更新保养项统计数量(已保养和未保养)
  461. updateBomStatistics();
  462. // 更新保养费用
  463. calculateMaintenanceCost();
  464. };
  465. // ----------------------------------物料列表---------------------------------
  466. // 所有物料列表
  467. const materialListAll = ref([]);
  468. // 获取的保养项物料列表
  469. const materialByWorkOrderIdList = ref([]);
  470. // 获取的保养项物料列表根据bomNodeId与id组合的对象
  471. const materialByWorkOrderIdKeyAndList = ref({});
  472. // 传递给子组件的物料列表
  473. const materialList = ref([]);
  474. // 是否展示所有物料
  475. const allMaterialShow = ref(false);
  476. // 新添加的物料项
  477. const newMaterialItem = ref({
  478. id: null, //物料id
  479. bomNodeId: null, // 保养项id
  480. bomName: null, // 保养项名称
  481. materialCode: null, // 物料编码
  482. materialName: null, // 物料名称
  483. unit: null, // 物料单位
  484. unitPrice: 0, // 物料单价
  485. quantity: 0, // 物料消耗数量
  486. materialSource: t("workOrder.manualAdd"), // 物料来源
  487. isNew: true, // 是否是新添加的物料
  488. });
  489. // 查询保养工单已经选择的所有物料
  490. const getBomsMaterialList = async (id) => {
  491. const getBomMaterialsByWorkOrderIdAsync = getBomMaterialsByWorkOrderId({
  492. workOrderId: id,
  493. });
  494. const bomMaterials = (await getBomMaterialsByWorkOrderIdAsync).data;
  495. if (bomMaterials && bomMaterials.length > 0) {
  496. materialByWorkOrderIdList.value = bomMaterials;
  497. // 根据bomNodeId与id组合保养项物料列表
  498. materialByWorkOrderIdList.value.forEach((material) => {
  499. const key = material.bomNodeId;
  500. if (materialByWorkOrderIdKeyAndList.value[key]) {
  501. materialByWorkOrderIdKeyAndList.value[key].push(material);
  502. } else {
  503. materialByWorkOrderIdKeyAndList.value[key] = [material];
  504. }
  505. });
  506. }
  507. };
  508. // 新增物料
  509. const addMaterial = () => {
  510. /**
  511. * 新增物料
  512. * 1. 不直接在materialList.value中添加新物料是因为:
  513. * 在展示所有物料时添加的新物料时再次切换到展示当前物料,新增的物料不会更新到当前保养项中
  514. * 2. 将新添加的物料项放入当前保养项的物料列表第一个
  515. */
  516. currentBomData.value.deviceBomMaterials.unshift({
  517. ...newMaterialItem.value,
  518. bomNodeId: currentBomData.value.bomNodeId,
  519. bomName: currentBomData.value.name,
  520. });
  521. // 更新展示的物料列表
  522. changeMaterialListByShow();
  523. // 更新保养项数量统计及保养费用
  524. updateBomStatisticsAndCost();
  525. };
  526. // 删除物料
  527. const onDeleteMaterial = (data) => {
  528. materialList.value.splice(data.index, 1);
  529. // 更新当前保养项的物料数量
  530. calculateMaterialCount(currentBomData.value);
  531. // 更新保养项数量统计及保养费用
  532. updateBomStatisticsAndCost();
  533. };
  534. // 选择物料
  535. const materialsChooseRef = ref(null);
  536. const onMaterialChoose = (item) => {
  537. console.log("onMaterialChoose", item);
  538. materialsChooseRef.value.open(item);
  539. };
  540. // 选择物料弹窗提交
  541. const onMaterialChooseSubmit = (item) => {
  542. console.log("onMaterialChooseSubmit", item);
  543. /**
  544. * 1. 检查当前物料是否已存在
  545. * 保养项id(bomNodeId)、
  546. * 物料编码(materialCode)或物料名称(materialName)、
  547. * 成本中心id(costCenterId)、
  548. * 工厂id(factoryId)、
  549. * 单价(unitPrice)、
  550. * 库位id(storageLocationId)
  551. * 全部相同时视为同一物料
  552. * 2. 若已存在,则累加数量
  553. * 3. 若不存在,则新增物料
  554. */
  555. // 是否存在相同物料,若存在则累加数量,若不存在则新增
  556. const existMaterial = currentBomData.value.deviceBomMaterials.find(
  557. (material) => {
  558. const materialChooseKey = `${
  559. material.materialCode ? material.materialCode : material.materialName
  560. }_${material.costCenterId}_${material.factoryId}_${material.unitPrice}_${
  561. material.storageLocationId
  562. }`;
  563. return materialChooseKey === item.chooseKey;
  564. }
  565. );
  566. if (existMaterial) {
  567. existMaterial.quantity += item.quantity;
  568. } else {
  569. currentBomData.value.deviceBomMaterials.unshift({
  570. ...item,
  571. bomNodeId: currentBomData.value.bomNodeId, // 保养项id
  572. bomName: currentBomData.value.name, // 保养项名称
  573. isNew: false, // 是否是新添加的物料
  574. });
  575. }
  576. // 更新展示的物料列表
  577. changeMaterialListByShow();
  578. // 更新保养项数量统计及保养费用
  579. updateBomStatisticsAndCost();
  580. };
  581. // 切换展示的物料列表
  582. const changeMaterialList = () => {
  583. // 切换展示所有物料状态
  584. allMaterialShow.value = !allMaterialShow.value;
  585. // 根据展示状态切换物料列表
  586. changeMaterialListByShow();
  587. };
  588. // 返回所有物料列表
  589. const getAllMaterialList = () => {
  590. const list = mainWorkOrderBom.value
  591. .map((bom) => {
  592. if (bom.deviceBomMaterials) {
  593. return bom.deviceBomMaterials;
  594. }
  595. })
  596. .flat();
  597. return list;
  598. };
  599. // 根据物料展示状态(allMaterialShow),切换展示的物料列表
  600. const changeMaterialListByShow = () => {
  601. // allMaterialShow 为 true 时展示所有物料列表
  602. if (allMaterialShow.value) {
  603. materialList.value = getAllMaterialList();
  604. } else {
  605. // allMaterialShow 为 false 时展示当前所选保养项的物料列表
  606. materialList.value = currentBomData.value.deviceBomMaterials;
  607. }
  608. };
  609. // 保存物料列表
  610. const saveMartialList = async () => {
  611. /**
  612. * 校验当前展示的物料列表
  613. * 1.物料列表为空时,提示请添加物料
  614. * 2.新增的物料(isNew=true)物料名称不能为空,单价不能为空,消耗数量>0
  615. * 3.已有的物料(isNew=false)单价不能为空,消耗数量>0
  616. * 记录所有不符合校验的物料保养项名称,拼接提示
  617. */
  618. console.log("保存物料列表:开始");
  619. // 校验物料列表是否为空
  620. if (materialList.value.length === 0) {
  621. uni.showToast({
  622. title: t("maintenanceWorkOrder.materialEmpty"),
  623. icon: "none",
  624. });
  625. return;
  626. }
  627. const invalidBomNames = [];
  628. materialList.value.map((material, index) => {
  629. // 校验新增的物料
  630. if (material.isNew) {
  631. if (
  632. !material.materialName ||
  633. !material.unitPrice ||
  634. !material.quantity ||
  635. material.quantity <= 0
  636. ) {
  637. invalidBomNames.push(
  638. material.bomName +
  639. ":" +
  640. (material.materialName
  641. ? material.materialName
  642. : t("workOrder.serialNumber") + "(" + (index + 1) + ")")
  643. );
  644. }
  645. } else {
  646. // 校验已有的物料
  647. if (!material.unitPrice || !material.quantity || material.quantity <= 0) {
  648. invalidBomNames.push(material.bomName + ":" + material.materialName);
  649. }
  650. }
  651. });
  652. // 去重
  653. const uniqueInvalidBomNames = [...new Set(invalidBomNames)];
  654. /**
  655. * 拆分提示
  656. * 若不符合校验的物料大于3,提示前3项,其余项用...表示
  657. * 若不符合保养项的数量小于3,则全部提示
  658. */
  659. if (uniqueInvalidBomNames.length > 0) {
  660. const msg =
  661. uniqueInvalidBomNames.slice(0, 3).join(";\n") +
  662. (uniqueInvalidBomNames.length > 3 ? "..." : "\n") +
  663. t("workOrder.materialInvalid");
  664. showMessage("error", msg);
  665. return;
  666. }
  667. // 校验通过,将tab切换到保养项列表并提示保存成功
  668. const msg = t("workOrder.currentMaterialList") + t("operation.saveSuccess");
  669. showMessage("success", msg);
  670. currentTab.value = 1;
  671. console.log("保存物料列表:结束");
  672. };
  673. // ----------------------------------工单提交---------------------------------
  674. const submitWorkOrder = async () => {
  675. // 校验表单信息
  676. const formIsValid = await validateForm();
  677. if (!formIsValid) return;
  678. // 校验是否存在未保养项
  679. if (bomNumbers.notMaintained > 0) {
  680. uni.showToast({
  681. title: t("maintenanceWorkOrder.unFinishedMaintenanceItems"),
  682. icon: "none",
  683. });
  684. return;
  685. }
  686. // 处理提交的参数
  687. // 提取数据处理逻辑为独立函数 - 使用对象解构赋值代替 delete 操作,避免修改原始数据
  688. const processBomData = (bomItems) => {
  689. console.log("🚀 ~ processBomData ~ bomItems:", bomItems);
  690. console.log(
  691. "🚀 ~ processBomData ~ mainWorkOrderBomOriginal.value:",
  692. mainWorkOrderBomOriginal.value
  693. );
  694. return bomItems.map((item) => {
  695. const {
  696. materialCount,
  697. maintainedFlag,
  698. deviceBomMaterials,
  699. isDelay,
  700. isNotConsumeMaterial,
  701. ...rest
  702. } = item;
  703. // 查找原始数据中对应的deviceBomMaterials
  704. const originalDeviceBomMaterials = mainWorkOrderBomOriginal.value.find(
  705. (bom) => bom.bomNodeId === item.bomNodeId
  706. )?.deviceBomMaterials;
  707. // 用原始数据的deviceBomMaterials重新赋值给 rest.deviceBomMaterials
  708. rest.deviceBomMaterials = originalDeviceBomMaterials;
  709. return rest;
  710. });
  711. };
  712. const processMaterialData = (materialItems) => {
  713. return materialItems.map((item) => {
  714. const {
  715. bomName,
  716. isNew,
  717. bomDisabled,
  718. isDelay,
  719. isNotConsumeMaterial,
  720. isStatusClosed,
  721. ...rest
  722. } = item;
  723. // 将 materialName/name和materialCode/code保持一致
  724. rest.materialName = rest.materialName ? rest.materialName : rest.name;
  725. rest.name = rest.materialName;
  726. rest.materialCode = rest.materialCode ? rest.materialCode : rest.code;
  727. rest.code = rest.materialCode;
  728. return rest;
  729. });
  730. };
  731. // 删除mainWorkOrderBom.value中的maintainedFlag, materialCount字段
  732. const orderBom = processBomData([
  733. ...JSON.parse(JSON.stringify(mainWorkOrderBom.value)),
  734. ]);
  735. console.log("orderBom", orderBom);
  736. // 获取所有物料列表,删除bomName,isNew,bomDisabled字段
  737. const mainWorkOrderMaterials = getAllMaterialList();
  738. const orderMaterials = processMaterialData([...mainWorkOrderMaterials]);
  739. fillWorkOrder({
  740. mainWorkOrder: mainWorkOrder.value,
  741. mainWorkOrderBom: orderBom,
  742. mainWorkOrderMaterials: orderMaterials,
  743. })
  744. .then((res) => {
  745. console.log("fillWorkOrder", res);
  746. if (res.code == 0) {
  747. uni.showToast({
  748. title: t("operation.success"),
  749. icon: "none",
  750. });
  751. setTimeout(() => {
  752. uni.navigateBack();
  753. }, 2000);
  754. } else {
  755. uni.showToast({
  756. title: res.msg,
  757. icon: "none",
  758. });
  759. }
  760. })
  761. .catch((err) => {
  762. console.log("err", err);
  763. });
  764. };
  765. // ----------------------------工单详情初始化---------------------------------
  766. const workOrderDetailInit = async (id) => {
  767. // 获取工单信息
  768. const getMaintenanceDetailAsync = getMaintenanceDetail({ id: id });
  769. mainWorkOrder.value = (await getMaintenanceDetailAsync).data;
  770. // 获取保养项信息
  771. const getWorkOrderBOMsAsync = getWorkOrderBOMs({ workOrderId: id });
  772. const data = (await getWorkOrderBOMsAsync).data;
  773. if (!data.length) return;
  774. // 备份保养项列表原数据(提交用:不修改deviceBomMaterials)
  775. mainWorkOrderBomOriginal.value = JSON.parse(JSON.stringify(data));
  776. //组合mainWorkOrderBom,合并保养项物料列表
  777. data.forEach((bom) => {
  778. // 合并保养项物料列表
  779. const bomKey = bom.bomNodeId;
  780. if (materialByWorkOrderIdKeyAndList.value[bomKey]) {
  781. bom.deviceBomMaterials = materialByWorkOrderIdKeyAndList.value[bomKey];
  782. }
  783. // 若保养项物料列表为空,则初始化空数组
  784. if (!bom.deviceBomMaterials) {
  785. bom.deviceBomMaterials = [];
  786. }
  787. // 若保养项不在保养项列表中,则添加到保养项列表
  788. if (
  789. !mainWorkOrderBom.value.some(
  790. (item) => item.bomNodeId === bom.bomNodeId && item.id === bom.id
  791. )
  792. ) {
  793. mainWorkOrderBom.value.push({
  794. ...bom,
  795. materialCount: 0,
  796. });
  797. }
  798. // 将保养项名称添加到对应的物料列表中
  799. if (bom.deviceBomMaterials) {
  800. bom.deviceBomMaterials.forEach((material) => {
  801. // 为物料添加保养项名称
  802. material.bomName = bom.name;
  803. // 为物料添加是否消耗物料标识(rule=0时为消耗物料,1时为不消耗物料)
  804. material.bomDisabled = bom.rule == 1;
  805. // 确保物料名称和物料编码不为空 且 materialName/name和materialCode/code保持一致
  806. material.materialName = material.materialName
  807. ? material.materialName
  808. : material.name;
  809. material.name = material.materialName;
  810. material.materialCode = material.materialCode
  811. ? material.materialCode
  812. : material.code;
  813. material.code = material.materialCode;
  814. });
  815. }
  816. });
  817. // 默认选中第一个保养项
  818. currentBomData.value = mainWorkOrderBom.value[0];
  819. // 更新传递给子组件的物料列表
  820. materialList.value = currentBomData.value.deviceBomMaterials || [];
  821. // 初始化统计数据
  822. updateBomStatistics();
  823. };
  824. onLoad(async (option) => {
  825. console.log("onLoad", option);
  826. // 通过userId刷新token
  827. await reloginByUserId(option.reloginUserId);
  828. // 查询保养工单已经选择的所有物料
  829. await getBomsMaterialList(option.id);
  830. // 初始化工单详情
  831. await workOrderDetailInit(option.id);
  832. });
  833. onReady(() => {
  834. console.log("onReady");
  835. });
  836. onMounted(() => {
  837. console.log("onMounted");
  838. });
  839. onBackPress(() => {
  840. console.log("onBackPress");
  841. });
  842. </script>
  843. <style lang="scss" scoped>
  844. @import "@/style/work-order-segmented.scss";
  845. .page {
  846. padding-bottom: 0;
  847. }
  848. </style>