| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014 |
- package org.dromara.crm.service.impl;
- import cn.hutool.core.date.DateUtil;
- import cn.hutool.json.JSONArray;
- import cn.hutool.json.JSONObject;
- import cn.hutool.json.JSONUtil;
- import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
- import com.baomidou.mybatisplus.core.toolkit.Wrappers;
- import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
- import lombok.RequiredArgsConstructor;
- import lombok.extern.slf4j.Slf4j;
- import org.dromara.common.core.exception.ServiceException;
- import org.dromara.common.core.service.UserService;
- 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.common.satoken.utils.LoginHelper;
- import org.dromara.crm.domain.*;
- import org.dromara.crm.domain.bo.CrmContractBo;
- import org.dromara.crm.domain.bo.CrmContractProductBo;
- import org.dromara.crm.domain.bo.CrmContractProductPriceTierBo;
- import org.dromara.crm.domain.bo.CrmProductBo;
- import org.dromara.crm.domain.vo.*;
- import org.dromara.crm.mapper.*;
- import org.dromara.crm.service.ICrmContractApprovalService;
- import org.dromara.crm.service.ICrmContractProductService;
- import org.dromara.crm.service.ICrmContractService;
- import org.dromara.crm.service.ICrmPaymentPlanService;
- import org.dromara.crm.service.ICrmProductService;
- import org.dromara.crm.service.ICrmSupplierInfoService;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
- import java.math.BigDecimal;
- import java.math.RoundingMode;
- import java.time.LocalDate;
- import java.time.LocalDateTime;
- import java.time.format.DateTimeFormatter;
- import java.util.*;
- import java.util.stream.Collectors;
- /**
- * 合同管理Service实现
- *
- * @author AI Assistant
- * @date 2025-10-04
- */
- @Slf4j
- @RequiredArgsConstructor
- @Service
- public class CrmContractServiceImpl implements ICrmContractService {
- private final CrmContractMapper baseMapper;
- private final ICrmContractProductService contractProductService;
- private final CrmContractApprovalMapper approvalMapper;
- private final ICrmContractApprovalService contractApprovalService;
- private final ICrmPaymentPlanService paymentPlanService;
- private final CrmPaymentMapper paymentMapper;
- private final CrmOpportunityMapper opportunityMapper;
- private final CrmQuoteMapper quoteMapper;
- private final CrmContractProductMapper contractProductMapper;
- private final CrmContractTemplateMapper contractTemplateMapper;
- private final CrmInvoiceMapper invoiceMapper;
- private final CrmCustomerMapper customerMapper;
- private final CrmContactMapper contactMapper;
- private final UserService userService;
- private final ICrmSupplierInfoService supplierInfoService;
- private final ICrmProductService productService;
- private final CrmProductMapper productMapper;
- private final CrmContractProductPriceTierMapper contractProductPriceTierMapper;
- /**
- * 构建查询条件
- */
- private LambdaQueryWrapper<CrmContract> buildQueryWrapper(CrmContractBo bo) {
- LambdaQueryWrapper<CrmContract> lqw = Wrappers.lambdaQuery();
- // 合同编号、合同名称和客户名称的OR查询
- // 如果三个字段都有值且相同,则认为是联合搜索(OR关系)
- if (StringUtils.isNotBlank(bo.getContractNo()) && StringUtils.isNotBlank(bo.getContractName())
- && StringUtils.isNotBlank(bo.getCustomerName())
- && bo.getContractNo().equals(bo.getContractName())
- && bo.getContractName().equals(bo.getCustomerName())) {
- // 合同编号、合同名称或客户名称包含关键字
- String keyword = bo.getContractNo();
- lqw.and(wrapper -> wrapper
- .like(CrmContract::getContractNo, keyword)
- .or()
- .like(CrmContract::getContractName, keyword)
- .or()
- .like(CrmContract::getCustomerName, keyword));
- } else {
- // 分别查询
- lqw.like(StringUtils.isNotBlank(bo.getContractNo()), CrmContract::getContractNo, bo.getContractNo());
- lqw.like(StringUtils.isNotBlank(bo.getContractName()), CrmContract::getContractName, bo.getContractName());
- lqw.like(StringUtils.isNotBlank(bo.getCustomerName()), CrmContract::getCustomerName, bo.getCustomerName());
- }
- lqw.eq(bo.getCustomerId() != null, CrmContract::getCustomerId, bo.getCustomerId());
- lqw.eq(bo.getOpportunityId() != null, CrmContract::getOpportunityId, bo.getOpportunityId());
- lqw.eq(bo.getQuoteId() != null, CrmContract::getQuoteId, bo.getQuoteId());
- lqw.eq(StringUtils.isNotBlank(bo.getContractType()), CrmContract::getContractType, bo.getContractType());
- lqw.eq(StringUtils.isNotBlank(bo.getStatus()), CrmContract::getStatus, bo.getStatus());
- // 排除指定状态(如默认排除已作废的记录)
- lqw.ne(StringUtils.isNotBlank(bo.getExcludeStatus()), CrmContract::getStatus, bo.getExcludeStatus());
- lqw.eq(StringUtils.isNotBlank(bo.getApprovalStatus()), CrmContract::getApprovalStatus, bo.getApprovalStatus());
- lqw.eq(bo.getOwnerUserId() != null, CrmContract::getOwnerUserId, bo.getOwnerUserId());
- // 如果指定了待审批人ID,查询该用户待审批的合同
- if (bo.getPendingApprovalUserId() != null) {
- // 使用SQL子查询:查询审批记录中审批人是当前用户且审批结果是pending且isCurrent=1的合同
- lqw.inSql(CrmContract::getId,
- "SELECT DISTINCT contract_id FROM crm_contract_approval " +
- "WHERE approver_id = " + bo.getPendingApprovalUserId() +
- " AND approval_result = 'pending' " +
- " AND is_current = 1 " +
- " AND del_flag = 0");
- }
- // 如果指定了审批人ID,查询该用户审批过的合同
- if (bo.getApprovedByUserId() != null) {
- // 查询该审批人已审批过的合同ID列表
- LambdaQueryWrapper<CrmContractApproval> approvalLqw = Wrappers.lambdaQuery();
- approvalLqw.eq(CrmContractApproval::getApproverId, bo.getApprovedByUserId());
- approvalLqw.in(CrmContractApproval::getApprovalResult, "approved", "rejected");
- List<CrmContractApproval> approvalList = approvalMapper.selectList(approvalLqw);
- if (!approvalList.isEmpty()) {
- List<Long> contractIds = approvalList.stream()
- .map(CrmContractApproval::getContractId)
- .distinct()
- .collect(java.util.stream.Collectors.toList());
- lqw.in(CrmContract::getId, contractIds);
- } else {
- // 如果没有审批记录,返回空结果
- lqw.eq(CrmContract::getId, -1L);
- }
- }
- lqw.ge(bo.getParams().get("minAmount") != null, CrmContract::getAmount, bo.getParams().get("minAmount"));
- lqw.le(bo.getParams().get("maxAmount") != null, CrmContract::getAmount, bo.getParams().get("maxAmount"));
- // 签订日期范围查询
- if (bo.getSignDateBegin() != null && !bo.getSignDateBegin().isEmpty()) {
- lqw.ge(CrmContract::getSignDate, LocalDate.parse(bo.getSignDateBegin()));
- }
- if (bo.getSignDateEnd() != null && !bo.getSignDateEnd().isEmpty()) {
- lqw.le(CrmContract::getSignDate, LocalDate.parse(bo.getSignDateEnd()));
- }
- lqw.between(bo.getParams().get("beginTime") != null && bo.getParams().get("endTime") != null,
- CrmContract::getCreateTime, bo.getParams().get("beginTime"), bo.getParams().get("endTime"));
- lqw.orderByDesc(CrmContract::getCreateTime);
- return lqw;
- }
- @Override
- public TableDataInfo<CrmContractVo> queryPageList(CrmContractBo bo, PageQuery pageQuery) {
- LambdaQueryWrapper<CrmContract> lqw = buildQueryWrapper(bo);
- Page<CrmContractVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
- // 设置每个合同的审批权限
- try {
- Long currentUserId = LoginHelper.getUserId();
- for (CrmContractVo vo : result.getRecords()) {
- vo.setCanApprove(contractApprovalService.canUserApprove(vo.getId(), currentUserId));
- }
- } catch (Exception e) {
- log.warn("获取当前用户审批权限失败: {}", e.getMessage());
- }
- return TableDataInfo.build(result);
- }
- @Override
- public List<CrmContractVo> queryList(CrmContractBo bo) {
- LambdaQueryWrapper<CrmContract> lqw = buildQueryWrapper(bo);
- return baseMapper.selectVoList(lqw);
- }
- @Override
- public CrmContractVo queryById(Long id) {
- CrmContractVo vo = baseMapper.selectVoById(id);
- if (vo != null) {
- // 设置当前用户是否可以审批
- try {
- Long currentUserId = LoginHelper.getUserId();
- vo.setCanApprove(contractApprovalService.canUserApprove(id, currentUserId));
- } catch (Exception e) {
- log.warn("获取当前用户审批权限失败: {}", e.getMessage());
- vo.setCanApprove(false);
- }
-
- // 关联查询供方主体名称
- if (vo.getSupplierId() != null) {
- try {
- CrmSupplierInfoVo supplierInfo = supplierInfoService.queryById(vo.getSupplierId());
- if (supplierInfo != null) {
- vo.setSupplierName(supplierInfo.getSupplierName());
- }
- } catch (Exception e) {
- log.warn("获取供方主体信息失败: {}", e.getMessage());
- }
- }
- // 查询并设置产品明细列表
- try {
- List<CrmContractProductVo> products = contractProductService.queryListByContractId(id);
- if (vo.getProducts() == null || vo.getProducts().isEmpty()) {
- vo.setProducts(products);
- }
- if ("framework".equals(vo.getContractType()) && products != null && !products.isEmpty()) {
- List<CrmContractProductPriceTier> tierEntities = contractProductPriceTierMapper.selectList(
- Wrappers.lambdaQuery(CrmContractProductPriceTier.class)
- .eq(CrmContractProductPriceTier::getContractId, id)
- .orderByAsc(CrmContractProductPriceTier::getContractProductId)
- .orderByAsc(CrmContractProductPriceTier::getSort));
- List<CrmContractProductPriceTierVo> allTiers = new ArrayList<>();
- for (CrmContractProductPriceTier en : tierEntities) {
- allTiers.add(convertTierEntityToVo(en));
- }
- Map<Long, List<CrmContractProductPriceTierVo>> tierMap = new HashMap<>();
- for (CrmContractProductPriceTierVo t : allTiers) {
- tierMap.computeIfAbsent(t.getContractProductId(), k -> new ArrayList<>()).add(t);
- }
- for (CrmContractProductVo p : products) {
- p.setPriceTiers(tierMap.getOrDefault(p.getId(), new ArrayList<>()));
- }
- }
- log.info("查询合同详情成功,合同ID: {}, 产品数量: {}", id, products != null ? products.size() : 0);
- } catch (Exception e) {
- log.error("查询合同产品明细失败,合同ID: {}", id, e);
- }
- }
- return vo;
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean insertByBo(CrmContractBo bo) {
- validateContractTypeAndProductsPresence(bo);
- normalizeFrameworkContractData(bo);
- validateProductPricesByContractType(bo);
- validateSalePurchaseContractAmount(bo);
- CrmContract entity = MapstructUtils.convert(bo, CrmContract.class);
- // 查询供方信息(用于生成合同编号和合同名称)
- String supplierShortName = "山峡"; // 默认简称
- if (entity.getSupplierId() != null) {
- try {
- CrmSupplierInfoVo supplierInfo = supplierInfoService.queryById(entity.getSupplierId());
- if (supplierInfo != null && StringUtils.isNotBlank(supplierInfo.getShortName())) {
- supplierShortName = supplierInfo.getShortName();
- }
- } catch (Exception e) {
- log.warn("查询供方信息失败,使用默认简称,供方ID: {}", entity.getSupplierId(), e);
- }
- }
- // 生成合同编号
- if (StringUtils.isBlank(entity.getContractNo())) {
- entity.setContractNo(generateContractNo(entity.getSupplierId(), entity.getSignDate()));
- }
- // 查询客户信息
- CrmCustomer customer = null;
- if (entity.getCustomerId() != null) {
- customer = customerMapper.selectById(entity.getCustomerId());
- if (customer != null) {
- entity.setCustomerName(customer.getCustomerName());
- }
- }
- String contractType = entity.getContractType();
- // 自动生成合同名称
- String contractNo = entity.getContractNo();
- String customerShortName = customer != null ? customer.getShortName() : "";
- entity.setContractName(generateContractName(contractNo, contractType, supplierShortName,
- customerShortName, entity.getContractName()));
- // 查询并设置联系人信息
- if (entity.getContactId() != null) {
- CrmContact contact = contactMapper.selectById(entity.getContactId());
- if (contact != null) {
- entity.setContactName(contact.getContactName());
- entity.setContactMobile(contact.getMobile());
- }
- }
- // 设置默认值
- if (entity.getStatus() == null) {
- entity.setStatus("draft");
- }
- // 草稿状态下,审批状态应该为空,只有提交审批后才设置为pending
- if ("draft".equals(entity.getStatus())) {
- entity.setApprovalStatus(null);
- }
- if (entity.getOwnerUserId() == null) {
- entity.setOwnerUserId(LoginHelper.getUserId());
- }
- // 设置负责人姓名
- if (entity.getOwnerUserId() != null) {
- try {
- String ownerUserName = userService.selectNicknameById(entity.getOwnerUserId());
- entity.setOwnerUserName(ownerUserName);
- } catch (Exception e) {
- log.warn("获取负责人姓名失败,用户ID: {}", entity.getOwnerUserId(), e);
- entity.setOwnerUserName(null);
- }
- } else {
- entity.setOwnerUserName(null);
- }
- if (entity.getReceivedAmount() == null) {
- entity.setReceivedAmount(BigDecimal.ZERO);
- }
- if (entity.getInvoiceAmount() == null) {
- entity.setInvoiceAmount(BigDecimal.ZERO);
- }
- // 计算未回款金额和未开票金额
- entity.setUnreceivedAmount(entity.getAmount().subtract(entity.getReceivedAmount()));
- entity.setUninvoiceAmount(entity.getAmount().subtract(entity.getInvoiceAmount()));
- // 保存合同
- resolveContractProductIds(bo.getProducts());
- boolean flag = baseMapper.insert(entity) > 0;
- if (flag) {
- bo.setId(entity.getId());
- // 保存产品明细
- if (bo.getProducts() != null && !bo.getProducts().isEmpty()) {
- int sort = 1;
- for (CrmContractProductBo product : bo.getProducts()) {
- product.setContractId(entity.getId());
- // 清空ID,让MyBatis-Plus自动生成新ID,避免主键冲突
- product.setId(null);
- // 如果没有设置排序号,则自动设置为循环索引
- if (product.getSort() == null) {
- product.setSort(sort);
- }
- sort++;
- contractProductService.insertByBo(product);
- if ("framework".equals(entity.getContractType())) {
- savePriceTiersForProduct(product.getId(), entity.getId(), product.getPriceTiers());
- }
- }
- }
- // 生成回款计划
- if (bo.getPaymentPlans() != null && !bo.getPaymentPlans().isEmpty()) {
- try {
- paymentPlanService.batchGenerate(entity.getId(), bo.getPaymentPlans());
- log.info("合同创建成功,已生成回款计划,合同ID: {}, 期数: {}", entity.getId(), bo.getPaymentPlans().size());
- } catch (Exception e) {
- log.error("生成回款计划失败,合同ID: {}", entity.getId(), e);
- // 不影响合同创建,只记录日志
- }
- }
- }
- return flag;
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean updateByBo(CrmContractBo bo) {
- // 只有草稿状态的合同才能修改
- CrmContract existContract = baseMapper.selectById(bo.getId());
- if (existContract == null) {
- throw new ServiceException("合同不存在");
- }
- if (!"draft".equals(existContract.getStatus())) {
- throw new ServiceException("只有草稿状态的合同才能修改");
- }
- validateContractTypeAndProductsPresence(bo);
- normalizeFrameworkContractData(bo);
- validateProductPricesByContractType(bo);
- validateSalePurchaseContractAmount(bo);
- CrmContract entity = MapstructUtils.convert(bo, CrmContract.class);
- // 草稿状态下,审批状态应该为空
- if ("draft".equals(existContract.getStatus())) {
- entity.setApprovalStatus(null);
- }
- // 查询并设置客户名称(如果客户ID有变化)
- CrmCustomer customer = null;
- if (entity.getCustomerId() != null && !entity.getCustomerId().equals(existContract.getCustomerId())) {
- customer = customerMapper.selectById(entity.getCustomerId());
- if (customer != null) {
- entity.setCustomerName(customer.getCustomerName());
- }
- } else if (entity.getCustomerId() != null) {
- // 客户ID没变化,但可能需要用于生成合同名称
- customer = customerMapper.selectById(entity.getCustomerId());
- }
- // 查询并设置联系人信息(如果联系人ID有变化)
- if (entity.getContactId() != null && !entity.getContactId().equals(existContract.getContactId())) {
- CrmContact contact = contactMapper.selectById(entity.getContactId());
- if (contact != null) {
- entity.setContactName(contact.getContactName());
- entity.setContactMobile(contact.getMobile());
- }
- }
- // 检查签订日期是否变化,如果变化则重新生成合同编号和合同名称
- boolean signDateChanged = entity.getSignDate() != null
- && !entity.getSignDate().equals(existContract.getSignDate());
- boolean supplierIdChanged = entity.getSupplierId() != null
- && !entity.getSupplierId().equals(existContract.getSupplierId());
-
- if (signDateChanged || supplierIdChanged) {
- // 查询供方信息(用于生成合同编号和合同名称)
- String supplierShortName = "山峡"; // 默认简称
- if (entity.getSupplierId() != null) {
- try {
- CrmSupplierInfoVo supplierInfo = supplierInfoService.queryById(entity.getSupplierId());
- if (supplierInfo != null && StringUtils.isNotBlank(supplierInfo.getShortName())) {
- supplierShortName = supplierInfo.getShortName();
- }
- } catch (Exception e) {
- log.warn("查询供方信息失败,使用默认简称,供方ID: {}", entity.getSupplierId(), e);
- }
- }
-
- // 重新生成合同编号
- entity.setContractNo(generateContractNo(entity.getSupplierId(), entity.getSignDate()));
-
- // 重新生成合同名称
- String contractNo = entity.getContractNo();
- String customerShortName = customer != null ? customer.getShortName() : "";
- // 判断用户是否修改了合同名称
- // 如果用户修改了合同名称,使用用户的新名称;否则使用旧名称并更新编号
- String userContractName = null;
- if (StringUtils.isNotBlank(entity.getContractName())
- && !entity.getContractName().equals(existContract.getContractName())) {
- // 用户修改了合同名称,使用用户的新名称(移除可能存在的旧编号)
- userContractName = entity.getContractName().replaceAll("([^)]+)$", "");
- } else {
- // 用户没有修改合同名称,使用旧名称(移除旧的合同编号)
- String oldContractName = existContract.getContractName();
- if (StringUtils.isNotBlank(oldContractName)) {
- userContractName = oldContractName.replaceAll("([^)]+)$", "");
- }
- }
- entity.setContractName(generateContractName(contractNo, entity.getContractType(), supplierShortName,
- customerShortName, userContractName));
- }
- // 重新计算未回款金额和未开票金额
- if (entity.getAmount() != null) {
- BigDecimal receivedAmount = existContract.getReceivedAmount() != null
- ? existContract.getReceivedAmount()
- : BigDecimal.ZERO;
- BigDecimal invoiceAmount = existContract.getInvoiceAmount() != null
- ? existContract.getInvoiceAmount()
- : BigDecimal.ZERO;
- entity.setUnreceivedAmount(entity.getAmount().subtract(receivedAmount));
- entity.setUninvoiceAmount(entity.getAmount().subtract(invoiceAmount));
- }
- boolean flag = baseMapper.updateById(entity) > 0;
- if (flag && bo.getProducts() != null) {
- resolveContractProductIds(bo.getProducts());
- contractProductPriceTierMapper.delete(Wrappers.lambdaQuery(CrmContractProductPriceTier.class)
- .eq(CrmContractProductPriceTier::getContractId, bo.getId()));
- // 更新产品明细(简化处理:先删除再新增)
- contractProductService.deleteByIds(
- contractProductService.queryListByContractId(bo.getId())
- .stream().map(p -> p.getId()).toList());
- int sort = 1;
- for (CrmContractProductBo product : bo.getProducts()) {
- product.setContractId(bo.getId());
- // 清空ID,让MyBatis-Plus自动生成新ID,避免主键冲突
- product.setId(null);
- // 如果没有设置排序号,则自动设置为循环索引
- if (product.getSort() == null) {
- product.setSort(sort);
- }
- sort++;
- contractProductService.insertByBo(product);
- if ("framework".equals(bo.getContractType())) {
- savePriceTiersForProduct(product.getId(), bo.getId(), product.getPriceTiers());
- }
- }
- }
- return flag;
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
- if (isValid) {
- // 校验:只能删除草稿状态的合同
- for (Long id : ids) {
- CrmContract contract = baseMapper.selectById(id);
- if (contract != null && !"draft".equals(contract.getStatus())) {
- throw new ServiceException("只能删除草稿状态的合同");
- }
- }
- }
- return baseMapper.deleteByIds(ids) > 0;
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean submitApproval(Long id, List<Long> approverIds, String remark) {
- CrmContract contract = baseMapper.selectById(id);
- if (contract == null) {
- throw new ServiceException("合同不存在");
- }
- if (!"draft".equals(contract.getStatus())) {
- throw new ServiceException("只有草稿状态的合同才能提交审批");
- }
- validateFrameworkContractForSubmitApproval(id, contract);
- // 更新合同状态
- CrmContract update = new CrmContract();
- update.setId(id);
- update.setStatus("approving");
- update.setApprovalStatus("pending");
- baseMapper.updateById(update);
- // 创建审批记录
- if (approverIds != null && !approverIds.isEmpty()) {
- for (int i = 0; i < approverIds.size(); i++) {
- Long approverId = approverIds.get(i);
- CrmContractApproval approval = new CrmContractApproval();
- approval.setContractId(id);
- approval.setStep(i + 1);
- approval.setStepName("第" + (i + 1) + "步审批");
- approval.setApproverId(approverId);
- // TODO: 这里应该查询用户信息填充审批人姓名和部门
- // 由于跨模块调用复杂性,暂时在审批时填充
- // 或者可以通过远程API调用System模块的用户服务
- approval.setApproverName("审批人" + approverId); // 临时占位
- approval.setApproverDept("待审批时填充"); // 临时占位
- approval.setApprovalResult("pending");
- approval.setIsCurrent(i == 0 ? 1 : 0);
- approval.setSort(i + 1);
- approvalMapper.insert(approval);
- }
- }
- return true;
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public int batchSubmitApproval(List<Long> ids, List<Long> approverIds, String remark) {
- int successCount = 0;
- for (Long id : ids) {
- try {
- submitApproval(id, approverIds, remark);
- successCount++;
- } catch (Exception e) {
- // 记录错误但继续处理其他合同
- log.error("合同ID {} 提交审批失败: {}", id, e.getMessage());
- }
- }
- return successCount;
- }
- @Override
- public Boolean saveTemplate(Long id, String templateContent, String settlementClauseDrafts) {
- // 检查合同是否存在
- CrmContract entity = baseMapper.selectById(id);
- if (entity == null) {
- throw new ServiceException("合同不存在");
- }
- String finalSettlementClauseDrafts = settlementClauseDrafts;
- if (StringUtils.isBlank(finalSettlementClauseDrafts)) {
- String method = StringUtils.isNotBlank(entity.getSettlementMethod()) ? entity.getSettlementMethod() : "arrive_payment";
- String defaultClause = "advance_payment".equals(method)
- ? "款到发货。需方付款后安排发货,需方收到货物后,供方开具13%增值税专用发票。"
- : "货到付款,需方收到供方货物后,供方开具13%增值税专用发票,无异议后,以现汇方式月结。";
- JSONObject drafts = new JSONObject();
- drafts.set(method, defaultClause);
- finalSettlementClauseDrafts = drafts.toString();
- }
- log.info("========== 开始保存合同模板内容 ==========");
- log.info("contractId={}", id);
- log.info("contentLength={}", templateContent != null ? templateContent.length() : 0);
- log.info("content前200字符={}", templateContent != null && templateContent.length() > 200
- ? templateContent.substring(0, 200)
- : templateContent);
- log.info("包含HTML标签={}", templateContent != null && templateContent.contains("<"));
- log.info("第五条草稿长度={}", finalSettlementClauseDrafts != null ? finalSettlementClauseDrafts.length() : 0);
- // 使用 LambdaUpdateWrapper 只更新模板相关字段,避免乐观锁问题
- int result = baseMapper.update(null,
- Wrappers.<CrmContract>lambdaUpdate()
- .eq(CrmContract::getId, id)
- .set(CrmContract::getTemplateContent, templateContent)
- .set(CrmContract::getSettlementClauseDrafts, finalSettlementClauseDrafts));
- log.info("保存合同模板内容完成: contractId={}, success={}, affectedRows={}",
- id, result > 0, result);
- // 验证保存结果
- CrmContract savedEntity = baseMapper.selectById(id);
- if (savedEntity != null && savedEntity.getTemplateContent() != null) {
- log.info("验证保存结果: 数据库中的内容长度={}", savedEntity.getTemplateContent().length());
- log.info("验证保存结果: 数据库中的内容前200字符={}",
- savedEntity.getTemplateContent().length() > 200
- ? savedEntity.getTemplateContent().substring(0, 200)
- : savedEntity.getTemplateContent());
- } else {
- log.warn("验证保存结果: 数据库中未找到模板内容!");
- }
- return result > 0;
- }
- @Override
- public String getTemplate(Long id) {
- log.info("========== 开始获取合同模板内容 ==========");
- log.info("contractId={}", id);
- // 直接查询,确保获取最新数据
- CrmContract entity = baseMapper.selectById(id);
- if (entity == null) {
- throw new ServiceException("合同不存在");
- }
- String templateContent = entity.getTemplateContent();
- log.info("获取合同模板内容完成:");
- log.info("- hasTemplate={}", templateContent != null);
- log.info("- contentLength={}", templateContent != null ? templateContent.length() : 0);
- log.info("- content前200字符={}", templateContent != null && templateContent.length() > 200
- ? templateContent.substring(0, 200)
- : templateContent);
- log.info("- 包含HTML标签={}", templateContent != null && templateContent.contains("<"));
- return templateContent;
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean withdrawApproval(Long id, String remark) {
- CrmContract contract = baseMapper.selectById(id);
- if (contract == null) {
- throw new ServiceException("合同不存在");
- }
- if (!"approving".equals(contract.getStatus())) {
- throw new ServiceException("只有审批中的合同才能撤回");
- }
- // 使用 UpdateWrapper 来明确更新 null 值
- com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<CrmContract> updateWrapper = Wrappers
- .lambdaUpdate();
- updateWrapper.eq(CrmContract::getId, id)
- .set(CrmContract::getStatus, "draft")
- .set(CrmContract::getApprovalStatus, null); // 明确设置为 null
- baseMapper.update(null, updateWrapper);
- // 删除待审批的审批记录
- LambdaQueryWrapper<CrmContractApproval> wrapper = Wrappers.lambdaQuery();
- wrapper.eq(CrmContractApproval::getContractId, id);
- wrapper.eq(CrmContractApproval::getApprovalResult, "pending");
- approvalMapper.delete(wrapper);
- log.info("撤回审批成功,合同ID: {}, 撤回原因: {}", id, remark);
- return true;
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean startExecute(Long id, String remark) {
- CrmContract contract = baseMapper.selectById(id);
- if (contract == null) {
- throw new ServiceException("合同不存在");
- }
- if (!"approved".equals(contract.getStatus())) {
- throw new ServiceException("只有已审批的合同才能开始执行");
- }
- CrmContract update = new CrmContract();
- update.setId(id);
- update.setStatus("executing");
- return baseMapper.updateById(update) > 0;
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean finishContract(Long id, String remark) {
- CrmContract contract = baseMapper.selectById(id);
- if (contract == null) {
- throw new ServiceException("合同不存在");
- }
- if (!"executing".equals(contract.getStatus())) {
- throw new ServiceException("只有执行中的合同才能完成");
- }
- CrmContract update = new CrmContract();
- update.setId(id);
- update.setStatus("finished");
- if (StringUtils.isNotBlank(remark)) {
- update.setRemark(remark);
- }
- return baseMapper.updateById(update) > 0;
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean invalidContract(Long id, String invalidReason, String remark) {
- if (StringUtils.isBlank(invalidReason)) {
- throw new ServiceException("作废原因不能为空");
- }
- CrmContract contract = baseMapper.selectById(id);
- if (contract == null) {
- throw new ServiceException("合同不存在");
- }
- if ("finished".equals(contract.getStatus())) {
- throw new ServiceException("已完成的合同不能作废");
- }
- CrmContract update = new CrmContract();
- update.setId(id);
- update.setStatus("invalid");
- update.setInvalidReason(invalidReason);
- update.setInvalidTime(LocalDateTime.now());
- update.setRemark(remark);
- return baseMapper.updateById(update) > 0;
- }
- @Override
- public Boolean uploadFile(Long id, String fileUrl) {
- CrmContract update = new CrmContract();
- update.setId(id);
- update.setContractFile(fileUrl);
- return baseMapper.updateById(update) > 0;
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean uploadAttachments(Long id, String ossIds) {
- if (StringUtils.isBlank(ossIds)) {
- throw new ServiceException("附件ossId不能为空");
- }
- CrmContract contract = baseMapper.selectById(id);
- if (contract == null) {
- throw new ServiceException("合同不存在");
- }
- // 追加附件ossId
- String existingFiles = contract.getAttachmentFiles();
- String newFiles;
- if (StringUtils.isBlank(existingFiles)) {
- newFiles = ossIds;
- } else {
- // 将现有附件和新附件合并(去重)
- String[] existingArr = existingFiles.split(",");
- String[] newArr = ossIds.split(",");
- java.util.Set<String> fileSet = new java.util.HashSet<>(java.util.Arrays.asList(existingArr));
- fileSet.addAll(java.util.Arrays.asList(newArr));
- newFiles = String.join(",", fileSet);
- }
- CrmContract update = new CrmContract();
- update.setId(id);
- update.setAttachmentFiles(newFiles);
- return baseMapper.updateById(update) > 0;
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean uploadAttachmentsWithCategory(Long id, java.util.List<java.util.Map<String, Object>> attachments) {
- if (attachments == null || attachments.isEmpty()) {
- throw new ServiceException("附件列表不能为空");
- }
- CrmContract contract = baseMapper.selectById(id);
- if (contract == null) {
- throw new ServiceException("合同不存在");
- }
- // 解析现有附件(支持JSON格式和逗号分隔格式)
- JSONArray existingAttachments = new JSONArray();
- String existingFiles = contract.getAttachmentFiles();
- if (StringUtils.isNotBlank(existingFiles)) {
- // 尝试解析为JSON格式
- if (existingFiles.trim().startsWith("[")) {
- existingAttachments = JSONUtil.parseArray(existingFiles);
- } else {
- // 兼容旧格式:逗号分隔的ossId,转换为JSON格式
- String[] ossIds = existingFiles.split(",");
- for (String ossId : ossIds) {
- if (StringUtils.isNotBlank(ossId)) {
- JSONObject item = new JSONObject();
- item.set("ossId", ossId.trim());
- item.set("category", "other"); // 旧数据默认分类为"其他附件"
- existingAttachments.add(item);
- }
- }
- }
- }
- // 添加新附件(去重:基于ossId)
- java.util.Set<String> existingOssIds = new java.util.HashSet<>();
- for (int i = 0; i < existingAttachments.size(); i++) {
- JSONObject item = existingAttachments.getJSONObject(i);
- String ossId = item.getStr("ossId");
- if (StringUtils.isNotBlank(ossId)) {
- existingOssIds.add(ossId);
- }
- }
- for (java.util.Map<String, Object> attachment : attachments) {
- String ossId = attachment.get("ossId") != null ? attachment.get("ossId").toString() : null;
- String category = attachment.get("category") != null ? attachment.get("category").toString() : "other";
-
- if (StringUtils.isBlank(ossId)) {
- continue;
- }
- // 如果已存在,更新分类;否则添加新附件
- if (existingOssIds.contains(ossId)) {
- // 更新现有附件的分类
- for (int i = 0; i < existingAttachments.size(); i++) {
- JSONObject item = existingAttachments.getJSONObject(i);
- if (ossId.equals(item.getStr("ossId"))) {
- item.set("category", category);
- break;
- }
- }
- } else {
- // 添加新附件
- JSONObject newItem = new JSONObject();
- newItem.set("ossId", ossId);
- newItem.set("category", category);
- existingAttachments.add(newItem);
- existingOssIds.add(ossId);
- }
- }
- // 保存为JSON格式
- String newFiles = JSONUtil.toJsonStr(existingAttachments);
- CrmContract update = new CrmContract();
- update.setId(id);
- update.setAttachmentFiles(newFiles);
- return baseMapper.updateById(update) > 0;
- }
- @Override
- public Map<String, Object> statisticsOverview(Long ownerUserId, String beginTime, String endTime) {
- Map<String, Object> result = new HashMap<>();
- // 构建查询条件
- LambdaQueryWrapper<CrmContract> lqw = Wrappers.lambdaQuery();
- // 负责人筛选
- if (ownerUserId != null) {
- lqw.eq(CrmContract::getOwnerUserId, ownerUserId);
- }
- // 时间范围筛选
- if (StringUtils.isNotBlank(beginTime) && StringUtils.isNotBlank(endTime)) {
- lqw.between(CrmContract::getCreateTime, beginTime, endTime);
- }
- // 查询所有合同
- List<CrmContract> contracts = baseMapper.selectList(lqw);
- // 统计各状态数量
- long totalCount = contracts.size();
- long draftCount = contracts.stream().filter(c -> "draft".equals(c.getStatus())).count();
- long approvingCount = contracts.stream().filter(c -> "approving".equals(c.getStatus())).count();
- long executingCount = contracts.stream().filter(c -> "executing".equals(c.getStatus())).count();
- long finishedCount = contracts.stream().filter(c -> "finished".equals(c.getStatus())).count();
- long invalidCount = contracts.stream().filter(c -> "invalid".equals(c.getStatus())).count();
- // 统计金额
- BigDecimal totalAmount = contracts.stream()
- .map(CrmContract::getAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- BigDecimal receivedAmount = contracts.stream()
- .map(CrmContract::getReceivedAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- BigDecimal unreceivedAmount = contracts.stream()
- .map(CrmContract::getUnreceivedAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- BigDecimal invoiceAmount = contracts.stream()
- .map(CrmContract::getInvoiceAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- BigDecimal uninvoiceAmount = contracts.stream()
- .map(CrmContract::getUninvoiceAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- // 计算回款率和开票率
- BigDecimal receivedRate = totalAmount.compareTo(BigDecimal.ZERO) > 0
- ? receivedAmount.divide(totalAmount, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"))
- : BigDecimal.ZERO;
- BigDecimal invoiceRate = totalAmount.compareTo(BigDecimal.ZERO) > 0
- ? invoiceAmount.divide(totalAmount, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"))
- : BigDecimal.ZERO;
- // 组装结果
- result.put("totalCount", totalCount);
- result.put("draftCount", draftCount);
- result.put("approvingCount", approvingCount);
- result.put("executingCount", executingCount);
- result.put("finishedCount", finishedCount);
- result.put("invalidCount", invalidCount);
- result.put("totalAmount", totalAmount);
- result.put("receivedAmount", receivedAmount);
- result.put("unreceivedAmount", unreceivedAmount);
- result.put("receivedRate", receivedRate.setScale(2, RoundingMode.HALF_UP));
- result.put("invoiceAmount", invoiceAmount);
- result.put("uninvoiceAmount", uninvoiceAmount);
- result.put("invoiceRate", invoiceRate.setScale(2, RoundingMode.HALF_UP));
- return result;
- }
- @Override
- public List<Map<String, Object>> statisticsByStatus() {
- List<Map<String, Object>> result = new ArrayList<>();
- // 查询所有合同
- List<CrmContract> contracts = baseMapper.selectList(Wrappers.lambdaQuery());
- // 计算总数和总金额
- long totalCount = contracts.size();
- BigDecimal totalAmount = contracts.stream()
- .map(CrmContract::getAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- // 定义状态映射
- Map<String, String> statusMap = new LinkedHashMap<>();
- statusMap.put("draft", "草稿");
- statusMap.put("approving", "审批中");
- statusMap.put("approved", "已审批");
- statusMap.put("executing", "执行中");
- statusMap.put("finished", "已完成");
- statusMap.put("invalid", "已作废");
- // 按状态统计
- for (Map.Entry<String, String> entry : statusMap.entrySet()) {
- String status = entry.getKey();
- String statusName = entry.getValue();
- List<CrmContract> statusContracts = contracts.stream()
- .filter(c -> status.equals(c.getStatus()))
- .toList();
- long count = statusContracts.size();
- BigDecimal amount = statusContracts.stream()
- .map(CrmContract::getAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- // 计算百分比
- BigDecimal percent = totalCount > 0
- ? new BigDecimal(count).divide(new BigDecimal(totalCount), 4, RoundingMode.HALF_UP)
- .multiply(new BigDecimal("100"))
- : BigDecimal.ZERO;
- Map<String, Object> item = new HashMap<>();
- item.put("status", status);
- item.put("statusName", statusName);
- item.put("count", count);
- item.put("amount", amount);
- item.put("percent", percent.setScale(2, RoundingMode.HALF_UP));
- result.add(item);
- }
- return result;
- }
- @Override
- public List<Map<String, Object>> statisticsByType() {
- List<Map<String, Object>> result = new ArrayList<>();
- // 查询所有合同
- List<CrmContract> contracts = baseMapper.selectList(Wrappers.lambdaQuery());
- // 计算总数和总金额
- long totalCount = contracts.size();
- BigDecimal totalAmount = contracts.stream()
- .map(CrmContract::getAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- // 定义类型映射
- Map<String, String> typeMap = new LinkedHashMap<>();
- typeMap.put("sale", "销售合同");
- typeMap.put("service", "服务合同");
- typeMap.put("purchase", "采购合同");
- typeMap.put("framework", "框架合同");
- typeMap.put("supplement", "补充协议");
- typeMap.put("lease", "租赁合同");
- typeMap.put("other", "其他");
- // 按类型统计
- for (Map.Entry<String, String> entry : typeMap.entrySet()) {
- String type = entry.getKey();
- String typeName = entry.getValue();
- List<CrmContract> typeContracts = contracts.stream()
- .filter(c -> type.equals(c.getContractType()))
- .toList();
- long count = typeContracts.size();
- // 如果该类型没有合同,跳过
- if (count == 0) {
- continue;
- }
- BigDecimal amount = typeContracts.stream()
- .map(CrmContract::getAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- // 计算百分比
- BigDecimal percent = totalCount > 0
- ? new BigDecimal(count).divide(new BigDecimal(totalCount), 4, RoundingMode.HALF_UP)
- .multiply(new BigDecimal("100"))
- : BigDecimal.ZERO;
- Map<String, Object> item = new HashMap<>();
- item.put("contractType", type);
- item.put("typeName", typeName);
- item.put("count", count);
- item.put("amount", amount);
- item.put("percent", percent.setScale(2, RoundingMode.HALF_UP));
- result.add(item);
- }
- return result;
- }
- @Override
- public List<Map<String, Object>> statisticsTrend(String type, String beginTime, String endTime) {
- List<Map<String, Object>> result = new ArrayList<>();
- // 解析开始和结束时间
- LocalDateTime startDateTime;
- LocalDateTime endDateTime;
- DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
- try {
- startDateTime = LocalDateTime.parse(beginTime, formatter);
- endDateTime = LocalDateTime.parse(endTime, formatter);
- } catch (Exception e) {
- log.error("日期格式错误", e);
- return result;
- }
- // 查询时间范围内的合同
- LambdaQueryWrapper<CrmContract> lqw = Wrappers.lambdaQuery();
- lqw.between(CrmContract::getCreateTime, startDateTime, endDateTime);
- List<CrmContract> contracts = baseMapper.selectList(lqw);
- // 根据type确定统计粒度
- String period;
- if ("month".equals(type)) {
- period = "month";
- } else if ("quarter".equals(type)) {
- period = "quarter";
- } else if ("year".equals(type)) {
- period = "year";
- } else {
- period = "month"; // 默认按月统计
- }
- // 按时间分组统计
- Map<String, List<CrmContract>> groupedContracts = new LinkedHashMap<>();
- for (CrmContract contract : contracts) {
- if (contract.getCreateTime() == null) {
- continue;
- }
- // 将Date转换为LocalDateTime
- LocalDateTime createTime = DateUtil.toLocalDateTime(contract.getCreateTime());
- String periodKey;
- if ("month".equals(period)) {
- periodKey = createTime.format(DateTimeFormatter.ofPattern("yyyy-MM"));
- } else if ("quarter".equals(period)) {
- int quarter = (createTime.getMonthValue() - 1) / 3 + 1;
- periodKey = createTime.getYear() + "-Q" + quarter;
- } else { // year
- periodKey = String.valueOf(createTime.getYear());
- }
- groupedContracts.computeIfAbsent(periodKey, k -> new ArrayList<>()).add(contract);
- }
- // 生成统计结果
- for (Map.Entry<String, List<CrmContract>> entry : groupedContracts.entrySet()) {
- String periodKey = entry.getKey();
- List<CrmContract> periodContracts = entry.getValue();
- long totalCount = periodContracts.size();
- BigDecimal totalAmount = periodContracts.stream()
- .map(CrmContract::getAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- BigDecimal receivedAmount = periodContracts.stream()
- .map(CrmContract::getReceivedAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- // 计算回款率
- BigDecimal receivedRate = totalAmount.compareTo(BigDecimal.ZERO) > 0
- ? receivedAmount.divide(totalAmount, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"))
- : BigDecimal.ZERO;
- Map<String, Object> item = new HashMap<>();
- item.put("period", periodKey);
- item.put("totalCount", totalCount);
- item.put("totalAmount", totalAmount);
- item.put("receivedAmount", receivedAmount);
- item.put("receivedRate", receivedRate.setScale(2, RoundingMode.HALF_UP));
- result.add(item);
- }
- return result;
- }
- @Override
- public List<CrmContractVo> queryExpiring(Integer days) {
- if (days == null || days <= 0) {
- days = 30;
- }
- LocalDate endDate = LocalDate.now().plusDays(days);
- LambdaQueryWrapper<CrmContract> lqw = Wrappers.lambdaQuery();
- lqw.eq(CrmContract::getStatus, "executing");
- lqw.le(CrmContract::getEndDate, endDate);
- lqw.ge(CrmContract::getEndDate, LocalDate.now());
- lqw.orderByAsc(CrmContract::getEndDate);
- return baseMapper.selectVoList(lqw);
- }
- /**
- * 生成合同名称
- * <p>销售合同、框架合同:供方简称-客户简称(合同编号);采购合同:客户简称-供方简称(合同编号);其余类型未填名称时为「合同(编号)」。</p>
- *
- * @param contractNo 合同编号
- * @param contractType 合同类型
- * @param supplierShortName 供方简称
- * @param customerShortName 客户简称
- * @param userContractName 用户填写的合同名称(如果为空则自动生成)
- * @return 合同名称
- */
- private String generateContractName(String contractNo, String contractType,
- String supplierShortName, String customerShortName,
- String userContractName) {
- if (StringUtils.isBlank(userContractName)) {
- // 用户未填写合同名称,根据类型自动生成
- if ("sale".equals(contractType) || "framework".equals(contractType)) {
- // 销售合同、框架合同:供方简称-客户简称(合同编号)
- return supplierShortName + "-" + customerShortName + "(" + contractNo + ")";
- } else if ("purchase".equals(contractType)) {
- // 采购合同:客户简称-供方简称(合同编号)
- return customerShortName + "-" + supplierShortName + "(" + contractNo + ")";
- } else {
- // 其他类型:使用合同编号作为名称
- return "合同(" + contractNo + ")";
- }
- } else {
- // 用户手工填写了合同名称,自动补充合同编号
- // 先移除旧的合同编号(如果存在)
- String cleanedName = userContractName.replaceAll("([^)]+)$", "");
- // 检查是否已经包含新的合同编号,避免重复添加
- if (!cleanedName.contains("(" + contractNo + ")")) {
- return cleanedName + "(" + contractNo + ")";
- }
- return cleanedName;
- }
- }
- /**
- * 生成合同编号
- * 格式:{供方编码}/X-JT + yyyyMMdd + 3位流水号
- * 示例:SX/X-JT20250926002
- *
- * @param supplierId 供方主体ID,如果为null则使用默认编码"SX"
- * @param signDate 签订日期,如果为null则使用当前日期
- */
- @Override
- public String generateContractNo(Long supplierId, LocalDate signDate) {
- // 获取供方编码,如果没有则使用默认值"SX"
- String supplierCode = "SX";
- if (supplierId != null) {
- try {
- CrmSupplierInfoVo supplierInfo = supplierInfoService.queryById(supplierId);
- if (supplierInfo != null && StringUtils.isNotBlank(supplierInfo.getSupplierCode())) {
- supplierCode = supplierInfo.getSupplierCode();
- }
- } catch (Exception e) {
- log.warn("查询供方信息失败,使用默认编码,供方ID: {}", supplierId, e);
- }
- }
-
- String prefix = supplierCode + "/X-JT";
- // 使用签订日期,如果为null则使用当前日期
- LocalDate dateToUse = signDate != null ? signDate : LocalDate.now();
- String date = dateToUse.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
- // 查询该签订日期最大流水号(使用动态前缀)
- LambdaQueryWrapper<CrmContract> wrapper = Wrappers.lambdaQuery();
- wrapper.likeRight(CrmContract::getContractNo, prefix + date);
- wrapper.orderByDesc(CrmContract::getContractNo);
- wrapper.last("LIMIT 1");
- CrmContract lastContract = baseMapper.selectOne(wrapper);
- int serialNum = 1;
- if (lastContract != null && lastContract.getContractNo() != null) {
- String lastNo = lastContract.getContractNo();
- // 完整编号格式:{供方编码}/X-JT20250926002
- // 前缀长度可能不同,需要动态计算
- int prefixLength = prefix.length();
- int dateLength = 8; // yyyyMMdd
- int totalPrefixLength = prefixLength + dateLength;
-
- if (lastNo.length() >= totalPrefixLength + 3) {
- try {
- String serialStr = lastNo.substring(totalPrefixLength);
- serialNum = Integer.parseInt(serialStr) + 1;
- } catch (NumberFormatException e) {
- log.warn("解析合同编号流水号失败,使用默认值1,合同编号: {}", lastNo);
- serialNum = 1;
- }
- }
- }
- return String.format("%s%s%03d", prefix, date, serialNum);
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean updateReceivedAmount(Long contractId) {
- // 查询该合同的所有回款记录
- LambdaQueryWrapper<CrmPayment> lqw = Wrappers.lambdaQuery();
- lqw.eq(CrmPayment::getContractId, contractId);
- lqw.eq(CrmPayment::getDelFlag, 0);
- List<CrmPayment> payments = paymentMapper.selectList(lqw);
- // 计算已回款金额
- BigDecimal receivedAmount = payments.stream()
- .map(CrmPayment::getAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- // 查询合同
- CrmContract contract = baseMapper.selectById(contractId);
- if (contract == null) {
- log.error("合同不存在,无法更新回款金额,合同ID: {}", contractId);
- return false;
- }
- // 更新合同回款金额
- CrmContract update = new CrmContract();
- update.setId(contractId);
- update.setReceivedAmount(receivedAmount);
- update.setUnreceivedAmount(contract.getAmount().subtract(receivedAmount));
- boolean flag = baseMapper.updateById(update) > 0;
- if (flag) {
- log.info("更新合同回款金额成功,合同ID: {}, 已回款: {}", contractId, receivedAmount);
- }
- return flag;
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Long renewContract(Long parentContractId, CrmContractBo bo) {
- // 1. 查询原合同
- CrmContract parentContract = baseMapper.selectById(parentContractId);
- if (parentContract == null) {
- throw new ServiceException("原合同不存在");
- }
- // 2. 创建续签合同
- CrmContract newContract = new CrmContract();
- // 从原合同复制基础信息
- newContract.setCustomerId(parentContract.getCustomerId());
- newContract.setCustomerName(parentContract.getCustomerName());
- newContract.setContactId(parentContract.getContactId());
- newContract.setContactName(parentContract.getContactName());
- newContract.setContactMobile(parentContract.getContactMobile());
- newContract.setContractType(parentContract.getContractType());
- newContract.setOwnerUserId(parentContract.getOwnerUserId());
- newContract.setOwnerUserName(parentContract.getOwnerUserName());
- // 复制供方主体ID
- newContract.setSupplierId(bo.getSupplierId() != null ? bo.getSupplierId() : parentContract.getSupplierId());
- // 设置续签相关字段
- newContract.setContractNature("renewal");
- newContract.setParentContractId(parentContractId);
- newContract
- .setRenewalCount((parentContract.getRenewalCount() == null ? 0 : parentContract.getRenewalCount()) + 1);
- // 使用传入的新信息覆盖
- if (StringUtils.isNotBlank(bo.getContractName())) {
- newContract.setContractName(bo.getContractName());
- } else {
- newContract.setContractName(parentContract.getContractName() + "-续签" + newContract.getRenewalCount());
- }
- newContract.setAmount(bo.getAmount() != null ? bo.getAmount() : parentContract.getAmount());
- newContract.setCurrency(bo.getCurrency() != null ? bo.getCurrency() : parentContract.getCurrency());
- newContract.setTaxRate(bo.getTaxRate() != null ? bo.getTaxRate() : parentContract.getTaxRate());
- // 计算含税金额
- if (newContract.getTaxRate() != null && newContract.getAmount() != null) {
- newContract.setAmountWithTax(
- newContract.getAmount().multiply(
- BigDecimal.ONE.add(
- newContract.getTaxRate().divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP))));
- }
- newContract.setSignDate(bo.getSignDate());
- newContract.setStartDate(bo.getStartDate());
- newContract.setEndDate(bo.getEndDate());
- newContract.setContractPeriod(bo.getContractPeriod());
- // 复制甲乙方信息
- newContract.setPartyA(parentContract.getPartyA());
- newContract.setPartyAAddress(parentContract.getPartyAAddress());
- newContract.setPartyAContact(parentContract.getPartyAContact());
- newContract.setPartyAMobile(parentContract.getPartyAMobile());
- newContract.setPartyB(parentContract.getPartyB());
- newContract.setPartyBAddress(parentContract.getPartyBAddress());
- newContract.setPartyBContact(parentContract.getPartyBContact());
- newContract.setPartyBMobile(parentContract.getPartyBMobile());
- // 复制合同条款
- newContract.setPaymentTerms(
- bo.getPaymentTerms() != null ? bo.getPaymentTerms() : parentContract.getPaymentTerms());
- newContract.setDeliveryTerms(
- bo.getDeliveryTerms() != null ? bo.getDeliveryTerms() : parentContract.getDeliveryTerms());
- newContract.setContractTerms(
- bo.getContractTerms() != null ? bo.getContractTerms() : parentContract.getContractTerms());
- // 生成合同编号(续签合同使用原合同的供方主体)
- newContract.setContractNo(generateContractNo(newContract.getSupplierId(), newContract.getSignDate()));
- // 设置默认状态
- newContract.setStatus("draft");
- newContract.setApprovalStatus(null); // 草稿状态下,审批状态应该为空
- newContract.setPerformanceStatus("normal");
- newContract.setPerformanceRate(BigDecimal.ZERO);
- newContract.setReceivedAmount(BigDecimal.ZERO);
- newContract.setUnreceivedAmount(newContract.getAmount());
- newContract.setInvoiceAmount(BigDecimal.ZERO);
- newContract.setUninvoiceAmount(newContract.getAmount());
- // 保存续签合同
- boolean flag = baseMapper.insert(newContract) > 0;
- if (!flag) {
- throw new ServiceException("创建续签合同失败");
- }
- // 复制产品明细
- if (bo.getProducts() != null && !bo.getProducts().isEmpty()) {
- resolveContractProductIds(bo.getProducts());
- // 使用传入的产品
- for (CrmContractProductBo product : bo.getProducts()) {
- product.setContractId(newContract.getId());
- // 清空ID,让MyBatis-Plus自动生成新ID,避免主键冲突
- product.setId(null);
- contractProductService.insertByBo(product);
- if ("framework".equals(parentContract.getContractType())) {
- savePriceTiersForProduct(product.getId(), newContract.getId(), product.getPriceTiers());
- }
- }
- } else {
- // 复制原合同的产品
- LambdaQueryWrapper<CrmContractProduct> productWrapper = Wrappers.lambdaQuery();
- productWrapper.eq(CrmContractProduct::getContractId, parentContractId);
- List<CrmContractProduct> parentProducts = contractProductMapper.selectList(productWrapper);
- for (CrmContractProduct parentProduct : parentProducts) {
- CrmContractProduct newProduct = new CrmContractProduct();
- newProduct.setContractId(newContract.getId());
- newProduct.setProductId(parentProduct.getProductId());
- newProduct.setProductName(parentProduct.getProductName());
- newProduct.setProductNo(parentProduct.getProductNo());
- newProduct.setCategoryId(parentProduct.getCategoryId());
- newProduct.setCategoryName(parentProduct.getCategoryName());
- newProduct.setModel(parentProduct.getModel());
- newProduct.setUnit(parentProduct.getUnit());
- newProduct.setQuantity(parentProduct.getQuantity());
- newProduct.setPrice(parentProduct.getPrice());
- newProduct.setDiscount(parentProduct.getDiscount());
- newProduct.setSubtotal(parentProduct.getSubtotal());
- newProduct.setDescription(parentProduct.getDescription());
- newProduct.setSort(parentProduct.getSort());
- contractProductMapper.insert(newProduct);
- if ("framework".equals(parentContract.getContractType())) {
- copyPriceTiersToNewProduct(parentProduct.getId(), newContract.getId(), newProduct.getId());
- }
- }
- }
- log.info("合同续签成功,原合同ID: {}, 新合同ID: {}, 续签次数: {}", parentContractId, newContract.getId(),
- newContract.getRenewalCount());
- return newContract.getId();
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean createFromOpportunity(Long opportunityId, CrmContractBo bo) {
- // 1. 查询商机
- CrmOpportunity opportunity = opportunityMapper.selectById(opportunityId);
- if (opportunity == null) {
- throw new ServiceException("商机不存在");
- }
- // 2. 自动填充信息
- bo.setOpportunityId(opportunityId);
- bo.setCustomerId(opportunity.getCustomerId());
- if (bo.getAmount() == null) {
- bo.setAmount(opportunity.getAmount());
- }
- if (bo.getOwnerUserId() == null) {
- bo.setOwnerUserId(opportunity.getOwnerUserId());
- }
- // 3. 创建合同
- boolean flag = insertByBo(bo);
- // 4. 更新商机状态为"已转合同"
- if (flag) {
- CrmOpportunity updateOpp = new CrmOpportunity();
- updateOpp.setId(opportunityId);
- updateOpp.setStage("won"); // 赢单
- opportunityMapper.updateById(updateOpp);
- log.info("从商机创建合同成功,商机ID: {}, 合同ID: {}", opportunityId, bo.getId());
- }
- return flag;
- }
- /**
- * 保存合同前解析产品ID(按名称/规格/分类/单位/税率匹配,未命中自动新增)
- */
- private void resolveContractProductIds(List<CrmContractProductBo> products) {
- if (products == null || products.isEmpty()) {
- return;
- }
- for (CrmContractProductBo product : products) {
- product.setProductId(resolveProductIdForContractItem(product));
- }
- }
- private Long resolveProductIdForContractItem(CrmContractProductBo contractProductBo) {
- if (contractProductBo == null) {
- throw new ServiceException("产品明细不能为空");
- }
- String productName = normalizeText(contractProductBo.getProductName());
- if (productName == null) {
- throw new ServiceException("产品名称不能为空");
- }
- String model = normalizeText(contractProductBo.getModel());
- String unit = normalizeText(contractProductBo.getUnit());
- BigDecimal taxRateForCompare = normalizeTaxRateForCompare(contractProductBo.getTaxRate());
- BigDecimal taxRateForSave = normalizeTaxRateForSave(contractProductBo.getTaxRate());
- LambdaQueryWrapper<CrmProduct> lqw = Wrappers.lambdaQuery();
- // 名称/规格/单位:trim + 不区分大小写 + null与空字符串视为相同
- lqw.apply("LOWER(TRIM(COALESCE(product_name, ''))) = {0}", toLower(productName));
- lqw.apply("LOWER(TRIM(COALESCE(model, ''))) = {0}", toLower(model));
- lqw.apply("LOWER(TRIM(COALESCE(unit, ''))) = {0}", toLower(unit));
- // 税率:统一保留2位后比较
- lqw.apply("ROUND(COALESCE(tax_rate, 0), 2) = {0}", taxRateForCompare);
- lqw.orderByDesc(CrmProduct::getId);
- List<CrmProduct> candidates = productMapper.selectList(lqw);
- if (!candidates.isEmpty()) {
- Long categoryId = contractProductBo.getCategoryId();
- // 分类为空允许匹配;若有分类,优先取分类一致
- if (categoryId != null) {
- for (CrmProduct candidate : candidates) {
- if (categoryId.equals(candidate.getCategoryId())) {
- return candidate.getId();
- }
- }
- }
- // 命中多条取最新一条(id最大)
- return candidates.get(0).getId();
- }
- // 未命中则自动新增产品
- CrmProductBo newBo = new CrmProductBo();
- newBo.setProductNo(productService.generateAutoProductNo());
- newBo.setProductName(productName);
- newBo.setCategoryId(contractProductBo.getCategoryId());
- newBo.setCategoryName(normalizeText(contractProductBo.getCategoryName()));
- newBo.setModel(model);
- newBo.setUnit(unit);
- newBo.setTaxRate(taxRateForSave);
- // 合同保存自动新增产品时,标准价格默认0
- newBo.setPrice(BigDecimal.ZERO);
- newBo.setStatus("on");
- productService.insertByBo(newBo);
- return newBo.getId();
- }
- private String normalizeText(String value) {
- if (value == null) {
- return null;
- }
- String trimmed = value.trim();
- return trimmed.isEmpty() ? null : trimmed;
- }
- private String toLower(String value) {
- return value == null ? "" : value.toLowerCase();
- }
- private BigDecimal normalizeTaxRateForCompare(BigDecimal value) {
- BigDecimal taxRate = value == null ? BigDecimal.ZERO : value;
- return taxRate.setScale(2, RoundingMode.HALF_UP);
- }
- /**
- * 保存税率时尽量保留前端传入形态:
- * - 传整数则保存整数
- * - 传小数则保存小数
- */
- private BigDecimal normalizeTaxRateForSave(BigDecimal value) {
- if (value == null) {
- return BigDecimal.ZERO;
- }
- BigDecimal normalized = value.stripTrailingZeros();
- if (normalized.scale() <= 0) {
- return normalized.setScale(0, RoundingMode.UNNECESSARY);
- }
- return normalized;
- }
- private void validateContractTypeAndProductsPresence(CrmContractBo bo) {
- if (bo == null || StringUtils.isBlank(bo.getContractType())) {
- return;
- }
- String contractType = bo.getContractType();
- if ("sale".equals(contractType) || "purchase".equals(contractType) || "framework".equals(contractType)) {
- if (bo.getProducts() == null || bo.getProducts().isEmpty()) {
- throw new ServiceException("销售合同、采购合同或框架合同必须至少添加一个产品");
- }
- }
- }
- private void normalizeFrameworkContractData(CrmContractBo bo) {
- if (bo == null || !"framework".equals(bo.getContractType())) {
- return;
- }
- bo.setAmount(BigDecimal.ZERO);
- bo.setAmountWithTax(BigDecimal.ZERO);
- if (bo.getProducts() == null) {
- return;
- }
- for (CrmContractProductBo p : bo.getProducts()) {
- p.setPrice(BigDecimal.ZERO);
- p.setSubtotal(BigDecimal.ZERO);
- p.setTaxAmount(BigDecimal.ZERO);
- }
- }
- private void validateProductPricesByContractType(CrmContractBo bo) {
- if (bo == null || StringUtils.isBlank(bo.getContractType())) {
- return;
- }
- String contractType = bo.getContractType();
- if (!"sale".equals(contractType) && !"purchase".equals(contractType)) {
- return;
- }
- List<CrmContractProductBo> products = bo.getProducts();
- if (products == null) {
- return;
- }
- for (CrmContractProductBo p : products) {
- if (p.getPrice() == null || p.getPrice().compareTo(new BigDecimal("0.01")) < 0) {
- throw new ServiceException("销售/采购合同产品的含税单价必须大于0");
- }
- if (p.getSubtotal() == null || p.getSubtotal().compareTo(new BigDecimal("0.01")) < 0) {
- throw new ServiceException("销售/采购合同产品的含税总价必须大于0");
- }
- }
- }
- private void validateSalePurchaseContractAmount(CrmContractBo bo) {
- if (bo == null || StringUtils.isBlank(bo.getContractType())) {
- return;
- }
- String contractType = bo.getContractType();
- if (!"sale".equals(contractType) && !"purchase".equals(contractType)) {
- return;
- }
- if (bo.getAmount() == null || bo.getAmount().compareTo(new BigDecimal("0.01")) < 0) {
- throw new ServiceException("销售合同或采购合同的合同金额必须大于0");
- }
- }
- private List<String> parseLetterOssIdsJson(String json) {
- if (StringUtils.isBlank(json)) {
- return Collections.emptyList();
- }
- try {
- List<String> list = JSONUtil.toList(json, String.class);
- return list.stream().filter(StringUtils::isNotBlank).map(String::trim).toList();
- } catch (Exception e) {
- log.warn("解析 letter_oss_ids JSON 失败: {}", json);
- return Collections.emptyList();
- }
- }
- private List<String> parseLetterOssIdsFromEntity(CrmContractProductPriceTier en) {
- if (StringUtils.isNotBlank(en.getLetterOssIds())) {
- List<String> parsed = parseLetterOssIdsJson(en.getLetterOssIds());
- if (!parsed.isEmpty()) {
- return new ArrayList<>(parsed);
- }
- }
- if (en.getLetterOssId() != null) {
- return new ArrayList<>(List.of(en.getLetterOssId().toString()));
- }
- return new ArrayList<>();
- }
- private CrmContractProductPriceTierVo convertTierEntityToVo(CrmContractProductPriceTier en) {
- CrmContractProductPriceTierVo vo = new CrmContractProductPriceTierVo();
- vo.setId(en.getId());
- vo.setContractId(en.getContractId());
- vo.setContractProductId(en.getContractProductId());
- vo.setStartDate(en.getStartDate());
- vo.setEndDate(en.getEndDate());
- vo.setPrice(en.getPrice());
- vo.setSort(en.getSort());
- vo.setLetterOssIds(parseLetterOssIdsFromEntity(en));
- return vo;
- }
- private void savePriceTiersForProduct(Long contractProductId, Long contractId, List<CrmContractProductPriceTierBo> tiers) {
- if (tiers == null || tiers.isEmpty()) {
- return;
- }
- int sort = 1;
- for (CrmContractProductPriceTierBo t : tiers) {
- if (t.getStartDate() == null || t.getEndDate() == null || t.getPrice() == null) {
- continue;
- }
- List<String> ossIds = t.getLetterOssIds();
- if (ossIds == null || ossIds.isEmpty()) {
- continue;
- }
- List<String> normalized = ossIds.stream()
- .filter(StringUtils::isNotBlank)
- .map(String::trim)
- .toList();
- if (normalized.isEmpty()) {
- continue;
- }
- CrmContractProductPriceTier e = new CrmContractProductPriceTier();
- e.setContractId(contractId);
- e.setContractProductId(contractProductId);
- e.setStartDate(t.getStartDate());
- e.setEndDate(t.getEndDate());
- e.setPrice(t.getPrice());
- e.setLetterOssIds(JSONUtil.toJsonStr(normalized));
- e.setLetterOssId(null);
- if (t.getSort() == null) {
- e.setSort(sort);
- } else {
- e.setSort(t.getSort());
- }
- sort++;
- contractProductPriceTierMapper.insert(e);
- }
- }
- private void copyPriceTiersToNewProduct(Long oldContractProductId, Long newContractId, Long newContractProductId) {
- List<CrmContractProductPriceTier> rows = contractProductPriceTierMapper.selectList(
- Wrappers.lambdaQuery(CrmContractProductPriceTier.class)
- .eq(CrmContractProductPriceTier::getContractProductId, oldContractProductId));
- if (rows == null || rows.isEmpty()) {
- return;
- }
- int s = 1;
- for (CrmContractProductPriceTier row : rows) {
- CrmContractProductPriceTier n = new CrmContractProductPriceTier();
- n.setContractId(newContractId);
- n.setContractProductId(newContractProductId);
- n.setStartDate(row.getStartDate());
- n.setEndDate(row.getEndDate());
- n.setPrice(row.getPrice());
- n.setLetterOssIds(row.getLetterOssIds());
- n.setLetterOssId(row.getLetterOssId());
- n.setSort(row.getSort() != null ? row.getSort() : s);
- contractProductPriceTierMapper.insert(n);
- s++;
- }
- }
- private void validateFrameworkContractForSubmitApproval(Long contractId, CrmContract contract) {
- if (!"framework".equals(contract.getContractType())) {
- return;
- }
- List<CrmContractProductVo> products = contractProductService.queryListByContractId(contractId);
- if (products == null || products.isEmpty()) {
- throw new ServiceException("框架合同必须维护产品明细");
- }
- List<CrmContractProductPriceTier> tiers = contractProductPriceTierMapper.selectList(
- Wrappers.lambdaQuery(CrmContractProductPriceTier.class)
- .eq(CrmContractProductPriceTier::getContractId, contractId));
- Map<Long, List<CrmContractProductPriceTier>> byProduct = tiers.stream()
- .collect(Collectors.groupingBy(CrmContractProductPriceTier::getContractProductId));
- for (CrmContractProductVo prod : products) {
- List<CrmContractProductPriceTier> list = byProduct.get(prod.getId());
- if (list == null || list.isEmpty()) {
- throw new ServiceException("框架合同产品「" + prod.getProductName() + "」至少维护一条调价区间(含调价函)");
- }
- for (CrmContractProductPriceTier t : list) {
- if (t.getStartDate() == null || t.getEndDate() == null) {
- throw new ServiceException("框架合同产品「" + prod.getProductName() + "」调价区间日期不能为空");
- }
- if (t.getStartDate().isAfter(t.getEndDate())) {
- throw new ServiceException("框架合同产品「" + prod.getProductName() + "」调价开始日期不能晚于结束日期");
- }
- if (t.getPrice() == null || t.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
- throw new ServiceException("框架合同产品「" + prod.getProductName() + "」调价含税单价必须大于0");
- }
- List<String> letterIds = parseLetterOssIdsFromEntity(t);
- if (letterIds.isEmpty()) {
- throw new ServiceException("框架合同产品「" + prod.getProductName() + "」每条调价区间须上传调价函附件");
- }
- }
- }
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean createFromQuote(Long quoteId, CrmContractBo bo) {
- // 1. 查询报价单
- CrmQuote quote = quoteMapper.selectById(quoteId);
- if (quote == null) {
- throw new ServiceException("报价单不存在");
- }
- // 2. 自动填充信息
- bo.setQuoteId(quoteId);
- bo.setCustomerId(quote.getCustomerId());
- bo.setOpportunityId(quote.getOpportunityId());
- if (bo.getAmount() == null) {
- bo.setAmount(quote.getTotalAmount());
- }
- if (bo.getOwnerUserId() == null) {
- bo.setOwnerUserId(quote.getOwnerUserId());
- }
- // 从报价单复制产品明细(如果没有传入)
- if ((bo.getProducts() == null || bo.getProducts().isEmpty()) && quote.getId() != null) {
- // TODO: 从报价单产品表复制产品明细
- log.info("从报价单复制产品明细,报价单ID: {}", quoteId);
- }
- // 3. 创建合同
- boolean flag = insertByBo(bo);
- // 4. 更新报价单状态为"已转合同"
- if (flag) {
- CrmQuote updateQuote = new CrmQuote();
- updateQuote.setId(quoteId);
- updateQuote.setStatus("converted"); // 已转化
- quoteMapper.updateById(updateQuote);
- log.info("从报价单创建合同成功,报价单ID: {}, 合同ID: {}", quoteId, bo.getId());
- }
- return flag;
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean createFromTemplate(Long templateId, CrmContractBo bo) {
- // 1. 查询模板
- CrmContractTemplate template = contractTemplateMapper.selectById(templateId);
- if (template == null) {
- throw new ServiceException("合同模板不存在");
- }
- if (template.getIsActive() == null || template.getIsActive() != 1) {
- throw new ServiceException("该模板已停用,无法使用");
- }
- // 2. 从模板填充信息
- if (bo.getContractType() == null) {
- bo.setContractType(template.getContractType());
- }
- if (bo.getCurrency() == null) {
- bo.setCurrency(template.getDefaultCurrency());
- }
- if (bo.getTaxRate() == null) {
- bo.setTaxRate(template.getDefaultTaxRate());
- }
- if (bo.getContractPeriod() == null) {
- bo.setContractPeriod(template.getDefaultPeriod());
- }
- // 填充合同条款
- if (StringUtils.isBlank(bo.getPaymentTerms())) {
- bo.setPaymentTerms(template.getPaymentTerms());
- }
- if (StringUtils.isBlank(bo.getDeliveryTerms())) {
- bo.setDeliveryTerms(template.getDeliveryTerms());
- }
- if (StringUtils.isBlank(bo.getContractTerms())) {
- bo.setContractTerms(template.getContractTerms());
- }
- // 填充乙方信息
- if (StringUtils.isBlank(bo.getPartyB())) {
- bo.setPartyB(template.getPartyB());
- }
- if (StringUtils.isBlank(bo.getPartyBAddress())) {
- bo.setPartyBAddress(template.getPartyBAddress());
- }
- if (StringUtils.isBlank(bo.getPartyBContact())) {
- bo.setPartyBContact(template.getPartyBContact());
- }
- if (StringUtils.isBlank(bo.getPartyBMobile())) {
- bo.setPartyBMobile(template.getPartyBMobile());
- }
- // 3. 创建合同
- boolean flag = insertByBo(bo);
- // 4. 更新模板使用次数
- if (flag) {
- CrmContractTemplate updateTemplate = new CrmContractTemplate();
- updateTemplate.setId(templateId);
- updateTemplate.setUsageCount((template.getUsageCount() == null ? 0 : template.getUsageCount()) + 1);
- contractTemplateMapper.updateById(updateTemplate);
- log.info("从模板创建合同成功,模板ID: {}, 合同ID: {}", templateId, bo.getId());
- }
- return flag;
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean signContract(Long id, LocalDate actualSignDate, String contractFile, String remark) {
- // 1. 查询合同
- CrmContract contract = baseMapper.selectById(id);
- if (contract == null) {
- throw new ServiceException("合同不存在");
- }
- // 2. 校验合同状态
- if (!"approved".equals(contract.getStatus())) {
- throw new ServiceException("只有审批通过的合同才能签订");
- }
- // 3. 更新合同信息
- CrmContract update = new CrmContract();
- update.setId(id);
- update.setActualSignDate(actualSignDate);
- update.setContractFile(contractFile);
- update.setStatus("executing"); // 签订后进入执行中状态
- update.setPerformanceStatus("normal");
- update.setPerformanceRate(BigDecimal.ZERO);
- if (StringUtils.isNotBlank(remark)) {
- update.setRemark(remark);
- }
- boolean flag = baseMapper.updateById(update) > 0;
- if (flag) {
- log.info("合同签订成功,合同ID: {}, 签订日期: {}", id, actualSignDate);
- // TODO: 发送通知给负责人和财务
- // TODO: 激活回款计划
- }
- return flag;
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean batchAssignOwner(Collection<Long> ids, Long ownerUserId, String remark) {
- if (ids == null || ids.isEmpty()) {
- throw new ServiceException("合同ID列表不能为空");
- }
- if (ownerUserId == null) {
- throw new ServiceException("负责人ID不能为空");
- }
- // 批量更新
- int successCount = 0;
- for (Long id : ids) {
- CrmContract contract = baseMapper.selectById(id);
- if (contract == null) {
- log.warn("合同不存在,跳过,ID: {}", id);
- continue;
- }
- CrmContract update = new CrmContract();
- update.setId(id);
- update.setOwnerUserId(ownerUserId);
- // 设置负责人姓名
- try {
- String ownerUserName = userService.selectNicknameById(ownerUserId);
- update.setOwnerUserName(ownerUserName);
- } catch (Exception e) {
- log.warn("获取负责人姓名失败,用户ID: {}", ownerUserId, e);
- update.setOwnerUserName(null);
- }
- if (StringUtils.isNotBlank(remark)) {
- update.setRemark(remark);
- }
- if (baseMapper.updateById(update) > 0) {
- successCount++;
- log.info("合同分配成功,合同ID: {}, 新负责人ID: {}", id, ownerUserId);
- }
- }
- log.info("批量分配合同负责人完成,成功: {}, 总数: {}", successCount, ids.size());
- return successCount > 0;
- }
- @Override
- public String downloadContractPdf(Long id) {
- // 查询合同
- CrmContractVo contract = queryById(id);
- if (contract == null) {
- throw new ServiceException("合同不存在");
- }
- // 返回合同文件路径(如果存在)
- if (StringUtils.isNotBlank(contract.getContractFile())) {
- return contract.getContractFile();
- }
- throw new ServiceException("合同PDF文件不存在");
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Boolean updateInvoiceAmount(Long contractId) {
- // 查询该合同的所有发票记录
- LambdaQueryWrapper<CrmInvoice> lqw = Wrappers.lambdaQuery();
- lqw.eq(CrmInvoice::getContractId, contractId);
- lqw.eq(CrmInvoice::getDelFlag, 0);
- List<CrmInvoice> invoices = invoiceMapper.selectList(lqw);
- // 计算已开票金额
- BigDecimal invoiceAmount = invoices.stream()
- .map(CrmInvoice::getAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- // 查询合同
- CrmContract contract = baseMapper.selectById(contractId);
- if (contract == null) {
- log.error("合同不存在,无法更新开票金额,合同ID: {}", contractId);
- return false;
- }
- // 更新合同开票金额
- CrmContract update = new CrmContract();
- update.setId(contractId);
- update.setInvoiceAmount(invoiceAmount);
- update.setUninvoiceAmount(contract.getAmount().subtract(invoiceAmount));
- boolean flag = baseMapper.updateById(update) > 0;
- if (flag) {
- log.info("更新合同开票金额成功,合同ID: {}, 已开票: {}", contractId, invoiceAmount);
- }
- return flag;
- }
- }
|