Route Prismatic file import through the shared upsert
The manual JSON file import had its own insert/update loop; it now maps to PowderCatalogItem and calls IPowderCatalogUpsertService.UpsertAsync — the same path the Columbia API sync uses — so there is a single upsert/diff implementation (and the file import now gets inventory propagation for free). Items are tagged Source = "Manual JSON Import". Also makes the shared upsert merge-not-wipe: it only overwrites a field when the incoming feed provides a value (non-blank string, price > 0, nullable HasValue), so a partial feed like the Prismatic scrape (no cure/chemistry) can't null out data another source or enrichment populated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -194,7 +194,7 @@ public class PowderCatalogUpsertService : IPowderCatalogUpsertService
|
||||
|
||||
changed |= Set(() => dest.ColorName, v => dest.ColorName = v, src.ColorName);
|
||||
changed |= Set(() => dest.Description, v => dest.Description = v, src.Description);
|
||||
changed |= dest.UnitPrice != src.UnitPrice && Assign(() => dest.UnitPrice = src.UnitPrice);
|
||||
changed |= src.UnitPrice > 0 && dest.UnitPrice != src.UnitPrice && Assign(() => dest.UnitPrice = src.UnitPrice);
|
||||
changed |= Set(() => dest.PriceTiersJson, v => dest.PriceTiersJson = v, src.PriceTiersJson);
|
||||
changed |= Set(() => dest.ImageUrl, v => dest.ImageUrl = v, src.ImageUrl);
|
||||
changed |= Set(() => dest.SdsUrl, v => dest.SdsUrl = v, src.SdsUrl);
|
||||
@@ -205,9 +205,9 @@ public class PowderCatalogUpsertService : IPowderCatalogUpsertService
|
||||
changed |= Set(() => dest.MilThickness, v => dest.MilThickness = v, src.MilThickness);
|
||||
changed |= Set(() => dest.CureScheduleText, v => dest.CureScheduleText = v, src.CureScheduleText);
|
||||
changed |= Set(() => dest.CureCurvesJson, v => dest.CureCurvesJson = v, src.CureCurvesJson);
|
||||
changed |= dest.CureTemperatureF != src.CureTemperatureF && Assign(() => dest.CureTemperatureF = src.CureTemperatureF);
|
||||
changed |= dest.CureTimeMinutes != src.CureTimeMinutes && Assign(() => dest.CureTimeMinutes = src.CureTimeMinutes);
|
||||
changed |= dest.RequiresClearCoat != src.RequiresClearCoat && Assign(() => dest.RequiresClearCoat = src.RequiresClearCoat);
|
||||
changed |= src.CureTemperatureF.HasValue && dest.CureTemperatureF != src.CureTemperatureF && Assign(() => dest.CureTemperatureF = src.CureTemperatureF);
|
||||
changed |= src.CureTimeMinutes.HasValue && dest.CureTimeMinutes != src.CureTimeMinutes && Assign(() => dest.CureTimeMinutes = src.CureTimeMinutes);
|
||||
changed |= src.RequiresClearCoat.HasValue && dest.RequiresClearCoat != src.RequiresClearCoat && Assign(() => dest.RequiresClearCoat = src.RequiresClearCoat);
|
||||
changed |= Set(() => dest.ColorFamilies, v => dest.ColorFamilies = v, src.ColorFamilies);
|
||||
changed |= Set(() => dest.FormulationChanges, v => dest.FormulationChanges = v, src.FormulationChanges);
|
||||
changed |= Set(() => dest.Category, v => dest.Category = v, src.Category);
|
||||
@@ -216,9 +216,15 @@ public class PowderCatalogUpsertService : IPowderCatalogUpsertService
|
||||
return changed;
|
||||
}
|
||||
|
||||
/// <summary>Sets a nullable-string property when it differs (ordinal compare); returns whether it changed.</summary>
|
||||
/// <summary>
|
||||
/// Sets a nullable-string property when the feed provides a non-blank value that differs.
|
||||
/// Merge semantics: a blank incoming value is ignored, so a partial feed (e.g. the Prismatic
|
||||
/// file import, which omits cure/chemistry) never nulls out existing data.
|
||||
/// </summary>
|
||||
private static bool Set(Func<string?> get, Action<string?> set, string? newValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(newValue))
|
||||
return false;
|
||||
if (!string.Equals(get(), newValue, StringComparison.Ordinal))
|
||||
{
|
||||
set(newValue);
|
||||
|
||||
Reference in New Issue
Block a user