From df12cee1ef8d74b6868a427790158d1752defbfa Mon Sep 17 00:00:00 2001 From: agentd Date: Tue, 4 Aug 2026 23:17:03 +0000 Subject: [PATCH] Preisrechner: Rappenrundung im Backend und Rabatt-Rechner im Web agentd task d23b198e9bef4e0c99f52c532c317f70 --- src/Prices/PriceCalculator.cs | 5 +- tests/Prices.Tests/PriceCalculatorTests.cs | 29 +- web/src/app/app.html | 345 +----------------- web/src/app/app.routes.ts | 8 +- web/src/app/app.spec.ts | 9 +- .../discount-calculator.html | 39 ++ .../discount-calculator.scss | 79 ++++ .../discount-calculator.spec.ts | 44 +++ .../discount-calculator.ts | 30 ++ 9 files changed, 238 insertions(+), 350 deletions(-) create mode 100644 web/src/app/discount-calculator/discount-calculator.html create mode 100644 web/src/app/discount-calculator/discount-calculator.scss create mode 100644 web/src/app/discount-calculator/discount-calculator.spec.ts create mode 100644 web/src/app/discount-calculator/discount-calculator.ts diff --git a/src/Prices/PriceCalculator.cs b/src/Prices/PriceCalculator.cs index c2b9cb3..522a52a 100644 --- a/src/Prices/PriceCalculator.cs +++ b/src/Prices/PriceCalculator.cs @@ -16,6 +16,7 @@ public static class PriceCalculator throw new ArgumentOutOfRangeException(nameof(discountPercent), "A discount is between 0 and 100 percent."); } - return price - discountPercent; + var raw = price * (100 - discountPercent) / 100; + return Math.Round(raw / 0.05m, MidpointRounding.AwayFromZero) * 0.05m; } -} +} \ No newline at end of file diff --git a/tests/Prices.Tests/PriceCalculatorTests.cs b/tests/Prices.Tests/PriceCalculatorTests.cs index c49c29a..ff2c6b1 100644 --- a/tests/Prices.Tests/PriceCalculatorTests.cs +++ b/tests/Prices.Tests/PriceCalculatorTests.cs @@ -33,4 +33,31 @@ public sealed class PriceCalculatorTests { Assert.Throws(() => PriceCalculator.ApplyDiscount(100m, 101m)); } -} + + [Fact] + public void Swiss_rounding_example() + { + // 99.90 * 90 / 100 = 89.91, gerundet auf 0.05 = 89.90 + Assert.Equal(89.90m, PriceCalculator.ApplyDiscount(99.90m, 10m)); + } + + [Fact] + public void Swiss_rounding_exact() + { + // 100.00 * 85 / 100 = 85.00, exakt ein 0.05-Vielfaches + Assert.Equal(85.00m, PriceCalculator.ApplyDiscount(100.00m, 15m)); + } + + [Fact] + public void Swiss_rounding_midpoint_up() + { + // 10.00 * 95 / 100 = 9.50, exakt + Assert.Equal(9.50m, PriceCalculator.ApplyDiscount(10.00m, 5m)); + } + + [Fact] + public void Negative_price_throws() + { + Assert.Throws(() => PriceCalculator.ApplyDiscount(-1m, 10m)); + } +} \ No newline at end of file diff --git a/web/src/app/app.html b/web/src/app/app.html index a1c4296..410663c 100644 --- a/web/src/app/app.html +++ b/web/src/app/app.html @@ -1,344 +1,3 @@ - - - - - - - - - - -
-
-
- -

Hello, {{ title() }}

-

Congratulations! Your app is running. 🎉

-
- -
-
- @for (item of [ - { title: 'Explore the Docs', link: 'https://angular.dev' }, - { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, - { title: 'Prompt and best practices for AI', link: 'https://angular.dev/ai/develop-with-ai'}, - { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, - { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, - { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, - ]; track item.title) { - - {{ item.title }} - - - - - } -
- -
-
-
- - - - - - - - - - - + + \ No newline at end of file diff --git a/web/src/app/app.routes.ts b/web/src/app/app.routes.ts index dc39edb..b3e7cc1 100644 --- a/web/src/app/app.routes.ts +++ b/web/src/app/app.routes.ts @@ -1,3 +1,9 @@ import { Routes } from '@angular/router'; +import { DiscountCalculator } from './discount-calculator/discount-calculator'; -export const routes: Routes = []; +export const routes: Routes = [ + { + path: '', + component: DiscountCalculator, + }, +]; \ No newline at end of file diff --git a/web/src/app/app.spec.ts b/web/src/app/app.spec.ts index 92618e6..b67bb85 100644 --- a/web/src/app/app.spec.ts +++ b/web/src/app/app.spec.ts @@ -1,10 +1,13 @@ import { TestBed } from '@angular/core/testing'; import { App } from './app'; +import { provideRouter } from '@angular/router'; +import { routes } from './app.routes'; describe('App', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [App], + providers: [provideRouter(routes)], }).compileComponents(); }); @@ -14,10 +17,10 @@ describe('App', () => { expect(app).toBeTruthy(); }); - it('should render title', async () => { + it('should render the router outlet', async () => { const fixture = TestBed.createComponent(App); await fixture.whenStable(); const compiled = fixture.nativeElement as HTMLElement; - expect(compiled.querySelector('h1')?.textContent).toContain('Hello, web'); + expect(compiled.querySelector('router-outlet')).toBeTruthy(); }); -}); +}); \ No newline at end of file diff --git a/web/src/app/discount-calculator/discount-calculator.html b/web/src/app/discount-calculator/discount-calculator.html new file mode 100644 index 0000000..f1d0b40 --- /dev/null +++ b/web/src/app/discount-calculator/discount-calculator.html @@ -0,0 +1,39 @@ +
+

Rabattrechner

+ +
+ + +
+ +
+ + +
+ + + + @if (result() !== null) { +
+ Reduzierter Preis: + {{ result()!.toFixed(2) }} CHF +
+ } +
\ No newline at end of file diff --git a/web/src/app/discount-calculator/discount-calculator.scss b/web/src/app/discount-calculator/discount-calculator.scss new file mode 100644 index 0000000..01827a7 --- /dev/null +++ b/web/src/app/discount-calculator/discount-calculator.scss @@ -0,0 +1,79 @@ +.calculator { + max-width: 400px; + margin: 2rem auto; + padding: 2rem; + border: 1px solid #ddd; + border-radius: 8px; + background: #fafafa; + + h2 { + margin-top: 0; + margin-bottom: 1.5rem; + font-size: 1.5rem; + color: #333; + } + + .field { + margin-bottom: 1rem; + + label { + display: block; + margin-bottom: 0.25rem; + font-weight: 500; + color: #555; + } + + input { + width: 100%; + padding: 0.5rem; + border: 1px solid #ccc; + border-radius: 4px; + font-size: 1rem; + box-sizing: border-box; + + &:focus { + outline: none; + border-color: #007bff; + box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); + } + } + } + + .btn { + display: inline-block; + padding: 0.5rem 1.5rem; + font-size: 1rem; + color: #fff; + background: #007bff; + border: none; + border-radius: 4px; + cursor: pointer; + margin-top: 0.5rem; + + &:hover { + background: #0056b3; + } + } + + .result { + margin-top: 1.5rem; + padding: 1rem; + background: #e8f5e9; + border: 1px solid #c8e6c9; + border-radius: 4px; + text-align: center; + + .result-label { + display: block; + font-size: 0.9rem; + color: #555; + margin-bottom: 0.25rem; + } + + .result-value { + font-size: 1.75rem; + font-weight: 700; + color: #2e7d32; + } + } +} \ No newline at end of file diff --git a/web/src/app/discount-calculator/discount-calculator.spec.ts b/web/src/app/discount-calculator/discount-calculator.spec.ts new file mode 100644 index 0000000..18b0964 --- /dev/null +++ b/web/src/app/discount-calculator/discount-calculator.spec.ts @@ -0,0 +1,44 @@ +import { TestBed } from '@angular/core/testing'; +import { DiscountCalculator } from './discount-calculator'; + +describe('DiscountCalculator', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DiscountCalculator], + }).compileComponents(); + }); + + it('should create the component', () => { + const fixture = TestBed.createComponent(DiscountCalculator); + const component = fixture.componentInstance; + expect(component).toBeTruthy(); + }); + + it('should calculate 89.90 for price 99.90 and discount 10%', () => { + const fixture = TestBed.createComponent(DiscountCalculator); + const component = fixture.componentInstance; + component.price.set(99.90); + component.discountPercent.set(10); + component.calculate(); + expect(component.result()).toBeCloseTo(89.90, 2); + }); + + it('should round to nearest 0.05 (Swiss Rappenrundung)', () => { + const fixture = TestBed.createComponent(DiscountCalculator); + const component = fixture.componentInstance; + component.price.set(10); + component.discountPercent.set(2.5); + component.calculate(); + // 10 * (100 - 2.5) / 100 = 9.75 → rounded to 0.05 → 9.75 + expect(component.result()).toBeCloseTo(9.75, 2); + }); + + it('should handle zero price', () => { + const fixture = TestBed.createComponent(DiscountCalculator); + const component = fixture.componentInstance; + component.price.set(0); + component.discountPercent.set(50); + component.calculate(); + expect(component.result()).toBeCloseTo(0, 2); + }); +}); \ No newline at end of file diff --git a/web/src/app/discount-calculator/discount-calculator.ts b/web/src/app/discount-calculator/discount-calculator.ts new file mode 100644 index 0000000..fdab970 --- /dev/null +++ b/web/src/app/discount-calculator/discount-calculator.ts @@ -0,0 +1,30 @@ +import { Component, signal } from '@angular/core'; + +@Component({ + selector: 'app-discount-calculator', + templateUrl: './discount-calculator.html', + styleUrl: './discount-calculator.scss', +}) +export class DiscountCalculator { + readonly price = signal(0); + readonly discountPercent = signal(0); + readonly result = signal(null); + + onPriceInput(event: Event): void { + const input = event.target as HTMLInputElement; + this.price.set(parseFloat(input.value) || 0); + this.result.set(null); + } + + onDiscountInput(event: Event): void { + const input = event.target as HTMLInputElement; + this.discountPercent.set(parseFloat(input.value) || 0); + this.result.set(null); + } + + calculate(): void { + const discountedPrice = this.price() * (100 - this.discountPercent()) / 100; + const rounded = Math.round(discountedPrice / 0.05) * 0.05; + this.result.set(rounded); + } +} \ No newline at end of file -- 2.49.1