sync: 同步 v1.0.4 开源代码并脱敏
This commit is contained in:
@@ -46,7 +46,7 @@ func (r *Repository) loadParentTaskTx(ctx context.Context, tx gdb.TX, parent *en
|
||||
continue
|
||||
}
|
||||
detail := &Detail{OriginalSnapshot: batchDetail.Snapshot, ID: batchDetail.Id, BatchNo: batchDetail.BatchNo, RowNo: batchDetail.RowNo, DetailStatus: batchDetail.DetailStatus, ICCID: strings.TrimSpace(batchDetail.ItemId), Reason: strings.TrimSpace(batchDetail.ErrorReason)}
|
||||
if detail.DetailStatus != consts.BatchDetailStatusSuccess && detail.Reason == "" {
|
||||
if detail.DetailStatus != consts.BatchDetailStatusSuccess && detail.DetailStatus != consts.BatchDetailStatusRunning && detail.Reason == "" {
|
||||
detail.Reason = "子批次明细尚未全部验证成功"
|
||||
}
|
||||
if snapshot := batchDetail.Snapshot; snapshot != nil {
|
||||
|
||||
@@ -2,19 +2,19 @@ package allocate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model/entity"
|
||||
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model/entity"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPersistedAllocateDetailsRequireExactBusinessResult(t *testing.T) {
|
||||
task := &Task{Batch: Batch{BatchNo: "ALC-child", TenantID: "000000", CreatedBy: 133, CreatedDept: 175}}
|
||||
detail := &Detail{ID: 1, ICCID: "89860401790000277308", TargetDeptID: 185}
|
||||
detail := &Detail{ID: 1, ICCID: "89860401791508277308", TargetDeptID: 185}
|
||||
good := entity.BatchDetail{Id: 1, BatchNo: task.Batch.BatchNo, BatchType: consts.BatchTypeAllocate, TenantId: task.Batch.TenantID, DetailStatus: consts.BatchDetailStatusSuccess, UpdatedBy: 133, Snapshot: gjson.New(detail.Snapshot(task.Batch).String())}
|
||||
if err := validatePersistedDetailResults(task, []*Detail{detail}, []*entity.BatchDetail{&good}); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -166,68 +166,99 @@ func markAtomicFailure(task *Task, reason string) {
|
||||
task.FailureReason = reason
|
||||
for _, detail := range task.Details {
|
||||
if detail != nil && strings.TrimSpace(detail.Reason) == "" {
|
||||
detail.Reason = "本批次存在失败明细,整批未执行"
|
||||
detail.Reason = atomicNotExecutedReason
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const atomicNotExecutedReason = "校验通过,但本批次存在失败明细,整批未执行划拨"
|
||||
|
||||
func allocationDetailStatus(detail *Detail) int {
|
||||
if detail.Reason == atomicNotExecutedReason {
|
||||
return consts.BatchDetailStatusNotExecuted
|
||||
}
|
||||
if detail.Success() {
|
||||
return consts.BatchDetailStatusSuccess
|
||||
}
|
||||
return consts.BatchDetailStatusFailed
|
||||
}
|
||||
|
||||
func countValidationFailures(task *Task) int {
|
||||
count := 0
|
||||
for _, detail := range task.Details {
|
||||
if detail != nil && !detail.Success() && detail.Reason != atomicNotExecutedReason {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func persistedAllocationDetailStatus(task *Task, detail *Detail) int {
|
||||
status := allocationDetailStatus(detail)
|
||||
if strings.TrimSpace(task.Batch.ParentBatchNo) != "" && status == consts.BatchDetailStatusSuccess {
|
||||
return consts.BatchDetailStatusRunning
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
func validateDetail(task *Task, detail *Detail) {
|
||||
if detail == nil || strings.TrimSpace(detail.Reason) != "" {
|
||||
if task == nil || detail == nil {
|
||||
return
|
||||
}
|
||||
var reasons []string
|
||||
if reason := strings.TrimSpace(detail.Reason); reason != "" {
|
||||
reasons = append(reasons, reason)
|
||||
}
|
||||
defer func() { detail.Reason = strings.Join(reasons, ";") }()
|
||||
if detail.ICCID == "" {
|
||||
detail.Reason = "ICCID不能为空"
|
||||
reasons = append(reasons, "ICCID不能为空")
|
||||
return
|
||||
}
|
||||
detail.Reason = ""
|
||||
target := resolveTarget(task, detail)
|
||||
if target == nil {
|
||||
return
|
||||
}
|
||||
detail.Target = target
|
||||
detail.TargetDeptID = target.DeptID
|
||||
if detail.TargetDeptKey == "" {
|
||||
detail.TargetDeptKey = strings.TrimSpace(target.DeptKey)
|
||||
}
|
||||
if detail.TargetDeptName == "" {
|
||||
detail.TargetDeptName = strings.TrimSpace(target.DeptName)
|
||||
reasons = append(reasons, detail.Reason)
|
||||
} else {
|
||||
detail.Target = target
|
||||
detail.TargetDeptID = target.DeptID
|
||||
if detail.TargetDeptKey == "" {
|
||||
detail.TargetDeptKey = strings.TrimSpace(target.DeptKey)
|
||||
}
|
||||
if detail.TargetDeptName == "" {
|
||||
detail.TargetDeptName = strings.TrimSpace(target.DeptName)
|
||||
}
|
||||
}
|
||||
if detail.Card == nil {
|
||||
detail.Reason = "ICCID不存在或不属于当前租户"
|
||||
reasons = append(reasons, "ICCID不存在或不属于当前租户")
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(detail.ICCID) != strings.TrimSpace(detail.Card.ICCID) {
|
||||
detail.Reason = "ICCID与卡档案不一致"
|
||||
reasons = append(reasons, "ICCID与卡档案不一致")
|
||||
return
|
||||
}
|
||||
detail.MSISDN = coalesce(detail.MSISDN, detail.Card.MSISDN)
|
||||
detail.IMSI = coalesce(detail.IMSI, detail.Card.IMSI)
|
||||
if detail.Card.DeptID != task.Batch.CreatedDept {
|
||||
detail.Reason = "ICCID不在当前操作员授权范围内"
|
||||
return
|
||||
reasons = append(reasons, "ICCID不在当前操作员授权范围内")
|
||||
}
|
||||
if strings.TrimSpace(detail.MSISDN) != "" && strings.TrimSpace(detail.MSISDN) != detail.Card.MSISDN {
|
||||
detail.Reason = "MSISDN与卡档案不一致"
|
||||
return
|
||||
reasons = append(reasons, "MSISDN与卡档案不一致")
|
||||
}
|
||||
if strings.TrimSpace(detail.IMSI) != "" && strings.TrimSpace(detail.IMSI) != detail.Card.IMSI {
|
||||
detail.Reason = "IMSI与卡档案不一致"
|
||||
return
|
||||
reasons = append(reasons, "IMSI与卡档案不一致")
|
||||
}
|
||||
if detail.Card.BossCardStatus == consts.SimBossCardStatusStopped {
|
||||
detail.Reason = "卡已停机,不能划拨"
|
||||
return
|
||||
reasons = append(reasons, "卡已停机,不能划拨")
|
||||
}
|
||||
if detail.Card.BossCardStatus == consts.SimBossCardStatusCanceled {
|
||||
detail.Reason = "卡已销户,不能划拨"
|
||||
return
|
||||
reasons = append(reasons, "卡已销户,不能划拨")
|
||||
}
|
||||
if detail.Card.PackageStatus == consts.PackageStatusExpired ||
|
||||
(detail.Card.FlowPkgEndAt != nil && !detail.Card.FlowPkgEndAt.IsZero() && detail.Card.FlowPkgEndAt.Before(gtime.Now())) {
|
||||
detail.Reason = "套餐已过期,不能划拨"
|
||||
return
|
||||
reasons = append(reasons, "套餐已过期,不能划拨")
|
||||
}
|
||||
if detail.Card.DeptID == target.DeptID {
|
||||
detail.Reason = "卡已属于目标客户,无需重复划拨"
|
||||
if target != nil && detail.Card.DeptID == target.DeptID {
|
||||
reasons = append(reasons, "卡已属于目标客户,无需重复划拨")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ func TestAllocateBatchSizesRemainBounded(t *testing.T) {
|
||||
func TestBuildDetailUpdateStatementUsesBoundParameters(t *testing.T) {
|
||||
task := &Task{Batch: Batch{BatchNo: "ALC-1", CreatedBy: 7}}
|
||||
details := []*Detail{
|
||||
{ID: 11, DetailStatus: consts.BatchDetailStatusPending, Reason: "unsafe ' reason"},
|
||||
{ID: 11, DetailStatus: consts.BatchDetailStatusPending, Reason: atomicNotExecutedReason},
|
||||
{ID: 12, DetailStatus: consts.BatchDetailStatusRunning, Reason: "second"},
|
||||
}
|
||||
statement, args := buildDetailUpdateStatement(task, details, gtime.Now())
|
||||
@@ -84,8 +84,11 @@ func TestBuildDetailUpdateStatementUsesBoundParameters(t *testing.T) {
|
||||
if !strings.Contains(statement, "CASE") || !strings.Contains(statement, "IN (?,?)") {
|
||||
t.Fatalf("unexpected statement: %s", statement)
|
||||
}
|
||||
if len(args) != len(details)*7+9 {
|
||||
t.Fatalf("argument count = %d, want %d", len(args), len(details)*7+9)
|
||||
if len(args) != len(details)*7+10 {
|
||||
t.Fatalf("argument count = %d, want %d", len(args), len(details)*7+10)
|
||||
}
|
||||
if args[1] != consts.BatchDetailStatusNotExecuted || args[3] != consts.BatchDetailStatusFailed {
|
||||
t.Fatalf("status parameters = %v, %v; want not executed and failed", args[1], args[3])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,20 +99,68 @@ func TestNormalizeAtomicParentResultRejectsPartialSuccess(t *testing.T) {
|
||||
}}
|
||||
|
||||
successCount, failCount := normalizeAtomicParentResult(task)
|
||||
if successCount != 0 || failCount != len(task.Details) {
|
||||
t.Fatalf("normalizeAtomicParentResult() = %d, %d; want 0, %d", successCount, failCount, len(task.Details))
|
||||
if successCount != 0 || failCount != 1 {
|
||||
t.Fatalf("normalizeAtomicParentResult() = %d, %d; want 0, 1", successCount, failCount)
|
||||
}
|
||||
for _, detail := range task.Details {
|
||||
if detail.Success() {
|
||||
t.Fatalf("detail %s remained successful", detail.ICCID)
|
||||
}
|
||||
}
|
||||
if allocationDetailStatus(task.Details[0]) != consts.BatchDetailStatusNotExecuted || allocationDetailStatus(task.Details[1]) != consts.BatchDetailStatusFailed {
|
||||
t.Fatalf("wrong detail statuses: %+v", task.Details)
|
||||
}
|
||||
rows := task.ResultRows()
|
||||
if rows[0].Status != "未执行" || rows[1].Status != "失败" {
|
||||
t.Fatalf("wrong result file statuses: %+v", rows)
|
||||
}
|
||||
status, result, _ := parentBatchResult(successCount, failCount)
|
||||
if status != consts.BatchAsyncStatusFailed || result != consts.BatchRunResultFailed {
|
||||
t.Fatalf("parentBatchResult() = %d, %d; want failed, failed", status, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocateChildValidationDoesNotReportExecutedSuccess(t *testing.T) {
|
||||
task := &Task{Batch: Batch{BatchNo: "child", ParentBatchNo: "parent"}}
|
||||
detail := &Detail{ID: 1, ICCID: "valid"}
|
||||
_, args := buildDetailUpdateStatement(task, []*Detail{detail}, gtime.Now())
|
||||
if args[1] != consts.BatchDetailStatusRunning {
|
||||
t.Fatalf("validated child status = %v, want running until parent commits", args[1])
|
||||
}
|
||||
task.Batch.ParentBatchNo = ""
|
||||
_, args = buildDetailUpdateStatement(task, []*Detail{detail}, gtime.Now())
|
||||
if args[1] != consts.BatchDetailStatusSuccess {
|
||||
t.Fatalf("committed parent status = %v, want success", args[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateTaskCollectsAllCardFailuresBeforeBlockingBatch(t *testing.T) {
|
||||
task := &Task{
|
||||
Batch: Batch{CreatedDept: 100, TargetDeptID: 200},
|
||||
TargetsByID: map[int64]*Target{200: {DeptID: 200, ParentID: 100}},
|
||||
Details: []*Detail{
|
||||
{ICCID: "missing"},
|
||||
{ICCID: "valid", Card: &Card{ICCID: "valid", DeptID: 100}},
|
||||
{ICCID: "stopped", Card: &Card{ICCID: "stopped", DeptID: 100, BossCardStatus: consts.SimBossCardStatusStopped, PackageStatus: consts.PackageStatusExpired}},
|
||||
},
|
||||
}
|
||||
validateTask(task)
|
||||
if task.Details[0].Reason != "ICCID不存在或不属于当前租户" {
|
||||
t.Fatalf("first failure was lost: %q", task.Details[0].Reason)
|
||||
}
|
||||
if task.Details[1].Reason != atomicNotExecutedReason || allocationDetailStatus(task.Details[1]) != consts.BatchDetailStatusNotExecuted {
|
||||
t.Fatalf("validated card was not blocked: %+v", task.Details[1])
|
||||
}
|
||||
for _, reason := range []string{"卡已停机,不能划拨", "套餐已过期,不能划拨"} {
|
||||
if !strings.Contains(task.Details[2].Reason, reason) {
|
||||
t.Fatalf("later card was not fully validated: %q", task.Details[2].Reason)
|
||||
}
|
||||
}
|
||||
if successes, failures := normalizeAtomicParentResult(task); successes != 0 || failures != 2 {
|
||||
t.Fatalf("got successes=%d failures=%d, want 0 and 2", successes, failures)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDetailAllowsCurrentHolderReallocation(t *testing.T) {
|
||||
task := &Task{
|
||||
Batch: Batch{
|
||||
|
||||
@@ -283,6 +283,9 @@ func (r *Repository) Commit(ctx context.Context, task *Task) error {
|
||||
markAtomicFailure(task, "划拨批次存在失败明细,整批未执行")
|
||||
}
|
||||
successCount, failCount := countResult(task)
|
||||
if failCount > 0 {
|
||||
failCount = countValidationFailures(task)
|
||||
}
|
||||
autoSplitChild := strings.TrimSpace(task.Batch.ParentBatchNo) != ""
|
||||
var artifact batchresult.Artifact
|
||||
var artifactErr error
|
||||
@@ -443,7 +446,7 @@ func (r *Repository) aggregateParentOnce(ctx context.Context, parentBatchNo stri
|
||||
finalInfo["successCount"] = successCount
|
||||
finalInfo["failCount"] = failCount
|
||||
startTime := resolveAllocateParentStartTime(parent.StartTime, aggregate.EarliestStartTime, now)
|
||||
data := do.BatchMain{StartTime: startTime, BatchAsyncStatus: status, RunResult: runResult, TotalCount: len(task.Details), ValidCount: successCount, SuccessCount: successCount, FailCount: failCount, FailReason: failReason, BatchInfo: mergeBatchInfo(gjson.New(finalInfo), ossID, downloadID, artifactErr != nil), EndTime: now, UpdatedBy: parent.CreatedBy, UpdatedAt: now}
|
||||
data := do.BatchMain{StartTime: startTime, BatchAsyncStatus: status, RunResult: runResult, TotalCount: len(task.Details), ValidCount: len(task.Details) - failCount, SuccessCount: successCount, FailCount: failCount, FailReason: failReason, BatchInfo: mergeBatchInfo(gjson.New(finalInfo), ossID, downloadID, artifactErr != nil), EndTime: now, UpdatedBy: parent.CreatedBy, UpdatedAt: now}
|
||||
result, err := tx.Ctx(ctx).Model(dao.BatchMain.Table()).Where(mainColumns.Id, parent.Id).WhereIn(mainColumns.BatchAsyncStatus, []int{consts.BatchAsyncStatusPending, consts.BatchAsyncStatusProcessing}).Data(data).Update()
|
||||
if err != nil {
|
||||
if artifactErr == nil {
|
||||
@@ -536,7 +539,7 @@ func normalizeAtomicParentResult(task *Task) (int, int) {
|
||||
}
|
||||
task.FailureReason = coalesce(task.FailureReason, "划拨批次存在失败明细,整批未执行")
|
||||
markAtomicFailure(task, task.FailureReason)
|
||||
return 0, len(task.Details)
|
||||
return 0, countValidationFailures(task)
|
||||
}
|
||||
|
||||
func (r *Repository) updateDetails(ctx context.Context, tx gdb.TX, task *Task, now *gtime.Time) error {
|
||||
@@ -644,10 +647,7 @@ func validatePersistedDetailResults(task *Task, details []*Detail, persisted []*
|
||||
}
|
||||
for _, detail := range details {
|
||||
row := byID[detail.ID]
|
||||
status := consts.BatchDetailStatusFailed
|
||||
if detail.Success() {
|
||||
status = consts.BatchDetailStatusSuccess
|
||||
}
|
||||
status := persistedAllocationDetailStatus(task, detail)
|
||||
if row == nil || row.DetailStatus != status || row.ErrorReason != detail.Reason || row.UpdatedBy != task.Batch.CreatedBy || row.Snapshot == nil || !sameDetailSnapshot(row.Snapshot, detail.Snapshot(task.Batch)) {
|
||||
return gerror.Newf("划拨明细最终结果不一致,明细ID:%d", detail.ID)
|
||||
}
|
||||
@@ -669,12 +669,9 @@ func buildDetailUpdateStatement(task *Task, details []*Detail, now *gtime.Time)
|
||||
columns := dao.BatchDetail.Columns()
|
||||
var builder strings.Builder
|
||||
fmt.Fprintf(&builder, "UPDATE %s SET %s = CASE %s ", dao.BatchDetail.Table(), columns.DetailStatus, columns.Id)
|
||||
args := make([]any, 0, len(details)*7+8)
|
||||
args := make([]any, 0, len(details)*7+9)
|
||||
for _, detail := range details {
|
||||
status := consts.BatchDetailStatusFailed
|
||||
if detail.Success() {
|
||||
status = consts.BatchDetailStatusSuccess
|
||||
}
|
||||
status := persistedAllocationDetailStatus(task, detail)
|
||||
builder.WriteString("WHEN ? THEN ? ")
|
||||
args = append(args, detail.ID, status)
|
||||
}
|
||||
@@ -690,7 +687,7 @@ func buildDetailUpdateStatement(task *Task, details []*Detail, now *gtime.Time)
|
||||
}
|
||||
fmt.Fprintf(&builder, "ELSE %s END, %s = ?, %s = ? ", columns.Snapshot, columns.UpdatedBy, columns.UpdatedAt)
|
||||
args = append(args, task.Batch.CreatedBy, now)
|
||||
fmt.Fprintf(&builder, "WHERE %s = ? AND %s = ? AND %s = ? AND %s IN (?, ?, ?, ?) AND %s IN (%s)",
|
||||
fmt.Fprintf(&builder, "WHERE %s = ? AND %s = ? AND %s = ? AND %s IN (?, ?, ?, ?, ?) AND %s IN (%s)",
|
||||
columns.BatchNo,
|
||||
columns.BatchType,
|
||||
columns.TenantId,
|
||||
@@ -706,6 +703,7 @@ func buildDetailUpdateStatement(task *Task, details []*Detail, now *gtime.Time)
|
||||
consts.BatchDetailStatusRunning,
|
||||
consts.BatchDetailStatusSuccess,
|
||||
consts.BatchDetailStatusFailed,
|
||||
consts.BatchDetailStatusNotExecuted,
|
||||
)
|
||||
for _, detail := range details {
|
||||
args = append(args, detail.ID)
|
||||
@@ -845,10 +843,7 @@ func (r *Repository) updateDetail(ctx context.Context, tx gdb.TX, task *Task, de
|
||||
detail.DetailStatus != consts.BatchDetailStatusRunning) {
|
||||
return nil
|
||||
}
|
||||
status := consts.BatchDetailStatusFailed
|
||||
if detail.Success() {
|
||||
status = consts.BatchDetailStatusSuccess
|
||||
}
|
||||
status := persistedAllocationDetailStatus(task, detail)
|
||||
result, err := tx.Ctx(ctx).Model(dao.BatchDetail.Table()).
|
||||
Where(dao.BatchDetail.Columns().Id, detail.ID).
|
||||
Where(dao.BatchDetail.Columns().BatchNo, task.Batch.BatchNo).
|
||||
@@ -949,7 +944,7 @@ func (r *Repository) finalizeBatch(ctx context.Context, tx gdb.TX, task *Task, s
|
||||
BatchAsyncStatus: batchAsyncStatus,
|
||||
RunResult: runResult,
|
||||
TotalCount: len(task.Details),
|
||||
ValidCount: successCount,
|
||||
ValidCount: len(task.Details) - failCount,
|
||||
SuccessCount: successCount,
|
||||
FailCount: failCount,
|
||||
FailReason: task.FailureReason,
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/batchresult"
|
||||
)
|
||||
|
||||
@@ -155,8 +156,19 @@ func (t *Task) ResultRows() []batchresult.Row {
|
||||
TargetDeptName: targetName,
|
||||
Operation: "卡划拨",
|
||||
Success: detail.Success(),
|
||||
Status: allocationResultStatus(detail),
|
||||
Reason: strings.TrimSpace(detail.Reason),
|
||||
})
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func allocationResultStatus(detail *Detail) string {
|
||||
if allocationDetailStatus(detail) == consts.BatchDetailStatusNotExecuted {
|
||||
return "未执行"
|
||||
}
|
||||
if detail.Success() {
|
||||
return "成功"
|
||||
}
|
||||
return "失败"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user