mirror of
https://gitea.gofwd.group/Forward_Group/ballistic-builder-spring.git
synced 2026-01-21 01:01:05 -05:00
fixed the duplicate mapping stuff.
This commit is contained in:
@@ -1,65 +0,0 @@
|
|||||||
package group.goforward.battlbuilder.controllers;
|
|
||||||
|
|
||||||
import group.goforward.battlbuilder.model.Merchant;
|
|
||||||
import group.goforward.battlbuilder.model.MerchantCategoryMapping;
|
|
||||||
import group.goforward.battlbuilder.repos.MerchantRepository;
|
|
||||||
import group.goforward.battlbuilder.services.MerchantCategoryMappingService;
|
|
||||||
import group.goforward.battlbuilder.web.dto.MerchantCategoryMappingDto;
|
|
||||||
import group.goforward.battlbuilder.web.dto.UpsertMerchantCategoryMappingRequest;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/admin/merchant-category-mappings")
|
|
||||||
@CrossOrigin
|
|
||||||
public class MerchantCategoryMappingController {
|
|
||||||
|
|
||||||
private final MerchantCategoryMappingService mappingService;
|
|
||||||
private final MerchantRepository merchantRepository;
|
|
||||||
|
|
||||||
public MerchantCategoryMappingController(
|
|
||||||
MerchantCategoryMappingService mappingService,
|
|
||||||
MerchantRepository merchantRepository
|
|
||||||
) {
|
|
||||||
this.mappingService = mappingService;
|
|
||||||
this.merchantRepository = merchantRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
public List<MerchantCategoryMappingDto> listMappings(
|
|
||||||
@RequestParam("merchantId") Integer merchantId
|
|
||||||
) {
|
|
||||||
List<MerchantCategoryMapping> mappings = mappingService.findByMerchant(merchantId);
|
|
||||||
return mappings.stream()
|
|
||||||
.map(this::toDto)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping
|
|
||||||
public MerchantCategoryMappingDto upsertMapping(
|
|
||||||
@RequestBody UpsertMerchantCategoryMappingRequest request
|
|
||||||
) {
|
|
||||||
Merchant merchant = merchantRepository
|
|
||||||
.findById(request.getMerchantId())
|
|
||||||
.orElseThrow(() -> new IllegalArgumentException("Merchant not found: " + request.getMerchantId()));
|
|
||||||
|
|
||||||
MerchantCategoryMapping mapping = mappingService.upsertMapping(
|
|
||||||
merchant,
|
|
||||||
request.getRawCategory(),
|
|
||||||
request.getMappedPartRole()
|
|
||||||
);
|
|
||||||
|
|
||||||
return toDto(mapping);
|
|
||||||
}
|
|
||||||
|
|
||||||
private MerchantCategoryMappingDto toDto(MerchantCategoryMapping mapping) {
|
|
||||||
MerchantCategoryMappingDto dto = new MerchantCategoryMappingDto();
|
|
||||||
dto.setId(mapping.getId());
|
|
||||||
dto.setMerchantId(mapping.getMerchant().getId());
|
|
||||||
dto.setMerchantName(mapping.getMerchant().getName());
|
|
||||||
dto.setRawCategory(mapping.getRawCategory());
|
|
||||||
dto.setMappedPartRole(mapping.getMappedPartRole());
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
package group.goforward.battlbuilder.controllers.admin;
|
|
||||||
|
|
||||||
import group.goforward.battlbuilder.model.CategoryMapping;
|
|
||||||
import group.goforward.battlbuilder.model.Merchant;
|
|
||||||
import group.goforward.battlbuilder.model.PartCategory;
|
|
||||||
import group.goforward.battlbuilder.repos.CategoryMappingRepository;
|
|
||||||
import group.goforward.battlbuilder.repos.PartCategoryRepository;
|
|
||||||
import group.goforward.battlbuilder.web.dto.admin.MerchantCategoryMappingDto;
|
|
||||||
import group.goforward.battlbuilder.web.dto.admin.SimpleMerchantDto;
|
|
||||||
import group.goforward.battlbuilder.web.dto.admin.UpdateMerchantCategoryMappingRequest;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/admin/category-mappings")
|
|
||||||
@CrossOrigin // tighten later
|
|
||||||
public class AdminCategoryMappingController {
|
|
||||||
|
|
||||||
private final CategoryMappingRepository categoryMappingRepository;
|
|
||||||
private final PartCategoryRepository partCategoryRepository;
|
|
||||||
|
|
||||||
public AdminCategoryMappingController(
|
|
||||||
CategoryMappingRepository categoryMappingRepository,
|
|
||||||
PartCategoryRepository partCategoryRepository
|
|
||||||
) {
|
|
||||||
this.categoryMappingRepository = categoryMappingRepository;
|
|
||||||
this.partCategoryRepository = partCategoryRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merchants that have at least one category_mappings row.
|
|
||||||
* Used for the "All Merchants" dropdown in the UI.
|
|
||||||
*/
|
|
||||||
@GetMapping("/merchants")
|
|
||||||
public List<SimpleMerchantDto> listMerchantsWithMappings() {
|
|
||||||
List<Merchant> merchants = categoryMappingRepository.findDistinctMerchantsWithMappings();
|
|
||||||
return merchants.stream()
|
|
||||||
.map(m -> new SimpleMerchantDto(m.getId(), m.getName()))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List mappings for a specific merchant, or all mappings if no merchantId is provided.
|
|
||||||
* GET /api/admin/category-mappings?merchantId=1
|
|
||||||
*/
|
|
||||||
@GetMapping
|
|
||||||
public List<MerchantCategoryMappingDto> listByMerchant(
|
|
||||||
@RequestParam(name = "merchantId", required = false) Integer merchantId
|
|
||||||
) {
|
|
||||||
List<CategoryMapping> mappings;
|
|
||||||
|
|
||||||
if (merchantId != null) {
|
|
||||||
mappings = categoryMappingRepository.findByMerchantIdOrderByRawCategoryPathAsc(merchantId);
|
|
||||||
} else {
|
|
||||||
mappings = categoryMappingRepository.findAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
return mappings.stream()
|
|
||||||
.map(cm -> new MerchantCategoryMappingDto(
|
|
||||||
cm.getId(),
|
|
||||||
cm.getMerchant().getId(),
|
|
||||||
cm.getMerchant().getName(),
|
|
||||||
cm.getRawCategoryPath(),
|
|
||||||
cm.getPartCategory() != null ? cm.getPartCategory().getId() : null,
|
|
||||||
cm.getPartCategory() != null ? cm.getPartCategory().getName() : null
|
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a single mapping's part_category.
|
|
||||||
* PUT /api/admin/category-mappings/{id}
|
|
||||||
* Body: { "partCategoryId": 24 }
|
|
||||||
*/
|
|
||||||
@PutMapping("/{id}")
|
|
||||||
public MerchantCategoryMappingDto updateMapping(
|
|
||||||
@PathVariable Integer id,
|
|
||||||
@RequestBody UpdateMerchantCategoryMappingRequest request
|
|
||||||
) {
|
|
||||||
CategoryMapping mapping = categoryMappingRepository.findById(id)
|
|
||||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Mapping not found"));
|
|
||||||
|
|
||||||
PartCategory partCategory = null;
|
|
||||||
if (request.partCategoryId() != null) {
|
|
||||||
partCategory = partCategoryRepository.findById(request.partCategoryId())
|
|
||||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Part category not found"));
|
|
||||||
}
|
|
||||||
|
|
||||||
mapping.setPartCategory(partCategory);
|
|
||||||
mapping = categoryMappingRepository.save(mapping);
|
|
||||||
|
|
||||||
return new MerchantCategoryMappingDto(
|
|
||||||
mapping.getId(),
|
|
||||||
mapping.getMerchant().getId(),
|
|
||||||
mapping.getMerchant().getName(),
|
|
||||||
mapping.getRawCategoryPath(),
|
|
||||||
mapping.getPartCategory() != null ? mapping.getPartCategory().getId() : null,
|
|
||||||
mapping.getPartCategory() != null ? mapping.getPartCategory().getName() : null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package group.goforward.battlbuilder.web;
|
package group.goforward.battlbuilder.controllers.admin;
|
||||||
|
|
||||||
import group.goforward.battlbuilder.services.AdminDashboardService;
|
import group.goforward.battlbuilder.services.admin.impl.AdminDashboardService;
|
||||||
import group.goforward.battlbuilder.web.dto.AdminDashboardOverviewDto;
|
import group.goforward.battlbuilder.web.dto.AdminDashboardOverviewDto;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import java.time.OffsetDateTime;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "merchant_category_mappings")
|
@Table(name = "merchant_category_map")
|
||||||
public class MerchantCategoryMap {
|
public class MerchantCategoryMap {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@@ -36,11 +36,11 @@ public class MerchantCategoryMap {
|
|||||||
@Column(name = "raw_category", nullable = false, length = Integer.MAX_VALUE)
|
@Column(name = "raw_category", nullable = false, length = Integer.MAX_VALUE)
|
||||||
private String rawCategory;
|
private String rawCategory;
|
||||||
|
|
||||||
@Column(name = "mapped_part_role", length = Integer.MAX_VALUE)
|
@Column(name = "part_role", length = 255)
|
||||||
private String mappedPartRole;
|
private String partRole;
|
||||||
|
|
||||||
@Column(name = "mapped_configuration", length = Integer.MAX_VALUE)
|
// @Column(name = "mapped_configuration", length = Integer.MAX_VALUE)
|
||||||
private String mappedConfiguration;
|
// private String mappedConfiguration;
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Column(name = "created_at", nullable = false)
|
@Column(name = "created_at", nullable = false)
|
||||||
@@ -62,11 +62,11 @@ public class MerchantCategoryMap {
|
|||||||
public String getRawCategory() { return rawCategory; }
|
public String getRawCategory() { return rawCategory; }
|
||||||
public void setRawCategory(String rawCategory) { this.rawCategory = rawCategory; }
|
public void setRawCategory(String rawCategory) { this.rawCategory = rawCategory; }
|
||||||
|
|
||||||
public String getMappedPartRole() { return mappedPartRole; }
|
public String getPartRole() { return partRole; }
|
||||||
public void setMappedPartRole(String mappedPartRole) { this.mappedPartRole = mappedPartRole; }
|
public void setPartRole(String partRole) { this.partRole = partRole; }
|
||||||
|
|
||||||
public String getMappedConfiguration() { return mappedConfiguration; }
|
// public String getMappedConfiguration() { return mappedConfiguration; }
|
||||||
public void setMappedConfiguration(String mappedConfiguration) { this.mappedConfiguration = mappedConfiguration; }
|
// public void setMappedConfiguration(String mappedConfiguration) { this.mappedConfiguration = mappedConfiguration; }
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() { return createdAt; }
|
public OffsetDateTime getCreatedAt() { return createdAt; }
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) { this.createdAt = createdAt; }
|
public void setCreatedAt(OffsetDateTime createdAt) { this.createdAt = createdAt; }
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
package group.goforward.battlbuilder.model;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(
|
|
||||||
name = "merchant_category_mappings",
|
|
||||||
uniqueConstraints = @UniqueConstraint(
|
|
||||||
name = "uq_merchant_category",
|
|
||||||
columnNames = { "merchant_id", "raw_category" }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
public class MerchantCategoryMapping {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY) // SERIAL
|
|
||||||
@Column(name = "id", nullable = false)
|
|
||||||
private Integer id;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
|
||||||
@JoinColumn(name = "merchant_id", nullable = false)
|
|
||||||
private Merchant merchant;
|
|
||||||
|
|
||||||
@Column(name = "raw_category", nullable = false, length = 512)
|
|
||||||
private String rawCategory;
|
|
||||||
|
|
||||||
@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();
|
|
||||||
|
|
||||||
@Column(name = "updated_at", nullable = false)
|
|
||||||
private OffsetDateTime updatedAt = OffsetDateTime.now();
|
|
||||||
|
|
||||||
@PreUpdate
|
|
||||||
public void onUpdate() {
|
|
||||||
this.updatedAt = OffsetDateTime.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
// getters & setters
|
|
||||||
|
|
||||||
public Integer getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Integer id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Merchant getMerchant() {
|
|
||||||
return merchant;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMerchant(Merchant merchant) {
|
|
||||||
this.merchant = merchant;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRawCategory() {
|
|
||||||
return rawCategory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRawCategory(String rawCategory) {
|
|
||||||
this.rawCategory = rawCategory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMappedPartRole() {
|
|
||||||
return mappedPartRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMappedPartRole(String mappedPartRole) {
|
|
||||||
this.mappedPartRole = mappedPartRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProductConfiguration getMappedConfiguration() {
|
|
||||||
return mappedConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMappedConfiguration(ProductConfiguration mappedConfiguration) {
|
|
||||||
this.mappedConfiguration = mappedConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getUpdatedAt() {
|
|
||||||
return updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
|
||||||
this.updatedAt = updatedAt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package group.goforward.battlbuilder.repos;
|
package group.goforward.battlbuilder.repos;
|
||||||
|
|
||||||
import group.goforward.battlbuilder.model.MerchantCategoryMap;
|
import group.goforward.battlbuilder.model.MerchantCategoryMap;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@@ -13,4 +15,9 @@ public interface MerchantCategoryMapRepository extends JpaRepository<MerchantCat
|
|||||||
Integer merchantId,
|
Integer merchantId,
|
||||||
String rawCategory
|
String rawCategory
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Optional<MerchantCategoryMap> findFirstByMerchant_IdAndRawCategoryAndDeletedAtIsNull(
|
||||||
|
Integer merchantId,
|
||||||
|
String rawCategory
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package group.goforward.battlbuilder.repos;
|
|
||||||
|
|
||||||
import group.goforward.battlbuilder.model.Merchant;
|
|
||||||
import group.goforward.battlbuilder.model.MerchantCategoryMapping;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
public interface MerchantCategoryMappingRepository
|
|
||||||
extends JpaRepository<MerchantCategoryMapping, Integer> {
|
|
||||||
|
|
||||||
Optional<MerchantCategoryMapping> findByMerchantIdAndRawCategoryIgnoreCase(
|
|
||||||
Integer merchantId,
|
|
||||||
String rawCategory
|
|
||||||
);
|
|
||||||
|
|
||||||
Optional<MerchantCategoryMapping> findByMerchantIdAndRawCategory(
|
|
||||||
Integer merchantId,
|
|
||||||
String rawCategory
|
|
||||||
);
|
|
||||||
|
|
||||||
List<MerchantCategoryMapping> findByMerchantIdOrderByRawCategoryAsc(Integer merchantId);
|
|
||||||
|
|
||||||
Optional<MerchantCategoryMapping> findByMerchantAndRawCategoryIgnoreCase(
|
|
||||||
Merchant merchant,
|
|
||||||
String rawCategory
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package group.goforward.battlbuilder.repos;
|
package group.goforward.battlbuilder.repos;
|
||||||
|
|
||||||
import aj.org.objectweb.asm.commons.Remapper;
|
|
||||||
import group.goforward.battlbuilder.model.ImportStatus;
|
import group.goforward.battlbuilder.model.ImportStatus;
|
||||||
import group.goforward.battlbuilder.model.Brand;
|
import group.goforward.battlbuilder.model.Brand;
|
||||||
import group.goforward.battlbuilder.model.Product;
|
import group.goforward.battlbuilder.model.Product;
|
||||||
@@ -87,7 +86,7 @@ public interface ProductRepository extends JpaRepository<Product, Integer> {
|
|||||||
AND p.deletedAt IS NULL
|
AND p.deletedAt IS NULL
|
||||||
ORDER BY p.id
|
ORDER BY p.id
|
||||||
""")
|
""")
|
||||||
List<Product> findTop5ByPlatformWithBrand(@Param("platform") String platform);
|
List<Product> findByPlatformWithBrandOrdered(@Param("platform") String platform);
|
||||||
|
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
// Used by GunbuilderProductService (builder UI)
|
// Used by GunbuilderProductService (builder UI)
|
||||||
@@ -178,45 +177,46 @@ public interface ProductRepository extends JpaRepository<Product, Integer> {
|
|||||||
// Mapping admin – pending buckets (all merchants)
|
// Mapping admin – pending buckets (all merchants)
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT m.id AS merchantId,
|
SELECT m.id AS merchantId,
|
||||||
m.name AS merchantName,
|
m.name AS merchantName,
|
||||||
p.rawCategoryKey AS rawCategoryKey,
|
p.rawCategoryKey AS rawCategoryKey,
|
||||||
mcm.mappedPartRole AS mappedPartRole,
|
mcm.partRole AS mappedPartRole,
|
||||||
COUNT(DISTINCT p.id) AS productCount
|
COUNT(DISTINCT p.id) AS productCount
|
||||||
FROM Product p
|
FROM Product p
|
||||||
JOIN p.offers o
|
JOIN p.offers o
|
||||||
JOIN o.merchant m
|
JOIN o.merchant m
|
||||||
LEFT JOIN MerchantCategoryMapping mcm
|
LEFT JOIN MerchantCategoryMap mcm
|
||||||
ON mcm.merchant.id = m.id
|
ON mcm.merchant.id = m.id
|
||||||
AND mcm.rawCategory = p.rawCategoryKey
|
AND mcm.rawCategory = p.rawCategoryKey
|
||||||
WHERE p.importStatus = :status
|
AND mcm.deletedAt IS NULL
|
||||||
GROUP BY m.id, m.name, p.rawCategoryKey, mcm.mappedPartRole
|
WHERE p.importStatus = :status
|
||||||
ORDER BY productCount DESC
|
GROUP BY m.id, m.name, p.rawCategoryKey, mcm.partRole
|
||||||
""")
|
ORDER BY productCount DESC
|
||||||
List<Object[]> findPendingMappingBuckets(
|
""")
|
||||||
@Param("status") ImportStatus status
|
List<Object[]> findPendingMappingBuckets(@Param("status") ImportStatus status);
|
||||||
);
|
|
||||||
|
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
// Mapping admin – pending buckets for a single merchant
|
// Mapping admin – pending buckets for a single merchant
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT m.id AS merchantId,
|
SELECT m.id AS merchantId,
|
||||||
m.name AS merchantName,
|
m.name AS merchantName,
|
||||||
p.rawCategoryKey AS rawCategoryKey,
|
p.rawCategoryKey AS rawCategoryKey,
|
||||||
mcm.mappedPartRole AS mappedPartRole,
|
mcm.partRole AS mappedPartRole,
|
||||||
COUNT(DISTINCT p.id) AS productCount
|
COUNT(DISTINCT p.id) AS productCount
|
||||||
FROM Product p
|
FROM Product p
|
||||||
JOIN p.offers o
|
JOIN p.offers o
|
||||||
JOIN o.merchant m
|
JOIN o.merchant m
|
||||||
LEFT JOIN MerchantCategoryMapping mcm
|
LEFT JOIN MerchantCategoryMap mcm
|
||||||
ON mcm.merchant.id = m.id
|
ON mcm.merchant.id = m.id
|
||||||
AND mcm.rawCategory = p.rawCategoryKey
|
AND mcm.rawCategory = p.rawCategoryKey
|
||||||
WHERE p.importStatus = :status
|
AND mcm.deletedAt IS NULL
|
||||||
AND m.id = :merchantId
|
WHERE p.importStatus = :status
|
||||||
GROUP BY m.id, m.name, p.rawCategoryKey, mcm.mappedPartRole
|
AND m.id = :merchantId
|
||||||
ORDER BY productCount DESC
|
GROUP BY m.id, m.name, p.rawCategoryKey, mcm.partRole
|
||||||
""")
|
ORDER BY productCount DESC
|
||||||
|
""")
|
||||||
List<Object[]> findPendingMappingBucketsForMerchant(
|
List<Object[]> findPendingMappingBucketsForMerchant(
|
||||||
@Param("merchantId") Integer merchantId,
|
@Param("merchantId") Integer merchantId,
|
||||||
@Param("status") ImportStatus status
|
@Param("status") ImportStatus status
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package group.goforward.battlbuilder.services;
|
|||||||
|
|
||||||
import group.goforward.battlbuilder.model.ImportStatus;
|
import group.goforward.battlbuilder.model.ImportStatus;
|
||||||
import group.goforward.battlbuilder.model.Merchant;
|
import group.goforward.battlbuilder.model.Merchant;
|
||||||
import group.goforward.battlbuilder.model.MerchantCategoryMapping;
|
import group.goforward.battlbuilder.model.MerchantCategoryMap;
|
||||||
import group.goforward.battlbuilder.repos.MerchantCategoryMappingRepository;
|
import group.goforward.battlbuilder.repos.MerchantCategoryMapRepository;
|
||||||
import group.goforward.battlbuilder.repos.MerchantRepository;
|
import group.goforward.battlbuilder.repos.MerchantRepository;
|
||||||
import group.goforward.battlbuilder.repos.ProductRepository;
|
import group.goforward.battlbuilder.repos.ProductRepository;
|
||||||
import group.goforward.battlbuilder.web.dto.PendingMappingBucketDto;
|
import group.goforward.battlbuilder.web.dto.PendingMappingBucketDto;
|
||||||
@@ -16,28 +16,19 @@ import java.util.List;
|
|||||||
public class MappingAdminService {
|
public class MappingAdminService {
|
||||||
|
|
||||||
private final ProductRepository productRepository;
|
private final ProductRepository productRepository;
|
||||||
private final MerchantCategoryMappingRepository merchantCategoryMappingRepository;
|
private final MerchantCategoryMapRepository merchantCategoryMapRepository;
|
||||||
private final MerchantRepository merchantRepository;
|
private final MerchantRepository merchantRepository;
|
||||||
|
|
||||||
public MappingAdminService(
|
public MappingAdminService(
|
||||||
ProductRepository productRepository,
|
ProductRepository productRepository,
|
||||||
MerchantCategoryMappingRepository merchantCategoryMappingRepository,
|
MerchantCategoryMapRepository merchantCategoryMapRepository,
|
||||||
MerchantRepository merchantRepository
|
MerchantRepository merchantRepository
|
||||||
) {
|
) {
|
||||||
this.productRepository = productRepository;
|
this.productRepository = productRepository;
|
||||||
this.merchantCategoryMappingRepository = merchantCategoryMappingRepository;
|
this.merchantCategoryMapRepository = merchantCategoryMapRepository;
|
||||||
this.merchantRepository = merchantRepository;
|
this.merchantRepository = merchantRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all pending mapping buckets across all merchants.
|
|
||||||
* Each row is:
|
|
||||||
* [0] merchantId (Integer)
|
|
||||||
* [1] merchantName (String)
|
|
||||||
* [2] rawCategoryKey (String)
|
|
||||||
* [3] mappedPartRole (String, currently null from query)
|
|
||||||
* [4] productCount (Long)
|
|
||||||
*/
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public List<PendingMappingBucketDto> listPendingBuckets() {
|
public List<PendingMappingBucketDto> listPendingBuckets() {
|
||||||
List<Object[]> rows = productRepository.findPendingMappingBuckets(
|
List<Object[]> rows = productRepository.findPendingMappingBuckets(
|
||||||
@@ -63,10 +54,6 @@ public class MappingAdminService {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies or updates a mapping for (merchant, rawCategoryKey) to a given partRole.
|
|
||||||
* Does NOT retroactively update Product rows; they will be updated on the next import.
|
|
||||||
*/
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void applyMapping(Integer merchantId, String rawCategoryKey, String mappedPartRole) {
|
public void applyMapping(Integer merchantId, String rawCategoryKey, String mappedPartRole) {
|
||||||
if (merchantId == null || rawCategoryKey == null || mappedPartRole == null || mappedPartRole.isBlank()) {
|
if (merchantId == null || rawCategoryKey == null || mappedPartRole == null || mappedPartRole.isBlank()) {
|
||||||
@@ -76,18 +63,22 @@ public class MappingAdminService {
|
|||||||
Merchant merchant = merchantRepository.findById(merchantId)
|
Merchant merchant = merchantRepository.findById(merchantId)
|
||||||
.orElseThrow(() -> new IllegalArgumentException("Merchant not found: " + merchantId));
|
.orElseThrow(() -> new IllegalArgumentException("Merchant not found: " + merchantId));
|
||||||
|
|
||||||
MerchantCategoryMapping mapping = merchantCategoryMappingRepository
|
List<MerchantCategoryMap> existing =
|
||||||
.findByMerchantIdAndRawCategory(merchantId, rawCategoryKey)
|
merchantCategoryMapRepository.findAllByMerchant_IdAndRawCategoryAndDeletedAtIsNull(
|
||||||
.orElseGet(() -> {
|
merchantId,
|
||||||
MerchantCategoryMapping m = new MerchantCategoryMapping();
|
rawCategoryKey
|
||||||
m.setMerchant(merchant);
|
);
|
||||||
m.setRawCategory(rawCategoryKey);
|
|
||||||
return m;
|
|
||||||
});
|
|
||||||
|
|
||||||
mapping.setMappedPartRole(mappedPartRole.trim());
|
MerchantCategoryMap mapping = existing.isEmpty()
|
||||||
merchantCategoryMappingRepository.save(mapping);
|
? new MerchantCategoryMap()
|
||||||
|
: existing.get(0);
|
||||||
|
|
||||||
// Products will pick up this mapping on the next merchant import run.
|
if (mapping.getId() == null) {
|
||||||
|
mapping.setMerchant(merchant);
|
||||||
|
mapping.setRawCategory(rawCategoryKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
mapping.setPartRole(mappedPartRole.trim());
|
||||||
|
merchantCategoryMapRepository.save(mapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
package group.goforward.battlbuilder.services;
|
|
||||||
|
|
||||||
import group.goforward.battlbuilder.model.Merchant;
|
|
||||||
import group.goforward.battlbuilder.model.MerchantCategoryMapping;
|
|
||||||
import group.goforward.battlbuilder.model.ProductConfiguration;
|
|
||||||
import group.goforward.battlbuilder.repos.MerchantCategoryMappingRepository;
|
|
||||||
import jakarta.transaction.Transactional;
|
|
||||||
import java.util.List;
|
|
||||||
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,7 +1,7 @@
|
|||||||
package group.goforward.battlbuilder.services;
|
package group.goforward.battlbuilder.services.admin.impl;
|
||||||
|
|
||||||
import group.goforward.battlbuilder.model.ImportStatus;
|
import group.goforward.battlbuilder.model.ImportStatus;
|
||||||
import group.goforward.battlbuilder.repos.MerchantCategoryMappingRepository;
|
import group.goforward.battlbuilder.repos.MerchantCategoryMapRepository;
|
||||||
import group.goforward.battlbuilder.repos.MerchantRepository;
|
import group.goforward.battlbuilder.repos.MerchantRepository;
|
||||||
import group.goforward.battlbuilder.repos.ProductRepository;
|
import group.goforward.battlbuilder.repos.ProductRepository;
|
||||||
import group.goforward.battlbuilder.web.dto.AdminDashboardOverviewDto;
|
import group.goforward.battlbuilder.web.dto.AdminDashboardOverviewDto;
|
||||||
@@ -13,16 +13,16 @@ public class AdminDashboardService {
|
|||||||
|
|
||||||
private final ProductRepository productRepository;
|
private final ProductRepository productRepository;
|
||||||
private final MerchantRepository merchantRepository;
|
private final MerchantRepository merchantRepository;
|
||||||
private final MerchantCategoryMappingRepository merchantCategoryMappingRepository;
|
private final MerchantCategoryMapRepository merchantCategoryMapRepository;
|
||||||
|
|
||||||
public AdminDashboardService(
|
public AdminDashboardService(
|
||||||
ProductRepository productRepository,
|
ProductRepository productRepository,
|
||||||
MerchantRepository merchantRepository,
|
MerchantRepository merchantRepository,
|
||||||
MerchantCategoryMappingRepository merchantCategoryMappingRepository
|
MerchantCategoryMapRepository merchantCategoryMapRepository
|
||||||
) {
|
) {
|
||||||
this.productRepository = productRepository;
|
this.productRepository = productRepository;
|
||||||
this.merchantRepository = merchantRepository;
|
this.merchantRepository = merchantRepository;
|
||||||
this.merchantCategoryMappingRepository = merchantCategoryMappingRepository;
|
this.merchantCategoryMapRepository = merchantCategoryMapRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
@@ -32,7 +32,7 @@ public class AdminDashboardService {
|
|||||||
long mappedProducts = totalProducts - unmappedProducts;
|
long mappedProducts = totalProducts - unmappedProducts;
|
||||||
|
|
||||||
long merchantCount = merchantRepository.count();
|
long merchantCount = merchantRepository.count();
|
||||||
long categoryMappings = merchantCategoryMappingRepository.count();
|
long categoryMappings = merchantCategoryMapRepository.count();
|
||||||
|
|
||||||
return new AdminDashboardOverviewDto(
|
return new AdminDashboardOverviewDto(
|
||||||
totalProducts,
|
totalProducts,
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public class CategoryClassificationServiceImpl implements CategoryClassification
|
|||||||
);
|
);
|
||||||
|
|
||||||
return mappings.stream()
|
return mappings.stream()
|
||||||
.map(MerchantCategoryMap::getMappedPartRole)
|
.map(MerchantCategoryMap::getPartRole)
|
||||||
.filter(r -> r != null && !r.isBlank())
|
.filter(r -> r != null && !r.isBlank())
|
||||||
.findFirst();
|
.findFirst();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ public class ReclassificationServiceImpl implements ReclassificationService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return mappings.stream()
|
return mappings.stream()
|
||||||
.map(MerchantCategoryMap::getMappedPartRole)
|
.map(MerchantCategoryMap::getPartRole)
|
||||||
.filter(v -> v != null && !v.isBlank())
|
.filter(v -> v != null && !v.isBlank())
|
||||||
.findFirst();
|
.findFirst();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package group.goforward.battlbuilder.web.admin;
|
package group.goforward.battlbuilder.web.admin;
|
||||||
|
|
||||||
import group.goforward.battlbuilder.services.MappingAdminService;
|
|
||||||
import group.goforward.battlbuilder.web.dto.PendingMappingBucketDto;
|
import group.goforward.battlbuilder.web.dto.PendingMappingBucketDto;
|
||||||
|
import group.goforward.battlbuilder.services.MappingAdminService;
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package group.goforward.battlbuilder.web.admin;
|
package group.goforward.battlbuilder.web.admin;
|
||||||
|
|
||||||
import group.goforward.battlbuilder.services.MappingAdminService;
|
|
||||||
import group.goforward.battlbuilder.web.dto.PendingMappingBucketDto;
|
import group.goforward.battlbuilder.web.dto.PendingMappingBucketDto;
|
||||||
|
import group.goforward.battlbuilder.services.MappingAdminService;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user