diff --git a/yudao-module-fms/pom.xml b/yudao-module-fms/pom.xml
new file mode 100644
index 0000000..dc91b66
--- /dev/null
+++ b/yudao-module-fms/pom.xml
@@ -0,0 +1,67 @@
+
+
+ 4.0.0
+
+ cn.iocoder.boot
+ yudao
+ ${revision}
+
+
+ yudao-module-fms
+ jar
+
+ ${project.artifactId}
+
+ fms 包下,财务管理(Financial Management System)
+ 例如说:凭证、账簿、报表、结账等等
+
+
+
+
+ cn.iocoder.boot
+ yudao-module-system
+ ${revision}
+
+
+ cn.iocoder.boot
+ yudao-module-infra
+ ${revision}
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-web
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-security
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-excel
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-mybatis
+
+
+
+
+ com.googlecode.aviator
+ aviator
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-test
+ test
+
+
+
+
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/FmsClosingPeriodController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/FmsClosingPeriodController.java
new file mode 100644
index 0000000..c93d72d
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/FmsClosingPeriodController.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.fms.controller.admin.closing;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.fms.controller.admin.closing.vo.FmsClosingOverviewRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.closing.vo.FmsClosingQueryReqVO;
+import cn.iocoder.yudao.module.fms.service.closing.FmsClosingPeriodService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - FMS 结账期间")
+@RestController
+@RequestMapping("/fms/closing/period")
+@Validated
+public class FmsClosingPeriodController {
+
+ @Resource
+ private FmsClosingPeriodService closingPeriodService;
+
+ @GetMapping("/current-month")
+ @Operation(summary = "获得当前会计期间")
+ public CommonResult getCurrentMonth(@RequestParam("accountSetId") @NotNull Long accountSetId) {
+ return success(closingPeriodService.getCurrentMonth(accountSetId, getLoginUserId()));
+ }
+
+ @GetMapping("/overview")
+ @Operation(summary = "获得结账概况")
+ @PreAuthorize("@ss.hasPermission('fms:closing:query')")
+ public CommonResult getClosingOverview(@Valid FmsClosingQueryReqVO queryReqVO) {
+ return success(closingPeriodService.getClosingOverview(queryReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/close")
+ @Operation(summary = "结账")
+ @PreAuthorize("@ss.hasPermission('fms:closing:close')")
+ public CommonResult closePeriod(@Valid @RequestBody FmsClosingQueryReqVO queryReqVO) {
+ closingPeriodService.closePeriod(queryReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/cancel")
+ @Operation(summary = "反结账")
+ @PreAuthorize("@ss.hasPermission('fms:closing:cancel')")
+ public CommonResult cancelClosePeriod(@Valid FmsClosingQueryReqVO queryReqVO) {
+ closingPeriodService.cancelClosePeriod(queryReqVO, getLoginUserId());
+ return success(true);
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/FmsClosingSchemeController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/FmsClosingSchemeController.java
new file mode 100644
index 0000000..5e95e44
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/FmsClosingSchemeController.java
@@ -0,0 +1,86 @@
+package cn.iocoder.yudao.module.fms.controller.admin.closing;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.fms.controller.admin.closing.vo.FmsClosingQueryReqVO;
+import cn.iocoder.yudao.module.fms.controller.admin.closing.vo.FmsClosingSchemeRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.closing.vo.FmsClosingSchemeSaveReqVO;
+import cn.iocoder.yudao.module.fms.controller.admin.closing.vo.FmsProfitLossSettingsSaveReqVO;
+import cn.iocoder.yudao.module.fms.controller.admin.closing.vo.FmsSpecialClosingSettingsSaveReqVO;
+import cn.iocoder.yudao.module.fms.service.closing.FmsClosingSchemeService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - FMS 结账方案")
+@RestController
+@RequestMapping("/fms/closing/scheme")
+@Validated
+public class FmsClosingSchemeController {
+
+ @Resource
+ private FmsClosingSchemeService closingSchemeService;
+
+ @GetMapping("/list")
+ @Operation(summary = "获得结账方案列表")
+ @PreAuthorize("@ss.hasPermission('fms:closing:query')")
+ public CommonResult> getClosingSchemeList(@Valid FmsClosingQueryReqVO queryReqVO) {
+ return success(closingSchemeService.getClosingSchemeList(queryReqVO, getLoginUserId()));
+ }
+
+ @PostMapping("/create")
+ @Operation(summary = "创建结账方案")
+ @PreAuthorize("@ss.hasPermission('fms:closing:update')")
+ public CommonResult createClosingScheme(@Valid @RequestBody FmsClosingSchemeSaveReqVO createReqVO) {
+ return success(closingSchemeService.createClosingScheme(createReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "修改结账方案")
+ @PreAuthorize("@ss.hasPermission('fms:closing:update')")
+ public CommonResult updateClosingScheme(@Valid @RequestBody FmsClosingSchemeSaveReqVO updateReqVO) {
+ closingSchemeService.updateClosingScheme(updateReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @PutMapping("/update-profit-loss-settings")
+ @Operation(summary = "保存结转损益设置")
+ @PreAuthorize("@ss.hasPermission('fms:closing:update')")
+ public CommonResult saveProfitLossSettings(@Valid @RequestBody FmsProfitLossSettingsSaveReqVO saveReqVO) {
+ return success(closingSchemeService.saveProfitLossSettings(saveReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update-special-settings")
+ @Operation(summary = "保存专用结转设置")
+ @PreAuthorize("@ss.hasPermission('fms:closing:update')")
+ public CommonResult updateSpecialClosingSettings(@Valid @RequestBody FmsSpecialClosingSettingsSaveReqVO updateReqVO) {
+ closingSchemeService.updateSpecialClosingSettings(updateReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除结账方案")
+ @PreAuthorize("@ss.hasPermission('fms:closing:update')")
+ public CommonResult deleteClosingScheme(@RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("id") @NotNull Long id) {
+ closingSchemeService.deleteClosingScheme(accountSetId, id, getLoginUserId());
+ return success(true);
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/FmsClosingTemplateController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/FmsClosingTemplateController.java
new file mode 100644
index 0000000..9fb6dc1
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/FmsClosingTemplateController.java
@@ -0,0 +1,78 @@
+package cn.iocoder.yudao.module.fms.controller.admin.closing;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.fms.controller.admin.closing.vo.FmsClosingTemplateRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.closing.vo.FmsClosingTemplateSaveReqVO;
+import cn.iocoder.yudao.module.fms.service.closing.FmsClosingTemplateService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - FMS 结账模板")
+@RestController
+@RequestMapping("/fms/closing/template")
+@Validated
+public class FmsClosingTemplateController {
+
+ @Resource
+ private FmsClosingTemplateService closingTemplateService;
+
+ @GetMapping("/list")
+ @Operation(summary = "获得结账模板列表")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('fms:closing:query')")
+ public CommonResult> getClosingTemplateList(
+ @RequestParam("accountSetId") @NotNull Long accountSetId) {
+ return success(closingTemplateService.getClosingTemplateList(accountSetId, getLoginUserId()));
+ }
+
+ @PostMapping("/create")
+ @Operation(summary = "创建结账模板")
+ @PreAuthorize("@ss.hasPermission('fms:closing:update')")
+ public CommonResult createClosingTemplate(
+ @Valid @RequestBody FmsClosingTemplateSaveReqVO createReqVO) {
+ return success(closingTemplateService.createClosingTemplate(createReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新结账模板")
+ @PreAuthorize("@ss.hasPermission('fms:closing:update')")
+ public CommonResult updateClosingTemplate(
+ @Valid @RequestBody FmsClosingTemplateSaveReqVO updateReqVO) {
+ closingTemplateService.updateClosingTemplate(updateReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除结账模板")
+ @Parameters({
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024"),
+ @Parameter(name = "id", description = "结账模板编号", required = true, example = "1024")
+ })
+ @PreAuthorize("@ss.hasPermission('fms:closing:update')")
+ public CommonResult deleteClosingTemplate(@RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("id") @NotNull Long id) {
+ closingTemplateService.deleteClosingTemplate(accountSetId, id, getLoginUserId());
+ return success(true);
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/FmsClosingVoucherController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/FmsClosingVoucherController.java
new file mode 100644
index 0000000..4aa1d3b
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/FmsClosingVoucherController.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.fms.controller.admin.closing;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.fms.controller.admin.closing.vo.FmsClosingSchemeGenerateReqVO;
+import cn.iocoder.yudao.module.fms.controller.admin.closing.vo.FmsClosingVoucherGenerateReqVO;
+import cn.iocoder.yudao.module.fms.controller.admin.closing.vo.FmsProfitLossGenerateReqVO;
+import cn.iocoder.yudao.module.fms.service.closing.FmsClosingVoucherService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - FMS 结转凭证")
+@RestController
+@RequestMapping("/fms/closing/voucher")
+@Validated
+public class FmsClosingVoucherController {
+
+ @Resource
+ private FmsClosingVoucherService closingVoucherService;
+
+ @PostMapping("/generate-profit-loss")
+ @Operation(summary = "生成结转损益凭证")
+ @PreAuthorize("@ss.hasPermission('fms:closing:profit-loss')")
+ public CommonResult generateProfitLossVoucher(@Valid @RequestBody FmsProfitLossGenerateReqVO generateReqVO) {
+ return success(closingVoucherService.generateProfitLossVoucher(generateReqVO, getLoginUserId()));
+ }
+
+ @PostMapping("/generate-scheme")
+ @Operation(summary = "生成结账方案凭证")
+ @PreAuthorize("@ss.hasPermission('fms:closing:profit-loss')")
+ public CommonResult generateClosingSchemeVoucher(@Valid @RequestBody FmsClosingSchemeGenerateReqVO generateReqVO) {
+ return success(closingVoucherService.generateClosingSchemeVoucher(generateReqVO, getLoginUserId()));
+ }
+
+ @PostMapping("/generate-list")
+ @Operation(summary = "批量生成结转凭证")
+ @PreAuthorize("@ss.hasPermission('fms:closing:profit-loss')")
+ public CommonResult> generateClosingVoucherList(@Valid @RequestBody FmsClosingVoucherGenerateReqVO generateReqVO) {
+ return success(closingVoucherService.generateClosingVoucherList(generateReqVO, getLoginUserId()));
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingOverviewRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingOverviewRespVO.java
new file mode 100644
index 0000000..e12f59f
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingOverviewRespVO.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.fms.controller.admin.closing.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "管理后台 - FMS 结账概况 Response VO")
+@Data
+public class FmsClosingOverviewRespVO {
+
+ @Schema(description = "会计期间", example = "2026-08")
+ private String month;
+
+ @Schema(description = "是否已结账")
+ private Boolean closed;
+
+ @Schema(description = "是否要求凭证审核")
+ private Boolean voucherReviewRequired;
+
+ @Schema(description = "未审核凭证数量", example = "2")
+ private Long pendingVoucherCount;
+
+ @Schema(description = "凭证数量", example = "12")
+ private Long voucherCount;
+
+ @Schema(description = "损益类科目余额", example = "9100.00")
+ private BigDecimal profitLossBalance;
+
+ @Schema(description = "资产负债表差额", example = "0.00")
+ private BigDecimal balanceSheetDifference;
+
+ @Schema(description = "结转损益凭证编号", example = "1024")
+ private Long profitLossVoucherId;
+
+ @Schema(description = "初始余额是否试算平衡")
+ private Boolean initialBalanceBalanced;
+
+ @Schema(description = "凭证编号是否连续")
+ private Boolean voucherNumberContinuous;
+
+ @Schema(description = "需要结转损益时是否已生成凭证")
+ private Boolean profitLossVoucherGenerated;
+
+ @Schema(description = "利润表勾稽是否平衡")
+ private Boolean incomeStatementBalanced;
+
+ @Schema(description = "利润表未纳入公式的科目数量", example = "0")
+ private Integer incomeStatementUnmappedSubjectCount;
+
+ @Schema(description = "资产负债表损益是否已结转")
+ private Boolean balanceSheetProfitLossTransferred;
+
+ @Schema(description = "资产负债表是否平衡")
+ private Boolean balanceSheetBalanced;
+
+ @Schema(description = "资产负债表未纳入公式的科目数量", example = "0")
+ private Integer balanceSheetUnmappedSubjectCount;
+
+ @Schema(description = "是否满足全部结账条件")
+ private Boolean canClose;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingQueryReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingQueryReqVO.java
new file mode 100644
index 0000000..04e1521
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingQueryReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.fms.controller.admin.closing.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+import lombok.Data;
+
+@Schema(description = "管理后台 - FMS 结账查询 Request VO")
+@Data
+public class FmsClosingQueryReqVO {
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "账套不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "目标会计期间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-08")
+ @NotBlank(message = "会计期间不能为空")
+ @Pattern(regexp = "\\d{4}-(0[1-9]|1[0-2])", message = "会计期间格式不正确")
+ private String month;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingSchemeGenerateReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingSchemeGenerateReqVO.java
new file mode 100644
index 0000000..14cfa82
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingSchemeGenerateReqVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.fms.controller.admin.closing.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import lombok.Data;
+
+@Schema(description = "管理后台 - FMS 结账方案生成凭证 Request VO")
+@Data
+public class FmsClosingSchemeGenerateReqVO {
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "账套不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "方案编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "方案编号不能为空")
+ private Long id;
+
+ @Schema(description = "会计期间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-08")
+ @NotBlank(message = "会计期间不能为空")
+ @Pattern(regexp = "\\d{4}-(0[1-9]|1[0-2])", message = "会计期间格式不正确")
+ private String month;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingSchemeRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingSchemeRespVO.java
new file mode 100644
index 0000000..30c3e58
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingSchemeRespVO.java
@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.fms.controller.admin.closing.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Schema(description = "管理后台 - FMS 结账方案 Response VO")
+@Data
+public class FmsClosingSchemeRespVO {
+
+ @Schema(description = "方案编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", example = "1")
+ private Long accountSetId;
+
+ @Schema(description = "方案名称", example = "结转本月房租")
+ private String name;
+
+ @Schema(description = "是否期末结转")
+ private Boolean periodEnd;
+
+ @Schema(description = "来源科目编号", example = "1024")
+ private Long subjectId;
+
+ @Schema(description = "取数规则", example = "0")
+ private Integer formulaRule;
+
+ @Schema(description = "取数时间类型", example = "1")
+ private Integer timeType;
+
+ @Schema(description = "凭证字编号", example = "11")
+ private Long voucherWordId;
+
+ @Schema(description = "凭证摘要", example = "结转损益")
+ private String digest;
+
+ @Schema(description = "结转凭证类型", example = "2")
+ private Integer voucherType;
+
+ @Schema(description = "以前年度损益调整科目编号", example = "1024")
+ private Long priorYearAdjustmentSubjectId;
+
+ @Schema(description = "以前年度损益调整结转科目编号", example = "1025")
+ private Long adjustmentClosingSubjectId;
+
+ @Schema(description = "其他损益结转科目编号", example = "1026")
+ private Long otherClosingSubjectId;
+
+ @Schema(description = "是否按余额反向结转")
+ private Boolean reverseBalance;
+
+ @Schema(description = "结转日期", example = "31")
+ private Integer closingDay;
+
+ @Schema(description = "类型", example = "1")
+ private Integer type;
+
+ @Schema(description = "结转科目规则数组")
+ private List subjects;
+
+ @Schema(description = "待结转金额", example = "12000.00")
+ private BigDecimal balance;
+
+ @Schema(description = "当前期间已生成凭证编号数组")
+ private List voucherIds;
+
+ @Schema(description = "管理后台 - FMS 结账方案科目规则")
+ @Data
+ public static class SubjectRule {
+
+ @Schema(description = "科目编号", example = "1024")
+ private Long subjectId;
+
+ @Schema(description = "科目编码快照", example = "6602")
+ private String subjectCode;
+
+ @Schema(description = "摘要", example = "结转本月房租")
+ private String digest;
+
+ @Schema(description = "借贷方向", example = "1")
+ private Integer direction;
+
+ @Schema(description = "金额比例", example = "100")
+ private BigDecimal amountRatio;
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingSchemeSaveReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingSchemeSaveReqVO.java
new file mode 100644
index 0000000..80d6b9f
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingSchemeSaveReqVO.java
@@ -0,0 +1,89 @@
+package cn.iocoder.yudao.module.fms.controller.admin.closing.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.fms.enums.common.FmsDebitCreditDirectionEnum;
+import cn.iocoder.yudao.module.fms.enums.closing.FmsClosingTimeTypeEnum;
+import cn.iocoder.yudao.module.fms.enums.report.FmsFormulaRuleEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.DecimalMax;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Schema(description = "管理后台 - FMS 结账方案保存 Request VO")
+@Data
+public class FmsClosingSchemeSaveReqVO {
+
+ @Schema(description = "方案编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "账套不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "方案名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "结转本月房租")
+ @NotBlank(message = "方案名称不能为空")
+ @Size(max = 255, message = "方案名称长度不能超过 255 个字符")
+ private String name;
+
+ @Schema(description = "是否期末结转", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "是否期末结转不能为空")
+ private Boolean periodEnd;
+
+ @Schema(description = "来源科目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "来源科目不能为空")
+ private Long subjectId;
+
+ @Schema(description = "取数规则", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ @NotNull(message = "取数规则不能为空")
+ @InEnum(FmsFormulaRuleEnum.class)
+ private Integer formulaRule;
+
+ @Schema(description = "取数时间类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "取数时间类型不能为空")
+ @InEnum(FmsClosingTimeTypeEnum.class)
+ private Integer timeType;
+
+ @Schema(description = "凭证字编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11")
+ @NotNull(message = "凭证字不能为空")
+ private Long voucherWordId;
+
+ @Schema(description = "结转科目规则数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ @Valid
+ @NotEmpty(message = "结转科目规则不能为空")
+ @Size(min = 2, message = "结转科目规则至少需要两条")
+ private List subjects;
+
+ @Schema(description = "管理后台 - FMS 结账方案科目规则")
+ @Data
+ public static class SubjectRule {
+
+ @Schema(description = "科目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "科目不能为空")
+ private Long subjectId;
+
+ @Schema(description = "摘要", requiredMode = Schema.RequiredMode.REQUIRED, example = "结转本月房租")
+ @NotBlank(message = "摘要不能为空")
+ @Size(max = 500, message = "摘要长度不能超过 500 个字符")
+ private String digest;
+
+ @Schema(description = "借贷方向", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "借贷方向不能为空")
+ @InEnum(FmsDebitCreditDirectionEnum.class)
+ private Integer direction;
+
+ @Schema(description = "金额比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ @NotNull(message = "金额比例不能为空")
+ @DecimalMin(value = "0", inclusive = false, message = "金额比例必须大于 0")
+ @DecimalMax(value = "100", message = "金额比例不能超过 100")
+ private BigDecimal amountRatio;
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingTemplateRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingTemplateRespVO.java
new file mode 100644
index 0000000..0789aad
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingTemplateRespVO.java
@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.module.fms.controller.admin.closing.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - FMS 结账模板 Response VO")
+@Data
+public class FmsClosingTemplateRespVO {
+
+ @Schema(description = "模板编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long accountSetId;
+
+ @Schema(description = "系统预置编码", example = "daily-travel-reimbursement")
+ private String presetCode;
+
+ @Schema(description = "模板名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "报销差旅费")
+ private String name;
+
+ @Schema(description = "模板分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer category;
+
+ @Schema(description = "是否期末结转", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean periodEnd;
+
+ @Schema(description = "来源科目编号", example = "1024")
+ private Long subjectId;
+
+ @Schema(description = "取数规则", example = "0")
+ private Integer formulaRule;
+
+ @Schema(description = "取数时间类型", example = "1")
+ private Integer timeType;
+
+ @Schema(description = "结转科目规则数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List subjects;
+
+ @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ private Integer sort;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+ @Schema(description = "管理后台 - FMS 结账模板科目规则")
+ @Data
+ public static class SubjectRule {
+
+ @Schema(description = "科目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long subjectId;
+
+ @Schema(description = "科目编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "560107")
+ private String subjectCode;
+
+ @Schema(description = "摘要", requiredMode = Schema.RequiredMode.REQUIRED, example = "报销差旅费")
+ private String digest;
+
+ @Schema(description = "借贷方向", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer direction;
+
+ @Schema(description = "金额比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ private BigDecimal amountRatio;
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingTemplateSaveReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingTemplateSaveReqVO.java
new file mode 100644
index 0000000..af914a3
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingTemplateSaveReqVO.java
@@ -0,0 +1,92 @@
+package cn.iocoder.yudao.module.fms.controller.admin.closing.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.fms.enums.closing.FmsClosingTemplateCategoryEnum;
+import cn.iocoder.yudao.module.fms.enums.closing.FmsClosingTimeTypeEnum;
+import cn.iocoder.yudao.module.fms.enums.common.FmsDebitCreditDirectionEnum;
+import cn.iocoder.yudao.module.fms.enums.report.FmsFormulaRuleEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.DecimalMax;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Schema(description = "管理后台 - FMS 结账模板保存 Request VO")
+@Data
+public class FmsClosingTemplateSaveReqVO {
+
+ @Schema(description = "模板编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "账套不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "模板名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "报销差旅费")
+ @NotBlank(message = "模板名称不能为空")
+ @Size(max = 255, message = "模板名称长度不能超过 255 个字符")
+ private String name;
+
+ @Schema(description = "模板分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "模板分类不能为空")
+ @InEnum(FmsClosingTemplateCategoryEnum.class)
+ private Integer category;
+
+ @Schema(description = "是否期末结转", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "是否期末结转不能为空")
+ private Boolean periodEnd;
+
+ @Schema(description = "来源科目编号", example = "1024")
+ private Long subjectId;
+
+ @Schema(description = "取数规则", example = "0")
+ @InEnum(FmsFormulaRuleEnum.class)
+ private Integer formulaRule;
+
+ @Schema(description = "取数时间类型", example = "1")
+ @InEnum(FmsClosingTimeTypeEnum.class)
+ private Integer timeType;
+
+ @Schema(description = "结转科目规则数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ @Valid
+ @NotEmpty(message = "结转科目规则不能为空")
+ @Size(min = 2, message = "结转科目规则至少需要两条")
+ private List subjects;
+
+ @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ @NotNull(message = "显示顺序不能为空")
+ private Integer sort;
+
+ @Schema(description = "管理后台 - FMS 结账模板科目规则")
+ @Data
+ public static class SubjectRule {
+
+ @Schema(description = "科目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "科目不能为空")
+ private Long subjectId;
+
+ @Schema(description = "摘要", requiredMode = Schema.RequiredMode.REQUIRED, example = "报销差旅费")
+ @NotBlank(message = "摘要不能为空")
+ @Size(max = 500, message = "摘要长度不能超过 500 个字符")
+ private String digest;
+
+ @Schema(description = "借贷方向", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "借贷方向不能为空")
+ @InEnum(FmsDebitCreditDirectionEnum.class)
+ private Integer direction;
+
+ @Schema(description = "金额比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ @NotNull(message = "金额比例不能为空")
+ @DecimalMin(value = "0", inclusive = false, message = "金额比例必须大于 0")
+ @DecimalMax(value = "100", message = "金额比例不能超过 100")
+ private BigDecimal amountRatio;
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingVoucherGenerateReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingVoucherGenerateReqVO.java
new file mode 100644
index 0000000..36970d8
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsClosingVoucherGenerateReqVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.fms.controller.admin.closing.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - FMS 批量生成结转凭证 Request VO")
+@Data
+public class FmsClosingVoucherGenerateReqVO {
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "账套不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "方案编号数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotEmpty(message = "方案编号不能为空")
+ private List<@NotNull(message = "方案编号不能为空") Long> ids;
+
+ @Schema(description = "会计期间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-08")
+ @NotBlank(message = "会计期间不能为空")
+ @Pattern(regexp = "\\d{4}-(0[1-9]|1[0-2])", message = "会计期间格式不正确")
+ private String month;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsProfitLossGenerateReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsProfitLossGenerateReqVO.java
new file mode 100644
index 0000000..d48e181
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsProfitLossGenerateReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.fms.controller.admin.closing.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import lombok.Data;
+
+@Schema(description = "管理后台 - FMS 生成结转损益凭证 Request VO")
+@Data
+public class FmsProfitLossGenerateReqVO {
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "账套不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "会计期间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-08")
+ @NotBlank(message = "会计期间不能为空")
+ @Pattern(regexp = "\\d{4}-(0[1-9]|1[0-2])", message = "会计期间格式不正确")
+ private String month;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsProfitLossSettingsSaveReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsProfitLossSettingsSaveReqVO.java
new file mode 100644
index 0000000..85e5908
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsProfitLossSettingsSaveReqVO.java
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.fms.controller.admin.closing.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.fms.enums.closing.FmsClosingVoucherTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Schema(description = "管理后台 - FMS 结转损益设置保存 Request VO")
+@Data
+public class FmsProfitLossSettingsSaveReqVO {
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "账套不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "凭证字编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11")
+ @NotNull(message = "凭证字不能为空")
+ private Long voucherWordId;
+
+ @Schema(description = "凭证摘要", requiredMode = Schema.RequiredMode.REQUIRED, example = "结转损益")
+ @NotBlank(message = "凭证摘要不能为空")
+ @Size(max = 500, message = "凭证摘要长度不能超过 500 个字符")
+ private String digest;
+
+ @Schema(description = "结转凭证类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @NotNull(message = "结转凭证类型不能为空")
+ @InEnum(FmsClosingVoucherTypeEnum.class)
+ private Integer voucherType;
+
+ @Schema(description = "以前年度损益调整科目编号", requiredMode = Schema.RequiredMode.REQUIRED,
+ example = "1024")
+ @NotNull(message = "以前年度损益调整科目不能为空")
+ private Long priorYearAdjustmentSubjectId;
+
+ @Schema(description = "以前年度损益调整结转科目编号", requiredMode = Schema.RequiredMode.REQUIRED,
+ example = "1025")
+ @NotNull(message = "以前年度损益调整结转科目不能为空")
+ private Long adjustmentClosingSubjectId;
+
+ @Schema(description = "其他损益结转科目编号", requiredMode = Schema.RequiredMode.REQUIRED,
+ example = "1026")
+ @NotNull(message = "其他损益结转科目不能为空")
+ private Long otherClosingSubjectId;
+
+ @Schema(description = "是否按余额反向结转", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "结转方式不能为空")
+ private Boolean reverseBalance;
+
+ @Schema(description = "结转日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "31")
+ @NotNull(message = "结转日期不能为空")
+ @Min(value = 1, message = "结转日期不能小于 1")
+ @Max(value = 31, message = "结转日期不能大于 31")
+ private Integer closingDay;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsSpecialClosingSettingsSaveReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsSpecialClosingSettingsSaveReqVO.java
new file mode 100644
index 0000000..5e19ab5
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/closing/vo/FmsSpecialClosingSettingsSaveReqVO.java
@@ -0,0 +1,65 @@
+package cn.iocoder.yudao.module.fms.controller.admin.closing.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.fms.enums.common.FmsDebitCreditDirectionEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.DecimalMax;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Schema(description = "管理后台 - FMS 专用结转设置保存 Request VO")
+@Data
+public class FmsSpecialClosingSettingsSaveReqVO {
+
+ @Schema(description = "方案编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "方案编号不能为空")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "账套不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "凭证字编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11")
+ @NotNull(message = "凭证字不能为空")
+ private Long voucherWordId;
+
+ @Schema(description = "结转科目规则数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ @Valid
+ @NotEmpty(message = "结转科目规则不能为空")
+ @Size(min = 2, message = "结转科目规则至少需要两条")
+ private List subjects;
+
+ @Schema(description = "管理后台 - FMS 专用结转科目规则")
+ @Data
+ public static class SubjectRule {
+
+ @Schema(description = "科目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "科目不能为空")
+ private Long subjectId;
+
+ @Schema(description = "摘要", requiredMode = Schema.RequiredMode.REQUIRED, example = "计提所得税")
+ @NotBlank(message = "摘要不能为空")
+ @Size(max = 500, message = "摘要长度不能超过 500 个字符")
+ private String digest;
+
+ @Schema(description = "借贷方向", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "借贷方向不能为空")
+ @InEnum(FmsDebitCreditDirectionEnum.class)
+ private Integer direction;
+
+ @Schema(description = "金额比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "25")
+ @NotNull(message = "金额比例不能为空")
+ @DecimalMin(value = "0", inclusive = false, message = "金额比例必须大于 0")
+ @DecimalMax(value = "100", message = "金额比例不能超过 100")
+ private BigDecimal amountRatio;
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsAccountSetController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsAccountSetController.java
new file mode 100644
index 0000000..d139d75
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsAccountSetController.java
@@ -0,0 +1,105 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.accountset.FmsAccountSetInitializeReqVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.accountset.FmsAccountSetRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.accountset.FmsAccountSetSaveReqVO;
+import cn.iocoder.yudao.module.fms.dal.dataobject.config.FmsAccountSetDO;
+import cn.iocoder.yudao.module.fms.dal.dataobject.config.FmsAccountUserDO;
+import cn.iocoder.yudao.module.fms.service.config.FmsAccountSetService;
+import cn.iocoder.yudao.module.fms.service.config.FmsAccountUserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - FMS 账套")
+@RestController
+@RequestMapping("/fms/config/account-set")
+@Validated
+public class FmsAccountSetController {
+
+ @Resource
+ private FmsAccountSetService accountSetService;
+ @Resource
+ private FmsAccountUserService accountUserService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建账套")
+ @PreAuthorize("@ss.hasPermission('fms:config:account-set:create')")
+ public CommonResult createAccountSet(@Valid @RequestBody FmsAccountSetSaveReqVO createReqVO) {
+ return success(accountSetService.createAccountSet(createReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新账套")
+ @PreAuthorize("@ss.hasPermission('fms:config:account-set:update')")
+ public CommonResult updateAccountSet(@Valid @RequestBody FmsAccountSetSaveReqVO updateReqVO) {
+ accountSetService.updateAccountSet(updateReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @PutMapping("/initialize")
+ @Operation(summary = "初始化账套")
+ @PreAuthorize("@ss.hasPermission('fms:config:account-set:initialize')")
+ public CommonResult initializeAccountSet(
+ @Valid @RequestBody FmsAccountSetInitializeReqVO initializeReqVO) {
+ accountSetService.initializeAccountSet(initializeReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得账套")
+ @Parameter(name = "id", description = "账套编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('fms:config:account-set:query')")
+ public CommonResult getAccountSet(@RequestParam("id") Long id) {
+ FmsAccountSetDO accountSet = accountSetService.validateAccountSetReadPermission(id, getLoginUserId());
+ FmsAccountUserDO accountUser = accountUserService.getAccountUser(id, getLoginUserId());
+ return success(buildAccountSetRespVO(accountSet, accountUser));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得当前用户账套列表")
+ @PreAuthorize("@ss.hasPermission('fms:config:account-set:query')")
+ public CommonResult> getAccountSetList() {
+ List accountUsers = accountUserService.getAccountUserList(getLoginUserId());
+ Map accountSetMap = accountSetService.getAccountSetMap(
+ convertSet(accountUsers, FmsAccountUserDO::getAccountSetId));
+ return success(buildAccountSetRespVOList(accountSetMap, accountUsers));
+ }
+
+ // ==================== 拼接 VO ====================
+
+ private FmsAccountSetRespVO buildAccountSetRespVO(
+ FmsAccountSetDO accountSet, FmsAccountUserDO accountUser) {
+ return BeanUtils.toBean(accountSet, FmsAccountSetRespVO.class)
+ .setDefaultStatus(accountUser.getDefaultStatus()).setFounder(accountUser.getFounder())
+ .setLevel(accountUser.getLevel());
+ }
+
+ private List buildAccountSetRespVOList(
+ Map accountSetMap, List accountUsers) {
+ return convertList(accountUsers, accountUser -> buildAccountSetRespVO(
+ accountSetMap.get(accountUser.getAccountSetId()), accountUser));
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsAccountUserController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsAccountUserController.java
new file mode 100644
index 0000000..d055ab7
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsAccountUserController.java
@@ -0,0 +1,95 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.accountuser.FmsAccountUserRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.accountuser.FmsAccountUserUpdateReqVO;
+import cn.iocoder.yudao.module.fms.dal.dataobject.config.FmsAccountUserDO;
+import cn.iocoder.yudao.module.fms.service.config.FmsAccountUserService;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - FMS 账套用户")
+@RestController
+@RequestMapping("/fms/config/account-user")
+@Validated
+public class FmsAccountUserController {
+
+ @Resource
+ private FmsAccountUserService accountUserService;
+ @Resource
+ private AdminUserApi adminUserApi;
+ @Resource
+ private DeptApi deptApi;
+
+ @GetMapping("/list")
+ @Operation(summary = "获得账套用户列表")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('fms:config:account-set:authorize')")
+ public CommonResult> getAccountUserList(
+ @RequestParam("accountSetId") Long accountSetId) {
+ List accountUsers = accountUserService.getAccountUserList(
+ accountSetId, getLoginUserId());
+ return success(buildAccountUserRespVOList(accountUsers));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新账套用户列表")
+ @PreAuthorize("@ss.hasPermission('fms:config:account-set:authorize')")
+ public CommonResult updateAccountUserList(
+ @Valid @RequestBody FmsAccountUserUpdateReqVO updateReqVO) {
+ accountUserService.updateAccountUserList(updateReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @PutMapping("/update-default-status")
+ @Operation(summary = "更新默认账套状态")
+ @PreAuthorize("@ss.hasPermission('fms:config:account-set:query')")
+ public CommonResult updateAccountSetDefaultStatus(@RequestParam("accountSetId") Long accountSetId) {
+ accountUserService.updateAccountSetDefaultStatus(accountSetId, getLoginUserId());
+ return success(true);
+ }
+
+ // ==================== 拼接 VO ====================
+
+ private List buildAccountUserRespVOList(List accountUsers) {
+ Map userMap = adminUserApi.getUserMap(
+ convertSet(accountUsers, FmsAccountUserDO::getUserId));
+ Map deptMap = deptApi.getDeptMap(
+ convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
+ return convertList(accountUsers, accountUser -> {
+ FmsAccountUserRespVO respVO = BeanUtils.toBean(accountUser, FmsAccountUserRespVO.class);
+ findAndThen(userMap, accountUser.getUserId(), user -> {
+ respVO.setNickname(user.getNickname()).setMobile(user.getMobile())
+ .setEmail(user.getEmail()).setStatus(user.getStatus());
+ findAndThen(deptMap, user.getDeptId(), dept -> respVO.setDeptName(dept.getName()));
+ });
+ return respVO;
+ });
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsAuxiliaryItemController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsAuxiliaryItemController.java
new file mode 100644
index 0000000..9abc515
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsAuxiliaryItemController.java
@@ -0,0 +1,228 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliaryitem.FmsAuxiliaryItemImportExcelVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliaryitem.FmsAuxiliaryItemImportRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliaryitem.FmsAuxiliaryItemPageReqVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliaryitem.FmsAuxiliaryItemRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliaryitem.FmsAuxiliaryItemSaveReqVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliaryitem.FmsAuxiliaryItemStatusReqVO;
+import cn.iocoder.yudao.module.fms.dal.dataobject.config.FmsAuxiliaryItemDO;
+import cn.iocoder.yudao.module.fms.dal.dataobject.config.FmsAuxiliaryTypeDO;
+import cn.iocoder.yudao.module.fms.enums.config.FmsAuxiliaryTypeEnum;
+import cn.iocoder.yudao.module.fms.service.config.FmsAuxiliaryItemService;
+import cn.iocoder.yudao.module.fms.service.config.FmsAuxiliaryTypeService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - FMS 辅助核算项目")
+@RestController
+@RequestMapping("/fms/config/auxiliary-item")
+@Validated
+public class FmsAuxiliaryItemController {
+
+ @Resource
+ private FmsAuxiliaryItemService auxiliaryItemService;
+ @Resource
+ private FmsAuxiliaryTypeService auxiliaryTypeService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建辅助核算项目")
+ @PreAuthorize("@ss.hasPermission('fms:config:auxiliary:create')")
+ public CommonResult createAuxiliaryItem(@Valid @RequestBody FmsAuxiliaryItemSaveReqVO createReqVO) {
+ return success(auxiliaryItemService.createAuxiliaryItem(createReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新辅助核算项目")
+ @PreAuthorize("@ss.hasPermission('fms:config:auxiliary:update')")
+ public CommonResult updateAuxiliaryItem(
+ @Valid @RequestBody FmsAuxiliaryItemSaveReqVO updateReqVO) {
+ auxiliaryItemService.updateAuxiliaryItem(updateReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete-list")
+ @Operation(summary = "删除辅助核算项目")
+ @Parameters({
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024"),
+ @Parameter(name = "ids", description = "辅助核算项目编号数组", required = true, example = "[1024]")
+ })
+ @PreAuthorize("@ss.hasPermission('fms:config:auxiliary:delete')")
+ public CommonResult deleteAuxiliaryItemList(
+ @RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("ids") @NotEmpty List ids) {
+ auxiliaryItemService.deleteAuxiliaryItemList(accountSetId, ids, getLoginUserId());
+ return success(true);
+ }
+
+ @PutMapping("/update-status")
+ @Operation(summary = "更新辅助核算项目状态")
+ @PreAuthorize("@ss.hasPermission('fms:config:auxiliary:update')")
+ public CommonResult updateAuxiliaryItemStatus(
+ @Valid @RequestBody FmsAuxiliaryItemStatusReqVO reqVO) {
+ auxiliaryItemService.updateAuxiliaryItemStatus(
+ reqVO.getAccountSetId(), reqVO.getId(), reqVO.getStatus(), getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得辅助核算项目")
+ @Parameters({
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024"),
+ @Parameter(name = "id", description = "辅助核算项目编号", required = true, example = "1024")
+ })
+ @PreAuthorize("@ss.hasPermission('fms:config:auxiliary:query')")
+ public CommonResult getAuxiliaryItem(@RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("id") @NotNull Long id) {
+ FmsAuxiliaryItemDO item = auxiliaryItemService.getAuxiliaryItem(accountSetId, id, getLoginUserId());
+ return success(BeanUtils.toBean(item, FmsAuxiliaryItemRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得辅助核算项目分页")
+ @PreAuthorize("@ss.hasAnyPermissions('fms:config:auxiliary:query', 'fms:voucher:create', "
+ + "'fms:voucher:update')")
+ public CommonResult> getAuxiliaryItemPage(
+ @Valid FmsAuxiliaryItemPageReqVO pageReqVO) {
+ PageResult pageResult = auxiliaryItemService
+ .getAuxiliaryItemPage(pageReqVO, getLoginUserId());
+ return success(BeanUtils.toBean(pageResult, FmsAuxiliaryItemRespVO.class));
+ }
+
+ @GetMapping("/simple-list")
+ @Operation(summary = "获得辅助核算项目精简列表", description = "只包含启用的项目,主要用于前端下拉选项")
+ @Parameters({
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024"),
+ @Parameter(name = "auxiliaryTypeId", description = "辅助核算类别编号", required = true,
+ example = "1024")
+ })
+ public CommonResult> getAuxiliaryItemSimpleList(
+ @RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("auxiliaryTypeId") @NotNull Long auxiliaryTypeId) {
+ List list = auxiliaryItemService
+ .getAuxiliaryItemListByAccountSetIdAndAuxiliaryTypeIdAndStatus(
+ accountSetId, auxiliaryTypeId, CommonStatusEnum.ENABLE.getStatus(), getLoginUserId());
+ return success(convertList(list, item -> new FmsAuxiliaryItemRespVO()
+ .setId(item.getId()).setCode(item.getCode()).setName(item.getName())));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "导出辅助核算项目")
+ @PreAuthorize("@ss.hasPermission('fms:config:auxiliary:export')")
+ @ApiAccessLog(operateType = OperateTypeEnum.EXPORT)
+ public void exportAuxiliaryItemExcel(
+ @Valid FmsAuxiliaryItemPageReqVO pageReqVO, HttpServletResponse response) throws IOException {
+ pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ List list = auxiliaryItemService
+ .getAuxiliaryItemPage(pageReqVO, getLoginUserId()).getList();
+ FmsAuxiliaryTypeDO auxiliaryType = auxiliaryTypeService.getAuxiliaryType(
+ pageReqVO.getAccountSetId(), pageReqVO.getAuxiliaryTypeId(), getLoginUserId());
+ ExcelUtils.write(response, auxiliaryType.getName() + ".xlsx", "辅助核算项目",
+ buildAuxiliaryItemExcelHead(auxiliaryType.getType()), buildAuxiliaryItemExcelData(list,
+ auxiliaryType.getType()));
+ }
+
+ @GetMapping("/get-import-template")
+ @Operation(summary = "获得辅助核算项目导入模板")
+ @Parameter(name = "type", description = "辅助核算类别类型", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('fms:config:auxiliary:import')")
+ @ApiAccessLog(operateType = OperateTypeEnum.EXPORT)
+ public void getAuxiliaryItemImportTemplate(
+ @RequestParam("type") @InEnum(FmsAuxiliaryTypeEnum.class) Integer type,
+ HttpServletResponse response) throws IOException {
+ FmsAuxiliaryItemImportExcelVO example = new FmsAuxiliaryItemImportExcelVO()
+ .setCode("001").setName("示例项目").setRemark("示例备注");
+ if (FmsAuxiliaryTypeEnum.INVENTORY.getType().equals(type)) {
+ example.setSpecification("标准版").setUnit("个");
+ }
+ ExcelUtils.write(response, "辅助核算项目导入模板.xlsx", "辅助核算项目",
+ FmsAuxiliaryItemImportExcelVO.class, Collections.singletonList(example));
+ }
+
+ @PostMapping("/import")
+ @Operation(summary = "导入辅助核算项目")
+ @Parameters({
+ @Parameter(name = "accountSetId", description = "账套编号", required = true),
+ @Parameter(name = "auxiliaryTypeId", description = "辅助核算类别编号", required = true),
+ @Parameter(name = "file", description = "Excel 文件", required = true)
+ })
+ @PreAuthorize("@ss.hasPermission('fms:config:auxiliary:import')")
+ @ApiAccessLog(operateType = OperateTypeEnum.IMPORT)
+ public CommonResult importAuxiliaryItem(
+ @RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("auxiliaryTypeId") @NotNull Long auxiliaryTypeId,
+ @RequestParam("file") MultipartFile file) throws Exception {
+ List importItems = ExcelUtils.read(
+ file, FmsAuxiliaryItemImportExcelVO.class);
+ return success(auxiliaryItemService.importAuxiliaryItemList(
+ accountSetId, auxiliaryTypeId, importItems, getLoginUserId()));
+ }
+
+ /** 构建辅助核算项目导出的动态表头 */
+ private List> buildAuxiliaryItemExcelHead(Integer type) {
+ if (FmsAuxiliaryTypeEnum.CUSTOMER.getType().equals(type)
+ || FmsAuxiliaryTypeEnum.SUPPLIER.getType().equals(type)) {
+ return Arrays.asList(Collections.singletonList("编码"), Collections.singletonList("名称"),
+ Collections.singletonList("备注"));
+ }
+ if (FmsAuxiliaryTypeEnum.INVENTORY.getType().equals(type)) {
+ return Arrays.asList(Collections.singletonList("编码"), Collections.singletonList("名称"),
+ Collections.singletonList("规格"), Collections.singletonList("单位"));
+ }
+ return Arrays.asList(Collections.singletonList("编码"), Collections.singletonList("名称"));
+ }
+
+ /** 构建辅助核算项目导出的动态数据 */
+ private List> buildAuxiliaryItemExcelData(List items, Integer type) {
+ List> rows = new ArrayList<>(items.size());
+ for (FmsAuxiliaryItemDO item : items) {
+ if (FmsAuxiliaryTypeEnum.CUSTOMER.getType().equals(type)
+ || FmsAuxiliaryTypeEnum.SUPPLIER.getType().equals(type)) {
+ rows.add(Arrays.asList(item.getCode(), item.getName(), item.getRemark()));
+ } else if (FmsAuxiliaryTypeEnum.INVENTORY.getType().equals(type)) {
+ rows.add(Arrays.asList(item.getCode(), item.getName(), item.getSpecification(), item.getUnit()));
+ } else {
+ rows.add(Arrays.asList(item.getCode(), item.getName()));
+ }
+ }
+ return rows;
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsAuxiliaryTypeController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsAuxiliaryTypeController.java
new file mode 100644
index 0000000..b44f548
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsAuxiliaryTypeController.java
@@ -0,0 +1,92 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliarytype.FmsAuxiliaryTypeRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliarytype.FmsAuxiliaryTypeSaveReqVO;
+import cn.iocoder.yudao.module.fms.dal.dataobject.config.FmsAuxiliaryTypeDO;
+import cn.iocoder.yudao.module.fms.service.config.FmsAuxiliaryTypeService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - FMS 辅助核算类别")
+@RestController
+@RequestMapping("/fms/config/auxiliary-type")
+@Validated
+public class FmsAuxiliaryTypeController {
+
+ @Resource
+ private FmsAuxiliaryTypeService auxiliaryTypeService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建辅助核算类别")
+ @PreAuthorize("@ss.hasPermission('fms:config:auxiliary:create')")
+ public CommonResult createAuxiliaryType(@Valid @RequestBody FmsAuxiliaryTypeSaveReqVO createReqVO) {
+ return success(auxiliaryTypeService.createAuxiliaryType(createReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新辅助核算类别")
+ @PreAuthorize("@ss.hasPermission('fms:config:auxiliary:update')")
+ public CommonResult updateAuxiliaryType(@Valid @RequestBody FmsAuxiliaryTypeSaveReqVO updateReqVO) {
+ auxiliaryTypeService.updateAuxiliaryType(updateReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除辅助核算类别")
+ @Parameters({
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024"),
+ @Parameter(name = "id", description = "辅助核算类别编号", required = true, example = "1024")
+ })
+ @PreAuthorize("@ss.hasPermission('fms:config:auxiliary:delete')")
+ public CommonResult deleteAuxiliaryType(
+ @RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("id") @NotNull Long id) {
+ auxiliaryTypeService.deleteAuxiliaryType(accountSetId, id, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得辅助核算类别列表")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('fms:config:auxiliary:query')")
+ public CommonResult> getAuxiliaryTypeList(
+ @RequestParam("accountSetId") @NotNull Long accountSetId) {
+ return success(BeanUtils.toBean(auxiliaryTypeService.getAuxiliaryTypeList(
+ accountSetId, getLoginUserId()), FmsAuxiliaryTypeRespVO.class));
+ }
+
+ @GetMapping("/simple-list")
+ @Operation(summary = "获得辅助核算类别精简列表", description = "主要用于前端的下拉选项")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ public CommonResult> getAuxiliaryTypeSimpleList(
+ @RequestParam("accountSetId") @NotNull Long accountSetId) {
+ List list = auxiliaryTypeService.getAuxiliaryTypeList(
+ accountSetId, getLoginUserId());
+ return success(convertList(list, auxiliaryType -> new FmsAuxiliaryTypeRespVO()
+ .setId(auxiliaryType.getId()).setName(auxiliaryType.getName()).setType(auxiliaryType.getType())));
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsCurrencyController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsCurrencyController.java
new file mode 100644
index 0000000..0f583b1
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsCurrencyController.java
@@ -0,0 +1,92 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.currency.FmsCurrencyRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.currency.FmsCurrencySaveReqVO;
+import cn.iocoder.yudao.module.fms.dal.dataobject.config.FmsCurrencyDO;
+import cn.iocoder.yudao.module.fms.service.config.FmsCurrencyService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - FMS 币别")
+@RestController
+@RequestMapping("/fms/config/currency")
+@Validated
+public class FmsCurrencyController {
+
+ @Resource
+ private FmsCurrencyService currencyService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建币别")
+ @PreAuthorize("@ss.hasPermission('fms:config:currency:create')")
+ public CommonResult createCurrency(@Valid @RequestBody FmsCurrencySaveReqVO createReqVO) {
+ return success(currencyService.createCurrency(createReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新币别")
+ @PreAuthorize("@ss.hasPermission('fms:config:currency:update')")
+ public CommonResult updateCurrency(@Valid @RequestBody FmsCurrencySaveReqVO updateReqVO) {
+ currencyService.updateCurrency(updateReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除币别")
+ @Parameters({
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024"),
+ @Parameter(name = "id", description = "币别编号", required = true, example = "1024")
+ })
+ @PreAuthorize("@ss.hasPermission('fms:config:currency:delete')")
+ public CommonResult deleteCurrency(
+ @RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("id") @NotNull Long id) {
+ currencyService.deleteCurrency(accountSetId, id, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得币别列表")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('fms:config:currency:query')")
+ public CommonResult> getCurrencyList(
+ @RequestParam("accountSetId") @NotNull Long accountSetId) {
+ List list = currencyService.getCurrencyList(accountSetId, getLoginUserId());
+ return success(BeanUtils.toBean(list, FmsCurrencyRespVO.class));
+ }
+
+ @GetMapping("/simple-list")
+ @Operation(summary = "获得币别精简列表", description = "主要用于前端的下拉选项")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ public CommonResult> getCurrencySimpleList(
+ @RequestParam("accountSetId") @NotNull Long accountSetId) {
+ List list = currencyService.getCurrencyList(accountSetId, getLoginUserId());
+ return success(convertList(list, currency -> new FmsCurrencyRespVO()
+ .setId(currency.getId()).setCode(currency.getCode()).setName(currency.getName())
+ .setExchangeRate(currency.getExchangeRate()).setStandard(currency.getStandard())));
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsDigestController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsDigestController.java
new file mode 100644
index 0000000..3eafb82
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsDigestController.java
@@ -0,0 +1,91 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.digest.FmsDigestRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.digest.FmsDigestSaveReqVO;
+import cn.iocoder.yudao.module.fms.dal.dataobject.config.FmsDigestDO;
+import cn.iocoder.yudao.module.fms.service.config.FmsDigestService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+/**
+ * FMS 常用摘要 Controller
+ *
+ * @author 芋道源码
+ */
+@Tag(name = "管理后台 - FMS 常用摘要")
+@RestController
+@RequestMapping("/fms/config/digest")
+@Validated
+public class FmsDigestController {
+
+ @Resource
+ private FmsDigestService digestService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建常用摘要")
+ @PreAuthorize("@ss.hasPermission('fms:config:digest:create')")
+ public CommonResult createDigest(@Valid @RequestBody FmsDigestSaveReqVO createReqVO) {
+ return success(digestService.createDigest(createReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "修改常用摘要")
+ @PreAuthorize("@ss.hasPermission('fms:config:digest:update')")
+ public CommonResult updateDigest(@Valid @RequestBody FmsDigestSaveReqVO updateReqVO) {
+ digestService.updateDigest(updateReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除常用摘要")
+ @Parameter(name = "id", description = "常用摘要编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('fms:config:digest:delete')")
+ public CommonResult deleteDigest(@RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("id") @NotNull Long id) {
+ digestService.deleteDigest(accountSetId, id, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得常用摘要管理列表")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('fms:config:digest:query')")
+ public CommonResult> getDigestList(
+ @RequestParam("accountSetId") @NotNull Long accountSetId) {
+ List list = digestService.getDigestList(accountSetId, getLoginUserId());
+ return success(BeanUtils.toBean(list, FmsDigestRespVO.class));
+ }
+
+ @GetMapping("/simple-list")
+ @Operation(summary = "获得常用摘要精简列表")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ public CommonResult> getDigestSimpleList(
+ @RequestParam("accountSetId") @NotNull Long accountSetId) {
+ List list = digestService.getDigestList(accountSetId, getLoginUserId());
+ return success(convertList(list, digest -> new FmsDigestRespVO()
+ .setId(digest.getId()).setAccountSetId(digest.getAccountSetId()).setContent(digest.getContent())));
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsFinanceIndicatorController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsFinanceIndicatorController.java
new file mode 100644
index 0000000..5ea15d5
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsFinanceIndicatorController.java
@@ -0,0 +1,86 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.financeindicator.FmsFinanceIndicatorRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.financeindicator.FmsFinanceIndicatorSaveReqVO;
+import cn.iocoder.yudao.module.fms.dal.dataobject.config.FmsFinanceIndicatorDO;
+import cn.iocoder.yudao.module.fms.service.config.FmsFinanceIndicatorService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - FMS 财务指标")
+@RestController
+@RequestMapping("/fms/config/finance-indicator")
+@Validated
+public class FmsFinanceIndicatorController {
+
+ @Resource
+ private FmsFinanceIndicatorService financeIndicatorService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建首页财务指标")
+ @PreAuthorize("@ss.hasPermission('fms:config:finance-indicator:create')")
+ public CommonResult create(@Valid @RequestBody FmsFinanceIndicatorSaveReqVO reqVO) {
+ return success(financeIndicatorService.createFinanceIndicator(reqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "修改首页财务指标")
+ @PreAuthorize("@ss.hasPermission('fms:config:finance-indicator:update')")
+ public CommonResult update(@Valid @RequestBody FmsFinanceIndicatorSaveReqVO reqVO) {
+ financeIndicatorService.updateFinanceIndicator(reqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除首页财务指标")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ @Parameter(name = "id", description = "指标编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('fms:config:finance-indicator:delete')")
+ public CommonResult delete(@RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("id") @NotNull Long id) {
+ financeIndicatorService.deleteFinanceIndicator(accountSetId, id, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得首页财务指标")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ @Parameter(name = "id", description = "指标编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('fms:config:finance-indicator:query')")
+ public CommonResult get(@RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("id") @NotNull Long id) {
+ FmsFinanceIndicatorDO indicator = financeIndicatorService.getFinanceIndicator(accountSetId, id, getLoginUserId());
+ return success(BeanUtils.toBean(indicator, FmsFinanceIndicatorRespVO.class));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得首页财务指标列表")
+ @PreAuthorize("@ss.hasPermission('fms:config:finance-indicator:query')")
+ public CommonResult> getList(@RequestParam("accountSetId") @NotNull Long accountSetId) {
+ List list = financeIndicatorService.getFinanceIndicatorList(accountSetId,
+ getLoginUserId());
+ return success(BeanUtils.toBean(list, FmsFinanceIndicatorRespVO.class));
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsFinanceParameterController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsFinanceParameterController.java
new file mode 100644
index 0000000..931b949
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsFinanceParameterController.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.financeparameter.FmsFinanceParameterRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.financeparameter.FmsFinanceParameterUpdateReqVO;
+import cn.iocoder.yudao.module.fms.dal.dataobject.config.FmsFinanceParameterDO;
+import cn.iocoder.yudao.module.fms.service.config.FmsFinanceParameterService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - FMS 财务参数")
+@RestController
+@RequestMapping("/fms/config/finance-parameter")
+@Validated
+public class FmsFinanceParameterController {
+
+ @Resource
+ private FmsFinanceParameterService financeParameterService;
+
+ @GetMapping("/get")
+ @Operation(summary = "获得财务参数")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('fms:config:finance-parameter:query')")
+ public CommonResult getFinanceParameter(
+ @RequestParam("accountSetId") Long accountSetId) {
+ FmsFinanceParameterDO financeParameter = financeParameterService.getFinanceParameter(
+ accountSetId, getLoginUserId());
+ return success(BeanUtils.toBean(financeParameter, FmsFinanceParameterRespVO.class));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新财务参数")
+ @PreAuthorize("@ss.hasPermission('fms:config:finance-parameter:update')")
+ public CommonResult updateFinanceParameter(
+ @Valid @RequestBody FmsFinanceParameterUpdateReqVO updateReqVO) {
+ financeParameterService.updateFinanceParameter(updateReqVO, getLoginUserId());
+ return success(true);
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsInitialBalanceController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsInitialBalanceController.java
new file mode 100644
index 0000000..16498bc
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsInitialBalanceController.java
@@ -0,0 +1,136 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
+import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.initialbalance.FmsInitialBalanceRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.initialbalance.FmsInitialBalanceSaveReqVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.initialbalance.FmsTrialBalanceRespVO;
+import cn.iocoder.yudao.module.fms.dal.dataobject.config.FmsAccountSetDO;
+import cn.iocoder.yudao.module.fms.service.config.FmsAccountSetService;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.initialbalance.FmsInitialBalanceExcelVO;
+import cn.iocoder.yudao.module.fms.service.config.FmsInitialBalanceService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static cn.iocoder.yudao.module.fms.enums.ErrorCodeConstants.INITIAL_BALANCE_IMPORT_TEMPLATE_INVALID;
+
+@Tag(name = "管理后台 - FMS 初始余额")
+@RestController
+@RequestMapping("/fms/config/initial-balance")
+@Validated
+public class FmsInitialBalanceController {
+
+ @Resource
+ private FmsInitialBalanceService initialBalanceService;
+ @Resource
+ private FmsAccountSetService accountSetService;
+
+ @GetMapping("/list")
+ @Operation(summary = "获得初始余额列表")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ @Parameter(name = "subjectType", description = "科目类型", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('fms:config:initial-balance:query')")
+ public CommonResult> getInitialBalanceList(
+ @RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("subjectType") @NotNull Integer subjectType) {
+ return success(initialBalanceService.getInitialBalanceList(accountSetId, subjectType, getLoginUserId()));
+ }
+
+ @PutMapping("/save")
+ @Operation(summary = "保存初始余额")
+ @PreAuthorize("@ss.hasPermission('fms:config:initial-balance:update')")
+ public CommonResult saveInitialBalance(@Valid @RequestBody FmsInitialBalanceSaveReqVO saveReqVO) {
+ initialBalanceService.saveInitialBalance(saveReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/trial-balance")
+ @Operation(summary = "获得试算平衡结果")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('fms:config:initial-balance:query')")
+ public CommonResult getTrialBalance(
+ @RequestParam("accountSetId") @NotNull Long accountSetId) {
+ return success(initialBalanceService.getTrialBalance(accountSetId, getLoginUserId()));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "导出初始余额")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('fms:config:initial-balance:export')")
+ @ApiAccessLog(operateType = OperateTypeEnum.EXPORT)
+ public void exportInitialBalance(
+ @RequestParam("accountSetId") @NotNull Long accountSetId,
+ HttpServletResponse response) throws IOException {
+ // 1. 读取初始余额列表,并展开为 Excel 行
+ List rows = FmsInitialBalanceExcelHelper.toRows(
+ initialBalanceService.getInitialBalanceList(accountSetId, null, getLoginUserId()), true);
+ // 2. 输出 Excel
+ ExcelUtils.write(response, "财务初始余额.xlsx", "财务初始余额",
+ FmsInitialBalanceExcelVO.class, rows);
+ }
+
+ @GetMapping("/get-import-template")
+ @Operation(summary = "获得初始余额导入模板")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('fms:config:initial-balance:import')")
+ @ApiAccessLog(operateType = OperateTypeEnum.EXPORT)
+ public void getInitialBalanceImportTemplate(@RequestParam("accountSetId") @NotNull Long accountSetId,
+ HttpServletResponse response)
+ throws IOException {
+ // 1. 读取初始余额列表,并展开为 Excel 行
+ Long userId = getLoginUserId();
+ List initialBalances = initialBalanceService.getInitialBalanceList(accountSetId, null, userId);
+ List rows = FmsInitialBalanceExcelHelper.toRows(initialBalances, false);
+ // 2. 生成导入模板,一月启用时只需要录入期初余额
+ FmsAccountSetDO accountSet = accountSetService.validateAccountSetReadPermission(accountSetId, userId);
+ byte[] content = FmsInitialBalanceExcelHelper.writeTemplate(
+ rows, LocalDateTimeUtils.isJanuary(accountSet.getStartTime()));
+ // 3. 写入响应
+ ServletUtils.writeAttachment(response, "财务初始余额导入模板.xlsx", content);
+ }
+
+ @PostMapping("/import")
+ @Operation(summary = "导入初始余额")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('fms:config:initial-balance:import')")
+ @ApiAccessLog(operateType = OperateTypeEnum.IMPORT)
+ public CommonResult importInitialBalance(
+ @RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("file") MultipartFile file) throws IOException {
+ // 1. 读取 Excel
+ List rows;
+ try {
+ rows = FmsInitialBalanceExcelHelper.read(file);
+ } catch (IllegalArgumentException | IOException ex) {
+ throw exception(INITIAL_BALANCE_IMPORT_TEMPLATE_INVALID);
+ }
+ // 2. 导入数据
+ return success(initialBalanceService.importInitialBalance(accountSetId, rows, getLoginUserId()));
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsInitialBalanceExcelHelper.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsInitialBalanceExcelHelper.java
new file mode 100644
index 0000000..8b0d445
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsInitialBalanceExcelHelper.java
@@ -0,0 +1,343 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.initialbalance.FmsInitialBalanceRespVO;
+import cn.iocoder.yudao.module.fms.enums.common.FmsDebitCreditDirectionEnum;
+import cn.iocoder.yudao.module.fms.enums.config.FmsSubjectTypeEnum;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.initialbalance.FmsInitialBalanceExcelVO;
+import lombok.experimental.UtilityClass;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.FormulaEvaluator;
+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.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.number.NumberUtils.zeroIfNull;
+import static cn.iocoder.yudao.framework.excel.core.util.PoiExcelUtils.createDataStyle;
+import static cn.iocoder.yudao.framework.excel.core.util.PoiExcelUtils.createHeaderStyle;
+import static cn.iocoder.yudao.framework.excel.core.util.PoiExcelUtils.createNumberStyle;
+import static cn.iocoder.yudao.framework.excel.core.util.PoiExcelUtils.getCellText;
+import static cn.iocoder.yudao.framework.excel.core.util.PoiExcelUtils.getDecimal;
+import static cn.iocoder.yudao.framework.excel.core.util.PoiExcelUtils.isEmptyRow;
+import static cn.iocoder.yudao.framework.excel.core.util.PoiExcelUtils.mergeHeader;
+import static cn.iocoder.yudao.framework.excel.core.util.PoiExcelUtils.writeFormula;
+import static cn.iocoder.yudao.framework.excel.core.util.PoiExcelUtils.writeNumber;
+import static cn.iocoder.yudao.framework.excel.core.util.PoiExcelUtils.writeText;
+
+/**
+ * FMS 初始余额 Excel 工具
+ *
+ * @author 芋道源码
+ */
+@UtilityClass
+public final class FmsInitialBalanceExcelHelper {
+
+ private static final String TEMPLATE_SHEET_NAME = "初始余额模板";
+
+ /**
+ * 展开初始余额平铺列表为 Excel 行
+ *
+ * 父级科目仅在 includeParent 时输出行;启用辅助核算且存在明细的科目按明细逐行输出
+ */
+ public static List toRows(List results,
+ boolean includeParent) {
+ Set parentIds = convertSet(results, FmsInitialBalanceRespVO::getParentId);
+ List rows = new ArrayList<>();
+ results.forEach(result -> {
+ if (parentIds.contains(result.getSubjectId())) {
+ if (includeParent) {
+ rows.add(buildRow(result, null));
+ }
+ return;
+ }
+ if (Boolean.FALSE.equals(result.getAuxiliaryAccounting())
+ || CollUtil.isEmpty(result.getAssistBalances())) {
+ rows.add(buildRow(result, null));
+ return;
+ }
+ result.getAssistBalances().forEach(assist -> rows.add(buildRow(result, assist)));
+ });
+ return rows;
+ }
+
+ /**
+ * 构建单个科目或辅助核算明细对应的 Excel 行
+ */
+ private static FmsInitialBalanceExcelVO buildRow(FmsInitialBalanceRespVO result,
+ FmsInitialBalanceRespVO.AssistBalance assist) {
+ FmsInitialBalanceExcelVO row = new FmsInitialBalanceExcelVO()
+ .setSubjectCode(result.getSubjectCode()).setSubjectName(result.getSubjectName())
+ .setDirectionName(FmsDebitCreditDirectionEnum.valueOf(result.getBalanceDirection()).getName())
+ .setSubjectType(result.getType()).setQuantityAccounting(result.getQuantityAccounting())
+ .setAuxiliaryAccounting(result.getAuxiliaryAccounting());
+ if (assist == null) {
+ copyAmounts(result, row);
+ } else {
+ // 辅助核算项目拼接为“类别:名称/类别:名称”,与导入解析的格式对应
+ Map auxiliaryTypeNameMap = convertMap(result.getAuxiliaryConfigs(),
+ FmsInitialBalanceRespVO.AuxiliaryConfig::getAuxiliaryTypeId,
+ FmsInitialBalanceRespVO.AuxiliaryConfig::getName);
+ row.setAuxiliaryItems(String.join("/", convertList(assist.getAuxiliaries(),
+ item -> auxiliaryTypeNameMap.get(item.getTypeId()) + ":" + item.getName())));
+ copyAmounts(assist, row);
+ }
+ return row;
+ }
+
+ private static void copyAmounts(FmsInitialBalanceRespVO source, FmsInitialBalanceExcelVO target) {
+ target.setOpeningAmount(zeroIfNull(source.getOpeningAmount()))
+ .setOpeningQuantity(zeroIfNull(source.getOpeningQuantity()))
+ .setYearDebitAmount(zeroIfNull(source.getYearDebitAmount()))
+ .setYearDebitQuantity(zeroIfNull(source.getYearDebitQuantity()))
+ .setYearCreditAmount(zeroIfNull(source.getYearCreditAmount()))
+ .setYearCreditQuantity(zeroIfNull(source.getYearCreditQuantity()))
+ .setYearOpeningAmount(zeroIfNull(source.getYearOpeningAmount()))
+ .setYearOpeningQuantity(zeroIfNull(source.getYearOpeningQuantity()))
+ .setProfitLossAmount(zeroIfNull(source.getProfitLossAmount()))
+ .setProfitLossQuantity(zeroIfNull(source.getProfitLossQuantity()));
+ }
+
+ private static void copyAmounts(FmsInitialBalanceRespVO.AssistBalance source,
+ FmsInitialBalanceExcelVO target) {
+ target.setOpeningAmount(zeroIfNull(source.getOpeningAmount()))
+ .setOpeningQuantity(zeroIfNull(source.getOpeningQuantity()))
+ .setYearDebitAmount(zeroIfNull(source.getYearDebitAmount()))
+ .setYearDebitQuantity(zeroIfNull(source.getYearDebitQuantity()))
+ .setYearCreditAmount(zeroIfNull(source.getYearCreditAmount()))
+ .setYearCreditQuantity(zeroIfNull(source.getYearCreditQuantity()))
+ .setYearOpeningAmount(zeroIfNull(source.getYearOpeningAmount()))
+ .setYearOpeningQuantity(zeroIfNull(source.getYearOpeningQuantity()))
+ .setProfitLossAmount(zeroIfNull(source.getProfitLossAmount()))
+ .setProfitLossQuantity(zeroIfNull(source.getProfitLossQuantity()));
+ }
+
+ /**
+ * 生成导入模板
+ *
+ * 模板锁定系统生成列,年初余额列写入公式,并通过 protectSheet 防止误改
+ */
+ public static byte[] writeTemplate(List rows,
+ boolean january) throws IOException {
+ try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream output = new ByteArrayOutputStream()) {
+ // 1. 写入表头,一月启用时只保留期初余额
+ Sheet sheet = workbook.createSheet(TEMPLATE_SHEET_NAME);
+ writeHeaders(workbook, sheet, 0, january);
+ // 2. 写入科目数据,系统生成列锁定,可填写列解锁
+ CellStyle lockedStyle = createDataStyle(workbook, true, IndexedColors.GREY_25_PERCENT);
+ CellStyle editableStyle = createDataStyle(workbook, false, IndexedColors.WHITE);
+ CellStyle numberLockedStyle = createNumberStyle(workbook, lockedStyle, "#,##0.0000");
+ CellStyle amountLockedStyle = createNumberStyle(workbook, lockedStyle, "#,##0.00");
+ CellStyle numberEditableStyle = createNumberStyle(workbook, editableStyle, "#,##0.0000");
+ CellStyle amountEditableStyle = createNumberStyle(workbook, editableStyle, "#,##0.00");
+ for (int index = 0; index < rows.size(); index++) {
+ writeDataRow(sheet.createRow(index + 2), rows.get(index), january, true,
+ lockedStyle, editableStyle, numberLockedStyle, amountLockedStyle,
+ numberEditableStyle, amountEditableStyle);
+ }
+ // 3. 保护工作表并补充填写说明
+ sheet.protectSheet("yudao-fms");
+ sheet.createFreezePane(4, 2);
+ writeInstructions(workbook);
+ workbook.write(output);
+ return output.toByteArray();
+ }
+ }
+
+ /**
+ * 读取导入文件
+ *
+ * 按模板表头校验文件合法性,年初余额等公式单元格取公式计算值;不限制导入行数
+ */
+ public static List read(MultipartFile file) throws IOException {
+ try (Workbook workbook = WorkbookFactory.create(file.getInputStream())) {
+ // 1. 校验模板 sheet 和表头
+ Sheet sheet = workbook.getSheet(TEMPLATE_SHEET_NAME);
+ if (sheet == null || !hasValidHeaders(sheet)) {
+ throw new IllegalArgumentException("初始余额模板表头不正确");
+ }
+ // 2. 逐行解析,跳过空行
+ FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
+ boolean january = sheet.getRow(1).getLastCellNum() <= 6;
+ List rows = new ArrayList<>();
+ for (int rowIndex = 2; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
+ Row row = sheet.getRow(rowIndex);
+ if (isEmptyRow(row, evaluator, Arrays.asList(0, 1, 3, 4, 5))) {
+ continue;
+ }
+ rows.add(parseRow(row, january, evaluator));
+ }
+ return rows;
+ }
+ }
+
+ /**
+ * 写入双行表头,数量和金额成组;一月启用时只保留期初余额组
+ */
+ private static void writeHeaders(Workbook workbook, Sheet sheet, int rowIndex, boolean january) {
+ CellStyle style = createHeaderStyle(workbook, IndexedColors.GREY_25_PERCENT);
+ int lastColumn = january ? 5 : 13;
+ Row firstRow = sheet.createRow(rowIndex);
+ Row secondRow = sheet.createRow(rowIndex + 1);
+ mergeHeader(sheet, firstRow, secondRow, 0, 0, "科目编码", style);
+ mergeHeader(sheet, firstRow, secondRow, 1, 1, "科目名称", style);
+ mergeHeader(sheet, firstRow, secondRow, 2, 2, "方向", style);
+ mergeHeader(sheet, firstRow, secondRow, 3, 3, "辅助核算项目", style);
+ writeGroupHeader(sheet, firstRow, secondRow, 4, "期初余额", style);
+ if (!january) {
+ writeGroupHeader(sheet, firstRow, secondRow, 6, "本年累计借方", style);
+ writeGroupHeader(sheet, firstRow, secondRow, 8, "本年累计贷方", style);
+ writeGroupHeader(sheet, firstRow, secondRow, 10, "年初余额", style);
+ writeGroupHeader(sheet, firstRow, secondRow, 12, "实际损益发生额", style);
+ }
+ for (int column = 0; column <= lastColumn; column++) {
+ sheet.setColumnWidth(column, column == 1 || column == 3 ? 7_000 : 4_000);
+ }
+ firstRow.setHeightInPoints(28);
+ secondRow.setHeightInPoints(24);
+ }
+
+ /**
+ * 写入“数量/金额”成组表头,第一行合并组名,第二行拆出数量和金额
+ */
+ private static void writeGroupHeader(Sheet sheet, Row firstRow, Row secondRow,
+ int beginColumn, String title, CellStyle style) {
+ sheet.addMergedRegion(new CellRangeAddress(firstRow.getRowNum(), firstRow.getRowNum(), beginColumn, beginColumn + 1));
+ Cell groupCell = firstRow.createCell(beginColumn);
+ groupCell.setCellValue(title);
+ groupCell.setCellStyle(style);
+ firstRow.createCell(beginColumn + 1).setCellStyle(style);
+ Cell quantityCell = secondRow.createCell(beginColumn);
+ quantityCell.setCellValue("数量");
+ quantityCell.setCellStyle(style);
+ Cell amountCell = secondRow.createCell(beginColumn + 1);
+ amountCell.setCellValue("金额");
+ amountCell.setCellStyle(style);
+ }
+
+ /**
+ * 写入一行数据
+ *
+ * 模板模式下按科目配置解锁可填写单元格(数量列仅数量核算科目可填,损益列仅损益类科目可填),
+ * 年初余额列写入“期初 ± 累计借贷”公式
+ */
+ private static void writeDataRow(Row excelRow, FmsInitialBalanceExcelVO data,
+ boolean january, boolean template, CellStyle lockedStyle, CellStyle editableStyle,
+ CellStyle numberLockedStyle, CellStyle amountLockedStyle,
+ CellStyle numberEditableStyle, CellStyle amountEditableStyle) {
+ writeText(excelRow, 0, data.getSubjectCode(), lockedStyle);
+ writeText(excelRow, 1, data.getSubjectName(), lockedStyle);
+ writeText(excelRow, 2, data.getDirectionName(), lockedStyle);
+ writeText(excelRow, 3, data.getAuxiliaryItems(),
+ template && Boolean.TRUE.equals(data.getAuxiliaryAccounting()) ? editableStyle : lockedStyle);
+ writeNumber(excelRow, 4, data.getOpeningQuantity(),
+ template && Boolean.TRUE.equals(data.getQuantityAccounting())
+ ? numberEditableStyle : numberLockedStyle);
+ writeNumber(excelRow, 5, data.getOpeningAmount(), template ? amountEditableStyle : amountLockedStyle);
+ if (january) {
+ return;
+ }
+ writeNumber(excelRow, 6, data.getYearDebitQuantity(),
+ template && Boolean.TRUE.equals(data.getQuantityAccounting())
+ ? numberEditableStyle : numberLockedStyle);
+ writeNumber(excelRow, 7, data.getYearDebitAmount(), template ? amountEditableStyle : amountLockedStyle);
+ writeNumber(excelRow, 8, data.getYearCreditQuantity(),
+ template && Boolean.TRUE.equals(data.getQuantityAccounting())
+ ? numberEditableStyle : numberLockedStyle);
+ writeNumber(excelRow, 9, data.getYearCreditAmount(), template ? amountEditableStyle : amountLockedStyle);
+ if (template) {
+ String quantityFormula = "借".equals(data.getDirectionName())
+ ? "E" + (excelRow.getRowNum() + 1) + "-G" + (excelRow.getRowNum() + 1)
+ + "+I" + (excelRow.getRowNum() + 1)
+ : "E" + (excelRow.getRowNum() + 1) + "+G" + (excelRow.getRowNum() + 1)
+ + "-I" + (excelRow.getRowNum() + 1);
+ String amountFormula = "借".equals(data.getDirectionName())
+ ? "F" + (excelRow.getRowNum() + 1) + "-H" + (excelRow.getRowNum() + 1)
+ + "+J" + (excelRow.getRowNum() + 1)
+ : "F" + (excelRow.getRowNum() + 1) + "+H" + (excelRow.getRowNum() + 1)
+ + "-J" + (excelRow.getRowNum() + 1);
+ writeFormula(excelRow, 10, quantityFormula, numberLockedStyle);
+ writeFormula(excelRow, 11, amountFormula, amountLockedStyle);
+ } else {
+ writeNumber(excelRow, 10, data.getYearOpeningQuantity(), numberLockedStyle);
+ writeNumber(excelRow, 11, data.getYearOpeningAmount(), amountLockedStyle);
+ }
+ boolean profitLoss = FmsSubjectTypeEnum.PROFIT_LOSS.getType().equals(data.getSubjectType());
+ writeNumber(excelRow, 12, profitLoss ? data.getProfitLossQuantity() : null,
+ template && profitLoss && Boolean.TRUE.equals(data.getQuantityAccounting())
+ ? numberEditableStyle : numberLockedStyle);
+ writeNumber(excelRow, 13, profitLoss ? data.getProfitLossAmount() : null,
+ template && profitLoss ? amountEditableStyle : amountLockedStyle);
+ }
+
+ /**
+ * 解析一行数据,公式单元格通过 evaluator 取计算值
+ */
+ private static FmsInitialBalanceExcelVO parseRow(Row row, boolean january, FormulaEvaluator evaluator) {
+ FmsInitialBalanceExcelVO result = new FmsInitialBalanceExcelVO()
+ .setRowNumber(row.getRowNum() + 1)
+ .setSubjectCode(getCellText(row, 0, evaluator))
+ .setSubjectName(getCellText(row, 1, evaluator))
+ .setDirectionName(getCellText(row, 2, evaluator))
+ .setAuxiliaryItems(getCellText(row, 3, evaluator))
+ .setOpeningQuantity(getDecimal(row, 4, evaluator))
+ .setOpeningAmount(getDecimal(row, 5, evaluator));
+ if (!january) {
+ result.setYearDebitQuantity(getDecimal(row, 6, evaluator))
+ .setYearDebitAmount(getDecimal(row, 7, evaluator))
+ .setYearCreditQuantity(getDecimal(row, 8, evaluator))
+ .setYearCreditAmount(getDecimal(row, 9, evaluator))
+ .setYearOpeningQuantity(getDecimal(row, 10, evaluator))
+ .setYearOpeningAmount(getDecimal(row, 11, evaluator))
+ .setProfitLossQuantity(getDecimal(row, 12, evaluator))
+ .setProfitLossAmount(getDecimal(row, 13, evaluator));
+ }
+ return result;
+ }
+
+ private static boolean hasValidHeaders(Sheet sheet) {
+ Row firstRow = sheet.getRow(0);
+ Row secondRow = sheet.getRow(1);
+ return firstRow != null && secondRow != null
+ && "科目编码".equals(getCellText(firstRow, 0, null))
+ && "科目名称".equals(getCellText(firstRow, 1, null))
+ && "方向".equals(getCellText(firstRow, 2, null))
+ && "辅助核算项目".equals(getCellText(firstRow, 3, null))
+ && "期初余额".equals(getCellText(firstRow, 4, null))
+ && "数量".equals(getCellText(secondRow, 4, null))
+ && "金额".equals(getCellText(secondRow, 5, null));
+ }
+
+ private static void writeInstructions(Workbook workbook) {
+ Sheet sheet = workbook.createSheet("填写说明");
+ String[] instructions = {
+ "1. 灰色单元格由系统生成,请勿修改",
+ "2. 仅末级科目可以导入初始余额",
+ "3. 辅助核算项目使用“类别:名称/类别:名称”格式,例如“客户:北京客户/部门:销售部”",
+ "4. 数量核算科目可填写数量,金额和数量不能小于 0",
+ "5. 年初余额由期初余额和本年累计发生额自动计算"
+ };
+ for (int index = 0; index < instructions.length; index++) {
+ sheet.createRow(index).createCell(0).setCellValue(instructions[index]);
+ }
+ sheet.setColumnWidth(0, 20_000);
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsSubjectController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsSubjectController.java
new file mode 100644
index 0000000..516b686
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsSubjectController.java
@@ -0,0 +1,195 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject.FmsSubjectDeleteReqVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject.FmsSubjectImportExcelVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject.FmsSubjectImportRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject.FmsSubjectListReqVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject.FmsSubjectRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject.FmsSubjectSaveReqVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject.FmsSubjectStatusReqVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject.FmsSubjectUsageRespVO;
+import cn.iocoder.yudao.module.fms.dal.dataobject.config.FmsAuxiliaryTypeDO;
+import cn.iocoder.yudao.module.fms.dal.dataobject.config.FmsSubjectDO;
+import cn.iocoder.yudao.module.fms.service.config.FmsAuxiliaryTypeService;
+import cn.iocoder.yudao.module.fms.service.config.FmsSubjectService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - FMS 会计科目")
+@RestController
+@RequestMapping("/fms/config/subject")
+@Validated
+public class FmsSubjectController {
+
+ @Resource
+ private FmsSubjectService subjectService;
+ @Resource
+ private FmsAuxiliaryTypeService auxiliaryTypeService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建科目")
+ @PreAuthorize("@ss.hasPermission('fms:config:subject:create')")
+ public CommonResult createSubject(@Valid @RequestBody FmsSubjectSaveReqVO createReqVO) {
+ return success(subjectService.createSubject(createReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新科目")
+ @PreAuthorize("@ss.hasPermission('fms:config:subject:update')")
+ public CommonResult updateSubject(@Valid @RequestBody FmsSubjectSaveReqVO updateReqVO) {
+ subjectService.updateSubject(updateReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete-list")
+ @Operation(summary = "删除科目")
+ @PreAuthorize("@ss.hasPermission('fms:config:subject:delete')")
+ public CommonResult deleteSubjectList(@Valid @RequestBody FmsSubjectDeleteReqVO deleteReqVO) {
+ subjectService.deleteSubjectList(deleteReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @PutMapping("/update-status")
+ @Operation(summary = "更新科目状态")
+ @PreAuthorize("@ss.hasPermission('fms:config:subject:update')")
+ public CommonResult updateSubjectStatus(@Valid @RequestBody FmsSubjectStatusReqVO statusReqVO) {
+ subjectService.updateSubjectStatus(statusReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得科目")
+ @Parameters({
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024"),
+ @Parameter(name = "id", description = "科目编号", required = true, example = "1024")
+ })
+ @PreAuthorize("@ss.hasPermission('fms:config:subject:query')")
+ public CommonResult getSubject(@RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("id") @NotNull Long id) {
+ FmsSubjectDO subject = subjectService.getSubject(accountSetId, id, getLoginUserId());
+ return success(buildSubjectRespVO(subject));
+ }
+
+ @GetMapping("/get-usage")
+ @Operation(summary = "获得科目使用情况")
+ @Parameters({
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024"),
+ @Parameter(name = "id", description = "科目编号", required = true, example = "1024")
+ })
+ @PreAuthorize("@ss.hasPermission('fms:config:subject:query')")
+ public CommonResult getSubjectUsage(
+ @RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("id") @NotNull Long id) {
+ return success(subjectService.getSubjectUsage(accountSetId, id, getLoginUserId()));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得科目列表")
+ @PreAuthorize("@ss.hasPermission('fms:config:subject:query')")
+ public CommonResult> getSubjectList(@Valid FmsSubjectListReqVO listReqVO) {
+ List subjects = subjectService.getSubjectList(listReqVO, getLoginUserId());
+ return success(buildSubjectRespVOList(subjects));
+ }
+
+ @GetMapping("/simple-list")
+ @Operation(summary = "获得科目精简列表")
+ public CommonResult> getSubjectSimpleList(@Valid FmsSubjectListReqVO listReqVO) {
+ List subjects = subjectService.getSubjectList(listReqVO, getLoginUserId());
+ return success(convertList(subjects, subject -> new FmsSubjectRespVO()
+ .setId(subject.getId()).setParentId(subject.getParentId())
+ .setCode(subject.getCode()).setName(subject.getName()).setType(subject.getType())
+ .setBalanceDirection(subject.getBalanceDirection()).setStatus(subject.getStatus())
+ .setLevel(subject.getLevel()).setAuxiliaryTypeIds(subject.getAuxiliaryTypeIds())
+ .setQuantityAccounting(subject.getQuantityAccounting()).setQuantityUnit(subject.getQuantityUnit())));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "导出科目")
+ @PreAuthorize("@ss.hasPermission('fms:config:subject:export')")
+ @ApiAccessLog(operateType = OperateTypeEnum.EXPORT)
+ public void exportSubject(@Valid FmsSubjectListReqVO listReqVO,
+ HttpServletResponse response) throws IOException {
+ List subjects = subjectService.getSubjectList(listReqVO, getLoginUserId());
+ ExcelUtils.write(response, "科目设置.xlsx", "科目设置", FmsSubjectRespVO.class,
+ buildSubjectRespVOList(subjects));
+ }
+
+ @GetMapping("/get-import-template")
+ @Operation(summary = "获得科目导入模板")
+ @PreAuthorize("@ss.hasPermission('fms:config:subject:import')")
+ @ApiAccessLog(operateType = OperateTypeEnum.EXPORT)
+ public void getSubjectImportTemplate(HttpServletResponse response) throws IOException {
+ List list = Arrays.asList(
+ FmsSubjectImportExcelVO.builder().code("1001").name("库存现金")
+ .parentSubjectCode("0").balanceDirection("借").categoryName("流动资产").build(),
+ FmsSubjectImportExcelVO.builder().code("100101").name("人民币现金")
+ .parentSubjectCode("1001").balanceDirection("借").categoryName("流动资产")
+ .auxiliaryNames("部门/职员").build());
+ ExcelUtils.write(response, "科目导入模板.xlsx", "科目列表", FmsSubjectImportExcelVO.class, list);
+ }
+
+ @PostMapping("/import")
+ @Operation(summary = "导入科目")
+ @Parameters({
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024"),
+ @Parameter(name = "file", description = "Excel 文件", required = true)
+ })
+ @PreAuthorize("@ss.hasPermission('fms:config:subject:import')")
+ @ApiAccessLog(operateType = OperateTypeEnum.IMPORT)
+ public CommonResult importSubject(
+ @RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("file") MultipartFile file) throws IOException {
+ List importSubjects = ExcelUtils.read(file, FmsSubjectImportExcelVO.class);
+ return success(subjectService.importSubjectList(accountSetId, importSubjects, getLoginUserId()));
+ }
+
+ // ==================== 拼接 VO ====================
+
+ private List buildSubjectRespVOList(List subjects) {
+ Map auxiliaryTypeMap = auxiliaryTypeService.getAuxiliaryTypeMap(
+ convertSetByFlatMap(subjects, FmsSubjectDO::getAuxiliaryTypeIds, List::stream));
+ return convertList(subjects, subject -> {
+ FmsSubjectRespVO subjectVO = BeanUtils.toBean(subject, FmsSubjectRespVO.class)
+ .setCategoryDictValue(subject.getType() + "-" + subject.getCategory());
+ subjectVO.setAuxiliaryTypeNames(convertList(subject.getAuxiliaryTypeIds(),
+ auxiliaryTypeId -> auxiliaryTypeMap.get(auxiliaryTypeId).getName()));
+ if (Boolean.FALSE.equals(subject.getQuantityAccounting())) {
+ subjectVO.setQuantityUnit("");
+ }
+ return subjectVO;
+ });
+ }
+
+ private FmsSubjectRespVO buildSubjectRespVO(FmsSubjectDO subject) {
+ return subject == null ? null
+ : CollUtil.getFirst(buildSubjectRespVOList(Collections.singletonList(subject)));
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsVoucherTemplateCategoryController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsVoucherTemplateCategoryController.java
new file mode 100644
index 0000000..6201f0d
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsVoucherTemplateCategoryController.java
@@ -0,0 +1,89 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.vouchertemplatecategory.FmsVoucherTemplateCategoryRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.vouchertemplatecategory.FmsVoucherTemplateCategorySaveReqVO;
+import cn.iocoder.yudao.module.fms.dal.dataobject.config.FmsVoucherTemplateCategoryDO;
+import cn.iocoder.yudao.module.fms.service.config.FmsAccountSetService;
+import cn.iocoder.yudao.module.fms.service.config.FmsVoucherTemplateService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - FMS 凭证模板分类")
+@RestController
+@RequestMapping("/fms/config/voucher-template-category")
+@Validated
+public class FmsVoucherTemplateCategoryController {
+
+ @Resource
+ private FmsVoucherTemplateService voucherTemplateService;
+ @Resource
+ private FmsAccountSetService accountSetService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建凭证模板分类")
+ @PreAuthorize("@ss.hasPermission('fms:config:voucher-template-category:create')")
+ public CommonResult createTemplateCategory(
+ @Valid @RequestBody FmsVoucherTemplateCategorySaveReqVO createReqVO) {
+ return success(voucherTemplateService.createTemplateCategory(createReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "修改凭证模板分类")
+ @PreAuthorize("@ss.hasPermission('fms:config:voucher-template-category:update')")
+ public CommonResult updateTemplateCategory(
+ @Valid @RequestBody FmsVoucherTemplateCategorySaveReqVO updateReqVO) {
+ voucherTemplateService.updateTemplateCategory(updateReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除凭证模板分类")
+ @PreAuthorize("@ss.hasPermission('fms:config:voucher-template-category:delete')")
+ public CommonResult deleteTemplateCategory(@RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("id") @NotNull Long id) {
+ voucherTemplateService.deleteTemplateCategory(accountSetId, id, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得凭证模板分类列表")
+ @PreAuthorize("@ss.hasPermission('fms:config:voucher-template-category:query')")
+ public CommonResult> getTemplateCategoryList(
+ @RequestParam("accountSetId") @NotNull Long accountSetId) {
+ accountSetService.validateAccountSetReadPermission(accountSetId, getLoginUserId());
+ List categories = voucherTemplateService.getTemplateCategoryList(accountSetId);
+ return success(BeanUtils.toBean(categories, FmsVoucherTemplateCategoryRespVO.class));
+ }
+
+ @GetMapping("/simple-list")
+ @Operation(summary = "获得凭证模板分类精简列表", description = "主要用于前端的下拉选项")
+ public CommonResult> getTemplateCategorySimpleList(
+ @RequestParam("accountSetId") @NotNull Long accountSetId) {
+ accountSetService.validateAccountSetReadPermission(accountSetId, getLoginUserId());
+ List categories = voucherTemplateService.getTemplateCategoryList(accountSetId);
+ return success(convertList(categories, category -> new FmsVoucherTemplateCategoryRespVO()
+ .setId(category.getId()).setName(category.getName())));
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsVoucherTemplateController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsVoucherTemplateController.java
new file mode 100644
index 0000000..e5a8ce2
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsVoucherTemplateController.java
@@ -0,0 +1,100 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.vouchertemplate.FmsVoucherTemplateRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.vouchertemplate.FmsVoucherTemplateSaveReqVO;
+import cn.iocoder.yudao.module.fms.dal.dataobject.config.FmsVoucherTemplateCategoryDO;
+import cn.iocoder.yudao.module.fms.dal.dataobject.config.FmsVoucherTemplateDO;
+import cn.iocoder.yudao.module.fms.service.config.FmsAccountSetService;
+import cn.iocoder.yudao.module.fms.service.config.FmsVoucherTemplateService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - FMS 凭证模板")
+@RestController
+@RequestMapping("/fms/config/voucher-template")
+@Validated
+public class FmsVoucherTemplateController {
+
+ @Resource
+ private FmsVoucherTemplateService voucherTemplateService;
+ @Resource
+ private FmsAccountSetService accountSetService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建凭证模板")
+ @PreAuthorize("@ss.hasPermission('fms:config:voucher-template:create')")
+ public CommonResult createVoucherTemplate(
+ @Valid @RequestBody FmsVoucherTemplateSaveReqVO createReqVO) {
+ return success(voucherTemplateService.createVoucherTemplate(createReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "修改凭证模板")
+ @PreAuthorize("@ss.hasPermission('fms:config:voucher-template:update')")
+ public CommonResult updateVoucherTemplate(
+ @Valid @RequestBody FmsVoucherTemplateSaveReqVO updateReqVO) {
+ voucherTemplateService.updateVoucherTemplate(updateReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除凭证模板")
+ @PreAuthorize("@ss.hasPermission('fms:config:voucher-template:delete')")
+ public CommonResult deleteVoucherTemplate(@RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("id") @NotNull Long id) {
+ voucherTemplateService.deleteVoucherTemplate(accountSetId, id, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得凭证模板列表")
+ @PreAuthorize("@ss.hasPermission('fms:config:voucher-template:query')")
+ public CommonResult> getVoucherTemplateList(
+ @RequestParam("accountSetId") @NotNull Long accountSetId) {
+ accountSetService.validateAccountSetReadPermission(accountSetId, getLoginUserId());
+ return success(buildVoucherTemplateRespVOList(accountSetId));
+ }
+
+ @GetMapping("/simple-list")
+ @Operation(summary = "获得凭证模板精简列表", description = "主要用于录凭证时套用凭证模板")
+ public CommonResult> getVoucherTemplateSimpleList(
+ @RequestParam("accountSetId") @NotNull Long accountSetId) {
+ accountSetService.validateAccountSetReadPermission(accountSetId, getLoginUserId());
+ return success(buildVoucherTemplateRespVOList(accountSetId));
+ }
+
+ // ==================== 拼接 VO ====================
+
+ private List buildVoucherTemplateRespVOList(Long accountSetId) {
+ Map categoryMap =
+ voucherTemplateService.getTemplateCategoryMap(accountSetId);
+ List templates = voucherTemplateService.getVoucherTemplateList(accountSetId);
+ return BeanUtils.toBean(templates, FmsVoucherTemplateRespVO.class, template -> {
+ findAndThen(categoryMap, template.getCategoryId(),
+ category -> template.setCategoryName(category.getName()));
+ });
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsVoucherWordController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsVoucherWordController.java
new file mode 100644
index 0000000..00e8ce6
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/FmsVoucherWordController.java
@@ -0,0 +1,93 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.voucherword.FmsVoucherWordRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.voucherword.FmsVoucherWordSaveReqVO;
+import cn.iocoder.yudao.module.fms.dal.dataobject.config.FmsVoucherWordDO;
+import cn.iocoder.yudao.module.fms.service.config.FmsVoucherWordService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+/**
+ * FMS 凭证字 Controller
+ *
+ * @author 芋道源码
+ */
+@Tag(name = "管理后台 - FMS 凭证字")
+@RestController
+@RequestMapping("/fms/config/voucher-word")
+@Validated
+public class FmsVoucherWordController {
+
+ @Resource
+ private FmsVoucherWordService voucherWordService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建凭证字")
+ @PreAuthorize("@ss.hasPermission('fms:config:voucher-word:create')")
+ public CommonResult createVoucherWord(@Valid @RequestBody FmsVoucherWordSaveReqVO createReqVO) {
+ return success(voucherWordService.createVoucherWord(createReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "修改凭证字")
+ @PreAuthorize("@ss.hasPermission('fms:config:voucher-word:update')")
+ public CommonResult updateVoucherWord(@Valid @RequestBody FmsVoucherWordSaveReqVO updateReqVO) {
+ voucherWordService.updateVoucherWord(updateReqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除凭证字")
+ @Parameter(name = "id", description = "凭证字编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('fms:config:voucher-word:delete')")
+ public CommonResult deleteVoucherWord(
+ @RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("id") @NotNull Long id) {
+ voucherWordService.deleteVoucherWord(accountSetId, id, getLoginUserId());
+ return success(true);
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得凭证字管理列表")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('fms:config:voucher-word:query')")
+ public CommonResult> getVoucherWordList(
+ @RequestParam("accountSetId") @NotNull Long accountSetId) {
+ List list = voucherWordService.getVoucherWordList(accountSetId, getLoginUserId());
+ return success(BeanUtils.toBean(list, FmsVoucherWordRespVO.class));
+ }
+
+ @GetMapping("/simple-list")
+ @Operation(summary = "获得凭证字精简列表")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ public CommonResult> getVoucherWordSimpleList(
+ @RequestParam("accountSetId") @NotNull Long accountSetId) {
+ return success(convertList(voucherWordService.getVoucherWordList(
+ accountSetId, getLoginUserId()), voucherWord -> new FmsVoucherWordRespVO()
+ .setId(voucherWord.getId()).setName(voucherWord.getName())
+ .setDefaultStatus(voucherWord.getDefaultStatus())));
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/accountset/FmsAccountSetInitializeReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/accountset/FmsAccountSetInitializeReqVO.java
new file mode 100644
index 0000000..ac0996c
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/accountset/FmsAccountSetInitializeReqVO.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.accountset;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.fms.enums.config.FmsAccountingStandardEnum;
+import cn.iocoder.yudao.module.fms.enums.config.FmsCurrencyPresetEnum;
+import cn.iocoder.yudao.module.fms.enums.ledger.FmsLedgerBalanceModeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - FMS 账套初始化 Request VO")
+@Data
+public class FmsAccountSetInitializeReqVO {
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "账套编号不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "本位币编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "RMB")
+ @NotBlank(message = "本位币不能为空")
+ @InEnum(value = FmsCurrencyPresetEnum.class, message = "本位币必须是 {value}")
+ private String currencyCode;
+
+ @Schema(description = "启用期间", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "启用期间不能为空")
+ private LocalDateTime startTime;
+
+ @Schema(description = "会计制度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "会计制度不能为空")
+ @InEnum(value = FmsAccountingStandardEnum.class, message = "会计制度必须是 {value}")
+ private Integer standard;
+
+ @Schema(description = "科目层级", requiredMode = Schema.RequiredMode.REQUIRED, example = "4")
+ @NotNull(message = "科目层级不能为空")
+ @Min(value = 1, message = "科目层级不能小于 1")
+ @Max(value = 8, message = "科目层级不能大于 8")
+ private Integer level;
+
+ @Schema(description = "科目编码规则", requiredMode = Schema.RequiredMode.REQUIRED, example = "4-2-2-2")
+ @NotBlank(message = "科目编码规则不能为空")
+ @Size(max = 64, message = "科目编码规则不能超过 64 个字符")
+ private String subjectCodeRule;
+
+ @Schema(description = "账簿余额方向模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "账簿余额方向模式不能为空")
+ @InEnum(FmsLedgerBalanceModeEnum.class)
+ private Integer ledgerBalanceMode;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/accountset/FmsAccountSetRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/accountset/FmsAccountSetRespVO.java
new file mode 100644
index 0000000..7ae198a
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/accountset/FmsAccountSetRespVO.java
@@ -0,0 +1,95 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.accountset;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - FMS 账套 Response VO")
+@Data
+public class FmsAccountSetRespVO {
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "公司编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "WK001")
+ private String companyCode;
+
+ @Schema(description = "公司名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "测试科技有限公司")
+ private String companyName;
+
+ @Schema(description = "公司简介", example = "专注企业数字化服务")
+ private String companyProfile;
+
+ @Schema(description = "所在行业", example = "软件和信息技术服务业")
+ private String industry;
+
+ @Schema(description = "所在地", example = "上海市浦东新区")
+ private String location;
+
+ @Schema(description = "法人代表", example = "张三")
+ private String legalRepresentative;
+
+ @Schema(description = "法人身份证号", example = "310101199001011234")
+ private String legalRepresentativeIdNumber;
+
+ @Schema(description = "营业执照号", example = "91310000MA1K123456")
+ private String businessLicenseNumber;
+
+ @Schema(description = "组织机构代码", example = "MA1K12345")
+ private String organizationCode;
+
+ @Schema(description = "备注", example = "一般纳税人")
+ private String remark;
+
+ @Schema(description = "联系人", example = "李四")
+ private String contactName;
+
+ @Schema(description = "办公电话", example = "021-12345678")
+ private String officeTelephone;
+
+ @Schema(description = "手机号码", example = "15601691399")
+ private String mobile;
+
+ @Schema(description = "传真号码", example = "021-87654321")
+ private String faxNumber;
+
+ @Schema(description = "QQ 号码", example = "12345678")
+ private String qqNumber;
+
+ @Schema(description = "邮箱", example = "finance@example.com")
+ private String email;
+
+ @Schema(description = "其他联系方式", example = "企业微信:wk-finance")
+ private String otherContact;
+
+ @Schema(description = "详细地址", example = "张江高科技园区 1 号楼")
+ private String address;
+
+ @Schema(description = "币种编号", example = "1024")
+ private Long currencyId;
+
+ @Schema(description = "启用期间")
+ private LocalDateTime startTime;
+
+ @Schema(description = "会计制度", example = "1")
+ private Integer standard;
+
+ @Schema(description = "是否已初始化", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ private Boolean initialized;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+ // ==================== 来自 FmsAccountUserDO ====================
+
+ @Schema(description = "是否默认账套", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ private Boolean defaultStatus;
+
+ @Schema(description = "是否账套创建人", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ private Boolean founder;
+
+ @Schema(description = "当前用户的成员权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer level;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/accountset/FmsAccountSetSaveReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/accountset/FmsAccountSetSaveReqVO.java
new file mode 100644
index 0000000..10b1601
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/accountset/FmsAccountSetSaveReqVO.java
@@ -0,0 +1,110 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.accountset;
+
+import com.mzt.logapi.starter.annotation.DiffLogField;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Schema(description = "管理后台 - FMS 账套新增/修改 Request VO")
+@Data
+public class FmsAccountSetSaveReqVO {
+
+ @Schema(description = "账套编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "公司编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "WK001")
+ @NotBlank(message = "公司编码不能为空")
+ @Size(max = 64, message = "公司编码不能超过 64 个字符")
+ @DiffLogField(name = "公司编码")
+ private String companyCode;
+
+ @Schema(description = "公司名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "测试科技有限公司")
+ @NotBlank(message = "公司名称不能为空")
+ @Size(max = 255, message = "公司名称不能超过 255 个字符")
+ @DiffLogField(name = "公司名称")
+ private String companyName;
+
+ @Schema(description = "公司简介", example = "专注企业数字化服务")
+ @Size(max = 500, message = "公司简介不能超过 500 个字符")
+ @DiffLogField(name = "公司简介")
+ private String companyProfile;
+
+ @Schema(description = "所在行业", example = "软件和信息技术服务业")
+ @Size(max = 255, message = "所在行业不能超过 255 个字符")
+ @DiffLogField(name = "所在行业")
+ private String industry;
+
+ @Schema(description = "所在地", example = "上海市浦东新区")
+ @Size(max = 255, message = "所在地不能超过 255 个字符")
+ @DiffLogField(name = "所在地")
+ private String location;
+
+ @Schema(description = "法人代表", example = "张三")
+ @Size(max = 255, message = "法人代表不能超过 255 个字符")
+ @DiffLogField(name = "法人代表")
+ private String legalRepresentative;
+
+ @Schema(description = "法人身份证号", example = "310101199001011234")
+ @Size(max = 255, message = "法人身份证号不能超过 255 个字符")
+ @DiffLogField(name = "法人身份证号")
+ private String legalRepresentativeIdNumber;
+
+ @Schema(description = "营业执照号", example = "91310000MA1K123456")
+ @Size(max = 255, message = "营业执照号不能超过 255 个字符")
+ @DiffLogField(name = "营业执照号")
+ private String businessLicenseNumber;
+
+ @Schema(description = "组织机构代码", example = "MA1K12345")
+ @Size(max = 255, message = "组织机构代码不能超过 255 个字符")
+ @DiffLogField(name = "组织机构代码")
+ private String organizationCode;
+
+ @Schema(description = "备注", example = "一般纳税人")
+ @Size(max = 500, message = "备注不能超过 500 个字符")
+ @DiffLogField(name = "备注")
+ private String remark;
+
+ @Schema(description = "联系人", example = "李四")
+ @Size(max = 255, message = "联系人不能超过 255 个字符")
+ @DiffLogField(name = "联系人")
+ private String contactName;
+
+ @Schema(description = "办公电话", example = "021-12345678")
+ @Size(max = 32, message = "办公电话不能超过 32 个字符")
+ @DiffLogField(name = "办公电话")
+ private String officeTelephone;
+
+ @Schema(description = "手机号码", example = "15601691399")
+ @Size(max = 32, message = "手机号码不能超过 32 个字符")
+ @DiffLogField(name = "手机号码")
+ private String mobile;
+
+ @Schema(description = "传真号码", example = "021-87654321")
+ @Size(max = 32, message = "传真号码不能超过 32 个字符")
+ @DiffLogField(name = "传真号码")
+ private String faxNumber;
+
+ @Schema(description = "QQ 号码", example = "12345678")
+ @Size(max = 255, message = "QQ 号码不能超过 255 个字符")
+ @DiffLogField(name = "QQ 号码")
+ private String qqNumber;
+
+ @Schema(description = "邮箱", example = "finance@example.com")
+ @Email(message = "邮箱格式不正确")
+ @Size(max = 255, message = "邮箱不能超过 255 个字符")
+ @DiffLogField(name = "邮箱")
+ private String email;
+
+ @Schema(description = "其他联系方式", example = "企业微信:wk-finance")
+ @Size(max = 255, message = "其他联系方式不能超过 255 个字符")
+ @DiffLogField(name = "其他联系方式")
+ private String otherContact;
+
+ @Schema(description = "详细地址", example = "张江高科技园区 1 号楼")
+ @Size(max = 255, message = "详细地址不能超过 255 个字符")
+ @DiffLogField(name = "详细地址")
+ private String address;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/accountuser/FmsAccountUserRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/accountuser/FmsAccountUserRespVO.java
new file mode 100644
index 0000000..d841f70
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/accountuser/FmsAccountUserRespVO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.accountuser;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - FMS 账套用户 Response VO")
+@Data
+public class FmsAccountUserRespVO {
+
+ @Schema(description = "后台用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long userId;
+
+ @Schema(description = "用户昵称", example = "芋道")
+ private String nickname;
+
+ @Schema(description = "部门名称", example = "财务部")
+ private String deptName;
+
+ @Schema(description = "手机号码", example = "15601691399")
+ private String mobile;
+
+ @Schema(description = "用户邮箱", example = "finance@example.com")
+ private String email;
+
+ @Schema(description = "用户状态", example = "0")
+ private Integer status;
+
+ @Schema(description = "是否默认账套", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ private Boolean defaultStatus;
+
+ @Schema(description = "是否账套创建人", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ private Boolean founder;
+
+ @Schema(description = "成员权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ private Integer level;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/accountuser/FmsAccountUserUpdateReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/accountuser/FmsAccountUserUpdateReqVO.java
new file mode 100644
index 0000000..16565f1
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/accountuser/FmsAccountUserUpdateReqVO.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.accountuser;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.fms.enums.config.FmsAccountUserLevelEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - FMS 账套用户更新 Request VO")
+@Data
+public class FmsAccountUserUpdateReqVO {
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "账套编号不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "账套成员数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "账套成员数组不能为空")
+ @Size(max = 100, message = "单个账套最多授权 100 名成员")
+ @Valid
+ private List members;
+
+ @Schema(description = "管理后台 - FMS 账套成员")
+ @Data
+ public static class Member {
+
+ @Schema(description = "后台用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "成员用户编号不能为空")
+ private Long userId;
+
+ @Schema(description = "成员权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @NotNull(message = "成员权限级别不能为空")
+ @InEnum(FmsAccountUserLevelEnum.class)
+ private Integer level;
+
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemImportExcelVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemImportExcelVO.java
new file mode 100644
index 0000000..6a15e3c
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemImportExcelVO.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliaryitem;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+/**
+ * FMS 辅助核算项目 Excel 导入 VO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class FmsAuxiliaryItemImportExcelVO {
+
+ @ExcelProperty("编码")
+ @NotBlank(message = "编码不能为空")
+ @Size(max = 64, message = "编码长度不能超过 64 个字符")
+ private String code;
+
+ @ExcelProperty("名称")
+ @NotBlank(message = "名称不能为空")
+ @Size(max = 255, message = "名称长度不能超过 255 个字符")
+ private String name;
+
+ @ExcelProperty("备注")
+ @Size(max = 500, message = "备注长度不能超过 500 个字符")
+ private String remark;
+
+ @ExcelProperty("规格")
+ @Size(max = 255, message = "规格长度不能超过 255 个字符")
+ private String specification;
+
+ @ExcelProperty("单位")
+ @Size(max = 255, message = "单位长度不能超过 255 个字符")
+ private String unit;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemImportRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemImportRespVO.java
new file mode 100644
index 0000000..8cb7a35
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemImportRespVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliaryitem;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * FMS 辅助核算项目导入结果 Response VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 辅助核算项目导入结果 Response VO")
+@Data
+public class FmsAuxiliaryItemImportRespVO {
+
+ @Schema(description = "总数量", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Integer totalCount;
+
+ @Schema(description = "成功编码数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List successItemCodes;
+
+ @Schema(description = "失败原因 Map", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Map failureReasons;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemPageReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemPageReqVO.java
new file mode 100644
index 0000000..2707058
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemPageReqVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliaryitem;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * FMS 辅助核算项目分页 Request VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 辅助核算项目分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FmsAuxiliaryItemPageReqVO extends PageParam {
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "账套编号不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "辅助核算类别编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "辅助核算类别编号不能为空")
+ private Long auxiliaryTypeId;
+
+ @Schema(description = "关键词,匹配编码或名称", example = "测试")
+ private String search;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemRespVO.java
new file mode 100644
index 0000000..f41a550
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemRespVO.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliaryitem;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * FMS 辅助核算项目 Response VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 辅助核算项目 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class FmsAuxiliaryItemRespVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long accountSetId;
+
+ @Schema(description = "辅助核算类别编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long auxiliaryTypeId;
+
+ @Schema(description = "编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "KH001")
+ @ExcelProperty("编码")
+ private String code;
+
+ @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "上海测试数字科技有限公司")
+ @ExcelProperty("名称")
+ private String name;
+
+ @Schema(description = "备注", example = "重点客户")
+ @ExcelProperty("备注")
+ private String remark;
+
+ @Schema(description = "规格", example = "标准版")
+ @ExcelProperty("规格")
+ private String specification;
+
+ @Schema(description = "单位", example = "台")
+ @ExcelProperty("单位")
+ private String unit;
+
+ @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer status;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemSaveReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemSaveReqVO.java
new file mode 100644
index 0000000..0d16e1b
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemSaveReqVO.java
@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliaryitem;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+/**
+ * FMS 辅助核算项目创建或修改 Request VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 辅助核算项目创建或修改 Request VO")
+@Data
+public class FmsAuxiliaryItemSaveReqVO {
+
+ @Schema(description = "编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "账套编号不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "辅助核算类别编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "辅助核算类别编号不能为空")
+ private Long auxiliaryTypeId;
+
+ @Schema(description = "编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "KH001")
+ @NotBlank(message = "编码不能为空")
+ @Size(max = 64, message = "编码长度不能超过 64 个字符")
+ private String code;
+
+ @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "上海测试数字科技有限公司")
+ @NotBlank(message = "名称不能为空")
+ @Size(max = 255, message = "名称长度不能超过 255 个字符")
+ private String name;
+
+ @Schema(description = "备注", example = "重点客户")
+ @Size(max = 500, message = "备注长度不能超过 500 个字符")
+ private String remark;
+
+ @Schema(description = "规格", example = "标准版")
+ @Size(max = 255, message = "规格长度不能超过 255 个字符")
+ private String specification;
+
+ @Schema(description = "单位", example = "台")
+ @Size(max = 255, message = "单位长度不能超过 255 个字符")
+ private String unit;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemStatusReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemStatusReqVO.java
new file mode 100644
index 0000000..ed29d9e
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliaryitem/FmsAuxiliaryItemStatusReqVO.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliaryitem;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * FMS 辅助核算项目状态更新 Request VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 辅助核算项目状态更新 Request VO")
+@Data
+public class FmsAuxiliaryItemStatusReqVO {
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "账套编号不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "编号不能为空")
+ private Long id;
+
+ @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ @NotNull(message = "状态不能为空")
+ @InEnum(CommonStatusEnum.class)
+ private Integer status;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliarytype/FmsAuxiliaryTypeRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliarytype/FmsAuxiliaryTypeRespVO.java
new file mode 100644
index 0000000..ba027b7
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliarytype/FmsAuxiliaryTypeRespVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliarytype;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * FMS 辅助核算类别 Response VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 辅助核算类别 Response VO")
+@Data
+public class FmsAuxiliaryTypeRespVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "客户")
+ private String name;
+
+ @Schema(description = "是否系统预置", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean systemPreset;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long accountSetId;
+
+ @Schema(description = "类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer type;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliarytype/FmsAuxiliaryTypeSaveReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliarytype/FmsAuxiliaryTypeSaveReqVO.java
new file mode 100644
index 0000000..db5508c
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/auxiliarytype/FmsAuxiliaryTypeSaveReqVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.auxiliarytype;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+/**
+ * FMS 辅助核算类别创建或修改 Request VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 辅助核算类别创建或修改 Request VO")
+@Data
+public class FmsAuxiliaryTypeSaveReqVO {
+
+ @Schema(description = "编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "账套编号不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "门店")
+ @NotBlank(message = "名称不能为空")
+ @Size(max = 255, message = "名称长度不能超过 255 个字符")
+ private String name;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/currency/FmsCurrencyRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/currency/FmsCurrencyRespVO.java
new file mode 100644
index 0000000..9c7a6a8
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/currency/FmsCurrencyRespVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.currency;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * FMS 币别 Response VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 币别 Response VO")
+@Data
+public class FmsCurrencyRespVO {
+
+ @Schema(description = "币别编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long accountSetId;
+
+ @Schema(description = "币别编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "USD")
+ private String code;
+
+ @Schema(description = "币别名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "美元")
+ private String name;
+
+ @Schema(description = "汇率", requiredMode = Schema.RequiredMode.REQUIRED, example = "7.120000")
+ private BigDecimal exchangeRate;
+
+ @Schema(description = "是否本位币", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean standard;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/currency/FmsCurrencySaveReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/currency/FmsCurrencySaveReqVO.java
new file mode 100644
index 0000000..7839f87
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/currency/FmsCurrencySaveReqVO.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.currency;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.Digits;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * FMS 币别保存 Request VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 币别保存 Request VO")
+@Data
+public class FmsCurrencySaveReqVO {
+
+ @Schema(description = "币别编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "账套不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "币别编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "USD")
+ @NotBlank(message = "币别编码不能为空")
+ @Size(max = 64, message = "币别编码长度不能超过 64 个字符")
+ @Pattern(regexp = "^[A-Za-z][A-Za-z0-9_]*$", message = "币别编码必须以字母开头,只能包含字母、数字和下划线")
+ private String code;
+
+ @Schema(description = "币别名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "美元")
+ @NotBlank(message = "币别名称不能为空")
+ @Size(max = 255, message = "币别名称长度不能超过 255 个字符")
+ private String name;
+
+ @Schema(description = "汇率", requiredMode = Schema.RequiredMode.REQUIRED, example = "7.120000")
+ @NotNull(message = "汇率不能为空")
+ @DecimalMin(value = "0", inclusive = false, message = "汇率必须大于 0")
+ @Digits(integer = 12, fraction = 6, message = "汇率最多 12 位整数和 6 位小数")
+ private BigDecimal exchangeRate;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/digest/FmsDigestRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/digest/FmsDigestRespVO.java
new file mode 100644
index 0000000..029e16d
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/digest/FmsDigestRespVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.digest;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * FMS 常用摘要 Response VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 常用摘要 Response VO")
+@Data
+public class FmsDigestRespVO {
+
+ @Schema(description = "常用摘要编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long accountSetId;
+
+ @Schema(description = "摘要内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "购买办公用品")
+ private String content;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/digest/FmsDigestSaveReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/digest/FmsDigestSaveReqVO.java
new file mode 100644
index 0000000..2b296a0
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/digest/FmsDigestSaveReqVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.digest;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+/**
+ * FMS 常用摘要保存 Request VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 常用摘要保存 Request VO")
+@Data
+public class FmsDigestSaveReqVO {
+
+ @Schema(description = "常用摘要编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "账套编号不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "摘要内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "购买办公用品")
+ @NotBlank(message = "摘要内容不能为空")
+ @Size(max = 500, message = "摘要内容长度不能超过 500 个字符")
+ private String content;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/financeindicator/FmsFinanceIndicatorRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/financeindicator/FmsFinanceIndicatorRespVO.java
new file mode 100644
index 0000000..410e1c3
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/financeindicator/FmsFinanceIndicatorRespVO.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.financeindicator;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+// TODO DONE @AI:字段 Swagger 注解和空行已补齐。
+@Schema(description = "管理后台 - FMS 财务指标 Response VO")
+@Data
+public class FmsFinanceIndicatorRespVO {
+
+ @Schema(description = "指标编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", example = "1")
+ private Long accountSetId;
+
+ @Schema(description = "指标名称", example = "现金")
+ private String name;
+
+ @Schema(description = "指标编码", example = "cash")
+ private String code;
+
+ @Schema(description = "取数报表类型", example = "1")
+ private Integer type;
+
+ @Schema(description = "指标公式")
+ private String formula;
+
+ @Schema(description = "展示顺序", example = "10")
+ private Integer sort;
+
+ @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Integer status;
+
+ @Schema(description = "创建时间")
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/financeindicator/FmsFinanceIndicatorSaveReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/financeindicator/FmsFinanceIndicatorSaveReqVO.java
new file mode 100644
index 0000000..91eabdc
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/financeindicator/FmsFinanceIndicatorSaveReqVO.java
@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.financeindicator;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.fms.enums.config.FmsFinanceIndicatorTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+// TODO DONE @AI:字段空行已对齐现有 SaveReqVO。
+@Schema(description = "管理后台 - FMS 财务指标保存 Request VO")
+@Data
+public class FmsFinanceIndicatorSaveReqVO {
+
+ @Schema(description = "指标编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "账套编号不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "指标名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "现金")
+ @NotBlank(message = "指标名称不能为空")
+ @Size(max = 100, message = "指标名称长度不能超过 100 个字符")
+ private String name;
+
+ @Schema(description = "指标编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "cash")
+ @NotBlank(message = "指标编码不能为空")
+ @Size(max = 64, message = "指标编码长度不能超过 64 个字符")
+ private String code;
+
+ @Schema(description = "取数报表类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @NotNull(message = "取数报表类型不能为空")
+ @InEnum(FmsFinanceIndicatorTypeEnum.class)
+ private Integer type;
+
+ @Schema(description = "指标公式,支持 L1+L2 或科目公式 JSON", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotBlank(message = "指标公式不能为空")
+ @Size(max = 2000, message = "指标公式长度不能超过 2000 个字符")
+ private String formula;
+
+ @Schema(description = "展示顺序", example = "10")
+ @NotNull(message = "展示顺序不能为空")
+ private Integer sort;
+
+ @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ @NotNull(message = "状态不能为空")
+ @InEnum(CommonStatusEnum.class)
+ private Integer status;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/financeparameter/FmsFinanceParameterRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/financeparameter/FmsFinanceParameterRespVO.java
new file mode 100644
index 0000000..2e31efb
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/financeparameter/FmsFinanceParameterRespVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.financeparameter;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - FMS 财务参数 Response VO")
+@Data
+public class FmsFinanceParameterRespVO {
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long accountSetId;
+
+ @Schema(description = "科目层级", requiredMode = Schema.RequiredMode.REQUIRED, example = "4")
+ private Integer level;
+
+ @Schema(description = "科目编码规则", requiredMode = Schema.RequiredMode.REQUIRED, example = "4-2-2-2")
+ private String subjectCodeRule;
+
+ @Schema(description = "账簿余额方向模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer ledgerBalanceMode;
+
+ @Schema(description = "结账前是否要求凭证审核", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ private Boolean voucherReviewRequired;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/financeparameter/FmsFinanceParameterUpdateReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/financeparameter/FmsFinanceParameterUpdateReqVO.java
new file mode 100644
index 0000000..6299af4
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/financeparameter/FmsFinanceParameterUpdateReqVO.java
@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.financeparameter;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.fms.enums.ledger.FmsLedgerBalanceModeEnum;
+import cn.iocoder.yudao.module.fms.enums.config.FmsAccountingStandardEnum;
+import com.mzt.logapi.starter.annotation.DiffLogField;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Schema(description = "管理后台 - FMS 财务参数更新 Request VO")
+@Data
+public class FmsFinanceParameterUpdateReqVO {
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "账套编号不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "会计制度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "会计制度不能为空")
+ @InEnum(FmsAccountingStandardEnum.class)
+ @DiffLogField(name = "会计制度")
+ private Integer standard;
+
+ @Schema(description = "科目层级", requiredMode = Schema.RequiredMode.REQUIRED, example = "4")
+ @NotNull(message = "科目层级不能为空")
+ @Min(value = 1, message = "科目层级不能小于 1")
+ @Max(value = 8, message = "科目层级不能大于 8")
+ @DiffLogField(name = "科目层级")
+ private Integer level;
+
+ @Schema(description = "科目编码规则", requiredMode = Schema.RequiredMode.REQUIRED, example = "4-2-2-2")
+ @NotBlank(message = "科目编码规则不能为空")
+ @Size(max = 64, message = "科目编码规则不能超过 64 个字符")
+ @DiffLogField(name = "科目编码规则")
+ private String subjectCodeRule;
+
+ @Schema(description = "账簿余额方向模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "账簿余额方向模式不能为空")
+ @InEnum(FmsLedgerBalanceModeEnum.class)
+ @DiffLogField(name = "账簿余额方向")
+ private Integer ledgerBalanceMode;
+
+ @Schema(description = "结账前是否要求凭证审核", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ @NotNull(message = "结账前凭证审核设置不能为空")
+ @DiffLogField(name = "凭证审核后才允许结账")
+ private Boolean voucherReviewRequired;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/initialbalance/FmsInitialBalanceExcelVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/initialbalance/FmsInitialBalanceExcelVO.java
new file mode 100644
index 0000000..df1e229
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/initialbalance/FmsInitialBalanceExcelVO.java
@@ -0,0 +1,105 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.initialbalance;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * FMS 初始余额 Excel VO
+ *
+ * @author 芋道源码
+ */
+@Data
+@ExcelIgnoreUnannotated
+public class FmsInitialBalanceExcelVO {
+
+ /**
+ * Excel 行号
+ */
+ private Integer rowNumber;
+ /**
+ * 科目编码
+ */
+ @ExcelProperty("科目编码")
+ private String subjectCode;
+ /**
+ * 科目名称
+ */
+ @ExcelProperty("科目名称")
+ private String subjectName;
+ /**
+ * 余额方向名称
+ */
+ @ExcelProperty("方向")
+ private String directionName;
+ /**
+ * 辅助核算项目,格式为“类别:名称/类别:名称”
+ */
+ @ExcelProperty("辅助核算项目")
+ private String auxiliaryItems;
+ /**
+ * 科目类型
+ */
+ private Integer subjectType;
+ /**
+ * 是否启用数量核算
+ */
+ private Boolean quantityAccounting;
+ /**
+ * 是否启用辅助核算
+ */
+ private Boolean auxiliaryAccounting;
+ /**
+ * 期初数量
+ */
+ @ExcelProperty("期初数量")
+ private BigDecimal openingQuantity;
+ /**
+ * 期初金额
+ */
+ @ExcelProperty("期初金额")
+ private BigDecimal openingAmount;
+ /**
+ * 本年累计借方数量
+ */
+ @ExcelProperty("本年累计借方数量")
+ private BigDecimal yearDebitQuantity;
+ /**
+ * 本年累计借方金额
+ */
+ @ExcelProperty("本年累计借方金额")
+ private BigDecimal yearDebitAmount;
+ /**
+ * 本年累计贷方数量
+ */
+ @ExcelProperty("本年累计贷方数量")
+ private BigDecimal yearCreditQuantity;
+ /**
+ * 本年累计贷方金额
+ */
+ @ExcelProperty("本年累计贷方金额")
+ private BigDecimal yearCreditAmount;
+ /**
+ * 年初数量
+ */
+ @ExcelProperty("年初数量")
+ private BigDecimal yearOpeningQuantity;
+ /**
+ * 年初金额
+ */
+ @ExcelProperty("年初金额")
+ private BigDecimal yearOpeningAmount;
+ /**
+ * 实际损益发生数量
+ */
+ @ExcelProperty("实际损益发生数量")
+ private BigDecimal profitLossQuantity;
+ /**
+ * 实际损益发生金额
+ */
+ @ExcelProperty("实际损益发生金额")
+ private BigDecimal profitLossAmount;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/initialbalance/FmsInitialBalanceRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/initialbalance/FmsInitialBalanceRespVO.java
new file mode 100644
index 0000000..8a8cfc2
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/initialbalance/FmsInitialBalanceRespVO.java
@@ -0,0 +1,154 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.initialbalance;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Schema(description = "管理后台 - FMS 初始余额 Response VO")
+@Data
+public class FmsInitialBalanceRespVO {
+
+ @Schema(description = "初始余额编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "科目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long subjectId;
+
+ @Schema(description = "科目编码", requiredMode = Schema.RequiredMode.REQUIRED)
+ private String subjectCode;
+
+ @Schema(description = "科目名称", requiredMode = Schema.RequiredMode.REQUIRED)
+ private String subjectName;
+
+ @Schema(description = "上级科目编号", example = "1024")
+ private Long parentId;
+
+ @Schema(description = "科目类型", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Integer type;
+
+ @Schema(description = "余额方向", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Integer balanceDirection;
+
+ @Schema(description = "是否启用数量核算", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean quantityAccounting;
+
+ @Schema(description = "数量单位")
+ private String quantityUnit;
+
+ @Schema(description = "是否启用辅助核算", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean auxiliaryAccounting;
+
+ @Schema(description = "期初金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000.00")
+ private BigDecimal openingAmount;
+
+ @Schema(description = "期初数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10.0000")
+ private BigDecimal openingQuantity;
+
+ @Schema(description = "本年累计借方金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
+ private BigDecimal yearDebitAmount;
+
+ @Schema(description = "本年累计借方数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0000")
+ private BigDecimal yearDebitQuantity;
+
+ @Schema(description = "本年累计贷方金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "50.00")
+ private BigDecimal yearCreditAmount;
+
+ @Schema(description = "本年累计贷方数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0000")
+ private BigDecimal yearCreditQuantity;
+
+ @Schema(description = "年初金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "950.00")
+ private BigDecimal yearOpeningAmount;
+
+ @Schema(description = "年初数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10.0000")
+ private BigDecimal yearOpeningQuantity;
+
+ @Schema(description = "实际损益发生额", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.00")
+ private BigDecimal profitLossAmount;
+
+ @Schema(description = "实际损益发生数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0000")
+ private BigDecimal profitLossQuantity;
+
+ @Schema(description = "辅助核算配置数组")
+ private List auxiliaryConfigs;
+
+ @Schema(description = "辅助核算余额数组")
+ private List assistBalances;
+
+ @Schema(description = "管理后台 - FMS 初始余额辅助核算配置")
+ @Data
+ public static class AuxiliaryConfig {
+
+ @Schema(description = "辅助核算类别编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long auxiliaryTypeId;
+
+ @Schema(description = "辅助核算类型", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Integer type;
+
+ @Schema(description = "辅助核算类别名称", requiredMode = Schema.RequiredMode.REQUIRED)
+ private String name;
+
+ }
+
+ @Schema(description = "管理后台 - FMS 初始余额辅助核算余额")
+ @Data
+ public static class AssistBalance {
+
+ @Schema(description = "辅助核算组合编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long assistCombinationId;
+
+ @Schema(description = "辅助核算项目数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List auxiliaries;
+
+ @Schema(description = "期初金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000.00")
+ private BigDecimal openingAmount;
+
+ @Schema(description = "期初数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10.0000")
+ private BigDecimal openingQuantity;
+
+ @Schema(description = "本年累计借方金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
+ private BigDecimal yearDebitAmount;
+
+ @Schema(description = "本年累计借方数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0000")
+ private BigDecimal yearDebitQuantity;
+
+ @Schema(description = "本年累计贷方金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "50.00")
+ private BigDecimal yearCreditAmount;
+
+ @Schema(description = "本年累计贷方数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0000")
+ private BigDecimal yearCreditQuantity;
+
+ @Schema(description = "年初金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "950.00")
+ private BigDecimal yearOpeningAmount;
+
+ @Schema(description = "年初数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10.0000")
+ private BigDecimal yearOpeningQuantity;
+
+ @Schema(description = "实际损益发生额", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.00")
+ private BigDecimal profitLossAmount;
+
+ @Schema(description = "实际损益发生数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0000")
+ private BigDecimal profitLossQuantity;
+
+ }
+
+ @Schema(description = "管理后台 - FMS 初始余额辅助核算项目")
+ @Data
+ public static class AuxiliaryItem {
+
+ @Schema(description = "辅助核算类型", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Integer type;
+
+ @Schema(description = "辅助核算类别编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long typeId;
+
+ @Schema(description = "辅助核算项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long itemId;
+
+ @Schema(description = "辅助核算项目名称", requiredMode = Schema.RequiredMode.REQUIRED)
+ private String name;
+
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/initialbalance/FmsInitialBalanceSaveReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/initialbalance/FmsInitialBalanceSaveReqVO.java
new file mode 100644
index 0000000..6f8b2a7
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/initialbalance/FmsInitialBalanceSaveReqVO.java
@@ -0,0 +1,91 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.initialbalance;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Schema(description = "管理后台 - FMS 初始余额保存 Request VO")
+@Data
+public class FmsInitialBalanceSaveReqVO {
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "账套编号不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "科目余额数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ @Valid
+ @NotEmpty(message = "科目余额不能为空")
+ private List balances;
+
+ @Data
+ public static class Balance extends Amounts {
+
+ @Schema(description = "科目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "科目编号不能为空")
+ private Long subjectId;
+
+ @Schema(description = "辅助核算余额数组")
+ @Valid
+ private List assistBalances;
+
+ }
+
+ @Data
+ public static class AssistBalance extends Amounts {
+
+ @Schema(description = "辅助核算项目编号数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotEmpty(message = "辅助核算项目不能为空")
+ private List auxiliaryItemIds;
+ }
+
+ @Data
+ public static class Amounts {
+
+ @Schema(description = "期初金额", example = "1000.00")
+ @DecimalMin(value = "0", message = "期初金额不能小于 0")
+ private BigDecimal openingAmount;
+
+ @Schema(description = "期初数量", example = "10.0000")
+ @DecimalMin(value = "0", message = "期初数量不能小于 0")
+ private BigDecimal openingQuantity;
+
+ @Schema(description = "本年累计借方金额", example = "100.00")
+ @DecimalMin(value = "0", message = "本年累计借方金额不能小于 0")
+ private BigDecimal yearDebitAmount;
+
+ @Schema(description = "本年累计借方数量", example = "1.0000")
+ @DecimalMin(value = "0", message = "本年累计借方数量不能小于 0")
+ private BigDecimal yearDebitQuantity;
+
+ @Schema(description = "本年累计贷方金额", example = "50.00")
+ @DecimalMin(value = "0", message = "本年累计贷方金额不能小于 0")
+ private BigDecimal yearCreditAmount;
+
+ @Schema(description = "本年累计贷方数量", example = "1.0000")
+ @DecimalMin(value = "0", message = "本年累计贷方数量不能小于 0")
+ private BigDecimal yearCreditQuantity;
+
+ @Schema(description = "年初金额", example = "950.00")
+ @DecimalMin(value = "0", message = "年初金额不能小于 0")
+ private BigDecimal yearOpeningAmount;
+
+ @Schema(description = "年初数量", example = "10.0000")
+ @DecimalMin(value = "0", message = "年初数量不能小于 0")
+ private BigDecimal yearOpeningQuantity;
+
+ @Schema(description = "实际损益发生额", example = "0.00")
+ @DecimalMin(value = "0", message = "实际损益发生额不能小于 0")
+ private BigDecimal profitLossAmount;
+
+ @Schema(description = "实际损益发生数量", example = "1.0000")
+ @DecimalMin(value = "0", message = "实际损益发生数量不能小于 0")
+ private BigDecimal profitLossQuantity;
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/initialbalance/FmsTrialBalanceRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/initialbalance/FmsTrialBalanceRespVO.java
new file mode 100644
index 0000000..94a1689
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/initialbalance/FmsTrialBalanceRespVO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.initialbalance;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "管理后台 - FMS 试算平衡 Response VO")
+@Data
+public class FmsTrialBalanceRespVO {
+
+ @Schema(description = "期初借方金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000.00")
+ private BigDecimal openingDebitAmount;
+
+ @Schema(description = "期初贷方金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000.00")
+ private BigDecimal openingCreditAmount;
+
+ @Schema(description = "期初差额", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.00")
+ private BigDecimal openingDifferenceAmount;
+
+ @Schema(description = "本年累计借方金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
+ private BigDecimal yearDebitAmount;
+
+ @Schema(description = "本年累计贷方金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
+ private BigDecimal yearCreditAmount;
+
+ @Schema(description = "本年累计差额", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.00")
+ private BigDecimal yearDifferenceAmount;
+
+ @Schema(description = "是否平衡", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean balanced;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectDeleteReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectDeleteReqVO.java
new file mode 100644
index 0000000..34b84fc
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectDeleteReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * FMS 科目删除 Request VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 科目删除 Request VO")
+@Data
+public class FmsSubjectDeleteReqVO {
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "账套编号不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "科目编号数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotEmpty(message = "科目编号数组不能为空")
+ private List ids;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectImportExcelVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectImportExcelVO.java
new file mode 100644
index 0000000..07a8dcf
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectImportExcelVO.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * FMS 科目 Excel 导入 VO
+ *
+ * @author 芋道源码
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class FmsSubjectImportExcelVO {
+
+ @ExcelProperty("科目编码")
+ @NotBlank(message = "科目编码不能为空")
+ private String code;
+
+ @ExcelProperty("科目名称")
+ @NotBlank(message = "科目名称不能为空")
+ private String name;
+
+ @ExcelProperty("上级科目编码")
+ private String parentSubjectCode;
+
+ @ExcelProperty("余额方向")
+ @NotBlank(message = "余额方向不能为空")
+ private String balanceDirection;
+
+ @ExcelProperty("类别")
+ @NotBlank(message = "类别不能为空")
+ private String categoryName;
+
+ @ExcelProperty("辅助核算")
+ private String auxiliaryNames;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectImportRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectImportRespVO.java
new file mode 100644
index 0000000..f1691a8
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectImportRespVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+@Schema(description = "管理后台 - FMS 科目导入结果 Response VO")
+@Data
+public class FmsSubjectImportRespVO {
+
+ @Schema(description = "总数量", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Integer totalCount;
+
+ @Schema(description = "成功科目编码数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List successSubjectCodes;
+
+ @Schema(description = "失败原因 Map", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Map failureReasons;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectListReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectListReqVO.java
new file mode 100644
index 0000000..cd84baa
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectListReqVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.fms.enums.config.FmsSubjectTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * FMS 科目列表 Request VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 科目列表 Request VO")
+@Data
+public class FmsSubjectListReqVO {
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "账套编号不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "科目类型", example = "1")
+ @InEnum(FmsSubjectTypeEnum.class)
+ private Integer type;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectRespVO.java
new file mode 100644
index 0000000..9a4bf25
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectRespVO.java
@@ -0,0 +1,92 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.framework.excel.core.convert.StringListConvert;
+import cn.iocoder.yudao.module.fms.enums.DictTypeConstants;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+import static cn.iocoder.yudao.module.infra.enums.DictTypeConstants.BOOLEAN_STRING;
+
+/**
+ * FMS 科目 Response VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 科目 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class FmsSubjectRespVO {
+
+ @Schema(description = "科目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long accountSetId;
+
+ @Schema(description = "科目编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001")
+ @ExcelProperty(value = "编码", index = 0)
+ private String code;
+
+ @Schema(description = "科目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "库存现金")
+ @ExcelProperty(value = "名称", index = 1)
+ private String name;
+
+ @Schema(description = "上级科目编号", example = "1024")
+ private Long parentId;
+
+ @Schema(description = "科目类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer type;
+
+ @Schema(description = "科目类别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer category;
+
+ @JsonIgnore // 仅 excel 导出使用
+ @ExcelProperty(value = "类别", index = 2, converter = DictConvert.class)
+ @DictFormat(DictTypeConstants.SUBJECT_CATEGORY)
+ private String categoryDictValue;
+
+ @Schema(description = "余额方向", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @ExcelProperty(value = "余额方向", index = 3, converter = DictConvert.class)
+ @DictFormat(DictTypeConstants.DEBIT_CREDIT_DIRECTION)
+ private Integer balanceDirection;
+
+ @Schema(description = "辅助核算类别编号数组")
+ private List auxiliaryTypeIds;
+
+ @Schema(description = "外币核算币别编号数组")
+ private List currencyIds;
+
+ @Schema(description = "是否启用数量核算", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean quantityAccounting;
+
+ @Schema(description = "数量单位", example = "件")
+ @ExcelProperty(value = "数量", index = 5)
+ private String quantityUnit;
+
+ @Schema(description = "是否现金及现金等价物", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty(value = "是否现金项", index = 6, converter = DictConvert.class)
+ @DictFormat(BOOLEAN_STRING)
+ private Boolean cash;
+
+ @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer status;
+
+ @Schema(description = "层级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer level;
+
+ @Schema(description = "辅助核算类别名称数组")
+ @ExcelProperty(value = "辅助核算", index = 4, converter = StringListConvert.class)
+ private List auxiliaryTypeNames;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectSaveReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectSaveReqVO.java
new file mode 100644
index 0000000..9ce9779
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectSaveReqVO.java
@@ -0,0 +1,113 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.fms.enums.common.FmsDebitCreditDirectionEnum;
+import cn.iocoder.yudao.module.fms.enums.config.FmsSubjectCategoryEnum;
+import cn.iocoder.yudao.module.fms.enums.config.FmsSubjectTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.AssertTrue;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * FMS 科目保存 Request VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 科目保存 Request VO")
+@Data
+public class FmsSubjectSaveReqVO {
+
+ @Schema(description = "科目编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "账套编号不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "科目编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001")
+ @NotBlank(message = "科目编码不能为空")
+ @Size(max = 64, message = "科目编码长度不能超过 64 个字符")
+ private String code;
+
+ @Schema(description = "科目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "库存现金")
+ @NotBlank(message = "科目名称不能为空")
+ @Size(max = 255, message = "科目名称长度不能超过 255 个字符")
+ private String name;
+
+ @Schema(description = "上级科目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "上级科目编号不能为空")
+ private Long parentId;
+
+ @Schema(description = "科目类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "科目类型不能为空")
+ @InEnum(FmsSubjectTypeEnum.class)
+ private Integer type;
+
+ @Schema(description = "科目类别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "科目类别不能为空")
+ private Integer category;
+
+ @Schema(description = "余额方向", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "余额方向不能为空")
+ @InEnum(FmsDebitCreditDirectionEnum.class)
+ private Integer balanceDirection;
+
+ @Schema(description = "辅助核算类别编号数组")
+ @NotNull(message = "辅助核算类别编号数组不能为空")
+ private List auxiliaryTypeIds;
+
+ @Schema(description = "外币核算币别编号数组")
+ @NotNull(message = "外币核算币别编号数组不能为空")
+ private List currencyIds;
+
+ @Schema(description = "是否启用数量核算", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "是否启用数量核算不能为空")
+ private Boolean quantityAccounting;
+
+ @Schema(description = "数量单位", example = "件")
+ @Size(max = 255, message = "数量单位长度不能超过 255 个字符")
+ private String quantityUnit;
+
+ @Schema(description = "是否现金及现金等价物", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "是否现金及现金等价物不能为空")
+ private Boolean cash;
+
+ @Schema(description = "是否迁移上级科目历史数据")
+ private Boolean migrateParentData;
+
+ @Schema(description = "辅助核算历史数据迁移项目数组")
+ private List auxiliaryMappings;
+
+ @AssertTrue(message = "科目类别与科目类型不匹配")
+ public boolean isCategoryValid() {
+ return type == null || category == null || FmsSubjectCategoryEnum.valueOf(type, category) != null;
+ }
+
+ @AssertTrue(message = "启用数量核算时数量单位不能为空")
+ public boolean isQuantityUnitValid() {
+ return !Boolean.TRUE.equals(quantityAccounting) || StrUtil.isNotBlank(quantityUnit);
+ }
+
+ /**
+ * 辅助核算历史数据迁移项目
+ */
+ @Data
+ public static class AuxiliaryMapping {
+
+ @Schema(description = "辅助核算类别编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "辅助核算类别编号不能为空")
+ private Long typeId;
+
+ @Schema(description = "辅助核算项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "辅助核算项目编号不能为空")
+ private Long itemId;
+
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectStatusReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectStatusReqVO.java
new file mode 100644
index 0000000..80d8685
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectStatusReqVO.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * FMS 科目状态更新 Request VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 科目状态更新 Request VO")
+@Data
+public class FmsSubjectStatusReqVO {
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "账套编号不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "科目编号数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotEmpty(message = "科目编号数组不能为空")
+ private List ids;
+
+ @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "状态不能为空")
+ @InEnum(CommonStatusEnum.class)
+ private Integer status;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectUsageRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectUsageRespVO.java
new file mode 100644
index 0000000..1d0939f
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/subject/FmsSubjectUsageRespVO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * FMS 科目使用情况 Response VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 科目使用情况 Response VO")
+@Data
+public class FmsSubjectUsageRespVO {
+
+ @Schema(description = "下级科目数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ private Long childCount;
+
+ @Schema(description = "凭证分录数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ private Long voucherEntryCount;
+
+ @Schema(description = "初始余额数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long initialBalanceCount;
+
+ @Schema(description = "辅助核算组合数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Long auxiliaryCombinationCount;
+
+ @Schema(description = "包含数量数据的记录数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long quantityDataCount;
+
+ @Schema(description = "是否已被业务使用", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean used;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/vouchertemplate/FmsVoucherTemplateEntryVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/vouchertemplate/FmsVoucherTemplateEntryVO.java
new file mode 100644
index 0000000..45c3751
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/vouchertemplate/FmsVoucherTemplateEntryVO.java
@@ -0,0 +1,69 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.vouchertemplate;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.Digits;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Schema(description = "管理后台 - FMS 凭证模板分录 VO")
+@Data
+public class FmsVoucherTemplateEntryVO {
+
+ @Schema(description = "摘要内容", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotBlank(message = "摘要不能为空")
+ @Size(max = 500, message = "摘要长度不能超过 500 个字符")
+ private String digest;
+
+ @Schema(description = "科目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "科目不能为空")
+ private Long subjectId;
+
+ @Schema(description = "数量")
+ @DecimalMin(value = "0", message = "数量不能小于 0")
+ @Digits(integer = 14, fraction = 4, message = "数量最多保留 4 位小数")
+ private BigDecimal quantity;
+
+ @Schema(description = "单价")
+ @DecimalMin(value = "0", message = "单价不能小于 0")
+ @Digits(integer = 12, fraction = 6, message = "单价最多保留 6 位小数")
+ private BigDecimal unitPrice;
+
+ @Schema(description = "借方金额")
+ @Digits(integer = 16, fraction = 2, message = "借方金额最多保留 2 位小数")
+ private BigDecimal debitAmount;
+
+ @Schema(description = "贷方金额")
+ @Digits(integer = 16, fraction = 2, message = "贷方金额最多保留 2 位小数")
+ private BigDecimal creditAmount;
+
+ @Schema(description = "辅助核算项目数组")
+ @Valid
+ private List auxiliaries;
+
+ @Schema(description = "管理后台 - FMS 凭证模板分录辅助核算项目")
+ @Data
+ public static class AuxiliaryItem {
+
+ @Schema(description = "辅助核算类型")
+ private Integer type;
+
+ @Schema(description = "辅助核算类别编号", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "辅助核算类别不能为空")
+ private Long typeId;
+
+ @Schema(description = "辅助核算项目编号", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "辅助核算项目不能为空")
+ private Long itemId;
+
+ @Schema(description = "辅助核算项目名称")
+ private String name;
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/vouchertemplate/FmsVoucherTemplateRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/vouchertemplate/FmsVoucherTemplateRespVO.java
new file mode 100644
index 0000000..736c8c4
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/vouchertemplate/FmsVoucherTemplateRespVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.vouchertemplate;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - FMS 凭证模板 Response VO")
+@Data
+public class FmsVoucherTemplateRespVO {
+
+ @Schema(description = "模板编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long accountSetId;
+
+ @Schema(description = "模板名称", requiredMode = Schema.RequiredMode.REQUIRED)
+ private String name;
+
+ @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Long categoryId;
+
+ @Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED)
+ private String categoryName;
+
+ @Schema(description = "凭证模板分录数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List entries;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/vouchertemplate/FmsVoucherTemplateSaveReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/vouchertemplate/FmsVoucherTemplateSaveReqVO.java
new file mode 100644
index 0000000..1c53561
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/vouchertemplate/FmsVoucherTemplateSaveReqVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.vouchertemplate;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - FMS 凭证模板保存 Request VO")
+@Data
+public class FmsVoucherTemplateSaveReqVO {
+
+ @Schema(description = "模板编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "账套编号不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "模板名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "办公用品采购")
+ @NotBlank(message = "模板名称不能为空")
+ @Size(max = 255, message = "模板名称长度不能超过 255 个字符")
+ private String name;
+
+ @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "模板分类不能为空")
+ private Long categoryId;
+
+ @Schema(description = "凭证模板分录数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ @Valid
+ @NotEmpty(message = "凭证模板分录不能为空")
+ @Size(min = 2, message = "凭证模板至少需要两条分录")
+ private List entries;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/vouchertemplatecategory/FmsVoucherTemplateCategoryRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/vouchertemplatecategory/FmsVoucherTemplateCategoryRespVO.java
new file mode 100644
index 0000000..c82bac6
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/vouchertemplatecategory/FmsVoucherTemplateCategoryRespVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.vouchertemplatecategory;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - FMS 凭证模板分类 Response VO")
+@Data
+public class FmsVoucherTemplateCategoryRespVO {
+
+ @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long accountSetId;
+
+ @Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "日常收支")
+ private String name;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/vouchertemplatecategory/FmsVoucherTemplateCategorySaveReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/vouchertemplatecategory/FmsVoucherTemplateCategorySaveReqVO.java
new file mode 100644
index 0000000..73777fd
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/vouchertemplatecategory/FmsVoucherTemplateCategorySaveReqVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.vouchertemplatecategory;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Schema(description = "管理后台 - FMS 凭证模板分类保存 Request VO")
+@Data
+public class FmsVoucherTemplateCategorySaveReqVO {
+
+ @Schema(description = "分类编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "账套编号不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "日常收支")
+ @NotBlank(message = "分类名称不能为空")
+ @Size(max = 255, message = "分类名称长度不能超过 255 个字符")
+ private String name;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/voucherword/FmsVoucherWordRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/voucherword/FmsVoucherWordRespVO.java
new file mode 100644
index 0000000..49fcad9
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/voucherword/FmsVoucherWordRespVO.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.voucherword;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * FMS 凭证字 Response VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 凭证字 Response VO")
+@Data
+public class FmsVoucherWordRespVO {
+
+ @Schema(description = "凭证字编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "凭证字", requiredMode = Schema.RequiredMode.REQUIRED, example = "记")
+ private String name;
+
+ @Schema(description = "打印标题", example = "记账凭证")
+ private String printTitle;
+
+ @Schema(description = "是否默认凭证字", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean defaultStatus;
+
+ @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Integer sort;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long accountSetId;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/voucherword/FmsVoucherWordSaveReqVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/voucherword/FmsVoucherWordSaveReqVO.java
new file mode 100644
index 0000000..1753ddf
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/config/vo/voucherword/FmsVoucherWordSaveReqVO.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.fms.controller.admin.config.vo.voucherword;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+/**
+ * FMS 凭证字保存 Request VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - FMS 凭证字保存 Request VO")
+@Data
+public class FmsVoucherWordSaveReqVO {
+
+ @Schema(description = "凭证字编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "账套编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "账套编号不能为空")
+ private Long accountSetId;
+
+ @Schema(description = "凭证字", requiredMode = Schema.RequiredMode.REQUIRED, example = "记")
+ @NotBlank(message = "凭证字不能为空")
+ @Size(max = 255, message = "凭证字长度不能超过 255 个字符")
+ private String name;
+
+ @Schema(description = "打印标题", example = "记账凭证")
+ @Size(max = 255, message = "打印标题长度不能超过 255 个字符")
+ private String printTitle;
+
+ @Schema(description = "是否默认凭证字", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "是否默认凭证字不能为空")
+ private Boolean defaultStatus;
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/home/FmsHomeController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/home/FmsHomeController.java
new file mode 100644
index 0000000..45322b5
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/home/FmsHomeController.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.fms.controller.admin.home;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.fms.controller.admin.home.vo.FmsHomeMetricDetailRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.home.vo.FmsHomeRespVO;
+import cn.iocoder.yudao.module.fms.service.home.FmsHomeService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - FMS 首页")
+@RestController
+@RequestMapping("/fms/home")
+@Validated
+public class FmsHomeController {
+
+ @Resource
+ private FmsHomeService homeService;
+
+ @GetMapping("/get")
+ @Operation(summary = "获得首页")
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('fms:home:query')")
+ public CommonResult getHome(@RequestParam("accountSetId") @NotNull Long accountSetId) {
+ return success(homeService.getHome(accountSetId, getLoginUserId()));
+ }
+
+ @GetMapping("/metric-detail")
+ @Operation(summary = "获得财务指标明细")
+ @Parameters({
+ @Parameter(name = "accountSetId", description = "账套编号", required = true, example = "1024"),
+ @Parameter(name = "metricKey", description = "财务指标标识", required = true, example = "income")
+ })
+ @PreAuthorize("@ss.hasPermission('fms:home:query')")
+ public CommonResult getMetricDetail(
+ @RequestParam("accountSetId") @NotNull Long accountSetId,
+ @RequestParam("metricKey") @NotBlank String metricKey) {
+ return success(homeService.getMetricDetail(accountSetId, metricKey, getLoginUserId()));
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/home/vo/FmsHomeMetricDetailRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/home/vo/FmsHomeMetricDetailRespVO.java
new file mode 100644
index 0000000..fdc7e11
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/home/vo/FmsHomeMetricDetailRespVO.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.fms.controller.admin.home.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Schema(description = "管理后台 - FMS 首页财务指标明细 Response VO")
+@Data
+public class FmsHomeMetricDetailRespVO {
+
+ @Schema(description = "指标标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "income")
+ private String key;
+
+ @Schema(description = "指标名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "收入")
+ private String name;
+
+ @Schema(description = "财务指标趋势数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List trends;
+
+ @Schema(description = "当期科目构成数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List structure;
+
+ @Schema(description = "管理后台 - FMS 首页财务指标趋势 Response VO")
+ @Data
+ public static class Trend {
+
+ @Schema(description = "会计期间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-08")
+ private String month;
+
+ @Schema(description = "指标金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000.00")
+ private BigDecimal amount;
+
+ }
+
+ @Schema(description = "管理后台 - FMS 首页财务指标科目构成 Response VO")
+ @Data
+ public static class Structure {
+
+ @Schema(description = "科目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long subjectId;
+
+ @Schema(description = "科目编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "5001")
+ private String subjectCode;
+
+ @Schema(description = "科目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "主营业务收入")
+ private String subjectName;
+
+ @Schema(description = "科目金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "8000.00")
+ private BigDecimal amount;
+
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/home/vo/FmsHomeRespVO.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/home/vo/FmsHomeRespVO.java
new file mode 100644
index 0000000..fc0a97c
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/home/vo/FmsHomeRespVO.java
@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.fms.controller.admin.home.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Schema(description = "管理后台 - FMS 首页 Response VO")
+@Data
+public class FmsHomeRespVO {
+
+ @Schema(description = "当前会计期间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-08")
+ private String currentMonth;
+
+ @Schema(description = "当期财务指标数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List metrics;
+
+ @Schema(description = "财务指标趋势数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List trends;
+
+ @Schema(description = "管理后台 - FMS 首页财务指标 Response VO")
+ @Data
+ public static class Metric {
+
+ @Schema(description = "指标标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "income")
+ private String key;
+
+ @Schema(description = "指标名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "收入")
+ private String name;
+
+ @Schema(description = "指标金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000.00")
+ private BigDecimal amount;
+
+ }
+
+ @Schema(description = "管理后台 - FMS 首页财务指标趋势 Response VO")
+ @Data
+ public static class Trend {
+
+ @Schema(description = "会计期间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-08")
+ private String month;
+
+ @Schema(description = "动态财务指标数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List metrics;
+
+ @Schema(description = "收入", requiredMode = Schema.RequiredMode.REQUIRED)
+ private BigDecimal income;
+
+ @Schema(description = "成本", requiredMode = Schema.RequiredMode.REQUIRED)
+ private BigDecimal operatingCost;
+
+ @Schema(description = "利润", requiredMode = Schema.RequiredMode.REQUIRED)
+ private BigDecimal profit;
+
+ @Schema(description = "费用", requiredMode = Schema.RequiredMode.REQUIRED)
+ private BigDecimal expense;
+
+ @Schema(description = "其他", requiredMode = Schema.RequiredMode.REQUIRED)
+ private BigDecimal other;
+
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/ledger/FmsLedgerController.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/ledger/FmsLedgerController.java
new file mode 100644
index 0000000..5b606e4
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/ledger/FmsLedgerController.java
@@ -0,0 +1,236 @@
+package cn.iocoder.yudao.module.fms.controller.admin.ledger;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.fms.controller.admin.config.vo.subject.FmsSubjectRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.FmsLedgerAuxiliaryListReqVO;
+import cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.FmsLedgerListReqVO;
+import cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.auxiliarybalance.FmsLedgerAuxiliaryBalanceRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.auxiliarydetail.FmsLedgerAuxiliaryDetailRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.detail.FmsLedgerDetailExportRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.detail.FmsLedgerDetailRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.general.FmsLedgerGeneralRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.multicolumn.FmsLedgerMultiColumnRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.quantitydetail.FmsLedgerQuantityDetailRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.quantitygeneral.FmsLedgerQuantityGeneralRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.subjectbalance.FmsLedgerSubjectBalanceRespVO;
+import cn.iocoder.yudao.module.fms.service.ledger.FmsLedgerService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - FMS 账簿")
+@RestController
+@RequestMapping("/fms/ledger")
+@Validated
+public class FmsLedgerController {
+
+ @Resource
+ private FmsLedgerService ledgerService;
+
+ // ==================== 明细账 ====================
+
+ @GetMapping("/detail/list")
+ @Operation(summary = "获得明细账")
+ @PreAuthorize("@ss.hasPermission('fms:ledger:detail:query')")
+ public CommonResult> getDetailList(@Valid FmsLedgerListReqVO listReqVO) {
+ return success(ledgerService.getDetailList(listReqVO, getLoginUserId()));
+ }
+
+ @GetMapping("/detail/subject-list")
+ @Operation(summary = "获得指定期间有发生额的明细账科目")
+ public CommonResult> getDetailSubjectList(@Valid FmsLedgerListReqVO listReqVO) {
+ // TODO @AI:先查询出来,list 变量;然后在去 convert;字段不多,倒是可以考虑 beanutils 去 copy 下?
+ return success(convertList(ledgerService.getDetailSubjectList(listReqVO, getLoginUserId()), subject ->
+ new FmsSubjectRespVO().setId(subject.getId()).setParentId(subject.getParentId())
+ .setCode(subject.getCode()).setName(subject.getName()).setType(subject.getType())
+ .setBalanceDirection(subject.getBalanceDirection()).setStatus(subject.getStatus())
+ .setLevel(subject.getLevel()).setAuxiliaryTypeIds(subject.getAuxiliaryTypeIds())
+ .setQuantityAccounting(subject.getQuantityAccounting()).setQuantityUnit(subject.getQuantityUnit())));
+ }
+
+ @GetMapping("/detail/export-excel")
+ @Operation(summary = "导出明细账")
+ @PreAuthorize("@ss.hasPermission('fms:ledger:detail:export')")
+ @ApiAccessLog(operateType = OperateTypeEnum.EXPORT)
+ public void exportDetail(@Valid FmsLedgerListReqVO listReqVO, HttpServletResponse response) throws IOException {
+ List rows = convertList(
+ ledgerService.getDetailList(listReqVO, getLoginUserId()), item ->
+ BeanUtils.toBean(item, FmsLedgerDetailExportRespVO.class)
+ .setSubject(item.getSubjectCode() + " " + item.getSubjectName()));
+ ExcelUtils.write(response, "明细账.xls", "明细账", FmsLedgerDetailExportRespVO.class, rows);
+ }
+
+ // ==================== 总账 ====================
+
+ @GetMapping("/general/list")
+ @Operation(summary = "获得总账")
+ @PreAuthorize("@ss.hasPermission('fms:ledger:general:query')")
+ public CommonResult> getGeneralList(@Valid FmsLedgerListReqVO listReqVO) {
+ return success(ledgerService.getGeneralList(listReqVO, getLoginUserId()));
+ }
+
+ @GetMapping("/general/export-excel")
+ @Operation(summary = "导出总账")
+ @PreAuthorize("@ss.hasPermission('fms:ledger:general:export')")
+ @ApiAccessLog(operateType = OperateTypeEnum.EXPORT)
+ public void exportGeneral(@Valid FmsLedgerListReqVO listReqVO, HttpServletResponse response) throws IOException {
+ ExcelUtils.write(response, "总账.xls", "总账", FmsLedgerGeneralRespVO.class,
+ ledgerService.getGeneralList(listReqVO, getLoginUserId()));
+ }
+
+ // ==================== 科目余额表 ====================
+
+ @GetMapping("/subject-balance/list")
+ @Operation(summary = "获得科目余额表")
+ @PreAuthorize("@ss.hasPermission('fms:ledger:subject-balance:query')")
+ public CommonResult> getSubjectBalanceList(@Valid FmsLedgerListReqVO listReqVO) {
+ return success(ledgerService.getSubjectBalanceList(listReqVO, getLoginUserId()));
+ }
+
+ @GetMapping("/subject-balance/export-excel")
+ @Operation(summary = "导出科目余额表")
+ @PreAuthorize("@ss.hasPermission('fms:ledger:subject-balance:export')")
+ @ApiAccessLog(operateType = OperateTypeEnum.EXPORT)
+ public void exportSubjectBalance(@Valid FmsLedgerListReqVO listReqVO, HttpServletResponse response) throws IOException {
+ ExcelUtils.write(response, "科目余额表.xls", "科目余额表", FmsLedgerSubjectBalanceRespVO.class,
+ flattenSubjectBalanceList(ledgerService.getSubjectBalanceList(listReqVO, getLoginUserId())));
+ }
+
+ // ==================== 多栏账 ====================
+
+ @GetMapping("/multi-column/list")
+ @Operation(summary = "获得多栏账")
+ @PreAuthorize("@ss.hasPermission('fms:ledger:multi-column:query')")
+ public CommonResult getMultiColumn(@Valid FmsLedgerListReqVO listReqVO) {
+ return success(ledgerService.getMultiColumn(listReqVO, getLoginUserId()));
+ }
+
+ @GetMapping("/multi-column/export-excel")
+ @Operation(summary = "导出多栏账")
+ @PreAuthorize("@ss.hasPermission('fms:ledger:multi-column:export')")
+ @ApiAccessLog(operateType = OperateTypeEnum.EXPORT)
+ public void exportMultiColumn(@Valid FmsLedgerListReqVO listReqVO, HttpServletResponse response) throws IOException {
+ FmsLedgerMultiColumnRespVO result = ledgerService.getMultiColumn(listReqVO, getLoginUserId());
+ ExcelUtils.write(response, "多栏账.xls", "多栏账",
+ FmsLedgerMultiColumnExcelHelper.buildHead(result),
+ FmsLedgerMultiColumnExcelHelper.buildData(result));
+ }
+
+ // ==================== 核算项目明细账 ====================
+
+ @GetMapping("/auxiliary-detail/list")
+ @Operation(summary = "获得核算项目明细账")
+ @PreAuthorize("@ss.hasPermission('fms:ledger:detail:query')")
+ public CommonResult> getAuxiliaryDetailList(@Valid FmsLedgerAuxiliaryListReqVO listReqVO) {
+ return success(ledgerService.getAuxiliaryDetailList(listReqVO, getLoginUserId()));
+ }
+
+ @GetMapping("/auxiliary-detail/export-excel")
+ @Operation(summary = "导出核算项目明细账")
+ @PreAuthorize("@ss.hasPermission('fms:ledger:detail:export')")
+ @ApiAccessLog(operateType = OperateTypeEnum.EXPORT)
+ public void exportAuxiliaryDetail(@Valid FmsLedgerAuxiliaryListReqVO listReqVO, HttpServletResponse response) throws IOException {
+ ExcelUtils.write(response, "核算项目明细账.xls", "核算项目明细账",
+ FmsLedgerAuxiliaryDetailRespVO.class,
+ ledgerService.getAuxiliaryDetailList(listReqVO, getLoginUserId()));
+ }
+
+ // ==================== 核算项目余额表 ====================
+
+ @GetMapping("/auxiliary-balance/list")
+ @Operation(summary = "获得核算项目余额表")
+ @PreAuthorize("@ss.hasPermission('fms:ledger:subject-balance:query')")
+ public CommonResult> getAuxiliaryBalanceList(@Valid FmsLedgerAuxiliaryListReqVO listReqVO) {
+ return success(ledgerService.getAuxiliaryBalanceList(listReqVO, getLoginUserId()));
+ }
+
+ @GetMapping("/auxiliary-balance/export-excel")
+ @Operation(summary = "导出核算项目余额表")
+ @PreAuthorize("@ss.hasPermission('fms:ledger:subject-balance:export')")
+ @ApiAccessLog(operateType = OperateTypeEnum.EXPORT)
+ public void exportAuxiliaryBalance(@Valid FmsLedgerAuxiliaryListReqVO listReqVO, HttpServletResponse response) throws IOException {
+ ExcelUtils.write(response, "核算项目余额表.xls", "核算项目余额表",
+ FmsLedgerAuxiliaryBalanceRespVO.class,
+ ledgerService.getAuxiliaryBalanceList(listReqVO, getLoginUserId()));
+ }
+
+ // ==================== 数量金额明细账 ====================
+
+ @GetMapping("/quantity-detail/list")
+ @Operation(summary = "获得数量金额明细账")
+ @PreAuthorize("@ss.hasPermission('fms:ledger:detail:query')")
+ public CommonResult> getQuantityDetailList(@Valid FmsLedgerListReqVO listReqVO) {
+ return success(ledgerService.getQuantityDetailList(listReqVO, getLoginUserId()));
+ }
+
+ @GetMapping("/quantity-detail/export-excel")
+ @Operation(summary = "导出数量金额明细账")
+ @PreAuthorize("@ss.hasPermission('fms:ledger:detail:export')")
+ @ApiAccessLog(operateType = OperateTypeEnum.EXPORT)
+ public void exportQuantityDetail(@Valid FmsLedgerListReqVO listReqVO, HttpServletResponse response) throws IOException {
+ ExcelUtils.write(response, "数量金额明细账.xls", "数量金额明细账",
+ FmsLedgerQuantityDetailRespVO.class,
+ ledgerService.getQuantityDetailList(listReqVO, getLoginUserId()));
+ }
+
+ // ==================== 数量金额总账 ====================
+
+ @GetMapping("/quantity-general/list")
+ @Operation(summary = "获得数量金额总账")
+ @PreAuthorize("@ss.hasPermission('fms:ledger:general:query')")
+ public CommonResult> getQuantityGeneralList(@Valid FmsLedgerListReqVO listReqVO) {
+ return success(ledgerService.getQuantityGeneralList(listReqVO, getLoginUserId()));
+ }
+
+ @GetMapping("/quantity-general/export-excel")
+ @Operation(summary = "导出数量金额总账")
+ @PreAuthorize("@ss.hasPermission('fms:ledger:general:export')")
+ @ApiAccessLog(operateType = OperateTypeEnum.EXPORT)
+ public void exportQuantityGeneral(@Valid FmsLedgerListReqVO listReqVO, HttpServletResponse response) throws IOException {
+ ExcelUtils.write(response, "数量金额总账.xls", "数量金额总账",
+ FmsLedgerQuantityGeneralRespVO.class,
+ flattenQuantityGeneralList(ledgerService.getQuantityGeneralList(
+ listReqVO, getLoginUserId())));
+ }
+
+ private static List flattenSubjectBalanceList(List list) {
+ List result = new ArrayList<>();
+ list.forEach(item -> {
+ result.add(item);
+ result.addAll(flattenSubjectBalanceList(item.getChildren()));
+ });
+ return result;
+ }
+
+ private static List flattenQuantityGeneralList(List list) {
+ List result = new ArrayList<>();
+ list.forEach(item -> {
+ if (Boolean.TRUE.equals(item.getQuantityAccounting()) || CollUtil.isNotEmpty(item.getChildren())) {
+ result.add(item);
+ }
+ result.addAll(flattenQuantityGeneralList(item.getChildren()));
+ });
+ return result;
+ }
+
+}
diff --git a/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/ledger/FmsLedgerMultiColumnExcelHelper.java b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/ledger/FmsLedgerMultiColumnExcelHelper.java
new file mode 100644
index 0000000..79ec67b
--- /dev/null
+++ b/yudao-module-fms/src/main/java/cn/iocoder/yudao/module/fms/controller/admin/ledger/FmsLedgerMultiColumnExcelHelper.java
@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.module.fms.controller.admin.ledger;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.detail.FmsLedgerDetailRespVO;
+import cn.iocoder.yudao.module.fms.controller.admin.ledger.vo.multicolumn.FmsLedgerMultiColumnRespVO;
+import cn.iocoder.yudao.module.fms.enums.common.FmsDebitCreditDirectionEnum;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
+
+/**
+ * FMS 多栏账 Excel 构建工具
+ *
+ * @author 芋道源码
+ */
+public class FmsLedgerMultiColumnExcelHelper {
+
+ private FmsLedgerMultiColumnExcelHelper() {
+ }
+
+ /**
+ * 构建多栏账动态表头
+ *
+ * @param result 多栏账结果
+ * @return 动态表头
+ */
+ public static List> buildHead(FmsLedgerMultiColumnRespVO result) {
+ List> head = new ArrayList<>();
+ head.add(Arrays.asList("日期", "日期"));
+ head.add(Arrays.asList("凭证字号", "凭证字号"));
+ head.add(Arrays.asList("摘要", "摘要"));
+ head.add(Arrays.asList("发生额", "借方"));
+ head.add(Arrays.asList("发生额", "贷方"));
+ head.add(Arrays.asList("余额", "方向"));
+ head.add(Arrays.asList("余额", "金额"));
+ for (FmsLedgerMultiColumnRespVO.Column column : getColumns(result,
+ FmsDebitCreditDirectionEnum.DEBIT.getType())) {
+ head.add(Arrays.asList(FmsDebitCreditDirectionEnum.DEBIT.getName(),
+ column.getSubjectCode() + "/" + column.getSubjectName()));
+ }
+ for (FmsLedgerMultiColumnRespVO.Column column : getColumns(result,
+ FmsDebitCreditDirectionEnum.CREDIT.getType())) {
+ head.add(Arrays.asList(FmsDebitCreditDirectionEnum.CREDIT.getName(),
+ column.getSubjectCode() + "/" + column.getSubjectName()));
+ }
+ return head;
+ }
+
+ /**
+ * 构建多栏账动态数据
+ *
+ * @param result 多栏账结果
+ * @return 动态数据
+ */
+ public static List> buildData(FmsLedgerMultiColumnRespVO result) {
+ List debitColumns = getColumns(
+ result, FmsDebitCreditDirectionEnum.DEBIT.getType());
+ List creditColumns = getColumns(
+ result, FmsDebitCreditDirectionEnum.CREDIT.getType());
+ List> data = new ArrayList<>();
+ for (FmsLedgerDetailRespVO row : CollUtil.emptyIfNull(result.getRows())) {
+ List