288 lines
No EOL
8.7 KiB
Text
288 lines
No EOL
8.7 KiB
Text
[](https://classroom.github.com/a/D1c8q0tI)
|
|
:sectnums:
|
|
:nofooter:
|
|
:toc: left
|
|
:icons: font
|
|
:data-uri:
|
|
:source-highlighter: highlightjs
|
|
:stem: latexmath
|
|
|
|
= Inh.05 -- Transport
|
|
|
|
You need to implement a management system for a vehicle rental business.
|
|
Customers can rent a vehicle for a trip.
|
|
For that they have to pay a fee which is dependent on various factors.
|
|
The vehicles incur costs for the company.
|
|
The goal is to make a profit!
|
|
|
|
The _special_ feature of this exercise is that you will read & create the most important data format for every company all over the world: **Excel documents** 😮
|
|
|
|
WARNING: The specifics of this assignment did not allow to provide extensive unit tests -- otherwise big parts of the implementation would have to be duplicated/visible in the tests. So you can't rely on tests, but have to check the output files to see if your implementation is looking good.
|
|
|
|
== Data Model
|
|
|
|
[plantuml]
|
|
----
|
|
@startuml
|
|
hide empty methods
|
|
|
|
abstract class Vehicle {
|
|
-List<Ride> _rides [readonly]
|
|
-RentalInfo? _rentalInfo
|
|
{abstract} +decimal BasePrice [readonly]
|
|
{abstract} +decimal PricePerKM [readonly]
|
|
{abstract} +decimal CostPerKM [readonly]
|
|
+Color Color [readonly]
|
|
+int Weight [readonly]
|
|
+int Id [readonly]
|
|
+double Mileage [private set]
|
|
+bool IsFree [readonly]
|
|
|
|
#Vehicle(int, Color, int, double)
|
|
+bool Rent(string, double)
|
|
+(bool, decimal?) EndRide()
|
|
+void AddTravelLogSheet(IXLWorkbook)
|
|
{static} #ExcelData? ProcessExcelRow(IXLRow, int)
|
|
{static} #List<IXLRow> GetRowsFromWorksheet(IXLWorksheet)
|
|
{static} #bool TryParseDoubleInvariant(string?, out double)
|
|
{static} #int GetColumnsOfWorksheet(IXLWorksheet)
|
|
}
|
|
class Car <<sealed>> {
|
|
+string WorksheetName [const]
|
|
-decimal BaseCostPerKM
|
|
{static} -Dictionary<CarBrand, double> brandCoolness [readonly]
|
|
+CarBrand Brand [readonly]
|
|
+double Power [readonly]
|
|
|
|
{static} +List<Car> CreateFromWorksheet(IXLWorksheet)
|
|
}
|
|
class Rickshaw <<sealed>> {
|
|
+string WorksheetName [const]
|
|
|
|
{static} +List<Rickshaw> CreateFromWorksheet(IXLWorksheet)
|
|
}
|
|
class Ride <<record,sealed>> {
|
|
+string CustomerName [readonly]
|
|
+Vehicle Vehicle [readonly]
|
|
+double StartMileage [readonly]
|
|
+double EndMileage [readonly]
|
|
+double Distance [readonly]
|
|
+decimal TotalCost [readonly]
|
|
+decimal TotalPrice [readonly]
|
|
|
|
-decimal TotalForDistance(decimal)
|
|
}
|
|
class TravelManagement <<sealed>> {
|
|
+double MaxRickshawDistance [const]
|
|
-List<Vehicle> _vehicles [readonly]
|
|
|
|
+TravelManagement()
|
|
+bool InitializeFromFile(string)
|
|
+Vehicle? RentVehicle<TPreferredVehicle>(string, double)
|
|
+IXLWorkbook GetTravelLog()
|
|
}
|
|
enum Color {
|
|
Red
|
|
Blue
|
|
White
|
|
Grey
|
|
Black
|
|
}
|
|
enum CarBrand
|
|
{
|
|
AlfaRomeo
|
|
BMW
|
|
Dacia
|
|
VW
|
|
}
|
|
|
|
Vehicle <|-- Car
|
|
Vehicle <|-- Rickshaw
|
|
Vehicle o-- Ride
|
|
Ride *-- Vehicle
|
|
Vehicle *-- Color
|
|
Car *-u- CarBrand
|
|
TravelManagement o-r- Vehicle
|
|
note top of Vehicle : + Equality members
|
|
|
|
@enduml
|
|
----
|
|
|
|
== Vehicles
|
|
Currently, two types of vehicles are available:
|
|
|
|
* Rickshaws
|
|
* Cars
|
|
|
|
[cols="a,a",frame=none, grid=none]
|
|
|===
|
|
| image::pics/rickshaw.png[Rickshaw]
|
|
| image::pics/car.jpg[Car]
|
|
|===
|
|
|
|
Each vehicle has the following properties:
|
|
|
|
* `Id`
|
|
** Unique
|
|
** Cannot be stem:[<0]
|
|
* `Color`
|
|
* `Weight`
|
|
** Cannot be stem:[<0]
|
|
* `Mileage`
|
|
** Initial mileage before rides are performed
|
|
*** => will change after each ride
|
|
** Cannot be stem:[<0]
|
|
|
|
=== Rickshaw
|
|
* Has no additional properties
|
|
* Has the following, _fixed_ values:
|
|
** `BasePrice`: 2
|
|
** `PricePerKM`: 2.5
|
|
** `CostPerKM`: 2.2
|
|
|
|
=== Car
|
|
* Has the following, additional properties:
|
|
** `Brand`
|
|
** `Power` (engine power in kw)
|
|
*** Cannot be stem:[<=0]
|
|
* Has the following, _dynamic_ values:
|
|
** `BasePrice`: stem:[3 * CoolnessOfBrand]
|
|
** `PricePerKM`: stem:[CostPerKM * 1.05]
|
|
*** We want to make a profit after all
|
|
** `CostPerKM`: stem:[BaseCostPerKM * (\frac{engineFactor + weightFactor}{100} + 1)]
|
|
|
|
.CoolnessOfBrand
|
|
|===
|
|
|Brand |Coolness
|
|
|
|
|AlfaRomeo
|
|
|stem:[1.6]
|
|
|
|
|BMW
|
|
|stem:[1.1]
|
|
|
|
|Dacia
|
|
|stem:[1]
|
|
|
|
|VW
|
|
|stem:[0.6]
|
|
|===
|
|
|
|
.engineFactor
|
|
|===
|
|
|Power |Factor
|
|
|
|
|stem:[<= 66]
|
|
|stem:[0]
|
|
|
|
|stem:[<= 88]
|
|
|stem:[5]
|
|
|
|
|stem:[<= 110]
|
|
|stem:[10]
|
|
|
|
|stem:[> 110]
|
|
|stem:[20]
|
|
|===
|
|
|
|
.weightFactor
|
|
|===
|
|
|Weight |Factor
|
|
|
|
|stem:[< 1000]
|
|
|stem:[0]
|
|
|
|
|stem:[< 1500]
|
|
|stem:[10]
|
|
|
|
|stem:[< 2000]
|
|
|stem:[15]
|
|
|
|
|stem:[< 2400]
|
|
|stem:[20]
|
|
|
|
|stem:[>= 2400]
|
|
|stem:[25]
|
|
|===
|
|
|
|
== Excel
|
|
As stated before you'll have to read & write Excel files.
|
|
That is important, because almost every clerk knows Excel, so the most request import & export format in any application is always 'Excel!' -- no matter if there would be technically better suited formats.
|
|
There is also an old joke 'no matter how much code we write, the world continues to run on Excel'.
|
|
|
|
TIP: Try to open any `*.xlsx` File in a text editor: you'll realize that will be harder to process than CSV.
|
|
|
|
Contrary to simple text (or binary) files like CSV, Excel files allow for advanced formatting.
|
|
Each cell can for example have a type (e.g. format as number, as text, as date,...), a style (e.g. bold, italics, font, font size,...) or even a formula (e.g. SUM,...).
|
|
Columns, rows and cells can also be addressed by 'name', e.g. `A4`.
|
|
And finally, one file (= workbook) can contain multiple worksheets.
|
|
For this assignment we'll stick to basic features, but knowing how to read & create such complex files will be a valuable skill for you to have.
|
|
|
|
To process Excel file we are going to use a free & open source library called https://github.com/ClosedXML/ClosedXML[ClosedXML].
|
|
The appropriate https://www.nuget.org/packages/ClosedXML/[NuGet] package has already been added to your starter project.
|
|
However, learning how to use the API is mostly up to you -- _it might be a good idea to read the documentation_ 😉.
|
|
Several methods already accept or return types you'll need (e.g. `IXLWorksheet`), these are a good starting point for your research.
|
|
|
|
== Program Description
|
|
|
|
The program runs through the following steps:
|
|
|
|
. Vehicle data is read from the file `vehicles.xlsx`
|
|
** Each worksheet contains data for one type of vehicle
|
|
** Invalid rows are skipped
|
|
. Several rental operations are performed
|
|
** The ride information is stored by each vehicle
|
|
. The travel log is written to a `travel-log-{date&time}.xlsx` file
|
|
** The workbook contains multiple sheets -- one for each vehicle
|
|
*** Only vehicles with at least one ride create a sheet
|
|
** Each sheet contains the rentals/rides of one vehicle
|
|
|
|
=== Implementation Hints & Requirements
|
|
|
|
A couple of additional explanations for several, more complex methods:
|
|
|
|
* `Vehicle.EndRide`
|
|
** Ending an active rental (ride) adds this ride to the list of rides and increases the mileage accordingly
|
|
** The vehicle is then free to be rented again
|
|
* `Vehicle.GetColumnsOfWorksheet`
|
|
** Returns the number of _used_ columns in the given sheet
|
|
** A column is used if any of its cells has a non-empty value
|
|
* `TravelManagement.InitializeFromFile`
|
|
** Populates the internal list of vehicles
|
|
** Make use of the `CreateFromWorksheet` methods
|
|
** You might want to use https://learn.microsoft.com/en-us/dotnet/api/system.io.file.exists[`File.Exists`] to check if a file exists
|
|
* `TravelManagement.RentVehicle`
|
|
** Selects a _free_ vehicle of the specified type with the _lowest_ mileage of all vehicles of this type => the least worn vehicles are preferred
|
|
*** If the required distance exceeds the limit for a `Rickshaw` a `Car` is selected instead
|
|
** You can use `typeof(TPreferredVehicle)` to get the requested type
|
|
*** If you are unsure what that operator does https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/type-testing-and-cast#typeof-operator[read up on it]
|
|
** To find out the _runtime type_ of an _instance_ https://learn.microsoft.com/en-us/dotnet/api/system.object.gettype?view=net-8.0[use the `GetType()`] method
|
|
|
|
TIP: Also carefully read the provided XMLDoc, it contains valueable information
|
|
|
|
== Tasks
|
|
|
|
. Learn how to use the ClosedXML library
|
|
. Complete the application
|
|
. Ensure that the sample run in `Program` leads to a result as defined below
|
|
** This includes the _formatting_ in the Excel file!
|
|
|
|
=== Sample Run
|
|
|
|
Executing `Program` should result in this console output:
|
|
|
|
[source]
|
|
----
|
|
*** Transport ***
|
|
Total cost of all rides: € 1.114,15
|
|
Travel log saved to: <path-to-file>\travel-log-<date&time>.xlsx
|
|
----
|
|
|
|
The created travel log file should contain the following worksheets with these values and the proper formatting:
|
|
|
|
image::pics/travel_log_01.png[TravelLog]
|
|
image::pics/travel_log_02.png[TravelLog]
|
|
image::pics/travel_log_03.png[TravelLog]
|
|
image::pics/travel_log_04.png[TravelLog]
|
|
image::pics/travel_log_05.png[TravelLog]
|
|
image::pics/travel_log_06.png[TravelLog] |