shiyunteng
2026-08-25 dd4284136cb12dc3c2e498e0f0ff002d553763af
feat:feat:新增降点损 赊销业务均使用代储
14个文件已修改
11个文件已添加
998 ■■■■■ 已修改文件
platformx-boot/src/main/resources/application-dev.yml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/DropLossAddDTO.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/DropLossQueryDTO.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/InvoiceQueryDTO.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/SyncProductDTO.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/Contract.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/ContractInvoice.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/DropLoss.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/vo/ContractDetailVo.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/vo/DropLossPageVo.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/ContractInvoiceController.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/DropLossController.java 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/ProductController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/mapper/DropLossMapper.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/mapper/ProductMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/ContractInvoiceService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/DropLossService.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/ProductService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/ContractInvoiceServiceImpl.java 314 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/DropLossServiceImpl.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/PaymentConfirmServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/PaymentOffsetServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/ProductServiceImpl.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/resources/mapper/DropLossMapper.xml 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/resources/mapper/ProductMapper.xml 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-boot/src/main/resources/application-dev.yml
@@ -79,3 +79,6 @@
  sgb:
  jxc:
  tfgs:
file:
  path: /zbjt/temp
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/DropLossAddDTO.java
New file
@@ -0,0 +1,35 @@
package com.by4cloud.platformx.business.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class DropLossAddDTO {
    @Schema(description = "关联合同ID")
    private Long contractId;
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Schema(description = "降点损失时间")
    private Date dropDate;
    @Schema(description = "损失金额")
    private BigDecimal dropAmount;
    @Schema(description = "降点原因")
    private String dropRemark;
    @Schema(description = "附件URL")
    private String attachmentUrl;
    @Schema(description = "附件名称")
    private String attachmentName;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/DropLossQueryDTO.java
New file
@@ -0,0 +1,10 @@
package com.by4cloud.platformx.business.dto;
import lombok.Data;
@Data
public class DropLossQueryDTO {
    private String contractNo;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/InvoiceQueryDTO.java
New file
@@ -0,0 +1,34 @@
package com.by4cloud.platformx.business.dto;
import lombok.Data;
@Data
public class InvoiceQueryDTO {
    private String serialNo;
    private String invoiceCode;
    private String invoiceNo;
    /**
     *  004:增值税专用发票;007:增值税普通发票;026:增
     * 值税电子发票;025:增值税卷式发票;028:增值税电子专用发票 005 机
     * 动车发票 006 二手车发票
     */
    private String invoiceTypeCode;
    private String invoiceTerminalCode;
    private String sourceMark;
    private String invoiceSpecialMark;
    private String buyerTaxNo;
    private String buyerName;
    private String invoiceStatus;
    private String invoiceType;
    private String invoiceStartDate;
    private String invoiceEndDate;
    private String systemId;
    private String systemName;
    private String machineNo;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/SyncProductDTO.java
New file
@@ -0,0 +1,10 @@
package com.by4cloud.platformx.business.dto;
import lombok.Data;
@Data
public class SyncProductDTO {
    private String startDate;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/Contract.java
@@ -400,11 +400,14 @@
    private String paymentMethod;
    @Schema(description = "出库金额")
    @Column(columnDefinition = "decimal(10,2) comment '出库金额'")
    @Column(columnDefinition = "decimal(14,2) comment '出库金额'")
    private BigDecimal outBoundAmout;
    @Schema(description = "合同中心推送标识")
    @Column(columnDefinition = "char DEFAULT '0' comment '合同中心推送标识 0 未推 1 已推'")
    private String centerPushFlag;
    @Schema(description = "损失金额")
    @Column(columnDefinition = "decimal(10,2) DEFAULT '0.00' comment '损失金额'")
    private BigDecimal dropAmount;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/ContractInvoice.java
@@ -114,6 +114,10 @@
    @Column(columnDefinition="text comment '发票预览bip推送成功后返回的数据结构'")
    private String bipBody;
    @Schema(description = "bip推送成功后返回数据ID")
    @Column(columnDefinition="varchar(64) comment 'bip推送成功后返回数据ID'")
    private String bipFileId;
    @Schema(description = "关联请求ID")
    @Column(columnDefinition = "varchar(64) comment '关联请求ID'")
    private String bwRequestId;
@@ -129,4 +133,8 @@
    @Schema(description = "预收开票标识")
    @Column(columnDefinition = "char default '0' comment '0/否 1/是'")
    private String advanceFlag;
    @Schema(description = "发票来源")
    @Column(columnDefinition="char default '0' comment '发票来源 0/系统生成 1/外部获取'")
    private String invoiceSource;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/DropLoss.java
New file
@@ -0,0 +1,59 @@
package com.by4cloud.platformx.business.entity;
import com.by4cloud.platformx.common.data.mybatis.BaseModel;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import lombok.Data;
import org.hibernate.annotations.Table;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.Date;
/**
 * 2026年8月7日 16:04:22
 * syt
 */
@Data
@Entity//加了才能自动生成表
@Table(appliesTo="drop_loss",comment = "降点损失")//给表加注释
@jakarta.persistence.Table(name = "drop_loss")//数据库创建的表明
public class DropLoss extends BaseModel<DropLoss> {
    @Schema(description = "关联合同ID")
    @Column(columnDefinition = "bigint not null comment '关联合同ID'")
    private Long contractId;
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Schema(description = "降点损失时间")
    @Column(columnDefinition = "datetime comment '降点损失时间'")
    private Date dropDate;
    @Schema(description = "损失金额")
    @Column(columnDefinition = "decimal(10,2) comment '损失金额'")
    private BigDecimal dropAmount;
    @Schema(description = "降点原因")
    @Column(columnDefinition="text comment '降点原因'")
    private String dropRemark;
    @Schema(description = "录入人员")
    @Column(columnDefinition = "varchar(64) not null comment '录入人员'")
    private String dropUser;
    @Schema(description = "录入人员ID")
    @Column(columnDefinition = "bigint not null comment '录入人员ID'")
    private Long dropUserId;
    @Schema(description = "附件URL")
    @Column(columnDefinition = "VARCHAR(500) comment '合同附件URL'")
    private String attachmentUrl;
    @Schema(description = "附件名称")
    @Column(columnDefinition = "VARCHAR(100) comment '合同附件名称'")
    private String attachmentName;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/vo/ContractDetailVo.java
@@ -37,7 +37,7 @@
    private String partyB;
    @Schema(description = "合同金额")
    private Double amount;
    private BigDecimal amount;
    @Schema(description = "币种(CNY/USD等)")
    private String currency;
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/vo/DropLossPageVo.java
New file
@@ -0,0 +1,54 @@
package com.by4cloud.platformx.business.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class DropLossPageVo {
    private Long id;
    @Schema(description = "关联合同ID")
    private Long contractId;
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Schema(description = "降点损失时间")
    private Date dropDate;
    @Schema(description = "损失金额")
    private BigDecimal dropAmount;
    @Schema(description = "降点原因")
    private String dropRemark;
    @Schema(description = "录入人员")
    private String dropUser;
    @Schema(description = "录入人员ID")
    private Long dropUserId;
    @Schema(description = "附件URL")
    private String attachmentUrl;
    @Schema(description = "附件名称")
    private String attachmentName;
    @Schema(description = "合同名称")
    private String contractNo;
    @Schema(description = "甲方名称")
    private String partyA;
    @Schema(description = "乙方名称")
    private String partyB;
    @Schema(description = "合同金额")
    private BigDecimal amount;
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/ContractInvoiceController.java
@@ -7,11 +7,15 @@
import com.by4cloud.platformx.business.dto.ContractInvoiceQueryDTO;
import com.by4cloud.platformx.business.dto.ContractQueryDTO;
import com.by4cloud.platformx.business.dto.GenInvoiceInfoDTO;
import com.by4cloud.platformx.business.dto.InvoiceQueryDTO;
import com.by4cloud.platformx.business.entity.Contract;
import com.by4cloud.platformx.business.entity.ContractInvoice;
import com.by4cloud.platformx.business.entity.ContractOutBound;
import com.by4cloud.platformx.business.service.ContractInvoiceService;
import com.by4cloud.platformx.business.service.ContractService;
import com.by4cloud.platformx.common.core.util.R;
import com.by4cloud.platformx.common.data.mybatis.BaseModel;
import com.by4cloud.platformx.common.log.annotation.SysLog;
import com.by4cloud.platformx.common.security.annotation.Inner;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
@@ -65,6 +69,17 @@
//        }
        return R.ok(contractInvoiceService.pageScope(page, queryDTO));
    }
    /**
     * 新增发票
     * @return R
     */
    @Operation(summary = "新增发票" , description = "新增发票" )
    @SysLog("新增发票" )
    @PostMapping
    public R save(@RequestBody ContractInvoice contractInvoice) {
        return contractInvoiceService.add(contractInvoice);
    }
    /**
@@ -150,6 +165,15 @@
    }
    /**
     * 手动推送bip
     * @return
     */
    @GetMapping("/bipPush/{invoiceId}")
    public R bipPush(@PathVariable("invoiceId")Long invoiceId) {
        return contractInvoiceService.bipPush(invoiceId);
    }
    /**
     * 红票查询
     * @return
     */
@@ -164,4 +188,8 @@
        return R.ok(contractInvoiceService.pageInvoice(page, queryDTO));
    }
    @GetMapping("/queryInvoice")
    public R queryInvoice(@ParameterObject Page page, @ParameterObject InvoiceQueryDTO queryDTO) {
        return contractInvoiceService.queryInvoice(page, queryDTO);
    }
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/DropLossController.java
New file
@@ -0,0 +1,121 @@
package com.by4cloud.platformx.business.controller;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.by4cloud.platformx.business.dto.DropLossAddDTO;
import com.by4cloud.platformx.business.dto.DropLossQueryDTO;
import com.by4cloud.platformx.common.core.util.R;
import com.by4cloud.platformx.common.log.annotation.SysLog;
import com.by4cloud.platformx.business.entity.DropLoss;
import com.by4cloud.platformx.business.service.DropLossService;
import org.springframework.security.access.prepost.PreAuthorize;
import com.by4cloud.platformx.common.excel.annotation.ResponseExcel;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.HttpHeaders;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Objects;
/**
 * 降点损失
 *
 * @author syt
 * @date 2026-08-25 09:11:48
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/dropLoss" )
@Tag(description = "dropLoss" , name = "降点损失管理" )
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class DropLossController {
    private final  DropLossService dropLossService;
    /**
     * 分页查询
     * @param page 分页对象
     * @param queryDTO 降点损失
     * @return
     */
    @Operation(summary = "分页查询" , description = "分页查询" )
    @GetMapping("/page" )
    @PreAuthorize("@pms.hasPermission('business_dropLoss_view')" )
    public R getDropLossPage(@ParameterObject Page page, @ParameterObject DropLossQueryDTO queryDTO) {
        return R.ok(dropLossService.pageScope(page, queryDTO));
    }
    /**
     * 通过id查询降点损失
     * @param id id
     * @return R
     */
    @Operation(summary = "通过id查询" , description = "通过id查询" )
    @GetMapping("/{id}" )
    @PreAuthorize("@pms.hasPermission('business_dropLoss_view')" )
    public R getById(@PathVariable("id" ) Long id) {
        return R.ok(dropLossService.getById(id));
    }
    /**
     * 新增降点损失
     * @param addDTO 降点损失
     * @return R
     */
    @Operation(summary = "新增降点损失" , description = "新增降点损失" )
    @SysLog("新增降点损失" )
    @PostMapping
    @PreAuthorize("@pms.hasPermission('business_dropLoss_add')" )
    public R save(@RequestBody DropLossAddDTO addDTO) {
        return dropLossService.add(addDTO);
    }
    /**
     * 修改降点损失
     * @param dropLoss 降点损失
     * @return R
     */
    @Operation(summary = "修改降点损失" , description = "修改降点损失" )
    @SysLog("修改降点损失" )
    @PutMapping
    @PreAuthorize("@pms.hasPermission('business_dropLoss_edit')" )
    public R updateById(@RequestBody DropLoss dropLoss) {
        return R.ok(dropLossService.updateById(dropLoss));
    }
    /**
     * 通过id删除降点损失
     * @param ids id列表
     * @return R
     */
    @Operation(summary = "通过id删除降点损失" , description = "通过id删除降点损失" )
    @SysLog("通过id删除降点损失" )
    @DeleteMapping
    @PreAuthorize("@pms.hasPermission('business_dropLoss_del')" )
    public R removeById(@RequestBody Long[] ids) {
        return R.ok(dropLossService.removeBatchByIds(CollUtil.toList(ids)));
    }
    /**
     * 导出excel 表格
     * @param dropLoss 查询条件
        * @param ids 导出指定ID
     * @return excel 文件流
     */
    @ResponseExcel
    @GetMapping("/export")
    @PreAuthorize("@pms.hasPermission('business_dropLoss_export')" )
    public List<DropLoss> export(DropLoss dropLoss,Long[] ids) {
        return dropLossService.list(Wrappers.lambdaQuery(dropLoss).in(ArrayUtil.isNotEmpty(ids), DropLoss::getId, ids));
    }
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/ProductController.java
@@ -11,6 +11,7 @@
import com.by4cloud.platformx.admin.api.vo.DeptExcelVO;
import com.by4cloud.platformx.business.dto.ProductAddDTO;
import com.by4cloud.platformx.business.dto.ProductQueryDTO;
import com.by4cloud.platformx.business.dto.SyncProductDTO;
import com.by4cloud.platformx.business.vo.ProductExcelVO;
import com.by4cloud.platformx.business.vo.ProductTreeVo;
import com.by4cloud.platformx.common.core.util.R;
@@ -214,4 +215,14 @@
    public R importProduct(@RequestExcel List<ProductExcelVO> excelVOList, BindingResult bindingResult) {
        return productService.importProduct(excelVOList, bindingResult);
    }
    /**
     * 石煤机产品同步
     * @return
     */
    @Inner(value = false)
    @GetMapping("/syncSMJByDate")
    public R syncSMJByDate(@ParameterObject SyncProductDTO dto) {
        return productService.syncSMJByDate(dto);
    }
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/mapper/DropLossMapper.java
New file
@@ -0,0 +1,17 @@
package com.by4cloud.platformx.business.mapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.by4cloud.platformx.business.dto.DropLossQueryDTO;
import com.by4cloud.platformx.business.entity.DropLoss;
import com.by4cloud.platformx.business.vo.DropLossPageVo;
import com.by4cloud.platformx.common.data.datascope.DataScope;
import com.by4cloud.platformx.common.data.datascope.PlatformxBaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface DropLossMapper extends PlatformxBaseMapper<DropLoss> {
     Page<DropLossPageVo> pageScope(Page page, @Param("queryDTO") DropLossQueryDTO queryDTO, DataScope comp_id);
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/mapper/ProductMapper.java
@@ -17,4 +17,6 @@
    List<ProductSelectVo> getProductListByScope(@Param("product") Product product, DataScope comp_id);
    List<ProductSelectVo> getProductListByParentId(@Param("queryDTO") ProductQueryDTO queryDTO,@Param("erpCodes") List<String> erpCodes, DataScope comp_id);
    List<ProductSelectVo> getProductSMJListByParentId(@Param("queryDTO")ProductQueryDTO queryDTO,@Param("erpCodes") List<String> erpCodes);
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/ContractInvoiceService.java
@@ -4,6 +4,8 @@
import com.by4cloud.platformx.business.dto.ContractInvoiceQueryDTO;
import com.by4cloud.platformx.business.dto.ContractQueryDTO;
import com.by4cloud.platformx.business.dto.GenInvoiceInfoDTO;
import com.by4cloud.platformx.business.dto.InvoiceQueryDTO;
import com.by4cloud.platformx.business.entity.ContractInvoice;
import com.by4cloud.platformx.common.core.util.R;
public interface ContractInvoiceService {
@@ -34,4 +36,10 @@
    R getContractHouseWater(Long contractId);
    R refresh(Long invoiceId);
    R bipPush(Long invoiceId);
    R queryInvoice(Page page, InvoiceQueryDTO queryDTO);
    R add(ContractInvoice contractInvoice);
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/DropLossService.java
New file
@@ -0,0 +1,15 @@
package com.by4cloud.platformx.business.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.by4cloud.platformx.business.dto.DropLossAddDTO;
import com.by4cloud.platformx.business.dto.DropLossQueryDTO;
import com.by4cloud.platformx.business.entity.DropLoss;
import com.by4cloud.platformx.common.core.util.R;
import com.by4cloud.platformx.common.data.mybatis.IIService;
public interface DropLossService extends IIService<DropLoss> {
    Page pageScope(Page page, DropLossQueryDTO queryDTO);
    R add(DropLossAddDTO addDTO);
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/ProductService.java
@@ -3,6 +3,7 @@
import cn.hutool.core.lang.tree.Tree;
import com.by4cloud.platformx.business.dto.ProductAddDTO;
import com.by4cloud.platformx.business.dto.ProductQueryDTO;
import com.by4cloud.platformx.business.dto.SyncProductDTO;
import com.by4cloud.platformx.business.entity.Product;
import com.by4cloud.platformx.business.vo.ProductExcelVO;
import com.by4cloud.platformx.business.vo.ProductSelectVo;
@@ -24,4 +25,6 @@
    R saveErpProduct(List<ProductAddDTO> addDTOS);
    R importProduct(List<ProductExcelVO> excelVOList, BindingResult bindingResult);
    R syncSMJByDate(SyncProductDTO dto);
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/ContractInvoiceServiceImpl.java
@@ -5,10 +5,12 @@
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.StreamProgress;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
@@ -22,6 +24,7 @@
import com.by4cloud.platformx.business.dto.ContractInvoiceQueryDTO;
import com.by4cloud.platformx.business.dto.ContractQueryDTO;
import com.by4cloud.platformx.business.dto.GenInvoiceInfoDTO;
import com.by4cloud.platformx.business.dto.InvoiceQueryDTO;
import com.by4cloud.platformx.business.entity.*;
import com.by4cloud.platformx.business.mapper.*;
import com.by4cloud.platformx.business.service.ContractInvoiceService;
@@ -38,6 +41,9 @@
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
@@ -79,6 +85,9 @@
    @Value("${bip.appSecret}")
    private String appSecret;
    @Value("${file.path}")
    private String localPath;
    @Override
@@ -869,11 +878,101 @@
                        contractInvoice.setInvoicePath(model.getString("queryData"));
                        contractInvoice.setInvoiceStatus("2");
                        baseMapper.updateById(contractInvoice);
                        //传输附件
                        pushBipInvoicePdf(contractInvoice);
                    }
                });
            }
        }
        return R.ok();
    }
    private void pushBipInvoicePdf(ContractInvoice contractInvoice) {
        String accessToken = "";
        if (redisTemplate.hasKey("BIP_TOKEN")) {
            accessToken = (String) redisTemplate.opsForValue().get("BIP_TOKEN");
        } else {
            accessToken = paymentConfirmService.getAccessToken(accessToken);
        }
        if (StrUtil.isEmpty(accessToken)) {
            log.error("bip accessToken 获取异常");
            return;
        }
        String finalAccessToken = accessToken;
        String requestId = IdUtil.fastUUID();
        JSONObject request = new JSONObject();
        request.put("taxNo", contractInvoice.getTaxNo());
        JSONObject data = new JSONObject();
        data.put("serialNo", contractInvoice.getSerialNo());
        data.put("returnType", "2");
        data.put("invoiceIssueMode", "1");
        request.put("data", data);
        log.info("serial_no:{},发票查询入参:{},", contractInvoice.getSerialNo(), request.toJSONString());
        String result = HttpUtil.post(bwUrl + "method=baiwang.output.format.query&version=1.0&request_id=" + requestId, request.toJSONString());
        log.info("serial_no:{},发票查询回参:{},", contractInvoice.getSerialNo(), result);
        JSONObject resultObj = JSONObject.parseObject(result);
        String filePath = localPath + File.separator + DateUtil.today() + File.separator + IdUtil.fastUUID() + ".pdf";
        if (resultObj.containsKey("requestId") && StrUtil.equals(resultObj.getString("requestId"), requestId) && resultObj.getBoolean("success")) {
            JSONObject model = resultObj.getJSONObject("model");
            saveBase64ToFile(model.getString("queryData"), filePath);
        }
        File file = new File(filePath);
        if (file.exists()) {
            log.info("应收发票附件上传入参:{}", url + "/yonbip/uspace/iuap-apcom-file/rest/v1/file?access_token="
                    + finalAccessToken + "&businessType=yonbip-fi-earapbill&businessId=" + contractInvoice.getBipId());
            HttpResponse response = HttpUtil.createPost(url + "/yonbip/uspace/iuap-apcom-file/rest/v1/file?access_token="
                            + finalAccessToken + "&businessType=yonbip-fi-earapbill&businessId=" + contractInvoice.getBipId())
                    .header("Content-Type", "multipart/form-data")
                    // 设置表单参数(文件),第一个参数为接口接收的参数名(如"file")
                    .form("files", file)
                    // 执行请求
                    .execute();
            log.info("应收发票附件上传回参:{}", response.body());
            //保存请求记录
            paymentConfirmService.saveBipRequestRecord(request, "应收发票集成接口", response.body());
            JSONObject resultJson = JSONObject.parseObject(response.body());
            if (resultJson.containsKey("code") && resultJson.getString("code").equals("200")) {
                JSONObject datas = resultJson.getJSONObject("data");
                JSONArray arr = datas.getJSONArray("data");
                if (CollUtil.isNotEmpty(arr)) {
                    JSONObject data0 = (JSONObject) arr.get(0);
                    contractInvoice.setBipFileId(data0.getString("id"));
                    baseMapper.updateById(contractInvoice);
                }
            }
        }
    }
    /**
     * 将 Base64 字符串转换为本地文件
     *
     * @param base64Str  Base64 编码的字符串
     * @param targetPath 目标文件路径(包含文件名和后缀,如 "D:/test.pdf")
     */
    public static void saveBase64ToFile(String base64Str, String targetPath) {
        if (base64Str == null || base64Str.isEmpty()) {
            throw new IllegalArgumentException("Base64 字符串不能为空");
        }
        // 1. 处理可能存在的 Data URI 前缀 (如: "data:application/pdf;base64,JVBERi...")
        if (base64Str.contains(",")) {
            base64Str = base64Str.split(",")[1];
        }
        // 2. 解码 Base64 为字节数组
        byte[] fileBytes = Base64.getDecoder().decode(base64Str);
        // 3. 将字节数组写入文件
        try (FileOutputStream fos = new FileOutputStream(new File(targetPath))) {
            fos.write(fileBytes);
            fos.flush();
            System.out.println("文件保存成功: " + targetPath);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("文件保存失败", e);
        }
    }
    private void pushBipInvoice(ContractInvoice contractInvoice) {
@@ -902,14 +1001,21 @@
            log.error("查询单位信息异常");
            return;
        }
        SysDept dept = r.getData();
        SysDept comp = r.getData();
        R<SysDept> rDept = remoteDeptService.getById(SecurityUtils.getUser().getDeptId());
        if (!r.isOk()) {
            log.error("查询单位信息异常");
            return;
        }
        SysDept dept = rDept.getData();
        //蓝票
        if (StrUtil.equals(contractInvoice.getInvoiceCategory(), "1")) {
            JSONObject params = genBlueReceiveParams(contractInvoice, contract, customer, dept);
            JSONObject params = genBlueReceiveParams(contractInvoice, contract, customer, comp, dept);
            String finalAccessToken = accessToken;
            log.info("应收发票集成入参:{}", params.toJSONString());
            String result = HttpUtil.post(url + "/yonbip/EFI/receivable/save?access_token=" + finalAccessToken, params.toJSONString());
            String result = HttpUtil.post(url + "/yonbip/EFI/receivable/saveandsubmit?access_token=" + finalAccessToken, params.toJSONString());
            log.info("应收发票集成回参:{}", result);
            //保存请求记录
            paymentConfirmService.saveBipRequestRecord(params, "应收发票集成接口", result);
@@ -935,6 +1041,7 @@
            JSONObject resultJson = JSONObject.parseObject(result);
            if (resultJson.containsKey("code") && resultJson.getString("code").equals("200")) {
                contractInvoice.setBipPush("1");
                contractInvoice.setBipBody(resultJson.getString("data"));
                baseMapper.updateById(contractInvoice);
            }
        }
@@ -949,7 +1056,8 @@
        return jsonObject;
    }
    private JSONObject genBlueReceiveParams(ContractInvoice contractInvoice, Contract contract, BusinessCustomer customer, SysDept dept) {
    private JSONObject genBlueReceiveParams(ContractInvoice contractInvoice, Contract contract, BusinessCustomer customer
            , SysDept comp, SysDept dept) {
        JSONObject jsonObject = new JSONObject();
        JSONObject data = new JSONObject();
        if (contractInvoice.getInvoiceCategory().equals("1")) {
@@ -960,11 +1068,13 @@
        data.put("billDate", DateUtil.format(contractInvoice.getInvoiceTime(), DatePattern.NORM_DATETIME_FORMAT));
        data.put("exchangeRate", "1");//汇率
        data.put("exchangeRateDate", DateUtil.now());//汇率时间
        data.put("objectType", "1");
        data.put("objectType", "1");//往来对象类型(枚举值 1:客户; 2:员工;3:资金业务对象)
        data.put("deptCode", dept.getOrgCode());//部门编码 SS2
        data.put("staffCode", SecurityUtils.getUser().getNickname());//业务员编码
        data.put("customerCode", customer.getBipCode());//客户编码
        data.put("financeOrgCode", dept.getBipCode());//开票组织编码
        data.put("orgCode", dept.getBipCode());//业务组织编码
        data.put("bustypeCode", "");//交易类型编码 暂估默认ZGYSFP,销售默认MJHYXGLXT
        data.put("financeOrgCode", comp.getBipCode());//开票组织编码
        data.put("orgCode", comp.getBipCode());//业务组织编码
        data.put("bustypeCode", "ZBYXGLXT");//交易类型编码 暂估默认ZGYSFP,销售默认ZBYXGLXT
        data.put("contractNo", contract.getContractNo());
        data.put("oriCurrencyCode", "CNY");//币种
        data.put("_status", "Insert");
@@ -1112,8 +1222,10 @@
                //合同状态
                if (StrUtil.equals(contract.getBillingStatus(), "2")) {
                    contract.setBillingAmout(vo.getTotalPrice().add(contract.getBillingAmout()));
                    if (contract.getBillingAmout().compareTo(contract.getAmount()) == 0) {
                    if (contract.getBillingAmout().compareTo(contract.getAmount()) == 0 &&
                            contract.getPaidAmount().compareTo(contract.getAmount().subtract(contract.getDropAmount())) == 0) {
                        contract.setBillingStatus("3");
                        contract.setContractStatus(3);
                    }
                    if (contract.getBillingAmout().compareTo(contract.getAmount()) < 0) {
                        contract.setBillingStatus("1");
@@ -1144,7 +1256,16 @@
                    } else {
                        contract.setBillingStatus("2");
                    }
                    contract.setBillingAmout(vo.getTotalPrice().add(contract.getBillingAmout()));
                    if (contract.getBillingAmout().compareTo(contract.getAmount()) == 0 &&
                            contract.getPaidAmount().compareTo(contract.getAmount().subtract(contract.getDropAmount())) == 0) {
                        contract.setBillingStatus("3");
                        contract.setContractStatus(3);
                    }
                    //防止重复审开票审核通过后 开票金额超出合同金额
                    if (contract.getBillingAmout().compareTo(contract.getAmount()) > 0) {
                        contract.setBillingAmout(contract.getAmount());
                    }
                } else {
                    //判断是否有可开票记录
                    List<ContractPaymentSchedule> scheduleList = contractPaymentScheduleMapper.selectList(Wrappers.<ContractPaymentSchedule>lambdaQuery()
@@ -1155,11 +1276,7 @@
                        contract.setBillingStatus("2");
                    }
                }
                contract.setBillingAmout(vo.getTotalPrice().add(contract.getBillingAmout()));
                //防止重复审开票审核通过后 开票金额超出合同金额
                if (contract.getBillingAmout().compareTo(contract.getAmount()) > 0) {
                    contract.setBillingAmout(contract.getAmount());
                }
                contractMapper.updateById(contract);
            }
            baseMapper.updateById(invoice);
@@ -1430,4 +1547,171 @@
        baseMapper.updateById(invoice);
        return R.ok();
    }
    @Override
    public R bipPush(Long invoiceId) {
        ContractInvoice contractInvoice = baseMapper.selectById(invoiceId);
        String accessToken = "";
        if (redisTemplate.hasKey("BIP_TOKEN")) {
            accessToken = (String) redisTemplate.opsForValue().get("BIP_TOKEN");
        } else {
            accessToken = paymentConfirmService.getAccessToken(accessToken);
        }
        if (StrUtil.isEmpty(accessToken)) {
            log.error("bip accessToken 获取异常");
            return R.failed("bip accessToken 获取异常");
        }
        Contract contract = contractMapper.selectById(contractInvoice.getContractId());
        if (ObjUtil.isNull(contract)) {
            log.error("查询合同信息异常");
            return R.failed("查询合同信息异常");
        }
        BusinessCustomer customer = businessCustomerMapper.selectById(contract.getPartyAId());
        if (ObjUtil.isNull(customer) || StrUtil.isEmpty(customer.getBipCode())) {
            log.error("查询合同信息异常");
            return R.failed("查询合同信息异常");
        }
        R<SysDept> r = remoteDeptService.getById(contract.getPartyBId());
        if (!r.isOk()) {
            log.error("查询单位信息异常");
            return R.failed("查询单位信息异常");
        }
        SysDept comp = r.getData();
        R<SysDept> rDept = remoteDeptService.getById(SecurityUtils.getUser().getDeptId());
        if (!r.isOk()) {
            log.error("查询部门信息异常");
            return R.failed("查询部门信息异常");
        }
        SysDept dept = rDept.getData();
        //蓝票
        if (StrUtil.equals(contractInvoice.getInvoiceCategory(), "1")) {
            JSONObject params = genBlueReceiveParams(contractInvoice, contract, customer, comp, dept);
            String finalAccessToken = accessToken;
            log.info("应收发票集成入参:{}", params.toJSONString());
            String result = HttpUtil.post(url + "/yonbip/EFI/receivable/saveandsubmit?access_token=" + finalAccessToken, params.toJSONString());
            log.info("应收发票集成回参:{}", result);
            //保存请求记录
            paymentConfirmService.saveBipRequestRecord(params, "应收发票集成接口", result);
            JSONObject resultJson = JSONObject.parseObject(result);
            if (resultJson.containsKey("code") && resultJson.getString("code").equals("200")) {
                JSONObject data = resultJson.getJSONObject("data");
                contractInvoice.setBipPush("1");
                contractInvoice.setBipId(data.getLong("id"));
                contractInvoice.setBipBody(resultJson.getString("data"));
                baseMapper.updateById(contractInvoice);
                return R.ok();
            }
            return R.failed(resultJson, "应收发票集成失败");
        }
        //红票
        if (StrUtil.equals(contractInvoice.getInvoiceCategory(), "2")) {
            JSONObject params = genRedReceiveParams(contractInvoice);
            String finalAccessToken = accessToken;
            log.info("应收发票删除入参:{}", params.toJSONString());
            String result = HttpUtil.post(url + "/yonbip/EFI/receivable/delete?access_token=" + finalAccessToken, params.toJSONString());
            log.info("应收发票删除回参:{}", result);
            //保存请求记录
            paymentConfirmService.saveBipRequestRecord(params, "应收发票删除接口", result);
            JSONObject resultJson = JSONObject.parseObject(result);
            if (resultJson.containsKey("code") && resultJson.getString("code").equals("200")) {
                contractInvoice.setBipPush("1");
                contractInvoice.setBipBody(resultJson.getString("data"));
                baseMapper.updateById(contractInvoice);
                return R.ok();
            }
            return R.failed(resultJson, "应收发票删除失败");
        }
        return R.ok();
    }
    @Override
    public R queryInvoice(Page page, InvoiceQueryDTO queryDTO) {
        R<SysDept> r = remoteDeptService.getById(SecurityUtils.getUser().getCompId());
        if (!r.isOk()) {
            log.error("查询单位信息异常");
            return R.failed("查询单位信息异常");
        }
        SysDept comp = r.getData();
        JSONObject request = genQueryInvoiceParam(page, queryDTO, comp);
        String requestId = IdUtil.fastUUID();
        log.info("已开发票查询接口入参:{},", request.toJSONString());
        String result = HttpUtil.post(bwUrl + "method=baiwang.output.invoice.query&version=1.0&request_id=" + requestId, request.toJSONString());
        log.info("已开发票查询接口回参:{},", result);
        saveBwRequestRecord("baiwang.output.invoice.query", requestId, request.toJSONString(), result);
        JSONObject resultObj = JSONObject.parseObject(result);
        if (resultObj.containsKey("requestId") && StrUtil.equals(resultObj.getString("requestId"), requestId) &&
                !resultObj.getBoolean("success")) {
            return R.failed("已开发票查询", result);
        }
        JSONArray models = resultObj.getJSONArray("model");
        if (CollUtil.isNotEmpty(models)) {
            page.setRecords(models);
            page.setTotal(resultObj.getInteger("total"));
            return R.ok(page);
        }
        page.setTotal(0);
        page.setRecords(null);
        return R.ok(page);
    }
    private JSONObject genQueryInvoiceParam(Page page, InvoiceQueryDTO queryDTO, SysDept comp) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("taxNo", comp.getOrgCode());
        JSONObject data = new JSONObject();
        data.put("serialNo", StrUtil.isNotBlank(queryDTO.getSerialNo()) ? queryDTO.getSerialNo() : "");
        data.put("invoiceCode", StrUtil.isNotBlank(queryDTO.getInvoiceCode()) ? queryDTO.getInvoiceCode() : "");
        data.put("invoiceNo", StrUtil.isNotBlank(queryDTO.getInvoiceNo()) ? queryDTO.getInvoiceNo() : "");
        data.put("invoiceTypeCode", StrUtil.isNotBlank(queryDTO.getInvoiceTypeCode()) ? queryDTO.getInvoiceTypeCode() : "");
        data.put("invoiceTerminalCode", StrUtil.isNotBlank(queryDTO.getInvoiceTerminalCode()) ? queryDTO.getInvoiceTerminalCode() : "");
        data.put("sourceMark", StrUtil.isNotBlank(queryDTO.getSourceMark()) ? queryDTO.getSourceMark() : "");
        data.put("invoiceSpecialMark", StrUtil.isNotBlank(queryDTO.getInvoiceSpecialMark()) ? queryDTO.getInvoiceSpecialMark() : "");
        data.put("buyerTaxNo", StrUtil.isNotBlank(queryDTO.getBuyerTaxNo()) ? queryDTO.getBuyerTaxNo() : "");
        data.put("buyerName", StrUtil.isNotBlank(queryDTO.getBuyerName()) ? queryDTO.getBuyerName() : "");
        data.put("invoiceStatus", StrUtil.isNotBlank(queryDTO.getInvoiceStatus()) ? queryDTO.getInvoiceStatus() : "");
        data.put("invoiceType", StrUtil.isNotBlank(queryDTO.getInvoiceType()) ? queryDTO.getInvoiceType() : "");
        data.put("invoiceStartDate", StrUtil.isNotBlank(queryDTO.getInvoiceStartDate()) ? queryDTO.getInvoiceStartDate() : "");
        data.put("invoiceEndDate", StrUtil.isNotBlank(queryDTO.getInvoiceEndDate()) ? queryDTO.getInvoiceEndDate() : "");
        data.put("machineNo", StrUtil.isNotBlank(queryDTO.getMachineNo()) ? queryDTO.getMachineNo() : "");
        data.put("systemName", StrUtil.isNotBlank(queryDTO.getSystemName()) ? queryDTO.getSystemName() : "");
        data.put("systemId", StrUtil.isNotBlank(queryDTO.getSystemId()) ? queryDTO.getSystemId() : "");
        data.put("pageSize", page.getSize());
        data.put("pageNo", page.getCurrent());
        jsonObject.put("data", data);
        return jsonObject;
    }
    @Override
    public R add(ContractInvoice contractInvoice) {
        Contract contract = contractMapper.selectById(contractInvoice.getContractId());
        if (ObjUtil.isNull(contract)) {
            return R.failed("合同信息异常");
        }
        List<ContractInvoice> invoiceList = baseMapper.selectList(Wrappers.<ContractInvoice>lambdaQuery()
                .eq(ContractInvoice::getInvoiceNo, contractInvoice.getInvoiceNo()));
        if (CollUtil.isNotEmpty(invoiceList)) {
            return R.failed("发票已存在,请勿重复操作");
        }
        contractInvoice.setInvoiceStatus("2");
        contractInvoice.setInvoiceSource("1");
        contractInvoice.setRedReversal("0");
        contractInvoice.setApprovalStatus("1");
        baseMapper.insert(contractInvoice);
        contract.setBillingAmout(contract.getBillingAmout().add(contractInvoice.getInvoiceTotalPriceTax()));
        if (contract.getBillingAmout().compareTo(contract.getAmount()) >= 0) {
            contract.setBillingStatus("3");
        }
        contractMapper.updateById(contract);
        return R.ok();
    }
    public static void main(String[] args) {
        File file = new File("d://tmp//" + IdUtil.fastUUID() + ".pdf");
        HttpUtil.downloadFile("http://fp.baiwang.com/format/d?d=BB99645F8ED2AE7D2055E7D29330D366B7CAE92C278C6BB84D222610CEC2118D", file);
    }
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/DropLossServiceImpl.java
New file
@@ -0,0 +1,54 @@
package com.by4cloud.platformx.business.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.by4cloud.platformx.business.dto.DropLossAddDTO;
import com.by4cloud.platformx.business.dto.DropLossQueryDTO;
import com.by4cloud.platformx.business.entity.Contract;
import com.by4cloud.platformx.business.entity.DropLoss;
import com.by4cloud.platformx.business.mapper.ContractMapper;
import com.by4cloud.platformx.business.mapper.DropLossMapper;
import com.by4cloud.platformx.business.service.DropLossService;
import com.by4cloud.platformx.common.core.util.R;
import com.by4cloud.platformx.common.data.datascope.DataScope;
import com.by4cloud.platformx.common.security.util.SecurityUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
 * 降点损失
 *
 * @author syt
 * @date 2026-08-25 09:11:48
 */
@Service
@RequiredArgsConstructor
public class DropLossServiceImpl extends ServiceImpl<DropLossMapper, DropLoss> implements DropLossService {
    private final ContractMapper contractMapper;
    @Override
    public Page pageScope(Page page, DropLossQueryDTO queryDTO) {
        return baseMapper.pageScope(page,queryDTO, DataScope.of("comp_id"));
    }
    @Override
    public R add(DropLossAddDTO addDTO) {
        Contract contract = contractMapper.selectById(addDTO.getContractId());
        if (ObjUtil.isNull(contract)){
            return R.failed("合同查询异常");
        }
        if (addDTO.getDropAmount().compareTo(contract.getAmount())==0){
            return R.failed("损失金额不能超过合同金额");
        }
        DropLoss loss = BeanUtil.copyProperties(addDTO,DropLoss.class);
        loss.setDropUser(SecurityUtils.getUser().getName());
        loss.setDropUserId(SecurityUtils.getUser().getId());
        baseMapper.insert(loss);
        contract.setDropAmount(addDTO.getDropAmount());
        contractMapper.updateById(contract);
        return R.ok();
    }
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/PaymentConfirmServiceImpl.java
@@ -209,18 +209,15 @@
                }
                paid = paid.add(addDTO.getTransationAmount());
                contract.setPaidAmount(paid);
                if (paid.compareTo(contract.getAmount()) == 0&&contract.getBillingAmout().compareTo(contract.getAmount())==0) {
                if (paid.compareTo(contract.getAmount().subtract(contract.getDropAmount())) == 0&&contract.getBillingAmout().compareTo(contract.getAmount())==0) {
                    contract.setContractStatus(3);
                    contract.setPaidAmount(contract.getAmount());
                    contract.setPaidAmount(contract.getAmount().subtract(contract.getDropAmount()));
                }
                contractService.updateById(contract);
            }else {
                List<ContractExecDate> execDateList = contractExecDateMapper.selectList(Wrappers.<ContractExecDate>lambdaQuery().eq(ContractExecDate::getContractId,contract.getId())
                        .eq(ContractExecDate::getGenFlag,"0"));
                List<MeterReadRecord> recordList = meterReadRecordMapper.selectList(Wrappers.<MeterReadRecord>lambdaQuery()
                        .eq(MeterReadRecord::getContractId,contract.getId())
                        .isNull(MeterReadRecord::getMeterReadNum));
                if (CollUtil.isEmpty(execDateList)&&CollUtil.isEmpty(recordList)){
                if (CollUtil.isEmpty(execDateList)){
                    //更新合同已付款
                    BigDecimal paid = new BigDecimal("0");
                    if (ObjUtil.isNotNull(contract.getPaidAmount())) {
@@ -228,7 +225,7 @@
                    }
                    paid = paid.add(addDTO.getTransationAmount());
                    contract.setPaidAmount(paid);
                    if (contract.getAmount().compareTo(new BigDecimal("0"))==0&&paid.compareTo(contract.getAmount()) >= 0) {
                    if (contract.getBillingAmout().compareTo(contract.getAmount())==0&&paid.compareTo(contract.getAmount().subtract(contract.getDropAmount())) >= 0) {
                        contract.setContractStatus(3);
                        contract.setPaidAmount(contract.getAmount());
                    }
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/PaymentOffsetServiceImpl.java
@@ -137,7 +137,7 @@
        }
        paid = paid.add(paymentOffset.getOffsetAmount());
        contract.setPaidAmount(paid);
        if (paid.compareTo(contract.getAmount())==0&&contract.getBillingAmout().compareTo(contract.getAmount())==0){
        if (paid.compareTo(contract.getAmount().subtract(contract.getDropAmount()))==0&&contract.getBillingAmout().compareTo(contract.getAmount())==0){
//            contract.setBillingStatus("2");
            contract.setContractStatus(3);
        }
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/ProductServiceImpl.java
@@ -1,15 +1,23 @@
package com.by4cloud.platformx.business.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNode;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.by4cloud.platformx.business.dto.ProductAddDTO;
import com.by4cloud.platformx.business.dto.ProductQueryDTO;
import com.by4cloud.platformx.business.dto.SyncProductDTO;
import com.by4cloud.platformx.business.entity.BusinessCustomer;
import com.by4cloud.platformx.business.entity.Product;
import com.by4cloud.platformx.business.entity.TaxCode;
import com.by4cloud.platformx.business.mapper.ProductMapper;
@@ -20,8 +28,11 @@
import com.by4cloud.platformx.common.core.util.R;
import com.by4cloud.platformx.common.data.datascope.DataScope;
import com.by4cloud.platformx.common.excel.vo.ErrorMessage;
import com.by4cloud.platformx.common.security.util.SecurityUtils;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.validation.BindingResult;
@@ -38,11 +49,15 @@
 * @author platformx
 * @date 2026-05-07 10:17:52
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
    private final TaxCodeMapper taxCodeMapper;
    @Value("${erp.smj}")
    private String smjErp;
    @Override
    public List<Tree<Long>> treeList() {
@@ -111,6 +126,15 @@
//                .eq(StrUtil.isNotEmpty(queryDTO.getParentId()),Product::getParentId,queryDTO.getParentId())
//                .in(StrUtil.isNotEmpty(queryDTO.getContractCategory())&&StrUtil.equals(queryDTO.getContractCategory(), "water_house"),
//                        Product::getErpCode,erpCodes);
        if (StrUtil.equals(SecurityUtils.getUser().getCompId()+"","2089995177814495234")||
                StrUtil.equals(SecurityUtils.getUser().getCompId()+"","2089996921978040321")||
                StrUtil.equals(SecurityUtils.getUser().getCompId()+"","2089997578009128962")){
            List<ProductSelectVo> selectVos = baseMapper.getProductSMJListByParentId(queryDTO, Arrays.asList(erpCodes));
            if (selectVos.size()>2000){
                return R.ok(selectVos.subList(0,1999));
            }
            return R.ok(selectVos);
        }
        List<ProductSelectVo> selectVos = baseMapper.getProductListByParentId(queryDTO, Arrays.asList(erpCodes),DataScope.of("comp_id"));
        if (selectVos.size()>2000){
            return R.ok(selectVos.subList(0,1999));
@@ -232,4 +256,76 @@
                        Math.min((i + 1) * batchSize, totalSize)))
                .collect(Collectors.toList());
    }
    @Override
    public R syncSMJByDate(SyncProductDTO dto) {
        String startDate = "";
        if (StrUtil.isBlank(dto.getStartDate())){
            startDate = DateUtil.formatDate(DateUtil.offsetDay(new Date(), -1));
        }else {
            startDate = dto.getStartDate();
        }
        JSONObject loginJson = new JSONObject();
        loginJson.put("username", "admin");
        loginJson.put("password", "Zimafold@2026");
        log.info("石煤机产品获取token入参:{}", loginJson.toJSONString());
        String loginResp = HttpUtil.post(smjErp + "/api/account/v1/login", loginJson.toJSONString());
        log.info("石煤机产品获取token回参:{}", loginResp);
        JSONObject respJson = JSONObject.parseObject(loginResp);
        if (respJson.getInteger("code") == 0) {
            JSONObject dataJson = JSONObject.parseObject(respJson.getString("data"));
            HttpResponse response = HttpRequest.get(smjErp + "/api/item/v1/byDateCre?startDate=" + startDate)
                    .header("token", dataJson.getString("token"))       // 添加认证头
                    .execute();                                         // 执行请求.post(ymjErp,request.toJSONString());
            log.info("石煤机产品获取回参:{}", response.body());
            JSONObject proResult = JSONObject.parseObject(response.body());
            JSONArray items = proResult.getJSONArray("data");
            if (CollUtil.isNotEmpty(items)){
                log.info("石煤机产品获取数量:{}", items.size());
                items.stream().forEach(item->{
                    JSONObject product = (JSONObject) item;
                    Product productClass = baseMapper.selectOne(Wrappers.<Product>lambdaQuery()
                            .eq(Product::getProductName,product.getString("itemClassName"))
                            .and(wrapper -> wrapper.eq(Product::getCompId,"2089995177814495234")
                                    .or().eq(Product::getCompId,"2089996921978040321")
                                    .or().eq(Product::getCompId,"2089997578009128962")
                                    )
                            .last("limit 1")
                    );
                    if(ObjUtil.isNull(productClass)){
                        log.info("产品分类:{}不存在",product.getString("itemClassName"));
                        productClass = new Product();
                        productClass.setProductName(product.getString("itemClassName"));
                        productClass.setErpCode(product.getString("itemClassName"));
                        productClass.setParentId(0L);
                        productClass.setCompId(2089995177814495234L);
                        baseMapper.insert(productClass);
                        log.info("产品分类:{}新增成功",product.getString("itemClassName"));
                    }
                    Product productNew = baseMapper.selectOne(Wrappers.<Product>lambdaQuery()
                            .eq(Product::getErpCode,product.getString("itemNo"))
                            .and(wrapper -> wrapper.eq(Product::getCompId,"2089995177814495234")
                                    .or().eq(Product::getCompId,"2089996921978040321")
                                    .or().eq(Product::getCompId,"2089997578009128962")
                            )
                            .last("limit 1")
                    );
                    if(ObjUtil.isNull(productNew)){
                        log.info("产品:{}不存在",product.getString("itemNo"));
                        productNew = new Product();
                        productNew.setProductName(product.getString("note"));
                        productNew.setErpCode(product.getString("itemNo"));
                        productNew.setProductType(product.getString("grade"));
                        productNew.setCompId(2089995177814495234L);
                        productNew.setParentId(productClass.getId());
                        baseMapper.insert(productNew);
                        log.info("产品:{}新增成功",product.getString("itemNo"));
                        return;
                    }
                    log.info("产品:{}存在",product.getString("itemNo"));
                });
            }
        }
        return R.ok();
    }
}
platformx-business-finance-biz/src/main/resources/mapper/DropLossMapper.xml
New file
@@ -0,0 +1,51 @@
<?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.by4cloud.platformx.business.mapper.DropLossMapper">
  <resultMap id="dropLossMap" type="com.by4cloud.platformx.business.entity.DropLoss">
        <id property="id" column="id"/>
        <result property="compId" column="comp_id"/>
        <result property="attachmentName" column="attachment_name"/>
        <result property="attachmentUrl" column="attachment_url"/>
        <result property="contractId" column="contract_id"/>
        <result property="dropAmount" column="drop_amount"/>
        <result property="dropDate" column="drop_date"/>
        <result property="dropRemark" column="drop_remark"/>
        <result property="dropUser" column="drop_user"/>
        <result property="dropUserId" column="drop_user_id"/>
        <result property="createBy" column="create_by"/>
        <result property="createTime" column="create_time"/>
        <result property="updateBy" column="update_by"/>
        <result property="updateTime" column="update_time"/>
        <result property="delFlag" column="del_flag"/>
  </resultMap>
    <select id="pageScope" resultType="com.by4cloud.platformx.business.vo.DropLossPageVo">
        select
            d.id,
            d.comp_id,
            d.attachment_name,
            d.attachment_url,
            d.contract_id,
            d.drop_amount,
            d.drop_date,
            d.drop_remark,
            d.drop_user,
            d.drop_user_id,
            c.contract_no,
            c.party_a,
            c.party_b,
            c.amount
        from
            drop_loss d
        left join contract c
        on d.contract_id = c.id
        where
            d.del_flag='0'
        and c.del_flag='0'
        <if test="queryDTO.contractNo!=null and queryDTO.contractNo != ''">
            and c.contract_no like CONCAT('%', #{queryDTO.contractNo}, '%')
        </if>
        order by d.create_time desc
    </select>
</mapper>
platformx-business-finance-biz/src/main/resources/mapper/ProductMapper.xml
@@ -88,4 +88,49 @@
        </choose>
    </select>
    <select id="getProductSMJListByParentId" resultType="com.by4cloud.platformx.business.vo.ProductSelectVo">
        SELECT
        t.id,
        t.product_name,
        t.main_code,
        t.erp_code,
        t.product_type,
        t.tax_rate,
        t.tax_class,
        t.tax_code,
        t.parent_id,
        t.create_by,
        t.update_by,
        t.create_time,
        t.update_time,
        t.del_flag,
        t.comp_id ,
        ( SELECT count( 1 ) FROM product t1 WHERE t1.parent_id = t.id ) AS childNum
        FROM
        product t
        WHERE
        del_flag = '0'
        <if test="queryDTO.productName !=null and queryDTO.productName !=''">
            and ( t.product_name LIKE CONCAT('%', #{queryDTO.productName}, '%') or
            t.erp_code LIKE CONCAT('%', #{queryDTO.productName}, '%'))
        </if>
        <if test="queryDTO.parentId !=null ">
            and t.parent_id = #{queryDTO.parentId}
        </if>
        <choose>
            <when test="queryDTO.contractCategory !=null and queryDTO.contractCategory !='' and queryDTO.contractCategory=='water_house'">
                and t.erp_code in
                <foreach collection="erpCodes" item="erp" open="(" separator="," close=")">
                    #{erp}
                </foreach>
            </when>
            <otherwise>
                and t.erp_code not in
                <foreach collection="erpCodes" item="erp" open="(" separator="," close=")">
                    #{erp}
                </foreach>
            </otherwise>
        </choose>
        and ( t.comp_id = 2089995177814495234  or t.comp_id = 2089996921978040321 or t.comp_id = 2089997578009128962)
    </select>
</mapper>