order_optimizer 0.3.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7582fc4c48f45ba73759ff312cc21a0bfe7731a5b109eafcb313c2c0930d435
4
- data.tar.gz: b10e675e41325916aede4dae3cbbc2f3dddc075bd0f0164daa5f61948c813c9c
3
+ metadata.gz: dc5f8c71f67ec7c63ccf96363c1653637f5f4ea50d866485cdd4f6238594522d
4
+ data.tar.gz: 13c69c75805106de2e1ae6a0031bb86639f05c435c49732aaae3124375458efb
5
5
  SHA512:
6
- metadata.gz: 3b92cd98cbb49a93f5dd152673fca3e44d8ff531a6eae3fd87e1bd44c0bfeaebc02e580c69694da21069450dfae897a0ed641e2fe217b18a93a3d1ead28c4efd
7
- data.tar.gz: 1bb4af7ddd21d6bb5ddf22b18ed0b0e099609de555c3350837a84478b516a1f43adf2c0a58e818ca17abd443ce254dc862596661d77f8b5d46937d8f9f5b1a05
6
+ metadata.gz: '0633497552abad8d67891a0f2952fafa9221ba4941709440c0e515f19f8241fb05bd30ccf4129b1c4a879686ef9203d75565c37556e78fdc0b73db0bca862085'
7
+ data.tar.gz: 03742d3da4bd0db3beb4b19c824f99cc17db427a84c63e27944bd0d20327eff779cfdb355fb0613ab6179c9950279b5427e247bc8802517955a326d523b992e1
@@ -0,0 +1,63 @@
1
+ version: 2.1
2
+ jobs:
3
+ test:
4
+ docker:
5
+ - image: circleci/ruby:3.0
6
+
7
+ working_directory: ~/repo
8
+
9
+ steps:
10
+ - checkout
11
+
12
+ - restore_cache:
13
+ keys:
14
+ - v1-dependencies-{{ checksum "order_optimizer.gemspec" }}
15
+ # fallback to using the latest cache if no exact match is found
16
+ - v1-dependencies-
17
+
18
+ - run:
19
+ name: install dependencies
20
+ command: bundle install --jobs=4 --retry=3 --path vendor/bundle
21
+
22
+ - save_cache:
23
+ paths:
24
+ - ./vendor/bundle
25
+ key: v1-dependencies-{{ checksum "order_optimizer.gemspec" }}
26
+
27
+ - run:
28
+ name: run tests
29
+ command: bundle exec rake
30
+
31
+ publish:
32
+ docker:
33
+ - image: circleci/ruby:3.0
34
+ working_directory: ~/repo
35
+ steps:
36
+ - checkout
37
+ - run:
38
+ name: Build package
39
+ command: gem build order_optimizer.gemspec
40
+ - run:
41
+ name: Push package
42
+ command: |
43
+ VERSION=$(ruby -r "./lib/order_optimizer/version.rb" -e "print OrderOptimizer::VERSION")
44
+ gem push order_optimizer-${VERSION}.gem
45
+
46
+ workflows:
47
+ default:
48
+ jobs:
49
+ - test:
50
+ filters:
51
+ tags:
52
+ only: /.*/
53
+ branches:
54
+ only: /.*/
55
+ - publish:
56
+ context:
57
+ - rubygems-push
58
+ requires: [test]
59
+ filters:
60
+ tags:
61
+ only: /^v.*/
62
+ branches:
63
+ ignore: /.*/
data/.gitignore CHANGED
@@ -6,3 +6,4 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ Gemfile.lock
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # OrderOptimizer Gem Changelog
2
2
 
3
+ ## 0.5.1 (2021-11-29)
4
+ - Consider `max_quantity` when desired exceeds OR EQUALS max_quantity
5
+
6
+ ## 0.5.0 (2021-04-15)
7
+ - Add `maximum_quantity` for SKUs
8
+
9
+ ## 0.4.1 (2021-02-15)
10
+
11
+ - Fix issue with floating point conversion [#3]
12
+
13
+ ## 0.4.0 (2020-12-11)
14
+
15
+ - Add `possible_orders` method to find all possible orders
16
+ - Fix issues with zero remainders on calculations
17
+
3
18
  ## 0.3.0 (2020-11-23)
4
19
 
5
20
  - Add `cheapest_exact_order` method to optimize an order with an exact amount
@@ -13,3 +28,5 @@
13
28
  - Add support for discounts when ordering a minimum quantity
14
29
 
15
30
  ## 0.1.0 First release
31
+
32
+ [#3]: https://github.com/zaikio/order_optimizer/pull/3
data/README.md CHANGED
@@ -72,7 +72,7 @@ cheapest_exact_order_for_58_units.total # => 322
72
72
  cheapest_exact_order_for_58_units.skus # => { '10-pack' => 5, '1-pack' => 8 }
73
73
  ```
74
74
 
75
- It is possible to define dicount prices a minimum quantity:
75
+ It is possible to define discount prices with a minimum quantity:
76
76
 
77
77
  ```ruby
78
78
  order_optimizer = OrderOptimizer.new(
@@ -100,15 +100,52 @@ order_optimizer.cheapest_order(required_qty: 29).skus
100
100
  #=> { '20-pack' => 1, '10-discount' => 10 }
101
101
  ```
102
102
 
103
+ It is also possible to define discount prices with a maximum quantity:
104
+ ```ruby
105
+ order_optimizer = OrderOptimizer.new(
106
+ '1-pack' => { quantity: 1, price_per_unit: 9 },
107
+ '10-pack' => { quantity: 10, price_per_unit: 8, max_quantity: 20 },
108
+ )
109
+
110
+ order_optimizer.cheapest_order(required_qty: 30).skus
111
+ #=> { '10-pack' => 2, '1-pack' => 10 }
112
+ ```
113
+
114
+ If you're just interested in all possible order combinations:
115
+
116
+ ```ruby
117
+ # Initialize the optimize with a catalog
118
+ order_optimizer = OrderOptimizer.new(
119
+ '1-pack' => { quantity: 1, price_per_unit: 9 },
120
+ '10-pack' => { quantity: 10, price_per_unit: 5 },
121
+ )
122
+
123
+ order_optimizer.possible_orders(required_qty: 11).size
124
+ #=> 3
125
+ order_optimizer.possible_orders(required_qty: 10).map(&:skus)
126
+ #=> [{"10-pack"=>1, "1-pack"=>1}, {"1-pack"=>11}, {"10-pack"=>2}]
127
+ ```
128
+
103
129
  ## Development
104
130
 
105
131
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
106
132
 
107
- 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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
108
-
109
133
  ## Contributing
110
134
 
111
- Bug reports and pull requests are welcome on GitHub at https://github.com/crispymtn/order_optimizer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
135
+ Bug reports and pull requests are welcome on GitHub at
136
+ https://github.com/zaikio/order_optimizer. This project is intended to be a safe,
137
+ welcoming space for collaboration, and contributors are expected to adhere to the
138
+ [Contributor Covenant](http://contributor-covenant.org) code of conduct.
139
+
140
+ - Make your changes and submit a pull request for them
141
+ - Make sure to update `CHANGELOG.md`
142
+
143
+ To release a new version of the gem:
144
+ - Update the version in `lib/order_optimizer/version.rb`
145
+ - Update `CHANGELOG.md` to include the new version and its release date
146
+ - Commit and push your changes
147
+ - Create a [new release on GitHub](https://github.com/zaikio/order_optimizer/releases/new)
148
+ - CircleCI will build the Gem package and push it Rubygems for you
112
149
 
113
150
  ## License
114
151
 
@@ -116,4 +153,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
116
153
 
117
154
  ## Code of Conduct
118
155
 
119
- Everyone interacting in the OrderOptimizer project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/crispymtn/order_optimizer/blob/master/CODE_OF_CONDUCT.md).
156
+ Everyone interacting in the OrderOptimizer project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/zaikio/order_optimizer/blob/master/CODE_OF_CONDUCT.md).
@@ -1,21 +1,24 @@
1
1
  class OrderOptimizer
2
2
  class Sku
3
- attr_reader :id, :quantity, :price_per_unit, :price_per_sku, :min_quantity
3
+ attr_reader :id, :quantity, :price_per_unit, :price_per_sku, :min_quantity, :max_quantity
4
4
 
5
- def initialize(id, quantity:, price_per_sku: nil, price_per_unit: nil, min_quantity: nil)
5
+ def initialize(id, quantity:, price_per_sku: nil, price_per_unit: nil, min_quantity: nil, max_quantity: nil)
6
6
  @id = id
7
- @quantity = quantity
8
- @min_quantity = min_quantity
7
+ @quantity = BigDecimal(quantity, 2)
8
+ @min_quantity = BigDecimal(min_quantity, 2) if min_quantity
9
+ @max_quantity = BigDecimal(max_quantity, 2) if max_quantity
9
10
 
10
- @price_per_unit = BigDecimal(price_per_unit, 2) if price_per_unit
11
- @price_per_sku = BigDecimal(price_per_sku, 2) if price_per_sku
12
-
13
- unless price_per_unit || price_per_sku
14
- raise ':price_per_sku or :price_per_unit must be set'
11
+ if min_quantity && max_quantity && (min_quantity > max_quantity)
12
+ raise ArgumentError, "min_quantity can't be larger than max_quantity"
15
13
  end
16
14
 
17
- @price_per_unit ||= price_per_sku.to_f / quantity
18
- @price_per_sku ||= quantity * price_per_unit
15
+ raise ArgumentError, ':price_per_sku or :price_per_unit must be set' unless price_per_unit || price_per_sku
16
+
17
+ @price_per_unit = BigDecimal(price_per_unit, 2) if price_per_unit
18
+ @price_per_unit ||= BigDecimal(price_per_sku, 2) / quantity
19
+
20
+ @price_per_sku = BigDecimal(price_per_sku, 2) if price_per_sku
21
+ @price_per_sku ||= quantity * price_per_unit
19
22
  end
20
23
  end
21
24
  end
@@ -1,3 +1,3 @@
1
1
  class OrderOptimizer
2
- VERSION = "0.3.0"
2
+ VERSION = "0.5.1".freeze
3
3
  end
@@ -10,37 +10,41 @@ class OrderOptimizer
10
10
  end
11
11
 
12
12
  def cheapest_order(required_qty:)
13
- possible_orders(skus: @catalog.skus, required_qty: required_qty).min_by(&:total) ||
13
+ find_possible_orders(skus: @catalog.skus, required_qty: required_qty).min_by(&:total) ||
14
14
  OrderOptimizer::Order.new(required_qty: required_qty)
15
15
  end
16
16
 
17
17
  def cheapest_exact_order(required_qty:)
18
- possible_orders(skus: @catalog.skus, required_qty: required_qty).select(&:exact?).min_by(&:total) ||
18
+ find_possible_orders(skus: @catalog.skus, required_qty: required_qty).select(&:exact?).min_by(&:total) ||
19
19
  OrderOptimizer::Order.new(required_qty: required_qty)
20
20
  end
21
21
 
22
+ def possible_orders(required_qty:)
23
+ find_possible_orders(skus: @catalog.skus, required_qty: required_qty).sort_by(&:total)
24
+ end
25
+
22
26
  private
23
27
 
24
- def possible_orders(required_qty:, skus:)
28
+ def find_possible_orders(required_qty:, skus:)
25
29
  return [] if required_qty < 1 || skus.empty?
26
30
 
27
31
  orders = []
28
32
 
29
33
  skus.each do |sku|
30
34
  orders.reject(&:complete?).each do |order|
31
- count, remainder = count_and_remainder_for_sku(order.missing_qty, sku)
35
+ count, remainder, skip_increase = count_and_remainder_for_sku(order.missing_qty, sku)
32
36
 
33
37
  orders << order.dup.add(sku, count: count) unless count.zero?
34
- orders << order.dup.add(sku, count: count + 1) if remainder
38
+ orders << order.dup.add(sku, count: count + 1) unless remainder.zero? || skip_increase
35
39
  end
36
40
 
37
- count, remainder = count_and_remainder_for_sku(required_qty, sku)
41
+ count, remainder, skip_increase = count_and_remainder_for_sku(required_qty, sku)
38
42
 
39
43
  unless count.zero?
40
44
  orders << OrderOptimizer::Order.new(required_qty: required_qty).add(sku, count: count)
41
45
  end
42
46
 
43
- if remainder
47
+ unless remainder.zero? || skip_increase
44
48
  orders << OrderOptimizer::Order.new(required_qty: required_qty).add(sku, count: count + 1)
45
49
  end
46
50
  end
@@ -51,12 +55,18 @@ class OrderOptimizer
51
55
  def count_and_remainder_for_sku(quantity, sku)
52
56
  count, remainder = quantity.divmod(sku.quantity)
53
57
 
54
- if sku.min_quantity && count * sku.quantity < sku.min_quantity
55
- new_count = (sku.min_quantity.to_f / sku.quantity).ceil
58
+ if sku.max_quantity && count * sku.quantity >= sku.max_quantity
59
+ new_count = (sku.max_quantity / sku.quantity).ceil
60
+ remainder = (count - new_count) * sku.quantity
61
+
62
+ [new_count, [remainder, 0].max, true]
63
+ elsif sku.min_quantity && count * sku.quantity < sku.min_quantity
64
+ new_count = (sku.min_quantity / sku.quantity).ceil
56
65
  remainder -= (new_count - count) * sku.quantity
57
- [new_count, [remainder, 0].max]
66
+
67
+ [new_count, [remainder, 0].max, false]
58
68
  else
59
- [count, remainder]
69
+ [count, remainder, false]
60
70
  end
61
71
  end
62
72
  end
@@ -7,13 +7,13 @@ Gem::Specification.new do |spec|
7
7
  spec.version = OrderOptimizer::VERSION
8
8
 
9
9
  spec.authors = ["crispymtn", "Martin Spickermann", "Maurice Vogel"]
10
- spec.email = ["op@crispymtn.com", "spickermann@gmail.com"]
11
- spec.homepage = "https://github.com/crispymtn/order_optimizer"
10
+ spec.email = ["op@zaikio.com", "spickermann@gmail.com"]
11
+ spec.homepage = "https://github.com/zaikio/order_optimizer"
12
12
  spec.license = "MIT"
13
13
  spec.summary = "Helps to optimize orders if the goods are offered in different pack sizes and in different discount levels."
14
14
 
15
15
 
16
- spec.metadata["changelog_uri"] = "https://github.com/crispymtn/order_optimizer/blob/master/CHANGELOG.md"
16
+ spec.metadata["changelog_uri"] = "https://github.com/zaikio/order_optimizer/blob/master/CHANGELOG.md"
17
17
  spec.metadata["homepage_uri"] = spec.homepage
18
18
  spec.metadata["source_code_uri"] = spec.homepage
19
19
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: order_optimizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - crispymtn
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2020-11-23 00:00:00.000000000 Z
13
+ date: 2021-11-29 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler
@@ -56,18 +56,18 @@ dependencies:
56
56
  version: '0'
57
57
  description:
58
58
  email:
59
- - op@crispymtn.com
59
+ - op@zaikio.com
60
60
  - spickermann@gmail.com
61
61
  executables: []
62
62
  extensions: []
63
63
  extra_rdoc_files: []
64
64
  files:
65
+ - ".circleci/config.yml"
65
66
  - ".gitignore"
66
67
  - ".travis.yml"
67
68
  - CHANGELOG.md
68
69
  - CODE_OF_CONDUCT.md
69
70
  - Gemfile
70
- - Gemfile.lock
71
71
  - LICENSE.txt
72
72
  - README.md
73
73
  - Rakefile
@@ -79,13 +79,13 @@ files:
79
79
  - lib/order_optimizer/sku.rb
80
80
  - lib/order_optimizer/version.rb
81
81
  - order_optimizer.gemspec
82
- homepage: https://github.com/crispymtn/order_optimizer
82
+ homepage: https://github.com/zaikio/order_optimizer
83
83
  licenses:
84
84
  - MIT
85
85
  metadata:
86
- changelog_uri: https://github.com/crispymtn/order_optimizer/blob/master/CHANGELOG.md
87
- homepage_uri: https://github.com/crispymtn/order_optimizer
88
- source_code_uri: https://github.com/crispymtn/order_optimizer
86
+ changelog_uri: https://github.com/zaikio/order_optimizer/blob/master/CHANGELOG.md
87
+ homepage_uri: https://github.com/zaikio/order_optimizer
88
+ source_code_uri: https://github.com/zaikio/order_optimizer
89
89
  post_install_message:
90
90
  rdoc_options: []
91
91
  require_paths:
@@ -101,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
101
  - !ruby/object:Gem::Version
102
102
  version: '0'
103
103
  requirements: []
104
- rubygems_version: 3.1.2
104
+ rubygems_version: 3.2.22
105
105
  signing_key:
106
106
  specification_version: 4
107
107
  summary: Helps to optimize orders if the goods are offered in different pack sizes
data/Gemfile.lock DELETED
@@ -1,22 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- order_optimizer (0.3.0)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- minitest (5.13.0)
10
- rake (13.0.1)
11
-
12
- PLATFORMS
13
- ruby
14
-
15
- DEPENDENCIES
16
- bundler
17
- minitest
18
- order_optimizer!
19
- rake
20
-
21
- BUNDLED WITH
22
- 2.1.4