From d0778790db76914260907ea4e98b5d846d06bb66 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Aaron=20D=C3=B6tsch?= <aaron@fsmpi.rwth-aachen.de>
Date: Wed, 12 Jul 2023 14:34:46 +0200
Subject: [PATCH] Add CANT_GO_NEGATIVE flag

Until now you could always go into debt. There is a flag so you don't have to
pay extra if your balance is negative, but now there also is a flag so you
can't even go negative.
---
 src/components/ItemList.svelte   | 14 ++++++++++----
 src/lib/flags.js                 |  1 +
 src/routes/+page.svelte          |  9 ++++++---
 src/routes/api/[slug]/+server.js |  1 +
 4 files changed, 18 insertions(+), 7 deletions(-)

diff --git a/src/components/ItemList.svelte b/src/components/ItemList.svelte
index 2311f44..bc33036 100644
--- a/src/components/ItemList.svelte
+++ b/src/components/ItemList.svelte
@@ -3,9 +3,9 @@
 	import ItemIcon from "./ItemIcon.svelte";
 	
 	export let items;
-	export let priceModifier;
 	export let onClick;
 	export let inView;
+	export let cartWorth, balance, cantGoNegative;
 	
 	let element;
 </script>
@@ -34,17 +34,23 @@
 	.item__premium {
 		font-size: .8em;
 	}
+	
+	.disabled {
+		opacity: 0.5;
+		cursor: not-allowed;
+	}
 </style>
 
 <IntersectionObserver {element} bind:intersecting={inView} rootMargin="0px 0px -50% 0px">
 	<div class="available-items" bind:this={element}>
 		{#each items as item}
-			<div class="item" on:click={()=>onClick(item)}>
+			{@const disabled = cantGoNegative && item.price+item.premium>balance-cartWorth}
+			<div class="item" class:disabled on:click={()=>onClick(item)}>
 				<ItemIcon icon={item.image} name={item.name} size="100%" containerStyle="margin-bottom:.5em" />
 				<div class="item__name">{item.name}</div>
-				<div class="item__price">{(priceModifier(item.price)/100).toFixed(2)}€</div>
+				<div class="item__price">{(item.price/100).toFixed(2)}€</div>
 				{#if item.premium}
-					<div class="item__premium">+{(priceModifier(item.premium)/100).toFixed(2)}€</div>
+					<div class="item__premium">+{(item.premium/100).toFixed(2)}€</div>
 				{/if}
 			</div>
 		{/each}
diff --git a/src/lib/flags.js b/src/lib/flags.js
index a9b331f..0edc761 100644
--- a/src/lib/flags.js
+++ b/src/lib/flags.js
@@ -3,4 +3,5 @@ export const Flags = {
 	NO_PREMIUM: 1<<1,
 	NO_BALANCE: 1<<2,
 	CANT_TRANSFER: 1<<3,
+	CANT_GO_NEGATIVE: 1<<4,
 };
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 65e1304..80efc51 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -23,13 +23,14 @@
 	let isFetchingBuy = false;
 	
 	let cart = [];
-	$: cartItems = cart.map(item => {
+	function getItemFromRaw(item){
 		if(item.type === "article") return Object.assign({type: "article"}, items.find(i => i.code === item.code));
 		else if(item.type === "custom") return {type: "custom", name: "Manuell", price: item.price, premium: 0, code: item.code};
 		else if(item.type === "pfand") return {type: "pfand", name: "Pfand", price: item.price, premium: 0, code: item.code};
 		else if(item.type === "voucher") return {type: "voucher", name: "Gutschein", price: item.price, premium: 0, code: item.code};
 		else return addMessage(MessageType.ERROR, "Unbekannter Artikeltyp: " + item.type);
-	});
+	}
+	$: cartItems = cart.map(item => getItemFromRaw(item));
 	function emptyCart(){
 		if(isFetchingBuy) return addMessage(MessageType.ERROR, "Der Kauf wird gerade ausgeführt. Bitte warten...");
 		cart = [];
@@ -37,6 +38,8 @@
 	}
 	function addToCart(item){
 		if(isFetchingBuy) return addMessage(MessageType.ERROR, "Der Kauf wird gerade ausgeführt. Bitte warten...");
+		let article = getItemFromRaw(item);
+		if(cartItems.reduce((acc, item)=>acc+item.price+item.premium, 0) + article.price + article.premium > data.user.balance && data.user.perms & Flags.CANT_GO_NEGATIVE) return addMessage(MessageType.WARN, "Nicht genügend Guthaben");
 		cart = [...cart, item];
 		resetTimer();
 	}
@@ -313,7 +316,7 @@
 				{#each categories as category, i}
 					<h2 bind:this={categoryElements[i].element}>{category.name}</h2>
 					<div class="item-list">
-						<ItemList items={items.filter(item=>item.categoryId===category.id&&item.show)} onClick={item=>addToCart({type: "article", code: item.code})} priceModifier={x=>x} bind:inView={elementsInView[i]} />
+						<ItemList items={items.filter(item=>item.categoryId===category.id&&item.show)} onClick={item=>addToCart({type: "article", code: item.code})} bind:inView={elementsInView[i]} cartWorth={cartItems.reduce((acc,item)=>acc+item.price+item.premium,0)} balance={data.user.balance} cantGoNegative={!!(data.user.perms&Flags.CANT_GO_NEGATIVE)} />
 					</div>
 				{/each}
 			</div>
diff --git a/src/routes/api/[slug]/+server.js b/src/routes/api/[slug]/+server.js
index 666681d..d172f41 100644
--- a/src/routes/api/[slug]/+server.js
+++ b/src/routes/api/[slug]/+server.js
@@ -41,6 +41,7 @@ export async function POST(event) {
 				const priceModifier = getPriceModifier(user.balance, !!(user.perms & Flags.NO_PREMIUM));
 				if(!isValidCart(data.items, articles, data.vouchers, vouchers, priceModifier)) return new Response(JSON.stringify({message: "Invalid cart"}), {headers: {'content-type': 'application/json', 'status': 400}});
 				const items = [...data.items, ...data.vouchers.map(voucher => ({...voucher, code: Codes.Voucher}))];
+				if(user.perms & Flags.CANT_GO_NEGATIVE && items.reduce((acc, item) => acc + item.price + item.premium, 0) > user.balance) return new Response(JSON.stringify({message: "Not enough balance"}), {headers: {'content-type': 'application/json', 'status': 400}});
 				const res = await buyArticles(user.id, user.card, items, data.vouchers, !!(user.perms & Flags.NO_BALANCE));
 				return new Response(JSON.stringify(res), {headers: {'content-type': 'application/json', 'status': 200}});
 			}catch(e){
-- 
GitLab