九号项目
门店模块背景
商更侧重于指代那些从事商品买卖、批发、进出口等业务的企业或个人。一般是作为与公司直接合作的客户,所以我们也将商称为客户,商(客户)可能不直接面对最终消费者。
店一般是指直接面向消费者的零售场所。店铺主要通过销售商品给个人消费者来实现盈利。
店的经营重点在于选址、店面设计、客户服务以及促销策略等方面,以吸引顾客并提高销售额。
为了更方便的管理众多的门店信息,我们更多的是在商与店之间建立一个上下级关系,而公司则更多的是与商直接发生关系,我们将商称为一网,店称为二网。
暂不考虑直通车店。
门店管理系统功能概述
门店主要包括门店档案的新增(编辑)、门店变更以及门店关闭。
脑想问题
灰色框表示无需填写
灰色输入框表示无需填写?
门店档案新增的基础信息/店铺信息/代理信息/服务信息之间的关系
即门店档案新增的基础信息、店铺信息、代理信息、服务信息之间有必然的填写顺序联系么?
未填写基础信息之前,能否先填写代理信息、服务信息并进行保存?
因为看到基础信息、店铺信息、代理信息、服务信息等每一页右下角都是提交按钮,而不是下一页,所以确认一下
门店档案新增同一页面内的不同表格信息之间的关系
以基本信息页为例,假设基本信息页只有用户基本信息和门店实际控制人信息
- 两个表格的信息一次性全部校验,只要有一个输入框与要求不符,即整体拒绝信息的存储(新增),否则一次性存储成功
- 还是说各个表格之间的字段没有必然地联系,只要某一个表格的数据是正确的、完整的即支持先保存单个表格的数据
如果各个表格数据之间没有必然地联系,建议写一个多表新增(更新)接口,另外也建议再分成多个查询/新增(更新)的接口,因为一般除了第一次新建是一次性全部提交,后续单独修改某个表格信息的可能性更大,减少数据传输和校验,提高性能。
用户依然可以只点提交按钮,前端将每个标的数据作为一个单独的对象,使用v-model实现双向绑定,使用watch监听表格数据对象的变化,如果变化多,则调用统一的接口,如果是单表变化,则用户在点击提交按钮时,调用对应的接口提交表格信息,否则不调用相应接口
如果说是有关系的,还是拿用户基本信息和门店实际控制人信息为例,如何判定
表格单条数据
一般都应该保证单条数据的完整性吧,即不允许存储某条数据的中间状态。看到设计图中比如基础信息页门店营业执照信息存在中间状态,所以确认一下
下拉选项较少的枚举字段(如性别、营业状态等)是由后端提供查询接口还是前端自定义
请业务老师提供门店档案新增下拉框等输入框的完整选项
下拉框如由后端定义时,建议采用哪种方式
- 硬编码方式,将所有可能的值集范围直接写在枚举代码中(如果不存在多语言等问题,推荐,因为基本上单个下拉框选项值都很少且基本不改变,不需要专门建一张表)
- 建立一张表,通过redis缓存,注意一致性问题(如果支持多语言或内容经常改变,建议使用该方式,统一使用一张表,单加一个字段来区分所属枚举) 在本业务中,我更推荐方式一,因为下拉选项大部分都比较少,很少有超过五隔得,而且也不易变动。
对于下拉框属性是建议存储实际值还是code?
- 存储code更节省空间,但需在查询过程中做转换,逻辑实现起来更加繁琐,且数据不直接明了,不便于调试
- 相反,直接存储实际值相对浪费空间,但逻辑简单,数据清晰明了 无好坏之分,看项目组更侧重于节省容量还是性能
门店档案新建-相关实体
TIP
目前只定义了基础信息相关实体
用户基本信息
| 属性名 | 类型 | 是否指定值集范围 | 是否能为空 | 是否唯一 | 描述 | 备注 |
|---|---|---|---|---|---|---|
storeName | String | 否 | 否 | 是 | 门店名称 | 是否具有唯一性 |
storeAbbrev | String | 否 | 否 | 是 | 门店缩写 | 门店名称缩写,是否具有唯一性 |
storeCode | String | 否 | 否 | 是 | 门店编码 | 系统自动生成是调用外部接口还是指定的工具类,还是自定义,保证不重复即可 |
natureOfBusiness | String | 是 | 否 | 否 | 经营范围 | 建立枚举,通过枚举值校验入参 |
vehicleClassification | String | 是 | 否 | 否 | 车辆分类 | 建立枚举,通过枚举值校验入参 |
storeType | String | 是 | 否 | 否 | 门店类型 | 建立枚举,通过枚举值校验入参 |
belongGroup | Long | 是 | 否 | 否 | 所属客户组名称 | 所有值均需来自客户组表,直接存储客户组表对应数据的主键值 |
customerGroupCode | String | 是 | 否 | 否 | 所属客户组编码 | 在客户组编码唯一的情况下,建议所属客户组置灰吧,前端通过客户组编码连带出来。我给你一个接口,下拉框滑动,分页查询客户组名和客户组编码,但该结果在客户组表的客户组编码不可变且客户组名称可变的情况下,可能导致数据不一致问题,只能实现最终一致性,或客户组那边同步修改 |
storeAuthCode | String | 是 | 否 | 是 | 门店授权码 | 门店授权码的生成逻辑,与 storeCode 字段同问 |
applicationType | String | 是 | 否 | 否 | 申请类型 | 申请类型存储枚举硬编码? 1 新建 2 移店 |
originalStore | 否 | 是 | 否 | 原门店 | 原门店是指原门店地址? 当申请类型为移店时,原门店字段不能为空? | |
createTime | Date | 否 | 否 | 否 | 创建时间 | 直接使用 BaseEntity 的 createTime 数据创建时间 字段 |
closeReson | String | 是 | 是 | 否 | 闭店原因 | 枚举。当申请类型为移店时必填?具体可选原因都有哪些? |
businessStatus | String | 是 | 否 | 否 | 营业状态 | 枚举 |
storeStatus | String | 是 | 否 | 否 | 门店状态 | 枚举 |
freezeReason | String | 是 | 否 | 否 | 冻结原因 | 枚举,冻结原因无论什么时间必填? |
closeTime | Date | 否 | 是 | 否 | 闭店时间 | 枚举,闭店时间无论什么时间必填? |
auditTime | Date | 否 | 是 | 否 | 审核时间 | 审核时间商户不用填写吧,这个字段只是提交后审核后查询展示吧? |
businessStartTime | Date | 否 | 否 | 否 | 营业开始时间 | |
annualOperatingHours | 是 | 否 | 否 | 全年经营时长 | 是否支持小数 |
门店实控人
门店实控人信息都由商户自己填写么?后台是否需要校验,比如验证实际控制人姓名与身份证号是否匹配等
| 属性名 | 类型 | 是否指定值集范围 | 是否能为空 | 是否唯一 | 描述 | 备注 |
|---|---|---|---|---|---|---|
actualControllerName | String | 否 | 否 | 是 | 实际控制人姓名 | |
actualControllerIdCard | String | 否 | 否 | 是 | 实际控制人身份证号 | |
controllerPhone | String | 否 | 否 | 是 | 实控人手机号 | |
relationshipWithLegalPerson | String | 是 | 否 | 否 | 与法人关系 | 建立枚举,通过枚举值校验入参 |
status | String | 是 | 否 | 否 | 状态 | 建立枚举,通过枚举值校验入参 |
门店营业执照信息
| 属性名 | 类型 | 是否指定值集范围 | 是否能为空 | 是否唯一 | 描述 | 备注 |
|---|---|---|---|---|---|---|
legalPersonName | String | 否 | 否 | 否 | 法人姓名 | |
legalPersonPhone | String | 否 | 否 | 否 | 法人手机号 | |
legalPersonIdCard | String | 否 | 否 | 否 | 法人身份证号 | |
legalPersonIdcardStartDate | Date | 否 | 否 | 否 | 法人身份证有效期-开始时间 | |
legalPersonIdcardEndDate | Date | 否 | 否 | 否 | 法人身份证有效期-结束时间 | |
legalPersonIdcardValidityPeriodType | String | 是 | 否 | 否 | 法人证件有效期类型 | 枚举 |
taxpayerIdentificationNumber | String | 否 | 否 | 是 | 纳税人识别号 | |
businessLicenseName | String | 否 | 否 | 否 | 营业执照名称 | |
registeredPhone | String | 否 | 否 | 否 | 注册电话 | |
registeredAddress | String | 否 | 否 | 否 | 注册地址 | |
isRegisteredAddressSameAsOperational | boolean | 是 | 否 | 否 | 注册地址是否等于实际经营地址 | 既有注册地址也有经营地址,这个字段还有必要么?非是即否,不可能为空,选用基本类型即可 |
actualBusinessAddress | String | 否 | 否 | 否 | 实际经营地址 | |
longitude | decimal | 否 | 否 | 否 | 经度,是否保留小数 | |
latitude | decimal | 否 | 否 | 否 | 纬度,是否保留小数 | |
registeredCapital | decimal | 否 | 否 | 否 | 注册资金(万) ,是否保留小数 | |
businessLicenseType | String | 是 | 否 | 否 | 营业执照有效期类型 | 枚举 |
businessLicenseStartDate | Date | 否 | 否 | 否 | 营业执照有效期开始日期 | |
businessLicenseEndDate | Date | 否 | 否 | 否 | 营业执照有效期结束日期 | |
electronicSignatureContact | String | 否 | 否 | 否 | 电子签章联系人 | |
electronicSignaturePhone | String | 否 | 否 | 否 | 电子签章电话 | |
electronicSignatureIdCard | String | 否 | 否 | 否 | 电子签章身份证 | |
electronicSignatureEmail | String | 否 | 否 | 否 | 电子签章邮箱 |
门店仓库地址信息
| 属性名 | 类型 | 是否指定值集范围 | 是否能为空 | 是否唯一 | 描述 | 备注 |
|---|---|---|---|---|---|---|
addressType | String | 是 | 否 | 否 | 地址类型 | 枚举 |
warehouseCode | String | 否 | 否 | 是 | 仓库编码 | 仓库编码商户填写还是逻辑生成 |
area | decimal | 否 | 否 | 是 | 面积 | 单位是什么?保留几位 |
province | String | 是 | 否 | 否 | 所属省份 | 枚举 |
city | String | 是 | 否 | 否 | 所属市 | 枚举 |
county | String | 是 | 否 | 否 | 所属区县 | 枚举 |
detailedAddress | String | 否 | 否 | 否 | 详细地址 | |
contactPerson | String | 否 | 否 | 否 | 联系人 | |
contactPhone | String | 否 | 否 | 否 | 联系电话 | |
isDefaultDeliveryAddress | boolean | 否 | 否 | 否 | 是否默认送货地址 | 非是即否,使用默认类型 |
门店银行
是否需要校验数据的真实性,保持四者数据的一致性,还是等转账时银行那边自己返回对应结果?
| 属性名 | 类型 | 是否指定值集范围 | 是否能为空 | 是否唯一 | 描述 | 备注 |
|---|---|---|---|---|---|---|
unionPayNumber | String | 否 | 否 | 否 | 银联号 | |
bankName | String | 否 | 否 | 否 | 银行名称 | |
paymentBankAccount | String | 否 | 否 | 否 | 付款银行账户 | |
cardholderName | String | 是 | 否 | 否 | 持卡人名称 |
软件费用缴纳情况
| 属性名 | 类型 | 是否指定值集范围 | 是否能为空 | 是否唯一 | 描述 | 备注 |
|---|---|---|---|---|---|---|
paidSoftwareType | String | 否 | 否 | 否 | 付费软件类型 | 枚举 |
premiumReceived | decimal | 否 | 否 | 否 | 缴纳金额 | 涉及金额的一定是decimal了,保留两位 |
effectiveDate | Date | 否 | 否 | 是 | 生效日期 | |
dueDate | Date | 是 | 否 | 否 | 到期日期 |
门店人员档案
| 属性名 | 类型 | 是否指定值集范围 | 是否能为空 | 是否唯一 | 描述 | 备注 |
|---|---|---|---|---|---|---|
staffCode | String | 否 | 否 | 是 | 人员编码 | |
staffName | String | 否 | 否 | 否 | 人员名称 | |
post | String | 否 | 否 | 否 | 岗位 | 枚举 |
mobilePhone | String | 否 | 否 | 否 | 手机号 | |
orgIds | String | 是 | 否 | 否 | 所属机构 | 所属机构是下拉框形式么?从哪里获取数据 |
natureOfBusiness | String | 是 | 否 | 否 | 经营范围 | 经营范围是什么类型,下拉框? |
合同列表
| 属性名 | 类型 | 是否指定值集范围 | 是否能为空 | 是否唯一 | 描述 | 备注 |
|---|---|---|---|---|---|---|
contractCode | String | 否 | 否 | 是 | 合同编码 | 是商户直接填写么?具体生成逻辑 |
oaContractCode | String | 否 | 否 | 是 | OA合同编码 | 是商户直接填写么?具体生成逻辑 |
contractName | String | 否 | 否 | 否 | 合同名称 | |
contractType | String | 是 | 否 | 否 | 合同类型 | 枚举 |
contractSigningDate | String | 否 | 否 | 否 | 合同签订日期 | |
contractStatus | String | 是 | 否 | 否 | 合同状态 | 枚举 |
线上运营平台账号
| 属性名 | 类型 | 是否指定值集范围 | 是否能为空 | 是否唯一 | 描述 | 备注 |
|---|---|---|---|---|---|---|
platformName | String | 否 | 否 | 否 | 平台名称 | |
accountCode | String | 否 | 否 | 是 | 账户编码 | |
accountName | String | 否 | 否 | 否 | 账户名称 |
门店档案新建-接口设计
WARNING
因为上述部分脑想问题还未与业务等老师及负责人沟通,所以未做详细设计。 问题沟通解决后会更详细的进行接口设计和开发
但大致想法如下: 依然是 Spring + Mybatis-Plus + MySQL
如果最后新建只开发一个接口,所有表单的存储共用一个接口,需要注意不同表之间的事务一致性问题
如果最终是开发多个接口,即单表数据存储对应单表接口,则无需关注。
如果某个注册信息需要分别调用多个接口分别单表存储的话,需要小心并发问题,可能A用户和B用户的数据本身都没问题,但可能A用户线调了存储basic(用户基本信息的表),而B用户先调用了存储contract(合同基本信息的表),当A试图存储contract数据时发现与B的数据冲突问题,而B试图存储basic数据时发现也与A的数据冲突而造成两者均存储失败的问题。 解决方式
- 队列串行化执行
- 在redis中将一个唯一的有意义的名称作为key,使用setnx,如果设置成功,则执行,否则失败
虽然门店档案新增与客户档案新增存在多个相似对象,但之间依然有差距,无法完全复用。 拿合同信息举例,如果双方共用一张表,必然都会留下某些空白字段,而且需要添加一列来区分是商的合同还是店的合同,客户档案新增才开发完毕,处于测试阶段,还是不动为好。
计划明天从develop新建一个feature分支,并在ninebot-beforesale下新建一个module,名为ninebot-store,与ninebot-merchant平级,自己在开发时,尽可能将都存在的对象如合同对象建立一个abstract,并提取一些公用的属性,这样未来优化时方便抽取出来
