diff --git a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/statistics/TheRapyRecuperationLineFragmentController.java b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/statistics/TheRapyRecuperationLineFragmentController.java index 35e15a8..8471000 100644 --- a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/statistics/TheRapyRecuperationLineFragmentController.java +++ b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/statistics/TheRapyRecuperationLineFragmentController.java @@ -244,7 +244,13 @@ public class TheRapyRecuperationLineFragmentController { .and("isNormal", "=", true).and("year(signingUptime)", "=", management.getYear()) .and("stateId", "not in", Lang.array(TheRapyRecuperationState.UNITFAIL, TheRapyRecuperationState.LINEUNITFAIL, TheRapyRecuperationState.SCHOOLFAIL))); - if (enrolls.size() < config.getFragmentCount()) { + if (Lang.isEmpty(enrolls)) { + return Result.error("当前没有可成团报名人员"); + } + + // 被配置为不限制的旅行社,分段式成团确认时不再校验年度最少成团人数。 + boolean unlimitedMinimumGroupSize = configService.isMinimumGroupSizeUnlimited(management.getYear(), management.getTravelAgencyId()); + if (!unlimitedMinimumGroupSize && enrolls.size() < config.getFragmentCount()) { return Result.error("报名人数为%s人,未达到%s人的成团条件".formatted(enrolls.size(), config.getFragmentCount())); } @@ -319,9 +325,14 @@ public class TheRapyRecuperationLineFragmentController { @ViReturn @Aop(TransAop.READ_COMMITTED) @RequiresPermissions("theRapyRecuperation.fragment") - public Object oneKeyGroupSuccess(String travelAgencyId, String baseId, String playStart, String playEnd, Boolean type) { + public Object oneKeyGroupSuccess(String travelAgencyId, String baseId, String playStart, String playEnd, Integer startYear, Integer endYear, Boolean type) { + Result checkYearResult = checkSingleYear(startYear, endYear); + if (checkYearResult != null) { + return checkYearResult; + } TheRapyRecuperationBaseManagement currentManagement = StrUtil.isBlank(baseId) ? null : dao.fetch(TheRapyRecuperationBaseManagement.class, baseId); - TheRapyRecuperationConfig config = configService.requireByYear(currentManagement == null ? DateUtil.thisYear() : currentManagement.getYear()); + Integer configYear = currentManagement == null ? (startYear == null ? DateUtil.thisYear() : startYear) : currentManagement.getYear(); + TheRapyRecuperationConfig config = configService.requireByYear(configYear); Integer fragmentCount = config.getFragmentCount(); if (DateUtil.compare(new Date(), config.getFragmentEndTime()) < 0) { @@ -335,6 +346,7 @@ public class TheRapyRecuperationLineFragmentController { en.takePartInBaseManagementId, en.specificTime, en.playDay, + bm.travelAgencyId, count( specificTime ) as userCount FROM the_rapy_recuperation_enroll en @@ -349,8 +361,8 @@ public class TheRapyRecuperationLineFragmentController { cnd.andEX("REPLACE(REPLACE(SUBSTRING_INDEX(specificTime, '-', 1), '月', '-'), '日', '')", "<=", playEnd); cnd.andEX("bm.travelAgencyId", "=", travelAgencyId); cnd.andEX("en.takePartInBaseManagementId", "=", baseId); - cnd.groupBy("en.takePartInBaseManagementId,en.playDay,en.specificTime"); - cnd.having(Cnd.where("userCount", ">=", fragmentCount)); + cnd.groupBy("en.takePartInBaseManagementId,en.playDay,en.specificTime,bm.travelAgencyId"); + addFragmentGroupCountHaving(cnd, fragmentCount, config.getUnlimitedGroupTravelAgencyIds()); sql.setCondition(cnd); List enrolls = enrollService.listMap(sql); for (NutMap enroll : enrolls) { @@ -454,13 +466,20 @@ public class TheRapyRecuperationLineFragmentController { @Param(value = "playStart", required = false) String playStart, @Param(value = "playEnd", required = false) String playEnd, HttpServletResponse response) throws IOException { + Result checkYearResult = checkSingleYear(startYear, endYear); + if (checkYearResult != null) { + response.setContentType("text/plain;charset=UTF-8"); + response.getWriter().write(checkYearResult.getMsg()); + return; + } response.setContentType("application/octet-stream"); response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode("分段疗休养成团表.zip", StandardCharsets.UTF_8)); TheRapyRecuperationBaseManagement currentManagement = StrUtil.isBlank(baseManagementId) ? null : dao.fetch(TheRapyRecuperationBaseManagement.class, baseManagementId); - TheRapyRecuperationConfig rapyConfig = configService.requireByYear(currentManagement == null ? DateUtil.thisYear() : currentManagement.getYear()); + Integer configYear = currentManagement == null ? (startYear == null ? DateUtil.thisYear() : startYear) : currentManagement.getYear(); + TheRapyRecuperationConfig rapyConfig = configService.requireByYear(configYear); Integer fragmentCount = rapyConfig.getFragmentCount(); Sql sql = Sqls.create(""" SELECT @@ -469,6 +488,7 @@ public class TheRapyRecuperationLineFragmentController { en.playDay, count( specificTime ) as userCount, en.hasLocked, + bm.travelAgencyId, bm.baseName, ta.travelAgencyName FROM @@ -480,10 +500,13 @@ public class TheRapyRecuperationLineFragmentController { Cnd cnd = Cnd.NEW(); cnd.andEX("en.takePartInBaseManagementId", "=", baseManagementId); cnd.and("en.takePartInBaseManagementId", "is not", null); + cnd.and("en.isNormal", "=", true); + cnd.and("en.stateId", "=", TheRapyRecuperationState.PASS); + cnd.and("en.hasLocked", "=", true); cnd.andEX("REPLACE(REPLACE(SUBSTRING_INDEX(specificTime, '-', 1), '月', '-'), '日', '')", ">=", playStart); cnd.andEX("REPLACE(REPLACE(SUBSTRING_INDEX(specificTime, '-', 1), '月', '-'), '日', '')", "<=", playEnd); - cnd.groupBy("en.takePartInBaseManagementId,en.playDay,en.specificTime"); - cnd.having(Cnd.where("userCount", ">=", fragmentCount)); + cnd.groupBy("en.takePartInBaseManagementId,en.playDay,en.specificTime,bm.travelAgencyId"); + addFragmentGroupCountHaving(cnd, fragmentCount, rapyConfig.getUnlimitedGroupTravelAgencyIds()); cnd.desc("en.specificTime"); sql.setCondition(cnd); List listMap = enrollService.listMap(sql); @@ -545,4 +568,25 @@ public class TheRapyRecuperationLineFragmentController { zipOutputStream.flush(); zipOutputStream.close(); } + + /** + * 分段式批量成团和成团表导出按每个酒店所属旅行社分别判断,配置豁免的旅行社不受最少成团人数限制。 + */ + private void addFragmentGroupCountHaving(Cnd cnd, Integer fragmentCount, List unlimitedTravelAgencyIds) { + Cnd having = Cnd.where("userCount", ">=", fragmentCount); + if (Lang.isNotEmpty(unlimitedTravelAgencyIds)) { + having.or("travelAgencyId", "in", unlimitedTravelAgencyIds); + } + cnd.having(having); + } + + /** + * 分段式成团配置按年度维护,批量成团和成团表导出只允许单年度操作,避免跨年度混用配置。 + */ + private Result checkSingleYear(Integer startYear, Integer endYear) { + if (startYear != null && endYear != null && !startYear.equals(endYear)) { + return Result.error("请只选择一个年度操作"); + } + return null; + } } diff --git a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/statistics/TheRapyRecuperationLineStatisticsController.java b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/statistics/TheRapyRecuperationLineStatisticsController.java index 115a7d3..28117e5 100644 --- a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/statistics/TheRapyRecuperationLineStatisticsController.java +++ b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/statistics/TheRapyRecuperationLineStatisticsController.java @@ -227,6 +227,24 @@ public class TheRapyRecuperationLineStatisticsController { return Result.error("报名还未结束"); } + //找出报名人员 + List enrolls = dao.query( + TheRapyRecuperationEnroll.class, + Cnd.where("takePartInLineId", "=", id) + .and("isNormal", "=", true).and("year(signingUptime)", "=", line.getYear()) + .and("stateId", "not in", Lang.array(TheRapyRecuperationState.UNITFAIL, TheRapyRecuperationState.LINEUNITFAIL, TheRapyRecuperationState.SCHOOLFAIL))); + + if (Lang.isEmpty(enrolls)) { + return Result.error("当前没有可成团报名人员"); + } + + // 年度配置中的旅行社豁免名单只跳过最少成团人数限制,不改变报名人数统计和锁定逻辑。 + boolean unlimitedMinimumGroupSize = configService.isMinimumGroupSizeUnlimited(line.getYear(), line.getTravelAgencyId()); + Integer count = line.getGroupType() == 1 ? config.getConventionalCount() : config.getBoutiqueCount(); + if(!unlimitedMinimumGroupSize && enrolls.size() < count) { + return Result.error("报名人数为%s人,未达到%s人的成团条件".formatted(enrolls.size(), count)); + } + unionSelect.setGroupSuccess(true); unionSelect.setGroupTime(DateUtil.now()); /*switch (unionSelect.getSignUpMode()) { @@ -237,18 +255,6 @@ public class TheRapyRecuperationLineStatisticsController { unionSelect.setAuditState(7720); dao.update(unionSelect); - //找出报名人员 - List enrolls = dao.query( - TheRapyRecuperationEnroll.class, - Cnd.where("takePartInLineId", "=", id) - .and("isNormal", "=", true).and("year(signingUptime)", "=", line.getYear()) - .and("stateId", "not in", Lang.array(TheRapyRecuperationState.UNITFAIL, TheRapyRecuperationState.LINEUNITFAIL, TheRapyRecuperationState.SCHOOLFAIL))); - - Integer count = line.getGroupType() == 1 ? config.getConventionalCount() : config.getBoutiqueCount(); - if(enrolls.size() < count) { - return Result.error("报名人数为%s人,未达到%s人的成团条件".formatted(enrolls.size(), count)); - } - enrolls.forEach(item -> { item.setHasLocked(true); }); diff --git a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/theRapyConfig/TheRapyRecuperationConfigController.java b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/theRapyConfig/TheRapyRecuperationConfigController.java index cdccb65..1128799 100644 --- a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/theRapyConfig/TheRapyRecuperationConfigController.java +++ b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/theRapyConfig/TheRapyRecuperationConfigController.java @@ -47,6 +47,13 @@ public class TheRapyRecuperationConfigController { return configService.findByYear(year); } + @At + @ViReturn + @RequiresAuthentication + public Object listEnabledTravelAgencies(Integer year) { + return configService.listEnabledTravelAgenciesByYear(year); + } + @At @ViReturn @RequiresAuthentication diff --git a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/model/TheRapyRecuperationConfig.java b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/model/TheRapyRecuperationConfig.java index 654f6e6..19d871d 100644 --- a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/model/TheRapyRecuperationConfig.java +++ b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/model/TheRapyRecuperationConfig.java @@ -140,6 +140,11 @@ public class TheRapyRecuperationConfig { @Comment("分段式最少成团人数") private Integer fragmentCount; + @Column + @ColDefine(type = ColType.MYSQL_JSON) + @Comment("不限制最少成团人数的旅行社") + private List unlimitedGroupTravelAgencyIds; + @Column @ColDefine(type = ColType.DATETIME) @Comment("精品团创建线路开始日期") diff --git a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/TheRapyRecuperationConfigService.java b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/TheRapyRecuperationConfigService.java index d621640..01bf577 100644 --- a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/TheRapyRecuperationConfigService.java +++ b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/TheRapyRecuperationConfigService.java @@ -2,8 +2,11 @@ package io.v.nutz.zhgh.therapyRecuperation.service; import io.v.nutz.base.service.ViService; import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationConfig; +import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationTravelAgency; import org.nutz.lang.util.NutMap; +import java.util.List; + /** * 疗休养基础配置服务。 */ @@ -33,6 +36,23 @@ public interface TheRapyRecuperationConfigService extends ViService listEnabledTravelAgenciesByYear(Integer configYear); + + /** + * 判断指定年度下某旅行社是否不受最少成团人数限制。 + * + * @param configYear 配置年度 + * @param travelAgencyId 旅行社id + * @return 是否不限制最少成团人数 + */ + boolean isMinimumGroupSizeUnlimited(Integer configYear, String travelAgencyId); + /** * 将上一年度配置复制为当前年度配置。 * diff --git a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/impl/TheRapyRecuperationConfigServiceImpl.java b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/impl/TheRapyRecuperationConfigServiceImpl.java index 61c8579..fa9ae34 100644 --- a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/impl/TheRapyRecuperationConfigServiceImpl.java +++ b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/impl/TheRapyRecuperationConfigServiceImpl.java @@ -6,6 +6,7 @@ import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationBaseManagemen import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationConfig; import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationLine; import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationLot; +import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationTravelAgency; import io.v.nutz.zhgh.therapyRecuperation.service.TheRapyRecuperationConfigService; import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Chain; @@ -118,6 +119,24 @@ public class TheRapyRecuperationConfigServiceImpl extends ViServiceImpl listEnabledTravelAgenciesByYear(Integer configYear) { + return dao().query(TheRapyRecuperationTravelAgency.class, + Cnd.where("year", "=", getConfigYear(configYear)) + .and("isDisabled", "=", false) + .asc("serialNumber")); + } + + @Override + public boolean isMinimumGroupSizeUnlimited(Integer configYear, String travelAgencyId) { + if (Strings.isBlank(travelAgencyId)) { + return false; + } + TheRapyRecuperationConfig config = requireByYear(configYear); + List travelAgencyIds = config.getUnlimitedGroupTravelAgencyIds(); + return Lang.isNotEmpty(travelAgencyIds) && travelAgencyIds.contains(travelAgencyId); + } + /** * 每次提交年度配置后,仅保留当前配置为最新配置。 * @@ -144,6 +163,7 @@ public class TheRapyRecuperationConfigServiceImpl extends ViServiceImpl()); + } + /** * 沿用上一年度配置时,将所有年度相关时间改为当前配置年度,月日和时分秒保持不变。 * diff --git a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/impl/TheRapyRecuperationEnrollServiceImpl.java b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/impl/TheRapyRecuperationEnrollServiceImpl.java index cb5eb9f..7de5855 100644 --- a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/impl/TheRapyRecuperationEnrollServiceImpl.java +++ b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/impl/TheRapyRecuperationEnrollServiceImpl.java @@ -185,7 +185,9 @@ public class TheRapyRecuperationEnrollServiceImpl extends ViServiceImpl=", (DateUtil.thisYear() - 3) + "-01-01"); + // 省外间隔按参加年度判断,参加时间为空的历史记录再按报名年度兜底。 + threeYearOutCnd.andEX("IFNULL(YEAR(r.takePartInTime), YEAR(r.signingUptime))", ">=", outsideLimitYear); + threeYearOutCnd.andEX("IFNULL(YEAR(r.takePartInTime), YEAR(r.signingUptime))", "<=", targetYear); threeYearOutCnd.and("regionalNature", "=", "省外"); threeYearOutCnd.and("isTakePartIn", "=", true); threeYearOutSql.setCondition(threeYearOutCnd); @@ -679,7 +684,7 @@ public class TheRapyRecuperationEnrollServiceImpl extends ViServiceImpl lineList = dao().query(TheRapyRecuperationLine.class, Cnd.where("year", "=", lineInfo.getYear()) diff --git a/src/main/resources/views/mobile/therapyRecuperation/index.html b/src/main/resources/views/mobile/therapyRecuperation/index.html index d596a6a..7a25e2f 100644 --- a/src/main/resources/views/mobile/therapyRecuperation/index.html +++ b/src/main/resources/views/mobile/therapyRecuperation/index.html @@ -40,6 +40,25 @@ layout("/mobile/platform.html"){ bottom: 30px; } + .notice-file-list { + margin-top: 8px; + } + + .notice-file-list .van-cell { + padding: 10px 0; + } + + .notice-file-name { + color: #1867b0; + line-height: 22px; + word-break: break-all; + } + + .notice-file-download { + color: #1867b0; + line-height: 24px; + } +
@@ -71,8 +90,20 @@ layout("/mobile/platform.html"){
-
-
+
+ + + + +
+
暂无服务须知文件
@@ -84,7 +115,6 @@ layout("/mobile/platform.html"){
diff --git a/src/main/resources/views/platform/theRapyRecuperation/statistics/fragment.html b/src/main/resources/views/platform/theRapyRecuperation/statistics/fragment.html index 83c5577..97a0e73 100644 --- a/src/main/resources/views/platform/theRapyRecuperation/statistics/fragment.html +++ b/src/main/resources/views/platform/theRapyRecuperation/statistics/fragment.html @@ -276,6 +276,10 @@ layout("/layouts/platform.html"){ }, methods: { oneKeyGroupSuccess(type) { + if (this.pageForm.startYear !== this.pageForm.endYear) { + this.$message.warning('请只选择一个年度操作') + return + } this.$confirm("您确定要一键成团吗?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", @@ -286,6 +290,8 @@ layout("/layouts/platform.html"){ baseId: this.pageForm.baseManagementId, playStart: this.pageForm.playStart, playEnd: this.pageForm.playEnd, + startYear: this.pageForm.startYear, + endYear: this.pageForm.endYear, type: type, }).then((res) => { if (res.code === 0) { @@ -404,6 +410,10 @@ layout("/layouts/platform.html"){ ) }, exportGroup() { + if (this.pageForm.startYear !== this.pageForm.endYear) { + this.$message.warning('请只选择一个年度操作') + return + } window.open("/platform/theRapyRecuperation/fragment/exportGroup?" + "startYear=" + this.pageForm.startYear + "&endYear=" + this.pageForm.endYear + diff --git a/src/main/resources/views/platform/theRapyRecuperation/theRapyConfig/TheRapyConfig.html b/src/main/resources/views/platform/theRapyRecuperation/theRapyConfig/TheRapyConfig.html index 50020af..ee95f27 100644 --- a/src/main/resources/views/platform/theRapyRecuperation/theRapyConfig/TheRapyConfig.html +++ b/src/main/resources/views/platform/theRapyRecuperation/theRapyConfig/TheRapyConfig.html @@ -99,6 +99,20 @@ layout("/layouts/platform.html"){ v-model.number="formData.fragmentCount"> + + + + + + - - + + @@ -344,6 +361,7 @@ layout("/layouts/platform.html"){ currentYear: currentYear, yearOptions: yearOptions, activityGroupList: [], + travelAgencyList: [], subLoading: false, copyLoading: false, formData: { @@ -361,9 +379,11 @@ layout("/layouts/platform.html"){ modifyNumber: null, allLineSignUpNumber: null, notice: null, + files: [], provinceStartYear: null, bedInfo: true, familyInfo: 1, + unlimitedGroupTravelAgencyIds: [], }, formRules: { configName: [{required: true, message: '请填写配置名称', trigger: ['blur', 'change']}], @@ -377,7 +397,7 @@ layout("/layouts/platform.html"){ modifyNumber: [{required: true, message: '请填写可以修改几次', trigger: ['blur', 'change']}], outsideQuotaProportion: [{required: true, message: '请填写比例', trigger: ['blur', 'change']}], allLineSignUpNumber: [{required: true, message: '请输入全批次最多报名人数', trigger: ['blur', 'change']}], - notice: [{required: true, message: '请输入详细内容', trigger: ['change', 'blur']}], + files: [{type: 'array', required: true, message: '请上传服务须知文件', trigger: ['change', 'blur']}], provinceStartYear: [{required: true, message: '请输入省内起始年份', trigger: ['change', 'blur']}], bedInfo: [{required: true, message: '请选择床位信息', trigger: ['change', 'blur']}], familyInfo: [{required: true, message: '请选择家属信息', trigger: ['change', 'blur']}], @@ -418,9 +438,11 @@ layout("/layouts/platform.html"){ modifyNumber: null, allLineSignUpNumber: null, notice: null, + files: [], provinceStartYear: null, bedInfo: true, familyInfo: 1, + unlimitedGroupTravelAgencyIds: [], } }, async changeYear() { @@ -526,6 +548,7 @@ layout("/layouts/platform.html"){ if (this.formData.lots && this.formData.lots.length > 0) { cloneData.lots = JSON.stringify(this.formData.lots) } + cloneData.unlimitedGroupTravelAgencyIds = JSON.stringify(cloneData.unlimitedGroupTravelAgencyIds || []) cloneData.outsideQuotaProportion = cloneData.outsideQuotaProportion / 100 const resp = await $.post(loc() + "/operation", cloneData) if (resp.code === 0){ @@ -542,6 +565,7 @@ layout("/layouts/platform.html"){ }, async findOne() { await this.getActivityGroup() + await this.getTravelAgencies() const {data} = await $.get(loc() + "/findOne", {year: this.currentYear}) const formData = data || this.getDefaultFormData(this.currentYear) this.$set(formData, 'configYear', this.currentYear) @@ -564,12 +588,22 @@ layout("/layouts/platform.html"){ if (!formData.lots) { this.$set(formData, 'lots', []) } + if (!formData.unlimitedGroupTravelAgencyIds) { + this.$set(formData, 'unlimitedGroupTravelAgencyIds', []) + } + if (!formData.files) { + this.$set(formData, 'files', []) + } this.$set(this, 'formData', formData) }, async getActivityGroup() { const {data} = await $.get('/platform/activity/basic/scope/getActivityUserScopeGroup') this.activityGroupList = data }, + async getTravelAgencies() { + const {data} = await $.get(loc() + "/listEnabledTravelAgencies", {year: this.currentYear}) + this.$set(this, 'travelAgencyList', data || []) + }, async closeDialog() { this.tableScopeIndex = ''; this.tableScopeId = '';