1
0
Go to file
2024-08-25 10:16:28 +02:00
.github/workflows Remove publishing task 2021-11-22 08:44:26 +01:00
bin Add command line program to process a file 2024-08-25 10:16:28 +02:00
lib Apply linting rules 2024-08-25 09:28:20 +02:00
spec Apply linting rules 2024-08-25 09:28:20 +02:00
.gitignore Add gemfile.lock to ignored files 2024-08-24 22:26:03 +02:00
.rspec First commit 2021-03-05 21:02:21 +01:00
.standard.yml Ignore performance count lint rule 2024-08-25 09:28:12 +02:00
.tool-versions Add tool versions file with ruby version 2024-08-24 22:28:15 +02:00
CODE_OF_CONDUCT.md First commit 2021-03-05 21:02:21 +01:00
Gemfile Apply linting rules 2024-08-25 09:28:20 +02:00
LICENSE.txt Update year in license 2024-08-25 09:20:22 +02:00
planet_express.gemspec Apply linting rules 2024-08-25 09:28:20 +02:00
Rakefile Apply linting rules 2024-08-25 09:28:20 +02:00
README.md Update readme links 2024-03-30 10:00:17 +01:00

PlanetExpress

Planet Express is your little helper to calculate possible discounts for your shipments based on a given rule set and an available discount budget.

Installation

Get the code from github and run bundle install

Afterwards you are ready to rumble.

I recommend ruby 2.6.6 or MRI 2.7 to run the code. Other ruby versions should work as well, but are not officially supported.

Usage

The input file has to adhere to the following format:

# ISO8601Date PackageSize Provider
2015-02-01 S PR

Now that you have an input file like that you can run the processing with the following rake task:

$ bundle exec rake 'process[path/to/input_file.txt]'

# 2015-02-01 S MR 1.50 0.50
# 2015-02-02 S MR 1.50 0.50
# 2015-02-03 L LP 6.90 -
# 2015-02-05 S LP 1.50 -
# 2015-02-06 S MR 1.50 0.50
# 2015-02-06 L LP 6.90 -
# 2015-02-07 L MR 4.00 -
# 2015-02-08 M MR 3.00 -
# 2015-02-09 L LP 0.00 6.90
# 2015-02-10 L LP 6.90 -
# 2015-02-10 S MR 1.50 0.50
# 2015-02-10 S MR 1.50 0.50
# 2015-02-11 L LP 6.90 -
# 2015-02-12 M MR 3.00 -
# 2015-02-13 M LP 4.90 -
# 2015-02-15 S MR 1.50 0.50
# 2015-02-17 L LP 6.90 -
# 2015-02-17 S MR 1.90 0.10
# 2015-02-24 L LP 6.90 -
# 2015-02-29 CUSPS IGNORED
# 2015-03-01 S MR 1.50 0.50

Architecture

The system is divided into four big groups:

  • Discounts - provides a class to represent discounts as well as a budget to keep track of the discount budgets for a given month
  • Shipments - Basic representation of a shipment as in "an order that will be shipped"
  • ShippingOptions - Struct and repository to represent providers and their shipping options
  • Rules - contains all the logic for discount rules including a methods to apply discounts to shipments

Discounts

The discount class represents a discount that originated from a rule and contains the monetary amount as well as the rule that was responsible for applying the discount.

Use the DiscountBudgetForMonthRepository to get the discount budget for a given month.

Shipments

Shipments are the input for all calculation and have a date and a reference to a shipping option. They also keep track of their applied discounts.

Important: There's a distinction between a valid shipment (one that was parsed correctly) and an invalid shipment. To not make you constantly check whether you handle a valid shipment or an invalid one, you can use the if_valid helper.

shipment.if_valid do |valid_shipment|
  # code that is only executed in case you have a valid shipment on hand
end

The package also includes a ShipmentReader that adheres to a simple enumerable interface in order to decouple the IO/parsing from the remaining processing. If you don't need it you can replace all instances of a ShipmentReader with a simple array.

To provide structure output you can use the ShipmentFormatter which formats a given Shipment in a CSV (whitespace separated) format with their discounts.

Shipping Options

Shipping options are a struct to represent an individual shipping option, it includes the price, the provider as well as the package size.

Use the ShippingOptionRepository to retrieve shipping options and query for specific providers/package sizes.

ShipmentDiscountCalculator

This class is the central entry point for calculating discounts for a list of shipments. It takes a shipment_repository and a list of rules and applies the list of rules to the given shipments

Example:

calculator = ShipmentDiscountCalculator.new(
  shipment_repository: ShipmentRepository.new(data: []),
  rules: [Rule.new(...)])

calculator.run([Shipment.new])

Repository

The repository base class is the managing unit to retrieve and store shared state. When possible nobody should query the data directly, but define a query method in the repository to retrieve the required data.

It provides an ActiveRecord like where method with which you can query for multiple attributes in one call:

repo.where(attribute_a: 5, attribute_b: "Test") # => enumerable matching the criteria

Money Handling

In order to prevent floating errors when calculating discounts the system uses integers and all prices are defined including the cents, so 1.5€ are represented as 150.

Development

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.

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.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the PlanetExpress project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.