shiyunteng
2026-08-25 dd4284136cb12dc3c2e498e0f0ff002d553763af
feat:feat:新增降点损 赊销业务均使用代储
14个文件已修改
11个文件已添加
1136 ■■■■■ 已修改文件
platformx-boot/src/main/resources/application-dev.yml 5 ●●●●● 补丁 | 查看 | 原始文档 | 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 450 ●●●●● 补丁 | 查看 | 原始文档 | 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
@@ -78,4 +78,7 @@
  smj:
  sgb:
  jxc:
  tfgs:
  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.*;
@@ -80,6 +86,9 @@
    @Value("${bip.appSecret}")
    private String appSecret;
    @Value("${file.path}")
    private String localPath;
    @Override
    public R getContractOutBound(Long contractId) {
@@ -91,11 +100,11 @@
            invoiceOutBoundVoList.stream().forEach(contractInvoiceOutBoundVo -> {
                contractInvoiceOutBoundVo.setInvoiceNum(contractInvoiceOutBoundVo.getOutBoundNum().subtract(contractInvoiceOutBoundVo.getInvoiceNum()));
                ContractSubjectMatter subjectMatter = contractSubjectMatterMapper.selectOne(Wrappers.<ContractSubjectMatter>lambdaQuery().eq(ContractSubjectMatter::getContractId,contractId)
                        .eq(ContractSubjectMatter::getMaterialCode,contractInvoiceOutBoundVo.getSubjectMatterCode())
                        .eq(StrUtil.isNotBlank(contractInvoiceOutBoundVo.getLineNo()),ContractSubjectMatter::getLineNo,contractInvoiceOutBoundVo.getLineNo())
                ContractSubjectMatter subjectMatter = contractSubjectMatterMapper.selectOne(Wrappers.<ContractSubjectMatter>lambdaQuery().eq(ContractSubjectMatter::getContractId, contractId)
                        .eq(ContractSubjectMatter::getMaterialCode, contractInvoiceOutBoundVo.getSubjectMatterCode())
                        .eq(StrUtil.isNotBlank(contractInvoiceOutBoundVo.getLineNo()), ContractSubjectMatter::getLineNo, contractInvoiceOutBoundVo.getLineNo())
                        .last("limit 1"));
                if (ObjUtil.isNotNull(subjectMatter)&&subjectMatter.getAdvanceNum().compareTo(new BigDecimal("0"))>0){
                if (ObjUtil.isNotNull(subjectMatter) && subjectMatter.getAdvanceNum().compareTo(new BigDecimal("0")) > 0) {
                    contractInvoiceOutBoundVo.setInvoiceNum(contractInvoiceOutBoundVo.getOutBoundNum().subtract(subjectMatter.getAdvanceNum()));
                }
                contractInvoiceOutBoundVo.setToInvoiceNum(contractInvoiceOutBoundVo.getInvoiceNum());
@@ -141,7 +150,7 @@
        //预收款开票
        if (StrUtil.isNotBlank(genInvoiceInfoDTO.getContractId())) {
            ContractPaymentSchedule schedule = contractPaymentScheduleMapper.selectOne(Wrappers.<ContractPaymentSchedule>lambdaQuery()
                    .eq(ContractPaymentSchedule::getStageName, "合同签订").eq(ContractPaymentSchedule::getContractId,genInvoiceInfoDTO.getContractId())
                    .eq(ContractPaymentSchedule::getStageName, "合同签订").eq(ContractPaymentSchedule::getContractId, genInvoiceInfoDTO.getContractId())
                    .last("limit 1"));
            if (ObjUtil.isNull(schedule)) {
                return R.failed("合同预收款阶段异常,请联系技术人员");
@@ -162,8 +171,8 @@
                    advanceAmount = advanceAmount.add(contractOutBoundInvoiceVo.getInvoiceNum().multiply(subjectMatter.getUnitPrice()));
                }
            }
            log.info("advanceAmount:{}",advanceAmount);
            log.info("schedule.getPlannedAmount:{}",schedule.getPlannedAmount());
            log.info("advanceAmount:{}", advanceAmount);
            log.info("schedule.getPlannedAmount:{}", schedule.getPlannedAmount());
            if (advanceAmount.compareTo(schedule.getPlannedAmount()) > 0) {
                return R.failed("开票金额超出合同签订阶段应收金额:" + schedule.getPlannedAmount() + ",请调整开票数量");
            }
@@ -190,22 +199,22 @@
        }
        SysDept b = r.getData();
        //机动车开票
        if (StrUtil.equals(genInvoiceInfoDTO.getInvoiceType(),"3")||StrUtil.equals(genInvoiceInfoDTO.getInvoiceType(),"4")){
            if (StrUtil.isBlank(genInvoiceInfoDTO.getVehicleNo())){
        if (StrUtil.equals(genInvoiceInfoDTO.getInvoiceType(), "3") || StrUtil.equals(genInvoiceInfoDTO.getInvoiceType(), "4")) {
            if (StrUtil.isBlank(genInvoiceInfoDTO.getVehicleNo())) {
                return R.failed("机动车开票请输入车架号");
            }
            BigDecimal invoiceNumSum = genInvoiceInfoDTO.getOutBoundInvoiceList().stream().map(item->item.getInvoiceNum()).reduce(BigDecimal.ZERO,BigDecimal::add);
            if (invoiceNumSum.compareTo(new BigDecimal("1"))>0){
            BigDecimal invoiceNumSum = genInvoiceInfoDTO.getOutBoundInvoiceList().stream().map(item -> item.getInvoiceNum()).reduce(BigDecimal.ZERO, BigDecimal::add);
            if (invoiceNumSum.compareTo(new BigDecimal("1")) > 0) {
                return R.failed("机动车开票只能1次开1辆");
            }
            JSONObject request = genQueryVinParam(genInvoiceInfoDTO.getVehicleNo(),b);
            JSONObject request = genQueryVinParam(genInvoiceInfoDTO.getVehicleNo(), b);
            R r1 = requestQueryVehicleNo(request);
            if (!r1.isOk()){
            if (!r1.isOk()) {
                return r1;
            }
            JSONObject request1 = genQueryVehDetailParam(genInvoiceInfoDTO.getVehicleNo(),b);
            JSONObject request1 = genQueryVehDetailParam(genInvoiceInfoDTO.getVehicleNo(), b);
            R r2 = requestQueryVehicleDetail(request1);
            if (!r2.isOk()){
            if (!r2.isOk()) {
                return r2;
            }
            genInvoiceInfoDTO.setVehicleData(r2.getData().toString());
@@ -220,16 +229,16 @@
        String requestId = IdUtil.fastUUID();
        log.info("机动车合格证查询接口入参:{},", request.toJSONString());
        String result = HttpUtil.post(bwUrl+"method=baiwang.output.invoice.queryVinDetail&version=1.0&request_id=" + requestId, request.toJSONString());
        String result = HttpUtil.post(bwUrl + "method=baiwang.output.invoice.queryVinDetail&version=1.0&request_id=" + requestId, request.toJSONString());
        log.info("机动车合格证查询接口回参:{},", result);
        saveBwRequestRecord("baiwang.output.invoice.queryVinDetail", 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);
            return R.failed("机动车合格证查询失败", result);
        }
        JSONArray models = resultObj.getJSONArray("model");
        if (CollUtil.isNotEmpty(models)){
        if (CollUtil.isNotEmpty(models)) {
            JSONObject model = (JSONObject) models.get(0);
            return R.ok(model);
        }
@@ -238,10 +247,10 @@
    private JSONObject genQueryVehDetailParam(String vehicleNo, SysDept b) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("taxNo",b.getOrgCode());
        jsonObject.put("taxNo", b.getOrgCode());
        JSONObject data = new JSONObject();
        data.put("vehicleNo", vehicleNo);
        jsonObject.put("data",data);
        jsonObject.put("data", data);
        return jsonObject;
    }
@@ -249,30 +258,30 @@
        String requestId = IdUtil.fastUUID();
        log.info("查询税局车架号码入参:{},", request.toJSONString());
        String result = HttpUtil.post(bwUrl+"method=baiwang.output.einvoice.queryvinlist&version=1.0&request_id=" + requestId, request.toJSONString());
        String result = HttpUtil.post(bwUrl + "method=baiwang.output.einvoice.queryvinlist&version=1.0&request_id=" + requestId, request.toJSONString());
        log.info("查询税局车架号码回参:{},", result);
        saveBwRequestRecord("baiwang.output.einvoice.queryvinlist", 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);
            return R.failed("查询税局车架号码失败", result);
        }
        JSONArray models = resultObj.getJSONArray("model");
        if (CollUtil.isNotEmpty(models)){
        if (CollUtil.isNotEmpty(models)) {
            JSONObject model = (JSONObject) models.get(0);
            if (!model.getBoolean("isOpenInvoice")){
            if (!model.getBoolean("isOpenInvoice")) {
                return R.failed(model.getString("isNotOpenInvReason"));
            }
        }
        return R.ok();
    }
    private JSONObject genQueryVinParam(String vehicleNo,SysDept b) {
    private JSONObject genQueryVinParam(String vehicleNo, SysDept b) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("taxNo",b.getOrgCode());
        jsonObject.put("taxNo", b.getOrgCode());
        JSONObject data = new JSONObject();
        data.put("vinList", new ArrayList<String>(Arrays.asList(vehicleNo)));
        jsonObject.put("data",data);
        jsonObject.put("data", data);
        return jsonObject;
    }
@@ -517,12 +526,12 @@
        if (ArrayUtil.isEmpty(genInvoiceInfoDTO.getOutBoundInvoiceList())) {
            return R.failed("请选择开票出库单后再操作");
        }
        if (StrUtil.equals(genInvoiceInfoDTO.getInvoiceType(),"3")||StrUtil.equals(genInvoiceInfoDTO.getInvoiceType(),"4")){
            if (StrUtil.isBlank(genInvoiceInfoDTO.getVehicleNo())){
        if (StrUtil.equals(genInvoiceInfoDTO.getInvoiceType(), "3") || StrUtil.equals(genInvoiceInfoDTO.getInvoiceType(), "4")) {
            if (StrUtil.isBlank(genInvoiceInfoDTO.getVehicleNo())) {
                return R.failed("机动车开票请输入车架号");
            }
            BigDecimal invoiceNumSum = genInvoiceInfoDTO.getOutBoundInvoiceList().stream().map(item->item.getInvoiceNum()).reduce(BigDecimal.ZERO,BigDecimal::add);
            if (invoiceNumSum.compareTo(new BigDecimal("1"))>0){
            BigDecimal invoiceNumSum = genInvoiceInfoDTO.getOutBoundInvoiceList().stream().map(item -> item.getInvoiceNum()).reduce(BigDecimal.ZERO, BigDecimal::add);
            if (invoiceNumSum.compareTo(new BigDecimal("1")) > 0) {
                return R.failed("机动车开票只能1次开1辆");
            }
        }
@@ -530,7 +539,7 @@
        if (StrUtil.isNotBlank(genInvoiceInfoDTO.getContractId())) {
            ContractPaymentSchedule schedule = contractPaymentScheduleMapper.selectOne(Wrappers.<ContractPaymentSchedule>lambdaQuery()
                    .eq(ContractPaymentSchedule::getStageName, "合同签订").eq(ContractPaymentSchedule::getContractId,genInvoiceInfoDTO.getContractId())
                    .eq(ContractPaymentSchedule::getStageName, "合同签订").eq(ContractPaymentSchedule::getContractId, genInvoiceInfoDTO.getContractId())
                    .last("limit 1"));
            if (ObjUtil.isNull(schedule)) {
                return R.failed("合同预收款阶段异常,请联系技术人员");
@@ -539,9 +548,9 @@
            if (ObjUtil.isNull(contract)) {
                return R.failed("合同信息异常,请联系技术人员");
            }
            ContractInvoice lastInvoice = baseMapper.selectOne(Wrappers.<ContractInvoice>lambdaQuery().eq(ContractInvoice::getContractId,contract.getId())
            ContractInvoice lastInvoice = baseMapper.selectOne(Wrappers.<ContractInvoice>lambdaQuery().eq(ContractInvoice::getContractId, contract.getId())
                    .orderByDesc(ContractInvoice::getCreateTime).last("limit 1"));
            if (ObjUtil.isNotNull(lastInvoice)&&StrUtil.equals(lastInvoice.getApprovalStatus(),"0")){
            if (ObjUtil.isNotNull(lastInvoice) && StrUtil.equals(lastInvoice.getApprovalStatus(), "0")) {
                return R.failed("请等待最近的发票审核完成后再进行预收开票");
            }
            R<SysDept> r = remoteDeptService.getById(contract.getPartyBId());
@@ -562,7 +571,7 @@
            }
            GenInvoiceInfoVo vo = genAdvanceInvoiceInfoVo(genInvoiceInfoDTO, contract, b);
            //发票保存
            ContractInvoice invoice = saveContractInvoice(vo, contract, genInvoiceInfoDTO.getOutBoundInvoiceList(), DateUtil.current() + "","1");
            ContractInvoice invoice = saveContractInvoice(vo, contract, genInvoiceInfoDTO.getOutBoundInvoiceList(), DateUtil.current() + "", "1");
            //启动流程
            ProcessInstanceParamDto dto = new ProcessInstanceParamDto();
            Map<String, Object> map = BeanUtil.beanToMap(invoice);
@@ -597,29 +606,29 @@
        }
        SysDept b = r.getData();
        //机动车开票
        if (StrUtil.equals(genInvoiceInfoDTO.getInvoiceType(),"3")||StrUtil.equals(genInvoiceInfoDTO.getInvoiceType(),"4")){
            if (StrUtil.isBlank(genInvoiceInfoDTO.getVehicleNo())){
        if (StrUtil.equals(genInvoiceInfoDTO.getInvoiceType(), "3") || StrUtil.equals(genInvoiceInfoDTO.getInvoiceType(), "4")) {
            if (StrUtil.isBlank(genInvoiceInfoDTO.getVehicleNo())) {
                return R.failed("机动车开票请输入车架号");
            }
            BigDecimal invoiceNumSum = genInvoiceInfoDTO.getOutBoundInvoiceList().stream().map(item->item.getInvoiceNum()).reduce(BigDecimal.ZERO,BigDecimal::add);
            if (invoiceNumSum.compareTo(new BigDecimal("1"))>0){
            BigDecimal invoiceNumSum = genInvoiceInfoDTO.getOutBoundInvoiceList().stream().map(item -> item.getInvoiceNum()).reduce(BigDecimal.ZERO, BigDecimal::add);
            if (invoiceNumSum.compareTo(new BigDecimal("1")) > 0) {
                return R.failed("机动车开票只能1次开1辆");
            }
            JSONObject request = genQueryVinParam(genInvoiceInfoDTO.getVehicleNo(),b);
            JSONObject request = genQueryVinParam(genInvoiceInfoDTO.getVehicleNo(), b);
            R r1 = requestQueryVehicleNo(request);
            if (!r1.isOk()){
            if (!r1.isOk()) {
                return r1;
            }
            JSONObject request1 = genQueryVehDetailParam(genInvoiceInfoDTO.getVehicleNo(),b);
            JSONObject request1 = genQueryVehDetailParam(genInvoiceInfoDTO.getVehicleNo(), b);
            R r2 = requestQueryVehicleDetail(request1);
            if (!r2.isOk()){
            if (!r2.isOk()) {
                return r2;
            }
            genInvoiceInfoDTO.setVehicleData(r2.getData().toString());
        }
        GenInvoiceInfoVo vo = genInvoiceInfoVo(genInvoiceInfoDTO, contract, b);
        //发票保存
        ContractInvoice invoice = saveContractInvoice(vo, contract, genInvoiceInfoDTO.getOutBoundInvoiceList(), DateUtil.current() + "","0");
        ContractInvoice invoice = saveContractInvoice(vo, contract, genInvoiceInfoDTO.getOutBoundInvoiceList(), DateUtil.current() + "", "0");
        //启动流程
        ProcessInstanceParamDto dto = new ProcessInstanceParamDto();
        Map<String, Object> map = BeanUtil.beanToMap(invoice);
@@ -663,7 +672,7 @@
        bwRequestRecordMapper.insert(bwRequestRecord);
    }
    private JSONObject genBlueInvoice(GenInvoiceInfoVo vo, String serialNo,SysDept dept) {
    private JSONObject genBlueInvoice(GenInvoiceInfoVo vo, String serialNo, SysDept dept) {
        JSONObject jsonObject = new JSONObject();
        //销方
        jsonObject.put("taxNo", vo.getPartyBOrgCode());//税号
@@ -688,9 +697,9 @@
            invoiceDetail.put("goodsUnit", invoiceItemVo.getUnit());
            invoiceDetail.put("goodsQuantity", invoiceItemVo.getQuantity());
//            invoiceDetail.put("goodsPrice", invoiceItemVo.getUnitPrice());
            if (StrUtil.equals(vo.getInvoiceVehicleType(),"3")){
            if (StrUtil.equals(vo.getInvoiceVehicleType(), "3")) {
                invoiceDetail.put("goodsSpecification", vo.getVehicleNo());
            }else {
            } else {
                invoiceDetail.put("goodsSpecification", invoiceItemVo.getSpecification());
            }
@@ -718,7 +727,7 @@
    }
    private ContractInvoice saveContractInvoice(GenInvoiceInfoVo genInvoiceInfoVo, Contract contract,
                List<ContractOutBoundInvoiceVo> outBoundInvoiceVoList, String serialNo,String advanceFlag) {
                                                List<ContractOutBoundInvoiceVo> outBoundInvoiceVoList, String serialNo, String advanceFlag) {
        ContractInvoice invoice = new ContractInvoice();
        invoice.setContractId(contract.getId());
        invoice.setInvoiceCategory("1");
@@ -846,7 +855,7 @@
        if (bwFlag) {
            List<ContractInvoice> contractInvoices = baseMapper.selectList(Wrappers.<ContractInvoice>lambdaQuery()
                    .eq(ContractInvoice::getApprovalStatus, "1")
                    .eq(ContractInvoice::getBwRequestResult,"1")
                    .eq(ContractInvoice::getBwRequestResult, "1")
                    .isNull(ContractInvoice::getInvoicePath));
            if (ArrayUtil.isNotEmpty(contractInvoices)) {
                contractInvoices.stream().forEach(contractInvoice -> {
@@ -856,11 +865,11 @@
                    data.put("serialNo", contractInvoice.getSerialNo());
                    data.put("returnType", "3");
                    data.put("invoiceIssueMode", "1");
                    request.put("data",data);
                    request.put("data", data);
                    String requestId = IdUtil.fastUUID();
                    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());
                    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);
                    saveBwRequestRecord("baiwang.output.format.query", requestId, request.toJSONString(), result);
                    JSONObject resultObj = JSONObject.parseObject(result);
@@ -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");
@@ -1034,7 +1144,7 @@
    public void takeEffect(Long id) {
        ContractInvoice invoice = baseMapper.selectById(id);
        R<SysDept> r = remoteDeptService.getById(SecurityUtils.getUser().getCompId());
        if (!r.isOk()){
        if (!r.isOk()) {
            log.error("单位信息查询异常");
            return;
        }
@@ -1049,8 +1159,8 @@
        //蓝票
        if (invoice.getInvoiceCategory().equals("1")) {
            // TODO 开蓝票接口机动车
            if (StrUtil.equals(vo.getInvoiceVehicleType(),"3")){
                JSONObject request = genBlueInvoiceVehicle(vo, invoice.getSerialNo(),dept);
            if (StrUtil.equals(vo.getInvoiceVehicleType(), "3")) {
                JSONObject request = genBlueInvoiceVehicle(vo, invoice.getSerialNo(), dept);
                String requestId = IdUtil.fastUUID();
                invoice.setBwRequestId(requestId);
                invoice.setBwRequestParam(request.toJSONString());
@@ -1075,7 +1185,7 @@
            }
            // TODO 开蓝票接口 非机动车
            JSONObject request = genBlueInvoice(vo, invoice.getSerialNo(),dept);
            JSONObject request = genBlueInvoice(vo, invoice.getSerialNo(), dept);
            if (bwFlag) {
                String requestId = IdUtil.fastUUID();
                invoice.setBwRequestId(requestId);
@@ -1103,7 +1213,7 @@
                    }
                }
            }
            if (StrUtil.isNotBlank(vo.getAdvanceInvoice())){
            if (StrUtil.isNotBlank(vo.getAdvanceInvoice())) {
                contract.setBillingAmout(vo.getTotalPrice().add(contract.getBillingAmout()));
                contractMapper.updateById(contract);
                return;
@@ -1112,15 +1222,17 @@
                //合同状态
                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");
                        List<ContractOutBound> outBoundList = contractOutBoundMapper.selectList(Wrappers.<ContractOutBound>lambdaQuery()
                                .eq(ContractOutBound::getContractId,contract.getId())
                                .eq(ContractOutBound::getContractId, contract.getId())
                                .apply("invoice_num < out_bound_num"));
                        if (CollUtil.isNotEmpty(outBoundList)){
                        if (CollUtil.isNotEmpty(outBoundList)) {
                            contract.setBillingStatus("2");
                        }
@@ -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);
@@ -1209,15 +1326,15 @@
            contractMapper.updateById(contract);
            blueInvoice.setRedReversal("1");
            baseMapper.updateById(blueInvoice);
            if (StrUtil.isNotBlank(vo.getAdvanceInvoice())){
            if (StrUtil.isNotBlank(vo.getAdvanceInvoice())) {
                //还原预开数量
                List<ContractOutBoundInvoiceVo> outBoundInvoiceVoList = BeanUtil.copyToList(JSONArray.parseArray(blueInvoice.getInvoicePreviewParam()), ContractOutBoundInvoiceVo.class);
                if (ArrayUtil.isNotEmpty(outBoundInvoiceVoList)) {
                    outBoundInvoiceVoList.stream().forEach(contractOutBoundInvoiceVo -> {
                        ContractSubjectMatter subjectMatter = contractSubjectMatterMapper.selectById(contractOutBoundInvoiceVo.getContractOutBoundId());
                        if (ObjUtil.isNotNull(subjectMatter)){
                        if (ObjUtil.isNotNull(subjectMatter)) {
                            subjectMatter.setAdvanceNum(subjectMatter.getAdvanceNum().subtract(contractOutBoundInvoiceVo.getInvoiceNum()));
                            if (subjectMatter.getAdvanceNum().compareTo(new BigDecimal("0"))<=0){
                            if (subjectMatter.getAdvanceNum().compareTo(new BigDecimal("0")) <= 0) {
                                subjectMatter.setAdvanceNum(new BigDecimal("0"));
                            }
                            contractSubjectMatterMapper.updateById(subjectMatter);
@@ -1256,7 +1373,7 @@
    }
    private JSONObject genBlueInvoiceVehicle(GenInvoiceInfoVo vo, String serialNo,SysDept dept) {
    private JSONObject genBlueInvoiceVehicle(GenInvoiceInfoVo vo, String serialNo, SysDept dept) {
        JSONObject vechicleData = JSONObject.parseObject(vo.getVehicleData());
        JSONObject jsonObject = new JSONObject();
        //销方
@@ -1275,13 +1392,13 @@
        JSONObject invoiceDetail = new JSONObject();
        invoiceDetail.put("vehicleNo", vo.getVehicleNo());
        invoiceDetail.put("manufacturingEnterprise","03");
        invoiceDetail.put("manufacturingEnterprise", "03");
        invoiceDetail.put("originPlace", vechicleData.getString("originPlace"));
        invoiceDetail.put("certificateNo",vo.getCertificateNo());
        invoiceDetail.put("engineNo",vechicleData.getString("engineNo"));
        invoiceDetail.put("tonnage",vechicleData.getString("tonnage"));
        invoiceDetail.put("passengersLimited",vechicleData.getString("passengersLimited"));
        invoiceDetail.put("brandModel",vechicleData.getString("brandModel"));
        invoiceDetail.put("certificateNo", vo.getCertificateNo());
        invoiceDetail.put("engineNo", vechicleData.getString("engineNo"));
        invoiceDetail.put("tonnage", vechicleData.getString("tonnage"));
        invoiceDetail.put("passengersLimited", vechicleData.getString("passengersLimited"));
        invoiceDetail.put("brandModel", vechicleData.getString("brandModel"));
        invoiceDetail.put("goodsCode", vo.getInvoiceItemList().get(0).getTaxCode());
        invoiceDetail.put("goodsTaxRate", vo.getInvoiceItemList().get(0).getTaxRate().divide(new BigDecimal("100")));
@@ -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>