2026-04-29 16500befda17ad48812e7a14a972c08321ff6475
feat(purchase): 新增企业采购智能助理功能

- 实现采购智能助理接口,集成LangChain4j AI服务
- 开发采购助理工具类,提供台账查询、统计分析等功能
- 添加采购助理配置,实现聊天记忆存储管理
- 构建采购AI控制器,支持流式对话和会话管理
- 实现意图识别执行器,自动解析采购查询需求
- 定义采购助理系统提示词,规范AI响应行为
已添加7个文件
已修改5个文件
513 ■■■■■ 文件已修改
doc/add.sql 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/Customer.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/FileNameType.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/BidWinningLedgerController.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/BusinessOpportunityController.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/BidWinningLedgerMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/BidWinningLedgerRecordMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/BidWinningLedger.java 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/BidWinningLedgerRecord.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/BidWinningLedgerService.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/BidWinningLedgerServiceImpl.java 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/add.sql
@@ -130,3 +130,41 @@
ALTER TABLE storage_blob MODIFY bucket_filename VARCHAR(255) COMMENT '存储桶中文件名';
ALTER TABLE storage_blob MODIFY bucket_name VARCHAR(255) COMMENT '存储桶名';
ALTER TABLE storage_blob MODIFY byte_size BIGINT COMMENT '资源尺寸(字节)';
DROP TABLE IF EXISTS bid_winning_ledger;
CREATE TABLE bid_winning_ledger
(
    id                  BIGINT AUTO_INCREMENT PRIMARY KEY,
    status              VARCHAR(100)   NULL COMMENT '状态',
    province            VARCHAR(100)   NULL COMMENT '省份',
    city                VARCHAR(100)   NULL COMMENT '城市',
    customer_name       VARCHAR(255)   NOT NULL COMMENT '客户名称',
    contract_amount     DECIMAL(18, 2) NULL COMMENT '合同金额',
    bid_bond            DECIMAL(18, 2) NULL COMMENT '投标保证金',
    winning_service_fee DECIMAL(18, 2) NULL COMMENT '中标服务费',
    industry            VARCHAR(100)   NULL COMMENT '行业',
    business_source     VARCHAR(100)   NULL COMMENT '商机来源',
    remark              VARCHAR(1000)  NULL COMMENT '备注',
    entry_person        VARCHAR(100)   NULL COMMENT '录入人',
    entry_date          DATETIME       NULL COMMENT '录入日期',
    create_time         DATETIME       NULL COMMENT '创建时间',
    create_user         INT            NULL COMMENT '创建用户',
    update_time         DATETIME       NULL COMMENT '更新时间',
    update_user         INT            NULL COMMENT '更新用户',
    tenant_id           BIGINT         NULL COMMENT '租户id'
) COMMENT = '中标台账';
DROP TABLE IF EXISTS bid_winning_ledger_record;
CREATE TABLE bid_winning_ledger_record
(
    id                    BIGINT AUTO_INCREMENT PRIMARY KEY,
    bid_winning_ledger_id BIGINT        NOT NULL COMMENT '中标台账id',
    status                VARCHAR(100)  NULL COMMENT '状态',
    description           VARCHAR(2000) NULL COMMENT '记录内容',
    entry_person          VARCHAR(100)  NULL COMMENT '录入人',
    entry_date            DATETIME      NULL COMMENT '录入日期',
    create_time           DATETIME      NULL COMMENT '创建时间',
    create_user           INT           NULL COMMENT '创建用户',
    update_time           DATETIME      NULL COMMENT '更新时间',
    update_user           INT           NULL COMMENT '更新用户',
    tenant_id             BIGINT        NULL COMMENT '租户id'
) COMMENT = '中标台账记录';
src/main/java/com/ruoyi/basic/pojo/Customer.java
@@ -26,6 +26,10 @@
    @TableId(type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "客户类型 1-普通客户 2-中小客户")
    @Excel(name = "客户类型 1-普通客户 2-中小客户", readConverterExp = "1=普通客户,2=中小客户")
    private Integer customerType;
    /**
     * å®¢æˆ·åç§°
     */
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
@@ -175,6 +175,11 @@
    public List<Map<String, Object>> customerList(Customer customer) {
        LambdaQueryWrapper<Customer> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.select(Customer::getId, Customer::getCustomerName, Customer::getTaxpayerIdentificationNumber);
        if(customer != null){
            if(customer.getCustomerType() != null){
                queryWrapper.eq(Customer::getCustomerType, customer.getCustomerType());
            }
        }
        // èŽ·å–åŽŸå§‹æŸ¥è¯¢ç»“æžœ
        List<Map<String, Object>> result = customerMapper.selectMaps(queryWrapper);
src/main/java/com/ruoyi/common/enums/FileNameType.java
@@ -1,17 +1,17 @@
package com.ruoyi.common.enums;
import com.ruoyi.approve.pojo.ApproveProcess;
public enum FileNameType {
    SALE(1),      // é”€å”®
    PURCHASE(2),  // é‡‡è´­
    INVOICE(3),     //发票
    SALE(1),
    PURCHASE(2),
    INVOICE(3),
    PURCHASELEDGER(4),
    MEASURING(5),  //计量器具台账
    MEASURINGRecord(6),//计量器具台账记录
    ApproveNode(7),  //协同审批节点审核
    ApproveProcess(8);  //协同审批主数据
    MEASURING(5),
    MEASURINGRecord(6),
    ApproveNode(7),
    ApproveProcess(8),
    BUSINESS_OPPORTUNITY(9),
    BID_WINNING_LEDGER(10);
    private final int value;
@@ -21,10 +21,8 @@
    public int getValue() {
        return value;
    }
    // æ ¹æ®æ•´æ•°å€¼èŽ·å–å¯¹åº”çš„æžšä¸¾å€¼
    public static FileNameType fromValue(int value) {
        for (FileNameType type : FileNameType.values()) {
            if (type.getValue() == value) {
@@ -34,4 +32,3 @@
        throw new IllegalArgumentException("Invalid value: " + value);
    }
}
src/main/java/com/ruoyi/sales/controller/BidWinningLedgerController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,83 @@
package com.ruoyi.sales.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.sales.pojo.BidWinningLedger;
import com.ruoyi.sales.pojo.BidWinningLedgerRecord;
import com.ruoyi.sales.service.BidWinningLedgerService;
import io.jsonwebtoken.lang.Collections;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequiredArgsConstructor
@RequestMapping("/sales/bidWinningLedger")
@Api(tags = "中标台账")
public class BidWinningLedgerController extends BaseController {
    private final BidWinningLedgerService bidWinningLedgerService;
    @GetMapping("/getProvinceList")
    @ApiOperation("获取省级列表")
    public AjaxResult getProvinceList() {
        return AjaxResult.success(bidWinningLedgerService.getProvinceList());
    }
    @GetMapping("/getCityList")
    @ApiOperation("根据省份获取城市列表")
    public AjaxResult getCityList(@RequestParam("provinceId") Integer provinceId) {
        return AjaxResult.success(bidWinningLedgerService.getCityList(provinceId));
    }
    @GetMapping("/listPage")
    @ApiOperation("中标台账分页")
    public AjaxResult listPage(Page page, BidWinningLedger bidWinningLedger) {
        IPage<BidWinningLedger> result = bidWinningLedgerService.listPage(page, bidWinningLedger);
        return AjaxResult.success(result);
    }
    @GetMapping("/detail")
    @ApiOperation("中标台账详情")
    public AjaxResult detail(@RequestParam("id") Long id) {
        return AjaxResult.success(bidWinningLedgerService.getDetail(id));
    }
    @PostMapping("/add")
    @ApiOperation("新增中标台账")
    @Log(title = "中标台账-新增", businessType = BusinessType.INSERT)
    public AjaxResult add(@RequestBody BidWinningLedger bidWinningLedger) throws Exception {
        return bidWinningLedgerService.addLedger(bidWinningLedger) ? success() : error();
    }
    @PostMapping("/update")
    @ApiOperation("修改中标台账")
    @Log(title = "中标台账-修改", businessType = BusinessType.UPDATE)
    public AjaxResult update(@RequestBody BidWinningLedger bidWinningLedger) throws Exception {
        return bidWinningLedgerService.updateLedger(bidWinningLedger) ? success() : error();
    }
    @PostMapping("/addRecord")
    @ApiOperation("添加中标台账记录")
    @Log(title = "中标台账-添加记录", businessType = BusinessType.INSERT)
    public AjaxResult addRecord(@RequestBody BidWinningLedgerRecord bidWinningLedgerRecord) {
        return bidWinningLedgerService.addRecord(bidWinningLedgerRecord) ? success() : error();
    }
    @DeleteMapping("/delete")
    @ApiOperation("删除中标台账")
    @Log(title = "中标台账-删除", businessType = BusinessType.DELETE)
    public AjaxResult delete(@RequestBody List<Long> ids) {
        if (Collections.isEmpty(ids)) {
            return error("请选择要删除的中标台账");
        }
        return bidWinningLedgerService.removeLedgerBatchByIds(ids) ? success() : error();
    }
}
src/main/java/com/ruoyi/sales/controller/BusinessOpportunityController.java
@@ -3,6 +3,7 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
@@ -69,7 +70,7 @@
    public AjaxResult listPage(Page page, BusinessOpportunity businessOpportunity) {
        IPage<BusinessOpportunity> businessOpportunityIPage = businessOpportunityService.listPage(page, businessOpportunity);
        businessOpportunityIPage.getRecords().forEach(item -> {
            item.setBusinessCommonFiles(commonFileService.getFileList(item.getId(),9));
            item.setBusinessCommonFiles(commonFileService.getFileList(item.getId(), FileNameType.BUSINESS_OPPORTUNITY.getValue()));
            item.setBusinessDescription(businessDescriptionMapper.selectList(Wrappers.lambdaQuery(BusinessDescription.class)
                    .eq(BusinessDescription::getBusinessOpportunityId, item.getId())
                    .orderByDesc(BusinessDescription::getCreateTime)));
src/main/java/com/ruoyi/sales/mapper/BidWinningLedgerMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
package com.ruoyi.sales.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.sales.pojo.BidWinningLedger;
public interface BidWinningLedgerMapper extends BaseMapper<BidWinningLedger> {
}
src/main/java/com/ruoyi/sales/mapper/BidWinningLedgerRecordMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
package com.ruoyi.sales.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.sales.pojo.BidWinningLedgerRecord;
public interface BidWinningLedgerRecordMapper extends BaseMapper<BidWinningLedgerRecord> {
}
src/main/java/com/ruoyi/sales/pojo/BidWinningLedger.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,101 @@
package com.ruoyi.sales.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.dto.DateQueryDto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
@Data
@TableName("bid_winning_ledger")
@ApiModel("中标台账")
public class BidWinningLedger extends DateQueryDto {
    private static final long serialVersionUID = 1L;
    @TableField(exist = false)
    @ApiModelProperty("附件")
    private List<CommonFile> commonFiles;
    @TableField(exist = false)
    @ApiModelProperty("临时文件id")
    private List<String> tempFileIds;
    @TableField(exist = false)
    @ApiModelProperty("台账记录")
    private List<BidWinningLedgerRecord> recordList;
    @TableId(type = IdType.AUTO)
    private Long id;
    @ApiModelProperty("状态")
    private String status;
    @ApiModelProperty("省份")
    private String province;
    @ApiModelProperty("城市")
    private String city;
    @ApiModelProperty("客户名称")
    private String customerName;
    @ApiModelProperty("合同金额")
    private BigDecimal contractAmount;
    @ApiModelProperty("投标保证金")
    private BigDecimal bidBond;
    @ApiModelProperty("中标服务费")
    private BigDecimal winningServiceFee;
    @ApiModelProperty("行业")
    private String industry;
    @ApiModelProperty("商机来源")
    private String businessSource;
    @ApiModelProperty("备注")
    private String remark;
    @ApiModelProperty("录入人")
    private String entryPerson;
    @ApiModelProperty("录入日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date entryDate;
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",shape = JsonFormat.Shape.STRING)
    private LocalDateTime createTime;
    @ApiModelProperty("创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty("更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",shape = JsonFormat.Shape.STRING)
    private LocalDateTime updateTime;
    @ApiModelProperty("更新用户")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty("租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
}
src/main/java/com/ruoyi/sales/pojo/BidWinningLedgerRecord.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
package com.ruoyi.sales.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.Date;
@Data
@TableName("bid_winning_ledger_record")
@ApiModel("中标台账记录")
public class BidWinningLedgerRecord {
    private static final long serialVersionUID = 1L;
    @TableId(type = IdType.AUTO)
    private Long id;
    @ApiModelProperty("中标台账ID")
    private Long bidWinningLedgerId;
    @ApiModelProperty("状态")
    private String status;
    @ApiModelProperty("记录内容")
    private String description;
    @ApiModelProperty("录入人")
    private String entryPerson;
    @ApiModelProperty("录入日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date entryDate;
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty("创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty("更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty("更新用户")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty("租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
}
src/main/java/com/ruoyi/sales/service/BidWinningLedgerService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.ruoyi.sales.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.sales.pojo.Area;
import com.ruoyi.sales.pojo.BidWinningLedger;
import com.ruoyi.sales.pojo.BidWinningLedgerRecord;
import java.util.List;
public interface BidWinningLedgerService extends IService<BidWinningLedger> {
    IPage<BidWinningLedger> listPage(Page page, BidWinningLedger bidWinningLedger);
    BidWinningLedger getDetail(Long id);
    boolean addLedger(BidWinningLedger bidWinningLedger) throws Exception;
    boolean updateLedger(BidWinningLedger bidWinningLedger) throws Exception;
    boolean addRecord(BidWinningLedgerRecord bidWinningLedgerRecord);
    boolean removeLedgerBatchByIds(List<Long> ids);
    List<Area> getProvinceList();
    List<Area> getCityList(Integer provinceId);
}
src/main/java/com/ruoyi/sales/service/impl/BidWinningLedgerServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,152 @@
package com.ruoyi.sales.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.sales.mapper.AreaMapper;
import com.ruoyi.sales.mapper.BidWinningLedgerMapper;
import com.ruoyi.sales.mapper.BidWinningLedgerRecordMapper;
import com.ruoyi.sales.mapper.CommonFileMapper;
import com.ruoyi.sales.pojo.Area;
import com.ruoyi.sales.pojo.BidWinningLedger;
import com.ruoyi.sales.pojo.BidWinningLedgerRecord;
import com.ruoyi.sales.pojo.CommonFile;
import com.ruoyi.sales.service.BidWinningLedgerService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.util.Date;
import java.util.List;
@Service
@RequiredArgsConstructor
public class BidWinningLedgerServiceImpl extends ServiceImpl<BidWinningLedgerMapper, BidWinningLedger> implements BidWinningLedgerService {
    private final BidWinningLedgerMapper bidWinningLedgerMapper;
    private final BidWinningLedgerRecordMapper bidWinningLedgerRecordMapper;
    private final CommonFileMapper commonFileMapper;
    private final AreaMapper areaMapper;
    private final CommonFileServiceImpl commonFileService;
    @Override
    public IPage<BidWinningLedger> listPage(Page page, BidWinningLedger bidWinningLedger) {
        LambdaQueryWrapper<BidWinningLedger> queryWrapper = new LambdaQueryWrapper<>();
        if (bidWinningLedger != null) {
            if (StringUtils.isNotEmpty(bidWinningLedger.getCustomerName())) {
                queryWrapper.like(BidWinningLedger::getCustomerName, bidWinningLedger.getCustomerName());
            }
            if (StringUtils.isNotEmpty(bidWinningLedger.getProvince())) {
                queryWrapper.eq(BidWinningLedger::getProvince, bidWinningLedger.getProvince());
            }
            if (StringUtils.isNotEmpty(bidWinningLedger.getCity())) {
                queryWrapper.eq(BidWinningLedger::getCity, bidWinningLedger.getCity());
            }
            if (StringUtils.isNotEmpty(bidWinningLedger.getEntryPerson())) {
                queryWrapper.like(BidWinningLedger::getEntryPerson, bidWinningLedger.getEntryPerson());
            }
            if (StringUtils.isNotEmpty(bidWinningLedger.getStatus())) {
                queryWrapper.eq(BidWinningLedger::getStatus, bidWinningLedger.getStatus());
            }
            if (StringUtils.isNotEmpty(bidWinningLedger.getEntryDateStart()) && StringUtils.isNotEmpty(bidWinningLedger.getEntryDateEnd())) {
                queryWrapper.ge(BidWinningLedger::getEntryDate, bidWinningLedger.getEntryDateStart())
                        .le(BidWinningLedger::getEntryDate, bidWinningLedger.getEntryDateEnd());
            }
        }
        queryWrapper.orderByDesc(BidWinningLedger::getUpdateTime)
                .orderByDesc(BidWinningLedger::getEntryDate);
        return bidWinningLedgerMapper.selectPage(page, queryWrapper);
    }
    @Override
    public BidWinningLedger getDetail(Long id) {
        BidWinningLedger bidWinningLedger = bidWinningLedgerMapper.selectById(id);
        if (bidWinningLedger == null) {
            return null;
        }
        bidWinningLedger.setCommonFiles(commonFileService.getFileList(id, FileNameType.BID_WINNING_LEDGER.getValue()));
        bidWinningLedger.setRecordList(bidWinningLedgerRecordMapper.selectList(new LambdaQueryWrapper<BidWinningLedgerRecord>()
                .eq(BidWinningLedgerRecord::getBidWinningLedgerId, id)
                .orderByDesc(BidWinningLedgerRecord::getCreateTime)));
        return bidWinningLedger;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean addLedger(BidWinningLedger bidWinningLedger) throws IOException {
        fillDefaultFields(bidWinningLedger);
        boolean saved = save(bidWinningLedger);
        if (saved) {
            commonFileService.migrateTempFilesToFormal(bidWinningLedger.getId(), bidWinningLedger.getTempFileIds());
        }
        return saved;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateLedger(BidWinningLedger bidWinningLedger) throws IOException {
        fillDefaultFields(bidWinningLedger);
        boolean updated = updateById(bidWinningLedger);
        if (updated) {
            commonFileService.migrateTempFilesToFormal(bidWinningLedger.getId(), bidWinningLedger.getTempFileIds());
        }
        return updated;
    }
    @Override
    public boolean addRecord(BidWinningLedgerRecord bidWinningLedgerRecord) {
        if (StringUtils.isEmpty(bidWinningLedgerRecord.getEntryPerson())) {
            bidWinningLedgerRecord.setEntryPerson(SecurityUtils.getLoginUser().getUser().getNickName());
        }
        if (bidWinningLedgerRecord.getEntryDate() == null) {
            bidWinningLedgerRecord.setEntryDate(new Date());
        }
        int inserted = bidWinningLedgerRecordMapper.insert(bidWinningLedgerRecord);
        if (inserted > 0 && bidWinningLedgerRecord.getBidWinningLedgerId() != null) {
            BidWinningLedger updateEntity = new BidWinningLedger();
            updateEntity.setId(bidWinningLedgerRecord.getBidWinningLedgerId());
            updateEntity.setStatus(bidWinningLedgerRecord.getStatus());
            bidWinningLedgerMapper.updateById(updateEntity);
            return true;
        }
        return inserted > 0;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeLedgerBatchByIds(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return false;
        }
        bidWinningLedgerRecordMapper.delete(new LambdaQueryWrapper<BidWinningLedgerRecord>()
                .in(BidWinningLedgerRecord::getBidWinningLedgerId, ids));
        commonFileMapper.delete(new LambdaQueryWrapper<CommonFile>()
                .in(CommonFile::getCommonId, ids)
                .eq(CommonFile::getType, FileNameType.BID_WINNING_LEDGER.getValue()));
        return removeByIds(ids);
    }
    @Override
    public List<Area> getProvinceList() {
        return areaMapper.selectList(new LambdaQueryWrapper<Area>().eq(Area::getLevel, 1).eq(Area::getParentId, 0));
    }
    @Override
    public List<Area> getCityList(Integer provinceId) {
        return areaMapper.selectList(new LambdaQueryWrapper<Area>().eq(Area::getLevel, 2).eq(Area::getParentId, provinceId));
    }
    private void fillDefaultFields(BidWinningLedger bidWinningLedger) {
        if (StringUtils.isEmpty(bidWinningLedger.getEntryPerson())) {
            bidWinningLedger.setEntryPerson(SecurityUtils.getLoginUser().getUser().getNickName());
        }
        if (bidWinningLedger.getEntryDate() == null) {
            bidWinningLedger.setEntryDate(new Date());
        }
    }
}