diff --git a/index.html b/index.html index f5abd33..fbc14be 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - Teek Design Vue3 + 牛安管理系统
diff --git a/src/common/languages/locales/en-US.ts b/src/common/languages/locales/en-US.ts index b90ec5e..0dbba35 100644 --- a/src/common/languages/locales/en-US.ts +++ b/src/common/languages/locales/en-US.ts @@ -90,7 +90,7 @@ export default { remark: "Remark", startdate: "Start Date", enddate: "End Date", - searchCode: "Doc No/Name/Remark", + searchCode: "Doc No / Remark", }, warehouse: { warehouse: { @@ -233,6 +233,7 @@ export default { productCount: "Count", productMark: "Remark", createDate: "Create Date", + searchCode: "Form Code/Name/Remark", }, finishedproductreceipt: { totalValue: "Receipt Count", @@ -255,6 +256,7 @@ export default { remark: "Remark", createDate: "Create Date", keyAccountId: "Customer", + searchCode: "Form Code/Name/Remark", }, }, purchase: { @@ -268,6 +270,7 @@ export default { baseTitle: "Purchase Plan Basic Info", tableTitle: "Purchase Details", generateOrder: "Generate Purchase Order", + showItem: "Purchase Details", model: "Model", defaultVendor: "Default Vendor", demandQuantity: "Demand Qty", @@ -276,6 +279,7 @@ export default { unitPrice: "Unit Price", totalPrice: "Total Price", purchaseStatus: "Purchase Status", + searchCode: "Plan No/Name/Remark", }, purchase_plan_item: { partNumber: "Part Number", @@ -343,6 +347,7 @@ export default { sendCount: "Out Count", surplusCount: "Remaining", saleMark: "Remark", + searchCode: "Form Code/Name/Remark", }, devicesn: { softVersion: "Software Version", @@ -691,6 +696,10 @@ export default { outstock_success: "Outstock success", outstock_fail: "Outstock failed", input_outstock_mark: "Please enter outstock remark", + duplicate_productSn: "Duplicate SN: {value}", + duplicate_mac: "Duplicate MAC Address: {value}", + duplicate_serialNum: "Duplicate Serial Number: {value}", + validate_failed: "Device validation failed", }, }, purchase: { @@ -715,6 +724,7 @@ export default { }, purchaseorder: { delete_message: "Delete Order", + part_number_not_exists: "Part number does not exist", }, finishedproductshipment: { select_storeId: "Please select store", @@ -819,6 +829,7 @@ export default { stocktransferorder: { add: "Add Transfer", edit: "Edit Transfer", + stock_check_failed: "Stock check failed", }, warehousereceipt: { add: "Add Receipt", @@ -845,6 +856,10 @@ export default { }, production_issue: { returnDialog: "Return Edit", + showItem: "Issue Details", + }, + production_return: { + showItem: "Return Details", }, finishedproductreceipt: { add: "Add Receipt", diff --git a/src/common/languages/locales/zh-CN.ts b/src/common/languages/locales/zh-CN.ts index 768e13d..50d7fce 100644 --- a/src/common/languages/locales/zh-CN.ts +++ b/src/common/languages/locales/zh-CN.ts @@ -90,7 +90,7 @@ export default { remark: "备注", startdate: "开始时间", enddate: "结束时间", - searchCode: "单据编号/名称/备注", + searchCode: "单据编号 / 备注", }, warehouse: { warehouse: { @@ -233,6 +233,7 @@ export default { productCount: "数量", productMark: "备注", createDate: "创建时间", + searchCode: "单据编号/名称/备注", }, finishedproductreceipt: { totalValue: "入库数量", @@ -255,6 +256,7 @@ export default { remark: "备注", createDate: "创建时间", keyAccountId: "客户", + searchCode: "单据编号/名称/备注", }, }, purchase: { @@ -268,6 +270,7 @@ export default { baseTitle: "采购计划基本信息", tableTitle: "采购明细", generateOrder: "生成采购订单", + showItem: "采购明细", model: "型号", defaultVendor: "默认供应商", demandQuantity: "需求量", @@ -276,14 +279,15 @@ export default { unitPrice: "单价", totalPrice: "总价", purchaseStatus: "采购状态", + searchCode: "计划号/名称/备注", }, purchase_plan_item: { - partNumber: "商品编号", + partNumber: "物料编号", purchaseCount: "计划数量", completeCount: "已完成数量", price: "单价", currentCount: "本次采购数量", - input_partNumber: "请输入商品编号", + input_partNumber: "请输入物料编号", input_purchaseCount: "请输入计划数量", input_price: "请输入单价", input_currentCount: "请输入本次采购数量", @@ -343,6 +347,7 @@ export default { sendCount: "出库数量", surplusCount: "剩余量", saleMark: "备注", + searchCode: "单据编号/名称/备注", }, devicesn: { softVersion: "软件版本", @@ -699,6 +704,10 @@ export default { outstock_success: "出货成功", outstock_fail: "出货失败", input_outstock_mark: "请输入出货备注", + duplicate_productSn: "SN码重复:{value}", + duplicate_mac: "MAC地址重复:{value}", + duplicate_serialNum: "序列号重复:{value}", + validate_failed: "验证设备失败", }, finishedproductshipment: { select_storeId: "请选择仓库", @@ -763,6 +772,7 @@ export default { input_partNumber: "请输入物料编号", input_purchaseCount: "请输入采购数量", input_price: "请输入单价", + part_number_not_exists: "物料编号不存在", }, finishedproductshipment: { select_storeId: "请选择仓库", @@ -917,6 +927,7 @@ export default { showItem: "调拨明细", baseTitle: "调拨单基本信息", tableTitle: "调拨明细", + stock_check_failed: "库存检测失败", }, warehousereceipt: { add: "添加入库单", @@ -951,6 +962,9 @@ export default { returnDialog: "退料编辑", showItem: "发料明细", }, + production_return: { + showItem: "退料明细", + }, finishedproductreceipt: { add: "添加成品入库单", edit: "编辑成品入库单", diff --git a/src/common/languages/mapping/base-info-mapping.ts b/src/common/languages/mapping/base-info-mapping.ts index 362b83e..b52cd7f 100644 --- a/src/common/languages/mapping/base-info-mapping.ts +++ b/src/common/languages/mapping/base-info-mapping.ts @@ -82,7 +82,7 @@ export const useStatus = () => { }; const getPurchasePlanItemStatusLabel = (code: number | null): string => { - if (code === null || code === undefined) return ""; + if (code === null || code === undefined) return t("_base_info.purchase.purchase_plan_item.no_start"); const key = purchasePlanItemStatusKeyMap[code]; return key ? t(key) : t(unknown); }; diff --git a/src/components/base/base-item-dialog/BaseItemDialog.vue b/src/components/base/base-item-dialog/BaseItemDialog.vue index f00c80f..e517cea 100644 --- a/src/components/base/base-item-dialog/BaseItemDialog.vue +++ b/src/components/base/base-item-dialog/BaseItemDialog.vue @@ -78,7 +78,7 @@ watch(() => parentParamValue.value, loadData); } .item-dialog { - --el-dialog-width: fit-content !important; + --el-dialog-width: 1400px !important; --el-dialog-padding-primary: 20px; } diff --git a/src/components/base/base-select/BaseSelect.vue b/src/components/base/base-select/BaseSelect.vue index e919239..5803c48 100644 --- a/src/components/base/base-select/BaseSelect.vue +++ b/src/components/base/base-select/BaseSelect.vue @@ -11,6 +11,7 @@ const props = defineProps({ label: String, url: String, data: Array>, + parse: Function, }); const optionData = ref([]); @@ -37,10 +38,13 @@ defineExpose({ const option = optionData.value.find(item => item.value === model.value); return option?.label; }, + getOptions: () => { + return optionData.value; + }, }); diff --git a/src/components/base/search-bar/SearchBar.vue b/src/components/base/search-bar/SearchBar.vue index dbf2762..9bc5370 100644 --- a/src/components/base/search-bar/SearchBar.vue +++ b/src/components/base/search-bar/SearchBar.vue @@ -59,11 +59,13 @@ function reset() { display: flex; padding-right: 20px; } + .searcher-box { - padding-left: 5px; padding-right: 5px; + padding-left: 5px; } + .searcher { - width: 285px; + width: 400px; } diff --git a/src/views/production/finished-product-receipt/FinishedProductReceiptView.vue b/src/views/production/finished-product-receipt/FinishedProductReceiptView.vue index 6b7030d..2c4c018 100644 --- a/src/views/production/finished-product-receipt/FinishedProductReceiptView.vue +++ b/src/views/production/finished-product-receipt/FinishedProductReceiptView.vue @@ -32,8 +32,7 @@ const saveOutstockUrl = "/production/finishedproductreceipt/saveOutstock"; const getKeyAccountSelectListUrl = "/sys/keyaccount/getKeyAccountSelectList"; const itemArrayName = "deviceItems"; const searchers = [ - { name: "searchCode", type: "text" as const, placeholder: $t("_prop.production.finishedproductreceipt.formCode") }, - { name: "formName", type: "text" as const, placeholder: $t("_prop.production.finishedproductreceipt.formName") }, + { name: "searchCode", type: "text" as const, placeholder: $t("_prop.production.finishedproductreceipt.searchCode") }, ]; const rules = reactive({ formCode: [ @@ -58,6 +57,56 @@ const rules = reactive({ }, ], }); + +const validateDeviceDuplicates = (deviceItems: any[]): string | null => { + const productSns = new Set(); + const macs = new Set(); + const serialNums = new Set(); + + for (const item of deviceItems) { + if (item.productSn) { + if (productSns.has(item.productSn)) { + return $t("_message.production.finishedproductreceipt.duplicate_productSn", { value: item.productSn }); + } + productSns.add(item.productSn); + } + if (item.mac) { + if (macs.has(item.mac)) { + return $t("_message.production.finishedproductreceipt.duplicate_mac", { value: item.mac }); + } + macs.add(item.mac); + } + if (item.serialNum) { + if (serialNums.has(item.serialNum)) { + return $t("_message.production.finishedproductreceipt.duplicate_serialNum", { value: item.serialNum }); + } + serialNums.add(item.serialNum); + } + } + return null; +}; + +const validateDevicesWithBackend = async (deviceItems: any[]): Promise => { + const validateUrl = "/sale/device/validateDevices"; + const devices = deviceItems.map((item: any) => ({ + productSn: item.productSn || "", + mac: item.mac || "", + serialNum: item.serialNum || "", + })); + + try { + const res = await post(validateUrl, { devices }); + if (res.code === 0 && res.data) { + if (!res.data.valid) { + return res.data.message; + } + } + return null; + } catch (error) { + console.error("验证设备失败:", error); + return $t("_message.production.finishedproductreceipt.validate_failed"); + } +}; /** * 基本不变通用变量 */ @@ -160,7 +209,7 @@ const mappingConfig: FieldMappingConfig = { const add = () => { form.value = {}; title.value = "_title.production.finishedproductreceipt.add"; - form.value["formCode"] = generateDucumentNo("CPRK"); + form.value["formCode"] = generateDucumentNo("RK"); visible.value = true; formType.value = false; }; @@ -258,13 +307,26 @@ const showItem = (row: any) => { itemParentId.value = row.id; itemVisible.value = true; }; -const submit = (form: any, formRef: FormInstance | undefined) => { +const submit = async (formData: any, formRef: FormInstance | undefined) => { setName(); if (formRef !== undefined) { + const deviceItems = formData.deviceItems || []; + const localDuplicateError = validateDeviceDuplicates(deviceItems); + if (localDuplicateError) { + ElMessage.error(localDuplicateError); + return; + } + + const backendDuplicateError = await validateDevicesWithBackend(deviceItems); + if (backendDuplicateError) { + ElMessage.error(backendDuplicateError); + return; + } + formRef.validate(valid => { if (valid) { - if (formType.value) useEdit(editUrl, form, visible); - else useAdd(addUrl, form, visible); + if (formType.value) useEdit(editUrl, formData, visible); + else useAdd(addUrl, formData, visible); } }); } @@ -304,7 +366,7 @@ const setName = () => { form.value["storeName"] = warehouseSelectRef.value?.getLabel(); }; const handleReset = () => { - form.value["formCode"] = generateDucumentNo("CPRK"); + form.value["formCode"] = generateDucumentNo("RK"); }; diff --git a/src/views/sale/sale-order/SaleOrderView.vue b/src/views/sale/sale-order/SaleOrderView.vue index 3277763..946edb6 100644 --- a/src/views/sale/sale-order/SaleOrderView.vue +++ b/src/views/sale/sale-order/SaleOrderView.vue @@ -25,21 +25,37 @@ const approveUrl = "/sale/saleorder/approveSaleOrder"; const rejectUrl = "/sale/saleorder/unapproveSaleOrder"; const itemUrl = "/sale/saleorder/getSaleOrderItemList"; const keyAccountSelectUrl = "/sys/keyaccount/getKeyAccountSelectList"; +const partNumberExistsUrl = "/warehouse/warehouseitem/existsWarehouseItem"; -const searchers = [ - { - name: "customerName", - type: "text" as const, - placeholder: $t("_prop.sale.saleorder.customerName"), - clearable: true, - }, -]; +const searchers = [{ name: "searchCode", type: "text" as const, placeholder: $t("_prop.sale.saleorder.searchCode") }]; const rules = reactive({ formCode: [{ required: true, message: $t("_message.sale.saleorder.input_formCode"), trigger: "blur" }], formName: [{ required: true, message: $t("_message.sale.saleorder.input_formName"), trigger: "blur" }], customerId: [{ required: true, message: $t("_message.sale.saleorder.select_customerId"), trigger: "change" }], customerName: [{ required: true, message: $t("_message.sale.saleorder.select_customerId"), trigger: "change" }], + partNumber: [ + { + required: true, + validator: (rule, value, callback) => { + if (!value) { + callback($t("_message.sale.saleorder.input_partNumber")); + } + (async () => { + try { + const isExists = await get(partNumberExistsUrl, { partNumber: value }).then(res => res.data); + if (isExists) { + callback(); + } + callback($t("_message.purchase.purchase_order.part_number_not_exists")); + } catch (err: any) { + callback(new Error(err)); + } + })(); + }, + trigger: "blur", + }, + ], saleOrderItems: [ { required: true, @@ -96,7 +112,7 @@ const generateFormCode = () => { String(now.getHours()).padStart(2, "0") + String(now.getMinutes()).padStart(2, "0") + String(now.getSeconds()).padStart(2, "0"); - return "SO" + dateStr; + return "PO" + dateStr; }; // 监听客户选择变化,同步更新客户名称 diff --git a/src/views/warehouse/stock-transfer-order/StockTransferOrderView.vue b/src/views/warehouse/stock-transfer-order/StockTransferOrderView.vue index 87fe9c5..5461cc6 100644 --- a/src/views/warehouse/stock-transfer-order/StockTransferOrderView.vue +++ b/src/views/warehouse/stock-transfer-order/StockTransferOrderView.vue @@ -7,7 +7,8 @@ import type { FormInstance, FormItemRule, FormRules } from "element-plus"; import { formatDate } from "@/common/utils/format-utils"; import ExpandablePageableTable from "@/components/base/expandable-pageable-table/ExpandablePageableTable.vue"; import BaseFormWithTable from "@/components/base/base-form-with-table/BaseFormWithTable.vue"; -import { get } from "@/common/http/request"; +import { get, post } from "@/common/http/request"; +import { ElMessage } from "element-plus"; import { type FieldMappingConfig } from "@/components/base/base-form-with-table/type"; import DefaultStatusSwitchColumn from "@/components/base/default-column/DefaultStatusSwitchColumn.vue"; import { useStatus } from "@/common/languages/mapping/base-info-mapping"; @@ -28,6 +29,7 @@ const approveUrl = "/warehouse/stocktransferorder/approveStockTransferOrder"; const rejectUrl = "/warehouse/stocktransferorder/rejectStockTransferOrder"; const warehouseSelectUrl = "/warehouse/warehouse/getWarehouseSelectList"; const partNumberExistsUrl = "/warehouse/warehouseitem/existsWarehouseItem"; +const checkStockUrl = "/warehouse/stock/checkStock"; const searchers = [ { name: "searchCode", type: "text" as const, placeholder: $t("_prop.warehouse.stocktransferorder.searchCode") }, { name: "partNumber", type: "text" as const, placeholder: $t("_prop.warehouse.stocktransferorder.partNumber") }, @@ -96,6 +98,31 @@ const rules = reactive({ trigger: "change", }, ], + stockCheck: [ + { + validator: (rule, value, callback) => { + const items = form.value.transferOrderItems || []; + const partCountMap: Record = {}; + items.forEach((item: any) => { + if (item.partNumber) { + partCountMap[item.partNumber] = (partCountMap[item.partNumber] || 0) + (item.productCount || 0); + } + }); + const stockCheckItems = Object.keys(partCountMap).map(partNumber => ({ + partNumber, + requiredCount: partCountMap[partNumber], + })); + post(checkStockUrl, { storeNo: form.value.outStoreNo, items: stockCheckItems }) + .then(() => { + callback(); + }) + .catch((err: any) => { + callback(new Error(err)); + }); + }, + trigger: "submit", + }, + ], }); const itemArrayName = "transferOrderItems"; @@ -226,8 +253,11 @@ const submit = (form: any, formRef: FormInstance | undefined) => { if (targetRef !== undefined) { targetRef.validate((valid: any) => { if (valid) { - if (formType.value) useEdit(editUrl, form, visible); - else useAdd(addUrl, form, visible); + if (formType.value) { + useEdit(editUrl, form, visible); + } else { + useAdd(addUrl, form, visible); + } } }); } @@ -262,8 +292,8 @@ const operateButtonClick = (eventName: string, row: any) => { }; const authShowFunc = (row: any, button: globalThis.ButtonProp) => { - if (row.formStatus === 0 && button.eventName === "reject") return false; - if (row.formStatus === 1 && button.eventName === "approve") return false; + if (row.status === 0 && button.eventName === "reject") return false; + if (row.status === 1 && button.eventName === "approve") return false; return true; }; @@ -288,7 +318,7 @@ const authShowFunc = (row: any, button: globalThis.ButtonProp) => { diff --git a/src/views/warehouse/warehouse-receipt/WarehouseReceiptView.vue b/src/views/warehouse/warehouse-receipt/WarehouseReceiptView.vue index ccd0159..7c7a44c 100644 --- a/src/views/warehouse/warehouse-receipt/WarehouseReceiptView.vue +++ b/src/views/warehouse/warehouse-receipt/WarehouseReceiptView.vue @@ -156,9 +156,9 @@ const operateButtonClick = (eventName: string, row: any) => { }; const authShowFunc = (row: any, button: globalThis.ButtonProp) => { - if (row.formStatus === 0 && button.eventName === "reject") return false; - if (row.formStatus === 1 && button.eventName === "approve") return false; - if (row.formStatus === 1 && button.eventName === "edit") return false; + if (row.status === 0 && button.eventName === "reject") return false; + if (row.status === 1 && button.eventName === "approve") return false; + if (row.status === 1 && button.eventName === "edit") return false; return true; }; @@ -216,7 +216,7 @@ const submit = (form: any, formRef: FormInstance | undefined) => { diff --git a/src/views/warehouse/warehouse/WarehouseView.vue b/src/views/warehouse/warehouse/WarehouseView.vue index 28ea9da..92ad184 100644 --- a/src/views/warehouse/warehouse/WarehouseView.vue +++ b/src/views/warehouse/warehouse/WarehouseView.vue @@ -11,15 +11,18 @@ import { formatDate } from "@/common/utils/format-utils"; /** * 必须要的变量 */ - +// defineOptions({ +// name: "store", +// }); const getPageUrl = "/warehouse/warehouse/getWarehousePage"; const addUrl = "/warehouse/warehouse/addWarehouse"; const editUrl = "/warehouse/warehouse/updateWarehouse"; const removeUrl = "/warehouse/warehouse/deleteWarehouse"; -const searchers = [ - { name: "searchCode", type: "text" as const, placeholder: $t("_prop.warehouse.warehouse.searchCode") }, -]; -const rules = reactive({}); +const searchers = [{ name: "storeName", type: "text" as const, placeholder: $t("_prop.systemset.store.storeName") }]; +const rules = reactive({ + storeName: [{ required: true, message: $t("_message.systemset.store.input_storeName"), trigger: "blur" }], + storeMark: [{ required: false, message: $t("_message.systemset.store.input_storeMark"), trigger: "blur" }], +}); /** * 基本不变通用变量 */ @@ -32,18 +35,18 @@ const { title, visible, formType, form } = useGeneralPageRef(); const add = () => { form.value = {}; - title.value = "_title.warehouse.warehouse.add"; + title.value = "_title.systemset.store.add"; visible.value = true; formType.value = false; }; const edit = (row: any) => { - title.value = "_title.warehouse.warehouse.edit"; + title.value = "_title.systemset.store.edit"; form.value = { ...row }; visible.value = true; formType.value = true; }; const remove = (row: any) => { - useRemove(removeUrl, row.id, "_message.warehouse.warehouse.delete_message"); + useRemove(removeUrl, row.id, "_message.systemset.store.delete_message"); }; const submit = (form: any, formRef: FormInstance | undefined) => { if (formRef !== undefined) { @@ -79,9 +82,8 @@ const operateButtonClick = (eventName: string, row: any) => { @@ -89,6 +91,12 @@ const operateButtonClick = (eventName: string, row: any) => {