bakool 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e5009031cd942deffbc62f4c505271d2da9fabf81779928e5f9210a280c4439d
4
+ data.tar.gz: 6dfde5cdd258e3349488aeaee4312f0308072cbaf58b7f30c8358636b2ae9244
5
+ SHA512:
6
+ metadata.gz: 6ad281d985e7ff08ca30a5f48878c5604df6eb62822f3f2a4d539d30ec34815117b881c2de08ce96cabb2e88547e3815418d68c10771b0e8473fbc662d670890
7
+ data.tar.gz: 66e506ce07373e4d5d3b122e6349972d7c62e8f5fb68f09457f3371f550f9e10baffb62cb7929fc86563bbac6d00374dff7c4e18abda3bb1677989c41bdb7b37
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1
3
+
4
+ Style/StringLiterals:
5
+ EnforcedStyle: double_quotes
6
+
7
+ Style/StringLiteralsInInterpolation:
8
+ EnforcedStyle: double_quotes
data/.tool-versions ADDED
@@ -0,0 +1,2 @@
1
+ local ruby 3.4.4
2
+ ruby 3.4.4
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-08-09
4
+
5
+ - Initial release
@@ -0,0 +1,132 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or advances of
31
+ any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official email address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
+ [INSERT CONTACT METHOD].
64
+ All complaints will be reviewed and investigated promptly and fairly.
65
+
66
+ All community leaders are obligated to respect the privacy and security of the
67
+ reporter of any incident.
68
+
69
+ ## Enforcement Guidelines
70
+
71
+ Community leaders will follow these Community Impact Guidelines in determining
72
+ the consequences for any action they deem in violation of this Code of Conduct:
73
+
74
+ ### 1. Correction
75
+
76
+ **Community Impact**: Use of inappropriate language or other behavior deemed
77
+ unprofessional or unwelcome in the community.
78
+
79
+ **Consequence**: A private, written warning from community leaders, providing
80
+ clarity around the nature of the violation and an explanation of why the
81
+ behavior was inappropriate. A public apology may be requested.
82
+
83
+ ### 2. Warning
84
+
85
+ **Community Impact**: A violation through a single incident or series of
86
+ actions.
87
+
88
+ **Consequence**: A warning with consequences for continued behavior. No
89
+ interaction with the people involved, including unsolicited interaction with
90
+ those enforcing the Code of Conduct, for a specified period of time. This
91
+ includes avoiding interactions in community spaces as well as external channels
92
+ like social media. Violating these terms may lead to a temporary or permanent
93
+ ban.
94
+
95
+ ### 3. Temporary Ban
96
+
97
+ **Community Impact**: A serious violation of community standards, including
98
+ sustained inappropriate behavior.
99
+
100
+ **Consequence**: A temporary ban from any sort of interaction or public
101
+ communication with the community for a specified period of time. No public or
102
+ private interaction with the people involved, including unsolicited interaction
103
+ with those enforcing the Code of Conduct, is allowed during this period.
104
+ Violating these terms may lead to a permanent ban.
105
+
106
+ ### 4. Permanent Ban
107
+
108
+ **Community Impact**: Demonstrating a pattern of violation of community
109
+ standards, including sustained inappropriate behavior, harassment of an
110
+ individual, or aggression toward or disparagement of classes of individuals.
111
+
112
+ **Consequence**: A permanent ban from any sort of public interaction within the
113
+ community.
114
+
115
+ ## Attribution
116
+
117
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
+ version 2.1, available at
119
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120
+
121
+ Community Impact Guidelines were inspired by
122
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123
+
124
+ For answers to common questions about this code of conduct, see the FAQ at
125
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126
+ [https://www.contributor-covenant.org/translations][translations].
127
+
128
+ [homepage]: https://www.contributor-covenant.org
129
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130
+ [Mozilla CoC]: https://github.com/mozilla/diversity
131
+ [FAQ]: https://www.contributor-covenant.org/faq
132
+ [translations]: https://www.contributor-covenant.org/translations
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Fadhil Luqman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,344 @@
1
+ # Bakool: Shopping Basket Library
2
+
3
+ A flexible Ruby gem for managing shopping baskets with support for products, delivery charges, and discount strategies.
4
+
5
+ ## Features
6
+
7
+ - 🛒 **Shopping Basket Management**: Add products to a basket and calculate totals
8
+ - 📦 **Product Catalogue**: Manage product inventory with codes, names, and prices
9
+ - 🚚 **Flexible Delivery Charges**: Configurable delivery charge rules based on order total
10
+ - 🎯 **Discount Strategies**: Extensible discount strategy pattern for promotional offers
11
+ - 🧪 **Test Coverage**: Comprehensive test suite with RSpec
12
+ - 🔧 **Extensible Design**: Easy to extend with custom rules and behaviors
13
+
14
+ ## Assumptions made for development:
15
+
16
+ - We're dealing with an unspecified currency that uses 2 decimal places for cents
17
+ - We currently will only support having one discount strategy and one delivery charge rule per basket
18
+ - Delivery calculations are only done based on the total price after discount and is not dependent on what items are in the basket
19
+ - Discount calculations depend on the items/item combinations in the basket
20
+
21
+ ## Installation
22
+
23
+ ### From RubyGems (Recommended)
24
+
25
+ Add this line to your application's Gemfile:
26
+
27
+ ```ruby
28
+ gem 'bakool'
29
+ ```
30
+
31
+ And then execute:
32
+
33
+ ```bash
34
+ bundle install
35
+ ```
36
+
37
+ Or install it yourself as:
38
+
39
+ ```bash
40
+ gem install bakool
41
+ ```
42
+
43
+ ### From Source
44
+
45
+ 1. Clone the repository:
46
+ ```bash
47
+ git clone https://github.com/fadhil-luqman/bakool.git
48
+ cd bakool
49
+ ```
50
+
51
+ 2. Install dependencies:
52
+ ```bash
53
+ bundle install
54
+ ```
55
+
56
+ 3. Build and install the gem locally:
57
+ ```bash
58
+ bundle exec rake install
59
+ ```
60
+
61
+ ## Usage
62
+
63
+ ### Basic Usage
64
+
65
+ ```ruby
66
+ require 'bakool'
67
+
68
+ # Create a basket with default catalogue and rules
69
+ basket = Bakool::Basket.new
70
+
71
+ # Add products by their codes
72
+ basket.add("R01") # Red Widget
73
+ basket.add("G01") # Green Widget
74
+ basket.add("B01") # Blue Widget
75
+
76
+ # Calculate total (includes delivery charges and discounts)
77
+ total = basket.total
78
+ puts "Total: $#{total}"
79
+ ```
80
+
81
+ ### Default Products
82
+
83
+ The gem comes with a default catalogue containing:
84
+
85
+ | Code | Name | Price |
86
+ |------|------|-------|
87
+ | R01 | Red Widget | $32.95 |
88
+ | G01 | Green Widget | $24.95 |
89
+ | B01 | Blue Widget | $7.95 |
90
+
91
+ ### Custom Delivery Charges
92
+
93
+ ```ruby
94
+ # Create custom delivery charge rule
95
+ delivery_rule = Bakool::DeliveryChargeRule.new(lambda do |order_total|
96
+ if order_total < 5000 then 495 # $4.95 for orders under $50
97
+ elsif order_total < 9000 then 295 # $2.95 for orders under $90
98
+ else 0 # Free delivery for orders $90+
99
+ end
100
+ end)
101
+
102
+ basket = Bakool::Basket.new(delivery_charge_rule: delivery_rule)
103
+ ```
104
+
105
+ ### Discount Strategies
106
+
107
+ The gem uses the Strategy Pattern for discounts, making it easy to implement and extend different discount types.
108
+
109
+ #### Using Built-in Discount Strategies
110
+
111
+ ```ruby
112
+ # Use the default discount (no discount)
113
+ basket = Bakool::Basket.new(discount: Bakool::DefaultDiscount.new)
114
+
115
+ # Use 50% off second same item discount for Red Widgets
116
+ discount = Bakool::FiftyPercentOff2ndSameItemDiscount.new("R01")
117
+ basket = Bakool::Basket.new(discount: discount)
118
+ ```
119
+
120
+ #### Creating Custom Discount Strategies
121
+
122
+ ```ruby
123
+ # Create a custom discount strategy
124
+ class BuyOneGetOneFreeDiscount < Bakool::Discount
125
+ def initialize(item_code)
126
+ @item_code = item_code
127
+ end
128
+
129
+ def calculate(basket)
130
+ items = basket.items.filter { |item| item.code == @item_code }
131
+ if items.count >= 2
132
+ # Apply 100% discount to every second item
133
+ (items.count / 2) * items.first.price_in_cents
134
+ else
135
+ 0
136
+ end
137
+ end
138
+ end
139
+
140
+ # Use the custom discount
141
+ discount = BuyOneGetOneFreeDiscount.new("R01")
142
+ basket = Bakool::Basket.new(discount: discount)
143
+ ```
144
+
145
+ ### Complete Example
146
+
147
+ ```ruby
148
+ # Create basket with custom rules
149
+ delivery_rule = Bakool::DeliveryChargeRule.new(lambda do |order_total|
150
+ if order_total < 5000 then 495
151
+ elsif order_total < 9000 then 295
152
+ else 0
153
+ end
154
+ end)
155
+
156
+ # Use 50% off second Red Widget discount
157
+ discount = Bakool::FiftyPercentOff2ndSameItemDiscount.new("R01")
158
+
159
+ basket = Bakool::Basket.new(
160
+ delivery_charge_rule: delivery_rule,
161
+ discount: discount
162
+ )
163
+
164
+ # Add items
165
+ basket.add("R01") # Red Widget
166
+ basket.add("R01") # Second Red Widget (50% off)
167
+ basket.add("G01") # Green Widget
168
+
169
+ # Calculate total
170
+ total = basket.total
171
+ puts "Total: $#{total}" # Output: Total: $54.37
172
+ ```
173
+
174
+ ## API Reference
175
+
176
+ ### Basket
177
+
178
+ The main class for managing shopping baskets.
179
+
180
+ #### `Bakool::Basket.new(catalogue:, delivery_charge_rule:, discount_rule:, discount:)`
181
+
182
+ Creates a new basket instance.
183
+
184
+ - `catalogue` (optional): Product catalogue (defaults to `Bakool::Catalogue.default_catalogue`)
185
+ - `delivery_charge_rule` (optional): Delivery charge calculation rule (defaults to `Bakool::DeliveryChargeRule.default_delivery_charge_rule`)
186
+ - `discount_rule` (optional): Legacy discount rule (defaults to `Bakool::DiscountRule.default_discount_rule`)
187
+ - `discount` (optional): Discount strategy (defaults to `Bakool::DefaultDiscount.default_discount`)
188
+
189
+ #### `basket.add(product_code)`
190
+
191
+ Adds a product to the basket by its code.
192
+
193
+ - `product_code` (String): The product code to add
194
+ - Raises `Bakool::InvalidProductCodeError` if the product code doesn't exist
195
+
196
+ #### `basket.total`
197
+
198
+ Calculates the total price including delivery charges and discounts.
199
+
200
+ - Returns: `Float` - Total price in dollars
201
+
202
+ #### `basket.show_total`
203
+
204
+ Returns the items in the basket.
205
+
206
+ - Returns: `Array` - Array of Product objects
207
+
208
+ ### Product
209
+
210
+ Represents a product in the catalogue.
211
+
212
+ #### `Bakool::Product.new(name, code, price)`
213
+
214
+ Creates a new product.
215
+
216
+ - `name` (String): Product name (required)
217
+ - `code` (String): Product code (required)
218
+ - `price` (Float): Product price in dollars (default: 0)
219
+
220
+ ### Catalogue
221
+
222
+ Manages the product inventory.
223
+
224
+ #### `Bakool::Catalogue.new`
225
+
226
+ Creates a new empty catalogue.
227
+
228
+ #### `catalogue.add_product(product)`
229
+
230
+ Adds a product to the catalogue.
231
+
232
+ - `product` (Bakool::Product): Product object to add
233
+
234
+ #### `Bakool::Catalogue.default_catalogue`
235
+
236
+ Returns a catalogue with default products (Red Widget, Green Widget, Blue Widget).
237
+
238
+ ### DeliveryChargeRule
239
+
240
+ Handles delivery charge calculations.
241
+
242
+ #### `Bakool::DeliveryChargeRule.new(func)`
243
+
244
+ Creates a new delivery charge rule.
245
+
246
+ - `func` (Proc, optional): Function that takes order total in cents and returns delivery charge in cents
247
+
248
+ #### `delivery_charge_rule.calculate(order_total)`
249
+
250
+ Calculates delivery charge for an order total.
251
+
252
+ - `order_total` (Integer): Order total in cents
253
+ - Returns: `Integer` - Delivery charge in cents
254
+
255
+ ### Discount Strategies
256
+
257
+ The discount system uses the Strategy Pattern for flexible and extensible discount calculations.
258
+
259
+ #### `Bakool::Discount`
260
+
261
+ Base class for all discount strategies.
262
+
263
+ #### `discount.calculate(basket)`
264
+
265
+ Calculates discount for a basket.
266
+
267
+ - `basket` (Bakool::Basket): Basket object
268
+ - Returns: `Integer` - Discount amount in cents
269
+
270
+ #### `Bakool::DefaultDiscount`
271
+
272
+ Default discount strategy that applies no discount.
273
+
274
+ ```ruby
275
+ discount = Bakool::DefaultDiscount.new
276
+ # or
277
+ discount = Bakool::DefaultDiscount.default_discount
278
+ ```
279
+
280
+ #### `Bakool::FiftyPercentOff2ndSameItemDiscount`
281
+
282
+ Applies 50% discount to the second item of the same type.
283
+
284
+ ```ruby
285
+ # 50% off second Red Widget
286
+ discount = Bakool::FiftyPercentOff2ndSameItemDiscount.new("R01")
287
+ ```
288
+
289
+ #### Creating Custom Discount Strategies
290
+
291
+ To create a custom discount strategy, inherit from the `Bakool::Discount` base class:
292
+
293
+ ```ruby
294
+ class CustomDiscount < Bakool::Discount
295
+ def initialize(parameters)
296
+ # Initialize your discount strategy
297
+ end
298
+
299
+ def calculate(basket)
300
+ # Implement your discount logic
301
+ # Return discount amount in cents
302
+ end
303
+ end
304
+ ```
305
+
306
+ ## Development
307
+
308
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
309
+
310
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
311
+
312
+ ## Testing
313
+
314
+ Run the test suite:
315
+
316
+ ```bash
317
+ bundle exec rspec
318
+ ```
319
+
320
+ ## Error Handling
321
+
322
+ The gem includes custom error classes:
323
+
324
+ - `Bakool::InvalidProductCodeError`: Raised when trying to add a product with an invalid code
325
+
326
+ ## Contributing
327
+
328
+ Bug reports and pull requests are welcome on GitHub at https://github.com/fadhil-luqman/bakool. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/fadhil-luqman/bakool/blob/main/CODE_OF_CONDUCT.md).
329
+
330
+ 1. Fork the repository
331
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
332
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
333
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
334
+ 5. Open a Pull Request
335
+
336
+ ## License
337
+
338
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
339
+
340
+ ## Code of Conduct
341
+
342
+ Everyone interacting in the Bakool project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/fadhil-luqman/bakool/blob/main/CODE_OF_CONDUCT.md).
343
+
344
+
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A shopping basket that can hold products and calculate totals with discounts and delivery charges
4
+ module Bakool
5
+ class Basket
6
+ attr_accessor :catalogue, :items, :delivery_charge_rule, :discount_rule, :discount
7
+
8
+ def initialize(catalogue: Bakool::Catalogue.default_catalogue,
9
+ delivery_charge_rule: Bakool::DeliveryChargeRule.default_delivery_charge_rule,
10
+ discount: Bakool::DefaultDiscount.default_discount)
11
+ @catalogue = catalogue
12
+ @items = []
13
+ @delivery_charge_rule = delivery_charge_rule
14
+ @discount_rule = discount_rule
15
+ @discount = discount
16
+ end
17
+
18
+ def add(product_code)
19
+ product = catalogue.products.find { |p| p.code == product_code }
20
+ raise Bakool::InvalidProductCodeError, "Invalid product code: #{product_code}" if product.nil?
21
+
22
+ @items << product
23
+ end
24
+
25
+ def total
26
+ total_in_cents_before_adjustments = 0
27
+ items.each do |item|
28
+ total_in_cents_before_adjustments += item.price_in_cents
29
+ end
30
+ discounts = discount.calculate(self)
31
+ total_in_cents_after_discounts = total_in_cents_before_adjustments - discounts
32
+ delivery_charge = delivery_charge_rule.calculate(total_in_cents_after_discounts)
33
+ (total_in_cents_after_discounts + delivery_charge) / 100.0
34
+ end
35
+
36
+ def show_total
37
+ items
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A catalogue that holds a collection of products
4
+ module Bakool
5
+ class Catalogue
6
+ attr_accessor :products
7
+
8
+ def initialize
9
+ @products = []
10
+ end
11
+
12
+ def add_product(product)
13
+ @products << product
14
+ end
15
+
16
+ def self.default_catalogue
17
+ catalogue = Catalogue.new
18
+ catalogue.add_product(Bakool::Product.new("Red Widget", "R01", 32.95))
19
+ catalogue.add_product(Bakool::Product.new("Green Widget", "G01", 24.95))
20
+ catalogue.add_product(Bakool::Product.new("Blue Widget", "B01", 7.95))
21
+ catalogue
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A rule that calculates delivery charges based on order total
4
+ module Bakool
5
+ class DeliveryChargeRule
6
+ attr_accessor :func
7
+
8
+ # @param func [Proc] A function that takes an order total in cents and returns the delivery charge in cents
9
+ def initialize(func = nil)
10
+ @func = func || ->(order_total) { order_total.positive? ? 495 : 0 }
11
+ end
12
+
13
+ # Returns the delivery charge in cents
14
+ # We only take order_total because delivery charges are not dependent on the basket's composition (for now)
15
+ def calculate(order_total)
16
+ @func.call(order_total)
17
+ end
18
+
19
+ def self.default_delivery_charge_rule
20
+ Bakool::DeliveryChargeRule.new
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "discount"
4
+
5
+ # Default discount strategy that applies no discount
6
+ module Bakool
7
+ class DefaultDiscount < Discount
8
+ def calculate(_basket)
9
+ 0
10
+ end
11
+
12
+ def self.default_discount
13
+ Bakool::DefaultDiscount.new
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abstract base class for discount strategies
4
+ module Bakool
5
+ class Discount
6
+ def calculate(basket)
7
+ raise NotImplementedError, "Subclasses must implement this method"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Discount strategy that applies 50% off the second item of the same type
4
+ module Bakool
5
+ class FiftyPercentOff2ndSameItemDiscount < Discount
6
+ def initialize(item_code)
7
+ super()
8
+ @item_code = item_code
9
+ end
10
+
11
+ def calculate(basket)
12
+ matching_items = basket.items.filter { |item| item.code == @item_code }
13
+ return 0 unless matching_items.count >= 2
14
+
15
+ (matching_items.first.price_in_cents / 2.0).ceil
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bakool
4
+ class InvalidProductCodeError < StandardError
5
+ end
6
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A product that can be added to a shopping basket
4
+ module Bakool
5
+ class Product
6
+ attr_accessor :name, :code, :price
7
+
8
+ def initialize(name, code, price = 0)
9
+ raise ArgumentError, "Name and code are required" if name.nil? || name.empty? || code.nil? || code.empty?
10
+
11
+ raise ArgumentError, "Price must be a positive number" if price.nil? || price.negative?
12
+
13
+ @name = name
14
+ @code = code
15
+ @price = price
16
+ end
17
+
18
+ def price_in_cents
19
+ (@price * 100).to_i
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bakool
4
+ VERSION = "0.1.0"
5
+ end
data/lib/bakool.rb ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "bakool/version"
4
+
5
+ module Bakool
6
+ class Error < StandardError; end
7
+ # Your code goes here...
8
+ end
9
+
10
+ require_relative "bakool/basket"
11
+ require_relative "bakool/catalogue"
12
+ require_relative "bakool/delivery_charge_rule"
13
+ require_relative "bakool/product"
14
+ require_relative "bakool/discount_strategies/discount"
15
+ require_relative "bakool/discount_strategies/default_discount"
16
+ require_relative "bakool/discount_strategies/fifty_percent_off_2nd_same_item_discount"
17
+ require_relative "bakool/errors/invalid_product_code_error"
data/sig/bakool.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Bakool
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bakool
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Fadhil Luqman
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ email:
13
+ - fadhil.luqman@gmail.com
14
+ executables: []
15
+ extensions: []
16
+ extra_rdoc_files:
17
+ - README.md
18
+ files:
19
+ - ".rspec"
20
+ - ".rubocop.yml"
21
+ - ".tool-versions"
22
+ - CHANGELOG.md
23
+ - CODE_OF_CONDUCT.md
24
+ - LICENSE.txt
25
+ - README.md
26
+ - Rakefile
27
+ - lib/bakool.rb
28
+ - lib/bakool/basket.rb
29
+ - lib/bakool/catalogue.rb
30
+ - lib/bakool/delivery_charge_rule.rb
31
+ - lib/bakool/discount_strategies/default_discount.rb
32
+ - lib/bakool/discount_strategies/discount.rb
33
+ - lib/bakool/discount_strategies/fifty_percent_off_2nd_same_item_discount.rb
34
+ - lib/bakool/errors/invalid_product_code_error.rb
35
+ - lib/bakool/product.rb
36
+ - lib/bakool/version.rb
37
+ - sig/bakool.rbs
38
+ homepage: https://github.com/fadhil-luqman/bakool
39
+ licenses:
40
+ - MIT
41
+ metadata:
42
+ homepage_uri: https://github.com/fadhil-luqman/bakool
43
+ source_code_uri: https://github.com/fadhil-luqman/bakool
44
+ changelog_uri: https://github.com/fadhil-luqman/bakool/blob/main/CHANGELOG.md
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 3.1.0
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubygems_version: 3.6.7
60
+ specification_version: 4
61
+ summary: A flexible Ruby library for managing shopping baskets with support for products,
62
+ delivery charges, and discount strategies.
63
+ test_files: []