536 lines
20 KiB
Vue
536 lines
20 KiB
Vue
<script lang="ts" setup>
|
|
import BasePageableTable from "@/components/base/base-pageable-table/BasePageableTable.vue";
|
|
import DefaultToolButton from "@/components/base/default-tool-button/DefaultToolButton.vue";
|
|
import DefaultOperateButtonColumn from "@/components/base/default-column/DefaultOperateButtonColumn.vue";
|
|
import { usePage } from "@/composables/use-page";
|
|
import BaseForm from "@/components/base/base-form/BaseForm.vue";
|
|
import { $t } from "@/common/languages";
|
|
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from "element-plus";
|
|
import { formatDate } from "@/common/utils/format-utils";
|
|
import DefaultStatusSwitchColumn from "@/components/base/default-column/DefaultStatusSwitchColumn.vue";
|
|
import BaseSelect from "@/components/base/base-select/BaseSelect.vue";
|
|
import { generateDucumentNo } from "@/common/utils/document-no-generator/document-no-generator";
|
|
import { get, post } from "@/common/http/request";
|
|
import BaseTableForm from "@/components/base/base-table-form/BaseTableForm.vue";
|
|
import { DocumentType } from "@/common/enums/DocumentType";
|
|
import { useStatus } from "@/common/languages/mapping/base-info-mapping";
|
|
|
|
/**
|
|
* 必须要的变量
|
|
*/
|
|
|
|
const getPageUrl = "/production/productionPlan/getProductionPlanPage";
|
|
const addUrl = "/production/productionPlan/addProductionPlan";
|
|
const editUrl = "/production/productionPlan/updateProductionPlan";
|
|
const removeUrl = "/production/productionPlan/deleteProductionPlan";
|
|
const getProjectSelectList = "/production/bom/getBomSelectList";
|
|
const getWarehouseSelectListUrl = "/warehouse/warehouse/getWarehouseSelectList";
|
|
const getProductionPlanShortageListUrl = "/production/productionPlan/getMaterialShortageList";
|
|
const generatePurchasePlanUrl = "/production/productionPlan/generatePurchasePlan";
|
|
const generateTransferOrderUrl = "/production/productionPlan/generateTransferOrder";
|
|
const generateProductionIssueUrl = "/production/productionPlan/generateProductionIssue";
|
|
const searchers = [
|
|
{ name: "projectName", type: "text" as const, placeholder: $t("_prop.production.production_plan.projectName") },
|
|
];
|
|
const rules = reactive<FormRules>({
|
|
projectId: [{ required: true, message: $t("_message.production.production_plan.select_projectId"), trigger: "blur" }],
|
|
productionNum: [
|
|
{ required: true, message: $t("_message.production.production_plan.input_productionNum"), trigger: "blur" },
|
|
],
|
|
storeNo: [{ required: true, message: $t("_message.production.production_plan.select_storeNo"), trigger: "blur" }],
|
|
productionCount: [
|
|
{ required: true, message: $t("_message.production.production_plan.input_productionCount"), trigger: "blur" },
|
|
],
|
|
productionMark: [
|
|
{ required: false, message: $t("_message.production.production_plan.input_productionMark"), trigger: "blur" },
|
|
],
|
|
productionNote: [
|
|
{ required: false, message: $t("_message.production.production_plan.input_productionNote"), trigger: "blur" },
|
|
],
|
|
});
|
|
/**
|
|
* 基本不变通用变量
|
|
*/
|
|
const tableRef = ref<InstanceType<typeof BasePageableTable> | null>(null);
|
|
const { useAdd, useEdit, useRemove, useGeneralPageRef } = usePage(tableRef);
|
|
const { title, visible, formType, form } = useGeneralPageRef();
|
|
const { getProductionPlanStatusLabel } = useStatus();
|
|
const bomSelectRef = ref<InstanceType<typeof BaseSelect>>();
|
|
const warehouseSelectRef = ref<InstanceType<typeof BaseSelect>>();
|
|
const transferWarehouseSelectRef = ref<InstanceType<typeof BaseSelect>>();
|
|
const dialogRef = ref<InstanceType<typeof BaseTableForm>>();
|
|
const dialogVisible = ref(false);
|
|
const shortageData = ref([]);
|
|
const dialogTableData = ref([]);
|
|
const dialogSelectable = ref(false);
|
|
const transferWarehouse = ref();
|
|
const issueDialogVisible = ref(false);
|
|
/**
|
|
* 可以自定义的变量
|
|
*/
|
|
|
|
const add = () => {
|
|
form.value = {};
|
|
title.value = "_title.production.production_plan.add";
|
|
form.value["productionNum"] = generateDucumentNo("SCJH");
|
|
visible.value = true;
|
|
formType.value = false;
|
|
};
|
|
const edit = (row: any) => {
|
|
title.value = "_title.production.production_plan.edit";
|
|
form.value = { ...row };
|
|
visible.value = true;
|
|
formType.value = true;
|
|
};
|
|
const remove = (row: any) => {
|
|
useRemove(removeUrl, row.id, "_message.production.production_plan.delete_message");
|
|
};
|
|
const submit = (form: any, formRef: FormInstance | undefined) => {
|
|
setName();
|
|
if (formRef !== undefined) {
|
|
formRef.validate(valid => {
|
|
if (valid) {
|
|
if (formType.value) useEdit(editUrl, form, visible);
|
|
else useAdd(addUrl, form, visible);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
const validateSelection = (requireStatusZero = false) => {
|
|
const rows = tableRef.value?.tableMainRef?.tableRef?.getSelectionRows();
|
|
|
|
if (!rows || rows.length === 0) {
|
|
ElMessage.warning($t("_message.production.production_plan.select_production_plan"));
|
|
return null;
|
|
}
|
|
|
|
if (requireStatusZero && !rows.every(row => row.productionStatus === 0)) {
|
|
ElMessage.warning($t("_message.production.production_plan.error_production_status"));
|
|
return null;
|
|
}
|
|
|
|
const warehouseId = rows[0].storeNo;
|
|
if (!rows.every(item => item.storeNo === warehouseId)) {
|
|
ElMessage.warning($t("_message.production.production_plan.duplicate_store_no"));
|
|
return null;
|
|
}
|
|
|
|
return rows;
|
|
};
|
|
|
|
const buildDocumentInfo = (rows: any[], type: DocumentType | string, markSuffixKey: string, nameSuffixKey: string) => {
|
|
const code = generateDucumentNo(type);
|
|
const productionNums = rows.map((item: any) => item.productionNum).join(",");
|
|
const projectNames = rows.map((item: any) => item.projectName).join(",");
|
|
|
|
return {
|
|
code,
|
|
mark:
|
|
$t("_message.common.generate_document_mark_prefix") +
|
|
productionNums +
|
|
$t(markSuffixKey) +
|
|
code +
|
|
$t("_message.common.generate_document_mark_suffix"),
|
|
name: projectNames + $t(nameSuffixKey),
|
|
};
|
|
};
|
|
|
|
const viewMaterialShortage = async () => {
|
|
const rows = validateSelection(true); // 需要校验状态为 0
|
|
if (!rows) return;
|
|
|
|
const params = new URLSearchParams();
|
|
rows.forEach(({ id }) => params.append("ids", id));
|
|
|
|
shortageData.value = await get(getProductionPlanShortageListUrl, params).then(res => res.data);
|
|
|
|
calculateDiffQty();
|
|
dialogRef.value?.clearSelection();
|
|
dialogTableData.value = shortageData.value;
|
|
dialogSelectable.value = false;
|
|
dialogVisible.value = true;
|
|
};
|
|
|
|
const generatePurchasePlan = (selectedShortageRows: any[]) => {
|
|
ElMessageBox.confirm($t("_message.production.production_plan.generate_purchase_plan_confirm"), $t("_level.warning"), {
|
|
confirmButtonText: $t("_button.confirm"),
|
|
cancelButtonText: $t("_button.cancel"),
|
|
type: "warning",
|
|
}).then(async () => {
|
|
const rows = validateSelection();
|
|
if (!rows) return;
|
|
|
|
const {
|
|
code: planNo,
|
|
mark: planMark,
|
|
name: planName,
|
|
} = buildDocumentInfo(
|
|
rows,
|
|
"PA",
|
|
"_message.production.production_plan.generate_purchase_plan_mark_suffix",
|
|
"_message.production.production_plan.generate_purchase_plan_name_suffix"
|
|
);
|
|
|
|
const plan = {
|
|
planNo,
|
|
storeNo: rows[0].storeNo,
|
|
storeName: rows[0].storeName,
|
|
remask: planMark,
|
|
planName,
|
|
planItems: selectedShortageRows.map(({ partNumber, diffQty, id }) => ({
|
|
partNumber,
|
|
purchaseCount: -diffQty,
|
|
partId: id,
|
|
})),
|
|
};
|
|
|
|
try {
|
|
const res = await post(generatePurchasePlanUrl, plan);
|
|
ElMessage[res.code === 0 ? "success" : "error"](
|
|
res.code === 0
|
|
? $t("_message.production.production_plan.generate_purchase_plan_success")
|
|
: $t("_message.production.production_plan.generate_purchase_plan_fail") + res.msg
|
|
);
|
|
} catch (err) {
|
|
ElMessage.error($t("_message.production.production_plan.generate_purchase_plan_fail") + err);
|
|
}
|
|
});
|
|
};
|
|
|
|
const generateTransferOrder = (selectedShortageRows: any[]) => {
|
|
ElMessageBox.confirm(
|
|
$t("_message.production.production_plan.generate_transfer_order_confirm"),
|
|
$t("_level.warning"),
|
|
{
|
|
confirmButtonText: $t("_button.confirm"),
|
|
cancelButtonText: $t("_button.cancel"),
|
|
type: "warning",
|
|
}
|
|
).then(async () => {
|
|
const rows = validateSelection();
|
|
if (!rows) return;
|
|
|
|
const outWarehouseId = transferWarehouse.value;
|
|
if (!outWarehouseId) {
|
|
ElMessage.warning("仓库不能为空");
|
|
return;
|
|
}
|
|
|
|
const outWarehouseName = transferWarehouseSelectRef.value?.getLabel();
|
|
if (rows[0].storeNo === outWarehouseId) {
|
|
ElMessage.warning("仓库不能相同");
|
|
return;
|
|
}
|
|
|
|
const {
|
|
code: formCode,
|
|
mark: formMark,
|
|
name: formName,
|
|
} = buildDocumentInfo(
|
|
rows,
|
|
DocumentType.StockTransferOrder,
|
|
"_message.production.production_plan.generate_transfer_order_mark_suffix",
|
|
"_message.production.production_plan.generate_transfer_order_name_suffix"
|
|
);
|
|
|
|
const plan = {
|
|
formCode,
|
|
formName,
|
|
formMark,
|
|
storeNo: rows[0].storeNo,
|
|
storeName: rows[0].storeName,
|
|
outStoreNo: outWarehouseId,
|
|
outStoreName: outWarehouseName,
|
|
transferOrderItems: selectedShortageRows.map(({ partNumber, diffQty, productSpec, id }) => ({
|
|
partNumber,
|
|
productCount: -diffQty,
|
|
productSpec,
|
|
partId: id,
|
|
})),
|
|
};
|
|
|
|
try {
|
|
const res = await post(generateTransferOrderUrl, plan);
|
|
ElMessage[res.code === 0 ? "success" : "error"](
|
|
res.code === 0
|
|
? $t("_message.production.production_plan.generate_transfer_order_success")
|
|
: $t("_message.production.production_plan.generate_transfer_order_fail") + res.msg
|
|
);
|
|
} catch (err) {
|
|
ElMessage.error($t("_message.production.production_plan.generate_transfer_order_fail") + err);
|
|
}
|
|
});
|
|
};
|
|
|
|
const generateProductionIssue = async () => {
|
|
const rows = validateSelection(true); // 需要校验状态为 0
|
|
if (!rows) return;
|
|
|
|
const params = new URLSearchParams();
|
|
rows.forEach(({ id }) => params.append("ids", id));
|
|
|
|
dialogTableData.value = await get(getProductionPlanShortageListUrl, params).then(res => res.data);
|
|
setActualQtyAndDiffQty();
|
|
issueDialogVisible.value = true;
|
|
};
|
|
|
|
const _generateProductionIssue = async () => {
|
|
ElMessageBox.confirm(
|
|
$t("_message.production.production_plan.generate_production_issue_confirm"),
|
|
$t("_level.warning"),
|
|
{
|
|
confirmButtonText: $t("_button.confirm"),
|
|
cancelButtonText: $t("_button.cancel"),
|
|
type: "warning",
|
|
}
|
|
).then(async () => {
|
|
const rows = validateSelection();
|
|
if (!rows) return;
|
|
|
|
const {
|
|
code: formCode,
|
|
mark: formMark,
|
|
name: formName,
|
|
} = buildDocumentInfo(
|
|
rows,
|
|
DocumentType.ProductionIssue,
|
|
"_message.production.production_plan.generate_production_issue_mark_suffix",
|
|
"_message.production.production_plan.generate_production_issue_name_suffix"
|
|
);
|
|
|
|
const issue = {
|
|
formCode,
|
|
formName,
|
|
formMark,
|
|
storeNo: rows[0].storeNo,
|
|
storeName: rows[0].storeName,
|
|
items: dialogTableData.value.map(({ partNumber, storeNo, actualQty, requiredQty }) => ({
|
|
partNumber,
|
|
storeNo,
|
|
productCount: actualQty,
|
|
demandCount: requiredQty,
|
|
})),
|
|
};
|
|
|
|
try {
|
|
const res = await post(generateProductionIssueUrl, { ids: rows.map(r => r.id), issue });
|
|
if (res.code === 0) {
|
|
issueDialogVisible.value = false;
|
|
tableRef.value?.reload();
|
|
ElMessage.success($t("_message.production.production_plan.generate_production_issue_success"));
|
|
} else {
|
|
ElMessage.error($t("_message.production.production_plan.generate_production_issue_fail") + res.msg);
|
|
}
|
|
} catch (err) {
|
|
ElMessage.error($t("_message.production.production_plan.generate_production_issue_fail") + err);
|
|
}
|
|
});
|
|
};
|
|
|
|
const calculateDiffQty = () => {
|
|
shortageData.value.forEach((item: any) => {
|
|
item.diffQty = computed(() => item.stockQty - item.requiredQty);
|
|
});
|
|
};
|
|
|
|
const setActualQtyAndDiffQty = () => {
|
|
dialogTableData.value.forEach((item: any) => {
|
|
item.actualQty = item.requiredQty;
|
|
item.diffQty = computed(() => item.stockQty - item.requiredQty);
|
|
});
|
|
};
|
|
|
|
const handleShortageFilterSubmit = () => {
|
|
dialogTableData.value = shortageData.value.filter(item => (item as any).diffQty < 0);
|
|
dialogSelectable.value = true;
|
|
};
|
|
|
|
const handleUnfilterSubmit = () => {
|
|
dialogTableData.value = shortageData.value;
|
|
dialogSelectable.value = false;
|
|
};
|
|
|
|
const topButtonClick = (eventName: string) => {
|
|
switch (eventName) {
|
|
case "add":
|
|
add();
|
|
break;
|
|
case "viewMaterialShortage":
|
|
viewMaterialShortage();
|
|
break;
|
|
case "generateProductionIssue":
|
|
generateProductionIssue();
|
|
break;
|
|
}
|
|
};
|
|
const operateButtonClick = (eventName: string, row: any) => {
|
|
switch (eventName) {
|
|
case "edit":
|
|
edit(row);
|
|
break;
|
|
case "remove":
|
|
remove(row);
|
|
break;
|
|
}
|
|
};
|
|
const dialogButtonClick = (eventName: string, btn: any, rows: any[]) => {
|
|
switch (eventName) {
|
|
case "generatePurchasePlan":
|
|
generatePurchasePlan(rows);
|
|
break;
|
|
case "generateTransferOrder":
|
|
generateTransferOrder(rows);
|
|
break;
|
|
}
|
|
};
|
|
const setName = () => {
|
|
form.value["projectName"] = bomSelectRef.value?.getLabel();
|
|
form.value["storeName"] = warehouseSelectRef.value?.getLabel();
|
|
};
|
|
const handleReset = () => {
|
|
form.value["productionNum"] = generateDucumentNo("SCJH");
|
|
};
|
|
</script>
|
|
<template>
|
|
<BasePageableTable :url="getPageUrl" :searchers="searchers" ref="tableRef">
|
|
<template #tool-button>
|
|
<DefaultToolButton @top-button-click="topButtonClick" />
|
|
</template>
|
|
<template #columns>
|
|
<el-table-column prop="id" type="selection" width="40" />
|
|
<el-table-column :label="$t('_prop.production.production_plan.productionNum')" prop="productionNum" />
|
|
<el-table-column :label="$t('_prop.production.production_plan.projectName')" prop="projectName" />
|
|
<el-table-column :label="$t('_prop.production.production_plan.storeName')" prop="storeName" />
|
|
<el-table-column :label="$t('_prop.production.production_plan.productionCount')" prop="productionCount" />
|
|
<el-table-column :label="$t('_prop.production.production_plan.productionMark')" prop="productionMark" />
|
|
<el-table-column :label="$t('_prop.common.createDate')" prop="createDate" :formatter="formatDate" />
|
|
<DefaultStatusSwitchColumn
|
|
status-param-name="productionStatus"
|
|
:status-label-mapping="getProductionPlanStatusLabel"
|
|
/>
|
|
<DefaultOperateButtonColumn @operate-button-click="operateButtonClick" />
|
|
</template>
|
|
</BasePageableTable>
|
|
<BaseForm
|
|
v-model:visible="visible"
|
|
@submit="submit"
|
|
v-model:form="form"
|
|
:title="$t(title)"
|
|
:rules="rules"
|
|
@reset="handleReset"
|
|
>
|
|
<template #form-items>
|
|
<el-form-item prop="id" v-if="false"><el-input v-model="form.id" /></el-form-item>
|
|
<el-form-item prop="projectName" v-if="false"><el-input v-model="form.projectName" /></el-form-item>
|
|
<el-form-item prop="storeName" v-if="false"><el-input v-model="form.storeName" /></el-form-item>
|
|
|
|
<el-row :gutter="20">
|
|
<el-col :span="12">
|
|
<el-form-item :label="$t('_prop.production.production_plan.projectId')" prop="projectId">
|
|
<BaseSelect v-model="form.projectId" :url="getProjectSelectList" ref="bomSelectRef" />
|
|
</el-form-item>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<el-form-item :label="$t('_prop.production.production_plan.productionNum')" prop="productionNum">
|
|
<el-input
|
|
v-model="form.productionNum"
|
|
:placeholder="$t('_message.production.production_plan.input_productionNum')"
|
|
disabled
|
|
/>
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-row>
|
|
<el-row :gutter="20">
|
|
<el-col :span="12">
|
|
<el-form-item :label="$t('_prop.production.production_plan.storeNo')" prop="storeNo">
|
|
<BaseSelect v-model="form.storeNo" :url="getWarehouseSelectListUrl" ref="warehouseSelectRef" />
|
|
</el-form-item>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<el-form-item :label="$t('_prop.production.production_plan.productionCount')" prop="productionCount">
|
|
<el-input-number
|
|
:min="1"
|
|
v-model="form.productionCount"
|
|
:placeholder="$t('_message.production.production_plan.input_productionCount')"
|
|
/>
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-row>
|
|
<el-form-item :label="$t('_prop.production.production_plan.productionMark')" prop="productionMark">
|
|
<el-input
|
|
v-model="form.productionMark"
|
|
type="textarea"
|
|
autosize
|
|
:placeholder="$t('_message.production.production_plan.input_productionMark')"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item :label="$t('_prop.production.production_plan.productionNote')" prop="productionNote">
|
|
<el-input
|
|
v-model="form.productionNote"
|
|
type="textarea"
|
|
autosize
|
|
:placeholder="$t('_message.production.production_plan.input_productionNote')"
|
|
/>
|
|
</el-form-item>
|
|
</template>
|
|
</BaseForm>
|
|
<BaseTableForm
|
|
ref="dialogRef"
|
|
v-model:visible="dialogVisible"
|
|
:title="$t('_title.production.production_plan.dialog')"
|
|
:tableData="dialogTableData"
|
|
:selectable="dialogSelectable"
|
|
row-key="partNumber"
|
|
@dialog-button-click="dialogButtonClick"
|
|
>
|
|
<template #filter>
|
|
<el-button type="danger" @click="handleShortageFilterSubmit">{{ $t("_button.shortageFilterButton") }}</el-button>
|
|
<el-button type="primary" @click="handleUnfilterSubmit">{{ $t("_button.unfilterButton") }}</el-button>
|
|
</template>
|
|
<template #table-columns>
|
|
<el-table-column :label="$t('_prop.production.bom_item.partNumber')" prop="partNumber" />
|
|
<el-table-column :label="$t('_prop.production.bom_item.productSpecs')" prop="productSpecs" />
|
|
<el-table-column :label="$t('_prop.production.production_plan.requiredQty')" prop="requiredQty">
|
|
<template #default="{ row }">
|
|
<el-input-number v-model="row.requiredQty" :min="0" />
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column :label="$t('_prop.production.production_plan.stockQty')" prop="stockQty" />
|
|
<el-table-column :label="$t('_prop.production.production_plan.diffQty')" prop="diffQty" />
|
|
</template>
|
|
<template #attachment="{ checkPermission }">
|
|
<div v-if="checkPermission('generateTransferOrder')" style="display: flex; align-items: center">
|
|
<span style="width: 200px; padding-right: 20px">{{ $t("_prop.production.production_plan.outStoreNo") }}</span>
|
|
<BaseSelect v-model="transferWarehouse" :url="getWarehouseSelectListUrl" ref="transferWarehouseSelectRef" />
|
|
</div>
|
|
</template>
|
|
</BaseTableForm>
|
|
<BaseTableForm
|
|
ref="dialogRef"
|
|
v-model:visible="issueDialogVisible"
|
|
:title="$t('_title.production.production_plan.issueDialog')"
|
|
:tableData="dialogTableData"
|
|
:selectable="false"
|
|
:useAuth="false"
|
|
>
|
|
<template #table-columns>
|
|
<el-table-column :label="$t('_prop.production.bom_item.partNumber')" prop="partNumber" />
|
|
<el-table-column :label="$t('_prop.production.bom_item.productSpecs')" prop="productSpecs" />
|
|
<el-table-column :label="$t('_prop.production.production_plan.requiredQty')" prop="requiredQty" />
|
|
<el-table-column :label="$t('_prop.production.production_plan.actualQty')" prop="actualQty">
|
|
<template #default="{ row }">
|
|
<el-input-number v-model="row.actualQty" :min="0" />
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column :label="$t('_prop.production.production_plan.stockQty')" prop="stockQty" />
|
|
<el-table-column :label="$t('_prop.production.production_plan.diffQty')" prop="diffQty" />
|
|
</template>
|
|
<template #attachment>
|
|
<el-button type="primary" @click="_generateProductionIssue">
|
|
{{ $t("_button.submit") }}
|
|
</el-button>
|
|
</template>
|
|
</BaseTableForm>
|
|
</template>
|