mirror of
https://gitea.gofwd.group/Forward_Group/ballistic-builder-spring.git
synced 2025-12-06 02:56:44 -05:00
Compare commits
2 Commits
0f5978fd11
...
f1dcd10a79
| Author | SHA1 | Date | |
|---|---|---|---|
| f1dcd10a79 | |||
| 66d45a1113 |
@@ -7,7 +7,9 @@ import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
|
||||
@SpringBootApplication
|
||||
//@ComponentScan(basePackages = "group.goforward.ballistic")
|
||||
@ComponentScan("group.goforward.ballistic.controllers")
|
||||
@ComponentScan("group.goforward.ballistic.repos")
|
||||
@ComponentScan("group.goforward.ballistic.services")
|
||||
@EntityScan(basePackages = "group.goforward.ballistic.model")
|
||||
@EnableJpaRepositories(basePackages = "group.goforward.ballistic.repos")
|
||||
public class BallisticApplication {
|
||||
|
||||
@@ -1 +1,13 @@
|
||||
/**
|
||||
* Provides the classes necessary for the Spring Configurations for the ballistic -Builder application.
|
||||
* This package includes Configurations for Spring-Boot application
|
||||
*
|
||||
*
|
||||
* <p>The main entry point for managing the inventory is the
|
||||
* {@link group.goforward.ballistic.BallisticApplication} class.</p>
|
||||
*
|
||||
* @since 1.0
|
||||
* @author Don Strawsburg
|
||||
* @version 1.1
|
||||
*/
|
||||
package group.goforward.ballistic.configuration;
|
||||
@@ -1,6 +1,6 @@
|
||||
package group.goforward.ballistic.controllers;
|
||||
|
||||
import group.goforward.ballistic.imports.MerchantFeedImportService;
|
||||
import group.goforward.ballistic.services.MerchantFeedImportService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package group.goforward.ballistic.controllers;
|
||||
import group.goforward.ballistic.model.Merchant;
|
||||
import group.goforward.ballistic.model.MerchantCategoryMapping;
|
||||
import group.goforward.ballistic.repos.MerchantRepository;
|
||||
import group.goforward.ballistic.service.MerchantCategoryMappingService;
|
||||
import group.goforward.ballistic.services.MerchantCategoryMappingService;
|
||||
import group.goforward.ballistic.web.dto.MerchantCategoryMappingDto;
|
||||
import group.goforward.ballistic.web.dto.UpsertMerchantCategoryMappingRequest;
|
||||
import java.util.List;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package group.goforward.ballistic.controllers;
|
||||
|
||||
import group.goforward.ballistic.model.Psa;
|
||||
import group.goforward.ballistic.service.PsaService;
|
||||
import group.goforward.ballistic.services.PsaService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -3,7 +3,7 @@ package group.goforward.ballistic.controllers;
|
||||
import group.goforward.ballistic.ApiResponse;
|
||||
import group.goforward.ballistic.model.State;
|
||||
import group.goforward.ballistic.repos.StateRepository;
|
||||
import group.goforward.ballistic.service.StatesService;
|
||||
import group.goforward.ballistic.services.StatesService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package group.goforward.ballistic.controllers;
|
||||
@@ -0,0 +1 @@
|
||||
package group.goforward.ballistic.imports.dto;
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package group.goforward.ballistic.model;
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Provides the classes necessary for the Spring Repository for the ballistic -Builder application.
|
||||
* This package includes Repository for Spring-Boot application
|
||||
*
|
||||
*
|
||||
* <p>The main entry point for managing the inventory is the
|
||||
* {@link group.goforward.ballistic.BallisticApplication} class.</p>
|
||||
*
|
||||
* @since 1.0
|
||||
* @author Sean Strawsburg
|
||||
* @version 1.1
|
||||
*/
|
||||
package group.goforward.ballistic.repos;
|
||||
@@ -1,77 +0,0 @@
|
||||
package group.goforward.ballistic.service;
|
||||
|
||||
import group.goforward.ballistic.model.Merchant;
|
||||
import group.goforward.ballistic.model.MerchantCategoryMapping;
|
||||
import group.goforward.ballistic.repos.MerchantCategoryMappingRepository;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class MerchantCategoryMappingService {
|
||||
|
||||
private final MerchantCategoryMappingRepository mappingRepository;
|
||||
|
||||
public MerchantCategoryMappingService(MerchantCategoryMappingRepository mappingRepository) {
|
||||
this.mappingRepository = mappingRepository;
|
||||
}
|
||||
|
||||
public List<MerchantCategoryMapping> findByMerchant(Integer merchantId) {
|
||||
return mappingRepository.findByMerchantIdOrderByRawCategoryAsc(merchantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a partRole for a given raw category.
|
||||
* If not found, create a row with null mappedPartRole and return null (so importer can skip).
|
||||
*/
|
||||
@Transactional
|
||||
public String resolvePartRole(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upsert mapping (admin UI).
|
||||
*/
|
||||
@Transactional
|
||||
public MerchantCategoryMapping upsertMapping(Merchant merchant, String rawCategory, String mappedPartRole) {
|
||||
String trimmed = rawCategory.trim();
|
||||
|
||||
MerchantCategoryMapping mapping = mappingRepository
|
||||
.findByMerchantIdAndRawCategoryIgnoreCase(merchant.getId(), trimmed)
|
||||
.orElseGet(() -> {
|
||||
MerchantCategoryMapping m = new MerchantCategoryMapping();
|
||||
m.setMerchant(merchant);
|
||||
m.setRawCategory(trimmed);
|
||||
return m;
|
||||
});
|
||||
|
||||
mapping.setMappedPartRole(
|
||||
(mappedPartRole == null || mappedPartRole.isBlank()) ? null : mappedPartRole.trim()
|
||||
);
|
||||
|
||||
return mappingRepository.save(mapping);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
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;
|
||||
import java.util.Optional;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class MerchantCategoryMappingService {
|
||||
|
||||
private final MerchantCategoryMappingRepository mappingRepository;
|
||||
|
||||
public MerchantCategoryMappingService(MerchantCategoryMappingRepository mappingRepository) {
|
||||
this.mappingRepository = mappingRepository;
|
||||
}
|
||||
|
||||
public List<MerchantCategoryMapping> findByMerchant(Integer merchantId) {
|
||||
return mappingRepository.findByMerchantIdOrderByRawCategoryAsc(merchantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 MerchantCategoryMapping resolveMapping(Merchant merchant, String rawCategory) {
|
||||
if (rawCategory == null || rawCategory.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String trimmed = rawCategory.trim();
|
||||
|
||||
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,
|
||||
ProductConfiguration mappedConfiguration
|
||||
) {
|
||||
String trimmed = rawCategory.trim();
|
||||
|
||||
MerchantCategoryMapping mapping = mappingRepository
|
||||
.findByMerchantIdAndRawCategoryIgnoreCase(merchant.getId(), trimmed)
|
||||
.orElseGet(() -> {
|
||||
MerchantCategoryMapping m = new MerchantCategoryMapping();
|
||||
m.setMerchant(merchant);
|
||||
m.setRawCategory(trimmed);
|
||||
return m;
|
||||
});
|
||||
|
||||
mapping.setMappedPartRole(
|
||||
(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);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package group.goforward.ballistic.imports;
|
||||
package group.goforward.ballistic.services;
|
||||
|
||||
public interface MerchantFeedImportService {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package group.goforward.ballistic.service;
|
||||
package group.goforward.ballistic.services;
|
||||
import group.goforward.ballistic.model.Psa;
|
||||
import group.goforward.ballistic.repos.PsaRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -9,7 +9,7 @@ import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class PsaService {
|
||||
public class PsaService implements group.goforward.ballistic.services.impl.PsaService {
|
||||
|
||||
private final PsaRepository psaRepository;
|
||||
|
||||
@@ -18,18 +18,22 @@ public class PsaService {
|
||||
this.psaRepository = psaRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Psa> findAll() {
|
||||
return psaRepository.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Psa> findById(UUID id) {
|
||||
return psaRepository.findById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Psa save(Psa psa) {
|
||||
return psaRepository.save(psa);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(UUID id) {
|
||||
psaRepository.deleteById(id);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package group.goforward.ballistic.service;
|
||||
package group.goforward.ballistic.services;
|
||||
|
||||
import group.goforward.ballistic.model.State;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package group.goforward.ballistic.imports;
|
||||
package group.goforward.ballistic.services.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
@@ -9,6 +9,8 @@ import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import group.goforward.ballistic.imports.MerchantFeedRow;
|
||||
import group.goforward.ballistic.services.MerchantFeedImportService;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVParser;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
@@ -19,8 +21,8 @@ import group.goforward.ballistic.model.Product;
|
||||
import group.goforward.ballistic.repos.BrandRepository;
|
||||
import group.goforward.ballistic.repos.MerchantRepository;
|
||||
import group.goforward.ballistic.repos.ProductRepository;
|
||||
import group.goforward.ballistic.service.MerchantCategoryMappingService;
|
||||
import group.goforward.ballistic.service.MerchantCategoryMappingService;
|
||||
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;
|
||||
@@ -189,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) {
|
||||
@@ -262,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
|
||||
@@ -0,0 +1,17 @@
|
||||
package group.goforward.ballistic.services.impl;
|
||||
|
||||
import group.goforward.ballistic.model.Psa;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface PsaService {
|
||||
List<Psa> findAll();
|
||||
|
||||
Optional<Psa> findById(UUID id);
|
||||
|
||||
Psa save(Psa psa);
|
||||
|
||||
void deleteById(UUID id);
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package group.goforward.ballistic.service.impl;
|
||||
package group.goforward.ballistic.services.impl;
|
||||
|
||||
|
||||
import group.goforward.ballistic.model.State;
|
||||
import group.goforward.ballistic.repos.StateRepository;
|
||||
import group.goforward.ballistic.service.StatesService;
|
||||
import group.goforward.ballistic.services.StatesService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package group.goforward.ballistic.services.impl;
|
||||
@@ -0,0 +1 @@
|
||||
package group.goforward.ballistic.services;
|
||||
Reference in New Issue
Block a user