Deduplicate vendor seen by partnumber and fix vendor mappings list
This commit is contained in:
@@ -132,18 +132,40 @@ func (s *VendorMappingService) UpsertMapping(vendor, partnumber, lotName, descri
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
var existing models.VendorPartnumberSeen
|
||||
err = tx.Where("LOWER(TRIM(partnumber)) = LOWER(TRIM(?))", partnumber).First(&existing).Error
|
||||
if err == nil {
|
||||
updates := map[string]any{
|
||||
"last_seen_at": now,
|
||||
}
|
||||
if strings.TrimSpace(existing.SourceType) != "stock" {
|
||||
updates["source_type"] = "manual"
|
||||
}
|
||||
if strings.TrimSpace(existing.Vendor) == "" && vendor != "" {
|
||||
updates["vendor"] = vendor
|
||||
}
|
||||
if descPtr != nil {
|
||||
updates["description"] = *descPtr
|
||||
}
|
||||
return tx.Model(&models.VendorPartnumberSeen{}).Where("id = ?", existing.ID).Updates(updates).Error
|
||||
}
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
seen := models.VendorPartnumberSeen{
|
||||
SourceType: "manual",
|
||||
Vendor: vendor,
|
||||
Partnumber: partnumber,
|
||||
LastSeenAt: now,
|
||||
Description: func() *string {
|
||||
if descPtr == nil {
|
||||
return nil
|
||||
}
|
||||
v := *descPtr
|
||||
return &v
|
||||
}(),
|
||||
}
|
||||
if descPtr != nil {
|
||||
seen.Description = descPtr
|
||||
}
|
||||
return tx.Where("source_type = ? AND vendor = ? AND partnumber = ?", "manual", vendor, partnumber).
|
||||
Assign(seen).
|
||||
FirstOrCreate(&seen).Error
|
||||
return tx.Create(&seen).Error
|
||||
})
|
||||
}
|
||||
|
||||
@@ -168,7 +190,9 @@ func (s *VendorMappingService) SetIgnore(vendor, partnumber, ignoredBy string, i
|
||||
updates["ignored_at"] = nil
|
||||
updates["ignored_by"] = nil
|
||||
}
|
||||
res := tx.Model(&models.VendorPartnumberSeen{}).Where("vendor = ? AND partnumber = ?", vendor, partnumber).Updates(updates)
|
||||
res := tx.Model(&models.VendorPartnumberSeen{}).
|
||||
Where("LOWER(TRIM(partnumber)) = LOWER(TRIM(?))", partnumber).
|
||||
Updates(updates)
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
@@ -242,51 +266,71 @@ func (s *VendorMappingService) List(page, perPage int, search string, unmappedOn
|
||||
offset := (page - 1) * perPage
|
||||
|
||||
base := s.db.Table(`(
|
||||
SELECT
|
||||
COALESCE(TRIM(vendor), '') AS vendor,
|
||||
TRIM(partnumber) AS partnumber
|
||||
SELECT TRIM(partnumber) AS partnumber
|
||||
FROM lot_partnumbers
|
||||
WHERE TRIM(COALESCE(partnumber, '')) <> ''
|
||||
UNION
|
||||
SELECT
|
||||
COALESCE(TRIM(vendor), '') AS vendor,
|
||||
TRIM(partnumber) AS partnumber
|
||||
SELECT TRIM(partnumber) AS partnumber
|
||||
FROM qt_vendor_partnumber_seen
|
||||
WHERE TRIM(COALESCE(partnumber, '')) <> ''
|
||||
) k`).
|
||||
Select(`
|
||||
k.vendor,
|
||||
COALESCE(NULLIF(sa.vendor, ''), NULLIF(lp.vendor, ''), '') as vendor,
|
||||
k.partnumber,
|
||||
COALESCE(lp_exact.lot_name, lp_fallback.lot_name, '') as lot_name,
|
||||
COALESCE(lp_exact.description, lp_fallback.description, sa.description, '') as description,
|
||||
COALESCE(lp.lot_name, '') as lot_name,
|
||||
COALESCE(lp.description, sa.description, '') as description,
|
||||
COALESCE(sa.ignored, 0) as ignored,
|
||||
COALESCE(sa.sources, '') as sources,
|
||||
sa.last_seen_at as last_seen_at,
|
||||
CASE WHEN COALESCE(lp_exact.lot_name, lp_fallback.lot_name, '') = '' THEN 1 ELSE 0 END as unmapped,
|
||||
CASE WHEN COALESCE(lp.lot_name, '') = '' THEN 1 ELSE 0 END as unmapped,
|
||||
CASE WHEN b.bundle_lot_name IS NULL THEN 'single' ELSE 'bundle' END as mapping_type,
|
||||
COALESCE((SELECT COUNT(*) FROM qt_lot_bundle_items bi WHERE bi.bundle_lot_name = COALESCE(lp_exact.lot_name, lp_fallback.lot_name, '')), 0) as bundle_item_count
|
||||
COALESCE((SELECT COUNT(*) FROM qt_lot_bundle_items bi WHERE bi.bundle_lot_name = COALESCE(lp.lot_name, '')), 0) as bundle_item_count
|
||||
`).
|
||||
Joins("LEFT JOIN lot_partnumbers lp_exact ON lp_exact.vendor = k.vendor AND lp_exact.partnumber = k.partnumber").
|
||||
Joins("LEFT JOIN lot_partnumbers lp_fallback ON lp_fallback.vendor = '' AND lp_fallback.partnumber = k.partnumber AND k.vendor <> ''").
|
||||
Joins(`LEFT JOIN (
|
||||
SELECT
|
||||
vendor,
|
||||
partnumber,
|
||||
SUBSTRING_INDEX(
|
||||
GROUP_CONCAT(COALESCE(TRIM(vendor), '') ORDER BY (TRIM(COALESCE(vendor, '')) <> '') DESC, vendor ASC SEPARATOR '\n'),
|
||||
'\n',
|
||||
1
|
||||
) AS vendor,
|
||||
SUBSTRING_INDEX(
|
||||
GROUP_CONCAT(COALESCE(TRIM(lot_name), '') ORDER BY (TRIM(COALESCE(vendor, '')) <> '') DESC, vendor ASC SEPARATOR '\n'),
|
||||
'\n',
|
||||
1
|
||||
) AS lot_name,
|
||||
SUBSTRING_INDEX(
|
||||
GROUP_CONCAT(COALESCE(TRIM(description), '') ORDER BY (TRIM(COALESCE(vendor, '')) <> '') DESC, vendor ASC SEPARATOR '\n'),
|
||||
'\n',
|
||||
1
|
||||
) AS description
|
||||
FROM lot_partnumbers
|
||||
WHERE TRIM(COALESCE(partnumber, '')) <> ''
|
||||
GROUP BY partnumber
|
||||
) lp ON lp.partnumber = k.partnumber`).
|
||||
Joins(`LEFT JOIN (
|
||||
SELECT
|
||||
partnumber,
|
||||
SUBSTRING_INDEX(
|
||||
GROUP_CONCAT(COALESCE(TRIM(vendor), '') ORDER BY (TRIM(COALESCE(vendor, '')) <> '') DESC, (source_type = 'stock') DESC, last_seen_at DESC, id DESC SEPARATOR '\n'),
|
||||
'\n',
|
||||
1
|
||||
) AS vendor,
|
||||
MAX(is_ignored) AS ignored,
|
||||
GROUP_CONCAT(DISTINCT source_type ORDER BY source_type SEPARATOR ',') AS sources,
|
||||
MAX(last_seen_at) AS last_seen_at,
|
||||
MAX(description) AS description
|
||||
FROM qt_vendor_partnumber_seen
|
||||
GROUP BY vendor, partnumber
|
||||
) sa ON sa.vendor = k.vendor AND sa.partnumber = k.partnumber`).
|
||||
Joins("LEFT JOIN qt_lot_bundles b ON b.bundle_lot_name = COALESCE(lp_exact.lot_name, lp_fallback.lot_name)")
|
||||
GROUP BY partnumber
|
||||
) sa ON sa.partnumber = k.partnumber`).
|
||||
Joins("LEFT JOIN qt_lot_bundles b ON b.bundle_lot_name = COALESCE(lp.lot_name, '')")
|
||||
|
||||
if q := strings.TrimSpace(search); q != "" {
|
||||
like := "%" + q + "%"
|
||||
base = base.Where("k.vendor LIKE ? OR k.partnumber LIKE ? OR COALESCE(lp_exact.lot_name, lp_fallback.lot_name, '') LIKE ? OR COALESCE(lp_exact.description, lp_fallback.description, sa.description, '') LIKE ?", like, like, like, like)
|
||||
base = base.Where("COALESCE(NULLIF(sa.vendor, ''), NULLIF(lp.vendor, ''), '') LIKE ? OR k.partnumber LIKE ? OR COALESCE(lp.lot_name, '') LIKE ? OR COALESCE(lp.description, sa.description, '') LIKE ?", like, like, like, like)
|
||||
}
|
||||
if unmappedOnly {
|
||||
base = base.Where("COALESCE(lp_exact.lot_name, lp_fallback.lot_name, '') = ''").
|
||||
base = base.Where("COALESCE(lp.lot_name, '') = ''").
|
||||
Where("COALESCE(sa.ignored, 0) = 0")
|
||||
}
|
||||
if ignoredOnly {
|
||||
@@ -317,7 +361,7 @@ func (s *VendorMappingService) List(page, perPage int, search string, unmappedOn
|
||||
}
|
||||
|
||||
var rows []row
|
||||
if err := base.Order("k.partnumber ASC").Order("k.vendor ASC").Offset(offset).Limit(perPage).Scan(&rows).Error; err != nil {
|
||||
if err := base.Order("k.partnumber ASC").Offset(offset).Limit(perPage).Scan(&rows).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
items := make([]VendorMappingListRow, 0, len(rows))
|
||||
@@ -375,15 +419,9 @@ func (s *VendorMappingService) GetDetail(vendor, partnumber string) (*VendorMapp
|
||||
}
|
||||
|
||||
var seenRows []models.VendorPartnumberSeen
|
||||
if err := s.db.Where("vendor = ? AND partnumber = ?", vendor, partnumber).Find(&seenRows).Error; err != nil {
|
||||
if err := s.db.Where("LOWER(TRIM(partnumber)) = LOWER(TRIM(?))", partnumber).Find(&seenRows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if vendor != "" {
|
||||
var fallbackSeen []models.VendorPartnumberSeen
|
||||
if err := s.db.Where("vendor = '' AND partnumber = ?", partnumber).Find(&fallbackSeen).Error; err == nil {
|
||||
seenRows = append(seenRows, fallbackSeen...)
|
||||
}
|
||||
}
|
||||
ignored := false
|
||||
sourceSet := make(map[string]struct{})
|
||||
desc := ""
|
||||
|
||||
Reference in New Issue
Block a user