11 小时以前 c15e67c83394c1734eb4e9802d8f343c6076efc1
Merge branch 'dev_New_pro' into dev_宁夏_英泽防锈

# Conflicts:
# src/main/resources/application-dev.yml
已添加50个文件
已重命名11个文件
已修改97个文件
已删除23个文件
6515 ■■■■ 文件已修改
doc/20260511_设备保养定时任务设备项目字段前端联调说明.md 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260512_AccountSubject树形改造前端修改文档.md 185 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260512_add_parent_id_to_account_subject.sql 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260512_create_financial_management_tables.sql 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260512_财务管理模块前端联调文档.md 288 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/PurchaseInboundDto.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/PurchaseReturnDto.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/SalesOutboundDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/SalesReturnDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/financial/FinDetailLedgerQueryDto.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/financial/FinFixedAssetDto.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/financial/FinIdBatchDto.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/financial/FinIntangibleAssetDto.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/financial/FinLedgerQueryDto.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/financial/FinVoucherDto.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/financial/FinVoucherEntryDto.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/financial/FinVoucherPageDto.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/financial/FinVoucherStatusDto.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/AccountSubjectVo.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/PurchaseInboundVo.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/PurchaseReturnVo.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/SalesOutboundVo.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/financial/FinLedgerEntryRecordVo.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/financial/FinLedgerRowVo.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/financial/FinVoucherDetailVo.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/AccounPurchaseController.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/AccountSalesController.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/AccountSubjectController.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/financial/FinFixedAssetController.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/financial/FinIntangibleAssetController.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/financial/FinLedgerController.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/financial/FinVoucherController.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/mapper/AccountSubjectMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/mapper/financial/FinFixedAssetMapper.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/mapper/financial/FinIntangibleAssetMapper.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/mapper/financial/FinVoucherEntryMapper.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/mapper/financial/FinVoucherMapper.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/AccountSubject.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/financial/FinFixedAsset.java 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/financial/FinIntangibleAsset.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/financial/FinVoucher.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/financial/FinVoucherEntry.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/AccountPurchaseService.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/AccountSalesService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/AccountSubjectService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/financial/FinFixedAssetService.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/financial/FinIntangibleAssetService.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/financial/FinLedgerService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/financial/FinVoucherService.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/AccountPurchaseServiceImpl.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/AccountSalesServiceImpl.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/AccountSubjectServiceImpl.java 362 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/financial/FinFixedAssetServiceImpl.java 231 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/financial/FinIntangibleAssetServiceImpl.java 250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/financial/FinLedgerServiceImpl.java 206 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/financial/FinVoucherServiceImpl.java 299 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/CustomerController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/StorageBlobVO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/ProductModelMapper.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/ProductModel.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/utils/FileUtil.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/StockInUnQualifiedRecordTypeEnum.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/StockOutUnQualifiedRecordTypeEnum.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskJob.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/controller/OaProjectController.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/controller/OaProjectPhaseController.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/controller/OaProjectPhaseTaskController.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/dto/OaProjectDto.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/dto/OaProjectPhaseDto.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/mapper/OaProjectMapper.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/mapper/OaProjectPhaseMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/mapper/OaProjectPhaseTaskMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/pojo/OaProject.java 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/pojo/OaProjectPhase.java 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/pojo/OaProjectPhaseTask.java 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/service/OaProjectPhaseService.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/service/OaProjectPhaseTaskService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/service/OaProjectService.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/service/impl/OaProjectPhaseServiceImpl.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/service/impl/OaProjectPhaseTaskServiceImpl.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/service/impl/OaProjectServiceImpl.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/dto/Details.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/dto/InventoryInformationDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementAddDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementManagementUpdateDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementPageDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementPageDtoCopy.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementRecordOutAdd.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementRecordOutPageDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementUpdateDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/dto/ReturnManagementDto.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/dto/ReturnSaleProductDto.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/vo/ShippingInfoVo.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/vo/ShippingProductVo.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementExceptionRecordController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordController.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordOutController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/ReturnManagementController.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/dto/ReturnSaleProductDto.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementRecordMapper.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementRecordOutMapper.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/mapper/ReturnManagementMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/mapper/ReturnSaleProductMapper.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/pojo/ReturnManagement.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/pojo/ReturnSaleProduct.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordOutService.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/ReturnManagementService.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/ReturnSaleProductService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordOutServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnSaleProductServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionAccount.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionProductInput.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionProductOutput.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionBomStructureServiceImpl.java 181 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java 51 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/service/impl/PlanServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/service/impl/handle/InfoStageHandleService.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/PurchaseReturnOrdersController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/PurchaseReturnOrderProductsDto.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/SimpleReturnOrderGroupDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/mapper/PurchaseReturnOrdersMapper.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/PurchaseReturnOrderProducts.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/PurchaseReturnOrdersService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseReturnOrdersServiceImpl.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/vo/PurchaseReturnDetailsVo.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/vo/PurchaseReturnOrderProductsDetailVo.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/vo/PurchaseStockInProductVo.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/ShippingInfoMapper.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ShippingInfoService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java 164 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockUninventoryController.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/mapper/StockInRecordMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/mapper/StockOutRecordMapper.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/vo/TechnologyBomStructureVo.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/controller/TechnologyBomController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/TechnologyBomService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyBomServiceImpl.java 125 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/mapper/DocumentationMapper.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/AccountSubjectMapper.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/SalesRefundAmountOrderMapper.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/financial/FinVoucherEntryMapper.xml 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/oA/OaProjectMapper.xml 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/oA/OaProjectPhaseMapper.xml 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/oA/OaProjectPhaseTaskMapper.xml 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/procurementrecord/ProcurementRecordMapper.xml 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/procurementrecord/ProcurementRecordOutMapper.xml 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/PurchaseReturnOrderProductsMapper.xml 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/ShippingInfoMapper.xml 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockInRecordMapper.xml 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockInventoryMapper.xml 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockOutRecordMapper.xml 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyBomStructureMapper.xml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260511_É豸±£Ñø¶¨Ê±ÈÎÎñÉ豸ÏîÄ¿×Ö¶Îǰ¶ËÁªµ÷˵Ã÷.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,111 @@
# è®¾å¤‡ä¿å…»å®šæ—¶ä»»åŠ¡è®¾å¤‡é¡¹ç›®å­—æ®µå‰ç«¯è”è°ƒè¯´æ˜Ž
> æ›´æ–°æ—¶é—´ï¼š2026-05-11
> é€‚用范围:设备保养定时任务(`maintenance_task`)与设备保养记录(`device_maintenance`)
## 1. å˜æ›´æ¦‚è¿°
本次后端变更:
1. `MaintenanceTask` æ–°å¢žå­—段 `machineryCategory`(设备项目)。
2. å®šæ—¶ä»»åŠ¡æ‰§è¡Œç”Ÿæˆè®¾å¤‡ä¿å…»è®°å½•æ—¶ï¼Œä¼šæŠŠ `MaintenanceTask.machineryCategory` åŒæ­¥å†™å…¥ `DeviceMaintenance.machineryCategory`。
对应代码位置:
- `src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java`
- `src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskJob.java`
## 2. æ•°æ®åº“变更
需要先执行数据库变更(否则新增/查询该字段会异常):
```sql
ALTER TABLE `maintenance_task`
  ADD COLUMN `machinery_category` VARCHAR(100) NULL COMMENT '设备项目' AFTER `device_model`;
```
## 3. å­—段约定
| å­—段 | å«ä¹‰ | ç±»åž‹ | å»ºè®® |
| --- | --- | --- | --- |
| machineryCategory | è®¾å¤‡é¡¹ç›® | string | å‰ç«¯æ–°å¢ž/编辑定时任务时传值 |
说明:
- åŽç«¯å½“前未对该字段做强制非空校验,但业务上建议前端作为必填处理。
- åŽ†å² `maintenance_task` æ—§æ•°æ®è‹¥è¯¥å­—段为空,则基于旧任务生成的保养记录也会为空。
## 4. æŽ¥å£è”è°ƒ
### 4.1 æ–°å¢žå®šæ—¶ä»»åŠ¡
```http
POST /deviceMaintenanceTask/add
Content-Type: application/json
```
请求示例:
```json
{
  "taskName": "空压机A-周保养",
  "taskId": 1001,
  "deviceModel": "GA75",
  "machineryCategory": "动力设备",
  "frequencyType": "WEEKLY",
  "frequencyDetail": "MON,09:00",
  "registrantId": 1,
  "registrationDate": "2026-05-11",
  "remarks": "联调样例"
}
```
### 4.2 ä¿®æ”¹å®šæ—¶ä»»åŠ¡
```http
POST /deviceMaintenanceTask/update
Content-Type: application/json
```
请求示例(含 `id`):
```json
{
  "id": 12,
  "machineryCategory": "动力设备"
}
```
### 4.3 å®šæ—¶ä»»åŠ¡åˆ—è¡¨
```http
GET /deviceMaintenanceTask/listPage?pageNum=1&pageSize=10
```
返回记录中会包含 `machineryCategory` å­—段。
### 4.4 è®¾å¤‡ä¿å…»è®°å½•列表/详情
```http
GET /device/maintenance/page?pageNum=1&pageSize=10
GET /device/maintenance/{id}
```
返回中 `machineryCategory` æ¥æºäºŽ `device_maintenance.machinery_category`。
对于“由定时任务自动生成”的记录,该值会继承生成时对应任务的 `machineryCategory`。
## 5. å‰ç«¯æ”¹é€ ç‚¹
1. å®šæ—¶ä»»åŠ¡æ–°å¢ž/编辑表单增加“设备项目(machineryCategory)”输入项。
2. æäº¤ `/deviceMaintenanceTask/add`、`/deviceMaintenanceTask/update` æ—¶æºå¸¦ `machineryCategory`。
3. å®šæ—¶ä»»åŠ¡åˆ—è¡¨å¢žåŠ â€œè®¾å¤‡é¡¹ç›®â€åˆ—ï¼ˆå¦‚é¡µé¢æœ‰è¯¥åˆ—è¡¨ï¼‰ã€‚
4. è®¾å¤‡ä¿å…»è®°å½•列表/详情增加“设备项目”展示,空值显示 `--`。
## 6. è”调验收清单
1. æ–°å¢žä¸€ä¸ªå¸¦ `machineryCategory` çš„定时任务,保存成功。
2. æŸ¥è¯¢ `/deviceMaintenanceTask/listPage`,确认该任务返回 `machineryCategory`。
3. ç­‰å¾…一次定时触发(或将频率调到临近时间),生成保养记录。
4. æŸ¥è¯¢ `/device/maintenance/page` æˆ–详情接口,确认新记录的 `machineryCategory` ä¸Žä»»åŠ¡ä¸€è‡´ã€‚
5. ä¿®æ”¹ä»»åŠ¡ `machineryCategory` åŽå†æ¬¡è§¦å‘,确认“新生成记录”使用新值(历史记录不回写)。
doc/20260512_AccountSubjectÊ÷ÐθÄÔìǰ¶ËÐÞ¸ÄÎĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,185 @@
# AccountSubject æ ‘形改造前端修改文档
更新时间:2026-05-12
## 1. å˜æ›´èƒŒæ™¯
`AccountSubjectController` å·²æ”¹ä¸ºçˆ¶å­å±‚级递归模式,`/accountSubject/list` çŽ°åœ¨è¿”å›žæ ‘å½¢ç»“æž„ï¼ˆ`children` é€’归),不再是单纯的平铺列表。
---
## 2. åŽç«¯æŽ¥å£å˜åŒ–
### 2.1 æŸ¥è¯¢æŽ¥å£ï¼ˆå·²å˜æ›´ä¸ºæ ‘)
- URL:`GET /accountSubject/list`
- å…¥å‚:保持不变(`current,size,subjectCode,subjectName,subjectType,status`)
- å‡ºå‚:仍是分页壳(`records,total`),但 `records` å˜ä¸ºæ ‘节点数组(根节点分页,子节点递归内嵌)
示例:
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "records": [
      {
        "id": 1,
        "parentId": null,
        "subjectCode": "1002",
        "subjectName": "银行存款",
        "subjectType": "资产类",
        "balanceDirection": "借方",
        "status": 0,
        "leaf": false,
        "children": [
          {
            "id": 2,
            "parentId": 1,
            "subjectCode": "100201",
            "subjectName": "工行存款",
            "subjectType": "资产类",
            "balanceDirection": "借方",
            "status": 0,
            "leaf": true,
            "children": []
          }
        ]
      }
    ],
    "total": 1
  }
}
```
### 2.2 æ–°å¢ž/编辑接口字段变化
- `POST /accountSubject/add`
- `PUT /accountSubject/edit`
新增支持字段:
- `parentId`:父节点ID(为空表示根节点)
示例:
```json
{
  "id": 2,
  "parentId": 1,
  "subjectCode": "100201",
  "subjectName": "工行存款",
  "subjectType": "资产类",
  "balanceDirection": "借方",
  "status": 0,
  "remark": ""
}
```
### 2.3 åˆ é™¤æŽ¥å£è¡Œä¸ºå˜åŒ–
- `DELETE /accountSubject/remove/{ids}`
行为:
1. åˆ é™¤çˆ¶èŠ‚ç‚¹æ—¶ä¼šé€’å½’åˆ é™¤æ‰€æœ‰å­å­™èŠ‚ç‚¹ã€‚
2. è‹¥ä»»æ„å¾…删除节点(含子孙)已被 `fin_voucher_entry.subject_code` å¼•用,则整体删除失败。
---
## 3. å‰ç«¯æ”¹é€ æ¸…单
## 3.1 æ€»è´¦ç§‘目管理页
文件:`src/views/financialManagement/generalLedger/index.vue`
### å¿…改项
1. æ–°å¢žâ€œçˆ¶ç§‘目”选择控件(`el-cascader` æˆ– `el-tree-select`),保存时带 `parentId`。
2. åˆ—表改为树表展示(推荐 `el-table` + `row-key` + `tree-props`)。
3. æœç´¢é€»è¾‘保持不变,但要兼容 `records` ä¸ºæ ‘结构。
---
## 3.2 å‡­è¯é¡µç§‘目下拉
文件:`src/views/financialManagement/voucher/index.vue`
当前凭证分录使用 `el-select`(平铺)。
后端已返回树,需要前端扁平化后再绑定下拉。
示例(可复用):
```js
const flattenSubjectTree = (nodes, result = []) => {
  (nodes || []).forEach(node => {
    result.push({
      code: node.subjectCode,
      name: node.subjectName,
      id: node.id,
      parentId: node.parentId
    });
    if (node.children?.length) {
      flattenSubjectTree(node.children, result);
    }
  });
  return result;
};
// list æŽ¥å£è¿”回后:
const treeRecords = response.data?.records || [];
subjectList.value = flattenSubjectTree(treeRecords);
```
---
## 3.3 ç§‘目总账/明细账页级联
文件:
- `src/views/financialManagement/voucher/generalLedger.vue`
- `src/views/financialManagement/voucher/detailLedger.vue`
当前逻辑是把 `records` å¼ºåˆ¶æ˜ å°„成 `children: []`,需要删除这段“平铺改造”,直接使用后端树。
示例(可复用):
```js
const toCascaderTree = (nodes = []) =>
  nodes
    .filter(item => item.subjectCode && item.subjectName)
    .map(item => ({
      code: item.subjectCode,
      name: item.subjectName,
      children: toCascaderTree(item.children || [])
    }));
subjectOptions.value = toCascaderTree(response.data?.records || []);
```
---
## 4. å»ºè®®çš„前端字段约定
建议在前端 `form` å¢žåŠ ï¼š
- `parentId: null`
并在编辑回填时保持 `parentId`。
---
## 5. è”调注意事项
1. `/accountSubject/list` çš„ `total` æ˜¯æ ¹èŠ‚ç‚¹æ•°é‡ï¼Œä¸æ˜¯å…¨é‡èŠ‚ç‚¹æ•°ã€‚
2. è‹¥é¡µé¢ä»æŒ‰å¹³é“º `records.map(...)` å¤„理,会丢失子节点。
3. åˆ é™¤ç§‘目失败时,优先检查是否被凭证分录引用。
4. ä¿å­˜å¤±è´¥å‡ºçŽ°â€œçˆ¶ç§‘ç›®ä¸èƒ½æ˜¯å½“å‰ç§‘ç›®æˆ–å…¶å­ç§‘ç›®â€æ—¶ï¼Œéœ€è¦å‰ç«¯é™åˆ¶çˆ¶èŠ‚ç‚¹å¯é€‰èŒƒå›´ã€‚
---
## 6. æ•°æ®åº“字段要求
`account_subject` éœ€è¦åŒ…含 `parent_id` å­—段(`bigint`,可空)。
若线上库尚未添加,请先执行 DDL å†è”调。
doc/20260512_add_parent_id_to_account_subject.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,5 @@
-- account_subject å¢žåŠ çˆ¶çº§ç§‘ç›®å­—æ®µï¼ˆæ ‘å½¢ç»“æž„ï¼‰
ALTER TABLE `account_subject`
ADD COLUMN `parent_id` bigint NULL COMMENT '父科目ID(为空表示根节点)' AFTER `id`;
CREATE INDEX `idx_account_subject_parent_id` ON `account_subject` (`parent_id`);
doc/20260512_create_financial_management_tables.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,104 @@
-- è´¢åŠ¡ç®¡ç†æ¨¡å—å»ºè¡¨è„šæœ¬ï¼ˆå›ºå®šèµ„äº§/无形资产/凭证/科目账)
-- è¯´æ˜Žï¼š
-- 1) æ€»è´¦ç§‘目继续复用已有表 account_subject,不重复创建 fin_account_subject。
-- 2) é‡‘额字段统一 decimal(18,2)。
CREATE TABLE IF NOT EXISTS `fin_fixed_asset` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `asset_code` varchar(64) NOT NULL COMMENT '资产编号',
  `asset_name` varchar(128) NOT NULL COMMENT '资产名称',
  `category` varchar(64) NOT NULL COMMENT '资产类别',
  `specification` varchar(255) DEFAULT NULL COMMENT '规格型号',
  `purchase_date` date NOT NULL COMMENT '购置日期',
  `original_value` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '资产原值',
  `useful_life` int NOT NULL DEFAULT '1' COMMENT '使用年限(å¹´)',
  `residual_rate` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '残值率(%)',
  `accumulated_depreciation` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '累计折旧',
  `net_value` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '净值',
  `location` varchar(255) DEFAULT NULL COMMENT '存放地点',
  `department` varchar(128) DEFAULT NULL COMMENT '使用部门',
  `keeper` varchar(64) DEFAULT NULL COMMENT '保管人',
  `status` varchar(32) NOT NULL DEFAULT 'in_use' COMMENT '状态: in_use/idle/repair/scrapped',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `create_user` varchar(64) DEFAULT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_user` varchar(64) DEFAULT NULL COMMENT '修改人',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  `dept_id` bigint DEFAULT NULL COMMENT '部门ID',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_fin_fixed_asset_code` (`asset_code`),
  KEY `idx_fin_fixed_asset_status` (`status`),
  KEY `idx_fin_fixed_asset_category` (`category`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='固定资产';
CREATE TABLE IF NOT EXISTS `fin_intangible_asset` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `asset_code` varchar(64) NOT NULL COMMENT '资产编号',
  `asset_name` varchar(128) NOT NULL COMMENT '资产名称',
  `category` varchar(64) NOT NULL COMMENT '资产类别',
  `certificate_no` varchar(128) DEFAULT NULL COMMENT '证书编号',
  `acquisition_date` date NOT NULL COMMENT '取得日期',
  `original_value` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '资产原值',
  `amortization_period` int NOT NULL DEFAULT '1' COMMENT '摊销年限(å¹´)',
  `residual_rate` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '残值率(%)',
  `accumulated_amortization` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '累计摊销',
  `net_value` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '净值',
  `validity_date` date DEFAULT NULL COMMENT '有效期至',
  `status` varchar(32) NOT NULL DEFAULT 'in_use' COMMENT '状态: in_use/expired/amortized',
  `description` varchar(1000) DEFAULT NULL COMMENT '资产描述',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `create_user` varchar(64) DEFAULT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_user` varchar(64) DEFAULT NULL COMMENT '修改人',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  `dept_id` bigint DEFAULT NULL COMMENT '部门ID',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_fin_intangible_asset_code` (`asset_code`),
  KEY `idx_fin_intangible_asset_status` (`status`),
  KEY `idx_fin_intangible_asset_category` (`category`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='无形资产';
CREATE TABLE IF NOT EXISTS `fin_voucher` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `voucher_no` varchar(64) NOT NULL COMMENT '凭证字号',
  `voucher_date` date NOT NULL COMMENT '凭证日期',
  `summary` varchar(500) DEFAULT NULL COMMENT '摘要',
  `debit` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '借方合计',
  `credit` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '贷方合计',
  `creator` varchar(64) DEFAULT NULL COMMENT '制单人',
  `status` varchar(32) NOT NULL DEFAULT 'unposted' COMMENT '状态: unposted/posted/cancelled',
  `attachment_count` int NOT NULL DEFAULT '0' COMMENT '附件张数',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `create_user` varchar(64) DEFAULT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_user` varchar(64) DEFAULT NULL COMMENT '修改人',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  `dept_id` bigint DEFAULT NULL COMMENT '部门ID',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_fin_voucher_no` (`voucher_no`),
  KEY `idx_fin_voucher_date` (`voucher_date`),
  KEY `idx_fin_voucher_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='凭证主表';
CREATE TABLE IF NOT EXISTS `fin_voucher_entry` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `voucher_id` bigint NOT NULL COMMENT '凭证ID',
  `row_no` int NOT NULL DEFAULT '1' COMMENT '行号',
  `subject_code` varchar(64) NOT NULL COMMENT '科目编码',
  `subject_name` varchar(128) DEFAULT NULL COMMENT '科目名称',
  `summary` varchar(500) DEFAULT NULL COMMENT '摘要',
  `debit` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '借方金额',
  `credit` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '贷方金额',
  `auxiliary_type` varchar(32) DEFAULT NULL COMMENT '辅助核算类型',
  `auxiliary_id` varchar(64) DEFAULT NULL COMMENT '辅助核算对象ID',
  `auxiliary_name` varchar(128) DEFAULT NULL COMMENT '辅助核算对象名称',
  `create_user` varchar(64) DEFAULT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_user` varchar(64) DEFAULT NULL COMMENT '修改人',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  `dept_id` bigint DEFAULT NULL COMMENT '部门ID',
  PRIMARY KEY (`id`),
  KEY `idx_fin_voucher_entry_voucher` (`voucher_id`),
  KEY `idx_fin_voucher_entry_subject` (`subject_code`),
  KEY `idx_fin_voucher_entry_aux` (`auxiliary_type`, `auxiliary_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='凭证分录';
doc/20260512_²ÆÎñ¹ÜÀíÄ£¿éǰ¶ËÁªµ÷Îĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,288 @@
# è´¢åŠ¡ç®¡ç†æ¨¡å—å‰ç«¯è”è°ƒæ–‡æ¡£ï¼ˆaccount æ¨¡å—)
更新时间:2026-05-12
## 1. é€šç”¨è¯´æ˜Ž
### 1.1 å“åº”结构
成功响应:
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {}
}
```
业务校验失败(例如借贷不平衡、必填缺失)由全局异常返回:
```json
{
  "code": 500,
  "msg": "错误信息",
  "data": null
}
```
### 1.2 åˆ†é¡µç»“æž„
分页接口统一使用 MyBatis-Plus `Page`:
- è¯·æ±‚参数:`current`、`size`
- è¿”回:`data.records`、`data.total`
---
## 2. æ€»è´¦ç§‘目(已在原接口上增强校验)
接口保持不变:
- `GET /accountSubject/list`
- `POST /accountSubject/add`
- `PUT /accountSubject/edit`
- `DELETE /accountSubject/remove/{ids}`
- `POST /accountSubject/export`
新增规则:
1. `subjectCode`、`subjectName`、`subjectType` å¿…填。
2. `subjectCode` å”¯ä¸€æ ¡éªŒã€‚
3. åˆ é™¤å‰åšå¼•用校验:若被凭证分录(`fin_voucher_entry.subject_code`)引用,禁止删除。
---
## 3. å›ºå®šèµ„产
Base URL:`/financial/fixedAsset`
### 3.1 åˆ†é¡µæŸ¥è¯¢
- `GET /page`
- Query:`current,size,assetCode,assetName,category,status`
### 3.2 æ–°å¢ž
- `POST /add`
- Body(JSON):
```json
{
  "assetCode": "GD20260512001",
  "assetName": "办公电脑",
  "category": "electronic",
  "specification": "ThinkPad X1",
  "purchaseDate": "2026-05-01",
  "originalValue": 8000.00,
  "usefulLife": 5,
  "residualRate": 5.00,
  "location": "财务部",
  "department": "财务部",
  "keeper": "张三",
  "status": "in_use",
  "remark": "示例"
}
```
### 3.3 ä¿®æ”¹
- `PUT /update`
- Body:同新增,需包含 `id`
### 3.4 åˆ é™¤
- `DELETE /delete?ids=1&ids=2`
### 3.5 æŠ˜æ—§è®¡æï¼ˆæŒ‰æœˆï¼‰
- `POST /depreciate`
- Body å¯é€‰ï¼š
  - å…¨éƒ¨åœ¨ç”¨èµ„产:`{}`
  - æŒ‡å®šèµ„产:`{"ids":[1,2,3]}`
核心公式:
- `monthlyDepreciation = originalValue * (1 - residualRate/100) / (usefulLife*12)`
- `accumulatedDepreciation += monthlyDepreciation`
- `netValue = originalValue - accumulatedDepreciation`
状态建议值:
- `in_use`、`idle`、`repair`、`scrapped`
---
## 4. æ— å½¢èµ„产
Base URL:`/financial/intangibleAsset`
### 4.1 åˆ†é¡µæŸ¥è¯¢
- `GET /page`
- Query:`current,size,assetCode,assetName,category,status`
### 4.2 æ–°å¢ž
- `POST /add`
```json
{
  "assetCode": "WX20260512001",
  "assetName": "ERP软件许可",
  "category": "software",
  "certificateNo": "SW-001",
  "acquisitionDate": "2026-05-01",
  "originalValue": 50000.00,
  "amortizationPeriod": 10,
  "residualRate": 0.00,
  "validityDate": "2036-05-01",
  "status": "in_use",
  "description": "示例",
  "remark": ""
}
```
### 4.3 ä¿®æ”¹
- `PUT /update`
- Body:同新增,需包含 `id`
### 4.4 åˆ é™¤
- `DELETE /delete?ids=1&ids=2`
### 4.5 æ‘Šé”€è®¡æï¼ˆæŒ‰æœˆï¼‰
- `POST /amortize`
- Body å¯é€‰ï¼š
  - å…¨éƒ¨åœ¨ç”¨èµ„产:`{}`
  - æŒ‡å®šèµ„产:`{"ids":[1,2,3]}`
核心公式:
- `monthlyAmortization = originalValue * (1 - residualRate/100) / (amortizationPeriod*12)`
- `accumulatedAmortization += monthlyAmortization`
- `netValue = originalValue - accumulatedAmortization`
- å½“ `netValue <= 0` æ—¶ï¼š`netValue=0` ä¸” `status=amortized`
状态建议值:
- `in_use`、`expired`、`amortized`
---
## 5. å‡­è¯
Base URL:`/financial/voucher`
### 5.1 åˆ†é¡µæŸ¥è¯¢
- `GET /page`
- Query:`current,size,voucherNo,creator,status,startDate,endDate`
### 5.2 æ–°å¢ž
- `POST /add`
```json
{
  "voucherNo": "è®°-0001",
  "voucherDate": "2026-05-12",
  "summary": "销售收入",
  "creator": "张三",
  "attachmentCount": 0,
  "remark": "",
  "entries": [
    {
      "subjectCode": "1002",
      "subjectName": "银行存款",
      "summary": "收到货款",
      "debit": 1000.00,
      "credit": 0
    },
    {
      "subjectCode": "6001",
      "subjectName": "主营业务收入",
      "summary": "确认收入",
      "debit": 0,
      "credit": 1000.00
    }
  ]
}
```
### 5.3 ä¿®æ”¹
- `PUT /update`
- Body:同新增,需包含 `id`
- ä»… `unposted` çŠ¶æ€å…è®¸ä¿®æ”¹
### 5.4 è¿‡è´¦
- `POST /post`
```json
{
  "id": 1
}
```
### 5.5 ä½œåºŸ
- `POST /cancel`
```json
{
  "id": 1
}
```
### 5.6 è¯¦æƒ…
- `GET /detail/{id}`
关键校验:
1. åˆ†å½•至少一条有效行(科目不空,且借方或贷方 > 0)。
2. æ¯æ¡æœ‰æ•ˆåˆ†å½•不能借贷同时大于 0。
3. å€Ÿè´·å¹³è¡¡ï¼š`sum(debit) == sum(credit)` ä¸”都 > 0。
4. `subjectCode` å¿…须存在于 `account_subject`。
状态流转:
- `unposted -> posted`
- `unposted -> cancelled`
---
## 6. ç§‘目总账
### 6.1 æŸ¥è¯¢æŽ¥å£
- `GET /financial/ledger/general`
### 6.2 Query å‚æ•°
- `subjectCode`(必填)
- `startMonth`(YYYY-MM)
- `endMonth`(YYYY-MM)
### 6.3 è¿”回字段
- `rowType`:`opening` / `entry` / `monthly_total` / `yearly_total`
- `date`
- `voucherNo`
- `summary`
- `debit`
- `credit`
- `direction`(借/贷)
- `balance`(借正贷负)
说明:
- ç§‘目支持“指定科目及其下级(前缀匹配)”查询。
- è‡ªåŠ¨è¾“å‡ºâ€œæœŸåˆä½™é¢ / æœ¬æœˆåˆè®¡ / æœ¬å¹´ç´¯è®¡â€ã€‚
---
## 7. ç§‘目明细账
### 7.1 æŸ¥è¯¢æŽ¥å£
- `GET /financial/ledger/detail`
### 7.2 Query å‚æ•°
- `subjectCode`(必填)
- `auxiliaryType`(可选:`customer/supplier/department/employee/project`)
- `auxiliaryId`(可选)
- `startMonth`(YYYY-MM)
- `endMonth`(YYYY-MM)
### 7.3 è¿”回字段
同科目总账:
- `rowType,date,voucherNo,summary,debit,credit,direction,balance`
---
## 8. æ•°æ®åº“脚本
已提供脚本:
- `doc/20260512_create_financial_management_tables.sql`
包含:
- `fin_fixed_asset`
- `fin_intangible_asset`
- `fin_voucher`
- `fin_voucher_entry`
总账科目复用现有 `account_subject`。
src/main/java/com/ruoyi/account/bean/dto/PurchaseInboundDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.ruoyi.account.bean.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@Data
@Schema(name = "PurchaseInboundDto", description = "财务管理--采购入库台账(传参)")
public class PurchaseInboundDto {
    @Schema(description = "入库单号")
    private String inboundBatches;
    @Schema(description = "供应商")
    private String supplierName;
    @Schema(description = "开始日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date startDate;
    @Schema(description = "结束日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
}
src/main/java/com/ruoyi/account/bean/dto/PurchaseReturnDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.ruoyi.account.bean.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@Data
@Schema(name = "PurchaseReturnDto", description = "财务管理--采购退货台账(传参)")
public class PurchaseReturnDto {
    @Schema(description = "退货单号")
    private String returnNo;
    @Schema(description = "供应商")
    private String supplierName;
    @Schema(description = "开始日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date startDate;
    @Schema(description = "结束日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
}
src/main/java/com/ruoyi/account/bean/dto/SalesOutboundDto.java
@@ -3,6 +3,7 @@
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@@ -18,9 +19,11 @@
    @Schema(description = "开始日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date startDate;
    @Schema(description = "结束日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
}
src/main/java/com/ruoyi/account/bean/dto/SalesReturnDto.java
@@ -3,6 +3,7 @@
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@@ -18,9 +19,11 @@
    @Schema(description = "开始日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date startDate;
    @Schema(description = "结束日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
}
src/main/java/com/ruoyi/account/bean/dto/financial/FinDetailLedgerQueryDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package com.ruoyi.account.bean.dto.financial;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * ç§‘目明细账查询参数(含辅助核算条件)。
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class FinDetailLedgerQueryDto extends FinLedgerQueryDto {
    /**
     * è¾…助核算类型:customer/supplier/department/employee/project。
     */
    private String auxiliaryType;
    /**
     * è¾…助核算对象ID。
     */
    private String auxiliaryId;
}
src/main/java/com/ruoyi/account/bean/dto/financial/FinFixedAssetDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
package com.ruoyi.account.bean.dto.financial;
import com.ruoyi.account.pojo.financial.FinFixedAsset;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * å›ºå®šèµ„产查询与保存 DTO。
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class FinFixedAssetDto extends FinFixedAsset {
}
src/main/java/com/ruoyi/account/bean/dto/financial/FinIdBatchDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package com.ruoyi.account.bean.dto.financial;
import lombok.Data;
import java.util.List;
/**
 * æ‰¹é‡ID请求参数。
 */
@Data
public class FinIdBatchDto {
    /**
     * ID集合。
     */
    private List<Long> ids;
}
src/main/java/com/ruoyi/account/bean/dto/financial/FinIntangibleAssetDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
package com.ruoyi.account.bean.dto.financial;
import com.ruoyi.account.pojo.financial.FinIntangibleAsset;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * æ— å½¢èµ„产查询与保存 DTO。
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class FinIntangibleAssetDto extends FinIntangibleAsset {
}
src/main/java/com/ruoyi/account/bean/dto/financial/FinLedgerQueryDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
package com.ruoyi.account.bean.dto.financial;
import lombok.Data;
/**
 * ç§‘目账查询参数。
 */
@Data
public class FinLedgerQueryDto {
    /**
     * ç§‘目编码(支持末级或指定科目)。
     */
    private String subjectCode;
    /**
     * å¼€å§‹æœˆä»½ï¼Œæ ¼å¼ï¼šYYYY-MM。
     */
    private String startMonth;
    /**
     * ç»“束月份,格式:YYYY-MM。
     */
    private String endMonth;
}
src/main/java/com/ruoyi/account/bean/dto/financial/FinVoucherDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.account.bean.dto.financial;
import com.ruoyi.account.pojo.financial.FinVoucher;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
 * å‡­è¯ä¿å­˜ DTO(主表 + åˆ†å½•)。
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class FinVoucherDto extends FinVoucher {
    /**
     * å‡­è¯æ˜Žç»†åˆ†å½•。
     */
    private List<FinVoucherEntryDto> entries;
}
src/main/java/com/ruoyi/account/bean/dto/financial/FinVoucherEntryDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
package com.ruoyi.account.bean.dto.financial;
import com.ruoyi.account.pojo.financial.FinVoucherEntry;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * å‡­è¯åˆ†å½• DTO。
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class FinVoucherEntryDto extends FinVoucherEntry {
}
src/main/java/com/ruoyi/account/bean/dto/financial/FinVoucherPageDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package com.ruoyi.account.bean.dto.financial;
import lombok.Data;
import java.time.LocalDate;
/**
 * å‡­è¯åˆ†é¡µæŸ¥è¯¢å‚数。
 */
@Data
public class FinVoucherPageDto {
    /**
     * å‡­è¯å­—号(模糊匹配)。
     */
    private String voucherNo;
    /**
     * åˆ¶å•人。
     */
    private String creator;
    /**
     * å‡­è¯çŠ¶æ€ã€‚
     */
    private String status;
    /**
     * å¼€å§‹æ—¥æœŸï¼ˆå«ï¼‰ã€‚
     */
    private LocalDate startDate;
    /**
     * ç»“束日期(含)。
     */
    private LocalDate endDate;
}
src/main/java/com/ruoyi/account/bean/dto/financial/FinVoucherStatusDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package com.ruoyi.account.bean.dto.financial;
import lombok.Data;
/**
 * å‡­è¯çŠ¶æ€å˜æ›´å‚æ•°ã€‚
 */
@Data
public class FinVoucherStatusDto {
    /**
     * å‡­è¯ID。
     */
    private Long id;
}
src/main/java/com/ruoyi/account/bean/vo/AccountSubjectVo.java
@@ -3,6 +3,19 @@
import com.ruoyi.account.pojo.AccountSubject;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class AccountSubjectVo extends AccountSubject {
    /**
     * å­ç§‘目列表(递归结构)。
     */
    private List<AccountSubjectVo> children = new ArrayList<>();
    /**
     * æ˜¯å¦å¶å­èŠ‚ç‚¹ã€‚
     */
    private Boolean leaf;
}
src/main/java/com/ruoyi/account/bean/vo/PurchaseInboundVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package com.ruoyi.account.bean.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
@Schema(name = "PurchaseInboundVo", description = "财务管理--采购入库台账(返回)")
@ExcelIgnoreUnannotated
public class PurchaseInboundVo {
    @Schema(description = "入库单id")
    private Long id;
    @Schema(description = "入库单号")
    @Excel(name = "入库单号")
    private String inboundBatches;
    @Schema(description = "供应商")
    @Excel(name = "供应商")
    private String supplierName;
    @Schema(description = "入库日期")
    @Excel(name = "入库日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date InboundDate;
    @Schema(description = "产品名称")
    @Excel(name = "产品名称")
    private String productName;
    @Schema(description = "产品规格")
    @Excel(name = "产品规格")
    private String  specificationModel;
    @Schema(description = "金额")
    @Excel(name = "金额")
    private BigDecimal InboundAmount;
    @Schema(description = "采购订单号")
    @Excel(name = "采购订单号")
    private String purchaseContractNumber;
}
src/main/java/com/ruoyi/account/bean/vo/PurchaseReturnVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package com.ruoyi.account.bean.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@Schema(name = "PurchaseReturnVo", description = "财务管理--采购退货台账(返回)")
@ExcelIgnoreUnannotated
public class PurchaseReturnVo {
    @Schema(description = "退货单id")
    private Long id;
    @Excel(name = "退货单号")
    @Schema(description = "退货单号")
    private String returnNo;
    @Schema(description = "供应商")
    @Excel(name = "供应商")
    private String supplierName;
    @Schema(description = "关联入库单号")
    @Excel(name = "关联入库单号")
    private String inboundBatches;
    @Schema(description = "退货日期")
    @Excel(name = "退货日期")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime preparedAt;
    @Schema(description = "退款总额")
    @Excel(name = "退款总额")
    private BigDecimal totalAmount;
    @Schema(description = "退货方式")
    @Excel(name = "退货方式")
    private String returnType;
    @Schema(description = "采购订单号")
    @Excel(name = "采购订单号")
    private String purchaseContractNumber;
}
src/main/java/com/ruoyi/account/bean/vo/SalesOutboundVo.java
@@ -38,9 +38,9 @@
    @Excel(name = "产品规格")
    private String  specificationModel;
    @Schema(description = "出库数量")
    @Excel(name = "出库数量")
    private BigDecimal stockOutNum;
    @Schema(description = "金额")
    @Excel(name = "金额")
    private BigDecimal outboundAmount;
    @Schema(description = "发货编号")
    @Excel(name = "发货编号")
src/main/java/com/ruoyi/account/bean/vo/financial/FinLedgerEntryRecordVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
package com.ruoyi.account.bean.vo.financial;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
 * ç§‘目账基础分录查询对象(SQL映射使用)。
 */
@Data
public class FinLedgerEntryRecordVo {
    /**
     * å‡­è¯æ—¥æœŸã€‚
     */
    private LocalDate voucherDate;
    /**
     * å‡­è¯å­—号。
     */
    private String voucherNo;
    /**
     * æ‘˜è¦ã€‚
     */
    private String summary;
    /**
     * å€Ÿæ–¹é‡‘额。
     */
    private BigDecimal debit;
    /**
     * è´·æ–¹é‡‘额。
     */
    private BigDecimal credit;
    /**
     * è¡Œå·ï¼ˆæŽ’序字段)。
     */
    private Integer rowNo;
}
src/main/java/com/ruoyi/account/bean/vo/financial/FinLedgerRowVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
package com.ruoyi.account.bean.vo.financial;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
 * ç§‘目账行数据返回对象。
 */
@Data
public class FinLedgerRowVo {
    /**
     * è¡Œç±»åž‹ï¼šopening/entry/monthly_total/yearly_total。
     */
    private String rowType;
    /**
     * æ—¥æœŸã€‚
     */
    private LocalDate date;
    /**
     * å‡­è¯å­—号。
     */
    private String voucherNo;
    /**
     * æ‘˜è¦ã€‚
     */
    private String summary;
    /**
     * å€Ÿæ–¹é‡‘额。
     */
    private BigDecimal debit;
    /**
     * è´·æ–¹é‡‘额。
     */
    private BigDecimal credit;
    /**
     * ä½™é¢æ–¹å‘:借/贷。
     */
    private String direction;
    /**
     * ä½™é¢ï¼ˆå€Ÿæ­£è´·è´Ÿï¼‰ã€‚
     */
    private BigDecimal balance;
}
src/main/java/com/ruoyi/account/bean/vo/financial/FinVoucherDetailVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package com.ruoyi.account.bean.vo.financial;
import com.ruoyi.account.pojo.financial.FinVoucher;
import com.ruoyi.account.pojo.financial.FinVoucherEntry;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
 * å‡­è¯è¯¦æƒ…返回对象。
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class FinVoucherDetailVo extends FinVoucher {
    /**
     * å‡­è¯åˆ†å½•列表。
     */
    private List<FinVoucherEntry> entries;
}
src/main/java/com/ruoyi/account/controller/AccounPurchaseController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,68 @@
package com.ruoyi.account.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.PurchaseInboundDto;
import com.ruoyi.account.bean.dto.PurchaseReturnDto;
import com.ruoyi.account.bean.vo.PurchaseInboundVo;
import com.ruoyi.account.bean.vo.PurchaseReturnVo;
import com.ruoyi.account.service.AccountPurchaseService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†çš„é‡‡è´­éƒ¨åˆ† å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-07 04:45:30
 */
@RestController
@RequestMapping("/accountPurchase")
@RequiredArgsConstructor
@Tag(name = "财务管理的采购部分")
public class AccounPurchaseController {
    private final AccountPurchaseService accountPurchaseService;
    @GetMapping("/listPageAccountPurchase")
    @Log(title = "采购入库台账", businessType = BusinessType.OTHER)
    @Operation(summary = "财务管理--采购入库台账")
    public R<IPage<PurchaseInboundVo>> listPageAccountPurchase(Page page, PurchaseInboundDto purchaseInboundDto) {
        IPage<PurchaseInboundVo> listPage = accountPurchaseService.listPageAccountPurchase(page,purchaseInboundDto);
        return R.ok(listPage);
    }
    @PostMapping("/exportAccountPurchaseInbound")
    @Operation(summary = "导出采购入库文件")
    @Log(title = "导出采购入库文件", businessType = BusinessType.EXPORT)
    public void exportAccountPurchaseInbound(HttpServletResponse response, PurchaseInboundDto purchaseInboundDto) {
        accountPurchaseService.exportAccountPurchaseInbound(response,purchaseInboundDto);
    }
    @GetMapping("/listPageAccountPurchaseReturn")
    @Log(title = "采购退货台账", businessType = BusinessType.OTHER)
    @Operation(summary = "财务管理--采购退货台账")
    public R<IPage<PurchaseReturnVo>> listPageAccountPurchaseReturn(Page page, PurchaseReturnDto purchaseReturnDto) {
        IPage<PurchaseReturnVo> listPage = accountPurchaseService.listPageAccountPurchaseReturn(page,purchaseReturnDto);
        return R.ok(listPage);
    }
    @PostMapping("/exportAccountPurchaseReturn")
    @Operation(summary = "导出采购退货文件")
    @Log(title = "导出采购退货文件", businessType = BusinessType.EXPORT)
    public void exportAccountPurchaseReturn(HttpServletResponse response,PurchaseReturnDto purchaseReturnDto) {
        accountPurchaseService.exportAccountPurchaseReturn(response,purchaseReturnDto);
    }
}
src/main/java/com/ruoyi/account/controller/AccountSalesController.java
@@ -35,11 +35,11 @@
    private final AccountSalesService accountSalesService;
    @GetMapping("/listPageByOutbound")
    @GetMapping("/listPageAccountSales")
    @Log(title = "销售出库台账", businessType = BusinessType.OTHER)
    @Operation(summary = "财务管理--销售出库台账")
    public R<IPage<SalesOutboundVo>> listPageByOutbound(Page page, SalesOutboundDto salesOutboundDto) {
        IPage<SalesOutboundVo> listPage = accountSalesService.listPageByOutbound(page,salesOutboundDto);
    public R<IPage<SalesOutboundVo>> listPageAccountSales(Page page, SalesOutboundDto salesOutboundDto) {
        IPage<SalesOutboundVo> listPage = accountSalesService.listPageAccountSales(page,salesOutboundDto);
        return R.ok(listPage);
    }
@@ -50,11 +50,11 @@
        accountSalesService.exportAccountSalesOutbound(response,salesOutboundDto);
    }
    @GetMapping("/listPageByReturn")
    @GetMapping("/listPageAccountSalesReturn")
    @Log(title = "销售退货台账", businessType = BusinessType.OTHER)
    @Operation(summary = "财务管理--销售退货台账")
    public R<IPage<SalesReturnVo>> listPageBySalesReturn(Page page, SalesReturnDto salesReturnDto) {
        IPage<SalesReturnVo> listPage = accountSalesService.listPageBySalesReturn(page,salesReturnDto);
    public R<IPage<SalesReturnVo>> listPageAccountSalesReturn(Page page, SalesReturnDto salesReturnDto) {
        IPage<SalesReturnVo> listPage = accountSalesService.listPageAccountSalesReturn(page,salesReturnDto);
        return R.ok(listPage);
    }
src/main/java/com/ruoyi/account/controller/AccountSubjectController.java
@@ -33,7 +33,7 @@
    @GetMapping("/list")
    @Log(title = "总账科目数据集合", businessType = BusinessType.OTHER)
    @Operation(summary = "总账科目分页查询")
    @Operation(summary = "总账科目树形查询(递归)")
    public R<IPage<AccountSubjectVo>> AccountSubjectDtoList(Page<AccountSubjectDto> page, AccountSubjectDto accountSubjectDto) {
        IPage<AccountSubjectVo> paramList = accountSubjectService.baseList(page, accountSubjectDto);
        return R.ok(paramList);
@@ -43,21 +43,21 @@
    @Log(title = "新增总账科目", businessType = BusinessType.INSERT)
    @Operation(summary = "新增总账科目")
    public R AccountSubjectDtoAdd(@RequestBody AccountSubjectDto accountSubjectDto) {
        return R.ok(accountSubjectService.save(accountSubjectDto));
        return R.ok(accountSubjectService.saveAccountSubject(accountSubjectDto));
    }
    @PutMapping("/edit")
    @Log(title = "修改总账科目", businessType = BusinessType.UPDATE)
    @Operation(summary = "修改总账科目")
    public R AccountSubjectDtoEdit(@RequestBody AccountSubjectDto accountSubjectDto) {
        return R.ok(accountSubjectService.updateById(accountSubjectDto));
        return R.ok(accountSubjectService.updateAccountSubject(accountSubjectDto));
    }
    @DeleteMapping("/remove/{ids}")
    @Log(title = "删除总账科目", businessType = BusinessType.DELETE)
    @Operation(summary = "删除总账科目")
    public R AccountSubjectDtooRemove(@PathVariable Long[] ids) {
        return R.ok(accountSubjectService.removeBatchByIds(Arrays.asList(ids)));
        return R.ok(accountSubjectService.removeAccountSubjectByIds(Arrays.asList(ids)));
    }
    @PostMapping("/export")
src/main/java/com/ruoyi/account/controller/financial/FinFixedAssetController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
package com.ruoyi.account.controller.financial;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.financial.FinFixedAssetDto;
import com.ruoyi.account.bean.dto.financial.FinIdBatchDto;
import com.ruoyi.account.pojo.financial.FinFixedAsset;
import com.ruoyi.account.service.financial.FinFixedAssetService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
/**
 * å›ºå®šèµ„产控制器。
 */
@RestController
@RequestMapping("/financial/fixedAsset")
@RequiredArgsConstructor
@Tag(name = "财务管理-固定资产")
public class FinFixedAssetController {
    private final FinFixedAssetService finFixedAssetService;
    @GetMapping("/page")
    @Operation(summary = "固定资产分页查询")
    public R<IPage<FinFixedAsset>> page(Page<FinFixedAsset> page, FinFixedAssetDto queryDto) {
        return R.ok(finFixedAssetService.pageList(page, queryDto));
    }
    @PostMapping("/add")
    @Log(title = "固定资产", businessType = BusinessType.INSERT)
    @Operation(summary = "新增固定资产")
    public R<Boolean> add(@RequestBody FinFixedAssetDto dto) {
        return R.ok(finFixedAssetService.add(dto));
    }
    @PutMapping("/update")
    @Log(title = "固定资产", businessType = BusinessType.UPDATE)
    @Operation(summary = "修改固定资产")
    public R<Boolean> update(@RequestBody FinFixedAssetDto dto) {
        return R.ok(finFixedAssetService.update(dto));
    }
    @DeleteMapping("/delete")
    @Log(title = "固定资产", businessType = BusinessType.DELETE)
    @Operation(summary = "删除固定资产")
    public R<Boolean> delete(@RequestParam("ids") Long[] ids) {
        return R.ok(finFixedAssetService.deleteByIds(Arrays.asList(ids)));
    }
    @PostMapping("/depreciate")
    @Log(title = "固定资产折旧计提", businessType = BusinessType.UPDATE)
    @Operation(summary = "固定资产按月计提折旧")
    public R depreciate(@RequestBody(required = false) FinIdBatchDto dto) {
        return R.ok(finFixedAssetService.depreciate(dto == null ? null : dto.getIds()));
    }
}
src/main/java/com/ruoyi/account/controller/financial/FinIntangibleAssetController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
package com.ruoyi.account.controller.financial;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.financial.FinIdBatchDto;
import com.ruoyi.account.bean.dto.financial.FinIntangibleAssetDto;
import com.ruoyi.account.pojo.financial.FinIntangibleAsset;
import com.ruoyi.account.service.financial.FinIntangibleAssetService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
/**
 * æ— å½¢èµ„产控制器。
 */
@RestController
@RequestMapping("/financial/intangibleAsset")
@RequiredArgsConstructor
@Tag(name = "财务管理-无形资产")
public class FinIntangibleAssetController {
    private final FinIntangibleAssetService finIntangibleAssetService;
    @GetMapping("/page")
    @Operation(summary = "无形资产分页查询")
    public R<IPage<FinIntangibleAsset>> page(Page<FinIntangibleAsset> page, FinIntangibleAssetDto queryDto) {
        return R.ok(finIntangibleAssetService.pageList(page, queryDto));
    }
    @PostMapping("/add")
    @Log(title = "无形资产", businessType = BusinessType.INSERT)
    @Operation(summary = "新增无形资产")
    public R<Boolean> add(@RequestBody FinIntangibleAssetDto dto) {
        return R.ok(finIntangibleAssetService.add(dto));
    }
    @PutMapping("/update")
    @Log(title = "无形资产", businessType = BusinessType.UPDATE)
    @Operation(summary = "修改无形资产")
    public R<Boolean> update(@RequestBody FinIntangibleAssetDto dto) {
        return R.ok(finIntangibleAssetService.update(dto));
    }
    @DeleteMapping("/delete")
    @Log(title = "无形资产", businessType = BusinessType.DELETE)
    @Operation(summary = "删除无形资产")
    public R<Boolean> delete(@RequestParam("ids") Long[] ids) {
        return R.ok(finIntangibleAssetService.deleteByIds(Arrays.asList(ids)));
    }
    @PostMapping("/amortize")
    @Log(title = "无形资产摊销计提", businessType = BusinessType.UPDATE)
    @Operation(summary = "无形资产按月计提摊销")
    public R amortize(@RequestBody(required = false) FinIdBatchDto dto) {
        return R.ok(finIntangibleAssetService.amortize(dto == null ? null : dto.getIds()));
    }
}
src/main/java/com/ruoyi/account/controller/financial/FinLedgerController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package com.ruoyi.account.controller.financial;
import com.ruoyi.account.bean.dto.financial.FinDetailLedgerQueryDto;
import com.ruoyi.account.bean.dto.financial.FinLedgerQueryDto;
import com.ruoyi.account.bean.vo.financial.FinLedgerRowVo;
import com.ruoyi.account.service.financial.FinLedgerService;
import com.ruoyi.framework.web.domain.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * ç§‘目总账/明细账控制器。
 */
@RestController
@RequestMapping("/financial/ledger")
@RequiredArgsConstructor
@Tag(name = "财务管理-科目账")
public class FinLedgerController {
    private final FinLedgerService finLedgerService;
    @GetMapping("/general")
    @Operation(summary = "科目总账查询")
    public R<List<FinLedgerRowVo>> general(FinLedgerQueryDto queryDto) {
        return R.ok(finLedgerService.queryGeneralLedger(queryDto));
    }
    @GetMapping("/detail")
    @Operation(summary = "科目明细账查询")
    public R<List<FinLedgerRowVo>> detail(FinDetailLedgerQueryDto queryDto) {
        return R.ok(finLedgerService.queryDetailLedger(queryDto));
    }
}
src/main/java/com/ruoyi/account/controller/financial/FinVoucherController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
package com.ruoyi.account.controller.financial;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.financial.FinVoucherDto;
import com.ruoyi.account.bean.dto.financial.FinVoucherPageDto;
import com.ruoyi.account.bean.dto.financial.FinVoucherStatusDto;
import com.ruoyi.account.bean.vo.financial.FinVoucherDetailVo;
import com.ruoyi.account.pojo.financial.FinVoucher;
import com.ruoyi.account.service.financial.FinVoucherService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
 * å‡­è¯æŽ§åˆ¶å™¨ã€‚
 */
@RestController
@RequestMapping("/financial/voucher")
@RequiredArgsConstructor
@Tag(name = "财务管理-凭证")
public class FinVoucherController {
    private final FinVoucherService finVoucherService;
    @GetMapping("/page")
    @Operation(summary = "凭证分页查询")
    public R<IPage<FinVoucher>> page(Page<FinVoucher> page, FinVoucherPageDto queryDto) {
        return R.ok(finVoucherService.pageList(page, queryDto));
    }
    @PostMapping("/add")
    @Log(title = "凭证", businessType = BusinessType.INSERT)
    @Operation(summary = "新增凭证")
    public R<Boolean> add(@RequestBody FinVoucherDto dto) {
        return R.ok(finVoucherService.addVoucher(dto));
    }
    @PutMapping("/update")
    @Log(title = "凭证", businessType = BusinessType.UPDATE)
    @Operation(summary = "修改凭证")
    public R<Boolean> update(@RequestBody FinVoucherDto dto) {
        return R.ok(finVoucherService.updateVoucher(dto));
    }
    @PostMapping("/post")
    @Log(title = "凭证过账", businessType = BusinessType.UPDATE)
    @Operation(summary = "凭证过账")
    public R<Boolean> post(@RequestBody FinVoucherStatusDto dto) {
        return R.ok(finVoucherService.postVoucher(dto.getId()));
    }
    @PostMapping("/cancel")
    @Log(title = "凭证作废", businessType = BusinessType.UPDATE)
    @Operation(summary = "凭证作废")
    public R<Boolean> cancel(@RequestBody FinVoucherStatusDto dto) {
        return R.ok(finVoucherService.cancelVoucher(dto.getId()));
    }
    @GetMapping("/detail/{id}")
    @Operation(summary = "凭证详情")
    public R<FinVoucherDetailVo> detail(@PathVariable("id") Long id) {
        return R.ok(finVoucherService.detail(id));
    }
}
src/main/java/com/ruoyi/account/mapper/AccountSubjectMapper.java
@@ -3,6 +3,9 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.account.pojo.AccountSubject;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <p>
@@ -15,4 +18,6 @@
@Mapper
public interface AccountSubjectMapper extends BaseMapper<AccountSubject> {
    Long countReferencedBySubjectCodes(@Param("subjectCodes") List<String> subjectCodes);
}
src/main/java/com/ruoyi/account/mapper/financial/FinFixedAssetMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package com.ruoyi.account.mapper.financial;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.account.pojo.financial.FinFixedAsset;
import org.apache.ibatis.annotations.Mapper;
/**
 * å›ºå®šèµ„产 Mapper。
 */
@Mapper
public interface FinFixedAssetMapper extends BaseMapper<FinFixedAsset> {
}
src/main/java/com/ruoyi/account/mapper/financial/FinIntangibleAssetMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package com.ruoyi.account.mapper.financial;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.account.pojo.financial.FinIntangibleAsset;
import org.apache.ibatis.annotations.Mapper;
/**
 * æ— å½¢èµ„产 Mapper。
 */
@Mapper
public interface FinIntangibleAssetMapper extends BaseMapper<FinIntangibleAsset> {
}
src/main/java/com/ruoyi/account/mapper/financial/FinVoucherEntryMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package com.ruoyi.account.mapper.financial;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.account.bean.vo.financial.FinLedgerEntryRecordVo;
import com.ruoyi.account.pojo.financial.FinVoucherEntry;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDate;
import java.util.List;
/**
 * å‡­è¯åˆ†å½• Mapper。
 */
@Mapper
public interface FinVoucherEntryMapper extends BaseMapper<FinVoucherEntry> {
    List<FinLedgerEntryRecordVo> listPostedEntries(@Param("subjectCode") String subjectCode,
                                                   @Param("startDate") LocalDate startDate,
                                                   @Param("endDate") LocalDate endDate,
                                                   @Param("auxiliaryType") String auxiliaryType,
                                                   @Param("auxiliaryId") String auxiliaryId);
    List<FinLedgerEntryRecordVo> listPostedEntriesBefore(@Param("subjectCode") String subjectCode,
                                                         @Param("beforeDate") LocalDate beforeDate,
                                                         @Param("auxiliaryType") String auxiliaryType,
                                                         @Param("auxiliaryId") String auxiliaryId);
}
src/main/java/com/ruoyi/account/mapper/financial/FinVoucherMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package com.ruoyi.account.mapper.financial;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.account.pojo.financial.FinVoucher;
import org.apache.ibatis.annotations.Mapper;
/**
 * å‡­è¯ä¸»è¡¨ Mapper。
 */
@Mapper
public interface FinVoucherMapper extends BaseMapper<FinVoucher> {
}
src/main/java/com/ruoyi/account/pojo/AccountSubject.java
@@ -39,6 +39,12 @@
    private Long id;
    /**
     * çˆ¶ç§‘ç›®ID(为空表示根节点)
     */
    @ApiModelProperty("父科目ID(为空表示根节点)")
    private Long parentId;
    /**
     * ç§‘目编码(唯一标识)
     */
    @ApiModelProperty("科目编码(唯一标识)")
src/main/java/com/ruoyi/account/pojo/financial/FinFixedAsset.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,101 @@
package com.ruoyi.account.pojo.financial;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * å›ºå®šèµ„产实体。
 */
@Getter
@Setter
@ToString
@TableName("fin_fixed_asset")
@ApiModel(value = "FinFixedAsset对象", description = "固定资产")
public class FinFixedAsset implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    @ApiModelProperty("主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty("资产编号")
    private String assetCode;
    @ApiModelProperty("资产名称")
    private String assetName;
    @ApiModelProperty("资产类别")
    private String category;
    @ApiModelProperty("规格型号")
    private String specification;
    @ApiModelProperty("购置日期")
    private LocalDate purchaseDate;
    @ApiModelProperty("资产原值")
    private BigDecimal originalValue;
    @ApiModelProperty("使用年限(å¹´)")
    private Integer usefulLife;
    @ApiModelProperty("残值率(%)")
    private BigDecimal residualRate;
    @ApiModelProperty("累计折旧")
    private BigDecimal accumulatedDepreciation;
    @ApiModelProperty("净值")
    private BigDecimal netValue;
    @ApiModelProperty("存放地点")
    private String location;
    @ApiModelProperty("使用部门")
    private String department;
    @ApiModelProperty("保管人")
    private String keeper;
    @ApiModelProperty("状态")
    private String status;
    @ApiModelProperty("备注")
    private String remark;
    @ApiModelProperty("创建人")
    @TableField(fill = FieldFill.INSERT)
    private String createUser;
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty("修改人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updateUser;
    @ApiModelProperty("修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty("部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/account/pojo/financial/FinIntangibleAsset.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,98 @@
package com.ruoyi.account.pojo.financial;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * æ— å½¢èµ„产实体。
 */
@Getter
@Setter
@ToString
@TableName("fin_intangible_asset")
@ApiModel(value = "FinIntangibleAsset对象", description = "无形资产")
public class FinIntangibleAsset implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    @ApiModelProperty("主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty("资产编号")
    private String assetCode;
    @ApiModelProperty("资产名称")
    private String assetName;
    @ApiModelProperty("资产类别")
    private String category;
    @ApiModelProperty("证书编号")
    private String certificateNo;
    @ApiModelProperty("取得日期")
    private LocalDate acquisitionDate;
    @ApiModelProperty("资产原值")
    private BigDecimal originalValue;
    @ApiModelProperty("摊销年限(å¹´)")
    private Integer amortizationPeriod;
    @ApiModelProperty("残值率(%)")
    private BigDecimal residualRate;
    @ApiModelProperty("累计摊销")
    private BigDecimal accumulatedAmortization;
    @ApiModelProperty("净值")
    private BigDecimal netValue;
    @ApiModelProperty("有效期至")
    private LocalDate validityDate;
    @ApiModelProperty("状态")
    private String status;
    @ApiModelProperty("资产描述")
    private String description;
    @ApiModelProperty("备注")
    private String remark;
    @ApiModelProperty("创建人")
    @TableField(fill = FieldFill.INSERT)
    private String createUser;
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty("修改人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updateUser;
    @ApiModelProperty("修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty("部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/account/pojo/financial/FinVoucher.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,83 @@
package com.ruoyi.account.pojo.financial;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * å‡­è¯ä¸»è¡¨å®žä½“。
 */
@Getter
@Setter
@ToString
@TableName("fin_voucher")
@ApiModel(value = "FinVoucher对象", description = "凭证主表")
public class FinVoucher implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    @ApiModelProperty("主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty("凭证字号")
    private String voucherNo;
    @ApiModelProperty("凭证日期")
    private LocalDate voucherDate;
    @ApiModelProperty("摘要")
    private String summary;
    @ApiModelProperty("借方合计")
    private BigDecimal debit;
    @ApiModelProperty("贷方合计")
    private BigDecimal credit;
    @ApiModelProperty("制单人")
    private String creator;
    @ApiModelProperty("状态: unposted/posted/cancelled")
    private String status;
    @ApiModelProperty("附件数量")
    private Integer attachmentCount;
    @ApiModelProperty("备注")
    private String remark;
    @ApiModelProperty("创建人")
    @TableField(fill = FieldFill.INSERT)
    private String createUser;
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty("修改人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updateUser;
    @ApiModelProperty("修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty("部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/account/pojo/financial/FinVoucherEntry.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,85 @@
package com.ruoyi.account.pojo.financial;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * å‡­è¯åˆ†å½•实体。
 */
@Getter
@Setter
@ToString
@TableName("fin_voucher_entry")
@ApiModel(value = "FinVoucherEntry对象", description = "凭证分录")
public class FinVoucherEntry implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    @ApiModelProperty("主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty("凭证ID")
    private Long voucherId;
    @ApiModelProperty("行号")
    private Integer rowNo;
    @ApiModelProperty("科目编码")
    private String subjectCode;
    @ApiModelProperty("科目名称")
    private String subjectName;
    @ApiModelProperty("摘要")
    private String summary;
    @ApiModelProperty("借方金额")
    private BigDecimal debit;
    @ApiModelProperty("贷方金额")
    private BigDecimal credit;
    @ApiModelProperty("辅助核算类型")
    private String auxiliaryType;
    @ApiModelProperty("辅助核算对象ID")
    private String auxiliaryId;
    @ApiModelProperty("辅助核算对象名称")
    private String auxiliaryName;
    @ApiModelProperty("创建人")
    @TableField(fill = FieldFill.INSERT)
    private String createUser;
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty("修改人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updateUser;
    @ApiModelProperty("修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty("部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/account/service/AccountPurchaseService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package com.ruoyi.account.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.PurchaseInboundDto;
import com.ruoyi.account.bean.dto.PurchaseReturnDto;
import com.ruoyi.account.bean.vo.PurchaseInboundVo;
import com.ruoyi.account.bean.vo.PurchaseReturnVo;
import jakarta.servlet.http.HttpServletResponse;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†çš„é”€å”®éƒ¨åˆ† æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-07 04:45:30
 */
public interface AccountPurchaseService {
    IPage<PurchaseInboundVo> listPageAccountPurchase(Page page, PurchaseInboundDto purchaseInboundDto);
    void exportAccountPurchaseInbound(HttpServletResponse response, PurchaseInboundDto purchaseInboundDto);
    IPage<PurchaseReturnVo> listPageAccountPurchaseReturn(Page page, PurchaseReturnDto purchaseReturnDto);
    void exportAccountPurchaseReturn(HttpServletResponse response, PurchaseReturnDto purchaseReturnDto);
}
src/main/java/com/ruoyi/account/service/AccountSalesService.java
@@ -18,11 +18,11 @@
 */
public interface AccountSalesService  {
    IPage<SalesOutboundVo> listPageByOutbound(Page page, SalesOutboundDto salesOutboundDto);
    IPage<SalesOutboundVo> listPageAccountSales(Page page, SalesOutboundDto salesOutboundDto);
    void exportAccountSalesOutbound(HttpServletResponse response, SalesOutboundDto salesOutboundDto);
    IPage<SalesReturnVo> listPageBySalesReturn(Page page, SalesReturnDto salesReturnDto);
    IPage<SalesReturnVo> listPageAccountSalesReturn(Page page, SalesReturnDto salesReturnDto);
    void exportAccountSalesReturn(HttpServletResponse response, SalesReturnDto salesReturnDto);
}
src/main/java/com/ruoyi/account/service/AccountSubjectService.java
@@ -8,6 +8,8 @@
import com.baomidou.mybatisplus.extension.service.IService;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * <p>
 * æ€»è´¦ç§‘目表 æœåŠ¡ç±»
@@ -20,5 +22,11 @@
    IPage<AccountSubjectVo> baseList(Page<AccountSubjectDto> page, AccountSubjectDto accountSubjectDto);
    Boolean saveAccountSubject(AccountSubjectDto accountSubjectDto);
    Boolean updateAccountSubject(AccountSubjectDto accountSubjectDto);
    Boolean removeAccountSubjectByIds(List<Long> ids);
    void exportAccountSubject(HttpServletResponse response);
}
src/main/java/com/ruoyi/account/service/financial/FinFixedAssetService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package com.ruoyi.account.service.financial;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.account.bean.dto.financial.FinFixedAssetDto;
import com.ruoyi.account.pojo.financial.FinFixedAsset;
import java.util.List;
import java.util.Map;
/**
 * å›ºå®šèµ„产服务。
 */
public interface FinFixedAssetService extends IService<FinFixedAsset> {
    IPage<FinFixedAsset> pageList(Page<FinFixedAsset> page, FinFixedAssetDto queryDto);
    Boolean add(FinFixedAssetDto dto);
    Boolean update(FinFixedAssetDto dto);
    Boolean deleteByIds(List<Long> ids);
    Map<String, Object> depreciate(List<Long> ids);
}
src/main/java/com/ruoyi/account/service/financial/FinIntangibleAssetService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package com.ruoyi.account.service.financial;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.account.bean.dto.financial.FinIntangibleAssetDto;
import com.ruoyi.account.pojo.financial.FinIntangibleAsset;
import java.util.List;
import java.util.Map;
/**
 * æ— å½¢èµ„产服务。
 */
public interface FinIntangibleAssetService extends IService<FinIntangibleAsset> {
    IPage<FinIntangibleAsset> pageList(Page<FinIntangibleAsset> page, FinIntangibleAssetDto queryDto);
    Boolean add(FinIntangibleAssetDto dto);
    Boolean update(FinIntangibleAssetDto dto);
    Boolean deleteByIds(List<Long> ids);
    Map<String, Object> amortize(List<Long> ids);
}
src/main/java/com/ruoyi/account/service/financial/FinLedgerService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package com.ruoyi.account.service.financial;
import com.ruoyi.account.bean.dto.financial.FinDetailLedgerQueryDto;
import com.ruoyi.account.bean.dto.financial.FinLedgerQueryDto;
import com.ruoyi.account.bean.vo.financial.FinLedgerRowVo;
import java.util.List;
/**
 * ç§‘目账服务。
 */
public interface FinLedgerService {
    List<FinLedgerRowVo> queryGeneralLedger(FinLedgerQueryDto queryDto);
    List<FinLedgerRowVo> queryDetailLedger(FinDetailLedgerQueryDto queryDto);
}
src/main/java/com/ruoyi/account/service/financial/FinVoucherService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package com.ruoyi.account.service.financial;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.account.bean.dto.financial.FinVoucherDto;
import com.ruoyi.account.bean.dto.financial.FinVoucherPageDto;
import com.ruoyi.account.bean.vo.financial.FinVoucherDetailVo;
import com.ruoyi.account.pojo.financial.FinVoucher;
/**
 * å‡­è¯æœåŠ¡ã€‚
 */
public interface FinVoucherService extends IService<FinVoucher> {
    IPage<FinVoucher> pageList(Page<FinVoucher> page, FinVoucherPageDto queryDto);
    Boolean addVoucher(FinVoucherDto dto);
    Boolean updateVoucher(FinVoucherDto dto);
    Boolean postVoucher(Long id);
    Boolean cancelVoucher(Long id);
    FinVoucherDetailVo detail(Long id);
}
src/main/java/com/ruoyi/account/service/impl/AccountPurchaseServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package com.ruoyi.account.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.PurchaseInboundDto;
import com.ruoyi.account.bean.dto.PurchaseReturnDto;
import com.ruoyi.account.bean.vo.PurchaseInboundVo;
import com.ruoyi.account.bean.vo.PurchaseReturnVo;
import com.ruoyi.account.service.AccountPurchaseService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.purchase.mapper.PurchaseReturnOrdersMapper;
import com.ruoyi.stock.mapper.StockInRecordMapper;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†çš„é”€å”®éƒ¨åˆ† æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-07 04:45:30
 */
@Service
@RequiredArgsConstructor
public class AccountPurchaseServiceImpl implements AccountPurchaseService {
    private final StockInRecordMapper stockInRecordMapper;
    private final PurchaseReturnOrdersMapper purchaseReturnOrdersMapper;
    @Override
    public IPage<PurchaseInboundVo> listPageAccountPurchase(Page page, PurchaseInboundDto purchaseInboundDto) {
        return stockInRecordMapper.listPageAccountPurchase(page,purchaseInboundDto);
    }
    @Override
    public void exportAccountPurchaseInbound(HttpServletResponse response, PurchaseInboundDto purchaseInboundDto) {
        List<PurchaseInboundVo> list = stockInRecordMapper.listPageAccountPurchase(new Page(1,-1),purchaseInboundDto).getRecords();
        ExcelUtil<PurchaseInboundVo> util = new ExcelUtil<>(PurchaseInboundVo.class);
        util.exportExcel(response, list , "采购入库");
    }
    @Override
    public IPage<PurchaseReturnVo> listPageAccountPurchaseReturn(Page page, PurchaseReturnDto purchaseReturnDto) {
        return purchaseReturnOrdersMapper.listPageAccountPurchaseReturn(page,purchaseReturnDto);
    }
    @Override
    public void exportAccountPurchaseReturn(HttpServletResponse response, PurchaseReturnDto purchaseReturnDto) {
        List<PurchaseReturnVo> list = purchaseReturnOrdersMapper.listPageAccountPurchaseReturn(new Page(1,-1),purchaseReturnDto).getRecords();
        ExcelUtil<PurchaseReturnVo> util = new ExcelUtil<>(PurchaseReturnVo.class);
        util.exportExcel(response, list , "采购退货");
    }
}
src/main/java/com/ruoyi/account/service/impl/AccountSalesServiceImpl.java
@@ -9,7 +9,7 @@
import com.ruoyi.account.service.AccountSalesService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.procurementrecord.mapper.ReturnManagementMapper;
import com.ruoyi.sales.mapper.ShippingInfoMapper;
import com.ruoyi.stock.mapper.StockOutRecordMapper;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@@ -28,30 +28,30 @@
@RequiredArgsConstructor
public class AccountSalesServiceImpl  implements AccountSalesService {
    private final ShippingInfoMapper shippingInfoMapper;
    private final StockOutRecordMapper stockOutRecordMapper;
    private final ReturnManagementMapper returnManagementMapper;
    @Override
    public IPage<SalesOutboundVo> listPageByOutbound(Page page, SalesOutboundDto salesOutboundDto) {
        return shippingInfoMapper.listPageByOutbound(page,salesOutboundDto);
    public IPage<SalesOutboundVo> listPageAccountSales(Page page, SalesOutboundDto salesOutboundDto) {
        return stockOutRecordMapper.listPageAccountSales(page,salesOutboundDto);
    }
    @Override
    public void exportAccountSalesOutbound(HttpServletResponse response, SalesOutboundDto salesOutboundDto) {
        List<SalesOutboundVo> list = shippingInfoMapper.listPageByOutbound(new Page(1,-1),salesOutboundDto).getRecords();
        List<SalesOutboundVo> list = stockOutRecordMapper.listPageAccountSales(new Page(1,-1),salesOutboundDto).getRecords();
        ExcelUtil<SalesOutboundVo> util = new ExcelUtil<>(SalesOutboundVo.class);
        util.exportExcel(response, list , "销售出库");
    }
    @Override
    public IPage<SalesReturnVo> listPageBySalesReturn(Page page, SalesReturnDto salesReturnDto) {
        return returnManagementMapper.listPageBySalesReturn(page,salesReturnDto);
    public IPage<SalesReturnVo> listPageAccountSalesReturn(Page page, SalesReturnDto salesReturnDto) {
        return returnManagementMapper.listPageAccountSalesReturn(page,salesReturnDto);
    }
    @Override
    public void exportAccountSalesReturn(HttpServletResponse response, SalesReturnDto salesReturnDto) {
        List<SalesReturnVo> list = returnManagementMapper.listPageBySalesReturn(new Page(1,-1),salesReturnDto).getRecords();
        List<SalesReturnVo> list = returnManagementMapper.listPageAccountSalesReturn(new Page(1,-1),salesReturnDto).getRecords();
        ExcelUtil<SalesReturnVo> util = new ExcelUtil<>(SalesReturnVo.class);
        util.exportExcel(response, list , "销售退货");
    }
src/main/java/com/ruoyi/account/service/impl/AccountSubjectServiceImpl.java
@@ -10,6 +10,7 @@
import com.ruoyi.account.mapper.AccountSubjectMapper;
import com.ruoyi.account.pojo.AccountSubject;
import com.ruoyi.account.service.AccountSubjectService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import jakarta.servlet.http.HttpServletResponse;
@@ -18,7 +19,16 @@
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -37,30 +47,78 @@
    @Override
    public IPage<AccountSubjectVo> baseList(Page<AccountSubjectDto> page, AccountSubjectDto accountSubjectDto) {
        LambdaQueryWrapper<AccountSubject> queryWrapper = new LambdaQueryWrapper<>();
        if (accountSubjectDto != null && StringUtils.isNotEmpty(accountSubjectDto.getSubjectCode())) {
            queryWrapper.like(AccountSubject::getSubjectCode, accountSubjectDto.getSubjectCode());
        }
        if (accountSubjectDto != null && StringUtils.isNotEmpty(accountSubjectDto.getSubjectName())) {
            queryWrapper.like(AccountSubject::getSubjectName, accountSubjectDto.getSubjectName());
        }
        if (accountSubjectDto != null && StringUtils.isNotEmpty(accountSubjectDto.getSubjectType())) {
            queryWrapper.eq(AccountSubject::getSubjectType, accountSubjectDto.getSubjectType());
        }
        queryWrapper.orderByDesc(AccountSubject::getId);
        Page<AccountSubjectDto> requestPage = page == null ? new Page<>(1, 10) : page;
        List<AccountSubject> allSubjects = list(loadBaseQueryWrapper(accountSubjectDto));
        List<AccountSubject> filteredSubjects = applyTreeFilter(allSubjects, accountSubjectDto);
        List<AccountSubjectVo> fullTree = buildTree(filteredSubjects);
        Page<AccountSubject> entityPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
        Page<AccountSubject> paramPage = page(entityPage, queryWrapper);
        long current = requestPage.getCurrent() <= 0 ? 1 : requestPage.getCurrent();
        long size = requestPage.getSize() <= 0 ? 10 : requestPage.getSize();
        int fromIndex = (int) Math.min((current - 1) * size, fullTree.size());
        int toIndex = (int) Math.min(fromIndex + size, fullTree.size());
        List<AccountSubjectVo> pagedRoots = fromIndex >= toIndex
                ? Collections.emptyList()
                : fullTree.subList(fromIndex, toIndex);
        Page<AccountSubjectVo> resultPage = new Page<>(paramPage.getCurrent(), paramPage.getSize(), paramPage.getTotal());
        List<AccountSubjectVo> records = new ArrayList<>(paramPage.getRecords().size());
        for (AccountSubject item : paramPage.getRecords()) {
            AccountSubjectVo vo = new AccountSubjectVo();
            BeanUtils.copyProperties(item, vo);
            records.add(vo);
        }
        resultPage.setRecords(records);
        Page<AccountSubjectVo> resultPage = new Page<>(current, size, fullTree.size());
        resultPage.setRecords(pagedRoots);
        return resultPage;
    }
    @Override
    public Boolean saveAccountSubject(AccountSubjectDto accountSubjectDto) {
        validateSubjectRequiredFields(accountSubjectDto);
        validateSubjectCodeUnique(accountSubjectDto, false);
        validateParent(accountSubjectDto.getParentId(), null);
        if (accountSubjectDto.getStatus() == null) {
            accountSubjectDto.setStatus(0);
        }
        return save(accountSubjectDto);
    }
    @Override
    public Boolean updateAccountSubject(AccountSubjectDto accountSubjectDto) {
        if (accountSubjectDto == null || accountSubjectDto.getId() == null) {
            throw new ServiceException("修改失败,科目ID不能为空");
        }
        if (getById(accountSubjectDto.getId()) == null) {
            throw new ServiceException("修改失败,未找到对应科目");
        }
        validateParent(accountSubjectDto.getParentId(), accountSubjectDto.getId());
        validateSubjectRequiredFields(accountSubjectDto);
        validateSubjectCodeUnique(accountSubjectDto, true);
        return updateById(accountSubjectDto);
    }
    @Override
    public Boolean removeAccountSubjectByIds(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return true;
        }
        List<AccountSubject> allSubjects = list();
        if (allSubjects == null || allSubjects.isEmpty()) {
            return true;
        }
        Map<Long, List<Long>> childrenIdMap = buildChildrenIdMap(allSubjects);
        Set<Long> removeIds = new LinkedHashSet<>();
        for (Long id : ids) {
            collectDescendantIds(id, childrenIdMap, removeIds);
        }
        if (removeIds.isEmpty()) {
            return true;
        }
        List<String> subjectCodes = allSubjects.stream()
                .filter(subject -> removeIds.contains(subject.getId()))
                .map(AccountSubject::getSubjectCode)
                .filter(StringUtils::isNotEmpty)
                .collect(Collectors.toList());
        if (!subjectCodes.isEmpty()) {
            Long referencedCount = accountSubjectMapper.countReferencedBySubjectCodes(subjectCodes);
            if (referencedCount != null && referencedCount > 0) {
                throw new ServiceException("删除失败,科目已被凭证分录引用");
            }
        }
        return removeByIds(removeIds);
    }
    @Override
@@ -74,4 +132,266 @@
        ExcelUtil<AccountSubjectImportDto> util = new ExcelUtil<>(AccountSubjectImportDto.class);
        util.exportExcel(response, importDtos , "总账科目");
    }
    /**
     * æ ¡éªŒç§‘目必填字段,避免脏数据写入。
     */
    private void validateSubjectRequiredFields(AccountSubjectDto accountSubjectDto) {
        if (accountSubjectDto == null) {
            throw new ServiceException("总账科目数据不能为空");
        }
        if (StringUtils.isEmpty(accountSubjectDto.getSubjectCode())) {
            throw new ServiceException("科目编码不能为空");
        }
        if (StringUtils.isEmpty(accountSubjectDto.getSubjectName())) {
            throw new ServiceException("科目名称不能为空");
        }
        if (StringUtils.isEmpty(accountSubjectDto.getSubjectType())) {
            throw new ServiceException("科目类型不能为空");
        }
    }
    /**
     * æ ¡éªŒç§‘目编码唯一,新增和修改都要执行。
     */
    private void validateSubjectCodeUnique(AccountSubjectDto accountSubjectDto, boolean isUpdate) {
        LambdaQueryWrapper<AccountSubject> codeQueryWrapper = new LambdaQueryWrapper<>();
        codeQueryWrapper.eq(AccountSubject::getSubjectCode, accountSubjectDto.getSubjectCode());
        if (isUpdate) {
            codeQueryWrapper.ne(AccountSubject::getId, accountSubjectDto.getId());
        }
        AccountSubject exists = getOne(codeQueryWrapper, false);
        if (Objects.nonNull(exists)) {
            throw new ServiceException("科目编码已存在,请勿重复提交");
        }
    }
    /**
     * ä»…按通用过滤条件查询基础数据(树形过滤后续再做)。
     */
    private LambdaQueryWrapper<AccountSubject> loadBaseQueryWrapper(AccountSubjectDto accountSubjectDto) {
        LambdaQueryWrapper<AccountSubject> queryWrapper = new LambdaQueryWrapper<>();
        if (accountSubjectDto != null && accountSubjectDto.getStatus() != null) {
            queryWrapper.eq(AccountSubject::getStatus, accountSubjectDto.getStatus());
        }
        queryWrapper.orderByAsc(AccountSubject::getSubjectCode).orderByAsc(AccountSubject::getId);
        return queryWrapper;
    }
    /**
     * æ ‘形过滤:命中节点后保留其父链与子树,保证递归结构完整。
     */
    private List<AccountSubject> applyTreeFilter(List<AccountSubject> allSubjects, AccountSubjectDto queryDto) {
        if (allSubjects == null || allSubjects.isEmpty()) {
            return Collections.emptyList();
        }
        boolean hasFilter = queryDto != null && (
                StringUtils.isNotEmpty(queryDto.getSubjectCode())
                        || StringUtils.isNotEmpty(queryDto.getSubjectName())
                        || StringUtils.isNotEmpty(queryDto.getSubjectType())
        );
        if (!hasFilter) {
            return allSubjects;
        }
        Map<Long, AccountSubject> subjectMap = allSubjects.stream()
                .filter(item -> item.getId() != null)
                .collect(Collectors.toMap(AccountSubject::getId, item -> item, (a, b) -> a, LinkedHashMap::new));
        Map<Long, List<AccountSubject>> childrenMap = buildChildrenMap(allSubjects);
        Set<Long> matchedIds = new LinkedHashSet<>();
        for (AccountSubject subject : allSubjects) {
            if (subject.getId() == null) {
                continue;
            }
            if (matchesFilter(subject, queryDto)) {
                matchedIds.add(subject.getId());
            }
        }
        if (matchedIds.isEmpty()) {
            return Collections.emptyList();
        }
        Set<Long> resultIds = new LinkedHashSet<>(matchedIds);
        for (Long matchedId : matchedIds) {
            addAncestors(matchedId, subjectMap, resultIds);
            addDescendants(matchedId, childrenMap, resultIds);
        }
        return allSubjects.stream()
                .filter(item -> item.getId() != null && resultIds.contains(item.getId()))
                .collect(Collectors.toList());
    }
    private boolean matchesFilter(AccountSubject subject, AccountSubjectDto queryDto) {
        if (queryDto == null) {
            return true;
        }
        if (StringUtils.isNotEmpty(queryDto.getSubjectCode())
                && (subject.getSubjectCode() == null || !subject.getSubjectCode().contains(queryDto.getSubjectCode()))) {
            return false;
        }
        if (StringUtils.isNotEmpty(queryDto.getSubjectName())
                && (subject.getSubjectName() == null || !subject.getSubjectName().contains(queryDto.getSubjectName()))) {
            return false;
        }
        if (StringUtils.isNotEmpty(queryDto.getSubjectType())
                && !queryDto.getSubjectType().equals(subject.getSubjectType())) {
            return false;
        }
        return true;
    }
    private void addAncestors(Long subjectId, Map<Long, AccountSubject> subjectMap, Set<Long> resultIds) {
        AccountSubject current = subjectMap.get(subjectId);
        if (current == null) {
            return;
        }
        Long parentId = current.getParentId();
        while (parentId != null && parentId > 0) {
            AccountSubject parent = subjectMap.get(parentId);
            if (parent == null) {
                break;
            }
            if (!resultIds.add(parent.getId())) {
                break;
            }
            parentId = parent.getParentId();
        }
    }
    private void addDescendants(Long subjectId, Map<Long, List<AccountSubject>> childrenMap, Set<Long> resultIds) {
        List<AccountSubject> children = childrenMap.getOrDefault(subjectId, Collections.emptyList());
        for (AccountSubject child : children) {
            if (child.getId() == null) {
                continue;
            }
            if (resultIds.add(child.getId())) {
                addDescendants(child.getId(), childrenMap, resultIds);
            }
        }
    }
    private Map<Long, List<AccountSubject>> buildChildrenMap(List<AccountSubject> subjects) {
        Map<Long, List<AccountSubject>> childrenMap = new HashMap<>();
        for (AccountSubject subject : subjects) {
            if (subject.getId() == null) {
                continue;
            }
            Long parentId = subject.getParentId();
            if (parentId == null || parentId <= 0) {
                continue;
            }
            childrenMap.computeIfAbsent(parentId, key -> new ArrayList<>()).add(subject);
        }
        return childrenMap;
    }
    /**
     * åŸºäºŽ parentId é€’归构建科目树。
     */
    private List<AccountSubjectVo> buildTree(List<AccountSubject> subjects) {
        if (subjects == null || subjects.isEmpty()) {
            return Collections.emptyList();
        }
        List<AccountSubject> sortedSubjects = new ArrayList<>(subjects);
        sortedSubjects.sort(Comparator
                .comparing(AccountSubject::getSubjectCode, Comparator.nullsLast(String::compareTo))
                .thenComparing(AccountSubject::getId, Comparator.nullsLast(Long::compareTo)));
        Map<Long, AccountSubjectVo> subjectVoMap = new LinkedHashMap<>();
        for (AccountSubject subject : sortedSubjects) {
            if (subject.getId() == null) {
                continue;
            }
            AccountSubjectVo vo = new AccountSubjectVo();
            BeanUtils.copyProperties(subject, vo);
            subjectVoMap.put(subject.getId(), vo);
        }
        List<AccountSubjectVo> roots = new ArrayList<>();
        for (AccountSubject subject : sortedSubjects) {
            if (subject.getId() == null) {
                continue;
            }
            AccountSubjectVo current = subjectVoMap.get(subject.getId());
            Long parentId = subject.getParentId();
            if (parentId != null && parentId > 0 && subjectVoMap.containsKey(parentId)) {
                subjectVoMap.get(parentId).getChildren().add(current);
            } else {
                roots.add(current);
            }
        }
        markLeafRecursively(roots);
        return roots;
    }
    private void markLeafRecursively(List<AccountSubjectVo> nodes) {
        for (AccountSubjectVo node : nodes) {
            List<AccountSubjectVo> children = node.getChildren();
            node.setLeaf(children == null || children.isEmpty());
            if (children != null && !children.isEmpty()) {
                markLeafRecursively(children);
            }
        }
    }
    /**
     * æ ¡éªŒçˆ¶å­å…³ç³»ï¼šçˆ¶èŠ‚ç‚¹å¿…é¡»å­˜åœ¨ï¼Œä¸”ä¸èƒ½å½¢æˆå¾ªçŽ¯å¼•ç”¨ã€‚
     */
    private void validateParent(Long parentId, Long currentId) {
        if (parentId == null || parentId <= 0) {
            return;
        }
        if (currentId != null && parentId.equals(currentId)) {
            throw new ServiceException("父科目不能选择自身");
        }
        AccountSubject parent = getById(parentId);
        if (parent == null) {
            throw new ServiceException("父科目不存在,请重新选择");
        }
        // é˜²æ­¢å½¢æˆçŽ¯ï¼šæ›´æ–°æ—¶ï¼Œçˆ¶èŠ‚ç‚¹ä¸èƒ½æ˜¯å½“å‰èŠ‚ç‚¹çš„ä»»æ„å­å­™èŠ‚ç‚¹ã€‚
        if (currentId != null) {
            Set<Long> visited = new HashSet<>();
            Long traceParentId = parentId;
            while (traceParentId != null && traceParentId > 0) {
                if (!visited.add(traceParentId)) {
                    throw new ServiceException("科目层级存在循环引用,请检查父科目设置");
                }
                if (traceParentId.equals(currentId)) {
                    throw new ServiceException("父科目不能是当前科目或其子科目");
                }
                AccountSubject traceNode = getById(traceParentId);
                if (traceNode == null) {
                    break;
                }
                traceParentId = traceNode.getParentId();
            }
        }
    }
    private Map<Long, List<Long>> buildChildrenIdMap(List<AccountSubject> subjects) {
        Map<Long, List<Long>> map = new HashMap<>();
        for (AccountSubject subject : subjects) {
            if (subject.getId() == null || subject.getParentId() == null || subject.getParentId() <= 0) {
                continue;
            }
            map.computeIfAbsent(subject.getParentId(), key -> new ArrayList<>()).add(subject.getId());
        }
        return map;
    }
    /**
     * æ”¶é›†å¾…删除节点及其所有子孙节点。
     */
    private void collectDescendantIds(Long id, Map<Long, List<Long>> childrenIdMap, Set<Long> result) {
        if (id == null || !result.add(id)) {
            return;
        }
        List<Long> children = childrenIdMap.getOrDefault(id, Collections.emptyList());
        for (Long childId : children) {
            collectDescendantIds(childId, childrenIdMap, result);
        }
    }
}
src/main/java/com/ruoyi/account/service/impl/financial/FinFixedAssetServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,231 @@
package com.ruoyi.account.service.impl.financial;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.account.bean.dto.financial.FinFixedAssetDto;
import com.ruoyi.account.mapper.financial.FinFixedAssetMapper;
import com.ruoyi.account.pojo.financial.FinFixedAsset;
import com.ruoyi.account.service.financial.FinFixedAssetService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
/**
 * å›ºå®šèµ„产服务实现。
 */
@Service
@RequiredArgsConstructor
public class FinFixedAssetServiceImpl extends ServiceImpl<FinFixedAssetMapper, FinFixedAsset> implements FinFixedAssetService {
    private static final BigDecimal ONE_HUNDRED = new BigDecimal("100");
    private static final BigDecimal ZERO = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
    private static final DateTimeFormatter CODE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
    @Override
    public IPage<FinFixedAsset> pageList(Page<FinFixedAsset> page, FinFixedAssetDto queryDto) {
        LambdaQueryWrapper<FinFixedAsset> wrapper = new LambdaQueryWrapper<>();
        if (queryDto != null && StringUtils.isNotEmpty(queryDto.getAssetCode())) {
            wrapper.like(FinFixedAsset::getAssetCode, queryDto.getAssetCode());
        }
        if (queryDto != null && StringUtils.isNotEmpty(queryDto.getAssetName())) {
            wrapper.like(FinFixedAsset::getAssetName, queryDto.getAssetName());
        }
        if (queryDto != null && StringUtils.isNotEmpty(queryDto.getCategory())) {
            wrapper.eq(FinFixedAsset::getCategory, queryDto.getCategory());
        }
        if (queryDto != null && StringUtils.isNotEmpty(queryDto.getStatus())) {
            wrapper.eq(FinFixedAsset::getStatus, queryDto.getStatus());
        }
        wrapper.orderByDesc(FinFixedAsset::getId);
        return page(page, wrapper);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean add(FinFixedAssetDto dto) {
        validateForSave(dto, false);
        if (StringUtils.isEmpty(dto.getAssetCode())) {
            dto.setAssetCode(generateAssetCode());
        }
        BigDecimal residualRate = normalizeResidualRate(dto.getResidualRate());
        dto.setResidualRate(residualRate);
        BigDecimal accumulatedDepreciation = defaultMoney(dto.getAccumulatedDepreciation());
        dto.setAccumulatedDepreciation(accumulatedDepreciation);
        dto.setNetValue(calculateNetValue(dto.getOriginalValue(), accumulatedDepreciation));
        if (StringUtils.isEmpty(dto.getStatus())) {
            dto.setStatus("in_use");
        }
        return save(dto);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean update(FinFixedAssetDto dto) {
        if (dto == null || dto.getId() == null) {
            throw new ServiceException("修改失败,资产ID不能为空");
        }
        FinFixedAsset existed = getById(dto.getId());
        if (existed == null) {
            throw new ServiceException("修改失败,固定资产不存在");
        }
        if (StringUtils.isEmpty(dto.getAssetCode())) {
            dto.setAssetCode(existed.getAssetCode());
        }
        if (StringUtils.isEmpty(dto.getStatus())) {
            dto.setStatus(existed.getStatus());
        }
        validateForSave(dto, true);
        BigDecimal residualRate = normalizeResidualRate(dto.getResidualRate());
        dto.setResidualRate(residualRate);
        if (dto.getAccumulatedDepreciation() == null) {
            dto.setAccumulatedDepreciation(defaultMoney(existed.getAccumulatedDepreciation()));
        }
        dto.setNetValue(calculateNetValue(dto.getOriginalValue(), dto.getAccumulatedDepreciation()));
        return updateById(dto);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteByIds(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            throw new ServiceException("删除失败,请选择要删除的数据");
        }
        return removeByIds(ids);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> depreciate(List<Long> ids) {
        LambdaQueryWrapper<FinFixedAsset> wrapper = new LambdaQueryWrapper<>();
        if (ids != null && !ids.isEmpty()) {
            wrapper.in(FinFixedAsset::getId, ids);
        } else {
            wrapper.eq(FinFixedAsset::getStatus, "in_use");
        }
        List<FinFixedAsset> assets = list(wrapper);
        BigDecimal totalMonthlyDepreciation = ZERO;
        int processedCount = 0;
        for (FinFixedAsset asset : assets) {
            if (!"in_use".equals(asset.getStatus())) {
                continue;
            }
            BigDecimal monthlyDepreciation = calculateMonthlyDepreciation(
                    asset.getOriginalValue(),
                    asset.getResidualRate(),
                    asset.getUsefulLife()
            );
            BigDecimal accumulatedDepreciation = defaultMoney(asset.getAccumulatedDepreciation()).add(monthlyDepreciation);
            if (accumulatedDepreciation.compareTo(defaultMoney(asset.getOriginalValue())) > 0) {
                accumulatedDepreciation = defaultMoney(asset.getOriginalValue());
            }
            asset.setAccumulatedDepreciation(roundMoney(accumulatedDepreciation));
            asset.setNetValue(calculateNetValue(asset.getOriginalValue(), asset.getAccumulatedDepreciation()));
            updateById(asset);
            processedCount++;
            totalMonthlyDepreciation = totalMonthlyDepreciation.add(monthlyDepreciation);
        }
        Map<String, Object> result = new HashMap<>(4);
        result.put("processedCount", processedCount);
        result.put("totalMonthlyDepreciation", roundMoney(totalMonthlyDepreciation));
        result.put("executionTime", LocalDateTime.now());
        return result;
    }
    /**
     * æŒ‰æ–‡æ¡£è§„则校验固定资产数据。
     */
    private void validateForSave(FinFixedAssetDto dto, boolean isUpdate) {
        if (dto == null) {
            throw new ServiceException("固定资产数据不能为空");
        }
        if (isUpdate && dto.getId() == null) {
            throw new ServiceException("修改失败,资产ID不能为空");
        }
        if (StringUtils.isEmpty(dto.getAssetName())) {
            throw new ServiceException("资产名称不能为空");
        }
        if (StringUtils.isEmpty(dto.getCategory())) {
            throw new ServiceException("资产类别不能为空");
        }
        if (dto.getPurchaseDate() == null) {
            throw new ServiceException("购置日期不能为空");
        }
        if (dto.getOriginalValue() == null || dto.getOriginalValue().compareTo(BigDecimal.ZERO) < 0) {
            throw new ServiceException("资产原值不能为空且不能小于0");
        }
        if (dto.getUsefulLife() == null || dto.getUsefulLife() <= 0) {
            throw new ServiceException("使用年限必须大于0");
        }
        if (dto.getResidualRate() != null && dto.getResidualRate().compareTo(BigDecimal.ZERO) < 0) {
            throw new ServiceException("残值率不能小于0");
        }
        if (dto.getResidualRate() != null && dto.getResidualRate().compareTo(ONE_HUNDRED) > 0) {
            throw new ServiceException("残值率不能大于100%");
        }
        if (StringUtils.isNotEmpty(dto.getAssetCode())) {
            LambdaQueryWrapper<FinFixedAsset> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(FinFixedAsset::getAssetCode, dto.getAssetCode());
            if (isUpdate) {
                wrapper.ne(FinFixedAsset::getId, dto.getId());
            }
            if (count(wrapper) > 0) {
                throw new ServiceException("资产编号已存在,请勿重复提交");
            }
        }
    }
    /**
     * å›ºå®šèµ„产折旧公式:
     * monthlyDepreciation = originalValue * (1 - residualRate/100) / (usefulLife*12)
     */
    private BigDecimal calculateMonthlyDepreciation(BigDecimal originalValue, BigDecimal residualRate, Integer usefulLife) {
        BigDecimal normalizedOriginalValue = defaultMoney(originalValue);
        BigDecimal normalizedResidualRate = normalizeResidualRate(residualRate);
        BigDecimal depreciableRatio = BigDecimal.ONE.subtract(normalizedResidualRate.divide(ONE_HUNDRED, 8, RoundingMode.HALF_UP));
        BigDecimal months = BigDecimal.valueOf((long) usefulLife * 12L);
        if (months.compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("使用年限无效,无法计提折旧");
        }
        return roundMoney(normalizedOriginalValue.multiply(depreciableRatio).divide(months, 8, RoundingMode.HALF_UP));
    }
    /**
     * å‡€å€¼ = åŽŸå€¼ - ç´¯è®¡æŠ˜æ—§ã€‚
     */
    private BigDecimal calculateNetValue(BigDecimal originalValue, BigDecimal accumulatedDepreciation) {
        BigDecimal value = defaultMoney(originalValue).subtract(defaultMoney(accumulatedDepreciation));
        if (value.compareTo(BigDecimal.ZERO) < 0) {
            value = BigDecimal.ZERO;
        }
        return roundMoney(value);
    }
    private BigDecimal normalizeResidualRate(BigDecimal residualRate) {
        return residualRate == null ? BigDecimal.ZERO : residualRate;
    }
    private BigDecimal defaultMoney(BigDecimal value) {
        return value == null ? ZERO : roundMoney(value);
    }
    private BigDecimal roundMoney(BigDecimal value) {
        if (value == null) {
            return ZERO;
        }
        return value.setScale(2, RoundingMode.HALF_UP);
    }
    private String generateAssetCode() {
        return "GD" + LocalDateTime.now().format(CODE_TIME_FORMATTER) + new Random().nextInt(10);
    }
}
src/main/java/com/ruoyi/account/service/impl/financial/FinIntangibleAssetServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,250 @@
package com.ruoyi.account.service.impl.financial;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.account.bean.dto.financial.FinIntangibleAssetDto;
import com.ruoyi.account.mapper.financial.FinIntangibleAssetMapper;
import com.ruoyi.account.pojo.financial.FinIntangibleAsset;
import com.ruoyi.account.service.financial.FinIntangibleAssetService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
/**
 * æ— å½¢èµ„产服务实现。
 */
@Service
@RequiredArgsConstructor
public class FinIntangibleAssetServiceImpl extends ServiceImpl<FinIntangibleAssetMapper, FinIntangibleAsset> implements FinIntangibleAssetService {
    private static final BigDecimal ONE_HUNDRED = new BigDecimal("100");
    private static final BigDecimal ZERO = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
    private static final DateTimeFormatter CODE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
    @Override
    public IPage<FinIntangibleAsset> pageList(Page<FinIntangibleAsset> page, FinIntangibleAssetDto queryDto) {
        LambdaQueryWrapper<FinIntangibleAsset> wrapper = new LambdaQueryWrapper<>();
        if (queryDto != null && StringUtils.isNotEmpty(queryDto.getAssetCode())) {
            wrapper.like(FinIntangibleAsset::getAssetCode, queryDto.getAssetCode());
        }
        if (queryDto != null && StringUtils.isNotEmpty(queryDto.getAssetName())) {
            wrapper.like(FinIntangibleAsset::getAssetName, queryDto.getAssetName());
        }
        if (queryDto != null && StringUtils.isNotEmpty(queryDto.getCategory())) {
            wrapper.eq(FinIntangibleAsset::getCategory, queryDto.getCategory());
        }
        if (queryDto != null && StringUtils.isNotEmpty(queryDto.getStatus())) {
            wrapper.eq(FinIntangibleAsset::getStatus, queryDto.getStatus());
        }
        wrapper.orderByDesc(FinIntangibleAsset::getId);
        return page(page, wrapper);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean add(FinIntangibleAssetDto dto) {
        validateForSave(dto, false);
        if (StringUtils.isEmpty(dto.getAssetCode())) {
            dto.setAssetCode(generateAssetCode());
        }
        BigDecimal residualRate = normalizeResidualRate(dto.getResidualRate());
        dto.setResidualRate(residualRate);
        BigDecimal accumulatedAmortization = defaultMoney(dto.getAccumulatedAmortization());
        dto.setAccumulatedAmortization(accumulatedAmortization);
        dto.setNetValue(calculateNetValue(dto.getOriginalValue(), accumulatedAmortization));
        if (StringUtils.isEmpty(dto.getStatus())) {
            dto.setStatus("in_use");
        }
        return save(dto);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean update(FinIntangibleAssetDto dto) {
        if (dto == null || dto.getId() == null) {
            throw new ServiceException("修改失败,资产ID不能为空");
        }
        FinIntangibleAsset existed = getById(dto.getId());
        if (existed == null) {
            throw new ServiceException("修改失败,无形资产不存在");
        }
        if (StringUtils.isEmpty(dto.getAssetCode())) {
            dto.setAssetCode(existed.getAssetCode());
        }
        if (StringUtils.isEmpty(dto.getStatus())) {
            dto.setStatus(existed.getStatus());
        }
        validateForSave(dto, true);
        BigDecimal residualRate = normalizeResidualRate(dto.getResidualRate());
        dto.setResidualRate(residualRate);
        if (dto.getAccumulatedAmortization() == null) {
            dto.setAccumulatedAmortization(defaultMoney(existed.getAccumulatedAmortization()));
        }
        dto.setNetValue(calculateNetValue(dto.getOriginalValue(), dto.getAccumulatedAmortization()));
        if (dto.getNetValue().compareTo(BigDecimal.ZERO) <= 0) {
            dto.setStatus("amortized");
        } else if ("amortized".equals(dto.getStatus())) {
            dto.setStatus("in_use");
        }
        if (dto.getValidityDate() != null
                && dto.getValidityDate().isBefore(LocalDate.now())
                && !"amortized".equals(dto.getStatus())) {
            dto.setStatus("expired");
        }
        return updateById(dto);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteByIds(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            throw new ServiceException("删除失败,请选择要删除的数据");
        }
        return removeByIds(ids);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> amortize(List<Long> ids) {
        LambdaQueryWrapper<FinIntangibleAsset> wrapper = new LambdaQueryWrapper<>();
        if (ids != null && !ids.isEmpty()) {
            wrapper.in(FinIntangibleAsset::getId, ids);
        } else {
            wrapper.eq(FinIntangibleAsset::getStatus, "in_use");
        }
        List<FinIntangibleAsset> assets = list(wrapper);
        BigDecimal totalMonthlyAmortization = ZERO;
        int processedCount = 0;
        for (FinIntangibleAsset asset : assets) {
            if (!"in_use".equals(asset.getStatus())) {
                continue;
            }
            BigDecimal monthlyAmortization = calculateMonthlyAmortization(
                    asset.getOriginalValue(),
                    asset.getResidualRate(),
                    asset.getAmortizationPeriod()
            );
            BigDecimal accumulatedAmortization = defaultMoney(asset.getAccumulatedAmortization()).add(monthlyAmortization);
            if (accumulatedAmortization.compareTo(defaultMoney(asset.getOriginalValue())) > 0) {
                accumulatedAmortization = defaultMoney(asset.getOriginalValue());
            }
            asset.setAccumulatedAmortization(roundMoney(accumulatedAmortization));
            asset.setNetValue(calculateNetValue(asset.getOriginalValue(), asset.getAccumulatedAmortization()));
            // è§„则:当净值 <= 0 æ—¶ï¼Œå‡€å€¼å½’零并标记为已摊销完。
            if (asset.getNetValue().compareTo(BigDecimal.ZERO) <= 0) {
                asset.setNetValue(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
                asset.setStatus("amortized");
            } else if (asset.getValidityDate() != null && asset.getValidityDate().isBefore(LocalDate.now())) {
                asset.setStatus("expired");
            }
            updateById(asset);
            processedCount++;
            totalMonthlyAmortization = totalMonthlyAmortization.add(monthlyAmortization);
        }
        Map<String, Object> result = new HashMap<>(4);
        result.put("processedCount", processedCount);
        result.put("totalMonthlyAmortization", roundMoney(totalMonthlyAmortization));
        result.put("executionTime", LocalDateTime.now());
        return result;
    }
    /**
     * æŒ‰æ–‡æ¡£è§„则校验无形资产数据。
     */
    private void validateForSave(FinIntangibleAssetDto dto, boolean isUpdate) {
        if (dto == null) {
            throw new ServiceException("无形资产数据不能为空");
        }
        if (isUpdate && dto.getId() == null) {
            throw new ServiceException("修改失败,资产ID不能为空");
        }
        if (StringUtils.isEmpty(dto.getAssetName())) {
            throw new ServiceException("资产名称不能为空");
        }
        if (StringUtils.isEmpty(dto.getCategory())) {
            throw new ServiceException("资产类别不能为空");
        }
        if (dto.getAcquisitionDate() == null) {
            throw new ServiceException("取得日期不能为空");
        }
        if (dto.getOriginalValue() == null || dto.getOriginalValue().compareTo(BigDecimal.ZERO) < 0) {
            throw new ServiceException("资产原值不能为空且不能小于0");
        }
        if (dto.getAmortizationPeriod() == null || dto.getAmortizationPeriod() <= 0) {
            throw new ServiceException("摊销年限必须大于0");
        }
        if (dto.getResidualRate() != null && dto.getResidualRate().compareTo(BigDecimal.ZERO) < 0) {
            throw new ServiceException("残值率不能小于0");
        }
        if (dto.getResidualRate() != null && dto.getResidualRate().compareTo(ONE_HUNDRED) > 0) {
            throw new ServiceException("残值率不能大于100%");
        }
        if (StringUtils.isNotEmpty(dto.getAssetCode())) {
            LambdaQueryWrapper<FinIntangibleAsset> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(FinIntangibleAsset::getAssetCode, dto.getAssetCode());
            if (isUpdate) {
                wrapper.ne(FinIntangibleAsset::getId, dto.getId());
            }
            if (count(wrapper) > 0) {
                throw new ServiceException("资产编号已存在,请勿重复提交");
            }
        }
    }
    /**
     * æ— å½¢èµ„产摊销公式:
     * monthlyAmortization = originalValue * (1 - residualRate/100) / (amortizationPeriod*12)
     */
    private BigDecimal calculateMonthlyAmortization(BigDecimal originalValue, BigDecimal residualRate, Integer amortizationPeriod) {
        BigDecimal normalizedOriginalValue = defaultMoney(originalValue);
        BigDecimal normalizedResidualRate = normalizeResidualRate(residualRate);
        BigDecimal amortizableRatio = BigDecimal.ONE.subtract(normalizedResidualRate.divide(ONE_HUNDRED, 8, RoundingMode.HALF_UP));
        BigDecimal months = BigDecimal.valueOf((long) amortizationPeriod * 12L);
        if (months.compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("摊销年限无效,无法计提摊销");
        }
        return roundMoney(normalizedOriginalValue.multiply(amortizableRatio).divide(months, 8, RoundingMode.HALF_UP));
    }
    /**
     * å‡€å€¼ = åŽŸå€¼ - ç´¯è®¡æ‘Šé”€ã€‚
     */
    private BigDecimal calculateNetValue(BigDecimal originalValue, BigDecimal accumulatedAmortization) {
        BigDecimal value = defaultMoney(originalValue).subtract(defaultMoney(accumulatedAmortization));
        if (value.compareTo(BigDecimal.ZERO) < 0) {
            value = BigDecimal.ZERO;
        }
        return roundMoney(value);
    }
    private BigDecimal normalizeResidualRate(BigDecimal residualRate) {
        return residualRate == null ? BigDecimal.ZERO : residualRate;
    }
    private BigDecimal defaultMoney(BigDecimal value) {
        return value == null ? ZERO : roundMoney(value);
    }
    private BigDecimal roundMoney(BigDecimal value) {
        if (value == null) {
            return ZERO;
        }
        return value.setScale(2, RoundingMode.HALF_UP);
    }
    private String generateAssetCode() {
        return "WX" + LocalDateTime.now().format(CODE_TIME_FORMATTER) + new Random().nextInt(10);
    }
}
src/main/java/com/ruoyi/account/service/impl/financial/FinLedgerServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,206 @@
package com.ruoyi.account.service.impl.financial;
import com.ruoyi.account.bean.dto.financial.FinDetailLedgerQueryDto;
import com.ruoyi.account.bean.dto.financial.FinLedgerQueryDto;
import com.ruoyi.account.bean.vo.financial.FinLedgerEntryRecordVo;
import com.ruoyi.account.bean.vo.financial.FinLedgerRowVo;
import com.ruoyi.account.mapper.financial.FinVoucherEntryMapper;
import com.ruoyi.account.service.financial.FinLedgerService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
/**
 * ç§‘目总账/明细账服务实现。
 */
@Service
@RequiredArgsConstructor
public class FinLedgerServiceImpl implements FinLedgerService {
    private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM");
    private static final BigDecimal ZERO = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
    private final FinVoucherEntryMapper finVoucherEntryMapper;
    @Override
    public List<FinLedgerRowVo> queryGeneralLedger(FinLedgerQueryDto queryDto) {
        if (queryDto == null || StringUtils.isEmpty(queryDto.getSubjectCode())) {
            return Collections.emptyList();
        }
        YearMonth startMonth = parseMonth(queryDto.getStartMonth(), "开始月份");
        YearMonth endMonth = parseMonth(queryDto.getEndMonth(), "结束月份");
        if (startMonth.isAfter(endMonth)) {
            throw new ServiceException("开始月份不能大于结束月份");
        }
        return buildLedgerRows(queryDto.getSubjectCode(), startMonth, endMonth, null, null);
    }
    @Override
    public List<FinLedgerRowVo> queryDetailLedger(FinDetailLedgerQueryDto queryDto) {
        if (queryDto == null || StringUtils.isEmpty(queryDto.getSubjectCode())) {
            return Collections.emptyList();
        }
        YearMonth startMonth = parseMonth(queryDto.getStartMonth(), "开始月份");
        YearMonth endMonth = parseMonth(queryDto.getEndMonth(), "结束月份");
        if (startMonth.isAfter(endMonth)) {
            throw new ServiceException("开始月份不能大于结束月份");
        }
        return buildLedgerRows(queryDto.getSubjectCode(), startMonth, endMonth, queryDto.getAuxiliaryType(), queryDto.getAuxiliaryId());
    }
    /**
     * æž„建账簿行数据,输出期初、分录、本月合计、本年累计。
     */
    private List<FinLedgerRowVo> buildLedgerRows(String subjectCode,
                                                 YearMonth startMonth,
                                                 YearMonth endMonth,
                                                 String auxiliaryType,
                                                 String auxiliaryId) {
        LocalDate startDate = startMonth.atDay(1);
        LocalDate endDate = endMonth.atEndOfMonth();
        List<FinLedgerEntryRecordVo> openingEntries = finVoucherEntryMapper.listPostedEntriesBefore(
                subjectCode, startDate, auxiliaryType, auxiliaryId
        );
        BigDecimal openingBalance = calculateBalance(openingEntries);
        List<FinLedgerEntryRecordVo> currentPeriodEntries = finVoucherEntryMapper.listPostedEntries(
                subjectCode, startDate, endDate, auxiliaryType, auxiliaryId
        );
        Map<YearMonth, List<FinLedgerEntryRecordVo>> monthEntriesMap = groupEntriesByMonth(currentPeriodEntries);
        List<FinLedgerRowVo> rows = new ArrayList<>();
        BigDecimal runningBalance = openingBalance;
        BigDecimal yearDebit = ZERO;
        BigDecimal yearCredit = ZERO;
        for (YearMonth month = startMonth; !month.isAfter(endMonth); month = month.plusMonths(1)) {
            rows.add(buildOpeningRow(month.atDay(1), runningBalance));
            List<FinLedgerEntryRecordVo> monthEntries = monthEntriesMap.getOrDefault(month, Collections.emptyList());
            BigDecimal monthDebit = ZERO;
            BigDecimal monthCredit = ZERO;
            for (FinLedgerEntryRecordVo entry : monthEntries) {
                BigDecimal debit = money(entry.getDebit());
                BigDecimal credit = money(entry.getCredit());
                runningBalance = runningBalance.add(debit).subtract(credit);
                monthDebit = monthDebit.add(debit);
                monthCredit = monthCredit.add(credit);
                FinLedgerRowVo row = new FinLedgerRowVo();
                row.setRowType("entry");
                row.setDate(entry.getVoucherDate());
                row.setVoucherNo(entry.getVoucherNo());
                row.setSummary(StringUtils.isNotEmpty(entry.getSummary()) ? entry.getSummary() : "");
                row.setDebit(debit);
                row.setCredit(credit);
                row.setBalance(money(runningBalance));
                row.setDirection(resolveDirection(runningBalance));
                rows.add(row);
            }
            rows.add(buildMonthlyTotalRow(month.atEndOfMonth(), monthDebit, monthCredit, runningBalance));
            yearDebit = yearDebit.add(monthDebit);
            yearCredit = yearCredit.add(monthCredit);
        }
        rows.add(buildYearlyTotalRow(endMonth.atEndOfMonth(), yearDebit, yearCredit, runningBalance));
        return rows;
    }
    private Map<YearMonth, List<FinLedgerEntryRecordVo>> groupEntriesByMonth(List<FinLedgerEntryRecordVo> entries) {
        Map<YearMonth, List<FinLedgerEntryRecordVo>> map = new LinkedHashMap<>();
        for (FinLedgerEntryRecordVo entry : entries) {
            if (entry.getVoucherDate() == null) {
                continue;
            }
            YearMonth month = YearMonth.from(entry.getVoucherDate());
            map.computeIfAbsent(month, key -> new ArrayList<>()).add(entry);
        }
        return map;
    }
    private FinLedgerRowVo buildOpeningRow(LocalDate date, BigDecimal openingBalance) {
        FinLedgerRowVo row = new FinLedgerRowVo();
        row.setRowType("opening");
        row.setDate(date);
        row.setVoucherNo("-");
        row.setSummary("期初余额");
        row.setDebit(ZERO);
        row.setCredit(ZERO);
        row.setBalance(money(openingBalance));
        row.setDirection(resolveDirection(openingBalance));
        return row;
    }
    private FinLedgerRowVo buildMonthlyTotalRow(LocalDate date,
                                                BigDecimal monthDebit,
                                                BigDecimal monthCredit,
                                                BigDecimal monthBalance) {
        FinLedgerRowVo row = new FinLedgerRowVo();
        row.setRowType("monthly_total");
        row.setDate(date);
        row.setVoucherNo("-");
        row.setSummary("本月合计");
        row.setDebit(money(monthDebit));
        row.setCredit(money(monthCredit));
        row.setBalance(money(monthBalance));
        row.setDirection(resolveDirection(monthBalance));
        return row;
    }
    private FinLedgerRowVo buildYearlyTotalRow(LocalDate date,
                                               BigDecimal yearDebit,
                                               BigDecimal yearCredit,
                                               BigDecimal yearBalance) {
        FinLedgerRowVo row = new FinLedgerRowVo();
        row.setRowType("yearly_total");
        row.setDate(date);
        row.setVoucherNo("-");
        row.setSummary("合计");
        row.setDebit(money(yearDebit));
        row.setCredit(money(yearCredit));
        row.setBalance(money(yearBalance));
        row.setDirection(resolveDirection(yearBalance));
        return row;
    }
    private BigDecimal calculateBalance(List<FinLedgerEntryRecordVo> entries) {
        BigDecimal balance = ZERO;
        for (FinLedgerEntryRecordVo entry : entries) {
            balance = balance.add(money(entry.getDebit())).subtract(money(entry.getCredit()));
        }
        return money(balance);
    }
    private String resolveDirection(BigDecimal balance) {
        return money(balance).compareTo(BigDecimal.ZERO) >= 0 ? "借" : "è´·";
    }
    private YearMonth parseMonth(String value, String fieldLabel) {
        if (StringUtils.isEmpty(value)) {
            throw new ServiceException(fieldLabel + "不能为空,格式应为YYYY-MM");
        }
        try {
            return YearMonth.parse(value, MONTH_FORMATTER);
        } catch (DateTimeParseException ex) {
            throw new ServiceException(fieldLabel + "格式错误,格式应为YYYY-MM");
        }
    }
    private BigDecimal money(BigDecimal value) {
        if (value == null) {
            return ZERO;
        }
        return value.setScale(2, RoundingMode.HALF_UP);
    }
}
src/main/java/com/ruoyi/account/service/impl/financial/FinVoucherServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,299 @@
package com.ruoyi.account.service.impl.financial;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.account.bean.dto.financial.FinVoucherDto;
import com.ruoyi.account.bean.dto.financial.FinVoucherEntryDto;
import com.ruoyi.account.bean.dto.financial.FinVoucherPageDto;
import com.ruoyi.account.bean.vo.financial.FinVoucherDetailVo;
import com.ruoyi.account.mapper.AccountSubjectMapper;
import com.ruoyi.account.mapper.financial.FinVoucherEntryMapper;
import com.ruoyi.account.mapper.financial.FinVoucherMapper;
import com.ruoyi.account.pojo.AccountSubject;
import com.ruoyi.account.pojo.financial.FinVoucher;
import com.ruoyi.account.pojo.financial.FinVoucherEntry;
import com.ruoyi.account.service.financial.FinVoucherService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
/**
 * å‡­è¯æœåŠ¡å®žçŽ°ã€‚
 */
@Service
@RequiredArgsConstructor
public class FinVoucherServiceImpl extends ServiceImpl<FinVoucherMapper, FinVoucher> implements FinVoucherService {
    private static final BigDecimal ZERO = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
    private final FinVoucherEntryMapper finVoucherEntryMapper;
    private final AccountSubjectMapper accountSubjectMapper;
    @Override
    public IPage<FinVoucher> pageList(Page<FinVoucher> page, FinVoucherPageDto queryDto) {
        LambdaQueryWrapper<FinVoucher> wrapper = new LambdaQueryWrapper<>();
        if (queryDto != null && StringUtils.isNotEmpty(queryDto.getVoucherNo())) {
            wrapper.like(FinVoucher::getVoucherNo, queryDto.getVoucherNo());
        }
        if (queryDto != null && StringUtils.isNotEmpty(queryDto.getCreator())) {
            wrapper.eq(FinVoucher::getCreator, queryDto.getCreator());
        }
        if (queryDto != null && StringUtils.isNotEmpty(queryDto.getStatus())) {
            wrapper.eq(FinVoucher::getStatus, queryDto.getStatus());
        }
        if (queryDto != null && queryDto.getStartDate() != null) {
            wrapper.ge(FinVoucher::getVoucherDate, queryDto.getStartDate());
        }
        if (queryDto != null && queryDto.getEndDate() != null) {
            wrapper.le(FinVoucher::getVoucherDate, queryDto.getEndDate());
        }
        wrapper.orderByDesc(FinVoucher::getVoucherDate).orderByDesc(FinVoucher::getId);
        return page(page, wrapper);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean addVoucher(FinVoucherDto dto) {
        validateVoucherBasicInfo(dto, false);
        List<FinVoucherEntry> validEntries = buildAndValidateEntries(dto);
        FinVoucher voucher = new FinVoucher();
        BeanUtils.copyProperties(dto, voucher);
        voucher.setStatus("unposted");
        voucher.setAttachmentCount(voucher.getAttachmentCount() == null ? 0 : voucher.getAttachmentCount());
        BigDecimal totalDebit = calculateTotalDebit(validEntries);
        BigDecimal totalCredit = calculateTotalCredit(validEntries);
        voucher.setDebit(totalDebit);
        voucher.setCredit(totalCredit);
        if (StringUtils.isEmpty(voucher.getSummary())) {
            voucher.setSummary(findDefaultSummary(validEntries));
        }
        save(voucher);
        saveEntries(voucher.getId(), validEntries);
        return true;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateVoucher(FinVoucherDto dto) {
        validateVoucherBasicInfo(dto, true);
        FinVoucher existed = getById(dto.getId());
        if (existed == null) {
            throw new ServiceException("修改失败,凭证不存在");
        }
        if (!"unposted".equals(existed.getStatus())) {
            throw new ServiceException("仅未过账凭证允许修改");
        }
        List<FinVoucherEntry> validEntries = buildAndValidateEntries(dto);
        FinVoucher voucher = new FinVoucher();
        BeanUtils.copyProperties(dto, voucher);
        voucher.setStatus(existed.getStatus());
        voucher.setAttachmentCount(voucher.getAttachmentCount() == null ? 0 : voucher.getAttachmentCount());
        BigDecimal totalDebit = calculateTotalDebit(validEntries);
        BigDecimal totalCredit = calculateTotalCredit(validEntries);
        voucher.setDebit(totalDebit);
        voucher.setCredit(totalCredit);
        if (StringUtils.isEmpty(voucher.getSummary())) {
            voucher.setSummary(findDefaultSummary(validEntries));
        }
        updateById(voucher);
        LambdaQueryWrapper<FinVoucherEntry> deleteWrapper = new LambdaQueryWrapper<>();
        deleteWrapper.eq(FinVoucherEntry::getVoucherId, voucher.getId());
        finVoucherEntryMapper.delete(deleteWrapper);
        saveEntries(voucher.getId(), validEntries);
        return true;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean postVoucher(Long id) {
        FinVoucher voucher = getById(id);
        if (voucher == null) {
            throw new ServiceException("过账失败,凭证不存在");
        }
        if (!"unposted".equals(voucher.getStatus())) {
            throw new ServiceException("仅未过账凭证允许过账");
        }
        voucher.setStatus("posted");
        return updateById(voucher);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean cancelVoucher(Long id) {
        FinVoucher voucher = getById(id);
        if (voucher == null) {
            throw new ServiceException("作废失败,凭证不存在");
        }
        if (!"unposted".equals(voucher.getStatus())) {
            throw new ServiceException("仅未过账凭证允许作废");
        }
        voucher.setStatus("cancelled");
        return updateById(voucher);
    }
    @Override
    public FinVoucherDetailVo detail(Long id) {
        FinVoucher voucher = getById(id);
        if (voucher == null) {
            throw new ServiceException("查询失败,凭证不存在");
        }
        LambdaQueryWrapper<FinVoucherEntry> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(FinVoucherEntry::getVoucherId, id)
                .orderByAsc(FinVoucherEntry::getRowNo)
                .orderByAsc(FinVoucherEntry::getId);
        List<FinVoucherEntry> entries = finVoucherEntryMapper.selectList(wrapper);
        FinVoucherDetailVo vo = new FinVoucherDetailVo();
        BeanUtils.copyProperties(voucher, vo);
        vo.setEntries(entries);
        return vo;
    }
    /**
     * æ ¡éªŒå‡­è¯ä¸»è¡¨å­—段、状态字段与唯一性。
     */
    private void validateVoucherBasicInfo(FinVoucherDto dto, boolean isUpdate) {
        if (dto == null) {
            throw new ServiceException("凭证数据不能为空");
        }
        if (isUpdate && dto.getId() == null) {
            throw new ServiceException("修改失败,凭证ID不能为空");
        }
        if (StringUtils.isEmpty(dto.getVoucherNo())) {
            throw new ServiceException("凭证字号不能为空");
        }
        if (dto.getVoucherDate() == null) {
            throw new ServiceException("凭证日期不能为空");
        }
        LambdaQueryWrapper<FinVoucher> uniqueWrapper = new LambdaQueryWrapper<>();
        uniqueWrapper.eq(FinVoucher::getVoucherNo, dto.getVoucherNo());
        if (isUpdate) {
            uniqueWrapper.ne(FinVoucher::getId, dto.getId());
        }
        if (count(uniqueWrapper) > 0) {
            throw new ServiceException("凭证字号已存在,请勿重复提交");
        }
    }
    /**
     * è¿‡æ»¤æœ‰æ•ˆåˆ†å½•并执行借贷平衡校验。
     */
    private List<FinVoucherEntry> buildAndValidateEntries(FinVoucherDto dto) {
        List<FinVoucherEntryDto> rawEntries = dto.getEntries();
        if (rawEntries == null || rawEntries.isEmpty()) {
            throw new ServiceException("分录不能为空,至少需要一条有效分录");
        }
        List<FinVoucherEntry> validEntries = new ArrayList<>();
        int rowNo = 1;
        for (FinVoucherEntryDto entryDto : rawEntries) {
            if (entryDto == null || StringUtils.isEmpty(entryDto.getSubjectCode())) {
                continue;
            }
            BigDecimal debit = defaultMoney(entryDto.getDebit());
            BigDecimal credit = defaultMoney(entryDto.getCredit());
            if (debit.compareTo(BigDecimal.ZERO) <= 0 && credit.compareTo(BigDecimal.ZERO) <= 0) {
                continue;
            }
            if (debit.compareTo(BigDecimal.ZERO) > 0 && credit.compareTo(BigDecimal.ZERO) > 0) {
                throw new ServiceException("分录借方和贷方不能同时大于0");
            }
            FinVoucherEntry entry = new FinVoucherEntry();
            BeanUtils.copyProperties(entryDto, entry);
            entry.setDebit(debit);
            entry.setCredit(credit);
            entry.setRowNo(rowNo++);
            validEntries.add(entry);
        }
        if (validEntries.isEmpty()) {
            throw new ServiceException("分录至少需要一条有效行(科目不空,且借方或贷方大于0)");
        }
        // åˆ†å½•科目必须存在,避免脏科目编码入账。
        Set<String> subjectCodes = validEntries.stream()
                .map(FinVoucherEntry::getSubjectCode)
                .filter(StringUtils::isNotEmpty)
                .collect(Collectors.toSet());
        if (subjectCodes.isEmpty()) {
            throw new ServiceException("分录科目不能为空");
        }
        LambdaQueryWrapper<AccountSubject> subjectWrapper = new LambdaQueryWrapper<>();
        subjectWrapper.in(AccountSubject::getSubjectCode, subjectCodes);
        List<AccountSubject> subjects = accountSubjectMapper.selectList(subjectWrapper);
        Map<String, AccountSubject> subjectMap = subjects.stream()
                .collect(Collectors.toMap(AccountSubject::getSubjectCode, it -> it, (a, b) -> a));
        for (FinVoucherEntry entry : validEntries) {
            AccountSubject accountSubject = subjectMap.get(entry.getSubjectCode());
            if (accountSubject == null) {
                throw new ServiceException("科目编码不存在:" + entry.getSubjectCode());
            }
            if (StringUtils.isEmpty(entry.getSubjectName())) {
                entry.setSubjectName(accountSubject.getSubjectName());
            }
        }
        BigDecimal totalDebit = calculateTotalDebit(validEntries);
        BigDecimal totalCredit = calculateTotalCredit(validEntries);
        if (totalDebit.compareTo(BigDecimal.ZERO) <= 0 || totalCredit.compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("借贷金额必须大于0");
        }
        if (totalDebit.compareTo(totalCredit) != 0) {
            throw new ServiceException("借贷不平衡,禁止保存");
        }
        return validEntries;
    }
    private void saveEntries(Long voucherId, List<FinVoucherEntry> entries) {
        if (voucherId == null) {
            throw new ServiceException("凭证ID不能为空");
        }
        for (FinVoucherEntry entry : entries) {
            entry.setVoucherId(voucherId);
            finVoucherEntryMapper.insert(entry);
        }
    }
    private String findDefaultSummary(List<FinVoucherEntry> entries) {
        for (FinVoucherEntry entry : entries) {
            if (StringUtils.isNotEmpty(entry.getSummary())) {
                return entry.getSummary();
            }
        }
        return "";
    }
    private BigDecimal calculateTotalDebit(List<FinVoucherEntry> entries) {
        BigDecimal total = BigDecimal.ZERO;
        for (FinVoucherEntry entry : entries) {
            total = total.add(defaultMoney(entry.getDebit()));
        }
        return total.setScale(2, RoundingMode.HALF_UP);
    }
    private BigDecimal calculateTotalCredit(List<FinVoucherEntry> entries) {
        BigDecimal total = BigDecimal.ZERO;
        for (FinVoucherEntry entry : entries) {
            total = total.add(defaultMoney(entry.getCredit()));
        }
        return total.setScale(2, RoundingMode.HALF_UP);
    }
    private BigDecimal defaultMoney(BigDecimal value) {
        if (value == null) {
            return ZERO;
        }
        return value.setScale(2, RoundingMode.HALF_UP);
    }
}
src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
@@ -15,6 +15,7 @@
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.device.mapper.DeviceRepairMapper;
import com.ruoyi.procurementrecord.utils.StockUtils;
@@ -31,14 +32,8 @@
import com.ruoyi.quality.pojo.QualityInspectParam;
import com.ruoyi.quality.pojo.QualityTestStandard;
import com.ruoyi.quality.pojo.QualityTestStandardParam;
import com.ruoyi.sales.mapper.CommonFileMapper;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.mapper.SalesQuotationMapper;
import com.ruoyi.sales.mapper.ShippingInfoMapper;
import com.ruoyi.sales.pojo.CommonFile;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.pojo.SalesQuotation;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.sales.mapper.*;
import com.ruoyi.sales.pojo.*;
import com.ruoyi.sales.service.impl.CommonFileServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@@ -64,6 +59,7 @@
    private final PurchaseLedgerMapper purchaseLedgerMapper;
    private final SalesQuotationMapper salesQuotationMapper;
    private final ShippingInfoMapper shippingInfoMapper;
    private final ShippingProductDetailMapper shippingProductDetailMapper;
    private final CommonFileServiceImpl commonFileService;
    private final StockUtils stockUtils;
    private final SalesLedgerProductMapper salesLedgerProductMapper;
@@ -183,7 +179,7 @@
                            addQualityInspect(purchaseLedger, salesLedgerProduct);
                        } else {
                            //直接入库
                            stockUtils.addStockWithBatchNo(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(), purchaseLedger.getId(),purchaseLedger.getPurchaseContractNumber()+"-"+salesLedgerProduct.getId());
                            stockUtils.addStockWithBatchNo(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(), purchaseLedger.getId(), purchaseLedger.getPurchaseContractNumber() + "-" + salesLedgerProduct.getId());
                        }
                    }
                } else if (status.equals(3)) {
@@ -220,6 +216,9 @@
            if (shippingInfo != null) {
                if (status.equals(2)) {
                    shippingInfo.setStatus("审核通过");
                    shippingInfo.setShippingDate(new Date());
                    //更改出库审核状态(待确认改成待审核)
                    stockUtils.shipmentStatus(StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), shippingInfo.getId());
                } else if (status.equals(3)) {
                    shippingInfo.setStatus("审核拒绝");
                } else if (status.equals(1)) {
@@ -227,7 +226,6 @@
                }
                shippingInfoMapper.updateById(shippingInfo);
            }
            //库存扣减
        }
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.APPROVE_NODE, approveNode.getId(), approveNode.getStorageBlobDTOS());
src/main/java/com/ruoyi/basic/controller/CustomerController.java
@@ -148,8 +148,8 @@
     * ç§æµ·å®¢æˆ·æµå›žå…¬æµ·
     */
    @Log(title = "客户档案", businessType = BusinessType.OTHER)
    @PostMapping("/back")
    public R back(Long id) {
    @PostMapping("/back/{id}")
    public R back(@PathVariable("id") Long id) {
        return R.ok(customerService.back(id));
    }
}
src/main/java/com/ruoyi/basic/dto/StorageBlobVO.java
@@ -10,6 +10,10 @@
     */
    private String previewURL;
    private String url;
    private String name;
    /**
     * ä¸‹è½½åœ°å€
     */
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
@@ -132,10 +132,6 @@
    INBOUND_MANAGEMENT("inbound_management"),
    // Office Supplies
    OFFICE_SUPPLIES("office_supplies"),
    // OA
    OA_PROJECT_PHASE_TASK("oa_project_phase_task"),
    OA_PROJECT("oa_project"),
    OA_PROJECT_PHASE("oa_project_phase"),
    // Measuring Instrument Ledger
    SPARE_PARTS("spare_parts"),
    MEASURING_INSTRUMENT_LEDGER_RECORD("measuring_instrument_ledger_record"),
src/main/java/com/ruoyi/basic/mapper/ProductModelMapper.java
@@ -5,7 +5,7 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.vo.ProductModelVo;
import com.ruoyi.procurementrecord.dto.ProcurementPageDto;
import com.ruoyi.procurementrecord.bean.dto.ProcurementPageDto;
import org.apache.ibatis.annotations.Param;
import java.util.List;
src/main/java/com/ruoyi/basic/pojo/ProductModel.java
@@ -38,7 +38,6 @@
    private String model;
    @Excel(name = "产品编码")
    @TableField("product_code")
    private String productCode;
    /**
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
@@ -375,7 +375,7 @@
        //将客户的type改为1 ä¸”直接分配给当前用户
        Customer customer = customerMapper.selectById(id);
        customer.setType(1);
        customer.setIsAssigned(1);
        customer.setIsAssigned(0);
        return this.updateById(customer);
    }
src/main/java/com/ruoyi/basic/utils/FileUtil.java
@@ -98,8 +98,11 @@
        }
        // åˆ é™¤æ—§é™„件信息
        if (application == null) {
        if (application == null || application.trim().isEmpty()) {
            for (StorageBlobDTO storageBlobDTO : storageBlobDTOS) {
                if (storageBlobDTO.getApplication() == null || storageBlobDTO.getApplication().trim().isEmpty()) {
                    throw new RuntimeException("文件用途不能为空");
                }
                deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.getByType(storageBlobDTO.getApplication()), recordType, recordId);
            }
        } else {
@@ -344,6 +347,8 @@
            StorageBlobVO storageBlobVO = new StorageBlobVO();
            BeanUtils.copyProperties(storageBlob, storageBlobVO);
            storageBlobVO.setPreviewURL(buildSignedPreviewUrl(storageBlobVO));
            storageBlobVO.setUrl(buildSignedPreviewUrl(storageBlobVO));
            storageBlobVO.setName(storageBlob.getOriginalFilename());
            storageBlobVO.setDownloadURL(buildSignedDownloadUrl(storageBlobVO));
            storageBlobVO.setStorageAttachmentId(blobIdToAttachmentIdMap.get(storageBlob.getId()));
            storageBlobDTOS.add(storageBlobVO);
@@ -392,6 +397,8 @@
            StorageBlobVO storageBlobVO = new StorageBlobVO();
            BeanUtils.copyProperties(storageBlob, storageBlobVO);
            storageBlobVO.setPreviewURL(buildSignedPreviewUrl(storageBlobVO));
            storageBlobVO.setUrl(buildSignedPreviewUrl(storageBlobVO));
            storageBlobVO.setName(storageBlob.getOriginalFilename());
            storageBlobVO.setDownloadURL(buildSignedDownloadUrl(storageBlobVO));
            storageBlobVO.setStorageAttachmentId(blobIdToAttachmentIdMap.get(storageBlob.getId()));
            storageBlobDTOS.add(storageBlobVO);
@@ -417,6 +424,8 @@
            StorageBlobVO storageBlobVO = new StorageBlobVO();
            BeanUtils.copyProperties(storageBlob, storageBlobVO);
            storageBlobVO.setPreviewURL(buildSignedPreviewUrl(storageBlobVO));
            storageBlobVO.setUrl(buildSignedPreviewUrl(storageBlobVO));
            storageBlobVO.setName(storageBlob.getOriginalFilename());
            storageBlobVO.setDownloadURL(buildSignedDownloadUrl(storageBlobVO));
            storageBlobDTOS.add(storageBlobVO);
        }
src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java
@@ -2,29 +2,23 @@
import lombok.Getter;
//入库枚举
@Getter
public enum StockInQualifiedRecordTypeEnum implements BaseEnum<String> {
    CUSTOMIZATION_STOCK_IN("0", "合格自定义入库"),
    CUSTOMIZATION_STOCK_OUT("1", "合格自定义出库"),
    PRODUCTION_REPORT_STOCK_IN("2", "生产报工-入库"),
    PRODUCTION_REPORT_STOCK_OUT("3", "生产报工-出库"),
    DEFECTIVE_SCRAP("4", "不合格处理-报废"),
    PRODUCTION_SCRAP("5", "生产报工-报废"),
    QUALITYINSPECT_STOCK_IN("6", "质检-合格入库"),
    PURCHASE_STOCK_IN("7", "采购-入库"),
    SALE_STOCK_OUT("8", "销售-出库"),
    CUSTOMIZATION_UNSTOCK_IN("9", "不合格自定义入库"),
    CUSTOMIZATION_UNSTOCK_OUT("10", "不合格自定义出库"),
    CUSTOMIZATION_UNSTOCK_OUT("10", "采购-质检-合格入库"),
    DEFECTIVE_PASS("11", "不合格-让步放行"),
    QUALITYINSPECT_UNSTOCK_IN("12", "质检-不合格入库"),
    SALE_SHIP_STOCK_OUT("13", "销售-发货出库"),
    RETURN_HE_IN("14", "销售退货-合格入库"),
    RETURN_UNSTOCK_IN("15", "销售退货-不合格入库"),
    PICK_RETURN_IN("20", "领料退料-合格入库"),
    PURCHASE_RETURN_STOCK_OUT("21", "采购退货"),
    FEED_RETURN_IN("22", "生产退料-合格入库");
src/main/java/com/ruoyi/common/enums/StockInUnQualifiedRecordTypeEnum.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java
@@ -9,6 +9,7 @@
    PRODUCTION_REPORT_STOCK_OUT("3", "生产报工-出库"),
    SALE_STOCK_OUT("8", "销售-出库"),
    PURCHASE_RETURN_STOCK_OUT("9", "采购退货"),
    CUSTOMIZATION_UNSTOCK_OUT("10", "不合格自定义出库"),
    SALE_SHIP_STOCK_OUT("13", "销售-发货出库"),
    PICK_STOCK_OUT("14", "生产领料出库"),
    FEED_STOCK_OUT("15", "生产补料出库");
src/main/java/com/ruoyi/common/enums/StockOutUnQualifiedRecordTypeEnum.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java
@@ -28,6 +28,9 @@
    @Schema(description = "规格型号")
    private String deviceModel;
    @Schema(description = "设备项目")
    private String machineryCategory;
    /**
     * ä¸»é”®ID
     */
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskJob.java
@@ -4,6 +4,7 @@
import com.ruoyi.device.pojo.MaintenanceTask;
import lombok.RequiredArgsConstructor;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
@@ -18,14 +19,15 @@
import java.util.Set;
@Component
@DisallowConcurrentExecution // ç¦æ­¢å¹¶å‘执行同一个Job
@RequiredArgsConstructor
@DisallowConcurrentExecution
public class MaintenanceTaskJob implements Job, Serializable {
    private static final long serialVersionUID = 1L; // å¿…须定义序列化ID
    private static final long serialVersionUID = 1L;
    private final DeviceMaintenanceServiceImpl deviceMaintenanceService;
    @Autowired
    private DeviceMaintenanceServiceImpl deviceMaintenanceService;
    private final JdbcTemplate jdbcTemplate;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
@@ -96,6 +98,7 @@
        inspectionTask.setTenantId(timingTask.getTenantId());
        inspectionTask.setStatus(0);
        inspectionTask.setDeviceModel(timingTask.getDeviceModel());
        inspectionTask.setMachineryCategory(timingTask.getMachineryCategory());
        inspectionTask.setCreateUser(Integer.parseInt(timingTask.getRegistrantId().toString()));
        inspectionTask.setUpdateTime(LocalDateTime.now());
        inspectionTask.setCreateTime(LocalDateTime.now());
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java
@@ -5,6 +5,7 @@
import com.ruoyi.inspectiontask.pojo.TimingTask;
import lombok.RequiredArgsConstructor;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
@@ -20,12 +21,13 @@
@Component
@DisallowConcurrentExecution // ç¦æ­¢å¹¶å‘执行同一个Job
@RequiredArgsConstructor
public class TimingTaskJob implements Job, Serializable {
    private static final long serialVersionUID = 1L; // å¿…须定义序列化ID
    private final InspectionTaskMapper inspectionTaskMapper;
    private final JdbcTemplate jdbcTemplate;
    @Autowired
    private InspectionTaskMapper inspectionTaskMapper;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
@@ -46,7 +48,7 @@
            if (timingTask == null) {
                throw new JobExecutionException("找不到定时任务: " + taskId);
            }
//            if (!timingTask.isActive()) {
//                throw new JobExecutionException("定时任务已禁用: " + taskId);
//            }
src/main/java/com/ruoyi/oA/controller/OaProjectController.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/oA/controller/OaProjectPhaseController.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/oA/controller/OaProjectPhaseTaskController.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/oA/dto/OaProjectDto.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/oA/dto/OaProjectPhaseDto.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/oA/mapper/OaProjectMapper.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/oA/mapper/OaProjectPhaseMapper.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/oA/mapper/OaProjectPhaseTaskMapper.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/oA/pojo/OaProject.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/oA/pojo/OaProjectPhase.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/oA/pojo/OaProjectPhaseTask.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/oA/service/OaProjectPhaseService.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/oA/service/OaProjectPhaseTaskService.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/oA/service/OaProjectService.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/oA/service/impl/OaProjectPhaseServiceImpl.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/oA/service/impl/OaProjectPhaseTaskServiceImpl.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/oA/service/impl/OaProjectServiceImpl.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/procurementrecord/bean/dto/Details.java
ÎļþÃû´Ó src/main/java/com/ruoyi/procurementrecord/dto/Details.java ÐÞ¸Ä
@@ -1,4 +1,4 @@
package com.ruoyi.procurementrecord.dto;
package com.ruoyi.procurementrecord.bean.dto;
import lombok.Data;
src/main/java/com/ruoyi/procurementrecord/bean/dto/InventoryInformationDto.java
ÎļþÃû´Ó src/main/java/com/ruoyi/procurementrecord/dto/InventoryInformationDto.java ÐÞ¸Ä
@@ -1,4 +1,4 @@
package com.ruoyi.procurementrecord.dto;
package com.ruoyi.procurementrecord.bean.dto;
import lombok.Data;
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementAddDto.java
ÎļþÃû´Ó src/main/java/com/ruoyi/procurementrecord/dto/ProcurementAddDto.java ÐÞ¸Ä
@@ -1,4 +1,4 @@
package com.ruoyi.procurementrecord.dto;
package com.ruoyi.procurementrecord.bean.dto;
import lombok.Data;
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementDto.java
ÎļþÃû´Ó src/main/java/com/ruoyi/procurementrecord/dto/ProcurementDto.java ÐÞ¸Ä
@@ -1,4 +1,4 @@
package com.ruoyi.procurementrecord.dto;
package com.ruoyi.procurementrecord.bean.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementManagementUpdateDto.java
ÎļþÃû´Ó src/main/java/com/ruoyi/procurementrecord/dto/ProcurementManagementUpdateDto.java ÐÞ¸Ä
@@ -1,4 +1,4 @@
package com.ruoyi.procurementrecord.dto;
package com.ruoyi.procurementrecord.bean.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementPageDto.java
ÎļþÃû´Ó src/main/java/com/ruoyi/procurementrecord/dto/ProcurementPageDto.java ÐÞ¸Ä
@@ -1,4 +1,4 @@
package com.ruoyi.procurementrecord.dto;
package com.ruoyi.procurementrecord.bean.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementPageDtoCopy.java
ÎļþÃû´Ó src/main/java/com/ruoyi/procurementrecord/dto/ProcurementPageDtoCopy.java ÐÞ¸Ä
@@ -1,4 +1,4 @@
package com.ruoyi.procurementrecord.dto;
package com.ruoyi.procurementrecord.bean.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementRecordOutAdd.java
ÎļþÃû´Ó src/main/java/com/ruoyi/procurementrecord/dto/ProcurementRecordOutAdd.java ÐÞ¸Ä
@@ -1,4 +1,4 @@
package com.ruoyi.procurementrecord.dto;
package com.ruoyi.procurementrecord.bean.dto;
import lombok.Data;
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementRecordOutPageDto.java
ÎļþÃû´Ó src/main/java/com/ruoyi/procurementrecord/dto/ProcurementRecordOutPageDto.java ÐÞ¸Ä
@@ -1,4 +1,4 @@
package com.ruoyi.procurementrecord.dto;
package com.ruoyi.procurementrecord.bean.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
src/main/java/com/ruoyi/procurementrecord/bean/dto/ProcurementUpdateDto.java
ÎļþÃû´Ó src/main/java/com/ruoyi/procurementrecord/dto/ProcurementUpdateDto.java ÐÞ¸Ä
@@ -1,4 +1,4 @@
package com.ruoyi.procurementrecord.dto;
package com.ruoyi.procurementrecord.bean.dto;
import lombok.Data;
src/main/java/com/ruoyi/procurementrecord/bean/dto/ReturnManagementDto.java
ÎļþÃû´Ó src/main/java/com/ruoyi/procurementrecord/dto/ReturnManagementDto.java ÐÞ¸Ä
@@ -1,17 +1,9 @@
package com.ruoyi.procurementrecord.dto;
package com.ruoyi.procurementrecord.bean.dto;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.procurementrecord.pojo.ReturnManagement;
import com.ruoyi.procurementrecord.pojo.ReturnSaleProduct;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.List;
/**
src/main/java/com/ruoyi/procurementrecord/bean/dto/ReturnSaleProductDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
package com.ruoyi.procurementrecord.bean.dto;
import com.ruoyi.procurementrecord.pojo.ReturnSaleProduct;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class ReturnSaleProductDto extends ReturnSaleProduct {
    private String productName;
    private String model;
    private String unit;
    //未退货数量
    private BigDecimal unQuantity;
    //总退货数量
    private BigDecimal totalReturnNum;
    // é€€è´§æ€»ä»·
    private BigDecimal price;
    // é€€è´§æ€»ä»·
    private BigDecimal taxInclusiveUnitPrice;
    @Schema(description = "出库单号")
    private String outboundBatches;
    @Schema(description = "批次号")
    private String batchNo;
    @Schema(description = "发货出库数量")
    private BigDecimal stockOutNum;
}
src/main/java/com/ruoyi/procurementrecord/bean/vo/ShippingInfoVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package com.ruoyi.procurementrecord.bean.vo;
import com.ruoyi.sales.pojo.ShippingInfo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Data
@Schema(name = "ShippingInfoVo", description = "营销管理--发货信息")
public class ShippingInfoVo {
    private ShippingInfo shippingInfo;
    @Schema(description = "发货产品列表")
    private List<ShippingProductVo> shippingProductVoList;
}
src/main/java/com/ruoyi/procurementrecord/bean/vo/ShippingProductVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
package com.ruoyi.procurementrecord.bean.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Data
@Schema(name = "ShippingProductVo", description = "营销管理--发货出库产品列表")
public class ShippingProductVo {
    @Schema(description = "出库单id")
    private Long id;
    @Schema(description = "产品规格id")
    private Long productModelId;
    @Schema(description = "产品大类")
    private String productCategory;
    @Schema(description = "规格型号")
    private String specificationModel;
    @Schema(description = "单位")
    private String unit;
    @Schema(description = "出库单号")
    private String outboundBatches;
    @Schema(description = "发货出库数量")
    private BigDecimal stockOutNum;
    @Schema(description = "批次号")
    private String batchNo;
    @Schema(description = "未退货数")
    private BigDecimal unQuantity;
    @Schema(description = "退货总数")
    private BigDecimal totalReturnNum;
    @Schema(description = "含税单价")
    private BigDecimal taxInclusiveUnitPrice;
}
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementExceptionRecordController.java
@@ -1,24 +1,13 @@
package com.ruoyi.procurementrecord.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.procurementrecord.dto.*;
import com.ruoyi.procurementrecord.mapper.ProcurementExceptionRecordMapper;
import com.ruoyi.procurementrecord.pojo.ProcurementExceptionRecord;
import com.ruoyi.procurementrecord.service.ProcurementRecordService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * @author :yys
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordController.java
@@ -8,7 +8,7 @@
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.procurementrecord.dto.*;
import com.ruoyi.procurementrecord.bean.dto.*;
import com.ruoyi.procurementrecord.mapper.CustomStorageMapper;
import com.ruoyi.procurementrecord.pojo.CustomStorage;
import com.ruoyi.procurementrecord.service.ProcurementRecordService;
@@ -16,7 +16,6 @@
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.apache.ibatis.annotations.Delete;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordOutController.java
@@ -7,9 +7,9 @@
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.procurementrecord.dto.ProcurementRecordOutAdd;
import com.ruoyi.procurementrecord.dto.ProcurementRecordOutPageDto;
import com.ruoyi.procurementrecord.dto.ProcurementUpdateDto;
import com.ruoyi.procurementrecord.bean.dto.ProcurementRecordOutAdd;
import com.ruoyi.procurementrecord.bean.dto.ProcurementRecordOutPageDto;
import com.ruoyi.procurementrecord.bean.dto.ProcurementUpdateDto;
import com.ruoyi.procurementrecord.mapper.ProcurementRecordOutMapper;
import com.ruoyi.procurementrecord.service.ProcurementRecordOutService;
import io.swagger.v3.oas.annotations.tags.Tag;
src/main/java/com/ruoyi/procurementrecord/controller/ReturnManagementController.java
@@ -4,21 +4,16 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.procurementrecord.dto.ReturnManagementDto;
import com.ruoyi.procurementrecord.mapper.ReturnManagementMapper;
import com.ruoyi.procurementrecord.pojo.ReturnManagement;
import com.ruoyi.procurementrecord.bean.dto.ReturnManagementDto;
import com.ruoyi.procurementrecord.bean.vo.ShippingInfoVo;
import com.ruoyi.procurementrecord.pojo.ReturnSaleProduct;
import com.ruoyi.procurementrecord.service.ReturnManagementService;
import com.ruoyi.procurementrecord.service.ReturnSaleProductService;
import com.ruoyi.procurementrecord.service.impl.ReturnSaleProductServiceImpl;
import com.ruoyi.sales.dto.SalesLedgerDto;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
@@ -87,10 +82,10 @@
    }
    @GetMapping("/getByShippingId")
    @Operation(summary = "销售退货-根据出库单查询销售订单以及产品信息")
    @Operation(summary = "销售退货-根据发货单查询销售订单以及出库的产品信息")
    public AjaxResult getByShippingId(Long shippingId) {
        SalesLedgerDto salesLedgerDto = returnManagementService.getReturnManagementDtoByShippingIdId(shippingId);
        return success(salesLedgerDto);
        ShippingInfoVo shippingInfoVo = returnManagementService.getReturnManagementDtoByShippingIdId(shippingId);
        return success(shippingInfoVo);
    }
}
src/main/java/com/ruoyi/procurementrecord/dto/ReturnSaleProductDto.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementRecordMapper.java
@@ -3,9 +3,9 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.procurementrecord.dto.ProcurementDto;
import com.ruoyi.procurementrecord.dto.ProcurementPageDto;
import com.ruoyi.procurementrecord.dto.ProcurementPageDtoCopy;
import com.ruoyi.procurementrecord.bean.dto.ProcurementDto;
import com.ruoyi.procurementrecord.bean.dto.ProcurementPageDto;
import com.ruoyi.procurementrecord.bean.dto.ProcurementPageDtoCopy;
import com.ruoyi.procurementrecord.pojo.ProcurementRecordStorage;
import org.apache.ibatis.annotations.Param;
src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementRecordOutMapper.java
@@ -3,7 +3,7 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.procurementrecord.dto.ProcurementRecordOutPageDto;
import com.ruoyi.procurementrecord.bean.dto.ProcurementRecordOutPageDto;
import com.ruoyi.procurementrecord.pojo.ProcurementRecordOut;
import org.apache.ibatis.annotations.Param;
src/main/java/com/ruoyi/procurementrecord/mapper/ReturnManagementMapper.java
@@ -5,7 +5,7 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.SalesReturnDto;
import com.ruoyi.account.bean.vo.SalesReturnVo;
import com.ruoyi.procurementrecord.dto.ReturnManagementDto;
import com.ruoyi.procurementrecord.bean.dto.ReturnManagementDto;
import com.ruoyi.procurementrecord.pojo.ReturnManagement;
import org.apache.ibatis.annotations.Param;
@@ -25,5 +25,5 @@
    ReturnManagementDto getReturnManagementDtoById(Long id);
    IPage<SalesReturnVo> listPageBySalesReturn(Page page, @Param("req") SalesReturnDto salesReturnDto);
    IPage<SalesReturnVo> listPageAccountSalesReturn(Page page, @Param("req") SalesReturnDto salesReturnDto);
}
src/main/java/com/ruoyi/procurementrecord/mapper/ReturnSaleProductMapper.java
@@ -1,6 +1,6 @@
package com.ruoyi.procurementrecord.mapper;
import com.ruoyi.procurementrecord.dto.ReturnSaleProductDto;
import com.ruoyi.procurementrecord.bean.dto.ReturnSaleProductDto;
import com.ruoyi.procurementrecord.pojo.ReturnSaleProduct;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
src/main/java/com/ruoyi/procurementrecord/pojo/ReturnManagement.java
@@ -8,7 +8,6 @@
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
/**
 * @author :yys
@@ -64,10 +63,12 @@
    @Schema(description = "创建时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @Schema(description = "更新时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @Schema(description = "创建用户")
src/main/java/com/ruoyi/procurementrecord/pojo/ReturnSaleProduct.java
@@ -1,15 +1,12 @@
package com.ruoyi.procurementrecord.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.math.BigDecimal;
/**
 * <p>
@@ -37,8 +34,8 @@
    @Schema(description = "退货单id")
    private Long returnManagementId;
    @Schema(description = "退货产品id")
    private Long returnsalesLedgerProductId;
    @Schema(description = "关联出库单id")
    private Long stockOutRecordId;
    @Schema(description = "退货产品数量")
    private BigDecimal num;
src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordOutService.java
@@ -3,9 +3,9 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.procurementrecord.dto.ProcurementRecordOutAdd;
import com.ruoyi.procurementrecord.dto.ProcurementRecordOutPageDto;
import com.ruoyi.procurementrecord.dto.ProcurementUpdateDto;
import com.ruoyi.procurementrecord.bean.dto.ProcurementRecordOutAdd;
import com.ruoyi.procurementrecord.bean.dto.ProcurementRecordOutPageDto;
import com.ruoyi.procurementrecord.bean.dto.ProcurementUpdateDto;
import com.ruoyi.procurementrecord.pojo.ProcurementRecordOut;
import jakarta.servlet.http.HttpServletResponse;
src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordService.java
@@ -5,7 +5,7 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.procurementrecord.dto.*;
import com.ruoyi.procurementrecord.bean.dto.*;
import com.ruoyi.procurementrecord.pojo.CustomStorage;
import com.ruoyi.procurementrecord.pojo.ProcurementRecordStorage;
src/main/java/com/ruoyi/procurementrecord/service/ReturnManagementService.java
@@ -3,9 +3,9 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.procurementrecord.dto.ReturnManagementDto;
import com.ruoyi.procurementrecord.bean.dto.ReturnManagementDto;
import com.ruoyi.procurementrecord.bean.vo.ShippingInfoVo;
import com.ruoyi.procurementrecord.pojo.ReturnManagement;
import com.ruoyi.sales.dto.SalesLedgerDto;
/**
 * @author :yys
@@ -26,7 +26,7 @@
    boolean updateReturnManagementDto(ReturnManagementDto returnManagementDto);
    SalesLedgerDto getReturnManagementDtoByShippingIdId(Long shippingId);
    ShippingInfoVo getReturnManagementDtoByShippingIdId(Long shippingId);
    boolean handle(Long returnManagementId);
src/main/java/com/ruoyi/procurementrecord/service/ReturnSaleProductService.java
@@ -1,6 +1,6 @@
package com.ruoyi.procurementrecord.service;
import com.ruoyi.procurementrecord.dto.ReturnSaleProductDto;
import com.ruoyi.procurementrecord.bean.dto.ReturnSaleProductDto;
import com.ruoyi.procurementrecord.pojo.ReturnSaleProduct;
import com.baomidou.mybatisplus.extension.service.IService;
src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordOutServiceImpl.java
@@ -5,9 +5,9 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.procurementrecord.dto.ProcurementRecordOutAdd;
import com.ruoyi.procurementrecord.dto.ProcurementRecordOutPageDto;
import com.ruoyi.procurementrecord.dto.ProcurementUpdateDto;
import com.ruoyi.procurementrecord.bean.dto.ProcurementRecordOutAdd;
import com.ruoyi.procurementrecord.bean.dto.ProcurementRecordOutPageDto;
import com.ruoyi.procurementrecord.bean.dto.ProcurementUpdateDto;
import com.ruoyi.procurementrecord.mapper.ProcurementRecordOutMapper;
import com.ruoyi.procurementrecord.pojo.ProcurementRecordOut;
import com.ruoyi.procurementrecord.service.ProcurementRecordOutService;
src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordServiceImpl.java
@@ -12,7 +12,7 @@
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.procurementrecord.dto.*;
import com.ruoyi.procurementrecord.bean.dto.*;
import com.ruoyi.procurementrecord.mapper.CustomStorageMapper;
import com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper;
import com.ruoyi.procurementrecord.mapper.ProcurementRecordOutMapper;
src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java
@@ -8,21 +8,19 @@
import com.ruoyi.account.pojo.AccountExpense;
import com.ruoyi.account.service.SalesRefundAmountOrderService;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.procurementrecord.dto.ReturnManagementDto;
import com.ruoyi.procurementrecord.dto.ReturnSaleProductDto;
import com.ruoyi.procurementrecord.bean.dto.ReturnManagementDto;
import com.ruoyi.procurementrecord.bean.dto.ReturnSaleProductDto;
import com.ruoyi.procurementrecord.bean.vo.ShippingInfoVo;
import com.ruoyi.procurementrecord.bean.vo.ShippingProductVo;
import com.ruoyi.procurementrecord.mapper.ReturnManagementMapper;
import com.ruoyi.procurementrecord.pojo.ReturnManagement;
import com.ruoyi.procurementrecord.pojo.ReturnSaleProduct;
import com.ruoyi.procurementrecord.service.ReturnManagementService;
import com.ruoyi.procurementrecord.service.ReturnSaleProductService;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.sales.dto.SalesLedgerDto;
import com.ruoyi.sales.dto.SalesLedgerProductDto;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.sales.service.ShippingInfoService;
import lombok.RequiredArgsConstructor;
@@ -33,7 +31,6 @@
import org.springframework.util.ObjectUtils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -77,28 +74,28 @@
    @Override
    public boolean updateReturnManagementDto(ReturnManagementDto returnManagementDto) {
        List<ReturnSaleProduct> returnSaleProducts = new ArrayList<>();
        if (!CollectionUtils.isEmpty(returnManagementDto.getReturnSaleProducts())) {
            returnManagementDto.getReturnSaleProducts().stream().forEach(returnSaleProductDto -> {
                ReturnSaleProduct returnSaleProduct = new ReturnSaleProduct();
                BeanUtils.copyProperties(returnSaleProductDto, returnSaleProduct);
                returnSaleProducts.add(returnSaleProduct);
                if (returnSaleProductDto.getId() == null){
                    returnSaleProduct.setReturnManagementId(returnManagementDto.getId());
                    returnSaleProduct.setStatus(0);
                    returnSaleProductService.save(returnSaleProduct);
                }else returnSaleProductService.updateById(returnSaleProduct);
            });
        }
        returnSaleProductService.updateBatchById(returnSaleProducts);
        return updateById(returnManagementDto);
    }
    @Override
    public SalesLedgerDto getReturnManagementDtoByShippingIdId(Long shippingId) {
    public ShippingInfoVo getReturnManagementDtoByShippingIdId(Long shippingId) {
        ShippingInfo byId = shippingInfoService.getById(shippingId);
        SalesLedger salesLedger = salesLedgerMapper.selectById(byId.getSalesLedgerId());
        SalesLedgerDto salesLedgerDto = new SalesLedgerDto();
        BeanUtils.copyProperties(salesLedger, salesLedgerDto);
        List<SalesLedgerProductDto> salesLedgerProductDtos = shippingInfoService.getReturnManagementDtoById(byId.getId());
        salesLedgerDto.setProductDtoData(salesLedgerProductDtos);
         return salesLedgerDto;
        ShippingInfoVo shippingInfoVo = new ShippingInfoVo();
        shippingInfoVo.setShippingInfo(byId);
        List<ShippingProductVo> shippingProductVos = shippingInfoService.getReturnManagementDtoById(byId.getId());
        shippingInfoVo.setShippingProductVoList(shippingProductVos);
         return shippingInfoVo;
    }
    @Override
@@ -106,6 +103,7 @@
        ReturnManagement byId = this.getById(returnManagementId);
        List<ReturnSaleProductDto> list = returnSaleProductService.listReturnSaleProduct(returnManagementId);
        byId.setStatus(1);
        byId.setSettler(SecurityUtils.getLoginUser().getNickName());
        updateById(byId);
        SalesRefundAmountOrderDto salesRefundAmountOrder = new SalesRefundAmountOrderDto();
        salesRefundAmountOrder.setReturnManagementId(returnManagementId);
@@ -118,11 +116,11 @@
            salesRefundAmountOrder.setRefundedAmount(new BigDecimal(0));
            // æ˜¯å¦æœ‰è´¨é‡é—®é¢˜
            if (returnSaleProduct.getIsQuality() == 1) {
                // æœ‰è´¨é‡é—®é¢˜ï¼Œå…¥ä¸åˆæ ¼åº“
                stockUtils.addUnStock(returnSaleProduct.getProductModelId(),returnSaleProduct.getNum(), StockInUnQualifiedRecordTypeEnum.RETURN_UNSTOCK_IN.getCode(),returnSaleProduct.getId());
                // æœ‰è´¨é‡é—®é¢˜ï¼Œå…¥ä¸åˆæ ¼åº“(带批次)
                stockUtils.addUnStockWithBatchNo(returnSaleProduct.getProductModelId(),returnSaleProduct.getNum(), StockInQualifiedRecordTypeEnum.RETURN_UNSTOCK_IN.getCode(),returnSaleProduct.getId(),returnSaleProduct.getBatchNo());
            }else{
                // æ— è´¨é‡é—®é¢˜ï¼Œå…¥åˆæ ¼åº“
                stockUtils.addStock(returnSaleProduct.getProductModelId(),returnSaleProduct.getNum(), StockInQualifiedRecordTypeEnum.RETURN_HE_IN.getCode(),returnSaleProduct.getId());
                // æ— è´¨é‡é—®é¢˜ï¼Œå…¥åˆæ ¼åº“(带批次)
                stockUtils.addStockWithBatchNo(returnSaleProduct.getProductModelId(),returnSaleProduct.getNum(), StockInQualifiedRecordTypeEnum.RETURN_HE_IN.getCode(),returnSaleProduct.getId(),returnSaleProduct.getBatchNo());
            }
        }
        salesRefundAmountOrder.setRefundAmount(bigDecimal);
src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnSaleProductServiceImpl.java
@@ -1,7 +1,7 @@
package com.ruoyi.procurementrecord.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.procurementrecord.dto.ReturnSaleProductDto;
import com.ruoyi.procurementrecord.bean.dto.ReturnSaleProductDto;
import com.ruoyi.procurementrecord.mapper.ReturnSaleProductMapper;
import com.ruoyi.procurementrecord.pojo.ReturnSaleProduct;
import com.ruoyi.procurementrecord.service.ReturnSaleProductService;
src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
@@ -1,12 +1,13 @@
package com.ruoyi.procurementrecord.utils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.ruoyi.common.enums.ReviewStatusEnum;
import com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper;
import com.ruoyi.procurementrecord.mapper.ProcurementRecordOutMapper;
import com.ruoyi.stock.dto.StockInventoryDto;
import com.ruoyi.stock.dto.StockUninventoryDto;
import com.ruoyi.stock.mapper.StockInventoryMapper;
import com.ruoyi.stock.pojo.StockInRecord;
import com.ruoyi.stock.pojo.StockOutRecord;
import com.ruoyi.stock.service.StockInRecordService;
@@ -28,7 +29,6 @@
    private final StockInventoryService stockInventoryService;
    private final StockInRecordService stockInRecordService;
    private final StockOutRecordService stockOutRecordService;
    private final StockInventoryMapper stockInventoryMapper;
    /**
     * ä¸åˆæ ¼å…¥åº“
@@ -44,6 +44,24 @@
        stockUninventoryDto.setRecordType(String.valueOf(recordType));
        stockUninventoryDto.setQualitity(quantity);
        stockUninventoryDto.setProductModelId(productModelId);
        stockUninventoryService.addStockInRecordOnly(stockUninventoryDto);
    }
    /**
     * ä¸åˆæ ¼å…¥åº“带批次号
     *
     * @param productModelId
     * @param quantity
     * @param recordType
     * @param recordId
     */
    public void addUnStockWithBatchNo(Long productModelId, BigDecimal quantity, String recordType, Long recordId, String batchNo) {
        StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
        stockUninventoryDto.setRecordId(recordId);
        stockUninventoryDto.setRecordType(String.valueOf(recordType));
        stockUninventoryDto.setQualitity(quantity);
        stockUninventoryDto.setProductModelId(productModelId);
        stockUninventoryDto.setBatchNo(batchNo);
        stockUninventoryService.addStockInRecordOnly(stockUninventoryDto);
    }
@@ -66,8 +84,6 @@
    /**
     * åˆæ ¼å…¥åº“
     * @param productModelId
     * @param quantity
     * @param recordType
     * @param recordId
     */
@@ -105,15 +121,6 @@
     * @param recordType
     * @param recordId
     */
    public void substractStock(Long productModelId, BigDecimal quantity, String recordType, Long recordId) {
        StockInventoryDto stockInventoryDto = new StockInventoryDto();
        stockInventoryDto.setRecordId(recordId);
        stockInventoryDto.setRecordType(String.valueOf(recordType));
        stockInventoryDto.setQualitity(quantity);
        stockInventoryDto.setProductModelId(productModelId);
        stockInventoryService.subtractStockInventory(stockInventoryDto);
    }
    public void substractStock(Long productModelId, BigDecimal quantity, String recordType, Long recordId, String batchNo) {
        StockInventoryDto stockInventoryDto = new StockInventoryDto();
        stockInventoryDto.setRecordId(recordId);
@@ -121,22 +128,34 @@
        stockInventoryDto.setQualitity(quantity);
        stockInventoryDto.setProductModelId(productModelId);
        stockInventoryDto.setBatchNo(batchNo);
        stockInventoryService.subtractStockInventory(stockInventoryDto);
        stockInventoryService.addStockOutRecordOnly(stockInventoryDto);
    }
    /**
     * å‘货审批状态更改
     * @param recordType
     * @param recordId
     */
    public void shipmentStatus(String recordType, Long recordId) {
        LambdaQueryWrapper<StockOutRecord> queryWrapper = new LambdaQueryWrapper<StockOutRecord>().eq(StockOutRecord::getRecordType, recordType)
                .eq(StockOutRecord::getRecordId, recordId);
        stockOutRecordService.list(queryWrapper).stream().forEach(stockOutRecord -> {
            stockOutRecord.setApprovalStatus(0);
            stockOutRecordService.updateById(stockOutRecord);
        });
    }
    //不合格库存删除
    public void deleteStockInRecord(Long recordId, String recordType) {
        StockInRecord one = stockInRecordService.getOne(new QueryWrapper<StockInRecord>()
                .lambda().eq(StockInRecord::getRecordId, recordId)
                .eq(StockInRecord::getRecordType, recordType));
                .eq(StockInRecord::getRecordType, recordType), false);
        if (ObjectUtils.isNotEmpty(one)) {
            stockInRecordService.batchDelete(Collections.singletonList(one.getId()));
            //将库存减回来
            StockInventoryDto stockInventoryDto = new StockInventoryDto();
            stockInventoryDto.setRecordId(recordId);
            stockInventoryDto.setRecordType(recordType);
            stockInventoryDto.setQualitity(one.getStockInNum());
            stockInventoryMapper.updateSubtractStockInventory((stockInventoryDto));
            if (ReviewStatusEnum.APPROVED.getCode().equals(one.getApprovalStatus())) {
                stockInRecordService.batchDelete(Collections.singletonList(one.getId()));
            } else {
                stockInRecordService.removeById(one.getId());
            }
        }
    }
@@ -144,15 +163,13 @@
    public void deleteStockOutRecord(Long recordId, String recordType) {
        StockOutRecord one = stockOutRecordService.getOne(new QueryWrapper<StockOutRecord>()
                .lambda().eq(StockOutRecord::getRecordId, recordId)
                .eq(StockOutRecord::getRecordType, recordType));
                .eq(StockOutRecord::getRecordType, recordType), false);
        if (ObjectUtils.isNotEmpty(one)) {
            stockOutRecordService.batchDelete(Collections.singletonList(one.getId()));
            //将库存加回来
            StockInventoryDto stockInventoryDto = new StockInventoryDto();
            stockInventoryDto.setRecordId(recordId);
            stockInventoryDto.setRecordType(recordType);
            stockInventoryDto.setQualitity(one.getStockOutNum());
            stockInventoryMapper.updateAddStockInventory((stockInventoryDto));
            if (ReviewStatusEnum.APPROVED.getCode().equals(one.getApprovalStatus())) {
                stockOutRecordService.batchDelete(Collections.singletonList(one.getId()));
            } else {
                stockOutRecordService.removeById(one.getId());
            }
        }
    }
src/main/java/com/ruoyi/production/pojo/ProductionAccount.java
@@ -27,12 +27,6 @@
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @Schema(description = "销售台账id")
    private Long salesLedgerId;
    @Schema(description = "销售产品规格id")
    private Long salesLedgerProductId;
    @Schema(description = "报工表id")
    private Long productionProductMainId;
src/main/java/com/ruoyi/production/pojo/ProductionProductInput.java
@@ -14,9 +14,6 @@
    @TableId(type = IdType.AUTO)
    private Long id;
    @Schema(description = "报工id")
    private Long productMainId;
    @Schema(description = "生产报工主表id")
    private Long productionProductMainId;
src/main/java/com/ruoyi/production/pojo/ProductionProductOutput.java
@@ -14,9 +14,6 @@
    @TableId(type = IdType.AUTO)
    private Long id;
    @Schema(description = "报工id")
    private Long productMainId;
    @Schema(description = "生产报工主表id")
    private Long productionProductMainId;
src/main/java/com/ruoyi/production/service/impl/ProductionBomStructureServiceImpl.java
@@ -1,18 +1,29 @@
package com.ruoyi.production.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.production.bean.dto.ProductionBomStructureDto;
import com.ruoyi.production.bean.vo.ProductionBomStructureVo;
import com.ruoyi.production.mapper.ProductionBomStructureMapper;
import com.ruoyi.production.mapper.ProductionOperationTaskMapper;
import com.ruoyi.production.mapper.ProductionOrderBomMapper;
import com.ruoyi.production.mapper.ProductionOrderMapper;
import com.ruoyi.production.mapper.ProductionOrderRoutingOperationMapper;
import com.ruoyi.production.pojo.ProductionBomStructure;
import com.ruoyi.production.pojo.ProductionOperationTask;
import com.ruoyi.production.pojo.ProductionOrder;
import com.ruoyi.production.pojo.ProductionOrderBom;
import com.ruoyi.production.pojo.ProductionOrderRoutingOperation;
import com.ruoyi.production.service.ProductionBomStructureService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
/**
 * <p>
@@ -26,7 +37,11 @@
@RequiredArgsConstructor()
public class ProductionBomStructureServiceImpl extends ServiceImpl<ProductionBomStructureMapper, ProductionBomStructure> implements ProductionBomStructureService {
    private  final ProductionBomStructureMapper productionBomStructureMapper;
    private final ProductionBomStructureMapper productionBomStructureMapper;
    private final ProductionOrderBomMapper productionOrderBomMapper;
    private final ProductionOrderMapper productionOrderMapper;
    private final ProductionOrderRoutingOperationMapper productionOrderRoutingOperationMapper;
    private final ProductionOperationTaskMapper productionOperationTaskMapper;
    /**
     * æ ¹æ®BOM查询并组装结构树。
@@ -136,9 +151,173 @@
        if (!updateList.isEmpty()) {
            this.updateBatchById(updateList);
        }
        syncDemandedQuantityAndTaskPlanQuantity(orderBomId, dto.getProductionOrderId());
        return true;
    }
    private void syncDemandedQuantityAndTaskPlanQuantity(Long orderBomId, Long productionOrderId) {
        if (orderBomId == null) {
            return;
        }
        ProductionOrderBom orderBom = productionOrderBomMapper.selectById(orderBomId);
        if (orderBom == null) {
            return;
        }
        Long currentProductionOrderId = productionOrderId != null ? productionOrderId : orderBom.getProductionOrderId();
        if (currentProductionOrderId == null) {
            return;
        }
        ProductionOrder productionOrder = productionOrderMapper.selectById(currentProductionOrderId);
        if (productionOrder == null) {
            return;
        }
        BigDecimal orderQuantity = defaultDecimal(productionOrder.getQuantity());
        List<ProductionBomStructure> structureList = this.list(
                Wrappers.<ProductionBomStructure>lambdaQuery()
                        .eq(ProductionBomStructure::getProductionOrderBomId, orderBomId)
                        .orderByAsc(ProductionBomStructure::getId));
        syncStructureDemandedQuantity(structureList, orderQuantity);
        syncTaskPlanQuantity(
                currentProductionOrderId,
                structureList,
                orderQuantity,
                orderBom.getProductModelId() != null ? orderBom.getProductModelId() : productionOrder.getProductModelId());
    }
    private void syncStructureDemandedQuantity(List<ProductionBomStructure> structureList, BigDecimal orderQuantity) {
        if (structureList == null || structureList.isEmpty()) {
            return;
        }
        List<ProductionBomStructure> updateList = new ArrayList<>();
        for (ProductionBomStructure structure : structureList) {
            if (structure == null || structure.getId() == null) {
                continue;
            }
            BigDecimal demandedQuantity = defaultDecimal(structure.getUnitQuantity()).multiply(orderQuantity);
            if (compareDecimal(structure.getDemandedQuantity(), demandedQuantity) == 0) {
                continue;
            }
            ProductionBomStructure update = new ProductionBomStructure();
            update.setId(structure.getId());
            update.setDemandedQuantity(demandedQuantity);
            updateList.add(update);
            structure.setDemandedQuantity(demandedQuantity);
        }
        if (!updateList.isEmpty()) {
            this.updateBatchById(updateList);
        }
    }
    private void syncTaskPlanQuantity(Long productionOrderId,
                                      List<ProductionBomStructure> structureList,
                                      BigDecimal orderQuantity,
                                      Long rootProductModelId) {
        List<ProductionOperationTask> taskList = productionOperationTaskMapper.selectList(
                Wrappers.<ProductionOperationTask>lambdaQuery()
                        .eq(ProductionOperationTask::getProductionOrderId, productionOrderId)
                        .orderByAsc(ProductionOperationTask::getId));
        if (taskList == null || taskList.isEmpty()) {
            return;
        }
        Set<Long> routingOperationIds = taskList.stream()
                .map(ProductionOperationTask::getProductionOrderRoutingOperationId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (routingOperationIds.isEmpty()) {
            return;
        }
        Map<Long, ProductionOrderRoutingOperation> routingOperationMap = productionOrderRoutingOperationMapper
                .selectBatchIds(routingOperationIds)
                .stream()
                .filter(item -> item != null && item.getId() != null)
                .collect(Collectors.toMap(ProductionOrderRoutingOperation::getId, item -> item, (left, right) -> left));
        Map<String, BigDecimal> demandedQuantityMap = buildOperationDemandedQuantityMap(structureList, rootProductModelId, orderQuantity);
        for (ProductionOperationTask task : taskList) {
            if (task == null || task.getId() == null || task.getProductionOrderRoutingOperationId() == null) {
                continue;
            }
            ProductionOrderRoutingOperation routingOperation = routingOperationMap.get(task.getProductionOrderRoutingOperationId());
            if (routingOperation == null || routingOperation.getTechnologyRoutingOperationId() == null) {
                continue;
            }
            BigDecimal planQuantity = resolveTaskPlanQuantity(routingOperation, demandedQuantityMap, orderQuantity);
            if (compareDecimal(task.getPlanQuantity(), planQuantity) == 0) {
                continue;
            }
            ProductionOperationTask update = new ProductionOperationTask();
            update.setId(task.getId());
            update.setPlanQuantity(planQuantity);
            productionOperationTaskMapper.updateById(update);
        }
    }
    private Map<String, BigDecimal> buildOperationDemandedQuantityMap(List<ProductionBomStructure> structureList,
                                                                      Long rootProductModelId,
                                                                      BigDecimal orderQuantity) {
        if (structureList == null || structureList.isEmpty()) {
            return Collections.emptyMap();
        }
        Map<Long, ProductionBomStructure> structureById = structureList.stream()
                .filter(item -> item != null && item.getId() != null)
                .collect(Collectors.toMap(ProductionBomStructure::getId, item -> item, (left, right) -> left));
        Map<String, BigDecimal> demandedQuantityMap = new HashMap<>();
        for (ProductionBomStructure bomStructure : structureList) {
            if (bomStructure == null || bomStructure.getTechnologyOperationId() == null || bomStructure.getUnitQuantity() == null) {
                continue;
            }
            Long outputProductModelId = resolveOutputProductModelId(bomStructure, structureById, rootProductModelId);
            String key = buildOperationDemandedQuantityKey(bomStructure.getTechnologyOperationId(), outputProductModelId);
            demandedQuantityMap.merge(key, bomStructure.getUnitQuantity().multiply(orderQuantity), BigDecimal::add);
        }
        return demandedQuantityMap;
    }
    private BigDecimal resolveTaskPlanQuantity(ProductionOrderRoutingOperation routingOperation,
                                               Map<String, BigDecimal> demandedQuantityMap,
                                               BigDecimal orderQuantity) {
        if (routingOperation == null || demandedQuantityMap == null || demandedQuantityMap.isEmpty()) {
            return orderQuantity;
        }
        String key = buildOperationDemandedQuantityKey(
                routingOperation.getTechnologyOperationId(),
                routingOperation.getProductModelId());
        BigDecimal planQuantity = demandedQuantityMap.get(key);
        return planQuantity != null ? planQuantity : orderQuantity;
    }
    private String buildOperationDemandedQuantityKey(Long operationId, Long outputProductModelId) {
        return String.valueOf(operationId) + "#" + String.valueOf(outputProductModelId);
    }
    private Long resolveOutputProductModelId(ProductionBomStructure bomStructure,
                                             Map<Long, ProductionBomStructure> structureById,
                                             Long rootProductModelId) {
        if (bomStructure == null) {
            return rootProductModelId;
        }
        Long parentId = bomStructure.getParentId();
        if (parentId == null) {
            return rootProductModelId != null ? rootProductModelId : bomStructure.getProductModelId();
        }
        ProductionBomStructure parent = structureById.get(parentId);
        if (parent != null && parent.getProductModelId() != null) {
            return parent.getProductModelId();
        }
        return rootProductModelId != null ? rootProductModelId : bomStructure.getProductModelId();
    }
    private BigDecimal defaultDecimal(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }
    private int compareDecimal(BigDecimal left, BigDecimal right) {
        return defaultDecimal(left).compareTo(defaultDecimal(right));
    }
    /**
     * å°†æ ‘形结构拍平成列表,便于统一保存。
     */
src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
@@ -245,6 +245,7 @@
                        .eq(TechnologyRoutingOperation::getTechnologyRoutingId, technologyRouting.getId())
                        .orderByDesc(TechnologyRoutingOperation::getDragSort)
                        .orderByDesc(TechnologyRoutingOperation::getId));
        Map<String, BigDecimal> operationDemandedQuantityMap = buildOperationDemandedQuantityMap(technologyRouting, productionOrder);
        Map<Long, String> operationNameMap = technologyOperationMapper.selectBatchIds(
        // éåŽ†å¤„ç†æ•°æ®å¹¶ç»„è£…ç»“æžœ
                        routingOperations.stream()
@@ -278,7 +279,7 @@
                ProductionOperationTask task = new ProductionOperationTask();
                task.setProductionOrderRoutingOperationId(targetOperation.getId());
                task.setProductionOrderId(productionOrder.getId());
                task.setPlanQuantity(defaultDecimal(productionOrder.getQuantity()));
                task.setPlanQuantity(resolveTaskPlanQuantity(sourceOperation, operationDemandedQuantityMap, productionOrder));
                task.setCompleteQuantity(BigDecimal.ZERO);
                task.setWorkOrderNo(generateNextTaskNo());
                task.setStatus(2);
@@ -311,6 +312,72 @@
            }
        }
        return syncedParamCount;
    }
    private Map<String, BigDecimal> buildOperationDemandedQuantityMap(TechnologyRouting technologyRouting,
                                                                      ProductionOrder productionOrder) {
        if (technologyRouting == null || technologyRouting.getBomId() == null) {
            return Collections.emptyMap();
        }
        BigDecimal orderQuantity = defaultDecimal(productionOrder == null ? null : productionOrder.getQuantity());
        List<TechnologyBomStructure> bomStructures = technologyBomStructureMapper.selectList(
                Wrappers.<TechnologyBomStructure>lambdaQuery()
                        .eq(TechnologyBomStructure::getBomId, technologyRouting.getBomId())
                        .isNotNull(TechnologyBomStructure::getOperationId)
                        .orderByAsc(TechnologyBomStructure::getId));
        if (bomStructures.isEmpty()) {
            return Collections.emptyMap();
        }
        Map<Long, TechnologyBomStructure> structureById = bomStructures.stream()
                .filter(item -> item != null && item.getId() != null)
                .collect(Collectors.toMap(TechnologyBomStructure::getId, item -> item, (left, right) -> left));
        Map<String, BigDecimal> demandedQuantityMap = new HashMap<>();
        for (TechnologyBomStructure bomStructure : bomStructures) {
            if (bomStructure == null || bomStructure.getOperationId() == null) {
                continue;
            }
            BigDecimal unitQuantity = bomStructure.getUnitQuantity();
            if (unitQuantity == null) {
                continue;
            }
            Long outputProductModelId = resolveOutputProductModelId(bomStructure, structureById, technologyRouting.getProductModelId());
            String key = buildOperationDemandedQuantityKey(bomStructure.getOperationId(), outputProductModelId);
            demandedQuantityMap.merge(key, unitQuantity.multiply(orderQuantity), BigDecimal::add);
        }
        return demandedQuantityMap;
    }
    private BigDecimal resolveTaskPlanQuantity(TechnologyRoutingOperation sourceOperation,
                                               Map<String, BigDecimal> operationDemandedQuantityMap,
                                               ProductionOrder productionOrder) {
        if (sourceOperation == null || operationDemandedQuantityMap == null || operationDemandedQuantityMap.isEmpty()) {
            return defaultDecimal(productionOrder == null ? null : productionOrder.getQuantity());
        }
        String key = buildOperationDemandedQuantityKey(sourceOperation.getTechnologyOperationId(), sourceOperation.getProductModelId());
        BigDecimal planQuantity = operationDemandedQuantityMap.get(key);
        return planQuantity != null ? planQuantity : defaultDecimal(productionOrder == null ? null : productionOrder.getQuantity());
    }
    private String buildOperationDemandedQuantityKey(Long operationId, Long outputProductModelId) {
        return String.valueOf(operationId) + "#" + String.valueOf(outputProductModelId);
    }
    private Long resolveOutputProductModelId(TechnologyBomStructure bomStructure,
                                             Map<Long, TechnologyBomStructure> structureById,
                                             Long routingProductModelId) {
        if (bomStructure == null) {
            return routingProductModelId;
        }
        Long parentId = bomStructure.getParentId();
        if (parentId == null) {
            return routingProductModelId != null ? routingProductModelId : bomStructure.getProductModelId();
        }
        TechnologyBomStructure parent = structureById.get(parentId);
        if (parent != null && parent.getProductModelId() != null) {
            return parent.getProductModelId();
        }
        return routingProductModelId != null ? routingProductModelId : bomStructure.getProductModelId();
    }
    private ProductionOrderBom syncProductionOrderBomSnapshot(ProductionOrder productionOrder, TechnologyRouting technologyRouting) {
@@ -788,12 +855,10 @@
                if (reportOutput == null) {
                    continue;
                }
                Long reportMainId = reportOutput.getProductionProductMainId() != null
                        ? reportOutput.getProductionProductMainId()
                        : reportOutput.getProductMainId();
                if (reportMainId == null) {
                if (reportOutput.getProductionProductMainId() == null) {
                    continue;
                }
                Long reportMainId = reportOutput.getProductionProductMainId();
                reportOutputMap.computeIfAbsent(reportMainId, k -> new ArrayList<>()).add(reportOutput);
            }
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -12,7 +12,6 @@
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.bean.BeanUtils;
@@ -27,6 +26,8 @@
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.quality.mapper.*;
import com.ruoyi.quality.pojo.*;
import com.ruoyi.stock.pojo.StockInRecord;
import com.ruoyi.stock.service.StockInRecordService;
import com.ruoyi.stock.dto.StockInventoryDto;
import com.ruoyi.stock.service.StockInventoryService;
import com.ruoyi.technology.mapper.TechnologyOperationMapper;
@@ -78,6 +79,7 @@
    private final TechnologyRoutingOperationMapper technologyRoutingOperationMapper;
    private final TechnologyOperationMapper technologyOperationMapper;
    private final StockUtils stockUtils;
    private final StockInRecordService stockInRecordService;
    private final StockInventoryService stockInventoryService;
    @Override
@@ -292,7 +294,6 @@
            // å½“前实现按工序成品直接作为投入,后续若接入领料记录可在这里替换来源。
            ProductionProductInput productionProductInput = new ProductionProductInput();
            productionProductInput.setProductionProductMainId(productionProductMain.getId());
            productionProductInput.setProductMainId(productionProductMain.getId());
            productionProductInput.setProductModelId(item.getProductModelId());
            productionProductInput.setInputQuantity(item.getUnitQuantity().multiply(defaultDecimal(dto.getQuantity())));
            productionProductInput.setQuantity(productionProductInput.getInputQuantity());
@@ -301,13 +302,14 @@
        ProductionProductOutput productionProductOutput = new ProductionProductOutput();
        productionProductOutput.setProductionProductMainId(productionProductMain.getId());
        productionProductOutput.setProductMainId(productionProductMain.getId());
        productionProductOutput.setProductModelId(productModel.getId());
        productionProductOutput.setQuantity(defaultDecimal(dto.getQuantity()));
        productionProductOutput.setScrapQty(defaultDecimal(dto.getScrapQty()));
        productionProductOutputMapper.insert(productionProductOutput);
        BigDecimal reportQty = defaultDecimal(productionProductOutput.getQuantity());
        BigDecimal scrapQty = defaultDecimal(productionProductOutput.getScrapQty());
        BigDecimal productQty = reportQty;
        String qualifiedBatchNo = null;
        List<ProductionOrderRoutingOperation> routingOperationList = productionOrderRoutingOperationMapper.selectList(
                Wrappers.<ProductionOrderRoutingOperation>lambdaQuery()
@@ -353,6 +355,11 @@
                stockInventoryDto.setQualitity(productQty);
                stockInventoryDto.setProductModelId(productModel.getId());
                stockInventoryService.addStockInRecordOnly(stockInventoryDto);
                qualifiedBatchNo = resolveLatestStockInBatchNo(
                        productionProductMain.getId(),
                        StockInQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_IN.getCode(),
                        productModel.getId(),
                        "0");
            }
            productionOperationTask.setCompleteQuantity(defaultDecimal(productionOperationTask.getCompleteQuantity()).add(productQty));
@@ -391,8 +398,6 @@
            }
            ProductionAccount productionAccount = new ProductionAccount();
            productionAccount.setProductionProductMainId(productionProductMain.getId());
//            productionAccount.setSalesLedgerId(productionOrder.getSalesLedgerId());
//            productionAccount.setSalesLedgerProductId(productionOrder.getSalesLedgerProductId() == null ? null : productionOrder.getSalesLedgerProductId().longValue());
            productionAccount.setSchedulingUserId(user == null ? null : user.getUserId());
            productionAccount.setSchedulingUserName(user == null ? dto.getUserName() : user.getNickName());
            productionAccount.setFinishedNum(productQty);
@@ -401,11 +406,37 @@
            productionAccount.setSchedulingDate(LocalDateTime.now());
            productionAccountMapper.insert(productionAccount);
        }
//        if (defaultDecimal(dto.getScrapQty()).compareTo(BigDecimal.ZERO) > 0) {
//            stockUtils.addUnStock(productModel.getId(), dto.getScrapQty(),
//                    StockInUnQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode(), productionProductMain.getId());
//        }
        if (scrapQty.compareTo(BigDecimal.ZERO) > 0) {
            stockUtils.addUnStockWithBatchNo(
                    productModel.getId(),
                    scrapQty,
                    StockInQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode(),
                    productionProductMain.getId(),
                    qualifiedBatchNo);
        }
        return true;
    }
    private String resolveLatestStockInBatchNo(Long recordId,
                                               String recordType,
                                               Long productModelId,
                                               String stockType) {
        if (recordId == null || productModelId == null) {
            return null;
        }
        StockInRecord stockInRecord = stockInRecordService.getOne(
                Wrappers.<StockInRecord>lambdaQuery()
                        .eq(StockInRecord::getRecordId, recordId)
                        .eq(StockInRecord::getRecordType, recordType)
                        .eq(StockInRecord::getProductModelId, productModelId)
                        .eq(StockInRecord::getType, stockType)
                        .orderByDesc(StockInRecord::getId)
                        .last("limit 1"),
                false);
        if (stockInRecord == null) {
            throw new ServiceException("未找到对应的入库申请记录");
        }
        return stockInRecord.getBatchNo();
    }
    private void syncOperationParamInputValue(ProductionProductMainDto dto,
@@ -605,7 +636,7 @@
        productionOrderRoutingOperationParamMapper.delete(
                Wrappers.<ProductionOrderRoutingOperationParam>lambdaQuery()
                        .eq(ProductionOrderRoutingOperationParam::getProductionProductMainId, productionProductMain.getId()));
        stockUtils.deleteStockInRecord(productionProductMain.getId(), StockInUnQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode());
        stockUtils.deleteStockInRecord(productionProductMain.getId(), StockInQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode());
        stockUtils.deleteStockInRecord(productionProductMain.getId(), StockInQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_IN.getCode());
        stockUtils.deleteStockOutRecord(productionProductMain.getId(), StockOutQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_OUT.getCode());
        productionProductMainMapper.deleteById(productionProductMain.getId());
src/main/java/com/ruoyi/projectManagement/service/impl/PlanServiceImpl.java
@@ -7,6 +7,7 @@
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.service.CustomerFollowUpFileService;
import com.ruoyi.basic.utils.FileUtil;
@@ -53,13 +54,13 @@
    @Transactional(rollbackFor = Exception.class)
    public void savePlan(SavePlanVo savePlanVo) {
        Plan plan = BeanUtil.copyProperties(savePlanVo, Plan.class);
        // é™„件处理
        fileUtil.saveStorageAttachmentByRecordTypeAndRecordId("", RecordTypeEnum.PLAN, savePlanVo.getId(), savePlanVo.getStorageBlobDTOs());
        if (savePlanVo.getId() == null) {
            planMapper.insert(plan);
        } else {
            planMapper.updateById(plan);
        }
        // é™„件处理
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.PLAN, plan.getId(), savePlanVo.getStorageBlobDTOs());
        planService.savePlanNode(plan.getId(), savePlanVo.getSavePlanNodeList());
    }
src/main/java/com/ruoyi/projectManagement/service/impl/handle/InfoStageHandleService.java
@@ -5,6 +5,7 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.service.CustomerFollowUpFileService;
import com.ruoyi.basic.utils.FileUtil;
@@ -48,18 +49,12 @@
    @Transactional
    public void save(@NotNull SaveInfoStageVo saveInfoStageVo) {
        InfoStage infoStage = BeanUtil.copyProperties(saveInfoStageVo, InfoStage.class);
        // é™„件处理
        String attachmentIds = StrUtil.join(",", Optional.ofNullable(saveInfoStageVo.getAttachmentIds()).orElse(Collections.emptyList()));
        infoStage.setAttachment(attachmentIds);
        fileUtil.saveStorageAttachmentByRecordTypeAndRecordId("", RecordTypeEnum.INFO_STAGE, infoStage.getProjectManagementInfoId(), saveInfoStageVo.getStorageBlobDTOs());
        if (infoStage.getId() == null) {
            infoStageMapper.insert(infoStage);
        } else {
            infoStageMapper.updateById(infoStage);
        }
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.INFO_STAGE, infoStage.getId(), saveInfoStageVo.getStorageBlobDTOs());
        infoStageHandleService.syncInfoStage(infoStage.getProjectManagementInfoId());
    }
src/main/java/com/ruoyi/purchase/controller/PurchaseReturnOrdersController.java
@@ -8,9 +8,13 @@
import com.ruoyi.purchase.dto.PurchaseReturnOrderDto;
import com.ruoyi.purchase.mapper.PurchaseReturnOrdersMapper;
import com.ruoyi.purchase.service.PurchaseReturnOrdersService;
import com.ruoyi.purchase.vo.PurchaseStockInProductVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * <p>
@@ -56,5 +60,12 @@
        return AjaxResult.success();
    }
    @GetMapping("/getByPurchaseLedgerId")
    @Operation(summary = "采购退货-根据采购订单id查询采购订单对应的入库产品信息")
    public AjaxResult getByPurchaseLedgerId(Long purchaseLedgerId) {
        List<PurchaseStockInProductVo> purchaseStockInProductVos = purchaseReturnOrdersService.getByPurchaseLedgerId(purchaseLedgerId);
        return AjaxResult.success(purchaseStockInProductVos);
    }
}
src/main/java/com/ruoyi/purchase/dto/PurchaseReturnOrderProductsDto.java
@@ -9,5 +9,7 @@
    private String productName;
    private String model;
    private String unit;
    //批次号
    private String batchNo;
}
src/main/java/com/ruoyi/purchase/dto/SimpleReturnOrderGroupDto.java
@@ -16,6 +16,6 @@
@AllArgsConstructor
@NoArgsConstructor
public class SimpleReturnOrderGroupDto implements Serializable {
    private Long salesLedgerProductId;
    private Long productModelId;
    private BigDecimal sumReturnQuantity;
}
src/main/java/com/ruoyi/purchase/mapper/PurchaseReturnOrdersMapper.java
@@ -1,14 +1,20 @@
package com.ruoyi.purchase.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.PurchaseReturnDto;
import com.ruoyi.account.bean.vo.PurchaseReturnVo;
import com.ruoyi.purchase.dto.PurchaseReturnOrderDto;
import com.ruoyi.purchase.pojo.PurchaseReturnOrders;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.purchase.dto.PurchaseReturnOrderHasAllInfoDto;
import com.ruoyi.purchase.pojo.PurchaseReturnOrders;
import com.ruoyi.purchase.vo.PurchaseReturnOrderProductsDetailVo;
import com.ruoyi.purchase.vo.PurchaseStockInProductVo;
import jakarta.validation.constraints.NotNull;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <p>
@@ -23,4 +29,11 @@
    IPage<PurchaseReturnOrderHasAllInfoDto> listPage(Page page, @Param("params") PurchaseReturnOrderDto purchaseReturnOrder);
    PurchaseReturnOrderHasAllInfoDto getPurchaseReturnOrderHasAllInfoById(@Param("id") @NotNull Long id);
    IPage<PurchaseReturnVo> listPageAccountPurchaseReturn(Page page, @Param("req") PurchaseReturnDto purchaseReturnDto);
    //根据采购订单id查询采购订单对应的入库产品信息
    List<PurchaseStockInProductVo> getByPurchaseLedgerId(@Param("purchaseLedgerId") Long purchaseLedgerId);
    List<PurchaseReturnOrderProductsDetailVo> getPurchaseReturnOrderProductsDetailById(@Param("id") Long id);
}
src/main/java/com/ruoyi/purchase/pojo/PurchaseReturnOrderProducts.java
@@ -1,20 +1,17 @@
package com.ruoyi.purchase.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * <p>
 *
 *
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
@@ -34,7 +31,7 @@
    @Schema(description = "退货单id")
    private Long purchaseReturnOrderId;
    @Schema(description = "采购产品id")
    @Schema(description = "销售台账产品id")
    private Long salesLedgerProductId;
    @Schema(description = "退货数量")
@@ -54,4 +51,7 @@
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    @Schema(description = "关联入库单id")
    private Long stockInRecordId;
}
src/main/java/com/ruoyi/purchase/service/PurchaseReturnOrdersService.java
@@ -8,7 +8,10 @@
import com.ruoyi.purchase.vo.PurchaseReturnDetailsVo;
import com.ruoyi.purchase.dto.PurchaseReturnOrderHasAllInfoDto;
import com.ruoyi.purchase.vo.PurchaseStockInProductVo;
import jakarta.validation.constraints.NotNull;
import java.util.List;
/**
 * <p>
@@ -27,4 +30,7 @@
    PurchaseReturnDetailsVo getPurchaseReturnOrderDtoById(@NotNull Long id);
    void deleteById(@NotNull Long id);
    List<PurchaseStockInProductVo> getByPurchaseLedgerId(Long purchaseLedgerId);
}
src/main/java/com/ruoyi/purchase/service/impl/PurchaseReturnOrdersServiceImpl.java
@@ -1,7 +1,6 @@
package com.ruoyi.purchase.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -9,24 +8,24 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.account.pojo.AccountIncome;
import com.ruoyi.account.service.AccountIncomeService;
import com.ruoyi.common.enums.SaleEnum;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.purchase.dto.PurchaseReturnOrderDto;
import com.ruoyi.purchase.dto.PurchaseReturnOrderHasAllInfoDto;
import com.ruoyi.purchase.dto.PurchaseReturnOrderProductsDto;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.mapper.PurchaseReturnOrderProductsMapper;
import com.ruoyi.purchase.mapper.PurchaseReturnOrdersMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.purchase.pojo.PurchaseReturnOrderProducts;
import com.ruoyi.purchase.pojo.PurchaseReturnOrders;
import com.ruoyi.purchase.service.PurchaseReturnOrdersService;
import com.ruoyi.purchase.vo.PurchaseReturnDetailsVo;
import com.ruoyi.purchase.vo.PurchaseReturnOrderProductsDetailVo;
import com.ruoyi.purchase.vo.PurchaseStockInProductVo;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.purchase.dto.PurchaseReturnOrderHasAllInfoDto;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.service.ISalesLedgerService;
import com.ruoyi.stock.mapper.StockOutRecordMapper;
@@ -36,8 +35,6 @@
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
@@ -76,13 +73,13 @@
                purchaseReturnOrderProductsDto.setSalesLedgerProductId(purchaseReturnOrderProductsDto.getSalesLedgerProductId());
                purchaseReturnOrderProductsDto.setPurchaseReturnOrderId(purchaseReturnOrderDto.getId());
                purchaseReturnOrderProductsDto.setReturnQuantity(purchaseReturnOrderProductsDto.getReturnQuantity());
                purchaseReturnOrderProductsDto.setStockInRecordId(purchaseReturnOrderProductsDto.getStockInRecordId());
                // è¿™é‡Œä¸ºæ–°å¢žå› æ­¤id为null
                purchaseReturnOrderProductsDto.setId(null);
                purchaseReturnOrderProductsMapper.insert(purchaseReturnOrderProductsDto);
                //库存需要出库(采购退货)
                PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(purchaseReturnOrderDto.getPurchaseLedgerId());
                //库存需要出库(采购退货,带批次号)
                SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(purchaseReturnOrderProductsDto.getSalesLedgerProductId());
                stockUtils.substractStock(salesLedgerProduct.getProductModelId(), purchaseReturnOrderProductsDto.getReturnQuantity(), StockOutQualifiedRecordTypeEnum.PURCHASE_RETURN_STOCK_OUT.getCode(), purchaseReturnOrderDto.getId(), purchaseLedger.getPurchaseContractNumber()+"-"+salesLedgerProduct.getId());
                stockUtils.substractStock(salesLedgerProduct.getProductModelId(), purchaseReturnOrderProductsDto.getReturnQuantity(), StockOutQualifiedRecordTypeEnum.PURCHASE_RETURN_STOCK_OUT.getCode(), purchaseReturnOrderProductsDto.getId(), purchaseReturnOrderProductsDto.getBatchNo());
            }
        }else {
            throw new RuntimeException("请选择退货商品");
@@ -107,25 +104,12 @@
    @Override
    public PurchaseReturnDetailsVo getPurchaseReturnOrderDtoById(Long id) {
        //查主体
        PurchaseReturnOrderHasAllInfoDto purchaseReturnOrders = purchaseReturnOrdersMapper.getPurchaseReturnOrderHasAllInfoById(id);
        PurchaseReturnDetailsVo purchaseReturnOrderDto = BeanUtil.copyProperties(purchaseReturnOrders, PurchaseReturnDetailsVo.class);
        // æŸ¥è¯¢å‡ºä»–具体对应的退货
        LambdaQueryWrapper<PurchaseReturnOrderProducts> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PurchaseReturnOrderProducts::getPurchaseReturnOrderId, purchaseReturnOrders.getId());
        List<PurchaseReturnOrderProducts> purchaseReturnOrderProducts = purchaseReturnOrderProductsMapper.selectList(queryWrapper);
        List<PurchaseReturnDetailsVo.PurchaseReturnOrderProductsDetailVo> purchaseReturnOrderProductsDetailVos = BeanUtil.copyToList(purchaseReturnOrderProducts, PurchaseReturnDetailsVo.PurchaseReturnOrderProductsDetailVo.class);
        // æŸ¥è¯¢å‡ºå¯¹åº”的商品信息
        List<Long> productIds = purchaseReturnOrderProductsDetailVos.stream().map(PurchaseReturnDetailsVo.PurchaseReturnOrderProductsDetailVo::getSalesLedgerProductId).distinct().filter(Objects::nonNull).collect(Collectors.toList());
        List<SalesLedgerProduct> salesLedgerProducts = salesLedgerService.getSalesLedgerProductListByIds(productIds, SaleEnum.PURCHASE);
        Map<Long, SalesLedgerProduct> productmap = salesLedgerProducts.stream().collect(Collectors.toMap(SalesLedgerProduct::getId, product -> product));
        purchaseReturnOrderProductsDetailVos.forEach(purchaseReturnOrderProductsDetailVo -> {
            purchaseReturnOrderProductsDetailVo.setSalesLedgerProduct(productmap.get(purchaseReturnOrderProductsDetailVo.getSalesLedgerProductId()));
        });
        //查明细
        List<PurchaseReturnOrderProductsDetailVo> purchaseReturnOrderProductsDetailVos = purchaseReturnOrdersMapper.getPurchaseReturnOrderProductsDetailById(id);
        purchaseReturnOrderDto.setPurchaseReturnOrderProductsDetailVoList(purchaseReturnOrderProductsDetailVos);
        return purchaseReturnOrderDto;
    }
@@ -133,13 +117,14 @@
    @Transactional
    public void deleteById(Long id) {
        purchaseReturnOrdersMapper.deleteById(id);
        List<PurchaseReturnOrderProducts> purchaseReturnOrderProducts = purchaseReturnOrderProductsMapper.selectList(Wrappers.<PurchaseReturnOrderProducts>lambdaQuery().eq(PurchaseReturnOrderProducts::getPurchaseReturnOrderId, id));
        LambdaUpdateWrapper<PurchaseReturnOrderProducts> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(PurchaseReturnOrderProducts::getPurchaseReturnOrderId, id);
        purchaseReturnOrderProductsMapper.delete(updateWrapper);
        //(采购退货的数据需要删掉)
        stockOutRecordMapper.delete(Wrappers.<StockOutRecord>lambdaQuery()
                .eq(StockOutRecord::getRecordType,StockOutQualifiedRecordTypeEnum.PURCHASE_RETURN_STOCK_OUT.getCode())
                .eq(StockOutRecord::getRecordId, id));
                .in(StockOutRecord::getRecordId, purchaseReturnOrderProducts.stream().map(PurchaseReturnOrderProducts::getId).collect(Collectors.toList())));
        // è´¢åŠ¡
        LambdaUpdateWrapper<AccountIncome> updateWrapperAccountIncome = new LambdaUpdateWrapper<>();
        updateWrapperAccountIncome.eq(AccountIncome::getBusinessId, id);
@@ -147,4 +132,9 @@
        updateWrapperAccountIncome.eq(AccountIncome::getIncomeType, 4);
        accountIncomeService.remove(updateWrapperAccountIncome);
    }
    @Override
    public List<PurchaseStockInProductVo> getByPurchaseLedgerId(Long purchaseLedgerId) {
        return purchaseReturnOrdersMapper.getByPurchaseLedgerId(purchaseLedgerId);
    }
}
src/main/java/com/ruoyi/purchase/vo/PurchaseReturnDetailsVo.java
@@ -1,14 +1,12 @@
package com.ruoyi.purchase.vo;
import com.ruoyi.purchase.dto.PurchaseReturnOrderHasAllInfoDto;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
/**
@@ -24,15 +22,4 @@
    private List<PurchaseReturnOrderProductsDetailVo> purchaseReturnOrderProductsDetailVoList;
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class PurchaseReturnOrderProductsDetailVo implements Serializable {
        private Long id;
        private BigDecimal returnQuantity;
        private Long salesLedgerProductId;
        private Long purchaseReturnOrderId;
        private SalesLedgerProduct salesLedgerProduct;
    }
}
src/main/java/com/ruoyi/purchase/vo/PurchaseReturnOrderProductsDetailVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,62 @@
package com.ruoyi.purchase.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PurchaseReturnOrderProductsDetailVo  {
    @Schema(description = "退货明细id")
    private Long id;
    @Schema(description = "销售台账的产品id")
    private Long salesLedgerProductId;
    @Schema(description = "产品规格id")
    private Long productModelId;
    @Schema(description = "产品大类")
    private String productCategory;
    @Schema(description = "规格型号")
    private String specificationModel;
    @Schema(description = "单位")
    private String unit;
    @Schema(description = "入库单号")
    private String inboundBatches;
    @Schema(description = "入库数量")
    private BigDecimal stockInNum;
    @Schema(description = "批次号")
    private String batchNo;
    @Schema(description = "未退货数")
    private BigDecimal unQuantity;
    @Schema(description = "已退货数量")
    private BigDecimal totalReturnNum;
    @Schema(description = "含税单价")
    private BigDecimal taxInclusiveUnitPrice;
    @Schema(description = "退货数量")
    private BigDecimal returnQuantity;
    @Schema(description = "退货单id")
    private Long purchaseReturnOrderId;
    @Schema(description = "是否质检")
    private Boolean isChecked;
}
src/main/java/com/ruoyi/purchase/vo/PurchaseStockInProductVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,50 @@
package com.ruoyi.purchase.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Data
@Schema(name = "PurchaseStockInProductVo", description = "采购管理--采购订单下入库产品列表")
public class PurchaseStockInProductVo {
    @Schema(description = "入库单id")
    private Long id;
    @Schema(description = "销售台账的产品id")
    private Long salesLedgerProductId;
    @Schema(description = "产品规格id")
    private Long productModelId;
    @Schema(description = "产品大类")
    private String productCategory;
    @Schema(description = "规格型号")
    private String specificationModel;
    @Schema(description = "单位")
    private String unit;
    @Schema(description = "入库单号")
    private String inboundBatches;
    @Schema(description = "入库数量")
    private BigDecimal stockInNum;
    @Schema(description = "批次号")
    private String batchNo;
    @Schema(description = "可退货数")
    private BigDecimal unQuantity;
    @Schema(description = "退货总数")
    private BigDecimal totalReturnNum;
    @Schema(description = "含税单价")
    private BigDecimal taxInclusiveUnitPrice;
    @Schema(description = "是否质检")
    private Boolean isChecked;
}
src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
@@ -20,6 +20,8 @@
import com.ruoyi.quality.pojo.QualityInspect;
import com.ruoyi.quality.pojo.QualityInspectParam;
import com.ruoyi.quality.pojo.QualityUnqualified;
import com.ruoyi.stock.pojo.StockInRecord;
import com.ruoyi.stock.service.StockInRecordService;
import com.ruoyi.quality.service.IQualityInspectParamService;
import com.ruoyi.quality.service.IQualityInspectService;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
@@ -34,6 +36,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
@@ -45,6 +48,7 @@
    private final StockUtils stockUtils;
    private final StockInventoryService stockInventoryService;
    private final StockInRecordService stockInRecordService;
    private QualityInspectMapper qualityInspectMapper;
    private IQualityInspectParamService qualityInspectParamService;
@@ -103,16 +107,61 @@
            // stockUtils.addStock(qualityInspect.getProductModelId(), qualityInspect.getQuantity(), StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode(), qualityInspect.getId());
            //仅添加入库记录
            StockInventoryDto stockInventoryDto = new StockInventoryDto();
            //如果是采购质检合格入库选用CUSTOMIZATION_UNSTOCK_OUT,其余合格入库选用QUALITYINSPECT_STOCK_IN
            stockInventoryDto.setRecordType(String.valueOf(StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode()));
            if (ObjectUtils.isNotEmpty(qualityInspect.getPurchaseLedgerId())){
                stockInventoryDto.setRecordType(String.valueOf(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode()));
            }
            stockInventoryDto.setRecordId(qualityInspect.getId());
            stockInventoryDto.setProductModelId(qualityInspect.getProductModelId());
            stockInventoryDto.setQualitity(qualityInspect.getQuantity());
            stockInventoryDto.setBatchNo(resolveProductionBatchNo(
                    qualityInspect.getProductMainId(),
                    qualityInspect.getId(),
                    qualityInspect.getProductModelId()));
            stockInventoryService.addStockInRecordOnly(stockInventoryDto);
        }
        qualityInspect.setInspectState(1);//已提交
        return qualityInspectMapper.updateById(qualityInspect);
    }
    private String resolveProductionBatchNo(Long productionProductMainId,
                                            Long qualityInspectId,
                                            Long productModelId) {
        if (productModelId == null) {
            return null;
        }
        if (productionProductMainId != null) {
            StockInRecord productionRecord = stockInRecordService.getOne(
                    Wrappers.<StockInRecord>lambdaQuery()
                            .eq(StockInRecord::getRecordId, productionProductMainId)
                            .eq(StockInRecord::getProductModelId, productModelId)
                            .in(StockInRecord::getRecordType, Arrays.asList(
                                    StockInQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_IN.getCode(),
                                    StockInQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode()))
                            .isNotNull(StockInRecord::getBatchNo)
                            .orderByDesc(StockInRecord::getId)
                            .last("limit 1"),
                    false);
            if (productionRecord != null) {
                return productionRecord.getBatchNo();
            }
        }
        if (qualityInspectId == null) {
            return null;
        }
        StockInRecord inspectRecord = stockInRecordService.getOne(
                Wrappers.<StockInRecord>lambdaQuery()
                        .eq(StockInRecord::getRecordId, qualityInspectId)
                        .eq(StockInRecord::getProductModelId, productModelId)
                        .eq(StockInRecord::getRecordType, StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode())
                        .isNotNull(StockInRecord::getBatchNo)
                        .orderByDesc(StockInRecord::getId)
                        .last("limit 1"),
                false);
        return inspectRecord == null ? null : inspectRecord.getBatchNo();
    }
    /*生成检验报告*/
    @Override
    public void down(HttpServletResponse response, QualityInspect qualityInspect) {
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java
@@ -6,7 +6,6 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
@@ -24,6 +23,8 @@
import com.ruoyi.quality.mapper.QualityUnqualifiedMapper;
import com.ruoyi.quality.pojo.QualityInspect;
import com.ruoyi.quality.pojo.QualityUnqualified;
import com.ruoyi.stock.pojo.StockInRecord;
import com.ruoyi.stock.service.StockInRecordService;
import com.ruoyi.quality.service.IQualityInspectService;
import com.ruoyi.quality.service.IQualityUnqualifiedService;
import com.ruoyi.stock.service.StockUninventoryService;
@@ -34,6 +35,7 @@
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -51,6 +53,7 @@
    private final ProductionOrderRoutingOperationMapper productionOrderRoutingOperationMapper;
    private final ProductionOperationTaskMapper productionOperationTaskMapper;
    private final StockUninventoryService stockUninventoryService;
    private final StockInRecordService stockInRecordService;
    @Override
    public IPage<QualityUnqualified> qualityUnqualifiedListPage(Page page, QualityUnqualified qualityUnqualified) {
@@ -68,6 +71,8 @@
    public int deal(QualityUnqualified qualityUnqualified) {
        QualityUnqualified unqualified = qualityUnqualifiedMapper.selectById(qualityUnqualified.getId());
        QualityInspect qualityInspect = qualityInspectService.getById(unqualified.getInspectId());
        String batchNo = qualityInspect == null ? null
                : resolveProductionBatchNo(qualityInspect.getProductMainId(), qualityInspect.getId(), qualityInspect.getProductModelId());
        if (ObjectUtils.isNotNull(qualityInspect) && qualityInspect.getInspectType() != 0) {
            switch (qualityUnqualified.getDealResult()) {
                case "返修":
@@ -81,12 +86,12 @@
                    }
                    break;
                case "报废":
                    stockUtils.addUnStock(qualityInspect.getProductModelId(), unqualified.getQuantity(),
                            StockInUnQualifiedRecordTypeEnum.DEFECTIVE_SCRAP.getCode(), unqualified.getId());
                    stockUtils.addUnStockWithBatchNo(qualityInspect.getProductModelId(), unqualified.getQuantity(),
                            StockInQualifiedRecordTypeEnum.DEFECTIVE_SCRAP.getCode(), unqualified.getId(), batchNo);
                    break;
                case "让步放行":
                    stockUtils.addStock(qualityInspect.getProductModelId(), unqualified.getQuantity(),
                            StockInQualifiedRecordTypeEnum.DEFECTIVE_PASS.getCode(), unqualified.getId());
                    stockUtils.addStockWithBatchNo(qualityInspect.getProductModelId(), unqualified.getQuantity(),
                            StockInQualifiedRecordTypeEnum.DEFECTIVE_PASS.getCode(), unqualified.getId(), batchNo);
                    break;
                default:
                    break;
@@ -96,7 +101,7 @@
            switch (qualityUnqualified.getDealResult()) {
                case "报废":
                    stockUtils.addUnStock(modelId, unqualified.getQuantity(),
                            StockInUnQualifiedRecordTypeEnum.DEFECTIVE_SCRAP.getCode(), unqualified.getId());
                            StockInQualifiedRecordTypeEnum.DEFECTIVE_SCRAP.getCode(), unqualified.getId());
                    break;
                case "让步放行":
                    stockUtils.addStock(modelId, unqualified.getQuantity(),
@@ -220,4 +225,41 @@
    private BigDecimal defaultDecimal(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }
    private String resolveProductionBatchNo(Long productionProductMainId,
                                            Long qualityInspectId,
                                            Long productModelId) {
        if (productModelId == null) {
            return null;
        }
        if (productionProductMainId != null) {
            StockInRecord productionRecord = stockInRecordService.getOne(
                    Wrappers.<StockInRecord>lambdaQuery()
                            .eq(StockInRecord::getRecordId, productionProductMainId)
                            .eq(StockInRecord::getProductModelId, productModelId)
                            .in(StockInRecord::getRecordType, Arrays.asList(
                                    StockInQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_IN.getCode(),
                                    StockInQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode()))
                            .isNotNull(StockInRecord::getBatchNo)
                            .orderByDesc(StockInRecord::getId)
                            .last("limit 1"),
                    false);
            if (productionRecord != null) {
                return productionRecord.getBatchNo();
            }
        }
        if (qualityInspectId == null) {
            return null;
        }
        StockInRecord inspectRecord = stockInRecordService.getOne(
                Wrappers.<StockInRecord>lambdaQuery()
                        .eq(StockInRecord::getRecordId, qualityInspectId)
                        .eq(StockInRecord::getProductModelId, productModelId)
                        .eq(StockInRecord::getRecordType, StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode())
                        .isNotNull(StockInRecord::getBatchNo)
                        .orderByDesc(StockInRecord::getId)
                        .last("limit 1"),
                false);
        return inspectRecord == null ? null : inspectRecord.getBatchNo();
    }
}
src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java
@@ -71,9 +71,9 @@
        if (CollUtil.isEmpty(list)) {
            return AjaxResult.success(list);
        }
        List<Long> productIds = list.stream().map(SalesLedgerProduct::getId).collect(Collectors.toList());
        List<Long> productIds = list.stream().map(SalesLedgerProduct::getProductModelId).collect(Collectors.toList());
        List<SimpleReturnOrderGroupDto> groupListByProductIds = purchaseReturnOrderProductsMapper.getReturnOrderGroupListByProductIds(productIds);
        Map<Long, BigDecimal> returnOrderGroupDtoMap = groupListByProductIds.stream().collect(Collectors.toMap(SimpleReturnOrderGroupDto::getSalesLedgerProductId, item -> item.getSumReturnQuantity()));
        Map<Long, BigDecimal> returnOrderGroupDtoMap = groupListByProductIds.stream().collect(Collectors.toMap(SimpleReturnOrderGroupDto::getProductModelId, item -> item.getSumReturnQuantity()));
        list.forEach(item -> {
            if (item.getFutureTickets().compareTo(BigDecimal.ZERO) == 0) {
@@ -90,7 +90,7 @@
                }
            }
            // ç»Ÿè®¡é€€è´§æ•°é‡
            BigDecimal returnQuality = returnOrderGroupDtoMap.getOrDefault(item.getId(), BigDecimal.ZERO);
            BigDecimal returnQuality = returnOrderGroupDtoMap.getOrDefault(item.getProductModelId(), BigDecimal.ZERO);
            item.setReturnQuality(returnQuality);
            item.setAvailableQuality(item.getQuantity().subtract(returnQuality));
        });
src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java
@@ -116,7 +116,7 @@
    @GetMapping("/getByCustomerName")
    @Operation(summary = "通过客户名称查询")
    @Operation(summary = "通过客户名称查询关联的发货单号")
    public AjaxResult getByCustomerName(String customerName) {
        return AjaxResult.success(shippingInfoService.getShippingInfoByCustomerName(customerName));
    }
src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java
@@ -8,6 +8,7 @@
import com.ruoyi.sales.pojo.ShippingProductDetail;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
@@ -40,6 +41,11 @@
    private List<Long> batchNo;
    private List<ShippingProductDetail> batchNoDetailList;
    //关联的出库单号
    private String outboundBatches;
    //发货数量
    private BigDecimal totalQuantity;
}
src/main/java/com/ruoyi/sales/mapper/ShippingInfoMapper.java
@@ -3,9 +3,7 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.SalesOutboundDto;
import com.ruoyi.account.bean.vo.SalesOutboundVo;
import com.ruoyi.sales.dto.SalesLedgerProductDto;
import com.ruoyi.procurementrecord.bean.vo.ShippingProductVo;
import com.ruoyi.sales.dto.ShippingInfoDto;
import com.ruoyi.sales.pojo.ShippingInfo;
import org.apache.ibatis.annotations.Param;
@@ -21,9 +19,7 @@
    List<ShippingInfo> listAll();
    List<SalesLedgerProductDto> getReturnManagementDtoById(@Param("shippingId")Long shippingId);
    List<ShippingProductVo> getReturnManagementDtoById(@Param("shippingId")Long shippingId);
    List<ShippingInfo> getShippingInfoByCustomerName(String customerName);
    IPage<SalesOutboundVo> listPageByOutbound(Page page, @Param("req") SalesOutboundDto salesOutboundDto);
}
src/main/java/com/ruoyi/sales/service/ShippingInfoService.java
@@ -3,7 +3,7 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.sales.dto.SalesLedgerProductDto;
import com.ruoyi.procurementrecord.bean.vo.ShippingProductVo;
import com.ruoyi.sales.dto.ShippingApproveDto;
import com.ruoyi.sales.dto.ShippingInfoDto;
import com.ruoyi.sales.dto.ShippingProductDetailDto;
@@ -22,7 +22,7 @@
    boolean delete(List<Long> ids);
    List<SalesLedgerProductDto> getReturnManagementDtoById( Long shippingId);
    List<ShippingProductVo> getReturnManagementDtoById(Long shippingId);
    List<ShippingInfo> getShippingInfoByCustomerName(String customerName);
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -9,10 +9,13 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.account.service.AccountIncomeService;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.mapper.ProductMapper;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.enums.SaleEnum;
import com.ruoyi.common.exception.base.BaseException;
@@ -102,6 +105,8 @@
    private final ProductionProductInputMapper productionProductInputMapper;
    private final QualityInspectMapper qualityInspectMapper;
    private final RedisTemplate<String, String> redisTemplate;
    private final FileUtil fileUtil;
    @Autowired
    private SysDeptMapper sysDeptMapper;
    @Value("${file.upload-dir}")
@@ -142,14 +147,14 @@
        List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(productWrapper);
        if (type.equals(SaleEnum.PURCHASE)) {
            // æŸ¥è¯¢é€€è´§ä¿¡æ¯
            List<Long> productIds = salesLedgerProducts.stream().map(SalesLedgerProduct::getId).collect(Collectors.toList());
            List<Long> productIds = salesLedgerProducts.stream().map(SalesLedgerProduct::getProductModelId).collect(Collectors.toList());
            List<SimpleReturnOrderGroupDto> groupListByProductIds = new ArrayList<>();
            if(CollectionUtils.isNotEmpty(productIds)){
                groupListByProductIds = purchaseReturnOrderProductsMapper.getReturnOrderGroupListByProductIds(productIds);
            }
            Map<Long, BigDecimal> returnOrderGroupDtoMap = groupListByProductIds.stream().collect(Collectors.toMap(SimpleReturnOrderGroupDto::getSalesLedgerProductId, SimpleReturnOrderGroupDto::getSumReturnQuantity));
            Map<Long, BigDecimal> returnOrderGroupDtoMap = groupListByProductIds.stream().collect(Collectors.toMap(SimpleReturnOrderGroupDto::getProductModelId, SimpleReturnOrderGroupDto::getSumReturnQuantity));
            salesLedgerProducts.forEach(item -> {
                BigDecimal returnQuality = returnOrderGroupDtoMap.getOrDefault(item.getId(), BigDecimal.ZERO);
                BigDecimal returnQuality = returnOrderGroupDtoMap.getOrDefault(item.getProductModelId(), BigDecimal.ZERO);
                item.setReturnQuality(returnQuality);
                item.setAvailableQuality(item.getQuantity().subtract(returnQuality));
            });
@@ -582,125 +587,44 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int addOrUpdateSalesLedger(SalesLedgerDto salesLedgerDto) {
        try {
            // 1. æ ¡éªŒå®¢æˆ·ä¿¡æ¯
            Customer customer = customerMapper.selectById(salesLedgerDto.getCustomerId());
            if (customer == null) {
                throw new BaseException("客户不存在");
            }
            // 2. DTO转Entity
            SalesLedger salesLedger = convertToEntity(salesLedgerDto);
            salesLedger.setCustomerName(customer.getCustomerName());
            salesLedger.setTenantId(customer.getTenantId());
            // 3. æ–°å¢žæˆ–更新主表
            if (salesLedger.getId() == null) {
                String contractNo = generateSalesContractNo();
                salesLedger.setSalesContractNo(contractNo);
                salesLedgerMapper.insert(salesLedger);
            } else {
                salesLedgerMapper.updateById(salesLedger);
            }
            // 4. å¤„理子表数据
            List<SalesLedgerProduct> productList = salesLedgerDto.getProductData();
            if (productList != null && !productList.isEmpty()) {
                handleSalesLedgerProducts(salesLedger.getId(), productList, EnumUtil.fromCode(SaleEnum.class, salesLedgerDto.getType()));
                updateMainContractAmount(
                        salesLedger.getId(),
                        productList,
                        SalesLedgerProduct::getTaxInclusiveTotalPrice,
                        salesLedgerMapper,
                        SalesLedger.class
                );
            }
            // 5. è¿ç§»ä¸´æ—¶æ–‡ä»¶åˆ°æ­£å¼ç›®å½•
            if (salesLedgerDto.getTempFileIds() != null && !salesLedgerDto.getTempFileIds().isEmpty()) {
                migrateTempFilesToFormal(salesLedger.getId(), salesLedgerDto.getTempFileIds());
            }
            return 1;
        } catch (IOException e) {
            throw new BaseException("文件迁移失败: " + e.getMessage());
        // 1. æ ¡éªŒå®¢æˆ·ä¿¡æ¯
        Customer customer = customerMapper.selectById(salesLedgerDto.getCustomerId());
        if (customer == null) {
            throw new BaseException("客户不存在");
        }
        // 2. DTO转Entity
        SalesLedger salesLedger = convertToEntity(salesLedgerDto);
        salesLedger.setCustomerName(customer.getCustomerName());
        salesLedger.setTenantId(customer.getTenantId());
        // 3. æ–°å¢žæˆ–更新主表
        if (salesLedger.getId() == null) {
            String contractNo = generateSalesContractNo();
            salesLedger.setSalesContractNo(contractNo);
            salesLedgerMapper.insert(salesLedger);
        } else {
            salesLedgerMapper.updateById(salesLedger);
        }
        // 4. å¤„理子表数据
        List<SalesLedgerProduct> productList = salesLedgerDto.getProductData();
        if (productList != null && !productList.isEmpty()) {
            handleSalesLedgerProducts(salesLedger.getId(), productList, EnumUtil.fromCode(SaleEnum.class, salesLedgerDto.getType()));
            updateMainContractAmount(
                    salesLedger.getId(),
                    productList,
                    SalesLedgerProduct::getTaxInclusiveTotalPrice,
                    salesLedgerMapper,
                    SalesLedger.class
            );
        }
        // 5. ä¿å­˜é”€å”®å°è´¦é™„ä»¶
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.SALES_LEDGER, salesLedger.getId(), salesLedgerDto.getStorageBlobDTOs());
        return 1;
    }
    /**
     * å°†ä¸´æ—¶æ–‡ä»¶è¿ç§»åˆ°æ­£å¼ç›®å½•
     *
     * @param businessId  ä¸šåŠ¡ID(销售台账ID)
     * @param tempFileIds ä¸´æ—¶æ–‡ä»¶ID列表
     * @throws IOException æ–‡ä»¶æ“ä½œå¼‚常
     */
    private void migrateTempFilesToFormal(Long businessId, List<String> tempFileIds) throws IOException {
        if (CollectionUtils.isEmpty(tempFileIds)) {
            return;
        }
        // æž„建正式目录路径(按业务类型和日期分组)
        String formalDir = uploadDir + LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE);
        Path formalDirPath = Paths.get(formalDir);
        // ç¡®ä¿æ­£å¼ç›®å½•存在(递归创建)
        if (!Files.exists(formalDirPath)) {
            Files.createDirectories(formalDirPath);
        }
        for (String tempFileId : tempFileIds) {
            // æŸ¥è¯¢ä¸´æ—¶æ–‡ä»¶è®°å½•
            TempFile tempFile = tempFileMapper.selectById(tempFileId);
            if (tempFile == null) {
                log.warn("临时文件不存在,跳过处理: {}", tempFileId);
                continue;
            }
            // æž„建正式文件名(包含业务ID和时间戳,避免冲突)
            String originalFilename = tempFile.getOriginalName();
            String fileExtension = FilenameUtils.getExtension(originalFilename);
            String formalFilename = businessId + "_" +
                    System.currentTimeMillis() + "_" +
                    UUID.randomUUID().toString().substring(0, 8) +
                    (StringUtils.hasText(fileExtension) ? "." + fileExtension : "");
            Path formalFilePath = formalDirPath.resolve(formalFilename);
            try {
                // æ‰§è¡Œæ–‡ä»¶è¿ç§»ï¼ˆä½¿ç”¨åŽŸå­æ“ä½œç¡®ä¿å®‰å…¨æ€§ï¼‰
//                Files.move(
//                        Paths.get(tempFile.getTempPath()),
//                        formalFilePath,
//                        StandardCopyOption.REPLACE_EXISTING,
//                        StandardCopyOption.ATOMIC_MOVE
//                );
                // åŽŸå­ç§»åŠ¨å¤±è´¥ï¼Œä½¿ç”¨å¤åˆ¶+删除
                Files.copy(Paths.get(tempFile.getTempPath()), formalFilePath, StandardCopyOption.REPLACE_EXISTING);
                Files.deleteIfExists(Paths.get(tempFile.getTempPath()));
                log.info("文件迁移成功: {} -> {}", tempFile.getTempPath(), formalFilePath);
                // æ›´æ–°æ–‡ä»¶è®°å½•(关联到业务ID)
                CommonFile fileRecord = new CommonFile();
                fileRecord.setCommonId(businessId);
                fileRecord.setName(originalFilename);
                fileRecord.setUrl(formalFilePath.toString());
                fileRecord.setCreateTime(LocalDateTime.now());
                //销售
                fileRecord.setType(FileNameType.SALE.getValue());
                commonFileMapper.insert(fileRecord);
                // åˆ é™¤ä¸´æ—¶æ–‡ä»¶è®°å½•
                tempFileMapper.deleteById(tempFile);
                log.info("文件迁移成功: {} -> {}", tempFile.getTempPath(), formalFilePath);
            } catch (IOException e) {
                log.error("文件迁移失败: {}", tempFile.getTempPath(), e);
                // å¯é€‰æ‹©å›žæ»šäº‹åŠ¡æˆ–è®°å½•å¤±è´¥æ–‡ä»¶
                throw new IOException("文件迁移异常", e);
            }
        }
    }
    // æ–‡ä»¶è¿ç§»æ–¹æ³•
    @Override
    public void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products, SaleEnum type) {
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
@@ -11,6 +11,7 @@
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.procurementrecord.bean.vo.ShippingProductVo;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.sales.dto.SalesLedgerProductDto;
import com.ruoyi.sales.dto.ShippingApproveDto;
@@ -55,7 +56,7 @@
    @Override
    public IPage<ShippingInfoDto> listPage(Page page, ShippingInfo req) {
        IPage<ShippingInfoDto> listPage = shippingInfoMapper.listPage(page, req);
        listPage.getRecords().forEach(item ->{
        listPage.getRecords().forEach(item -> {
            item.setStorageBlobVOs(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.IMAGE, RecordTypeEnum.SHIPPING_INFO, item.getId()));
        });
        return listPage;
@@ -68,7 +69,7 @@
            throw new RuntimeException("发货信息不存在");
        }
        //扣减库存
        if(!"已发货".equals(byId.getStatus())){
        if (!"已发货".equals(byId.getStatus())) {
            List<ShippingProductDetail> shippingProductDetails = shippingProductDetailMapper.selectList(new LambdaQueryWrapper<ShippingProductDetail>().eq(ShippingProductDetail::getShippingInfoId, req.getId()));
            if (CollectionUtils.isEmpty(shippingProductDetails)) {
                throw new RuntimeException("发货信息不存在");
@@ -85,28 +86,28 @@
        boolean update = this.updateById(byId);
        // ä¿å­˜æ–‡ä»¶
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.IMAGE, RecordTypeEnum.SHIPPING_INFO, req.getId(), req.getStorageBlobDTOs());
        return update ;
        return update;
    }
    @Override
    public boolean delete(List<Long> ids) {
        List<ShippingInfo> shippingInfos = shippingInfoMapper.selectList(new LambdaQueryWrapper<ShippingInfo>()
                .in(ShippingInfo::getId, ids));
        if(CollectionUtils.isEmpty(shippingInfos)) return false;
        if (CollectionUtils.isEmpty(shippingInfos)) return false;
        // åˆ é™¤é™„ä»¶
        commonFileService.deleteByBusinessIds(ids, FileNameType.SHIP.getValue());
        // æ‰£å·²å‘货库存
        for (ShippingInfo shippingInfo : shippingInfos) {
            if("已发货".equals(shippingInfo.getStatus())) {
            if ("已发货".equals(shippingInfo.getStatus())) {
                stockUtils.deleteStockOutRecord(shippingInfo.getId(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode());
            }
        }
        // åˆ é™¤å‘货审批
        if(CollectionUtils.isNotEmpty(shippingInfos)){
            for (ShippingInfo shippingInfo : shippingInfos){
        if (CollectionUtils.isNotEmpty(shippingInfos)) {
            for (ShippingInfo shippingInfo : shippingInfos) {
                List<ApproveProcess> one = approveProcessService.list(new LambdaQueryWrapper<ApproveProcess>()
                        .like(ApproveProcess::getApproveReason, shippingInfo.getShippingNo()));
                if(one != null){
                if (one != null) {
                    List<Long> list = one.stream().map(ApproveProcess::getId).toList();
                    approveProcessService.delByIds(list);
                }
@@ -119,8 +120,8 @@
    }
    @Override
    public List<SalesLedgerProductDto> getReturnManagementDtoById(Long shippingId) {
        return shippingInfoMapper.getReturnManagementDtoById(shippingId );
    public List<ShippingProductVo> getReturnManagementDtoById(Long shippingId) {
        return shippingInfoMapper.getReturnManagementDtoById(shippingId);
    }
@@ -131,9 +132,14 @@
    @Override
    public boolean add(ShippingInfoDto req) {
        this.save( req);
        this.save(req);
        req.getBatchNoDetailList().forEach(item -> item.setShippingInfoId(req.getId()));
        shippingProductDetailMapper.insert(req.getBatchNoDetailList());
        for (ShippingProductDetail shippingProductDetail : req.getBatchNoDetailList()) {
            stockUtils.substractStock(shippingProductDetail.getProductModelId(), shippingProductDetail.getQuantity(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), req.getId(), shippingProductDetail.getBatchNo());
        }
        // ä¿å­˜æ–‡ä»¶
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.IMAGE, RecordTypeEnum.SHIPPING_INFO, req.getId(), req.getStorageBlobDTOs());
        return true;
    }
@@ -147,7 +153,7 @@
        ShippingApproveDto shippingApproveDto = new ShippingApproveDto();
        ShippingInfo shippingInfo = new ShippingInfo();
        shippingInfo.setShippingNo(shippingNo);
        shippingApproveDto.setShippingInfo(shippingInfoMapper.listPage(new Page(1, -1),shippingInfo).getRecords().get(0));
        shippingApproveDto.setShippingInfo(shippingInfoMapper.listPage(new Page(1, -1), shippingInfo).getRecords().get(0));
        List<ShippingProductDetailDto> dateilByShippingNo = shippingProductDetailMapper.getDateilByShippingNo(shippingNo);
        shippingApproveDto.setShippingProductDetailDtoList(dateilByShippingNo);
        return shippingApproveDto;
src/main/java/com/ruoyi/stock/controller/StockUninventoryController.java
@@ -2,8 +2,8 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockOutUnQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.stock.dto.StockInventoryDto;
import com.ruoyi.stock.dto.StockUninventoryDto;
@@ -37,7 +37,7 @@
    @PostMapping("/addstockUninventory")
    @Operation(summary = "新增库存")
    public R addstockUninventory(@RequestBody StockUninventoryDto stockUninventoryDto) {
        stockUninventoryDto.setRecordType(String.valueOf(StockInUnQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_IN.getCode()));
        stockUninventoryDto.setRecordType(String.valueOf(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_IN.getCode()));
        stockUninventoryDto.setRecordId(0L);
        return R.ok(stockUninventoryService.addStockUninventory(stockUninventoryDto));
    }
@@ -46,7 +46,7 @@
    @PostMapping("/subtractstockUninventory")
    @Operation(summary = "扣减库存")
    public R subtractstockUninventory(@RequestBody StockUninventoryDto stockUninventoryDto) {
        stockUninventoryDto.setRecordType(String.valueOf(StockOutUnQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode()));
        stockUninventoryDto.setRecordType(String.valueOf(StockOutQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode()));
        stockUninventoryDto.setRecordId(0L);
        return R.ok(stockUninventoryService.subtractStockUninventory(stockUninventoryDto));
    }
@@ -54,7 +54,7 @@
    @PostMapping("/addStockInRecordOnly")
    @Operation(summary = "新增入库记录(仅创建记录,不调整库存)")
    public R addStockInRecordOnly(@RequestBody StockUninventoryDto stockUninventoryDto) {
        stockUninventoryDto.setRecordType(String.valueOf(StockInUnQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_IN.getCode()));
        stockUninventoryDto.setRecordType(String.valueOf(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_IN.getCode()));
        stockUninventoryDto.setRecordId(0L);
        return R.ok(stockUninventoryService.addStockInRecordOnly(stockUninventoryDto));
    }
@@ -62,7 +62,7 @@
    @PostMapping("/addStockOutRecordOnly")
    @Operation(summary = "新增出库记录(仅创建记录,不调整库存)")
    public R addStockOutRecordOnly(@RequestBody StockUninventoryDto stockUninventoryDto) {
        stockUninventoryDto.setRecordType(String.valueOf(StockOutUnQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode()));
        stockUninventoryDto.setRecordType(String.valueOf(StockOutQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode()));
        stockUninventoryDto.setRecordId(0L);
        return R.ok(stockUninventoryService.addStockOutRecordOnly(stockUninventoryDto));
    }
src/main/java/com/ruoyi/stock/mapper/StockInRecordMapper.java
@@ -3,6 +3,8 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.PurchaseInboundDto;
import com.ruoyi.account.bean.vo.PurchaseInboundVo;
import com.ruoyi.stock.dto.StockInRecordDto;
import com.ruoyi.stock.execl.StockInRecordExportData;
import com.ruoyi.stock.pojo.StockInRecord;
@@ -14,4 +16,6 @@
    IPage<StockInRecordDto> listPage(Page page, @Param("params") StockInRecordDto stockInRecordDto);
    List<StockInRecordExportData> listStockInRecordExportData(@Param("params") StockInRecordDto stockInRecordDto);
    IPage<PurchaseInboundVo> listPageAccountPurchase(Page page, @Param("req") PurchaseInboundDto purchaseInboundDto);
}
src/main/java/com/ruoyi/stock/mapper/StockOutRecordMapper.java
@@ -3,6 +3,8 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.SalesOutboundDto;
import com.ruoyi.account.bean.vo.SalesOutboundVo;
import com.ruoyi.stock.dto.StockOutRecordDto;
import com.ruoyi.stock.execl.StockOutRecordExportData;
import com.ruoyi.stock.pojo.StockOutRecord;
@@ -25,4 +27,5 @@
    List<StockOutRecordExportData> listStockOutRecordExportData(@Param("params") StockOutRecordDto stockOutRecordDto);
    IPage<SalesOutboundVo> listPageAccountSales(Page page, @Param("req") SalesOutboundDto salesOutboundDto);
}
src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java
@@ -75,7 +75,7 @@
    @Schema(description = "类型  0合格入库 1不合格入库")
    private String type;
    @Schema(description = "审批状态  0-待审批 1-通过 2-驳回", implementation = ReviewStatusEnum.class)
    @Schema(description = "审批状态  0-待审批 1-通过 2-驳回 3-销售出库待确认", implementation = ReviewStatusEnum.class)
    private Integer approvalStatus;
    @TableField(fill = FieldFill.INSERT)
src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java
@@ -5,8 +5,8 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.enums.ReviewStatusEnum;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.EnumUtil;
import com.ruoyi.common.utils.OrderUtils;
@@ -24,12 +24,12 @@
import com.ruoyi.stock.pojo.StockInventory;
import com.ruoyi.stock.pojo.StockUninventory;
import com.ruoyi.stock.service.StockInRecordService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
@Service
@@ -123,7 +123,7 @@
            if (stockInRecordExportData.getType().equals("0")) {
                stockInRecordExportData.setRecordType(EnumUtil.fromCode(StockOutQualifiedRecordTypeEnum.class, Integer.parseInt(stockInRecordExportData.getRecordType())).getValue());
            }else {
                stockInRecordExportData.setRecordType(EnumUtil.fromCode(StockInUnQualifiedRecordTypeEnum.class, Integer.parseInt(stockInRecordExportData.getRecordType())).getValue());
                stockInRecordExportData.setRecordType(EnumUtil.fromCode(StockInQualifiedRecordTypeEnum.class, Integer.parseInt(stockInRecordExportData.getRecordType())).getValue());
            }
        }
        ExcelUtil<StockInRecordExportData> util = new ExcelUtil<>(StockInRecordExportData.class);
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
@@ -10,7 +10,6 @@
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
@@ -345,7 +344,7 @@
                    if (dto.getUnQualifiedQuantity() != null && dto.getUnQualifiedQuantity().compareTo(BigDecimal.ZERO) > 0) {
                        StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
                        stockUninventoryDto.setRecordId(0L);
                        stockUninventoryDto.setRecordType(StockInUnQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_IN.getCode());
                        stockUninventoryDto.setRecordType(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_IN.getCode());
                        stockUninventoryDto.setQualitity(dto.getUnQualifiedQuantity());
                        stockUninventoryDto.setRemark(dto.getRemark());
src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java
@@ -5,8 +5,8 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.enums.ReviewStatusEnum;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.EnumUtil;
import com.ruoyi.common.utils.OrderUtils;
@@ -20,19 +20,16 @@
import com.ruoyi.stock.mapper.StockInventoryMapper;
import com.ruoyi.stock.mapper.StockOutRecordMapper;
import com.ruoyi.stock.mapper.StockUninventoryMapper;
import com.ruoyi.stock.pojo.StockInRecord;
import com.ruoyi.stock.pojo.StockInventory;
import com.ruoyi.stock.pojo.StockOutRecord;
import com.ruoyi.stock.pojo.StockUninventory;
import com.ruoyi.stock.service.StockOutRecordService;
import lombok.AllArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
@@ -59,8 +56,9 @@
    public int add(StockOutRecordDto stockOutRecordDto) {
        String no = OrderUtils.countTodayByCreateTime(stockOutRecordMapper, "CK","outbound_batches");
        stockOutRecordDto.setOutboundBatches(no);
        StockInRecord stockInRecord = new StockInRecord();
        BeanUtils.copyProperties(stockOutRecordDto, stockInRecord);
        if (StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode().equals(stockOutRecordDto.getRecordType())){
            stockOutRecordDto.setApprovalStatus(3);
        }
        return stockOutRecordMapper.insert(stockOutRecordDto);
    }
@@ -129,7 +127,7 @@
            if (stockInRecordExportData.getType().equals("0")) {
                stockInRecordExportData.setRecordType(EnumUtil.fromCode(StockOutQualifiedRecordTypeEnum.class, Integer.parseInt(stockInRecordExportData.getRecordType())).getValue());
            }else {
                stockInRecordExportData.setRecordType(EnumUtil.fromCode(StockInUnQualifiedRecordTypeEnum.class, Integer.parseInt(stockInRecordExportData.getRecordType())).getValue());
                stockInRecordExportData.setRecordType(EnumUtil.fromCode(StockInQualifiedRecordTypeEnum.class, Integer.parseInt(stockInRecordExportData.getRecordType())).getValue());
            }
        }
        ExcelUtil<StockOutRecordExportData> util = new ExcelUtil<>(StockOutRecordExportData.class);
src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java
@@ -4,8 +4,12 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
@@ -14,21 +18,22 @@
import com.ruoyi.stock.dto.StockOutRecordDto;
import com.ruoyi.stock.dto.StockUninventoryDto;
import com.ruoyi.stock.execl.StockUnInventoryExportData;
import com.ruoyi.stock.mapper.StockInventoryMapper;
import com.ruoyi.stock.mapper.StockUninventoryMapper;
import com.ruoyi.stock.pojo.StockInRecord;
import com.ruoyi.stock.pojo.StockInventory;
import com.ruoyi.stock.pojo.StockUninventory;
import com.ruoyi.stock.service.StockInRecordService;
import com.ruoyi.stock.service.StockOutRecordService;
import com.ruoyi.stock.service.StockUninventoryService;
import lombok.AllArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
@@ -47,6 +52,8 @@
    private final StockUninventoryMapper stockUninventoryMapper;
    private final StockOutRecordService stockOutRecordService;
    private final StockInRecordService stockInRecordService;
    private final ProductModelMapper productModelMapper;
    private final StockInventoryMapper stockInventoryMapper;
    @Override
    public IPage<StockUninventoryDto> pageStockUninventory(Page page, StockUninventoryDto stockUninventoryDto) {
@@ -120,7 +127,11 @@
        stockInRecordDto.setRecordId(stockUninventoryDto.getRecordId());
        stockInRecordDto.setRecordType(stockUninventoryDto.getRecordType());
        stockInRecordDto.setStockInNum(stockUninventoryDto.getQualitity());
        stockInRecordDto.setBatchNo(stockUninventoryDto.getBatchNo());
        String batchNo = StringUtils.trim(stockUninventoryDto.getBatchNo());
        if (StringUtils.isEmpty(batchNo)) {
            batchNo = generateAutoBatchNo(stockUninventoryDto.getProductModelId());
        }
        stockInRecordDto.setBatchNo(batchNo);
        stockInRecordDto.setProductModelId(stockUninventoryDto.getProductModelId());
        stockInRecordDto.setType("1");
        stockInRecordDto.setRemark(stockUninventoryDto.getRemark());
@@ -200,4 +211,80 @@
        stockUninventory.setLockedQuantity(stockUninventory.getLockedQuantity().subtract(stockInventoryDto.getLockedQuantity()));
        return this.updateById(stockUninventory);
    }
    //规则生成:20260424-产品编号-001
    private String generateAutoBatchNo(Long productModelId) {
        if (productModelId == null) {
            throw new ServiceException("产品规格ID不能为空");
        }
        ProductModel productModel = productModelMapper.selectById(productModelId);
        if (productModel == null) {
            throw new ServiceException("产品规格不存在,ID=" + productModelId);
        }
        String productCode = StringUtils.trim(productModel.getProductCode());
        if (StringUtils.isEmpty(productCode)) {
            throw new ServiceException("产品规格未维护产品编码,ID=" + productModelId);
        }
        String dateText = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
        String prefix = dateText + "-" + productCode + "-";
        int maxSequence = resolveMaxSequence(prefix);
        int sequence = maxSequence + 1;
        while (sequence < 1_000_000) {
            String batchNo = prefix + String.format("%03d", sequence);
            if (!isBatchNoExists(batchNo)) {
                return batchNo;
            }
            sequence++;
        }
        throw new ServiceException("批号序号超出范围,请检查批号数据");
    }
    private int resolveMaxSequence(String prefix) {
        int maxSequence = 0;
        List<StockInventory> stockInventoryList = stockInventoryMapper.selectList(
                Wrappers.<StockInventory>lambdaQuery()
                        .select(StockInventory::getBatchNo)
                        .likeRight(StockInventory::getBatchNo, prefix));
        for (StockInventory stockInventory : stockInventoryList) {
            maxSequence = Math.max(maxSequence, parseSequence(stockInventory.getBatchNo(), prefix));
        }
        List<StockInRecord> stockInRecordList = stockInRecordService.list(
                Wrappers.<StockInRecord>lambdaQuery()
                        .select(StockInRecord::getBatchNo)
                        .likeRight(StockInRecord::getBatchNo, prefix));
        for (StockInRecord stockInRecord : stockInRecordList) {
            maxSequence = Math.max(maxSequence, parseSequence(stockInRecord.getBatchNo(), prefix));
        }
        return maxSequence;
    }
    private int parseSequence(String batchNo, String prefix) {
        if (StringUtils.isEmpty(batchNo) || StringUtils.isEmpty(prefix) || !batchNo.startsWith(prefix)) {
            return 0;
        }
        String sequenceText = batchNo.substring(prefix.length());
        if (StringUtils.isEmpty(sequenceText) || !sequenceText.matches("\\d+")) {
            return 0;
        }
        try {
            return Integer.parseInt(sequenceText);
        } catch (NumberFormatException ignored) {
            return 0;
        }
    }
    private boolean isBatchNoExists(String batchNo) {
        if (StringUtils.isEmpty(batchNo)) {
            return false;
        }
        Long inventoryCount = stockInventoryMapper.selectCount(
                Wrappers.<StockInventory>lambdaQuery().eq(StockInventory::getBatchNo, batchNo));
        if (inventoryCount != null && inventoryCount > 0) {
            return true;
        }
        return stockInRecordService.count(
                Wrappers.<StockInRecord>lambdaQuery().eq(StockInRecord::getBatchNo, batchNo)) > 0;
    }
}
src/main/java/com/ruoyi/technology/bean/vo/TechnologyBomStructureVo.java
@@ -23,5 +23,8 @@
    @Schema(description = "规格型号")
    private String model;
    @Schema(description = "产品编码")
    private String productCode;
    private List<TechnologyBomStructureVo> children;
}
src/main/java/com/ruoyi/technology/controller/TechnologyBomController.java
@@ -76,7 +76,7 @@
    @PreAuthorize("@ss.hasPermi('product:bom:export')")
    @Operation(summary = "导出BOM文件")
    @Log(title = "导出BOM文件", businessType = BusinessType.EXPORT)
    public void exportBom(HttpServletResponse response, @RequestParam Integer bomId) {
    public void exportBom(HttpServletResponse response, @RequestParam Long bomId) {
        technologyBomService.exportBom(response, bomId);
    }
src/main/java/com/ruoyi/technology/service/TechnologyBomService.java
@@ -26,7 +26,7 @@
    R uploadBom(MultipartFile file);
    void exportBom(HttpServletResponse response, Integer bomId);
    void exportBom(HttpServletResponse response, Long bomId);
    R copy(TechnologyBom technologyBom);
}
src/main/java/com/ruoyi/technology/service/impl/TechnologyBomServiceImpl.java
@@ -15,8 +15,7 @@
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.production.bean.dto.BomImportDto;
import com.ruoyi.production.bean.dto.ProductStructureDto;
import com.ruoyi.technology.bean.dto.BomImportDto;
import com.ruoyi.technology.bean.dto.TechnologyBomDto;
import com.ruoyi.technology.bean.dto.TechnologyBomStructureDto;
import com.ruoyi.technology.bean.vo.TechnologyBomStructureVo;
@@ -263,70 +262,72 @@
    @Override
    public void exportBom(HttpServletResponse response, Integer bomId) {
    public void exportBom(HttpServletResponse response, Long bomId) {
        if (bomId == null) {
            throw new ServiceException("BOM ID不能为空");
        }
        List<TechnologyBomStructureVo> treeData = technologyBomStructureService.listByBomId(bomId);
        if (treeData == null || treeData.isEmpty()) {
            return;
        }
//        List<ProductStructureDto> treeData = productStructureService.listBybomId(bomId);
//        if (treeData == null || treeData.isEmpty()) {
//            return;
//        }
//
//        //  å°†æ ‘形结构扁平化 ä½¿ç”¨ BFS算法 å¯¼å‡º,按层级顺序
//        List<BomImportDto> exportList = new ArrayList<>();
//
//        // Map<ID, Node> idMap ç”¨äºŽæŸ¥æ‰¾çˆ¶èŠ‚ç‚¹
//        Map<Long, ProductStructureDto> idMap = new HashMap<>();
//        populateMap(treeData, idMap);
//
//        //  treeData çš„第一个是根节点
//        for (ProductStructureDto root : treeData) {
//            //  æ·»åŠ æ ¹èŠ‚ç‚¹
//            BomImportDto rootRow = new BomImportDto();
//            rootRow.setParentName(root.getProductName());
//            rootRow.setParentSpec(root.getModel());
//            rootRow.setUnitQty(root.getUnitQuantity());
//            rootRow.setRemark("");
//            exportList.add(rootRow);
//
//            //  BFS éåކ-队列
//            Queue<ProductStructureDto> queue = new LinkedList<>();
//            if (root.getChildren() != null) {
//                queue.addAll(root.getChildren());
//            }
//
//            while (!queue.isEmpty()) {
//                ProductStructureDto child = queue.poll();
//
//                // æŸ¥æ‰¾çˆ¶èŠ‚ç‚¹
//                ProductStructureDto parent = idMap.get(child.getParentId());
//                if (parent == null) {
//                    // é™¤äº†æœ€å¤–层节点,其他节点的父类肯定是不会为空的
//                    continue;
//                }
//
//                BomImportDto row = new BomImportDto();
//                // çˆ¶ç±»ä¿¡æ¯
//                row.setParentName(parent.getProductName());
//                row.setParentSpec(parent.getModel());
//                // å­ç±»ä¿¡æ¯
//                row.setChildName(child.getProductName());
//                row.setChildSpec(child.getModel());
//                row.setUnitQty(child.getUnitQuantity());
//                row.setProcess(child.getProcessName());
//
//                exportList.add(row);
//
//                //  å°†å­èŠ‚ç‚¹çš„å­èŠ‚ç‚¹åŠ å…¥é˜Ÿåˆ—-下一层
//                if (child.getChildren() != null && !child.getChildren().isEmpty()) {
//                    queue.addAll(child.getChildren());
//                }
//            }
//        }
        //  å°†æ ‘形结构扁平化 ä½¿ç”¨ BFS算法 å¯¼å‡º,按层级顺序
        List<BomImportDto> exportList = new ArrayList<>();
        // Map<ID, Node> idMap ç”¨äºŽæŸ¥æ‰¾çˆ¶èŠ‚ç‚¹
        Map<Long, TechnologyBomStructureVo> idMap = new HashMap<>();
        populateMap(treeData, idMap);
        //  treeData çš„第一个是根节点
        for (TechnologyBomStructureVo root : treeData) {
            //  æ·»åŠ æ ¹èŠ‚ç‚¹
            BomImportDto rootRow = new BomImportDto();
            rootRow.setParentName(root.getProductName());
            rootRow.setParentSpec(root.getModel());
            rootRow.setUnitQty(root.getUnitQuantity());
            rootRow.setParentCode(root.getProductCode());
            rootRow.setRemark("");
            exportList.add(rootRow);
            //  BFS éåކ-队列
            Queue<TechnologyBomStructureVo> queue = new LinkedList<>();
            if (root.getChildren() != null) {
                queue.addAll(root.getChildren());
            }
            while (!queue.isEmpty()) {
                TechnologyBomStructureVo child = queue.poll();
                // æŸ¥æ‰¾çˆ¶èŠ‚ç‚¹
                TechnologyBomStructureVo parent = idMap.get(child.getParentId());
                if (parent == null) {
                    // é™¤äº†æœ€å¤–层节点,其他节点的父类肯定是不会为空的
                    continue;
                }
                BomImportDto row = new BomImportDto();
                // çˆ¶ç±»ä¿¡æ¯
                row.setParentName(parent.getProductName());
                row.setParentSpec(parent.getModel());
                // å­ç±»ä¿¡æ¯
                row.setChildName(child.getProductName());
                row.setChildSpec(child.getModel());
                row.setUnitQty(child.getUnitQuantity());
                row.setProcess(child.getOperationName());
                row.setChildCode(child.getProductCode());
                exportList.add(row);
                //  å°†å­èŠ‚ç‚¹çš„å­èŠ‚ç‚¹åŠ å…¥é˜Ÿåˆ—-下一层
                if (child.getChildren() != null && !child.getChildren().isEmpty()) {
                    queue.addAll(child.getChildren());
                }
            }
        }
        ExcelUtil<BomImportDto> util = new ExcelUtil<>(BomImportDto.class);
//        util.exportExcel(response, exportList, "BOM结构导出");
        util.exportExcel(response, exportList, "BOM结构导出");
    }
    @Override
@@ -408,11 +409,11 @@
        return s.replaceAll("[\\u00A0\\u3000]", "").trim();
    }
    private void populateMap(List<ProductStructureDto> nodes, Map<Long, ProductStructureDto> map) {
    private void populateMap(List<TechnologyBomStructureVo> nodes, Map<Long, TechnologyBomStructureVo> map) {
        if (nodes == null || nodes.isEmpty()) {
            return;
        }
        for (ProductStructureDto node : nodes) {
        for (TechnologyBomStructureVo node : nodes) {
            map.put(node.getId(), node);
            populateMap(node.getChildren(), map);
        }
src/main/java/com/ruoyi/warehouse/mapper/DocumentationMapper.java
@@ -2,7 +2,6 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.procurementrecord.dto.ProcurementRecordOutPageDto;
import com.ruoyi.warehouse.dto.DocumentationDto;
import com.ruoyi.warehouse.pojo.Documentation;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
src/main/resources/application-dev.yml
@@ -74,9 +74,9 @@
    druid:
      # ä¸»åº“数据源
      master:
        url: jdbc:mysql://localhost:3300/product-inventory-management-new-pro?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        url: jdbc:mysql://localhost:3306/product-inventory-management-new-pro?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: root
        password: 123456
      # ä»Žåº“数据源
      slave:
        # ä»Žæ•°æ®æºå¼€å…³/默认关闭
src/main/resources/mapper/account/AccountSubjectMapper.xml
@@ -5,6 +5,7 @@
    <!-- é€šç”¨æŸ¥è¯¢æ˜ å°„结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.account.pojo.AccountSubject">
        <id column="id" property="id" />
        <result column="parent_id" property="parentId" />
        <result column="subject_code" property="subjectCode" />
        <result column="subject_name" property="subjectName" />
        <result column="subject_type" property="subjectType" />
@@ -18,4 +19,13 @@
        <result column="dept_id" property="deptId" />
    </resultMap>
    <select id="countReferencedBySubjectCodes" resultType="java.lang.Long">
        SELECT COUNT(1)
        FROM fin_voucher_entry
        WHERE subject_code IN
        <foreach collection="subjectCodes" item="item" open="(" separator="," close=")">
            #{item}
        </foreach>
    </select>
</mapper>
src/main/resources/mapper/account/SalesRefundAmountOrderMapper.xml
@@ -18,9 +18,9 @@
    <select id="pageSalesRefundAmountOrderDto" resultType="com.ruoyi.account.bean.dto.SalesRefundAmountOrderDto">
        select sl.sales_contract_no,
        sl.customer_contract_no,
        slp.specification_model,
        slp.product_category as product_name,
        slp.unit,
        pm.model as specification_model,
        p.product_name ,
        pm.unit,
        sl.customer_name,
        rm.return_no as return_management_no,
        srao.*
@@ -28,6 +28,8 @@
        left join return_management rm on srao.return_management_id = rm.id
        left join return_sale_product rs on rm.id = rs.return_management_id
        left join sales_ledger_product slp on rs.return_sales_ledger_product_id = slp.id
        left join product_model pm on slp.product_model_id = pm.id
            left join product p on pm.product_id = p.id
        left join sales_ledger sl on slp.sales_ledger_id = sl.id
        <where>
            <if test="ew.salesContractNo != null and ew.salesContractNo !=''">
src/main/resources/mapper/account/financial/FinVoucherEntryMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.account.mapper.financial.FinVoucherEntryMapper">
    <resultMap id="BaseResultMap" type="com.ruoyi.account.pojo.financial.FinVoucherEntry">
        <id column="id" property="id"/>
        <result column="voucher_id" property="voucherId"/>
        <result column="row_no" property="rowNo"/>
        <result column="subject_code" property="subjectCode"/>
        <result column="subject_name" property="subjectName"/>
        <result column="summary" property="summary"/>
        <result column="debit" property="debit"/>
        <result column="credit" property="credit"/>
        <result column="auxiliary_type" property="auxiliaryType"/>
        <result column="auxiliary_id" property="auxiliaryId"/>
        <result column="auxiliary_name" property="auxiliaryName"/>
        <result column="create_user" property="createUser"/>
        <result column="create_time" property="createTime"/>
        <result column="update_user" property="updateUser"/>
        <result column="update_time" property="updateTime"/>
        <result column="dept_id" property="deptId"/>
    </resultMap>
    <select id="listPostedEntries" resultType="com.ruoyi.account.bean.vo.financial.FinLedgerEntryRecordVo">
        SELECT
            v.voucher_date AS voucherDate,
            v.voucher_no AS voucherNo,
            CASE
                WHEN e.summary IS NOT NULL AND e.summary != '' THEN e.summary
                ELSE v.summary
            END AS summary,
            e.debit AS debit,
            e.credit AS credit,
            e.row_no AS rowNo
        FROM fin_voucher_entry e
        INNER JOIN fin_voucher v ON e.voucher_id = v.id
        WHERE v.status = 'posted'
          AND (e.subject_code = #{subjectCode} OR e.subject_code LIKE CONCAT(#{subjectCode}, '%'))
          AND v.voucher_date <![CDATA[>=]]> #{startDate}
          AND v.voucher_date <![CDATA[<=]]> #{endDate}
        <if test="auxiliaryType != null and auxiliaryType != ''">
          AND e.auxiliary_type = #{auxiliaryType}
        </if>
        <if test="auxiliaryId != null and auxiliaryId != ''">
          AND e.auxiliary_id = #{auxiliaryId}
        </if>
        ORDER BY v.voucher_date ASC, v.id ASC, e.row_no ASC, e.id ASC
    </select>
    <select id="listPostedEntriesBefore" resultType="com.ruoyi.account.bean.vo.financial.FinLedgerEntryRecordVo">
        SELECT
            v.voucher_date AS voucherDate,
            v.voucher_no AS voucherNo,
            CASE
                WHEN e.summary IS NOT NULL AND e.summary != '' THEN e.summary
                ELSE v.summary
            END AS summary,
            e.debit AS debit,
            e.credit AS credit,
            e.row_no AS rowNo
        FROM fin_voucher_entry e
        INNER JOIN fin_voucher v ON e.voucher_id = v.id
        WHERE v.status = 'posted'
          AND (e.subject_code = #{subjectCode} OR e.subject_code LIKE CONCAT(#{subjectCode}, '%'))
          AND v.voucher_date <![CDATA[<]]> #{beforeDate}
        <if test="auxiliaryType != null and auxiliaryType != ''">
          AND e.auxiliary_type = #{auxiliaryType}
        </if>
        <if test="auxiliaryId != null and auxiliaryId != ''">
          AND e.auxiliary_id = #{auxiliaryId}
        </if>
    </select>
</mapper>
src/main/resources/mapper/oA/OaProjectMapper.xml
ÎļþÒÑɾ³ý
src/main/resources/mapper/oA/OaProjectPhaseMapper.xml
ÎļþÒÑɾ³ý
src/main/resources/mapper/oA/OaProjectPhaseTaskMapper.xml
ÎļþÒÑɾ³ý
src/main/resources/mapper/procurementrecord/ProcurementRecordMapper.xml
@@ -2,7 +2,7 @@
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper">
    <select id="listProcurementBySalesLedgerId" resultType="com.ruoyi.procurementrecord.dto.ProcurementDto">
    <select id="listProcurementBySalesLedgerId" resultType="com.ruoyi.procurementrecord.bean.dto.ProcurementDto">
        select
            t1.supplier_name,
            t2.product_category,
@@ -28,7 +28,7 @@
        </if>
        group by t2.id
    </select>
    <select id="listPage" resultType="com.ruoyi.procurementrecord.dto.ProcurementPageDto">
    <select id="listPage" resultType="com.ruoyi.procurementrecord.bean.dto.ProcurementPageDto">
        select
        t3.supplier_name,
        t3.purchase_contract_number,
@@ -68,7 +68,7 @@
        </where>
        order by t1.create_time desc
    </select>
    <select id="list" resultType="com.ruoyi.procurementrecord.dto.ProcurementPageDto">
    <select id="list" resultType="com.ruoyi.procurementrecord.bean.dto.ProcurementPageDto">
        select
            t3.supplier_name,
            t3.purchase_contract_number,
@@ -92,7 +92,7 @@
                  left join purchase_ledger t3 on t3.id = t2.sales_ledger_id
                where t1.type = 1
    </select>
    <select id="listOne" resultType="com.ruoyi.procurementrecord.dto.ProcurementPageDto">
    <select id="listOne" resultType="com.ruoyi.procurementrecord.bean.dto.ProcurementPageDto">
        select
            t3.customer_contract_no,
            t3.sales_contract_no,
@@ -117,7 +117,7 @@
            left join sales_ledger t3 on t3.id = t2.sales_ledger_id
        where t1.type = 2
    </select>
    <select id="listPageCopy" resultType="com.ruoyi.procurementrecord.dto.ProcurementPageDtoCopy">
    <select id="listPageCopy" resultType="com.ruoyi.procurementrecord.bean.dto.ProcurementPageDtoCopy">
        select
        t3.supplier_name,
        t3.purchase_contract_number,
@@ -175,7 +175,7 @@
        group by t3.supplier_name,t2.product_category,t2.specification_model,t1.unit_price
        order by t1.create_time desc
    </select>
    <select id="listCopy" resultType="com.ruoyi.procurementrecord.dto.ProcurementPageDtoCopy">
    <select id="listCopy" resultType="com.ruoyi.procurementrecord.bean.dto.ProcurementPageDtoCopy">
        select
            t3.supplier_name,
            t3.purchase_contract_number,
@@ -203,7 +203,7 @@
        where t1.type = 1
        group by t3.supplier_name,t2.product_category,t2.specification_model
    </select>
    <select id="listCopyOne" resultType="com.ruoyi.procurementrecord.dto.ProcurementPageDtoCopy">
    <select id="listCopyOne" resultType="com.ruoyi.procurementrecord.bean.dto.ProcurementPageDtoCopy">
        select
            t3.customer_contract_no,
            t3.sales_contract_no,
@@ -232,7 +232,7 @@
        where t1.type = 2
        group by t3.customer_name,t2.product_category,t2.specification_model
    </select>
    <select id="listPageByProduction" resultType="com.ruoyi.procurementrecord.dto.ProcurementPageDto">
    <select id="listPageByProduction" resultType="com.ruoyi.procurementrecord.bean.dto.ProcurementPageDto">
        select
        t3.customer_contract_no,
        t3.sales_contract_no,
@@ -274,7 +274,7 @@
        </where>
        order by t1.create_time desc
    </select>
    <select id="listPageCopyByProduction" resultType="com.ruoyi.procurementrecord.dto.ProcurementPageDtoCopy">
    <select id="listPageCopyByProduction" resultType="com.ruoyi.procurementrecord.bean.dto.ProcurementPageDtoCopy">
        select
        t3.customer_contract_no,
        t3.sales_contract_no,
@@ -336,7 +336,7 @@
        group by t2.product_category,t2.specification_model,t1.unit_price
        order by t1.create_time desc
    </select>
    <select id="listPagePRS" resultType="com.ruoyi.procurementrecord.dto.ProcurementPageDtoCopy">
    <select id="listPagePRS" resultType="com.ruoyi.procurementrecord.bean.dto.ProcurementPageDtoCopy">
        select *
        from  procurement_record_storage t1
        left join sales_ledger_product t2 on t2.id = t1.sales_ledger_product_id
@@ -358,7 +358,7 @@
        from procurement_record_storage
        where product_model_id = #{productModelId}
    </select>
    <select id="listPageByProductProduction" resultType="com.ruoyi.procurementrecord.dto.ProcurementPageDto">
    <select id="listPageByProductProduction" resultType="com.ruoyi.procurementrecord.bean.dto.ProcurementPageDto">
        select
        t1.*,
        t1.inbound_num as inboundNum0,
@@ -380,4 +380,4 @@
        </where>
        order by t1.create_time desc
    </select>
</mapper>
</mapper>
src/main/resources/mapper/procurementrecord/ProcurementRecordOutMapper.xml
@@ -2,7 +2,7 @@
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.procurementrecord.mapper.ProcurementRecordOutMapper">
    <select id="listPage" resultType="com.ruoyi.procurementrecord.dto.ProcurementRecordOutPageDto">
    <select id="listPage" resultType="com.ruoyi.procurementrecord.bean.dto.ProcurementRecordOutPageDto">
        select
        t3.supplier_name,
        t2.product_category,
@@ -38,7 +38,7 @@
        </where>
        order by t1.create_time desc
    </select>
    <select id="list" resultType="com.ruoyi.procurementrecord.dto.ProcurementRecordOutPageDto">
    <select id="list" resultType="com.ruoyi.procurementrecord.bean.dto.ProcurementRecordOutPageDto">
        select t3.supplier_name,
               t2.product_category,
               t1.id,
@@ -58,7 +58,7 @@
        where t1.type = 1
    </select>
    <select id="listOne" resultType="com.ruoyi.procurementrecord.dto.ProcurementRecordOutPageDto">
    <select id="listOne" resultType="com.ruoyi.procurementrecord.bean.dto.ProcurementRecordOutPageDto">
        select t3.customer_contract_no,
               t3.sales_contract_no,
               t3.customer_name,
@@ -80,7 +80,7 @@
        where t1.type = 2
    </select>
    <select id="listTwo" resultType="com.ruoyi.procurementrecord.dto.ProcurementRecordOutPageDto">
    <select id="listTwo" resultType="com.ruoyi.procurementrecord.bean.dto.ProcurementRecordOutPageDto">
        select t1.supplier_name,
               t1.product_category,
               t1.id,
@@ -97,7 +97,7 @@
        from procurement_record_out t1
        where t1.type = 3
    </select>
    <select id="listPageByProduct" resultType="com.ruoyi.procurementrecord.dto.ProcurementRecordOutPageDto">
    <select id="listPageByProduct" resultType="com.ruoyi.procurementrecord.bean.dto.ProcurementRecordOutPageDto">
        select
        t3.customer_contract_no,
        t3.sales_contract_no,
@@ -134,7 +134,7 @@
        </where>
        order by t1.create_time desc
    </select>
    <select id="listPageByCustom" resultType="com.ruoyi.procurementrecord.dto.ProcurementRecordOutPageDto">
    <select id="listPageByCustom" resultType="com.ruoyi.procurementrecord.bean.dto.ProcurementRecordOutPageDto">
        select
        t2.supplier_name,
        t2.product_category,
@@ -180,7 +180,7 @@
        order by id desc
        limit 1
    </select>
    <select id="listPageBySemiProduct" resultType="com.ruoyi.procurementrecord.dto.ProcurementRecordOutPageDto">
    <select id="listPageBySemiProduct" resultType="com.ruoyi.procurementrecord.bean.dto.ProcurementRecordOutPageDto">
        select
        t1.id,
        t1.code,
@@ -206,4 +206,4 @@
        order by t1.create_time desc
    </select>
</mapper>
</mapper>
src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml
@@ -2,7 +2,7 @@
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.procurementrecord.mapper.ReturnManagementMapper">
    <select id="listPage" resultType="com.ruoyi.procurementrecord.dto.ReturnManagementDto">
    <select id="listPage" resultType="com.ruoyi.procurementrecord.bean.dto.ReturnManagementDto">
        select rm.*,
               c.customer_name,
               si.shipping_no,
@@ -40,7 +40,7 @@
            </if>
        </where>
    </select>
    <select id="getReturnManagementDtoById" resultType="com.ruoyi.procurementrecord.dto.ReturnManagementDto">
    <select id="getReturnManagementDtoById" resultType="com.ruoyi.procurementrecord.bean.dto.ReturnManagementDto">
     select rm.*,
               c.customer_name,
               si.shipping_no,
@@ -53,7 +53,7 @@
                 left join sales_ledger sl on si.sales_ledger_id = sl.id
        where rm.id = #{id}
    </select>
    <select id="listPageBySalesReturn" resultType="com.ruoyi.account.bean.vo.SalesReturnVo">
    <select id="listPageAccountSalesReturn" resultType="com.ruoyi.account.bean.vo.SalesReturnVo">
         select rm.id,
                rm.return_no,
                c.customer_name,
@@ -75,7 +75,7 @@
                and c.customer_name like concat('%',#{req.customerName},'%')
            </if>
            <if test="req.startDate != null and req.endDate != null">
                AND DATE_FORMAT(rm.make_time, '%Y-%m-%d') BETWEEN #{startDate} AND #{endDate}
                AND DATE_FORMAT(rm.make_time, '%Y-%m-%d') BETWEEN #{req.startDate} AND #{req.endDate}
            </if>
         order by rm.id DESC
    </select>
src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml
@@ -6,33 +6,46 @@
    <resultMap id="BaseResultMap" type="com.ruoyi.procurementrecord.pojo.ReturnSaleProduct">
        <id column="id" property="id" />
        <result column="return_management_id" property="returnManagementId" />
        <result column="return_sales_ledger_product_id" property="returnsalesLedgerProductId" />
        <result column="stock_out_record_id" property="stockOutRecordId" />
        <result column="num" property="num" />
        <result column="status" property="status" />
    </resultMap>
    <select id="listReturnSaleProductDto" resultType="com.ruoyi.procurementrecord.dto.ReturnSaleProductDto">
        SELECT slp.product_category                                         as product_name,
               slp.specification_model                                      as model,
               slp.unit                                      as unit,
    <select id="listReturnSaleProductDto" resultType="com.ruoyi.procurementrecord.bean.dto.ReturnSaleProductDto">
        SELECT distinct
            p.product_name                                         as product_name,
               pm.model                                     as model,
               pm.unit                                      as unit,
               rsp.*,
               GREATEST(slp.quantity - COALESCE(rs.total_return_num, 0), 0) AS un_quantity,
            sor.outbound_batches,
            sor.stock_out_num,
            sor.batch_no,
               GREATEST(sor.stock_out_num - COALESCE(rsp.num, 0), 0) AS un_quantity,
               COALESCE(rs.total_return_num, 0)                             AS total_return_num
        FROM return_sale_product rsp
                 LEFT JOIN return_management rm ON rm.id = rsp.return_management_id
                 LEFT JOIN shipping_info si ON si.id = rm.shipping_id
                 LEFT JOIN shipping_product_detail spd ON spd.shipping_info_id = si.id
                 LEFT JOIN stock_out_record sor ON rsp.stock_out_record_id = sor.id and sor.record_type = '13'
                 LEFT JOIN sales_ledger_product slp ON si.sales_ledger_product_id = slp.id and slp.type = 1
                 LEFT JOIN (SELECT return_sales_ledger_product_id,
                left join product_model pm on slp.product_model_id = pm.id
                  LEFT JOIN product p on pm.product_id = p.id
                 LEFT JOIN (SELECT stock_out_record_id,
                                   SUM(num) AS total_return_num
                            FROM return_sale_product
                            WHERE 1 = 1 and return_management_id != #{returnManagementId}
                            GROUP BY return_sales_ledger_product_id) rs ON rs.return_sales_ledger_product_id = slp.id
                            WHERE 1 = 1 and return_management_id = #{returnManagementId}
                            GROUP BY stock_out_record_id) rs ON rs.stock_out_record_id = sor.id
        where rm.id =#{returnManagementId}
    </select>
    <select id="listReturnSaleProduct" resultType="com.ruoyi.procurementrecord.dto.ReturnSaleProductDto">
        select rsp.*,slp.tax_inclusive_unit_price ,slp.tax_inclusive_total_price*rsp.num as price
    <select id="listReturnSaleProduct" resultType="com.ruoyi.procurementrecord.bean.dto.ReturnSaleProductDto">
        select rsp.*,
               sor.batch_no,
               slp.tax_inclusive_unit_price ,
               slp.tax_inclusive_total_price*rsp.num as price
        from return_sale_product rsp
                 left join sales_ledger_product slp on slp.id = rsp.return_sales_ledger_product_id
                 LEFT JOIN return_management rm ON rm.id = rsp.return_management_id
                 LEFT JOIN shipping_info si ON si.id = rm.shipping_id
                 LEFT JOIN sales_ledger_product slp ON si.sales_ledger_product_id = slp.id and slp.type = 1
                 LEFT JOIN stock_out_record sor ON rsp.stock_out_record_id = sor.id
        where rsp.return_management_id = #{returnManagementId}
    </select>
src/main/resources/mapper/purchase/PurchaseReturnOrderProductsMapper.xml
@@ -13,7 +13,9 @@
    </resultMap>
    <select id="getReturnOrderGroupListByProductIds" resultType="com.ruoyi.purchase.dto.SimpleReturnOrderGroupDto"
            parameterType="java.util.List">
        select t1.sales_ledger_product_id,sum(t1.return_quantity) as sum_return_quantity from purchase_return_order_products as t1
        select t1.sales_ledger_product_id,
               sum(t1.return_quantity) as sum_return_quantity
        from purchase_return_order_products as t1
        inner join purchase_return_orders as t2 on t1.purchase_return_order_id = t2.id
        WHERE t1.sales_ledger_product_id IN
        <foreach item="id" collection="productIds" separator="," open="(" close=")">
src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml
@@ -53,4 +53,96 @@
        <include refid="getPurchaseReturnOrderHasAllInfoFormAndColumn"/>
        where pro.id = #{id}
    </select>
    <select id="listPageAccountPurchaseReturn"
            resultType="com.ruoyi.account.bean.vo.PurchaseReturnVo">
         select pro.id,
                pro.no returnNo,
                t.inboundBatches,
                sm.supplier_name,
                pro.prepared_at,
                pro.total_amount,
                CASE pro.return_type WHEN 0 THEN '退货退款' WHEN 1 THEN '拒收' END AS returnType,
                pl.purchase_contract_number
        from purchase_return_orders pro
        left join
            (select prop.purchase_return_order_id,
                    GROUP_CONCAT(sir.inbound_batches SEPARATOR ',') AS inboundBatches
            from purchase_return_order_products prop
            left join stock_in_record sir on prop.stock_in_record_id = sir.id
            GROUP BY prop.purchase_return_order_id) t on t.purchase_return_order_id = pro.id
        left join supplier_manage sm on pro.supplier_id = sm.id
        left join purchase_ledger pl on pro.purchase_ledger_id = pl.id
        where 1=1
            <if test="req.returnNo != null and req.returnNo != ''">
                and pro.no like concat('%',#{req.returnNo},'%')
            </if>
            <if test="req.supplierName != null and req.supplierName != ''">
                and sm.supplier_name like concat('%',#{req.supplierName},'%')
            </if>
            <if test="req.startDate != null and req.endDate != null">
                AND DATE_FORMAT(pro.prepared_at, '%Y-%m-%d') BETWEEN #{req.startDate} AND #{req.endDate}
            </if>
         order by pro.id DESC
    </select>
    <select id="getByPurchaseLedgerId" resultType="com.ruoyi.purchase.vo.PurchaseStockInProductVo">
         SELECT
            sir.id,
            sir.product_model_id,
            slp.id salesLedgerProductId,
            slp.product_category,
            slp.specification_model,
            slp.unit,
            slp.is_checked,
            sir.inbound_batches,
            sir.stock_in_num,
            sir.batch_no,
            slp.tax_inclusive_unit_price,
            GREATEST(sir.stock_in_num - COALESCE(rs.total_return_num, 0), 0) AS un_quantity,
            COALESCE(rs.total_return_num, 0) AS total_return_num
            FROM stock_in_record sir
            LEFT JOIN quality_inspect qi ON sir.record_type = 10 AND sir.record_id = qi.id
            LEFT JOIN purchase_ledger pl
            ON pl.id = IF(sir.record_type = 7, sir.record_id, qi.purchase_ledger_id)
            LEFT JOIN sales_ledger_product slp ON pl.id = slp.sales_ledger_id
            LEFT JOIN (
                SELECT
                    stock_in_record_id,
                    SUM(return_quantity) AS total_return_num
                FROM purchase_return_order_products prop
                         left join purchase_return_orders pro on pro.id = prop.purchase_return_order_id
                WHERE 1=1
                GROUP BY stock_in_record_id
            ) rs ON rs.stock_in_record_id = sir.id
        WHERE sir.approval_status = 1 AND slp.type = 2
        AND sir.record_type IN ('7','10')
         and pl.id = #{purchaseLedgerId}
    </select>
    <select id="getPurchaseReturnOrderProductsDetailById"
            resultType="com.ruoyi.purchase.vo.PurchaseReturnOrderProductsDetailVo">
    select prop.id,
           prop.sales_ledger_product_id,
           slp.product_model_id,
           slp.product_category,
           slp.specification_model,
           slp.is_checked,
           slp.unit,
           sir.inbound_batches,
           sir.stock_in_num,
           sir.batch_no,
           slp.tax_inclusive_unit_price,
           prop.return_quantity,
           prop.purchase_return_order_id,
           GREATEST(sir.stock_in_num - COALESCE(prop.return_quantity, 0), 0) AS un_quantity,
           COALESCE(rs.total_return_num, 0)                             AS total_return_num
    from purchase_return_order_products prop
    left join purchase_return_orders pro on prop.purchase_return_order_id = pro.id
    LEFT JOIN stock_in_record sir ON prop.stock_in_record_id = sir.id and sir.record_type in ('7','10')
    LEFT JOIN sales_ledger_product slp ON prop.sales_ledger_product_id = slp.id  and slp.type = 2
    LEFT JOIN (SELECT stock_in_record_id,
                      SUM(return_quantity) AS total_return_num
               FROM purchase_return_order_products
               WHERE 1 = 1 and purchase_return_order_id = #{id}
               GROUP BY stock_in_record_id) rs ON rs.stock_in_record_id = sir.id
    where pro.id = #{id}
    </select>
</mapper>
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
@@ -6,7 +6,42 @@
    <select id="selectSalesLedgerProductList" resultType="com.ruoyi.sales.pojo.SalesLedgerProduct">
        SELECT
        T1.*,
        T1.id,
        T1.sales_ledger_id,
        T1.warn_num,
        T1.speculative_trading_name,
        T1.quantity,
        T1.min_stock,
        T1.tax_rate,
        T1.tax_inclusive_unit_price,
        T1.tax_inclusive_total_price,
        T1.tax_exclusive_total_price,
        T1.invoice_type,
        T1.type,
        T1.tickets_num,
        T1.tickets_amount,
        T1.future_tickets,
        T1.future_tickets_amount,
        T1.invoice_num,
        T1.no_invoice_num,
        T1.invoice_amount,
        T1.no_invoice_amount,
        T1.product_id,
        T1.product_model_id,
        T1.register,
        T1.register_date,
        T1.approve_status,
        T1.pending_invoice_total,
        T1.invoice_total,
        T1.pending_tickets_total,
        T1.tickets_total,
        T1.is_checked,
        T1.is_production,
        T1.create_user,
        T1.dept_id,
        p.product_name as product_category,
        pm.model as specification_model,
        pm.unit as unit,
        CASE
        WHEN (IFNULL(t2.qualitity, 0) - IFNULL(t2.locked_quantity, 0)) >0 THEN 1
        ELSE 0
@@ -29,6 +64,8 @@
        LEFT JOIN shipping_product_detail spd ON si.id = spd.shipping_info_id
        GROUP BY sales_ledger_product_id
        ) t3 ON t3.sales_ledger_product_id = T1.id
        left join product_model pm ON T1.product_model_id = pm.id
        left join product p ON pm.product_id = p.id
        <where>
            <if test="salesLedgerProduct.salesLedgerId != null">
                AND T1.sales_ledger_id = #{salesLedgerProduct.salesLedgerId}
src/main/resources/mapper/sales/ShippingInfoMapper.xml
@@ -19,14 +19,22 @@
        s.update_user,
        s.tenant_id,
        sl.sales_contract_no,
        slp.specification_model,
        pm.model as specification_model,
        pm.unit,
        p.product_name,
        sl.customer_name
        sl.customer_name,
        spd.totalQuantity,
        sor.outboundBatches
        FROM shipping_info s
        LEFT JOIN (select shipping_info_id,sum(quantity) totalQuantity from shipping_product_detail GROUP BY shipping_info_id) spd ON spd.shipping_info_id = s.id
        LEFT JOIN sales_ledger sl ON s.sales_ledger_id = sl.id
        LEFT JOIN sales_ledger_product slp ON s.sales_ledger_product_id = slp.id and slp.type = 1
        left join product_model pm on slp.product_model_id = pm.id
        left join product p on pm.product_id = p.id
        left join (select record_id,GROUP_CONCAT(outbound_batches SEPARATOR ',') AS outboundBatches
                   from stock_out_record
                   where record_type='13'and approval_status=1
                   group by record_id)sor on sor.record_id= s.id
        WHERE 1=1
        <if test="req.salesContractNo != null and req.salesContractNo != ''">
            AND sl.sales_contract_no LIKE CONCAT('%',#{req.salesContractNo},'%')
@@ -58,62 +66,43 @@
        FROM shipping_info s
                 LEFT JOIN sales_ledger sl ON s.sales_ledger_id = sl.id
    </select>
    <select id="getReturnManagementDtoById" resultType="com.ruoyi.sales.dto.SalesLedgerProductDto">
        SELECT
        slp.*,
        si.shipping_no,
        GREATEST(slp.quantity - COALESCE(rs.total_return_num, 0), 0) AS un_quantity,
        COALESCE(rs.total_return_num, 0) AS total_return_num
    <select id="getReturnManagementDtoById" resultType="com.ruoyi.procurementrecord.bean.vo.ShippingProductVo">
        SELECT distinct
            sor.id,
            slp.product_category,
            slp.specification_model,
            slp.unit,
            slp.product_model_id,
            sor.outbound_batches,
            sor.stock_out_num,
            sor.batch_no,
            slp.tax_inclusive_unit_price,
            GREATEST(sor.stock_out_num - COALESCE(rs.total_return_num, 0), 0) AS un_quantity,
            COALESCE(rs.total_return_num, 0) AS total_return_num
        FROM shipping_info si
        LEFT JOIN shipping_product_detail spd ON spd.shipping_info_id = si.id
        LEFT JOIN stock_out_record sor ON sor.record_id = si.id and sor.record_type = '13'
        LEFT JOIN sales_ledger_product slp ON si.sales_ledger_product_id = slp.id and slp.type = 1
        LEFT JOIN (
        SELECT
        return_sales_ledger_product_id,
        stock_out_record_id,
        SUM(num) AS total_return_num
        FROM return_sale_product rsp
        left join return_management rm on rm.id = rsp.return_management_id
        left join shipping_info si on si.id = rm.shipping_id
        WHERE 1=1
        GROUP BY return_sales_ledger_product_id
        ) rs ON rs.return_sales_ledger_product_id = slp.id
        GROUP BY stock_out_record_id
        ) rs ON rs.stock_out_record_id = sor.id
        <where>
            <if test="shippingId != null">
                si.id = #{shippingId}
            </if>
        </where>
        order by sor.id
    </select>
    <select id="getShippingInfoByCustomerName" resultType="com.ruoyi.sales.pojo.ShippingInfo">
        select * from shipping_info si
        left join sales_ledger sl on si.sales_ledger_id = sl.id
        where si.status = '已发货' and sl.customer_name = #{customerName}
    </select>
    <select id="listPageByOutbound" resultType="com.ruoyi.account.bean.vo.SalesOutboundVo">
         SELECT
        sor.id,
        sor.outbound_batches,
        sl.customer_name,
        s.shipping_date,
        p.product_name,
        slp.specification_model,
        slp.stock_out_num,
        s.shipping_no,
        sl.sales_contract_no
        FROM shipping_info s
        LEFT JOIN sales_ledger sl ON s.sales_ledger_id = sl.id
        LEFT JOIN sales_ledger_product slp ON s.sales_ledger_product_id = slp.id and slp.type = 1
        left join product_model pm on slp.product_model_id = pm.id
        left join product p on pm.product_id = p.id
        left join stock_out_record sor on sor.record_id = s.id and sor.record_type='13'
        WHERE s.status='已发货'
        <if test="req.outboundBatches != null and req.outboundBatches != ''">
            AND sor.outbound_batches LIKE CONCAT('%',#{req.outboundBatches},'%')
        </if>
        <if test="req.customerName != null and req.customerName != ''">
            AND sl.customer_name LIKE CONCAT('%',#{req.customerName},'%')
        </if>
        <if test="req.startDate != null and req.endDate != null">
            AND s.shipping_date BETWEEN #{startDate} AND #{endDate}
        </if>
        order by sor.id DESC
        where  sl.customer_name = #{customerName}
    </select>
</mapper>
src/main/resources/mapper/stock/StockInRecordMapper.xml
@@ -70,4 +70,38 @@
        </where>
        order by sir.id desc
    </select>
    <select id="listPageAccountPurchase" resultType="com.ruoyi.account.bean.vo.PurchaseInboundVo">
        SELECT
            sir.id,
            sir.inbound_batches,
            pl.supplier_name,
            DATE(sir.create_time) AS inboundDate,
            p.product_name,
            pm.model as specification_model,
            sir.stock_in_num * slp.tax_inclusive_unit_price AS InboundAmount,
            pl.purchase_contract_number
            FROM stock_in_record sir
            -- 10 ç±»åž‹æ‰å…³è”质检表
            LEFT JOIN quality_inspect qi ON sir.record_type = 10 AND sir.record_id = qi.id
            -- åŠ¨æ€å…³è”é‡‡è´­ï¼ˆè‡ªåŠ¨é€‚é… 7 å’Œ 10)
            LEFT JOIN purchase_ledger pl
            ON pl.id = IF(sir.record_type = 7, sir.record_id, qi.purchase_ledger_id)
            -- äº§å“å…³è”不动
            LEFT JOIN sales_ledger_product slp ON pl.id = slp.product_id
            LEFT JOIN product_model pm ON sir.product_model_id = pm.id
            LEFT JOIN product p ON pm.product_id = p.id
            -- æ¡ä»¶
        WHERE sir.approval_status = 1 AND slp.type = 2
        AND sir.record_type IN ('7','10')
        <if test="req.inboundBatches != null and req.inboundBatches != ''">
            AND sir.inbound_batches LIKE CONCAT('%',#{req.inboundBatches},'%')
        </if>
        <if test="req.supplierName != null and req.supplierName != ''">
            AND pl.supplier_name LIKE CONCAT('%',#{req.supplierName},'%')
        </if>
        <if test="req.startDate != null and req.endDate != null">
            AND DATE(sir.create_time) BETWEEN #{req.startDate} AND #{req.endDate}
        </if>
        order by sir.id DESC
    </select>
</mapper>
src/main/resources/mapper/stock/StockInventoryMapper.xml
@@ -436,7 +436,7 @@
        WHERE sor.product_model_id = #{productModelId}
          AND (sor.batch_no = #{batchNo} OR (#{batchNo} IS NULL AND sor.batch_no IS NULL))
          AND sor.type = #{type}
          AND sor.approval_status = 0
          AND sor.approval_status IN (0, 3)
    </select>
    <select id="listSelectableBatchNoByProductModelIds" resultType="com.ruoyi.stock.pojo.StockInventory">
@@ -453,12 +453,24 @@
        order by si.product_model_id, si.batch_no
    </select>
    <select id="getByModelId" resultType="com.ruoyi.stock.pojo.StockInventory">
        select spd.id, spd.batch_no, spd.locked_quantity, (spd.qualitity - IFNULL(sd.qualitity, 0)) as qualitity
        from stock_inventory spd
                 left join (select stock_inventory_id, sum(quantity) as qualitity
                            from shipping_product_detail
                            group by stock_inventory_id) as sd on sd.stock_inventory_id = spd.id
        where product_model_id = #{productModelId}
        select si.id, si.batch_no, si.locked_quantity, (si.qualitity - IFNULL(sd.qualitity, 0)) as qualitity
        from stock_inventory si
                 left join (
                    select spd.stock_inventory_id, sum(spd.quantity) as qualitity
                    from shipping_product_detail spd
                    where exists (
                        select 1
                        from stock_out_record sor
                        where sor.record_id = spd.shipping_info_id
                          and sor.record_type = '13'
                          and sor.type = '0'
                          and sor.approval_status in (0, 3)
                          and sor.product_model_id = spd.product_model_id
                          and (sor.batch_no = spd.batch_no or (sor.batch_no is null and spd.batch_no is null))
                    )
                    group by spd.stock_inventory_id
                 ) as sd on sd.stock_inventory_id = si.id
        where si.product_model_id = #{productModelId}
    </select>
</mapper>
src/main/resources/mapper/stock/StockOutRecordMapper.xml
@@ -86,4 +86,33 @@
        order by sor.id desc
    </select>
    <select id="listPageAccountSales" resultType="com.ruoyi.account.bean.vo.SalesOutboundVo">
    SELECT
        sor.id,
        sor.outbound_batches,
        sl.customer_name,
        sor.create_time as shippingDate,
        p.product_name,
        pm.model as specification_model,
        sor.stock_out_num * slp.tax_inclusive_unit_price as outboundAmount,
        s.shipping_no,
        sl.sales_contract_no
        FROM stock_out_record sor
        left join shipping_info s on sor.record_id = s.id
        LEFT JOIN sales_ledger sl ON s.sales_ledger_id = sl.id
        LEFT JOIN sales_ledger_product slp ON s.sales_ledger_product_id = slp.id and slp.type = 1
        left join product_model pm on slp.product_model_id = pm.id
        left join product p on pm.product_id = p.id
        WHERE sor.record_type='13' and sor.approval_status=1
        <if test="req.outboundBatches != null and req.outboundBatches != ''">
            AND sor.outbound_batches LIKE CONCAT('%',#{req.outboundBatches},'%')
        </if>
        <if test="req.customerName != null and req.customerName != ''">
            AND sl.customer_name LIKE CONCAT('%',#{req.customerName},'%')
        </if>
        <if test="req.startDate != null and req.endDate != null">
            AND s.shipping_date BETWEEN #{req.startDate} AND #{req.endDate}
        </if>
        order by sor.id DESC
    </select>
</mapper>
src/main/resources/mapper/technology/TechnologyBomStructureMapper.xml
@@ -21,7 +21,8 @@
               p.product_name as productName,
               pm.product_id as productId,
               pm.model,
               top1.name as operationName
               top1.name as operationName,
               pm.product_code as productCode
        from technology_bom_structure tbs
        left join product_model pm on tbs.product_model_id = pm.id
        left join product p on pm.product_id = p.id