diff --git a/db/20260813_add_wallet_transaction_tags.sql b/db/20260813_add_wallet_transaction_tags.sql new file mode 100644 index 00000000..cbe85615 --- /dev/null +++ b/db/20260813_add_wallet_transaction_tags.sql @@ -0,0 +1,6 @@ +-- 为充值导入记录及最终钱包流水补充财务统计标签;历史数据保持为空。 +ALTER TABLE wallet_recharge_import + ADD COLUMN tags varchar(512) DEFAULT NULL COMMENT '财务统计标签,多个wallet_transaction_tag字典值使用英文逗号分隔' AFTER price; + +ALTER TABLE wallet_transaction + ADD COLUMN tags varchar(512) DEFAULT NULL COMMENT '财务统计标签,多个wallet_transaction_tag字典值使用英文逗号分隔' AFTER balance; diff --git a/db/800pjcloudx_wallet.sql b/db/800pjcloudx_wallet.sql index 42efd830..c4583ce2 100644 --- a/db/800pjcloudx_wallet.sql +++ b/db/800pjcloudx_wallet.sql @@ -455,6 +455,7 @@ CREATE TABLE wallet_transaction title varchar(128) NOT NULL COMMENT '流水标题', price int NOT NULL COMMENT '交易金额, 单位分', balance int NOT NULL COMMENT '余额, 单位分', + tags varchar(512) DEFAULT NULL COMMENT '财务统计标签,多个wallet_transaction_tag字典值使用英文逗号分隔', create_by varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人', create_time datetime DEFAULT NULL COMMENT '创建时间', update_by varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '修改人', @@ -536,6 +537,7 @@ CREATE TABLE wallet_recharge_import apply_id bigint NOT NULL COMMENT '申请id', before_price int DEFAULT NULL COMMENT '导入前的金额', price int NOT NULL COMMENT '交易金额, 单位分', + tags varchar(512) DEFAULT NULL COMMENT '财务统计标签,多个wallet_transaction_tag字典值使用英文逗号分隔', create_by varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人', create_time datetime DEFAULT NULL COMMENT '创建时间', update_by varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '修改人', diff --git a/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/annotation/ExcelDict.java b/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/annotation/ExcelDict.java index 2b88ff1f..828ceb59 100644 --- a/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/annotation/ExcelDict.java +++ b/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/annotation/ExcelDict.java @@ -9,6 +9,7 @@ import java.lang.annotation.Target; /** * Excel 字典字段。 *

导入时校验单元格内容是否为字典标签,导出模板时将同一字典标签写入下拉选项。

+ *

多选字段在普通 Excel 中使用英文逗号分隔多个标签;原生下拉仅负责提供选项,无法自动追加多次选择。

*/ @Documented @Target(ElementType.FIELD) @@ -20,4 +21,11 @@ public @interface ExcelDict { */ String value(); + /** + * 是否允许一个单元格填写多个字典标签。 + * + * @return {@code true} 时按英文逗号拆分并逐项校验,{@code false} 时整个单元格只能是一个字典标签 + */ + boolean multiple() default false; + } diff --git a/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/annotation/ExcelTemplate.java b/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/annotation/ExcelTemplate.java index b295cf39..1dc04717 100644 --- a/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/annotation/ExcelTemplate.java +++ b/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/annotation/ExcelTemplate.java @@ -8,11 +8,18 @@ import java.lang.annotation.Target; /** * Excel 模板增强。 - *

切面自动收集 {@link RequestExcel} 导入参数中的模型,下载接口无需感知文件名或具体业务模型。

+ *

切面默认自动收集当前服务 {@link RequestExcel} 导入参数中的模型;跨服务模型可通过注解参数显式补充。

*/ @Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ExcelTemplate { + /** + * 当前下载接口需要额外处理的 Excel 模型,用于补充当前服务无法自动扫描到的跨服务导入模型。 + * + * @return 需要参与模板字典下拉增强的模型类型;默认不额外补充模型 + */ + Class[] value() default {}; + } diff --git a/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/aop/ExcelTemplateAspect.java b/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/aop/ExcelTemplateAspect.java index 9ba53d64..6918a987 100644 --- a/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/aop/ExcelTemplateAspect.java +++ b/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/aop/ExcelTemplateAspect.java @@ -63,7 +63,7 @@ public class ExcelTemplateAspect { Object result = joinPoint.proceed(wrappedArgs); wrapper.flushBuffer(); byte[] content = wrapper.getContentAsByteArray(); - byte[] enhancedContent = enhance(content, getModelClasses()); + byte[] enhancedContent = enhance(content, resolveModelClasses(excelTemplate)); response.setContentLength(enhancedContent.length); response.getOutputStream().write(enhancedContent); response.getOutputStream().flush(); @@ -72,6 +72,18 @@ public class ExcelTemplateAspect { throw new IllegalStateException("@ExcelTemplate 方法必须包含 HttpServletResponse 参数"); } + /** + * 合并注解显式模型与当前服务自动扫描模型,保证跨服务模板可增强且保留原有自动发现能力。 + * + * @param excelTemplate 当前下载方法的模板增强注解 + * @return 去重后的模板模型集合 + */ + private Class[] resolveModelClasses(ExcelTemplate excelTemplate) { + return java.util.stream.Stream.concat(Arrays.stream(excelTemplate.value()), Arrays.stream(getModelClasses())) + .distinct() + .toArray(Class[]::new); + } + private boolean isExcelTemplateRequest(Object[] args) { for (Object arg : args) { if (arg instanceof String value) { @@ -85,7 +97,7 @@ public class ExcelTemplateAspect { } /** - * 导入接口的 {@code @RequestExcel List} 是模板模型的唯一来源,新增导入模型无需再修改下载接口或注册表。 + * 自动收集当前服务导入接口中的 {@code @RequestExcel List} 模型,供未显式注册的同服务模板继续使用。 */ private Class[] getModelClasses() { Class[] cachedModelClasses = modelClasses; @@ -162,7 +174,7 @@ public class ExcelTemplateAspect { HeaderCell headerCell = findHeaderCell(sheet, headerName); if (headerCell != null) { ExcelDictDataValidationWriter.write(sheet, headerCell.columnIndex(), headerCell.rowIndex() + 1, - excelDict.value()); + excelDict.value(), excelDict.multiple()); enhanced = true; } } diff --git a/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/dict/ExcelDictDataValidationWriter.java b/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/dict/ExcelDictDataValidationWriter.java index f0837dce..a9b73fb6 100644 --- a/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/dict/ExcelDictDataValidationWriter.java +++ b/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/dict/ExcelDictDataValidationWriter.java @@ -31,9 +31,15 @@ public final class ExcelDictDataValidationWriter { } /** - * 将字典标签写入隐藏页,并为目标列创建引用式下拉。 + * 将字典标签写入隐藏页,并为目标列创建引用式下拉;多选模式允许按英文逗号手工组合多个选项。 + * + * @param dataSheet 数据页 + * @param columnIndex 字典字段所在列下标 + * @param firstDataRowIndex 首行数据的下标 + * @param dictType 字典类型 + * @param multiple 是否允许一个单元格包含多个字典标签 */ - public static void write(Sheet dataSheet, int columnIndex, int firstDataRowIndex, String dictType) { + public static void write(Sheet dataSheet, int columnIndex, int firstDataRowIndex, String dictType, boolean multiple) { List labels = ExcelDictHelper.getLabels(dictType); if (labels.isEmpty()) { throw new IllegalStateException("Excel 字典未配置或没有可用项: " + dictType); @@ -41,7 +47,7 @@ public final class ExcelDictDataValidationWriter { Workbook workbook = dataSheet.getWorkbook(); String formulaName = writeDictColumn(workbook, dictType, labels); removeColumnValidations(dataSheet, columnIndex); - addValidation(dataSheet, columnIndex, firstDataRowIndex, formulaName); + addValidation(dataSheet, columnIndex, firstDataRowIndex, formulaName, multiple); } /** @@ -123,15 +129,30 @@ public final class ExcelDictDataValidationWriter { return false; } - private static void addValidation(Sheet sheet, int columnIndex, int firstDataRowIndex, String formulaName) { + /** + * 为目标列添加字典下拉。多选时关闭 Excel 原生错误拦截,否则逗号组合值会被单选列表拒绝。 + * + * @param sheet 数据页 + * @param columnIndex 字典字段所在列下标 + * @param firstDataRowIndex 首行数据的下标 + * @param formulaName 隐藏字典区域的名称 + * @param multiple 是否允许按英文逗号组合多个标签 + */ + private static void addValidation(Sheet sheet, int columnIndex, int firstDataRowIndex, String formulaName, boolean multiple) { DataValidationHelper helper = sheet.getDataValidationHelper(); DataValidationConstraint constraint = helper.createFormulaListConstraint(formulaName); CellRangeAddressList range = new CellRangeAddressList(firstDataRowIndex, sheet.getWorkbook().getSpreadsheetVersion().getLastRowIndex(), columnIndex, columnIndex); DataValidation validation = helper.createValidation(constraint, range); validation.setEmptyCellAllowed(true); - validation.setShowErrorBox(true); - validation.createErrorBox("填写错误", "请选择下拉中的有效字典项"); + validation.setShowErrorBox(!multiple); + if (multiple) { + validation.setShowPromptBox(true); + validation.createPromptBox("填写说明", "可填写多个选项,请使用英文逗号分隔"); + } + else { + validation.createErrorBox("填写错误", "请选择下拉中的有效字典项"); + } sheet.addValidationData(validation); } diff --git a/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/handler/DefaultAnalysisEventListener.java b/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/handler/DefaultAnalysisEventListener.java index ade9e13d..4bd9519b 100644 --- a/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/handler/DefaultAnalysisEventListener.java +++ b/pjcloud-common/pjcloud-common-excel/src/main/java/com/bsd/pjcloud/common/excel/handler/DefaultAnalysisEventListener.java @@ -12,6 +12,7 @@ import org.springframework.util.StringUtils; import jakarta.validation.ConstraintViolation; import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; @@ -67,7 +68,10 @@ public class DefaultAnalysisEventListener extends ListAnalysisEventListener validateExcelDict(Object data) { Set messages = new LinkedHashSet<>(); @@ -82,14 +86,24 @@ public class DefaultAnalysisEventListener extends ListAnalysisEventListener labels = dictLabelCache.computeIfAbsent(excelDict.value(), ExcelDictHelper::getLabels); String fieldName = getExcelFieldName(field); if (labels.isEmpty()) { messages.add(fieldName + "字典未配置或没有可用项"); } - else if (!labels.contains(label)) { - messages.add(fieldName + ":" + label + ",不在可选项中"); + else { + List selectedLabels = excelDict.multiple() + ? Arrays.stream(fieldText.split(",", -1)).map(String::trim).toList() + : List.of(fieldText); + if (selectedLabels.stream().anyMatch(label -> !StringUtils.hasText(label))) { + messages.add(fieldName + "包含空选项,请使用英文逗号正确分隔"); + continue; + } + List invalidLabels = selectedLabels.stream().filter(label -> !labels.contains(label)).distinct().toList(); + if (!invalidLabels.isEmpty()) { + messages.add(fieldName + ":" + String.join(",", invalidLabels) + ",不在可选项中"); + } } } catch (IllegalAccessException exception) { diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml b/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml index 8778e507..11ab4640 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml @@ -146,6 +146,13 @@ spring-boot-starter-test test + + + org.mockito + mockito-core + 5.11.0 + test + com.bsd diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusWechatServiceImplPayCallbackTest.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusWechatServiceImplPayCallbackTest.java index 4692cfbc..a436cf11 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusWechatServiceImplPayCallbackTest.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusWechatServiceImplPayCallbackTest.java @@ -19,7 +19,7 @@ import com.wechat.pay.java.core.notification.NotificationParser; import com.wechat.pay.java.core.notification.RequestParam; import com.wechat.pay.java.service.partnerpayments.app.AppServiceExtension; import com.wechat.pay.java.service.partnerpayments.app.model.Transaction; -import com.wechat.pay.java.service.partnerpayments.app.model.TransactionAmount; +import com.wechat.pay.java.service.partnerpayments.model.TransactionAmount; import com.wechat.pay.java.service.refund.RefundService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; diff --git a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/vo/StudentUserExcelVO.java b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/vo/StudentUserExcelVO.java index f9c428ae..3fcd3fa3 100644 --- a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/vo/StudentUserExcelVO.java +++ b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/vo/StudentUserExcelVO.java @@ -43,11 +43,6 @@ public class StudentUserExcelVO { */ private Long guardianUserId; - /** - * 监护人部门id - */ - private Long guardianDeptId; - /** * 学号 */ @@ -55,6 +50,19 @@ public class StudentUserExcelVO { @ExcelProperty("学号") private String studentNo; + /** + * 学生所属部门名称或路径,必须匹配当前租户已有部门。 + */ + @NotBlank(message = "学生部门") + @ExcelProperty("学生部门") + private String studentDeptName; + + /** + * 同步校验阶段解析出的学生部门 ID,仅供异步写入使用,不直接读取 Excel。 + */ + @ExcelIgnore + private Long studentDeptId; + /** * 学校名称 */ diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysFileController.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysFileController.java index 8279a215..79075bee 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysFileController.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysFileController.java @@ -31,6 +31,7 @@ import com.bsd.pjcloud.common.excel.annotation.ExcelTemplate; import com.bsd.pjcloud.common.log.annotation.SysLog; import com.bsd.pjcloud.common.security.annotation.HasPermission; import com.bsd.pjcloud.common.security.annotation.Inner; +import com.bsd.pjcloud.wallet.api.dto.recharge.excel.WalletRechargeExcelDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; @@ -152,7 +153,7 @@ public class SysFileController { * @param response 本地文件 */ @SneakyThrows - @ExcelTemplate + @ExcelTemplate(WalletRechargeExcelDto.class) @GetMapping("/local/file/{fileName}") public void localFile(@PathVariable String fileName, HttpServletResponse response) { ClassPathResource resource = new ClassPathResource("file/" + fileName); diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserController.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserController.java index fd105598..68070e18 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserController.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserController.java @@ -421,7 +421,7 @@ public class SysUserController { * @return 校验失败时包含对应行号和错误原因;校验通过时包含异步任务 ID 和初始进度 */ @PostMapping("/import") - @HasPermission("sys_user_export") + @HasPermission("sys_user_import") public R importUser(@RequestExcel List excelVOList, BindingResult bindingResult) { return userImportExportService.importUser(excelVOList, bindingResult); } @@ -479,7 +479,7 @@ public class SysUserController { * @return 校验失败时包含校验或图片处理错误;校验通过时包含异步任务 ID 和初始进度 */ @PostMapping("/import-face") - @HasPermission("sys_user_export") + @HasPermission("sys_user_import") @Idempotent(expireTime = 5, key = "'/user/import-face:' + #file.getSize() + ':' + #excelList.hashCode() + ':' + T(com.bsd.pjcloud.common.security.util.SecurityUtils).getUser().getId()", info = "正在导入中,请勿重复操作") public R importUserFace(@RequestParam("file") MultipartFile file, @RequestExcel List excelList, BindingResult bindingResult) { return userImportExportService.importUserFace(file, excelList, bindingResult); diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysUserImportExportServiceImpl.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysUserImportExportServiceImpl.java index 02dafddd..0b3123d5 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysUserImportExportServiceImpl.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysUserImportExportServiceImpl.java @@ -254,10 +254,13 @@ public class SysUserImportExportServiceImpl implements SysUserImportExportServic excel.setUsername(StrUtil.trim(excel.getUsername())); excel.setGuardianPhone(StrUtil.trim(excel.getGuardianPhone())); excel.setStudentNo(StrUtil.trim(excel.getStudentNo())); + excel.setStudentDeptName(StrUtil.trim(excel.getStudentDeptName())); excel.setSchoolName(StrUtil.trim(excel.getSchoolName())); excel.setClassName(StrUtil.trim(excel.getClassName())); }); + DepartmentIndex departmentIndex = new DepartmentIndex(sysDeptService.list()); + List guardianPhones = studentUserExcelList.stream().map(StudentUserExcelVO::getGuardianPhone) .filter(StrUtil::isNotBlank).distinct().toList(); Map guardianUserMap = guardianPhones.isEmpty() ? Map.of() : sysUserService.list( @@ -324,9 +327,12 @@ public class SysUserImportExportServiceImpl implements SysUserImportExportServic errorMessages.add("监护人手机号:" + excel.getGuardianPhone() + ",对应的用户未添加或未审批通过"); } else { excel.setGuardianUserId(guardian.getUserId()); - excel.setGuardianDeptId(guardian.getDeptId()); } } + SysDept studentDepartment = resolveDepartment(excel.getStudentDeptName(), departmentIndex, errorMessages); + if (studentDepartment != null) { + excel.setStudentDeptId(studentDepartment.getDeptId()); + } if (CollUtil.isNotEmpty(errorMessages)) { errorMessageList.add(new ErrorMessage(excel.getLineNum(), errorMessages)); } @@ -740,7 +746,7 @@ public class SysUserImportExportServiceImpl implements SysUserImportExportServic } /** - * 创建学生用户及监护关系,学生扩展信息只属于 STUDENT 身份。 + * 创建学生用户及监护关系,学生扩展信息只属于 STUDENT 身份,用户部门使用模板中已校验的学生部门。 * * @param excel 当前校验通过的学生行 * @param guardianWardRelationTypeId 监护关系类型 ID @@ -757,7 +763,7 @@ public class SysUserImportExportServiceImpl implements SysUserImportExportServic relationUser.setStaffingType(UserStaffingTypeEnum.NONE_STAFFING.getType()); relationUser.setIdentityTypes(List.of(UserIdentityTypeEnum.STUDENT.getType())); relationUser.setRole(studentRoleIds); - relationUser.setDeptId(excel.getGuardianDeptId()); + relationUser.setDeptId(excel.getStudentDeptId()); SysUserStudentDto studentInfo = new SysUserStudentDto(); studentInfo.setStudentNo(excel.getStudentNo()); studentInfo.setSchoolName(excel.getSchoolName()); diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/resources/file/student_user_import.xlsx b/pjcloud-upms/pjcloud-upms-biz/src/main/resources/file/student_user_import.xlsx index 284f54b7..320b9389 100644 Binary files a/pjcloud-upms/pjcloud-upms-biz/src/main/resources/file/student_user_import.xlsx and b/pjcloud-upms/pjcloud-upms-biz/src/main/resources/file/student_user_import.xlsx differ diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/resources/file/wallet_recharge.xlsx b/pjcloud-upms/pjcloud-upms-biz/src/main/resources/file/wallet_recharge.xlsx index 3d4d1cd2..d9a72455 100644 Binary files a/pjcloud-upms/pjcloud-upms-biz/src/main/resources/file/wallet_recharge.xlsx and b/pjcloud-upms/pjcloud-upms-biz/src/main/resources/file/wallet_recharge.xlsx differ diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/resources/mapper/SysUserMapper.xml b/pjcloud-upms/pjcloud-upms-biz/src/main/resources/mapper/SysUserMapper.xml index 45feeaad..5b261499 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/resources/mapper/SysUserMapper.xml +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/resources/mapper/SysUserMapper.xml @@ -219,6 +219,18 @@ AND u.staffing_type = #{query.staffingType} + + + AND EXISTS ( + SELECT 1 + FROM sys_user_identity user_identity + WHERE user_identity.user_id = u.user_id + AND user_identity.identity_type IN + + #{identityType} + + ) + and urole.role_id IN @@ -307,6 +319,18 @@ AND u.dept_id = #{query.deptId} + + + AND EXISTS ( + SELECT 1 + FROM sys_user_identity user_identity + WHERE user_identity.user_id = u.user_id + AND user_identity.identity_type IN + + #{identityType} + + ) + and urole.role_id IN diff --git a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/constant/WalletTransactionTag.java b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/constant/WalletTransactionTag.java new file mode 100644 index 00000000..0eb4aeef --- /dev/null +++ b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/constant/WalletTransactionTag.java @@ -0,0 +1,34 @@ +package com.bsd.pjcloud.wallet.api.constant; + +import cn.hutool.core.util.ObjectUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 充值流水tag + */ +@Getter +@AllArgsConstructor +public enum WalletTransactionTag { + + BJZ_CZ("1", "不记账充值"), + ; + + private final String code; + + private final String desc; + + public static WalletTransactionTag valueOfByCode(String code) { + if (ObjectUtil.isEmpty(code)) return null; + return Arrays.stream(values()).filter(v -> ObjectUtil.equals(code, v.getCode())).findFirst().orElse(null); + } + + public static WalletTransactionTag descOf(String description) { + return Arrays.stream(WalletTransactionTag.values()) + .filter(v -> ObjectUtil.equal(v.getDesc(), description)) + .findFirst().orElse(null); + } + +} diff --git a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/constant/WalletTypeEnum.java b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/constant/WalletTypeEnum.java index e71c525b..7630e505 100644 --- a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/constant/WalletTypeEnum.java +++ b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/constant/WalletTypeEnum.java @@ -23,12 +23,12 @@ public enum WalletTypeEnum { public static WalletTypeEnum valueOfByCode(String code) { if (ObjectUtil.isEmpty(code)) return null; - return Arrays.stream(values()).filter(v -> ObjectUtil.equals(code, v.getCode())).findFirst().get(); + return Arrays.stream(values()).filter(v -> ObjectUtil.equals(code, v.getCode())).findFirst().orElse(null); } public static WalletTypeEnum descOf(String description) { return Arrays.stream(WalletTypeEnum.values()) .filter(v -> ObjectUtil.equal(v.getDesc(), description)) - .findFirst().get(); + .findFirst().orElse(null); } } diff --git a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/WalletBalanceTransactionDto.java b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/WalletBalanceTransactionDto.java index 2e7d0e0f..5faf255e 100644 --- a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/WalletBalanceTransactionDto.java +++ b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/WalletBalanceTransactionDto.java @@ -32,4 +32,9 @@ public class WalletBalanceTransactionDto { @NotNull(message = "是否暴力消费") private Boolean force = false; + /** + * 财务统计标签字典值,多个值使用英文逗号分隔;未传时不为流水设置标签。 + */ + private String tags; + } diff --git a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/WalletTransactionDto.java b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/WalletTransactionDto.java index 3fdf245f..4a98183d 100644 --- a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/WalletTransactionDto.java +++ b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/WalletTransactionDto.java @@ -39,6 +39,11 @@ public class WalletTransactionDto extends WalletTransactionEntity implements Bas private List bizTypes; + /** + * 财务统计标签筛选值;传入多个字典值时匹配包含任一标签的流水。 + */ + private List tagList; + private LocalDate[] createTimeList; /** diff --git a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/recharge/WalletRechargeImportDto.java b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/recharge/WalletRechargeImportDto.java index 0220b420..32b451d6 100644 --- a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/recharge/WalletRechargeImportDto.java +++ b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/recharge/WalletRechargeImportDto.java @@ -18,6 +18,11 @@ public class WalletRechargeImportDto extends WalletRechargeImportEntity implemen private List userUniqueIds; + /** + * 财务统计标签筛选值;传入多个字典值时匹配包含任一标签的导入记录。 + */ + private List tagList; + private Long userDeptId; diff --git a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/recharge/excel/WalletRechargeExcelDto.java b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/recharge/excel/WalletRechargeExcelDto.java index 6697c9df..577256eb 100644 --- a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/recharge/excel/WalletRechargeExcelDto.java +++ b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/recharge/excel/WalletRechargeExcelDto.java @@ -6,6 +6,7 @@ import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.alibaba.excel.annotation.write.style.ContentRowHeight; import com.alibaba.excel.annotation.write.style.HeadRowHeight; +import com.bsd.pjcloud.common.excel.annotation.ExcelDict; import com.bsd.pjcloud.common.excel.annotation.ExcelLine; import lombok.AllArgsConstructor; import lombok.Builder; @@ -42,6 +43,20 @@ public class WalletRechargeExcelDto { @ExcelProperty("操作金额") private String price; + /** + * Excel 中展示和填写的财务标签名称;支持使用英文逗号分隔多个 {@code wallet_transaction_tag} 字典标签。 + */ + @ColumnWidth(100) + @ExcelProperty("操作标签") + @ExcelDict(value = "wallet_transaction_tag", multiple = true) + private String tag; + + /** + * 导入校验后解析出的财务标签字典值,多个值使用英文逗号分隔,不直接读取 Excel。 + */ + @ExcelIgnore + private String tags; + // 操作余额 private Integer priceNumber; diff --git a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/entity/WalletTransactionEntity.java b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/entity/WalletTransactionEntity.java index 55d771e3..7e0529e8 100644 --- a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/entity/WalletTransactionEntity.java +++ b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/entity/WalletTransactionEntity.java @@ -78,6 +78,12 @@ public class WalletTransactionEntity extends Model { @Schema(description = "余额, 单位分") private Integer balance; + /** + * 财务统计标签,保存 {@code wallet_transaction_tag} 字典值;多个值使用英文逗号分隔,历史流水可为空。 + */ + @Schema(description = "财务统计标签,多个字典值使用英文逗号分隔") + private String tags; + /** * 创建人 */ @@ -119,4 +125,4 @@ public class WalletTransactionEntity extends Model { */ @Schema(description = "租户ID") private Long tenantId; -} \ No newline at end of file +} diff --git a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/entity/recharge/WalletRechargeImportEntity.java b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/entity/recharge/WalletRechargeImportEntity.java index fc35e52f..d006d8dd 100644 --- a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/entity/recharge/WalletRechargeImportEntity.java +++ b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/entity/recharge/WalletRechargeImportEntity.java @@ -67,6 +67,12 @@ public class WalletRechargeImportEntity extends Model { + R> result = remoteDictService.getDictByType(dictType); + if (result == null || !result.isOk() || result.getData() == null) { + return Collections.emptyList(); + } + return result.getData().stream().map(SysDictItem::getLabel).toList(); + }; + } + +} diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/WalletTransactionController.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/WalletTransactionController.java index d9498856..e69a7a85 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/WalletTransactionController.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/WalletTransactionController.java @@ -56,7 +56,7 @@ public class WalletTransactionController { @Inner @PostMapping("/add-balance") public R addWalletBalance(@Valid @RequestBody WalletBalanceTransactionDto dto) { - WalletTransactionEntity walletTransaction = walletTransactionService.addWalletBalance(dto.getWalletId(), dto.getPrice(), dto.getBizId(), dto.getBizType()); + WalletTransactionEntity walletTransaction = walletTransactionService.addWalletBalance(dto.getWalletId(), dto.getPrice(), dto.getBizId(), dto.getBizType(), dto.getTags()); if (ObjectUtil.isEmpty(walletTransaction)) return R.failed("增加钱包余额错误"); return R.ok(walletTransaction); } diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/WalletTransactionMapper.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/WalletTransactionMapper.java index 24e1d0a8..225e311f 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/WalletTransactionMapper.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/WalletTransactionMapper.java @@ -61,6 +61,20 @@ public interface WalletTransactionMapper extends PjcloudBaseMapper { + boolean first = true; + for (String tag : dto.getTagList()) { + if (first) { + nested.apply("FIND_IN_SET({0}, tags) > 0", tag); + first = false; + } + else { + nested.or().apply("FIND_IN_SET({0}, tags) > 0", tag); + } + } + }); + } return wrapper; } @@ -98,5 +112,11 @@ public interface WalletTransactionMapper extends PjcloudBaseMapper sumAndCount(@Param("dto") WalletTransactionSumAndCountDto dto); - List exportCzTotalPerson(); -} \ No newline at end of file + /** + * 查询固定金额范围内的个人充值统计明细,并按财务标签筛选。 + * + * @param dto 充值统计筛选条件;标签多选时匹配包含任一标签的流水 + * @return 个人充值统计导出明细 + */ + List exportCzTotalPerson(@Param("dto") WalletTransactionDto dto); +} diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/WalletTransactionService.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/WalletTransactionService.java index 3f654347..7d3eeb85 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/WalletTransactionService.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/WalletTransactionService.java @@ -69,6 +69,18 @@ public interface WalletTransactionService extends IService list); /** diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/WalletTransactionServiceImpl.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/WalletTransactionServiceImpl.java index 6e1d0128..36cefd52 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/WalletTransactionServiceImpl.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/WalletTransactionServiceImpl.java @@ -212,6 +212,22 @@ public class WalletTransactionServiceImpl extends ServiceImpl list = baseMapper.exportCzTotalPerson(); + List list = baseMapper.exportCzTotalPerson(dto); WriteSheet writeSheet = EasyExcel.writerSheet().build(); FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/recharge/WalletRechargeApplyServiceImpl.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/recharge/WalletRechargeApplyServiceImpl.java index e4a2f74e..a755fa87 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/recharge/WalletRechargeApplyServiceImpl.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/recharge/WalletRechargeApplyServiceImpl.java @@ -12,8 +12,10 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.bsd.pjcloud.admin.api.dto.UserInfo; import com.bsd.pjcloud.admin.api.entity.SysDept; +import com.bsd.pjcloud.admin.api.entity.SysDictItem; import com.bsd.pjcloud.admin.api.entity.SysUser; import com.bsd.pjcloud.admin.api.feign.RemoteDeptService; +import com.bsd.pjcloud.admin.api.feign.RemoteDictService; import com.bsd.pjcloud.admin.api.feign.RemoteUserService; import com.bsd.pjcloud.admin.api.utils.DataUtil; import com.bsd.pjcloud.admin.api.vo.UserVO; @@ -90,6 +92,11 @@ public class WalletRechargeApplyServiceImpl extends ServiceImpl batchSaveList = list.stream().map(v -> WalletRechargeImportEntity.builder().price(v.getPriceNumber()).userId(v.getUserId()).userDeptId(v.getUserDeptId()).build()).toList(); + List batchSaveList = list.stream().map(v -> WalletRechargeImportEntity.builder().price(v.getPriceNumber()).userId(v.getUserId()).userDeptId(v.getUserDeptId()).tags(v.getTags()).build()).toList(); batchSaveList.forEach(v -> v.setApplyId(insertObj.getId())); rechargeImportService.saveBatch(batchSaveList); return R.ok(null, "导入成功"); @@ -349,7 +357,7 @@ public class WalletRechargeApplyServiceImpl extends ServiceImpl batchSaveList = list.stream().map(v -> WalletRechargeImportEntity.builder().price(0).userId(v.getUserId()).userDeptId(v.getUserDeptId()).build()).toList(); + List batchSaveList = list.stream().map(v -> WalletRechargeImportEntity.builder().price(0).userId(v.getUserId()).userDeptId(v.getUserDeptId()).tags(v.getTags()).build()).toList(); batchSaveList.forEach(v -> v.setApplyId(insertObj.getId())); rechargeImportService.saveBatch(batchSaveList); return R.ok(null, "导入成功"); @@ -371,7 +379,7 @@ public class WalletRechargeApplyServiceImpl extends ServiceImpl batchSaveList = list.stream().map(v -> WalletRechargeImportEntity.builder().price(v.getPriceNumber()).userId(v.getUserId()).userDeptId(v.getUserDeptId()).invoiceId(invoice.getId()).build()).toList(); + List batchSaveList = list.stream().map(v -> WalletRechargeImportEntity.builder().price(v.getPriceNumber()).userId(v.getUserId()).userDeptId(v.getUserDeptId()).invoiceId(invoice.getId()).tags(v.getTags()).build()).toList(); rechargeImportService.saveBatch(batchSaveList); return R.ok(null, "导入成功"); } @@ -388,6 +396,7 @@ public class WalletRechargeApplyServiceImpl extends ServiceImpl errorMessageList = (List) bindingResult.getTarget(); + resolveTransactionTagValues(list, errorMessageList); List usernameList = list.stream().map(WalletRechargeExcelDto::getUsername).toList(); List userPoneList = list.stream().map(WalletRechargeExcelDto::getUserMobile).toList(); for (WalletRechargeExcelDto v : list) { @@ -442,6 +451,41 @@ public class WalletRechargeApplyServiceImpl extends ServiceImpl list, List errorMessageList) { + List taggedRows = list.stream().filter(item -> ObjectUtil.isNotEmpty(item.getTag())).toList(); + if (taggedRows.isEmpty()) { + return; + } + R> result = remoteDictService.getDictByType(TRANSACTION_TAG_DICT_TYPE); + if (result == null || !result.isOk() || ObjectUtil.isEmpty(result.getData())) { + taggedRows.forEach(item -> errorMessageList.add(new ErrorMessage(item.getLineNum(), Set.of("操作标签字典查询失败")))); + return; + } + Map valueByLabel = result.getData().stream() + .filter(item -> ObjectUtil.isNotEmpty(item.getLabel()) && ObjectUtil.isNotEmpty(item.getItemValue())) + .collect(java.util.stream.Collectors.toMap(SysDictItem::getLabel, SysDictItem::getItemValue, (first, ignored) -> first)); + for (WalletRechargeExcelDto item : taggedRows) { + List tagValues = Arrays.stream(item.getTag().split(",")) + .map(String::trim) + .distinct() + .map(valueByLabel::get) + .filter(Objects::nonNull) + .toList(); + long selectedTagCount = Arrays.stream(item.getTag().split(",")).map(String::trim).distinct().count(); + if (tagValues.size() != selectedTagCount) { + errorMessageList.add(new ErrorMessage(item.getLineNum(), Set.of("操作标签包含无法映射的字典项"))); + continue; + } + item.setTags(String.join(",", tagValues)); + } + } + /* ************************************* end excel end ************************************* */ @Override @@ -497,7 +541,7 @@ public class WalletRechargeApplyServiceImpl extends ServiceImpl userIds = importList.stream().map(WalletRechargeImportEntity::getUserId).distinct().toList(); @@ -548,7 +592,7 @@ public class WalletRechargeApplyServiceImpl extends ServiceImpl + @@ -22,6 +23,7 @@ a.title, a.price, a.balance, + a.tags, a.create_by, a.create_time, a.update_by, @@ -44,6 +46,13 @@ and a.biz_type = #{dto.bizType} + + and ( + + FIND_IN_SET(#{tag}, a.tags) > 0 + + ) + and a.dept_id = #{dto.deptId} @@ -286,6 +295,13 @@ and t.biz_type in ('1', '2', '3') and t.price in (80000, 160000, 240000) and t.dept_id not in (1875013738914156546, 1910218102049226753, 1910218128624336898, 1942046316044144642) + + and ( + + FIND_IN_SET(#{tag}, t.tags) > 0 + + ) + order by v.mchId, v.deptId @@ -303,4 +319,4 @@ ) - \ No newline at end of file + diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/resources/mapper/recharge/WalletRechargeImportMapper.xml b/pjcloud-wallet/pjcloud-wallet-biz/src/main/resources/mapper/recharge/WalletRechargeImportMapper.xml index 2d7394b3..086f0d75 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/resources/mapper/recharge/WalletRechargeImportMapper.xml +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/resources/mapper/recharge/WalletRechargeImportMapper.xml @@ -9,6 +9,7 @@ + @@ -24,6 +25,7 @@ a.apply_id, a.before_price, a.price, + a.tags, a.create_by, a.create_time, a.update_by, @@ -71,6 +73,13 @@ and a.create_time between #{dto.createTimeList[0]} and #{dto.createTimeList[1]} + + and ( + + FIND_IN_SET(#{tag}, a.tags) > 0 + + ) + @@ -134,4 +143,4 @@ - \ No newline at end of file +