ex-int-04-shipping-cost/readme.adoc
github-classroom[bot] 8a169467c9
add deadline
2025-05-09 06:26:04 +00:00

160 lines
No EOL
5.6 KiB
Text

[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/HUcMlwx_)
:sectnums:
:nofooter:
:toc: left
:icons: font
:data-uri:
:source-highlighter: highlightjs
:stem:
= Int.04 -- Shipping Costs
You are tasked with implementing a shipping cost calculation component of a webshop.
The store is based in Austria and ships to several other countries.
But the owner does not like their neighbors, so they decided to not ship to Austria 🙃
Three shipping options are available to the customers:
. Self-Pickup: come by the store and pick up your order
** Free of charge!
. Leonding Parcel Service (LPS)
** Fast, affordable, operated by always reliable students of the local HTL
. LuxuryShipping
** Boxes are only handled with single-use silk gloves by employees who attended a butler school in Switzerland
The handling fee of the shop itself, together with the actual billing of the product(s) is handled by a different component of the webshop, so you don't have to concern yourself with these matters.
You have worked out the following data model:
[plantuml]
----
@startuml
hide empty fields
hide empty methods
interface ICountryDistanceProvider {
+CountryDistanceInformation? GetDistanceTo(string countryName)
+bool IsPossibleCountry(string countryName)
}
struct CountryDistanceInformation {
+int ApproxDistance [readonly]
+bool IsDirectNeighbor [readonly]
+bool IsEuMember [readonly]
}
interface IMeasuredBox {
+int Width [readonly]
+int Height [readonly]
+int Depth [readonly]
+double Weight [readonly]
}
interface IShippingCostCalculator {
+string CarrierName [readonly]
+decimal? CalculateShippingCosts(string targetCountry, IMeasuredBox box)
}
class CountryDistanceProvider <<sealed>> {
{static} -Dictionary<string, CountryDistanceInformation> distanceDatabase [readonly]
}
class Package <<sealed>> {
-int _width [readonly]
-int _height [readonly]
-int _depth [readonly]
-double _weight [readonly]
{static} -T SanitizeValue<T>(T value) [T : INumber<T>]
}
class SelfPickup <<sealed>> {}
abstract class ShippingCostCalculatorBase {
#ICountryDistanceProvider CountryDistanceProvider [readonly]
#ShippingCostCalculatorBase(ICountryDistanceProvider countryDistanceProvider)
{static} #int SumOfLongestAndShortestSides(IMeasuredBox box)
}
class LPSShippingCostCalculator <<sealed>> {
+LPSShippingCostCalculator(ICountryDistanceProvider countryDistanceProvider)
-decimal CalcDistanceCost(string targetCountry)
}
class LuxuryShippingCostCalculator <<sealed>> {
+LuxuryShippingCostCalculator(ICountryDistanceProvider countryDistanceProvider)
}
ICountryDistanceProvider <|.. CountryDistanceProvider
IMeasuredBox <|.. Package
IShippingCostCalculator <|.. SelfPickup
IShippingCostCalculator <|.. ShippingCostCalculatorBase
ShippingCostCalculatorBase <|-- LPSShippingCostCalculator
ShippingCostCalculatorBase <|-- LuxuryShippingCostCalculator
ShippingCostCalculatorBase *-- ICountryDistanceProvider
@enduml
----
== Implementation
You have been provided with a skeleton code, unit tests and XMLDoc comments.
In addition, there is an almost complete `Program` which provides a simple UI.
Complete the application!
=== `CountryDistanceProvider`
* A _default_ country distance provider
* Contains information about all countries the store ships to
** Approximate distance (calculated as great-circle distance between the capital cities)
** If the country shares a border with Austria
** If the country is a member of the EU
* Do you feel comfortable with the dictionary initializer?
=== `Package`
* Simple implementation of `IMeasuredBox`
* Hint: the interface demands `get` properties, that does _not_ stop you from adding `init` setters
* The `SanitizeValue` method allows to ensure that numeric values are not stem:[<0]
** It can be used for both `int` & `double`
** To allow for this flexibility it is a _generic math_ method, with a type parameter constraint of `T : INumber<T>` => this ensures, that `T.Zero` exists for every `T`
=== `SelfPickup`
* A very simple shipping cost 'calculator' => self pickup is always free of charge
* The 'carrier name' is `Self Pickup`
=== `ShippingCostCalculatorBase`
* Base class for shipping providers which need to calculate shipping cost based on distance and package measurements
* `SumOfLongestAndShortestSides` determines the sum of the longest and shortest side
** Example: `120x190x340` => `460`
=== `LPSShippingCostCalculator`
* Can only ship to a country known to the distance provider
* Total shipping cost is calculated based on three parts:
.. Weight
*** Max. 20€
*** stem:[< 1.5"kg"]: 0€
*** stem:[< 2"kg"]: 0.5€
*** stem:[< 3"kg"]: 1.5€
*** stem:[>= 3"kg"]: stem:[x * 0.55]€
.. Size
*** stem:[< 250"mm"]: 0€
*** stem:[< 400"mm"]: 0.75€
*** stem:[< 650"mm"]: 1.25€
*** stem:[< 900"mm"]: 2.6€
*** stem:[>= 900"mm"]: 3.5€
.. Distance
*** Base price 2.99€
*** If _not_ an EU member +2.49€
*** Per 500km +1.1€ (added at least once)
**** If direct neighbor: 25% reduction of price per 500km
* The 'carrier name' is `Leonding Parcel Service`
=== `LuxuryShippingCostCalculator`
* Can only ship to a country known to the distance provider
* Total shipping cost is calculated as follows:
** Max. 4444€
** Box size cost (dimensions in centimeter): stem:[("height"*"width"*"depth")^2]
*** 'Länge mal Breite mal Höhe zum Quadrat'
** Total cost: stem:["km" * "boxSizeCost"]
* The 'carrier name' is `LuxuryShipping`
== Sample Run
Here a recording of a sample run of the program, including invalid input retries:
video::pics/sample_run.mp4[Sample Run, width=800px]