From 17fc9c7c83b42391528eb14570ea2c85d825d1e8 Mon Sep 17 00:00:00 2001 From: zhouhefeng Date: Thu, 15 Oct 2026 09:23:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=8E=E4=BD=8F=E4=BC=9A=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=BA=BA=E5=91=98=E6=95=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/HuaZhuManageController.java | 38 +++++++- .../huazhu/service/HuaZhuEmployeeService.java | 14 +++ .../impl/HuaZhuEmployeeServiceImpl.java | 65 +++++++++++-- .../views/platform/zhgh/huazhu/index.html | 93 ++++++++++++++----- 4 files changed, 179 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/budwk/app/zhgh/huazhu/controller/HuaZhuManageController.java b/src/main/java/com/budwk/app/zhgh/huazhu/controller/HuaZhuManageController.java index 3c03adc..b9e98ef 100644 --- a/src/main/java/com/budwk/app/zhgh/huazhu/controller/HuaZhuManageController.java +++ b/src/main/java/com/budwk/app/zhgh/huazhu/controller/HuaZhuManageController.java @@ -5,15 +5,17 @@ import cn.hutool.core.util.StrUtil; import com.budwk.app.base.result.Result; import com.budwk.app.zhgh.huazhu.param.HuaZhuEmployeePageForm; import com.budwk.app.zhgh.huazhu.service.HuaZhuEmployeeService; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; +import org.nutz.lang.util.NutMap; import javax.validation.Valid; import java.util.Arrays; -import java.util.Collections; /** * PC 端华住员工同步管理。 @@ -38,7 +40,12 @@ public class HuaZhuManageController { return Result.success(huaZhuEmployeeService.pageUsers(pageForm)); } + /** + * @param userIds 页面勾选员工的本项目用户主键数组,至少传入一个主键 + * @return Result,data 为逐人同步结果,含 userId、loginName、userName、success 和 message + */ @At + @Aop(TransAop.READ_COMMITTED) @SaCheckPermission("huazhu.manage") public Result syncUsers(@Param("userIds") String[] userIds) { if (userIds == null || userIds.length == 0) { @@ -47,6 +54,35 @@ public class HuaZhuManageController { return Result.success(huaZhuEmployeeService.syncUsers(Arrays.asList(userIds))); } + /** + * 无需请求参数;只读取规则说明,供页面确认使用,不推送人员。 + * @return Result,data.description 为包含实际单位名称的中文规则;单位缺失时返回错误提示 + */ + @At + @SaCheckPermission("huazhu.manage") + public Result syncRuleDescription() { + try { + return Result.success(NutMap.NEW().addv("description", huaZhuEmployeeService.getSyncRuleDescription())); + } catch (IllegalStateException e) { + return Result.error(e.getMessage()); + } + } + + /** + * 无需请求参数;由服务端按固定业务规则筛选全部待同步人员。 + * @return Result,data 为逐人同步结果,含 userId、loginName、userName、success 和 message;无人符合时为空列表 + */ + @At + @Aop(TransAop.READ_COMMITTED) + @SaCheckPermission("huazhu.manage") + public Result syncRuleUsers() { + try { + return Result.success(huaZhuEmployeeService.syncRuleUsers()); + } catch (IllegalStateException e) { + return Result.error(e.getMessage()); + } + } + @At @SaCheckPermission("huazhu.manage") public Result queryStatus(@Param("userId") String userId) { diff --git a/src/main/java/com/budwk/app/zhgh/huazhu/service/HuaZhuEmployeeService.java b/src/main/java/com/budwk/app/zhgh/huazhu/service/HuaZhuEmployeeService.java index 0d16713..e7ee66d 100644 --- a/src/main/java/com/budwk/app/zhgh/huazhu/service/HuaZhuEmployeeService.java +++ b/src/main/java/com/budwk/app/zhgh/huazhu/service/HuaZhuEmployeeService.java @@ -29,6 +29,20 @@ public interface HuaZhuEmployeeService { */ List syncUsers(List userIds); + /** + * 获取规则同步的中文说明,无需传参;单位名称从当前单位表读取,缺失时拒绝继续同步。 + * + * @return 包含在岗条件、限定单位名称及非自聘条件的说明文字 + */ + String getSyncRuleDescription(); + + /** + * 同步全部符合规则且未删除的员工,无需传参,不受页面搜索、分页或勾选范围限制。 + * + * @return 每位员工的工号、姓名、success 成功标记和 message 结果说明;无人符合时返回空列表 + */ + List syncRuleUsers(); + /** * 查询指定本项目员工在华住的实时状态。 * diff --git a/src/main/java/com/budwk/app/zhgh/huazhu/service/impl/HuaZhuEmployeeServiceImpl.java b/src/main/java/com/budwk/app/zhgh/huazhu/service/impl/HuaZhuEmployeeServiceImpl.java index 772d59b..fe99cf6 100644 --- a/src/main/java/com/budwk/app/zhgh/huazhu/service/impl/HuaZhuEmployeeServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/huazhu/service/impl/HuaZhuEmployeeServiceImpl.java @@ -7,11 +7,14 @@ import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONObject; import com.budwk.app.base.page.Pagination; import com.budwk.app.sys.models.Sys_user; +import com.budwk.app.sys.models.Sys_unit; import com.budwk.app.zhgh.huazhu.constant.HuaZhuConstant; import com.budwk.app.zhgh.huazhu.param.HuaZhuEmployeePageForm; import com.budwk.app.zhgh.huazhu.service.HuaZhuEmployeeService; import com.budwk.app.zhgh.huazhu.vo.HuaZhuEmployeeStatus; import com.budwk.app.zhgh.huazhu.vo.HuaZhuSyncResult; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.ioc.aop.Aop; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; import org.nutz.dao.Sqls; @@ -34,6 +37,8 @@ import java.util.List; public class HuaZhuEmployeeServiceImpl implements HuaZhuEmployeeService { private static final String TOKEN_CACHE_KEY = "zhgh:huazhu:oauth-token"; + // 这两个单位的在岗人员还需满足非自聘条件;说明文字和筛选使用同一组单位主键。 + private static final List RULE_UNIT_IDS = List.of("110100", "116110"); private final Dao dao; @@ -69,20 +74,28 @@ public class HuaZhuEmployeeServiceImpl implements HuaZhuEmployeeService { return new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), totalCount, listSql.getList(NutMap.class)); } + /** + * 逐人推送,人员已删除、缺少工号或接口失败均记录为该人员失败,继续处理其余人员。 + * @param userIds 待同步的本项目用户主键列表 + * @return 每位员工的标识、success 成功标记和 message 结果说明 + */ @Override + @Aop(TransAop.READ_COMMITTED) public List syncUsers(List userIds) { if (userIds == null || userIds.isEmpty()) { return Collections.emptyList(); } List results = new ArrayList<>(); for (String userId : userIds) { - Sys_user user = findAvailableUser(userId); - HuaZhuSyncResult result = new HuaZhuSyncResult() - .setUserId(user.getId()) - .setLoginName(user.getLoginname()) - .setUserName(user.getUsername()); + HuaZhuSyncResult result = new HuaZhuSyncResult().setUserId(userId); try { - request("/userInfo/addUser", buildEmployeeRequest(user.getLoginname())); + Sys_user user = findAvailableUser(userId); + result.setLoginName(user.getLoginname()).setUserName(user.getUsername()); + // 新增员工按参考同步方法携带预订卡及姓名;状态查询仍使用原有参数。 + NutMap requestBody = buildEmployeeRequest(user.getLoginname()) + .addv("bookId", HuaZhuConstant.CARD_ID) + .addv("userName", user.getUsername()); + request("/userInfo/addUser", requestBody); result.setSuccess(true).setMessage("同步成功"); } catch (Exception e) { result.setSuccess(false).setMessage("同步失败:" + safeMessage(e)); @@ -92,6 +105,46 @@ public class HuaZhuEmployeeServiceImpl implements HuaZhuEmployeeService { return results; } + /** + * 无需入参,按规则内单位主键读取名称,避免向操作人员展示单位编码。 + * @return 中文同步规则;任一单位不存在或没有名称时抛出异常,禁止继续推送 + */ + @Override + public String getSyncRuleDescription() { + List unitNames = new ArrayList<>(); + for (String unitId : RULE_UNIT_IDS) { + Sys_unit unit = dao.fetch(Sys_unit.class, Cnd.where("id", "=", unitId).and("delFlag", "=", false)); + if (unit == null || StrUtil.isBlank(unit.getName())) { + throw new IllegalStateException("无法获取同步规则中的单位名称,请检查单位信息"); + } + unitNames.add("【" + unit.getName() + "】"); + } + return "仅同步在岗人员,其中" + String.join("和", unitNames) + + "仅同步非自聘人员,其他单位不限制人员类型。"; + } + + /** + * 无需入参,复用参考方法的在岗及单位/人员类型条件,并排除当前项目已删除人员。 + * @return 与选中同步一致的逐人结果列表;无符合人员时为空列表 + */ + @Override + @Aop(TransAop.READ_COMMITTED) + public List syncRuleUsers() { + // 推送前再次检查单位信息,避免确认后单位被删除或名称缺失仍继续同步。 + getSyncRuleDescription(); + // 保留原 SQL 的空值语义:单位为空不入选,限定单位的人员类型为空也不入选。 + Sql sql = Sqls.create("SELECT id FROM sys_user WHERE delFlag = 0 AND userState = @userState " + + "AND (unitId NOT IN (@unitOne, @unitTwo) " + + "OR (unitId IN (@unitOne, @unitTwo) AND personType != @personType)) ORDER BY id"); + sql.setParam("userState", "在岗"); + sql.setParam("unitOne", RULE_UNIT_IDS.get(0)); + sql.setParam("unitTwo", RULE_UNIT_IDS.get(1)); + sql.setParam("personType", "自聘"); + sql.setCallback(Sqls.callback.strList()); + dao.execute(sql); + return syncUsers(sql.getList(String.class)); + } + @Override public HuaZhuEmployeeStatus queryStatus(String userId) { Sys_user user = findAvailableUser(userId); diff --git a/src/main/resources/views/platform/zhgh/huazhu/index.html b/src/main/resources/views/platform/zhgh/huazhu/index.html index 1d095f9..eb1a6e7 100644 --- a/src/main/resources/views/platform/zhgh/huazhu/index.html +++ b/src/main/resources/views/platform/zhgh/huazhu/index.html @@ -13,7 +13,8 @@ layout("/layouts/platform.html"){ - 同步选中员工 + 同步选中员工 + 同步规则内人员 @@ -46,35 +47,79 @@ layout("/layouts/platform.html"){ new Vue({ el: "#app", mixins: [initTableMixins], - data: function () { return {pageForm: {searchKeyword: "", pageNumber: 1, pageSize: 10, totalCount: 0}, selectedRows: [], statusDialogVisible: false, statusData: null} }, + data() { return {selectedRows: [], syncLoading: false, statusDialogVisible: false, statusData: null} }, methods: { - selectionChange: function (rows) { this.$set(this, "selectedRows", rows) }, - syncSelected: function () { - var self = this; + selectionChange(rows) { this.$set(this, "selectedRows", rows) }, + // 确认期间锁定同步入口,并固定本次勾选范围,防止重复点击或勾选变化影响提交。 + syncSelected() { + if (this.syncLoading || this.tableLoading) { return } if (!this.selectedRows.length) { this.$message.warning("请先选择需要同步的员工"); return } - this.$confirm("确定同步选中的 " + this.selectedRows.length + " 名员工到华住吗?", "提示", {type: "warning"}).then(function () { - self.$set(self, "tableLoading", true); - return $.post(loc() + "/syncUsers", {userIds: self.selectedRows.map(function (row) { return row.id })}) - }).then(function (res) { - if (res && res.code === 0) { - var failed = (res.data || []).filter(function (item) { return !item.success }); - self.$message.success("同步完成,成功 " + ((res.data || []).length - failed.length) + " 人,失败 " + failed.length + " 人"); - if (failed.length) { self.$alert(failed.map(function (item) { return item.userName + "(" + item.loginName + "):" + item.message }).join("\n"), "失败明细") } - } else if (res) { self.$message.warning(res.msg || "同步失败") } - }).catch(function () {}).then(function () { self.$set(self, "tableLoading", false) }) + const userIds = this.selectedRows.map((row) => row.id); + this.$set(this, "syncLoading", true); + this.confirmSync("确定同步选中的 " + userIds.length + " 名员工到华住吗?", "/syncUsers", {userIds: userIds}); }, - queryStatus: function (row) { - var self = this; + // 先读取包含实际单位名称的规则;只有确认后才调用全量规则同步接口。 + syncRuleUsers() { + if (this.syncLoading || this.tableLoading) { return } + this.$set(this, "syncLoading", true); + let description = ""; + $.post(loc() + "/syncRuleDescription", {}).then((res) => { + if (!res || res.code !== 0 || !res.data || !res.data.description) { + this.$message.warning((res && res.msg) || "无法获取同步规则中的单位名称,请检查单位信息"); + return; + } + description = res.data.description; + }, () => { + this.$message.error("无法获取同步规则中的单位名称,请检查单位信息"); + }).always(() => { + if (!description) { this.$set(this, "syncLoading", false); return } + // 项目使用旧版 jQuery,完成读取后再开启原生确认 Promise,避免提前解除同步锁。 + const message = "确定将符合以下规则的人员同步至华住会吗? " + description + + " 本次将同步全部符合规则的有效人员,不受当前搜索条件、分页或勾选范围限制。"; + this.confirmSync(message, "/syncRuleUsers", {}); + }); + }, + // message 为中文同步范围,path/params 为确认后才提交的接口及参数;取消不推送。 + confirmSync(message, path, params) { + this.$confirm(message, "同步确认", { + type: "warning", confirmButtonText: "确定同步", cancelButtonText: "取消" + }).then(() => this.submitSync(path, params)).catch((reason) => { + if (reason !== "cancel" && reason !== "close") { + this.$message.error("同步请求失败,部分人员可能已同步,请核实状态后再操作"); + } + }).finally(() => { this.$set(this, "syncLoading", false) }); + }, + // path 为本菜单的同步接口后缀;params 为选中用户主键数组或规则同步的空参数。 + // 响应 data 为逐人结果数组,success 表示是否成功,message 为结果说明。 + submitSync(path, params) { this.$set(this, "tableLoading", true); - $.post(loc() + "/queryStatus", {userId: row.id}).then(function (res) { - if (res.code === 0) { self.$set(self, "statusData", res.data); self.$set(self, "statusDialogVisible", true) } else { self.$message.warning(res.msg || "查询失败") } - }).catch(function (xhr) { - var message = xhr && xhr.responseJSON && xhr.responseJSON.msg; - self.$message.error(message || "查询华住状态失败,请稍后重试"); - }).then(function () { self.$set(self, "tableLoading", false) }) + return $.post(loc() + path, params).then((res) => { + if (!res || res.code !== 0) { + this.$message.warning((res && res.msg) || "同步失败"); + return; + } + const results = res.data || []; + if (!results.length) { this.$message.info("没有符合规则的人员需要同步"); return } + const failed = results.filter((item) => !item.success); + const message = "同步完成,成功 " + (results.length - failed.length) + " 人,失败 " + failed.length + " 人"; + if (failed.length) { + this.$message.warning(message); + this.$alert(failed.map((item) => (item.userName || "未知姓名") + "(" + (item.loginName || item.userId) + "):" + item.message).join("\n"), "失败明细") + .catch(() => { /* 关闭失败明细不影响本次同步结果。 */ }); + } else { this.$message.success(message) } + }).always(() => { this.$set(this, "tableLoading", false) }); + }, + queryStatus(row) { + this.$set(this, "tableLoading", true); + $.post(loc() + "/queryStatus", {userId: row.id}).then((res) => { + if (res.code === 0) { this.$set(this, "statusData", res.data); this.$set(this, "statusDialogVisible", true) } else { this.$message.warning(res.msg || "查询失败") } + }, (xhr) => { + const message = xhr && xhr.responseJSON && xhr.responseJSON.msg; + this.$message.error(message || "查询华住状态失败,请稍后重试"); + }).always(() => { this.$set(this, "tableLoading", false) }) } }, - created: function () { this.pageData() } + created() { this.pageData() } })