- Add Dependency type to internal/kc and extract silo/dependencies.json
from .kc files on commit
- Create ItemDependencyRepository with ReplaceForRevision, ListByItem,
and Resolve (LEFT JOIN against items table)
- Add GET /{partNumber}/dependencies and
GET /{partNumber}/dependencies/resolve endpoints
- Index dependencies in extractKCMetadata with SSE broadcast
- Pack real dependency data into .kc files on checkout
- Update PackInput.Dependencies from []any to []Dependency
Closes #143
128 lines
4.1 KiB
Go
128 lines
4.1 KiB
Go
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
|
|
}
|