- BOM paste: auto-detect columns by content (price, qty, PN, description); handles $5,114.00 and European comma-decimal formats - LOT input: HTML5 datalist rebuilt on each renderBOMTable from allComponents; oninput updates data only (no re-render), onchange validates+resolves - BOM persistence: PUT handler explicitly marshals VendorSpec to JSON string (GORM Update does not reliably call driver.Valuer for custom types) - BOM autosave after every resolveBOM() call - Pricing tab: async renderPricingTab() calls /api/quote/price-levels for all resolved LOTs directly — Estimate prices shown even before cart apply - Unresolved PNs pushed to qt_vendor_partnumber_seen via POST /api/sync/partnumber-seen (fire-and-forget from JS) - sync.PushPartnumberSeen(): upsert with ON DUPLICATE KEY UPDATE last_seen_at - partnumber_books: pull ALL books (not only is_active=1); re-pull items when header exists but item count is 0; fallback for missing description column - partnumber_books UI: collapsible snapshot section (collapsed by default), pagination (10/page), sync button always visible in header - vendorSpec handlers: use GetConfigurationByUUID + IsActive check (removed original_username from WHERE — GetUsername returns "" without JWT) - bible/09-vendor-spec.md: updated with all architectural decisions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
50 lines
1.3 KiB
Go
50 lines
1.3 KiB
Go
package sync
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
)
|
|
|
|
// SeenPartnumber represents an unresolved vendor partnumber to report.
|
|
type SeenPartnumber struct {
|
|
Partnumber string
|
|
Description string
|
|
}
|
|
|
|
// PushPartnumberSeen inserts unresolved vendor partnumbers into qt_vendor_partnumber_seen on MariaDB.
|
|
// Uses INSERT ... ON DUPLICATE KEY UPDATE so existing rows are updated (last_seen_at) without error.
|
|
func (s *Service) PushPartnumberSeen(items []SeenPartnumber) error {
|
|
if len(items) == 0 {
|
|
return nil
|
|
}
|
|
|
|
mariaDB, err := s.getDB()
|
|
if err != nil {
|
|
return fmt.Errorf("database not available: %w", err)
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
for _, item := range items {
|
|
if item.Partnumber == "" {
|
|
continue
|
|
}
|
|
err := mariaDB.Exec(`
|
|
INSERT INTO qt_vendor_partnumber_seen
|
|
(source_type, vendor, partnumber, description, last_seen_at)
|
|
VALUES
|
|
('manual', '', ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE
|
|
last_seen_at = VALUES(last_seen_at),
|
|
description = COALESCE(NULLIF(VALUES(description), ''), description)
|
|
`, item.Partnumber, item.Description, now).Error
|
|
if err != nil {
|
|
slog.Error("failed to upsert partnumber_seen", "partnumber", item.Partnumber, "error", err)
|
|
// Continue with remaining items
|
|
}
|
|
}
|
|
|
|
slog.Info("partnumber_seen pushed to server", "count", len(items))
|
|
return nil
|
|
}
|