- CatalogStatus type with forward-only state machine (draft→indexed→…→complete) - Transition() enforces valid transitions, returns error with 'invalid transition' message - ParseCatalogStatus() validates known status strings - HardwareRecord domain type composing netbox.Device with quality gate state - CatalogUpdater.UpdateCatalogStatus() validates transition then PatchCustomFields - SyncTags() normalizes tags (slug form) and ensures they exist in NetBox - normalizeTags deduplicates across case/whitespace/space-vs-hyphen variants - ensureTag uses go-netbox v4 NewTagRequest(name, slug) / ExtrasTagsCreate - All 12 state machine table-driven tests pass
65 lines
2.1 KiB
Go
65 lines
2.1 KiB
Go
package inventory
|
|
|
|
import "fmt"
|
|
|
|
// CatalogStatus represents the lifecycle stage of a cataloged hardware item.
|
|
// Stored as the catalog_status custom field value in NetBox.
|
|
type CatalogStatus string
|
|
|
|
const (
|
|
StatusDraft CatalogStatus = "draft"
|
|
StatusIndexed CatalogStatus = "indexed"
|
|
StatusNeedsResearch CatalogStatus = "needs_research"
|
|
StatusResearched CatalogStatus = "researched"
|
|
StatusComplete CatalogStatus = "complete"
|
|
)
|
|
|
|
// validTransitions defines the allowed state machine transitions.
|
|
// No backward transitions are permitted (lifecycle is forward-only).
|
|
var validTransitions = map[CatalogStatus][]CatalogStatus{
|
|
StatusDraft: {StatusIndexed},
|
|
StatusIndexed: {StatusNeedsResearch, StatusResearched},
|
|
StatusNeedsResearch: {StatusResearched},
|
|
StatusResearched: {StatusComplete},
|
|
StatusComplete: {}, // terminal — no further transitions
|
|
}
|
|
|
|
// CanTransitionTo returns true if transitioning from s to next is permitted.
|
|
func (s CatalogStatus) CanTransitionTo(next CatalogStatus) bool {
|
|
allowed, ok := validTransitions[s]
|
|
if !ok {
|
|
return false
|
|
}
|
|
for _, a := range allowed {
|
|
if a == next {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Transition attempts to move from current to next status.
|
|
// Returns the new status on success, or an error describing the invalid transition.
|
|
func Transition(current, next CatalogStatus) (CatalogStatus, error) {
|
|
if !current.CanTransitionTo(next) {
|
|
return "", fmt.Errorf("invalid transition: %s → %s (not in valid transitions map)", current, next)
|
|
}
|
|
return next, nil
|
|
}
|
|
|
|
// ParseCatalogStatus parses a string to a CatalogStatus.
|
|
// Returns error for unknown status values.
|
|
func ParseCatalogStatus(s string) (CatalogStatus, error) {
|
|
cs := CatalogStatus(s)
|
|
if _, ok := validTransitions[cs]; ok {
|
|
return cs, nil
|
|
}
|
|
return "", fmt.Errorf("unknown catalog status: %q (valid: draft, indexed, needs_research, researched, complete)", s)
|
|
}
|
|
|
|
// AllStatuses returns all valid catalog statuses in lifecycle order.
|
|
func AllStatuses() []CatalogStatus {
|
|
return []CatalogStatus{
|
|
StatusDraft, StatusIndexed, StatusNeedsResearch, StatusResearched, StatusComplete,
|
|
}
|
|
}
|