2425-2ihif-pose-classroom-ex-int-04-shipping-cost-ex-int-04-shipping-template created by GitHub Classroom
Find a file
github-classroom[bot] 8a169467c9
add deadline
2025-05-09 06:26:04 +00:00
pics Initial commit 2025-04-29 15:01:53 +00:00
ShippingCosts Initial commit 2025-04-29 15:01:53 +00:00
ShippingCosts.Test Initial commit 2025-04-29 15:01:53 +00:00
.editorconfig Initial commit 2025-04-29 15:01:53 +00:00
.gitignore Initial commit 2025-04-29 15:01:53 +00:00
readme.adoc add deadline 2025-05-09 06:26:04 +00:00
ShippingCosts.sln Initial commit 2025-04-29 15:01:53 +00:00

[![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]