diff --git a/docs/BUSINESS_REQUIREMENTS.md b/docs/BUSINESS_REQUIREMENTS.md new file mode 100644 index 0000000..defb49b --- /dev/null +++ b/docs/BUSINESS_REQUIREMENTS.md @@ -0,0 +1,24 @@ +# 商务原型需求约定 + +本文件记录跨版本核对时需要保留的产品决定。商务按原型的 **V2 原型说明**核对;OA 按 OA 原型的说明核对,OA 本轮只实现后端。 + +## 已确认决定 + +- **激活归属**:审核通过只进入观察期,不改渠道归属;累计流水达标后才换绑。此项以用户明确更正为准,覆盖商务原型中“审核通过后即归属”的文字。 +- **暂缓页面(2026-11-07)**:原型包中有完整的 `game-payment.html`(付费分析)及 `game-retention.html`(留存分析),V2 清单将它们列为菜单外页面。用户已确认“先放着,记一下”。本轮不实现这两页、不新增入口;后续恢复时需确认入口和统计口径,不能直接当作已完成。 +- **OA 分支**:在 `oa` 最新 `master` 基础上创建 `codex/sw-new-oa-integration`,修改合入 `dev` 后更新测试服;不使用尚未迁移商务业务的 `oa-new`。 + +## 同一原型内的冲突处理 + +- 商务绩效矩阵:采用 2026-11-03 的“组内排序、按显示成员及其下级并集计算矩阵合计”;上方卡片仍统计权限范围全量。2026-11-02 的“整表跨组排序、不随筛选的矩阵合计”是旧描述。 +- 小组:2026-11-03 已移除组长及工作台团队目标卡。首页仅保留“我的目标”;团队目标在小组列表设置、在小组绩效复盘。 +- 目标按钮:采用商务列表当前详细说明,已设置时显示目标摘要;不采用较早改动汇总中的“按钮恒定不显示已设值”。 +- 新增与编辑:新增子商务的联系电话、入职日期选填,未填日期默认当天;编辑时姓名与入职日期必填。分别遵循各自弹窗的说明。 +- 演示数据与原型导航工具:原型中的模拟入池按钮、随机数据、版本选择、原型说明/更新记录/改动汇总悬浮按钮用于设计评审,不作为业务系统生产功能。 + +## 数据语义 + +- 激活列表、申请池、绑定期注册与流水读 OA 对应 SDK 数据源,避免商务历史报表副本不同步造成遗漏或数值冲突。 +- 锁定/解除合作在工作台按事件发生日期统计;渠道列表汇总按当前状态统计,两者不要求数字相同。 +- 充值与注册明细页排序仅改变当前页顺序;报表类列表排序在查询结果范围内执行,汇总行不参与。 +- 真实历史事件缺失时不以当前状态补造历史;没有覆盖完整统计周期时明确显示不可用。 diff --git a/internal/service/business/account.go b/internal/service/business/account.go index df6829f..59499ec 100644 --- a/internal/service/business/account.go +++ b/internal/service/business/account.go @@ -98,13 +98,10 @@ func validateAccount(req *dto.BusinessAccountRequest, create bool) (*int, error) if req == nil { return nil, business.ErrAccountInput } - if create && req.HireDate == nil { - return nil, business.ErrMemberInput - } if req.Phone != nil && !validMemberPhone(*req.Phone) { return nil, business.ErrMemberInput } - if req.HireDate != nil { + if req.HireDate != nil && (strings.TrimSpace(*req.HireDate) != "" || !create) { if _, err := time.Parse("2006-01-02", *req.HireDate); err != nil { return nil, business.ErrMemberInput } @@ -158,7 +155,10 @@ func (s *AccountService) Create(ctx context.Context, req *dto.BusinessAccountReq } a := &business.Account{Username: stringValue(req.Username), NickName: stringValue(req.NickName), RealName: stringValue(req.RealName), Phone: stringValue(req.Phone), Email: stringValue(req.Email), QQNumber: stringValue(req.QQNumber), Password: req.Password, RejectReason: stringValue(req.RejectReason)} - a.HireDate = stringValue(req.HireDate) + a.HireDate = strings.TrimSpace(stringValue(req.HireDate)) + if a.HireDate == "" { + a.HireDate = time.Now().In(time.FixedZone("Asia/Shanghai", 8*3600)).Format("2006-01-02") + } if status != nil { a.Status = *status } diff --git a/internal/service/business/account_test.go b/internal/service/business/account_test.go index f51310b..77af1c5 100644 --- a/internal/service/business/account_test.go +++ b/internal/service/business/account_test.go @@ -6,8 +6,28 @@ import ( "kra/internal/biz/business" "kra/internal/service/dto" "testing" + "time" ) +func TestCreateMemberDefaultsOptionalHireDate(t *testing.T) { + f := &accountUsecaseFake{} + s := &AccountService{uc: f} + username, name := "newmember", "负责人" + for _, date := range []*string{nil, new(string)} { + _, err := s.Create(context.Background(), &dto.BusinessAccountRequest{Username: &username, RealName: &name, Password: "abc123", HireDate: date}) + if err != nil { + t.Fatal(err) + } + if f.created.HireDate != time.Now().In(time.FixedZone("Asia/Shanghai", 8*3600)).Format("2006-01-02") { + t.Fatalf("default date: %s", f.created.HireDate) + } + } + invalid := "2026-02-30" + if _, err := s.Create(context.Background(), &dto.BusinessAccountRequest{Username: &username, RealName: &name, Password: "abc123", HireDate: &invalid}); err == nil { + t.Fatal("invalid explicit hire date accepted") + } +} + type accountUsecaseFake struct { AccountUsecase created *business.Account @@ -68,14 +88,15 @@ func TestAccountQueryEmptyFiltersAndPageBounds(t *testing.T) { } } -func TestCreateBusinessRequiresHireDateAndValidPhone(t *testing.T) { +func TestCreateBusinessDefaultsHireDateAndValidatesPhone(t *testing.T) { f := &accountUsecaseFake{} s := &AccountService{uc: f} name, owner, phone, hire := "member", "负责人", "abc", "2026-11-01" req := &dto.BusinessAccountRequest{Username: &name, RealName: &owner, Password: "test-password"} - if _, err := s.Create(context.Background(), req); err != business.ErrMemberInput || f.created != nil { - t.Fatal("missing hire date must not reach usecase") + if _, err := s.Create(context.Background(), req); err != nil || f.created.HireDate == "" { + t.Fatal("missing hire date should default to today") } + f.created = nil req.HireDate, req.Phone = &hire, &phone if _, err := s.Create(context.Background(), req); err != business.ErrMemberInput || f.created != nil { t.Fatal("invalid phone must not reach usecase") diff --git a/web/src/view/dashboard/components/BusinessDashboard.vue b/web/src/view/dashboard/components/BusinessDashboard.vue index df88a57..631fb24 100644 --- a/web/src/view/dashboard/components/BusinessDashboard.vue +++ b/web/src/view/dashboard/components/BusinessDashboard.vue @@ -1,5 +1,6 @@ + + diff --git a/web/src/view/sw/ChannelList.vue b/web/src/view/sw/ChannelList.vue index e073956..ec5b8b4 100644 --- a/web/src/view/sw/ChannelList.vue +++ b/web/src/view/sw/ChannelList.vue @@ -28,24 +28,24 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + +
@@ -57,6 +57,7 @@ diff --git a/web/src/view/sw/components/LedgerDetails.vue b/web/src/view/sw/components/LedgerDetails.vue index 30f9b65..13f3640 100644 --- a/web/src/view/sw/components/LedgerDetails.vue +++ b/web/src/view/sw/components/LedgerDetails.vue @@ -19,23 +19,22 @@
导出{{ currentSort }}
- - - - - + + + + - + - - + + - - + +

合计:成功支付总额 {{ error ? '未获取' : available ? channelMoney(totalPayAmount) : '未接入' }}

@@ -44,6 +43,7 @@