# 竞价系统-业务概述
# 高校端
# 待处理事项
待办事项之前是根据事项名称判断是否有对应的菜单开启,而发布审批和结果审批整合之后, 一审、二审、发布管理均不再拥有单独的菜单,因此不会显示其待办项。因此对待处理事项的逻辑进行了修改: 将判断依据从资源名称修改为授权码,待处理事项的JSON中增加perm字段,用于设置授权码。
# 填写申购单
- 检查自报价
- 如果高校开启了忽略自报价(已注册)检测,则可以填写在平台注册的供应商;否则不可以填写,默认为不可填写。
- 如果该自报价厂商出现以下情形,均不可填为自报价:
- 企业存在诚信问题(即欠费禁止竞价)
- 企业已被暂停竞价权限(除欠费禁止外)
- 企业状态异常(企业状态不是“正常”)
# 发布审批
- 发布审批可以由多个节点组成,比如西安电子科技大学就有院系审批、财务处审批、采购办审批、发布审核, 可以在高校端“系统设置”-“流程配置”-整合发布审批,对节点进行配置。
- 发布竞价页面的默认竞价结束时间是有系统自动生成的,默认是当前时间+参数设置的最小竞价时长+10分钟
- 可以通过order.publish.default.endBidTimeType.value,设置默认竞价结束时间的填充什么时间 (-1:建议竞价时间;0:最小竞价时间;1-7:周一到周日,如设置为6,则时间会设置为满足最小竞价时间的最近的周六)
- 可以通过order.publish.default.endBidTimeMoment.value,控制默认竞价结束时间的时刻 (如,西安交通大学设置为12:00:00)
- order.publish.endWorkTime.value,发布管理截止处理时间,超出此时间则顺延一天, 西安交通大学配置为17:00:00,即17点之后进入发布管理页面,则显示的默认时间在原来的基础上加上1天
- 发布竞价操作时,在会将申购单、申购单明细的全量数据交换到中心端。
# 截止竞价
竞价类型的申购单在到达竞价结束时间时,会根据竞价情况以及高校的参数配置来决定是转用户初选还是初选管理:
- 如果未开启自动转用户初选,则直接转初选管理
- 如果开启了自动转用户初选
- 如果竞价数满足参数指定数量(默认3家),则转用户初选;否则转初选管理。
- 如果开启了预算控制,还需要判断,每个明细都至少有一个满足预算的报价,且各明细最低价总和满足主单预算,才转用户初选; 否则转初选管理。
# 初选管理
- 延长竞价时,需要判断是否超过最大延长次数,如果超过,则无法继续延长
- 通过参数可以控制是否显示公司名称增加参数配置,默认为不显示 (不可见时,将名称用*号代替,并将companyId置空)
# 补充报价
- 初选管理中,如果申购单有部分明细的竞价数为0或者满足预算的竞价数为0,则这个申购单可以转到补充报价
- 补充报价功能只能查看申购明细信息,不能查看除自报价之外的竞价信息(防止报价泄露),只能补充自报价厂商信息。
- 补充报价相当是一个固定节点,目前只能从初选管理转入,提交后转到用户初选。
- 用户需要对所有可补充报价明细进行补充才能够提交
- 由于不是每一个申购单都需要经过此节点,因此在流程节点定义中也就不需要定义此节点了。
# 用户初选
用户初选提交后,计算申购单和申购单明细的成交总价,并根据当前汇率计算成交总价(人民币), 并记录汇率(主单和明细都记录)
(注:发布成交结果时,以上项目会重新计算)提交数据检查:
- 提交的明细数未达到预期数量(即有部分明细未提交)
- 查询所有处于当前节点且状态正常的明细,将数量与提交明细数量对比, 如果一致,还要检查两个明细ID集合是否一致,如果数量或集合不一致,则抛出异常
- 禁止修改非当前节点的明细:
- 禁止修改非本申购单下的报价的选择理由;
低价判定
每一项明细查询最低价,判断选中报价是否属于低价,如果仅允许单项低价,则直接判定为非低价;
如果允许综合低价,则首先判断所选报价是否为同一供应商,随后按所有报价按企业ID(为空时使用企业名称代替)、是否自报价进行分组计算报价总额(仅计算所有明细均报价的企业),判断选中报价总额是否属于低价超预算
每一项明细检查单项明细预算,同时累计总额检查整单预算。(预算如果为0,则认为未填写预算)自动审批
如果开启了自动审批,且所选数据符合低价判定,且所有明细未超预算,且成交金额在指定范围区间, 则系统会自动发布成交结果。提交确认 需要弹框供用户确认时,后台会抛出NeedConfirmException (继承ServiceException,因而声明了ServiceException的方法不需要改动函数签名) ,通过统一异常处理,返回错误码405和提示信息。
前端收到405时会弹框,弹框内容为异常信息,点击确认后才可完成提交。
# 结果审批
1.节点授权问题
- 由于结果审批对一审、二审等审批进行了整合,均调用同一接口,因此在高校资源中引入了节点的概念,
例如,整合结果审批接口,会添加“转交二审”、“发布成交结果”和“不通过”三个资源, 但有的学校需要在部分流程隐藏掉某个操作(例如,一审只能送交二审,而不能发布成交结果), 这时,就可以再添加一个流程为“一审”,状态为“禁用”的“发布成交结果”资源 - 菜单在获取按钮列表时,配置了流程的资源的判断优先级高于未配置流程的资源(判断为同一资源的依据:访问路径相同)
# 终止交易(管理员)
- 终止申购单
- 将所有未终止的申购明细(STATUS不为2或22)设置为终止状态(高校原因为2,供应商原因为22)
- 将成交总额、成交总额人民币归零,
- 如果有成交报价,则将成交报价状态设为R(高校原因)或E(违约)
- 终止单个采购项
- 将该采购项设置为终止状态(高校原因为2,供应商原因为22)
- 将成交总额、成交总额人民币归零,并从主单的成交总额、成交总额(人民币)中扣除此采购项的金额
- 如果有成交报价,则将成交报价状态设为R(高校原因)或E(违约)
- 检查是否所有明细均已终止,如果是,则将申购单一并终止
# 申请终止交易
- 单个采购项申请终止
- 将该明细项的状态修改为24(申请终止交易),并插入一条明细终止交易申请记录
- 整单申请终止
- 将所有非终止状态的明细状态修改为24,并对每条明细生成一条申请终止记录
- 将主单的状态修改为申请终止(24),同时插入一条明细ID为空的终止交易申请记录
- 重复提交申请 如果当前申请的项目已经存在待审批的记录,则之前待审批的申请记录会被自动驳回
# 终止审批
- 原则
- 只能对所有提交终止的明细一并审批,不能进行单项审批 (如果部分通过部分不通过的情况,则应直接驳回,由申请人重新发起申请)
- 终止审批流程配置
终止审批的流程可以通过高校端“系统配置”-“流程配置”-“终止审批流程”进行编辑。
默认配置为两个节点:终止一审,终止审批完成。 - 审批页面 终止交易审批页面,采购项分为待审批(已申请终止未审批)、已审批(已终止)、未申请(状态正常)三栏显示。
- 终止审批完成 所有终止审批的流程均审批通过后,将申请记录的结果设置为通过,并将申请的明细进行终止
- 终止审批驳回 如果驳回,则将申请记录结果设置为失败,并将申请的申购单、明细的流程、状态按照申请中的记录值进行恢复
# 申请再成交
原则 没有成交的明细(即bidId为空)不能申请再成交。 一旦申请再成交,申请再成交的采购项应该禁止收货、验收等操作。
提交
- 检查明细是否在指定的主单下
- 根据该明细生成申请记录,记录中会备份明细原本的状态、流程,报价原本的选择/不选理由
- 生成一条主单申请记录,用于备份主单的状态、流程(如果已经存在待审批的主单申请记录,则不再重复生成)
- 将主单状态设置为25(申请再成交)
- 根据申请时重新填写的理由,更新报价的选择/不选择理由
- 随后调用用户初选提交操作(ChooseSubmitOperation),则申购单会被转到一审
3.再成交审批通过
再成交审批的功能由结果审批(一审、二审等)承担,当该申购单从结果审批再次发布成交结果,
则视为再成交审批通过。
因此在发布成交结果操作中,如果申购单处于申请再成交,则会再调用再成交审批通过操作。 二者的权责归属是:- 再成交通过:
修改原成交报价的状态为R(高校原因)或E(供应商原因);将再成交申请记录标记为通过; 原成交报价的技术服务费减免(如果是高校原因)。 - 发布成交结果: 再成交新选择的报价的状态变更为Y;主单、明细的成交总额、流程、状态变更; 产生新的技术服务费(由中心端完成) 4.再成交审批驳回 当申购单从结果审批(可能是一审、二审等)被驳回到用户初选时(涉及操作:结果审批驳回、返回上一节点), 如果申购单状态为申请再成交,则将申购单、明细的流程、状态恢复为申请记录记录的原始状态, 并将审批记录设置为不通过。
- 再成交通过:
同时,主单、明细的成交总价、成交总价(人民币)需要按照原成交情况计算:
- 主单通过计算未终止明细下的已成交报价(即明细的bidId对应的cpBidOrder), 可以得到成交总额、成交总额(人民币)
- 明细可以直接采用原本成交报价cpBidOrder的总价、总价(人民币)
- 因此,要求申请再成交时不能修改原本成交报价的状态,否则再成交被驳回时将无法恢复原成交金额 (不能修改为N、E、R,只有再次发布成交结果,即再成交审批通过时才能修改), 也不能修改原本成交报价的总价(人民币)
# 合同审批
合同审批使用了工作流(Flowable)来实现,基本流程如下
- 供应商填写模板,提交后直接生成PDF,并上传OSS,并产生一条合同审批记录(co_contract_audit), 通过即时交换到高校端,审批的默认节点为WAIT(待处理)
- 高校端通过定时任务StartContAuditJob,处于WAIT(待处理)的审批记录开启工作流 (注意,如果定时任务被同时执行了多次,则会出现同一审批记录需要点击多次才能通过的问题)
- 随后进入工作流中规定的流程,其中审核人如果认为填写有问题,可以驳回,并给出修改意见
- 合同审批记录的流程变更是通过ContractAuditListener监听器,获取下一节点信息进行修改的
- 合同审批完成后供应商才能够下载正式版文件PDF(有水印),之前只能下载预览版(无水印,且页眉有预览版字样) 填写页面不能显示水印,否则供应商能够直接打印合同
工作流XML配置:
- 可以配置自动执行任务,即流程转到此任务时,自动调用指定的java类执行,如下所示,会自动执行BackModifyTask
<serviceTask id="Task_16vx7b9" name="驳回给供应商修改" activiti:class="com.soeasycenter.college.contaudit.task.BackModifyTask"> <incoming>SequenceFlow_0qn1l6l</incoming> <outgoing>SequenceFlow_1lxoysc</outgoing> </serviceTask>
- 针对用户审批操作增加监听器
<userTask xmlns:flowable="http://flowable.org/bpmn" id="CONTAU1" name="采购人确认" flowable:assignee="${createUserid}"> <extensionElements> <activiti:taskListener event="create" class="com.soeasycenter.college.contaudit.listener.ContractAuditListener" /> </extensionElements> <outgoing>SequenceFlow_1yyfmvi</outgoing> </userTask> <userTask xmlns:flowable="http://flowable.org/bpmn" id="CONTAU2" name="二审审批" flowable:candidateGroups="高校二审角色"> <extensionElements> <activiti:taskListener event="create" class="com.soeasycenter.college.contaudit.listener.ContractAuditListener" /> </extensionElements> <incoming>SequenceFlow_08goe9r</incoming> <outgoing>SequenceFlow_10vab5a</outgoing> </userTask>
# 管理端
技术服务费
查询指定申购单ID下的成交报价(BidStatus=Y)
每一条报价生成一条技术服务费,如果币种为外币,则使用汇率进行转换计算人民币; 币种名称从申购单获取,再根据币种名称从redis获取汇率(因此,获取最新汇率时,需要刷新redis)
(其中,申购单从solr获取,报价记录从数据库获取,因为申购单只需要取币种信息,不需要保证数据是最新的, 而报价记录因为要判断报价状态,因此需要保证数据是最新的(不能保证在技术服务费产生之前数据已经更新至solr))如果此报价已经存在技术服务费,且技术服务费总和大于0,则不再重新生成技术服务费
北京大学等开启减免(通过CoCollegeInfo中的是否收取服务费来控制)的高校同样生成技术服务费,只是设置收费为“已减免”
根据财务参数设置个人自购是否收费,如果不收费,则需要将将收费设置为“已减免”
根据供应商所属级别(cp_company_level),确定对应的收费比例 (pt_supplier_level表,需要分年度计算,用来规定每个级别的供应商对应的收费比例)
结算单
- 结算规则
- 正常结算(满足最低结算金额)
- 清算(到达清算时间,且满足最低清算金额)
- 减免的,以及老数据(remark是cancel)技术服务费不结算
- 如果企业账户内有历史结余,则会自动抵扣结算单金额
- 在最迟缴费期限后,系统通过定时任务进行检查
- 如果企业账户中有预充值金额,且金额充足,则在指定期限后直接使用余额抵扣,并将账单设置为已支付、已确认
- 如果账户内预充值余额不足,则不扣除金额,直接将该企业的竞价权限关闭(将bidRight设为0,欠费禁止)
- 结算规则
自动申请开票
- 结算单在到达申请发票截止期限(在确认支付时设置,默认为支付时间+30天)后,如果企业没有主动申请发票或者申请暂缓开具发票,则系统会自动生成发票申请。
- 其中,确认支付包含结余支付自动确认/人工确认/余额支付自动确认/减免确认
- 若企业填写了默认开票信息(cp_bank,ACCOUNT_TYPE=1(支付)),则按其填写信息申请, 且创建人名称为“自动申请开票”;
- 如果企业未填写,则按照电子发票进行开具,收件人电话、信息使用企业管理员账户信息,创建人名称标记为“默认开具”;
- 在平台财务点击开具前,企业依然可以对自动产生的申请进行修改。
- 当有一个结算单自动生成发票时,搜索该企业已支付未申请开票的结算单,合并申请开票
- 企业可以在申请开票页面,选择“暂缓申请”,则其截止期限在原本截止期限的基础上,再加上90天;
- 如果选择了多个账单进行暂缓申请,则按照其中最早的申请截止时间顺延90天
- 同一个结算单只能进行一次暂缓申请
- 以上提及的30天、以及是否发送短信,可以到管理端-财务管理-收款账号中进行设置。
- 如果开票信息的付款方式为9(第三方支付其他),或者收款公司为竞宝(JB),则不自动生成开票
- 结算单在到达申请发票截止期限(在确认支付时设置,默认为支付时间+30天)后,如果企业没有主动申请发票或者申请暂缓开具发票,则系统会自动生成发票申请。
可开票金额 企业可申请发票金额为实缴金额 - 退款金额,当可申请发票金额为0时,该账单不可申请开票。
其中退款金额,是企业向平台财务申请从其账户历史结余退款到其银行账户的金额,因此这个金额不应该开票, 所以在发生历史结余退款时,需要将退款金额绑定到已支付、未申请开票或已申请(待开)的结算单中 (优先绑定金额大的结算单,每单可绑定金额为 企业实际支付金额-已退款金额); 如果存在待开票的申请,则将申请金额扣除退款金额,如果结果小于等于0,则将发票申请标记为不开具 如果已经申请发票(开票中,已开),则将剩余未绑定的金额返回提示用户。 (注意:预付款退款则不需要进行以上处理)滞纳金
- 滞纳金开始实施时间:2020-2-10
- 每天陵城0点点调用一次重新计算未支付的结算单的滞纳金
- 当减免技术服务费时(包含自动和手动,其中手动减免时,已支付不能减免)
- 如果账单已支付,则在退还技术服务费的同时,还需要退还对应部分的滞纳金 (使用减免金额和截止支付时间、确认时间进行计算)
- 如果账单未支付,则按照减免后的账单应缴金额重新计算滞纳金, 滞纳金计算结束时间取截止支付时间和当前时间的较小值(否则未到截止支付时间的结算单会算多滞纳金)
- 手动添加/移除技术服务费
- 移除技术服务费时,同时需要移除该技术服务费对应的滞纳金(通过重新计算滞纳金来实现)
- 添加技术服务费时,由于有部分企业会提前交未结算的技术服务费, 比如企业账单是150元,已欠费,还有技术服务费10元未结算, 企业连带未结算的服务费交了160元, 这时候财务就会添加一条10元的技术服务费并确认收款, 而这新增加的10元技术服务费不应收取滞纳金。
# 商场
# 跳转登录
跳转信息分为两部分:用户信息和时间戳 。 其中,时间戳使用RSA非对称加密(时间戳作为用户信息加密的要素之一), 公钥定义在高校参数secret.toMallPublicKey.value,私钥定义在中心端参数mall.privateKey.value(collegeId=CENTER)。
公钥
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALybz/7WDOxEU62xvgXu4ZnrYuuDAWzn7yrNbafIqi1uOl0ObspejVJ6jJCF67siLRlih/Yw0D+6fPUG1vS3CrcCAwEAAQ==
私钥
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAvJvP/tYM7ERTrbG+Be7hmeti64MBbOfvKs1tp8iqLW46XQ5uyl6NUnqMkIXruyItGWKH9jDQP7p89QbW9LcKtwIDAQABAkEAjXrv/oCAtybWAmBnY7n632QUEwh1pEUjQl9RF2BOT76qWyXKkt9GliYEkHWYbNxAQRfhN1AcCE0wyESeqRnR4QIhAOxq6Q1bkLiGITSGCAtSsL+gIGQLzi0Bn89BAgDf/TYHAiEAzDskbdlWXx6iA68knL8BzThPrDO9boaxpS55Og2+2dECIQCo1jT3YCP7U3bVPr7x7yzgvOdE65VjWNybM27N1yjK8wIgE/J+11/P6NB0IIn9uHWLdoDWf0o6aU4skaadXoczKdECIFUe4wH5N3jxd7ieJgqas9p8G0iyvuKPtRXi/1om4Tmr
用户信息使用对称加密,密钥为固定密钥(与交换使用的密钥相同),iv使用时间戳在前面补充0,以补足16位的padding长度。
在跳转信息中添加高校地址,高校地址存放于两处:
用户信息(即,EntityUtils.getMallUser(), 登录态优先使用)和cookie(jumpBackTarget, 非登录态)。
当用户点击跳转回竞价网链接时,程序自动从以上两处取到高校地址,并自动重定向。
在开发过程中跳转商城出现异常时,异常始终无法抛出。检查原因发现是: feign全局异常捕捉类获取的body为Tomcat默认错误页面(且中文全部显示为?号),实际返回的字符串是html格式,而不是期望的JSON格式。 原因:feign在调用微服务时,会带上初始请求的header,当直接访问此接口时, 默认的accept是text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8 经过测试,以上accept会导致错误信息以HTML的方式返回, 因此修改了FeignConfiguration,当request.getAttribute("accept")不为空时(在rest-api进行设置),替换掉原本Header中的accept
# 隐藏价格
商城未登录时需要隐藏价格,具体实现为:搜索通过将storePrice和goodsPrice从fl指定返回字段中移除; 商品详情的同类商品、搜索推荐商品、首页推荐商品、首页楼层商品,由于使用了redis缓存,因此均通过循环移除storePrice和goodsPrice。
# 商场首页
首页楼层配置 首页楼层配置在shopping_goods_floor表中(目前没有管理菜单),可配置项为
- GF_CSS 楼层背景颜色 【示例】#6ad1a
- GF_DISPLAY 楼层是否显示 0:不显示;1:显示 【示例】1
- GF_GOODS_COUNT 楼层显示的推荐商品数量 【示例】7
- GF_NAME 楼层名称 【示例】电脑及配件
- GF_DESC 楼层宣传语 【示例】发现品质生活
- GF_PHOTO 楼层宣传图 【示例】http://gzgc-bid.oss-cn-hangzhou.aliyuncs.com/mall/2019/03/06/a0.png?Expires=316880318445&OSSAccessKeyId=LTAINaqxLlwPUh3q&Signature=JdW5ROaSMPb%2Br50A1GIUjReVy%2F4%3D
- GF_SEQUENCE 楼层顺序 【示例】
- GF_GC_GOODS 楼层对应的一级分类ID 【示例】2
- GF_GC_GOODS 推荐的二级分类词(JSON格式) 【示例】["电脑整机","电脑配件"," 电脑周边","网络设备"]
- GF_HOT_LIST 推荐的搜索关键词(JSON格式) 【示例】["台式机","笔记本","服务器","一体机","平板电脑","显示器","硬盘","显卡","内存","电源"]
其中点击二级分类词时,如果后台没有返回起对应的分类ID(表示该词语配置失误),则将该词语作为热搜词处理。
点击热搜词时,自动转到搜索页面,并将热搜词填充到搜索栏中。首页楼层配置在商城启动时会自动载入缓存,或者登录管理端,通过“系统设置”-“刷新Redis”-mallData/初始化商城数据刷新缓存。
通过调度中心的定时任务BEAN:mallPortalsHandler,每隔10分钟会刷新楼层的推荐商品、首页的推荐商品。
# 商品搜索
搜索结果右侧的推荐商品栏,显示内容所选一级分类下点击量最高的商品(4条), 如果尚未选择一级分类,则显示所有商品中点击量最高的商品。
# 商品详情
商品点击量处理
在访问商品详情页时,通过mq立即增加redis中的阅读数。如果此时redis中没有该商品的阅读数,或者阅读数被设为0, 则通过MQ发送一条延时消息(延时30分钟),该消息会在30分钟后才会传递给程序进行处理, 程序将redis中的阅读数持久化到数据库和solr中,并将redis中的阅读数设为0 redis中阅读数的过期时间应该高于延时消息的延时时间,否则消息处理时redis中的阅读数已经失效。 目前redis中阅读数的过期时间为1小时(延时消息的延时为30分钟)加入购物车
购物车使用了redis作为缓存,会定期从redis持久化到数据库(通过延时MQ来实现)。
将指定商品加入到购物车redis中,当用户再次获取购物车数据或加入购物车时, 如果redis中没有数据,则表示购物车数据已经被持久化到数据库,需要重新载入; 载入的同时发送延时消息(延时30min)以进行数据持久化,并从redis移除该购物车,防止用户下次登录时无法找回购物车数据的问题 (通过延时消息实现,处理延时消息时,如果redis已失效,则忽略)
每次从redis加载购物车数据时,购物车ID都会重新生成(避免加载出来的购物车项目对应的购物车ID不一致,不能确定用哪一个) 需要判断该商品是否已经存在购物车,当商品已存在指定类型的购物车时,则直接增加商品数量;如果不存在,则添加
每购物车类型对应一个购物车,不同购物车的相同商品,数量不累加
购物车商品数量修改时,判断是否超过库存、商品是否有效(优先从redis获取库存,获取不到时从solr获取库存),并返回商品当前库存、价格、商品是否有效 获取购物车全部信息时,需要检查修改商品价格、商品状态和商品库存(reids获取不到时,需要从solr获取,并更新至redis)
比价直采类型的购物车,只允许加入二级分类相同的商品; 且比价直采在调整数量时,所有商品的数量同步调整,如果有商品库存不足,则消息提示。
结算时,将商品数据从redis取出,生成订单,随后清空redis中的购物车商品数据,并将数据库中对应的购物车记录进行清除。
# 商场订单
购物车结算
购物车点击去结算时,需要将提交的商品放入redis,其中商品项的关键信息(如价格、库存等)会重新获取一并放入redis, 防止用户提交的商品的价格为旧数据,或者提交了库存不足的商品。
商品结算页面,从redis获取上一步保存的待提交商品信息,这个阶段价格锁定,即使价格发生变化,也不影响最终成交价格,有效期15分钟。
商品结算提交时,逐项检查商品的库存、状态,有任何一个商品未通过检查,则立即抛出异常,待结算商品redis立即失效, 页面退回购物车,用户需要重新提交。 在逐项检查商品库存的同时需要锁定库存(更新到redis和solr,立即更新redis,通过MQ更新solr), 如果最终生成了订单,则还需要更新到数据库;否则,需要释放锁定的库存(更新到redis和solr)
生成订单时,根据商家ID和配送方式进行拆单(比价直采除外,比价直采不拆单,所有明细直接挂在主单下,且主单可以不计算总价), 并计算订单及其子订单的总价。随后根据该订单生成竞价系统的申购单,并通过数据交换微服务返回到高校端。 其中,比价直采、带回竞价、单位直采(配置了需要结果审批时, 默认为需要)属于非立即成交,需要竞价系统发布成交结果时调用中心端接口,进行订单确认。 而单位直采(配置为直接发货时)、个人直采则属于立即成交(立即进入发货状态),则需要产生一条对应的技术服务费。
非立即成交的订单,生成订单时不需要不需要锁定/释放/扣除 库存,只需要判断库存是否满足。在竞价系统发布成交结果时, 需要调用中心端接口,进行订单确认(如果有库存不足等情况直接抛出异常,无法发布结果)。确认过程如下:
- 将初选选中的商城报价挑选出来,提取odId(即cpBidOrder.otherId),带上ofId(orderMain.otherId), 向中心端发送请求,对以上订单明细进行确认
- 商城调取这些订单的明细信息,逐项检查商品有效性并锁定库存,一旦某项订单明细检查未通过,直接抛出异常并释放库存
- 如果所有订单明细均检查通过,则扣除库存,确认订单明细、订单
- 如果此订单下存在未提交且未成交的订单明细,则将这些订单明细设置为取消交易
- 检查订单下的子订单,如果子订单没有成交明细(包括已成交和此次提交的成交明细)且未确认(包括已取消和已成交),则将该订单取消
- 技术服务费使用竞价系统申购单的方式收取,商城不单独处理。
非立即成交的订单在带回竞价系统后,有可能会修改申购数量(如比价直采、带回竞价),由于订单明细、报价信息已经生成, 则需要同步修改这些信息的申购数量。这些订单在申购人提交后会重新生成单号(C开头),会覆盖原本的M开头的商场单号, 新的单号会更新到对应的商场订单明细和报价信息中。
购物车结算完成后,需要从redis和数据库中将已结算的商品项清除
未成交订单的商品、商家信息修改处理
当商品价格、商品状态、商品库存、删除状态发生变更时,如果redis中有记录,则需要修改redis中的对应记录
由于非立即成交订单周期比较长,中间有可能出现商品修改价格情况,一旦出现该情况,应该立即修改对应的报价(单价、总价、总价人民币), 并修改对应的订单明细价格(单价、总价),并重新计算父级订单、主订单的总价
商品对应的订单明细,状态为正常才进行修改(已成交、已取消的订单明细价格不需要进行修改) 根据订单明细找到对应的报价,只有报价状态为N才需要修改(防止覆盖已成交的报价)
在发布成交结果时,如果出现价格不一致时,需要通过MQ将对应的订单、报价价格修改为最新价格
当企业信息(名称、省份、城市)发生变更时,需要更新至店铺表。如果店铺名称与原企业名称一致,则需要修改为新企业名称, 还需要修改该商家所有商品对应solr中的商家信息。目前只有企业变更信息审核通过时需要调用这一处理。
# 商场solr数据集
商场的数据集为shoppingGoods,但结构上与ShoppingGoods.java中定义的字段不完全一致, 这与其它数据集有所不同,需要注意一下。
为了提升搜索效率,shoppingGoods数据集除了商品字段外,还拥有店铺(ShoppingStore)、商品图(ShoppingPhoto)的部分字段, 因此在导入数据时需要通过联表SQL将店铺信息、商品主图信息一并导入到数据集中。
其中,商品详情(由企业使用富文本编辑)通常比较大,有的商品的详情甚至达到10-20MB,因此将商品详情拆分到到 shoppingGoodsDetail数据集中。而shoppingGoods数据集的导入SQL已经去掉了goodsDetail的字段返回。
但即便如此,部分商品还是由于详情过大,导致传输超时(前端),因此这种商品点击查看详情时是空白,一段时间后会报超时出错误。 为了解决此问题,后续可以考虑将商品详情放到OSS上,直接由浏览器访问OSS地址,不经过服务器。
# 企业端
# 其它问题
- 线程数据污染问题 由于在CpUserFilter中将用户信息设置到ThreadLocal,完成业务操作后,并没有清空ThreadLocal中的数据, 导致该线程在下一次从线程池唤醒后,ThreadLocal仍然保持之前的数据(即数据污染), 而商城未登录时则不会覆盖ThreadLocal中的数据,导致一定概率 (与被污染的线程比例有关,被污染的线程越多,概率越大)会出现未登录但ThreadLocal中的(用户信息)数据不为空的情况 因此在CpUserFilter的doFilter的最后一部分,调用了EntityUtils.removeUser()清除了用户信息数据,避免数据污染