Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
beaf349136 | ||
|
|
e788899fcd | ||
|
|
adad1bfe84 | ||
|
|
37a9c579cd | ||
|
|
c5a8c39bec | ||
|
|
3aa5c3406a | ||
|
|
d5d804df2f | ||
|
|
0fb6ae65e7 | ||
|
|
4bc9d9f073 | ||
|
|
0083cfeed4 | ||
|
|
9c31bf8153 | ||
|
|
3304b9113f | ||
|
|
d72c86cc14 | ||
|
|
97def4472b |
@@ -2,7 +2,7 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.budwk</groupId>
|
||||
<artifactId>zhgh_jshvc</artifactId>
|
||||
<artifactId>mini</artifactId>
|
||||
<version>5.6.0-plus</version>
|
||||
<build>
|
||||
<resources>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
<groupId>com.budwk</groupId>
|
||||
<artifactId>zhgh_jshvc</artifactId>
|
||||
<artifactId>mini</artifactId>
|
||||
<version>5.6.0-plus</version>
|
||||
<properties>
|
||||
<nutzboot.version>2.6.0-SNAPSHOT</nutzboot.version>
|
||||
|
||||
@@ -140,7 +140,8 @@ public class FlowTodoCenterController {
|
||||
ins.createdAt,
|
||||
ins.variable ->> '$.instanceName' AS instanceName,
|
||||
GROUP_CONCAT(DISTINCT t.displayName) as taskName,
|
||||
cat.`name` categoryName
|
||||
cat.`name` categoryName,
|
||||
def.instanceViewUrl formKey
|
||||
FROM
|
||||
wf_process_instance ins
|
||||
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id and t.taskState = 10
|
||||
|
||||
@@ -197,6 +197,7 @@ public class Sys_user extends BaseModel implements Serializable {
|
||||
@Column
|
||||
@Comment("编制类别码")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
@DataCenterColumn(name = "编制类别码", key = "BZLXM")
|
||||
private String preparedBy;
|
||||
|
||||
@Column
|
||||
@@ -380,4 +381,9 @@ public class Sys_user extends BaseModel implements Serializable {
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
@DataCenterColumn(name = "党政职务", key = "DZZW")
|
||||
private String position;
|
||||
|
||||
@Column
|
||||
@Comment("是否同步")
|
||||
@ColDefine(type = ColType.CHAR, width = 1)
|
||||
private String isSyn;
|
||||
}
|
||||
|
||||
@@ -92,6 +92,6 @@ public class SysDataUnitPullServiceImpl extends BaseServiceImpl<Sys_unit> implem
|
||||
}
|
||||
log.info("本次拉取单位数据,新增{}条,更新{}条", insertList.size(), updateList.size());
|
||||
dao().insert(insertList);
|
||||
dao().update(updateList);
|
||||
dao().updateIgnoreNull(updateList);
|
||||
}
|
||||
}
|
||||
|
||||
+34
-17
@@ -121,22 +121,25 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
*/
|
||||
private boolean checkMembershipEligibility(String userState, String preparedBy, Date postDoctoralJoinDate) {
|
||||
// 博士后单独判断:只要进站时间在两年内就是会员
|
||||
if ("博士后".equals(preparedBy)) {
|
||||
if (postDoctoralJoinDate != null) {
|
||||
Date twoYearsAgo = DateUtil.offset(DateUtil.date(), DateField.YEAR, -2).toJdkDate();
|
||||
return postDoctoralJoinDate.after(twoYearsAgo);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// if ("博士后".equals(preparedBy)) {
|
||||
// if (postDoctoralJoinDate != null) {
|
||||
// Date twoYearsAgo = DateUtil.offset(DateUtil.date(), DateField.YEAR, -2).toJdkDate();
|
||||
// return postDoctoralJoinDate.after(twoYearsAgo);
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// 其他人员需要判断在岗状态和聘用方式
|
||||
if (!"在岗".equals(userState)) {
|
||||
return false;
|
||||
}
|
||||
// if (!"在岗".equals(userState) && !"在岗-里仁学院".equals(userState)) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// // 判断聘用方式
|
||||
// Set<String> memberPreparedByTypes = new HashSet<>(Arrays.asList("新人事代理", "校聘合同制", "事业编制"));
|
||||
// return memberPreparedByTypes.contains(preparedBy);
|
||||
|
||||
// 判断聘用方式
|
||||
Set<String> memberPreparedByTypes = new HashSet<>(Arrays.asList("新人事代理", "校聘合同制", "事业编制"));
|
||||
return memberPreparedByTypes.contains(preparedBy);
|
||||
|
||||
// 其他人员只需要判断在岗状态
|
||||
return "在岗".equals(userState) || "在岗-里仁学院".equals(userState);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,6 +188,9 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
for (Sys_user_source source : sources) {
|
||||
Sys_user user = userMap.get(source.getLoginname());
|
||||
|
||||
if (ObjectUtil.isNotEmpty(user) && ObjectUtil.isNotEmpty(user.getIsSyn()) && user.getIsSyn().equals("0")) {
|
||||
continue;
|
||||
}
|
||||
// 创建用户对象
|
||||
Sys_user u = new Sys_user();
|
||||
BeanUtils.copyProperties(source, u);
|
||||
@@ -195,20 +201,31 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
u.setSalt(salt);
|
||||
u.setPassword(PwdUtil.getPassword(PwdUtil.generate(12), salt));
|
||||
|
||||
if (u.getMember()) {
|
||||
// 判断是否为会员
|
||||
boolean isMember = checkMembershipEligibility(u.getUserState(), u.getPreparedBy(), u.getPostDoctoralJoinDate());
|
||||
u.setMember(isMember);
|
||||
|
||||
if (isMember) {
|
||||
addMemberUserIds.add(u.getId());
|
||||
}
|
||||
needInitUserList.add(u);
|
||||
|
||||
} else {
|
||||
// 修改现有用户
|
||||
u.setId(user.getId());
|
||||
|
||||
// 更新会员状态
|
||||
boolean currentIsMember = user.getMember() != null && user.getMember();
|
||||
boolean shouldBeMember = checkMembershipEligibility(u.getUserState(), u.getPreparedBy(), u.getPostDoctoralJoinDate());
|
||||
|
||||
if (!currentIsMember) {
|
||||
// 设置新的会员状态
|
||||
u.setMember(shouldBeMember);
|
||||
|
||||
if (shouldBeMember && !currentIsMember) {
|
||||
// 应该是会员但当前不是,需要添加
|
||||
addMemberUserIds.add(user.getId());
|
||||
} else{
|
||||
} else if (!shouldBeMember && currentIsMember) {
|
||||
// 不应该是会员但当前是,需要移除
|
||||
removeMemberUserIds.add(user.getId());
|
||||
}
|
||||
|
||||
|
||||
+27
-22
@@ -70,31 +70,36 @@ public class SysDataUserIncrUpdateServiceImpl implements SysDataUserUpdateServic
|
||||
|
||||
// 检查是否符合会员条件
|
||||
// 1. 用户状态为"在岗"
|
||||
if ("在岗".equals(sysUser.getUserState())) {
|
||||
// 2. 检查聘用方式条件
|
||||
List<String> membershipQualifyingPreparedBy = Arrays.asList("新人事代理", "校聘合同制", "事业编制");
|
||||
|
||||
// 第一种情况:聘用方式在指定列表中
|
||||
boolean isQualifiedByPreparedBy = membershipQualifyingPreparedBy.contains(sysUser.getPreparedBy());
|
||||
|
||||
// 第二种情况:聘用方式是博士后,且进站时间不超过两年
|
||||
boolean isQualifiedPostdoc = false;
|
||||
if ("博士后".equals(sysUser.getPreparedBy()) && sysUser.getPostDoctoralJoinDate() != null) {
|
||||
Calendar twoYearsAgo = Calendar.getInstance();
|
||||
twoYearsAgo.add(Calendar.YEAR, -2);
|
||||
Date twoYearsAgoDate = twoYearsAgo.getTime();
|
||||
isQualifiedPostdoc = sysUser.getPostDoctoralJoinDate().after(twoYearsAgoDate);
|
||||
}
|
||||
|
||||
// 如果满足任一条件,设置为会员
|
||||
if (isQualifiedByPreparedBy || isQualifiedPostdoc) {
|
||||
sysUser.setMember(true);
|
||||
} else {
|
||||
sysUser.setMember(false);
|
||||
}
|
||||
if ("在岗".equals(sysUser.getUserState()) || "在岗-里仁学院".equals(sysUser.getUserState())) {
|
||||
sysUser.setMember(true);
|
||||
} else {
|
||||
sysUser.setMember(false);
|
||||
}
|
||||
// if ("在岗".equals(sysUser.getUserState()) || "在岗-里仁学院".equals(sysUser.getUserState())) {
|
||||
// // 2. 检查聘用方式条件
|
||||
// List<String> membershipQualifyingPreparedBy = Arrays.asList("新人事代理", "校聘合同制", "事业编制");
|
||||
//
|
||||
// // 第一种情况:聘用方式在指定列表中
|
||||
// boolean isQualifiedByPreparedBy = membershipQualifyingPreparedBy.contains(sysUser.getPreparedBy());
|
||||
//
|
||||
// // 第二种情况:聘用方式是博士后,且进站时间不超过两年
|
||||
// boolean isQualifiedPostdoc = false;
|
||||
//// if ("博士后".equals(sysUser.getPreparedBy()) && sysUser.getPostDoctoralJoinDate() != null) {
|
||||
//// Calendar twoYearsAgo = Calendar.getInstance();
|
||||
//// twoYearsAgo.add(Calendar.YEAR, -2);
|
||||
//// Date twoYearsAgoDate = twoYearsAgo.getTime();
|
||||
//// isQualifiedPostdoc = sysUser.getPostDoctoralJoinDate().after(twoYearsAgoDate);
|
||||
//// }
|
||||
//
|
||||
// // 如果满足任一条件,设置为会员
|
||||
// if (isQualifiedByPreparedBy || isQualifiedPostdoc) {
|
||||
// sysUser.setMember(true);
|
||||
// } else {
|
||||
// sysUser.setMember(false);
|
||||
// }
|
||||
// } else {
|
||||
// sysUser.setMember(false);
|
||||
// }
|
||||
}
|
||||
|
||||
List<List<Sys_user>> splitSysUsers = ListUtil.split(sysUsers, 500);
|
||||
|
||||
@@ -57,6 +57,6 @@ public class SysUserAllRenewJob implements Job {
|
||||
param.setUpdateMode(SysDataUpdateMode.ALL.name());
|
||||
param.setConditionGroup(conditionGroup);
|
||||
|
||||
// sysDataUserUpdateService.update(param);
|
||||
sysDataUserUpdateService.update(param);
|
||||
}
|
||||
}
|
||||
|
||||
+55
@@ -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<ExcelExportEntity> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<Asset> {
|
||||
|
||||
@@ -12,4 +15,12 @@ public interface AssetService extends BaseService<Asset> {
|
||||
Sql getSql(AssetPageForm pageForm);
|
||||
|
||||
NutMap findOne(String id);
|
||||
|
||||
/**
|
||||
* 导入资产台账数据,负责 Excel 解析、字段转换、预计报废时间计算和折旧记录初始化。
|
||||
*
|
||||
* @param file 页面上传的资产 Excel 文件
|
||||
* @return 导入结果,包含成功数和失败明细
|
||||
*/
|
||||
ExcelImportRes<AssetImportExcelMode> importAssets(TempFile file);
|
||||
}
|
||||
|
||||
+281
@@ -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<Asset> implements AssetSer
|
||||
super(dao);
|
||||
}
|
||||
|
||||
@Inject
|
||||
private AssetDepreciationRecordService assetDepreciationRecordService;
|
||||
|
||||
@Inject
|
||||
private SysDictService sysDictService;
|
||||
|
||||
@Override
|
||||
public Sql getSql(AssetPageForm pageForm) {
|
||||
Sql sql = Sqls.create("""
|
||||
@@ -51,6 +75,7 @@ public class AssetServiceImpl extends BaseServiceImpl<Asset> implements AssetSer
|
||||
}
|
||||
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
|
||||
cnd.and("t1.assetUseUnionId", "=", SecurityUtil.getUnionId());
|
||||
cnd.and("t1.assetTypeCode", "=", "2");
|
||||
}
|
||||
cnd.andEX("t1.assetUseUnionId", "=", pageForm.getAssetUseUnionId());
|
||||
cnd.andEX("t1.assetCategoryId", "=", pageForm.getAssetCategoryId());
|
||||
@@ -83,4 +108,260 @@ public class AssetServiceImpl extends BaseServiceImpl<Asset> implements AssetSer
|
||||
Cnd.where("assetId", "=", id)));
|
||||
return nutMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入资产台账数据,逐行完成字段校验和新增,失败行不影响其他行入库。
|
||||
*
|
||||
* @param file 页面上传的资产 Excel 文件
|
||||
* @return 导入统计结果和失败明细
|
||||
*/
|
||||
@Override
|
||||
public ExcelImportRes<AssetImportExcelMode> importAssets(TempFile file) {
|
||||
List<AssetImportExcelMode> importRows = ExcelImportUtil.importExcel(file.getFile(), AssetImportExcelMode.class, new ImportParams());
|
||||
ExcelImportRes<AssetImportExcelMode> importRes = new ExcelImportRes<>();
|
||||
importRes.setTotalRecords(importRows.size());
|
||||
for (int i = 0; i < importRows.size(); i++) {
|
||||
AssetImportExcelMode row = importRows.get(i);
|
||||
List<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> options, List<String> 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<String> errors) {
|
||||
String text = StrUtil.trim(value);
|
||||
if (StrUtil.isBlank(text)) {
|
||||
errors.add(fieldName + "不能为空");
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -15,6 +15,7 @@ import com.budwk.app.sys.models.Sys_dict;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
import com.budwk.app.zhgh.staffmanage.childManage.models.ChildManage;
|
||||
import com.budwk.app.zhgh.staffmanage.childManage.service.ChildManageService;
|
||||
import com.budwk.app.zhgh.staffmanage.childManage.service.OnlyChildFeeService;
|
||||
import com.budwk.app.zhgh.staffmanage.childManage.vo.ChildManagePageForm;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@@ -50,6 +51,8 @@ public class ChildManageManageController {
|
||||
@Inject
|
||||
private ChildManageService childManageService;
|
||||
@Inject
|
||||
private OnlyChildFeeService onlyChildFeeService;
|
||||
@Inject
|
||||
private SysDictService sysDictService;
|
||||
|
||||
|
||||
@@ -125,6 +128,8 @@ public class ChildManageManageController {
|
||||
@SaCheckPermission(value = {"childManage.manage", "h5.childManage.manage"}, mode = SaMode.OR)
|
||||
@SLog(tag = "子女信息-我的填报", msg = "删除id: ${args[0]}")
|
||||
public Result doDelete(String id) {
|
||||
onlyChildFeeService.lockChildInformation();
|
||||
if (onlyChildFeeService.isChildLinked(id)) return Result.error("子女资料已关联资格申请,不能删除");
|
||||
childManageService.dao().delete(ChildManage.class, id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
+18
-1
@@ -10,6 +10,7 @@ import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.staffmanage.childManage.models.ChildManage;
|
||||
import com.budwk.app.zhgh.staffmanage.childManage.service.ChildManageService;
|
||||
import com.budwk.app.zhgh.staffmanage.childManage.service.OnlyChildFeeService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -32,6 +33,8 @@ public class ChildManageMineController {
|
||||
|
||||
@Inject
|
||||
private ChildManageService childManageService;
|
||||
@Inject
|
||||
private OnlyChildFeeService onlyChildFeeService;
|
||||
|
||||
@At("/index")
|
||||
@Ok("beetl:/platform/zhgh/staffmanage/childManage/mine/index.html")
|
||||
@@ -59,19 +62,33 @@ public class ChildManageMineController {
|
||||
cnd.andEX("YEAR(info.applyTime)", "=", year);
|
||||
// 使用 or 条件查询两个监护人字段
|
||||
cnd.and(Cnd.exps("info.guardianUserId1", "=", SecurityUtil.getUserId())
|
||||
.or("info.guardianUserId2", "=", SecurityUtil.getUserId()));
|
||||
.or("info.guardianUserId2", "=", SecurityUtil.getUserId())
|
||||
.or("info.guardianLoginName1", "=", currentLoginName())
|
||||
.or("info.guardianLoginName2", "=", currentLoginName()));
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = childManageService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
|
||||
private String currentLoginName() {
|
||||
com.budwk.app.sys.views.View_user user = childManageService.dao().fetch(com.budwk.app.sys.views.View_user.class,
|
||||
Cnd.where("id", "=", SecurityUtil.getUserId()));
|
||||
return user == null ? SecurityUtil.getUserId() : user.getLoginname();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("删除子女信息")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission(value = {"childManage.mine", "h5.childManage.mine"}, mode = SaMode.OR)
|
||||
@SLog(tag = "子女信息-我的填报", msg = "删除id: ${args[0]}")
|
||||
public Result doDelete(String id) {
|
||||
onlyChildFeeService.lockChildInformation();
|
||||
ChildManage child = childManageService.dao().fetch(ChildManage.class, id);
|
||||
if (child == null || !(SecurityUtil.getUserId().equals(child.getGuardianUserId1())
|
||||
|| SecurityUtil.getUserId().equals(child.getGuardianUserId2())
|
||||
|| currentLoginName().equals(child.getGuardianLoginName1()) || currentLoginName().equals(child.getGuardianLoginName2()))) return Result.error("只能删除本人子女资料");
|
||||
if (onlyChildFeeService.isChildLinked(id)) return Result.error("子女资料已关联资格申请,不能删除");
|
||||
childManageService.dao().delete(ChildManage.class, id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
+112
-3
@@ -15,10 +15,13 @@ import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.staffmanage.childManage.models.ChildManage;
|
||||
import com.budwk.app.zhgh.staffmanage.childManage.service.ChildManageService;
|
||||
import com.budwk.app.zhgh.staffmanage.childManage.service.OnlyChildFeeService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
@@ -30,6 +33,7 @@ import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/childManage/write")
|
||||
@@ -42,6 +46,8 @@ public class ChildManageWriteController {
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private ChildManageService childManageService;
|
||||
@Inject
|
||||
private OnlyChildFeeService onlyChildFeeService;
|
||||
|
||||
|
||||
@At("/index")
|
||||
@@ -57,14 +63,88 @@ public class ChildManageWriteController {
|
||||
|
||||
@At
|
||||
@ApiOperation("保存子女信息")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission(value = {"childManage.write", "h5.childManage.write"}, mode = SaMode.OR)
|
||||
@SLog(tag = "子女信息管理-信息填写", msg = "保存子女信息")
|
||||
public Result save(@Param("data") ChildManage childManage) {
|
||||
if(StrUtil.isBlank(childManage.getId())) childManage.setApplyTime(new Date());
|
||||
onlyChildFeeService.lockChildInformation();
|
||||
View_user current = dao.fetch(View_user.class, Cnd.where("id", "=", SecurityUtil.getUserId()));
|
||||
if (current == null) return Result.error("人员资料不存在");
|
||||
ChildManage old = StrUtil.isBlank(childManage.getId()) ? null : dao.fetch(ChildManage.class, childManage.getId());
|
||||
boolean manager = AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name());
|
||||
if (StrUtil.isNotBlank(childManage.getId()) && old == null) return Result.error("子女资料不存在");
|
||||
if (old != null && !manager && !current.getId().equals(old.getGuardianUserId1())
|
||||
&& !current.getId().equals(old.getGuardianUserId2())
|
||||
&& !current.getLoginname().equals(old.getGuardianLoginName1())
|
||||
&& !current.getLoginname().equals(old.getGuardianLoginName2())) return Result.error("只能维护本人子女资料");
|
||||
if (old != null && onlyChildFeeService.isChildInformationLocked(old.getId())) {
|
||||
if (criticalInformationChanged(old, childManage)) {
|
||||
return Result.error("该子女已关联待核验、已核定或已停发的独生子女费资格,关键身份和监护关系不可直接修改");
|
||||
}
|
||||
// 锁定记录仅更新日常资料,不重新带入人员身份,也不改写资格或清算快照。
|
||||
old.setChildrenGrade(childManage.getChildrenGrade());
|
||||
old.setChildrenYear(childManage.getChildrenYear());
|
||||
old.setChildrenCountry(childManage.getChildrenCountry());
|
||||
old.setChildrenCurrentSchool(childManage.getChildrenCurrentSchool());
|
||||
old.setChildrenMiddleScore(childManage.getChildrenMiddleScore());
|
||||
old.setChildrenSubjectCombination(childManage.getChildrenSubjectCombination());
|
||||
old.setChildrenHuKouAddress(childManage.getChildrenHuKouAddress());
|
||||
old.setGuardianMobile1(childManage.getGuardianMobile1());
|
||||
old.setGuardianMobile2(childManage.getGuardianMobile2());
|
||||
old.setGuardianUnitName2(childManage.getGuardianUnitName2());
|
||||
old.setNote(childManage.getNote());
|
||||
dao.update(old);
|
||||
return Result.success();
|
||||
}
|
||||
if (StrUtil.isBlank(childManage.getChildrenName()) || StrUtil.isBlank(childManage.getChildrenIdCard())
|
||||
|| childManage.getChildrenBirthday() == null || StrUtil.isBlank(childManage.getChildrenSex())) return Result.error("请补全子女姓名、身份证、生日和性别");
|
||||
if (childManage.getChildrenBirthday().after(new Date())) return Result.error("出生日期不能晚于今天");
|
||||
// 普通填报恢复原监护人表单,监护关系在资格申请或关键资料更正时填写。
|
||||
if (dao.count(ChildManage.class, Cnd.where("childrenIdCard", "=", childManage.getChildrenIdCard())
|
||||
.andEX("id", "!=", childManage.getId())) > 0) return Result.error("该子女已登记,请维护已有资料");
|
||||
// 人员身份由系统带入;双职工共用子女资料,资格分别申请。
|
||||
View_user first = old == null ? current : dao.fetch(View_user.class, Cnd.where("loginname", "=", old.getGuardianLoginName1()));
|
||||
if (first == null) return Result.error("第一监护人人员资料不存在");
|
||||
childManage.setGuardianUserId1(first.getId());
|
||||
childManage.setGuardianUserName1(first.getUsername());
|
||||
childManage.setGuardianLoginName1(first.getLoginname());
|
||||
childManage.setGuardianUnitId1(first.getUnitId());
|
||||
childManage.setGuardianUnitName1(first.getUnitName());
|
||||
childManage.setGuardianUnionId1(first.getUnionId());
|
||||
childManage.setGuardianUnionName1(first.getUnionName());
|
||||
if (old != null && !manager && !current.getId().equals(first.getId())) childManage.setGuardianLoginName2(current.getLoginname());
|
||||
childManage.setGuardianUserId2(null);
|
||||
if (StrUtil.isNotBlank(childManage.getGuardianLoginName2())) {
|
||||
View_user second = dao.fetch(View_user.class, Cnd.where("loginname", "=", childManage.getGuardianLoginName2()));
|
||||
if (second == null) return Result.error("另一位监护人工号不存在,本校以外监护人请勿填写工号");
|
||||
if (second.getId().equals(first.getId())) return Result.error("两位监护人不能为同一教职工");
|
||||
childManage.setGuardianUserId2(second.getId());
|
||||
childManage.setGuardianUserName2(second.getUsername());
|
||||
childManage.setGuardianUnitId2(second.getUnitId());
|
||||
childManage.setGuardianUnitName2(second.getUnitName());
|
||||
childManage.setGuardianUnionId2(second.getUnionId());
|
||||
childManage.setGuardianUnionName2(second.getUnionName());
|
||||
}
|
||||
childManage.setApplyTime(old == null ? new Date() : old.getApplyTime());
|
||||
dao.insertOrUpdate(childManage);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交子女关键资料更正,核验通过前不写入原始记录。
|
||||
* @param childManage 拟更正资料
|
||||
* @param reason 更正原因和证明依据
|
||||
* @return 申请提交结果
|
||||
*/
|
||||
@At @Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission(value = {"childManage.write", "h5.childManage.write"}, mode = SaMode.OR)
|
||||
public Result requestChange(@Param("data") ChildManage childManage, String reason) {
|
||||
try {
|
||||
onlyChildFeeService.requestChildChange(childManage, reason);
|
||||
return Result.success("更正申请已提交,核验通过前原资料保持不变");
|
||||
} catch (IllegalArgumentException e) { return Result.error(e.getMessage()); }
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("查询用户")
|
||||
@SaCheckPermission(value = {"childManage.write", "h5.childManage.write"}, mode = SaMode.OR)
|
||||
@@ -112,7 +192,6 @@ public class ChildManageWriteController {
|
||||
public Result getIsRepeatByIdCard(String idCard, String id) {
|
||||
int count = childManageService.dao().count(ChildManage.class,
|
||||
Cnd.where(ChildManage::getChildrenIdCard, "=", idCard)
|
||||
.and("YEAR(applyTime)", "=", DateUtil.thisYear())
|
||||
.andEX(ChildManage::getId, "!=", id));
|
||||
|
||||
|
||||
@@ -122,7 +201,37 @@ public class ChildManageWriteController {
|
||||
@At
|
||||
@SaCheckLogin
|
||||
public Result findOne(String id) {
|
||||
return Result.success(childManageService.dao().fetch(ChildManage.class, id));
|
||||
ChildManage child = childManageService.dao().fetch(ChildManage.class, id);
|
||||
if (child != null) {
|
||||
child.setQualificationLocked(onlyChildFeeService.isChildInformationLocked(id));
|
||||
child.setCorrectionHistory(onlyChildFeeService.childChanges().stream()
|
||||
.filter(change -> id.equals(change.getStr("childId"))).collect(java.util.stream.Collectors.toList()));
|
||||
}
|
||||
return Result.success(child);
|
||||
}
|
||||
|
||||
// 比较原始记录而非客户端锁定标记;日期按自然日比较,避免前端日期序列化造成误判。
|
||||
private boolean criticalInformationChanged(ChildManage old, ChildManage value) {
|
||||
return !sameText(old.getChildrenName(), value.getChildrenName())
|
||||
|| !sameText(old.getChildrenIdCard(), value.getChildrenIdCard())
|
||||
|| !sameBirthday(old.getChildrenBirthday(), value.getChildrenBirthday())
|
||||
|| !sameText(old.getChildrenSex(), value.getChildrenSex())
|
||||
|| !sameText(old.getGuardianUserId1(), value.getGuardianUserId1())
|
||||
|| !sameText(old.getGuardianUserId2(), value.getGuardianUserId2())
|
||||
|| !sameText(old.getGuardianUserName1(), value.getGuardianUserName1())
|
||||
|| !sameText(old.getGuardianUserName2(), value.getGuardianUserName2())
|
||||
|| !sameText(old.getGuardianLoginName1(), value.getGuardianLoginName1())
|
||||
|| !sameText(old.getGuardianLoginName2(), value.getGuardianLoginName2())
|
||||
|| !sameText(old.getGuardianRelationship1(), value.getGuardianRelationship1())
|
||||
|| !sameText(old.getGuardianRelationship2(), value.getGuardianRelationship2());
|
||||
}
|
||||
|
||||
private boolean sameText(String a, String b) {
|
||||
return Objects.equals(StrUtil.nullToEmpty(a), StrUtil.nullToEmpty(b));
|
||||
}
|
||||
|
||||
private boolean sameBirthday(Date a, Date b) {
|
||||
return a == null || b == null ? a == b : DateUtil.formatDate(a).equals(DateUtil.formatDate(b));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
package com.budwk.app.zhgh.staffmanage.childManage.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.*;
|
||||
import cn.hutool.json.*;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.zhgh.staffmanage.childManage.service.OnlyChildFeeService;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.*;
|
||||
import org.nutz.mvc.annotation.*;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/** 子女信息管理中的独生子女费入口,复用既有角色并在服务端校验办理权限。 */
|
||||
@IocBean
|
||||
@At("/platform/childManage/onlyChildFee")
|
||||
@Ok("json:full")
|
||||
public class OnlyChildFeeController {
|
||||
@Inject private OnlyChildFeeService onlyChildFeeService;
|
||||
|
||||
/** 展示独生子女费工作台。 */
|
||||
@SaCheckLogin
|
||||
@At("/index")
|
||||
@Ok("beetl:/platform/zhgh/staffmanage/childManage/onlyChildFee/index.html")
|
||||
public void index() { }
|
||||
|
||||
/** 展示资格登记独立页面,数据及办理权限由服务端按子菜单授权校验。 */
|
||||
@SaCheckLogin
|
||||
@At("/qualification/index")
|
||||
@Ok("beetl:/platform/zhgh/staffmanage/childManage/onlyChildFee/qualification.html")
|
||||
public void qualificationPage() { }
|
||||
|
||||
/** 展示年度清算独立页面,数据及办理权限由服务端按子菜单授权校验。 */
|
||||
@SaCheckLogin
|
||||
@At("/settlement/index")
|
||||
@Ok("beetl:/platform/zhgh/staffmanage/childManage/onlyChildFee/settlement.html")
|
||||
public void settlementPage() { }
|
||||
|
||||
/** 展示变动报表独立页面,数据及办理权限由服务端按子菜单授权校验。 */
|
||||
@SaCheckLogin
|
||||
@At("/changes/index")
|
||||
@Ok("beetl:/platform/zhgh/staffmanage/childManage/onlyChildFee/changes.html")
|
||||
public void changesPage() { }
|
||||
|
||||
/** 展示退休奖励独立页面,数据及办理权限由服务端按子菜单授权校验。 */
|
||||
@SaCheckLogin
|
||||
@At("/retirement/index")
|
||||
@Ok("beetl:/platform/zhgh/staffmanage/childManage/onlyChildFee/retirement.html")
|
||||
public void retirementPage() { }
|
||||
|
||||
/** 展示待遇台账独立页面,数据及办理权限由服务端按子菜单授权校验。 */
|
||||
@SaCheckLogin
|
||||
@At("/ledger/index")
|
||||
@Ok("beetl:/platform/zhgh/staffmanage/childManage/onlyChildFee/ledger.html")
|
||||
public void ledgerPage() { }
|
||||
|
||||
/** 获取人员资料及可见资格。@return 初始化数据 */
|
||||
@SaCheckLogin
|
||||
@At @POST
|
||||
public Result context() { return Result.success(onlyChildFeeService.context()); }
|
||||
|
||||
private Result result(Supplier<Object> work) {
|
||||
try { return Result.success(work.get()); }
|
||||
catch (IllegalArgumentException e) { return Result.error(e.getMessage()); }
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户可见的资料更正申请。
|
||||
* @return 更正记录及审核结果
|
||||
*/
|
||||
@SaCheckLogin @At @POST
|
||||
public Result childChanges() { return Result.success(onlyChildFeeService.childChanges()); }
|
||||
|
||||
/**
|
||||
* 核验资料更正并保存生效月份和审核凭据。
|
||||
* @param data 更正申请ID、核验结论和依据
|
||||
* @return 办理结果
|
||||
*/
|
||||
@SaCheckLogin @At @POST @Aop(TransAop.READ_COMMITTED)
|
||||
public Result reviewChildChange(String data) {
|
||||
return result(() -> { onlyChildFeeService.reviewChildChange(JSONUtil.parseObj(data)); return null; });
|
||||
}
|
||||
|
||||
/** 保存或提交本人资格。@param data 申请内容 @param submit 是否提交 @return 保存结果 */
|
||||
@SaCheckLogin
|
||||
@At @POST @Aop(TransAop.READ_COMMITTED)
|
||||
public Result save(String data, boolean submit) {
|
||||
return result(() -> onlyChildFeeService.save(JSONUtil.parseObj(data), submit));
|
||||
}
|
||||
|
||||
/** 核定起发月份和证明。@param data 核验内容 @return 办理结果 */
|
||||
@SaCheckLogin
|
||||
@At @POST @Aop(TransAop.READ_COMMITTED)
|
||||
public Result review(String data) {
|
||||
return result(() -> { onlyChildFeeService.review(JSONUtil.parseObj(data)); return null; });
|
||||
}
|
||||
|
||||
/** 登记资格终止或实际停发。@param data 生效月份及依据 @return 办理结果 */
|
||||
@SaCheckLogin
|
||||
@At @POST @Aop(TransAop.READ_COMMITTED)
|
||||
public Result stop(String data) {
|
||||
return result(() -> { onlyChildFeeService.stop(JSONUtil.parseObj(data)); return null; });
|
||||
}
|
||||
|
||||
/** 登记工资实发。@param data 月份、实发金额及来源 @return 保存结果 */
|
||||
@SaCheckLogin
|
||||
@At @POST @Aop(TransAop.READ_COMMITTED)
|
||||
public Result payment(String data) {
|
||||
return result(() -> { onlyChildFeeService.payment(JSONUtil.parseObj(data)); return null; });
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存人员首次实际发放月份并同步重算各年度未确认草稿。
|
||||
* @param data 领取人、当前办理年度及首次实际发放月份
|
||||
* @return 保存结果;已确认批次或无办理权限时返回失败原因
|
||||
*/
|
||||
@SaCheckLogin
|
||||
@At @POST @Aop(TransAop.READ_COMMITTED)
|
||||
public Result paymentStart(String data) {
|
||||
return result(() -> { onlyChildFeeService.paymentStart(JSONUtil.parseObj(data)); return null; });
|
||||
}
|
||||
|
||||
/** 获取清算快照。@param year 清算年度 @return 快照和执行记录 */
|
||||
@SaCheckLogin
|
||||
@At @POST @Aop(TransAop.READ_COMMITTED)
|
||||
public Result settlement(int year) {
|
||||
// 转为原生集合及空值,避免 Hutool JSONNull 被 Nutz 序列化为对象,导致前端金额计算出现 NaN。
|
||||
return result(() -> org.nutz.json.Json.fromJson(java.util.Map.class, onlyChildFeeService.settlement(year).toString()));
|
||||
}
|
||||
|
||||
/** 生成或确认清算批次。@param year 清算年度 @param confirm 是否确认 @return 办理结果 */
|
||||
@SaCheckLogin
|
||||
@At @POST @Aop(TransAop.READ_COMMITTED)
|
||||
public Result batch(int year, boolean confirm) {
|
||||
return result(() -> { onlyChildFeeService.batch(year, confirm); return null; });
|
||||
}
|
||||
|
||||
/** 登记实际扣回。@param data 归属月份和实际执行凭据 @return 保存结果 */
|
||||
@SaCheckLogin
|
||||
@At @POST @Aop(TransAop.READ_COMMITTED)
|
||||
public Result recover(String data) {
|
||||
return result(() -> { onlyChildFeeService.recover(JSONUtil.parseObj(data)); return null; });
|
||||
}
|
||||
|
||||
/** 查询原退休奖励申请。@return 申请、审批与支付状态 */
|
||||
@SaCheckLogin
|
||||
@At @POST
|
||||
public Result retirement() { return result(() -> onlyChildFeeService.retirement()); }
|
||||
|
||||
/** 登记退休奖励发放。@param data 原申请及核实凭据 @return 保存结果 */
|
||||
@SaCheckLogin
|
||||
@At @POST @Aop(TransAop.READ_COMMITTED)
|
||||
public Result reward(String data) {
|
||||
return result(() -> { onlyChildFeeService.reward(JSONUtil.parseObj(data)); return null; });
|
||||
}
|
||||
|
||||
/** 按当前筛选导出原退休奖励申请与实际发放记录。
|
||||
* @param year 退休年度,可为空
|
||||
* @param unit 单位名称
|
||||
* @param keyword 姓名或工号
|
||||
* @param approval 审核状态
|
||||
* @param paidStatus 发放状态
|
||||
* @param response 下载响应
|
||||
*/
|
||||
@SaCheckLogin
|
||||
@At @Ok("void")
|
||||
public void exportReward(String year, String unit, String keyword, String approval, String paidStatus, HttpServletResponse response) {
|
||||
JSONArray list = onlyChildFeeService.retirement();
|
||||
Workbook book = new XSSFWorkbook(); Sheet sheet = book.createSheet("退休奖励名单");
|
||||
String[] labels = {"工号", "姓名", "单位", "退休日期", "奖励金额", "审核状态", "发放状态", "发放日期", "发放凭据", "历史核查依据"};
|
||||
Row header = sheet.createRow(0);
|
||||
for (int i = 0; i < labels.length; i++) { header.createCell(i).setCellValue(labels[i]); sheet.setColumnWidth(i, 22 * 256); }
|
||||
int index = 1;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
JSONObject r = list.getJSONObject(i), payment = r.getJSONObject("payment");
|
||||
String date = r.getDate("retireTime") == null ? "" : new java.text.SimpleDateFormat("yyyy-MM-dd").format(r.getDate("retireTime"));
|
||||
String state = r.getBool("approved") ? "已通过" : "未通过审批", paid = payment == null ? "未发放" : "已发放";
|
||||
if (year != null && !year.isEmpty() && !date.startsWith(year)) continue;
|
||||
if (unit != null && !unit.isEmpty() && !unit.equals(r.getStr("unitName"))) continue;
|
||||
if (keyword != null && !keyword.isEmpty() && !(r.getStr("userName") + r.getStr("loginName")).contains(keyword)) continue;
|
||||
if (approval != null && !approval.isEmpty() && !approval.equals(state)) continue;
|
||||
if (paidStatus != null && !paidStatus.isEmpty() && !paidStatus.equals(paid)) continue;
|
||||
String[] values = {r.getStr("loginName"),r.getStr("userName"),r.getStr("unitName"),date,"3000.00",state,paid,
|
||||
payment == null ? "" : payment.getStr("paidDate"),payment == null ? "" : payment.getStr("voucher"),payment == null ? "待核实" : payment.getStr("historyBasis")};
|
||||
Row row = sheet.createRow(index++);
|
||||
for (int j = 0; j < values.length; j++) row.createCell(j).setCellValue(values[j]);
|
||||
}
|
||||
CommonDownloadUtil.download("退休奖励名单.xlsx", book, response);
|
||||
}
|
||||
|
||||
/** 导出当前筛选的完整变动表,工作簿只含一个工作表。
|
||||
* @param year 年度
|
||||
* @param unit 单位名称
|
||||
* @param keyword 姓名或工号
|
||||
* @param type 变动类型(停发或扣回)
|
||||
* @param response 下载响应
|
||||
*/
|
||||
@SaCheckLogin
|
||||
@At @Ok("void") @Aop(TransAop.READ_COMMITTED)
|
||||
public void export(int year, String unit, String keyword, String type, HttpServletResponse response) {
|
||||
JSONObject b = onlyChildFeeService.settlement(year).getJSONObject("batch");
|
||||
if (b == null || "草稿".equals(b.getStr("status"))) throw new IllegalArgumentException("请先复核确认清算批次");
|
||||
Workbook book = new XSSFWorkbook(); Sheet sheet = book.createSheet("变动表");
|
||||
String[] labels = {"序号", "工号", "姓名", "单位", "调前标准", "调后标准", "扣发金额", "补发金额", "停发时间", "备注说明"};
|
||||
String periodStart = b.getStr("periodStart"), periodEnd = b.getStr("periodEnd");
|
||||
sheet.createRow(0).createCell(0).setCellValue((year - 1) + "年11月—" + year + "年10月独生子女父母奖励费变动通知单");
|
||||
sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(0, 0, 0, 9));
|
||||
Row recipient = sheet.createRow(1);
|
||||
recipient.createCell(0).setCellValue("人力资源处:"); recipient.createCell(4).setCellValue("单位:元");
|
||||
sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(1, 1, 0, 3));
|
||||
sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(1, 1, 4, 9));
|
||||
Row header = sheet.createRow(2);
|
||||
int[] widths = {6, 12, 12, 28, 8, 8, 10, 8, 24, 14};
|
||||
for (int i = 0; i < labels.length; i++) {
|
||||
header.createCell(i).setCellValue(i >= 4 && i <= 7 ? labels[i].substring(0, 2) + "\n" + labels[i].substring(2) : labels[i]);
|
||||
sheet.setColumnWidth(i, widths[i] * 256);
|
||||
}
|
||||
int index = 3;
|
||||
BigDecimal[] totals = {BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO};
|
||||
JSONArray rows = b.getJSONArray("rows");
|
||||
for (int i = 0; i < rows.size(); i++) {
|
||||
JSONObject r = rows.getJSONObject(i);
|
||||
String end = r.getStr("endMonth", r.getStr("stopMonth"));
|
||||
if (end.compareTo(r.getStr("stopMonth")) > 0) end = r.getStr("stopMonth");
|
||||
boolean stop = end.compareTo(periodStart) >= 0 && end.compareTo(periodEnd) <= 0;
|
||||
BigDecimal deduction = noticeDeduction(r, periodStart, periodEnd);
|
||||
if (!stop && deduction.signum() == 0) continue;
|
||||
if (unit != null && !unit.isEmpty() && !unit.equals(r.getStr("unitName"))) continue;
|
||||
if (keyword != null && !keyword.isEmpty() && !(r.getStr("userName") + r.getStr("loginName")).contains(keyword)) continue;
|
||||
if ("停发".equals(type) && !stop || "扣回".equals(type) && deduction.signum() == 0) continue;
|
||||
Row row = sheet.createRow(index++);
|
||||
boolean stopped = end.compareTo(periodEnd) <= 0;
|
||||
String note = r.getStr("stopBasis");
|
||||
if (note == null || note.isEmpty()) note = stopped ? "停发" : "扣发调整";
|
||||
String[] values = {String.valueOf(index - 3), r.getStr("loginName"), r.getStr("userName"), r.getStr("unitName"), "10", stopped ? "0" : "10",
|
||||
deduction.toPlainString(), "0", stopped ? end.replace('-', '.') + "-" + year + ".10" : "—", note};
|
||||
for (int j = 0; j < values.length; j++) {
|
||||
Cell cell = row.createCell(j);
|
||||
if (j >= 4 && j <= 7) {
|
||||
cell.setCellValue(Double.parseDouble(values[j]));
|
||||
totals[j - 4] = totals[j - 4].add(new BigDecimal(values[j]));
|
||||
} else cell.setCellValue(values[j]);
|
||||
}
|
||||
}
|
||||
Row total = sheet.createRow(index); total.createCell(0).setCellValue("合计");
|
||||
sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(index, index, 0, 3));
|
||||
for (int i = 0; i < totals.length; i++) total.createCell(i + 4).setCellValue(totals[i].doubleValue());
|
||||
|
||||
// 通知单与页面使用同一纸质表样:黑色边框、金额合计、收件单位及独立签字栏。
|
||||
Font font = book.createFont(); font.setFontName("宋体"); font.setFontHeightInPoints((short) 12);
|
||||
CellStyle bodyStyle = book.createCellStyle(); bodyStyle.setFont(font);
|
||||
bodyStyle.setAlignment(HorizontalAlignment.CENTER); bodyStyle.setVerticalAlignment(VerticalAlignment.CENTER); bodyStyle.setWrapText(true);
|
||||
bodyStyle.setBorderTop(BorderStyle.THIN); bodyStyle.setBorderBottom(BorderStyle.THIN);
|
||||
bodyStyle.setBorderLeft(BorderStyle.THIN); bodyStyle.setBorderRight(BorderStyle.THIN);
|
||||
CellStyle amountStyle = book.createCellStyle(); amountStyle.cloneStyleFrom(bodyStyle);
|
||||
amountStyle.setDataFormat(book.createDataFormat().getFormat("0.00"));
|
||||
Font boldFont = book.createFont(); boldFont.setFontName("宋体"); boldFont.setFontHeightInPoints((short) 12); boldFont.setBold(true);
|
||||
CellStyle headerStyle = book.createCellStyle(); headerStyle.cloneStyleFrom(bodyStyle); headerStyle.setFont(boldFont);
|
||||
Font titleFont = book.createFont(); titleFont.setFontName("宋体"); titleFont.setFontHeightInPoints((short) 18); titleFont.setBold(true);
|
||||
CellStyle titleStyle = book.createCellStyle(); titleStyle.cloneStyleFrom(bodyStyle); titleStyle.setFont(titleFont);
|
||||
CellStyle leftStyle = book.createCellStyle(); leftStyle.cloneStyleFrom(bodyStyle); leftStyle.setAlignment(HorizontalAlignment.LEFT);
|
||||
CellStyle rightStyle = book.createCellStyle(); rightStyle.cloneStyleFrom(bodyStyle); rightStyle.setAlignment(HorizontalAlignment.RIGHT);
|
||||
for (int i = 0; i <= index; i++) {
|
||||
Row row = sheet.getRow(i); row.setHeightInPoints(i == 0 ? 36 : i == 2 ? 42 : 32);
|
||||
for (int j = 0; j < labels.length; j++) {
|
||||
Cell cell = row.getCell(j); if (cell == null) cell = row.createCell(j);
|
||||
cell.setCellStyle(i == 0 ? titleStyle : i == 1 ? (j < 4 ? leftStyle : rightStyle)
|
||||
: i == 2 ? headerStyle : j >= 4 && j <= 7 ? amountStyle : bodyStyle);
|
||||
}
|
||||
}
|
||||
CellStyle signStyle = book.createCellStyle(); signStyle.setFont(font); signStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
Row signatures = sheet.createRow(index + 1); signatures.setHeightInPoints(54);
|
||||
signatures.createCell(0).setCellValue("审核人:"); signatures.getCell(0).setCellStyle(signStyle);
|
||||
signatures.createCell(4).setCellValue("经办人:"); signatures.getCell(4).setCellStyle(signStyle);
|
||||
sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(index + 1, index + 1, 0, 3));
|
||||
sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(index + 1, index + 1, 4, 9));
|
||||
sheet.setFitToPage(true); sheet.setHorizontallyCenter(true);
|
||||
sheet.getPrintSetup().setLandscape(true); sheet.getPrintSetup().setPaperSize(PrintSetup.A4_PAPERSIZE);
|
||||
sheet.getPrintSetup().setFitWidth((short) 1); sheet.getPrintSetup().setFitHeight((short) 0);
|
||||
book.setPrintArea(0, 0, 9, 0, index + 1);
|
||||
CommonDownloadUtil.download(year + "年独生子女费变动表.xlsx", book, response);
|
||||
}
|
||||
|
||||
// 按确认快照逐月汇总多发额,已扣回金额不冲减通知单,避免结清后扣发额变成零。
|
||||
private BigDecimal noticeDeduction(JSONObject row, String periodStart, String periodEnd) {
|
||||
JSONArray months = row.getJSONArray("months");
|
||||
if (months == null || months.isEmpty()) throw new IllegalArgumentException("清算明细不完整,无法导出通知单");
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (int i = 0; i < months.size(); i++) {
|
||||
JSONObject month = months.getJSONObject(i);
|
||||
String value = month.getStr("month");
|
||||
if (value.compareTo(periodStart) < 0 || value.compareTo(periodEnd) > 0) continue;
|
||||
BigDecimal paid = month.getBigDecimal("paid"), due = month.getBigDecimal("due");
|
||||
if (paid == null || due == null) throw new IllegalArgumentException("清算实发数据不完整,无法导出通知单");
|
||||
total = total.add(paid.subtract(due).max(BigDecimal.ZERO));
|
||||
}
|
||||
return total;
|
||||
}
|
||||
}
|
||||
@@ -36,12 +36,23 @@ public class ChildManage extends BaseModel implements Serializable {
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
// 由关联资格实时计算,仅用于页面展示,不作为数据库字段或保存依据。
|
||||
private boolean qualificationLocked;
|
||||
|
||||
// 更正申请和审核结果仅随详情返回,不覆盖子女原始字段。
|
||||
private List<JSONObject> correctionHistory;
|
||||
|
||||
|
||||
@Column
|
||||
@ColDefine(type = ColType.VARCHAR,width = 32)
|
||||
@Comment("guardianUserId1")
|
||||
private String guardianUserId1;
|
||||
|
||||
@Column
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
@Comment("第一监护人与子女关系")
|
||||
private String guardianRelationship1;
|
||||
|
||||
@Column
|
||||
@ColDefine(type = ColType.VARCHAR,width = 30)
|
||||
@Comment("监护人1(教工)姓名")
|
||||
@@ -82,6 +93,11 @@ public class ChildManage extends BaseModel implements Serializable {
|
||||
@Comment("guardianUserId2")
|
||||
private String guardianUserId2;
|
||||
|
||||
@Column
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
@Comment("第二监护人与子女关系")
|
||||
private String guardianRelationship2;
|
||||
|
||||
@Column
|
||||
@ColDefine(type = ColType.VARCHAR,width = 30)
|
||||
@Comment("监护人2(教工)姓名")
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.budwk.app.zhgh.staffmanage.childManage.models;
|
||||
|
||||
import lombok.Data;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
import java.util.Date;
|
||||
|
||||
/** 独生子女费业务凭据,按资格、月记录、清算快照和实际支付分别保存。 */
|
||||
@Data
|
||||
@Table("only_child_fee_record")
|
||||
public class OnlyChildFeeRecord {
|
||||
@Name
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String id;
|
||||
@Column
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String kind;
|
||||
@Column
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String userId;
|
||||
@Column
|
||||
@ColDefine(type = ColType.VARCHAR, width = 10)
|
||||
private String period;
|
||||
@Column
|
||||
@ColDefine(type = ColType.TEXT)
|
||||
private String content;
|
||||
@Column
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String operatorId;
|
||||
@Column
|
||||
private Date recordedAt;
|
||||
}
|
||||
+686
@@ -0,0 +1,686 @@
|
||||
package com.budwk.app.zhgh.staffmanage.childManage.service;
|
||||
|
||||
import cn.hutool.json.*;
|
||||
import com.budwk.app.sys.services.SysUserService;
|
||||
import com.budwk.app.sys.models.Sys_menu;
|
||||
import com.budwk.app.sys.views.View_user;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.staffmanage.childManage.models.*;
|
||||
import com.budwk.app.zhgh.staffbenefit.dsznfmtx.models.Dsznfmtx;
|
||||
import com.budwk.app.flow.entity.ProcessInstance;
|
||||
import org.nutz.dao.*;
|
||||
import org.nutz.ioc.loader.annotation.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.*;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
/** 月费核定、逐月清算和退休奖励登记;所有写操作由控制器事务统一保护。 */
|
||||
@IocBean
|
||||
public class OnlyChildFeeService {
|
||||
@Inject private Dao dao;
|
||||
@Inject private SysUserService sysUserService;
|
||||
private static final BigDecimal TEN = new BigDecimal("10.00");
|
||||
|
||||
// 页签与菜单使用同一授权来源,角色名称本身不再授予办理权限。
|
||||
private List<String> tabs() {
|
||||
Set<String> permissions = new HashSet<>();
|
||||
for (Sys_menu menu : sysUserService.getMenus(SecurityUtil.getUserId())) {
|
||||
permissions.add(menu.getPermission());
|
||||
}
|
||||
List<String> result = new ArrayList<>();
|
||||
for (String tab : Arrays.asList("qualification", "settlement", "changes", "retirement", "ledger")) {
|
||||
if (permissions.contains("childManage.onlyChildFee." + tab)) result.add(tab);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void require(boolean valid, String message) {
|
||||
if (!valid) throw new IllegalArgumentException(message);
|
||||
}
|
||||
|
||||
private void access(String... allowedTabs) {
|
||||
List<String> granted = tabs();
|
||||
require(Arrays.stream(allowedTabs).anyMatch(granted::contains), "未分配对应的独生子女费子菜单权限");
|
||||
}
|
||||
|
||||
private void lock() {
|
||||
dao.execute(Sqls.create("INSERT IGNORE INTO only_child_fee_record (id,kind,content) VALUES ('LOCK','LOCK','{}')"));
|
||||
dao.execute(Sqls.create("SELECT id FROM only_child_fee_record WHERE id='LOCK' FOR UPDATE"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 子女保存、删除与资格办理共用事务锁,避免检查后资格状态发生变化。
|
||||
* 调用方必须处于数据库事务中。
|
||||
*/
|
||||
public void lockChildInformation() {
|
||||
lock();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断任一监护人关联的资格是否要求锁定子女关键资料。
|
||||
*
|
||||
* @param childId 子女记录ID
|
||||
* @return 存在待核验、已核定或已停发资格时返回true
|
||||
*/
|
||||
public boolean isChildInformationLocked(String childId) {
|
||||
if (childId == null) return false;
|
||||
return records("QUALIFICATION").stream().anyMatch(q -> childId.equals(q.getStr("childId"))
|
||||
&& Arrays.asList("待核验", "已核定", "已停发").contains(q.getStr("status")));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断子女是否已经被资格申请引用,保留申请与历史依据的关联。
|
||||
*
|
||||
* @param childId 子女记录ID
|
||||
* @return 任一监护人存在关联申请时返回true
|
||||
*/
|
||||
public boolean isChildLinked(String childId) {
|
||||
return childId != null && records("QUALIFICATION").stream()
|
||||
.anyMatch(q -> childId.equals(q.getStr("childId")));
|
||||
}
|
||||
|
||||
private JSONObject childIdentity(ChildManage c) {
|
||||
return new JSONObject().set("childrenName", c.getChildrenName()).set("childrenIdCard", c.getChildrenIdCard())
|
||||
.set("childrenBirthday", c.getChildrenBirthday() == null ? null : new SimpleDateFormat("yyyy-MM-dd").format(c.getChildrenBirthday()))
|
||||
.set("childrenSex", c.getChildrenSex()).set("guardianUserId1", c.getGuardianUserId1())
|
||||
.set("guardianUserName1", c.getGuardianUserName1()).set("guardianLoginName1", c.getGuardianLoginName1())
|
||||
.set("guardianRelationship1", c.getGuardianRelationship1()).set("guardianUserId2", c.getGuardianUserId2())
|
||||
.set("guardianUserName2", c.getGuardianUserName2()).set("guardianLoginName2", c.getGuardianLoginName2())
|
||||
.set("guardianRelationship2", c.getGuardianRelationship2());
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交关键资料更正申请,保存前后快照,审批前不修改原始资料。
|
||||
*
|
||||
* @param proposed 拟更正的子女及监护人资料
|
||||
* @param reason 更正原因和证明依据说明
|
||||
*/
|
||||
public void requestChildChange(ChildManage proposed, String reason) {
|
||||
lock();
|
||||
ChildManage old = dao.fetch(ChildManage.class, proposed.getId());
|
||||
require(old != null && isChildInformationLocked(old.getId()), "该记录无需更正审批,请通过普通保存维护");
|
||||
String user = SecurityUtil.getUserId();
|
||||
View_user profile = dao.fetch(View_user.class, Cnd.where("id", "=", user));
|
||||
require(profile != null, "人员资料不存在");
|
||||
require(tabs().contains("ledger") || user.equals(old.getGuardianUserId1()) || user.equals(old.getGuardianUserId2())
|
||||
|| profile.getLoginname().equals(old.getGuardianLoginName1()) || profile.getLoginname().equals(old.getGuardianLoginName2()), "只能申请更正本人子女资料");
|
||||
require(reason != null && !reason.trim().isEmpty(), "请填写更正原因及证明依据");
|
||||
for (JSONObject change : records("CHILD_CHANGE")) {
|
||||
require(!old.getId().equals(change.getStr("childId")) || !"待核验".equals(change.getStr("status")), "该子女已有待核验的更正申请");
|
||||
}
|
||||
JSONObject after = childIdentity(proposed);
|
||||
for (String field : Arrays.asList("childrenName", "childrenIdCard", "childrenBirthday", "childrenSex", "guardianLoginName1", "guardianRelationship1")) text(after, field);
|
||||
require(!LocalDate.parse(after.getStr("childrenBirthday")).isAfter(LocalDate.now()), "出生日期不能晚于今天");
|
||||
require(Arrays.asList("男", "女").contains(after.getStr("childrenSex")), "请选择有效性别");
|
||||
for (int i = 1; i <= 2; i++) {
|
||||
String login = after.getStr("guardianLoginName" + i, "");
|
||||
if (!login.isEmpty()) {
|
||||
View_user guardian = dao.fetch(View_user.class, Cnd.where("loginname", "=", login));
|
||||
require(guardian != null, "监护人工号不存在");
|
||||
after.set("guardianUserId" + i, guardian.getId()).set("guardianUserName" + i, guardian.getUsername());
|
||||
} else after.set("guardianUserId" + i, null);
|
||||
if (!after.getStr("guardianUserName" + i, "").isEmpty()) text(after, "guardianRelationship" + i);
|
||||
}
|
||||
require(!Objects.equals(after.getStr("guardianUserId1"), after.getStr("guardianUserId2")), "两位监护人不能为同一教职工");
|
||||
JSONObject before = childIdentity(old);
|
||||
require(!before.equals(after), "关键资料没有变化");
|
||||
String id = UUID.randomUUID().toString();
|
||||
JSONObject change = new JSONObject().set("id", id).set("childId", old.getId()).set("userId", user)
|
||||
.set("applicant", profile.getUsername()).set("reason", reason).set("oldData", before).set("newData", after)
|
||||
.set("status", "待核验").set("submittedAt", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
|
||||
put(id, "CHILD_CHANGE", user, null, change);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询更正申请的办理记录,教职工仅可查看本人提交的申请。
|
||||
*
|
||||
* @return 更正前后资料、原因、核验结果和生效月份
|
||||
*/
|
||||
public List<JSONObject> childChanges() {
|
||||
boolean reviewer = tabs().contains("ledger");
|
||||
List<JSONObject> list = records("CHILD_CHANGE");
|
||||
list.removeIf(c -> !reviewer && !SecurityUtil.getUserId().equals(c.getStr("userId")));
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 核验并批准或退回更正,按生效月保留待遇历史版本,不改写已确认清算快照。
|
||||
*
|
||||
* @param input 申请ID、核验结论、审核依据和生效月份
|
||||
*/
|
||||
public void reviewChildChange(JSONObject input) {
|
||||
access("ledger"); lock();
|
||||
OnlyChildFeeRecord record = dao.fetch(OnlyChildFeeRecord.class, input.getStr("id"));
|
||||
require(record != null && "CHILD_CHANGE".equals(record.getKind()), "更正申请不存在");
|
||||
JSONObject change = get(input.getStr("id"));
|
||||
require(change != null && change.getJSONObject("oldData") != null && "待核验".equals(change.getStr("status")), "更正申请不存在或已处理");
|
||||
require(!SecurityUtil.getUserId().equals(change.getStr("userId")), "更正申请须由另一位授权经办人核验");
|
||||
text(input, "basis");
|
||||
String status = input.getStr("status");
|
||||
require(Arrays.asList("已批准", "退回").contains(status), "请选择批准或退回");
|
||||
if ("已批准".equals(status)) {
|
||||
require(Boolean.TRUE.equals(input.getBool("verified")), "请核实更正证明及双方监护人的领取资格");
|
||||
YearMonth effective = month(input.getStr("effectiveMonth"));
|
||||
require(!effective.isAfter(YearMonth.now()), "生效月份不能晚于当前月份");
|
||||
for (JSONObject b : records("BATCH")) {
|
||||
require("草稿".equals(b.getStr("status")) || effective.isAfter(YearMonth.of(b.getInt("year"), 10)), "生效月份不能覆盖已确认清算期间");
|
||||
}
|
||||
ChildManage child = dao.fetch(ChildManage.class, change.getStr("childId"));
|
||||
require(child != null && childIdentity(child).equals(change.getJSONObject("oldData")), "原始关键资料已变化,请退回后重新申请");
|
||||
JSONObject after = change.getJSONObject("newData");
|
||||
require(dao.count(ChildManage.class, Cnd.where("childrenIdCard", "=", after.getStr("childrenIdCard")).and("id", "!=", child.getId())) == 0, "更正后的身份证号已被其他子女记录使用");
|
||||
List<JSONObject> affected = new ArrayList<>();
|
||||
for (JSONObject q : records("QUALIFICATION")) {
|
||||
if (!child.getId().equals(q.getStr("childId"))) continue;
|
||||
JSONArray history = q.getJSONArray("benefitHistory");
|
||||
if (history == null) history = new JSONArray();
|
||||
if (!history.isEmpty()) require(!effective.isBefore(month(history.getJSONObject(history.size() - 1).getStr("untilMonth"))), "生效月份不能早于上次更正");
|
||||
JSONObject previous = new JSONObject(q.toString()); previous.remove("benefitHistory");
|
||||
history.add(previous.set("untilMonth", effective.toString()));
|
||||
q.set("benefitHistory", history).set("childName", after.getStr("childrenName"))
|
||||
.set("birthday", after.getStr("childrenBirthday"))
|
||||
.set("stopMonth", YearMonth.from(LocalDate.parse(after.getStr("childrenBirthday")).plusYears(18)).toString())
|
||||
.set("changeId", change.getStr("id")).set("changeEffectiveMonth", effective.toString());
|
||||
boolean first = q.getStr("userId").equals(after.getStr("guardianUserId1"));
|
||||
boolean second = q.getStr("userId").equals(after.getStr("guardianUserId2"));
|
||||
boolean eligible = (first || second) && LocalDate.parse(after.getStr("childrenBirthday")).isBefore(LocalDate.of(2016, 1, 1));
|
||||
q.set("eligible", eligible).set("relationship", first ? after.getStr("guardianRelationship1") : second ? after.getStr("guardianRelationship2") : "已解除监护关联");
|
||||
if (q.getStr("startMonth") != null) q.set("status", eligible && q.getStr("endMonth") == null && month(q.getStr("stopMonth")).isAfter(YearMonth.now()) ? "已核定" : "已停发");
|
||||
else if (!eligible) q.set("status", "不通过").set("basis", "资料更正后不符合原领取条件");
|
||||
affected.add(q);
|
||||
}
|
||||
// 所有校验先于写入;仅应用批准的关键字段,保留等待审批期间维护的日常资料。
|
||||
child.setChildrenName(after.getStr("childrenName")); child.setChildrenIdCard(after.getStr("childrenIdCard"));
|
||||
child.setChildrenBirthday(java.sql.Date.valueOf(after.getStr("childrenBirthday"))); child.setChildrenSex(after.getStr("childrenSex"));
|
||||
child.setGuardianUserId1(after.getStr("guardianUserId1")); child.setGuardianUserName1(after.getStr("guardianUserName1"));
|
||||
child.setGuardianLoginName1(after.getStr("guardianLoginName1")); child.setGuardianRelationship1(after.getStr("guardianRelationship1"));
|
||||
child.setGuardianUserId2(after.getStr("guardianUserId2")); child.setGuardianUserName2(after.getStr("guardianUserName2"));
|
||||
child.setGuardianLoginName2(after.getStr("guardianLoginName2")); child.setGuardianRelationship2(after.getStr("guardianRelationship2"));
|
||||
View_user first = dao.fetch(View_user.class, Cnd.where("id", "=", child.getGuardianUserId1()));
|
||||
View_user second = child.getGuardianUserId2() == null ? null : dao.fetch(View_user.class, Cnd.where("id", "=", child.getGuardianUserId2()));
|
||||
require(first != null && (child.getGuardianUserId2() == null || second != null), "监护人人员资料已变化,请重新申请");
|
||||
child.setGuardianUnitId1(first.getUnitId()); child.setGuardianUnitName1(first.getUnitName());
|
||||
child.setGuardianUnionId1(first.getUnionId()); child.setGuardianUnionName1(first.getUnionName());
|
||||
child.setGuardianUnitId2(second == null ? null : second.getUnitId());
|
||||
if (second != null) child.setGuardianUnitName2(second.getUnitName());
|
||||
else if (change.getJSONObject("oldData").getStr("guardianUserId2") != null) child.setGuardianUnitName2(null);
|
||||
child.setGuardianUnionId2(second == null ? null : second.getUnionId()); child.setGuardianUnionName2(second == null ? null : second.getUnionName());
|
||||
dao.update(child);
|
||||
for (JSONObject q : affected) { put("Q:" + q.getStr("userId"), "QUALIFICATION", q.getStr("userId"), null, q); audit(q); }
|
||||
change.set("effectiveMonth", effective.toString());
|
||||
}
|
||||
change.set("status", status).set("reviewBasis", input.getStr("basis")).set("reviewer", SecurityUtil.getUserId())
|
||||
.set("reviewedAt", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
|
||||
put(change.getStr("id"), "CHILD_CHANGE", change.getStr("userId"), change.getStr("effectiveMonth"), change);
|
||||
}
|
||||
|
||||
private JSONObject get(String id) {
|
||||
OnlyChildFeeRecord r = dao.fetch(OnlyChildFeeRecord.class, id);
|
||||
return r == null ? null : JSONUtil.parseObj(r.getContent());
|
||||
}
|
||||
|
||||
private void put(String id, String kind, String user, String period, JSONObject data) {
|
||||
OnlyChildFeeRecord r = new OnlyChildFeeRecord();
|
||||
r.setId(id); r.setKind(kind); r.setUserId(user); r.setPeriod(period);
|
||||
r.setContent(data.toString()); r.setOperatorId(SecurityUtil.getUserId()); r.setRecordedAt(new Date());
|
||||
dao.insertOrUpdate(r);
|
||||
}
|
||||
|
||||
private List<JSONObject> records(String kind) {
|
||||
List<JSONObject> result = new ArrayList<>();
|
||||
for (OnlyChildFeeRecord r : dao.query(OnlyChildFeeRecord.class, Cnd.where("kind", "=", kind).asc("id"))) {
|
||||
result.add(JSONUtil.parseObj(r.getContent()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void text(JSONObject d, String key) {
|
||||
require(d.getStr(key) != null && !d.getStr(key).trim().isEmpty(), "请填写" + key);
|
||||
}
|
||||
|
||||
private YearMonth month(String value) {
|
||||
require(value != null && value.matches("\\d{4}-\\d{2}"), "月份格式应为 yyyy-MM");
|
||||
return YearMonth.parse(value);
|
||||
}
|
||||
|
||||
private BigDecimal money(JSONObject d, String key) {
|
||||
BigDecimal n = d.getBigDecimal(key);
|
||||
require(n != null && n.signum() >= 0 && n.scale() <= 2, "金额必须为非负数,最多两位小数");
|
||||
return n;
|
||||
}
|
||||
|
||||
private JSONObject qualification(String user) {
|
||||
JSONObject q = get("Q:" + user);
|
||||
require(q != null, "资格记录不存在");
|
||||
return q;
|
||||
}
|
||||
|
||||
private void audit(JSONObject data) {
|
||||
put(UUID.randomUUID().toString(), "AUDIT", data.getStr("userId"), null, data);
|
||||
}
|
||||
|
||||
/** 返回本人子女、人员资料与可见资格,历史子女不按填报年度限制。
|
||||
* @return 页面初始化数据
|
||||
*/
|
||||
public JSONObject context() {
|
||||
List<String> granted = tabs();
|
||||
if (granted.isEmpty()) return new JSONObject().set("tabs", granted);
|
||||
String user = SecurityUtil.getUserId();
|
||||
View_user profile = dao.fetch(View_user.class, Cnd.where("id", "=", user));
|
||||
List<ChildManage> children = dao.query(ChildManage.class,
|
||||
Cnd.where("guardianUserId1", "=", user).or("guardianUserId2", "=", user)
|
||||
.or("guardianLoginName1", "=", profile == null ? user : profile.getLoginname())
|
||||
.or("guardianLoginName2", "=", profile == null ? user : profile.getLoginname()));
|
||||
List<JSONObject> list = granted.contains("ledger") ? records("QUALIFICATION") : Collections.emptyList();
|
||||
for (JSONObject q : list) {
|
||||
JSONObject baseline = paymentBaseline(q.getStr("userId"));
|
||||
q.set("actualStartMonth", baseline == null ? "" : baseline.getStr("actualStartMonth"));
|
||||
}
|
||||
return new JSONObject().set("tabs", granted)
|
||||
.set("children", granted.contains("qualification") ? children : Collections.emptyList())
|
||||
.set("profile", granted.contains("qualification") ? profile : null)
|
||||
.set("qualifications", list).set("mine", granted.contains("qualification") ? get("Q:" + user) : null);
|
||||
}
|
||||
|
||||
/** 保存本人资格草稿或提交核验,不允许申请人设置核定结果。
|
||||
* @param input 关联子女、证明和监护关系
|
||||
* @param submit 是否提交审核
|
||||
* @return 保存后的资格
|
||||
*/
|
||||
public JSONObject save(JSONObject input, boolean submit) {
|
||||
access("qualification");
|
||||
lock();
|
||||
String user = SecurityUtil.getUserId();
|
||||
JSONObject old = get("Q:" + user);
|
||||
View_user profile = dao.fetch(View_user.class, Cnd.where("id", "=", user));
|
||||
require(profile != null, "人员资料不存在");
|
||||
require(old == null || Arrays.asList("草稿", "退回", "不通过").contains(old.getStr("status")), "当前资格不可重复申请");
|
||||
ChildManage child = dao.fetch(ChildManage.class, input.getStr("childId"));
|
||||
require(child != null && (user.equals(child.getGuardianUserId1()) || user.equals(child.getGuardianUserId2())
|
||||
|| profile.getLoginname().equals(child.getGuardianLoginName1()) || profile.getLoginname().equals(child.getGuardianLoginName2())), "请关联本人已提交的子女资料");
|
||||
require(child.getApplyTime() != null && child.getChildrenBirthday() != null
|
||||
&& child.getChildrenName() != null && child.getChildrenIdCard() != null && child.getChildrenSex() != null, "请先补全并提交子女信息");
|
||||
if (submit) {
|
||||
text(input, "certificateNumber"); text(input, "relationship");
|
||||
require(input.getJSONArray("proof") != null && !input.getJSONArray("proof").isEmpty(), "请上传独生子女证明");
|
||||
require(Boolean.TRUE.equals(input.getBool("onlyChild")), "需要明确声明独生子女资格并提交证明");
|
||||
}
|
||||
String birthday = new SimpleDateFormat("yyyy-MM-dd").format(child.getChildrenBirthday());
|
||||
require(LocalDate.parse(birthday).isBefore(LocalDate.of(2016, 1, 1)), "子女出生日期须早于2016-01-01");
|
||||
JSONObject q = new JSONObject().set("userId", user).set("userName", profile.getUsername())
|
||||
.set("loginName", profile.getLoginname()).set("unitId", profile.getUnitId())
|
||||
.set("unitName", profile.getUnitName()).set("unionName", profile.getUnionName())
|
||||
.set("childId", child.getId()).set("childName", child.getChildrenName()).set("birthday", birthday)
|
||||
.set("stopMonth", YearMonth.from(LocalDate.parse(birthday).plusYears(18)).toString())
|
||||
.set("certificateNumber", input.getStr("certificateNumber")).set("relationship", input.getStr("relationship"))
|
||||
.set("proof", input.getJSONArray("proof")).set("onlyChild", input.getBool("onlyChild"))
|
||||
.set("status", submit ? "待核验" : "草稿");
|
||||
put("Q:" + user, "QUALIFICATION", user, null, q);
|
||||
audit(q);
|
||||
return q;
|
||||
}
|
||||
|
||||
/** 核验资格并独立核定起发月,保存依据和经办人。
|
||||
* @param d 核验结论、起发月份及依据
|
||||
*/
|
||||
public void review(JSONObject d) {
|
||||
access("ledger"); lock();
|
||||
JSONObject q = qualification(d.getStr("userId"));
|
||||
require("待核验".equals(q.getStr("status")), "仅待核验申请可以审核");
|
||||
String status = d.getStr("status");
|
||||
require(Arrays.asList("已核定", "退回", "不通过").contains(status), "无效审核结论");
|
||||
text(d, "basis");
|
||||
if ("已核定".equals(status)) {
|
||||
require(Boolean.TRUE.equals(d.getBool("verified")), "须核验证明和重复领取情况");
|
||||
YearMonth start = month(d.getStr("startMonth"));
|
||||
require(!start.isBefore(YearMonth.from(LocalDate.parse(q.getStr("birthday"))))
|
||||
&& start.isBefore(month(q.getStr("stopMonth"))), "起发月份须在出生月至年龄停发月之前");
|
||||
q.set("startMonth", start.toString()).set("standard", TEN);
|
||||
}
|
||||
q.set("status", status).set("basis", d.getStr("basis")).set("reviewer", SecurityUtil.getUserId());
|
||||
put("Q:" + q.getStr("userId"), "QUALIFICATION", q.getStr("userId"), null, q); audit(q);
|
||||
if ("已核定".equals(status)) {
|
||||
// 核定起发月默认同步为首次实际发放月,已有人工登记或旧年度登记优先保留。
|
||||
String user = q.getStr("userId");
|
||||
if (get("S:" + user) == null) {
|
||||
JSONObject baseline = paymentBaseline(user);
|
||||
put("S:" + user, "PAY_START", user, null, baseline); audit(baseline);
|
||||
}
|
||||
for (JSONObject draft : records("BATCH")) {
|
||||
if ("草稿".equals(draft.getStr("status"))) refreshDraft(draft);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 登记资格终止及实际停发办理依据,历史有效月份仍参与清算。
|
||||
* @param d 领取人、变更生效月份和核实依据
|
||||
*/
|
||||
public void stop(JSONObject d) {
|
||||
access("ledger"); lock(); JSONObject q = qualification(d.getStr("userId"));
|
||||
require(q.getStr("startMonth") != null, "尚未核定待遇"); text(d, "basis");
|
||||
YearMonth end = month(d.getStr("endMonth"));
|
||||
JSONArray history = q.getJSONArray("benefitHistory");
|
||||
if (history != null && !history.isEmpty()) require(!end.isBefore(month(history.getJSONObject(history.size() - 1).getStr("untilMonth"))), "终止月份不能早于最近更正生效月份");
|
||||
require(!end.isBefore(month(q.getStr("startMonth"))), "终止月份不得早于起发月份");
|
||||
require(q.getStr("endMonth") == null, "已登记终止,不可覆盖历史依据");
|
||||
q.set("endMonth", end.toString()).set("stopBasis", d.getStr("basis")).set("status", "已停发")
|
||||
.set("stopOperator", SecurityUtil.getUserId());
|
||||
put("Q:" + q.getStr("userId"), "QUALIFICATION", q.getStr("userId"), null, q); audit(q);
|
||||
}
|
||||
|
||||
private BigDecimal due(JSONObject q, YearMonth m) {
|
||||
JSONArray history = q.getJSONArray("benefitHistory");
|
||||
if (history != null) {
|
||||
for (int i = 0; i < history.size(); i++) {
|
||||
JSONObject version = history.getJSONObject(i);
|
||||
if (m.isBefore(month(version.getStr("untilMonth")))) return due(version, m);
|
||||
}
|
||||
}
|
||||
return !Boolean.FALSE.equals(q.getBool("eligible")) && q.getStr("startMonth") != null && !m.isBefore(month(q.getStr("startMonth")))
|
||||
&& !m.isBefore(YearMonth.from(LocalDate.parse(q.getStr("birthday"))))
|
||||
&& m.isBefore(month(q.getStr("stopMonth")))
|
||||
&& (q.getStr("endMonth") == null || m.isBefore(month(q.getStr("endMonth")))) ? TEN : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
/** 保存单月实发修订;空金额与零金额严格区分。
|
||||
* @param d 领取人、月份及实发金额
|
||||
*/
|
||||
public void payment(JSONObject d) {
|
||||
access("ledger", "settlement"); lock(); qualification(d.getStr("userId"));
|
||||
YearMonth m = month(d.getStr("month")); money(d, "paid");
|
||||
JSONObject batch = get("B:" + settlementYear(m));
|
||||
require(batch == null || "草稿".equals(batch.getStr("status")), "已确认期间的实发记录不可改写");
|
||||
JSONObject row = new JSONObject().set("userId", d.getStr("userId")).set("month", m.toString())
|
||||
.set("paid", d.getBigDecimal("paid")).set("source", "人工修订");
|
||||
put("M:" + d.getStr("userId") + ":" + m, "MONTH", d.getStr("userId"), m.toString(), row); audit(row);
|
||||
// 实发补录与草稿重算在同一事务中完成,确认后的历史快照保持不变。
|
||||
if (batch != null && "草稿".equals(batch.getStr("status"))) {
|
||||
refreshDraft(batch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存领取人首次实际发放月份,各年度共用,并重算未确认草稿。
|
||||
* @param d 领取人、当前办理年度和首次实际发放月份
|
||||
* 起发前为零、起发后每月10元,不按待遇停发月自动清零实发;单月人工修订优先,已确认批次禁止修改。
|
||||
*/
|
||||
public void paymentStart(JSONObject d) {
|
||||
access("settlement"); lock();
|
||||
String user = d.getStr("userId"); qualification(user);
|
||||
Integer year = d.getInt("year");
|
||||
require(year != null && year >= 1900 && year <= 9999, "年度无效");
|
||||
YearMonth start = month(d.getStr("actualStartMonth"));
|
||||
JSONObject batch = get("B:" + year);
|
||||
require(batch != null && "草稿".equals(batch.getStr("status")), "仅可修改未确认清算草稿的起发月份");
|
||||
boolean included = false;
|
||||
JSONArray rows = batch.getJSONArray("rows");
|
||||
for (int i = 0; i < rows.size(); i++) included |= rows.getJSONObject(i).getStr("userId").equals(user);
|
||||
require(included, "该领取人不在当前清算草稿中");
|
||||
JSONObject baseline = new JSONObject().set("userId", user).set("actualStartMonth", start.toString());
|
||||
// 人员级唯一记录,不再以年度拆分;历史确认快照及人工修订保持原值。
|
||||
put("S:" + user, "PAY_START", user, null, baseline); audit(baseline);
|
||||
for (JSONObject draft : records("BATCH")) {
|
||||
if (!"草稿".equals(draft.getStr("status"))) continue;
|
||||
refreshDraft(draft);
|
||||
}
|
||||
}
|
||||
|
||||
private JSONObject paymentBaseline(String user) {
|
||||
JSONObject baseline = get("S:" + user);
|
||||
if (baseline != null) return baseline;
|
||||
// 兼容旧年度登记:沿用最后一次保存的月份,重新确认后写入人员级记录。
|
||||
OnlyChildFeeRecord legacy = dao.fetch(OnlyChildFeeRecord.class,
|
||||
Cnd.where("kind", "=", "PAY_START").and("userId", "=", user).desc("recordedAt").desc("id"));
|
||||
if (legacy != null) return JSONUtil.parseObj(legacy.getContent());
|
||||
// 兼容已核定但未登记实际起发月的人员,清算和台账共同使用核定月份作为默认值。
|
||||
JSONObject q = get("Q:" + user);
|
||||
if (q == null || q.getStr("startMonth") == null) return null;
|
||||
return new JSONObject().set("userId", user).set("actualStartMonth", q.getStr("startMonth"));
|
||||
}
|
||||
|
||||
private JSONObject actualPayment(JSONObject q, String period, JSONObject baseline) {
|
||||
String user = q.getStr("userId");
|
||||
// 先读取单月修订记录,再按首次发放月份计算默认实发。
|
||||
JSONObject actual = get("M:" + user + ":" + period);
|
||||
if (actual != null && !Boolean.TRUE.equals(actual.getBool("automatic"))) return actual;
|
||||
if (baseline == null) return actual;
|
||||
// 待遇停发只影响应发,不能据此认定工资已停发;保留实发差额供管理员登记扣回。
|
||||
YearMonth start = month(baseline.getStr("actualStartMonth"));
|
||||
return new JSONObject().set("userId", user).set("month", period).set("automatic", true)
|
||||
.set("paid", month(period).isBefore(start) ? BigDecimal.ZERO : TEN)
|
||||
.set("source", "按首次实际发放月份" + start + "自动计算");
|
||||
}
|
||||
|
||||
// 11、12月归属下一年度清算,每批覆盖上一年11月至当年10月。
|
||||
private int settlementYear(YearMonth period) {
|
||||
return period.getMonthValue() >= 11 ? period.getYear() + 1 : period.getYear();
|
||||
}
|
||||
|
||||
private void refreshDraft(JSONObject draft) {
|
||||
int year = draft.getInt("year");
|
||||
draft.set("rows", calculate(year)).set("periodStart", YearMonth.of(year - 1, 11).toString())
|
||||
.set("periodEnd", YearMonth.of(year, 10).toString()).set("automaticStop", false).set("qualificationStartSynced", true);
|
||||
put("B:" + year, "BATCH", null, String.valueOf(year), draft);
|
||||
}
|
||||
|
||||
private BigDecimal recovered(String user, String period) {
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (OnlyChildFeeRecord r : dao.query(OnlyChildFeeRecord.class,
|
||||
Cnd.where("kind", "=", "RECOVERY").and("userId", "=", user).and("period", "=", period))) {
|
||||
total = total.add(JSONUtil.parseObj(r.getContent()).getBigDecimal("amount"));
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
private JSONArray calculate(int year) {
|
||||
require(year >= 1900 && year <= 9999, "年度无效");
|
||||
YearMonth periodStart = YearMonth.of(year - 1, 11), periodEnd = YearMonth.of(year, 10);
|
||||
JSONArray rows = new JSONArray();
|
||||
for (JSONObject q : records("QUALIFICATION")) {
|
||||
if (q.getStr("startMonth") == null || month(q.getStr("startMonth")).isAfter(periodEnd)) continue;
|
||||
JSONObject baseline = paymentBaseline(q.getStr("userId"));
|
||||
String end = q.getStr("endMonth", q.getStr("stopMonth"));
|
||||
if (end.compareTo(q.getStr("stopMonth")) > 0) end = q.getStr("stopMonth");
|
||||
JSONArray months = new JSONArray();
|
||||
BigDecimal expected = BigDecimal.ZERO, paid = BigDecimal.ZERO, back = BigDecimal.ZERO, pending = BigDecimal.ZERO;
|
||||
boolean complete = true;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
YearMonth current = periodStart.plusMonths(i);
|
||||
String m = current.toString();
|
||||
BigDecimal amount = due(q, current);
|
||||
JSONObject actual = actualPayment(q, m, baseline);
|
||||
BigDecimal deducted = recovered(q.getStr("userId"), m);
|
||||
BigDecimal over = actual == null ? null : actual.getBigDecimal("paid").subtract(amount).max(BigDecimal.ZERO);
|
||||
BigDecimal remaining = over == null ? null : over.subtract(deducted).max(BigDecimal.ZERO);
|
||||
complete &= actual != null; expected = expected.add(amount); back = back.add(deducted);
|
||||
if (actual != null) { paid = paid.add(actual.getBigDecimal("paid")); pending = pending.add(remaining); }
|
||||
months.add(new JSONObject().set("month", m).set("due", amount).set("paid", actual == null ? null : actual.getBigDecimal("paid"))
|
||||
.set("source", actual == null ? null : actual.getStr("source")).set("recovered", deducted).set("pending", remaining)
|
||||
.set("note", m.equals(q.getStr("stopMonth")) ? "满18周岁当月停发" : ""));
|
||||
}
|
||||
// 往期已停发但本期仍有实发的人员继续清算,仅排除本期应发、实发及扣回均为零的人员。
|
||||
if (end.compareTo(periodStart.toString()) < 0 && expected.signum() == 0 && paid.signum() == 0 && back.signum() == 0) continue;
|
||||
rows.add(new JSONObject(q.toString()).set("actualStartMonth", baseline == null ? null : baseline.getStr("actualStartMonth"))
|
||||
.set("months", months).set("due", expected).set("paid", complete ? paid : null)
|
||||
.set("recovered", back).set("pending", complete ? pending : null)
|
||||
.set("status", !complete ? "资料不全" : pending.signum() > 0 ? "待扣回" : "无变动"));
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/** 查询年度清算;确认后只读取冻结快照。
|
||||
* @param year 清算年度
|
||||
* @return 批次、逐月依据及扣回执行记录
|
||||
*/
|
||||
public JSONObject settlement(int year) {
|
||||
access("settlement", "changes"); lock();
|
||||
require(year >= 1900 && year <= 9999, "年度无效");
|
||||
JSONObject b = get("B:" + year);
|
||||
// 重算曾按待遇停发月清零实发的未确认草稿;已确认批次的金额及期间保持冻结。
|
||||
if (b != null && "草稿".equals(b.getStr("status"))
|
||||
&& (!YearMonth.of(year - 1, 11).toString().equals(b.getStr("periodStart"))
|
||||
|| !Boolean.FALSE.equals(b.getBool("automaticStop"))
|
||||
|| !Boolean.TRUE.equals(b.getBool("qualificationStartSynced")))) refreshDraft(b);
|
||||
if (b != null && b.getStr("periodStart") == null) {
|
||||
b.set("periodStart", YearMonth.of(year, 1).toString()).set("periodEnd", YearMonth.of(year, 10).toString());
|
||||
}
|
||||
return new JSONObject().set("batch", b).set("rows", b == null ? new JSONArray() : b.getJSONArray("rows"))
|
||||
.set("recoveries", records("RECOVERY"));
|
||||
}
|
||||
|
||||
/** 生成草稿或复核确认;确认时重新核算并拒绝缺失数据。
|
||||
* @param year 清算年度
|
||||
* @param confirm 是否确认并冻结
|
||||
*/
|
||||
public void batch(int year, boolean confirm) {
|
||||
access("settlement"); lock(); JSONObject old = get("B:" + year);
|
||||
require(old == null || "草稿".equals(old.getStr("status")), "批次已经确认,不能重建");
|
||||
require(!confirm || old != null, "请先生成草稿");
|
||||
JSONArray rows = calculate(year);
|
||||
require(!rows.isEmpty(), "没有可清算的核定待遇");
|
||||
if (confirm) {
|
||||
for (int i = 0; i < rows.size(); i++) require(!"资料不全".equals(rows.getJSONObject(i).getStr("status")), "请补齐全部实发记录后确认");
|
||||
require(rows.toString().equals(old.getJSONArray("rows").toString()), "数据已变化,请重新生成草稿并复核");
|
||||
}
|
||||
boolean pending = false;
|
||||
for (int i = 0; i < rows.size(); i++) pending |= rows.getJSONObject(i).getBigDecimal("pending", BigDecimal.ZERO).signum() > 0;
|
||||
JSONObject b = new JSONObject().set("year", year).set("id", year + "年11月").set("rows", rows)
|
||||
.set("periodStart", YearMonth.of(year - 1, 11).toString()).set("periodEnd", YearMonth.of(year, 10).toString()).set("automaticStop", false).set("qualificationStartSynced", true)
|
||||
.set("status", confirm ? (pending ? "已确认" : "已完成") : "草稿")
|
||||
.set("operator", SecurityUtil.getUserId()).set("time", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
|
||||
put("B:" + year, "BATCH", null, String.valueOf(year), b);
|
||||
}
|
||||
|
||||
/**
|
||||
* 登记本期全部结清,或按列出的多发月份分别登记实际扣回。
|
||||
* @param d 领取人、年度、全部结清标记及取消勾选时填写的月度扣回明细
|
||||
* 全部结清的金额由服务端计算;未提供的执行月份留空,单月金额不得超过余额。
|
||||
*/
|
||||
public void recover(JSONObject d) {
|
||||
access("settlement"); lock();
|
||||
// 保留旧单月调用入口;新表单由服务端分摊,不能依赖客户端计算的余额。
|
||||
if (!d.containsKey("settledAll")) { recoverMonth(d); return; }
|
||||
Integer year = d.getInt("year");
|
||||
require(year != null && year >= 1900 && year <= 9999, "年度无效");
|
||||
String user = d.getStr("userId"); qualification(user);
|
||||
JSONObject batch = settlement(year).getJSONObject("batch");
|
||||
require(batch != null, "请先生成清算批次");
|
||||
JSONObject person = null;
|
||||
JSONArray rows = batch.getJSONArray("rows");
|
||||
for (int i = 0; i < rows.size(); i++) {
|
||||
if (user.equals(rows.getJSONObject(i).getStr("userId"))) person = rows.getJSONObject(i);
|
||||
}
|
||||
require(person != null && person.getBigDecimal("paid") != null, "请先补齐该人员的实发记录");
|
||||
JSONArray months = person.getJSONArray("months"), entries = new JSONArray();
|
||||
Set<String> allowed = new HashSet<>();
|
||||
boolean all = Boolean.TRUE.equals(d.getBool("settledAll"));
|
||||
for (int i = 0; i < months.size(); i++) {
|
||||
JSONObject detail = months.getJSONObject(i);
|
||||
String period = detail.getStr("month"); allowed.add(period);
|
||||
if (!all) continue;
|
||||
BigDecimal remaining = detail.getBigDecimal("paid").subtract(detail.getBigDecimal("due")).max(BigDecimal.ZERO)
|
||||
.subtract(recovered(user, period)).max(BigDecimal.ZERO);
|
||||
if (remaining.signum() > 0) entries.add(new JSONObject().set("month", period).set("amount", remaining));
|
||||
}
|
||||
if (!all) {
|
||||
entries = d.getJSONArray("recoveryMonths");
|
||||
require(entries != null && !entries.isEmpty(), "请至少填写一个月份的实际扣回月份和金额");
|
||||
}
|
||||
Set<String> entered = new HashSet<>();
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
JSONObject detail = entries.getJSONObject(i);
|
||||
String period = detail.getStr("month");
|
||||
require(allowed.contains(period) && entered.add(period), "扣回明细月份不在本期内或重复");
|
||||
JSONObject item = new JSONObject().set("userId", user).set("year", year).set("month", period)
|
||||
.set("amount", detail.getBigDecimal("amount")).set("executionMonth", all ? null : detail.getStr("executionMonth"))
|
||||
.set("settledAll", all).set("historical", "草稿".equals(batch.getStr("status")));
|
||||
recoverMonth(item);
|
||||
}
|
||||
if ("草稿".equals(batch.getStr("status"))) refreshDraft(batch);
|
||||
}
|
||||
|
||||
/** 登记针对某一应发月份的实际扣回,限制余额并保留办理记录。
|
||||
* @param d 归属月份、实际扣回月份、金额、凭据及是否历史扣回
|
||||
*/
|
||||
private void recoverMonth(JSONObject d) {
|
||||
access("settlement"); lock();
|
||||
String user = d.getStr("userId"); JSONObject q = qualification(user);
|
||||
YearMonth period = month(d.getStr("month"));
|
||||
int year = settlementYear(period);
|
||||
require(d.getInt("year") == null || d.getInt("year") == year, "原发放月份不在当前清算期间内");
|
||||
if (!Boolean.TRUE.equals(d.getBool("settledAll"))) {
|
||||
require(!month(d.getStr("executionMonth")).isBefore(period), "扣回月份不得早于原发放月份");
|
||||
}
|
||||
BigDecimal amount = money(d, "amount"); require(amount.signum() > 0, "实际扣回须大于零");
|
||||
JSONObject b = get("B:" + year);
|
||||
boolean confirmed = b != null && !"草稿".equals(b.getStr("status"));
|
||||
require(confirmed || Boolean.TRUE.equals(d.getBool("historical")), "请先确认批次,或明确登记历史已扣回");
|
||||
JSONObject actual = actualPayment(q, period.toString(), paymentBaseline(user));
|
||||
require(actual != null, "请先登记该月实发");
|
||||
BigDecimal over = actual.getBigDecimal("paid").subtract(due(q, period)).max(BigDecimal.ZERO);
|
||||
if (confirmed) {
|
||||
JSONObject snapshot = null;
|
||||
for (int i = 0; i < b.getJSONArray("rows").size(); i++) {
|
||||
JSONObject row = b.getJSONArray("rows").getJSONObject(i);
|
||||
if (!user.equals(row.getStr("userId"))) continue;
|
||||
JSONArray details = row.getJSONArray("months");
|
||||
for (int j = 0; j < details.size(); j++) {
|
||||
JSONObject detail = details.getJSONObject(j);
|
||||
if (period.toString().equals(detail.getStr("month"))) snapshot = detail;
|
||||
}
|
||||
}
|
||||
require(snapshot != null, "该领取人不在确认批次中");
|
||||
over = snapshot.getBigDecimal("paid").subtract(snapshot.getBigDecimal("due")).max(BigDecimal.ZERO);
|
||||
}
|
||||
require(amount.compareTo(over.subtract(recovered(user, period.toString()))) <= 0, "超过该月份尚未扣回余额");
|
||||
JSONObject record = new JSONObject().set("userId", user).set("month", period.toString()).set("amount", amount)
|
||||
.set("executionMonth", d.getStr("executionMonth")).set("voucher", d.getStr("voucher"))
|
||||
.set("operator", SecurityUtil.getUserId()).set("historical", !confirmed).set("settledAll", Boolean.TRUE.equals(d.getBool("settledAll")));
|
||||
put(UUID.randomUUID().toString(), "RECOVERY", user, period.toString(), record);
|
||||
if (confirmed) {
|
||||
boolean finished = true;
|
||||
for (int i = 0; i < b.getJSONArray("rows").size(); i++) {
|
||||
JSONObject row = b.getJSONArray("rows").getJSONObject(i);
|
||||
for (int j = 0; j < row.getJSONArray("months").size(); j++) {
|
||||
JSONObject m = row.getJSONArray("months").getJSONObject(j);
|
||||
finished &= m.getBigDecimal("paid").subtract(m.getBigDecimal("due")).max(BigDecimal.ZERO)
|
||||
.compareTo(recovered(row.getStr("userId"), m.getStr("month"))) <= 0;
|
||||
}
|
||||
}
|
||||
if (finished) { b.set("status", "已完成"); put("B:" + year, "BATCH", null, String.valueOf(year), b); }
|
||||
}
|
||||
}
|
||||
|
||||
/** 读取原退休申请及审批状态,退休奖励不套用月费年龄限制。
|
||||
* @return 原申请、审批状态和独立支付凭据
|
||||
*/
|
||||
public JSONArray retirement() {
|
||||
access("retirement"); JSONArray rows = new JSONArray();
|
||||
for (Dsznfmtx a : dao.query(Dsznfmtx.class, Cnd.NEW().desc("applyTime"))) {
|
||||
boolean approved = dao.count(ProcessInstance.class, Cnd.where("businessNo", "=", a.getId()).and("state", "=", 20)) > 0;
|
||||
ProcessInstance instance = dao.fetch(ProcessInstance.class, Cnd.where("businessNo", "=", a.getId()).desc("id"));
|
||||
rows.add(JSONUtil.parseObj(a).set("approved", approved).set("payment", get("R:" + a.getUserId()))
|
||||
.set("instanceId", instance == null ? null : instance.getId())
|
||||
.set("instanceProcessDefineId", instance == null ? null : instance.getProcessDefineId()));
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/** 核实历史奖励后登记一次性3000元发放,同一教职工只允许登记一次。
|
||||
* @param d 原申请ID、历史核查依据、发放日期及凭据
|
||||
*/
|
||||
public void reward(JSONObject d) {
|
||||
access("retirement"); lock(); Dsznfmtx a = dao.fetch(Dsznfmtx.class, d.getStr("id"));
|
||||
require(a != null, "原退休申请不存在");
|
||||
require(dao.count(ProcessInstance.class, Cnd.where("businessNo", "=", a.getId()).and("state", "=", 20)) > 0, "原申请尚未审批通过");
|
||||
require(get("R:" + a.getUserId()) == null, "该教职工已有奖励发放记录");
|
||||
text(d, "historyBasis"); text(d, "voucher"); text(d, "paidDate");
|
||||
require(Boolean.TRUE.equals(d.getBool("historyChecked")), "须核实历史记录未重复领取");
|
||||
require(!LocalDate.parse(d.getStr("paidDate")).isAfter(LocalDate.now()), "实际发放日期不可晚于今天");
|
||||
JSONObject payment = new JSONObject().set("applicationId", a.getId()).set("userId", a.getUserId())
|
||||
.set("amount", new BigDecimal("3000.00")).set("paidDate", d.getStr("paidDate"))
|
||||
.set("voucher", d.getStr("voucher")).set("historyBasis", d.getStr("historyBasis")).set("operator", SecurityUtil.getUserId());
|
||||
put("R:" + a.getUserId(), "REWARD", a.getUserId(), d.getStr("paidDate"), payment);
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -65,10 +65,10 @@ public class MemberInfoGroupController {
|
||||
public Result pageData(MemberInfoPageForm pageForm) {
|
||||
Sql sql = memberInfoService.getSql(pageForm);
|
||||
Pagination<NutMap> 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);
|
||||
}
|
||||
|
||||
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
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 javax.servlet.http.HttpServletResponse;
|
||||
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<NutMap> pagination = memberInfoService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出个人信息维护列表数据,导出条件与页面查询保持一致,具体 Excel 生成逻辑放在 service 层。
|
||||
*
|
||||
* @param pageForm 页面查询和显示列参数
|
||||
* @param response 文件下载响应
|
||||
*/
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission(value = {"member.info.maintain", "member.info.group"}, mode = SaMode.OR)
|
||||
public void doExport(MemberInfoPageForm pageForm, HttpServletResponse response) {
|
||||
memberInfoService.exportPersonalMaintain(pageForm, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询个人信息维护详情,返回只读会员信息和可编辑子女信息。
|
||||
*
|
||||
* @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, String marriage, @Param("children") String childrenJson) {
|
||||
try {
|
||||
// 保存前复用详情权限校验,防止绕过列表入口修改无权限会员的子女信息。
|
||||
memberInfoService.getPersonalMaintainInfo(userId);
|
||||
List<MemberChild> children = new ArrayList<>();
|
||||
if (Strings.isNotBlank(childrenJson)) {
|
||||
children = Json.fromJsonAsList(MemberChild.class, childrenJson);
|
||||
}
|
||||
memberInfoService.savePersonalMaintainMarriage(userId, marriage);
|
||||
memberChildService.saveUserChildren(userId, children);
|
||||
return Result.success();
|
||||
} catch (RuntimeException e) {
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
-14
@@ -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<NutMap> 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<NutMap> list, String key) {
|
||||
return list.stream().mapToInt(item -> item.getInt(key, 0)).sum();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<MemberChild> {
|
||||
|
||||
/**
|
||||
* 根据会员用户ID查询子女信息列表。
|
||||
*
|
||||
* @param userId 会员用户ID
|
||||
* @return 子女信息列表
|
||||
*/
|
||||
List<MemberChild> listByUserId(String userId);
|
||||
|
||||
/**
|
||||
* 保存会员子女信息,页面按整表提交,服务层负责覆盖当前会员的子女资料。
|
||||
*
|
||||
* @param userId 会员用户ID
|
||||
* @param children 页面提交的子女信息列表
|
||||
*/
|
||||
void saveUserChildren(String userId, List<MemberChild> children);
|
||||
}
|
||||
@@ -27,6 +27,38 @@ public interface MemberInfoService extends BaseService<Sys_user> {
|
||||
*/
|
||||
Sql getSql(MemberInfoPageForm pageForm);
|
||||
|
||||
/**
|
||||
* 获取个人信息维护列表查询SQL,系统管理员查看全部,其他人员仅查看本人。
|
||||
*
|
||||
* @param pageForm 会员档案查询参数
|
||||
* @return 个人信息维护列表查询SQL
|
||||
*/
|
||||
Sql getPersonalMaintainSql(MemberInfoPageForm pageForm);
|
||||
|
||||
/**
|
||||
* 获取个人信息维护详情,复用会员档案查询权限展示会员基础信息。
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 会员基础信息
|
||||
*/
|
||||
NutMap getPersonalMaintainInfo(String userId);
|
||||
|
||||
/**
|
||||
* 保存个人信息维护中允许编辑的婚姻状况字段,其他会员原始字段不在此方法中更新。
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param marriage 婚姻状况字典值
|
||||
*/
|
||||
void savePersonalMaintainMarriage(String userId, String marriage);
|
||||
|
||||
/**
|
||||
* 导出个人信息维护列表数据,查询条件与页面列表保持一致,并汇总导出家庭主要成员信息。
|
||||
*
|
||||
* @param pageForm 页面查询和显示列参数
|
||||
* @param response 文件下载响应
|
||||
*/
|
||||
void exportPersonalMaintain(MemberInfoPageForm pageForm, HttpServletResponse response);
|
||||
|
||||
/**
|
||||
* 获取会员历史
|
||||
* @param pageForm
|
||||
|
||||
+60
@@ -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<MemberChild> implements MemberChildService {
|
||||
|
||||
public MemberChildServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据会员用户ID查询子女信息列表,并按创建时间保持页面展示顺序。
|
||||
*
|
||||
* @param userId 会员用户ID
|
||||
* @return 子女信息列表
|
||||
*/
|
||||
@Override
|
||||
public List<MemberChild> 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<MemberChild> 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
+310
-4
@@ -15,15 +15,29 @@ 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;
|
||||
import com.budwk.app.zhgh.staffmanage.member.models.MemberHistory;
|
||||
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.MemberInfoService;
|
||||
import com.budwk.app.zhgh.staffmanage.member.vo.MemberImportVo;
|
||||
import org.apache.poi.ss.usermodel.BorderStyle;
|
||||
import org.apache.poi.ss.formula.functions.T;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.ss.usermodel.FillPatternType;
|
||||
import org.apache.poi.ss.usermodel.Font;
|
||||
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
||||
import org.apache.poi.ss.usermodel.IndexedColors;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.VerticalAlignment;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
@@ -40,9 +54,12 @@ import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.upload.TempFile;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -113,6 +130,295 @@ public class MemberInfoServiceImpl extends BaseServiceImpl<Sys_user> 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());
|
||||
}
|
||||
// 会员查询条件统一作用于列表和导出:1 表示会员,0 表示非会员。
|
||||
if (pageForm.getMemberStatus() != null) {
|
||||
cnd.and("u.member", "=", pageForm.getMemberStatus() == 1);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存个人信息维护开放编辑的婚姻状况字段,权限校验复用详情查询口径。
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param marriage 婚姻状况字典值
|
||||
*/
|
||||
@Override
|
||||
public void savePersonalMaintainMarriage(String userId, String marriage) {
|
||||
// 先执行详情权限校验,确保非系统管理员不能通过保存接口修改其他人员婚姻状况。
|
||||
getPersonalMaintainInfo(userId);
|
||||
dao().update(Sys_user.class, Chain.make("marriage", marriage), Cnd.where("id", "=", userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出个人信息维护列表,人员数据复用页面查询 SQL,家庭主要成员按同一人员汇总到一行。
|
||||
*
|
||||
* @param pageForm 页面查询和显示列参数
|
||||
* @param response 文件下载响应
|
||||
*/
|
||||
@Override
|
||||
public void exportPersonalMaintain(MemberInfoPageForm pageForm, HttpServletResponse response) {
|
||||
Sql sql = getPersonalMaintainSql(pageForm);
|
||||
List<NutMap> list = listMap(sql);
|
||||
fillChildExportData(list);
|
||||
List<Map<String, String>> personColumns = Lang.isNotEmpty(pageForm.getColumns())
|
||||
? pageForm.getColumns()
|
||||
: buildDefaultPersonalMaintainColumns();
|
||||
Workbook workbook = buildPersonalMaintainWorkbook(personColumns, list);
|
||||
CommonDownloadUtil.download("人员信息维护.xlsx", workbook, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为查询结果追加子女信息汇总字段,多个子女之间使用“ | ”分隔。
|
||||
*
|
||||
* @param list 页面查询出的人员数据
|
||||
*/
|
||||
private void fillChildExportData(List<NutMap> list) {
|
||||
if (Lang.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
List<String> userIds = list.stream().map(item -> item.getString("id")).filter(StrUtil::isNotBlank).toList();
|
||||
if (Lang.isEmpty(userIds)) {
|
||||
return;
|
||||
}
|
||||
// Query child records in batch so exported child data follows the same people returned by the page query.
|
||||
Map<String, List<MemberChild>> childMap = dao().query(MemberChild.class, Cnd.where("userId", "in", userIds).asc("createdAt"))
|
||||
.stream().collect(Collectors.groupingBy(MemberChild::getUserId));
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||
for (NutMap item : list) {
|
||||
List<MemberChild> children = childMap.get(item.getString("id"));
|
||||
item.put("childRelation", joinChildField(children, MemberChild::getRelation));
|
||||
item.put("childName", joinChildField(children, MemberChild::getName));
|
||||
item.put("childSex", joinChildField(children, MemberChild::getSex));
|
||||
item.put("childBirthday", joinChildField(children, child -> child.getBirthday() == null ? "" : dateFormat.format(child.getBirthday())));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 聚合子女信息单个字段,保持每个子女字段在同一导出行内的顺序一致。
|
||||
*
|
||||
* @param children 子女信息列表
|
||||
* @param getter 字段取值函数
|
||||
* @return 使用“ | ”分隔的字段内容
|
||||
*/
|
||||
private String joinChildField(List<MemberChild> children, Function<MemberChild, String> getter) {
|
||||
if (Lang.isEmpty(children)) {
|
||||
return "";
|
||||
}
|
||||
// Keep child field order consistent across all exported child columns.
|
||||
return children.stream()
|
||||
.map(child -> StrUtil.nullToEmpty(getter.apply(child)))
|
||||
.collect(Collectors.joining(" | "));
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面未传显示列时,使用个人信息维护列表当前默认显示列作为导出字段。
|
||||
*
|
||||
* @return 默认人员信息导出列
|
||||
*/
|
||||
private List<Map<String, String>> buildDefaultPersonalMaintainColumns() {
|
||||
List<Map<String, String>> columns = new ArrayList<>();
|
||||
addExportColumn(columns, "工号", "loginname");
|
||||
addExportColumn(columns, "姓名", "username");
|
||||
addExportColumn(columns, "性别", "sex");
|
||||
addExportColumn(columns, "出生年月", "birthday");
|
||||
addExportColumn(columns, "民族", "nation");
|
||||
addExportColumn(columns, "联系电话", "mobile");
|
||||
addExportColumn(columns, "政治面貌", "political");
|
||||
addExportColumn(columns, "在职状态", "userState");
|
||||
addExportColumn(columns, "教职工类别", "personType");
|
||||
addExportColumn(columns, "编制类别", "preparedBy");
|
||||
addExportColumn(columns, "所属工会", "unionName");
|
||||
addExportColumn(columns, "所属单位", "unitName");
|
||||
return columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加导出列配置,使用有序 Map 保持 Excel 列顺序。
|
||||
*/
|
||||
private void addExportColumn(List<Map<String, String>> columns, String label, String prop) {
|
||||
Map<String, String> column = new LinkedHashMap<>();
|
||||
column.put("label", label);
|
||||
column.put("prop", prop);
|
||||
columns.add(column);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建带“人员信息 / 子女信息”分组表头的 Excel。
|
||||
*
|
||||
* @param personColumns 人员信息列
|
||||
* @param list 导出数据
|
||||
* @return Excel 工作簿
|
||||
*/
|
||||
private Workbook buildPersonalMaintainWorkbook(List<Map<String, String>> personColumns, List<NutMap> list) {
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
Workbook workbook = new XSSFWorkbook();
|
||||
Sheet sheet = workbook.createSheet("人员信息维护");
|
||||
CellStyle groupStyle = createExportCellStyle(workbook, true, IndexedColors.PALE_BLUE.getIndex());
|
||||
CellStyle childGroupStyle = createExportCellStyle(workbook, true, IndexedColors.LIGHT_GREEN.getIndex());
|
||||
CellStyle headerStyle = createExportCellStyle(workbook, true, IndexedColors.GREY_25_PERCENT.getIndex());
|
||||
CellStyle dataStyle = createExportCellStyle(workbook, false, IndexedColors.WHITE.getIndex());
|
||||
|
||||
List<Map<String, String>> childColumns = new ArrayList<>();
|
||||
addExportColumn(childColumns, "关系", "childRelation");
|
||||
addExportColumn(childColumns, "姓名", "childName");
|
||||
addExportColumn(childColumns, "性别", "childSex");
|
||||
addExportColumn(childColumns, "出生日期", "childBirthday");
|
||||
|
||||
Row groupRow = sheet.createRow(0);
|
||||
Row headerRow = sheet.createRow(1);
|
||||
int colIndex = 0;
|
||||
createMergedCell(sheet, groupRow, 0, Math.max(personColumns.size() - 1, 0), "人员信息", groupStyle);
|
||||
for (Map<String, String> column : personColumns) {
|
||||
createCell(headerRow, colIndex++, column.get("label"), headerStyle);
|
||||
}
|
||||
int childStartIndex = colIndex;
|
||||
createMergedCell(sheet, groupRow, childStartIndex, childStartIndex + childColumns.size() - 1, "子女信息", childGroupStyle);
|
||||
for (Map<String, String> column : childColumns) {
|
||||
createCell(headerRow, colIndex++, column.get("label"), headerStyle);
|
||||
}
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
Row dataRow = sheet.createRow(i + 2);
|
||||
int dataCol = 0;
|
||||
NutMap data = list.get(i);
|
||||
for (Map<String, String> column : personColumns) {
|
||||
createCell(dataRow, dataCol++, data.getString(column.get("prop"), ""), dataStyle);
|
||||
}
|
||||
for (Map<String, String> column : childColumns) {
|
||||
createCell(dataRow, dataCol++, data.getString(column.get("prop"), ""), dataStyle);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < colIndex; i++) {
|
||||
sheet.setColumnWidth(i, 20 * 256);
|
||||
}
|
||||
return workbook;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建导出单元格样式,统一表头和数据区的边框、对齐、换行。
|
||||
*/
|
||||
private CellStyle createExportCellStyle(Workbook workbook, boolean bold, short fillColor) {
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
style.setAlignment(HorizontalAlignment.CENTER);
|
||||
style.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
style.setWrapText(true);
|
||||
style.setFillForegroundColor(fillColor);
|
||||
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
||||
style.setBorderTop(BorderStyle.THIN);
|
||||
style.setBorderBottom(BorderStyle.THIN);
|
||||
style.setBorderLeft(BorderStyle.THIN);
|
||||
style.setBorderRight(BorderStyle.THIN);
|
||||
style.setTopBorderColor(IndexedColors.BLACK.getIndex());
|
||||
style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
|
||||
style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
|
||||
style.setRightBorderColor(IndexedColors.BLACK.getIndex());
|
||||
Font font = workbook.createFont();
|
||||
font.setBold(bold);
|
||||
style.setFont(font);
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建普通单元格并写入文本值。
|
||||
*/
|
||||
private void createCell(Row row, int colIndex, String value, CellStyle style) {
|
||||
Cell cell = row.createCell(colIndex);
|
||||
cell.setCellValue(value == null ? "" : value);
|
||||
cell.setCellStyle(style);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建横向合并单元格,作为 Excel 分组表头。
|
||||
*/
|
||||
private void createMergedCell(Sheet sheet, Row row, int firstCol, int lastCol, String value, CellStyle style) {
|
||||
createCell(row, firstCol, value, style);
|
||||
if (lastCol > firstCol) {
|
||||
sheet.addMergedRegion(new CellRangeAddress(row.getRowNum(), row.getRowNum(), firstCol, lastCol));
|
||||
for (int i = firstCol + 1; i <= lastCol; i++) {
|
||||
createCell(row, i, "", style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Sql getMemberHistorySql(MemberInfoPageForm pageForm) {
|
||||
@@ -243,10 +549,10 @@ public class MemberInfoServiceImpl extends BaseServiceImpl<Sys_user> implements
|
||||
public void exportMember(MemberInfoPageForm pageForm, HttpServletResponse response) {
|
||||
Sql sql = getSql(pageForm);
|
||||
List<NutMap> list = listMap(sql);
|
||||
for (NutMap row : list) {
|
||||
row.put("mobile", DesensitizedUtil.mobilePhone(row.getString("mobile")));
|
||||
row.put("idCard",DesensitizedUtil.idCardNum(row.getString("idCard"), 3, 4));
|
||||
}
|
||||
// for (NutMap row : list) {
|
||||
// row.put("mobile", DesensitizedUtil.mobilePhone(row.getString("mobile")));
|
||||
// row.put("idCard",DesensitizedUtil.idCardNum(row.getString("idCard"), 3, 4));
|
||||
// }
|
||||
|
||||
Workbook workbook = null;
|
||||
if (Lang.isNotEmpty(pageForm.getColumns())) {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
-- 月费与退休奖励凭据独立于子女资料;主键确保同一教职工、月份和年度不重复。
|
||||
CREATE TABLE IF NOT EXISTS only_child_fee_record (
|
||||
id varchar(100) PRIMARY KEY,
|
||||
kind varchar(20) NOT NULL,
|
||||
userId varchar(32),
|
||||
period varchar(10),
|
||||
content longtext,
|
||||
operatorId varchar(32),
|
||||
recordedAt datetime,
|
||||
KEY idx_fee_kind_user (kind, userId),
|
||||
KEY idx_fee_kind_period (kind, period)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
-- 事务锁串行化资格变更、快照确认与扣回登记,防止并发重复领取或超额扣回。
|
||||
INSERT IGNORE INTO only_child_fee_record (id, kind, content) VALUES ('LOCK', 'LOCK', '{}');
|
||||
|
||||
-- 补齐旧资料中缺失的账号关联,使原有完整子女信息可以直接复用。
|
||||
-- 使用信息架构判断列是否存在,兼容项目自动迁移及重复执行本脚本。
|
||||
SET @fee_column_sql = IF((SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='child_manage' AND column_name='guardianRelationship1')=0,
|
||||
'ALTER TABLE child_manage ADD COLUMN guardianRelationship1 varchar(30) NULL', 'SELECT 1');
|
||||
PREPARE fee_column_stmt FROM @fee_column_sql;
|
||||
EXECUTE fee_column_stmt;
|
||||
DEALLOCATE PREPARE fee_column_stmt;
|
||||
SET @fee_column_sql = IF((SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='child_manage' AND column_name='guardianRelationship2')=0,
|
||||
'ALTER TABLE child_manage ADD COLUMN guardianRelationship2 varchar(30) NULL', 'SELECT 1');
|
||||
PREPARE fee_column_stmt FROM @fee_column_sql;
|
||||
EXECUTE fee_column_stmt;
|
||||
DEALLOCATE PREPARE fee_column_stmt;
|
||||
UPDATE child_manage c JOIN vw_user u ON u.loginname = c.guardianLoginName1
|
||||
SET c.guardianUserId1 = u.id WHERE c.guardianUserId1 IS NULL OR c.guardianUserId1 = '';
|
||||
UPDATE child_manage c JOIN vw_user u ON u.loginname = c.guardianLoginName2
|
||||
SET c.guardianUserId2 = u.id WHERE c.guardianUserId2 IS NULL OR c.guardianUserId2 = '';
|
||||
|
||||
-- 与“我的子女”使用同一父菜单,沿用已有子女模块角色的可见范围。
|
||||
INSERT INTO sys_menu (id,parentId,path,name,type,href,target,icon,showit,disabled,permission,location,hasChildren,platform,moduleId,createdAt,delFlag)
|
||||
SELECT 'only_child_fee_menu',p.id,CONCAT(p.path,'0099'),'独生子女费','menu',
|
||||
'/platform/childManage/onlyChildFee/index','data-pjax','fa fa-list-alt',1,0,'childManage.onlyChildFee',99,0,'PC',m.moduleId,UNIX_TIMESTAMP(),0
|
||||
FROM sys_menu m JOIN sys_menu p ON p.id=m.parentId
|
||||
WHERE m.permission='childManage.mine'
|
||||
AND NOT EXISTS (SELECT 1 FROM sys_menu x WHERE x.permission='childManage.onlyChildFee');
|
||||
INSERT INTO sys_role_menu (roleId,menuId)
|
||||
SELECT DISTINCT rm.roleId,'only_child_fee_menu'
|
||||
FROM sys_role_menu rm JOIN sys_menu m ON m.id=rm.menuId
|
||||
WHERE m.permission IN ('childManage.mine','childManage.write','childManage.manage')
|
||||
AND EXISTS (SELECT 1 FROM sys_menu x WHERE x.id='only_child_fee_menu')
|
||||
AND NOT EXISTS (SELECT 1 FROM sys_role_menu x WHERE x.roleId=rm.roleId AND x.menuId='only_child_fee_menu');
|
||||
|
||||
-- 五个独立页面作为独生子女费的子菜单,沿用角色菜单授权;首次迁移后由角色配置维护。
|
||||
SET @fee_tabs_first_install = NOT EXISTS (
|
||||
SELECT 1 FROM sys_menu WHERE permission IN (
|
||||
'childManage.onlyChildFee.qualification', 'childManage.onlyChildFee.settlement',
|
||||
'childManage.onlyChildFee.changes', 'childManage.onlyChildFee.retirement', 'childManage.onlyChildFee.ledger'
|
||||
)
|
||||
);
|
||||
UPDATE sys_menu SET hasChildren=1, platform='PC'
|
||||
WHERE permission='childManage.onlyChildFee';
|
||||
|
||||
INSERT INTO sys_menu (id,parentId,path,name,type,href,target,icon,showit,disabled,permission,location,hasChildren,platform,moduleId,createdAt,delFlag)
|
||||
SELECT t.id,p.id,CONCAT(p.path,t.pathSuffix),t.name,'menu',
|
||||
CONCAT('/platform/childManage/onlyChildFee/',t.tab,'/index'),'data-pjax','fa fa-list-alt',1,0,
|
||||
CONCAT('childManage.onlyChildFee.',t.tab),t.location,0,'PC',p.moduleId,UNIX_TIMESTAMP(),0
|
||||
FROM sys_menu p
|
||||
CROSS JOIN (
|
||||
SELECT 'only_child_fee_qualification' AS id, 'qualification' AS tab, '资格登记' AS name, '0001' AS pathSuffix, 1 AS location
|
||||
UNION ALL SELECT 'only_child_fee_settlement','settlement','年度清算','0002',2
|
||||
UNION ALL SELECT 'only_child_fee_changes','changes','变动报表','0003',3
|
||||
UNION ALL SELECT 'only_child_fee_retirement','retirement','退休奖励','0004',4
|
||||
UNION ALL SELECT 'only_child_fee_ledger','ledger','待遇台账','0005',5
|
||||
) t
|
||||
WHERE p.permission='childManage.onlyChildFee'
|
||||
AND NOT EXISTS (SELECT 1 FROM sys_menu x WHERE x.permission=CONCAT('childManage.onlyChildFee.',t.tab));
|
||||
|
||||
-- 普通教职工保留本人资格登记;原管理角色保留办理页签,重复执行不恢复已撤销的子菜单授权。
|
||||
INSERT INTO sys_role_menu (roleId,menuId)
|
||||
SELECT DISTINCT r.id,c.id
|
||||
FROM sys_role r
|
||||
JOIN sys_role_menu rm ON rm.roleId=r.id
|
||||
JOIN sys_menu p ON p.id=rm.menuId AND p.permission='childManage.onlyChildFee'
|
||||
JOIN sys_menu c ON c.parentId=p.id
|
||||
WHERE @fee_tabs_first_install=1
|
||||
AND c.permission IN ('childManage.onlyChildFee.qualification','childManage.onlyChildFee.settlement',
|
||||
'childManage.onlyChildFee.changes','childManage.onlyChildFee.retirement','childManage.onlyChildFee.ledger')
|
||||
AND (c.permission='childManage.onlyChildFee.qualification'
|
||||
OR r.code IN ('SYSADMIN','SCHOOL_UNION_ADMIN','SCHOOL_UNION_WELFARE_ADMIN'))
|
||||
AND NOT EXISTS (SELECT 1 FROM sys_role_menu existing WHERE existing.roleId=r.id AND existing.menuId=c.id);
|
||||
|
||||
-- 将已有子菜单映射至独立页面,仅更新地址,保留现有角色授权。
|
||||
UPDATE sys_menu SET href=CONCAT('/platform/childManage/onlyChildFee/',SUBSTRING_INDEX(permission,'.',-1),'/index')
|
||||
WHERE permission IN ('childManage.onlyChildFee.qualification','childManage.onlyChildFee.settlement',
|
||||
'childManage.onlyChildFee.changes','childManage.onlyChildFee.retirement','childManage.onlyChildFee.ledger');
|
||||
@@ -1,17 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<configuration scan="false" scanPeriod="60000" debug="false">
|
||||
|
||||
<!-- 定义日志文件的存储路径 -->
|
||||
<!-- 日志存储路径 -->
|
||||
<property name="LOG_HOME" value="./logs"/>
|
||||
|
||||
<!-- 控制台输出配置 -->
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- INFO级别日志文件配置 -->
|
||||
|
||||
<!--
|
||||
#################################################################################
|
||||
# #
|
||||
# 全文输出日志(一个文件存储)start #
|
||||
# #
|
||||
#################################################################################
|
||||
-->
|
||||
|
||||
<!-- 所有级别日志写入同一个文件 -->
|
||||
<appender name="ALL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/app.log</file>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/app-%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>300</maxHistory>
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
|
||||
<!-- 框架日志级别(按需调整) -->
|
||||
<logger name="org.eclipse.jetty" level="INFO"/>
|
||||
<logger name="org.quartz" level="INFO"/>
|
||||
<logger name="org.nutz" level="DEBUG"/>
|
||||
|
||||
<!-- root logger:DEBUG 级别,输出到控制台和全量文件 -->
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="ALL_FILE"/>
|
||||
</root>
|
||||
|
||||
<!--
|
||||
#################################################################################
|
||||
# #
|
||||
# 全文输出日志(一个文件存储)end #
|
||||
# #
|
||||
#################################################################################
|
||||
-->
|
||||
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
#################################################################################
|
||||
# #
|
||||
# debug、info、error分文件输出 start #
|
||||
# #
|
||||
#################################################################################
|
||||
-->
|
||||
|
||||
<!-- DEBUG级别日志文件配置 -->
|
||||
<!--<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/debug.log</file>
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>DEBUG</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/debug-%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>300</maxHistory>
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
|
||||
<!– INFO级别日志文件配置 –>
|
||||
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/info.log</file>
|
||||
<encoder>
|
||||
@@ -24,11 +92,11 @@
|
||||
</filter>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/info-%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>30</maxHistory>
|
||||
<maxHistory>300</maxHistory>
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
|
||||
<!-- ERROR级别日志文件配置 -->
|
||||
<!– ERROR级别日志文件配置 –>
|
||||
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/error.log</file>
|
||||
<encoder>
|
||||
@@ -41,19 +109,31 @@
|
||||
</filter>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/error-%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>30</maxHistory>
|
||||
<maxHistory>300</maxHistory>
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
|
||||
<logger name="java" additivity="false" />
|
||||
<!– 框架日志级别 –>
|
||||
<!– <logger name="java" additivity="false" />–>
|
||||
<logger name="org.eclipse.jetty" level="INFO"/>
|
||||
<logger name="org.quartz" level="INFO"/>
|
||||
<logger name="org.nutz" level="DEBUG"/>
|
||||
|
||||
<!-- 日志级别和appender的关联 -->
|
||||
<!– 日志级别和appender的关联 –>
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="DEBUG_FILE"/>
|
||||
<appender-ref ref="INFO_FILE"/>
|
||||
<appender-ref ref="ERROR_FILE"/>
|
||||
</root>
|
||||
</root>-->
|
||||
|
||||
<!--
|
||||
#################################################################################
|
||||
# #
|
||||
# debug、info、error分文件输出 end #
|
||||
# #
|
||||
#################################################################################
|
||||
-->
|
||||
|
||||
</configuration>
|
||||
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="file-preview-container" v-if="fileList && fileList.length > 0">
|
||||
<div class="file-preview-list" v-if="compact && fileList && fileList.length > 0">
|
||||
<div class="file-preview-row" v-for="item in fileList" :key="item.id">
|
||||
<i class="el-icon-document file-preview-icon" aria-hidden="true"></i>
|
||||
<span class="file-preview-name" :title="item.name">{{ item.name }}</span>
|
||||
<div class="file-preview-actions">
|
||||
<button type="button" :aria-label="'预览' + item.name" @click="$commonUtil.previewFile(item)">预览</button>
|
||||
<button type="button" :aria-label="'下载' + item.name" @click="$downLoad(item.downloadPath)">下载</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="file-preview-container" v-else-if="fileList && fileList.length > 0">
|
||||
<div class="file-item" title="点击查看" v-for="item in fileList" :key="item.id">
|
||||
<el-image
|
||||
v-if="item.suffix === 'jpg' || item.suffix === 'png' || item.suffix === 'jpeg' || item.suffix === 'gif'"
|
||||
@@ -24,6 +34,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else-if="compact" class="file-preview-empty">暂无证明材料</span>
|
||||
<el-empty v-else description="暂无" :image-size="60"></el-empty>
|
||||
</div>
|
||||
</template>
|
||||
@@ -32,6 +43,11 @@
|
||||
module.exports = {
|
||||
name: "sysFilePreview",
|
||||
props: {
|
||||
// 表单只读态使用紧凑附件行,保留预览和下载操作,不占用缩略图空间。
|
||||
compact: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
files: {
|
||||
type: [String, Array],
|
||||
default: undefined,
|
||||
@@ -112,6 +128,69 @@ module.exports = {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.file-preview-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.file-preview-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
min-height: 40px;
|
||||
padding: 0 12px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
background: #f8fafc;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.file-preview-icon {
|
||||
flex-shrink: 0;
|
||||
color: #0075cf;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.file-preview-name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.file-preview-actions {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.file-preview-actions button {
|
||||
padding: 8px 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #0075cf;
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.file-preview-actions button:focus-visible {
|
||||
outline: 2px solid #0075cf;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.file-preview-empty {
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.file-preview-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<slot></slot>
|
||||
<div class="search-query">
|
||||
<el-button type="primary" icon="el-icon-search" @click="$emit('search', null)">搜索</el-button>
|
||||
<slot name="actions"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -83,7 +83,7 @@ layout("/layouts/platform.html"){
|
||||
<el-table-column prop="position" label="干部职务" sortable width="120" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="education" label="学历" show-overflow-tooltip sortable width="120"></el-table-column>
|
||||
<el-table-column prop="academicDegree" label="学位" show-overflow-tooltip sortable width="120"></el-table-column>
|
||||
<el-table-column prop="unitName" label="单位" show-overflow-tooltip sortable width="120"></el-table-column>
|
||||
<el-table-column prop="unit_name" label="单位" show-overflow-tooltip sortable width="120"></el-table-column>
|
||||
<el-table-column prop="unitId" label="单位编码" sortable width="120"></el-table-column>
|
||||
<el-table-column prop="political" label="政治面貌" sortable></el-table-column>
|
||||
<el-table-column prop="nation" label="民族" sortable></el-table-column>
|
||||
|
||||
@@ -53,6 +53,9 @@ layout("/layouts/platform.html"){
|
||||
<el-button @click="openAdd" size="small" type="primary">
|
||||
新增资产
|
||||
</el-button>
|
||||
<el-button @click="showImportDialog = true" size="small" type="primary">
|
||||
导入资产
|
||||
</el-button>
|
||||
<el-button @click="doExport" size="small" type="primary">
|
||||
导出资产
|
||||
</el-button>
|
||||
@@ -110,6 +113,16 @@ layout("/layouts/platform.html"){
|
||||
<template #edit>
|
||||
<asset-form ref="assetForm" @save_success="saveSuccess"></asset-form>
|
||||
</template>
|
||||
<excel-import
|
||||
ref="excelImportRef"
|
||||
url="/platform/asset/manage/doImport"
|
||||
template_url="/platform/asset/manage/downloadTemplate"
|
||||
:visible.sync="showImportDialog"
|
||||
title="导入资产"
|
||||
width="700px"
|
||||
@import-success="doSearch"
|
||||
:extra_params="{}"
|
||||
></excel-import>
|
||||
</guava>
|
||||
</div>
|
||||
|
||||
@@ -132,7 +145,8 @@ layout("/layouts/platform.html"){
|
||||
{prop: 'assetUsageStateName', label: '使用状况'},
|
||||
],
|
||||
assetCategoryOption: [],
|
||||
unionList: []
|
||||
unionList: [],
|
||||
showImportDialog: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
||||
@@ -17,10 +17,10 @@ const childManageInfo = {
|
||||
<el-descriptions-item label="中考总成绩">{{viewData.childrenMiddleScore}}</el-descriptions-item>
|
||||
<el-descriptions-item label="学科组合">{{viewData.childrenSubjectCombination}}</el-descriptions-item>
|
||||
<el-descriptions-item label="户籍所在地" :span="2">{{viewData.childrenHuKouAddress}}</el-descriptions-item>
|
||||
<el-descriptions-item label="监护人1">{{viewData.guardianUserName1}}</el-descriptions-item>
|
||||
<el-descriptions-item label="监护人1">{{viewData.guardianUserName1}}({{viewData.guardianRelationship1 || '关系待补充'}})</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{viewData.guardianMobile1}}</el-descriptions-item>
|
||||
<el-descriptions-item label="工作单位">{{viewData.guardianUnitName1}}</el-descriptions-item>
|
||||
<el-descriptions-item label="监护人2">{{viewData.guardianUserName2}}</el-descriptions-item>
|
||||
<el-descriptions-item label="监护人2">{{viewData.guardianUserName2}}({{viewData.guardianRelationship2 || '关系待补充'}})</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{viewData.guardianMobile2}}</el-descriptions-item>
|
||||
<el-descriptions-item label="工作单位">{{viewData.guardianUnitName2}}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">{{viewData.note}}</el-descriptions-item>
|
||||
|
||||
@@ -20,6 +20,7 @@ layout("/layouts/platform.html"){
|
||||
</el-card>
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool label="填报列表">
|
||||
<el-button type="primary" @click="openOnlyChildFee">独生子女费</el-button>
|
||||
</table-tool>
|
||||
<!-- 添加一个带有横向滚动的容器 -->
|
||||
<div class="table-container">
|
||||
@@ -89,11 +90,14 @@ layout("/layouts/platform.html"){
|
||||
return {
|
||||
pageDataUrl: "/platform/childManage/mine/pageData",
|
||||
pageForm: {
|
||||
year: moment().format("YYYY"),
|
||||
year: null,
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openOnlyChildFee() {
|
||||
commonUtil.pjaxPush('/platform/childManage/onlyChildFee/qualification/index')
|
||||
},
|
||||
openView(row) {
|
||||
this.$refs.guava.view(()=>{
|
||||
this.$refs.childManageInfoRef.onOpen(row)
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<!--# layout("/layouts/platform.html"){ #-->
|
||||
<div id="app" v-cloak class="only-child-fee" v-loading="busy">
|
||||
<el-card shadow="never">
|
||||
<el-empty v-if="ready && !tabs.includes(tab)" description="暂无当前页面的访问权限"></el-empty>
|
||||
<template v-if="tabs.includes(tab)">
|
||||
<div class="fee-filter"><label>年度 <el-date-picker v-model="year" type="year" value-format="yyyy" :clearable="false" @change="loadSettlement"></el-date-picker></label><el-select v-model="unit" clearable placeholder="全部单位"><el-option v-for="u in units" :key="u" :value="u"></el-option></el-select><el-input v-model="keyword" placeholder="姓名 / 工号" clearable></el-input><el-select v-model="changeType" clearable placeholder="全部变动"><el-option value="停发"></el-option><el-option value="扣回"></el-option></el-select><el-button type="primary" @click="loadSettlement">查询</el-button><el-button @click="reset">重置</el-button><el-button :disabled="!confirmed" @click="exportChanges">导出Excel</el-button></div>
|
||||
<div class="fee-recovery-summary fee-period-summary">
|
||||
<div class="fee-period-item"><span class="fee-period-label">批次</span><strong class="fee-period-value">{{batch ? batch.id : '尚未生成'}}</strong></div>
|
||||
<div class="fee-period-item"><span class="fee-period-label">核算期间</span><strong class="fee-period-value">{{periodLabel}}</strong></div>
|
||||
</div>
|
||||
<div class="fee-paper fee-notice-paper">
|
||||
<table class="fee-notice-table">
|
||||
<colgroup><col style="width:4%"><col style="width:10%"><col style="width:10%"><col style="width:22%"><col style="width:6%"><col style="width:6%"><col style="width:8%"><col style="width:6%"><col style="width:19%"><col style="width:9%"></colgroup>
|
||||
<thead>
|
||||
<tr><th colspan="10" class="fee-notice-title">{{Number(year)-1}}年11月—{{year}}年10月独生子女父母奖励费变动通知单</th></tr>
|
||||
<tr><td colspan="4" class="fee-notice-recipient">人力资源处:</td><td colspan="6" class="fee-notice-unit">单位:元</td></tr>
|
||||
<tr><th>序号</th><th>工号</th><th>姓名</th><th>单位</th><th>调前<br>标准</th><th>调后<br>标准</th><th>扣发<br>金额</th><th>补发<br>金额</th><th>停发时间</th><th>备注<br>说明</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row,index) in changes" :key="row.userId"><td>{{index+1}}</td><td>{{row.loginName}}</td><td>{{row.userName}}</td><td>{{row.unitName}}</td><td>10.00</td><td>{{cash(endMonth(row)<=periodEnd?0:10)}}</td><td>{{cash(row.deduction)}}</td><td>0.00</td><td>{{noticeStopRange(row)}}</td><td>{{row.stopBasis || (endMonth(row)<=periodEnd?'停发':'扣发调整')}}</td></tr>
|
||||
<tr v-if="!changes.length"><td colspan="10">暂无变动记录</td></tr>
|
||||
<tr><td colspan="4">合计</td><td>{{cash(changeTotals.before)}}</td><td>{{cash(changeTotals.after)}}</td><td>{{cash(changeTotals.deduction)}}</td><td>0.00</td><td></td><td></td></tr>
|
||||
</tbody>
|
||||
<tfoot><tr><td colspan="4">审核人:</td><td colspan="6">经办人:</td></tr></tfoot>
|
||||
</table>
|
||||
</div><el-alert title="导出当前筛选的全部记录,仅包含“变动表”工作表;确认前不可导出办理。" type="info" :closable="false" show-icon></el-alert>
|
||||
</template>
|
||||
</el-card>
|
||||
<!--#include('dialogs.html'){}#-->
|
||||
</div>
|
||||
<script>
|
||||
<!--#include('page.js'){}#-->
|
||||
initOnlyChildFeePage('changes');
|
||||
</script>
|
||||
<!--#include('style.html'){}#-->
|
||||
<!--# } #-->
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<el-dialog title="子女关键资料更正核验" :visible.sync="changeDialog" width="760px" :close-on-click-modal="false">
|
||||
<template v-if="selectedChange">
|
||||
<p>申请人:{{selectedChange.applicant}} 申请时间:{{selectedChange.submittedAt}}</p>
|
||||
<p>更正原因及证明依据:{{selectedChange.reason}}</p>
|
||||
<el-table :data="correctionDiff" border><el-table-column prop="label" label="更正字段"></el-table-column><el-table-column prop="before" label="修改前"></el-table-column><el-table-column prop="after" label="拟修改为"></el-table-column></el-table>
|
||||
<el-form v-if="selectedChange.status === '待核验'" label-width="140px" class="mt20">
|
||||
<el-form-item label="核验结论" required><el-select v-model="changeReview.status"><el-option value="已批准" label="批准更正"></el-option><el-option value="退回" label="退回补正"></el-option></el-select></el-form-item>
|
||||
<el-form-item label="生效月份" :required="changeReview.status === '已批准'"><el-date-picker v-model="changeReview.effectiveMonth" type="month" value-format="yyyy-MM"></el-date-picker></el-form-item>
|
||||
<el-form-item label="证明及领取资格"><el-checkbox v-model="changeReview.verified">已核实证明及双方监护人的领取资格</el-checkbox></el-form-item>
|
||||
<el-form-item label="审核依据 / 原因" required><el-input v-model="changeReview.basis" type="textarea" maxlength="2000"></el-input></el-form-item>
|
||||
<el-alert type="info" :closable="false" title="批准后按生效月份保留待遇历史版本;不得覆盖已确认清算期间。新监护人的月费资格仍需另行申请。"></el-alert>
|
||||
</el-form>
|
||||
<el-descriptions v-else border :column="2" class="mt20"><el-descriptions-item label="审核结果">{{selectedChange.status}}</el-descriptions-item><el-descriptions-item label="生效月份">{{selectedChange.effectiveMonth || '—'}}</el-descriptions-item><el-descriptions-item label="审核人">{{selectedChange.reviewer}}</el-descriptions-item><el-descriptions-item label="审核时间">{{selectedChange.reviewedAt}}</el-descriptions-item><el-descriptions-item label="审核依据">{{selectedChange.reviewBasis}}</el-descriptions-item></el-descriptions>
|
||||
</template>
|
||||
<span slot="footer"><el-button @click="changeDialog=false">关闭</el-button><el-button v-if="selectedChange && selectedChange.status === '待核验'" type="primary" :loading="busy" @click="submitCorrectionReview">保存核验结果</el-button></span>
|
||||
</el-dialog>
|
||||
<snaker-chart ref="retireChart"></snaker-chart><el-dialog :title="dialogTitle" :visible.sync="dialog" width="680px" :close-on-click-modal="false">
|
||||
<span slot="title" class="el-dialog__title">{{dialogTitle}}<el-tooltip v-if="['payment','recover'].includes(action)" :content="action === 'payment' ? '用于修订单个月份实际发放的金额。修订优先于起发月份自动计算,并更新未确认清算草稿;不会执行工资发放。' : '用于登记已实际扣回的多发费用,不会执行工资扣款。默认勾选已全部结清,按各月剩余金额结清;取消勾选后按月填写,未填写的月份保持不变。'" placement="top" popper-class="fee-help-popper"><i class="el-icon-question fee-help-icon" tabindex="0" role="button" :aria-label="dialogTitle+'说明'"></i></el-tooltip></span>
|
||||
<div v-if="['payment','recover','review','stop'].includes(action)" class="fee-recovery-summary">
|
||||
<div class="fee-recovery-person"><span class="fee-recovery-name">{{editing.userName}}</span><span class="fee-recovery-id">工号 {{editing.loginName}}</span><div class="fee-recovery-caption">{{({recover:'本期扣回登记',payment:'逐月实发修订',review:'资格核验',stop:'停发办理'})[action]}}</div></div>
|
||||
<div v-if="action === 'recover'" class="fee-recovery-balance"><div class="fee-recovery-caption">本期尚未扣回合计</div><strong>{{cash(remaining(editing))}}</strong><span class="fee-recovery-unit">元</span></div>
|
||||
<div v-else-if="action === 'payment'" class="fee-recovery-balance fee-payment-start"><div class="fee-recovery-caption">起发月份</div><strong>{{editing.actualStartMonth || '未登记'}}</strong></div>
|
||||
<div v-else class="fee-recovery-balance fee-record-state"><div class="fee-recovery-caption">{{action === 'review' ? '核验状态' : '年龄停发月'}}</div><strong>{{(action === 'review' ? editing.status : editing.stopMonth) || '—'}}</strong></div>
|
||||
</div>
|
||||
<p v-else>{{editing.userName}} {{editing.loginName}}</p>
|
||||
<div v-if="action === 'review'" class="fee-record-card">
|
||||
<div class="fee-record-fields">
|
||||
<div><span class="fee-record-label">独生子女证号</span><span>{{editing.certificateNumber || '—'}}</span></div>
|
||||
<div><span class="fee-record-label">监护关系</span><span>{{editing.relationship || '—'}}</span></div>
|
||||
<div><span class="fee-record-label">子女姓名</span><span>{{editing.childName || '—'}}</span></div>
|
||||
<div><span class="fee-record-label">出生日期</span><span>{{editing.birthday || '—'}}</span></div>
|
||||
<div class="fee-record-wide"><span class="fee-record-label">证明材料</span><file-preview :files="editing.proof" complete_result compact></file-preview></div>
|
||||
<div class="fee-record-wide"><span class="fee-record-label">原核定依据</span><span>{{editing.basis || '—'}}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<el-form :class="{'fee-form-card':['review','stop'].includes(action)}" :label-width="['payment','recover'].includes(action) ? '140px' : '150px'" :label-position="['payment','recover','review','stop'].includes(action) ? 'left' : 'right'" :disabled="action === 'review' && editing.status !== '待核验'">
|
||||
<template v-if="action === 'review'"><el-form-item label="审核结论" required><el-select v-model="entry.status"><el-option v-for="s in ['已核定','退回','不通过']" :key="s" :value="s"></el-option></el-select></el-form-item><el-form-item label="独立核定起发月份"><el-date-picker v-model="entry.startMonth" type="month" value-format="yyyy-MM"></el-date-picker></el-form-item><el-form-item label="资格核验"><el-checkbox v-model="entry.verified">已核验证明及重复领取情况</el-checkbox></el-form-item></template>
|
||||
<el-form-item v-if="action === 'stop'" label="终止 / 实际停发月" required><el-date-picker v-model="entry.endMonth" type="month" value-format="yyyy-MM"></el-date-picker></el-form-item>
|
||||
<el-form-item v-if="['review','stop'].includes(action)" label="核定依据 / 原因" required><el-input v-model="entry.basis" type="textarea" maxlength="2000"></el-input></el-form-item>
|
||||
<template v-if="action === 'payment'"><el-form-item label="原发放月份" required><span slot="label">原发放月份<el-tooltip content="选择要修订实发金额的那个月份,不是首次起发月份。" placement="top" popper-class="fee-help-popper"><i class="el-icon-question fee-help-icon" tabindex="0" role="button" aria-label="原发放月份说明"></i></el-tooltip></span><el-date-picker v-model="entry.month" type="month" value-format="yyyy-MM"></el-date-picker></el-form-item></template>
|
||||
<template v-if="action === 'payment'"><el-form-item label="实发金额" required><span slot="label">实发金额<el-tooltip content="填写该月实际发放的总金额,不是差额。核实未发放填0,未知金额请勿登记;其他月份不受影响。" placement="top" popper-class="fee-help-popper"><i class="el-icon-question fee-help-icon" tabindex="0" role="button" aria-label="实发金额说明"></i></el-tooltip></span><el-input-number v-model="entry.paid" :min="0" :precision="2"></el-input-number></el-form-item></template>
|
||||
<template v-if="action === 'recover'">
|
||||
<el-form-item label-width="0px"><el-checkbox v-model="entry.settledAll">已全部结清</el-checkbox><el-tooltip content="确认本期剩余多发费用均已追回后勾选,保存时按各月剩余金额结清,不要求指定扣回月份。仅追回部分时取消勾选。" placement="top" popper-class="fee-help-popper"><i class="el-icon-question fee-help-icon" tabindex="0" role="button" aria-label="已全部结清说明"></i></el-tooltip></el-form-item>
|
||||
<div v-if="!entry.settledAll" style="max-height:360px;overflow-y:auto;padding-right:8px">
|
||||
<div v-for="item in entry.recoveryMonths" :key="item.month" class="fee-recovery-month">
|
||||
<div class="fee-recovery-month-title">{{item.month}} · 尚未扣回 {{cash(item.remaining)}} 元</div>
|
||||
<el-form-item label="实际扣回月份"><span slot="label">实际扣回月份<el-tooltip content="填写实际完成扣款的月份,不得早于该笔费用原发放月份;未办理的月份留空。" placement="top" popper-class="fee-help-popper"><i class="el-icon-question fee-help-icon" tabindex="0" role="button" aria-label="实际扣回月份说明"></i></el-tooltip></span><el-date-picker v-model="item.executionMonth" type="month" value-format="yyyy-MM"></el-date-picker></el-form-item>
|
||||
<el-form-item label="实际扣回金额"><span slot="label">实际扣回金额<el-tooltip content="填写这次已扣回且归属于上方月份的金额,须大于0且不得超过该月剩余余额;已登记的扣回不要重复填写。" placement="top" popper-class="fee-help-popper"><i class="el-icon-question fee-help-icon" tabindex="0" role="button" aria-label="实际扣回金额说明"></i></el-tooltip></span><el-input-number v-model="item.amount" :min="0" :max="item.remaining" :precision="2"></el-input-number></el-form-item>
|
||||
</div>
|
||||
<el-empty v-if="!entry.recoveryMonths.length" description="暂无待扣回月份"></el-empty>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="action === 'reward'"><el-form-item label="一次性奖励">3000.00元</el-form-item><el-form-item label="历史奖励核查" required><el-checkbox v-model="entry.historyChecked">已核实该教职工未重复领取</el-checkbox></el-form-item><el-form-item label="历史核查依据" required><el-input v-model="entry.historyBasis" type="textarea"></el-input></el-form-item><el-form-item label="实际发放日期" required><el-date-picker v-model="entry.paidDate" type="date" value-format="yyyy-MM-dd"></el-date-picker></el-form-item></template>
|
||||
<el-form-item v-if="action === 'reward'" label="实际执行凭据" required><el-input v-model="entry.voucher" type="textarea"></el-input></el-form-item>
|
||||
</el-form><span slot="footer"><el-button @click="dialog=false">关闭</el-button><el-button v-if="action !== 'review' || editing.status === '待核验'" type="primary" :loading="busy" @click="submitEntry">保存办理结果</el-button></span>
|
||||
</el-dialog>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<!--# layout("/layouts/platform.html"){ #-->
|
||||
<div id="app" v-cloak class="only-child-fee" v-loading="busy">
|
||||
<el-card shadow="never">
|
||||
<el-empty v-if="ready && !tabs.includes(tab)" description="暂无当前页面的访问权限"></el-empty>
|
||||
</el-card>
|
||||
</div>
|
||||
<script>
|
||||
<!--#include('page.js'){}#-->
|
||||
initOnlyChildFeePage('');
|
||||
</script>
|
||||
<!--# } #-->
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<!--# layout("/layouts/platform.html"){ #-->
|
||||
<div id="app" v-cloak class="only-child-fee" v-loading="busy">
|
||||
<el-card shadow="never">
|
||||
<el-empty v-if="ready && !tabs.includes(tab)" description="暂无当前页面的访问权限"></el-empty>
|
||||
<template v-if="tabs.includes(tab)">
|
||||
<template v-if="corrections.length">
|
||||
<h4>关键资料更正申请</h4>
|
||||
<el-table :data="corrections" border class="mb10">
|
||||
<el-table-column prop="applicant" label="申请人"></el-table-column>
|
||||
<el-table-column label="子女"><template slot-scope="{row}">{{row.oldData.childrenName}}</template></el-table-column>
|
||||
<el-table-column prop="reason" label="更正原因"></el-table-column>
|
||||
<el-table-column prop="submittedAt" label="申请时间"></el-table-column>
|
||||
<el-table-column prop="status" label="状态"></el-table-column>
|
||||
<el-table-column prop="effectiveMonth" label="生效月份"></el-table-column>
|
||||
<el-table-column label="操作"><template slot-scope="{row}"><el-button type="text" @click="openCorrection(row)">{{row.status === '待核验' ? '核验更正' : '查看更正记录'}}</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
<search @search="perform(()=>fetchTab())">
|
||||
<search-item label="姓名 / 工号"><el-input v-model="ledgerKeyword" placeholder="请输入姓名或工号" clearable @keyup.enter.native="perform(()=>fetchTab())"></el-input></search-item>
|
||||
<search-item label="所属单位"><el-select v-model="ledgerUnit" placeholder="全部单位" clearable filterable><el-option v-for="unit in ledgerUnits" :key="unit" :value="unit"></el-option></el-select></search-item>
|
||||
<search-item label="子女姓名"><el-input v-model="ledgerChild" placeholder="请输入子女姓名" clearable @keyup.enter.native="perform(()=>fetchTab())"></el-input></search-item>
|
||||
<search-item label="核验状态"><el-select v-model="ledgerReviewStatus" placeholder="全部核验状态" clearable><el-option v-for="status in ledgerReviewStatuses" :key="status" :value="status"></el-option></el-select></search-item>
|
||||
<template slot="actions"><el-button @click="resetLedgerFilters">重置</el-button></template>
|
||||
</search>
|
||||
<table-tool label="待遇台账" class="fee-standard-toolbar">
|
||||
<el-radio-group v-model="ledgerState" aria-label="待遇状态筛选"><el-radio-button label="all">全部</el-radio-button><el-radio-button label="pending">未起发</el-radio-button><el-radio-button label="active">发放中</el-radio-button><el-radio-button label="stopped">已停发</el-radio-button></el-radio-group>
|
||||
</table-tool>
|
||||
<el-table :data="qualificationRows" border class="mt10">
|
||||
<el-table-column prop="loginName" label="工号"></el-table-column><el-table-column prop="userName" label="姓名"></el-table-column><el-table-column prop="unitName" label="单位"></el-table-column><el-table-column prop="childName" label="子女"></el-table-column><el-table-column prop="startMonth" label="起发月"></el-table-column><el-table-column prop="stopMonth" label="年龄停发月"></el-table-column><el-table-column prop="status" label="核验状态"></el-table-column>
|
||||
<el-table-column label="待遇状态"><template slot-scope="{row}">{{({pending:'未起发',active:'发放中',stopped:'已停发'})[ledgerBenefitStatus(row)]}}</template></el-table-column>
|
||||
<el-table-column label="操作" width="200"><template slot-scope="{row}"><el-button type="text" @click="open('review',row)">{{row.status === '待核验' ? '核验' : '查看依据'}}</el-button><el-button v-if="row.startMonth" type="text" @click="open('payment',row)">登记实发</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
</el-card>
|
||||
<!--#include('dialogs.html'){}#-->
|
||||
</div>
|
||||
<script>
|
||||
<!--#include('page.js'){}#-->
|
||||
initOnlyChildFeePage('ledger');
|
||||
</script>
|
||||
<!--#include('style.html'){}#-->
|
||||
<!--# } #-->
|
||||
@@ -0,0 +1,148 @@
|
||||
// 独立页面固定业务入口;旧入口仅跳转至已授权页面,不在无权限时替换页面内容。
|
||||
function initOnlyChildFeePage(page) {
|
||||
new Vue({
|
||||
el: '#app', store,
|
||||
data() { return {tab:page,busy:false,ready:false,tabs:[],profile:{},children:[],qualifications:[],form:{proof:[]},ledgerKeyword:'',ledgerUnit:'',ledgerChild:'',ledgerReviewStatus:'',ledgerState:'all',corrections:[],changeDialog:false,selectedChange:null,changeReview:{},
|
||||
year:moment().format('YYYY'),unit:'',keyword:'',changeType:'',batch:null,rows:[],recoveries:[],expandedRowKeys:[],settlementPage:1,settlementPageSize:10,
|
||||
retirements:[],retireYear:'',retireUnit:'',retireKeyword:'',approval:'',paidStatus:'',retireDetail:null,
|
||||
dialog:false,action:'',entry:{},editing:{},amountColumns:[{key:'due',label:'应发金额'},{key:'paid',label:'实发金额'},{key:'recovered',label:'已扣回'},{key:'pending',label:'本次扣回'}]}; },
|
||||
computed:{
|
||||
periodStart(){return this.batch&&this.batch.periodStart||String(Number(this.year)-1)+'-11';},
|
||||
periodEnd(){return this.batch&&this.batch.periodEnd||this.year+'-10';},
|
||||
periodLabel(){return this.periodStart.replace('-','.')+'—'+this.periodEnd.replace('-','.');},
|
||||
correctionDiff(){
|
||||
if(!this.selectedChange)return [];
|
||||
const labels={childrenName:'子女姓名',childrenIdCard:'身份证号',childrenBirthday:'出生日期',childrenSex:'性别',guardianUserName1:'第一监护人姓名',guardianLoginName1:'第一监护人工号',guardianRelationship1:'第一监护人关系',guardianUserName2:'第二监护人姓名',guardianLoginName2:'第二监护人工号',guardianRelationship2:'第二监护人关系'};
|
||||
return Object.keys(labels).filter(k=>(this.selectedChange.oldData[k]||'')!==(this.selectedChange.newData[k]||'')).map(k=>({label:labels[k],before:this.selectedChange.oldData[k]||'—',after:this.selectedChange.newData[k]||'—'}));
|
||||
},
|
||||
editable(){return !this.form.status || ['草稿','退回','不通过'].includes(this.form.status);},
|
||||
childBirthday(){if(!this.editable && this.form.birthday)return this.form.birthday;const c=this.children.find(c=>c.id===this.form.childId);return c?this.date(c.childrenBirthday):this.form.birthday;},
|
||||
ledgerUnits(){return [...new Set(this.qualifications.map(q=>q.unitName).filter(Boolean))];},
|
||||
ledgerReviewStatuses(){return [...new Set(this.qualifications.map(q=>q.status).filter(Boolean))];},
|
||||
qualificationRows(){
|
||||
const keyword=this.ledgerKeyword.trim(),child=this.ledgerChild.trim();
|
||||
return this.qualifications.filter(q=>(!keyword||((q.userName||'')+' '+(q.loginName||'')).includes(keyword))
|
||||
&&(!this.ledgerUnit||q.unitName===this.ledgerUnit)&&(!child||(q.childName||'').includes(child))
|
||||
&&(!this.ledgerReviewStatus||q.status===this.ledgerReviewStatus)
|
||||
&&(this.ledgerState==='all'||this.ledgerBenefitStatus(q)===this.ledgerState));
|
||||
},
|
||||
visibleAmountColumns(){return this.amountColumns.filter(c=>c.key!=='pending');},
|
||||
pagedSettlementRows(){const start=(this.settlementPage-1)*this.settlementPageSize;return this.filteredRows.slice(start,start+this.settlementPageSize);},
|
||||
units(){return [...new Set(this.rows.map(r=>r.unitName).filter(Boolean))];},
|
||||
filteredRows(){return this.rows.filter(r=>(!this.unit || r.unitName===this.unit)&&(!this.keyword || (r.userName+r.loginName).includes(this.keyword)));},
|
||||
confirmed(){return this.batch && this.batch.status!=='草稿';},
|
||||
canConfirm(){return this.batch && this.batch.status==='草稿' && this.rows.length && this.rows.every(r=>this.completeAmounts(r));},
|
||||
changes(){return this.filteredRows.map(r=>Object.assign({},r,{deduction:this.noticeDeduction(r)})).filter(r=>{const stop=this.endMonth(r)>=this.periodStart&&this.endMonth(r)<=this.periodEnd;return (stop||r.deduction>0)&&(!this.changeType||(this.changeType==='停发'?stop:r.deduction>0));});},
|
||||
changeTotals(){return {before:this.changes.length*10,after:this.changes.reduce((n,r)=>n+(this.endMonth(r)<=this.periodEnd?0:10),0),deduction:this.changes.some(r=>r.deduction===null)?null:this.changes.reduce((n,r)=>n+Math.round(r.deduction*100),0)/100};},
|
||||
recoveryRows(){const ids=this.filteredRows.map(r=>r.userId);return this.recoveries.filter(r=>r.month>=this.periodStart&&r.month<=this.periodEnd&&ids.includes(r.userId));},
|
||||
retirementUnits(){return [...new Set(this.retirements.map(r=>r.unitName).filter(Boolean))];},
|
||||
retirementRows(){return this.retirements.filter(r=>(!this.retireYear||this.date(r.retireTime).startsWith(this.retireYear))&&(!this.retireUnit||r.unitName===this.retireUnit)&&(!this.retireKeyword||(r.userName+r.loginName).includes(this.retireKeyword))&&(!this.approval||(this.approval==='已通过')===r.approved)&&(!this.paidStatus||(this.paidStatus==='已发放')===!!r.payment));},
|
||||
dialogTitle(){return {review:'资格核验',stop:'办理停发 / 资格终止',payment:'登记逐月实发',recover:'登记实际扣回',reward:'登记退休奖励发放'}[this.action];}
|
||||
},
|
||||
watch:{
|
||||
unit(){this.changeSettlementPage(1);},
|
||||
keyword(){this.changeSettlementPage(1);},
|
||||
// 刷新后保留当前页;数据减少时回到有效页,避免出现空白页。
|
||||
rows(){this.settlementPage=Math.min(this.settlementPage,Math.max(1,Math.ceil(this.filteredRows.length/this.settlementPageSize)));}
|
||||
},
|
||||
methods:{
|
||||
// 通知单保留清算期间的扣发总额,已执行扣回只影响余额,不冲减通知单金额。
|
||||
noticeDeduction(row){
|
||||
if(!Array.isArray(row.months)||!row.months.length)return null;
|
||||
let cents=0;
|
||||
for(const m of row.months){
|
||||
if(m.month<this.periodStart||m.month>this.periodEnd)continue;
|
||||
const paid=this.amount(m.paid),due=this.amount(m.due);
|
||||
if(paid===null||due===null)return null;
|
||||
cents+=Math.max(0,Math.round(paid*100)-Math.round(due*100));
|
||||
}
|
||||
return cents/100;
|
||||
},
|
||||
noticeStopRange(row){const end=this.endMonth(row);return end&&end<=this.periodEnd?end.replace('-','.')+'-'+this.year+'.10':'—';},
|
||||
changeSettlementPage(page){this.settlementPage=page;this.expandedRowKeys=[];},
|
||||
changeSettlementSize(size){this.settlementPageSize=size;this.changeSettlementPage(1);},
|
||||
resetLedgerFilters(){this.ledgerKeyword='';this.ledgerUnit='';this.ledgerChild='';this.ledgerReviewStatus='';this.ledgerState='all';},
|
||||
// 待遇状态按当前月份及终止信息判断,与资料审核状态分别筛选。
|
||||
ledgerBenefitStatus(row){
|
||||
const now=moment().format('YYYY-MM'),end=this.endMonth(row);
|
||||
if(row.status==='已停发'||row.eligible===false||(row.startMonth&&end&&end<=now))return 'stopped';
|
||||
return row.status==='已核定'&&row.startMonth&&row.startMonth<=now?'active':'pending';
|
||||
},
|
||||
openCorrection(row){this.selectedChange=row;this.changeReview={id:row.id,status:'已批准',basis:'',verified:false,effectiveMonth:''};this.changeDialog=true;},
|
||||
submitCorrectionReview(){this.perform(async()=>{await this.request('reviewChildChange',{data:JSON.stringify(this.changeReview)});this.changeDialog=false;await this.context();this.corrections=await this.request('childChanges');this.$message.success('更正核验结果已保存');});},
|
||||
async request(path,data={}){
|
||||
let r;
|
||||
try { r=await this.$axios.post('/platform/childManage/onlyChildFee/'+path,data); }
|
||||
// 全局拦截器已提示业务或网络错误,页面只中断后续操作,避免重复弹窗。
|
||||
catch(e){throw {handled:true,cause:e};}
|
||||
if(r.code!==0)throw new Error(r.msg||'操作未完成');
|
||||
return r.data;
|
||||
},
|
||||
async perform(work){if(this.busy)return;this.busy=true;try{await work();}catch(e){if(!e || !e.handled)this.$message.error((e && (e.msg||e.message))||'操作未完成');}finally{this.busy=false;}},
|
||||
async context(){const d=await this.request('context');this.tabs=d.tabs||[];this.ready=true;if(!this.tab){const requested=GetQueryString('tab');const target=this.tabs.includes(requested)?requested:this.tabs[0];if(target)this.go('/platform/childManage/onlyChildFee/'+target+'/index');return;}this.profile=d.profile||{};this.children=d.children||[];this.qualifications=d.qualifications||[];this.form=d.mine||{proof:[],onlyChild:null,childId:'',relationship:'',certificateNumber:''};},
|
||||
save(submit){this.perform(async()=>{await this.request('save',{data:JSON.stringify(this.form),submit});await this.context();this.$message.success(submit?'已提交核验':'草稿已保存');});},
|
||||
async fetchTab(){if(this.tab==='ledger'){await this.context();this.corrections=await this.request('childChanges');}else if(this.tab==='retirement')await this.fetchRetirement();else if(['settlement','changes'].includes(this.tab))await this.fetchSettlement();},
|
||||
async fetchSettlement(){const d=await this.request('settlement',{year:this.year});this.batch=d.batch;this.rows=d.rows.map(row=>Object.assign({},row,{actualStartMonth:row.actualStartMonth||'',savedActualStartMonth:row.actualStartMonth||''}));this.recoveries=d.recoveries;this.expandedRowKeys=[];},
|
||||
// 选择仅作为待确认值;取消或保存失败时继续显示已保存月份。
|
||||
savePaymentStart(row){
|
||||
const selected=row.actualStartMonth,previous=row.savedActualStartMonth;
|
||||
row.actualStartMonth=previous;
|
||||
if(!selected||selected===previous)return;
|
||||
this.perform(async()=>{
|
||||
try{await this.$confirm('将首次实际发放月份设为 '+selected+'。更改后,各年度未确认的实发记录按起发前0元、起发后每月10元重新计算,待遇停发月份只影响应发;单月修订和已确认快照保留。是否确认?','确认起发月份',{type:'warning',confirmButtonText:'确认保存',cancelButtonText:'取消'});}
|
||||
catch(e){return;}
|
||||
await this.request('paymentStart',{data:JSON.stringify({userId:row.userId,year:Number(this.year),actualStartMonth:selected})});
|
||||
await this.fetchSettlement();this.expandedRowKeys=[row.userId];
|
||||
this.$message.success('首次实际发放月份已保存,各年度共用,未确认清算草稿已更新');
|
||||
});
|
||||
},
|
||||
loadSettlement(){this.perform(()=>this.fetchSettlement());},
|
||||
makeBatch(confirm){const run=()=>this.perform(async()=>{await this.request('batch',{year:this.year,confirm});await this.fetchSettlement();this.$message.success(confirm?'批次已确认并保存快照':'草稿已生成');});if(confirm)this.$confirm('确认当前全部单位 '+this.periodLabel+' 的逐月依据?确认后快照不可修改。','复核确认').then(run).catch(()=>{});else run();},
|
||||
async fetchRetirement(){this.retirements=await this.request('retirement');this.retireDetail=null;},
|
||||
loadRetirement(){this.perform(()=>this.fetchRetirement());},
|
||||
reset(){this.unit='';this.keyword='';this.changeType='';this.loadSettlement();},
|
||||
open(action,row){this.action=action;this.editing=row;this.entry={userId:row.userId,id:row.id,status:'已核定',verified:false,startMonth:row.startMonth||'',basis:'',endMonth:row.stopMonth||'',month:'',paid:undefined,source:'',amount:undefined,executionMonth:'',voucher:'',historical:false,historyChecked:false,historyBasis:'',paidDate:'',settledAll:true,recoveryMonths:(row.months||[]).map(m=>({month:m.month,remaining:this.remaining(Object.assign({},row,{months:[m]})),executionMonth:'',amount:undefined})).filter(m=>m.remaining>0)};this.dialog=true;},
|
||||
submitEntry(){this.perform(async()=>{
|
||||
if(this.action==='recover')this.entry.year=Number(this.year);
|
||||
const payload=Object.assign({},this.entry);
|
||||
if(this.action==='recover')payload.recoveryMonths=this.entry.settledAll?[]:this.entry.recoveryMonths.filter(m=>m.executionMonth||m.amount!==undefined&&m.amount!==null);
|
||||
await this.request(this.action,{data:JSON.stringify(payload)});this.dialog=false;await this.context();
|
||||
if(['settlement','changes'].includes(this.tab)){
|
||||
await this.fetchSettlement();
|
||||
// 补录后展开办理人员,直接呈现更新后的逐月记录。
|
||||
if(this.action==='payment'&&this.rows.some(r=>r.userId===this.entry.userId))this.expandedRowKeys=[this.entry.userId];
|
||||
}
|
||||
if(this.tab==='retirement')await this.fetchRetirement();
|
||||
this.$message.success(this.action==='payment'?this.entry.month+' 实发记录已保存('+this.cash(this.entry.paid)+'元)':'办理结果已保存');
|
||||
});},
|
||||
// 起发前、发放期间及停发后均可能多发,登记入口只按尚未扣回余额判断。
|
||||
canRegisterRecovery(row){return this.remaining(row)>0;},
|
||||
recoveryStatus(row){return this.completeAmounts(row)&&this.remaining(row)===0&&(Number(row.pending)>0||Number(row.recovered)>0)?'已结清':row.status;},
|
||||
// 汇总全部筛选结果,不受分页影响;缺失实发整行不参与金额汇总。
|
||||
summary({columns}){const valid=this.filteredRows.filter(r=>this.completeAmounts(r));return columns.map(c=>c.property==='loginName'?'筛选结果合计':this.amountColumns.some(a=>a.key===c.property)?(valid.length?valid.reduce((sum,r)=>sum+this.amount(r[c.property]),0).toFixed(2):'—'):'');},
|
||||
// 展开新行时替换唯一的展开键,重复点击当前行则收起。
|
||||
expandSettlementRow(row,expandedRows){this.expandedRowKeys=expandedRows.some(r=>r.userId===row.userId)?[row.userId]:[];},
|
||||
// 空值、空字符串及非数值不能视作实发零元,也不能进入金额汇总。
|
||||
amount(value){if(typeof value!=='number'&&(typeof value!=='string'||!value.trim()))return null;const n=Number(value);return Number.isFinite(n)?n:null;},
|
||||
completeAmounts(row){return row.status!=='资料不全'&&this.amountColumns.every(c=>this.amount(row[c.key])!==null);},
|
||||
// 执行余额按实际扣回记录计算,确认快照的应扣金额保持不变。
|
||||
remaining(row){
|
||||
if(!Array.isArray(row.months)||!row.months.length||!this.completeAmounts(row))return null;
|
||||
let total=0;
|
||||
for(const m of row.months){
|
||||
const paid=this.amount(m.paid),due=this.amount(m.due);
|
||||
const backs=this.recoveries.filter(r=>r.userId===row.userId&&r.month===m.month).map(r=>this.amount(r.amount));
|
||||
if(paid===null||due===null||backs.some(n=>n===null))return null;
|
||||
total+=Math.max(0,Math.round((paid-due-backs.reduce((sum,n)=>sum+n,0))*100)/100);
|
||||
}
|
||||
return total;
|
||||
},
|
||||
exportRewards(){window.location.href='/platform/childManage/onlyChildFee/exportReward?'+new URLSearchParams({year:this.retireYear||'',unit:this.retireUnit,keyword:this.retireKeyword,approval:this.approval,paidStatus:this.paidStatus}).toString();},
|
||||
exportChanges(){window.location.href='/platform/childManage/onlyChildFee/export?'+new URLSearchParams({year:this.year,unit:this.unit,keyword:this.keyword,type:this.changeType}).toString();},
|
||||
endMonth(row){return row.endMonth && row.endMonth<row.stopMonth?row.endMonth:row.stopMonth;},
|
||||
person(id){const r=this.qualifications.concat(this.rows).find(q=>q.userId===id);return r?r.userName+' / '+r.loginName:id;},
|
||||
cash(n){const value=this.amount(n);return value===null?'待补充':value.toFixed(2);},date(v){return v?moment(v).format('YYYY-MM-DD'):'';},go(url){commonUtil.pjaxPush(url);}
|
||||
},
|
||||
created(){this.perform(async()=>{await this.context();if(this.tabs.includes(this.tab))await this.fetchTab();});}
|
||||
});
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
<!--# layout("/layouts/platform.html"){ #-->
|
||||
<div id="app" v-cloak class="only-child-fee" v-loading="busy">
|
||||
<el-card shadow="never">
|
||||
<el-empty v-if="ready && !tabs.includes(tab)" description="暂无当前页面的访问权限"></el-empty>
|
||||
<template v-if="tabs.includes(tab)">
|
||||
<h3>教职工信息</h3>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="姓名">{{profile.username}}</el-descriptions-item>
|
||||
<el-descriptions-item label="工号">{{profile.loginname}}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属工会">{{profile.unionName}}</el-descriptions-item>
|
||||
<el-descriptions-item label="所在单位">{{profile.unitName}}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<h3>子女与资格信息</h3>
|
||||
<el-form label-width="145px" :disabled="!editable" class="fee-grid">
|
||||
<el-form-item label="关联子女" required>
|
||||
<el-select v-model="form.childId" placeholder="请选择已提交子女" style="width:100%">
|
||||
<el-option v-for="c in children" :key="c.id" :value="c.id" :label="c.childrenName + '(' + date(c.childrenBirthday) + ')'"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="出生日期"><el-input :value="childBirthday" readonly></el-input></el-form-item>
|
||||
<el-form-item label="是否独生子女" required><el-select v-model="form.onlyChild"><el-option label="是" :value="true"></el-option><el-option label="否" :value="false"></el-option></el-select></el-form-item>
|
||||
<el-form-item label="独生子女证号" required><el-input v-model="form.certificateNumber" maxlength="100"></el-input></el-form-item>
|
||||
<el-form-item label="与子女关系" required><el-select v-model="form.relationship"><el-option v-for="r in ['父亲','母亲','其他法定监护人']" :key="r" :value="r"></el-option></el-select></el-form-item>
|
||||
<el-form-item label="证明材料" required>
|
||||
<file-upload v-if="editable" :value.sync="form.proof" :upload_number="5" upload_result_type="url" complete_result upload_result_category="array"></file-upload>
|
||||
<file-preview v-else :files="form.proof" complete_result compact></file-preview>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<h3>待遇核定</h3>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="领取主体">教职工本人(双职工分别核定)</el-descriptions-item>
|
||||
<el-descriptions-item label="每月标准">10.00元</el-descriptions-item>
|
||||
<el-descriptions-item label="起发月份">{{form.startMonth || '待独立核定'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="年龄停发月份">{{form.stopMonth || '核验后计算'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="资格状态">{{form.status || '未申请'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="核定依据 / 退回原因">{{form.basis || '—'}}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div class="fee-actions"><el-button @click="go('/platform/childManage/mine/index')">维护子女资料</el-button><el-button v-if="editable" @click="save(false)" :disabled="busy">保存草稿</el-button><el-button v-if="editable" type="primary" @click="save(true)" :disabled="busy">提交核定</el-button></div>
|
||||
</template>
|
||||
</el-card>
|
||||
<!--#include('dialogs.html'){}#-->
|
||||
</div>
|
||||
<script>
|
||||
<!--#include('page.js'){}#-->
|
||||
initOnlyChildFeePage('qualification');
|
||||
</script>
|
||||
<!--#include('style.html'){}#-->
|
||||
<!--# } #-->
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<!--# layout("/layouts/platform.html"){ #-->
|
||||
<div id="app" v-cloak class="only-child-fee" v-loading="busy">
|
||||
<el-card shadow="never">
|
||||
<el-empty v-if="ready && !tabs.includes(tab)" description="暂无当前页面的访问权限"></el-empty>
|
||||
<template v-if="tabs.includes(tab)">
|
||||
<!-- <el-alert title="退休奖励是否限制子女年龄待确认,不自动套用月费停发规则。一次性3000元,审批通过且核实未重复领取后登记实际发放。" type="warning" :closable="false" show-icon></el-alert>-->
|
||||
<template v-if="tabs.includes('retirement')"><search @search="loadRetirement">
|
||||
<search-item label="年度"><el-date-picker v-model="retireYear" type="year" value-format="yyyy" placeholder="全部年度" clearable></el-date-picker></search-item>
|
||||
<search-item label="所属单位"><el-select v-model="retireUnit" clearable placeholder="全部单位"><el-option v-for="u in retirementUnits" :key="u" :value="u"></el-option></el-select></search-item>
|
||||
<search-item label="姓名 / 工号"><el-input v-model="retireKeyword" placeholder="姓名 / 工号" clearable @keyup.enter.native="loadRetirement"></el-input></search-item>
|
||||
<search-item label="审核状态"><el-select v-model="approval" clearable placeholder="全部审核状态"><el-option value="已通过"></el-option><el-option value="未通过审批"></el-option></el-select></search-item>
|
||||
<search-item label="发放状态"><el-select v-model="paidStatus" clearable placeholder="全部发放状态"><el-option value="已发放"></el-option><el-option value="未发放"></el-option></el-select></search-item>
|
||||
<template slot="actions"><el-button @click="retireYear='';retireUnit='';retireKeyword='';approval='';paidStatus=''">重置</el-button></template>
|
||||
</search>
|
||||
<table-tool label="退休奖励申请" class="fee-standard-toolbar">
|
||||
<el-button type="primary" @click="exportRewards">导出奖励名单</el-button>
|
||||
<el-button @click="go('/platform/dsznfmtx/apply/index')">进入原申请模块</el-button>
|
||||
<el-button @click="go('/platform/dsznfmtx/mine/index')">查看原申请及审批流程</el-button>
|
||||
</table-tool>
|
||||
<el-table :data="retirementRows" border><el-table-column prop="loginName" label="工号"></el-table-column><el-table-column prop="userName" label="姓名"></el-table-column><el-table-column prop="unitName" label="单位"></el-table-column><el-table-column label="退休日期"><template slot-scope="{row}">{{date(row.retireTime)}}</template></el-table-column><el-table-column label="奖励金额"><template slot-scope="scope">3000.00</template></el-table-column><el-table-column label="审核状态"><template slot-scope="{row}">{{row.approved ? '已通过' : '未通过审批'}}</template></el-table-column><el-table-column label="发放状态"><template slot-scope="{row}">{{row.payment ? '已发放' : '未发放'}}</template></el-table-column><el-table-column label="操作"><template slot-scope="{row}"><el-button type="text" @click="retireDetail=row">查看申请</el-button><el-button type="text" :disabled="!row.approved || !!row.payment" @click="open('reward',row)">登记发放</el-button></template></el-table-column></el-table>
|
||||
<template v-if="retireDetail"><div class="fee-record-card"><h3>{{retireDetail.userName}} · 申请详情</h3><el-button v-if="retireDetail.instanceId" type="text" @click="$refs.retireChart.onOpenFull(retireDetail.instanceProcessDefineId,retireDetail.instanceId)">查看原审批流程</el-button><el-descriptions :column="2" border><el-descriptions-item label="子女出生日期">{{date(retireDetail.childrenBirthday)}}</el-descriptions-item><el-descriptions-item label="子女年龄条件">待确认</el-descriptions-item><el-descriptions-item label="独生子女证号">{{retireDetail.childrenGraceNumber}}</el-descriptions-item><el-descriptions-item label="退休证"><file-preview :files="retireDetail.retireFiles" complete_result compact></file-preview></el-descriptions-item><el-descriptions-item label="独生子女证明"><file-preview :files="retireDetail.honorFiles" complete_result compact></file-preview></el-descriptions-item><el-descriptions-item label="历史核查依据">{{retireDetail.payment ? retireDetail.payment.historyBasis : '待核实'}}</el-descriptions-item></el-descriptions><h3>实际发放记录</h3><el-descriptions :column="3" border><el-descriptions-item label="发放金额">{{retireDetail.payment ? retireDetail.payment.amount : '—'}}</el-descriptions-item><el-descriptions-item label="发放日期">{{retireDetail.payment ? retireDetail.payment.paidDate : '—'}}</el-descriptions-item><el-descriptions-item label="发放凭据">{{retireDetail.payment ? retireDetail.payment.voucher : '—'}}</el-descriptions-item></el-descriptions></div></template>
|
||||
</template>
|
||||
</template>
|
||||
</el-card>
|
||||
<!--#include('dialogs.html'){}#-->
|
||||
</div>
|
||||
<script>
|
||||
<!--#include('page.js'){}#-->
|
||||
initOnlyChildFeePage('retirement');
|
||||
</script>
|
||||
<!--#include('style.html'){}#-->
|
||||
<!--# } #-->
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<!--# layout("/layouts/platform.html"){ #-->
|
||||
<div id="app" v-cloak class="only-child-fee" v-loading="busy">
|
||||
<el-card shadow="never">
|
||||
<el-empty v-if="ready && !tabs.includes(tab)" description="暂无当前页面的访问权限"></el-empty>
|
||||
<template v-if="tabs.includes(tab)">
|
||||
<div class="fee-filter"><label>年度 <el-date-picker v-model="year" type="year" value-format="yyyy" :clearable="false" @change="loadSettlement"></el-date-picker></label><el-select v-model="unit" clearable placeholder="全部单位"><el-option v-for="u in units" :key="u" :value="u"></el-option></el-select><el-input v-model="keyword" placeholder="姓名 / 工号" clearable></el-input><el-button type="primary" @click="loadSettlement">查询</el-button><el-button @click="reset">重置</el-button></div>
|
||||
<div class="fee-recovery-summary fee-period-summary">
|
||||
<div class="fee-period-item"><span class="fee-period-label">统计月份</span><strong class="fee-period-value">11月</strong></div>
|
||||
<div class="fee-period-item"><span class="fee-period-label">核算期间</span><strong class="fee-period-value">{{periodLabel}}</strong></div>
|
||||
<div class="fee-period-item"><span class="fee-period-label">批次</span><strong class="fee-period-value">{{batch ? batch.id : '尚未生成'}}</strong></div>
|
||||
<div class="fee-period-item"><span class="fee-period-label">状态</span><strong class="fee-period-value">{{batch ? batch.status : '尚未生成'}}</strong></div>
|
||||
</div>
|
||||
<!-- <el-alert title="起发月份为第一笔费用实际开始发放的月份,一次登记、各年度共用。更改须确认后生效,未确认年度按起发前0元、起发后每月10元重算;单月修订及已确认快照保留。" type="info" :closable="false" show-icon></el-alert>-->
|
||||
<div class="fee-actions"><el-button type="primary" :disabled="batch && batch.status !== '草稿'" @click="makeBatch(false)">生成清算草稿</el-button><el-button :disabled="!canConfirm" @click="makeBatch(true)">复核确认</el-button><el-button :disabled="!confirmed" @click="exportChanges">导出变动表</el-button></div>
|
||||
<el-table :data="pagedSettlementRows" row-key="userId" :expand-row-keys="expandedRowKeys" @expand-change="expandSettlementRow" border show-summary :summary-method="summary">
|
||||
<el-table-column type="expand" width="48"><template slot-scope="{row: detail}"><h3>{{detail.userName}} · 逐月核算</h3><el-table :data="detail.months" border><el-table-column prop="month" label="月份"></el-table-column><el-table-column v-for="c in visibleAmountColumns" :key="c.key" :label="c.label"><template slot-scope="{row}">{{cash(row[c.key])}}</template></el-table-column><el-table-column prop="source" label="实发来源"></el-table-column><el-table-column prop="note" label="备注"></el-table-column></el-table></template></el-table-column>
|
||||
<el-table-column prop="loginName" label="工号" min-width="120"></el-table-column><el-table-column prop="userName" label="姓名" min-width="100"></el-table-column><el-table-column prop="birthday" label="子女出生日期" width="120"></el-table-column><el-table-column prop="actualStartMonth" label="起发月份" width="180"><template slot-scope="{row}"><span v-if="confirmed">{{row.actualStartMonth || '未设置'}}</span><template v-else><el-date-picker v-model="row.actualStartMonth" @change="savePaymentStart(row)" type="month" value-format="yyyy-MM" placeholder="实际起发月份" :clearable="false" :disabled="busy" size="small" style="width:150px"></el-date-picker></template></template></el-table-column><el-table-column prop="stopMonth" label="停发起月" min-width="100"></el-table-column>
|
||||
<el-table-column v-for="c in visibleAmountColumns" :key="c.key" :prop="c.key" :label="c.label" min-width="100"><template slot-scope="{row}">{{cash(row[c.key])}}</template></el-table-column>
|
||||
<el-table-column label="尚未扣回余额" min-width="130"><template slot-scope="{row}">{{cash(remaining(row))}}</template></el-table-column><el-table-column prop="status" label="状态" min-width="110"><template slot-scope="{row}"><el-tag :type="row.status === '资料不全' ? 'danger' : ['无变动','已结清'].includes(recoveryStatus(row)) ? 'success' : 'warning'">{{recoveryStatus(row)}}</el-tag></template></el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right"><template slot-scope="{row}"><el-button type="text" @click="open('payment',row)" :disabled="confirmed">补录实发</el-button><el-button v-if="canRegisterRecovery(row)" type="text" @click="open('recover',row)">登记扣回</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="fee-pagination"><el-pagination background :current-page="settlementPage" :page-size="settlementPageSize" :page-sizes="[10,20,50,100]" :total="filteredRows.length" layout="total, sizes, prev, pager, next, jumper" @current-change="changeSettlementPage" @size-change="changeSettlementSize"></el-pagination></div>
|
||||
</template>
|
||||
</el-card>
|
||||
<!--#include('dialogs.html'){}#-->
|
||||
</div>
|
||||
<script>
|
||||
<!--#include('page.js'){}#-->
|
||||
initOnlyChildFeePage('settlement');
|
||||
</script>
|
||||
<!--#include('style.html'){}#-->
|
||||
<!--# } #-->
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<style>
|
||||
.only-child-fee h2,.only-child-fee h3{color:#0075cf}.only-child-fee h3{border-left:5px solid #0086ed;padding-left:12px;margin:22px 0 14px}.only-child-fee .fee-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));padding:20px 12px 0;border:1px solid #e1e6ed}.only-child-fee .fee-filter{display:flex;gap:14px;flex-wrap:wrap;align-items:center;padding:20px;background:#f4f7fa;margin-bottom:15px}.only-child-fee .fee-filter .el-input{width:180px}.only-child-fee .fee-actions{display:flex;justify-content:flex-end;align-items:center;gap:12px;flex-wrap:wrap;margin:16px 0}.only-child-fee .fee-paper{border:1px solid #dce3ec;padding:28px;margin-bottom:12px}.only-child-fee .fee-paper h2{text-align:center;color:#182333}.only-child-fee .fee-sign{display:flex;justify-content:space-between;margin:50px 20px 20px}.only-child-fee small{float:right;font-size:13px;color:#9099aa}.only-child-fee .el-table th{background:#edf2f7}.only-child-fee .el-button:active{transform:scale(.98)}@media(max-width:900px){.only-child-fee .fee-grid{grid-template-columns:1fr}.only-child-fee .fee-paper{padding:12px}.only-child-fee .fee-sign{gap:20px;flex-wrap:wrap}}@media(prefers-reduced-motion:reduce){.only-child-fee .el-button:active{transform:none}}
|
||||
.only-child-fee .fee-recovery-summary{display:flex;justify-content:space-between;align-items:center;gap:20px;flex-wrap:wrap;padding:18px 20px;margin-bottom:26px;background:#f5f8fc;border:1px solid #e1e8f0;border-radius:6px}
|
||||
.only-child-fee .fee-recovery-person{min-width:0;overflow-wrap:anywhere}
|
||||
.only-child-fee .fee-recovery-name{font-size:16px;font-weight:600;color:#303133;margin-right:12px}
|
||||
.only-child-fee .fee-recovery-id{display:inline-block;font-size:13px;color:#606266}
|
||||
.only-child-fee .fee-recovery-caption{font-size:12px;line-height:20px;color:#606266;margin-top:6px}
|
||||
.only-child-fee .fee-recovery-balance{text-align:right;margin-left:auto}
|
||||
.only-child-fee .fee-recovery-balance strong{font-size:26px;line-height:36px;font-weight:600;color:#b75b00;font-variant-numeric:tabular-nums}
|
||||
.only-child-fee .fee-recovery-unit{margin-left:6px;font-size:13px;color:#606266}
|
||||
.only-child-fee .fee-field-help{margin-top:6px;font-size:12px;line-height:20px;color:#606266;overflow-wrap:anywhere}
|
||||
.only-child-fee .fee-payment-start .fee-recovery-caption,.only-child-fee .fee-payment-start strong{color:#268545}
|
||||
.only-child-fee .fee-recovery-month{padding:12px 0;border-top:1px solid #ebeef5}.only-child-fee .fee-recovery-month-title{margin-bottom:14px;color:#606266;font-weight:600}
|
||||
.only-child-fee .fee-help-icon{margin-left:6px;color:#909399;font-size:14px;cursor:help;vertical-align:middle}.only-child-fee .fee-help-icon:hover,.only-child-fee .fee-help-icon:focus{color:#0075cf}.fee-help-popper{max-width:320px;line-height:1.6}
|
||||
.only-child-fee .fee-period-summary{justify-content:flex-start;gap:20px 48px;margin-bottom:15px}.only-child-fee .fee-period-item{display:flex;flex-direction:column;gap:6px;min-width:0}.only-child-fee .fee-period-label{font-size:12px;line-height:20px;color:#606266}.only-child-fee .fee-period-value{font-size:20px;line-height:28px;font-weight:600;color:#0075cf;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
|
||||
.only-child-fee .fee-record-card,.only-child-fee .fee-form-card{padding:18px 20px;margin:16px 0 24px;background:#f5f8fc;border:1px solid #e1e8f0;border-radius:6px}
|
||||
.only-child-fee .fee-record-fields{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px 24px}.only-child-fee .fee-record-fields>div{min-width:0;overflow-wrap:anywhere}.only-child-fee .fee-record-label{display:block;margin-bottom:6px;color:#606266;font-size:12px}.only-child-fee .fee-record-wide{grid-column:1/-1}
|
||||
.only-child-fee .fee-record-state strong{font-size:20px;color:#0075cf}.only-child-fee .fee-form-card .el-form-item:last-child{margin-bottom:0}.only-child-fee .fee-record-card .el-descriptions__body{background:transparent}.only-child-fee .fee-record-card>h3:first-child{margin-top:0}
|
||||
@media(max-width:600px){.only-child-fee .fee-record-fields{grid-template-columns:1fr}.only-child-fee .fee-record-card,.only-child-fee .fee-form-card{padding:16px}}
|
||||
.only-child-fee .fee-pagination{display:flex;justify-content:flex-end;margin-top:20px;overflow-x:auto;padding-bottom:4px}
|
||||
.only-child-fee .fee-standard-toolbar{margin:16px 0}.only-child-fee .fee-standard-toolbar .ele-space{display:flex;align-items:center;flex-wrap:wrap;gap:12px}.only-child-fee .fee-standard-toolbar .el-button+.el-button{margin-left:0}
|
||||
.only-child-fee .fee-notice-paper{overflow-x:auto}.only-child-fee .fee-notice-table{width:100%;min-width:900px;border-collapse:collapse;table-layout:fixed;color:#000;background:#fff;font-family:SimSun,serif;font-size:14px}
|
||||
.only-child-fee .fee-notice-table th,.only-child-fee .fee-notice-table td{border:1px solid #000;padding:8px 6px;text-align:center;vertical-align:middle;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.only-child-fee .fee-notice-table .fee-notice-title{font-size:22px;padding:12px 6px}.only-child-fee .fee-notice-table .fee-notice-recipient{text-align:left}.only-child-fee .fee-notice-table .fee-notice-unit{text-align:right;padding-right:28px}.only-child-fee .fee-notice-table tfoot td{text-align:left;height:64px;padding-left:24px;border:0}
|
||||
</style>
|
||||
@@ -5,13 +5,14 @@ layout("/layouts/platform.html"){
|
||||
<div id="app" v-cloak>
|
||||
<el-card shadow="never">
|
||||
<h3 style="color: var(--color-primary)" slot="header">信息填报</h3>
|
||||
<el-alert v-if="formData.qualificationLocked" title="该子女已关联待核验、已核定或已停发的独生子女费资格,姓名、身份证号、生日、性别及监护人身份和关系不可直接修改;联系电话、学校、年级等日常信息仍可维护。" type="info" :closable="false" show-icon class="mb10"></el-alert>
|
||||
<el-form :model="formData" ref="formRef" :rules="formRules" label-width="0" label-suffix=":"
|
||||
class="flow-task-form">
|
||||
<table-tool label="子女信息"></table-tool>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="就读年级">
|
||||
<el-form-item prop="childRelationship">
|
||||
<dict-select v-model="formData.childRelationship" code="CHILD_MANAGE_GRADE" style="width: 100%" placeholder="请选择就读年级">
|
||||
<el-form-item prop="childrenGrade">
|
||||
<dict-select v-model="formData.childrenGrade" code="CHILD_MANAGE_GRADE" style="width: 100%" placeholder="请选择就读年级">
|
||||
</dict-select>
|
||||
</el-form-item></el-descriptions-item>
|
||||
<el-descriptions-item label="入学年份">
|
||||
@@ -26,20 +27,20 @@ layout("/layouts/platform.html"){
|
||||
</el-form-item></el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">
|
||||
<el-form-item prop="childrenName">
|
||||
<el-input v-model="formData.childrenName" placeholder="请输入子女姓名"
|
||||
<el-input :disabled="formData.qualificationLocked && !correctionMode" v-model="formData.childrenName" placeholder="请输入子女姓名"
|
||||
maxlength="30"></el-input>
|
||||
</el-form-item></el-descriptions-item>
|
||||
<el-descriptions-item label="身份证号">
|
||||
<el-form-item prop="childrenIdCard">
|
||||
<el-input v-model="formData.childrenIdCard" placeholder="请输入身份证号"
|
||||
<el-input :disabled="formData.qualificationLocked && !correctionMode" v-model="formData.childrenIdCard" placeholder="请输入身份证号"
|
||||
maxlength="19">
|
||||
<el-button slot="append" icon="el-icon-search" @click="getIsRepeatByIdCard"></el-button>
|
||||
<el-button :disabled="formData.qualificationLocked && !correctionMode" slot="append" icon="el-icon-search" @click="getIsRepeatByIdCard"></el-button>
|
||||
</el-input>
|
||||
</el-form-item></el-descriptions-item>
|
||||
<el-descriptions-item label="出生日期">
|
||||
<el-form-item prop="childrenBirthday">
|
||||
<el-date-picker
|
||||
v-model="formData.childrenBirthday"
|
||||
:disabled="formData.qualificationLocked && !correctionMode" v-model="formData.childrenBirthday"
|
||||
type="date"
|
||||
placeholder="请选择子女出生年月"
|
||||
style="width: 100%;"
|
||||
@@ -50,7 +51,7 @@ layout("/layouts/platform.html"){
|
||||
<el-form-item prop="childrenSex">
|
||||
<el-select clearable placeholder="请选择性别"
|
||||
style="width: 100%;"
|
||||
v-model="formData.childrenSex">
|
||||
:disabled="formData.qualificationLocked && !correctionMode" v-model="formData.childrenSex">
|
||||
<el-option label="男" value="男"></el-option>
|
||||
<el-option label="女" value="女"></el-option>
|
||||
</el-select>
|
||||
@@ -86,11 +87,16 @@ layout("/layouts/platform.html"){
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="监护人1">
|
||||
<el-form-item label="监护人1" prop="guardianUserName1">
|
||||
<el-input v-model="formData.guardianUserName1" readonly></el-input>
|
||||
<el-input :disabled="formData.qualificationLocked && !correctionMode" v-model="formData.guardianUserName1" readonly></el-input>
|
||||
<el-select v-if="correctionMode" v-model="formData.guardianRelationship1" placeholder="与子女关系" style="width:100%">
|
||||
<el-option label="父亲" value="父亲"></el-option>
|
||||
<el-option label="母亲" value="母亲"></el-option>
|
||||
<el-option label="其他法定监护人" value="其他法定监护人"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="工号" >
|
||||
<el-form-item prop="guardianLoginName1">{{formData.guardianLoginName1}}
|
||||
<el-form-item prop="guardianLoginName1"><el-input v-if="correctionMode" v-model="formData.guardianLoginName1" placeholder="本校教职工工号"></el-input><span v-else>{{formData.guardianLoginName1}}</span>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号码">
|
||||
@@ -105,12 +111,17 @@ layout("/layouts/platform.html"){
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="监护人2">
|
||||
<el-form-item label="监护人2" prop="guardianUserName2">
|
||||
<el-input v-model="formData.guardianUserName2" placeholder="请输入监护人姓名" maxlength="30"></el-input>
|
||||
<el-input :disabled="formData.qualificationLocked && !correctionMode" v-model="formData.guardianUserName2" placeholder="请输入监护人姓名" maxlength="30"></el-input>
|
||||
<el-select v-if="correctionMode" v-model="formData.guardianRelationship2" placeholder="与子女关系" style="width:100%">
|
||||
<el-option label="父亲" value="父亲"></el-option>
|
||||
<el-option label="母亲" value="母亲"></el-option>
|
||||
<el-option label="其他法定监护人" value="其他法定监护人"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="工号">
|
||||
<el-form-item prop="guardianLoginName2">
|
||||
<el-input v-model="formData.guardianLoginName2" placeholder="没有则不填" maxlength="30"></el-input>
|
||||
<el-input :disabled="formData.qualificationLocked && !correctionMode" v-model="formData.guardianLoginName2" placeholder="没有则不填" maxlength="30"></el-input>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号码">
|
||||
@@ -130,8 +141,18 @@ layout("/layouts/platform.html"){
|
||||
</el-form-item></el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-form>
|
||||
<el-form v-if="correctionMode" label-width="150px" class="mt20">
|
||||
<el-alert title="更正申请仅提交关键身份和监护关系,审批前原资料保持不变。日常资料请退出更正模式后保存。" type="info" :closable="false"></el-alert>
|
||||
<el-form-item label="更正原因及证明依据" required><el-input v-model="correctionReason" type="textarea" maxlength="2000" placeholder="说明更正原因及可供核验的证明依据"></el-input></el-form-item>
|
||||
</el-form>
|
||||
<el-table v-if="formData.correctionHistory && formData.correctionHistory.length" :data="formData.correctionHistory" border class="mt20">
|
||||
<el-table-column prop="submittedAt" label="更正申请时间"></el-table-column><el-table-column prop="reason" label="更正原因"></el-table-column><el-table-column prop="status" label="办理状态"></el-table-column><el-table-column prop="reviewBasis" label="核验结果"></el-table-column><el-table-column prop="effectiveMonth" label="生效月份"></el-table-column>
|
||||
</el-table>
|
||||
<el-row type="flex" justify="end" class="mt20">
|
||||
<el-button type="primary" plain @click="onSave">保存</el-button>
|
||||
<el-button v-if="formData.qualificationLocked && !correctionMode" @click="beginCorrection">申请关键资料更正</el-button>
|
||||
<el-button v-if="correctionMode" @click="cancelCorrection">取消更正</el-button>
|
||||
<el-button v-if="correctionMode" type="primary" @click="submitCorrection">提交更正申请</el-button>
|
||||
<el-button v-else type="primary" plain @click="onSave">{{formData.qualificationLocked ? '保存日常资料' : '保存'}}</el-button>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
@@ -144,9 +165,10 @@ layout("/layouts/platform.html"){
|
||||
data() {
|
||||
return {
|
||||
bizId: GetQueryString("bizId"),
|
||||
formData: {},
|
||||
formData: {}, correctionMode: false, correctionReason: '', originalData: null,
|
||||
|
||||
formRules: {
|
||||
childRelationship: [{ required: true, message: '请选择就读年级', trigger: 'change' }],
|
||||
childrenGrade: [{ required: true, message: '请选择就读年级', trigger: 'change' }],
|
||||
childrenYear: [{ required: true, message: '请选择入学年份', trigger: 'change' }],
|
||||
childrenName: [{ required: true, message: '请输入子女姓名', trigger: 'blur' }],
|
||||
childrenIdCard: [{ required: true, message: '请输入子女身份证号', trigger: 'blur' }, {pattern: /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/, message: '请输入正确的身份证号码', trigger: 'blur'}],
|
||||
@@ -158,6 +180,25 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
beginCorrection() {
|
||||
this.originalData = JSON.parse(JSON.stringify(this.formData))
|
||||
this.correctionReason = ''
|
||||
this.correctionMode = true
|
||||
},
|
||||
cancelCorrection() {
|
||||
this.formData = this.originalData
|
||||
this.correctionMode = false
|
||||
},
|
||||
submitCorrection() {
|
||||
if (!this.correctionReason.trim()) { this.$message.warning('请填写更正原因及证明依据'); return }
|
||||
this.$axios.post('/platform/childManage/write/requestChange', {data: JSON.stringify(this.formData), reason: this.correctionReason}).then(async res => {
|
||||
if (res.code === 0) {
|
||||
this.correctionMode = false
|
||||
this.formData = await this.findOne(this.bizId)
|
||||
this.$message.success(res.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
async getIsRepeatByIdCard() {
|
||||
if (!this.formData.childrenIdCard) {
|
||||
this.$message.error("请填写子女身份证号码")
|
||||
@@ -169,10 +210,10 @@ layout("/layouts/platform.html"){
|
||||
})
|
||||
if (res.code === 0) {
|
||||
if (res.data > 0) {
|
||||
this.$message.error("该身份证在本年度已填报!")
|
||||
this.$message.error("该身份证已登记,请维护已有子女资料!")
|
||||
return true
|
||||
} else {
|
||||
this.$message.success("该身份证在本年度暂未填报!")
|
||||
this.$message.success("该身份证暂未登记!")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
+37
-1
@@ -83,9 +83,35 @@
|
||||
<el-descriptions-item label="岗级">{{ viewData.positionLevel }}</el-descriptions-item>
|
||||
<el-descriptions-item label="行政岗级">{{ viewData.administrativePositionLevel }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 子女信息由个人信息维护详情接口返回,其他查看入口未返回 children 时不展示该区块 -->
|
||||
<template v-if="viewData.children">
|
||||
<div class="process-title">子女信息</div>
|
||||
<el-table ref="childrenTable" v-if="viewData.children.length" :data="viewData.children" size="mini" border :header-cell-style="{background:'#eff3f6'}" style="width: 100%">
|
||||
<el-table-column prop="relation" label="关系" align="center" min-width="160"></el-table-column>
|
||||
<el-table-column prop="name" label="姓名" align="center" min-width="160"></el-table-column>
|
||||
<el-table-column prop="sex" label="性别" align="center" min-width="120"></el-table-column>
|
||||
<el-table-column label="出生日期" align="center" min-width="160">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.birthday ? $moment(scope.row.birthday).format('YYYY-MM-DD') : '' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-else :image-size="50" description="暂无子女信息"></el-empty>
|
||||
</template>
|
||||
</div>
|
||||
`,
|
||||
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()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -45,12 +45,22 @@ const COMMON_QUERY = {
|
||||
<dict-select v-model="pageForm.preparedBys" placeholder="请选择编制类别" @change="doSearch"
|
||||
code="USER_PREPARED_BY_TYPE" multiple collapse-tags></dict-select>
|
||||
</search-item>
|
||||
<search-item label="会员查询" v-if="show_member_query">
|
||||
<el-select v-model="pageForm.memberStatus" clearable placeholder="请选择会员状态" @change="doSearch" style="width: 100%">
|
||||
<el-option label="会员" :value="1"></el-option>
|
||||
<el-option label="非会员" :value="0"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
</search>
|
||||
`,
|
||||
props: {
|
||||
query_disable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
show_member_query: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
mixins: [initTableMixins],
|
||||
@@ -67,6 +77,8 @@ const COMMON_QUERY = {
|
||||
userStates: [],
|
||||
personTypes: [],
|
||||
preparedBys: [],
|
||||
// 会员查询仅在指定页面开启,空值表示不限制会员状态。
|
||||
memberStatus: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -81,6 +93,8 @@ const COMMON_QUERY = {
|
||||
pageForm.userStates = JSON.stringify(this.pageForm.userStates)
|
||||
pageForm.personTypes = JSON.stringify(this.pageForm.personTypes)
|
||||
pageForm.preparedBys = JSON.stringify(this.pageForm.preparedBys)
|
||||
// 会员查询清空时传 null,避免后台 Integer 参数收到空字符串。
|
||||
pageForm.memberStatus = this.pageForm.memberStatus === '' ? null : this.pageForm.memberStatus
|
||||
this.$emit('search', pageForm)
|
||||
},
|
||||
async initData(){
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<el-card shadow="never">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>个人信息维护</span>
|
||||
</div>
|
||||
<el-form :model="formData" ref="formRef" label-width="0" label-suffix=":" class="flow-task-form">
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item label="工号">
|
||||
<el-input v-model="formData.loginname" readonly size="small"></el-input>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">
|
||||
<el-input v-model="formData.username" readonly size="small"></el-input>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
<el-radio-group disabled v-model="formData.sex" size="small">
|
||||
<el-radio border label="男">男</el-radio>
|
||||
<el-radio border label="女">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="民族">
|
||||
<dict-select style="width: 100%" v-model="formData.nation" disabled code="USER_NATION"></dict-select>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="出生日期">
|
||||
<el-date-picker v-model="formData.birthday" type="date" disabled format="yyyy-MM-dd" value-format="yyyy-MM-dd" style="width: 100%"></el-date-picker>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="政治面貌">
|
||||
<dict-select style="width: 100%" v-model="formData.political" disabled code="USER_POLITICAL"></dict-select>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="学历">
|
||||
<dict-select style="width: 100%" v-model="formData.education" disabled code="USER_EDUCATION"></dict-select>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="学位">
|
||||
<dict-select style="width: 100%" v-model="formData.academicDegree" disabled code="USER_ACADEMIC_DEGREE"></dict-select>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="党政职务">
|
||||
<el-input v-model="formData.position" disabled maxlength="50"></el-input>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="工作单位">
|
||||
<el-input v-model="formData.unitName" disabled></el-input>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="所属工会">
|
||||
<el-input v-model="formData.unionName" disabled></el-input>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="所属校区">
|
||||
<dict-select style="width: 100%" v-model="formData.campus" disabled code="USER_CAMPUS"></dict-select>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="身份证号码">
|
||||
<el-input v-model="formData.idCard" disabled maxlength="18"></el-input>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">
|
||||
<el-input v-model="formData.mobile" disabled maxlength="15"></el-input>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="电子邮箱">
|
||||
<el-input v-model="formData.email" disabled maxlength="50"></el-input>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="在职状态">
|
||||
<dict-select v-model="formData.userState" code="USER_STATE" disabled style="width: 100%"></dict-select>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="教职工类别">
|
||||
<dict-select v-model="formData.personType" code="USER_PERSON_TYPE" disabled style="width: 100%"></dict-select>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="编制类别">
|
||||
<dict-select v-model="formData.preparedBy" code="USER_PREPARED_BY_TYPE" disabled style="width: 100%"></dict-select>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="会员状态">
|
||||
<el-radio-group v-model="formData.member" disabled size="small">
|
||||
<el-radio border :label="true">会员</el-radio>
|
||||
<el-radio border :label="false">非会员</el-radio>
|
||||
</el-radio-group>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="福利会员状态">
|
||||
<el-radio-group v-model="formData.welfareMember" disabled size="small">
|
||||
<el-radio border :label="true">福利会员</el-radio>
|
||||
<el-radio border :label="false">非福利会员</el-radio>
|
||||
</el-radio-group>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="婚姻状况">
|
||||
<dict-select style="width: 100%" v-model="formData.marriage" code="USER_MARRIAGE"></dict-select>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="家庭主要成员" :span="3">
|
||||
<el-table :data="formData.families" border size="small">
|
||||
<el-table-column label="关系" prop="relation" align="center"></el-table-column>
|
||||
<el-table-column label="姓名" prop="name" align="center"></el-table-column>
|
||||
<el-table-column label="工作单位" prop="unit" align="center"></el-table-column>
|
||||
<el-table-column label="备注" prop="remark" align="center"></el-table-column>
|
||||
</el-table>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="子女信息" :span="3">
|
||||
<el-table :data="children" border size="small">
|
||||
<el-table-column label="关系" prop="relation">
|
||||
<template slot-scope="{row}">
|
||||
<el-select v-model="row.relation" placeholder="请选择关系" style="width: 100%">
|
||||
<el-option label="父子" value="父子"></el-option>
|
||||
<el-option label="父女" value="父女"></el-option>
|
||||
<el-option label="母子" value="母子"></el-option>
|
||||
<el-option label="母女" value="母女"></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="姓名" prop="name">
|
||||
<template slot-scope="{row}">
|
||||
<el-input v-model="row.name" maxlength="50" placeholder="请输入姓名"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="性别" prop="sex">
|
||||
<template slot-scope="{row}">
|
||||
<el-select v-model="row.sex" placeholder="请选择性别" style="width: 100%">
|
||||
<el-option label="男" value="男"></el-option>
|
||||
<el-option label="女" value="女"></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="出生日期" prop="birthday">
|
||||
<template slot-scope="{row}">
|
||||
<el-date-picker v-model="row.birthday" type="date" format="yyyy-MM-dd" value-format="yyyy-MM-dd" placeholder="请选择出生日期" style="width: 100%"></el-date-picker>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="100px">
|
||||
<template slot="header" slot-scope="scope">
|
||||
<el-button type="primary" size="mini" @click="children.push({})">添加</el-button>
|
||||
</template>
|
||||
<template slot-scope="scope">
|
||||
<el-button type="danger" icon="el-icon-delete" size="mini" @click="children.splice(scope.$index, 1)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-form>
|
||||
|
||||
<el-row type="flex" justify="end" class="mt20">
|
||||
<el-button type="primary" plain @click="goBack">返回</el-button>
|
||||
<el-button type="primary" @click="saveChildren">保存</el-button>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
data() {
|
||||
return {
|
||||
userId: GetQueryString("userId"),
|
||||
formData: {
|
||||
families: []
|
||||
},
|
||||
children: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
this.$axios.post("/platform/member/info/maintain/detail", { userId: this.userId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.formData = res.data
|
||||
this.formData.member = this.formData.member === true || this.formData.member === 1
|
||||
this.formData.welfareMember = this.formData.welfareMember === true || this.formData.welfareMember === 1
|
||||
this.formData.families = this.formData.families || []
|
||||
this.children = res.data.children || []
|
||||
} else {
|
||||
this.$message.error(res.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
saveChildren() {
|
||||
const cleanedChildren = this.cleanChildren()
|
||||
if (!this.validateChildren(cleanedChildren)) {
|
||||
return
|
||||
}
|
||||
this.$confirm("确定保存子女信息吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/member/info/maintain/saveChildren", {
|
||||
userId: this.userId,
|
||||
marriage: this.formData.marriage,
|
||||
children: JSON.stringify(cleanedChildren)
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("保存成功")
|
||||
this.goBack()
|
||||
} else {
|
||||
this.$message.error(res.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
cleanChildren() {
|
||||
// 保存前过滤完全空白的子女行,避免添加后未填写的空行进入查看页。
|
||||
return this.children.filter((child) => {
|
||||
return child.relation || child.name || child.sex || child.birthday
|
||||
})
|
||||
},
|
||||
validateChildren(children) {
|
||||
// 子女信息按整行保存,只要填写了一项,就要求关系、姓名、性别、出生日期完整。
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i]
|
||||
if (!child.relation || !child.name || !child.sex || !child.birthday) {
|
||||
this.$message.warning("请完整填写第" + (i + 1) + "行子女信息")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
goBack() {
|
||||
commonUtil.pjaxPush("/platform/member/info/maintain?refresh=" + new Date().getTime())
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,192 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<common-query ref="commonQueryRef" @search="search" show_member_query></common-query>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mt10">
|
||||
<table-tool label="个人信息维护">
|
||||
<el-popover placement="bottom" trigger="click" width="200">
|
||||
<div style="padding:4px 0;border-bottom:1px solid #eee;">
|
||||
<el-button size="mini" @click="checkAll">全选</el-button>
|
||||
<el-button size="mini" @click="invertCheck">反选</el-button>
|
||||
</div>
|
||||
<div style="max-height:40vh;overflow-y:auto;padding:6px 0;">
|
||||
<el-checkbox-group v-model="checkedFields">
|
||||
<el-checkbox
|
||||
v-for="f in tableColumns"
|
||||
:key="f.prop"
|
||||
:label="f.prop"
|
||||
style="display:block;margin:6px 0;">
|
||||
{{ f.label }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
<el-button
|
||||
slot="reference"
|
||||
type="text"
|
||||
icon="el-icon-s-operation"
|
||||
size="small"
|
||||
style="margin-left:12px;">
|
||||
列设置
|
||||
</el-button>
|
||||
</el-popover>
|
||||
<el-button type="primary" size="small" icon="el-icon-download" @click="doExport">导出</el-button>
|
||||
</table-tool>
|
||||
|
||||
<el-table :data="tableData" style="width: 100%" row-key="id" @sort-change="pageOrder" :size="tableSize" class="vi-table">
|
||||
<el-table-column
|
||||
align="center"
|
||||
header-align="center"
|
||||
type="index"
|
||||
fixed="left"
|
||||
:index="indexMethod"
|
||||
label="序号"
|
||||
width="80px"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
show-overflow-tooltip
|
||||
align="center"
|
||||
header-align="center"
|
||||
v-for="column in tableColumns"
|
||||
v-if="checkedFields.includes(column.prop)"
|
||||
:label="column.label"
|
||||
:fixed="column.fixed"
|
||||
:prop="column.prop"
|
||||
:sortable="column.sortable"
|
||||
:width="column.width"
|
||||
min-width="50"
|
||||
></el-table-column>
|
||||
|
||||
<el-table-column prop="userOnline" align="center" header-align="center" label="操作" fixed="right" width="160px">
|
||||
<template scope="scope">
|
||||
<el-button size="mini" type="primary" @click="openView(scope.row.id)">查看</el-button>
|
||||
<el-button size="mini" type="primary" @click="openEdit(scope.row.id)">编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<template #view>
|
||||
<member-info ref="infoRef" detail-url="/platform/member/info/maintain/detail" id-param="userId"></member-info>
|
||||
</template>
|
||||
</guava>
|
||||
</div>
|
||||
<script>
|
||||
<!--#include("../common/commonQuery.js"){}#-->
|
||||
<!--#include("../../common/info/memberInfo.js"){}#-->
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
pageForm: {},
|
||||
tableColumns: [
|
||||
{ prop: "loginname", label: "工号", width: 120, fixed: "left"},
|
||||
{ prop: "username", label: "姓名", width: 120, fixed: "left" },
|
||||
{ prop: "sex", label: "性别", width: 120},
|
||||
{ prop: "birthday", label: "出生年月", width: 120, sortable: true },
|
||||
{ prop: "nationality", label: "国籍", width: 120, sortable: true, checked: 0 },
|
||||
{ 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 },
|
||||
{ prop: "postDoctoralJoinDate", label: "进站时间", sortable: true, checked: 0 },
|
||||
{ prop: "unionName", label: "所属工会", width: 120, sortable: true },
|
||||
{ prop: "unitName", label: "所属单位", width: 120, sortable: true },
|
||||
{ prop: "idCardType", label: "证件类型", width: 120, sortable: true, checked: 0 },
|
||||
{ prop: "idCard", label: "证件号码", width: 120, sortable: true, checked: 0 },
|
||||
{ prop: "marriage", label: "婚姻状况", width: 120, sortable: true, checked: 0 },
|
||||
{ prop: "jobCategory", label: "岗位类别", width: 120, sortable: true, checked: 0 },
|
||||
{ prop: "professionalTitle", label: "职称", width: 120, sortable: true, checked: 0 },
|
||||
{ prop: "professionalLevel", label: "职级", width: 120, sortable: true, checked: 0 },
|
||||
{ prop: "employeeSource", label: "职工来源", width: 120, sortable: true, checked: 0 },
|
||||
{ prop: "administrativeLevel", label: "行政级别(管理岗位)", width: 120, sortable: true, checked: 0 },
|
||||
{ prop: "positionSeries", label: "岗位系列", width: 120, sortable: true, checked: 0 },
|
||||
{ prop: "positionLevel", label: "岗级", width: 120, sortable: true, checked: 0 },
|
||||
{ prop: "administrativePositionLevel", label: "行政岗级", width: 120, sortable: true, checked: 0 },
|
||||
{ prop: "arrivalAtSchoolDate", label: "来校时间", width: 120, sortable: true, checked: 0 },
|
||||
{ prop: "teachingTime", label: "从教年月", width: 120, sortable: true, checked: 0 },
|
||||
{ prop: "expectedLeaveSchoolDate", label: "预计/最后离校时间", width: 120, sortable: true, checked: 0 }
|
||||
],
|
||||
checkedFields: []
|
||||
}
|
||||
},
|
||||
components: {
|
||||
"member-info": MEMBER_INFO,
|
||||
"common-query": COMMON_QUERY
|
||||
},
|
||||
methods: {
|
||||
openView(userId) {
|
||||
this.$refs.guava.view(() => {
|
||||
this.$refs.infoRef.onOpen(userId)
|
||||
})
|
||||
},
|
||||
openEdit(userId) {
|
||||
commonUtil.pjaxPush("/platform/member/info/maintain/edit?userId=" + userId)
|
||||
},
|
||||
search(pageForm) {
|
||||
this.pageForm = Object.assign({}, this.pageForm, pageForm)
|
||||
this.pageData()
|
||||
},
|
||||
pageData() {
|
||||
this.tableLoading = true
|
||||
const pageForm = clone(this.pageForm)
|
||||
pageForm.userStates = Array.isArray(this.pageForm.userStates) ? JSON.stringify(this.pageForm.userStates) : this.pageForm.userStates
|
||||
pageForm.personTypes = Array.isArray(this.pageForm.personTypes) ? JSON.stringify(this.pageForm.personTypes) : this.pageForm.personTypes
|
||||
pageForm.preparedBys = Array.isArray(this.pageForm.preparedBys) ? JSON.stringify(this.pageForm.preparedBys) : this.pageForm.preparedBys
|
||||
this.$axios.post(loc() + "/pageData", pageForm)
|
||||
.then((data) => {
|
||||
if (data.code === 0) {
|
||||
this.tableData = data.data.list
|
||||
this.pageForm.totalCount = data.data.totalCount
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.tableLoading = false
|
||||
})
|
||||
},
|
||||
doExport() {
|
||||
const pageForm = clone(this.pageForm)
|
||||
pageForm.userStates = Array.isArray(this.pageForm.userStates) ? JSON.stringify(this.pageForm.userStates) : this.pageForm.userStates
|
||||
pageForm.personTypes = Array.isArray(this.pageForm.personTypes) ? JSON.stringify(this.pageForm.personTypes) : this.pageForm.personTypes
|
||||
pageForm.preparedBys = Array.isArray(this.pageForm.preparedBys) ? JSON.stringify(this.pageForm.preparedBys) : this.pageForm.preparedBys
|
||||
pageForm.columns = JSON.stringify(this.tableColumns.filter((column) => {
|
||||
return this.checkedFields.includes(column.prop)
|
||||
}).map((column) => {
|
||||
return {
|
||||
label: column.label,
|
||||
prop: column.prop
|
||||
}
|
||||
}))
|
||||
this.$downLoad(loc() + "/doExport", pageForm)
|
||||
},
|
||||
checkAll() {
|
||||
this.checkedFields = this.tableColumns.map(c => c.prop)
|
||||
},
|
||||
invertCheck() {
|
||||
const all = this.tableColumns.map(c => c.prop)
|
||||
this.checkedFields = all.filter(p => !this.checkedFields.includes(p))
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.checkedFields = this.tableColumns.filter(c => c.checked !== 0).map(c => c.prop)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+6
-2
@@ -42,7 +42,9 @@ layout("/layouts/platform.html"){
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mt10">
|
||||
<table-tool label="会员统计"></table-tool>
|
||||
<table-tool label="会员统计">
|
||||
<el-button type="primary" size="small" icon="el-icon-download" @click="doExport">导出</el-button>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" show-summary>
|
||||
<el-table-column
|
||||
align="center"
|
||||
@@ -90,7 +92,9 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
doExport() {},
|
||||
doExport() {
|
||||
this.$downLoad(loc() + "/doExport", this.pageForm)
|
||||
},
|
||||
initData() {
|
||||
if (this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN"])) {
|
||||
this.$businessTool.listUnion().then((data) => {
|
||||
|
||||
@@ -12,11 +12,11 @@ layout("/layouts/platform_h5.html"){
|
||||
<div id="app">
|
||||
<van-nav-bar title="子女信息填报" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
|
||||
<!-- 表单容器 -->
|
||||
<van-form ref="formRef" class="form-container">
|
||||
<van-notice-bar v-if="formData.qualificationLocked" wrapable :scrollable="false" text="该子女已关联待核验、已核定或已停发的独生子女费资格,关键身份和监护关系不可直接修改;联系电话、学校、年级等日常信息仍可维护。"></van-notice-bar><van-form ref="formRef" class="form-container">
|
||||
|
||||
<!-- 子女信息 -->
|
||||
<van-cell-group title="子女信息" class="form-section">
|
||||
<van-field label="姓名" :rules="[{ required: true }]" v-model="formData.childrenName" placeholder="请输入姓名" required></van-field>
|
||||
<van-field label="姓名" :rules="[{ required: true }]" :disabled="formData.qualificationLocked && !correctionMode" v-model="formData.childrenName" placeholder="请输入姓名" required></van-field>
|
||||
<van-field
|
||||
v-model="formData.childrenGrade"
|
||||
name="childrenGrade"
|
||||
@@ -48,16 +48,16 @@ layout("/layouts/platform_h5.html"){
|
||||
</van-popup>
|
||||
<van-field
|
||||
label="身份证号"
|
||||
v-model="formData.childrenIdCard"
|
||||
:disabled="formData.qualificationLocked && !correctionMode" v-model="formData.childrenIdCard"
|
||||
placeholder="请输入身份证号"
|
||||
:rules="formRules.childrenIdCard"
|
||||
required
|
||||
></van-field> <van-field
|
||||
label="出生年月"
|
||||
v-model="formData.childrenBirthday"
|
||||
:disabled="formData.qualificationLocked && !correctionMode" v-model="formData.childrenBirthday"
|
||||
placeholder="请选择出生年月"
|
||||
readonly
|
||||
@click="showBirthdayPicker = true"
|
||||
@click="(!formData.qualificationLocked || correctionMode) && (showBirthdayPicker = true)"
|
||||
clickable
|
||||
></van-field>
|
||||
<van-popup v-model="showBirthdayPicker" position="bottom">
|
||||
@@ -74,10 +74,10 @@ layout("/layouts/platform_h5.html"){
|
||||
|
||||
<van-field
|
||||
label="性别"
|
||||
v-model="formData.childrenSex"
|
||||
:disabled="formData.qualificationLocked && !correctionMode" v-model="formData.childrenSex"
|
||||
placeholder="请选择性别"
|
||||
readonly
|
||||
@click="showSexPicker = true"
|
||||
@click="(!formData.qualificationLocked || correctionMode) && (showSexPicker = true)"
|
||||
clickable
|
||||
></van-field>
|
||||
|
||||
@@ -99,8 +99,8 @@ layout("/layouts/platform_h5.html"){
|
||||
<!-- 监护人信息 -->
|
||||
<van-cell-group title="监护人信息" class="form-section">
|
||||
|
||||
<van-field label="监护人1" v-model="formData.guardianUserName1" readonly placeholder="读取信息中心数据"></van-field>
|
||||
<van-field label="工号" v-model="formData.guardianLoginName1" readonly placeholder="读取信息中心数据"></van-field>
|
||||
<van-field label="监护人1" :disabled="formData.qualificationLocked && !correctionMode" v-model="formData.guardianUserName1" readonly placeholder="读取信息中心数据"></van-field>
|
||||
<van-field label="监护关系" :disabled="formData.qualificationLocked && !correctionMode" v-model="formData.guardianRelationship1" placeholder="父亲、母亲或其他法定监护人" required maxlength="30"></van-field><van-field label="工号" :disabled="formData.qualificationLocked && !correctionMode" v-model="formData.guardianLoginName1" :readonly="!correctionMode" placeholder="读取信息中心数据"></van-field>
|
||||
<van-field
|
||||
label="手机号码"
|
||||
v-model="formData.guardianMobile1"
|
||||
@@ -110,10 +110,10 @@ layout("/layouts/platform_h5.html"){
|
||||
<van-field label="所在单位" v-model="formData.guardianUnitName1" readonly placeholder="读取信息中心数据"></van-field>
|
||||
<van-field
|
||||
label="监护人2"
|
||||
v-model="formData.guardianUserName2"
|
||||
:disabled="formData.qualificationLocked && !correctionMode" v-model="formData.guardianUserName2"
|
||||
placeholder="请输入监护人姓名"
|
||||
></van-field>
|
||||
<van-field label="工号" v-model="formData.guardianLoginName2" placeholder="没有则不填" ></van-field>
|
||||
<van-field label="监护关系" :disabled="formData.qualificationLocked && !correctionMode" v-model="formData.guardianRelationship2" placeholder="父亲、母亲或其他法定监护人" maxlength="30"></van-field><van-field label="工号" :disabled="formData.qualificationLocked && !correctionMode" v-model="formData.guardianLoginName2" placeholder="没有则不填" ></van-field>
|
||||
<van-field
|
||||
label="手机号码"
|
||||
v-model="formData.guardianMobile2"
|
||||
@@ -132,7 +132,12 @@ layout("/layouts/platform_h5.html"){
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<div class="form-actions">
|
||||
<van-button @click="onSave" round type="info">保存申请</van-button>
|
||||
<van-field v-if="correctionMode" v-model="correctionReason" type="textarea" label="更正原因" placeholder="说明更正原因及证明依据;审批前原资料保持不变" maxlength="2000" required></van-field>
|
||||
<van-button v-if="formData.qualificationLocked && !correctionMode" @click="beginCorrection" round>申请关键资料更正</van-button>
|
||||
<van-button v-if="correctionMode" @click="cancelCorrection" round>取消更正</van-button>
|
||||
<van-button v-if="correctionMode" @click="submitCorrection" round type="info">提交更正申请</van-button>
|
||||
<van-button v-else @click="onSave" round type="info">{{formData.qualificationLocked ? '保存日常资料' : '保存申请'}}</van-button>
|
||||
<van-cell v-for="change in formData.correctionHistory || []" :key="change.id" :title="'更正申请:' + change.status" :label="change.reason + ';核验结果:' + (change.reviewBasis || '待核验') + ';生效月份:' + (change.effectiveMonth || '待核定')"></van-cell>
|
||||
</div>
|
||||
</van-form>
|
||||
</div>
|
||||
@@ -143,7 +148,7 @@ layout("/layouts/platform_h5.html"){
|
||||
dicts: ["CHILD_MANAGE_GRADE"],
|
||||
data() {
|
||||
return {
|
||||
bizId: GetQueryString("bizId"),
|
||||
bizId: GetQueryString("bizId"), correctionMode: false, correctionReason: '', originalData: null,
|
||||
formData: {
|
||||
childrenName: '',
|
||||
childrenGrade: '',
|
||||
@@ -215,7 +220,26 @@ layout("/layouts/platform_h5.html"){
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSave() {
|
||||
beginCorrection() {
|
||||
this.originalData = JSON.parse(JSON.stringify(this.formData))
|
||||
this.correctionReason = ''
|
||||
this.correctionMode = true
|
||||
},
|
||||
cancelCorrection() {
|
||||
this.formData = this.originalData
|
||||
this.correctionMode = false
|
||||
},
|
||||
submitCorrection() {
|
||||
if (!this.correctionReason.trim()) { this.$toast('请填写更正原因及证明依据'); return }
|
||||
this.$axios.post('/platform/childManage/write/requestChange', {data: JSON.stringify(this.formData), reason: this.correctionReason}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.correctionMode = false
|
||||
this.$toast(res.msg)
|
||||
this.init()
|
||||
}
|
||||
})
|
||||
},
|
||||
onSave() {
|
||||
// 先进行表单验证
|
||||
this.$refs.formRef.validate().then(() => {
|
||||
this.$dialog.confirm({
|
||||
|
||||
Reference in New Issue
Block a user