diff --git a/src/main/java/com/budwk/app/sys/controller/SysDataUserPullController.java b/src/main/java/com/budwk/app/sys/controller/SysDataUserPullController.java index 1bb61fd..d931a97 100644 --- a/src/main/java/com/budwk/app/sys/controller/SysDataUserPullController.java +++ b/src/main/java/com/budwk/app/sys/controller/SysDataUserPullController.java @@ -42,7 +42,7 @@ public class SysDataUserPullController { @ApiOperation("分页数据") @Ok("json:{locked:'password|idCard'}") public Result pageData(@Valid SysDataUserPullPageForm pageForm) { - Sql sql = Sqls.create("select us.*,su.`name` AS unit_name from sys_user_source us left join sys_unit su ON su.id = us.unitId $condition"); + Sql sql = Sqls.create("select us.*,su.`name` AS unitName from sys_user_source us left join sys_unit su ON su.id = us.unitId $condition"); Cnd cnd = Cnd.NEW(); cnd.andEX(Sys_user_source::getPullTime, "=", pageForm.getPullTime()); cnd.and(Cnd.likeEX(Sys_user_source::getUsername, pageForm.getUserName())); diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/asset/controller/AssetManageController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/asset/controller/AssetManageController.java index a9346b0..161d4fc 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/asset/controller/AssetManageController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/asset/controller/AssetManageController.java @@ -30,12 +30,17 @@ import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.lang.util.NutMap; +import org.nutz.mvc.annotation.AdaptBy; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; +import org.nutz.mvc.upload.TempFile; +import org.nutz.mvc.upload.UploadAdaptor; import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; import java.util.List; /** @@ -179,6 +184,7 @@ public class AssetManageController { @At @ApiOperation("按条件导出资产台账") @SaCheckPermission("asset.manage") + @Ok("void") @SLog(type = "assetManage", tag = "资产台账", msg = "按条件导出资产台账") public void doExport(AssetPageForm pageForm, HttpServletResponse response) { Sql sql = assetService.getSql(pageForm); @@ -213,4 +219,53 @@ public class AssetManageController { e.printStackTrace(); } } + + /** + * 导入资产台账,controller 只负责接收文件,具体解析和业务转换放在 service 层。 + * + * @param file 页面上传的资产导入 Excel + * @return 导入结果,包含成功数量和失败明细 + */ + @At + @ApiOperation("导入资产台账") + @Aop(TransAop.READ_COMMITTED) + @SLog(type = "assetManage", tag = "资产台账", msg = "导入资产台账") + @SaCheckPermission("asset.manage") + @AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"}) + public Result doImport(@Param("file") TempFile file) { + return Result.success(assetService.importAssets(file)); + } + + /** + * 下载资产导入模板,字段与导入解析 VO 保持一致,不包含预计报废时间。 + * + * @param response 文件下载响应 + */ + @At + @Ok("void") + @ApiOperation("下载资产导入模板") + @SaCheckPermission("asset.manage") + public void downloadTemplate(HttpServletResponse response) { + List entities = new ArrayList<>(); + entities.add(new ExcelExportEntity("资产编号", "assetNumber", 20)); + entities.add(new ExcelExportEntity("资产名称", "assetName", 20)); + entities.add(new ExcelExportEntity("资产类别", "assetCategoryName", 20)); + entities.add(new ExcelExportEntity("资产规格型号", "assetSpecs", 20)); + entities.add(new ExcelExportEntity("供应商名称", "assetSupplierName", 20)); + entities.add(new ExcelExportEntity("采购方式", "assetFundingSubjectName", 20)); + entities.add(new ExcelExportEntity("用途", "assetPurposeName", 20)); + entities.add(new ExcelExportEntity("单价", "assetUnitPrice", 20)); + entities.add(new ExcelExportEntity("数量", "assetQuantity", 20)); + entities.add(new ExcelExportEntity("资产类型", "assetTypeName", 20)); + entities.add(new ExcelExportEntity("开始使用日期", "assetUsedDate", 20)); + entities.add(new ExcelExportEntity("负责人", "assetUseUserName", 20)); + entities.add(new ExcelExportEntity("使用/管理部门", "assetUseUnionName", 20)); + entities.add(new ExcelExportEntity("存放地点", "assetStorageLocation", 20)); + entities.add(new ExcelExportEntity("使用状况", "assetUsageStateName", 20)); + entities.add(new ExcelExportEntity("备注", "assetNotes", 30)); + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, Collections.emptyList()); + CommonDownloadUtil.download("资产导入模板.xlsx", workbook, response); + } } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/asset/mode/AssetImportExcelMode.java b/src/main/java/com/budwk/app/zhgh/dayofficework/asset/mode/AssetImportExcelMode.java new file mode 100644 index 0000000..044f86d --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/asset/mode/AssetImportExcelMode.java @@ -0,0 +1,65 @@ +package com.budwk.app.zhgh.dayofficework.asset.mode; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import com.budwk.app.base.model.ExcelImportError; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 资产台账导入模板行数据,字段与资产新增页面红框内可录入字段保持一致。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class AssetImportExcelMode extends ExcelImportError { + + @Excel(name = "资产编号", width = 20) + private String assetNumber; + + @Excel(name = "资产名称", width = 20) + private String assetName; + + @Excel(name = "资产类别", width = 20) + private String assetCategoryName; + + @Excel(name = "资产规格型号", width = 20) + private String assetSpecs; + + @Excel(name = "供应商名称", width = 20) + private String assetSupplierName; + + @Excel(name = "采购方式", width = 20) + private String assetFundingSubjectName; + + @Excel(name = "用途", width = 20) + private String assetPurposeName; + + @Excel(name = "单价", width = 20) + private BigDecimal assetUnitPrice; + + @Excel(name = "数量", width = 20) + private Integer assetQuantity; + + @Excel(name = "资产类型", width = 20) + private String assetTypeName; + + @Excel(name = "开始使用日期", width = 20, format = "yyyy-MM-dd") + private Date assetUsedDate; + + @Excel(name = "负责人", width = 20) + private String assetUseUserName; + + @Excel(name = "使用/管理部门", width = 20) + private String assetUseUnionName; + + @Excel(name = "存放地点", width = 20) + private String assetStorageLocation; + + @Excel(name = "使用状况", width = 20) + private String assetUsageStateName; + + @Excel(name = "备注", width = 30) + private String assetNotes; +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/asset/service/AssetService.java b/src/main/java/com/budwk/app/zhgh/dayofficework/asset/service/AssetService.java index 49bb5b6..8e78442 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/asset/service/AssetService.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/asset/service/AssetService.java @@ -1,10 +1,13 @@ package com.budwk.app.zhgh.dayofficework.asset.service; import com.budwk.app.base.service.BaseService; +import com.budwk.app.base.model.ExcelImportRes; +import com.budwk.app.zhgh.dayofficework.asset.mode.AssetImportExcelMode; import com.budwk.app.zhgh.dayofficework.asset.model.Asset; import com.budwk.app.zhgh.dayofficework.asset.param.AssetPageForm; import org.nutz.dao.sql.Sql; import org.nutz.lang.util.NutMap; +import org.nutz.mvc.upload.TempFile; public interface AssetService extends BaseService { @@ -12,4 +15,12 @@ public interface AssetService extends BaseService { Sql getSql(AssetPageForm pageForm); NutMap findOne(String id); + + /** + * 导入资产台账数据,负责 Excel 解析、字段转换、预计报废时间计算和折旧记录初始化。 + * + * @param file 页面上传的资产 Excel 文件 + * @return 导入结果,包含成功数和失败明细 + */ + ExcelImportRes importAssets(TempFile file); } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/asset/service/impl/AssetServiceImpl.java b/src/main/java/com/budwk/app/zhgh/dayofficework/asset/service/impl/AssetServiceImpl.java index 566d3da..5dd4b77 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/asset/service/impl/AssetServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/asset/service/impl/AssetServiceImpl.java @@ -1,21 +1,39 @@ package com.budwk.app.zhgh.dayofficework.asset.service.impl; +import cn.afterturn.easypoi.excel.ExcelImportUtil; +import cn.afterturn.easypoi.excel.entity.ImportParams; import cn.hutool.core.util.StrUtil; import com.budwk.app.base.constant.RoleConstant; +import com.budwk.app.base.model.ExcelImportRes; import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.sys.models.Sys_dict; +import com.budwk.app.sys.models.Sys_union; +import com.budwk.app.sys.services.SysDictService; import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.zhgh.dayofficework.asset.mode.AssetImportExcelMode; import com.budwk.app.zhgh.dayofficework.asset.model.Asset; +import com.budwk.app.zhgh.dayofficework.asset.model.AssetCategory; +import com.budwk.app.zhgh.dayofficework.asset.model.AssetDepreciationRecord; import com.budwk.app.zhgh.dayofficework.asset.model.AssetStocktaking; import com.budwk.app.zhgh.dayofficework.asset.param.AssetPageForm; +import com.budwk.app.zhgh.dayofficework.asset.service.AssetDepreciationRecordService; import com.budwk.app.zhgh.dayofficework.asset.service.AssetService; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; import org.nutz.dao.Sqls; import org.nutz.dao.sql.Sql; import org.nutz.dao.util.cri.SqlExpressionGroup; +import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.lang.util.NutMap; +import org.nutz.mvc.upload.TempFile; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; /** * @author zhf @@ -28,6 +46,12 @@ public class AssetServiceImpl extends BaseServiceImpl implements AssetSer super(dao); } + @Inject + private AssetDepreciationRecordService assetDepreciationRecordService; + + @Inject + private SysDictService sysDictService; + @Override public Sql getSql(AssetPageForm pageForm) { Sql sql = Sqls.create(""" @@ -83,4 +107,260 @@ public class AssetServiceImpl extends BaseServiceImpl implements AssetSer Cnd.where("assetId", "=", id))); return nutMap; } + + /** + * 导入资产台账数据,逐行完成字段校验和新增,失败行不影响其他行入库。 + * + * @param file 页面上传的资产 Excel 文件 + * @return 导入统计结果和失败明细 + */ + @Override + public ExcelImportRes importAssets(TempFile file) { + List importRows = ExcelImportUtil.importExcel(file.getFile(), AssetImportExcelMode.class, new ImportParams()); + ExcelImportRes importRes = new ExcelImportRes<>(); + importRes.setTotalRecords(importRows.size()); + for (int i = 0; i < importRows.size(); i++) { + AssetImportExcelMode row = importRows.get(i); + List errors = new ArrayList<>(); + Asset asset = buildImportAsset(row, errors); + if (!errors.isEmpty()) { + row.setErrInfo(String.join(";", errors), i + 2); + continue; + } + insertAssetWithDepreciation(asset); + } + importRes.setErrorDetails(importRows.stream().filter(row -> StrUtil.isNotBlank(row.getErrMsg())).collect(Collectors.toList())); + importRes.setFailedCount(importRes.getErrorDetails().size()); + importRes.setSuccessCount(Math.max(importRows.size() - importRes.getFailedCount(), 0)); + return importRes; + } + + /** + * 将 Excel 行转换为资产实体,包含字典值、单选值、负责人和部门等页面字段转换。 + * + * @param row Excel 导入行 + * @param errors 当前行错误收集器 + * @return 可入库的资产实体 + */ + private Asset buildImportAsset(AssetImportExcelMode row, List errors) { + Asset asset = new Asset(); + asset.setAssetNumber(StrUtil.trim(row.getAssetNumber())); + asset.setAssetName(requiredText(row.getAssetName(), "资产名称", errors)); + AssetCategory category = resolveAssetCategory(row.getAssetCategoryName(), errors); + if (category != null) { + asset.setAssetCategoryId(category.getId()); + } + asset.setAssetSpecs(requiredText(row.getAssetSpecs(), "资产规格型号", errors)); + asset.setAssetSupplierName(requiredText(row.getAssetSupplierName(), "供应商名称", errors)); + asset.setAssetFundingSubjectName(resolveFixedOption(row.getAssetFundingSubjectName(), "采购方式", Set.of("网购", "线下", "采购"), errors)); + asset.setAssetPurposeName(resolveFixedOption(row.getAssetPurposeName(), "用途", Set.of("职工之家建设", "日常办公", "其他"), errors)); + asset.setAssetUnitPrice(row.getAssetUnitPrice()); + asset.setAssetQuantity(row.getAssetQuantity()); + if (asset.getAssetUnitPrice() == null) { + errors.add("单价不能为空"); + } + if (asset.getAssetQuantity() == null || asset.getAssetQuantity() <= 0) { + errors.add("数量不能为空且必须大于0"); + } + asset.setAssetTypeCode(resolveAssetType(row.getAssetTypeName(), errors)); + asset.setAssetUsedDate(row.getAssetUsedDate()); + if (asset.getAssetUsedDate() == null) { + errors.add("开始使用日期不能为空"); + } + NutMap user = resolveAssetUseUser(row.getAssetUseUserName(), errors); + if (user != null) { + asset.setAssetUseUserId(user.getString("id")); + } + Sys_union union = resolveAssetUseUnion(row.getAssetUseUnionName(), asset.getAssetTypeCode(), user, errors); + if (union != null) { + asset.setAssetUseUnionId(union.getId()); + asset.setAssetUseUnionName(union.getName()); + } + asset.setAssetStorageLocation(requiredText(row.getAssetStorageLocation(), "存放地点", errors)); + asset.setAssetUsageStateName(resolveDictName(row.getAssetUsageStateName(), "ASSET_USAGE_STATE", "使用状况", errors)); + asset.setAssetNotes(StrUtil.trim(row.getAssetNotes())); + if (category != null && asset.getAssetUsedDate() != null) { + asset.setAssetRetiredAssetsDate(calculateRetiredAssetsDate(asset.getAssetUsedDate(), category.getDepreciationYear())); + } + return asset; + } + + /** + * 新增资产并按现有新增逻辑初始化折旧记录。 + * + * @param asset 已完成转换和校验的资产 + */ + private void insertAssetWithDepreciation(Asset asset) { + if (StrUtil.isBlank(asset.getAssetNumber())) { + asset.setAssetNumber(nextAssetNumber()); + } + asset.setApplyUserId(SecurityUtil.getUserId()); + insert(asset); + AssetDepreciationRecord record = assetDepreciationRecordService.initAssetDepreciationRecord(asset.getAssetUnitPrice(), asset.getAssetQuantity(), asset.getAssetUsedDate(), asset.getAssetRetiredAssetsDate()); + if (record != null) { + record.setAssetId(asset.getId()); + assetDepreciationRecordService.insert(record); + } + } + + /** + * 按新增资产页面逻辑计算预计报废时间:开始使用日期加资产类别折旧年限。 + * + * @param assetUsedDate 开始使用日期 + * @param depreciationYear 资产类别折旧年限 + * @return 预计报废时间 + */ + private java.util.Date calculateRetiredAssetsDate(java.util.Date assetUsedDate, int depreciationYear) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(assetUsedDate); + calendar.add(Calendar.YEAR, depreciationYear); + return calendar.getTime(); + } + + /** + * 生成与新增资产一致的资产编号:当前年份 + 4 位流水。 + * + * @return 新资产编号 + */ + private String nextAssetNumber() { + int year = Calendar.getInstance().get(Calendar.YEAR); + Sql sql = Sqls.create(""" + SELECT count(1) FROM `asset` where LEFT(assetNumber,4) = @year and LENGTH(assetNumber) = 8 + """).setParam("year", year); + int count = count(sql); + return year + String.format("%04d", count + 1); + } + + /** + * 按资产类别名称、编码或 ID 匹配资产类别。 + */ + private AssetCategory resolveAssetCategory(String value, List errors) { + String text = requiredText(value, "资产类别", errors); + if (StrUtil.isBlank(text)) { + return null; + } + AssetCategory category = dao().fetch(AssetCategory.class, Cnd.where("categoryName", "=", text).or("categoryCode", "=", text).or("id", "=", text)); + if (category == null) { + errors.add("资产类别不存在:" + text); + } + return category; + } + + /** + * 将资产类型单选文本转换为保存值。 + */ + private Integer resolveAssetType(String value, List errors) { + String text = requiredText(value, "资产类型", errors); + if (StrUtil.isBlank(text)) { + return null; + } + if ("校工会资产".equals(text) || "1".equals(text)) { + return 1; + } + if ("分工会资产".equals(text) || "2".equals(text)) { + return 2; + } + errors.add("资产类型只能填写校工会资产或分工会资产"); + return null; + } + + /** + * 使用/管理部门:校工会资产固定为校工会;分工会资产按导入值或负责人所属工会转换。 + */ + private Sys_union resolveAssetUseUnion(String value, Integer assetTypeCode, NutMap user, List errors) { + if (assetTypeCode != null && assetTypeCode == 1) { + Sys_union union = new Sys_union(); + union.setId(user == null ? null : user.getString("unionId")); + union.setName("校工会"); + return union; + } + String text = StrUtil.trim(value); + if (StrUtil.isBlank(text) && user != null) { + text = user.getString("unionName"); + } + if (StrUtil.isBlank(text)) { + errors.add("使用/管理部门不能为空"); + return null; + } + Sys_union union = dao().fetch(Sys_union.class, Cnd.where("name", "=", text).or("unionCode", "=", text).or("id", "=", text)); + if (union == null) { + errors.add("使用/管理部门不存在:" + text); + } + return union; + } + + /** + * 负责人支持工号、姓名、用户 ID,以及页面下拉常见的“姓名-工号”格式。 + */ + private NutMap resolveAssetUseUser(String value, List errors) { + String text = requiredText(value, "负责人", errors); + if (StrUtil.isBlank(text)) { + return null; + } + String keyword = text.contains("-") ? StrUtil.subAfter(text, "-", true) : text; + Sql sql = Sqls.create(""" + SELECT + id, + loginname, + username, + unionName, + unionId + FROM + `vw_user` + WHERE + id = @keyword + OR loginname = @keyword + OR username = @keyword + """).setParam("keyword", keyword); + NutMap user = (NutMap) dao().execute(sql.setCallback(Sqls.callback.map())).getResult(); + if (user == null) { + errors.add("负责人不存在:" + text); + } + return user; + } + + /** + * 将数据字典文本或编码转换为当前系统保存的字典名称。 + */ + private String resolveDictName(String value, String dictCode, String fieldName, List errors) { + String text = requiredText(value, fieldName, errors); + if (StrUtil.isBlank(text)) { + return null; + } + Sys_dict dict = sysDictService.getSubListByCode(dictCode).stream() + .filter(item -> text.equals(item.getName()) || text.equals(item.getCode())) + .findFirst() + .orElse(null); + if (dict == null) { + errors.add(fieldName + "字典值不存在:" + text); + return null; + } + return dict.getName(); + } + + /** + * 校验固定选项字段,导入值按新增页面下拉选项保存为中文名称。 + */ + private String resolveFixedOption(String value, String fieldName, Set options, List errors) { + String text = requiredText(value, fieldName, errors); + if (StrUtil.isBlank(text)) { + return null; + } + if (!options.contains(text)) { + errors.add(fieldName + "只能填写:" + String.join("、", options)); + return null; + } + return text; + } + + /** + * 必填文本统一去空格,便于导入错误提示保持一致。 + */ + private String requiredText(String value, String fieldName, List errors) { + String text = StrUtil.trim(value); + if (StrUtil.isBlank(text)) { + errors.add(fieldName + "不能为空"); + } + return text; + } } diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberInfoGroupController.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberInfoGroupController.java index 3ff2d15..4d49a4e 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberInfoGroupController.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberInfoGroupController.java @@ -65,10 +65,10 @@ public class MemberInfoGroupController { public Result pageData(MemberInfoPageForm pageForm) { Sql sql = memberInfoService.getSql(pageForm); Pagination pagination = memberInfoService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); - for (NutMap row : pagination.getList()) { - row.put("mobile",DesensitizedUtil.mobilePhone(row.getString("mobile"))); - row.put("idCard",DesensitizedUtil.idCardNum(row.getString("idCard"), 3, 4)); - } +// for (NutMap row : pagination.getList()) { +// row.put("mobile",DesensitizedUtil.mobilePhone(row.getString("mobile"))); +// row.put("idCard",DesensitizedUtil.idCardNum(row.getString("idCard"), 3, 4)); +// } return Result.success(pagination); } diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberPersonalInfoMaintainController.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberPersonalInfoMaintainController.java new file mode 100644 index 0000000..c61a4da --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/info/MemberPersonalInfoMaintainController.java @@ -0,0 +1,113 @@ +package com.budwk.app.zhgh.staffmanage.member.controller.info; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaMode; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.staffmanage.member.models.MemberChild; +import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberInfoPageForm; +import com.budwk.app.zhgh.staffmanage.member.service.MemberChildService; +import com.budwk.app.zhgh.staffmanage.member.service.MemberInfoService; +import org.nutz.dao.sql.Sql; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.json.Json; +import org.nutz.lang.Strings; +import org.nutz.lang.util.NutMap; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import java.util.ArrayList; +import java.util.List; + +/** + * 个人信息维护控制器,提供会员基础信息只读展示和子女信息维护入口。 + */ +@IocBean +@Ok("json:full") +@At("/platform/member/info/maintain") +public class MemberPersonalInfoMaintainController { + + @Inject + private MemberInfoService memberInfoService; + + @Inject + private MemberChildService memberChildService; + + /** + * 个人信息维护列表页,查询条件和列表字段复用会员档案管理页面。 + */ + @At("") + @Ok("beetl:/platform/zhgh/staffmanage/member/info/maintain/index.html") + @SaCheckPermission(value = {"member.info.maintain", "member.info.group"}, mode = SaMode.OR) + public void index() { + } + + /** + * 个人信息维护编辑页,会员原有字段只读,子女信息允许维护。 + */ + @At("/edit") + @Ok("beetl:/platform/zhgh/staffmanage/member/info/maintain/edit.html") + @SaCheckPermission(value = {"member.info.maintain", "member.info.group"}, mode = SaMode.OR) + public void edit() { + } + + /** + * 查询会员列表,沿用会员档案管理的查询条件和权限过滤。 + * + * @param pageForm 会员档案查询参数 + * @return 分页会员列表 + */ + @At + @Ok("json:{dateFormat:'yyyy-MM-dd'}") + @SaCheckPermission(value = {"member.info.maintain", "member.info.group"}, mode = SaMode.OR) + public Result pageData(MemberInfoPageForm pageForm) { + Sql sql = memberInfoService.getPersonalMaintainSql(pageForm); + Pagination pagination = memberInfoService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); + return Result.success(pagination); + } + + /** + * 查询个人信息维护详情,返回只读会员信息和可编辑子女信息。 + * + * @param userId 用户ID + * @return 会员信息和子女信息 + */ + @At + @Ok("json:{dateFormat:'yyyy-MM-dd'}") + @SaCheckPermission(value = {"member.info.maintain", "member.info.group"}, mode = SaMode.OR) + public Result detail(String userId) { + try { + NutMap data = memberInfoService.getPersonalMaintainInfo(userId); + data.put("children", memberChildService.listByUserId(userId)); + return Result.success(data); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } + } + + /** + * 保存子女信息,原有会员字段不接收保存,仅覆盖当前会员的子女资料。 + * + * @param userId 用户ID + * @param childrenJson 子女信息JSON字符串 + * @return 保存结果 + */ + @At + @SaCheckPermission(value = {"member.info.maintain", "member.info.group"}, mode = SaMode.OR) + public Result saveChildren(String userId, @Param("children") String childrenJson) { + try { + // 保存前复用详情权限校验,防止绕过列表入口修改无权限会员的子女信息。 + memberInfoService.getPersonalMaintainInfo(userId); + List children = new ArrayList<>(); + if (Strings.isNotBlank(childrenJson)) { + children = Json.fromJsonAsList(MemberChild.class, childrenJson); + } + memberChildService.saveUserChildren(userId, children); + return Result.success(); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } + } +} diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/statistics/MemberSummaryController.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/statistics/MemberSummaryController.java index 78a579b..22fe8f6 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/statistics/MemberSummaryController.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/statistics/MemberSummaryController.java @@ -2,6 +2,7 @@ package com.budwk.app.zhgh.staffmanage.member.controller.statistics; import cn.dev33.satoken.annotation.SaCheckPermission; import com.budwk.app.base.result.Result; +import com.budwk.app.base.utils.CommonDownloadUtil; import com.budwk.app.zhgh.staffmanage.member.service.MemberStatisticsService; import org.apache.poi.ss.usermodel.Workbook; import org.nutz.ioc.loader.annotation.Inject; @@ -56,28 +57,49 @@ public class MemberSummaryController { public void doExport(@Param("queryId") String queryId, @Param("queryType") String queryType, @Param("currentYear") Integer currentYear, HttpServletResponse response) { List list = new ArrayList<>(); - if ("union".equals(queryType)) { + boolean unionQuery = isUnionQuery(queryType); + if (unionQuery) { list = memberStatisticsService.getUnionStatisticsData(queryId, currentYear); } else { list = memberStatisticsService.getUnitStatisticsData(queryId, currentYear); } + // 导出数据与页面查询保持一致,并追加合计行方便 Excel 查看总量。 NutMap map = NutMap.NEW(); - map.put("union".equals(queryType) ? "unionName" : "unitName", "合计"); - map.put("totalNumber", list.stream().mapToInt(v -> v.getInt("TotalNumber")).sum()); - map.put("maleMember", list.stream().mapToInt(v -> v.getInt("maleMember")).sum()); - map.put("femaleMember", list.stream().mapToInt(v -> v.getInt("femaleMember")).sum()); - map.put("smallForty", list.stream().mapToInt(v -> v.getInt("smallForty")).sum()); - map.put("smallFifty", list.stream().mapToInt(v -> v.getInt("smallFifty")).sum()); - map.put("smallFiftyFive", list.stream().mapToInt(v -> v.getInt("smallFiftyFive")).sum()); - map.put("smallFiftyFive2", list.stream().mapToInt(v -> v.getInt("smallFiftyFive2")).sum()); + map.put(unionQuery ? "unionName" : "unitName", "合计"); + map.put("totalNumber", sumInt(list, "totalNumber")); + map.put("maleMember", sumInt(list, "maleMember")); + map.put("femaleMember", sumInt(list, "femaleMember")); + map.put("smallForty", sumInt(list, "smallForty")); + map.put("smallFifty", sumInt(list, "smallFifty")); + map.put("smallFiftyFive", sumInt(list, "smallFiftyFive")); + map.put("smallFiftyFive2", sumInt(list, "smallFiftyFive2")); + list.add(map); try { - response.setContentType("application/octet-stream"); - response.setHeader("Content-Disposition", "attachment;filename=" + new String(("会员性别统计.xlsx").getBytes("utf-8"), "ISO8859-1")); - Workbook workbook = memberStatisticsService.exportStatisticsExcel(list, queryType); - workbook.write(response.getOutputStream()); - workbook.close(); + Workbook workbook = memberStatisticsService.exportStatisticsExcel(list, unionQuery ? "union" : "unit"); + CommonDownloadUtil.download("会员统计.xlsx", workbook, response); } catch (Exception e) { e.printStackTrace(); } } + + /** + * 判断统计维度,兼容页面传入的 fgh 和服务层导出使用的 union。 + * + * @param queryType 统计维度 + * @return 是否按分工会统计 + */ + private boolean isUnionQuery(String queryType) { + return "fgh".equals(queryType) || "union".equals(queryType); + } + + /** + * 汇总 NutMap 数值字段,避免空值导致导出合计失败。 + * + * @param list 统计列表 + * @param key 汇总字段名 + * @return 汇总值 + */ + private int sumInt(List list, String key) { + return list.stream().mapToInt(item -> item.getInt(key, 0)).sum(); + } } diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/models/MemberChild.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/models/MemberChild.java new file mode 100644 index 0000000..96b3124 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/models/MemberChild.java @@ -0,0 +1,59 @@ +package com.budwk.app.zhgh.staffmanage.member.models; + +import com.budwk.app.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.nutz.dao.entity.annotation.ColDefine; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.entity.annotation.Column; +import org.nutz.dao.entity.annotation.Comment; +import org.nutz.dao.entity.annotation.Name; +import org.nutz.dao.entity.annotation.Table; +import org.nutz.dao.entity.annotation.TableMeta; +import org.nutz.dao.interceptor.annotation.PrevInsert; + +import java.util.Date; + +/** + * 会员子女信息表,维护个人信息维护页面可录入的子女资料。 + */ +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +@Table("member_child") +@TableMeta("{'mysql-charset':'utf8mb4'}") +@Comment("会员子女信息") +public class MemberChild extends BaseModel { + + @Name + @Comment("ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + @PrevInsert(uu32 = true) + private String id; + + @Column + @Comment("用户ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String userId; + + @Column + @Comment("关系") + @ColDefine(type = ColType.VARCHAR, width = 20) + private String relation; + + @Column + @Comment("姓名") + @ColDefine(type = ColType.VARCHAR, width = 50) + private String name; + + @Column + @Comment("性别") + @ColDefine(type = ColType.VARCHAR, width = 10) + private String sex; + + @Column + @Comment("出生日期") + @ColDefine(type = ColType.DATE) + private Date birthday; +} diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberChildService.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberChildService.java new file mode 100644 index 0000000..7e9ce68 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberChildService.java @@ -0,0 +1,28 @@ +package com.budwk.app.zhgh.staffmanage.member.service; + +import com.budwk.app.base.service.BaseService; +import com.budwk.app.zhgh.staffmanage.member.models.MemberChild; + +import java.util.List; + +/** + * 会员子女信息业务服务,集中处理子女信息查询和保存逻辑。 + */ +public interface MemberChildService extends BaseService { + + /** + * 根据会员用户ID查询子女信息列表。 + * + * @param userId 会员用户ID + * @return 子女信息列表 + */ + List listByUserId(String userId); + + /** + * 保存会员子女信息,页面按整表提交,服务层负责覆盖当前会员的子女资料。 + * + * @param userId 会员用户ID + * @param children 页面提交的子女信息列表 + */ + void saveUserChildren(String userId, List children); +} diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberInfoService.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberInfoService.java index 9b76e76..8420f6d 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberInfoService.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberInfoService.java @@ -27,6 +27,22 @@ public interface MemberInfoService extends BaseService { */ Sql getSql(MemberInfoPageForm pageForm); + /** + * 获取个人信息维护列表查询SQL,系统管理员查看全部,其他人员仅查看本人。 + * + * @param pageForm 会员档案查询参数 + * @return 个人信息维护列表查询SQL + */ + Sql getPersonalMaintainSql(MemberInfoPageForm pageForm); + + /** + * 获取个人信息维护详情,复用会员档案查询权限展示会员基础信息。 + * + * @param userId 用户ID + * @return 会员基础信息 + */ + NutMap getPersonalMaintainInfo(String userId); + /** * 获取会员历史 * @param pageForm diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberChildServiceImpl.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberChildServiceImpl.java new file mode 100644 index 0000000..04e8474 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberChildServiceImpl.java @@ -0,0 +1,60 @@ +package com.budwk.app.zhgh.staffmanage.member.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.zhgh.staffmanage.member.models.MemberChild; +import com.budwk.app.zhgh.staffmanage.member.service.MemberChildService; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.Lang; + +import java.util.List; + +/** + * 会员子女信息业务实现,负责个人信息维护页面的子女资料持久化。 + */ +@IocBean(args = {"refer:dao"}) +public class MemberChildServiceImpl extends BaseServiceImpl implements MemberChildService { + + public MemberChildServiceImpl(Dao dao) { + super(dao); + } + + /** + * 根据会员用户ID查询子女信息列表,并按创建时间保持页面展示顺序。 + * + * @param userId 会员用户ID + * @return 子女信息列表 + */ + @Override + public List listByUserId(String userId) { + return query(Cnd.where("userId", "=", userId).asc("createdAt")); + } + + /** + * 覆盖保存指定会员的子女信息,避免前端逐条新增删除导致数据残留。 + * + * @param userId 会员用户ID + * @param children 页面提交的子女信息列表 + */ + @Override + @Aop(TransAop.READ_COMMITTED) + public void saveUserChildren(String userId, List children) { + clear(Cnd.where("userId", "=", userId)); + if (Lang.isEmpty(children)) { + return; + } + children.stream() + .filter(child -> StrUtil.isNotBlank(child.getRelation()) || StrUtil.isNotBlank(child.getName()) + || StrUtil.isNotBlank(child.getSex()) || child.getBirthday() != null) + .forEach(child -> { + // 子女信息归属以后端传入的会员ID为准,防止前端篡改归属用户。 + child.setId(null); + child.setUserId(userId); + insert(child); + }); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberInfoServiceImpl.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberInfoServiceImpl.java index a5d227e..31d67c4 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberInfoServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberInfoServiceImpl.java @@ -15,6 +15,7 @@ import com.budwk.app.base.utils.easyexcel.EasyExcelUtil; import com.budwk.app.sys.models.Sys_role; import com.budwk.app.sys.models.Sys_unit; import com.budwk.app.sys.models.Sys_user_role; +import com.budwk.app.sys.views.View_user; import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.sys.models.Sys_user; import com.budwk.app.web.commons.auth.utils.SecurityUtil; @@ -113,6 +114,87 @@ public class MemberInfoServiceImpl extends BaseServiceImpl implements return sql; } + /** + * 获取个人信息维护详情,非学校管理员只能查看本分工会或未分配分工会的会员。 + * + * @param userId 用户ID + * @return 会员基础信息 + */ + /** + * 获取个人信息维护列表SQL,系统管理员按页面条件查询全部,其他人员强制查询本人数据。 + * + * @param pageForm 会员档案查询参数 + * @return 个人信息维护列表查询SQL + */ + @Override + public Sql getPersonalMaintainSql(MemberInfoPageForm pageForm) { + Sql sql = Sqls.create(""" + SELECT + u.id, + u.loginname, + u.username, + u.sex, + DATE_FORMAT(u.birthday, '%Y-%m-%d') AS birthday, + u.political, + DATE_FORMAT(u.joinPartyDate, '%Y-%m-%d') AS joinPartyDate, + u.nation, + u.nativePlace, + u.nationality, + u.idCardType, + u.mobile, + u.idCard, + u.marriage, + u.education, + u.academicDegree, + u.position, + u.jobCategory, + u.professionalTitle, + u.professionalLevel, + u.employeeSource, + u.administrativeLevel, + u.positionSeries, + u.positionLevel, + u.administrativePositionLevel, + u.arrivalAtSchoolDate, + u.personType, + u.preparedBy, + u.identityType, + u.userState, + u.unionName, + u.unitName, + DATE_FORMAT(u.teachingTime, '%Y-%m-%d') AS teachingTime, + DATE_FORMAT(u.expectedLeaveSchoolDate, '%Y-%m-%d') AS expectedLeaveSchoolDate, + u.member, + DATE_FORMAT(u.postDoctoralJoinDate, '%Y-%m-%d') AS postDoctoralJoinDate + FROM + vw_user u + $condition + """); + Cnd cnd = Cnd.NEW(); + if (AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) { + pageForm.buildSearch(cnd, "u."); + } else { + // 个人信息维护只允许非系统管理员查看本人,避免通过查询条件越权查看其他人员。 + cnd.and("u.id", "=", SecurityUtil.getUserId()); + } + sql.setCondition(cnd); + return sql; + } + + @Override + public NutMap getPersonalMaintainInfo(String userId) { + Cnd cnd = Cnd.where("id", "=", userId); + if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) { + // 非系统管理员只能维护本人信息,保存子女信息前也复用此校验。 + cnd.and("id", "=", SecurityUtil.getUserId()); + } + View_user user = dao().fetch(View_user.class, cnd); + if (user == null) { + throw new RuntimeException("用户信息不存在或无权限查看"); + } + return Lang.obj2nutmap(user); + } + @Override public Sql getMemberHistorySql(MemberInfoPageForm pageForm) { diff --git a/src/main/resources/views/platform/zhgh/dayofficework/asset/manage/index.html b/src/main/resources/views/platform/zhgh/dayofficework/asset/manage/index.html index 81e3466..b93c2cf 100644 --- a/src/main/resources/views/platform/zhgh/dayofficework/asset/manage/index.html +++ b/src/main/resources/views/platform/zhgh/dayofficework/asset/manage/index.html @@ -53,6 +53,9 @@ layout("/layouts/platform.html"){ 新增资产 + + 导入资产 + 导出资产 @@ -110,6 +113,16 @@ layout("/layouts/platform.html"){ + @@ -132,7 +145,8 @@ layout("/layouts/platform.html"){ {prop: 'assetUsageStateName', label: '使用状况'}, ], assetCategoryOption: [], - unionList: [] + unionList: [], + showImportDialog: false } }, components: { diff --git a/src/main/resources/views/platform/zhgh/staffmanage/member/common/info/memberInfo.js b/src/main/resources/views/platform/zhgh/staffmanage/member/common/info/memberInfo.js index 98edb76..293d839 100644 --- a/src/main/resources/views/platform/zhgh/staffmanage/member/common/info/memberInfo.js +++ b/src/main/resources/views/platform/zhgh/staffmanage/member/common/info/memberInfo.js @@ -83,9 +83,35 @@ {{ viewData.positionLevel }} {{ viewData.administrativePositionLevel }} + + + `, store, + props: { + detailUrl: { + type: String, + default: "/platform/member/apply/common/findOne" + }, + idParam: { + type: String, + default: "id" + } + }, data() { return { viewData: {} @@ -93,9 +119,19 @@ }, methods: { onOpen(id) { - this.$axios.post("/platform/member/apply/common/findOne", { id }).then((res) => { + // 详情接口和主键参数由调用页面决定,保证公共查看组件可复用不同权限口径的数据源。 + const params = {} + params[this.idParam] = id + this.viewData = {} + this.$axios.post(this.detailUrl, params).then((res) => { if (res.code === 0) { this.viewData = res.data + this.$nextTick(() => { + // 查看页在隐藏容器中打开时,Element 表格需要重新计算列宽,避免子女信息列头纵向错位。 + if (this.$refs.childrenTable) { + this.$refs.childrenTable.doLayout() + } + }) } }) } diff --git a/src/main/resources/views/platform/zhgh/staffmanage/member/info/group/index.html b/src/main/resources/views/platform/zhgh/staffmanage/member/info/group/index.html index d6ab691..ec01b93 100644 --- a/src/main/resources/views/platform/zhgh/staffmanage/member/info/group/index.html +++ b/src/main/resources/views/platform/zhgh/staffmanage/member/info/group/index.html @@ -125,6 +125,7 @@ layout("/layouts/platform.html"){ { prop: "nation", label: "民族", width: 120, sortable: true}, { prop: "mobile", label: "联系电话", width: 120 }, { prop: "political", label: "政治面貌", width: 120, sortable: true}, + { prop: "education", label: "学历", width: 120, sortable: true, checked: 0 }, { prop: "userState", label: "在职状态", width: 120, sortable: true }, { prop: "personType", label: "教职工类别", width: 120, sortable: true }, { prop: "preparedBy", label: "编制类别", width: 120, sortable: true }, diff --git a/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/edit.html b/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/edit.html new file mode 100644 index 0000000..d6135a6 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/edit.html @@ -0,0 +1,226 @@ + +
+ +
+ 个人信息维护 +
+ + + + + + + + + + + 男 + 女 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 会员 + 非会员 + + + + + 福利会员 + 非福利会员 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 返回 + 保存 + +
+
+ + diff --git a/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/index.html b/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/index.html new file mode 100644 index 0000000..0680c70 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/staffmanage/member/info/maintain/index.html @@ -0,0 +1,176 @@ + +
+ + + + + +
+ + diff --git a/src/main/resources/views/platform/zhgh/staffmanage/member/statistics/summary/index.html b/src/main/resources/views/platform/zhgh/staffmanage/member/statistics/summary/index.html index 82589b4..9b895c9 100644 --- a/src/main/resources/views/platform/zhgh/staffmanage/member/statistics/summary/index.html +++ b/src/main/resources/views/platform/zhgh/staffmanage/member/statistics/summary/index.html @@ -42,7 +42,9 @@ layout("/layouts/platform.html"){ - + + 导出 + {