fix: 完善日计划开始确认与取消入口
This commit is contained in:
@@ -426,7 +426,7 @@ git commit -m "feat: 增加LMS日计划管理接口"
|
|||||||
|
|
||||||
- [ ] **步骤 4:实现开始订单和详情**
|
- [ ] **步骤 4:实现开始订单和详情**
|
||||||
|
|
||||||
开始订单弹窗展示当前数量并允许填写追加数量。详情抽屉展示三类状态时间和按时间倒序的操作记录。
|
开始订单弹窗展示原计划量、确认计划量、已有追加、本次追加和开始后总量;确认计划量为必填正整数,ERP 与人工来源都可在开始时确认。服务端在同一事务内原子更新计划量、累计追加量和订单状态,并分别记录数量调整、追加和开始日志。管理页为未开始订单提供填写原因的取消入口。详情抽屉展示三类状态时间和按时间倒序的操作记录。
|
||||||
|
|
||||||
- [ ] **步骤 5:运行局部 ESLint**
|
- [ ] **步骤 5:运行局部 ESLint**
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
- 人工新增日计划。
|
- 人工新增日计划。
|
||||||
- 编辑未开始的人工日计划。
|
- 编辑未开始的人工日计划。
|
||||||
- 对未开始订单执行“开始订单”。
|
- 对未开始订单执行“开始订单”。
|
||||||
|
- 对未开始订单执行“取消订单”,填写原因后可从作业页恢复。
|
||||||
- 已结束订单只在本页面查询,不进入作业页面。
|
- 已结束订单只在本页面查询,不进入作业页面。
|
||||||
|
|
||||||
查询条件在桌面端一行多项展示,包括日计划编号或 ERP 订单号、计划日期范围、管芯编码或名称、来源和订单状态。
|
查询条件在桌面端一行多项展示,包括日计划编号或 ERP 订单号、计划日期范围、管芯编码或名称、来源和订单状态。
|
||||||
@@ -136,7 +137,7 @@ ERP 推送不允许直接修改顺序、权重、当前完成数量或作业状
|
|||||||
|
|
||||||
人工创建字段包括计划日期、人工订单号、管芯物料、计划数量、装箱信息、顺序和权重。人工订单号允许手填;未填写时使用 LMS 日计划编号作为订单号。
|
人工创建字段包括计划日期、人工订单号、管芯物料、计划数量、装箱信息、顺序和权重。人工订单号允许手填;未填写时使用 LMS 日计划编号作为订单号。
|
||||||
|
|
||||||
只有未开始的人工来源日计划允许编辑基本信息。ERP 来源计划通过 ERP 幂等推送维护,管理页面只读。开始订单时允许再次确认计划数量,并可填写追加数量;开始成功后进入日计划作业页面。
|
只有未开始的人工来源日计划允许编辑基本信息。ERP 来源计划通过 ERP 幂等推送维护,管理页面只读。开始订单时 ERP 和人工来源均允许再次确认计划数量,并可填写追加数量;计划数量、累计追加数量和开始状态在同一事务中原子更新,开始成功后进入日计划作业页面。
|
||||||
|
|
||||||
追加数量仅允许订单进行中时执行,必须大于零,按累计值保存并记录每次追加明细。
|
追加数量仅允许订单进行中时执行,必须大于零,按累计值保存并记录每次追加明细。
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ import lombok.EqualsAndHashCode;
|
|||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class DailyPlanStartReqVO extends DailyPlanVersionReqVO {
|
public class DailyPlanStartReqVO extends DailyPlanVersionReqVO {
|
||||||
|
@NotNull(message = "确认计划数量不能为空")
|
||||||
|
@Min(value = 1, message = "确认计划数量必须为正整数")
|
||||||
|
private Integer planQty;
|
||||||
|
|
||||||
@NotNull(message = "追加数量不能为空")
|
@NotNull(message = "追加数量不能为空")
|
||||||
@Min(value = 0, message = "追加数量不能小于0")
|
@Min(value = 0, message = "追加数量不能小于0")
|
||||||
private Integer appendQty;
|
private Integer appendQty;
|
||||||
|
|||||||
@@ -94,6 +94,9 @@ public interface DailyPlanMapper extends BaseMapperX<DailyPlanDO> {
|
|||||||
*/
|
*/
|
||||||
int updateStateWithVersion(@Param("plan") DailyPlanDO plan);
|
int updateStateWithVersion(@Param("plan") DailyPlanDO plan);
|
||||||
|
|
||||||
|
/** 原子确认数量并开始未开始订单。 */
|
||||||
|
int startOrderWithVersion(@Param("plan") DailyPlanDO plan);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 乐观锁恢复已取消的订单状态,并清空前一订单状态。
|
* 乐观锁恢复已取消的订单状态,并清空前一订单状态。
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -359,19 +359,32 @@ public class DailyPlanServiceImpl implements DailyPlanService {
|
|||||||
checkAffected(dailyPlanMapper.updateNotStartedPlan(update));
|
checkAffected(dailyPlanMapper.updateNotStartedPlan(update));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 开始订单并写入追加数量。 */
|
/** 开始订单,同时确认计划数量并写入本次追加数量。 */
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void startOrder(DailyPlanStartReqVO reqVO) {
|
public void startOrder(DailyPlanStartReqVO reqVO) {
|
||||||
DailyPlanDO current = validateVersion(reqVO.getId(), reqVO.getVersion());
|
DailyPlanDO current = validateVersion(reqVO.getId(), reqVO.getVersion());
|
||||||
validateOrderStatus(current, DailyPlanOrderStatusEnum.NOT_STARTED, "开始订单");
|
validateOrderStatus(current, DailyPlanOrderStatusEnum.NOT_STARTED, "开始订单");
|
||||||
DailyPlanDO update = newUpdate(current);
|
DailyPlanDO update = newUpdate(current);
|
||||||
|
int confirmedPlanQty = reqVO.getPlanQty();
|
||||||
|
int appendQty = safeAddQuantity(current.getAppendQty(), reqVO.getAppendQty());
|
||||||
|
safeAddQuantity(confirmedPlanQty, appendQty); // 校验开始后的需求总量不会溢出。
|
||||||
|
update.setPlanQty(confirmedPlanQty);
|
||||||
update.setOrderStatus(DailyPlanOrderStatusEnum.IN_PROGRESS.getCode());
|
update.setOrderStatus(DailyPlanOrderStatusEnum.IN_PROGRESS.getCode());
|
||||||
update.setOrderStartTime(LocalDateTime.now());
|
update.setOrderStartTime(LocalDateTime.now());
|
||||||
update.setAppendQty(safeAddQuantity(current.getAppendQty(), reqVO.getAppendQty()));
|
update.setAppendQty(appendQty);
|
||||||
checkAffected(dailyPlanMapper.updateStateWithVersion(update));
|
checkAffected(dailyPlanMapper.startOrderWithVersion(update));
|
||||||
|
if (!current.getPlanQty().equals(confirmedPlanQty)) {
|
||||||
|
insertLog(current.getDailyPlanId(), TARGET_QUANTITY, TYPE_UPDATE,
|
||||||
|
String.valueOf(current.getPlanQty()), String.valueOf(confirmedPlanQty),
|
||||||
|
confirmedPlanQty - current.getPlanQty(), null);
|
||||||
|
}
|
||||||
|
if (reqVO.getAppendQty() > 0) {
|
||||||
|
insertLog(current.getDailyPlanId(), TARGET_QUANTITY, TYPE_APPEND,
|
||||||
|
String.valueOf(current.getAppendQty()), String.valueOf(appendQty), reqVO.getAppendQty(), null);
|
||||||
|
}
|
||||||
insertLog(current.getDailyPlanId(), TARGET_ORDER, TYPE_START, String.valueOf(current.getOrderStatus()),
|
insertLog(current.getDailyPlanId(), TARGET_ORDER, TYPE_START, String.valueOf(current.getOrderStatus()),
|
||||||
String.valueOf(update.getOrderStatus()), reqVO.getAppendQty(), null);
|
String.valueOf(update.getOrderStatus()), null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 追加进行中订单的数量。 */
|
/** 追加进行中订单的数量。 */
|
||||||
|
|||||||
@@ -246,6 +246,20 @@
|
|||||||
AND deleted = b'0'
|
AND deleted = b'0'
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<update id="startOrderWithVersion">
|
||||||
|
UPDATE lms_daily_plan
|
||||||
|
SET plan_qty = #{plan.planQty},
|
||||||
|
append_qty = #{plan.appendQty},
|
||||||
|
order_status = 1,
|
||||||
|
order_start_time = #{plan.orderStartTime},
|
||||||
|
updater = #{plan.updater},
|
||||||
|
version = version + 1
|
||||||
|
WHERE daily_plan_id = #{plan.dailyPlanId}
|
||||||
|
AND version = #{plan.version}
|
||||||
|
AND order_status = 0
|
||||||
|
AND deleted = b'0'
|
||||||
|
</update>
|
||||||
|
|
||||||
<update id="updateSortWithVersion">
|
<update id="updateSortWithVersion">
|
||||||
UPDATE lms_daily_plan
|
UPDATE lms_daily_plan
|
||||||
SET sort_seq = #{sortSeq}, updater = #{updater}, version = version + 1
|
SET sort_seq = #{sortSeq}, updater = #{updater}, version = version + 1
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export namespace LmsDailyPlanApi {
|
|||||||
|
|
||||||
export interface StartReq extends VersionReq {
|
export interface StartReq extends VersionReq {
|
||||||
appendQty: number;
|
appendQty: number;
|
||||||
|
planQty: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppendReq extends VersionReq {
|
export interface AppendReq extends VersionReq {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui';
|
|||||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { getDailyPlanManagementPage } from '#/api/lms/dailyplan';
|
import { getDailyPlanManagementPage } from '#/api/lms/dailyplan';
|
||||||
|
|
||||||
|
import Operation from '../work/modules/operation.vue';
|
||||||
import { useGridColumns, useGridFormSchema } from './data';
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
import Detail from './modules/detail.vue';
|
import Detail from './modules/detail.vue';
|
||||||
import Form from './modules/form.vue';
|
import Form from './modules/form.vue';
|
||||||
@@ -27,6 +28,10 @@ const [StartOrderModal, startOrderModalApi] = useVbenModal({
|
|||||||
connectedComponent: StartOrder,
|
connectedComponent: StartOrder,
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
|
const [OperationModal, operationModalApi] = useVbenModal({
|
||||||
|
connectedComponent: Operation,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
function handleRefresh() {
|
function handleRefresh() {
|
||||||
gridApi.query();
|
gridApi.query();
|
||||||
@@ -48,6 +53,10 @@ function handleStart(row: LmsDailyPlanApi.DailyPlan) {
|
|||||||
startOrderModalApi.setData(row).open();
|
startOrderModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleCancel(row: LmsDailyPlanApi.DailyPlan) {
|
||||||
|
operationModalApi.setData({ row, type: 'cancelOrder' }).open();
|
||||||
|
}
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
@@ -82,6 +91,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<FormDrawer @success="handleRefresh" />
|
<FormDrawer @success="handleRefresh" />
|
||||||
<DetailDrawer />
|
<DetailDrawer />
|
||||||
<StartOrderModal @success="handleRefresh" />
|
<StartOrderModal @success="handleRefresh" />
|
||||||
|
<OperationModal @success="handleRefresh" />
|
||||||
<Grid table-title="日计划管理">
|
<Grid table-title="日计划管理">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<TableAction
|
<TableAction
|
||||||
@@ -126,6 +136,14 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
ifShow: row.orderStatus === ORDER_NOT_STARTED,
|
ifShow: row.orderStatus === ORDER_NOT_STARTED,
|
||||||
onClick: handleStart.bind(null, row),
|
onClick: handleStart.bind(null, row),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '取消订单',
|
||||||
|
type: 'link',
|
||||||
|
danger: true,
|
||||||
|
auth: ['lms:daily-plan:operate'],
|
||||||
|
ifShow: row.orderStatus === ORDER_NOT_STARTED,
|
||||||
|
onClick: handleCancel.bind(null, row),
|
||||||
|
},
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -11,12 +11,17 @@ import { startDailyPlanOrder } from '#/api/lms/dailyplan';
|
|||||||
|
|
||||||
const emit = defineEmits(['success']);
|
const emit = defineEmits(['success']);
|
||||||
const row = ref<LmsDailyPlanApi.DailyPlan>();
|
const row = ref<LmsDailyPlanApi.DailyPlan>();
|
||||||
|
const planQty = ref(1);
|
||||||
const appendQty = ref(0);
|
const appendQty = ref(0);
|
||||||
const totalAfterStart = computed(() => (row.value?.totalQty ?? 0) + appendQty.value);
|
const totalAfterStart = computed(() => planQty.value + (row.value?.appendQty ?? 0) + appendQty.value);
|
||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
async onConfirm() {
|
async onConfirm() {
|
||||||
if (!row.value) return;
|
if (!row.value) return;
|
||||||
|
if (!Number.isInteger(planQty.value) || planQty.value <= 0) {
|
||||||
|
message.warning('确认计划数量必须为正整数');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!Number.isInteger(appendQty.value) || appendQty.value < 0) {
|
if (!Number.isInteger(appendQty.value) || appendQty.value < 0) {
|
||||||
message.warning('追加数量必须为不小于 0 的整数');
|
message.warning('追加数量必须为不小于 0 的整数');
|
||||||
return;
|
return;
|
||||||
@@ -26,6 +31,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
await startDailyPlanOrder({
|
await startDailyPlanOrder({
|
||||||
id: row.value.dailyPlanId,
|
id: row.value.dailyPlanId,
|
||||||
version: row.value.version,
|
version: row.value.version,
|
||||||
|
planQty: planQty.value,
|
||||||
appendQty: appendQty.value,
|
appendQty: appendQty.value,
|
||||||
});
|
});
|
||||||
await modalApi.close();
|
await modalApi.close();
|
||||||
@@ -38,10 +44,12 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
onOpenChange(isOpen: boolean) {
|
onOpenChange(isOpen: boolean) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
row.value = undefined;
|
row.value = undefined;
|
||||||
|
planQty.value = 1;
|
||||||
appendQty.value = 0;
|
appendQty.value = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
row.value = modalApi.getData<LmsDailyPlanApi.DailyPlan>();
|
row.value = modalApi.getData<LmsDailyPlanApi.DailyPlan>();
|
||||||
|
planQty.value = row.value.planQty;
|
||||||
appendQty.value = 0;
|
appendQty.value = 0;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -52,7 +60,10 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
<Descriptions v-if="row" bordered :column="1" size="small">
|
<Descriptions v-if="row" bordered :column="1" size="small">
|
||||||
<DescriptionsItem label="日计划编号">{{ row.planCode }}</DescriptionsItem>
|
<DescriptionsItem label="日计划编号">{{ row.planCode }}</DescriptionsItem>
|
||||||
<DescriptionsItem label="管芯">{{ row.materialCode }} / {{ row.materialName }}</DescriptionsItem>
|
<DescriptionsItem label="管芯">{{ row.materialCode }} / {{ row.materialName }}</DescriptionsItem>
|
||||||
<DescriptionsItem label="当前计划数量">{{ row.planQty }}</DescriptionsItem>
|
<DescriptionsItem label="原计划数量">{{ row.planQty }}</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="确认计划数量">
|
||||||
|
<InputNumber v-model:value="planQty" :min="1" :precision="0" class="w-full" />
|
||||||
|
</DescriptionsItem>
|
||||||
<DescriptionsItem label="已有追加数量">{{ row.appendQty }}</DescriptionsItem>
|
<DescriptionsItem label="已有追加数量">{{ row.appendQty }}</DescriptionsItem>
|
||||||
<DescriptionsItem label="本次追加数量">
|
<DescriptionsItem label="本次追加数量">
|
||||||
<InputNumber v-model:value="appendQty" :min="0" :precision="0" class="w-full" />
|
<InputNumber v-model:value="appendQty" :min="0" :precision="0" class="w-full" />
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ CROSS JOIN (
|
|||||||
UNION ALL SELECT 4, '日计划启动', 'lms:daily-plan:start'
|
UNION ALL SELECT 4, '日计划启动', 'lms:daily-plan:start'
|
||||||
UNION ALL SELECT 5, '日计划排程', 'lms:daily-plan:schedule'
|
UNION ALL SELECT 5, '日计划排程', 'lms:daily-plan:schedule'
|
||||||
UNION ALL SELECT 6, '日计划策略', 'lms:daily-plan:strategy'
|
UNION ALL SELECT 6, '日计划策略', 'lms:daily-plan:strategy'
|
||||||
|
UNION ALL SELECT 7, '日计划操作', 'lms:daily-plan:operate'
|
||||||
) permission
|
) permission
|
||||||
WHERE menu.id = COALESCE(
|
WHERE menu.id = COALESCE(
|
||||||
(SELECT fixed.id FROM system_menu fixed WHERE fixed.id=2093000000000000100 AND fixed.deleted=b'0'),
|
(SELECT fixed.id FROM system_menu fixed WHERE fixed.id=2093000000000000100 AND fixed.deleted=b'0'),
|
||||||
@@ -63,7 +64,10 @@ WHERE menu.id = COALESCE(
|
|||||||
WHERE fallback.component='lms/daily-plan/management/index' AND fallback.deleted=b'0')
|
WHERE fallback.component='lms/daily-plan/management/index' AND fallback.deleted=b'0')
|
||||||
)
|
)
|
||||||
AND menu.deleted=b'0'
|
AND menu.deleted=b'0'
|
||||||
AND NOT EXISTS (SELECT 1 FROM system_menu existing WHERE existing.permission=permission.code AND existing.deleted=b'0');
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM system_menu existing
|
||||||
|
WHERE existing.parent_id=menu.id AND existing.permission=permission.code AND existing.deleted=b'0'
|
||||||
|
);
|
||||||
|
|
||||||
-- 作业页面独立挂载自身实际使用的按钮权限,避免仅授权作业菜单时按钮全部不可见。
|
-- 作业页面独立挂载自身实际使用的按钮权限,避免仅授权作业菜单时按钮全部不可见。
|
||||||
INSERT INTO system_menu
|
INSERT INTO system_menu
|
||||||
|
|||||||
Reference in New Issue
Block a user