From 084544376728ef42b73eecc11ab721d7eaae3c48 Mon Sep 17 00:00:00 2001 From: Sean Date: Sun, 7 Dec 2025 16:17:57 -0500 Subject: [PATCH] new admin dashboard controllers for mappings --- .../admin/AdminDashboardController.java | 25 +++ .../ballistic/repos/ProductRepository.java | 189 ++++++++++-------- .../services/AdminDashboardService.java | 45 +++++ .../services/MappingAdminService.java | 30 ++- .../admin/AdminImportStatusController.java | 73 +++++++ .../admin/AdminMappingController.java | 22 +- .../web/admin/AdminMerchantController.java | 35 ++++ .../admin/CategoryMappingAdminController.java | 27 +++ .../admin/ImportStatusAdminController.java | 32 --- .../dto/admin/AdminDashboardOverviewDto.java | 44 ++++ 10 files changed, 395 insertions(+), 127 deletions(-) create mode 100644 src/main/java/group/goforward/ballistic/controllers/admin/AdminDashboardController.java create mode 100644 src/main/java/group/goforward/ballistic/services/AdminDashboardService.java create mode 100644 src/main/java/group/goforward/ballistic/web/admin/AdminImportStatusController.java rename src/main/java/group/goforward/ballistic/web/{dto => }/admin/AdminMappingController.java (63%) create mode 100644 src/main/java/group/goforward/ballistic/web/admin/AdminMerchantController.java delete mode 100644 src/main/java/group/goforward/ballistic/web/admin/ImportStatusAdminController.java create mode 100644 src/main/java/group/goforward/ballistic/web/dto/admin/AdminDashboardOverviewDto.java diff --git a/src/main/java/group/goforward/ballistic/controllers/admin/AdminDashboardController.java b/src/main/java/group/goforward/ballistic/controllers/admin/AdminDashboardController.java new file mode 100644 index 0000000..0b35b39 --- /dev/null +++ b/src/main/java/group/goforward/ballistic/controllers/admin/AdminDashboardController.java @@ -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 getOverview() { + AdminDashboardOverviewDto dto = adminDashboardService.getOverview(); + return ResponseEntity.ok(dto); + } +} \ No newline at end of file diff --git a/src/main/java/group/goforward/ballistic/repos/ProductRepository.java b/src/main/java/group/goforward/ballistic/repos/ProductRepository.java index bd20f6b..8886663 100644 --- a/src/main/java/group/goforward/ballistic/repos/ProductRepository.java +++ b/src/main/java/group/goforward/ballistic/repos/ProductRepository.java @@ -1,11 +1,14 @@ package group.goforward.ballistic.repos; -import group.goforward.ballistic.model.Brand; 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 org.springframework.data.jpa.repository.JpaRepository; + import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.data.jpa.repository.JpaRepository; import java.util.Collection; import java.util.List; @@ -21,6 +24,8 @@ public interface ProductRepository extends JpaRepository { List findAllByBrandAndUpc(Brand brand, String upc); + long countByImportStatus(ImportStatus importStatus); + boolean existsBySlug(String slug); // ------------------------------------------------- @@ -28,12 +33,12 @@ public interface ProductRepository extends JpaRepository { // ------------------------------------------------- @Query(""" - SELECT p - FROM Product p - JOIN FETCH p.brand b - WHERE p.platform = :platform - AND p.deletedAt IS NULL - """) + SELECT p + FROM Product p + JOIN FETCH p.brand b + WHERE p.platform = :platform + AND p.deletedAt IS NULL + """) List findByPlatformWithBrand(@Param("platform") String platform); @Query(name = "Products.findByPlatformWithBrand") @@ -57,13 +62,13 @@ public interface ProductRepository extends JpaRepository { // ------------------------------------------------- @Query(""" - SELECT p - FROM Product p - JOIN FETCH p.brand b - WHERE p.platform = :platform - AND p.deletedAt IS NULL - ORDER BY p.id - """) + SELECT p + FROM Product p + JOIN FETCH p.brand b + WHERE p.platform = :platform + AND p.deletedAt IS NULL + ORDER BY p.id + """) List findTop5ByPlatformWithBrand(@Param("platform") String platform); // ------------------------------------------------- @@ -72,15 +77,15 @@ public interface ProductRepository extends JpaRepository { // ------------------------------------------------- @Query(""" - SELECT DISTINCT p - FROM Product p - LEFT JOIN FETCH p.brand b - LEFT JOIN FETCH p.offers o - WHERE p.platform = :platform - AND p.partRole IN :partRoles - AND p.importStatus = :status - AND p.deletedAt IS NULL - """) + SELECT DISTINCT p + FROM Product p + LEFT JOIN FETCH p.brand b + LEFT JOIN FETCH p.offers o + WHERE p.platform = :platform + AND p.partRole IN :partRoles + AND p.importStatus = :status + AND p.deletedAt IS NULL + """) List findForGunbuilderByPlatformAndPartRoles( @Param("platform") String platform, @Param("partRoles") Collection partRoles, @@ -91,85 +96,111 @@ public interface ProductRepository extends JpaRepository { // Admin import-status dashboard (summary) // ------------------------------------------------- @Query(""" - SELECT p.importStatus AS status, COUNT(p) AS count - FROM Product p - WHERE p.deletedAt IS NULL - GROUP BY p.importStatus - """) + SELECT p.importStatus AS status, COUNT(p) AS count + FROM Product p + WHERE p.deletedAt IS NULL + GROUP BY p.importStatus + """) List> aggregateByImportStatus(); // ------------------------------------------------- // Admin import-status dashboard (by merchant) // ------------------------------------------------- @Query(""" - SELECT m.name AS merchantName, - p.platform AS platform, - p.importStatus AS status, - COUNT(p) AS count - FROM Product p - JOIN p.offers o - JOIN o.merchant m - WHERE p.deletedAt IS NULL - GROUP BY m.name, p.platform, p.importStatus - """) + SELECT m.id AS merchantId, + m.name AS merchantName, + p.platform AS platform, + p.importStatus AS status, + COUNT(p) AS count + FROM Product p + JOIN p.offers o + JOIN o.merchant m + 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> aggregateByMerchantAndStatus(); // ------------------------------------------------- // Admin: Unmapped category clusters // ------------------------------------------------- @Query(""" - SELECT p.rawCategoryKey AS rawCategoryKey, - m.name AS merchantName, - COUNT(DISTINCT p.id) AS productCount - FROM Product p - JOIN p.offers o - JOIN o.merchant m - WHERE p.deletedAt IS NULL - AND (p.importStatus = group.goforward.ballistic.model.ImportStatus.PENDING_MAPPING - OR p.partRole IS NULL - OR LOWER(p.partRole) = 'unknown') - AND p.rawCategoryKey IS NOT NULL - GROUP BY p.rawCategoryKey, m.name - ORDER BY productCount DESC - """) + SELECT p.rawCategoryKey AS rawCategoryKey, + m.name AS merchantName, + COUNT(DISTINCT p.id) AS productCount + FROM Product p + JOIN p.offers o + JOIN o.merchant m + WHERE p.deletedAt IS NULL + AND (p.importStatus = group.goforward.ballistic.model.ImportStatus.PENDING_MAPPING + OR p.partRole IS NULL + OR LOWER(p.partRole) = 'unknown') + AND p.rawCategoryKey IS NOT NULL + GROUP BY p.rawCategoryKey, m.name + ORDER BY productCount DESC + """) List> findUnmappedCategoryGroups(); @Query(""" - SELECT p - FROM Product p - JOIN p.offers o - JOIN o.merchant m - WHERE p.deletedAt IS NULL - AND m.name = :merchantName - AND p.rawCategoryKey = :rawCategoryKey - ORDER BY p.id - """) + SELECT p + FROM Product p + JOIN p.offers o + JOIN o.merchant m + WHERE p.deletedAt IS NULL + AND m.name = :merchantName + AND p.rawCategoryKey = :rawCategoryKey + ORDER BY p.id + """) List findExamplesForCategoryGroup( @Param("merchantName") String merchantName, @Param("rawCategoryKey") String rawCategoryKey ); // ------------------------------------------------- - // Admin Bulk Mapper: Merchant + rawCategoryKey buckets + // Mapping admin – pending buckets (all merchants) // ------------------------------------------------- @Query(""" - SELECT m.id, - m.name, - p.rawCategoryKey, - COALESCE(mcm.mappedPartRole, '') AS mappedPartRole, - COUNT(DISTINCT p.id) - FROM Product p - JOIN p.offers o - JOIN o.merchant m - LEFT JOIN MerchantCategoryMapping mcm - ON mcm.merchant = m - AND mcm.rawCategory = p.rawCategoryKey - WHERE p.importStatus = :status - AND p.deletedAt IS NULL - GROUP BY m.id, m.name, p.rawCategoryKey, mcm.mappedPartRole - ORDER BY COUNT(DISTINCT p.id) DESC - """) + 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 + GROUP BY m.id, m.name, p.rawCategoryKey, mcm.mappedPartRole + ORDER BY productCount DESC + """) List findPendingMappingBuckets( @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 findPendingMappingBucketsForMerchant( + @Param("merchantId") Integer merchantId, + @Param("status") ImportStatus status + ); } \ No newline at end of file diff --git a/src/main/java/group/goforward/ballistic/services/AdminDashboardService.java b/src/main/java/group/goforward/ballistic/services/AdminDashboardService.java new file mode 100644 index 0000000..f84238a --- /dev/null +++ b/src/main/java/group/goforward/ballistic/services/AdminDashboardService.java @@ -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 + ); + } +} \ No newline at end of file diff --git a/src/main/java/group/goforward/ballistic/services/MappingAdminService.java b/src/main/java/group/goforward/ballistic/services/MappingAdminService.java index 4b90881..90af277 100644 --- a/src/main/java/group/goforward/ballistic/services/MappingAdminService.java +++ b/src/main/java/group/goforward/ballistic/services/MappingAdminService.java @@ -29,19 +29,28 @@ public class MappingAdminService { 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) public List listPendingBuckets() { List rows = productRepository.findPendingMappingBuckets( - ImportStatus.PENDING_MAPPING // use top-level enum + ImportStatus.PENDING_MAPPING ); return rows.stream() .map(row -> { - Integer merchantId = (Integer) row[0]; - String merchantName = (String) row[1]; - String rawCategoryKey = (String) row[2]; - String mappedPartRole = (String) row[3]; - Long count = (Long) row[4]; + Integer merchantId = (Integer) row[0]; + String merchantName = (String) row[1]; + String rawCategoryKey = (String) row[2]; + String mappedPartRole = (String) row[3]; + Long count = (Long) row[4]; return new PendingMappingBucketDto( merchantId, @@ -54,6 +63,10 @@ public class MappingAdminService { .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 public void applyMapping(Integer merchantId, String rawCategoryKey, String mappedPartRole) { if (merchantId == null || rawCategoryKey == null || mappedPartRole == null || mappedPartRole.isBlank()) { @@ -75,9 +88,6 @@ public class MappingAdminService { mapping.setMappedPartRole(mappedPartRole.trim()); merchantCategoryMappingRepository.save(mapping); - // NOTE: - // 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. + // Products will pick up this mapping on the next merchant import run. } } \ No newline at end of file diff --git a/src/main/java/group/goforward/ballistic/web/admin/AdminImportStatusController.java b/src/main/java/group/goforward/ballistic/web/admin/AdminImportStatusController.java new file mode 100644 index 0000000..962c868 --- /dev/null +++ b/src/main/java/group/goforward/ballistic/web/admin/AdminImportStatusController.java @@ -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> rows = productRepository.aggregateByImportStatus(); + + long total = 0L; + long mapped = 0L; + long pending = 0L; + + for (Map 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 byMerchant() { + List> 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(); + } +} \ No newline at end of file diff --git a/src/main/java/group/goforward/ballistic/web/dto/admin/AdminMappingController.java b/src/main/java/group/goforward/ballistic/web/admin/AdminMappingController.java similarity index 63% rename from src/main/java/group/goforward/ballistic/web/dto/admin/AdminMappingController.java rename to src/main/java/group/goforward/ballistic/web/admin/AdminMappingController.java index 3ef41c3..0135eec 100644 --- a/src/main/java/group/goforward/ballistic/web/dto/admin/AdminMappingController.java +++ b/src/main/java/group/goforward/ballistic/web/admin/AdminMappingController.java @@ -20,16 +20,26 @@ public class AdminMappingController { @GetMapping("/pending-buckets") public List listPendingBuckets() { + // Simple: just delegate to service return mappingAdminService.listPendingBuckets(); } - @PostMapping("/apply") - public ResponseEntity applyMapping(@RequestBody Map body) { - Integer merchantId = (Integer) body.get("merchantId"); - String rawCategoryKey = (String) body.get("rawCategoryKey"); - String mappedPartRole = (String) body.get("mappedPartRole"); + public record ApplyMappingRequest( + Integer merchantId, + String rawCategoryKey, + String mappedPartRole + ) {} + + @PostMapping("/apply") + public ResponseEntity> applyMapping( + @RequestBody ApplyMappingRequest request + ) { + mappingAdminService.applyMapping( + request.merchantId(), + request.rawCategoryKey(), + request.mappedPartRole() + ); - mappingAdminService.applyMapping(merchantId, rawCategoryKey, mappedPartRole); return ResponseEntity.ok(Map.of("ok", true)); } } \ No newline at end of file diff --git a/src/main/java/group/goforward/ballistic/web/admin/AdminMerchantController.java b/src/main/java/group/goforward/ballistic/web/admin/AdminMerchantController.java new file mode 100644 index 0000000..ca5ee46 --- /dev/null +++ b/src/main/java/group/goforward/ballistic/web/admin/AdminMerchantController.java @@ -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> 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" + ) + ); + } +} \ No newline at end of file diff --git a/src/main/java/group/goforward/ballistic/web/admin/CategoryMappingAdminController.java b/src/main/java/group/goforward/ballistic/web/admin/CategoryMappingAdminController.java index 740320f..94d4b35 100644 --- a/src/main/java/group/goforward/ballistic/web/admin/CategoryMappingAdminController.java +++ b/src/main/java/group/goforward/ballistic/web/admin/CategoryMappingAdminController.java @@ -6,6 +6,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Map; @RestController @RequestMapping("/api/admin/mappings") @@ -37,4 +38,30 @@ public class CategoryMappingAdminController { ); 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 listPendingBuckets() { +// return mappingAdminService.listPendingBuckets(); +// } +// +// @PostMapping("/apply") +// public ResponseEntity applyMapping(@RequestBody Map 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)); +// } +// } } \ No newline at end of file diff --git a/src/main/java/group/goforward/ballistic/web/admin/ImportStatusAdminController.java b/src/main/java/group/goforward/ballistic/web/admin/ImportStatusAdminController.java deleted file mode 100644 index 51f2d8c..0000000 --- a/src/main/java/group/goforward/ballistic/web/admin/ImportStatusAdminController.java +++ /dev/null @@ -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 summary() { - return service.summarizeByStatus(); - } - - @GetMapping("/by-merchant") - public List byMerchant() { - return service.summarizeByMerchant(); - } -} \ No newline at end of file diff --git a/src/main/java/group/goforward/ballistic/web/dto/admin/AdminDashboardOverviewDto.java b/src/main/java/group/goforward/ballistic/web/dto/admin/AdminDashboardOverviewDto.java new file mode 100644 index 0000000..ad9316d --- /dev/null +++ b/src/main/java/group/goforward/ballistic/web/dto/admin/AdminDashboardOverviewDto.java @@ -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; + } +} \ No newline at end of file