From ea97dafa4471c01a700fa4a0e7cb70a5c23907a7 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Thu, 6 Jan 2022 14:19:45 +0300 Subject: [PATCH] Added order repository tests and specifications --- ...ringServiceApplicationAutoMapperProfile.cs | 1 - .../Orders/IOrderRepository.cs | 9 + .../Orders/Order.cs | 2 - .../Orders/OrderManager.cs | 1 - .../Orders/PaymentType.cs | 2 +- .../Specifications/Last30DaysSpecification.cs | 14 + .../Specifications/MonthsAgoSpecification.cs | 21 + .../Specifications/SpecificationFactory.cs | 23 + .../Specifications/YearSpecification.cs | 20 + .../OrderServiceDataSeedContributor.cs | 1 - .../Orders/EfCoreOrderRepository.cs | 16 + .../Orders/OrderEfCoreQueryableExtensions.cs | 22 + .../Orders/OrderManager_Tests.cs | 1 - .../Orders/OrderRepository_Tests.cs | 39 + .../OrderingServiceDataSeedContributor.cs | 52 +- .../Product.cs | 31 + .../TestData.cs | 20 + .../TestProducts.cs | 42 + .../products.json | 902 ++++++++++++++++++ 19 files changed, 1196 insertions(+), 23 deletions(-) create mode 100644 services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Specifications/Last30DaysSpecification.cs create mode 100644 services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Specifications/MonthsAgoSpecification.cs create mode 100644 services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Specifications/SpecificationFactory.cs create mode 100644 services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Specifications/YearSpecification.cs create mode 100644 services/ordering/src/EShopOnAbp.OrderingService.EntityFrameworkCore/Orders/OrderEfCoreQueryableExtensions.cs create mode 100644 services/ordering/test/EShopOnAbp.OrderingService.EntityFrameworkCore.Tests/EntityFrameworkCore/Orders/OrderRepository_Tests.cs create mode 100644 services/ordering/test/EShopOnAbp.OrderingService.TestBase/Product.cs create mode 100644 services/ordering/test/EShopOnAbp.OrderingService.TestBase/TestData.cs create mode 100644 services/ordering/test/EShopOnAbp.OrderingService.TestBase/TestProducts.cs create mode 100644 services/ordering/test/EShopOnAbp.OrderingService.TestBase/products.json diff --git a/services/ordering/src/EShopOnAbp.OrderingService.Application/OrderingServiceApplicationAutoMapperProfile.cs b/services/ordering/src/EShopOnAbp.OrderingService.Application/OrderingServiceApplicationAutoMapperProfile.cs index f5f73fab..2153beb2 100644 --- a/services/ordering/src/EShopOnAbp.OrderingService.Application/OrderingServiceApplicationAutoMapperProfile.cs +++ b/services/ordering/src/EShopOnAbp.OrderingService.Application/OrderingServiceApplicationAutoMapperProfile.cs @@ -1,5 +1,4 @@ using AutoMapper; -using EShopOnAbp.OrderingService.Buyers; using EShopOnAbp.OrderingService.Orders; using Volo.Abp.AutoMapper; diff --git a/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/IOrderRepository.cs b/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/IOrderRepository.cs index 65c20d35..5851abf6 100644 --- a/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/IOrderRepository.cs +++ b/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/IOrderRepository.cs @@ -1,8 +1,17 @@ using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; +using Volo.Abp.Specifications; namespace EShopOnAbp.OrderingService.Orders; public interface IOrderRepository : IRepository { + Task> GetOrdersByUserId( + Guid userId, + ISpecification spec, + bool includeDetails = false, + CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Order.cs b/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Order.cs index 28e56344..2435c185 100644 --- a/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Order.cs +++ b/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Order.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using EShopOnAbp.OrderingService.Buyers; using Volo.Abp.Domain.Entities; namespace EShopOnAbp.OrderingService.Orders; @@ -11,7 +10,6 @@ public class Order : AggregateRoot private int _orderStatusId; private int _paymentTypeId; public DateTime OrderDate { get; private set; } - public PaymentType PaymentType { get; private set; } public Guid? PaymentRequestId { get; private set; } public string PaymentStatus { get; private set; } diff --git a/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/OrderManager.cs b/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/OrderManager.cs index fac49843..406bdc40 100644 --- a/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/OrderManager.cs +++ b/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/OrderManager.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using EShopOnAbp.OrderingService.Buyers; using Volo.Abp; using Volo.Abp.Domain.Services; using Volo.Abp.EventBus.Distributed; diff --git a/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/PaymentType.cs b/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/PaymentType.cs index 5ee66b7b..2e556acd 100644 --- a/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/PaymentType.cs +++ b/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/PaymentType.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using Volo.Abp; -namespace EShopOnAbp.OrderingService.Buyers; +namespace EShopOnAbp.OrderingService.Orders; public class PaymentType : Enumeration { diff --git a/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Specifications/Last30DaysSpecification.cs b/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Specifications/Last30DaysSpecification.cs new file mode 100644 index 00000000..6c90e62a --- /dev/null +++ b/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Specifications/Last30DaysSpecification.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq.Expressions; +using Volo.Abp.Specifications; + +namespace EShopOnAbp.OrderingService.Orders.Specifications; + +public class Last30DaysSpecification : Specification +{ + public override Expression> ToExpression() + { + var daysAgo30 = DateTime.Now.AddDays(-30); + return query => query.OrderDate >= daysAgo30 && query.OrderDate <= DateTime.Now; + } +} \ No newline at end of file diff --git a/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Specifications/MonthsAgoSpecification.cs b/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Specifications/MonthsAgoSpecification.cs new file mode 100644 index 00000000..fc085fcd --- /dev/null +++ b/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Specifications/MonthsAgoSpecification.cs @@ -0,0 +1,21 @@ +using System; +using System.Linq.Expressions; +using Volo.Abp.Specifications; + +namespace EShopOnAbp.OrderingService.Orders.Specifications; + +public class MonthsAgoSpecification : Specification +{ + protected int NumberOfMonths { get; set; } + + public MonthsAgoSpecification(int months) + { + NumberOfMonths = months; + } + + public override Expression> ToExpression() + { + var monthsAgo = DateTime.Now.AddMonths(NumberOfMonths); + return query => query.OrderDate >= monthsAgo && query.OrderDate <= DateTime.Now; + } +} \ No newline at end of file diff --git a/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Specifications/SpecificationFactory.cs b/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Specifications/SpecificationFactory.cs new file mode 100644 index 00000000..68f927f6 --- /dev/null +++ b/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Specifications/SpecificationFactory.cs @@ -0,0 +1,23 @@ +using Volo.Abp.Specifications; + +namespace EShopOnAbp.OrderingService.Orders.Specifications; + +public static class SpecificationFactory +{ + public static ISpecification Create(string filter) + { + if (filter.StartsWith("y")) + { + var year = int.Parse(filter.Split('y')[1]); + return new YearSpecification(year); + } + + if (filter.StartsWith("m")) + { + var months = int.Parse(filter.Split('m')[1]); + return new MonthsAgoSpecification(months); + } + + return new Last30DaysSpecification(); + } +} \ No newline at end of file diff --git a/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Specifications/YearSpecification.cs b/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Specifications/YearSpecification.cs new file mode 100644 index 00000000..44c0d1ae --- /dev/null +++ b/services/ordering/src/EShopOnAbp.OrderingService.Domain/Orders/Specifications/YearSpecification.cs @@ -0,0 +1,20 @@ +using System; +using System.Linq.Expressions; +using Volo.Abp.Specifications; + +namespace EShopOnAbp.OrderingService.Orders.Specifications; + +public class YearSpecification : Specification +{ + protected int Year { get; set; } + + public YearSpecification(int year) + { + Year = year; + } + + public override Expression> ToExpression() + { + return query => query.OrderDate.Year == Year; + } +} \ No newline at end of file diff --git a/services/ordering/src/EShopOnAbp.OrderingService.EntityFrameworkCore/EntityFrameworkCore/OrderServiceDataSeedContributor.cs b/services/ordering/src/EShopOnAbp.OrderingService.EntityFrameworkCore/EntityFrameworkCore/OrderServiceDataSeedContributor.cs index 468aca95..b1d2355a 100644 --- a/services/ordering/src/EShopOnAbp.OrderingService.EntityFrameworkCore/EntityFrameworkCore/OrderServiceDataSeedContributor.cs +++ b/services/ordering/src/EShopOnAbp.OrderingService.EntityFrameworkCore/EntityFrameworkCore/OrderServiceDataSeedContributor.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using EShopOnAbp.OrderingService.Buyers; using EShopOnAbp.OrderingService.Orders; using Microsoft.EntityFrameworkCore; using Volo.Abp.Data; diff --git a/services/ordering/src/EShopOnAbp.OrderingService.EntityFrameworkCore/Orders/EfCoreOrderRepository.cs b/services/ordering/src/EShopOnAbp.OrderingService.EntityFrameworkCore/Orders/EfCoreOrderRepository.cs index 8d13116d..bc7cb8c3 100644 --- a/services/ordering/src/EShopOnAbp.OrderingService.EntityFrameworkCore/Orders/EfCoreOrderRepository.cs +++ b/services/ordering/src/EShopOnAbp.OrderingService.EntityFrameworkCore/Orders/EfCoreOrderRepository.cs @@ -1,11 +1,14 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Linq.Dynamic.Core; using System.Threading; using System.Threading.Tasks; using EShopOnAbp.OrderingService.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.Specifications; namespace EShopOnAbp.OrderingService.Orders; @@ -25,6 +28,19 @@ public class EfCoreOrderRepository : EfCoreRepository> GetOrdersByUserId( + Guid userId, + ISpecification spec, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .IncludeDetails(includeDetails) + .Where(q=>q.Buyer.Id == userId) + .Where(spec.ToExpression()) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + public override async Task> WithDetailsAsync() { return (await GetQueryableAsync()) diff --git a/services/ordering/src/EShopOnAbp.OrderingService.EntityFrameworkCore/Orders/OrderEfCoreQueryableExtensions.cs b/services/ordering/src/EShopOnAbp.OrderingService.EntityFrameworkCore/Orders/OrderEfCoreQueryableExtensions.cs new file mode 100644 index 00000000..7b194839 --- /dev/null +++ b/services/ordering/src/EShopOnAbp.OrderingService.EntityFrameworkCore/Orders/OrderEfCoreQueryableExtensions.cs @@ -0,0 +1,22 @@ +using System.Linq; +using Microsoft.EntityFrameworkCore; + +namespace EShopOnAbp.OrderingService.Orders; + +public static class OrderEfCoreQueryableExtensions +{ + public static IQueryable IncludeDetails(this IQueryable queryable, bool include = true) + { + if (!include) + { + return queryable; + } + + return queryable + .Include(x => x.Address) + .Include(x => x.Buyer) + .Include(x => x.OrderItems) + .Include(x => x.PaymentType) + .Include(x => x.OrderStatus); + } +} \ No newline at end of file diff --git a/services/ordering/test/EShopOnAbp.OrderingService.Domain.Tests/Orders/OrderManager_Tests.cs b/services/ordering/test/EShopOnAbp.OrderingService.Domain.Tests/Orders/OrderManager_Tests.cs index 78708aae..7a37be0e 100644 --- a/services/ordering/test/EShopOnAbp.OrderingService.Domain.Tests/Orders/OrderManager_Tests.cs +++ b/services/ordering/test/EShopOnAbp.OrderingService.Domain.Tests/Orders/OrderManager_Tests.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using EShopOnAbp.OrderingService.Buyers; using Shouldly; using Xunit; diff --git a/services/ordering/test/EShopOnAbp.OrderingService.EntityFrameworkCore.Tests/EntityFrameworkCore/Orders/OrderRepository_Tests.cs b/services/ordering/test/EShopOnAbp.OrderingService.EntityFrameworkCore.Tests/EntityFrameworkCore/Orders/OrderRepository_Tests.cs new file mode 100644 index 00000000..77b9375f --- /dev/null +++ b/services/ordering/test/EShopOnAbp.OrderingService.EntityFrameworkCore.Tests/EntityFrameworkCore/Orders/OrderRepository_Tests.cs @@ -0,0 +1,39 @@ +using System.Threading.Tasks; +using EShopOnAbp.OrderingService.Orders; +using EShopOnAbp.OrderingService.Orders.Specifications; +using EShopOnAbp.OrderingService.Samples; +using Microsoft.EntityFrameworkCore; +using Shouldly; +using Xunit; + +namespace EShopOnAbp.OrderingService.EntityFrameworkCore.Orders; + +public class OrderRepository_Tests : SampleRepository_Tests +{ + private readonly TestData _testData; + private readonly IOrderRepository _orderRepository; + private readonly IOrderingServiceDbContext _dbContext; + + public OrderRepository_Tests() + { + _dbContext = GetRequiredService(); + _orderRepository = GetRequiredService(); + _testData = GetRequiredService(); + } + + [Fact] + public async Task Should_Get_OrderStatus() + { + var dbSet = _dbContext.Set(); + var statusList = await dbSet.ToListAsync(); + statusList.Count.ShouldNotBe(0); + } + + [Fact] + public async Task Should_Get_User_Orders() + { + var orders = + await _orderRepository.GetOrdersByUserId(_testData.CurrentUserId, new Last30DaysSpecification(), true); + orders.Count.ShouldBe(2); + } +} \ No newline at end of file diff --git a/services/ordering/test/EShopOnAbp.OrderingService.TestBase/OrderingServiceDataSeedContributor.cs b/services/ordering/test/EShopOnAbp.OrderingService.TestBase/OrderingServiceDataSeedContributor.cs index c606e6fa..b566a3ea 100644 --- a/services/ordering/test/EShopOnAbp.OrderingService.TestBase/OrderingServiceDataSeedContributor.cs +++ b/services/ordering/test/EShopOnAbp.OrderingService.TestBase/OrderingServiceDataSeedContributor.cs @@ -1,33 +1,53 @@ -using System.Threading.Tasks; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using EShopOnAbp.OrderingService.Orders; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.Guids; -using Volo.Abp.MultiTenancy; namespace EShopOnAbp.OrderingService { public class OrderingServiceDataSeedContributor : IDataSeedContributor, ITransientDependency { - private readonly IGuidGenerator _guidGenerator; - private readonly ICurrentTenant _currentTenant; + private readonly OrderManager _orderManager; + private readonly TestData _testData; + private readonly TestProducts _testProducts; public OrderingServiceDataSeedContributor( - IGuidGenerator guidGenerator, ICurrentTenant currentTenant) + OrderManager orderManager, + TestData testData, + TestProducts testProducts) { - _guidGenerator = guidGenerator; - _currentTenant = currentTenant; + _orderManager = orderManager; + _testData = testData; + _testProducts = testProducts; } - public Task SeedAsync(DataSeedContext context) + public async Task SeedAsync(DataSeedContext context) { - /* Instead of returning the Task.CompletedTask, you can insert your test data - * at this point! - */ + await SeedTestOrdersAsync(); + } + + private async Task SeedTestOrdersAsync() + { + var order1 = await _orderManager.CreateOrderAsync( + 1, _testData.CurrentUserId, _testData.CurrentUserName, _testData.CurrentUserEmail, + _testProducts.GetRandomProducts(0), + _testData.Address.Street, + _testData.Address.City, + _testData.Address.Country, + _testData.Address.ZipCode + ); - using (_currentTenant.Change(context?.TenantId)) - { - return Task.CompletedTask; - } + var order2 = await _orderManager.CreateOrderAsync( + 1, _testData.CurrentUserId, _testData.CurrentUserName, _testData.CurrentUserEmail, + _testProducts.GetRandomProducts(10), + _testData.Address.Street, + _testData.Address.City, + _testData.Address.Country, + _testData.Address.ZipCode + ); } } -} +} \ No newline at end of file diff --git a/services/ordering/test/EShopOnAbp.OrderingService.TestBase/Product.cs b/services/ordering/test/EShopOnAbp.OrderingService.TestBase/Product.cs new file mode 100644 index 00000000..39a4d6ee --- /dev/null +++ b/services/ordering/test/EShopOnAbp.OrderingService.TestBase/Product.cs @@ -0,0 +1,31 @@ +using System; +using System.Text.Json.Serialization; + +namespace EShopOnAbp.OrderingService; + +public class Product +{ + [JsonPropertyName("id")] public int Id { get; set; } + + public Guid ProductId + { + get + { + byte[] bytes = new byte[16]; + BitConverter.GetBytes(Id).CopyTo(bytes, 0); + return new Guid(bytes); + } + } + + [JsonPropertyName("productName")] public string ProductName { get; set; } + + [JsonPropertyName("pictureUrl")] public string PictureUrl { get; set; } + + [JsonPropertyName("productStock")] public int ProductStock { get; set; } + + [JsonPropertyName("unitPrice")] public string UnitPrice { get; set; } + + [JsonPropertyName("productSalePrice")] public string ProductSalePrice { get; set; } + + [JsonPropertyName("units")] public int Units { get; set; } +} \ No newline at end of file diff --git a/services/ordering/test/EShopOnAbp.OrderingService.TestBase/TestData.cs b/services/ordering/test/EShopOnAbp.OrderingService.TestBase/TestData.cs new file mode 100644 index 00000000..fd9a164b --- /dev/null +++ b/services/ordering/test/EShopOnAbp.OrderingService.TestBase/TestData.cs @@ -0,0 +1,20 @@ +using System; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.OrderingService; + +public class TestData : ISingletonDependency +{ + public string CurrentUserEmail { get; set; } = "galip.erdem@volosoft.com"; + public Guid CurrentUserId { get; set; } = Guid.NewGuid(); + public string CurrentUserName { get; set; } = "Galip T. ERDEM"; + public DemoAddress Address { get; set; } = new DemoAddress(); + + public class DemoAddress + { + public string Street { get; set; } = "Test Demo Street"; + public string City { get; set; } = "DemoCity"; + public string Country { get; set; } = "DemoLand"; + public string ZipCode { get; set; } = "123-456-789"; + } +} \ No newline at end of file diff --git a/services/ordering/test/EShopOnAbp.OrderingService.TestBase/TestProducts.cs b/services/ordering/test/EShopOnAbp.OrderingService.TestBase/TestProducts.cs new file mode 100644 index 00000000..357c1f08 --- /dev/null +++ b/services/ordering/test/EShopOnAbp.OrderingService.TestBase/TestProducts.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Newtonsoft.Json; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.OrderingService; + +public class TestProducts : ISingletonDependency +{ + private readonly List _products; + private readonly Random _random = new(); + + public TestProducts() + { + _products = new List(); + using (StreamReader r = new StreamReader("../../../../EShopOnAbp.OrderingService.TestBase/products.json")) + { + string json = r.ReadToEnd(); + _products = JsonConvert.DeserializeObject>(json); + } + } + + public List<(Guid productId, string productName, string productCode, decimal unitPrice, decimal discount, + string pictureUrl, int units)> GetRandomProducts(int quantity) + { + List<(Guid productId, string productName, string productCode, decimal unitPrice, decimal discount, + string pictureUrl, int units)> randomProducts = + new List<(Guid productId, string productName, string productCode, decimal unitPrice, decimal discount, + string pictureUrl, int units)>(); + for (int i = 0; i < quantity; i++) + { + int num = _random.Next(_products.Count); + var product = _products[num]; + + randomProducts.Add((Guid.NewGuid(), product.ProductName, $"Code-{num}", decimal.Parse(product.UnitPrice), + 0, product.PictureUrl, product.Units)); + } + + return randomProducts; + } +} \ No newline at end of file diff --git a/services/ordering/test/EShopOnAbp.OrderingService.TestBase/products.json b/services/ordering/test/EShopOnAbp.OrderingService.TestBase/products.json new file mode 100644 index 00000000..2d52a8b7 --- /dev/null +++ b/services/ordering/test/EShopOnAbp.OrderingService.TestBase/products.json @@ -0,0 +1,902 @@ +[ + { + "id": 1000, + "productName": "delightful chocolate Ilise", + "pictureUrl": "https://picsum.photos/400?image=659", + "productStock": 81, + "unitPrice": "23488.67", + "productSalePrice": "23488.67", + "units": 4 + }, + { + "id": 1001, + "productName": "civilian salmon Delinda", + "pictureUrl": "https://picsum.photos/400?image=261", + "productStock": 66, + "unitPrice": "42992.62", + "productSalePrice": "20636.46", + "units": 2 + }, + { + "id": 1002, + "productName": "northern chocolate Dagmar", + "pictureUrl": "https://picsum.photos/400?image=921", + "productStock": 58, + "unitPrice": "26956.67", + "productSalePrice": "26956.67", + "units": 4 + }, + { + "id": 1003, + "productName": "spatial plum Mattie", + "pictureUrl": "https://picsum.photos/400?image=753", + "productStock": 74, + "unitPrice": "32827.45", + "productSalePrice": "32827.45", + "units": 2 + }, + { + "id": 1004, + "productName": "arrogant tan Nelia", + "pictureUrl": "https://picsum.photos/400?image=924", + "productStock": 85, + "unitPrice": "27379.36", + "productSalePrice": "9308.98", + "units": 4 + }, + { + "id": 1005, + "productName": "following bronze Misti", + "pictureUrl": "https://picsum.photos/400?image=340", + "productStock": 24, + "unitPrice": "34617.96", + "productSalePrice": "34617.96", + "units": 3 + }, + { + "id": 1006, + "productName": "continued rose Nesta", + "pictureUrl": "https://picsum.photos/400?image=712", + "productStock": 34, + "unitPrice": "27822.85", + "productSalePrice": "27822.85", + "units": 1 + }, + { + "id": 1007, + "productName": "sufficient peach Georgiana", + "pictureUrl": "https://picsum.photos/400?image=860", + "productStock": 55, + "unitPrice": "15506.06", + "productSalePrice": "15506.06", + "units": 4 + }, + { + "id": 1008, + "productName": "frequent sapphire Petronella", + "pictureUrl": "https://picsum.photos/400?image=913", + "productStock": 36, + "unitPrice": "44132.59", + "productSalePrice": "44132.59", + "units": 5 + }, + { + "id": 1009, + "productName": "indirect indigo Fiona", + "pictureUrl": "https://picsum.photos/400?image=583", + "productStock": 49, + "unitPrice": "40702.21", + "productSalePrice": "4477.24", + "units": 1 + }, + { + "id": 1010, + "productName": "gross plum Veriee", + "pictureUrl": "https://picsum.photos/400?image=306", + "productStock": 12, + "unitPrice": "42194.21", + "productSalePrice": "42194.21", + "units": 4 + }, + { + "id": 1011, + "productName": "gigantic red Lissy", + "pictureUrl": "https://picsum.photos/400?image=184", + "productStock": 45, + "unitPrice": "18745.19", + "productSalePrice": "8810.24", + "units": 2 + }, + { + "id": 1012, + "productName": "blind copper Dionne", + "pictureUrl": "https://picsum.photos/400?image=308", + "productStock": 27, + "unitPrice": "2038.28", + "productSalePrice": "448.42", + "units": 3 + }, + { + "id": 1013, + "productName": "varied indigo Lizzie", + "pictureUrl": "https://picsum.photos/400?image=453", + "productStock": 97, + "unitPrice": "28245.22", + "productSalePrice": "9603.37", + "units": 4 + }, + { + "id": 1014, + "productName": "single amaranth Doris", + "pictureUrl": "https://picsum.photos/400?image=325", + "productStock": 21, + "unitPrice": "4200.08", + "productSalePrice": "4200.08", + "units": 2 + }, + { + "id": 1015, + "productName": "keen maroon Lenette", + "pictureUrl": "https://picsum.photos/400?image=92", + "productStock": 88, + "unitPrice": "2282.88", + "productSalePrice": "2282.88", + "units": 3 + }, + { + "id": 1016, + "productName": "conservation peach Nari", + "pictureUrl": "https://picsum.photos/400?image=798", + "productStock": 31, + "unitPrice": "45707.28", + "productSalePrice": "45707.28", + "units": 5 + }, + { + "id": 1017, + "productName": "loyal blush Happy", + "pictureUrl": "https://picsum.photos/400?image=163", + "productStock": 58, + "unitPrice": "40390.62", + "productSalePrice": "16560.15", + "units": 5 + }, + { + "id": 1018, + "productName": "awkward jade Wini", + "pictureUrl": "https://picsum.photos/400?image=475", + "productStock": 46, + "unitPrice": "4221.73", + "productSalePrice": "4221.73", + "units": 5 + }, + { + "id": 1019, + "productName": "uncertain cyan Tawsha", + "pictureUrl": "https://picsum.photos/400?image=18", + "productStock": 24, + "unitPrice": "6790.39", + "productSalePrice": "6790.39", + "units": 2 + }, + { + "id": 1020, + "productName": "partial harlequin Katlin", + "pictureUrl": "https://picsum.photos/400?image=466", + "productStock": 15, + "unitPrice": "9001.19", + "productSalePrice": "3510.47", + "units": 2 + }, + { + "id": 1021, + "productName": "redundant emerald Allyson", + "pictureUrl": "https://picsum.photos/400?image=110", + "productStock": 59, + "unitPrice": "25272.78", + "productSalePrice": "3032.73", + "units": 4 + }, + { + "id": 1022, + "productName": "far beige Megan", + "pictureUrl": "https://picsum.photos/400?image=264", + "productStock": 95, + "unitPrice": "33122.53", + "productSalePrice": "33122.53", + "units": 5 + }, + { + "id": 1023, + "productName": "select tomato Berty", + "pictureUrl": "https://picsum.photos/400?image=316", + "productStock": 57, + "unitPrice": "6265.37", + "productSalePrice": "2819.42", + "units": 5 + }, + { + "id": 1024, + "productName": "recent harlequin Kally", + "pictureUrl": "https://picsum.photos/400?image=896", + "productStock": 12, + "unitPrice": "30807.26", + "productSalePrice": "9550.25", + "units": 2 + }, + { + "id": 1025, + "productName": "limited emerald Alethea", + "pictureUrl": "https://picsum.photos/400?image=671", + "productStock": 85, + "unitPrice": "5782.65", + "productSalePrice": "2370.89", + "units": 2 + }, + { + "id": 1026, + "productName": "initial pink Riane", + "pictureUrl": "https://picsum.photos/400?image=917", + "productStock": 70, + "unitPrice": "34095.19", + "productSalePrice": "34095.19", + "units": 5 + }, + { + "id": 1027, + "productName": "long yellow Dorey", + "pictureUrl": "https://picsum.photos/400?image=419", + "productStock": 77, + "unitPrice": "24712.30", + "productSalePrice": "24712.30", + "units": 3 + }, + { + "id": 1028, + "productName": "acute brown Phaidra", + "pictureUrl": "https://picsum.photos/400?image=889", + "productStock": 15, + "unitPrice": "6488.51", + "productSalePrice": "6488.51", + "units": 4 + }, + { + "id": 1029, + "productName": "accepted orange Harriett", + "pictureUrl": "https://picsum.photos/400?image=719", + "productStock": 41, + "unitPrice": "19609.46", + "productSalePrice": "19609.46", + "units": 2 + }, + { + "id": 1030, + "productName": "creative amethyst Kali", + "pictureUrl": "https://picsum.photos/400?image=888", + "productStock": 22, + "unitPrice": "26368.88", + "productSalePrice": "12393.37", + "units": 2 + }, + { + "id": 1031, + "productName": "crooked indigo Karen", + "pictureUrl": "https://picsum.photos/400?image=443", + "productStock": 60, + "unitPrice": "49558.08", + "productSalePrice": "8424.87", + "units": 1 + }, + { + "id": 1032, + "productName": "operational orange Myrtia", + "pictureUrl": "https://picsum.photos/400?image=649", + "productStock": 61, + "unitPrice": "14925.93", + "productSalePrice": "2238.89", + "units": 3 + }, + { + "id": 1033, + "productName": "disturbing aqua Natalya", + "pictureUrl": "https://picsum.photos/400?image=554", + "productStock": 78, + "unitPrice": "27159.30", + "productSalePrice": "11950.09", + "units": 5 + }, + { + "id": 1034, + "productName": "forward green Alis", + "pictureUrl": "https://picsum.photos/400?image=746", + "productStock": 23, + "unitPrice": "27608.78", + "productSalePrice": "27608.78", + "units": 5 + }, + { + "id": 1035, + "productName": "complicated maroon Esmaria", + "pictureUrl": "https://picsum.photos/400?image=648", + "productStock": 69, + "unitPrice": "3128.23", + "productSalePrice": "938.47", + "units": 4 + }, + { + "id": 1036, + "productName": "frequent chocolate Michell", + "pictureUrl": "https://picsum.photos/400?image=439", + "productStock": 64, + "unitPrice": "39521.86", + "productSalePrice": "39521.86", + "units": 4 + }, + { + "id": 1037, + "productName": "inquisitive cyan Janette", + "pictureUrl": "https://picsum.photos/400?image=857", + "productStock": 35, + "unitPrice": "9689.55", + "productSalePrice": "9689.55", + "units": 4 + }, + { + "id": 1038, + "productName": "regional tomato Melamie", + "pictureUrl": "https://picsum.photos/400?image=48", + "productStock": 20, + "unitPrice": "5948.06", + "productSalePrice": "1605.98", + "units": 4 + }, + { + "id": 1039, + "productName": "notable maroon Anthe", + "pictureUrl": "https://picsum.photos/400?image=802", + "productStock": 34, + "unitPrice": "49448.68", + "productSalePrice": "49448.68", + "units": 3 + }, + { + "id": 1040, + "productName": "unfortunate maroon Aileen", + "pictureUrl": "https://picsum.photos/400?image=38", + "productStock": 65, + "unitPrice": "24131.28", + "productSalePrice": "24131.28", + "units": 1 + }, + { + "id": 1041, + "productName": "illegal black Karylin", + "pictureUrl": "https://picsum.photos/400?image=859", + "productStock": 57, + "unitPrice": "36521.61", + "productSalePrice": "16799.94", + "units": 5 + }, + { + "id": 1042, + "productName": "diverse crimson Chiquia", + "pictureUrl": "https://picsum.photos/400?image=928", + "productStock": 63, + "unitPrice": "30920.27", + "productSalePrice": "30920.27", + "units": 4 + }, + { + "id": 1043, + "productName": "coming magenta Siana", + "pictureUrl": "https://picsum.photos/400?image=54", + "productStock": 66, + "unitPrice": "41255.15", + "productSalePrice": "41255.15", + "units": 3 + }, + { + "id": 1044, + "productName": "psychological maroon Cesya", + "pictureUrl": "https://picsum.photos/400?image=596", + "productStock": 25, + "unitPrice": "20085.00", + "productSalePrice": "7230.60", + "units": 2 + }, + { + "id": 1045, + "productName": "stingy moccasin Conny", + "pictureUrl": "https://picsum.photos/400?image=399", + "productStock": 95, + "unitPrice": "30494.11", + "productSalePrice": "30494.11", + "units": 4 + }, + { + "id": 1046, + "productName": "voiceless emerald Sarita", + "pictureUrl": "https://picsum.photos/400?image=695", + "productStock": 48, + "unitPrice": "21625.98", + "productSalePrice": "8001.61", + "units": 3 + }, + { + "id": 1047, + "productName": "old lime Elvera", + "pictureUrl": "https://picsum.photos/400?image=761", + "productStock": 4, + "unitPrice": "30869.36", + "productSalePrice": "30869.36", + "units": 5 + }, + { + "id": 1048, + "productName": "grumpy plum Jessica", + "pictureUrl": "https://picsum.photos/400?image=570", + "productStock": 73, + "unitPrice": "35395.72", + "productSalePrice": "11326.63", + "units": 3 + }, + { + "id": 1049, + "productName": "unknown beige Maddalena", + "pictureUrl": "https://picsum.photos/400?image=784", + "productStock": 11, + "unitPrice": "24573.75", + "productSalePrice": "2457.37", + "units": 4 + }, + { + "id": 1050, + "productName": "clever rose Karoly", + "pictureUrl": "https://picsum.photos/400?image=500", + "productStock": 81, + "unitPrice": "12289.15", + "productSalePrice": "12289.15", + "units": 4 + }, + { + "id": 1051, + "productName": "unfortunate tan Maris", + "pictureUrl": "https://picsum.photos/400?image=622", + "productStock": 63, + "unitPrice": "39042.14", + "productSalePrice": "11322.22", + "units": 2 + }, + { + "id": 1052, + "productName": "statutory emerald Arlyn", + "pictureUrl": "https://picsum.photos/400?image=472", + "productStock": 50, + "unitPrice": "35325.05", + "productSalePrice": "35325.05", + "units": 1 + }, + { + "id": 1053, + "productName": "dynamic gold Gratiana", + "pictureUrl": "https://picsum.photos/400?image=71", + "productStock": 59, + "unitPrice": "21332.58", + "productSalePrice": "21332.58", + "units": 5 + }, + { + "id": 1054, + "productName": "spotty cyan Micky", + "pictureUrl": "https://picsum.photos/400?image=471", + "productStock": 78, + "unitPrice": "49952.94", + "productSalePrice": "10490.12", + "units": 2 + }, + { + "id": 1055, + "productName": "creative peach Loria", + "pictureUrl": "https://picsum.photos/400?image=619", + "productStock": 6, + "unitPrice": "20547.31", + "productSalePrice": "8424.40", + "units": 2 + }, + { + "id": 1056, + "productName": "dutch peach Em", + "pictureUrl": "https://picsum.photos/400?image=236", + "productStock": 98, + "unitPrice": "42634.53", + "productSalePrice": "19611.88", + "units": 3 + }, + { + "id": 1057, + "productName": "civilian fuchsia Jackelyn", + "pictureUrl": "https://picsum.photos/400?image=409", + "productStock": 62, + "unitPrice": "21268.92", + "productSalePrice": "21268.92", + "units": 1 + }, + { + "id": 1058, + "productName": "elderly turquoise Storm", + "pictureUrl": "https://picsum.photos/400?image=270", + "productStock": 68, + "unitPrice": "45826.03", + "productSalePrice": "9623.47", + "units": 4 + }, + { + "id": 1059, + "productName": "charming jade Emelia", + "pictureUrl": "https://picsum.photos/400?image=696", + "productStock": 36, + "unitPrice": "23535.56", + "productSalePrice": "23535.56", + "units": 4 + }, + { + "id": 1060, + "productName": "uninterested violet Lind", + "pictureUrl": "https://picsum.photos/400?image=732", + "productStock": 51, + "unitPrice": "18257.20", + "productSalePrice": "18257.20", + "units": 2 + }, + { + "id": 1061, + "productName": "assistant gray Regine", + "pictureUrl": "https://picsum.photos/400?image=129", + "productStock": 6, + "unitPrice": "31745.83", + "productSalePrice": "31745.83", + "units": 2 + }, + { + "id": 1062, + "productName": "thick blush Pennie", + "pictureUrl": "https://picsum.photos/400?image=605", + "productStock": 3, + "unitPrice": "3405.51", + "productSalePrice": "3405.51", + "units": 4 + }, + { + "id": 1063, + "productName": "lovely brown Theressa", + "pictureUrl": "https://picsum.photos/400?image=891", + "productStock": 31, + "unitPrice": "40426.73", + "productSalePrice": "12936.55", + "units": 4 + }, + { + "id": 1064, + "productName": "smooth plum Robbi", + "pictureUrl": "https://picsum.photos/400?image=690", + "productStock": 18, + "unitPrice": "32980.16", + "productSalePrice": "10883.45", + "units": 2 + }, + { + "id": 1065, + "productName": "crude cyan Aloysia", + "pictureUrl": "https://picsum.photos/400?image=896", + "productStock": 56, + "unitPrice": "3933.53", + "productSalePrice": "3933.53", + "units": 5 + }, + { + "id": 1066, + "productName": "prominent turquoise Pattie", + "pictureUrl": "https://picsum.photos/400?image=855", + "productStock": 45, + "unitPrice": "30615.94", + "productSalePrice": "30615.94", + "units": 2 + }, + { + "id": 1067, + "productName": "daily tan Wenda", + "pictureUrl": "https://picsum.photos/400?image=869", + "productStock": 41, + "unitPrice": "36740.69", + "productSalePrice": "13226.65", + "units": 2 + }, + { + "id": 1068, + "productName": "primary gold Tera", + "pictureUrl": "https://picsum.photos/400?image=463", + "productStock": 88, + "unitPrice": "25121.63", + "productSalePrice": "25121.63", + "units": 4 + }, + { + "id": 1069, + "productName": "geographical amethyst Tania", + "pictureUrl": "https://picsum.photos/400?image=14", + "productStock": 79, + "unitPrice": "24384.38", + "productSalePrice": "24384.38", + "units": 2 + }, + { + "id": 1070, + "productName": "historic salmon Laraine", + "pictureUrl": "https://picsum.photos/400?image=275", + "productStock": 33, + "unitPrice": "46883.08", + "productSalePrice": "46883.08", + "units": 3 + }, + { + "id": 1071, + "productName": "bare jade Mabelle", + "pictureUrl": "https://picsum.photos/400?image=177", + "productStock": 79, + "unitPrice": "46760.90", + "productSalePrice": "16833.92", + "units": 3 + }, + { + "id": 1072, + "productName": "tame crimson Natalie", + "pictureUrl": "https://picsum.photos/400?image=152", + "productStock": 48, + "unitPrice": "22356.48", + "productSalePrice": "22356.48", + "units": 2 + }, + { + "id": 1073, + "productName": "regular harlequin Aigneis", + "pictureUrl": "https://picsum.photos/400?image=523", + "productStock": 8, + "unitPrice": "45933.08", + "productSalePrice": "45933.08", + "units": 5 + }, + { + "id": 1074, + "productName": "tight red Bernice", + "pictureUrl": "https://picsum.photos/400?image=417", + "productStock": 2, + "unitPrice": "3766.15", + "productSalePrice": "1732.43", + "units": 4 + }, + { + "id": 1075, + "productName": "ready chocolate Mirelle", + "pictureUrl": "https://picsum.photos/400?image=843", + "productStock": 38, + "unitPrice": "24113.38", + "productSalePrice": "24113.38", + "units": 3 + }, + { + "id": 1076, + "productName": "secondary red Blythe", + "pictureUrl": "https://picsum.photos/400?image=350", + "productStock": 97, + "unitPrice": "47086.10", + "productSalePrice": "9888.08", + "units": 2 + }, + { + "id": 1077, + "productName": "known orange Nollie", + "pictureUrl": "https://picsum.photos/400?image=578", + "productStock": 18, + "unitPrice": "30645.75", + "productSalePrice": "30645.75", + "units": 3 + }, + { + "id": 1078, + "productName": "very indigo Beverie", + "pictureUrl": "https://picsum.photos/400?image=304", + "productStock": 6, + "unitPrice": "36357.98", + "productSalePrice": "36357.98", + "units": 2 + }, + { + "id": 1079, + "productName": "legislative tan Gracie", + "pictureUrl": "https://picsum.photos/400?image=419", + "productStock": 50, + "unitPrice": "14884.72", + "productSalePrice": "2976.94", + "units": 3 + }, + { + "id": 1080, + "productName": "husky pink Nicola", + "pictureUrl": "https://picsum.photos/400?image=330", + "productStock": 64, + "unitPrice": "17199.38", + "productSalePrice": "17199.38", + "units": 2 + }, + { + "id": 1081, + "productName": "hot bronze Bertha", + "pictureUrl": "https://picsum.photos/400?image=571", + "productStock": 8, + "unitPrice": "23919.62", + "productSalePrice": "23919.62", + "units": 2 + }, + { + "id": 1082, + "productName": "conscious magenta Jessie", + "pictureUrl": "https://picsum.photos/400?image=707", + "productStock": 36, + "unitPrice": "1355.81", + "productSalePrice": "1355.81", + "units": 3 + }, + { + "id": 1083, + "productName": "shiny silver Tammie", + "pictureUrl": "https://picsum.photos/400?image=531", + "productStock": 55, + "unitPrice": "35674.84", + "productSalePrice": "10702.45", + "units": 4 + }, + { + "id": 1084, + "productName": "wasteful orange Stephana", + "pictureUrl": "https://picsum.photos/400?image=540", + "productStock": 26, + "unitPrice": "9266.72", + "productSalePrice": "9266.72", + "units": 4 + }, + { + "id": 1085, + "productName": "accepted coffee Karoly", + "pictureUrl": "https://picsum.photos/400?image=77", + "productStock": 9, + "unitPrice": "40394.96", + "productSalePrice": "8886.89", + "units": 4 + }, + { + "id": 1086, + "productName": "biological yellow Corene", + "pictureUrl": "https://picsum.photos/400?image=220", + "productStock": 99, + "unitPrice": "40799.67", + "productSalePrice": "12647.90", + "units": 1 + }, + { + "id": 1087, + "productName": "ugly white Jeanine", + "pictureUrl": "https://picsum.photos/400?image=607", + "productStock": 70, + "unitPrice": "20261.08", + "productSalePrice": "20261.08", + "units": 2 + }, + { + "id": 1088, + "productName": "above cyan Dredi", + "pictureUrl": "https://picsum.photos/400?image=946", + "productStock": 94, + "unitPrice": "40979.58", + "productSalePrice": "40979.58", + "units": 4 + }, + { + "id": 1089, + "productName": "stable plum Barbabra", + "pictureUrl": "https://picsum.photos/400?image=994", + "productStock": 30, + "unitPrice": "31625.74", + "productSalePrice": "31625.74", + "units": 5 + }, + { + "id": 1090, + "productName": "ambitious amber Alayne", + "pictureUrl": "https://picsum.photos/400?image=592", + "productStock": 83, + "unitPrice": "23998.74", + "productSalePrice": "9359.51", + "units": 4 + }, + { + "id": 1091, + "productName": "nice indigo Steffie", + "pictureUrl": "https://picsum.photos/400?image=644", + "productStock": 3, + "unitPrice": "18201.33", + "productSalePrice": "18201.33", + "units": 4 + }, + { + "id": 1092, + "productName": "cute ivory Ofilia", + "pictureUrl": "https://picsum.photos/400?image=832", + "productStock": 74, + "unitPrice": "49212.53", + "productSalePrice": "13287.38", + "units": 4 + }, + { + "id": 1093, + "productName": "fun lime Mallorie", + "pictureUrl": "https://picsum.photos/400?image=614", + "productStock": 65, + "unitPrice": "22883.28", + "productSalePrice": "22883.28", + "units": 2 + }, + { + "id": 1094, + "productName": "united indigo Silva", + "pictureUrl": "https://picsum.photos/400?image=404", + "productStock": 9, + "unitPrice": "40058.13", + "productSalePrice": "40058.13", + "units": 5 + }, + { + "id": 1095, + "productName": "pale gold Gwyn", + "pictureUrl": "https://picsum.photos/400?image=250", + "productStock": 74, + "unitPrice": "25333.23", + "productSalePrice": "11653.29", + "units": 2 + }, + { + "id": 1096, + "productName": "valid amaranth Constanta", + "pictureUrl": "https://picsum.photos/400?image=466", + "productStock": 19, + "unitPrice": "40992.99", + "productSalePrice": "40992.99", + "units": 2 + }, + { + "id": 1097, + "productName": "symbolic orange Courtney", + "pictureUrl": "https://picsum.photos/400?image=36", + "productStock": 63, + "unitPrice": "32930.92", + "productSalePrice": "32930.92", + "units": 4 + }, + { + "id": 1098, + "productName": "existing fuchsia Maggy", + "pictureUrl": "https://picsum.photos/400?image=991", + "productStock": 87, + "unitPrice": "29786.00", + "productSalePrice": "14595.14", + "units": 2 + }, + { + "id": 1099, + "productName": "surprising cyan Gene", + "pictureUrl": "https://picsum.photos/400?image=634", + "productStock": 61, + "unitPrice": "8908.76", + "productSalePrice": "8908.76", + "units": 3 + } +] \ No newline at end of file