use fixed tarifs and add additional costs

This commit is contained in:
Thomas Ruoff
2020-10-10 00:39:29 +02:00
parent da91a9b09c
commit 396e4b0a86
7 changed files with 185 additions and 89 deletions

View File

@@ -1,10 +1,6 @@
import React from 'react'
export default function Header({
label = 'Pfadi Bussle Buchen',
}: {
label?: string
}) {
export default function Header({ label = 'Pfadi Bussle' }: { label?: string }) {
return (
<div className="flex flex-row items-center p-3 my-3 text-white bg-gray-600 rounded-sm">
<h1 className="flex-grow text-3xl">{label}</h1>

View File

@@ -1,7 +1,8 @@
import * as mongoose from 'mongoose'
import { BILL_STATUS, MILAGE_RATES, getMilageRateValue } from './enums'
import { BILL_STATUS, MILAGE_TARIFS } from './enums'
import { getBillTotal } from '../helpers/bill'
export interface AdditionalCosts {
export interface AdditionalCost {
name: string
value: number
}
@@ -12,9 +13,9 @@ export interface BillDocument
milageStart: number
milageEnd: number
milage?: number
rate: MILAGE_RATES
tarif: MILAGE_TARIFS
status: BILL_STATUS
additionalCosts: AdditionalCosts[]
additionalCosts: AdditionalCost[]
}
export interface BillModel extends mongoose.Model<BillDocument> {}
@@ -46,10 +47,10 @@ const BillSchema = new mongoose.Schema<BillDocument>(
message: (props) => `${props.value} is smaller than milageStart!`,
},
},
rate: {
tarif: {
type: String,
enum: Object.values(MILAGE_RATES),
default: MILAGE_RATES.EXTERN_LTE_200,
enum: Object.values(MILAGE_TARIFS),
default: MILAGE_TARIFS.EXTERN,
required: true,
},
additionalCosts: [
@@ -79,13 +80,7 @@ BillSchema.virtual('milage').get(function () {
BillSchema.virtual('total').get(function () {
const bill = this as BillDocument
const milageCosts =
Math.round(bill.milage * getMilageRateValue(bill.rate) * 100) / 100
const additionalCostSum = bill.additionalCosts
.map(({ value }) => value)
.reduce((acc, value) => acc + value, 0)
return milageCosts + additionalCostSum
return getBillTotal(bill)
})
export default <BillModel>mongoose.models.Bill ||

View File

@@ -11,30 +11,8 @@ export enum BILL_STATUS {
PAID = 'paid',
}
export enum MILAGE_RATES {
INTERN_LTE_200 = 'intern_lte_200_km',
INTERN_201_1000 = 'intern_201_1000_km',
INTERN_1001_2000 = 'intern_1001_2000_km',
INTERN_GTE_2001 = 'intern_gte_2001_km',
EXTERN_LTE_200 = 'extern_lte_200_km',
EXTERN_201_1000 = 'extern_201_1000_km',
EXTERN_1001_2000 = 'extern_1001_2000_km',
EXTERN_GTE_2001 = 'extern_gte_2001_km',
FREE_OF_CHARGE = 'free_of_charge',
}
const rates = {
[MILAGE_RATES.INTERN_LTE_200]: 0.37,
[MILAGE_RATES.INTERN_201_1000]: 0.22,
[MILAGE_RATES.INTERN_1001_2000]: 0.15,
[MILAGE_RATES.INTERN_GTE_2001]: 0.13,
[MILAGE_RATES.EXTERN_LTE_200]: 0.42,
[MILAGE_RATES.EXTERN_201_1000]: 0.25,
[MILAGE_RATES.EXTERN_1001_2000]: 0.2,
[MILAGE_RATES.EXTERN_GTE_2001]: 0.18,
[MILAGE_RATES.FREE_OF_CHARGE]: 0,
}
export function getMilageRateValue(milageRate: MILAGE_RATES): number {
return rates[milageRate]
export enum MILAGE_TARIFS {
INTERN = 'intern',
EXTERN = 'extern',
NOCHARGE = 'nocharge',
}

65
helpers/bill.ts Normal file
View File

@@ -0,0 +1,65 @@
import { MILAGE_TARIFS } from '../db/enums'
import { AdditionalCost } from '../db/bill'
function roundToCent(amount: number) {
return Math.round(amount * 100) / 100
}
export function getMilageCosts(tarif: MILAGE_TARIFS, km: number): number {
if (tarif === MILAGE_TARIFS.NOCHARGE) {
return 0
}
if (km <= 0) {
return 0
}
let rate: number
if (tarif === MILAGE_TARIFS.EXTERN) {
if (km <= 200) {
rate = 0.42
} else if (km <= 1000) {
rate = 0.25
} else if (km <= 2000) {
rate = 0.2
} else {
rate = 0.18
}
}
if (tarif === MILAGE_TARIFS.INTERN) {
if (km <= 200) {
rate = 0.37
} else if (km <= 1000) {
rate = 0.22
} else if (km <= 2000) {
rate = 0.15
} else {
rate = 0.13
}
}
if (rate === undefined) {
throw Error('Unable to determine rate')
}
return roundToCent(km * rate)
}
export function getBillTotal({
tarif,
milage,
additionalCosts,
}: {
tarif: MILAGE_TARIFS
milage?: number
additionalCosts: AdditionalCost[]
}): number {
const milageCosts = getMilageCosts(tarif, milage)
const additionalCostsSum = additionalCosts
.map(({ value }) => value)
.reduce((acc: number, value: number) => acc + value, 0)
return roundToCent(milageCosts + additionalCostsSum)
}

View File

@@ -4,20 +4,17 @@ import Footer from '../../../components/footer'
import Header from '../../../components/header'
import Input from '../../../components/input'
import Select from '../../../components/select'
import { AdditionalCosts, BillDocument } from '../../../db/bill'
import { AdditionalCost, BillDocument } from '../../../db/bill'
import { BookingDocument } from '../../../db/booking'
import {
BILL_STATUS,
MILAGE_RATES,
getMilageRateValue,
} from '../../../db/enums'
import { BILL_STATUS, MILAGE_TARIFS } from '../../../db/enums'
import { getBookingByUUID, getMilageMax } from '../../../db/index'
import { dateFormatFrontend } from '../../../helpers/date'
import { getBillTotal } from '../../../helpers/bill'
const milageRateOptions = Object.values(MILAGE_RATES).map((rate) => {
const milageTarifOptions = Object.values(MILAGE_TARIFS).map((tarif) => {
return (
<option value={rate} key={rate}>
{getRateLabel(rate)} ({getMilageRateValue(rate)} EUR)
<option value={tarif} key={tarif}>
{getTarifLabel(tarif)}
</option>
)
})
@@ -54,26 +51,14 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
}
}
function getRateLabel(rate: MILAGE_RATES) {
switch (rate) {
case MILAGE_RATES.INTERN_LTE_200:
return 'Intern bis zu 200km'
case MILAGE_RATES.INTERN_201_1000:
return 'Intern 201-1.000km'
case MILAGE_RATES.INTERN_1001_2000:
return 'Intern 1.001-2.000km'
case MILAGE_RATES.INTERN_GTE_2001:
return 'Intern ab 2.001km'
case MILAGE_RATES.EXTERN_LTE_200:
return 'Extern bis zu 200km'
case MILAGE_RATES.EXTERN_201_1000:
return 'Extern 201-1.000km'
case MILAGE_RATES.EXTERN_1001_2000:
return 'Extern 1.001-2.000km'
case MILAGE_RATES.EXTERN_GTE_2001:
return 'Extern ab 2.001km'
case MILAGE_RATES.FREE_OF_CHARGE:
return 'Frei'
function getTarifLabel(tarif: MILAGE_TARIFS) {
switch (tarif) {
case MILAGE_TARIFS.EXTERN:
return 'Extern'
case MILAGE_TARIFS.INTERN:
return 'Intern'
case MILAGE_TARIFS.NOCHARGE:
return 'Frei von Kosten'
default:
return 'Keine'
}
@@ -98,8 +83,8 @@ async function saveBill(
milageStart: number
milageEnd: number
milage?: number
rate: MILAGE_RATES
additionalCosts?: AdditionalCosts[]
tarif: MILAGE_TARIFS
additionalCosts: AdditionalCost[]
status: BILL_STATUS
}
): Promise<BillDocument> {
@@ -129,17 +114,16 @@ export default function BillPage({
booking.bill?.milageStart || milageMax
)
const [milageEnd, setMilageEnd] = useState(booking.bill?.milageEnd)
const [rate, setRate] = useState(
booking.bill?.rate || MILAGE_RATES.EXTERN_LTE_200
const [tarif, setTarif] = useState(
booking.bill?.tarif || MILAGE_TARIFS.EXTERN
)
const [status, setStatus] = useState(booking.bill?.status)
const [additionalCosts, setAdditionalCosts] = useState([])
const [storingInProgress, setStoringInProgress] = useState(false)
const [storingError, setStoringError] = useState(null)
const milage =
(0 < milageStart && milageStart < milageEnd && milageEnd - milageStart) || 0
const total =
Math.round(milage && rate && milage * getMilageRateValue(rate) * 100) /
100 || 0
const total = getBillTotal({ tarif, milage, additionalCosts })
// in case the props change, update the internal state
useEffect(() => setBooking(bookingProp), [bookingProp])
@@ -154,8 +138,9 @@ export default function BillPage({
milageStart,
milageEnd,
milage,
rate,
tarif,
status,
additionalCosts,
})
booking.bill = bill
@@ -167,11 +152,29 @@ export default function BillPage({
setStoringInProgress(false)
}
const onAddAdditionalCost = function (
event: React.MouseEvent<HTMLButtonElement>
) {
event.preventDefault()
setAdditionalCosts([...additionalCosts, { name: '', value: 0 }])
}
const onRemoveAdditionalCost = function (
event: React.MouseEvent<HTMLButtonElement>,
index: number
) {
event.preventDefault()
setAdditionalCosts([
...additionalCosts.slice(0, index),
...additionalCosts.slice(index + 1),
])
}
return (
<div className="mx-3 flex flex-col min-h-screen">
<Header />
<main className="flex-grow">
<h2 className="text-3xl">Pfadi Bussle Buchung</h2>
<h2 className="text-3xl">Abrechnung</h2>
<form className="form" onSubmit={onSubmit}>
<div>
<strong>Buchungszeitraum:</strong>{' '}
@@ -205,10 +208,10 @@ export default function BillPage({
<Input label="Gefahren" name="milage" readOnly value={milage} />
<Select
label="Rate"
value={rate}
onChange={(e) => setRate(e.target.value as MILAGE_RATES)}
value={tarif}
onChange={(e) => setTarif(e.target.value as MILAGE_TARIFS)}
>
{milageRateOptions}
{milageTarifOptions}
</Select>
<Select
label="Status"
@@ -217,7 +220,66 @@ export default function BillPage({
>
{bookingStatusOptions}
</Select>
<Input label="Summe" name="milage" readOnly value={total} />
<div className="mb-3">
<button
className="ibtn btn-gray mr-3"
onClick={onAddAdditionalCost}
title="Zusätzliche Kosten hinzufügen"
>
+
</button>
<label className="flabel inline">Zusätzliche Kosten</label>
</div>
{additionalCosts.map((_, index) => {
return (
<>
<div className="mb-3" key={`label${index}`}>
<button
className="ibtn btn-gray mr-3"
onClick={(event) => onRemoveAdditionalCost(event, index)}
title="Entfernen"
>
-
</button>
<label className="flabel inline">{`Kostenpunkt ${
index + 1
}`}</label>
</div>
<div className="ml-10 mb-3" key={`input{index}`}>
<Input
label={`Name`}
name={`additionalCostName${index}`}
key={`additionalCostName${index}`}
value={additionalCosts[index].name}
onChange={(event) => {
const newAdditonalCosts = [...additionalCosts]
newAdditonalCosts[index] = {
value: newAdditonalCosts[index].value,
name: event.target.value,
}
setAdditionalCosts(newAdditonalCosts)
}}
/>
<Input
label={`Betrag`}
name={`additionalCostValue${index}`}
key={`additionalCostValue${index}`}
value={additionalCosts[index].value}
type="number"
onChange={(event) => {
const newAdditonalCosts = [...additionalCosts]
newAdditonalCosts[index] = {
name: newAdditonalCosts[index].name,
value: Number(event.target.value),
}
setAdditionalCosts(newAdditonalCosts)
}}
/>
</div>
</>
)
})}
<Input label="Summe" name="total" readOnly value={total} />
</div>
{storingError && (
<div className="error-message flex-grow">{storingError}</div>

View File

@@ -94,7 +94,7 @@ export default function ShowBooking({
<div className="mx-3 flex flex-col min-h-screen">
<Header />
<main className="flex-grow">
<h2 className="text-3xl">Ihre Pfadi Bussle Buchung</h2>
<h2 className="text-3xl">Ihre Buchung</h2>
<div>
<strong>Buchungsstatus:</strong> {getBookingStatus(booking)}
</div>

View File

@@ -60,7 +60,7 @@
}
.ibtn {
@apply py-2 px-2 text-gray-400 w-10 h-10;
@apply p-1 text-gray-400 text-xs w-6 h-6 rounded;
}
.ibtn:hover {