301 lines
11 KiB
Markdown
301 lines
11 KiB
Markdown
|
|
# 企业认证信息自动填充实现总结
|
||
|
|
|
||
|
|
## 概述
|
||
|
|
|
||
|
|
根据用户需求,公司名称和纳税人识别号应该从用户的企业认证信息中自动获取,用户不能修改,系统自动回显。这样可以确保开票信息与企业认证信息保持一致,提高数据准确性和用户体验。
|
||
|
|
|
||
|
|
## 核心设计思路
|
||
|
|
|
||
|
|
### 1. 数据来源
|
||
|
|
- **公司名称**:从`EnterpriseInfo.CompanyName`获取
|
||
|
|
- **纳税人识别号**:从`EnterpriseInfo.UnifiedSocialCode`获取(统一社会信用代码)
|
||
|
|
|
||
|
|
### 2. 权限控制
|
||
|
|
- 公司名称和纳税人识别号为**只读字段**
|
||
|
|
- 用户不能在前端修改这两个字段
|
||
|
|
- 系统自动从企业认证信息中填充
|
||
|
|
|
||
|
|
### 3. 业务逻辑
|
||
|
|
- 用户必须先完成企业认证才能创建开票信息
|
||
|
|
- 开票信息中的公司名称和纳税人识别号始终与企业认证信息保持一致
|
||
|
|
- 支持用户修改其他开票信息字段(银行信息、地址、电话、邮箱等)
|
||
|
|
|
||
|
|
## 主要变更
|
||
|
|
|
||
|
|
### 1. 服务层更新
|
||
|
|
|
||
|
|
#### `UserInvoiceInfoService`接口和实现
|
||
|
|
```go
|
||
|
|
// 新增依赖
|
||
|
|
type UserInvoiceInfoServiceImpl struct {
|
||
|
|
userInvoiceInfoRepo repositories.UserInvoiceInfoRepository
|
||
|
|
userRepo user_repo.UserRepository // 新增:用户仓储依赖
|
||
|
|
}
|
||
|
|
|
||
|
|
// 更新构造函数
|
||
|
|
func NewUserInvoiceInfoService(
|
||
|
|
userInvoiceInfoRepo repositories.UserInvoiceInfoRepository,
|
||
|
|
userRepo user_repo.UserRepository, // 新增参数
|
||
|
|
) UserInvoiceInfoService {
|
||
|
|
return &UserInvoiceInfoServiceImpl{
|
||
|
|
userInvoiceInfoRepo: userInvoiceInfoRepo,
|
||
|
|
userRepo: userRepo,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### `GetUserInvoiceInfo`方法更新
|
||
|
|
```go
|
||
|
|
func (s *UserInvoiceInfoServiceImpl) GetUserInvoiceInfo(ctx context.Context, userID string) (*entities.UserInvoiceInfo, error) {
|
||
|
|
// 获取开票信息
|
||
|
|
info, err := s.userInvoiceInfoRepo.FindByUserID(ctx, userID)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("获取用户开票信息失败: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 获取用户企业认证信息
|
||
|
|
user, err := s.userRepo.GetByID(ctx, userID)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("获取用户信息失败: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 如果没有找到开票信息记录,创建新的实体
|
||
|
|
if info == nil {
|
||
|
|
info = &entities.UserInvoiceInfo{
|
||
|
|
ID: uuid.New().String(),
|
||
|
|
UserID: userID,
|
||
|
|
CompanyName: "",
|
||
|
|
TaxpayerID: "",
|
||
|
|
BankName: "",
|
||
|
|
BankAccount: "",
|
||
|
|
CompanyAddress: "",
|
||
|
|
CompanyPhone: "",
|
||
|
|
ReceivingEmail: "",
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 如果用户有企业认证信息,自动填充公司名称和纳税人识别号
|
||
|
|
if user.EnterpriseInfo != nil {
|
||
|
|
info.CompanyName = user.EnterpriseInfo.CompanyName
|
||
|
|
info.TaxpayerID = user.EnterpriseInfo.UnifiedSocialCode
|
||
|
|
}
|
||
|
|
|
||
|
|
return info, nil
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### `CreateOrUpdateUserInvoiceInfo`方法更新
|
||
|
|
```go
|
||
|
|
func (s *UserInvoiceInfoServiceImpl) CreateOrUpdateUserInvoiceInfo(ctx context.Context, userID string, invoiceInfo *value_objects.InvoiceInfo) (*entities.UserInvoiceInfo, error) {
|
||
|
|
// 获取用户企业认证信息
|
||
|
|
user, err := s.userRepo.GetByID(ctx, userID)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("获取用户信息失败: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 检查用户是否有企业认证信息
|
||
|
|
if user.EnterpriseInfo == nil {
|
||
|
|
return nil, fmt.Errorf("用户未完成企业认证,无法创建开票信息")
|
||
|
|
}
|
||
|
|
|
||
|
|
// 创建新的开票信息对象,自动从企业认证信息中获取公司名称和纳税人识别号
|
||
|
|
updatedInvoiceInfo := &value_objects.InvoiceInfo{
|
||
|
|
CompanyName: user.EnterpriseInfo.CompanyName, // 从企业认证信息获取
|
||
|
|
TaxpayerID: user.EnterpriseInfo.UnifiedSocialCode, // 从企业认证信息获取
|
||
|
|
BankName: invoiceInfo.BankName, // 用户输入
|
||
|
|
BankAccount: invoiceInfo.BankAccount, // 用户输入
|
||
|
|
CompanyAddress: invoiceInfo.CompanyAddress, // 用户输入
|
||
|
|
CompanyPhone: invoiceInfo.CompanyPhone, // 用户输入
|
||
|
|
ReceivingEmail: invoiceInfo.ReceivingEmail, // 用户输入
|
||
|
|
}
|
||
|
|
|
||
|
|
// 验证和保存逻辑...
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. 应用服务层更新
|
||
|
|
|
||
|
|
#### `InvoiceInfoResponse`DTO更新
|
||
|
|
```go
|
||
|
|
type InvoiceInfoResponse struct {
|
||
|
|
CompanyName string `json:"company_name"` // 从企业认证信息获取,只读
|
||
|
|
TaxpayerID string `json:"taxpayer_id"` // 从企业认证信息获取,只读
|
||
|
|
BankName string `json:"bank_name"` // 用户可编辑
|
||
|
|
BankAccount string `json:"bank_account"` // 用户可编辑
|
||
|
|
CompanyAddress string `json:"company_address"` // 用户可编辑
|
||
|
|
CompanyPhone string `json:"company_phone"` // 用户可编辑
|
||
|
|
ReceivingEmail string `json:"receiving_email"` // 用户可编辑
|
||
|
|
IsComplete bool `json:"is_complete"`
|
||
|
|
MissingFields []string `json:"missing_fields,omitempty"`
|
||
|
|
// 字段权限标识
|
||
|
|
CompanyNameReadOnly bool `json:"company_name_read_only"` // 公司名称是否只读
|
||
|
|
TaxpayerIDReadOnly bool `json:"taxpayer_id_read_only"` // 纳税人识别号是否只读
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### `GetUserInvoiceInfo`方法更新
|
||
|
|
```go
|
||
|
|
func (s *InvoiceApplicationServiceImpl) GetUserInvoiceInfo(ctx context.Context, userID string) (*dto.InvoiceInfoResponse, error) {
|
||
|
|
userInvoiceInfo, err := s.userInvoiceInfoService.GetUserInvoiceInfo(ctx, userID)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
// 检查用户是否有企业认证信息
|
||
|
|
user, err := s.userRepo.GetByID(ctx, userID)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("获取用户信息失败: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 设置只读标识
|
||
|
|
companyNameReadOnly := user.EnterpriseInfo != nil
|
||
|
|
taxpayerIDReadOnly := user.EnterpriseInfo != nil
|
||
|
|
|
||
|
|
return &dto.InvoiceInfoResponse{
|
||
|
|
CompanyName: userInvoiceInfo.CompanyName,
|
||
|
|
TaxpayerID: userInvoiceInfo.TaxpayerID,
|
||
|
|
BankName: userInvoiceInfo.BankName,
|
||
|
|
BankAccount: userInvoiceInfo.BankAccount,
|
||
|
|
CompanyAddress: userInvoiceInfo.CompanyAddress,
|
||
|
|
CompanyPhone: userInvoiceInfo.CompanyPhone,
|
||
|
|
ReceivingEmail: userInvoiceInfo.ReceivingEmail,
|
||
|
|
IsComplete: userInvoiceInfo.IsComplete(),
|
||
|
|
MissingFields: userInvoiceInfo.GetMissingFields(),
|
||
|
|
// 字段权限标识
|
||
|
|
CompanyNameReadOnly: companyNameReadOnly, // 公司名称只读(从企业认证信息获取)
|
||
|
|
TaxpayerIDReadOnly: taxpayerIDReadOnly, // 纳税人识别号只读(从企业认证信息获取)
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### `UpdateInvoiceInfoRequest`DTO更新
|
||
|
|
```go
|
||
|
|
type UpdateInvoiceInfoRequest struct {
|
||
|
|
CompanyName string `json:"company_name"` // 公司名称(从企业认证信息获取,用户不可修改)
|
||
|
|
TaxpayerID string `json:"taxpayer_id"` // 纳税人识别号(从企业认证信息获取,用户不可修改)
|
||
|
|
BankName string `json:"bank_name"` // 银行名称
|
||
|
|
CompanyAddress string `json:"company_address"` // 公司地址
|
||
|
|
BankAccount string `json:"bank_account"` // 银行账户
|
||
|
|
CompanyPhone string `json:"company_phone"` // 企业注册电话
|
||
|
|
ReceivingEmail string `json:"receiving_email" binding:"required,email"` // 发票接收邮箱
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### `UpdateUserInvoiceInfo`方法更新
|
||
|
|
```go
|
||
|
|
func (s *InvoiceApplicationServiceImpl) UpdateUserInvoiceInfo(ctx context.Context, userID string, req UpdateInvoiceInfoRequest) error {
|
||
|
|
// 创建开票信息对象,公司名称和纳税人识别号会被服务层自动从企业认证信息中获取
|
||
|
|
invoiceInfo := value_objects.NewInvoiceInfo(
|
||
|
|
"", // 公司名称将由服务层从企业认证信息中获取
|
||
|
|
"", // 纳税人识别号将由服务层从企业认证信息中获取
|
||
|
|
req.BankName,
|
||
|
|
req.BankAccount,
|
||
|
|
req.CompanyAddress,
|
||
|
|
req.CompanyPhone,
|
||
|
|
req.ReceivingEmail,
|
||
|
|
)
|
||
|
|
|
||
|
|
_, err := s.userInvoiceInfoService.CreateOrUpdateUserInvoiceInfo(ctx, userID, invoiceInfo)
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. 依赖注入更新
|
||
|
|
|
||
|
|
#### 容器配置更新
|
||
|
|
```go
|
||
|
|
// 用户开票信息服务
|
||
|
|
fx.Annotate(
|
||
|
|
finance_service.NewUserInvoiceInfoService,
|
||
|
|
fx.ParamTags(
|
||
|
|
`name:"userInvoiceInfoRepo"`,
|
||
|
|
`name:"userRepo"`, // 新增依赖
|
||
|
|
),
|
||
|
|
fx.ResultTags(`name:"userInvoiceInfoService"`),
|
||
|
|
),
|
||
|
|
```
|
||
|
|
|
||
|
|
## 业务优势
|
||
|
|
|
||
|
|
### 1. 数据一致性
|
||
|
|
- **企业认证信息统一**:开票信息中的公司名称和纳税人识别号始终与企业认证信息保持一致
|
||
|
|
- **避免数据冲突**:用户无法手动输入错误的企业信息
|
||
|
|
- **数据准确性**:确保开票信息的准确性
|
||
|
|
|
||
|
|
### 2. 用户体验
|
||
|
|
- **自动填充**:用户无需重复输入企业基本信息
|
||
|
|
- **简化操作**:减少用户输入错误
|
||
|
|
- **清晰标识**:前端可以明确显示哪些字段是只读的
|
||
|
|
|
||
|
|
### 3. 业务逻辑
|
||
|
|
- **认证前置**:必须先完成企业认证才能创建开票信息
|
||
|
|
- **权限控制**:企业核心信息不可修改,只能通过重新认证更新
|
||
|
|
- **审计追踪**:可以追踪企业信息的变更历史
|
||
|
|
|
||
|
|
## 工作流程
|
||
|
|
|
||
|
|
### 1. 用户企业认证
|
||
|
|
```
|
||
|
|
用户完成企业认证 → 系统保存EnterpriseInfo → 包含CompanyName和UnifiedSocialCode
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. 获取开票信息
|
||
|
|
```
|
||
|
|
用户访问开票信息页面 → 系统获取EnterpriseInfo → 自动填充CompanyName和TaxpayerID → 返回只读标识
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. 更新开票信息
|
||
|
|
```
|
||
|
|
用户修改开票信息 → 系统忽略CompanyName和TaxpayerID输入 → 从EnterpriseInfo获取这两个字段 → 保存其他用户输入字段
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. 申请开票
|
||
|
|
```
|
||
|
|
用户申请开票 → 系统验证企业认证状态 → 使用企业认证信息创建快照 → 保存申请记录
|
||
|
|
```
|
||
|
|
|
||
|
|
## 前端适配建议
|
||
|
|
|
||
|
|
### 1. 字段显示
|
||
|
|
- 公司名称和纳税人识别号显示为**只读状态**
|
||
|
|
- 添加视觉标识(如灰色背景、禁用状态)
|
||
|
|
- 显示提示信息:"此信息来自企业认证"
|
||
|
|
|
||
|
|
### 2. 表单验证
|
||
|
|
- 移除公司名称和纳税人识别号的前端验证
|
||
|
|
- 保留其他字段的验证规则
|
||
|
|
- 添加企业认证状态检查
|
||
|
|
|
||
|
|
### 3. 错误处理
|
||
|
|
- 如果用户未完成企业认证,显示引导完成认证的提示
|
||
|
|
- 如果企业认证信息缺失,显示相应的错误信息
|
||
|
|
|
||
|
|
## 技术实现要点
|
||
|
|
|
||
|
|
### 1. 数据获取
|
||
|
|
- 在服务层统一从企业认证信息中获取公司名称和纳税人识别号
|
||
|
|
- 确保数据的一致性和准确性
|
||
|
|
|
||
|
|
### 2. 权限控制
|
||
|
|
- 通过DTO字段标识控制前端显示
|
||
|
|
- 在服务层忽略用户对只读字段的输入
|
||
|
|
|
||
|
|
### 3. 错误处理
|
||
|
|
- 检查用户企业认证状态
|
||
|
|
- 提供清晰的错误信息和解决建议
|
||
|
|
|
||
|
|
### 4. 性能优化
|
||
|
|
- 合理使用缓存减少数据库查询
|
||
|
|
- 优化关联查询性能
|
||
|
|
|
||
|
|
## 总结
|
||
|
|
|
||
|
|
通过实现企业认证信息自动填充功能,我们成功实现了:
|
||
|
|
|
||
|
|
1. ✅ **数据一致性**:开票信息与企业认证信息保持一致
|
||
|
|
2. ✅ **用户体验**:自动填充企业基本信息,减少用户输入
|
||
|
|
3. ✅ **权限控制**:企业核心信息不可修改,确保数据准确性
|
||
|
|
4. ✅ **业务逻辑**:必须先完成企业认证才能使用开票功能
|
||
|
|
5. ✅ **前端适配**:提供清晰的字段权限标识和用户提示
|
||
|
|
|
||
|
|
这种设计既保证了数据的准确性和一致性,又提供了良好的用户体验,是一个优秀的业务功能实现。
|