feat(t224): add incremental freight sync

This commit is contained in:
QiuSW
2026-07-29 00:26:05 +08:00
parent 433db45100
commit c69879650e
35 changed files with 2085 additions and 156 deletions
@@ -52,14 +52,18 @@ func (store *Store) CreateFreightSync(
ctx,
`INSERT INTO erp_sync_runs (
id, creator_subject, created_by_user_id, mode, order_number,
query_sha256, idempotency_key, request_sha256, status,
order_count, item_count, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, ?)`,
created_from, created_to, watermark_through, query_sha256,
idempotency_key, request_sha256, status, order_count, item_count,
created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, ?)`,
run.ID,
run.CreatorSubject,
run.CreatedByUserID,
run.Mode,
run.OrderNumber,
nullableFreightSyncValue(run.OrderNumber),
nullableFreightSyncValue(run.CreatedFrom),
nullableFreightSyncValue(run.CreatedTo),
nullableTimestamp(run.WatermarkThrough),
run.QuerySHA256,
idempotencyKey,
requestSHA256,
@@ -106,6 +110,32 @@ func (store *Store) CompleteFreightSync(
run domain.FreightSyncRun,
batch domain.FreightImportBatch,
finishedAt time.Time,
) error {
return store.completeFreightSync(ctx, run, batch, nil, finishedAt)
}
func (store *Store) CompleteFreightDateSync(
ctx context.Context,
run domain.FreightSyncRun,
batch domain.FreightImportBatch,
watermarkThrough time.Time,
finishedAt time.Time,
) error {
return store.completeFreightSync(
ctx,
run,
batch,
&watermarkThrough,
finishedAt,
)
}
func (store *Store) completeFreightSync(
ctx context.Context,
run domain.FreightSyncRun,
batch domain.FreightImportBatch,
watermarkThrough *time.Time,
finishedAt time.Time,
) error {
tx, err := store.db.BeginTx(ctx, nil)
if err != nil {
@@ -184,6 +214,28 @@ func (store *Store) CompleteFreightSync(
if changed != 1 {
return usecase.ErrTaskStateConflict
}
if watermarkThrough != nil {
if _, err := tx.ExecContext(
ctx,
`INSERT INTO erp_sync_watermarks (
creator_subject, source_system, last_successful_to,
last_successful_run_id, updated_at
) VALUES (?, 'SHUNYUNBAO', ?, ?, ?)
ON CONFLICT (creator_subject, source_system)
DO UPDATE SET
last_successful_to = excluded.last_successful_to,
last_successful_run_id = excluded.last_successful_run_id,
updated_at = excluded.updated_at
WHERE julianday(erp_sync_watermarks.last_successful_to)
< julianday(excluded.last_successful_to)`,
run.CreatorSubject,
formatTimestamp(*watermarkThrough),
run.ID,
formatTimestamp(finishedAt),
); err != nil {
return repositoryFailure(err)
}
}
if err := tx.Commit(); err != nil {
return repositoryFailure(err)
}
@@ -377,6 +429,43 @@ func (store *Store) GetFreightSync(
return run, nil
}
func (store *Store) GetFreightSyncWatermark(
ctx context.Context,
creatorSubject string,
) (*domain.FreightSyncWatermark, error) {
var watermark domain.FreightSyncWatermark
var lastSuccessfulTo, updatedAt string
err := store.db.QueryRowContext(
ctx,
`SELECT creator_subject, source_system, last_successful_to,
last_successful_run_id, updated_at
FROM erp_sync_watermarks
WHERE creator_subject = ? AND source_system = 'SHUNYUNBAO'`,
creatorSubject,
).Scan(
&watermark.CreatorSubject,
&watermark.SourceSystem,
&lastSuccessfulTo,
&watermark.LastSuccessfulRunID,
&updatedAt,
)
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, repositoryFailure(err)
}
watermark.LastSuccessfulTo, err = parseTimestamp(lastSuccessfulTo)
if err != nil {
return nil, repositoryFailure(err)
}
watermark.UpdatedAt, err = parseTimestamp(updatedAt)
if err != nil {
return nil, repositoryFailure(err)
}
return &watermark, nil
}
func (store *Store) ListFreightOrders(
ctx context.Context,
creatorSubject string,
@@ -458,13 +547,15 @@ func (store *Store) GetFreightOrder(
const freightSyncSelect = `SELECT
id, creator_subject, created_by_user_id, mode, order_number,
query_sha256, status, error_code, order_count, item_count,
created_at, started_at, finished_at
created_from, created_to, watermark_through, query_sha256, status,
error_code, order_count, item_count, created_at, started_at, finished_at
FROM erp_sync_runs
`
func scanFreightSync(scanner rowScanner) (domain.FreightSyncRun, error) {
var run domain.FreightSyncRun
var orderNumber, createdFrom, createdTo sql.NullString
var watermarkThrough sql.NullString
var errorCode sql.NullString
var createdAt string
var startedAt sql.NullString
@@ -474,7 +565,10 @@ func scanFreightSync(scanner rowScanner) (domain.FreightSyncRun, error) {
&run.CreatorSubject,
&run.CreatedByUserID,
&run.Mode,
&run.OrderNumber,
&orderNumber,
&createdFrom,
&createdTo,
&watermarkThrough,
&run.QuerySHA256,
&run.Status,
&errorCode,
@@ -490,6 +584,19 @@ func scanFreightSync(scanner rowScanner) (domain.FreightSyncRun, error) {
if errorCode.Valid {
run.ErrorCode = &errorCode.String
}
if orderNumber.Valid {
run.OrderNumber = orderNumber.String
}
if createdFrom.Valid {
run.CreatedFrom = createdFrom.String
}
if createdTo.Valid {
run.CreatedTo = createdTo.String
}
run.WatermarkThrough, err = parseNullableTimestamp(watermarkThrough)
if err != nil {
return domain.FreightSyncRun{}, err
}
run.CreatedAt, err = parseTimestamp(createdAt)
if err != nil {
return domain.FreightSyncRun{}, err
@@ -622,6 +729,13 @@ func nullableFreightQuantity(value *int) any {
return *value
}
func nullableFreightSyncValue(value string) any {
if value == "" {
return nil
}
return value
}
func optionalString(value sql.NullString) *string {
if !value.Valid {
return nil