Compare commits

...

4 Commits

Author SHA1 Message Date
tzy
0542bb739e 前端展示的是否质检是否入库 2026-07-31 14:55:15 +08:00
tzy
cb67fc53a2 Merge branch 'master' of https://git.evo-techina.com/sgc/mes-vue
# Conflicts:
#	src/views/biz/orderProcessPlan/board.vue
2026-07-31 14:10:34 +08:00
tzy
ea5fcea2fb Merge branch 'master' of origin; integrate remote updates with local process plan changes 2026-07-28 11:30:51 +08:00
tzy
18b4399789 优化工序计划派工待派数量与卡片信息展示
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 15:40:42 +08:00
11 changed files with 316 additions and 126 deletions

View File

@ -45,6 +45,8 @@ export interface OrderItem {
starter?: number starter?: number
mainCode?: string
orderCode?: string orderCode?: string
routeCode?: string routeCode?: string

View File

@ -88,6 +88,12 @@ export interface ProcessPlanItemVO {
planFinishTime?: string planFinishTime?: string
/** 计划开始(部分接口直接返回 beginTime */
beginTime?: string
/** 计划结束(部分接口直接返回 endTime */
endTime?: string
actualStartTime?: string | null actualStartTime?: string | null
actualFinishTime?: string | null actualFinishTime?: string | null
@ -242,9 +248,9 @@ export function processItemToEntity(
orderItemId, orderItemId,
beginTime: item.planStartTime, beginTime: item.planStartTime || item.beginTime,
endTime: item.planFinishTime, endTime: item.planFinishTime || item.endTime,
procurement: item.procurement, procurement: item.procurement,

View File

@ -89,6 +89,10 @@ export interface KingdeeProcessRoute {
planStartTime: string | null planStartTime: string | null
/** 计划结束时间 yyyy-MM-dd HH:mm:ss */ /** 计划结束时间 yyyy-MM-dd HH:mm:ss */
planFinishTime: string | null planFinishTime: string | null
/** 是否质检 1是 0否后端按控制码推导后返回 */
qualityInspection?: number | null
/** 是否入库 1是 0否后端仅末序为 1 */
storageEntry?: number | null
} }
/** 解析工序名称,兼容旧字段及后端误映射 */ /** 解析工序名称,兼容旧字段及后端误映射 */

View File

@ -1,4 +1,8 @@
import { normalizeKingdeeProcessRoute, type KingdeePrdMo, type KingdeeProcessRoute } from '@/api/orderProject' import {
normalizeKingdeeProcessRoute,
type KingdeePrdMo,
type KingdeeProcessRoute,
} from '@/api/orderProject'
export type KingdeeMoTableRow = Omit<KingdeePrdMo, 'children'> & { processRoutes: KingdeeProcessRoute[] } export type KingdeeMoTableRow = Omit<KingdeePrdMo, 'children'> & { processRoutes: KingdeeProcessRoute[] }
@ -11,6 +15,9 @@ export function mapKingdeeMoListToTableRows(data: KingdeePrdMo[]): KingdeeMoTabl
...r, ...r,
planStartTime: r.planStartTime ?? null, planStartTime: r.planStartTime ?? null,
planFinishTime: r.planFinishTime ?? null, planFinishTime: r.planFinishTime ?? null,
// 质检/入库由后端查询金蝶时已写入,前端原样带出
qualityInspection: r.qualityInspection ?? null,
storageEntry: r.storageEntry ?? null,
})) }))
return { ...rest, processRoutes: routes } return { ...rest, processRoutes: routes }
}) })

View File

@ -54,7 +54,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, h, computed, defineProps, onMounted, watch, defineEmits} from 'vue' import { ref, h, computed, defineProps, onMounted, watch, defineEmits} from 'vue'
import { NButton, NSpace, NIcon, NTag, NDatePicker, NDataTable, useMessage, useDialog, type DataTableColumns } from 'naive-ui' import { NButton, NSpace, NIcon, NTag, NDatePicker, NDataTable, useMessage, useDialog, type DataTableColumns } from 'naive-ui'
import { type KingdeePrdMo, type KingdeeProcessRoute } from '@/api/orderProject' import { type KingdeePrdMo, type KingdeeProcessRoute, resolveProcessName } from '@/api/orderProject'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { CalendarOutline } from '@vicons/ionicons5' import { CalendarOutline } from '@vicons/ionicons5'
@ -72,20 +72,27 @@ const props = defineProps({
selectedId:{ selectedId:{
type: Number, type: Number,
default: null default: null
},
/** 父页面查询/重读数据时的加载状态 */
loading: {
type: Boolean,
default: false
} }
}) })
onMounted(() => { onMounted(() => {
kingdeeTableData.value = props.kingdeeTableData as KingdeeMoTableRow[] kingdeeTableData.value = (props.kingdeeTableData as KingdeeMoTableRow[]) || []
}) })
watch(() => props.kingdeeTableData, () => { watch(() => props.kingdeeTableData, () => {
kingdeeTableData.value = props.kingdeeTableData as KingdeeMoTableRow[] kingdeeTableData.value = (props.kingdeeTableData as KingdeeMoTableRow[]) || []
}, { deep: true }) }, { deep: true })
const kingdeeModalVisible = ref(false) const kingdeeModalVisible = ref(false)
const kingdeeLoading = ref(false) /** 子组件自身操作(保存/同步)的加载状态 */
const actionLoading = ref(false)
/** 表格与按钮共用:父级查询 loading 或本地操作 loading */
const kingdeeLoading = computed(() => props.loading || actionLoading.value)
/** 主表行:工序仅存 processRoutes避免 children 触发树形重复行 */ /** 主表行:工序仅存 processRoutes避免 children 触发树形重复行 */
type KingdeeMoTableRow = Omit<KingdeePrdMo, 'children'> & { processRoutes: KingdeeProcessRoute[] } type KingdeeMoTableRow = Omit<KingdeePrdMo, 'children'> & { processRoutes: KingdeeProcessRoute[] }
const kingdeeTableData = ref<KingdeeMoTableRow[]>([]) const kingdeeTableData = ref<KingdeeMoTableRow[]>([])
@ -93,7 +100,7 @@ const kingdeeExpandedKeys = ref<Array<string | number>>([])
// const kingdeeCurrentProjectId = ref<number | null>(null) // ID // const kingdeeCurrentProjectId = ref<number | null>(null) // ID
const kingdeeTableMaxHeight = 'min(78vh, 720px)' const kingdeeTableMaxHeight = 'min(78vh, 720px)'
const kingdeeMoScrollX = 1820 const kingdeeMoScrollX = 1820
const kingdeeProcessScrollX = 1360 const kingdeeProcessScrollX = 1540
const kingdeeMoColumns: DataTableColumns<KingdeeMoTableRow> = [ const kingdeeMoColumns: DataTableColumns<KingdeeMoTableRow> = [
{ {
@ -155,7 +162,12 @@ const kingdeeProcessColumns: DataTableColumns<KingdeeProcessRoute> = [
return row.operNumber != null ? String(row.operNumber) : '-' return row.operNumber != null ? String(row.operNumber) : '-'
} }
}, },
{ title: '工序名称', key: 'processProperty', width: 100, align: 'center' }, {
title: '工序名称', key: 'processName', width: 100, align: 'center',
render(row) {
return resolveProcessName(row) || '-'
}
},
{ title: '工序说明', key: 'operDescription', width: 140, ellipsis: { tooltip: true } }, { title: '工序说明', key: 'operDescription', width: 140, ellipsis: { tooltip: true } },
{ title: '工作中心', key: 'workCenterName', width: 110, align: 'center', ellipsis: { tooltip: true } }, { title: '工作中心', key: 'workCenterName', width: 110, align: 'center', ellipsis: { tooltip: true } },
{ title: '生产车间', key: 'departmentName', width: 110, align: 'center', ellipsis: { tooltip: true } }, { title: '生产车间', key: 'departmentName', width: 110, align: 'center', ellipsis: { tooltip: true } },
@ -179,6 +191,28 @@ const kingdeeProcessColumns: DataTableColumns<KingdeeProcessRoute> = [
}, },
{ title: '活动单位', key: 'activityUnit', width: 80, align: 'center' }, { title: '活动单位', key: 'activityUnit', width: 80, align: 'center' },
{ title: '控制码', key: 'optCtrlCodeName', width: 120, align: 'center', ellipsis: { tooltip: true } }, { title: '控制码', key: 'optCtrlCodeName', width: 120, align: 'center', ellipsis: { tooltip: true } },
{
title: '是否质检', key: 'qualityInspection', width: 88, align: 'center',
render(row) {
const yes = Number(row.qualityInspection) === 1
return h(NTag, { size: 'small', type: yes ? 'success' : 'default', bordered: false }, {
default: () => (yes ? '是' : '否'),
})
}
},
{
title: '是否入库', key: 'storageEntry', width: 88, align: 'center',
render(row) {
const yes = Number(row.storageEntry) === 1
return h(NTag, {
size: 'small',
type: yes ? 'warning' : 'default',
bordered: false,
}, {
default: () => (yes ? '是' : '否'),
})
}
},
] ]
@ -302,7 +336,7 @@ function handleGanttSchedule() {
async function handleSyncOrderAndPlan() { async function handleSyncOrderAndPlan() {
if (!props.selectedId) return if (!props.selectedId) return
kingdeeLoading.value = true actionLoading.value = true
try { try {
// processRoutes children // processRoutes children
const draftData = kingdeeTableData.value.map(mo => { const draftData = kingdeeTableData.value.map(mo => {
@ -320,7 +354,7 @@ async function handleSyncOrderAndPlan() {
} catch (error) { } catch (error) {
// //
} finally { } finally {
kingdeeLoading.value = false actionLoading.value = false
} }
} }
@ -336,15 +370,8 @@ const reCrawlReorderForm = () => {
positiveText: '确定', positiveText: '确定',
negativeText: '取消', negativeText: '取消',
onPositiveClick: () => { onPositiveClick: () => {
kingdeeLoading.value = true // reCrawlReorderForm kingdeeLoading
try {
emit('reCrawlCallback', props.selectedId, true) emit('reCrawlCallback', props.selectedId, true)
message.success('重启读取成功')
} catch (error) {
//
} finally {
kingdeeLoading.value = false
}
} }
}) })
@ -356,7 +383,7 @@ const handleSaveDraft = () => {
// // 稿 // // 稿
if (!props.selectedId) return if (!props.selectedId) return
kingdeeLoading.value = true actionLoading.value = true
try { try {
const draftData = kingdeeTableData.value.map(mo => { const draftData = kingdeeTableData.value.map(mo => {
const { processRoutes, ...rest } = mo const { processRoutes, ...rest } = mo
@ -367,7 +394,7 @@ const handleSaveDraft = () => {
} catch (error) { } catch (error) {
// //
} finally { } finally {
kingdeeLoading.value = false actionLoading.value = false
} }
} }

View File

@ -4,6 +4,9 @@
<!-- 搜索表单 --> <!-- 搜索表单 -->
<div class="search-form"> <div class="search-form">
<n-form inline :model="searchForm" label-placement="left"> <n-form inline :model="searchForm" label-placement="left">
<n-form-item label="生产令号">
<n-input v-model:value="searchForm.productionCode" placeholder="请输入生产令号" clearable />
</n-form-item>
<n-form-item label="物料编码"> <n-form-item label="物料编码">
<n-input v-model:value="searchForm.code" placeholder="请输入物料编码" clearable /> <n-input v-model:value="searchForm.code" placeholder="请输入物料编码" clearable />
</n-form-item> </n-form-item>
@ -371,6 +374,7 @@
<KingdeeVue <KingdeeVue
:kingdeeTableData="kingdeeTableData" :kingdeeTableData="kingdeeTableData"
:selectedId="kingdeeOrderOtemId" :selectedId="kingdeeOrderOtemId"
:loading="kingdeeLoading"
@close-modal="kingdeeModalVisible = false" @close-modal="kingdeeModalVisible = false"
@holdTemporarilyCallback="holdTemporarilySave" @holdTemporarilyCallback="holdTemporarilySave"
@synchronizationCallback="synchronizationSave" @synchronizationCallback="synchronizationSave"
@ -381,16 +385,18 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, h, onMounted } from 'vue' import { ref, reactive, h, computed, onMounted } from 'vue'
import { NButton, NSpace, NIcon, useMessage, useDialog, type UploadCustomRequestOptions} from 'naive-ui' import { NButton, NSpace, NIcon, NTag, useMessage, useDialog, type UploadCustomRequestOptions} from 'naive-ui'
import { orderItemApi, type OrderItem } from '@/api/orderItem' import { orderItemApi, type OrderItem } from '@/api/orderItem'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { VueDraggable } from 'vue-draggable-plus' import { VueDraggable } from 'vue-draggable-plus'
import KingdeeVue from '@/views/biz/kingdee/index.vue' import KingdeeVue from '@/views/biz/kingdee/index.vue'
import { type KingdeePrdMo, type KingdeeProcessRoute } from '@/api/orderProject' import { type KingdeePrdMo } from '@/api/orderProject'
import { mapKingdeeMoListToTableRows, type KingdeeMoTableRow } from '@/utils/kingdeeSchedule'
import { userApi } from '@/api/system' import { userApi } from '@/api/system'
import { dictDataApi } from '@/api/org'
const message = useMessage() const message = useMessage()
const dialog = useDialog() const dialog = useDialog()
@ -407,6 +413,7 @@ const hasPermission = (permission: string) => userStore.hasPermission(permission
// //
const searchForm = reactive<any>({ const searchForm = reactive<any>({
productionCode: '',
code: '', code: '',
projectName: '', projectName: '',
orderCode: '', orderCode: '',
@ -466,25 +473,30 @@ const defaultFormData = {
const formData = reactive<any>({ ...defaultFormData }) const formData = reactive<any>({ ...defaultFormData })
// //使 // //使
const workshopList = ref<{ label: string; value: any; class: any }[]>([])
// //
const formRules = { const formRules = {
} }
// // computed
const columns = [ const columns = computed(() => [
{ {
type: 'selection' type: 'selection'
}, },
{ {
align:'center', align:'center',
title: '产品名称', title: '生产令号',
key: 'projectName', key: 'productionCode',
// ellipsis: { width: '140'
// tooltip: true
// }
minWidth: 150,
}, },
{
align:'center',
title: '订单编号',
key: 'orderCode',
width:"120"
},
{ {
align:'center', align:'center',
title: '物料名称', title: '物料名称',
@ -497,12 +509,7 @@ const columns = [
key: 'code', key: 'code',
width:"120" width:"120"
}, },
{
align:'center',
title: '订单编号',
key: 'orderCode',
width:"120"
},
{ {
align:'center', align:'center',
title: '工艺路线编码', title: '工艺路线编码',
@ -513,43 +520,58 @@ const columns = [
align:'center', align:'center',
title: '生产车间', title: '生产车间',
key: 'proWorkshop', key: 'proWorkshop',
width:"120" width:"120",
}, render(row: any) {
{ const val = row.proWorkshop
align:'center', // dictValue WorkShopName
title: '产品序列', const opt = workshopList.value.find(o =>
key: 'sort', o.value === val ||
width:"120" String(o.value) === String(val) ||
o.label === val ||
String(o.label) === String(val)
)
if (!opt) return val ?? '-'
const tagType = opt.class && ['default', 'primary', 'info', 'success', 'warning', 'error'].includes(opt.class)
? opt.class
: 'info'
return h(NTag, { type: tagType, size: 'small' }, { default: () => opt.label })
}
}, },
// {
// align:'center',
// title: '',
// key: 'sort',
// width:"120"
// },
{ {
align:'center', align:'center',
title: '生产数量', title: '生产数量',
key: 'quantity', key: 'quantity',
width:"120" width:"120"
}, },
{ // {
align:'center', // align:'center',
title: '是否半成品', // title: '',
key: 'sfProduct', // key: 'sfProduct',
width:"120" // width:"120"
}, // },
{ // {
align:'center', // align:'center',
title: '是否采购', // title: '',
key: 'procurement', // key: 'procurement',
width:"120" // width:"120"
}, // },
{ {
align:'center', align:'center',
title: '开始时间', title: '开始时间',
key: 'beginTime', key: 'beginTime',
width:"120" width:"160"
}, },
{ {
align:'center', align:'center',
title: '结束时间', title: '结束时间',
key: 'endTime', key: 'endTime',
width:"120" width:"160"
}, },
{ {
align:'center', align:'center',
@ -612,15 +634,15 @@ const columns = [
{ default: () => '编辑'} { default: () => '编辑'}
)) ))
} }
if(hasPermission('biz:orderItem:assign')){ // if(hasPermission('biz:orderItem:assign')){
buttons.push(h(NButton, { // buttons.push(h(NButton, {
size: 'small', // size: 'small',
type:'success', // type:'success',
ghost:true, // ghost:true,
onClick: () => { disphand(row) } }, // onClick: () => { disphand(row) } },
{ default: () => '派工'} // { default: () => ''}
)) // ))
} // }
if(hasPermission('biz:orderItem:reorderForm')){ if(hasPermission('biz:orderItem:reorderForm')){
buttons.push(h(NButton, { buttons.push(h(NButton, {
size: 'small', size: 'small',
@ -644,7 +666,7 @@ const columns = [
// ]) // ])
} }
} }
] ])
// //
let scrollX = ref(0) let scrollX = ref(0)
@ -666,6 +688,7 @@ async function loadData() {
const res = await orderItemApi.page({ const res = await orderItemApi.page({
page: pagination.page, page: pagination.page,
pageSize: pagination.pageSize, pageSize: pagination.pageSize,
productionCode: searchForm.productionCode || undefined,
code: searchForm.code, code: searchForm.code,
projectName: searchForm.projectName, projectName: searchForm.projectName,
orderCode: searchForm.orderCode, orderCode: searchForm.orderCode,
@ -689,6 +712,7 @@ function handleSearch() {
// //
function handleReset() { function handleReset() {
searchForm.productionCode = ''
searchForm.code = '' searchForm.code = ''
searchForm.projectName = '' searchForm.projectName = ''
searchForm.orderCode = '' searchForm.orderCode = ''
@ -800,22 +824,14 @@ const kingdeeModalVisible = ref(false)
const kingdeeModalTitle = ref('金蝶生产订单') const kingdeeModalTitle = ref('金蝶生产订单')
const kingdeeLoading = ref(false) const kingdeeLoading = ref(false)
type KingdeeMoTableRow = Omit<KingdeePrdMo, 'children'> & { processRoutes: KingdeeProcessRoute[] }
const kingdeeTableData = ref<KingdeeMoTableRow[]>([]) const kingdeeTableData = ref<KingdeeMoTableRow[]>([])
// const kingdeeExpandedKeys = ref<Array<string | number>>([]) // const kingdeeExpandedKeys = ref<Array<string | number>>([])
async function reCrawlReorderForm(id:number,reCrawl:Boolean){ async function reCrawlReorderForm(id:number,reCrawl:Boolean){
kingdeeLoading.value = true
try { try {
const data = await orderItemApi.preCreationInfo(id, {reCrawl:reCrawl}) const data = await orderItemApi.preCreationInfo(id, {reCrawl:reCrawl})
kingdeeTableData.value = (Array.isArray(data) ? data : []).map((mo) => { kingdeeTableData.value = mapKingdeeMoListToTableRows(Array.isArray(data) ? data : [])
const { children, ...rest } = mo
const routes = [...(children ?? [])].sort((a, b) => (a.operNumber ?? 0) - (b.operNumber ?? 0)).map((r) => ({
...r,
planStartTime: r.planStartTime ?? null,
planFinishTime: r.planFinishTime ?? null,
}))
return { ...rest, processRoutes: routes }
})
} finally { } finally {
kingdeeLoading.value = false kingdeeLoading.value = false
} }
@ -829,9 +845,8 @@ async function handleReorderForm(row:OrderItem, reCrawl:Boolean){
kingdeeOrderOtemId.value = row.id kingdeeOrderOtemId.value = row.id
kingdeeModalTitle.value = `金蝶生产订单${row.code ? ` - ${row.code}` : ''}` kingdeeModalTitle.value = `金蝶生产订单${row.code ? ` - ${row.code}` : ''}`
kingdeeModalVisible.value = true kingdeeModalVisible.value = true
kingdeeLoading.value = true kingdeeTableData.value = []
reCrawlReorderForm(row.id, false) await reCrawlReorderForm(row.id, reCrawl)
kingdeeLoading.value = false
} }
// 稿 // 稿
@ -1102,6 +1117,7 @@ async function handleExport() {
try { try {
const params: Record<string, any> = {} const params: Record<string, any> = {}
if (selectedIds.value.length > 0) params.ids = selectedIds.value if (selectedIds.value.length > 0) params.ids = selectedIds.value
if (searchForm.productionCode) params.productionCode = searchForm.productionCode
if (searchForm.code) params.code = searchForm.code if (searchForm.code) params.code = searchForm.code
if (searchForm.projectName) params.projectName = searchForm.projectName if (searchForm.projectName) params.projectName = searchForm.projectName
if (searchForm.orderCode) params.orderCode = searchForm.orderCode if (searchForm.orderCode) params.orderCode = searchForm.orderCode
@ -1159,6 +1175,10 @@ async function handleImportUpload({ file }: UploadCustomRequestOptions) {
// //
async function loadDictOptions() { async function loadDictOptions() {
try {
const data = await dictDataApi.listByType('work_shop')
workshopList.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue), class: d.listClass }))
} catch {}
} }
onMounted(() => { onMounted(() => {

View File

@ -190,6 +190,11 @@
v-for="(proc, idx) in sortedProcessList(order.processList)" v-for="(proc, idx) in sortedProcessList(order.processList)"
:key="proc.planId ?? idx" :key="proc.planId ?? idx"
:process="proc" :process="proc"
:material-code="order.materialCode"
:order-code="order.orderCode"
:order-begin-time="order.beginTime"
:order-end-time="order.endTime"
:pro-workshop="order.proWorkshop"
:is-last="idx === (order.processList?.length ?? 0) - 1" :is-last="idx === (order.processList?.length ?? 0) - 1"
:can-edit="hasPermission('biz:orderItem:edit')" :can-edit="hasPermission('biz:orderItem:edit')"
:can-assign="hasPermission('biz:orderItem:assign')" :can-assign="hasPermission('biz:orderItem:assign')"
@ -246,6 +251,11 @@
v-for="(proc, idx) in (detailData.processList)" v-for="(proc, idx) in (detailData.processList)"
:key="proc.planId ?? idx" :key="proc.planId ?? idx"
:process="proc" :process="proc"
:material-code="detailData.materialCode"
:order-code="detailData.orderCode"
:order-begin-time="detailData.beginTime"
:order-end-time="detailData.endTime"
:pro-workshop="detailData.proWorkshop"
:is-last="idx === (detailData.processList?.length ?? 0) - 1" :is-last="idx === (detailData.processList?.length ?? 0) - 1"
:can-edit="hasPermission('biz:orderItem:edit')" :can-edit="hasPermission('biz:orderItem:edit')"
:can-assign="hasPermission('biz:orderItem:assign')" :can-assign="hasPermission('biz:orderItem:assign')"
@ -329,6 +339,16 @@
<span>{{dispatchform.quantity}}</span> <span>{{dispatchform.quantity}}</span>
</n-gi> </n-gi>
</n-grid> </n-grid>
<n-grid :cols="2">
<n-gi>
<span class="pgClass">已完成</span>
<span>{{dispatchform.completedQty ?? 0}}</span>
</n-gi>
<n-gi>
<span class="pgClass">本次待派</span>
<span style="color: #2080f0; font-weight: 600">{{dispatchform.remainQty}}</span>
</n-gi>
</n-grid>
<n-grid :cols="2"> <n-grid :cols="2">
<n-gi> <n-gi>
<span class="pgClass">开始时间</span> <span class="pgClass">开始时间</span>
@ -628,7 +648,7 @@ const dispatchmodal = ref(false)
const dispatchLoading = ref(false) const dispatchLoading = ref(false)
const dispatchformRef = ref() const dispatchformRef = ref()
const dispatchform = reactive<any>({ const dispatchform = reactive<any>({
id: '', name: '', beginTime: '', endTime: '', quantity: '',workCenterName:'',proWorkshop:'', id: '', name: '', beginTime: '', endTime: '', quantity: '',workCenterName:'',
list: [{ sectionId:'',deviceId:'', quantity: '' }], list: [{ sectionId:'',deviceId:'', quantity: '' }],
}) })
const dispatchrules = {} const dispatchrules = {}
@ -1063,13 +1083,20 @@ async function handleSubmit() {
function disphand(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) { function disphand(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) {
const entity = processItemToEntity(process, order?.orderItemId,order?.proWorkshop) const entity = processItemToEntity(process, order?.orderItemId,order?.proWorkshop)
const planQty = Number(entity.quantity) || 0
const completedQty = Number(process.completedQty) || 0
const remainQty = Math.max(0, planQty - completedQty)
dispatchmodal.value = true dispatchmodal.value = true
dispatchformRef.value?.restoreValidation() dispatchformRef.value?.restoreValidation()
dispatchform.id = entity.id dispatchform.id = entity.id
dispatchform.name = entity.name dispatchform.name = entity.name
dispatchform.beginTime = entity.beginTime // beginTime/endTime
dispatchform.endTime = entity.endTime dispatchform.beginTime = entity.beginTime || order?.beginTime || ''
dispatchform.endTime = entity.endTime || order?.endTime || ''
// quantity
dispatchform.quantity = entity.quantity dispatchform.quantity = entity.quantity
dispatchform.completedQty = completedQty
dispatchform.remainQty = remainQty
dispatchform.orderItemId = entity.orderItemId dispatchform.orderItemId = entity.orderItemId
dispatchform.sort = entity.sort dispatchform.sort = entity.sort
dispatchform.workCenterName = entity.workCenterName dispatchform.workCenterName = entity.workCenterName
@ -1079,10 +1106,11 @@ function disphand(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) {
console.log(dispatchform) console.log(dispatchform)
dispatchform.list = [{ sectionId: '', deviceId: '', userList: '', quantity: remainQty || '' }]
} }
function addpgnum() { function addpgnum() {
dispatchform.list.push({ sectionId:'',deviceId:'', quantity: '' }) dispatchform.list.push({ sectionId: '', deviceId: '', userList: '', quantity: '' })
} }
function deletenum(index: number) { function deletenum(index: number) {
@ -1094,15 +1122,24 @@ function dispatchSubmit() {
dispatchform.list.forEach((n: any) => { dispatchform.list.forEach((n: any) => {
total += Number(n.quantity) || 0 total += Number(n.quantity) || 0
}) })
const remainQty = Number(dispatchform.remainQty)
if (remainQty <= 0) {
message.warning('当前工序已全部完成,无需再派工')
return
}
dispatchformRef.value?.validate((v: boolean) => { dispatchformRef.value?.validate((errors: any) => {
if (v) return // Naive UI errors
if (total !== Number(dispatchform.quantity)) { if (errors) return
message.error('派工总数量需等于生产总数', { duration: 4000 }) if (total !== remainQty) {
message.error(`派工总数量需等于待派数量 ${remainQty}(计划 ${dispatchform.quantity},已完成 ${dispatchform.completedQty ?? 0}`, {
duration: 4000,
})
return return
} }
dispatchLoading.value = true dispatchLoading.value = true
// quantity
orderProcessPlanApi.assignWork(dispatchform).then(() => { orderProcessPlanApi.assignWork(dispatchform).then(() => {
message.success('派工成功') message.success('派工成功')
dispatchmodal.value = false dispatchmodal.value = false

View File

@ -2,7 +2,13 @@
<div class="process-card-wrap"> <div class="process-card-wrap">
<div class="process-card" :class="themeClass"> <div class="process-card" :class="themeClass">
<div class="card-head"> <div class="card-head">
<span class="card-title">{{ process.processName || '-' }}</span> <div class="card-title-block">
<span class="card-title">
<em v-if="process.operNumber != null" class="card-oper">{{ process.operNumber }}</em>
{{ process.processName || '-' }}
</span>
<span v-if="materialCode" class="card-material">{{ materialCode }}</span>
</div>
<n-tag size="small" :bordered="false" :type="statusTag.type">{{ statusTag.label }}</n-tag> <n-tag size="small" :bordered="false" :type="statusTag.type">{{ statusTag.label }}</n-tag>
</div> </div>
@ -39,22 +45,28 @@
</div> </div>
<div class="card-flow"> <div class="card-flow">
<div class="flow-item">
<span>转入</span>
<strong>{{ process.transferInQty ?? 0 }}</strong>
</div>
<div class="flow-item"> <div class="flow-item">
<span>剩余物料</span> <span>剩余物料</span>
<strong>{{ remainQty }}</strong> <strong>{{ remainQty }}</strong>
</div> </div>
<div class="flow-item"> <div class="flow-item">
<span>下道</span> <span></span>
<strong>{{ process.transferOutQty ?? 0 }}</strong> <strong>{{ process.transferOutQty ?? 0 }}</strong>
</div> </div>
</div> </div>
<div class="card-meta"> <div class="card-meta">
<div class="meta-line"><span>生产订单</span><em>{{ orderCode || '-' }}</em></div>
<div class="meta-line"><span>工单号</span><em>{{ process.workOrderCode || '-' }}</em></div> <div class="meta-line"><span>工单号</span><em>{{ process.workOrderCode || '-' }}</em></div>
<div class="meta-line"><span>人员</span><em>{{ personnel }}</em></div> <div class="meta-line"><span>人员</span><em>{{ personnel }}</em></div>
<div class="meta-line"><span>车间</span><em>{{ process.departmentName || '-' }}</em></div> <div class="meta-line"><span>车间</span><em>{{ workshopText }}</em></div>
<div class="meta-line"><span>工位</span><em>{{ process.workCenterName || '-' }}</em></div> <div class="meta-line"><span>工位</span><em>{{ process.workCenterName || '-' }}</em></div>
<div class="meta-line"><span>设备</span><em>-</em></div> <div class="meta-line"><span>设备</span><em>{{ deviceText }}</em></div>
<div class="meta-line"><span>计划时间</span><em>{{ planTimeText }}</em></div> <div class="meta-line"><span>计划时间</span><em>{{ planTimeText }}</em></div>
<div class="meta-line"><span>实际开始</span><em>{{ process.actualStartTime || '-' }}</em></div> <div class="meta-line"><span>实际开始</span><em>{{ process.actualStartTime || '-' }}</em></div>
</div> </div>
@ -110,6 +122,16 @@ import { PROCESS_STATUS_MAP, type ProcessPlanItemVO } from '@/api/orderProcessPl
const props = defineProps<{ const props = defineProps<{
process: ProcessPlanItemVO process: ProcessPlanItemVO
/** 物料编码(订单头) */
materialCode?: string
/** 生产订单编号(订单头 orderCode */
orderCode?: string
/** 订单计划开始(订单头 beginTime工序无计划时间时回退 */
orderBeginTime?: string
/** 订单计划结束(订单头 endTime */
orderEndTime?: string
/** 生产车间(订单头 proWorkshop */
proWorkshop?: string
isLast?: boolean isLast?: boolean
canEdit?: boolean canEdit?: boolean
canAssign?: boolean canAssign?: boolean
@ -171,15 +193,39 @@ const railColor = computed(() => {
const personnel = computed(() => { const personnel = computed(() => {
if (props.process.assignByName) return props.process.assignByName if (props.process.assignByName) return props.process.assignByName
const list = props.process.assignWorkList const list = props.process.assignWorkList
if (list?.length) return `${list.length} 人派工` if (list?.length) {
const names = list
.map((a: any) => a.userName || a.userId)
.filter(Boolean)
if (names.length) return names.join('、')
return `${list.length} 人派工`
}
return '-' return '-'
}) })
const workshopText = computed(() => {
return props.process.departmentName || props.proWorkshop || '-'
})
const deviceText = computed(() => {
const list = props.process.assignWorkList as any[] | undefined
if (!list?.length) return '-'
const devices = list
.map((a) => a.deviceName || a.deviceId)
.filter(Boolean)
return devices.length ? [...new Set(devices)].join('、') : '-'
})
const planTimeText = computed(() => { const planTimeText = computed(() => {
const start = props.process.planStartTime // 退 beginTime/endTime
const end = props.process.planFinishTime const start = props.process.planStartTime || props.process.beginTime || props.orderBeginTime
const end = props.process.planFinishTime || props.process.endTime || props.orderEndTime
if (!start && !end) return '-' if (!start && !end) return '-'
if (start && end) return `${start.slice(0, 16)} ~ ${end.slice(11, 16)}` if (start && end) {
const startText = start.length >= 16 ? start.slice(0, 16) : start
const endText = end.length >= 16 ? end.slice(11, 16) : end
return `${startText} ~ ${endText}`
}
return start || end || '-' return start || end || '-'
}) })
</script> </script>
@ -252,6 +298,29 @@ const planTimeText = computed(() => {
white-space: nowrap; white-space: nowrap;
} }
.card-oper {
font-style: normal;
font-weight: 700;
margin-right: 4px;
opacity: 0.95;
}
.card-title-block {
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.card-material {
font-size: 11px;
font-weight: 500;
opacity: 0.85;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-progress { .card-progress {
display: flex; display: flex;
justify-content: center; justify-content: center;

View File

@ -233,6 +233,16 @@
<n-input v-model:value="dispatchform.quantity" disabled /> <n-input v-model:value="dispatchform.quantity" disabled />
</n-form-item> </n-form-item>
</n-gi> </n-gi>
<n-gi>
<n-form-item label="已完成">
<n-input :value="String(dispatchform.completedQty ?? 0)" disabled />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="本次待派">
<n-input :value="String(dispatchform.remainQty ?? 0)" disabled />
</n-form-item>
</n-gi>
<n-gi> <n-gi>
<n-form-item label="开始时间"> <n-form-item label="开始时间">
<n-input v-model:value="dispatchform.beginTime" disabled /> <n-input v-model:value="dispatchform.beginTime" disabled />
@ -816,6 +826,8 @@ let dispatchform = reactive<any>({
endTime:'', endTime:'',
userid:'', userid:'',
quantity:'', quantity:'',
completedQty: 0,
remainQty: 0,
list:[{ list:[{
userId:'', userId:'',
quantity:'' quantity:''
@ -866,17 +878,22 @@ function deletenum(index:any) {
function disphand(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) { function disphand(process: ProcessPlanItemVO, order?: OrderProcessPlanVO) {
const entity = processItemToEntity(process, order?.orderItemId) const entity = processItemToEntity(process, order?.orderItemId)
const planQty = Number(entity.quantity) || 0
const completedQty = Number(process.completedQty) || 0
const remainQty = Math.max(0, planQty - completedQty)
dispatchmodal.value = true dispatchmodal.value = true
dispatchformRef.value?.restoreValidation() dispatchformRef.value?.restoreValidation()
dispatchform.id = entity.id dispatchform.id = entity.id
dispatchform.name = entity.name dispatchform.name = entity.name
dispatchform.beginTime = entity.beginTime dispatchform.beginTime = entity.beginTime || order?.beginTime || ''
dispatchform.endTime = entity.endTime dispatchform.endTime = entity.endTime || order?.endTime || ''
dispatchform.quantity = entity.quantity dispatchform.quantity = entity.quantity
dispatchform.completedQty = completedQty
dispatchform.remainQty = remainQty
dispatchform.userid = '' dispatchform.userid = ''
dispatchform.list = [{ dispatchform.list = [{
userId: '', userId: '',
quantity: '', quantity: remainQty || '',
}] }]
} }
@ -885,10 +902,15 @@ function dispatchSubmit() {
dispatchform.list.forEach((n:any) => { dispatchform.list.forEach((n:any) => {
total = n.quantity*1+total total = n.quantity*1+total
}) })
const remainQty = Number(dispatchform.remainQty)
if (remainQty <= 0) {
message.warning('当前工序已全部完成,无需再派工')
return
}
dispatchformRef.value?.validate((v:any) => { dispatchformRef.value?.validate((v:any) => {
if(!v){ if(!v){
if(total != dispatchform.quantity){ if(total != remainQty){
message.error('派工总数量需等于生产总数',{ message.error(`派工总数量需等于待派数量 ${remainQty}(计划 ${dispatchform.quantity},已完成 ${dispatchform.completedQty ?? 0}`,{
duration:4000 duration:4000
}) })
}else{ }else{

View File

@ -151,6 +151,7 @@
<KingdeeVue <KingdeeVue
:kingdeeTableData="kingdeeTableData" :kingdeeTableData="kingdeeTableData"
:selectedId="kingdeeCurrentProjectId" :selectedId="kingdeeCurrentProjectId"
:loading="kingdeeLoading"
@close-modal="kingdeeModalVisible = false" @close-modal="kingdeeModalVisible = false"
@holdTemporarilyCallback="handleSaveDraft" @holdTemporarilyCallback="handleSaveDraft"
@synchronizationCallback="handleSyncOrderAndPlan" @synchronizationCallback="handleSyncOrderAndPlan"
@ -194,7 +195,8 @@
import { ref, reactive, h, computed, onMounted, watch } from 'vue' import { ref, reactive, h, computed, onMounted, watch } from 'vue'
import { NButton, NSpace, NIcon, NTag, NDatePicker, NDataTable, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui' import { NButton, NSpace, NIcon, NTag, NDatePicker, NDataTable, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline, EyeOutline, CalendarOutline } from '@vicons/ionicons5' import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline, EyeOutline, CalendarOutline } from '@vicons/ionicons5'
import { orderProjectApi, type OrderProject, type KingdeePrdMo, type KingdeeProcessRoute, normalizeKingdeeProcessRoute } from '@/api/orderProject' import { orderProjectApi, type OrderProject, type KingdeePrdMo, type KingdeeProcessRoute } from '@/api/orderProject'
import { mapKingdeeMoListToTableRows } from '@/utils/kingdeeSchedule'
import { dictDataApi } from '@/api/org' import { dictDataApi } from '@/api/org'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
@ -660,21 +662,14 @@ function handleGanttSchedule() {
} }
async function reCrawlReorderForm(id:number,reCrawl:Boolean){ async function reCrawlReorderForm(id:number,reCrawl:Boolean){
kingdeeLoading.value = true
try { try {
const data = await orderProjectApi.getKingdeeOrder(id,{reCrawl:reCrawl}) const data = await orderProjectApi.getKingdeeOrder(id,{reCrawl:reCrawl})
kingdeeTableData.value = (Array.isArray(data) ? data : []).map((mo) => { kingdeeTableData.value = mapKingdeeMoListToTableRows(Array.isArray(data) ? data : [])
const { children, ...rest } = mo
const routes = [...(children ?? [])]
.sort((a, b) => (a.operNumber ?? 0) - (b.operNumber ?? 0))
.map((r) => normalizeKingdeeProcessRoute({
...r,
planStartTime: r.planStartTime ?? null,
planFinishTime: r.planFinishTime ?? null,
}))
return { ...rest, processRoutes: routes }
})
if (kingdeeTableData.value.length === 0) { if (kingdeeTableData.value.length === 0) {
message.info('未查询到金蝶生产订单数据') message.info('未查询到金蝶生产订单数据')
} else if (reCrawl) {
message.success('重新读取成功')
} }
} finally { } finally {
kingdeeLoading.value = false kingdeeLoading.value = false

View File

@ -20,7 +20,8 @@ export default defineConfig({
port: 3000, port: 3000,
proxy: { proxy: {
'/api': { '/api': {
target:'http://192.168.12.4:8888/', target:'http://192.168.12.12:8888/', //'http://192.168.5.230:8888',
//target:'http://192.168.12.4:8888/',
//target:'http://192.168.12.230:8888', //target:'http://192.168.12.230:8888',
//target:'http://192.168.5.230:8888', //target:'http://192.168.5.230:8888',
//target:'http://192.168.5.232:8888/', //target:'http://192.168.5.232:8888/',