Skip to content

E-Commerce Examples

Real-world patterns for e-commerce applications.

Product Catalog

Search Products with Filters

@Service
class ProductService(mongoTemplate: MongoTemplate) {
    private val konduct = Konduct(mongoTemplate)

    fun searchProducts(
        query: String?,
        category: String?,
        minPrice: Double?,
        maxPrice: Double?,
        inStockOnly: Boolean,
        page: Int,
        pageSize: Int
    ): PagedResult<Product> {
        return konduct.collection<Product>()
            .match {
                query?.let {
                    Product::name regex it.toRegex(RegexOption.IGNORE_CASE)
                }
                category?.let { Product::category eq it }
                minPrice?.let { Product::price gte it }
                maxPrice?.let { Product::price lte it }
                if (inStockOnly) {
                    Product::inStock eq true
                }
                Product::status eq "active"
            }
            .sort {
                Product::rating.desc()
                Product::reviewCount.desc()
            }
            .paginate(page, pageSize)
            .firstOrNull() ?: PagedResult(
                data = emptyList(),
                total = 0,
                page = page,
                pageSize = pageSize,
                totalPages = 0
            )
    }
}
fun getRelatedProducts(productId: String, limit: Int = 5): List<Product> {
    val product = konduct.collection<Product>()
        .match { Product::id eq productId }
        .firstOrNull() ?: return emptyList()

    return konduct.collection<Product>()
        .match {
            Product::category eq product.category
            Product::id ne productId
            Product::status eq "active"
            Product::inStock eq true
        }
        .sort { Product::rating.desc() }
        .limit(limit)
        .toList()
}

Order Management

Customer Order History

data class OrderSummary(
    val _id: String,
    val orderDate: Date,
    val total: Double,
    val itemCount: Int,
    val status: String
)

fun getCustomerOrders(customerId: String, limit: Int = 20): List<OrderSummary> {
    return konduct.collection<Order>()
        .match { Order::customerId eq customerId }
        .sort { Order::orderDate.desc() }
        .limit(limit)
        .into<OrderSummary>()
        .toList()
}

Order Totals Calculation

data class OrderTotal(
    val orderId: String,
    val subtotal: Double,
    val tax: Double,
    val discount: Double,
    val shipping: Double,
    val total: Double
)

fun calculateOrderTotals(orderId: String): OrderTotal? {
    return konduct.collection<OrderItem>()
        .match { OrderItem::orderId eq orderId }
        .group {
            by(OrderItem::orderId)
            accumulate {
                "subtotal" sum (OrderItem::quantity * OrderItem::price)
                "tax" sum (
                    OrderItem::quantity * OrderItem::price * (OrderItem::taxRate / 100)
                )
                "discount" sum (
                    OrderItem::quantity * OrderItem::price * (OrderItem::discountRate / 100)
                )
            }
        }
        .addFields {
            "shipping" from 10.0  // Flat rate
            "total" from (
                "subtotal" + "tax" - "discount" + "shipping"
            )
        }
        .into<OrderTotal>()
        .firstOrNull()
}

Customer Analytics

Customer Lifetime Value

data class CustomerLTV(
    val customerId: String,
    val totalSpent: Double,
    val orderCount: Int,
    val avgOrderValue: Double,
    val firstOrderDate: Date,
    val lastOrderDate: Date,
    val daysSinceFirst: Long,
    val daysSinceLast: Long
)

fun getHighValueCustomers(minSpent: Double = 1000): List<CustomerLTV> {
    return konduct.collection<Order>()
        .match {
            Order::status `in` listOf("completed", "shipped")
        }
        .group {
            by(Order::customerId)
            accumulate {
                "totalSpent" sum Order::total
                "orderCount" count Unit
                "avgOrderValue" avg Order::total
                "firstOrderDate" min Order::orderDate
                "lastOrderDate" max Order::orderDate
            }
        }
        .addFields {
            "daysSinceFirst" from dateDiffDays("firstOrderDate", Date())
            "daysSinceLast" from dateDiffDays("lastOrderDate", Date())
        }
        .match { "totalSpent" gte minSpent }
        .sort { "totalSpent".desc() }
        .into<CustomerLTV>()
        .toList()
}

Purchase Frequency

data class PurchaseFrequency(
    val customerId: String,
    val totalOrders: Int,
    val daysBetweenOrders: Double,
    val isFrequentBuyer: Boolean
)

fun analyzePurchaseFrequency(): List<PurchaseFrequency> {
    return konduct.collection<Order>()
        .match { Order::status eq "completed" }
        .group {
            by(Order::customerId)
            accumulate {
                "totalOrders" count Unit
                "firstOrder" min Order::orderDate
                "lastOrder" max Order::orderDate
            }
        }
        .addFields {
            "daysBetweenOrders" from (
                dateDiffDays("firstOrder", "lastOrder") / ("totalOrders" - 1)
            )
            "isFrequentBuyer" from ("daysBetweenOrders" lt 30)
        }
        .match { "totalOrders" gte 2 }
        .into<PurchaseFrequency>()
        .toList()
}

Inventory Management

Low Stock Alert

data class LowStockProduct(
    val id: String,
    val name: String,
    val category: String,
    val currentStock: Int,
    val reorderPoint: Int,
    val daysOfStock: Int
)

fun getLowStockProducts(): List<LowStockProduct> {
    return konduct.collection<Product>()
        .match {
            Product::status eq "active"
        }
        .addFields {
            "daysOfStock" from (
                Product::stock / Product::avgDailySales
            )
        }
        .match {
            or(
                "stock" lte "reorderPoint",
                "daysOfStock" lte 7
            )
        }
        .sort { "daysOfStock".asc() }
        .into<LowStockProduct>()
        .toList()
}

Inventory Valuation

data class InventoryValue(
    val category: String,
    val productCount: Int,
    val totalUnits: Int,
    val costValue: Double,
    val retailValue: Double,
    val potentialProfit: Double
)

fun getInventoryValuation(): List<InventoryValue> {
    return konduct.collection<Product>()
        .match { Product::status eq "active" }
        .group {
            by(Product::category)
            accumulate {
                "productCount" count Unit
                "totalUnits" sum Product::stock
                "costValue" sum (Product::stock * Product::costPrice)
                "retailValue" sum (Product::stock * Product::sellingPrice)
            }
        }
        .addFields {
            "potentialProfit" from ("retailValue" - "costValue")
        }
        .sort { "retailValue".desc() }
        .into<InventoryValue>()
        .toList()
}

Sales Analytics

Daily Sales Report

data class DailySales(
    val date: String,
    val revenue: Double,
    val orders: Int,
    val avgOrderValue: Double,
    val uniqueCustomers: Int
)

fun getDailySales(startDate: Date, endDate: Date): List<DailySales> {
    return konduct.collection<Order>()
        .match {
            Order::status eq "completed"
            Order::orderDate gte startDate
            Order::orderDate lte endDate
        }
        .group {
            by(Order::orderDate, unit = TimeUnit.DAY)
            accumulate {
                "revenue" sum Order::total
                "orders" count Unit
                "avgOrderValue" avg Order::total
                "uniqueCustomers" countDistinct Order::customerId
            }
        }
        .sort { "date".asc() }
        .into<DailySales>()
        .toList()
}

Top Selling Products

data class ProductSales(
    val productId: String,
    val productName: String,
    val unitsSold: Int,
    val revenue: Double,
    val orderCount: Int
)

fun getTopSellingProducts(days: Int = 30, limit: Int = 10): List<ProductSales> {
    val since = Date(System.currentTimeMillis() - days * 24 * 60 * 60 * 1000)

    return konduct.collection<OrderItem>()
        .match {
            OrderItem::orderDate gte since
            OrderItem::status eq "completed"
        }
        .group {
            by(OrderItem::productId)
            accumulate {
                "unitsSold" sum OrderItem::quantity
                "revenue" sum (OrderItem::quantity * OrderItem::price)
                "orderCount" count Unit
            }
        }
        .lookup {
            from<Product>()
            localField("_id")
            foreignField(Product::id)
            into("product")
        }
        .unwind("product")
        .addFields {
            "productName" from "\$product.name"
        }
        .sort { "revenue".desc() }
        .limit(limit)
        .into<ProductSales>()
        .toList()
}

Abandoned Carts

Find Abandoned Carts

data class AbandonedCart(
    val cartId: String,
    val customerId: String,
    val customerEmail: String,
    val itemCount: Int,
    val totalValue: Double,
    val lastUpdated: Date,
    val hoursSinceUpdate: Long
)

fun getAbandonedCarts(minHours: Int = 24): List<AbandonedCart> {
    val cutoff = Date(System.currentTimeMillis() - minHours * 60 * 60 * 1000)

    return konduct.collection<Cart>()
        .match {
            Cart::status eq "active"
            Cart::lastUpdated lte cutoff
            Cart::itemCount gt 0
        }
        .addFields {
            "hoursSinceUpdate" from hoursDiff(Cart::lastUpdated, Date())
        }
        .lookup {
            from<Customer>()
            localField(Cart::customerId)
            foreignField(Customer::id)
            into("customer")
        }
        .unwind("customer")
        .addFields {
            "customerEmail" from "\$customer.email"
        }
        .sort { "totalValue".desc() }
        .into<AbandonedCart>()
        .toList()
}

See Also