华住会更新人员整改

This commit is contained in:
2026-10-15 09:23:53 +08:00
parent 22a0e8f383
commit 17fc9c7c83
4 changed files with 179 additions and 31 deletions
@@ -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) {
@@ -29,6 +29,20 @@ public interface HuaZhuEmployeeService {
*/
List<HuaZhuSyncResult> syncUsers(List<String> userIds);
/**
* 获取规则同步的中文说明,无需传参;单位名称从当前单位表读取,缺失时拒绝继续同步。
*
* @return 包含在岗条件、限定单位名称及非自聘条件的说明文字
*/
String getSyncRuleDescription();
/**
* 同步全部符合规则且未删除的员工,无需传参,不受页面搜索、分页或勾选范围限制。
*
* @return 每位员工的工号、姓名、success 成功标记和 message 结果说明;无人符合时返回空列表
*/
List<HuaZhuSyncResult> syncRuleUsers();
/**
* 查询指定本项目员工在华住的实时状态。
*
@@ -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<String> 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<HuaZhuSyncResult> syncUsers(List<String> userIds) {
if (userIds == null || userIds.isEmpty()) {
return Collections.emptyList();
}
List<HuaZhuSyncResult> 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<String> 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<HuaZhuSyncResult> 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);
@@ -13,7 +13,8 @@ layout("/layouts/platform.html"){
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :app="this" label="华住员工管理">
<el-button @click="syncSelected" icon="el-icon-refresh" size="small" type="primary">同步选中员工</el-button>
<el-button :disabled="syncLoading || tableLoading" @click="syncSelected" icon="el-icon-refresh" size="small" type="primary">同步选中员工</el-button>
<el-button :disabled="tableLoading" :loading="syncLoading" @click="syncRuleUsers" icon="el-icon-refresh" size="small" type="primary">同步规则内人员</el-button>
</table-tool>
<el-table :data="tableData" :size="tableSize" @selection-change="selectionChange" class="vi-table" v-loading="tableLoading">
<el-table-column type="selection" width="55"></el-table-column>
@@ -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() }
})
</script>
<!--#