commit
This commit is contained in:
+52
-8
@@ -244,7 +244,13 @@ public class TheRapyRecuperationLineFragmentController {
|
|||||||
.and("isNormal", "=", true).and("year(signingUptime)", "=", management.getYear())
|
.and("isNormal", "=", true).and("year(signingUptime)", "=", management.getYear())
|
||||||
.and("stateId", "not in", Lang.array(TheRapyRecuperationState.UNITFAIL, TheRapyRecuperationState.LINEUNITFAIL, TheRapyRecuperationState.SCHOOLFAIL)));
|
.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()));
|
return Result.error("报名人数为%s人,未达到%s人的成团条件".formatted(enrolls.size(), config.getFragmentCount()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,9 +325,14 @@ public class TheRapyRecuperationLineFragmentController {
|
|||||||
@ViReturn
|
@ViReturn
|
||||||
@Aop(TransAop.READ_COMMITTED)
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
@RequiresPermissions("theRapyRecuperation.fragment")
|
@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);
|
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();
|
Integer fragmentCount = config.getFragmentCount();
|
||||||
|
|
||||||
if (DateUtil.compare(new Date(), config.getFragmentEndTime()) < 0) {
|
if (DateUtil.compare(new Date(), config.getFragmentEndTime()) < 0) {
|
||||||
@@ -335,6 +346,7 @@ public class TheRapyRecuperationLineFragmentController {
|
|||||||
en.takePartInBaseManagementId,
|
en.takePartInBaseManagementId,
|
||||||
en.specificTime,
|
en.specificTime,
|
||||||
en.playDay,
|
en.playDay,
|
||||||
|
bm.travelAgencyId,
|
||||||
count( specificTime ) as userCount
|
count( specificTime ) as userCount
|
||||||
FROM
|
FROM
|
||||||
the_rapy_recuperation_enroll en
|
the_rapy_recuperation_enroll en
|
||||||
@@ -349,8 +361,8 @@ public class TheRapyRecuperationLineFragmentController {
|
|||||||
cnd.andEX("REPLACE(REPLACE(SUBSTRING_INDEX(specificTime, '-', 1), '月', '-'), '日', '')", "<=", playEnd);
|
cnd.andEX("REPLACE(REPLACE(SUBSTRING_INDEX(specificTime, '-', 1), '月', '-'), '日', '')", "<=", playEnd);
|
||||||
cnd.andEX("bm.travelAgencyId", "=", travelAgencyId);
|
cnd.andEX("bm.travelAgencyId", "=", travelAgencyId);
|
||||||
cnd.andEX("en.takePartInBaseManagementId", "=", baseId);
|
cnd.andEX("en.takePartInBaseManagementId", "=", baseId);
|
||||||
cnd.groupBy("en.takePartInBaseManagementId,en.playDay,en.specificTime");
|
cnd.groupBy("en.takePartInBaseManagementId,en.playDay,en.specificTime,bm.travelAgencyId");
|
||||||
cnd.having(Cnd.where("userCount", ">=", fragmentCount));
|
addFragmentGroupCountHaving(cnd, fragmentCount, config.getUnlimitedGroupTravelAgencyIds());
|
||||||
sql.setCondition(cnd);
|
sql.setCondition(cnd);
|
||||||
List<NutMap> enrolls = enrollService.listMap(sql);
|
List<NutMap> enrolls = enrollService.listMap(sql);
|
||||||
for (NutMap enroll : enrolls) {
|
for (NutMap enroll : enrolls) {
|
||||||
@@ -454,13 +466,20 @@ public class TheRapyRecuperationLineFragmentController {
|
|||||||
@Param(value = "playStart", required = false) String playStart,
|
@Param(value = "playStart", required = false) String playStart,
|
||||||
@Param(value = "playEnd", required = false) String playEnd,
|
@Param(value = "playEnd", required = false) String playEnd,
|
||||||
HttpServletResponse response) throws IOException {
|
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.setContentType("application/octet-stream");
|
||||||
response.setHeader("content-disposition", "attachment;filename="
|
response.setHeader("content-disposition", "attachment;filename="
|
||||||
+ URLEncoder.encode("分段疗休养成团表.zip", StandardCharsets.UTF_8));
|
+ URLEncoder.encode("分段疗休养成团表.zip", StandardCharsets.UTF_8));
|
||||||
|
|
||||||
TheRapyRecuperationBaseManagement currentManagement = StrUtil.isBlank(baseManagementId) ? null : dao.fetch(TheRapyRecuperationBaseManagement.class, baseManagementId);
|
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();
|
Integer fragmentCount = rapyConfig.getFragmentCount();
|
||||||
Sql sql = Sqls.create("""
|
Sql sql = Sqls.create("""
|
||||||
SELECT
|
SELECT
|
||||||
@@ -469,6 +488,7 @@ public class TheRapyRecuperationLineFragmentController {
|
|||||||
en.playDay,
|
en.playDay,
|
||||||
count( specificTime ) as userCount,
|
count( specificTime ) as userCount,
|
||||||
en.hasLocked,
|
en.hasLocked,
|
||||||
|
bm.travelAgencyId,
|
||||||
bm.baseName,
|
bm.baseName,
|
||||||
ta.travelAgencyName
|
ta.travelAgencyName
|
||||||
FROM
|
FROM
|
||||||
@@ -480,10 +500,13 @@ public class TheRapyRecuperationLineFragmentController {
|
|||||||
Cnd cnd = Cnd.NEW();
|
Cnd cnd = Cnd.NEW();
|
||||||
cnd.andEX("en.takePartInBaseManagementId", "=", baseManagementId);
|
cnd.andEX("en.takePartInBaseManagementId", "=", baseManagementId);
|
||||||
cnd.and("en.takePartInBaseManagementId", "is not", null);
|
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), '月', '-'), '日', '')", ">=", playStart);
|
||||||
cnd.andEX("REPLACE(REPLACE(SUBSTRING_INDEX(specificTime, '-', 1), '月', '-'), '日', '')", "<=", playEnd);
|
cnd.andEX("REPLACE(REPLACE(SUBSTRING_INDEX(specificTime, '-', 1), '月', '-'), '日', '')", "<=", playEnd);
|
||||||
cnd.groupBy("en.takePartInBaseManagementId,en.playDay,en.specificTime");
|
cnd.groupBy("en.takePartInBaseManagementId,en.playDay,en.specificTime,bm.travelAgencyId");
|
||||||
cnd.having(Cnd.where("userCount", ">=", fragmentCount));
|
addFragmentGroupCountHaving(cnd, fragmentCount, rapyConfig.getUnlimitedGroupTravelAgencyIds());
|
||||||
cnd.desc("en.specificTime");
|
cnd.desc("en.specificTime");
|
||||||
sql.setCondition(cnd);
|
sql.setCondition(cnd);
|
||||||
List<NutMap> listMap = enrollService.listMap(sql);
|
List<NutMap> listMap = enrollService.listMap(sql);
|
||||||
@@ -545,4 +568,25 @@ public class TheRapyRecuperationLineFragmentController {
|
|||||||
zipOutputStream.flush();
|
zipOutputStream.flush();
|
||||||
zipOutputStream.close();
|
zipOutputStream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分段式批量成团和成团表导出按每个酒店所属旅行社分别判断,配置豁免的旅行社不受最少成团人数限制。
|
||||||
|
*/
|
||||||
|
private void addFragmentGroupCountHaving(Cnd cnd, Integer fragmentCount, List<String> 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-12
@@ -227,6 +227,24 @@ public class TheRapyRecuperationLineStatisticsController {
|
|||||||
return Result.error("报名还未结束");
|
return Result.error("报名还未结束");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//找出报名人员
|
||||||
|
List<TheRapyRecuperationEnroll> 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.setGroupSuccess(true);
|
||||||
unionSelect.setGroupTime(DateUtil.now());
|
unionSelect.setGroupTime(DateUtil.now());
|
||||||
/*switch (unionSelect.getSignUpMode()) {
|
/*switch (unionSelect.getSignUpMode()) {
|
||||||
@@ -237,18 +255,6 @@ public class TheRapyRecuperationLineStatisticsController {
|
|||||||
unionSelect.setAuditState(7720);
|
unionSelect.setAuditState(7720);
|
||||||
dao.update(unionSelect);
|
dao.update(unionSelect);
|
||||||
|
|
||||||
//找出报名人员
|
|
||||||
List<TheRapyRecuperationEnroll> 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 -> {
|
enrolls.forEach(item -> {
|
||||||
item.setHasLocked(true);
|
item.setHasLocked(true);
|
||||||
});
|
});
|
||||||
|
|||||||
+7
@@ -47,6 +47,13 @@ public class TheRapyRecuperationConfigController {
|
|||||||
return configService.findByYear(year);
|
return configService.findByYear(year);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@At
|
||||||
|
@ViReturn
|
||||||
|
@RequiresAuthentication
|
||||||
|
public Object listEnabledTravelAgencies(Integer year) {
|
||||||
|
return configService.listEnabledTravelAgenciesByYear(year);
|
||||||
|
}
|
||||||
|
|
||||||
@At
|
@At
|
||||||
@ViReturn
|
@ViReturn
|
||||||
@RequiresAuthentication
|
@RequiresAuthentication
|
||||||
|
|||||||
@@ -140,6 +140,11 @@ public class TheRapyRecuperationConfig {
|
|||||||
@Comment("分段式最少成团人数")
|
@Comment("分段式最少成团人数")
|
||||||
private Integer fragmentCount;
|
private Integer fragmentCount;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@ColDefine(type = ColType.MYSQL_JSON)
|
||||||
|
@Comment("不限制最少成团人数的旅行社")
|
||||||
|
private List<String> unlimitedGroupTravelAgencyIds;
|
||||||
|
|
||||||
@Column
|
@Column
|
||||||
@ColDefine(type = ColType.DATETIME)
|
@ColDefine(type = ColType.DATETIME)
|
||||||
@Comment("精品团创建线路开始日期")
|
@Comment("精品团创建线路开始日期")
|
||||||
|
|||||||
+20
@@ -2,8 +2,11 @@ package io.v.nutz.zhgh.therapyRecuperation.service;
|
|||||||
|
|
||||||
import io.v.nutz.base.service.ViService;
|
import io.v.nutz.base.service.ViService;
|
||||||
import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationConfig;
|
import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationConfig;
|
||||||
|
import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationTravelAgency;
|
||||||
import org.nutz.lang.util.NutMap;
|
import org.nutz.lang.util.NutMap;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 疗休养基础配置服务。
|
* 疗休养基础配置服务。
|
||||||
*/
|
*/
|
||||||
@@ -33,6 +36,23 @@ public interface TheRapyRecuperationConfigService extends ViService<TheRapyRecup
|
|||||||
*/
|
*/
|
||||||
void saveConfig(TheRapyRecuperationConfig config, String[] lotDeleteList);
|
void saveConfig(TheRapyRecuperationConfig config, String[] lotDeleteList);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定年度启用中的旅行社,供年度配置页选择不限制最少成团人数的旅行社。
|
||||||
|
*
|
||||||
|
* @param configYear 配置年度
|
||||||
|
* @return 启用中的旅行社列表
|
||||||
|
*/
|
||||||
|
List<TheRapyRecuperationTravelAgency> listEnabledTravelAgenciesByYear(Integer configYear);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断指定年度下某旅行社是否不受最少成团人数限制。
|
||||||
|
*
|
||||||
|
* @param configYear 配置年度
|
||||||
|
* @param travelAgencyId 旅行社id
|
||||||
|
* @return 是否不限制最少成团人数
|
||||||
|
*/
|
||||||
|
boolean isMinimumGroupSizeUnlimited(Integer configYear, String travelAgencyId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将上一年度配置复制为当前年度配置。
|
* 将上一年度配置复制为当前年度配置。
|
||||||
*
|
*
|
||||||
|
|||||||
+28
@@ -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.TheRapyRecuperationConfig;
|
||||||
import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationLine;
|
import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationLine;
|
||||||
import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationLot;
|
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 io.v.nutz.zhgh.therapyRecuperation.service.TheRapyRecuperationConfigService;
|
||||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||||
import org.nutz.dao.Chain;
|
import org.nutz.dao.Chain;
|
||||||
@@ -118,6 +119,24 @@ public class TheRapyRecuperationConfigServiceImpl extends ViServiceImpl<TheRapyR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TheRapyRecuperationTravelAgency> 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<String> travelAgencyIds = config.getUnlimitedGroupTravelAgencyIds();
|
||||||
|
return Lang.isNotEmpty(travelAgencyIds) && travelAgencyIds.contains(travelAgencyId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 每次提交年度配置后,仅保留当前配置为最新配置。
|
* 每次提交年度配置后,仅保留当前配置为最新配置。
|
||||||
*
|
*
|
||||||
@@ -144,6 +163,7 @@ public class TheRapyRecuperationConfigServiceImpl extends ViServiceImpl<TheRapyR
|
|||||||
lastConfig.setConfigYear(year);
|
lastConfig.setConfigYear(year);
|
||||||
lastConfig.setLatestConfig(true);
|
lastConfig.setLatestConfig(true);
|
||||||
resetConfigTimeYear(lastConfig, year);
|
resetConfigTimeYear(lastConfig, year);
|
||||||
|
resetUnlimitedGroupTravelAgencyIds(lastConfig);
|
||||||
resetLatestConfig(null);
|
resetLatestConfig(null);
|
||||||
if (currentConfig == null) {
|
if (currentConfig == null) {
|
||||||
copyLotsToNewConfig(lastConfig);
|
copyLotsToNewConfig(lastConfig);
|
||||||
@@ -162,6 +182,14 @@ public class TheRapyRecuperationConfigServiceImpl extends ViServiceImpl<TheRapyR
|
|||||||
return findByYear(year);
|
return findByYear(year);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 沿用上一年度配置时,清空旅行社豁免名单,避免新年度继续使用旧年度的特殊成团规则。
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private void resetUnlimitedGroupTravelAgencyIds(TheRapyRecuperationConfig config) {
|
||||||
|
config.setUnlimitedGroupTravelAgencyIds(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 沿用上一年度配置时,将所有年度相关时间改为当前配置年度,月日和时分秒保持不变。
|
* 沿用上一年度配置时,将所有年度相关时间改为当前配置年度,月日和时分秒保持不变。
|
||||||
*
|
*
|
||||||
|
|||||||
+13
-8
@@ -185,7 +185,9 @@ public class TheRapyRecuperationEnrollServiceImpl extends ViServiceImpl<TheRapyR
|
|||||||
taSql.setCondition(cnd);
|
taSql.setCondition(cnd);
|
||||||
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), taSql);
|
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), taSql);
|
||||||
} else if (TheRapyRecuperationType.provinceInHotel.getValue() == trrt) {
|
} else if (TheRapyRecuperationType.provinceInHotel.getValue() == trrt) {
|
||||||
cnd.andEX("b.year", "=", year != null ? year : DateUtil.thisYear());
|
// 手机端分段疗休养报名固定展示本年度数据,避免前端传入历史年度时查出往年酒店。
|
||||||
|
Integer currentYear = DateUtil.thisYear();
|
||||||
|
cnd.andEX("b.year", "=", currentYear);
|
||||||
cnd.and("b.isDisabled", "=", false);
|
cnd.and("b.isDisabled", "=", false);
|
||||||
if (StrUtil.isNotBlank(routeName)) {
|
if (StrUtil.isNotBlank(routeName)) {
|
||||||
cnd.and(Cnd.likeEX("b.baseName", routeName));
|
cnd.and(Cnd.likeEX("b.baseName", routeName));
|
||||||
@@ -208,14 +210,13 @@ public class TheRapyRecuperationEnrollServiceImpl extends ViServiceImpl<TheRapyR
|
|||||||
cnd.and(Cnd.likeEX("b.baseName", baseName));
|
cnd.and(Cnd.likeEX("b.baseName", baseName));
|
||||||
}
|
}
|
||||||
cnd.andEX("b.travelAgencyId", "=", travelId);
|
cnd.andEX("b.travelAgencyId", "=", travelId);
|
||||||
if (year != null) {
|
taSql.setVar("yearCnd", "AND YEAR(signingUptime) = '%s' ".formatted(currentYear));
|
||||||
taSql.setVar("yearCnd", "AND YEAR(signingUptime) = '%s' ".formatted(year));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化基础查询条件
|
// 初始化基础查询条件
|
||||||
Cnd enrollCnd = Cnd.where("takePartInBaseManagementId", "is not", null)
|
Cnd enrollCnd = Cnd.where("takePartInBaseManagementId", "is not", null)
|
||||||
.and("isNormal", "=", true)
|
.and("isNormal", "=", true)
|
||||||
.and("stateId", "=", 2750);
|
.and("stateId", "=", 2750)
|
||||||
|
.and("YEAR(signingUptime)", "=", currentYear);
|
||||||
if (StrUtil.isNotBlank(searchKeyword)) {
|
if (StrUtil.isNotBlank(searchKeyword)) {
|
||||||
enrollCnd.and(Cnd.likeEX("userName", searchKeyword));
|
enrollCnd.and(Cnd.likeEX("userName", searchKeyword));
|
||||||
}
|
}
|
||||||
@@ -662,14 +663,18 @@ public class TheRapyRecuperationEnrollServiceImpl extends ViServiceImpl<TheRapyR
|
|||||||
|
|
||||||
Cnd commonCnd = Cnd.NEW();
|
Cnd commonCnd = Cnd.NEW();
|
||||||
commonCnd.and("r.takePartInLineId", "is not", null);
|
commonCnd.and("r.takePartInLineId", "is not", null);
|
||||||
commonCnd.and("loginName", "=", ShiroUtil.getPrincipalProperty("loginname"));
|
commonCnd.and("loginName", "=", loginName);
|
||||||
commonCnd.and("r.isNormal", "=", true);
|
commonCnd.and("r.isNormal", "=", true);
|
||||||
commonCnd.and("r.stateId", "not in", Lang.array(TheRapyRecuperationState.UNITFAIL, TheRapyRecuperationState.LINEUNITFAIL, TheRapyRecuperationState.SCHOOLFAIL));
|
commonCnd.and("r.stateId", "not in", Lang.array(TheRapyRecuperationState.UNITFAIL, TheRapyRecuperationState.LINEUNITFAIL, TheRapyRecuperationState.SCHOOLFAIL));
|
||||||
commonCnd.and("signingUptime", "<=", DateUtil.now());
|
commonCnd.and("signingUptime", "<=", DateUtil.now());
|
||||||
|
|
||||||
//三年参加省外
|
//三年参加省外
|
||||||
|
Integer targetYear = lineInfo.getYear() == null ? DateUtil.thisYear() : lineInfo.getYear();
|
||||||
|
Integer outsideLimitYear = targetYear - (outsideNumber == null ? 3 : outsideNumber) + 1;
|
||||||
Cnd threeYearOutCnd = commonCnd.clone();
|
Cnd threeYearOutCnd = commonCnd.clone();
|
||||||
threeYearOutCnd.and("signingUptime", ">=", (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("regionalNature", "=", "省外");
|
||||||
threeYearOutCnd.and("isTakePartIn", "=", true);
|
threeYearOutCnd.and("isTakePartIn", "=", true);
|
||||||
threeYearOutSql.setCondition(threeYearOutCnd);
|
threeYearOutSql.setCondition(threeYearOutCnd);
|
||||||
@@ -679,7 +684,7 @@ public class TheRapyRecuperationEnrollServiceImpl extends ViServiceImpl<TheRapyR
|
|||||||
if (trrt.getValue() == TheRapyRecuperationType.provinceOutLine.getValue()) {
|
if (trrt.getValue() == TheRapyRecuperationType.provinceOutLine.getValue()) {
|
||||||
//三年内有没有参加过省外
|
//三年内有没有参加过省外
|
||||||
if (Lang.isNotEmpty(threeYearOutMapList)) {
|
if (Lang.isNotEmpty(threeYearOutMapList)) {
|
||||||
return Map.of(false, "近三年内您已参加过省外线路,不能再次报名");
|
return Map.of(false, "近" + (outsideNumber == null ? 3 : outsideNumber) + "年内您已参加过省外线路,不能再次报名");
|
||||||
}
|
}
|
||||||
//获取今年的所有线路
|
//获取今年的所有线路
|
||||||
List<TheRapyRecuperationLine> lineList = dao().query(TheRapyRecuperationLine.class, Cnd.where("year", "=", lineInfo.getYear())
|
List<TheRapyRecuperationLine> lineList = dao().query(TheRapyRecuperationLine.class, Cnd.where("year", "=", lineInfo.getYear())
|
||||||
|
|||||||
@@ -40,6 +40,25 @@ layout("/mobile/platform.html"){
|
|||||||
bottom: 30px;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div id="app" v-cloak>
|
<div id="app" v-cloak>
|
||||||
@@ -71,8 +90,20 @@ layout("/mobile/platform.html"){
|
|||||||
<!--疗休养描述-->
|
<!--疗休养描述-->
|
||||||
<div class="van-doc-card" style=" height: calc(100vh - 430px)">
|
<div class="van-doc-card" style=" height: calc(100vh - 430px)">
|
||||||
<van-image width="190" height="43" src="/assets/mobile/img/therapyRecuperation/title.png"></van-image>
|
<van-image width="190" height="43" src="/assets/mobile/img/therapyRecuperation/title.png"></van-image>
|
||||||
<div v-if="isPdf" class="pre_text" v-html="configData.notice"></div>
|
<div v-if="serviceNoticeFiles.length > 0" class="notice-file-list">
|
||||||
<div v-else id="demo" class="pdf_div"></div>
|
<van-cell v-for="item in serviceNoticeFiles"
|
||||||
|
:key="item.id"
|
||||||
|
clickable
|
||||||
|
@click="downloadNoticeFile(item)">
|
||||||
|
<template #title>
|
||||||
|
<div class="notice-file-name">{{item.filename}}</div>
|
||||||
|
</template>
|
||||||
|
<template #right-icon>
|
||||||
|
<van-icon name="down" class="notice-file-download"></van-icon>
|
||||||
|
</template>
|
||||||
|
</van-cell>
|
||||||
|
</div>
|
||||||
|
<div v-else class="pre_text">暂无服务须知文件</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -84,7 +115,6 @@ layout("/mobile/platform.html"){
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
let pdfh5 = null
|
|
||||||
const vue = new Vue({
|
const vue = new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
mixins: [mobileMixins],
|
mixins: [mobileMixins],
|
||||||
@@ -93,7 +123,11 @@ layout("/mobile/platform.html"){
|
|||||||
tarBarActive: 0,
|
tarBarActive: 0,
|
||||||
chooseButton: [],
|
chooseButton: [],
|
||||||
configData: {},
|
configData: {},
|
||||||
isPdf: false,
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
serviceNoticeFiles() {
|
||||||
|
return this.configData.files || []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -112,47 +146,19 @@ layout("/mobile/platform.html"){
|
|||||||
},
|
},
|
||||||
async getConfigData() {
|
async getConfigData() {
|
||||||
const resp = await $.get('/platform/theRapyRecuperation/TheRapyConfig/findOne')
|
const resp = await $.get('/platform/theRapyRecuperation/TheRapyConfig/findOne')
|
||||||
this.configData = resp.data
|
this.$set(this, 'configData', resp.data || {})
|
||||||
},
|
},
|
||||||
initPdf() {
|
downloadNoticeFile(file) {
|
||||||
try {
|
if (!file || !file.id) {
|
||||||
const parser = new DOMParser();
|
return
|
||||||
const doc = parser.parseFromString(this.configData.notice, 'text/html');
|
|
||||||
const link = doc.querySelector('a');
|
|
||||||
const href = link.getAttribute('href');
|
|
||||||
this.$nextTick(() => {
|
|
||||||
pdfh5 = new Pdfh5('#demo', {
|
|
||||||
pdfurl: href,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}catch (e) {
|
|
||||||
this.isPdf = true
|
|
||||||
}
|
}
|
||||||
this.$nextTick(() => {
|
window.open('/file_server/download?id=' + file.id)
|
||||||
pdfh5.on("complete", function () {
|
|
||||||
const elements = document.querySelectorAll('[class*="canvasImg"]')
|
|
||||||
let classArray = []
|
|
||||||
elements.forEach(element => {
|
|
||||||
classArray.push(element.getAttribute('src'))
|
|
||||||
})
|
|
||||||
elements.forEach((element,index) => {
|
|
||||||
element.addEventListener('click', function() {
|
|
||||||
vant.ImagePreview({
|
|
||||||
images: classArray,
|
|
||||||
startPosition: index,
|
|
||||||
closeable: true,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
this.chooseButton = await getEnumOptions('TheRapyRecuperationType')
|
this.chooseButton = await getEnumOptions('TheRapyRecuperationType')
|
||||||
this.chooseButton = this.chooseButton.filter(o => o.value !== 2)
|
this.chooseButton = this.chooseButton.filter(o => o.value !== 2)
|
||||||
await this.getConfigData()
|
await this.getConfigData()
|
||||||
this.initPdf()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -276,6 +276,10 @@ layout("/layouts/platform.html"){
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
oneKeyGroupSuccess(type) {
|
oneKeyGroupSuccess(type) {
|
||||||
|
if (this.pageForm.startYear !== this.pageForm.endYear) {
|
||||||
|
this.$message.warning('请只选择一个年度操作')
|
||||||
|
return
|
||||||
|
}
|
||||||
this.$confirm("您确定要一键成团吗?", "提示", {
|
this.$confirm("您确定要一键成团吗?", "提示", {
|
||||||
confirmButtonText: "确定",
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: "取消",
|
cancelButtonText: "取消",
|
||||||
@@ -286,6 +290,8 @@ layout("/layouts/platform.html"){
|
|||||||
baseId: this.pageForm.baseManagementId,
|
baseId: this.pageForm.baseManagementId,
|
||||||
playStart: this.pageForm.playStart,
|
playStart: this.pageForm.playStart,
|
||||||
playEnd: this.pageForm.playEnd,
|
playEnd: this.pageForm.playEnd,
|
||||||
|
startYear: this.pageForm.startYear,
|
||||||
|
endYear: this.pageForm.endYear,
|
||||||
type: type,
|
type: type,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -404,6 +410,10 @@ layout("/layouts/platform.html"){
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
exportGroup() {
|
exportGroup() {
|
||||||
|
if (this.pageForm.startYear !== this.pageForm.endYear) {
|
||||||
|
this.$message.warning('请只选择一个年度操作')
|
||||||
|
return
|
||||||
|
}
|
||||||
window.open("/platform/theRapyRecuperation/fragment/exportGroup?" +
|
window.open("/platform/theRapyRecuperation/fragment/exportGroup?" +
|
||||||
"startYear=" + this.pageForm.startYear +
|
"startYear=" + this.pageForm.startYear +
|
||||||
"&endYear=" + this.pageForm.endYear +
|
"&endYear=" + this.pageForm.endYear +
|
||||||
|
|||||||
+37
-3
@@ -99,6 +99,20 @@ layout("/layouts/platform.html"){
|
|||||||
v-model.number="formData.fragmentCount"></el-input>
|
v-model.number="formData.fragmentCount"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="不限制成团人数旅行社" prop="unlimitedGroupTravelAgencyIds">
|
||||||
|
<el-select v-model="formData.unlimitedGroupTravelAgencyIds"
|
||||||
|
placeholder="请选择不限制最少成团人数的旅行社"
|
||||||
|
style="width: 100%"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
multiple>
|
||||||
|
<el-option v-for="item in travelAgencyList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.travelAgencyName + (item.serialNumber ? '(' + item.serialNumber + ')' : '')"
|
||||||
|
:value="item.id"></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="精品团创建线路时间" prop="createLineTimes">
|
<el-form-item label="精品团创建线路时间" prop="createLineTimes">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@@ -276,8 +290,11 @@ layout("/layouts/platform.html"){
|
|||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<vi-title title="服务须知" class="mt20"></vi-title>
|
<vi-title title="服务须知" class="mt20"></vi-title>
|
||||||
<el-form-item label="疗休养服务须知" prop="notice">
|
<el-form-item label="疗休养服务须知文件" prop="files">
|
||||||
<text-editor v-model="formData.notice"></text-editor>
|
<file-upload :files.sync="formData.files"
|
||||||
|
:max="5"
|
||||||
|
:type="['doc','docx','pdf']"
|
||||||
|
></file-upload>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item class="item-button">
|
<el-form-item class="item-button">
|
||||||
@@ -344,6 +361,7 @@ layout("/layouts/platform.html"){
|
|||||||
currentYear: currentYear,
|
currentYear: currentYear,
|
||||||
yearOptions: yearOptions,
|
yearOptions: yearOptions,
|
||||||
activityGroupList: [],
|
activityGroupList: [],
|
||||||
|
travelAgencyList: [],
|
||||||
subLoading: false,
|
subLoading: false,
|
||||||
copyLoading: false,
|
copyLoading: false,
|
||||||
formData: {
|
formData: {
|
||||||
@@ -361,9 +379,11 @@ layout("/layouts/platform.html"){
|
|||||||
modifyNumber: null,
|
modifyNumber: null,
|
||||||
allLineSignUpNumber: null,
|
allLineSignUpNumber: null,
|
||||||
notice: null,
|
notice: null,
|
||||||
|
files: [],
|
||||||
provinceStartYear: null,
|
provinceStartYear: null,
|
||||||
bedInfo: true,
|
bedInfo: true,
|
||||||
familyInfo: 1,
|
familyInfo: 1,
|
||||||
|
unlimitedGroupTravelAgencyIds: [],
|
||||||
},
|
},
|
||||||
formRules: {
|
formRules: {
|
||||||
configName: [{required: true, message: '请填写配置名称', trigger: ['blur', 'change']}],
|
configName: [{required: true, message: '请填写配置名称', trigger: ['blur', 'change']}],
|
||||||
@@ -377,7 +397,7 @@ layout("/layouts/platform.html"){
|
|||||||
modifyNumber: [{required: true, message: '请填写可以修改几次', trigger: ['blur', 'change']}],
|
modifyNumber: [{required: true, message: '请填写可以修改几次', trigger: ['blur', 'change']}],
|
||||||
outsideQuotaProportion: [{required: true, message: '请填写比例', trigger: ['blur', 'change']}],
|
outsideQuotaProportion: [{required: true, message: '请填写比例', trigger: ['blur', 'change']}],
|
||||||
allLineSignUpNumber: [{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']}],
|
provinceStartYear: [{required: true, message: '请输入省内起始年份', trigger: ['change', 'blur']}],
|
||||||
bedInfo: [{required: true, message: '请选择床位信息', trigger: ['change', 'blur']}],
|
bedInfo: [{required: true, message: '请选择床位信息', trigger: ['change', 'blur']}],
|
||||||
familyInfo: [{required: true, message: '请选择家属信息', trigger: ['change', 'blur']}],
|
familyInfo: [{required: true, message: '请选择家属信息', trigger: ['change', 'blur']}],
|
||||||
@@ -418,9 +438,11 @@ layout("/layouts/platform.html"){
|
|||||||
modifyNumber: null,
|
modifyNumber: null,
|
||||||
allLineSignUpNumber: null,
|
allLineSignUpNumber: null,
|
||||||
notice: null,
|
notice: null,
|
||||||
|
files: [],
|
||||||
provinceStartYear: null,
|
provinceStartYear: null,
|
||||||
bedInfo: true,
|
bedInfo: true,
|
||||||
familyInfo: 1,
|
familyInfo: 1,
|
||||||
|
unlimitedGroupTravelAgencyIds: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async changeYear() {
|
async changeYear() {
|
||||||
@@ -526,6 +548,7 @@ layout("/layouts/platform.html"){
|
|||||||
if (this.formData.lots && this.formData.lots.length > 0) {
|
if (this.formData.lots && this.formData.lots.length > 0) {
|
||||||
cloneData.lots = JSON.stringify(this.formData.lots)
|
cloneData.lots = JSON.stringify(this.formData.lots)
|
||||||
}
|
}
|
||||||
|
cloneData.unlimitedGroupTravelAgencyIds = JSON.stringify(cloneData.unlimitedGroupTravelAgencyIds || [])
|
||||||
cloneData.outsideQuotaProportion = cloneData.outsideQuotaProportion / 100
|
cloneData.outsideQuotaProportion = cloneData.outsideQuotaProportion / 100
|
||||||
const resp = await $.post(loc() + "/operation", cloneData)
|
const resp = await $.post(loc() + "/operation", cloneData)
|
||||||
if (resp.code === 0){
|
if (resp.code === 0){
|
||||||
@@ -542,6 +565,7 @@ layout("/layouts/platform.html"){
|
|||||||
},
|
},
|
||||||
async findOne() {
|
async findOne() {
|
||||||
await this.getActivityGroup()
|
await this.getActivityGroup()
|
||||||
|
await this.getTravelAgencies()
|
||||||
const {data} = await $.get(loc() + "/findOne", {year: this.currentYear})
|
const {data} = await $.get(loc() + "/findOne", {year: this.currentYear})
|
||||||
const formData = data || this.getDefaultFormData(this.currentYear)
|
const formData = data || this.getDefaultFormData(this.currentYear)
|
||||||
this.$set(formData, 'configYear', this.currentYear)
|
this.$set(formData, 'configYear', this.currentYear)
|
||||||
@@ -564,12 +588,22 @@ layout("/layouts/platform.html"){
|
|||||||
if (!formData.lots) {
|
if (!formData.lots) {
|
||||||
this.$set(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)
|
this.$set(this, 'formData', formData)
|
||||||
},
|
},
|
||||||
async getActivityGroup() {
|
async getActivityGroup() {
|
||||||
const {data} = await $.get('/platform/activity/basic/scope/getActivityUserScopeGroup')
|
const {data} = await $.get('/platform/activity/basic/scope/getActivityUserScopeGroup')
|
||||||
this.activityGroupList = data
|
this.activityGroupList = data
|
||||||
},
|
},
|
||||||
|
async getTravelAgencies() {
|
||||||
|
const {data} = await $.get(loc() + "/listEnabledTravelAgencies", {year: this.currentYear})
|
||||||
|
this.$set(this, 'travelAgencyList', data || [])
|
||||||
|
},
|
||||||
async closeDialog() {
|
async closeDialog() {
|
||||||
this.tableScopeIndex = '';
|
this.tableScopeIndex = '';
|
||||||
this.tableScopeId = '';
|
this.tableScopeId = '';
|
||||||
|
|||||||
Reference in New Issue
Block a user