The pupose of this execise is to calculate total order prices by bundling items in the checkout cart.
This implementation uses two types of bundling:
(1) In-Category-Bundling : All items bundled are in the same product category, e.g. fruit. The items in the bundle are discounted after their number reaches a certain threshold. For example, the first apple is $1.25. Every apple after the first apple is (1 - 0.25)*1.25 = $0.94.
(2) Cross-Category-Bundling: The items bundled come from different product cataegories. For instance, this implementation contains two cross-category bundles: camera-pack and sandwich-lunch. The camera-pack bundle contains a camera, tripod and battery six-pack, which is at a 100% discount - a real bargain. The sandwich-lunch bundle contains a loaf of bread (50% off), cornbeef cold-cuts (35% off) and a jar of spicy mustard (50% off)
The implementation identifies a cart item by an SKU (Shelf Keeping Unit), which is specified by the business. A bundle in a cart also has an SKU. For an in-category bundle, the bundle SKU is the same as the SKU for the consituent product. A cross-category bundle SKU is the concatenation of all the product SKUs in the bundle.
To distinguish a simple product cart item (ProductCartItem) from a bundle cart item (BundleCartItem), the implementation uses ,respectively, a ProductSKU and a BundleSKU.
The implementation stores bundles in the BundleRegistry. Each bundle is indexed by a BundleSKU. The bundle also has a name, which would allow for distinguishing between bundles with the same BundleSKU. This functionality was not explored in the exercise implementation.
Given a cart, the BundleRegistry tries to find candidate bundles suitable for the cart. The is done with its getRelevantBundles method. The BundleRegistry is iniialized/configured with an application.conf file by the BundleRegistry method, configureRegistry.
Given a BundleRegistry, the BundleProcessor tries to create a new cart with as many bundle items it can with its bundleItemsInCart method. It recursively tries to remove simple product items from the cart and replace them with appropriate bundle items. The resultant cart could consist completely of bundled items, simple product items or mixture of both. Notice: the BundleProcessor does not attempt to minimize the total cost of the cart through bundling.
The CartPricingService is a simple public API on top of the BundleRegistry and BundleProcessor that calculates the total cart price from the items in the cart. For the purpose or demonstration, CartPricingService has methods that calculate totals without bundling: priceCartSimply and priceListOfCartsSimply. The last method does simple totalling for multiple carts concurrently. The equivalent method for totalling multiple carts with bundling is priceListOfCartsWithBundling.
The implementation includes a specs2 test BundleSpec which tests all the bundling components with calls to CartPricingService methods.