diff --git a/.gitignore b/.gitignore index 93f7381..4a24407 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ .idea/ target/ master_key.txt +.classpath +.project +*.prefs diff --git a/README.md b/README.md index 2f9477b..2910692 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ To get started with MongoDB Atlas and get a free cluster read [this blog post](h Add MongoDB Atlas Cluster URI in # src/main/resources/application.properties: ``` -spring.data.mongodb.uri=mongodb+srv://:@>t/?retryWrites=true&w=majority +spring.data.mongodb.uri=mongodb+srv://:@/?retryWrites=true&w=majority ``` # Command lines @@ -29,6 +29,8 @@ spring.data.mongodb.uri=mongodb+srv://:@>t/?retryWrites mvn clean compile ``` +## Session 1 + - Run the `Create` class: ```sh @@ -59,3 +61,43 @@ mvn spring-boot:run -Dspring-boot.run.arguments=delete mvn spring-boot:run -Dspring-boot.run.arguments=mapping ``` +## Session 2 +For this session make sure to switch to the `sample_supplies` database in your `application.properties`. + +- Run the `TotalSalesByLocationService` class: + +```sh + mvn spring-boot:run -Dspring-boot.run.arguments=total-sales-by-location +``` + +- Run the `AverageCustomerSatisfactionService` class: + +```sh +mvn spring-boot:run -Dspring-boot.run.arguments=average-customer-satisfaction +``` + +- Run the `AverageItemPricePerStoreService` class: +```sh +mvn spring-boot:run -Dspring-boot.run.arguments=average-item-price-per-store +``` + +- Run the `CountDistinctCustomersService` class: + +```sh +mvn spring-boot:run -Dspring-boot.run.arguments=count-distinct-customers +``` + +- Run the `TotalSalesByDayOfWeekService` class: +```sh +mvn spring-boot:run -Dspring-boot.run.arguments=total-sales-by-day-of-week +``` + +- Run the `RevenueByLocationService` class: +```sh +mvn spring-boot:run -Dspring-boot.run.arguments=revenue-by-location +``` + +- Run the `SalesPerformanceService` class: +```sh +mvn spring-boot:run -Dspring-boot.run.arguments=sales-performance +``` diff --git a/pom.xml b/pom.xml index fdb8200..bdbf0d6 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ 3.12.1 5.0.0 1.8.0 - 3.2.0 + 3.2.5 3.1.1 @@ -32,24 +32,22 @@ mongodb-crypt ${mongodb-crypt.version} - - - org.springframework.boot - spring-boot-starter - ${spring-boot.version} - - - org.springframework.boot - spring-boot-starter-logging - - - org.springframework.boot spring-boot-starter-data-mongodb ${spring-boot.version} + + ch.qos.logback + logback-classic + 1.4.14 + + + org.slf4j + slf4j-api + 2.0.13 + @@ -57,6 +55,10 @@ org.springframework.boot spring-boot-maven-plugin + ${spring-boot.version} + + com.mongodb.quickstart.Application + org.apache.maven.plugins @@ -73,7 +75,7 @@ ${exec-maven-plugin.version} false - com.mongodb.quickstart.Application + com.mongodb.quickstart.Application diff --git a/src/main/java/com/mongodb/quickstart/Application.java b/src/main/java/com/mongodb/quickstart/Application.java index a0f32c1..fcec94e 100644 --- a/src/main/java/com/mongodb/quickstart/Application.java +++ b/src/main/java/com/mongodb/quickstart/Application.java @@ -7,6 +7,14 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ComponentScan; +import com.mongodb.quickstart.services.AverageCustomerSatisfactionService; +import com.mongodb.quickstart.services.AverageItemPricePerStoreService; +import com.mongodb.quickstart.services.CountDistinctCustomersService; +import com.mongodb.quickstart.services.RevenueByLocationService; +import com.mongodb.quickstart.services.SalesPerformanceService; +import com.mongodb.quickstart.services.TotalSalesByDayOfWeekService; +import com.mongodb.quickstart.services.TotalSalesByLocationService; + @SpringBootApplication @ComponentScan(basePackages = "com.mongodb.quickstart") public class Application implements CommandLineRunner { @@ -37,6 +45,27 @@ public void run(String... args) { case "update": context.getBean(Update.class).run(); break; + case "total-sales-by-location": + context.getBean(TotalSalesByLocationService.class).run(); + break; + case "average-customer-satisfaction": + context.getBean(AverageCustomerSatisfactionService.class).run(); + break; + case "average-item-price-per-store": + context.getBean(AverageItemPricePerStoreService.class).run(); + break; + case "count-distinct-customers": + context.getBean(CountDistinctCustomersService.class).run(); + break; + case "total-sales-by-day-of-week": + context.getBean(TotalSalesByDayOfWeekService.class).run(); + break; + case "sales-performance": + context.getBean(SalesPerformanceService.class).run(); + break; + case "revenue-by-location": + context.getBean(RevenueByLocationService.class).run(); + break; case "read": default: context.getBean(Read.class).run(); diff --git a/src/main/java/com/mongodb/quickstart/Delete.java b/src/main/java/com/mongodb/quickstart/Delete.java index 1bd9d5e..034e797 100644 --- a/src/main/java/com/mongodb/quickstart/Delete.java +++ b/src/main/java/com/mongodb/quickstart/Delete.java @@ -2,8 +2,6 @@ import com.mongodb.quickstart.models.Grade; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; -import org.springframework.boot.SpringApplication; import org.springframework.stereotype.Component; @@ -15,14 +13,14 @@ public class Delete { public void run() { // Delete one document - Grade grade = repository.findByStudentId(10000d); + Grade grade = repository.findFirstByStudentId(10000d); if (grade != null) { repository.delete(grade); System.out.println("Deleted grade: " + grade); } // Find and delete one document - grade = repository.findByStudentId(10002d); + grade = repository.findFirstByStudentId(10002d); if (grade != null) { repository.delete(grade); System.out.println("Deleted grade: " + grade); @@ -32,8 +30,5 @@ public void run() { repository.deleteAll(repository.findByStudentIdGreaterThanEqual(10000d)); System.out.println("Deleted all grades with student_id >= 10000."); - // Drop entire collection - repository.deleteAll(); - System.out.println("Deleted the entire grades collection."); } } \ No newline at end of file diff --git a/src/main/java/com/mongodb/quickstart/MappingPOJO.java b/src/main/java/com/mongodb/quickstart/MappingPOJO.java index b0cfaaf..8e8d220 100644 --- a/src/main/java/com/mongodb/quickstart/MappingPOJO.java +++ b/src/main/java/com/mongodb/quickstart/MappingPOJO.java @@ -4,8 +4,6 @@ import com.mongodb.quickstart.models.Score; import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; -import org.springframework.boot.SpringApplication; import org.springframework.stereotype.Component; import java.util.ArrayList; @@ -29,7 +27,7 @@ public void run() { System.out.println("Grade inserted: " + newGrade); // Find this grade - Grade grade = repository.findByStudentId(10003d); + Grade grade = repository.findFirstByStudentId(10003d); System.out.println("Grade found: " + (grade != null ? grade : "No data found")); // Update this grade by adding an exam grade diff --git a/src/main/java/com/mongodb/quickstart/Read.java b/src/main/java/com/mongodb/quickstart/Read.java index 19084a1..b5e062d 100644 --- a/src/main/java/com/mongodb/quickstart/Read.java +++ b/src/main/java/com/mongodb/quickstart/Read.java @@ -2,7 +2,6 @@ import com.mongodb.quickstart.models.Grade; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import java.util.List; @@ -16,7 +15,7 @@ public class Read { public void run() { try { // Find a grade by student ID - Grade grade = repository.findByStudentId(10000d); + Grade grade = repository.findFirstByStudentId(10000d); System.out.println("Student 1 (via Repository): " + (grade != null ? grade : "No data found")); // Find all grades with student IDs greater than or equal to 10000 diff --git a/src/main/java/com/mongodb/quickstart/StudentRepository.java b/src/main/java/com/mongodb/quickstart/StudentRepository.java index be631cf..e2133cd 100644 --- a/src/main/java/com/mongodb/quickstart/StudentRepository.java +++ b/src/main/java/com/mongodb/quickstart/StudentRepository.java @@ -8,8 +8,8 @@ @Repository public interface StudentRepository extends MongoRepository { - Grade findByStudentId(Double studentId); + Grade findFirstByStudentId(Double studentId); List findByStudentIdGreaterThanEqual(Double studentId); List findByStudentIdAndClassIdLessThanEqual(Double studentId, Double classId); Grade findByStudentIdAndClassId(Double studentId, Double classId); -} +} \ No newline at end of file diff --git a/src/main/java/com/mongodb/quickstart/Update.java b/src/main/java/com/mongodb/quickstart/Update.java index a2277be..653da47 100644 --- a/src/main/java/com/mongodb/quickstart/Update.java +++ b/src/main/java/com/mongodb/quickstart/Update.java @@ -17,7 +17,7 @@ public class Update { public void run() { // Update one document by adding a comment - Grade grade = repository.findByStudentId(10000d); + Grade grade = repository.findFirstByStudentId(10000d); if (grade != null) { Grade updatedGrade = repository.save(grade); System.out.println("Grade updated: " + updatedGrade); @@ -44,7 +44,7 @@ public void run() { System.out.println("Updated all grades with student_id >= 10001."); // Find and update - grade = repository.findByStudentId(10000d); + grade = repository.findFirstByStudentId(10000d); if (grade != null) { Grade updated = repository.save(grade); System.out.println("Updated grade after finding: " + updated); diff --git a/src/main/java/com/mongodb/quickstart/config/MongoConfig.java b/src/main/java/com/mongodb/quickstart/config/MongoConfig.java new file mode 100644 index 0000000..27cdb60 --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/config/MongoConfig.java @@ -0,0 +1,38 @@ +package com.mongodb.quickstart.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; + +@Configuration +public class MongoConfig extends AbstractMongoClientConfiguration { + + @Value("${spring.data.mongodb.uri}") + private String mongoUri; + + @Value("${spring.data.mongodb.database}") + private String databaseName; + + @Override + protected String getDatabaseName() { + return databaseName; + } + + @Bean + @Override + public MongoClient mongoClient() { + ConnectionString connectionString = new ConnectionString(mongoUri); + MongoClientSettings mongoClientSettings = MongoClientSettings.builder() + .applyConnectionString(connectionString) + .applicationName("devrel.springio.workshop.java") + .build(); + + return MongoClients.create(mongoClientSettings); + } +} diff --git a/src/main/java/com/mongodb/quickstart/dtos/AverageItemPricePerStoreDTO.java b/src/main/java/com/mongodb/quickstart/dtos/AverageItemPricePerStoreDTO.java new file mode 100644 index 0000000..a0d8e83 --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/dtos/AverageItemPricePerStoreDTO.java @@ -0,0 +1,8 @@ +package com.mongodb.quickstart.dtos; + +public record AverageItemPricePerStoreDTO(String _id, double averagePrice) { + public AverageItemPricePerStoreDTO(String _id, double averagePrice) { + this._id = _id; + this.averagePrice = averagePrice; + } +} diff --git a/src/main/java/com/mongodb/quickstart/dtos/CustomerSatisfactionDTO.java b/src/main/java/com/mongodb/quickstart/dtos/CustomerSatisfactionDTO.java new file mode 100644 index 0000000..9c53b46 --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/dtos/CustomerSatisfactionDTO.java @@ -0,0 +1,8 @@ +package com.mongodb.quickstart.dtos; + +public record CustomerSatisfactionDTO(String storeLocation, double averageSatisfaction) { + public CustomerSatisfactionDTO(String storeLocation, double averageSatisfaction) { + this.storeLocation = storeLocation; + this.averageSatisfaction = averageSatisfaction; + } +} diff --git a/src/main/java/com/mongodb/quickstart/dtos/DistinctCustomersCountDTO.java b/src/main/java/com/mongodb/quickstart/dtos/DistinctCustomersCountDTO.java new file mode 100644 index 0000000..4bcf749 --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/dtos/DistinctCustomersCountDTO.java @@ -0,0 +1,8 @@ +package com.mongodb.quickstart.dtos; + +public record DistinctCustomersCountDTO(String _id, int count) { + public DistinctCustomersCountDTO(String _id, int count) { + this._id = _id; + this.count = count; + } +} diff --git a/src/main/java/com/mongodb/quickstart/dtos/RevenueByLocationDTO.java b/src/main/java/com/mongodb/quickstart/dtos/RevenueByLocationDTO.java new file mode 100644 index 0000000..faada07 --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/dtos/RevenueByLocationDTO.java @@ -0,0 +1,8 @@ +package com.mongodb.quickstart.dtos; + +public record RevenueByLocationDTO(String storeLocation, double totalRevenue) { + public RevenueByLocationDTO(String storeLocation, double totalRevenue) { + this.storeLocation = storeLocation; + this.totalRevenue = totalRevenue; + } +} diff --git a/src/main/java/com/mongodb/quickstart/dtos/SalesByDayOfWeekDTO.java b/src/main/java/com/mongodb/quickstart/dtos/SalesByDayOfWeekDTO.java new file mode 100644 index 0000000..703ebc5 --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/dtos/SalesByDayOfWeekDTO.java @@ -0,0 +1,8 @@ +package com.mongodb.quickstart.dtos; + +public record SalesByDayOfWeekDTO(String _id, int totalSales) { + public SalesByDayOfWeekDTO(String _id, int totalSales) { + this._id = _id; + this.totalSales = totalSales; + } +} diff --git a/src/main/java/com/mongodb/quickstart/dtos/SalesPerformanceDTO.java b/src/main/java/com/mongodb/quickstart/dtos/SalesPerformanceDTO.java new file mode 100644 index 0000000..25852e3 --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/dtos/SalesPerformanceDTO.java @@ -0,0 +1,9 @@ +package com.mongodb.quickstart.dtos; + +public record SalesPerformanceDTO(String storeLocation, int salesWithCoupons, int salesWithoutCoupons) { + public SalesPerformanceDTO(String storeLocation, int salesWithCoupons, int salesWithoutCoupons) { + this.storeLocation = storeLocation; + this.salesWithCoupons = salesWithCoupons; + this.salesWithoutCoupons = salesWithoutCoupons; + } +} diff --git a/src/main/java/com/mongodb/quickstart/dtos/TotalSalesByLocationDTO.java b/src/main/java/com/mongodb/quickstart/dtos/TotalSalesByLocationDTO.java new file mode 100644 index 0000000..ff875bb --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/dtos/TotalSalesByLocationDTO.java @@ -0,0 +1,8 @@ +package com.mongodb.quickstart.dtos; + +public record TotalSalesByLocationDTO(String _id, int totalSales) { + public TotalSalesByLocationDTO(String _id, int totalSales) { + this._id = _id; + this.totalSales = totalSales; + } +} diff --git a/src/main/java/com/mongodb/quickstart/models/Customer.java b/src/main/java/com/mongodb/quickstart/models/Customer.java new file mode 100644 index 0000000..232d8a4 --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/models/Customer.java @@ -0,0 +1,50 @@ +package com.mongodb.quickstart.models; + +public class Customer { + private String gender; + private int age; + private String email; + private int satisfaction; + + public Customer() { + } + + public Customer(String gender, int age, String email, int satisfaction) { + this.gender = gender; + this.age = age; + this.email = email; + this.satisfaction = satisfaction; + } + + public String getGender() { + return gender; + } + + public void setGender(String gender) { + this.gender = gender; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public int getSatisfaction() { + return satisfaction; + } + + public void setSatisfaction(int satisfaction) { + this.satisfaction = satisfaction; + } +} \ No newline at end of file diff --git a/src/main/java/com/mongodb/quickstart/models/Item.java b/src/main/java/com/mongodb/quickstart/models/Item.java new file mode 100644 index 0000000..adfa1cf --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/models/Item.java @@ -0,0 +1,53 @@ +package com.mongodb.quickstart.models; + +import java.math.BigDecimal; +import java.util.List; + +public class Item { + private String name; + private List tags; + private BigDecimal price; + private int quantity; + + public Item() { + } + + public Item(String name, List tags, BigDecimal price, int quantity) { + this.name = name; + this.tags = tags; + this.price = price; + this.quantity = quantity; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } + + public BigDecimal getPrice() { + return price; + } + + public void setPrice(BigDecimal price) { + this.price = price; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } +} \ No newline at end of file diff --git a/src/main/java/com/mongodb/quickstart/models/Sale.java b/src/main/java/com/mongodb/quickstart/models/Sale.java new file mode 100644 index 0000000..7549f56 --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/models/Sale.java @@ -0,0 +1,95 @@ +package com.mongodb.quickstart.models; + +import java.util.Date; +import java.util.List; + +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document("sale") +public class Sale { + + @Id + private ObjectId id; + private Date saleDate = new Date(); + private List items; + private String storeLocation; + private Customer customer; + private boolean couponUsed; + private String purchaseMethod; + + public Sale() { + } + + public Sale(ObjectId id, Date saleDate, List items, String storeLocation, Customer customer, + boolean couponUsed, String purchaseMethod) { + this.id = id; + this.saleDate = saleDate; + this.items = items; + this.storeLocation = storeLocation; + this.customer = customer; + this.couponUsed = couponUsed; + this.purchaseMethod = purchaseMethod; + } + + public Sale(String id, Date saleDate, String storeLocation, Boolean couponUsed, Customer customer, + List items) { + } + + public ObjectId getId() { + return id; + } + + public void setId(ObjectId id) { + this.id = id; + } + + public Date getSaleDate() { + return saleDate; + } + + public void setSaleDate(Date saleDate) { + this.saleDate = saleDate; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public String getStoreLocation() { + return storeLocation; + } + + public void setStoreLocation(String storeLocation) { + this.storeLocation = storeLocation; + } + + public Customer getCustomer() { + return customer; + } + + public void setCustomer(Customer customer) { + this.customer = customer; + } + + public boolean isCouponUsed() { + return couponUsed; + } + + public void setCouponUsed(boolean couponUsed) { + this.couponUsed = couponUsed; + } + + public String getPurchaseMethod() { + return purchaseMethod; + } + + public void setPurchaseMethod(String purchaseMethod) { + this.purchaseMethod = purchaseMethod; + } +} \ No newline at end of file diff --git a/src/main/java/com/mongodb/quickstart/repositories/SalesCustomRepository.java b/src/main/java/com/mongodb/quickstart/repositories/SalesCustomRepository.java new file mode 100644 index 0000000..0e5a1ec --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/repositories/SalesCustomRepository.java @@ -0,0 +1,21 @@ +package com.mongodb.quickstart.repositories; + +import java.util.List; + +import com.mongodb.quickstart.dtos.AverageItemPricePerStoreDTO; +import com.mongodb.quickstart.dtos.CustomerSatisfactionDTO; +import com.mongodb.quickstart.dtos.DistinctCustomersCountDTO; +import com.mongodb.quickstart.dtos.RevenueByLocationDTO; +import com.mongodb.quickstart.dtos.SalesByDayOfWeekDTO; +import com.mongodb.quickstart.dtos.SalesPerformanceDTO; +import com.mongodb.quickstart.dtos.TotalSalesByLocationDTO; + +public interface SalesCustomRepository { + List calculateTotalSalesByLocation(); + List averageItemPricePerStore(); + List countDistinctCustomersByLocation(); + List totalSalesByDayOfWeek(); + List compareSalesWithAndWithoutCoupons(); + List calculateTotalRevenueByLocation(); + List averageCustomerSatisfactionByLocation(); +} \ No newline at end of file diff --git a/src/main/java/com/mongodb/quickstart/repositories/SalesCustomRepositoryImpl.java b/src/main/java/com/mongodb/quickstart/repositories/SalesCustomRepositoryImpl.java new file mode 100644 index 0000000..3457298 --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/repositories/SalesCustomRepositoryImpl.java @@ -0,0 +1,196 @@ +package com.mongodb.quickstart.repositories; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Primary; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators; +import org.springframework.data.mongodb.core.aggregation.ProjectionOperation; +import org.springframework.data.mongodb.core.aggregation.SortOperation; +import org.springframework.data.mongodb.core.aggregation.UnwindOperation; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.aggregation.GroupOperation; +import org.springframework.data.mongodb.core.aggregation.MatchOperation; +import org.springframework.stereotype.Repository; + +import com.mongodb.quickstart.dtos.AverageItemPricePerStoreDTO; +import com.mongodb.quickstart.dtos.CustomerSatisfactionDTO; +import com.mongodb.quickstart.dtos.DistinctCustomersCountDTO; +import com.mongodb.quickstart.dtos.RevenueByLocationDTO; +import com.mongodb.quickstart.dtos.SalesByDayOfWeekDTO; +import com.mongodb.quickstart.dtos.SalesPerformanceDTO; +import com.mongodb.quickstart.dtos.TotalSalesByLocationDTO; + +import java.util.List; + +@Repository +@Primary +public class SalesCustomRepositoryImpl implements SalesCustomRepository { + + @Autowired + private MongoTemplate mongoTemplate; + + /** + * Question 1: Total Sales by Location + * + * Calculate the total sales grouped by each location and return a result that + * includes the location's name (or ID) and the total sales amount. + */ + @Override + public List calculateTotalSalesByLocation() { + GroupOperation groupStage = Aggregation.group("storeLocation").count().as("totalSales"); + + Aggregation aggregation = Aggregation.newAggregation(groupStage); + + AggregationResults results = mongoTemplate.aggregate(aggregation, "sales", + TotalSalesByLocationDTO.class); + + return results.getMappedResults(); + } + + /** + * Question 2: Customer Satisfaction by Location + * + * Calculate the average customer satisfaction rating for each store location, + * based on a 'customer.satisfaction' field in each sale document. Only accept + * satisfaction ratings thats are greater than or equal to 1. + */ + @Override + public List averageCustomerSatisfactionByLocation() { + MatchOperation matchStage = Aggregation.match(Criteria.where("customer.satisfaction").gte(1)); + + GroupOperation groupStage = Aggregation.group("storeLocation") + .avg("customer.satisfaction").as("averageSatisfaction"); + + ProjectionOperation projectStage = Aggregation.project() + .andExpression("_id").as("storeLocation") + .andExpression("averageSatisfaction").as("averageSatisfaction"); + + Aggregation aggregation = Aggregation.newAggregation(matchStage, groupStage, projectStage); + + AggregationResults results = mongoTemplate.aggregate(aggregation, "sales", + CustomerSatisfactionDTO.class); + + return results.getMappedResults(); + } + + /** + * Question 3: Average Price of Items per Store + * + * Calculate the average price of items sold per store and return a result that + * includes the store's name (or ID) and the average price. + */ + @Override + public List averageItemPricePerStore() { + UnwindOperation unwindStage = Aggregation.unwind("items"); + GroupOperation groupStage = Aggregation.group("storeLocation").avg("items.price").as("averagePrice"); + + Aggregation aggregation = Aggregation.newAggregation(unwindStage, groupStage); + + AggregationResults results = mongoTemplate.aggregate(aggregation, "sales", + AverageItemPricePerStoreDTO.class); + + return results.getMappedResults(); + } + + /** + * Question 4: Number of Distinct Customers + * + * Count the number of distinct customers and return a result that includes the + * identifier for grouping and the count of customers. + */ + @Override + public List countDistinctCustomersByLocation() { + GroupOperation groupStage = Aggregation.group("storeLocation").addToSet("customer").as("uniqueCustomers"); + ProjectionOperation projectStage = Aggregation.project().andExpression("size(uniqueCustomers)").as("count"); + + Aggregation aggregation = Aggregation.newAggregation(groupStage, projectStage); + + AggregationResults results = mongoTemplate.aggregate(aggregation, "sales", + DistinctCustomersCountDTO.class); + + return results.getMappedResults(); + } + + /** + * Question 5: Total Sales by Day + * Calculate the total sales grouped by each day of the week and return a result that includes the day's name (or ID) and the + * total sales amount. + */ + @Override + public List totalSalesByDayOfWeek() { + ProjectionOperation projectStage = Aggregation.project().andExpression("dayOfWeek(saleDate)").as("dayOfWeek"); + GroupOperation groupStage = Aggregation.group("dayOfWeek").count().as("totalSales"); + Aggregation aggregation = Aggregation.newAggregation(projectStage, groupStage); + + AggregationResults results = mongoTemplate.aggregate(aggregation, "sales", + SalesByDayOfWeekDTO.class); + + return results.getMappedResults(); + } + + /** + * Question 6: Sum of Sales Revenue by Location + * + * Calculate the total revenue from sales grouped by each location and return a + * result that includes the location's name (or ID) and the total revenue. Sort these from highest to lowest. + */ + @Override + public List calculateTotalRevenueByLocation() { + UnwindOperation unwindStage = Aggregation.unwind("items"); + + ProjectionOperation projectStage = Aggregation.project("storeLocation") + .and("items.price").as("price") + .and("items.quantity").as("quantity") + .andExpression("items.price * items.quantity").as("total"); + + GroupOperation groupStage = Aggregation.group("storeLocation") + .sum("total").as("totalRevenue"); + + ProjectionOperation finalProjectStage = Aggregation.project("totalRevenue") + .and("_id").as("storeLocation"); + + SortOperation sortStage = Aggregation.sort(Sort.by(Sort.Direction.DESC, "totalRevenue")); + + Aggregation aggregation = Aggregation.newAggregation(unwindStage, projectStage, groupStage, finalProjectStage, sortStage); + + AggregationResults results = mongoTemplate.aggregate(aggregation, "sales", RevenueByLocationDTO.class); + + return results.getMappedResults(); + } + + /** + * Question 7: Sales Performance Before and After Applying Coupons + * + * Compare the number of sales transactions with and without coupons at each + * store location and return a detailed breakdown. + */ + @Override + public List compareSalesWithAndWithoutCoupons() { + GroupOperation groupStage = Aggregation.group("storeLocation") + .sum(ConditionalOperators.Cond.newBuilder() + .when(Criteria.where("couponUsed").is(true)) + .then(1) + .otherwise(0)) + .as("salesWithCoupons") + .sum(ConditionalOperators.Cond.newBuilder() + .when(Criteria.where("couponUsed").is(false)) + .then(1).otherwise(0)) + .as("salesWithoutCoupons"); + + ProjectionOperation projectStage = Aggregation.project() + .andExpression("_id").as("storeLocation") + .andExpression("salesWithCoupons").as("salesWithCoupons") + .andExpression("salesWithoutCoupons").as("salesWithoutCoupons"); + + Aggregation aggregation = Aggregation.newAggregation(groupStage, projectStage); + + AggregationResults results = mongoTemplate.aggregate(aggregation, "sales", + SalesPerformanceDTO.class); + + return results.getMappedResults(); + } + +} diff --git a/src/main/java/com/mongodb/quickstart/repositories/SalesRepository.java b/src/main/java/com/mongodb/quickstart/repositories/SalesRepository.java new file mode 100644 index 0000000..96a963e --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/repositories/SalesRepository.java @@ -0,0 +1,10 @@ +package com.mongodb.quickstart.repositories; + +import org.springframework.data.mongodb.repository.MongoRepository; + +import com.mongodb.quickstart.models.Sale; +import org.springframework.stereotype.Repository; + +@Repository +public interface SalesRepository extends MongoRepository, SalesCustomRepository { +} diff --git a/src/main/java/com/mongodb/quickstart/services/AverageCustomerSatisfactionService.java b/src/main/java/com/mongodb/quickstart/services/AverageCustomerSatisfactionService.java new file mode 100644 index 0000000..97d976c --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/services/AverageCustomerSatisfactionService.java @@ -0,0 +1,21 @@ +package com.mongodb.quickstart.services; + +import com.mongodb.quickstart.dtos.CustomerSatisfactionDTO; +import com.mongodb.quickstart.repositories.SalesCustomRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class AverageCustomerSatisfactionService implements Runnable { + + @Autowired + private SalesCustomRepository salesCustomRepository; + + @Override + public void run() { + List results = salesCustomRepository.averageCustomerSatisfactionByLocation(); + results.forEach(System.out::println); + } +} diff --git a/src/main/java/com/mongodb/quickstart/services/AverageItemPricePerStoreService.java b/src/main/java/com/mongodb/quickstart/services/AverageItemPricePerStoreService.java new file mode 100644 index 0000000..ffc4f45 --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/services/AverageItemPricePerStoreService.java @@ -0,0 +1,21 @@ +package com.mongodb.quickstart.services; + +import com.mongodb.quickstart.dtos.AverageItemPricePerStoreDTO; +import com.mongodb.quickstart.repositories.SalesCustomRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component("averageItemPricePerStore") +public class AverageItemPricePerStoreService implements Runnable { + + @Autowired + private SalesCustomRepository salesCustomRepository; + + @Override + public void run() { + List results = salesCustomRepository.averageItemPricePerStore(); + results.forEach(System.out::println); + } +} diff --git a/src/main/java/com/mongodb/quickstart/services/CountDistinctCustomersService.java b/src/main/java/com/mongodb/quickstart/services/CountDistinctCustomersService.java new file mode 100644 index 0000000..b03ecfc --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/services/CountDistinctCustomersService.java @@ -0,0 +1,21 @@ +package com.mongodb.quickstart.services; + +import com.mongodb.quickstart.dtos.DistinctCustomersCountDTO; +import com.mongodb.quickstart.repositories.SalesCustomRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component("countDistinctCustomers") +public class CountDistinctCustomersService implements Runnable { + + @Autowired + private SalesCustomRepository salesCustomRepository; + + @Override + public void run() { + List results = salesCustomRepository.countDistinctCustomersByLocation(); + results.forEach(System.out::println); + } +} diff --git a/src/main/java/com/mongodb/quickstart/services/RevenueByLocationService.java b/src/main/java/com/mongodb/quickstart/services/RevenueByLocationService.java new file mode 100644 index 0000000..3105949 --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/services/RevenueByLocationService.java @@ -0,0 +1,21 @@ +package com.mongodb.quickstart.services; + +import com.mongodb.quickstart.dtos.RevenueByLocationDTO; +import com.mongodb.quickstart.repositories.SalesCustomRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component("revenueByLocation") +public class RevenueByLocationService implements Runnable { + + @Autowired + private SalesCustomRepository salesCustomRepository; + + @Override + public void run() { + List results = salesCustomRepository.calculateTotalRevenueByLocation(); + results.forEach(System.out::println); + } +} diff --git a/src/main/java/com/mongodb/quickstart/services/SalesPerformanceService.java b/src/main/java/com/mongodb/quickstart/services/SalesPerformanceService.java new file mode 100644 index 0000000..1c1f400 --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/services/SalesPerformanceService.java @@ -0,0 +1,21 @@ +package com.mongodb.quickstart.services; + +import com.mongodb.quickstart.dtos.SalesPerformanceDTO; +import com.mongodb.quickstart.repositories.SalesCustomRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component("salesPerformance") +public class SalesPerformanceService implements Runnable { + + @Autowired + private SalesCustomRepository salesCustomRepository; + + @Override + public void run() { + List results = salesCustomRepository.compareSalesWithAndWithoutCoupons(); + results.forEach(System.out::println); + } +} diff --git a/src/main/java/com/mongodb/quickstart/services/TotalSalesByDayOfWeekService.java b/src/main/java/com/mongodb/quickstart/services/TotalSalesByDayOfWeekService.java new file mode 100644 index 0000000..a0a1a97 --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/services/TotalSalesByDayOfWeekService.java @@ -0,0 +1,21 @@ +package com.mongodb.quickstart.services; + +import com.mongodb.quickstart.dtos.SalesByDayOfWeekDTO; +import com.mongodb.quickstart.repositories.SalesCustomRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component("totalSalesByDayOfWeek") +public class TotalSalesByDayOfWeekService implements Runnable { + + @Autowired + private SalesCustomRepository salesCustomRepository; + + @Override + public void run() { + List results = salesCustomRepository.totalSalesByDayOfWeek(); + results.forEach(System.out::println); + } +} diff --git a/src/main/java/com/mongodb/quickstart/services/TotalSalesByLocationService.java b/src/main/java/com/mongodb/quickstart/services/TotalSalesByLocationService.java new file mode 100644 index 0000000..feef6f0 --- /dev/null +++ b/src/main/java/com/mongodb/quickstart/services/TotalSalesByLocationService.java @@ -0,0 +1,20 @@ +package com.mongodb.quickstart.services; + +import com.mongodb.quickstart.dtos.TotalSalesByLocationDTO; +import com.mongodb.quickstart.repositories.SalesCustomRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component("totalSalesByLocation") +public class TotalSalesByLocationService { + + @Autowired + private SalesCustomRepository salesCustomRepository; + + public void run() { + List results = salesCustomRepository.calculateTotalSalesByLocation(); + results.forEach(result -> System.out.println(result)); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9578319..0bc7e64 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,3 @@ # src/main/resources/application.properties -spring.data.mongodb.uri=mongodb+srv://:@>t/?retryWrites=true&w=majority -spring.data.mongodb.database=sample_training +spring.data.mongodb.uri=mongodb+srv://:@/?retryWrites=true&w=majority +spring.data.mongodb.database=sample_supplies