You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1074 lines
32 KiB
Vue

<template>
<div class="sales-order-scheduling">
<el-card class="mb-4">
<template #header>
<div class="flex justify-between items-center">
<span>销售订单排产</span>
</div>
</template>
<!-- 搜索区域 -->
<el-form :inline="true" :model="searchForm" class="mb-4">
<el-form-item label="订单编号">
<el-input v-model="searchForm.orderNo" placeholder="请输入订单编号" />
</el-form-item>
<el-form-item label="交货日期">
<el-date-picker
v-model="searchForm.deliveryDate"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<el-form-item label="优先级">
<el-select v-model="searchForm.priority" placeholder="请选择优先级">
<el-option label="紧急" value="urgent" />
<el-option label="普通" value="normal" />
</el-select>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="searchForm.status" placeholder="请选择状态">
<el-option label="待排产" value="pending" />
<el-option label="排产中" value="processing" />
<el-option label="已完成" value="completed" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch"></el-button>
<el-button @click="resetSearch"></el-button>
</el-form-item>
</el-form>
<!-- 订单列表 -->
<el-table
:data="sortedOrderList"
border
style="width: 100%"
@sort-change="handleSortChange"
>
<el-table-column prop="saleorder_code" label="订单编号" width="180" sortable="custom" />
<el-table-column prop="material_name" label="物料名称" width="180" />
<el-table-column prop="material_code" label="物料编码" width="120" />
<el-table-column prop="material_model" label="规格型号" width="120" />
<el-table-column prop="order_amount" label="订单数量" width="120" sortable="custom" />
<el-table-column prop="complete_amount" label="完成数量" width="120" />
<el-table-column prop="release_qty" label="下达数量" width="120" />
<el-table-column prop="plan_delivery_date" label="计划交货日期" width="180" sortable="custom" />
<el-table-column prop="priority" label="优先级" width="120">
<template #default="{ row }">
<el-tag :type="getPriorityType(row.priority)">
{{ getPriorityText(row.priority) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="document_status" label="状态" width="120">
<template #default="{ row }">
<el-tag :type="getStatusType(row.document_status)">
{{ getStatusText(row.document_status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="create_time" label="创建时间" width="180" sortable="custom" />
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleViewBom(row)">BOM</el-button>
<el-button
v-if="row.document_status === 'pending'"
type="primary"
link
@click="handleSchedule(row)"
>排产</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="flex justify-end mt-4">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
<!-- BOM分解弹窗 -->
<el-dialog
v-model="bomDialogVisible"
title="BOM分解结果"
width="90%"
>
<div class="bom-container">
<!-- 订单信息 -->
<div class="bom-header mb-4">
<el-descriptions :column="4" border>
<el-descriptions-item label="订单编号">{{ currentOrder?.saleorder_code }}</el-descriptions-item>
<el-descriptions-item label="物料名称">{{ currentOrder?.material_name }}</el-descriptions-item>
<el-descriptions-item label="订单数量">{{ currentOrder?.order_amount }}</el-descriptions-item>
<el-descriptions-item label="计划交货日期">{{ currentOrder?.plan_delivery_date }}</el-descriptions-item>
</el-descriptions>
</div>
<!-- BOM树 -->
<el-table
:data="flattenedBomData"
border
style="width: 100%"
row-key="id"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column prop="materialName" label="物料名称" min-width="200">
<template #default="{ row }">
<span :style="{ paddingLeft: row.level * 20 + 'px' }">
{{ row.level > 0 ? '└─ ' : '' }}{{ row.materialName }}
</span>
</template>
</el-table-column>
<el-table-column prop="materialCode" label="物料编码" width="120" />
<el-table-column prop="unit" label="单位" width="80" />
<el-table-column prop="bomRatio" label="BOM用量系数" width="120">
<template #default="{ row }">
{{ row.bomRatio }}
</template>
</el-table-column>
<el-table-column prop="requiredAmount" label="需求数量" width="120">
<template #default="{ row }">
{{ row.requiredAmount }}
</template>
</el-table-column>
<el-table-column prop="inventoryAmount" label="当前库存" width="120">
<template #default="{ row }">
<el-tag :type="row.inventoryAmount >= row.requiredAmount ? 'success' : 'warning'">
{{ row.inventoryAmount }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="inTransitAmount" label="在途库存" width="120">
<template #default="{ row }">
{{ row.inTransitAmount }}
</template>
</el-table-column>
<el-table-column prop="netRequiredAmount" label="净需求" width="120">
<template #default="{ row }">
<el-tag :type="row.netRequiredAmount > 0 ? 'danger' : 'success'">
{{ row.netRequiredAmount }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="120">
<template #default="{ row }">
<el-tag :type="getBomStatusType(row)">
{{ getBomStatusText(row) }}
</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</el-dialog>
<!-- 排产弹窗 -->
<el-dialog
v-model="scheduleDialogVisible"
title="生产计划排产"
width="90%"
>
<div class="schedule-container">
<!-- 订单信息 -->
<div class="schedule-header mb-4">
<el-descriptions :column="4" border>
<el-descriptions-item label="订单编号">{{ currentOrder?.saleorder_code }}</el-descriptions-item>
<el-descriptions-item label="物料名称">{{ currentOrder?.material_name }}</el-descriptions-item>
<el-descriptions-item label="订单数量">{{ currentOrder?.order_amount }}</el-descriptions-item>
<el-descriptions-item label="计划交货日期">{{ currentOrder?.plan_delivery_date }}</el-descriptions-item>
</el-descriptions>
</div>
<!-- 排产表单 -->
<el-form :model="scheduleForm" label-width="120px" class="mb-4">
<el-form-item label="排产类型">
<el-radio-group v-model="scheduleForm.scheduleType" @change="handleScheduleTypeChange">
<el-radio label="auto">自动排产</el-radio>
<el-radio label="manual">手动排产</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<!-- BOM物料排产列表 -->
<div class="schedule-bom-list">
<h3 class="mb-2">BOM物料排产</h3>
<el-table :data="scheduleBomList" border style="width: 100%">
<el-table-column prop="materialName" label="物料名称" min-width="200">
<template #default="{ row }">
<span :style="{ paddingLeft: row.level * 20 + 'px' }">
{{ row.level > 0 ? '└─ ' : '' }}{{ row.materialName }}
</span>
</template>
</el-table-column>
<el-table-column prop="materialCode" label="物料编码" width="120" />
<el-table-column prop="unit" label="单位" width="80" />
<el-table-column prop="bomRatio" label="BOM用量系数" width="120" />
<el-table-column prop="netRequiredAmount" label="净需求" width="120">
<template #default="{ row }">
<el-tag :type="row.netRequiredAmount > 0 ? 'danger' : 'success'">
{{ row.netRequiredAmount }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="计划生产数量" width="150">
<template #default="{ row }">
<el-input-number
v-model="row.planAmount"
:min="0"
:max="row.netRequiredAmount"
:step="1"
@change="handlePlanAmountChange(row)"
/>
</template>
</el-table-column>
<el-table-column label="计划开始时间" width="180">
<template #default="{ row }">
<el-date-picker
v-model="row.planStartDate"
type="datetime"
placeholder="选择开始时间"
@change="handleStartDateChange(row)"
/>
</template>
</el-table-column>
<el-table-column label="计划结束时间" width="180">
<template #default="{ row }">
<el-date-picker
v-model="row.planEndDate"
type="datetime"
placeholder="选择结束时间"
:disabled-date="(time) => time < row.planStartDate"
/>
</template>
</el-table-column>
<el-table-column label="生产车间" width="150">
<template #default="{ row }">
<el-tag :type="getWorkshopTagType(row.workshop)">
{{ getWorkshopName(row.workshop) }}
</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="scheduleDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirmSchedule"></el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup name="salesOrderScheduling" lang="ts">
import { ref, computed, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
// 搜索表单
const searchForm = ref({
orderNo: '',
deliveryDate: [],
priority: '',
status: ''
})
// 排序配置
const sortConfig = ref({
prop: 'planDeliveryDate',
order: 'ascending'
})
// BOM数据结构
interface BomItem {
id: string
materialName: string
materialCode: string
unit: string
bomRatio: number
requiredAmount: number
inventoryAmount: number
inTransitAmount: number
netRequiredAmount: number
level: number
children?: BomItem[]
planAmount?: number
workshop?: string
planStartDate?: string | Date
planEndDate?: string | Date
}
// 订单数据结构
interface SaleOrder {
sale_order_id: number
saleorder_code: string
material_id: number
material_code: string
material_name: string
material_model: string
order_amount: number
complete_amount: number
release_qty: number
plan_delivery_date: string
priority: number
document_status: string
create_time: string
remark: string
}
// 生产计划数据结构
interface ProductionPlan {
orderId: number
orderNo: string
materialId: number
materialName: string
orderAmount: number
planDeliveryDate: string
priority: number
bomItems: BomItem[]
}
// 订单列表数据
const orderList = ref<SaleOrder[]>([
{
sale_order_id: 1,
saleorder_code: 'SO20240301001',
material_id: 1001,
material_code: 'TYRE-A',
material_name: '轮胎成品A',
material_model: '205/55R16',
order_amount: 100,
complete_amount: 0,
release_qty: 0,
plan_delivery_date: '2024-03-15',
priority: 1,
document_status: 'pending',
create_time: '2024-03-01 10:00:00',
remark: '加急订单'
},
{
sale_order_id: 2,
saleorder_code: 'SO20240301002',
material_id: 1002,
material_code: 'TYRE-B',
material_name: '轮胎成品B',
material_model: '215/55R17',
order_amount: 200,
complete_amount: 50,
release_qty: 100,
plan_delivery_date: '2024-03-20',
priority: 2,
document_status: 'processing',
create_time: '2024-03-01 11:00:00',
remark: '常规订单'
},
{
sale_order_id: 3,
saleorder_code: 'SO20240301003',
material_id: 1003,
material_code: 'TYRE-C',
material_name: '轮胎成品C',
material_model: '225/55R18',
order_amount: 150,
complete_amount: 150,
release_qty: 150,
plan_delivery_date: '2024-03-25',
priority: 2,
document_status: 'completed',
create_time: '2024-03-01 12:00:00',
remark: '常规订单'
}
])
// 计算排序后的订单列表
const sortedOrderList = computed(() => {
let result = [...orderList.value]
// 优先级排序
result.sort((a, b) => {
return a.priority - b.priority
})
// 交货日期排序
if (sortConfig.value.prop === 'plan_delivery_date') {
result.sort((a, b) => {
const dateA = new Date(a.plan_delivery_date).getTime()
const dateB = new Date(b.plan_delivery_date).getTime()
return sortConfig.value.order === 'ascending'
? dateA - dateB
: dateB - dateA
})
}
// 订单数量排序
if (sortConfig.value.prop === 'order_amount') {
result.sort((a, b) => {
return sortConfig.value.order === 'ascending'
? a.order_amount - b.order_amount
: b.order_amount - a.order_amount
})
}
// 创建时间排序
if (sortConfig.value.prop === 'create_time') {
result.sort((a, b) => {
const dateA = new Date(a.create_time).getTime()
const dateB = new Date(b.create_time).getTime()
return sortConfig.value.order === 'ascending'
? dateA - dateB
: dateB - dateA
})
}
return result
})
// 当前选中的订单
const currentOrder = ref<SaleOrder | null>(null)
// BOM弹窗数据
const bomDialogVisible = ref(false)
const bomTreeData = ref<BomItem[]>([
{
id: '1001',
materialName: '轮胎成品A',
materialCode: 'TYRE-A',
unit: '个',
bomRatio: 1,
requiredAmount: 100,
inventoryAmount: 20,
inTransitAmount: 10,
netRequiredAmount: 70,
level: 0,
children: [
{
id: '1001-1',
materialName: '胎面组件',
materialCode: 'TREAD-ASSY',
unit: '套',
bomRatio: 1,
requiredAmount: 100,
inventoryAmount: 30,
inTransitAmount: 20,
netRequiredAmount: 50,
level: 1,
children: [
{
id: '1001-1-1',
materialName: '胎面胶',
materialCode: 'TREAD-1',
unit: 'kg',
bomRatio: 2.5,
requiredAmount: 250,
inventoryAmount: 100,
inTransitAmount: 50,
netRequiredAmount: 100,
level: 2
},
{
id: '1001-1-2',
materialName: '钢丝帘布',
materialCode: 'STEEL-CORD',
unit: '米',
bomRatio: 3,
requiredAmount: 300,
inventoryAmount: 150,
inTransitAmount: 50,
netRequiredAmount: 100,
level: 2
}
]
},
{
id: '1001-2',
materialName: '胎侧组件',
materialCode: 'SIDEWALL-ASSY',
unit: '套',
bomRatio: 2,
requiredAmount: 200,
inventoryAmount: 80,
inTransitAmount: 40,
netRequiredAmount: 80,
level: 1,
children: [
{
id: '1001-2-1',
materialName: '胎侧胶',
materialCode: 'SIDEWALL-1',
unit: 'kg',
bomRatio: 1.5,
requiredAmount: 300,
inventoryAmount: 100,
inTransitAmount: 50,
netRequiredAmount: 150,
level: 2
},
{
id: '1001-2-2',
materialName: '尼龙帘布',
materialCode: 'NYLON-CORD',
unit: '米',
bomRatio: 2,
requiredAmount: 400,
inventoryAmount: 200,
inTransitAmount: 100,
netRequiredAmount: 100,
level: 2
}
]
},
{
id: '1001-3',
materialName: '胎圈组件',
materialCode: 'BEAD-ASSY',
unit: '套',
bomRatio: 2,
requiredAmount: 200,
inventoryAmount: 60,
inTransitAmount: 40,
netRequiredAmount: 100,
level: 1,
children: [
{
id: '1001-3-1',
materialName: '胎圈钢丝',
materialCode: 'BEAD-WIRE',
unit: 'kg',
bomRatio: 0.8,
requiredAmount: 160,
inventoryAmount: 50,
inTransitAmount: 30,
netRequiredAmount: 80,
level: 2
},
{
id: '1001-3-2',
materialName: '胎圈包布',
materialCode: 'BEAD-WRAP',
unit: '米',
bomRatio: 1.2,
requiredAmount: 240,
inventoryAmount: 100,
inTransitAmount: 50,
netRequiredAmount: 90,
level: 2
}
]
}
]
}
])
// 扁平化的BOM数据
const flattenedBomData = computed(() => {
const flatten = (items: BomItem[], level = 0): BomItem[] => {
return items.reduce((acc: BomItem[], item) => {
const flatItem = { ...item, level }
acc.push(flatItem)
if (item.children) {
acc.push(...flatten(item.children, level + 1))
}
return acc
}, [])
}
return flatten(bomTreeData.value)
})
// 获取BOM状态类型
const getBomStatusType = (row: BomItem) => {
if (row.netRequiredAmount <= 0) return 'success'
if (row.inventoryAmount + row.inTransitAmount > 0) return 'warning'
return 'danger'
}
// 获取BOM状态文本
const getBomStatusText = (row: BomItem) => {
if (row.netRequiredAmount <= 0) return '库存充足'
if (row.inventoryAmount + row.inTransitAmount > 0) return '部分库存'
return '需要生产'
}
// 排产弹窗
const scheduleDialogVisible = ref(false)
const scheduleForm = ref({
scheduleType: 'auto'
})
// 排产BOM列表
const scheduleBomList = ref<BomItem[]>([])
// 获取优先级类型
const getPriorityType = (priority) => {
const priorityMap = {
1: 'danger',
2: 'info'
}
return priorityMap[priority] || 'info'
}
// 获取优先级文本
const getPriorityText = (priority) => {
const priorityMap = {
1: '紧急',
2: '普通'
}
return priorityMap[priority] || '普通'
}
// 处理排序变化
const handleSortChange = ({ prop, order }) => {
sortConfig.value = { prop, order }
}
// 获取订单列表
const getOrderList = async () => {
try {
// TODO: 调用后端API获取订单列表
// const res = await getSaleOrders({
// page: currentPage.value,
// pageSize: pageSize.value,
// ...searchForm.value
// })
// orderList.value = res.data.list
// total.value = res.data.total
} catch (error) {
ElMessage.error('获取订单列表失败')
}
}
// 查看BOM
const handleViewBom = async (row) => {
try {
currentOrder.value = row
// 模拟API调用延迟
await new Promise(resolve => setTimeout(resolve, 500))
// 根据订单ID筛选对应的BOM数据
const bomData = bomTreeData.value.find(item => item.id === row.material_id.toString())
if (bomData) {
bomTreeData.value = [bomData]
bomDialogVisible.value = true
} else {
ElMessage.warning('未找到对应的BOM数据')
}
} catch (error) {
ElMessage.error('获取BOM数据失败')
}
}
// 排产
const handleSchedule = (row) => {
currentOrder.value = row
scheduleForm.value.scheduleType = 'auto' // 默认选择自动排产
// 根据BOM数据生成排产列表自动关联车间和计算时间
scheduleBomList.value = flattenedBomData.value.map(item => {
const workshop = determineWorkshop(item.materialCode)
const planAmount = item.netRequiredAmount > 0 ? item.netRequiredAmount : 0
const planStartDate = calculatePlanStartDate(item.materialCode, row.plan_delivery_date)
const planEndDate = calculatePlanEndDate(item.materialCode, planAmount, planStartDate)
return {
...item,
planAmount,
workshop,
planStartDate,
planEndDate
}
})
scheduleDialogVisible.value = true
}
// 处理计划生产数量变化
const handlePlanAmountChange = (row: BomItem) => {
// 更新子件的计划生产数量
if (row.children) {
row.children.forEach(child => {
child.planAmount = row.planAmount * child.bomRatio
})
}
}
// 处理计划开始时间变化
const handleStartDateChange = (row: BomItem) => {
// 如果结束时间早于新的开始时间,清空结束时间
if (row.planEndDate && new Date(row.planEndDate) < new Date(row.planStartDate)) {
row.planEndDate = null
}
}
// 确认排产
const handleConfirmSchedule = async () => {
try {
// 验证BOM排产数据
const invalidItems = scheduleBomList.value.filter(item =>
item.netRequiredAmount > 0 && (
item.planAmount <= 0 ||
!item.planStartDate ||
!item.planEndDate ||
new Date(item.planStartDate) > new Date(item.planEndDate)
)
)
if (invalidItems.length > 0) {
ElMessage.warning('请完善BOM物料的排产信息')
return
}
// TODO: 调用后端API保存排产计划
// await saveSchedulePlan({
// orderId: currentOrder.value.id,
// scheduleType: scheduleForm.value.scheduleType,
// bomScheduleList: scheduleBomList.value
// })
ElMessage.success('排产计划保存成功')
scheduleDialogVisible.value = false
} catch (error) {
ElMessage.error('排产计划保存失败')
}
}
// 物料类型枚举
const MaterialType = {
FINISHED: 'TYRE', // 成品轮胎
SEMIFINISHED: { // 半成品
TREAD: 'TREAD', // 胎面
SIDEWALL: 'SIDEWALL',// 胎侧
BEAD: 'BEAD', // 胎圈
INNER: 'INNER' // 内胎
},
RAW: { // 原材料
RUBBER: 'RUBBER', // 橡胶
CORD: 'CORD', // 帘布
WIRE: 'WIRE', // 钢丝
OTHER: 'OTHER' // 其他
}
}
// 车间类型枚举
const WorkshopType = {
VULCANIZATION: 'vulcanization', // 硫化车间
FORMING: 'forming', // 成型车间
SEMIFINISHED: 'semiFinished' // 半成品车间
}
// 根据物料编码确定生产车间
const determineWorkshop = (materialCode: string): string => {
// 成品轮胎在硫化车间生产
if (materialCode.startsWith(MaterialType.FINISHED)) {
return WorkshopType.VULCANIZATION
}
// 胎面组件、胎侧组件、胎圈组件在成型车间组装
if (materialCode.includes('-ASSY')) {
return WorkshopType.FORMING
}
// 其他半成品在半成品车间生产
return WorkshopType.SEMIFINISHED
}
// 计算BOM生产计划
const calculateBomPlan = (bomItem: BomItem, orderAmount: number) => {
const plans: any[] = []
const workshop = determineWorkshop(bomItem.materialCode)
// 计算当前层级的计划
const requiredAmount = orderAmount * bomItem.bomRatio
const netRequiredAmount = requiredAmount - bomItem.inventoryAmount - bomItem.inTransitAmount
if (netRequiredAmount > 0) {
// 只为需要生产的物料创建计划
plans.push({
materialId: bomItem.id,
materialName: bomItem.materialName,
materialCode: bomItem.materialCode,
unit: bomItem.unit,
bomRatio: bomItem.bomRatio,
requiredAmount,
inventoryAmount: bomItem.inventoryAmount,
inTransitAmount: bomItem.inTransitAmount,
netRequiredAmount,
workshop,
planStartDate: calculatePlanStartDate(bomItem.materialCode, bomItem.plan_delivery_date),
planEndDate: calculatePlanEndDate(bomItem.materialCode, netRequiredAmount, calculatePlanStartDate(bomItem.materialCode, bomItem.plan_delivery_date))
})
}
// 递归计算子件的计划
if (bomItem.children) {
bomItem.children.forEach(child => {
plans.push(...calculateBomPlan(child, orderAmount))
})
}
return plans
}
// 生成生产计划
const handleGeneratePlan = async () => {
try {
// 获取所有待排产的订单
const pendingOrders = orderList.value.filter(order => order.document_status === 'pending')
if (pendingOrders.length === 0) {
ElMessage.warning('没有待排产的订单')
return
}
// 按优先级和交货日期排序
const sortedOrders = [...pendingOrders].sort((a, b) => {
// 优先级排序
const priorityDiff = a.priority - b.priority
if (priorityDiff !== 0) return priorityDiff
// 交货日期排序
const dateA = new Date(a.plan_delivery_date).getTime()
const dateB = new Date(b.plan_delivery_date).getTime()
return dateA - dateB
})
// 生成生产计划
const productionPlans: ProductionPlan[] = []
for (const order of sortedOrders) {
// 获取订单的BOM数据
const bomData = bomTreeData.value.find(item => item.id === order.material_id.toString())
if (!bomData) {
ElMessage.warning(`订单 ${order.saleorder_code} 未找到BOM数据`)
continue
}
// 计算所有需要生产的物料计划
const allBomPlans = calculateBomPlan(bomData, order.order_amount)
// 按车间分组整理生产计划
const vulcanizationPlans = allBomPlans.filter(plan => plan.workshop === WorkshopType.VULCANIZATION)
const formingPlans = allBomPlans.filter(plan => plan.workshop === WorkshopType.FORMING)
const semiFinishedPlans = allBomPlans.filter(plan => plan.workshop === WorkshopType.SEMIFINISHED)
// 确保每个订单只生成一个硫化计划和一个成型计划
if (vulcanizationPlans.length > 1 || formingPlans.length > 1) {
ElMessage.warning(`订单 ${order.saleorder_code} 的成型或硫化计划数量异常`)
continue
}
// 生成最终的生产计划
const plan: ProductionPlan = {
orderId: order.sale_order_id,
orderNo: order.saleorder_code,
materialId: order.material_id,
materialName: order.material_name,
orderAmount: order.order_amount,
planDeliveryDate: order.plan_delivery_date,
priority: order.priority,
vulcanizationPlan: vulcanizationPlans[0],
formingPlan: formingPlans[0],
semiFinishedPlans: semiFinishedPlans
}
productionPlans.push(plan)
}
// TODO: 调用后端API保存生产计划
// await saveProductionPlans(productionPlans)
ElMessage.success('生产计划生成成功')
// 刷新订单列表
getOrderList()
} catch (error) {
ElMessage.error('生产计划生成失败')
}
}
// 计算计划开始时间
const calculatePlanStartDate = (materialCode: string, deliveryDate: string): Date => {
const delivery = new Date(deliveryDate)
const now = new Date()
// 根据物料类型和车间计算提前天数
let daysBeforeDelivery = 0
if (materialCode.startsWith(MaterialType.FINISHED)) {
// 成品轮胎在硫化车间提前3天
daysBeforeDelivery = 3
} else if (materialCode.includes('-ASSY')) {
// 组件在成型车间提前5天
daysBeforeDelivery = 5
} else {
// 半成品提前7天
daysBeforeDelivery = 7
}
// 计算计划开始时间
const planStartDate = new Date(delivery)
planStartDate.setDate(planStartDate.getDate() - daysBeforeDelivery)
// 如果计算出的开始时间早于当前时间,则使用当前时间
return planStartDate < now ? now : planStartDate
}
// 计算计划结束时间
const calculatePlanEndDate = (materialCode: string, amount: number, startDate: Date): Date => {
// 根据物料类型和数量计算生产时间
let hoursPerUnit = 0
if (materialCode.startsWith(MaterialType.FINISHED)) {
// 成品轮胎每个单位需要2小时
hoursPerUnit = 2
} else if (materialCode.includes('-ASSY')) {
// 组件每个单位需要1.5小时
hoursPerUnit = 1.5
} else {
// 半成品每个单位需要1小时
hoursPerUnit = 1
}
// 计算总生产时间(小时)
const totalHours = amount * hoursPerUnit
// 计算结束时间
return new Date(startDate.getTime() + totalHours * 60 * 60 * 1000)
}
// 获取状态类型
const getStatusType = (status) => {
const statusMap = {
pending: 'info',
processing: 'warning',
completed: 'success'
}
return statusMap[status] || 'info'
}
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
pending: '待排产',
processing: '排产中',
completed: '已完成'
}
return statusMap[status] || status
}
// 分页相关方法
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
const handleSizeChange = (val) => {
pageSize.value = val
getOrderList()
}
const handleCurrentChange = (val) => {
currentPage.value = val
getOrderList()
}
// 搜索和重置
const handleSearch = () => {
currentPage.value = 1
getOrderList()
}
const resetSearch = () => {
searchForm.value = {
orderNo: '',
deliveryDate: [],
priority: '',
status: ''
}
handleSearch()
}
// 获取车间标签类型
const getWorkshopTagType = (workshop: string) => {
const typeMap = {
[WorkshopType.VULCANIZATION]: 'danger',
[WorkshopType.FORMING]: 'warning',
[WorkshopType.SEMIFINISHED]: 'info'
}
return typeMap[workshop] || 'info'
}
// 获取车间名称
const getWorkshopName = (workshop: string) => {
const nameMap = {
[WorkshopType.VULCANIZATION]: '硫化车间',
[WorkshopType.FORMING]: '成型车间',
[WorkshopType.SEMIFINISHED]: '半成品车间'
}
return nameMap[workshop] || workshop
}
// 处理排产类型变化
const handleScheduleTypeChange = (type: string) => {
if (type === 'auto' && currentOrder.value) {
// 自动排产时,重新计算所有物料的时间
scheduleBomList.value = scheduleBomList.value.map(item => {
const planStartDate = calculatePlanStartDate(item.materialCode, currentOrder.value.plan_delivery_date)
const planEndDate = calculatePlanEndDate(item.materialCode, item.planAmount, planStartDate)
return {
...item,
planStartDate,
planEndDate
}
})
} else {
// 手动排产时,清空时间
scheduleBomList.value = scheduleBomList.value.map(item => ({
...item,
planStartDate: null,
planEndDate: null
}))
}
}
onMounted(() => {
getOrderList()
})
</script>
<style scoped>
.sales-order-scheduling {
padding: 20px;
}
.bom-container {
padding: 10px;
}
.bom-header {
background-color: #f5f7fa;
padding: 15px;
border-radius: 4px;
}
:deep(.el-table .el-table__row) {
height: 50px;
}
:deep(.el-table .el-table__row:hover) {
background-color: #f5f7fa;
}
.schedule-container {
padding: 10px;
}
.schedule-header {
background-color: #f5f7fa;
padding: 15px;
border-radius: 4px;
}
.schedule-bom-list {
margin-top: 20px;
}
</style>