Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
129e866
fix: 修复多个问题 — 错误处理、宽限期、邮件通知、法人代表同步
JavaPythonAIForBAT Jul 14, 2026
2fb4554
fix: 修复邮件URL路径和Status字段问题
JavaPythonAIForBAT Jul 14, 2026
2a88cda
fix: CLA更新时URL未持久化到数据库的问题
JavaPythonAIForBAT Jul 14, 2026
b8e3107
refactor: 移除冗余的 status 字段
JavaPythonAIForBAT Jul 14, 2026
eb48a0b
fix: ContainsCla 缓存未命中时兜底查询数据库
JavaPythonAIForBAT Jul 14, 2026
519b787
fix: GetClaId/GetLastUpdateTime 也加数据库兜底查询
JavaPythonAIForBAT Jul 14, 2026
a49af97
fix: 企业同意新CLA后清除cla_notify字段
JavaPythonAIForBAT Jul 14, 2026
c949753
fix: CLA更新未持久化id字段导致前端显示旧协议
JavaPythonAIForBAT Jul 14, 2026
cdd4278
feat: CLA更新后立即标记企业签署为待通知状态
JavaPythonAIForBAT Jul 14, 2026
dead18e
fix: 兼容存量数据,避免上线时大量重复发送通知邮件
JavaPythonAIForBAT Jul 14, 2026
370feaa
feat: CLA更新后立即触发通知扫描,不再等待定时器周期
JavaPythonAIForBAT Jul 15, 2026
0c098c2
feat: 通知扫描改为每日夜间零点执行,减少不必要的频繁扫描
JavaPythonAIForBAT Jul 15, 2026
63c6997
feat: 定时扫描改为每日中午12点和凌晨0点各执行一次
JavaPythonAIForBAT Jul 15, 2026
4b413f4
fix: 修复邮件通知首次被跳过等3个问题
JavaPythonAIForBAT Jul 15, 2026
fe5b011
feat: 通知扫描加入批次上限,每轮最多发送N人,可配置默认500
JavaPythonAIForBAT Jul 15, 2026
93835fd
fix: 测试用例补充 NotifyAdminConfig 避免 nil panic
JavaPythonAIForBAT Jul 15, 2026
3a87b69
feat: 通知扫描支持可配定时间隔与社区白名单
JavaPythonAIForBAT Jul 15, 2026
f61c167
feat: 提醒间隔支持秒级精度,新增 remind_seconds 可配字段
JavaPythonAIForBAT Jul 15, 2026
32413ea
Revert "feat: 提醒间隔支持秒级精度,新增 remind_seconds 可配字段"
JavaPythonAIForBAT Jul 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ lastupdate.tmp
.trae
*.b*
config.yaml
CLAUDE.md
CLAUDE.md
# Test binaries
*.test
10 changes: 2 additions & 8 deletions conf/email-template/cla-updated.tmpl
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
Dear {{.AdminName}},

The Contributor License Agreement (CLA) of the {{.Org}} community has been updated on {{.UpdateDate}}.
A new version of CLA is published. Please login to the CLA management system to see the details.

Your corporation "{{.CorpName}}" has previously signed the CLA, and team members can still contribute code normally — the smooth transition mechanism ensures no disruption to your team's daily development workflow.

What does this mean for you?
Within {{.GracePeriodDays}} days after the update, you would still match the latest agreement version. Failure to do so will result in your team's CLA signing privileges being revoked.

Please log in to the CLA management system to agree to the latest agreement version when convenient.
CLA management system URL: {{.URLOfCLAPlatform}}
The CLA management system login URL is {{.URLOfCLAPlatform}}.

Have questions or need help? Just reply to this email and the {{.Org}} Community Support Team will help you sort it out.

Expand Down
2 changes: 1 addition & 1 deletion controllers/individual_signing.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func (ctl *IndividualSigningController) Check() {
ctl.GetString(":link_id"), ctl.GetString("email"),
)

// status 是权威状态字段,始终返回;调试信息仅在 debug 模式下返回
// signed + version_matched 覆盖全部状态;调试信息仅在 debug 模式下返回
if !debug {
v.DebugInfo = nil
}
Expand Down
1 change: 1 addition & 0 deletions models/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const (
ErrCLAIsUsed ModelErrCode = "cla_is_used"
ErrLinkIsUsed ModelErrCode = "link_is_used"
ErrNoPermission ModelErrCode = "no_permission"
ErrCLAIsLatest ModelErrCode = "cla_is_latest"
)

type IModelError interface {
Expand Down
1 change: 0 additions & 1 deletion models/individual_signing.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ type IndividualSigned struct {
Type string `json:"type"` // "individual" 或 "corp"
Signed bool `json:"signed"` // 是否已签署过
VersionMatched bool `json:"version_matched"` // 签署是否当前有效(考虑宽限期),始终返回以兼容旧版本调用
Status string `json:"status"` // 内部状态:"not_signed" | "valid" | "expired"
DebugInfo *DebugInfo `json:"_debug,omitempty"` // 调试信息(仅debug=true时返回)
}

Expand Down
8 changes: 4 additions & 4 deletions signing.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,14 @@ func initSigning(cfg *config.Config) error {

// link
localCLA := localclaimpl.NewLocalCLAImpl(&cfg.LocalCLA)
cla, err := claservice.NewCLAService(linkRepo, localCLA, messageimpl.NewMessageImpl(), repo)
cla, err := claservice.NewCLAService(linkRepo, localCLA, messageimpl.NewMessageImpl())
if err != nil {
return err
}

models.RegisterCorpSigningAdapter(
adapter.NewCorpSigningAdapter(
app.NewCorpSigningService(repo, vcService, interval, linkRepo, cla),
app.NewCorpSigningService(repo, vcService, interval, linkRepo, cla, userService),
cfg.Domain.Config.InvalidCorpEmailDomains(),
),
)
Expand Down Expand Up @@ -173,7 +173,7 @@ func initSigning(cfg *config.Config) error {
repo,
linkRepo,
interval,
cfg.Domain.Config.DefaultGracePeriodDays,
cfg.Domain.DefaultGracePeriodDays,
)),
)

Expand Down Expand Up @@ -212,7 +212,7 @@ func initSigning(cfg *config.Config) error {
// watch
watch.Start(&cfg.Watch, repo, individual)
watch.CLAUpdatedWatchStart(linkRepo, localCLA, &cfg.Watch.CLAUpdateConfig, cfg.PDF.PythonBin)
watch.NotifyAdminWatchStart(&cfg.Watch.SendEmailConfig, linkRepo, repo, individual, cfg.API.CLAPlatformURL, cfg.Domain.Config.DefaultGracePeriodDays)
watch.NotifyAdminWatchStart(&cfg.Watch.SendEmailConfig, linkRepo, repo, individual, cfg.API.CLAPlatformURL, cfg.Domain.DefaultGracePeriodDays)

return nil
}
11 changes: 11 additions & 0 deletions signing/adapter/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package adapter
import (
"fmt"

commonRepo "github.com/opensourceways/app-cla-server/common/domain/repository"
"github.com/opensourceways/app-cla-server/models"
"github.com/opensourceways/app-cla-server/signing/domain"
)
Expand All @@ -12,6 +13,10 @@ type errorCode interface {
}

func toModelError(err error) models.IModelError {
if commonRepo.IsErrorResourceNotFound(err) {
return models.NewModelError(models.ErrNoLink, err)
}

code, ok := err.(errorCode)
if !ok {
fmt.Println("toModelError: unexpected error type:", err)
Expand Down Expand Up @@ -46,6 +51,9 @@ func codeMap(code string) models.ModelErrCode {
case domain.ErrorCodeCorpSigningCanNotDelete:
return models.ErrCorpManagerExists

case domain.ErrorCodeCorpSigningCLAIsLatest:
return models.ErrCLAIsLatest

// corp pdf
case domain.ErrorCodeCorpPDFNotFound:
return models.ErrUnuploaed
Expand Down Expand Up @@ -103,6 +111,9 @@ func codeMap(code string) models.ModelErrCode {
case domain.ErrorCodeIndividualSigningCorpExists:
return models.ErrGoToSignEmployeeCLA

case domain.ErrorCodeIndividualSigningCLAIsLatest:
return models.ErrCLAIsLatest

// cla
case domain.ErrorCodeCLAExists:
return models.ErrCLAExists
Expand Down
27 changes: 5 additions & 22 deletions signing/adapter/individual_signing.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ import (
"github.com/opensourceways/app-cla-server/signing/domain/dp"
)

// 类型别名,使转换更简洁
type DebugInfoDTO = app.DebugInfoDTO

func NewIndividualSigningAdapter(s app.IndividualSigningService) *individualSigningAdatper {
return &individualSigningAdatper{s}
}
Expand Down Expand Up @@ -56,8 +53,11 @@ func (adapter *individualSigningAdatper) cmdToSignIndividualCLA(
return
}

if cmd.Rep.Name, err = dp.NewName(opt.Name); err != nil {
return
// Name 仅在签署(Sign)时需要,同意新版本(Agree)时不需要
if claFields != nil {
if cmd.Rep.Name, err = dp.NewName(opt.Name); err != nil {
return
}
}

if cmd.Rep.EmailAddr, err = dp.NewEmailAddr(opt.Email); err != nil {
Expand Down Expand Up @@ -128,26 +128,9 @@ func (adapter *individualSigningAdatper) Check(linkId string, email string,
Type: v.Type,
Signed: v.Signed,
VersionMatched: v.VersionMatched,
Status: v.Status,
DebugInfo: convertDebugInfo(v.DebugInfo),
}, nil
}

// convertDebugInfo 将 DTO 的调试信息转换为 models 的调试信息
func convertDebugInfo(dto *DebugInfoDTO) *models.DebugInfo {
if dto == nil {
return nil
}
return &models.DebugInfo{
IsLatestClaVersion: dto.IsLatestClaVersion,
InGracePeriod: dto.InGracePeriod,
GracePeriodDays: dto.GracePeriodDays,
ClaUpdatedAt: dto.ClaUpdatedAt,
DaysSinceLastUpdate: dto.DaysSinceLastUpdate,
GracePeriodEndsAt: dto.GracePeriodEndsAt,
}
}

func createCodeForSigning(
index string, email string,
f func(*app.CmdToCreateVerificationCode) (string, error),
Expand Down
15 changes: 14 additions & 1 deletion signing/app/cla.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package app

import (
"github.com/beego/beego/v2/core/logs"

"github.com/opensourceways/app-cla-server/signing/domain"
"github.com/opensourceways/app-cla-server/signing/domain/claservice"
"github.com/opensourceways/app-cla-server/signing/domain/dp"
"github.com/opensourceways/app-cla-server/signing/domain/repository"
"github.com/opensourceways/app-cla-server/signing/watch"
)

func NewCLAService(
Expand Down Expand Up @@ -55,7 +58,17 @@ func (s *claService) Update(cmd *CmdToUpdateCLA) error {

cla := cmd.newCLA()

return s.cla.Update(link, &cla)
if err = s.cla.Update(link, &cla); err != nil {
return err
}

if err = s.cs.SetPendingCLAForLink(cmd.LinkId, cla.Id); err != nil {
logs.Error("set pending CLA for link failed: %s, err: %v", cmd.LinkId, err)
}

watch.TriggerNotify()

return nil
}

func (s *claService) Remove(cmd *CmdToRemoveCLA) error {
Expand Down
90 changes: 56 additions & 34 deletions signing/app/corp_signing.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/opensourceways/app-cla-server/signing/domain/claservice"
"github.com/opensourceways/app-cla-server/signing/domain/dp"
"github.com/opensourceways/app-cla-server/signing/domain/repository"
"github.com/opensourceways/app-cla-server/signing/domain/userservice"
"github.com/opensourceways/app-cla-server/signing/domain/vcservice"
)

Expand All @@ -18,13 +19,15 @@ func NewCorpSigningService(
interval time.Duration,
linkRepo repository.Link,
cla claservice.CLAService,
userService userservice.UserService,
) *corpSigningService {
return &corpSigningService{
repo: repo,
vc: verificationCodeService{vc},
interval: interval,
linkRepo: linkRepo,
cla: cla,
repo: repo,
vc: verificationCodeService{vc},
interval: interval,
linkRepo: linkRepo,
cla: cla,
userService: userService,
}
}

Expand All @@ -43,11 +46,12 @@ type CorpSigningService interface {
}

type corpSigningService struct {
vc verificationCodeService
cla claservice.CLAService
repo repository.CorpSigning
interval time.Duration
linkRepo repository.Link
vc verificationCodeService
cla claservice.CLAService
repo repository.CorpSigning
interval time.Duration
linkRepo repository.Link
userService userservice.UserService
}

func (s *corpSigningService) Verify(cmd *CmdToCreateVerificationCode) (string, error) {
Expand Down Expand Up @@ -108,15 +112,13 @@ func (s *corpSigningService) Get(userId, csId string, email dp.EmailAddr) (linkI
}

dto = CorpSigningInfoDTO{
Date: item.Date,
CLAId: item.Link.CLAId,
Language: item.Link.Language.Language(),
CorpName: item.Corp.Name.CorpName(),
RepName: item.Rep.Name.Name(),
RepEmail: item.Rep.EmailAddr.EmailAddr(),
AllInfo: item.AllInfo,
PendingCLAId: item.PendingCLAId,
Logs: toCorpSigningLogDTOs(item.Logs),
Date: item.Date,
CLAId: item.Link.CLAId,
Language: item.Link.Language.Language(),
CorpName: item.Corp.Name.CorpName(),
RepName: item.Rep.Name.Name(),
RepEmail: item.Rep.EmailAddr.EmailAddr(),
AllInfo: item.AllInfo,
}

return
Expand Down Expand Up @@ -247,52 +249,72 @@ func (s *corpSigningService) AgreeWithLatestCLA(signingId string) error {
}

func (s *corpSigningService) UpdateRepresentative(userId, linkID, signingID, repName, repEmail string) error {
// 权限验证 - 只有社区管理员可以操作
if _, err := checkIfCommunityManager(userId, linkID, s.linkRepo); err != nil {
return err
}

// 查找企业签名
cs, err := s.repo.Find(signingID)
if err != nil {
return err
}

// 验证link_id匹配
if cs.Link.Id != linkID {
return commonRepo.NewErrorResourceNotFound(errors.New("signing not found"))
}

// 创建新的代表信息
newRep, err := domain.NewRepresentative(repName, repEmail)
if err != nil {
return err
}

oldEmail := cs.Rep.EmailAddr

// 更新代表信息
cs.Rep = newRep

return s.repo.Update(&cs)
// 同步更新 Admin 信息(管理员登录账号)
if cs.Admin.Id != "" {
cs.Admin.Representative = newRep
}

// 保存到数据库
if err := s.repo.Update(&cs); err != nil {
return err
}

// 同步更新 User 表中的邮箱
if cs.Admin.Id != "" {
if err := s.userService.UpdateEmail(cs.Link.Id, oldEmail, newRep.EmailAddr); err != nil {
return err
}
}

return nil
}

func (s *corpSigningService) FindPendingAgreements(userId, linkId string) ([]CorpSigningPendingDTO, error) {
if _, err := checkIfCommunityManager(userId, linkId, s.linkRepo); err != nil {
return nil, err
}

v, err := s.repo.FindPendingAgreements(linkId)
if err != nil || len(v) == 0 {
summaries, err := s.repo.FindPendingAgreements(linkId)
if err != nil {
return nil, err
}

dtos := make([]CorpSigningPendingDTO, len(v))
for i := range v {
item := &v[i]
dtos[i] = CorpSigningPendingDTO{
Id: item.Id,
CorpName: item.Corp.Name.CorpName(),
AdminEmail: item.Admin.EmailAddr.EmailAddr(),
SignedCLAId: item.Link.CLAId,
PendingCLAId: item.PendingCLAId,
NotifyCount: item.ClaNotifyCount,
LastNotifyTime: item.ClaNotifyTime,
r := make([]CorpSigningPendingDTO, len(summaries))
for i := range summaries {
item := &summaries[i]
r[i] = CorpSigningPendingDTO{
Id: item.Id,
CorpName: item.Corp.Name.CorpName(),
AdminEmail: item.Admin.EmailAddr.EmailAddr(),
}
}

return dtos, nil
return r, nil
}
Loading