feat(oa/crm): 新增报价单信息管理功能

- 新增报价单信息实体类 CrmQuoteInfo 及其业务对象 CrmQuoteInfoBo
- 新增报价单信息控制器 CrmQuoteInfoController,支持增删改查及导出功能
- 新增报价单信息 Mapper 接口及 XML 映射文件,实现数据持久化操作
- 新增报价单信息服务实现类 CrmQuoteInfoServiceImpl,包含完整的业务逻辑处理- 支持报价单分页查询、详情查看、多轮报价查询及金额汇总计算功能
- 提供报价单子表物料明细的关联保存与更新逻辑
- 实现客户方与供货方联系人的关联查询展示- 添加权限控制注解,确保接口访问安全
- 支持报价单导出为 Excel 文件功能
- 支持根据报价单 ID 进行删除及批量删除操作
- 添加重复提交限制注解,防止重复新增或修改
- 新增报价单打印模板及附件存储字段支持
- 实现报价单状态及流程状态字段管理
- 添加报价单编号、名称、类别等基础信息维护功能
- 支持币种、税率、含税信息配置及相关金额字段计算- 实现报价有效期、交付方式、付款方式等相关业务字段管理- 提供客户及供应商联系人信息快速录入与展示功能- 新增报价单备注信息及创建/更新时间追踪功能
- 使用 MyBatis Plus 实现高效的数据访问层封装
- 利用 MapStruct 实现实体与 BO 对象之间的转换
- 添加事务管理以确保主子表数据一致性- 引入参数校验机制保障数据合法性
- 支持通过报价单号或名称进行模糊搜索查询
- 新增报价单轮次管理和相同报价历史追溯功能
dev
zangch@mesnac.com 3 months ago
parent 9dbb796508
commit e409437714

@ -0,0 +1,133 @@
package org.dromara.oa.crm.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.oa.crm.domain.bo.CrmQuoteInfoBo;
import org.dromara.oa.crm.domain.vo.CrmQuoteInfoVo;
import org.dromara.oa.crm.service.ICrmQuoteInfoService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
*
* 访:/oa/crm/crmQuoteInfo
*
* @author Yinq
* @date 2025-10-28
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/crm/crmQuoteInfo")
public class CrmQuoteInfoController extends BaseController {
private final ICrmQuoteInfoService crmQuoteInfoService;
/**
*
*/
@SaCheckPermission("oa/crm:crmQuoteInfo:list")
@GetMapping("/list")
public TableDataInfo<CrmQuoteInfoVo> list(CrmQuoteInfoBo bo, PageQuery pageQuery) {
return crmQuoteInfoService.queryPageList(bo, pageQuery);
}
/**
*
*/
@SaCheckPermission("oa/crm:crmQuoteInfo:export")
@Log(title = "报价单信息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(CrmQuoteInfoBo bo, HttpServletResponse response) {
List<CrmQuoteInfoVo> list = crmQuoteInfoService.queryList(bo);
ExcelUtil.exportExcel(list, "报价单信息", CrmQuoteInfoVo.class, response);
}
/**
*
*
* @param quoteId
*/
@SaCheckPermission("oa/crm:crmQuoteInfo:query")
@GetMapping("/{quoteId}")
public R<CrmQuoteInfoVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable("quoteId") Long quoteId) {
return R.ok(crmQuoteInfoService.queryById(quoteId));
}
/**
*
*/
@SaCheckPermission("oa/crm:crmQuoteInfo:add")
@Log(title = "报价单信息", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody CrmQuoteInfoBo bo) {
return toAjax(crmQuoteInfoService.insertByBo(bo));
}
/**
*
*/
@SaCheckPermission("oa/crm:crmQuoteInfo:edit")
@Log(title = "报价单信息", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody CrmQuoteInfoBo bo) {
return toAjax(crmQuoteInfoService.updateByBo(bo));
}
/**
*
*
* @param quoteIds
*/
@SaCheckPermission("oa/crm:crmQuoteInfo:remove")
@Log(title = "报价单信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{quoteIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable("quoteIds") Long[] quoteIds) {
return toAjax(crmQuoteInfoService.deleteWithValidByIds(List.of(quoteIds), true));
}
/**
*
*/
@GetMapping("/getCrmQuoteInfoList")
public R<List<CrmQuoteInfoVo>> getCrmQuoteInfoList(CrmQuoteInfoBo bo) {
List<CrmQuoteInfoVo> list = crmQuoteInfoService.queryList(bo);
return R.ok(list);
}
/**
* +
*/
@GetMapping("/rounds/{quoteId}")
public R<List<CrmQuoteInfoVo>> listRounds(@PathVariable("quoteId") Long quoteId) {
return R.ok(crmQuoteInfoService.listRoundsByQuoteId(quoteId));
}
/**
*
*/
@PostMapping("/recalc/{quoteId}")
public R<Boolean> recalcTotals(@PathVariable("quoteId") Long quoteId) {
return R.ok(crmQuoteInfoService.recalcTotals(quoteId));
}
}

@ -0,0 +1,117 @@
package org.dromara.oa.crm.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.oa.crm.domain.bo.CrmQuoteMaterialBo;
import org.dromara.oa.crm.domain.vo.CrmQuoteMaterialVo;
import org.dromara.oa.crm.service.ICrmQuoteMaterialService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
*
* 访:/oa/crm/crmQuoteMaterial
*
* @author Yinq
* @date 2025-10-28
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/crm/crmQuoteMaterial")
public class CrmQuoteMaterialController extends BaseController {
private final ICrmQuoteMaterialService crmQuoteMaterialService;
/**
*
*/
@SaCheckPermission("oa/crm:crmQuoteMaterial:list")
@GetMapping("/list")
public TableDataInfo<CrmQuoteMaterialVo> list(CrmQuoteMaterialBo bo, PageQuery pageQuery) {
return crmQuoteMaterialService.queryPageList(bo, pageQuery);
}
/**
*
*/
@SaCheckPermission("oa/crm:crmQuoteMaterial:export")
@Log(title = "报价单物料明细", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(CrmQuoteMaterialBo bo, HttpServletResponse response) {
List<CrmQuoteMaterialVo> list = crmQuoteMaterialService.queryList(bo);
ExcelUtil.exportExcel(list, "报价单物料明细", CrmQuoteMaterialVo.class, response);
}
/**
*
*
* @param quoteMaterialId
*/
@SaCheckPermission("oa/crm:crmQuoteMaterial:query")
@GetMapping("/{quoteMaterialId}")
public R<CrmQuoteMaterialVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable("quoteMaterialId") Long quoteMaterialId) {
return R.ok(crmQuoteMaterialService.queryById(quoteMaterialId));
}
/**
*
*/
@SaCheckPermission("oa/crm:crmQuoteMaterial:add")
@Log(title = "报价单物料明细", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody CrmQuoteMaterialBo bo) {
return toAjax(crmQuoteMaterialService.insertByBo(bo));
}
/**
*
*/
@SaCheckPermission("oa/crm:crmQuoteMaterial:edit")
@Log(title = "报价单物料明细", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody CrmQuoteMaterialBo bo) {
return toAjax(crmQuoteMaterialService.updateByBo(bo));
}
/**
*
*
* @param quoteMaterialIds
*/
@SaCheckPermission("oa/crm:crmQuoteMaterial:remove")
@Log(title = "报价单物料明细", businessType = BusinessType.DELETE)
@DeleteMapping("/{quoteMaterialIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable("quoteMaterialIds") Long[] quoteMaterialIds) {
return toAjax(crmQuoteMaterialService.deleteWithValidByIds(List.of(quoteMaterialIds), true));
}
/**
*
*/
@GetMapping("/getCrmQuoteMaterialList")
public R<List<CrmQuoteMaterialVo>> getCrmQuoteMaterialList(CrmQuoteMaterialBo bo) {
List<CrmQuoteMaterialVo> list = crmQuoteMaterialService.queryList(bo);
return R.ok(list);
}
}

@ -0,0 +1,235 @@
package org.dromara.oa.crm.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.tenant.core.TenantEntity;
import java.io.Serial;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* crm_quote_info
*
* @author Yinq
* @date 2025-10-28
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("crm_quote_info")
public class CrmQuoteInfo extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
@TableId(value = "quote_id", type = IdType.ASSIGN_ID)
private Long quoteId;
/**
*
*/
private String quoteCode;
/**
*
*/
private String quoteName;
/**
*
*/
private Long quoteRound;
/**
* (1 2)
*/
private String quoteCategory;
/**
* (1 2)
*/
private String quoteType;
/**
* 1 2 3 4 5 6 7
*/
private String businessDirection;
/**
*
*/
private Long quoteDeptId;
/**
*
*/
private Date quoteDate;
/**
*
*/
private Date validFrom;
/**
*
*/
private Long validDays;
/**
*
*/
private Date validTo;
/**
*
*/
private Long deliveryPeriod;
/**
* /
*/
private String deliveryMethod;
/**
* (1 2)
*/
private String paymentMethod;
/**
*
*/
private String currencyType;
/**
* 13%
*/
private String taxIncludedInfo;
/**
* ()
*/
private BigDecimal taxRate;
/**
*
*/
private BigDecimal totalPrice;
/**
*
*/
private BigDecimal totalBeforeTax;
/**
*
*/
private BigDecimal totalTax;
/**
*
*/
private BigDecimal totalIncludingTax;
/**
* ID
*/
private Long customerContactId;
/**
*
*/
private String customerContactName;
/**
*
*/
private String customerContactPhone;
/**
*
*/
private String customerContactEmail;
/**
* ID
*/
private Long supplierContactId;
/**
*
*/
private String supplierContactName;
/**
*
*/
private String supplierContactPhone;
/**
*
*/
private String supplierContactEmail;
/**
* ID()
*/
private Long projectId;
/**
* ID()
*/
private Long templateId;
/**
* ID
*/
private String ossId;
/**
* (1 2 3)
*/
private String quoteStatus;
/**
*
*/
private String flowStatus;
/**
*
*/
private String remark;
/**
* 0 1
*/
@TableLogic
private String delFlag;
/**
* CrmCustomerContact
*/
@TableField(exist = false)
private String customerContactRealName;
/**
* CrmCustomerContact
*/
@TableField(exist = false)
private String supplierContactRealName;
/**
* -
*/
@TableField(exist = false)
private List<CrmQuoteMaterial> items;
}

@ -0,0 +1,116 @@
package org.dromara.oa.crm.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.tenant.core.TenantEntity;
import java.io.Serial;
import java.math.BigDecimal;
/**
* crm_quote_material
*
* @author Yinq
* @date 2025-10-28
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("crm_quote_material")
public class CrmQuoteMaterial extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
@TableId(value = "quote_material_id", type = IdType.AUTO)
private Long quoteMaterialId;
/**
* ID
*/
private Long quoteId;
/**
* (ERP)
*/
private Long itemNo;
/**
* /
*/
private String productName;
/**
*
*/
private String specificationDescription;
/**
* ID(SAP)
*/
private Long materialId;
/**
* ID()
*/
private Long relationMaterialId;
/**
*
*/
private BigDecimal amount;
/**
* ID
*/
private Long unitId;
/**
*
*/
private String unitName;
/**
*
*/
private BigDecimal beforePrice;
/**
*
*/
private BigDecimal taxRate;
/**
*
*/
private BigDecimal includingPrice;
/**
* ()
*/
private BigDecimal subtotal;
/**
*
*/
private String remark;
/**
* 1 0
*/
private String activeFlag;
/**
* 0 1
*/
@TableLogic
private String delFlag;
}

@ -0,0 +1,212 @@
package org.dromara.oa.crm.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.oa.crm.domain.CrmQuoteInfo;
import java.util.Date;
import java.math.BigDecimal;
import java.util.List;
/**
* crm_quote_info
*
* @author Yinq
* @date 2025-10-28
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = CrmQuoteInfo.class, reverseConvertGenerate = false)
public class CrmQuoteInfoBo extends BaseEntity {
/**
* ID
*/
@NotNull(message = "报价ID不能为空", groups = { EditGroup.class })
private Long quoteId;
/**
*
*/
private String quoteCode;
/**
*
*/
private String quoteName;
/**
*
*/
private Long quoteRound;
/**
* (1 2)
*/
private String quoteCategory;
/**
* (1 2)
*/
private String quoteType;
/**
* 1 2 3 4 5 6 7
*/
private String businessDirection;
/**
*
*/
private Long quoteDeptId;
/**
*
*/
private Date quoteDate;
/**
*
*/
private Date validFrom;
/**
*
*/
private Long validDays;
/**
*
*/
private Date validTo;
/**
*
*/
private Long deliveryPeriod;
/**
* /
*/
private String deliveryMethod;
/**
* (1 2)
*/
private String paymentMethod;
/**
*
*/
private String currencyType;
/**
* 13%
*/
private String taxIncludedInfo;
/**
* ()
*/
private BigDecimal taxRate;
/**
*
*/
private BigDecimal totalPrice;
/**
*
*/
private BigDecimal totalBeforeTax;
/**
*
*/
private BigDecimal totalTax;
/**
*
*/
private BigDecimal totalIncludingTax;
/**
* ID
*/
private Long customerContactId;
/**
*
*/
private String customerContactName;
/**
*
*/
private String customerContactPhone;
/**
*
*/
private String customerContactEmail;
/**
* ID
*/
private Long supplierContactId;
/**
*
*/
private String supplierContactName;
/**
*
*/
private String supplierContactPhone;
/**
*
*/
private String supplierContactEmail;
/**
* ID()
*/
private Long projectId;
/**
* ID()
*/
private Long templateId;
/**
* ID
*/
private String ossId;
/**
* (1 2 3)
*/
private String quoteStatus;
/**
*
*/
private String flowStatus;
/**
*
*/
private String remark;
/**
* -
*/
private List<CrmQuoteMaterialBo> itemsBo;
}

@ -0,0 +1,105 @@
package org.dromara.oa.crm.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.oa.crm.domain.CrmQuoteMaterial;
import java.math.BigDecimal;
/**
* crm_quote_material
*
* @author Yinq
* @date 2025-10-28
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = CrmQuoteMaterial.class, reverseConvertGenerate = false)
public class CrmQuoteMaterialBo extends BaseEntity {
/**
* ID
*/
@NotNull(message = "报价物料ID不能为空", groups = { EditGroup.class })
private Long quoteMaterialId;
/**
* ID
*/
private Long quoteId;
/**
* (ERP)
*/
private Long itemNo;
/**
* /
*/
private String productName;
/**
*
*/
private String specificationDescription;
/**
* ID(SAP)
*/
private Long materialId;
/**
* ID()
*/
private Long relationMaterialId;
/**
*
*/
private BigDecimal amount;
/**
* ID
*/
private Long unitId;
/**
*
*/
private String unitName;
/**
*
*/
private BigDecimal beforePrice;
/**
*
*/
private BigDecimal taxRate;
/**
*
*/
private BigDecimal includingPrice;
/**
* ()
*/
private BigDecimal subtotal;
/**
*
*/
private String remark;
/**
* 1 0
*/
private String activeFlag;
}

@ -0,0 +1,274 @@
package org.dromara.oa.crm.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import org.dromara.oa.crm.domain.CrmQuoteInfo;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* crm_quote_info
*
* @author Yinq
* @date 2025-10-28
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = CrmQuoteInfo.class)
public class CrmQuoteInfoVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
@ExcelProperty(value = "报价ID")
private Long quoteId;
/**
*
*/
@ExcelProperty(value = "报价单号")
private String quoteCode;
/**
*
*/
@ExcelProperty(value = "报价单名称")
private String quoteName;
/**
*
*/
@ExcelProperty(value = "报价轮次")
private Long quoteRound;
/**
* (1 2)
*/
@ExcelProperty(value = "报价大类(1橡机板块 2非橡机板块)", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "quote_category")
private String quoteCategory;
/**
* (1 2)
*/
@ExcelProperty(value = "报价类型(1对内 2对外)", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "contract_type")
private String quoteType;
/**
* 1 2 3 4 5 6 7
*/
@ExcelProperty(value = "业务方向", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "business_direction")
private String businessDirection;
/**
*
*/
@ExcelProperty(value = "部门")
private Long quoteDeptId;
/**
*
*/
@ExcelProperty(value = "报价日期")
private Date quoteDate;
/**
*
*/
@ExcelProperty(value = "报价有效期起")
private Date validFrom;
/**
*
*/
@ExcelProperty(value = "报价有效期", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "天=")
private Long validDays;
/**
*
*/
@ExcelProperty(value = "报价有效期止")
private Date validTo;
/**
*
*/
@ExcelProperty(value = "交货期", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "天=")
private Long deliveryPeriod;
/**
* /
*/
@ExcelProperty(value = "交付/交货方式")
private String deliveryMethod;
/**
* (1 2)
*/
@ExcelProperty(value = "付款方式(1电汇 2承兑)", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "payment_method")
private String paymentMethod;
/**
*
*/
@ExcelProperty(value = "币种", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "currency_type")
private String currencyType;
/**
* 13%
*/
@ExcelProperty(value = "含税信息", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "如=含13%增值税")
private String taxIncludedInfo;
/**
* ()
*/
@ExcelProperty(value = "税率(默认按明细可覆盖)")
private BigDecimal taxRate;
/**
*
*/
@ExcelProperty(value = "总报价")
private BigDecimal totalPrice;
/**
*
*/
@ExcelProperty(value = "未税总价")
private BigDecimal totalBeforeTax;
/**
*
*/
@ExcelProperty(value = "税额")
private BigDecimal totalTax;
/**
*
*/
@ExcelProperty(value = "含税总价")
private BigDecimal totalIncludingTax;
/**
* ID
*/
@ExcelProperty(value = "客户方联系人ID")
private Long customerContactId;
/**
*
*/
@ExcelProperty(value = "客户方联系人")
private String customerContactName;
/**
*
*/
@ExcelProperty(value = "客户方联系电话")
private String customerContactPhone;
/**
*
*/
@ExcelProperty(value = "客户方电子邮箱")
private String customerContactEmail;
/**
* ID
*/
@ExcelProperty(value = "供货方联系人ID")
private Long supplierContactId;
/**
*
*/
@ExcelProperty(value = "供货方联系人")
private String supplierContactName;
/**
*
*/
@ExcelProperty(value = "供货方联系电话")
private String supplierContactPhone;
/**
*
*/
@ExcelProperty(value = "供货方电子邮箱")
private String supplierContactEmail;
/**
* ID()
*/
@ExcelProperty(value = "项目ID(可选)")
private Long projectId;
/**
* ID()
*/
@ExcelProperty(value = "打印模板ID(可选)")
private Long templateId;
/**
* ID
*/
@ExcelProperty(value = "附件ID")
private String ossId;
/**
* (1 2 3)
*/
@ExcelProperty(value = "报价单状态(1暂存 2审批中 3可用)", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "quote_status")
private String quoteStatus;
/**
*
*/
@ExcelProperty(value = "流程状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "flow_status")
private String flowStatus;
/**
*
*/
@ExcelProperty(value = "备注")
private String remark;
/** 派生客户方联系人真实名称CrmCustomerContact */
@ExcelProperty(value = "客户方联系人(实时)")
private String customerContactRealName;
/** 派生供货方联系人真实名称CrmCustomerContact */
@ExcelProperty(value = "供货方联系人(实时)")
private String supplierContactRealName;
/**
* -
*/
@ExcelProperty(value = "报价单子表-物料明细")
private List<CrmQuoteMaterialVo> itemsVo;
}

@ -0,0 +1,141 @@
package org.dromara.oa.crm.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import org.dromara.oa.crm.domain.CrmQuoteMaterial;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* crm_quote_material
*
* @author Yinq
* @date 2025-10-28
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = CrmQuoteMaterial.class)
public class CrmQuoteMaterialVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
@ExcelProperty(value = "报价物料ID")
private Long quoteMaterialId;
/**
* ID
*/
@ExcelProperty(value = "报价ID")
private Long quoteId;
/**
* (ERP)
*/
@ExcelProperty(value = "序号(ERP风格)")
private Long itemNo;
/**
* /
*/
@ExcelProperty(value = "产品/服务名称")
private String productName;
/**
*
*/
@ExcelProperty(value = "规格描述")
private String specificationDescription;
/**
* ID(SAP)
*/
@ExcelProperty(value = "物料ID(SAP)")
private Long materialId;
/**
* ID()
*/
@ExcelProperty(value = "销售物料ID(关联名)")
private Long relationMaterialId;
/**
* SAP
*/
@ExcelProperty(value = "SAP物料名称")
private String materialName;
/**
*
*/
@ExcelProperty(value = "销售物料名称")
private String saleMaterialName;
/**
*
*/
@ExcelProperty(value = "数量")
private BigDecimal amount;
/**
* ID
*/
@ExcelProperty(value = "单位ID")
private Long unitId;
/**
*
*/
@ExcelProperty(value = "单位名称")
private String unitName;
/**
*
*/
@ExcelProperty(value = "未税单价")
private BigDecimal beforePrice;
/**
*
*/
@ExcelProperty(value = "税率")
private BigDecimal taxRate;
/**
*
*/
@ExcelProperty(value = "含税单价")
private BigDecimal includingPrice;
/**
* ()
*/
@ExcelProperty(value = "小计(含税)")
private BigDecimal subtotal;
/**
*
*/
@ExcelProperty(value = "备注")
private String remark;
/**
* 1 0
*/
@ExcelProperty(value = "激活标识", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "active_flag")
private String activeFlag;
}

@ -0,0 +1,114 @@
package org.dromara.oa.crm.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.apache.ibatis.annotations.Param;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.oa.crm.domain.CrmQuoteInfo;
import org.dromara.oa.crm.domain.vo.CrmQuoteInfoVo;
import java.util.Collection;
import java.util.List;
/**
* Mapper
*
* @author Yinq
* @date 2025-10-28
*/
public interface CrmQuoteInfoMapper extends BaseMapperPlus<CrmQuoteInfo, CrmQuoteInfoVo> {
/**
*
*
* @param page
* @param queryWrapper
* @return
*/
public Page<CrmQuoteInfoVo> selectCustomCrmQuoteInfoVoList(@Param("page") Page<CrmQuoteInfoVo> page, @Param(Constants.WRAPPER) MPJLambdaWrapper<CrmQuoteInfo> queryWrapper);
/**
*
*
* @param queryWrapper
* @return
*/
public List<CrmQuoteInfoVo> selectCustomCrmQuoteInfoVoList(@Param(Constants.WRAPPER) MPJLambdaWrapper<CrmQuoteInfo> queryWrapper);
/**
* ID
*
* @param quoteId ID
* @return
*/
CrmQuoteInfoVo selectCustomCrmQuoteInfoVoById(@Param("quoteId") Long quoteId);
/**
* ID
*
* @param ids ID
* @return
*/
List<CrmQuoteInfoVo> selectCustomCrmQuoteInfoVoByIds(@Param("ids") Collection<Long> ids);
/**
*
*
* @param queryWrapper
* @return
*/
Long countCustomCrmQuoteInfo(@Param(Constants.WRAPPER) Wrapper<CrmQuoteInfo> queryWrapper);
/**
*
*
* @param page
* @param queryWrapper
* @return
*/
Page<CrmQuoteInfoVo> selectCustomCrmQuoteInfoVoPage(@Param("page") Page<CrmQuoteInfoVo> page, @Param(Constants.WRAPPER) Wrapper<CrmQuoteInfo> queryWrapper);
/**
*
*
* @param list
* @return
*/
int batchInsertCrmQuoteInfo(@Param("list") List<CrmQuoteInfo> list);
/**
*
*
* @param list
* @return
*/
int batchUpdateCrmQuoteInfo(@Param("list") List<CrmQuoteInfo> list);
/**
*
*
* @param queryWrapper
* @return
*/
int deleteCustomCrmQuoteInfo(@Param(Constants.WRAPPER) Wrapper<CrmQuoteInfo> queryWrapper);
/**
* ID
*
* @param ids ID
* @return
*/
int deleteCustomCrmQuoteInfoByIds(@Param("ids") Collection<Long> ids);
/**
*
*
* @param queryWrapper
* @return
*/
Boolean existsCrmQuoteInfo(@Param(Constants.WRAPPER) Wrapper<CrmQuoteInfo> queryWrapper);
}

@ -0,0 +1,114 @@
package org.dromara.oa.crm.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.apache.ibatis.annotations.Param;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.oa.crm.domain.CrmQuoteMaterial;
import org.dromara.oa.crm.domain.vo.CrmQuoteMaterialVo;
import java.util.Collection;
import java.util.List;
/**
* Mapper
*
* @author Yinq
* @date 2025-10-28
*/
public interface CrmQuoteMaterialMapper extends BaseMapperPlus<CrmQuoteMaterial, CrmQuoteMaterialVo> {
/**
*
*
* @param page
* @param queryWrapper
* @return
*/
public Page<CrmQuoteMaterialVo> selectCustomCrmQuoteMaterialVoList(@Param("page") Page<CrmQuoteMaterialVo> page, @Param(Constants.WRAPPER) MPJLambdaWrapper<CrmQuoteMaterial> queryWrapper);
/**
*
*
* @param queryWrapper
* @return
*/
public List<CrmQuoteMaterialVo> selectCustomCrmQuoteMaterialVoList(@Param(Constants.WRAPPER) MPJLambdaWrapper<CrmQuoteMaterial> queryWrapper);
/**
* ID
*
* @param quoteMaterialId ID
* @return
*/
CrmQuoteMaterialVo selectCustomCrmQuoteMaterialVoById(@Param("quoteMaterialId") Long quoteMaterialId);
/**
* ID
*
* @param ids ID
* @return
*/
List<CrmQuoteMaterialVo> selectCustomCrmQuoteMaterialVoByIds(@Param("ids") Collection<Long> ids);
/**
*
*
* @param queryWrapper
* @return
*/
Long countCustomCrmQuoteMaterial(@Param(Constants.WRAPPER) Wrapper<CrmQuoteMaterial> queryWrapper);
/**
*
*
* @param page
* @param queryWrapper
* @return
*/
Page<CrmQuoteMaterialVo> selectCustomCrmQuoteMaterialVoPage(@Param("page") Page<CrmQuoteMaterialVo> page, @Param(Constants.WRAPPER) Wrapper<CrmQuoteMaterial> queryWrapper);
/**
*
*
* @param list
* @return
*/
int batchInsertCrmQuoteMaterial(@Param("list") List<CrmQuoteMaterial> list);
/**
*
*
* @param list
* @return
*/
int batchUpdateCrmQuoteMaterial(@Param("list") List<CrmQuoteMaterial> list);
/**
*
*
* @param queryWrapper
* @return
*/
int deleteCustomCrmQuoteMaterial(@Param(Constants.WRAPPER) Wrapper<CrmQuoteMaterial> queryWrapper);
/**
* ID
*
* @param ids ID
* @return
*/
int deleteCustomCrmQuoteMaterialByIds(@Param("ids") Collection<Long> ids);
/**
*
*
* @param queryWrapper
* @return
*/
Boolean existsCrmQuoteMaterial(@Param(Constants.WRAPPER) Wrapper<CrmQuoteMaterial> queryWrapper);
}

@ -0,0 +1,78 @@
package org.dromara.oa.crm.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.oa.crm.domain.bo.CrmQuoteInfoBo;
import org.dromara.oa.crm.domain.vo.CrmQuoteInfoVo;
import java.util.Collection;
import java.util.List;
/**
* Service
*
* @author Yinq
* @date 2025-10-28
*/
public interface ICrmQuoteInfoService {
/**
*
*
* @param quoteId
* @return
*/
CrmQuoteInfoVo queryById(Long quoteId);
/**
*
*
* @param bo
* @param pageQuery
* @return
*/
TableDataInfo<CrmQuoteInfoVo> queryPageList(CrmQuoteInfoBo bo, PageQuery pageQuery);
/**
*
*
* @param bo
* @return
*/
List<CrmQuoteInfoVo> queryList(CrmQuoteInfoBo bo);
/**
*
*
* @param bo
* @return
*/
Boolean insertByBo(CrmQuoteInfoBo bo);
/**
*
*
* @param bo
* @return
*/
Boolean updateByBo(CrmQuoteInfoBo bo);
/**
*
*
* @param ids
* @param isValid
* @return
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* +
*/
List<CrmQuoteInfoVo> listRoundsByQuoteId(Long quoteId);
/**
*
*/
Boolean recalcTotals(Long quoteId);
}

@ -0,0 +1,68 @@
package org.dromara.oa.crm.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.oa.crm.domain.bo.CrmQuoteMaterialBo;
import org.dromara.oa.crm.domain.vo.CrmQuoteMaterialVo;
import java.util.Collection;
import java.util.List;
/**
* Service
*
* @author Yinq
* @date 2025-10-28
*/
public interface ICrmQuoteMaterialService {
/**
*
*
* @param quoteMaterialId
* @return
*/
CrmQuoteMaterialVo queryById(Long quoteMaterialId);
/**
*
*
* @param bo
* @param pageQuery
* @return
*/
TableDataInfo<CrmQuoteMaterialVo> queryPageList(CrmQuoteMaterialBo bo, PageQuery pageQuery);
/**
*
*
* @param bo
* @return
*/
List<CrmQuoteMaterialVo> queryList(CrmQuoteMaterialBo bo);
/**
*
*
* @param bo
* @return
*/
Boolean insertByBo(CrmQuoteMaterialBo bo);
/**
*
*
* @param bo
* @return
*/
Boolean updateByBo(CrmQuoteMaterialBo bo);
/**
*
*
* @param ids
* @param isValid
* @return
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

@ -0,0 +1,324 @@
package org.dromara.oa.crm.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.toolkit.JoinWrappers;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.oa.crm.domain.CrmCustomerContact;
import org.dromara.oa.crm.domain.CrmCustomerInfo;
import org.dromara.oa.crm.domain.CrmQuoteInfo;
import org.dromara.oa.crm.domain.CrmQuoteMaterial;
import org.dromara.oa.crm.domain.bo.CrmQuoteInfoBo;
import org.dromara.oa.crm.domain.bo.CrmQuoteMaterialBo;
import org.dromara.oa.crm.domain.vo.CrmCustomerContactVo;
import org.dromara.oa.crm.domain.vo.CrmQuoteInfoVo;
import org.dromara.oa.crm.domain.vo.CrmQuoteMaterialVo;
import org.dromara.oa.crm.mapper.CrmQuoteInfoMapper;
import org.dromara.oa.crm.mapper.CrmQuoteMaterialMapper;
import org.dromara.oa.crm.service.ICrmCustomerContactService;
import org.dromara.oa.crm.service.ICrmQuoteInfoService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Map;
/**
* Service
*
* @author Yinq
* @date 2025-10-28
*/
@RequiredArgsConstructor
@Service
public class CrmQuoteInfoServiceImpl implements ICrmQuoteInfoService {
private final CrmQuoteInfoMapper baseMapper;
private final CrmQuoteMaterialMapper quoteMaterialMapper;
private final ICrmCustomerContactService customerContactService;
/**
*
*
* @param quoteId
* @return
*/
@Override
public CrmQuoteInfoVo queryById(Long quoteId){
CrmQuoteInfoVo vo = baseMapper.selectVoById(quoteId);
if (vo != null) {
// 关联查询子表明细
List<CrmQuoteMaterialVo> items = quoteMaterialMapper.selectVoList(
JoinWrappers.lambda(CrmQuoteMaterial.class)
.selectAll(CrmQuoteMaterial.class)
.eq(CrmQuoteMaterial::getQuoteId, quoteId)
.orderByAsc(CrmQuoteMaterial::getItemNo)
);
vo.setItemsVo(items);
}
return vo;
}
/**
*
*
* @param bo
* @param pageQuery
* @return
*/
@Override
public TableDataInfo<CrmQuoteInfoVo> queryPageList(CrmQuoteInfoBo bo, PageQuery pageQuery) {
MPJLambdaWrapper<CrmQuoteInfo> lqw = buildQueryWrapper(bo);
Page<CrmQuoteInfoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
*
*
* @param bo
* @return
*/
@Override
public List<CrmQuoteInfoVo> queryList(CrmQuoteInfoBo bo) {
MPJLambdaWrapper<CrmQuoteInfo> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private MPJLambdaWrapper<CrmQuoteInfo> buildQueryWrapper(CrmQuoteInfoBo bo) {
Map<String, Object> params = bo.getParams();
MPJLambdaWrapper<CrmQuoteInfo> lqw = JoinWrappers.lambda(CrmQuoteInfo.class)
// 主表全部字段
.selectAll(CrmQuoteInfo.class)
// 客户方联系人CrmCustomerContact别名CustomerContact
.selectAs("CustomerContact", CrmCustomerContact::getContactName, CrmQuoteInfo::getCustomerContactRealName)
.leftJoin(CrmCustomerContact.class, "CustomerContact", CrmCustomerContact::getContactId, CrmQuoteInfo::getCustomerContactId)
// 供货方联系人CrmCustomerContact别名SupplierContact
.selectAs("SupplierContact", CrmCustomerContact::getContactName, CrmQuoteInfo::getSupplierContactRealName)
.leftJoin(CrmCustomerContact.class, "SupplierContact", CrmCustomerContact::getContactId, CrmQuoteInfo::getSupplierContactId)
.eq(StringUtils.isNotBlank(bo.getQuoteCode()), CrmQuoteInfo::getQuoteCode, bo.getQuoteCode())
.like(StringUtils.isNotBlank(bo.getQuoteName()), CrmQuoteInfo::getQuoteName, bo.getQuoteName())
.eq(bo.getQuoteRound() != null, CrmQuoteInfo::getQuoteRound, bo.getQuoteRound())
.eq(StringUtils.isNotBlank(bo.getQuoteCategory()), CrmQuoteInfo::getQuoteCategory, bo.getQuoteCategory())
.eq(StringUtils.isNotBlank(bo.getQuoteType()), CrmQuoteInfo::getQuoteType, bo.getQuoteType())
.eq(StringUtils.isNotBlank(bo.getBusinessDirection()), CrmQuoteInfo::getBusinessDirection, bo.getBusinessDirection())
.eq(bo.getQuoteDeptId() != null, CrmQuoteInfo::getQuoteDeptId, bo.getQuoteDeptId())
.eq(bo.getQuoteDate() != null, CrmQuoteInfo::getQuoteDate, bo.getQuoteDate())
.eq(bo.getValidFrom() != null, CrmQuoteInfo::getValidFrom, bo.getValidFrom())
.eq(bo.getValidDays() != null, CrmQuoteInfo::getValidDays, bo.getValidDays())
.eq(bo.getValidTo() != null, CrmQuoteInfo::getValidTo, bo.getValidTo())
.eq(bo.getDeliveryPeriod() != null, CrmQuoteInfo::getDeliveryPeriod, bo.getDeliveryPeriod())
.eq(StringUtils.isNotBlank(bo.getDeliveryMethod()), CrmQuoteInfo::getDeliveryMethod, bo.getDeliveryMethod())
.eq(StringUtils.isNotBlank(bo.getPaymentMethod()), CrmQuoteInfo::getPaymentMethod, bo.getPaymentMethod())
.eq(StringUtils.isNotBlank(bo.getCurrencyType()), CrmQuoteInfo::getCurrencyType, bo.getCurrencyType())
.eq(StringUtils.isNotBlank(bo.getTaxIncludedInfo()), CrmQuoteInfo::getTaxIncludedInfo, bo.getTaxIncludedInfo())
.eq(bo.getTaxRate() != null, CrmQuoteInfo::getTaxRate, bo.getTaxRate())
.eq(bo.getTotalPrice() != null, CrmQuoteInfo::getTotalPrice, bo.getTotalPrice())
.eq(bo.getTotalBeforeTax() != null, CrmQuoteInfo::getTotalBeforeTax, bo.getTotalBeforeTax())
.eq(bo.getTotalTax() != null, CrmQuoteInfo::getTotalTax, bo.getTotalTax())
.eq(bo.getTotalIncludingTax() != null, CrmQuoteInfo::getTotalIncludingTax, bo.getTotalIncludingTax())
.eq(bo.getCustomerContactId() != null, CrmQuoteInfo::getCustomerContactId, bo.getCustomerContactId())
.like(StringUtils.isNotBlank(bo.getCustomerContactName()), CrmQuoteInfo::getCustomerContactName, bo.getCustomerContactName())
.eq(StringUtils.isNotBlank(bo.getCustomerContactPhone()), CrmQuoteInfo::getCustomerContactPhone, bo.getCustomerContactPhone())
.eq(StringUtils.isNotBlank(bo.getCustomerContactEmail()), CrmQuoteInfo::getCustomerContactEmail, bo.getCustomerContactEmail())
.eq(bo.getSupplierContactId() != null, CrmQuoteInfo::getSupplierContactId, bo.getSupplierContactId())
.like(StringUtils.isNotBlank(bo.getSupplierContactName()), CrmQuoteInfo::getSupplierContactName, bo.getSupplierContactName())
.eq(StringUtils.isNotBlank(bo.getSupplierContactPhone()), CrmQuoteInfo::getSupplierContactPhone, bo.getSupplierContactPhone())
.eq(StringUtils.isNotBlank(bo.getSupplierContactEmail()), CrmQuoteInfo::getSupplierContactEmail, bo.getSupplierContactEmail())
.eq(bo.getProjectId() != null, CrmQuoteInfo::getProjectId, bo.getProjectId())
.eq(bo.getTemplateId() != null, CrmQuoteInfo::getTemplateId, bo.getTemplateId())
.eq(StringUtils.isNotBlank(bo.getOssId()), CrmQuoteInfo::getOssId, bo.getOssId())
.eq(StringUtils.isNotBlank(bo.getQuoteStatus()), CrmQuoteInfo::getQuoteStatus, bo.getQuoteStatus())
.eq(StringUtils.isNotBlank(bo.getFlowStatus()), CrmQuoteInfo::getFlowStatus, bo.getFlowStatus())
;
return lqw;
}
/**
*
*
* @param bo
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(CrmQuoteInfoBo bo) {
CrmQuoteInfo add = MapstructUtils.convert(bo, CrmQuoteInfo.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setQuoteId(add.getQuoteId());
// 保存子表
List<CrmQuoteMaterialBo> itemsBo = bo.getItemsBo();
if (itemsBo != null && !itemsBo.isEmpty()) {
for (int i = 0; i < itemsBo.size(); i++) {
CrmQuoteMaterialBo itemBo = itemsBo.get(i);
CrmQuoteMaterial entity = MapstructUtils.convert(itemBo, CrmQuoteMaterial.class);
// 避免主键冲突:子表使用自增主键,插入前显式置空
entity.setQuoteMaterialId(null);
entity.setQuoteId(add.getQuoteId());
// 若未指定序号则按顺序回填
if (entity.getItemNo() == null) {
entity.setItemNo((long) (i + 1));
}
quoteMaterialMapper.insert(entity);
}
// 回写主表金额汇总
this.recalcTotals(add.getQuoteId());
}
}
return flag;
}
/**
*
*
* @param bo
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(CrmQuoteInfoBo bo) {
CrmQuoteInfo update = MapstructUtils.convert(bo, CrmQuoteInfo.class);
validEntityBeforeSave(update);
boolean ok = baseMapper.updateById(update) > 0;
if (!ok) return false;
// 全量覆盖子表:先删后增(保持简单一致性)
Long qid = bo.getQuoteId();
quoteMaterialMapper.delete(Wrappers.<CrmQuoteMaterial>lambdaQuery().eq(CrmQuoteMaterial::getQuoteId, qid));
List<CrmQuoteMaterialBo> itemsBo = bo.getItemsBo();
if (itemsBo != null && !itemsBo.isEmpty()) {
for (int i = 0; i < itemsBo.size(); i++) {
CrmQuoteMaterialBo itemBo = itemsBo.get(i);
CrmQuoteMaterial entity = MapstructUtils.convert(itemBo, CrmQuoteMaterial.class);
// 避免主键冲突:子表使用自增主键,插入前显式置空
entity.setQuoteMaterialId(null);
entity.setQuoteId(qid);
if (entity.getItemNo() == null) {
entity.setItemNo((long) (i + 1));
}
quoteMaterialMapper.insert(entity);
}
}
// 回写主表金额汇总
this.recalcTotals(qid);
return true;
}
/**
*
*/
private void validEntityBeforeSave(CrmQuoteInfo entity){
//TODO 做一些数据校验,如唯一约束
}
/**
*
*
* @param ids
* @param isValid
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
// 先删除子表
int delete = quoteMaterialMapper.delete(Wrappers.<CrmQuoteMaterial>lambdaQuery().in(CrmQuoteMaterial::getQuoteId, ids));
int i = baseMapper.deleteByIds(ids);
return i > 0;
}
/**
* +
* IDID
*/
public List<CrmQuoteInfoVo> listRoundsByQuoteId(Long quoteId) {
CrmQuoteInfoVo base = baseMapper.selectVoById(quoteId);
if (base == null) {
return List.of();
}
// 通过联系人拿客户ID
Long contactId = base.getCustomerContactId();
Long customerId = null;
if (contactId != null) {
CrmCustomerContactVo contactVo = customerContactService.queryById(contactId);
if (contactVo != null) {
customerId = contactVo.getCustomerId();
}
}
MPJLambdaWrapper<CrmQuoteInfo> lqw = JoinWrappers.lambda(CrmQuoteInfo.class)
.selectAll(CrmQuoteInfo.class)
// 别名连接与派生字段选择
.leftJoin(CrmCustomerContact.class, "CustomerContact", CrmCustomerContact::getContactId, CrmQuoteInfo::getCustomerContactId)
.leftJoin(CrmCustomerContact.class, "SupplierContact", CrmCustomerContact::getContactId, CrmQuoteInfo::getSupplierContactId)
.leftJoin(CrmCustomerInfo.class, "Customer", CrmCustomerInfo::getCustomerId, CrmCustomerContact::getCustomerId)
.selectAs("CustomerContact", CrmCustomerContact::getContactName, CrmQuoteInfo::getCustomerContactRealName)
.selectAs("SupplierContact", CrmCustomerContact::getContactName, CrmQuoteInfo::getSupplierContactRealName)
.eq(CrmQuoteInfo::getQuoteName, base.getQuoteName())
// 使用客户方联系人别名的 customer_id 过滤同一客户
.eq(customerId != null, CrmCustomerContact::getCustomerId, customerId)
.orderByAsc(CrmQuoteInfo::getQuoteDate);
return baseMapper.selectVoList(lqw);
}
/**
* ///
*/
public Boolean recalcTotals(Long quoteId) {
if (quoteId == null) return false;
// 查询该报价的所有明细
MPJLambdaWrapper<CrmQuoteMaterial> lqw = JoinWrappers.lambda(CrmQuoteMaterial.class)
.selectAll(CrmQuoteMaterial.class)
.eq(CrmQuoteMaterial::getQuoteId, quoteId);
List<CrmQuoteMaterialVo> items = quoteMaterialMapper.selectVoList(lqw);
if (items == null || items.isEmpty()) {
return true; // 无明细则不处理
}
// 汇总金额(统一两位小数,四舍五入)
BigDecimal totalIncluding = BigDecimal.ZERO;
BigDecimal totalBefore = BigDecimal.ZERO;
BigDecimal totalTax = BigDecimal.ZERO;
for (CrmQuoteMaterialVo m : items) {
BigDecimal amt = m.getAmount() == null ? BigDecimal.ZERO : m.getAmount();
BigDecimal before = m.getBeforePrice() == null ? BigDecimal.ZERO : m.getBeforePrice();
BigDecimal rate = m.getTaxRate() == null ? BigDecimal.ZERO : m.getTaxRate();
BigDecimal including = m.getIncludingPrice();
if (including == null) {
// 含税单价 = 未税单价 × (1 + 税率/100)
including = before.multiply(BigDecimal.ONE.add(rate.movePointLeft(2))).setScale(2, RoundingMode.HALF_UP);
}
BigDecimal subtotal = m.getSubtotal();
if (subtotal == null) {
subtotal = including.multiply(amt).setScale(2, RoundingMode.HALF_UP);
}
totalIncluding = totalIncluding.add(subtotal);
totalBefore = totalBefore.add(before.multiply(amt));
totalTax = totalTax.add(including.subtract(before).multiply(amt));
}
// 总报价与含税总价保持一致
BigDecimal totalPrice = totalIncluding;
CrmQuoteInfo update = new CrmQuoteInfo();
update.setQuoteId(quoteId);
update.setTotalIncludingTax(totalIncluding.setScale(2, RoundingMode.HALF_UP));
update.setTotalBeforeTax(totalBefore.setScale(2, RoundingMode.HALF_UP));
update.setTotalTax(totalTax.setScale(2, RoundingMode.HALF_UP));
update.setTotalPrice(totalPrice.setScale(2, RoundingMode.HALF_UP));
return baseMapper.updateById(update) > 0;
}
}

@ -0,0 +1,151 @@
package org.dromara.oa.crm.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.toolkit.JoinWrappers;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.oa.base.domain.BaseMaterialInfo;
import org.dromara.oa.base.domain.BaseRelationMaterial;
import org.dromara.oa.crm.domain.CrmQuoteMaterial;
import org.dromara.oa.crm.domain.bo.CrmQuoteMaterialBo;
import org.dromara.oa.crm.domain.vo.CrmQuoteMaterialVo;
import org.dromara.oa.crm.mapper.CrmQuoteMaterialMapper;
import org.dromara.oa.crm.service.ICrmQuoteMaterialService;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Service
*
* @author Yinq
* @date 2025-10-28
*/
@RequiredArgsConstructor
@Service
public class CrmQuoteMaterialServiceImpl implements ICrmQuoteMaterialService {
private final CrmQuoteMaterialMapper baseMapper;
/**
*
*
* @param quoteMaterialId
* @return
*/
@Override
public CrmQuoteMaterialVo queryById(Long quoteMaterialId){
return baseMapper.selectVoById(quoteMaterialId);
}
/**
*
*
* @param bo
* @param pageQuery
* @return
*/
@Override
public TableDataInfo<CrmQuoteMaterialVo> queryPageList(CrmQuoteMaterialBo bo, PageQuery pageQuery) {
MPJLambdaWrapper<CrmQuoteMaterial> lqw = buildQueryWrapper(bo);
Page<CrmQuoteMaterialVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
*
*
* @param bo
* @return
*/
@Override
public List<CrmQuoteMaterialVo> queryList(CrmQuoteMaterialBo bo) {
MPJLambdaWrapper<CrmQuoteMaterial> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private MPJLambdaWrapper<CrmQuoteMaterial> buildQueryWrapper(CrmQuoteMaterialBo bo) {
Map<String, Object> params = bo.getParams();
MPJLambdaWrapper<CrmQuoteMaterial> lqw = JoinWrappers.lambda(CrmQuoteMaterial.class)
.selectAll(CrmQuoteMaterial.class)
// 联表选择SAP物料名称 & 销售物料名称
.select(BaseMaterialInfo::getMaterialName)
.select(BaseRelationMaterial::getSaleMaterialName)
.leftJoin(BaseMaterialInfo.class, BaseMaterialInfo::getMaterialId, CrmQuoteMaterial::getMaterialId)
.leftJoin(BaseRelationMaterial.class, BaseRelationMaterial::getRelationMaterialId, CrmQuoteMaterial::getRelationMaterialId)
.eq(bo.getQuoteId() != null, CrmQuoteMaterial::getQuoteId, bo.getQuoteId())
.eq(bo.getItemNo() != null, CrmQuoteMaterial::getItemNo, bo.getItemNo())
.like(StringUtils.isNotBlank(bo.getProductName()), CrmQuoteMaterial::getProductName, bo.getProductName())
.eq(StringUtils.isNotBlank(bo.getSpecificationDescription()), CrmQuoteMaterial::getSpecificationDescription, bo.getSpecificationDescription())
.eq(bo.getMaterialId() != null, CrmQuoteMaterial::getMaterialId, bo.getMaterialId())
.eq(bo.getRelationMaterialId() != null, CrmQuoteMaterial::getRelationMaterialId, bo.getRelationMaterialId())
.eq(bo.getAmount() != null, CrmQuoteMaterial::getAmount, bo.getAmount())
.eq(bo.getUnitId() != null, CrmQuoteMaterial::getUnitId, bo.getUnitId())
.like(StringUtils.isNotBlank(bo.getUnitName()), CrmQuoteMaterial::getUnitName, bo.getUnitName())
.eq(bo.getBeforePrice() != null, CrmQuoteMaterial::getBeforePrice, bo.getBeforePrice())
.eq(bo.getTaxRate() != null, CrmQuoteMaterial::getTaxRate, bo.getTaxRate())
.eq(bo.getIncludingPrice() != null, CrmQuoteMaterial::getIncludingPrice, bo.getIncludingPrice())
.eq(bo.getSubtotal() != null, CrmQuoteMaterial::getSubtotal, bo.getSubtotal())
.eq(StringUtils.isNotBlank(bo.getActiveFlag()), CrmQuoteMaterial::getActiveFlag, bo.getActiveFlag())
;
return lqw;
}
/**
*
*
* @param bo
* @return
*/
@Override
public Boolean insertByBo(CrmQuoteMaterialBo bo) {
CrmQuoteMaterial add = MapstructUtils.convert(bo, CrmQuoteMaterial.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setQuoteMaterialId(add.getQuoteMaterialId());
}
return flag;
}
/**
*
*
* @param bo
* @return
*/
@Override
public Boolean updateByBo(CrmQuoteMaterialBo bo) {
CrmQuoteMaterial update = MapstructUtils.convert(bo, CrmQuoteMaterial.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
*
*/
private void validEntityBeforeSave(CrmQuoteMaterial entity){
//TODO 做一些数据校验,如唯一约束
}
/**
*
*
* @param ids
* @param isValid
* @return
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
}
}

@ -0,0 +1,381 @@
<?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="org.dromara.oa.crm.mapper.CrmQuoteInfoMapper">
<resultMap type="org.dromara.oa.crm.domain.vo.CrmQuoteInfoVo" id="CrmQuoteInfoResult">
</resultMap>
<select id="selectCustomCrmQuoteInfoVoList" resultMap="CrmQuoteInfoResult">
select quote_id, tenant_id, quote_code, quote_name, quote_round, quote_category, quote_type, business_direction, quote_dept_id, quote_date, valid_from, valid_days, valid_to, delivery_period, delivery_method, payment_method, currency_type, tax_included_info, tax_rate, total_price, total_before_tax, total_tax, total_including_tax, customer_contact_id, customer_contact_name, customer_contact_phone, customer_contact_email, supplier_contact_id, supplier_contact_name, supplier_contact_phone, supplier_contact_email, project_id, template_id, oss_id, quote_status, flow_status, remark, del_flag, create_dept, create_by, create_time, update_by, update_time from crm_quote_info t
${ew.getCustomSqlSegment}
</select>
<!-- 根据ID查询详情 -->
<select id="selectCustomCrmQuoteInfoVoById" resultMap="CrmQuoteInfoResult">
select quote_id, tenant_id, quote_code, quote_name, quote_round, quote_category, quote_type, business_direction, quote_dept_id, quote_date, valid_from, valid_days, valid_to, delivery_period, delivery_method, payment_method, currency_type, tax_included_info, tax_rate, total_price, total_before_tax, total_tax, total_including_tax, customer_contact_id, customer_contact_name, customer_contact_phone, customer_contact_email, supplier_contact_id, supplier_contact_name, supplier_contact_phone, supplier_contact_email, project_id, template_id, oss_id, quote_status, flow_status, remark, del_flag, create_dept, create_by, create_time, update_by, update_time
from crm_quote_info t
where t.quote_id = #{quoteId}
</select>
<!-- 批量查询 - 根据ID列表 -->
<select id="selectCustomCrmQuoteInfoVoByIds" resultMap="CrmQuoteInfoResult">
select quote_id, tenant_id, quote_code, quote_name, quote_round, quote_category, quote_type, business_direction, quote_dept_id, quote_date, valid_from, valid_days, valid_to, delivery_period, delivery_method, payment_method, currency_type, tax_included_info, tax_rate, total_price, total_before_tax, total_tax, total_including_tax, customer_contact_id, customer_contact_name, customer_contact_phone, customer_contact_email, supplier_contact_id, supplier_contact_name, supplier_contact_phone, supplier_contact_email, project_id, template_id, oss_id, quote_status, flow_status, remark, del_flag, create_dept, create_by, create_time, update_by, update_time
from crm_quote_info t
where t.quote_id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- 统计查询 -->
<select id="countCustomCrmQuoteInfo" resultType="java.lang.Long">
select count(1) from crm_quote_info t
${ew.getCustomSqlSegment}
</select>
<!-- 分页查询(带自定义条件) -->
<select id="selectCustomCrmQuoteInfoVoPage" resultMap="CrmQuoteInfoResult">
select quote_id, tenant_id, quote_code, quote_name, quote_round, quote_category, quote_type, business_direction, quote_dept_id, quote_date, valid_from, valid_days, valid_to, delivery_period, delivery_method, payment_method, currency_type, tax_included_info, tax_rate, total_price, total_before_tax, total_tax, total_including_tax, customer_contact_id, customer_contact_name, customer_contact_phone, customer_contact_email, supplier_contact_id, supplier_contact_name, supplier_contact_phone, supplier_contact_email, project_id, template_id, oss_id, quote_status, flow_status, remark, del_flag, create_dept, create_by, create_time, update_by, update_time
from crm_quote_info t
${ew.getCustomSqlSegment}
</select>
<!-- 批量插入 -->
<insert id="batchInsertCrmQuoteInfo">
insert into crm_quote_info(
tenant_id,
quote_code,
quote_name,
quote_round,
quote_category,
quote_type,
business_direction,
quote_dept_id,
quote_date,
valid_from,
valid_days,
valid_to,
delivery_period,
delivery_method,
payment_method,
currency_type,
tax_included_info,
tax_rate,
total_price,
total_before_tax,
total_tax,
total_including_tax,
customer_contact_id,
customer_contact_name,
customer_contact_phone,
customer_contact_email,
supplier_contact_id,
supplier_contact_name,
supplier_contact_phone,
supplier_contact_email,
project_id,
template_id,
oss_id,
quote_status,
flow_status,
remark,
del_flag,
create_dept,
create_by,
create_time,
update_by,
update_time
)
values
<foreach collection="list" item="item" separator=",">
(
#{item.tenantId},
#{item.quoteCode},
#{item.quoteName},
#{item.quoteRound},
#{item.quoteCategory},
#{item.quoteType},
#{item.businessDirection},
#{item.quoteDeptId},
#{item.quoteDate},
#{item.validFrom},
#{item.validDays},
#{item.validTo},
#{item.deliveryPeriod},
#{item.deliveryMethod},
#{item.paymentMethod},
#{item.currencyType},
#{item.taxIncludedInfo},
#{item.taxRate},
#{item.totalPrice},
#{item.totalBeforeTax},
#{item.totalTax},
#{item.totalIncludingTax},
#{item.customerContactId},
#{item.customerContactName},
#{item.customerContactPhone},
#{item.customerContactEmail},
#{item.supplierContactId},
#{item.supplierContactName},
#{item.supplierContactPhone},
#{item.supplierContactEmail},
#{item.projectId},
#{item.templateId},
#{item.ossId},
#{item.quoteStatus},
#{item.flowStatus},
#{item.remark},
#{item.delFlag},
#{item.createDept},
#{item.createBy},
#{item.createTime},
#{item.updateBy},
#{item.updateTime}
)
</foreach>
</insert>
<!-- 批量更新 -->
<update id="batchUpdateCrmQuoteInfo">
<foreach collection="list" item="item" separator=";">
update crm_quote_info
<set>
<if test="item.tenantId != null and item.tenantId != ''">
tenant_id = #{item.tenantId},
</if>
<if test="item.quoteCode != null and item.quoteCode != ''">
quote_code = #{item.quoteCode},
</if>
<if test="item.quoteName != null and item.quoteName != ''">
quote_name = #{item.quoteName},
</if>
<if test="item.quoteRound != null">
quote_round = #{item.quoteRound},
</if>
<if test="item.quoteCategory != null and item.quoteCategory != ''">
quote_category = #{item.quoteCategory},
</if>
<if test="item.quoteType != null and item.quoteType != ''">
quote_type = #{item.quoteType},
</if>
<if test="item.businessDirection != null and item.businessDirection != ''">
business_direction = #{item.businessDirection},
</if>
<if test="item.quoteDeptId != null">
quote_dept_id = #{item.quoteDeptId},
</if>
<if test="item.quoteDate != null">
quote_date = #{item.quoteDate},
</if>
<if test="item.validFrom != null">
valid_from = #{item.validFrom},
</if>
<if test="item.validDays != null">
valid_days = #{item.validDays},
</if>
<if test="item.validTo != null">
valid_to = #{item.validTo},
</if>
<if test="item.deliveryPeriod != null">
delivery_period = #{item.deliveryPeriod},
</if>
<if test="item.deliveryMethod != null and item.deliveryMethod != ''">
delivery_method = #{item.deliveryMethod},
</if>
<if test="item.paymentMethod != null and item.paymentMethod != ''">
payment_method = #{item.paymentMethod},
</if>
<if test="item.currencyType != null and item.currencyType != ''">
currency_type = #{item.currencyType},
</if>
<if test="item.taxIncludedInfo != null and item.taxIncludedInfo != ''">
tax_included_info = #{item.taxIncludedInfo},
</if>
<if test="item.taxRate != null">
tax_rate = #{item.taxRate},
</if>
<if test="item.totalPrice != null">
total_price = #{item.totalPrice},
</if>
<if test="item.totalBeforeTax != null">
total_before_tax = #{item.totalBeforeTax},
</if>
<if test="item.totalTax != null">
total_tax = #{item.totalTax},
</if>
<if test="item.totalIncludingTax != null">
total_including_tax = #{item.totalIncludingTax},
</if>
<if test="item.customerContactId != null">
customer_contact_id = #{item.customerContactId},
</if>
<if test="item.customerContactName != null and item.customerContactName != ''">
customer_contact_name = #{item.customerContactName},
</if>
<if test="item.customerContactPhone != null and item.customerContactPhone != ''">
customer_contact_phone = #{item.customerContactPhone},
</if>
<if test="item.customerContactEmail != null and item.customerContactEmail != ''">
customer_contact_email = #{item.customerContactEmail},
</if>
<if test="item.supplierContactId != null">
supplier_contact_id = #{item.supplierContactId},
</if>
<if test="item.supplierContactName != null and item.supplierContactName != ''">
supplier_contact_name = #{item.supplierContactName},
</if>
<if test="item.supplierContactPhone != null and item.supplierContactPhone != ''">
supplier_contact_phone = #{item.supplierContactPhone},
</if>
<if test="item.supplierContactEmail != null and item.supplierContactEmail != ''">
supplier_contact_email = #{item.supplierContactEmail},
</if>
<if test="item.projectId != null">
project_id = #{item.projectId},
</if>
<if test="item.templateId != null">
template_id = #{item.templateId},
</if>
<if test="item.ossId != null and item.ossId != ''">
oss_id = #{item.ossId},
</if>
<if test="item.quoteStatus != null and item.quoteStatus != ''">
quote_status = #{item.quoteStatus},
</if>
<if test="item.flowStatus != null and item.flowStatus != ''">
flow_status = #{item.flowStatus},
</if>
<if test="item.remark != null and item.remark != ''">
remark = #{item.remark},
</if>
<if test="item.delFlag != null and item.delFlag != ''">
del_flag = #{item.delFlag},
</if>
<if test="item.createDept != null">
create_dept = #{item.createDept},
</if>
<if test="item.createBy != null">
create_by = #{item.createBy},
</if>
<if test="item.createTime != null">
create_time = #{item.createTime},
</if>
<if test="item.updateBy != null">
update_by = #{item.updateBy},
</if>
<if test="item.updateTime != null">
update_time = #{item.updateTime}
</if>
</set>
where quote_id = #{item.quoteId}
</foreach>
</update>
<!-- 根据自定义条件删除 -->
<delete id="deleteCustomCrmQuoteInfo">
delete from crm_quote_info
${ew.getCustomSqlSegment}
</delete>
<!-- 根据ID列表批量删除 -->
<delete id="deleteCustomCrmQuoteInfoByIds">
delete from crm_quote_info
where quote_id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<!-- 检查是否存在 -->
<select id="existsCrmQuoteInfo" resultType="java.lang.Boolean">
select count(1) > 0 from crm_quote_info t
${ew.getCustomSqlSegment}
</select>
</mapper>

@ -0,0 +1,241 @@
<?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="org.dromara.oa.crm.mapper.CrmQuoteMaterialMapper">
<resultMap type="org.dromara.oa.crm.domain.vo.CrmQuoteMaterialVo" id="CrmQuoteMaterialResult">
</resultMap>
<select id="selectCustomCrmQuoteMaterialVoList" resultMap="CrmQuoteMaterialResult">
select quote_material_id, tenant_id, quote_id, item_no, product_name, specification_description, material_id, relation_material_id, amount, unit_id, unit_name, before_price, tax_rate, including_price, subtotal, remark, active_flag, del_flag, create_dept, create_by, create_time, update_by, update_time from crm_quote_material t
${ew.getCustomSqlSegment}
</select>
<!-- 根据ID查询详情 -->
<select id="selectCustomCrmQuoteMaterialVoById" resultMap="CrmQuoteMaterialResult">
select quote_material_id, tenant_id, quote_id, item_no, product_name, specification_description, material_id, relation_material_id, amount, unit_id, unit_name, before_price, tax_rate, including_price, subtotal, remark, active_flag, del_flag, create_dept, create_by, create_time, update_by, update_time
from crm_quote_material t
where t.quote_material_id = #{quoteMaterialId}
</select>
<!-- 批量查询 - 根据ID列表 -->
<select id="selectCustomCrmQuoteMaterialVoByIds" resultMap="CrmQuoteMaterialResult">
select quote_material_id, tenant_id, quote_id, item_no, product_name, specification_description, material_id, relation_material_id, amount, unit_id, unit_name, before_price, tax_rate, including_price, subtotal, remark, active_flag, del_flag, create_dept, create_by, create_time, update_by, update_time
from crm_quote_material t
where t.quote_material_id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- 统计查询 -->
<select id="countCustomCrmQuoteMaterial" resultType="java.lang.Long">
select count(1) from crm_quote_material t
${ew.getCustomSqlSegment}
</select>
<!-- 分页查询(带自定义条件) -->
<select id="selectCustomCrmQuoteMaterialVoPage" resultMap="CrmQuoteMaterialResult">
select quote_material_id, tenant_id, quote_id, item_no, product_name, specification_description, material_id, relation_material_id, amount, unit_id, unit_name, before_price, tax_rate, including_price, subtotal, remark, active_flag, del_flag, create_dept, create_by, create_time, update_by, update_time
from crm_quote_material t
${ew.getCustomSqlSegment}
</select>
<!-- 批量插入 -->
<insert id="batchInsertCrmQuoteMaterial">
insert into crm_quote_material(
tenant_id,
quote_id,
item_no,
product_name,
specification_description,
material_id,
relation_material_id,
amount,
unit_id,
unit_name,
before_price,
tax_rate,
including_price,
subtotal,
remark,
active_flag,
del_flag,
create_dept,
create_by,
create_time,
update_by,
update_time
)
values
<foreach collection="list" item="item" separator=",">
(
#{item.tenantId},
#{item.quoteId},
#{item.itemNo},
#{item.productName},
#{item.specificationDescription},
#{item.materialId},
#{item.relationMaterialId},
#{item.amount},
#{item.unitId},
#{item.unitName},
#{item.beforePrice},
#{item.taxRate},
#{item.includingPrice},
#{item.subtotal},
#{item.remark},
#{item.activeFlag},
#{item.delFlag},
#{item.createDept},
#{item.createBy},
#{item.createTime},
#{item.updateBy},
#{item.updateTime}
)
</foreach>
</insert>
<!-- 批量更新 -->
<update id="batchUpdateCrmQuoteMaterial">
<foreach collection="list" item="item" separator=";">
update crm_quote_material
<set>
<if test="item.tenantId != null and item.tenantId != ''">
tenant_id = #{item.tenantId},
</if>
<if test="item.quoteId != null">
quote_id = #{item.quoteId},
</if>
<if test="item.itemNo != null">
item_no = #{item.itemNo},
</if>
<if test="item.productName != null and item.productName != ''">
product_name = #{item.productName},
</if>
<if test="item.specificationDescription != null and item.specificationDescription != ''">
specification_description = #{item.specificationDescription},
</if>
<if test="item.materialId != null">
material_id = #{item.materialId},
</if>
<if test="item.relationMaterialId != null">
relation_material_id = #{item.relationMaterialId},
</if>
<if test="item.amount != null">
amount = #{item.amount},
</if>
<if test="item.unitId != null">
unit_id = #{item.unitId},
</if>
<if test="item.unitName != null and item.unitName != ''">
unit_name = #{item.unitName},
</if>
<if test="item.beforePrice != null">
before_price = #{item.beforePrice},
</if>
<if test="item.taxRate != null">
tax_rate = #{item.taxRate},
</if>
<if test="item.includingPrice != null">
including_price = #{item.includingPrice},
</if>
<if test="item.subtotal != null">
subtotal = #{item.subtotal},
</if>
<if test="item.remark != null and item.remark != ''">
remark = #{item.remark},
</if>
<if test="item.activeFlag != null and item.activeFlag != ''">
active_flag = #{item.activeFlag},
</if>
<if test="item.delFlag != null and item.delFlag != ''">
del_flag = #{item.delFlag},
</if>
<if test="item.createDept != null">
create_dept = #{item.createDept},
</if>
<if test="item.createBy != null">
create_by = #{item.createBy},
</if>
<if test="item.createTime != null">
create_time = #{item.createTime},
</if>
<if test="item.updateBy != null">
update_by = #{item.updateBy},
</if>
<if test="item.updateTime != null">
update_time = #{item.updateTime}
</if>
</set>
where quote_material_id = #{item.quoteMaterialId}
</foreach>
</update>
<!-- 根据自定义条件删除 -->
<delete id="deleteCustomCrmQuoteMaterial">
delete from crm_quote_material
${ew.getCustomSqlSegment}
</delete>
<!-- 根据ID列表批量删除 -->
<delete id="deleteCustomCrmQuoteMaterialByIds">
delete from crm_quote_material
where quote_material_id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<!-- 检查是否存在 -->
<select id="existsCrmQuoteMaterial" resultType="java.lang.Boolean">
select count(1) > 0 from crm_quote_material t
${ew.getCustomSqlSegment}
</select>
</mapper>
Loading…
Cancel
Save