mirror of
https://gitea.gofwd.group/Forward_Group/ballistic-builder-spring.git
synced 2026-01-20 16:51:03 -05:00
new admin dashboard controllers for mappings
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
package group.goforward.ballistic.web;
|
||||||
|
|
||||||
|
import group.goforward.ballistic.services.AdminDashboardService;
|
||||||
|
import group.goforward.ballistic.web.dto.AdminDashboardOverviewDto;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/admin/dashboard")
|
||||||
|
public class AdminDashboardController {
|
||||||
|
|
||||||
|
private final AdminDashboardService adminDashboardService;
|
||||||
|
|
||||||
|
public AdminDashboardController(AdminDashboardService adminDashboardService) {
|
||||||
|
this.adminDashboardService = adminDashboardService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/overview")
|
||||||
|
public ResponseEntity<AdminDashboardOverviewDto> getOverview() {
|
||||||
|
AdminDashboardOverviewDto dto = adminDashboardService.getOverview();
|
||||||
|
return ResponseEntity.ok(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
package group.goforward.ballistic.repos;
|
package group.goforward.ballistic.repos;
|
||||||
|
|
||||||
import group.goforward.ballistic.model.Brand;
|
|
||||||
import group.goforward.ballistic.model.ImportStatus;
|
import group.goforward.ballistic.model.ImportStatus;
|
||||||
|
import group.goforward.ballistic.model.Merchant;
|
||||||
|
import group.goforward.ballistic.model.MerchantCategoryMapping;
|
||||||
|
import group.goforward.ballistic.model.Brand;
|
||||||
import group.goforward.ballistic.model.Product;
|
import group.goforward.ballistic.model.Product;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -21,6 +24,8 @@ public interface ProductRepository extends JpaRepository<Product, Integer> {
|
|||||||
|
|
||||||
List<Product> findAllByBrandAndUpc(Brand brand, String upc);
|
List<Product> findAllByBrandAndUpc(Brand brand, String upc);
|
||||||
|
|
||||||
|
long countByImportStatus(ImportStatus importStatus);
|
||||||
|
|
||||||
boolean existsBySlug(String slug);
|
boolean existsBySlug(String slug);
|
||||||
|
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
@@ -28,12 +33,12 @@ public interface ProductRepository extends JpaRepository<Product, Integer> {
|
|||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
|
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT p
|
SELECT p
|
||||||
FROM Product p
|
FROM Product p
|
||||||
JOIN FETCH p.brand b
|
JOIN FETCH p.brand b
|
||||||
WHERE p.platform = :platform
|
WHERE p.platform = :platform
|
||||||
AND p.deletedAt IS NULL
|
AND p.deletedAt IS NULL
|
||||||
""")
|
""")
|
||||||
List<Product> findByPlatformWithBrand(@Param("platform") String platform);
|
List<Product> findByPlatformWithBrand(@Param("platform") String platform);
|
||||||
|
|
||||||
@Query(name = "Products.findByPlatformWithBrand")
|
@Query(name = "Products.findByPlatformWithBrand")
|
||||||
@@ -57,13 +62,13 @@ public interface ProductRepository extends JpaRepository<Product, Integer> {
|
|||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
|
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT p
|
SELECT p
|
||||||
FROM Product p
|
FROM Product p
|
||||||
JOIN FETCH p.brand b
|
JOIN FETCH p.brand b
|
||||||
WHERE p.platform = :platform
|
WHERE p.platform = :platform
|
||||||
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> findTop5ByPlatformWithBrand(@Param("platform") String platform);
|
||||||
|
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
@@ -72,15 +77,15 @@ public interface ProductRepository extends JpaRepository<Product, Integer> {
|
|||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
|
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT DISTINCT p
|
SELECT DISTINCT p
|
||||||
FROM Product p
|
FROM Product p
|
||||||
LEFT JOIN FETCH p.brand b
|
LEFT JOIN FETCH p.brand b
|
||||||
LEFT JOIN FETCH p.offers o
|
LEFT JOIN FETCH p.offers o
|
||||||
WHERE p.platform = :platform
|
WHERE p.platform = :platform
|
||||||
AND p.partRole IN :partRoles
|
AND p.partRole IN :partRoles
|
||||||
AND p.importStatus = :status
|
AND p.importStatus = :status
|
||||||
AND p.deletedAt IS NULL
|
AND p.deletedAt IS NULL
|
||||||
""")
|
""")
|
||||||
List<Product> findForGunbuilderByPlatformAndPartRoles(
|
List<Product> findForGunbuilderByPlatformAndPartRoles(
|
||||||
@Param("platform") String platform,
|
@Param("platform") String platform,
|
||||||
@Param("partRoles") Collection<String> partRoles,
|
@Param("partRoles") Collection<String> partRoles,
|
||||||
@@ -91,85 +96,111 @@ public interface ProductRepository extends JpaRepository<Product, Integer> {
|
|||||||
// Admin import-status dashboard (summary)
|
// Admin import-status dashboard (summary)
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT p.importStatus AS status, COUNT(p) AS count
|
SELECT p.importStatus AS status, COUNT(p) AS count
|
||||||
FROM Product p
|
FROM Product p
|
||||||
WHERE p.deletedAt IS NULL
|
WHERE p.deletedAt IS NULL
|
||||||
GROUP BY p.importStatus
|
GROUP BY p.importStatus
|
||||||
""")
|
""")
|
||||||
List<Map<String, Object>> aggregateByImportStatus();
|
List<Map<String, Object>> aggregateByImportStatus();
|
||||||
|
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
// Admin import-status dashboard (by merchant)
|
// Admin import-status dashboard (by merchant)
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT m.name AS merchantName,
|
SELECT m.id AS merchantId,
|
||||||
p.platform AS platform,
|
m.name AS merchantName,
|
||||||
p.importStatus AS status,
|
p.platform AS platform,
|
||||||
COUNT(p) AS count
|
p.importStatus AS status,
|
||||||
FROM Product p
|
COUNT(p) AS count
|
||||||
JOIN p.offers o
|
FROM Product p
|
||||||
JOIN o.merchant m
|
JOIN p.offers o
|
||||||
WHERE p.deletedAt IS NULL
|
JOIN o.merchant m
|
||||||
GROUP BY m.name, p.platform, p.importStatus
|
WHERE p.deletedAt IS NULL
|
||||||
""")
|
GROUP BY m.id, m.name, p.platform, p.importStatus
|
||||||
|
ORDER BY m.name ASC, p.platform ASC, p.importStatus ASC
|
||||||
|
""")
|
||||||
List<Map<String, Object>> aggregateByMerchantAndStatus();
|
List<Map<String, Object>> aggregateByMerchantAndStatus();
|
||||||
|
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
// Admin: Unmapped category clusters
|
// Admin: Unmapped category clusters
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT p.rawCategoryKey AS rawCategoryKey,
|
SELECT p.rawCategoryKey AS rawCategoryKey,
|
||||||
m.name AS merchantName,
|
m.name AS merchantName,
|
||||||
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
|
||||||
WHERE p.deletedAt IS NULL
|
WHERE p.deletedAt IS NULL
|
||||||
AND (p.importStatus = group.goforward.ballistic.model.ImportStatus.PENDING_MAPPING
|
AND (p.importStatus = group.goforward.ballistic.model.ImportStatus.PENDING_MAPPING
|
||||||
OR p.partRole IS NULL
|
OR p.partRole IS NULL
|
||||||
OR LOWER(p.partRole) = 'unknown')
|
OR LOWER(p.partRole) = 'unknown')
|
||||||
AND p.rawCategoryKey IS NOT NULL
|
AND p.rawCategoryKey IS NOT NULL
|
||||||
GROUP BY p.rawCategoryKey, m.name
|
GROUP BY p.rawCategoryKey, m.name
|
||||||
ORDER BY productCount DESC
|
ORDER BY productCount DESC
|
||||||
""")
|
""")
|
||||||
List<Map<String, Object>> findUnmappedCategoryGroups();
|
List<Map<String, Object>> findUnmappedCategoryGroups();
|
||||||
|
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT p
|
SELECT p
|
||||||
FROM Product p
|
FROM Product p
|
||||||
JOIN p.offers o
|
JOIN p.offers o
|
||||||
JOIN o.merchant m
|
JOIN o.merchant m
|
||||||
WHERE p.deletedAt IS NULL
|
WHERE p.deletedAt IS NULL
|
||||||
AND m.name = :merchantName
|
AND m.name = :merchantName
|
||||||
AND p.rawCategoryKey = :rawCategoryKey
|
AND p.rawCategoryKey = :rawCategoryKey
|
||||||
ORDER BY p.id
|
ORDER BY p.id
|
||||||
""")
|
""")
|
||||||
List<Product> findExamplesForCategoryGroup(
|
List<Product> findExamplesForCategoryGroup(
|
||||||
@Param("merchantName") String merchantName,
|
@Param("merchantName") String merchantName,
|
||||||
@Param("rawCategoryKey") String rawCategoryKey
|
@Param("rawCategoryKey") String rawCategoryKey
|
||||||
);
|
);
|
||||||
|
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
// Admin Bulk Mapper: Merchant + rawCategoryKey buckets
|
// Mapping admin – pending buckets (all merchants)
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT m.id,
|
SELECT m.id AS merchantId,
|
||||||
m.name,
|
m.name AS merchantName,
|
||||||
p.rawCategoryKey,
|
p.rawCategoryKey AS rawCategoryKey,
|
||||||
COALESCE(mcm.mappedPartRole, '') AS mappedPartRole,
|
mcm.mappedPartRole AS mappedPartRole,
|
||||||
COUNT(DISTINCT p.id)
|
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 MerchantCategoryMapping mcm
|
||||||
ON mcm.merchant = m
|
ON mcm.merchant.id = m.id
|
||||||
AND mcm.rawCategory = p.rawCategoryKey
|
AND mcm.rawCategory = p.rawCategoryKey
|
||||||
WHERE p.importStatus = :status
|
WHERE p.importStatus = :status
|
||||||
AND p.deletedAt IS NULL
|
GROUP BY m.id, m.name, p.rawCategoryKey, mcm.mappedPartRole
|
||||||
GROUP BY m.id, m.name, p.rawCategoryKey, mcm.mappedPartRole
|
ORDER BY productCount DESC
|
||||||
ORDER BY COUNT(DISTINCT p.id) DESC
|
""")
|
||||||
""")
|
|
||||||
List<Object[]> findPendingMappingBuckets(
|
List<Object[]> findPendingMappingBuckets(
|
||||||
@Param("status") ImportStatus status
|
@Param("status") ImportStatus status
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// -------------------------------------------------
|
||||||
|
// Mapping admin – pending buckets for a single merchant
|
||||||
|
// -------------------------------------------------
|
||||||
|
@Query("""
|
||||||
|
SELECT m.id AS merchantId,
|
||||||
|
m.name AS merchantName,
|
||||||
|
p.rawCategoryKey AS rawCategoryKey,
|
||||||
|
mcm.mappedPartRole AS mappedPartRole,
|
||||||
|
COUNT(DISTINCT p.id) AS productCount
|
||||||
|
FROM Product p
|
||||||
|
JOIN p.offers o
|
||||||
|
JOIN o.merchant m
|
||||||
|
LEFT JOIN MerchantCategoryMapping mcm
|
||||||
|
ON mcm.merchant.id = m.id
|
||||||
|
AND mcm.rawCategory = p.rawCategoryKey
|
||||||
|
WHERE p.importStatus = :status
|
||||||
|
AND m.id = :merchantId
|
||||||
|
GROUP BY m.id, m.name, p.rawCategoryKey, mcm.mappedPartRole
|
||||||
|
ORDER BY productCount DESC
|
||||||
|
""")
|
||||||
|
List<Object[]> findPendingMappingBucketsForMerchant(
|
||||||
|
@Param("merchantId") Integer merchantId,
|
||||||
|
@Param("status") ImportStatus status
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package group.goforward.ballistic.services;
|
||||||
|
|
||||||
|
import group.goforward.ballistic.model.ImportStatus;
|
||||||
|
import group.goforward.ballistic.repos.MerchantCategoryMappingRepository;
|
||||||
|
import group.goforward.ballistic.repos.MerchantRepository;
|
||||||
|
import group.goforward.ballistic.repos.ProductRepository;
|
||||||
|
import group.goforward.ballistic.web.dto.AdminDashboardOverviewDto;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class AdminDashboardService {
|
||||||
|
|
||||||
|
private final ProductRepository productRepository;
|
||||||
|
private final MerchantRepository merchantRepository;
|
||||||
|
private final MerchantCategoryMappingRepository merchantCategoryMappingRepository;
|
||||||
|
|
||||||
|
public AdminDashboardService(
|
||||||
|
ProductRepository productRepository,
|
||||||
|
MerchantRepository merchantRepository,
|
||||||
|
MerchantCategoryMappingRepository merchantCategoryMappingRepository
|
||||||
|
) {
|
||||||
|
this.productRepository = productRepository;
|
||||||
|
this.merchantRepository = merchantRepository;
|
||||||
|
this.merchantCategoryMappingRepository = merchantCategoryMappingRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public AdminDashboardOverviewDto getOverview() {
|
||||||
|
long totalProducts = productRepository.count();
|
||||||
|
long unmappedProducts = productRepository.countByImportStatus(ImportStatus.PENDING_MAPPING);
|
||||||
|
long mappedProducts = totalProducts - unmappedProducts;
|
||||||
|
|
||||||
|
long merchantCount = merchantRepository.count();
|
||||||
|
long categoryMappings = merchantCategoryMappingRepository.count();
|
||||||
|
|
||||||
|
return new AdminDashboardOverviewDto(
|
||||||
|
totalProducts,
|
||||||
|
mappedProducts,
|
||||||
|
unmappedProducts,
|
||||||
|
merchantCount,
|
||||||
|
categoryMappings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,19 +29,28 @@ public class MappingAdminService {
|
|||||||
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(
|
||||||
ImportStatus.PENDING_MAPPING // use top-level enum
|
ImportStatus.PENDING_MAPPING
|
||||||
);
|
);
|
||||||
|
|
||||||
return rows.stream()
|
return rows.stream()
|
||||||
.map(row -> {
|
.map(row -> {
|
||||||
Integer merchantId = (Integer) row[0];
|
Integer merchantId = (Integer) row[0];
|
||||||
String merchantName = (String) row[1];
|
String merchantName = (String) row[1];
|
||||||
String rawCategoryKey = (String) row[2];
|
String rawCategoryKey = (String) row[2];
|
||||||
String mappedPartRole = (String) row[3];
|
String mappedPartRole = (String) row[3];
|
||||||
Long count = (Long) row[4];
|
Long count = (Long) row[4];
|
||||||
|
|
||||||
return new PendingMappingBucketDto(
|
return new PendingMappingBucketDto(
|
||||||
merchantId,
|
merchantId,
|
||||||
@@ -54,6 +63,10 @@ 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()) {
|
||||||
@@ -75,9 +88,6 @@ public class MappingAdminService {
|
|||||||
mapping.setMappedPartRole(mappedPartRole.trim());
|
mapping.setMappedPartRole(mappedPartRole.trim());
|
||||||
merchantCategoryMappingRepository.save(mapping);
|
merchantCategoryMappingRepository.save(mapping);
|
||||||
|
|
||||||
// NOTE:
|
// Products will pick up this mapping on the next merchant import run.
|
||||||
// We're not touching existing Product rows here.
|
|
||||||
// They will pick up this mapping on the next merchant import,
|
|
||||||
// which keeps this endpoint fast and side-effect simple.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package group.goforward.ballistic.web.admin;
|
||||||
|
|
||||||
|
import group.goforward.ballistic.model.ImportStatus;
|
||||||
|
import group.goforward.ballistic.repos.ProductRepository;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/admin/import-status")
|
||||||
|
public class AdminImportStatusController {
|
||||||
|
|
||||||
|
private final ProductRepository productRepository;
|
||||||
|
|
||||||
|
public AdminImportStatusController(ProductRepository productRepository) {
|
||||||
|
this.productRepository = productRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ImportSummaryDto(
|
||||||
|
long totalProducts,
|
||||||
|
long mappedProducts,
|
||||||
|
long pendingProducts
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public record ByMerchantRowDto(
|
||||||
|
Integer merchantId,
|
||||||
|
String merchantName,
|
||||||
|
String platform,
|
||||||
|
ImportStatus status,
|
||||||
|
long count
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@GetMapping("/summary")
|
||||||
|
public ImportSummaryDto summary() {
|
||||||
|
List<Map<String, Object>> rows = productRepository.aggregateByImportStatus();
|
||||||
|
|
||||||
|
long total = 0L;
|
||||||
|
long mapped = 0L;
|
||||||
|
long pending = 0L;
|
||||||
|
|
||||||
|
for (Map<String, Object> row : rows) {
|
||||||
|
ImportStatus status = (ImportStatus) row.get("status");
|
||||||
|
long count = ((Number) row.get("count")).longValue();
|
||||||
|
total += count;
|
||||||
|
|
||||||
|
if (status == ImportStatus.MAPPED) {
|
||||||
|
mapped += count;
|
||||||
|
} else if (status == ImportStatus.PENDING_MAPPING) {
|
||||||
|
pending += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ImportSummaryDto(total, mapped, pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/by-merchant")
|
||||||
|
public List<ByMerchantRowDto> byMerchant() {
|
||||||
|
List<Map<String, Object>> rows = productRepository.aggregateByMerchantAndStatus();
|
||||||
|
|
||||||
|
return rows.stream()
|
||||||
|
.map(row -> new ByMerchantRowDto(
|
||||||
|
(Integer) row.get("merchantId"),
|
||||||
|
(String) row.get("merchantName"),
|
||||||
|
(String) row.get("platform"),
|
||||||
|
(ImportStatus) row.get("status"),
|
||||||
|
((Number) row.get("count")).longValue()
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,16 +20,26 @@ public class AdminMappingController {
|
|||||||
|
|
||||||
@GetMapping("/pending-buckets")
|
@GetMapping("/pending-buckets")
|
||||||
public List<PendingMappingBucketDto> listPendingBuckets() {
|
public List<PendingMappingBucketDto> listPendingBuckets() {
|
||||||
|
// Simple: just delegate to service
|
||||||
return mappingAdminService.listPendingBuckets();
|
return mappingAdminService.listPendingBuckets();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/apply")
|
public record ApplyMappingRequest(
|
||||||
public ResponseEntity<?> applyMapping(@RequestBody Map<String, Object> body) {
|
Integer merchantId,
|
||||||
Integer merchantId = (Integer) body.get("merchantId");
|
String rawCategoryKey,
|
||||||
String rawCategoryKey = (String) body.get("rawCategoryKey");
|
String mappedPartRole
|
||||||
String mappedPartRole = (String) body.get("mappedPartRole");
|
) {}
|
||||||
|
|
||||||
|
@PostMapping("/apply")
|
||||||
|
public ResponseEntity<Map<String, Object>> applyMapping(
|
||||||
|
@RequestBody ApplyMappingRequest request
|
||||||
|
) {
|
||||||
|
mappingAdminService.applyMapping(
|
||||||
|
request.merchantId(),
|
||||||
|
request.rawCategoryKey(),
|
||||||
|
request.mappedPartRole()
|
||||||
|
);
|
||||||
|
|
||||||
mappingAdminService.applyMapping(merchantId, rawCategoryKey, mappedPartRole);
|
|
||||||
return ResponseEntity.ok(Map.of("ok", true));
|
return ResponseEntity.ok(Map.of("ok", true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package group.goforward.ballistic.web.admin;
|
||||||
|
|
||||||
|
import group.goforward.ballistic.services.MerchantFeedImportService;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/admin/merchants")
|
||||||
|
public class AdminMerchantController {
|
||||||
|
|
||||||
|
private final MerchantFeedImportService merchantFeedImportService;
|
||||||
|
|
||||||
|
public AdminMerchantController(MerchantFeedImportService merchantFeedImportService) {
|
||||||
|
this.merchantFeedImportService = merchantFeedImportService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{merchantId}/import")
|
||||||
|
public ResponseEntity<Map<String, Object>> triggerFullImport(
|
||||||
|
@PathVariable Integer merchantId
|
||||||
|
) {
|
||||||
|
// Fire off the full import for this merchant.
|
||||||
|
// (Right now this is synchronous; later we can push to a queue if needed.)
|
||||||
|
merchantFeedImportService.importMerchantFeed(merchantId);
|
||||||
|
|
||||||
|
return ResponseEntity.accepted().body(
|
||||||
|
Map.of(
|
||||||
|
"ok", true,
|
||||||
|
"merchantId", merchantId,
|
||||||
|
"message", "Import triggered"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import org.springframework.http.ResponseEntity;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/admin/mappings")
|
@RequestMapping("/api/admin/mappings")
|
||||||
@@ -37,4 +38,30 @@ public class CategoryMappingAdminController {
|
|||||||
);
|
);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @RestController
|
||||||
|
// @RequestMapping("/api/admin/mapping")
|
||||||
|
// public static class AdminMappingController {
|
||||||
|
//
|
||||||
|
// private final MappingAdminService mappingAdminService;
|
||||||
|
//
|
||||||
|
// public AdminMappingController(MappingAdminService mappingAdminService) {
|
||||||
|
// this.mappingAdminService = mappingAdminService;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @GetMapping("/pending-buckets")
|
||||||
|
// public List<PendingMappingBucketDto> listPendingBuckets() {
|
||||||
|
// return mappingAdminService.listPendingBuckets();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @PostMapping("/apply")
|
||||||
|
// public ResponseEntity<?> applyMapping(@RequestBody Map<String, Object> body) {
|
||||||
|
// Integer merchantId = (Integer) body.get("merchantId");
|
||||||
|
// String rawCategoryKey = (String) body.get("rawCategoryKey");
|
||||||
|
// String mappedPartRole = (String) body.get("mappedPartRole");
|
||||||
|
//
|
||||||
|
// mappingAdminService.applyMapping(merchantId, rawCategoryKey, mappedPartRole);
|
||||||
|
// return ResponseEntity.ok(Map.of("ok", true));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
// src/main/java/group/goforward/ballistic/web/admin/ImportStatusAdminController.java
|
|
||||||
package group.goforward.ballistic.web.admin;
|
|
||||||
|
|
||||||
import group.goforward.ballistic.services.ImportStatusAdminService;
|
|
||||||
import group.goforward.ballistic.web.dto.ImportStatusByMerchantDto;
|
|
||||||
import group.goforward.ballistic.web.dto.ImportStatusSummaryDto;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/admin/import-status")
|
|
||||||
public class ImportStatusAdminController {
|
|
||||||
|
|
||||||
private final ImportStatusAdminService service;
|
|
||||||
|
|
||||||
public ImportStatusAdminController(ImportStatusAdminService service) {
|
|
||||||
this.service = service;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/summary")
|
|
||||||
public List<ImportStatusSummaryDto> summary() {
|
|
||||||
return service.summarizeByStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/by-merchant")
|
|
||||||
public List<ImportStatusByMerchantDto> byMerchant() {
|
|
||||||
return service.summarizeByMerchant();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package group.goforward.ballistic.web.dto;
|
||||||
|
|
||||||
|
public class AdminDashboardOverviewDto {
|
||||||
|
|
||||||
|
private long totalProducts;
|
||||||
|
private long mappedProducts;
|
||||||
|
private long unmappedProducts;
|
||||||
|
private long merchantCount;
|
||||||
|
private long categoryMappingCount;
|
||||||
|
|
||||||
|
public AdminDashboardOverviewDto(
|
||||||
|
long totalProducts,
|
||||||
|
long mappedProducts,
|
||||||
|
long unmappedProducts,
|
||||||
|
long merchantCount,
|
||||||
|
long categoryMappingCount
|
||||||
|
) {
|
||||||
|
this.totalProducts = totalProducts;
|
||||||
|
this.mappedProducts = mappedProducts;
|
||||||
|
this.unmappedProducts = unmappedProducts;
|
||||||
|
this.merchantCount = merchantCount;
|
||||||
|
this.categoryMappingCount = categoryMappingCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTotalProducts() {
|
||||||
|
return totalProducts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getMappedProducts() {
|
||||||
|
return mappedProducts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getUnmappedProducts() {
|
||||||
|
return unmappedProducts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getMerchantCount() {
|
||||||
|
return merchantCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCategoryMappingCount() {
|
||||||
|
return categoryMappingCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user