mirror of
https://gitea.gofwd.group/Forward_Group/ballistic-builder-spring.git
synced 2025-12-05 18:46:44 -05:00
new categories and mapping logic
This commit is contained in:
@@ -3,6 +3,8 @@ package group.goforward.ballistic.model;
|
||||
import jakarta.persistence.*;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
import group.goforward.ballistic.model.ProductConfiguration;
|
||||
|
||||
@Entity
|
||||
@Table(
|
||||
name = "merchant_category_mappings",
|
||||
@@ -28,6 +30,10 @@ public class MerchantCategoryMapping {
|
||||
@Column(name = "mapped_part_role", length = 128)
|
||||
private String mappedPartRole; // e.g. "upper-receiver", "barrel"
|
||||
|
||||
@Column(name = "mapped_configuration")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private ProductConfiguration mappedConfiguration;
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private OffsetDateTime createdAt = OffsetDateTime.now();
|
||||
|
||||
@@ -73,6 +79,14 @@ public class MerchantCategoryMapping {
|
||||
this.mappedPartRole = mappedPartRole;
|
||||
}
|
||||
|
||||
public ProductConfiguration getMappedConfiguration() {
|
||||
return mappedConfiguration;
|
||||
}
|
||||
|
||||
public void setMappedConfiguration(ProductConfiguration mappedConfiguration) {
|
||||
this.mappedConfiguration = mappedConfiguration;
|
||||
}
|
||||
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import jakarta.persistence.*;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import group.goforward.ballistic.model.ProductConfiguration;
|
||||
|
||||
@Entity
|
||||
@Table(name = "products")
|
||||
public class Product {
|
||||
@@ -38,6 +40,10 @@ public class Product {
|
||||
@Column(name = "part_role")
|
||||
private String partRole;
|
||||
|
||||
@Column(name = "configuration")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private ProductConfiguration configuration;
|
||||
|
||||
@Column(name = "short_description")
|
||||
private String shortDescription;
|
||||
|
||||
@@ -223,4 +229,11 @@ public class Product {
|
||||
this.platformLocked = platformLocked;
|
||||
}
|
||||
|
||||
public ProductConfiguration getConfiguration() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public void setConfiguration(ProductConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package group.goforward.ballistic.model;
|
||||
|
||||
public enum ProductConfiguration {
|
||||
STRIPPED, // bare receiver / component
|
||||
ASSEMBLED, // built up but not fully complete
|
||||
BARRELED, // upper + barrel + gas system, no BCG/CH
|
||||
COMPLETE, // full assembly ready to run
|
||||
KIT, // collection of parts (LPK, trigger kits, etc.)
|
||||
OTHER // fallback / unknown
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package group.goforward.ballistic.services;
|
||||
|
||||
import group.goforward.ballistic.model.Merchant;
|
||||
import group.goforward.ballistic.model.MerchantCategoryMapping;
|
||||
import group.goforward.ballistic.model.ProductConfiguration;
|
||||
import group.goforward.ballistic.repos.MerchantCategoryMappingRepository;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.util.List;
|
||||
@@ -22,41 +23,44 @@ public class MerchantCategoryMappingService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a partRole for a given raw category.
|
||||
* If not found, create a row with null mappedPartRole and return null (so importer can skip).
|
||||
* Resolve (or create) a mapping row for this merchant + raw category.
|
||||
* - If it exists, returns it (with whatever mappedPartRole / mappedConfiguration are set).
|
||||
* - If it doesn't exist, creates a placeholder row with null mappings and returns it.
|
||||
*
|
||||
* The importer can then:
|
||||
* - skip rows where mappedPartRole is still null
|
||||
* - use mappedConfiguration if present
|
||||
*/
|
||||
@Transactional
|
||||
public String resolvePartRole(Merchant merchant, String rawCategory) {
|
||||
public MerchantCategoryMapping resolveMapping(Merchant merchant, String rawCategory) {
|
||||
if (rawCategory == null || rawCategory.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String trimmed = rawCategory.trim();
|
||||
|
||||
Optional<MerchantCategoryMapping> existingOpt =
|
||||
mappingRepository.findByMerchantIdAndRawCategoryIgnoreCase(merchant.getId(), trimmed);
|
||||
|
||||
if (existingOpt.isPresent()) {
|
||||
return existingOpt.get().getMappedPartRole();
|
||||
}
|
||||
|
||||
// Create placeholder row
|
||||
MerchantCategoryMapping mapping = new MerchantCategoryMapping();
|
||||
mapping.setMerchant(merchant);
|
||||
mapping.setRawCategory(trimmed);
|
||||
mapping.setMappedPartRole(null);
|
||||
|
||||
mappingRepository.save(mapping);
|
||||
|
||||
// No mapping yet → importer should skip this product
|
||||
return null;
|
||||
return mappingRepository
|
||||
.findByMerchantIdAndRawCategoryIgnoreCase(merchant.getId(), trimmed)
|
||||
.orElseGet(() -> {
|
||||
MerchantCategoryMapping mapping = new MerchantCategoryMapping();
|
||||
mapping.setMerchant(merchant);
|
||||
mapping.setRawCategory(trimmed);
|
||||
mapping.setMappedPartRole(null);
|
||||
mapping.setMappedConfiguration(null);
|
||||
return mappingRepository.save(mapping);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Upsert mapping (admin UI).
|
||||
*/
|
||||
@Transactional
|
||||
public MerchantCategoryMapping upsertMapping(Merchant merchant, String rawCategory, String mappedPartRole) {
|
||||
public MerchantCategoryMapping upsertMapping(
|
||||
Merchant merchant,
|
||||
String rawCategory,
|
||||
String mappedPartRole,
|
||||
ProductConfiguration mappedConfiguration
|
||||
) {
|
||||
String trimmed = rawCategory.trim();
|
||||
|
||||
MerchantCategoryMapping mapping = mappingRepository
|
||||
@@ -72,6 +76,21 @@ public class MerchantCategoryMappingService {
|
||||
(mappedPartRole == null || mappedPartRole.isBlank()) ? null : mappedPartRole.trim()
|
||||
);
|
||||
|
||||
mapping.setMappedConfiguration(mappedConfiguration);
|
||||
|
||||
return mappingRepository.save(mapping);
|
||||
}
|
||||
/**
|
||||
* Backwards-compatible overload for existing callers (e.g. controller)
|
||||
* that don’t care about productConfiguration yet.
|
||||
*/
|
||||
@Transactional
|
||||
public MerchantCategoryMapping upsertMapping(
|
||||
Merchant merchant,
|
||||
String rawCategory,
|
||||
String mappedPartRole
|
||||
) {
|
||||
// Delegate to the new method with `null` configuration
|
||||
return upsertMapping(merchant, rawCategory, mappedPartRole, null);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import group.goforward.ballistic.repos.BrandRepository;
|
||||
import group.goforward.ballistic.repos.MerchantRepository;
|
||||
import group.goforward.ballistic.repos.ProductRepository;
|
||||
import group.goforward.ballistic.services.MerchantCategoryMappingService;
|
||||
import group.goforward.ballistic.model.MerchantCategoryMapping;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import group.goforward.ballistic.repos.ProductOfferRepository;
|
||||
@@ -190,11 +191,28 @@ public class MerchantFeedImportServiceImpl implements MerchantFeedImportService
|
||||
String rawCategoryKey = buildRawCategoryKey(row);
|
||||
p.setRawCategoryKey(rawCategoryKey);
|
||||
|
||||
// ---------- PART ROLE ----------
|
||||
String partRole = resolvePartRole(merchant, row);
|
||||
// ---------- PART ROLE (via category mapping, with keyword fallback) ----------
|
||||
String partRole = null;
|
||||
|
||||
if (rawCategoryKey != null) {
|
||||
// Ask the mapping service for (or to create) a mapping row
|
||||
MerchantCategoryMapping mapping =
|
||||
merchantCategoryMappingService.resolveMapping(merchant, rawCategoryKey);
|
||||
|
||||
if (mapping != null && mapping.getMappedPartRole() != null && !mapping.getMappedPartRole().isBlank()) {
|
||||
partRole = mapping.getMappedPartRole().trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: keyword-based inference if we still don't have a mapped partRole
|
||||
if (partRole == null || partRole.isBlank()) {
|
||||
partRole = inferPartRole(row);
|
||||
}
|
||||
|
||||
if (partRole == null || partRole.isBlank()) {
|
||||
partRole = "unknown";
|
||||
}
|
||||
|
||||
p.setPartRole(partRole);
|
||||
}
|
||||
private void upsertOfferFromRow(Product product, Merchant merchant, MerchantFeedRow row) {
|
||||
@@ -263,35 +281,6 @@ public class MerchantFeedImportServiceImpl implements MerchantFeedImportService
|
||||
productOfferRepository.save(offer);
|
||||
}
|
||||
|
||||
private String resolvePartRole(Merchant merchant, MerchantFeedRow row) {
|
||||
// Build a merchant-specific raw category key like "Department > Category > SubCategory"
|
||||
String rawCategoryKey = buildRawCategoryKey(row);
|
||||
|
||||
if (rawCategoryKey != null) {
|
||||
// Delegate to the mapping service, which will:
|
||||
// - Look up an existing mapping
|
||||
// - If none exists, create a placeholder row with null mappedPartRole
|
||||
// - Return the mapped partRole, or null if not yet mapped
|
||||
String mapped = merchantCategoryMappingService.resolvePartRole(merchant, rawCategoryKey);
|
||||
if (mapped != null && !mapped.isBlank()) {
|
||||
return mapped;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: keyword-based inference
|
||||
String keywordRole = inferPartRole(row);
|
||||
if (keywordRole != null && !keywordRole.isBlank()) {
|
||||
return keywordRole;
|
||||
}
|
||||
|
||||
// Last resort: log as unmapped and return null
|
||||
System.out.println("IMPORT !!! UNMAPPED CATEGORY for merchant=" + merchant.getName()
|
||||
+ ", rawCategoryKey='" + rawCategoryKey + "'"
|
||||
+ ", sku=" + row.sku()
|
||||
+ ", productName=" + row.productName());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Feed reading + brand resolution
|
||||
|
||||
Reference in New Issue
Block a user