mirror of
https://gitea.gofwd.group/Forward_Group/ballistic-builder-spring.git
synced 2025-12-05 18:46:44 -05:00
slug handling changes
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
package group.goforward.ballistic.imports;
|
package group.goforward.ballistic.imports;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import group.goforward.ballistic.model.Brand;
|
import group.goforward.ballistic.model.Brand;
|
||||||
import group.goforward.ballistic.model.Merchant;
|
import group.goforward.ballistic.model.Merchant;
|
||||||
@@ -9,7 +11,6 @@ import group.goforward.ballistic.repos.ProductRepository;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
public class MerchantFeedImportServiceImpl implements MerchantFeedImportService {
|
public class MerchantFeedImportServiceImpl implements MerchantFeedImportService {
|
||||||
@@ -33,25 +34,48 @@ public class MerchantFeedImportServiceImpl implements MerchantFeedImportService
|
|||||||
Merchant merchant = merchantRepository.findById(merchantId)
|
Merchant merchant = merchantRepository.findById(merchantId)
|
||||||
.orElseThrow(() -> new IllegalArgumentException("Merchant not found: " + merchantId));
|
.orElseThrow(() -> new IllegalArgumentException("Merchant not found: " + merchantId));
|
||||||
|
|
||||||
// For now, just pick a brand to prove inserts work.
|
// For now, just pick a brand to prove inserts work (Aero Precision for merchant 4).
|
||||||
|
// Later we can switch to row.brandName() + auto-create brands.
|
||||||
Brand brand = brandRepository.findByNameIgnoreCase("Aero Precision")
|
Brand brand = brandRepository.findByNameIgnoreCase("Aero Precision")
|
||||||
.orElseThrow(() -> new IllegalStateException("Brand 'Aero Precision' not found"));
|
.orElseThrow(() -> new IllegalStateException("Brand 'Aero Precision' not found"));
|
||||||
|
|
||||||
// Fake a single row – we’ll swap this for real CSV parsing once the plumbing works
|
// TODO: replace this with real feed parsing:
|
||||||
|
// List<MerchantFeedRow> rows = feedClient.fetch(merchant);
|
||||||
|
// rows.forEach(row -> upsertProduct(merchant, row));
|
||||||
MerchantFeedRow row = new MerchantFeedRow(
|
MerchantFeedRow row = new MerchantFeedRow(
|
||||||
"TEST-SKU-001",
|
"TEST-SKU-001",
|
||||||
"APPG100002",
|
"APPG100002",
|
||||||
brand.getName(),
|
brand.getName(),
|
||||||
"Test Product From Import",
|
"Test Product From Import",
|
||||||
null, null, null, null, null,
|
"This is a long description from AvantLink.",
|
||||||
null, null, null, null, null,
|
"Short description from AvantLink.",
|
||||||
null, null,
|
"Rifles",
|
||||||
null, null, null, null, null, null, null, null
|
"AR-15 Parts",
|
||||||
|
"Handguards & Rails",
|
||||||
|
"https://example.com/thumb.jpg",
|
||||||
|
"https://example.com/image.jpg",
|
||||||
|
"https://example.com/buy-link",
|
||||||
|
"ar-15, handguard, aero",
|
||||||
|
null,
|
||||||
|
new BigDecimal("199.99"), // retailPrice
|
||||||
|
new BigDecimal("149.99"), // salePrice
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"https://example.com/medium.jpg",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
Product p = createProduct(brand, row);
|
Product p = createProduct(brand, row);
|
||||||
|
|
||||||
System.out.println("IMPORT >>> created product id=" + p.getId()
|
System.out.println("IMPORT >>> created product id=" + p.getId()
|
||||||
+ ", name=" + p.getName()
|
+ ", name=" + p.getName()
|
||||||
|
+ ", slug=" + p.getSlug()
|
||||||
|
+ ", platform=" + p.getPlatform()
|
||||||
|
+ ", partRole=" + p.getPartRole()
|
||||||
+ ", merchant=" + merchant.getName());
|
+ ", merchant=" + merchant.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,23 +87,24 @@ public class MerchantFeedImportServiceImpl implements MerchantFeedImportService
|
|||||||
Product p = new Product();
|
Product p = new Product();
|
||||||
p.setBrand(brand);
|
p.setBrand(brand);
|
||||||
|
|
||||||
String name = row.productName();
|
// ---------- NAME ----------
|
||||||
if (name == null || name.isBlank()) {
|
String name = coalesce(
|
||||||
name = row.sku();
|
trimOrNull(row.productName()),
|
||||||
}
|
trimOrNull(row.shortDescription()),
|
||||||
if (name == null || name.isBlank()) {
|
trimOrNull(row.longDescription()),
|
||||||
|
trimOrNull(row.sku())
|
||||||
|
);
|
||||||
|
if (name == null) {
|
||||||
name = "Unknown Product";
|
name = "Unknown Product";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set required fields: name and slug
|
|
||||||
p.setName(name);
|
p.setName(name);
|
||||||
|
|
||||||
// Generate a simple slug from the name (fallback to SKU if needed)
|
// ---------- SLUG ----------
|
||||||
String baseForSlug = name;
|
String baseForSlug = coalesce(
|
||||||
if (baseForSlug == null || baseForSlug.isBlank()) {
|
trimOrNull(name),
|
||||||
baseForSlug = row.sku();
|
trimOrNull(row.sku())
|
||||||
}
|
);
|
||||||
if (baseForSlug == null || baseForSlug.isBlank()) {
|
if (baseForSlug == null) {
|
||||||
baseForSlug = "product-" + System.currentTimeMillis();
|
baseForSlug = "product-" + System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,22 +112,124 @@ public class MerchantFeedImportServiceImpl implements MerchantFeedImportService
|
|||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replaceAll("[^a-z0-9]+", "-")
|
.replaceAll("[^a-z0-9]+", "-")
|
||||||
.replaceAll("(^-|-$)", "");
|
.replaceAll("(^-|-$)", "");
|
||||||
|
|
||||||
if (slug.isBlank()) {
|
if (slug.isBlank()) {
|
||||||
slug = "product-" + System.currentTimeMillis();
|
slug = "product-" + System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
p.setSlug(slug);
|
// Ensure slug is unique by appending a numeric suffix if needed
|
||||||
|
String uniqueSlug = generateUniqueSlug(slug);
|
||||||
|
p.setSlug(uniqueSlug);
|
||||||
|
|
||||||
if (p.getPlatform() == null || p.getPlatform().isBlank()) {
|
// ---------- DESCRIPTIONS ----------
|
||||||
p.setPlatform("AR-15");
|
p.setShortDescription(trimOrNull(row.shortDescription()));
|
||||||
|
p.setDescription(trimOrNull(row.longDescription()));
|
||||||
|
|
||||||
|
// ---------- IMAGE ----------
|
||||||
|
String mainImage = coalesce(
|
||||||
|
trimOrNull(row.imageUrl()),
|
||||||
|
trimOrNull(row.mediumImageUrl()),
|
||||||
|
trimOrNull(row.thumbUrl())
|
||||||
|
);
|
||||||
|
p.setMainImageUrl(mainImage);
|
||||||
|
|
||||||
|
// ---------- IDENTIFIERS ----------
|
||||||
|
// AvantLink "Manufacturer Id" is a good fit for MPN.
|
||||||
|
String mpn = coalesce(
|
||||||
|
trimOrNull(row.manufacturerId()),
|
||||||
|
trimOrNull(row.sku())
|
||||||
|
);
|
||||||
|
p.setMpn(mpn);
|
||||||
|
|
||||||
|
// Feed doesn’t give us UPC in the header you showed.
|
||||||
|
// We’ll leave UPC null for now.
|
||||||
|
p.setUpc(null);
|
||||||
|
|
||||||
|
// ---------- PLATFORM ----------
|
||||||
|
// For now, hard-code to AR-15 to satisfy not-null constraint.
|
||||||
|
// Later we can infer from row.category()/row.department().
|
||||||
|
String platform = inferPlatform(row);
|
||||||
|
p.setPlatform(platform != null ? platform : "AR-15");
|
||||||
|
|
||||||
|
// ---------- PART ROLE ----------
|
||||||
|
// We can do a tiny heuristic off category/subcategory.
|
||||||
|
String partRole = inferPartRole(row);
|
||||||
|
if (partRole == null || partRole.isBlank()) {
|
||||||
|
partRole = "unknown";
|
||||||
}
|
}
|
||||||
|
p.setPartRole(partRole);
|
||||||
if (p.getPartRole() == null || p.getPartRole().isBlank()) {
|
|
||||||
p.setPartRole("unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return productRepository.save(p);
|
return productRepository.save(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Helpers ----------------------------------------------------------
|
||||||
|
|
||||||
|
private String trimOrNull(String value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
String trimmed = value.trim();
|
||||||
|
return trimmed.isEmpty() ? null : trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String coalesce(String... values) {
|
||||||
|
if (values == null) return null;
|
||||||
|
for (String v : values) {
|
||||||
|
if (v != null && !v.isBlank()) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateUniqueSlug(String baseSlug) {
|
||||||
|
String candidate = baseSlug;
|
||||||
|
int suffix = 1;
|
||||||
|
while (productRepository.existsBySlug(candidate)) {
|
||||||
|
candidate = baseSlug + "-" + suffix;
|
||||||
|
suffix++;
|
||||||
|
}
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String inferPlatform(MerchantFeedRow row) {
|
||||||
|
String department = coalesce(trimOrNull(row.department()), trimOrNull(row.category()));
|
||||||
|
if (department == null) return null;
|
||||||
|
|
||||||
|
String lower = department.toLowerCase();
|
||||||
|
if (lower.contains("ar-15") || lower.contains("ar15")) return "AR-15";
|
||||||
|
if (lower.contains("ar-10") || lower.contains("ar10")) return "AR-10";
|
||||||
|
if (lower.contains("ak-47") || lower.contains("ak47")) return "AK-47";
|
||||||
|
|
||||||
|
// Default: treat Aero as AR-15 universe for now
|
||||||
|
return "AR-15";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String inferPartRole(MerchantFeedRow row) {
|
||||||
|
String cat = coalesce(
|
||||||
|
trimOrNull(row.subCategory()),
|
||||||
|
trimOrNull(row.category())
|
||||||
|
);
|
||||||
|
if (cat == null) return null;
|
||||||
|
|
||||||
|
String lower = cat.toLowerCase();
|
||||||
|
|
||||||
|
if (lower.contains("handguard") || lower.contains("rail")) {
|
||||||
|
return "handguard";
|
||||||
|
}
|
||||||
|
if (lower.contains("barrel")) {
|
||||||
|
return "barrel";
|
||||||
|
}
|
||||||
|
if (lower.contains("upper")) {
|
||||||
|
return "upper-receiver";
|
||||||
|
}
|
||||||
|
if (lower.contains("lower")) {
|
||||||
|
return "lower-receiver";
|
||||||
|
}
|
||||||
|
if (lower.contains("stock") || lower.contains("buttstock")) {
|
||||||
|
return "stock";
|
||||||
|
}
|
||||||
|
if (lower.contains("grip")) {
|
||||||
|
return "grip";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -14,4 +14,7 @@ public interface ProductRepository extends JpaRepository<Product, Integer> {
|
|||||||
Optional<Product> findByBrandAndMpn(Brand brand, String mpn);
|
Optional<Product> findByBrandAndMpn(Brand brand, String mpn);
|
||||||
|
|
||||||
Optional<Product> findByBrandAndUpc(Brand brand, String upc);
|
Optional<Product> findByBrandAndUpc(Brand brand, String upc);
|
||||||
|
|
||||||
|
boolean existsBySlug(String slug);
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user