refactor: move sourcing_link and standard_cost from item columns to revision properties

- Add migration 013 to copy sourcing_link/standard_cost values into
  current revision properties JSONB and drop the columns from items table
- Remove SourcingLink/StandardCost from Go Item struct and all DB queries
  (items.go, audit_queries.go, projects.go)
- Remove from API request/response structs and handlers
- Update CSV/ODS/BOM export/import to read these from revision properties
- Update audit handlers to score as regular property fields
- Remove from frontend Item type and hardcoded form fields
- MainTab now reads sourcing_link/standard_cost from item.properties
- CreateItemPane/EditItemPane no longer have dedicated fields for these;
  they will be rendered as schema-driven property fields
This commit is contained in:
2026-02-11 09:50:31 -06:00
parent 2157b40d06
commit b3c748ef10
16 changed files with 483 additions and 300 deletions

View File

@@ -114,8 +114,6 @@ var manufacturedWeights = map[string]float64{
var itemLevelFields = map[string]bool{
"description": true,
"sourcing_type": true,
"sourcing_link": true,
"standard_cost": true,
"long_description": true,
}
@@ -258,18 +256,6 @@ func scoreItem(
processField("description", "item", "string", item.Description)
processField("sourcing_type", "item", "string", item.SourcingType)
var sourcingLinkVal any
if item.SourcingLink != nil {
sourcingLinkVal = *item.SourcingLink
}
processField("sourcing_link", "item", "string", sourcingLinkVal)
var stdCostVal any
if item.StandardCost != nil {
stdCostVal = *item.StandardCost
}
processField("standard_cost", "item", "number", stdCostVal)
var longDescVal any
if item.LongDescription != nil {
longDescVal = *item.LongDescription
@@ -287,10 +273,6 @@ func scoreItem(
if skipFields[key] || itemLevelFields[key] {
continue
}
// sourcing_link and standard_cost are already handled at item level.
if key == "sourcing_link" || key == "standard_cost" {
continue
}
value := item.Properties[key]
processField(key, "property", def.Type, value)
}

View File

@@ -573,8 +573,20 @@ func (s *Server) HandleGetBOMCost(w http.ResponseWriter, r *http.Request) {
for i, e := range entries {
unitCost := 0.0
leaf, err := s.items.GetByID(ctx, e.ItemID)
if err == nil && leaf != nil && leaf.StandardCost != nil {
unitCost = *leaf.StandardCost
if err == nil && leaf != nil {
// Get standard_cost from revision properties
if revs, rerr := s.items.GetRevisions(ctx, leaf.ID); rerr == nil {
for _, rev := range revs {
if rev.RevisionNumber == leaf.CurrentRevision && rev.Properties != nil {
if sc, ok := rev.Properties["standard_cost"]; ok {
if cost, cok := sc.(float64); cok {
unitCost = cost
}
}
break
}
}
}
}
extCost := e.TotalQuantity * unitCost
totalCost += extCost

View File

@@ -51,9 +51,7 @@ var csvColumns = []string{
"category",
"projects", // comma-separated project codes
"sourcing_type",
"sourcing_link",
"long_description",
"standard_cost",
}
// HandleExportCSV exports items to CSV format.
@@ -158,14 +156,8 @@ func (s *Server) HandleExportCSV(w http.ResponseWriter, r *http.Request) {
row[6] = category
row[7] = projectCodes
row[8] = item.SourcingType
if item.SourcingLink != nil {
row[9] = *item.SourcingLink
}
if item.LongDescription != nil {
row[10] = *item.LongDescription
}
if item.StandardCost != nil {
row[11] = strconv.FormatFloat(*item.StandardCost, 'f', -1, 64)
row[9] = *item.LongDescription
}
// Property columns
@@ -366,9 +358,17 @@ func (s *Server) HandleImportCSV(w http.ResponseWriter, r *http.Request) {
// Parse extended fields
sourcingType := getCSVValue(record, colIndex, "sourcing_type")
sourcingLink := getCSVValue(record, colIndex, "sourcing_link")
longDesc := getCSVValue(record, colIndex, "long_description")
stdCostStr := getCSVValue(record, colIndex, "standard_cost")
// sourcing_link and standard_cost are now properties — add to properties map
if sl := getCSVValue(record, colIndex, "sourcing_link"); sl != "" {
properties["sourcing_link"] = sl
}
if sc := getCSVValue(record, colIndex, "standard_cost"); sc != "" {
if cost, err := strconv.ParseFloat(sc, 64); err == nil {
properties["standard_cost"] = cost
}
}
// Create item
item := &db.Item{
@@ -382,17 +382,9 @@ func (s *Server) HandleImportCSV(w http.ResponseWriter, r *http.Request) {
if sourcingType != "" {
item.SourcingType = sourcingType
}
if sourcingLink != "" {
item.SourcingLink = &sourcingLink
}
if longDesc != "" {
item.LongDescription = &longDesc
}
if stdCostStr != "" {
if cost, err := strconv.ParseFloat(stdCostStr, 64); err == nil {
item.StandardCost = &cost
}
}
if err := s.items.Create(ctx, item, properties); err != nil {
result.Errors = append(result.Errors, CSVImportErr{
@@ -585,9 +577,7 @@ func isStandardColumn(col string) bool {
"objects": true, // FreeCAD objects data - skip on import
"archived_at": true,
"sourcing_type": true,
"sourcing_link": true,
"long_description": true,
"standard_cost": true,
}
return standardCols[col]
}

View File

@@ -256,9 +256,7 @@ type ItemResponse struct {
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
SourcingType string `json:"sourcing_type"`
SourcingLink *string `json:"sourcing_link,omitempty"`
LongDescription *string `json:"long_description,omitempty"`
StandardCost *float64 `json:"standard_cost,omitempty"`
ThumbnailKey *string `json:"thumbnail_key,omitempty"`
FileCount int `json:"file_count"`
FilesTotalSize int64 `json:"files_total_size"`
@@ -273,9 +271,7 @@ type CreateItemRequest struct {
Projects []string `json:"projects,omitempty"`
Properties map[string]any `json:"properties,omitempty"`
SourcingType string `json:"sourcing_type,omitempty"`
SourcingLink *string `json:"sourcing_link,omitempty"`
LongDescription *string `json:"long_description,omitempty"`
StandardCost *float64 `json:"standard_cost,omitempty"`
}
// HandleListItems lists items with optional filtering.
@@ -429,9 +425,7 @@ func (s *Server) HandleCreateItem(w http.ResponseWriter, r *http.Request) {
ItemType: itemType,
Description: req.Description,
SourcingType: req.SourcingType,
SourcingLink: req.SourcingLink,
LongDescription: req.LongDescription,
StandardCost: req.StandardCost,
}
if user := auth.UserFromContext(ctx); user != nil {
item.CreatedBy = &user.Username
@@ -557,9 +551,7 @@ type UpdateItemRequest struct {
Properties map[string]any `json:"properties,omitempty"`
Comment string `json:"comment,omitempty"`
SourcingType *string `json:"sourcing_type,omitempty"`
SourcingLink *string `json:"sourcing_link,omitempty"`
LongDescription *string `json:"long_description,omitempty"`
StandardCost *float64 `json:"standard_cost,omitempty"`
}
// HandleUpdateItem updates an item's fields and/or creates a new revision.
@@ -590,9 +582,7 @@ func (s *Server) HandleUpdateItem(w http.ResponseWriter, r *http.Request) {
ItemType: item.ItemType,
Description: item.Description,
SourcingType: req.SourcingType,
SourcingLink: req.SourcingLink,
LongDescription: req.LongDescription,
StandardCost: req.StandardCost,
}
if req.PartNumber != "" {
@@ -1204,9 +1194,7 @@ func itemToResponse(item *db.Item) ItemResponse {
CreatedAt: item.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
UpdatedAt: item.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
SourcingType: item.SourcingType,
SourcingLink: item.SourcingLink,
LongDescription: item.LongDescription,
StandardCost: item.StandardCost,
ThumbnailKey: item.ThumbnailKey,
}
}

View File

@@ -138,21 +138,11 @@ func (s *Server) HandleExportODS(w http.ResponseWriter, r *http.Request) {
ods.StringCell(item.SourcingType),
}
if item.SourcingLink != nil {
cells = append(cells, ods.StringCell(*item.SourcingLink))
} else {
cells = append(cells, ods.EmptyCell())
}
if item.LongDescription != nil {
cells = append(cells, ods.StringCell(*item.LongDescription))
} else {
cells = append(cells, ods.EmptyCell())
}
if item.StandardCost != nil {
cells = append(cells, ods.CurrencyCell(*item.StandardCost))
} else {
cells = append(cells, ods.EmptyCell())
}
// Property columns
if includeProps {
@@ -419,6 +409,16 @@ func (s *Server) HandleImportODS(w http.ResponseWriter, r *http.Request) {
longDesc := getCellValue("long_description")
stdCostStr := getCellValue("standard_cost")
// Put sourcing_link and standard_cost into properties
if sourcingLink != "" {
properties["sourcing_link"] = sourcingLink
}
if stdCostStr != "" {
if cost, err := strconv.ParseFloat(strings.TrimLeft(stdCostStr, "$"), 64); err == nil {
properties["standard_cost"] = cost
}
}
item := &db.Item{
PartNumber: partNumber,
ItemType: itemType,
@@ -430,17 +430,9 @@ func (s *Server) HandleImportODS(w http.ResponseWriter, r *http.Request) {
if sourcingType != "" {
item.SourcingType = sourcingType
}
if sourcingLink != "" {
item.SourcingLink = &sourcingLink
}
if longDesc != "" {
item.LongDescription = &longDesc
}
if stdCostStr != "" {
if cost, err := strconv.ParseFloat(strings.TrimLeft(stdCostStr, "$"), 64); err == nil {
item.StandardCost = &cost
}
}
if err := s.items.Create(ctx, item, properties); err != nil {
result.Errors = append(result.Errors, CSVImportErr{
@@ -580,9 +572,16 @@ func (s *Server) HandleExportBOMODS(w http.ResponseWriter, r *http.Request) {
childItem, _ := s.items.GetByPartNumber(ctx, e.ChildPartNumber)
unitCost, hasUnitCost := getMetaFloat(e.Metadata, "unit_cost")
if !hasUnitCost && childItem != nil && childItem.StandardCost != nil {
unitCost = *childItem.StandardCost
hasUnitCost = true
if !hasUnitCost && childItem != nil {
// Fall back to standard_cost from revision properties
if childProps := itemPropsCache[e.ChildPartNumber]; childProps != nil {
if sc, ok := childProps["standard_cost"]; ok {
if cost, cok := sc.(float64); cok {
unitCost = cost
hasUnitCost = true
}
}
}
}
qty := 0.0
@@ -682,6 +681,21 @@ func (s *Server) HandleProjectSheetODS(w http.ResponseWriter, r *http.Request) {
return
}
// Build item properties cache for sourcing_link / standard_cost
itemPropsMap := make(map[string]map[string]any)
for _, item := range items {
revisions, err := s.items.GetRevisions(ctx, item.ID)
if err != nil {
continue
}
for _, rev := range revisions {
if rev.RevisionNumber == item.CurrentRevision && rev.Properties != nil {
itemPropsMap[item.ID] = rev.Properties
break
}
}
}
// Sheet 1: Items list
itemHeaders := []string{
"PN", "Type", "Description", "Revision", "Category",
@@ -696,6 +710,8 @@ func (s *Server) HandleProjectSheetODS(w http.ResponseWriter, r *http.Request) {
itemRows = append(itemRows, ods.Row{Cells: itemHeaderCells})
for _, item := range items {
props := itemPropsMap[item.ID]
cells := []ods.Cell{
ods.StringCell(item.PartNumber),
ods.StringCell(item.ItemType),
@@ -704,13 +720,17 @@ func (s *Server) HandleProjectSheetODS(w http.ResponseWriter, r *http.Request) {
ods.StringCell(parseCategory(item.PartNumber)),
ods.StringCell(item.SourcingType),
}
if item.SourcingLink != nil {
cells = append(cells, ods.StringCell(*item.SourcingLink))
if sl, ok := props["sourcing_link"]; ok {
cells = append(cells, ods.StringCell(formatPropertyValue(sl)))
} else {
cells = append(cells, ods.EmptyCell())
}
if item.StandardCost != nil {
cells = append(cells, ods.CurrencyCell(*item.StandardCost))
if sc, ok := props["standard_cost"]; ok {
if cost, cok := sc.(float64); cok {
cells = append(cells, ods.CurrencyCell(cost))
} else {
cells = append(cells, ods.StringCell(formatPropertyValue(sc)))
}
} else {
cells = append(cells, ods.EmptyCell())
}
@@ -746,9 +766,27 @@ func (s *Server) HandleProjectSheetODS(w http.ResponseWriter, r *http.Request) {
for _, e := range bomEntries {
childItem, _ := s.items.GetByPartNumber(ctx, e.ChildPartNumber)
unitCost, hasUnitCost := getMetaFloat(e.Metadata, "unit_cost")
if !hasUnitCost && childItem != nil && childItem.StandardCost != nil {
unitCost = *childItem.StandardCost
hasUnitCost = true
if !hasUnitCost && childItem != nil {
// Fall back to standard_cost from revision properties
// Ensure child item props are cached
if _, cached := itemPropsMap[childItem.ID]; !cached {
if revs, err := s.items.GetRevisions(ctx, childItem.ID); err == nil {
for _, rev := range revs {
if rev.RevisionNumber == childItem.CurrentRevision && rev.Properties != nil {
itemPropsMap[childItem.ID] = rev.Properties
break
}
}
}
}
if childRevProps := itemPropsMap[childItem.ID]; childRevProps != nil {
if sc, ok := childRevProps["standard_cost"]; ok {
if cost, cok := sc.(float64); cok {
unitCost = cost
hasUnitCost = true
}
}
}
}
qty := 0.0
if e.Quantity != nil {
@@ -957,7 +995,20 @@ func (s *Server) HandleSheetDiff(w http.ResponseWriter, r *http.Request) {
if costStr != "" {
costStr = strings.TrimLeft(costStr, "$")
if cost, err := strconv.ParseFloat(costStr, 64); err == nil {
if dbItem.StandardCost == nil || *dbItem.StandardCost != cost {
// Compare against standard_cost in revision properties
revisions, _ := s.items.GetRevisions(ctx, dbItem.ID)
var dbCost *float64
for _, rev := range revisions {
if rev.RevisionNumber == dbItem.CurrentRevision && rev.Properties != nil {
if sc, ok := rev.Properties["standard_cost"]; ok {
if c, cok := sc.(float64); cok {
dbCost = &c
}
}
break
}
}
if dbCost == nil || *dbCost != cost {
changes["standard_cost"] = cost
}
}
@@ -986,8 +1037,11 @@ func buildBOMRow(itemLabel string, depth int, source, pn string, item *db.Item,
if item != nil {
description = item.Description
if sourcingLink == "" && item.SourcingLink != nil {
sourcingLink = *item.SourcingLink
}
// Fall back to sourcing_link from revision properties
if sourcingLink == "" && props != nil {
if sl, ok := props["sourcing_link"]; ok {
sourcingLink = formatPropertyValue(sl)
}
}

View File

@@ -31,7 +31,7 @@ func (r *ItemRepository) ListItemsWithProperties(ctx context.Context, opts Audit
query = `
SELECT DISTINCT i.id, i.part_number, i.schema_id, i.item_type, i.description,
i.created_at, i.updated_at, i.archived_at, i.current_revision,
i.sourcing_type, i.sourcing_link, i.long_description, i.standard_cost,
i.sourcing_type, i.long_description,
COALESCE(r.properties, '{}'::jsonb) as properties
FROM items i
LEFT JOIN revisions r ON r.item_id = i.id AND r.revision_number = i.current_revision
@@ -45,7 +45,7 @@ func (r *ItemRepository) ListItemsWithProperties(ctx context.Context, opts Audit
query = `
SELECT i.id, i.part_number, i.schema_id, i.item_type, i.description,
i.created_at, i.updated_at, i.archived_at, i.current_revision,
i.sourcing_type, i.sourcing_link, i.long_description, i.standard_cost,
i.sourcing_type, i.long_description,
COALESCE(r.properties, '{}'::jsonb) as properties
FROM items i
LEFT JOIN revisions r ON r.item_id = i.id AND r.revision_number = i.current_revision
@@ -85,7 +85,7 @@ func (r *ItemRepository) ListItemsWithProperties(ctx context.Context, opts Audit
err := rows.Scan(
&iwp.ID, &iwp.PartNumber, &iwp.SchemaID, &iwp.ItemType, &iwp.Description,
&iwp.CreatedAt, &iwp.UpdatedAt, &iwp.ArchivedAt, &iwp.CurrentRevision,
&iwp.SourcingType, &iwp.SourcingLink, &iwp.LongDescription, &iwp.StandardCost,
&iwp.SourcingType, &iwp.LongDescription,
&propsJSON,
)
if err != nil {

View File

@@ -24,11 +24,9 @@ type Item struct {
CADFilePath *string
CreatedBy *string
UpdatedBy *string
SourcingType string // "manufactured" or "purchased"
SourcingLink *string // URL to supplier/datasheet
LongDescription *string // extended description
StandardCost *float64 // baseline unit cost
ThumbnailKey *string // MinIO key for item thumbnail
SourcingType string // "manufactured" or "purchased"
LongDescription *string // extended description
ThumbnailKey *string // MinIO key for item thumbnail
}
// Revision represents a revision record.
@@ -96,11 +94,11 @@ func (r *ItemRepository) Create(ctx context.Context, item *Item, properties map[
}
err := tx.QueryRow(ctx, `
INSERT INTO items (part_number, schema_id, item_type, description, created_by,
sourcing_type, sourcing_link, long_description, standard_cost)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
sourcing_type, long_description)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, created_at, updated_at, current_revision
`, item.PartNumber, item.SchemaID, item.ItemType, item.Description, item.CreatedBy,
sourcingType, item.SourcingLink, item.LongDescription, item.StandardCost,
sourcingType, item.LongDescription,
).Scan(
&item.ID, &item.CreatedAt, &item.UpdatedAt, &item.CurrentRevision,
)
@@ -133,7 +131,7 @@ func (r *ItemRepository) GetByPartNumber(ctx context.Context, partNumber string)
SELECT id, part_number, schema_id, item_type, description,
created_at, updated_at, archived_at, current_revision,
cad_synced_at, cad_file_path,
sourcing_type, sourcing_link, long_description, standard_cost,
sourcing_type, long_description,
thumbnail_key
FROM items
WHERE part_number = $1 AND archived_at IS NULL
@@ -141,7 +139,7 @@ func (r *ItemRepository) GetByPartNumber(ctx context.Context, partNumber string)
&item.ID, &item.PartNumber, &item.SchemaID, &item.ItemType, &item.Description,
&item.CreatedAt, &item.UpdatedAt, &item.ArchivedAt, &item.CurrentRevision,
&item.CADSyncedAt, &item.CADFilePath,
&item.SourcingType, &item.SourcingLink, &item.LongDescription, &item.StandardCost,
&item.SourcingType, &item.LongDescription,
&item.ThumbnailKey,
)
if err == pgx.ErrNoRows {
@@ -160,7 +158,7 @@ func (r *ItemRepository) GetByID(ctx context.Context, id string) (*Item, error)
SELECT id, part_number, schema_id, item_type, description,
created_at, updated_at, archived_at, current_revision,
cad_synced_at, cad_file_path,
sourcing_type, sourcing_link, long_description, standard_cost,
sourcing_type, long_description,
thumbnail_key
FROM items
WHERE id = $1
@@ -168,7 +166,7 @@ func (r *ItemRepository) GetByID(ctx context.Context, id string) (*Item, error)
&item.ID, &item.PartNumber, &item.SchemaID, &item.ItemType, &item.Description,
&item.CreatedAt, &item.UpdatedAt, &item.ArchivedAt, &item.CurrentRevision,
&item.CADSyncedAt, &item.CADFilePath,
&item.SourcingType, &item.SourcingLink, &item.LongDescription, &item.StandardCost,
&item.SourcingType, &item.LongDescription,
&item.ThumbnailKey,
)
if err == pgx.ErrNoRows {
@@ -192,7 +190,7 @@ func (r *ItemRepository) List(ctx context.Context, opts ListOptions) ([]*Item, e
query = `
SELECT DISTINCT i.id, i.part_number, i.schema_id, i.item_type, i.description,
i.created_at, i.updated_at, i.archived_at, i.current_revision,
i.sourcing_type, i.sourcing_link, i.long_description, i.standard_cost,
i.sourcing_type, i.long_description,
i.thumbnail_key
FROM items i
JOIN item_projects ip ON ip.item_id = i.id
@@ -205,7 +203,7 @@ func (r *ItemRepository) List(ctx context.Context, opts ListOptions) ([]*Item, e
query = `
SELECT id, part_number, schema_id, item_type, description,
created_at, updated_at, archived_at, current_revision,
sourcing_type, sourcing_link, long_description, standard_cost,
sourcing_type, long_description,
thumbnail_key
FROM items
WHERE archived_at IS NULL
@@ -257,7 +255,7 @@ func (r *ItemRepository) List(ctx context.Context, opts ListOptions) ([]*Item, e
err := rows.Scan(
&item.ID, &item.PartNumber, &item.SchemaID, &item.ItemType, &item.Description,
&item.CreatedAt, &item.UpdatedAt, &item.ArchivedAt, &item.CurrentRevision,
&item.SourcingType, &item.SourcingLink, &item.LongDescription, &item.StandardCost,
&item.SourcingType, &item.LongDescription,
&item.ThumbnailKey,
)
if err != nil {
@@ -659,9 +657,7 @@ type UpdateItemFields struct {
Description string
UpdatedBy *string
SourcingType *string
SourcingLink *string
LongDescription *string
StandardCost *float64
}
// Update modifies an item's fields. The UUID remains stable.
@@ -670,16 +666,12 @@ func (r *ItemRepository) Update(ctx context.Context, id string, fields UpdateIte
UPDATE items
SET part_number = $2, item_type = $3, description = $4, updated_by = $5,
sourcing_type = COALESCE($6, sourcing_type),
sourcing_link = CASE WHEN $7::boolean THEN $8 ELSE sourcing_link END,
long_description = CASE WHEN $9::boolean THEN $10 ELSE long_description END,
standard_cost = CASE WHEN $11::boolean THEN $12 ELSE standard_cost END,
long_description = CASE WHEN $7::boolean THEN $8 ELSE long_description END,
updated_at = now()
WHERE id = $1 AND archived_at IS NULL
`, id, fields.PartNumber, fields.ItemType, fields.Description, fields.UpdatedBy,
fields.SourcingType,
fields.SourcingLink != nil, fields.SourcingLink,
fields.LongDescription != nil, fields.LongDescription,
fields.StandardCost != nil, fields.StandardCost,
)
if err != nil {
return fmt.Errorf("updating item: %w", err)

View File

@@ -134,12 +134,10 @@ func TestItemUpdate(t *testing.T) {
t.Fatalf("Create: %v", err)
}
cost := 42.50
err := repo.Update(ctx, item.ID, UpdateItemFields{
PartNumber: "UPD-001",
ItemType: "part",
Description: "updated",
StandardCost: &cost,
PartNumber: "UPD-001",
ItemType: "part",
Description: "updated",
})
if err != nil {
t.Fatalf("Update: %v", err)
@@ -149,9 +147,6 @@ func TestItemUpdate(t *testing.T) {
if got.Description != "updated" {
t.Errorf("description: got %q, want %q", got.Description, "updated")
}
if got.StandardCost == nil || *got.StandardCost != 42.50 {
t.Errorf("standard_cost: got %v, want 42.50", got.StandardCost)
}
}
func TestItemArchiveUnarchive(t *testing.T) {

View File

@@ -240,7 +240,7 @@ func (r *ProjectRepository) GetItemsForProject(ctx context.Context, projectID st
SELECT i.id, i.part_number, i.schema_id, i.item_type, i.description,
i.created_at, i.updated_at, i.archived_at, i.current_revision,
i.cad_synced_at, i.cad_file_path,
i.sourcing_type, i.sourcing_link, i.long_description, i.standard_cost,
i.sourcing_type, i.long_description,
i.thumbnail_key
FROM items i
JOIN item_projects ip ON ip.item_id = i.id
@@ -259,7 +259,7 @@ func (r *ProjectRepository) GetItemsForProject(ctx context.Context, projectID st
&item.ID, &item.PartNumber, &item.SchemaID, &item.ItemType, &item.Description,
&item.CreatedAt, &item.UpdatedAt, &item.ArchivedAt, &item.CurrentRevision,
&item.CADSyncedAt, &item.CADFilePath,
&item.SourcingType, &item.SourcingLink, &item.LongDescription, &item.StandardCost,
&item.SourcingType, &item.LongDescription,
&item.ThumbnailKey,
); err != nil {
return nil, err