This commit is contained in:
2026-06-22 10:00:54 +08:00
parent 0141365c61
commit 5477ae58f4
2 changed files with 26 additions and 4 deletions
@@ -498,7 +498,7 @@ public class TourUserAssignmentServiceImpl extends BaseServiceImpl<TourUserAssig
if (!TourUserAssignment.PERSON_TYPE_FORMAL.equals(personType)) {
throw new IllegalArgumentException("只有正式人员可以选择分配路线");
}
NutMap matterInfo = fetchMatterInfo(assignment.getSettingId(), matterId);
NutMap matterInfo = fetchMatterInfoForUpdate(assignment.getSettingId(), matterId);
if (matterInfo == null) {
throw new IllegalArgumentException("分配路线不存在或未配置线路");
}
@@ -524,6 +524,13 @@ public class TourUserAssignmentServiceImpl extends BaseServiceImpl<TourUserAssig
}
String normalizedPersonType = normalizePersonType(personType);
NutMap matterInfo = prepareMatterInfoForAssignment(settingId, matterId, normalizedPersonType);
// 分工会代报名占用事项最大成团人数;在同一事务中锁定事项行,避免多个分工会同时校验通过后并发写入导致超额。
if (matterInfo != null) {
matterInfo = fetchMatterInfoForUpdate(settingId, matterInfo.getString("matterId"));
if (matterInfo == null) {
throw new IllegalArgumentException("疗休养事项不存在或未配置线路");
}
}
TourSetting setting = fetchSettingForAssignment(settingId);
if (setting == null) {
throw new IllegalArgumentException("疗休养配置不存在");
@@ -1252,6 +1259,21 @@ public class TourUserAssignmentServiceImpl extends BaseServiceImpl<TourUserAssig
}
private NutMap fetchMatterInfo(String settingId, String matterId) {
return fetchMatterInfo(settingId, matterId, false);
}
/**
* 在分工会代报名容量校验前锁定事项行,保证同一事项的“统计人数-校验上限-写入台账”串行执行。
* 该方法必须在 controller 已开启的事务中调用,FOR UPDATE 锁会持续到事务提交或回滚。
*/
private NutMap fetchMatterInfoForUpdate(String settingId, String matterId) {
return fetchMatterInfo(settingId, matterId, true);
}
/**
* 查询事项和线路快照信息;forUpdate 为 true 时追加行锁,用于并发容量控制。
*/
private NutMap fetchMatterInfo(String settingId, String matterId, boolean forUpdate) {
Sql sql = Sqls.create("""
SELECT
m.id AS matterId,
@@ -1271,7 +1293,7 @@ public class TourUserAssignmentServiceImpl extends BaseServiceImpl<TourUserAssig
LEFT JOIN tour_line l ON l.id = m.lineId
LEFT JOIN tour_travel_agency ta ON ta.id = l.travelAgencyId
$condition
""");
""" + (forUpdate ? " FOR UPDATE" : ""));
Cnd cnd = Cnd.NEW();
cnd.and("m.delFlag", "=", false);
cnd.and("m.enabled", "=", true);
@@ -122,14 +122,14 @@ layout("/layouts/platform.html"){
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-col :span="6" v-if="$auth.hasPermission('tour.branchUserAssignment.selectMatter')">
<el-form-item label="分配路线" prop="matterId">
<el-select v-model="assignForm.matterId" clearable filterable placeholder="可选,选择后代报名" style="width: 100%" :disabled="assignForm.personType === 'BACKUP'">
<el-option v-for="item in assignMatterOptions" :key="item.matterId" :label="matterOptionLabel(item)" :value="item.matterId"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-col>
<el-form-item label="人员类型" prop="personType">
<el-radio-group v-model="assignForm.personType" @change="assignPersonTypeChange">
<el-radio-button label="FORMAL">正式人员</el-radio-button>