package db import ( "context" "fmt" "time" "github.com/jackc/pgx/v5" ) // ItemDependency represents a row in the item_dependencies table. type ItemDependency struct { ID string ParentItemID string ChildUUID string ChildPartNumber *string ChildRevision *int Quantity *float64 Label *string Relationship string RevisionNumber int CreatedAt time.Time } // ResolvedDependency extends ItemDependency with resolution info from a LEFT JOIN. type ResolvedDependency struct { ItemDependency ResolvedPartNumber *string ResolvedRevision *int Resolved bool } // ItemDependencyRepository provides item_dependencies database operations. type ItemDependencyRepository struct { db *DB } // NewItemDependencyRepository creates a new item dependency repository. func NewItemDependencyRepository(db *DB) *ItemDependencyRepository { return &ItemDependencyRepository{db: db} } // ReplaceForRevision atomically replaces all dependencies for an item's revision. // Deletes existing rows for the parent item and inserts the new set. func (r *ItemDependencyRepository) ReplaceForRevision(ctx context.Context, parentItemID string, revisionNumber int, deps []*ItemDependency) error { return r.db.Tx(ctx, func(tx pgx.Tx) error { _, err := tx.Exec(ctx, `DELETE FROM item_dependencies WHERE parent_item_id = $1`, parentItemID) if err != nil { return fmt.Errorf("deleting old dependencies: %w", err) } for _, d := range deps { _, err := tx.Exec(ctx, ` INSERT INTO item_dependencies (parent_item_id, child_uuid, child_part_number, child_revision, quantity, label, relationship, revision_number) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) `, parentItemID, d.ChildUUID, d.ChildPartNumber, d.ChildRevision, d.Quantity, d.Label, d.Relationship, revisionNumber) if err != nil { return fmt.Errorf("inserting dependency: %w", err) } } return nil }) } // ListByItem returns all dependencies for an item. func (r *ItemDependencyRepository) ListByItem(ctx context.Context, parentItemID string) ([]*ItemDependency, error) { rows, err := r.db.pool.Query(ctx, ` SELECT id, parent_item_id, child_uuid, child_part_number, child_revision, quantity, label, relationship, revision_number, created_at FROM item_dependencies WHERE parent_item_id = $1 ORDER BY label NULLS LAST `, parentItemID) if err != nil { return nil, fmt.Errorf("listing dependencies: %w", err) } defer rows.Close() var deps []*ItemDependency for rows.Next() { d := &ItemDependency{} if err := rows.Scan( &d.ID, &d.ParentItemID, &d.ChildUUID, &d.ChildPartNumber, &d.ChildRevision, &d.Quantity, &d.Label, &d.Relationship, &d.RevisionNumber, &d.CreatedAt, ); err != nil { return nil, fmt.Errorf("scanning dependency: %w", err) } deps = append(deps, d) } return deps, nil } // Resolve returns dependencies with child UUIDs resolved against the items table. // Unresolvable UUIDs (external or deleted items) have Resolved=false. func (r *ItemDependencyRepository) Resolve(ctx context.Context, parentItemID string) ([]*ResolvedDependency, error) { rows, err := r.db.pool.Query(ctx, ` SELECT d.id, d.parent_item_id, d.child_uuid, d.child_part_number, d.child_revision, d.quantity, d.label, d.relationship, d.revision_number, d.created_at, i.part_number, i.current_revision FROM item_dependencies d LEFT JOIN items i ON i.id = d.child_uuid AND i.archived_at IS NULL WHERE d.parent_item_id = $1 ORDER BY d.label NULLS LAST `, parentItemID) if err != nil { return nil, fmt.Errorf("resolving dependencies: %w", err) } defer rows.Close() var deps []*ResolvedDependency for rows.Next() { d := &ResolvedDependency{} if err := rows.Scan( &d.ID, &d.ParentItemID, &d.ChildUUID, &d.ChildPartNumber, &d.ChildRevision, &d.Quantity, &d.Label, &d.Relationship, &d.RevisionNumber, &d.CreatedAt, &d.ResolvedPartNumber, &d.ResolvedRevision, ); err != nil { return nil, fmt.Errorf("scanning resolved dependency: %w", err) } d.Resolved = d.ResolvedPartNumber != nil deps = append(deps, d) } return deps, nil }