Move document_records duty-slot writes into docgov, project ledger heads through docgov/freedoc ports, and workspace deliverable I/O through DeliverableStore so arch-check baseline drops to sql_write:1 path:13.
94 lines
3.0 KiB
Go
94 lines
3.0 KiB
Go
package docduty
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgconn"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// Compile-time check: pool satisfies DB.
|
|
var _ DB = (*pgxpool.Pool)(nil)
|
|
|
|
// DB covers pool and tx for shared member/resolve reads.
|
|
type DB interface {
|
|
Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
|
|
QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
|
|
Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error)
|
|
}
|
|
|
|
// LoadProjectMembers 读取项目在任/历史成员与创建者,供职责解析。
|
|
func LoadProjectMembers(ctx context.Context, pool *pgxpool.Pool, projectID int64) ([]Member, Holder, error) {
|
|
if pool == nil || projectID <= 0 {
|
|
return nil, Holder{}, nil
|
|
}
|
|
return LoadProjectMembersDB(ctx, pool, projectID)
|
|
}
|
|
|
|
// LoadProjectMembersDB reads members via pool or an open transaction.
|
|
func LoadProjectMembersDB(ctx context.Context, db DB, projectID int64) ([]Member, Holder, error) {
|
|
if db == nil || projectID <= 0 {
|
|
return nil, Holder{}, nil
|
|
}
|
|
return loadProjectMembers(ctx, db, projectID)
|
|
}
|
|
|
|
func loadProjectMembers(ctx context.Context, db DB, projectID int64) ([]Member, Holder, error) {
|
|
var creator Holder
|
|
err := db.QueryRow(ctx, `
|
|
SELECT COALESCE(p.creator_user_id, 0), COALESCE(u.name, '')
|
|
FROM projects p
|
|
LEFT JOIN users u ON u.id = p.creator_user_id
|
|
WHERE p.id = $1
|
|
`, projectID).Scan(&creator.UserID, &creator.Name)
|
|
if err != nil && err != pgx.ErrNoRows {
|
|
return nil, Holder{}, fmt.Errorf("docduty: load creator: %w", err)
|
|
}
|
|
creator.Name = strings.TrimSpace(creator.Name)
|
|
|
|
rows, err := db.Query(ctx, `
|
|
SELECT pm.user_id, COALESCE(u.name, ''), COALESCE(pm.status, ''),
|
|
COALESCE(pm.project_role, ''), COALESCE(pm.document_duty, '')
|
|
FROM project_members pm
|
|
JOIN users u ON u.id = pm.user_id
|
|
WHERE pm.project_id = $1
|
|
ORDER BY pm.user_id ASC
|
|
`, projectID)
|
|
if err != nil {
|
|
return nil, creator, fmt.Errorf("docduty: load members: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
members := make([]Member, 0)
|
|
for rows.Next() {
|
|
var m Member
|
|
if err := rows.Scan(&m.UserID, &m.Name, &m.Status, &m.ProjectRole, &m.DocumentDuty); err != nil {
|
|
return nil, creator, err
|
|
}
|
|
m.Name = strings.TrimSpace(m.Name)
|
|
members = append(members, m)
|
|
}
|
|
return members, creator, rows.Err()
|
|
}
|
|
|
|
// ResolveProject 从库表解析项目默认文档职责。
|
|
func ResolveProject(ctx context.Context, pool *pgxpool.Pool, projectID int64, req RequestDuties) (Binding, error) {
|
|
members, creator, err := LoadProjectMembers(ctx, pool, projectID)
|
|
if err != nil {
|
|
return Binding{}, err
|
|
}
|
|
return Resolve(members, creator, req), nil
|
|
}
|
|
|
|
// ResolveProjectTx resolves project document duties inside the caller's transaction.
|
|
func ResolveProjectTx(ctx context.Context, tx pgx.Tx, projectID int64, req RequestDuties) (Binding, error) {
|
|
members, creator, err := loadProjectMembers(ctx, tx, projectID)
|
|
if err != nil {
|
|
return Binding{}, err
|
|
}
|
|
return Resolve(members, creator, req), nil
|
|
}
|