Connect Orders database table with Payments and Addresses, visualize it in the frontend

This commit is contained in:
2024-09-24 23:41:35 +02:00
parent 531f964841
commit 6dd49f630d
19 changed files with 517 additions and 223 deletions

View File

@@ -9,6 +9,7 @@
"lastName": "Hagemeister",
"addresses": [
{
"id": 0,
"accountId": 0,
"street": "Laportestraße",
"houseNumber": 22,
@@ -18,6 +19,7 @@
],
"payments": [
{
"id": 0,
"accountId": 0,
"bankName": "Deutsche Bank",
"iban": "DE92500105175721645777"
@@ -34,6 +36,7 @@
"lastName": "Stoiber",
"addresses": [
{
"id": 1,
"accountId": 1,
"street": "Gustav-Adolf-Straße",
"houseNumber": 30,
@@ -43,6 +46,7 @@
],
"payments": [
{
"id": 1,
"accountId": 1,
"bankName": "DZ Bank",
"iban": "DE12500105179557939114"
@@ -59,6 +63,7 @@
"lastName": "Oeter",
"addresses": [
{
"id": 2,
"accountId": 2,
"street": "Eckermannstraße",
"houseNumber": 1,
@@ -66,6 +71,7 @@
"city": "Hannover"
},
{
"id": 3,
"accountId": 2,
"street": "Gehrdener Straße",
"houseNumber": 14,
@@ -75,6 +81,7 @@
],
"payments": [
{
"id": 2,
"accountId": 2,
"bankName": "Commerzbank",
"iban": "DE31500105175417833272"
@@ -91,6 +98,7 @@
"lastName": "Durand",
"addresses": [
{
"id": 4,
"accountId": 3,
"street": "Schlägerstraße",
"houseNumber": 36,
@@ -98,6 +106,7 @@
"city": "Hannover"
},
{
"id": 5,
"accountId": 3,
"street": "Else-Ury-Weg",
"houseNumber": 20,
@@ -107,6 +116,7 @@
],
"payments": [
{
"id": 3,
"accountId": 3,
"bankName": "ING",
"iban": "DE41500105172184936679"
@@ -123,6 +133,7 @@
"lastName": "Furtwängler",
"addresses": [
{
"id": 6,
"accountId": 4,
"street": "Steinmetzstraße",
"houseNumber": 12,
@@ -132,6 +143,7 @@
],
"payments": [
{
"id": 4,
"accountId": 4,
"bankName": "Sparkasse Hannover",
"iban": "DE85500105172283979774"
@@ -148,6 +160,7 @@
"lastName": "Herbst",
"addresses": [
{
"id": 7,
"accountId": 5,
"street": "Allerweg",
"houseNumber": 33,
@@ -157,6 +170,7 @@
],
"payments": [
{
"id": 5,
"accountId": 5,
"bankName": "Postbank",
"iban": "DE45500105178862417577"
@@ -173,6 +187,7 @@
"lastName": "Seibert",
"addresses": [
{
"id": 8,
"accountId": 6,
"street": "Marktstraße",
"houseNumber": 26,
@@ -180,6 +195,7 @@
"city": "Laatzen"
},
{
"id": 9,
"accountId": 6,
"street": "Kleiner Hillen",
"houseNumber": 24,
@@ -189,6 +205,7 @@
],
"payments": [
{
"id": 6,
"accountId": 6,
"bankName": "Sparkasse Hannover",
"iban": "DE51500105177526222196"

View File

@@ -3,17 +3,23 @@
{
"id": 0,
"accountId": 0,
"shippingProgress": 4
"shippingProgress": 4,
"addressId": 0,
"paymentId": 0
},
{
"id": 1,
"accountId": 3,
"shippingProgress": 5
"shippingProgress": 5,
"addressId": 4,
"paymentId": 3
},
{
"id": 2,
"accountId": 3,
"shippingProgress": 2
"shippingProgress": 2,
"addressId": 5,
"paymentId": 3
}
]
}

View File

@@ -1,5 +1,6 @@
import { BelongsTo, Column, ForeignKey, Model, Table } from "sequelize-typescript";
import { BelongsTo, Column, ForeignKey, HasMany, Model, Table } from "sequelize-typescript";
import { Account } from "./account.model";
import { Order } from "./order.model";
@Table({ timestamps: false })
export class Address extends Model {
@@ -24,4 +25,7 @@ export class Address extends Model {
@BelongsTo(() => Account)
account: Account
@HasMany(() => Order)
orders: Order[]
}

View File

@@ -1,6 +1,8 @@
import { Table, Column, Model, BelongsTo, ForeignKey, HasMany, BelongsToMany, Default } from 'sequelize-typescript';
import { Account } from './account.model';
import { OrderItem } from './orderItem.model';
import { Address } from './address.model';
import { Payment } from './payment.model';
@Table({
updatedAt: false,
@@ -18,11 +20,26 @@ export class Order extends Model {
@Column
shippingProgress: number
@ForeignKey(() => Address)
@Column
addressId: number
@ForeignKey(() => Payment)
@Column
paymentId: number
// Relations
@BelongsTo(() => Account)
account: Account
@BelongsTo(() => Address)
address: Address
@BelongsTo(() => Payment)
payment: Payment
@HasMany(() => OrderItem)
orderItems: OrderItem[]
}

View File

@@ -1,5 +1,6 @@
import { BelongsTo, Column, ForeignKey, Model, Table } from "sequelize-typescript";
import { BelongsTo, Column, ForeignKey, HasMany, Model, Table } from "sequelize-typescript";
import { Account } from "./account.model";
import { Order } from "./order.model";
@Table({ timestamps: false })
export class Payment extends Model {
@@ -15,6 +16,10 @@ export class Payment extends Model {
// Relations
@BelongsTo(() => Account)
account: Account
@HasMany(() => Order)
orders: Order[]
}

View File

@@ -4,7 +4,8 @@ import { Product } from "../models/product.model";
import { OrderItem } from "../models/orderItem.model";
import { Brand } from "../models/brand.model";
import { Category } from "../models/category.model";
import { Sequelize } from "sequelize-typescript";
import { Payment } from "../models/payment.model";
import { Address } from "../models/address.model";
export const order = Router()
@@ -27,7 +28,9 @@ order.get("/:id", (req: Request, res: Response) => {
}
},
]
}
},
Payment,
Address
]
})
.then(orders => {
@@ -49,7 +52,10 @@ order.post("/", (req: Request, res: Response) => {
Product.decrement(
"inStock",
{ where: { id: orderItem.productId } }
{
by: orderItem.quantity,
where: { id: orderItem.productId }
}
)
}

View File

@@ -32,13 +32,15 @@ function confirmPressed() {
<template #actions>
<outlined-button
@click="showDialog = false"
color="green"
prepend-icon="mdi-close"
color="orange"
>
{{ $t("dialog.cancel") }}
</outlined-button>
<outlined-button
@click="confirmPressed"
prepend-icon="mdi-check"
color="red"
>
{{ $t("dialog.confirm") }}

View File

@@ -9,7 +9,12 @@ export async function getUserOrders(userId: number) {
return axios.get(BASE_URL + "/" + userId)
}
export async function addOrder(accountId: number, basketItems: Array<BasketItemModel>) {
export async function addOrder(
accountId: number,
basketItems: Array<BasketItemModel>,
paymentId: number,
addressId: number
) {
let orderItems = []
for (let basketItem of basketItems) {
@@ -22,6 +27,8 @@ export async function addOrder(accountId: number, basketItems: Array<BasketItemM
return axios.post(BASE_URL, {
accountId: accountId,
orderItems: orderItems
orderItems: orderItems,
paymentId: paymentId,
addressId: addressId
})
}

View File

@@ -1,4 +1,6 @@
import { AddressModel } from "./addressModel"
import { OrderItemModel } from "./orderItemModel"
import { PaymentModel } from "./paymentModel"
export class OrderModel {
id: number
@@ -6,4 +8,6 @@ export class OrderModel {
shippingProgress: number
orderItems: Array<OrderItemModel>
orderedAt: string
payment: PaymentModel
address: AddressModel
}

View File

@@ -8,10 +8,14 @@ import { addOrder } from "../api/orderApi";
import { useAccountStore } from "./accountStore";
import { ProductModel } from "../models/productModel";
import { useProductStore } from "./productStore";
import { AddressModel } from "../models/addressModel";
import { PaymentModel } from "../models/paymentModel";
export const useBasketStore = defineStore('basketStore', {
state: () => ({
itemsInBasket: useLocalStorage<Array<BasketItemModel>>("hackmycart/basketStore/productsInBasket", [])
itemsInBasket: useLocalStorage<Array<BasketItemModel>>("hackmycart/basketStore/productsInBasket", []),
usedAddress: useLocalStorage("hackmycart/basketStore/usedAddress", new AddressModel()),
usedPayment: useLocalStorage("hackmycart/basketStore/usedPayment", new PaymentModel())
}),
getters: {
@@ -73,12 +77,20 @@ export const useBasketStore = defineStore('basketStore', {
async takeOrder() {
const accountStore = useAccountStore()
const productStore = useProductStore()
const feedbackStore = useFeedbackStore()
await addOrder(accountStore.userAccount.id, this.itemsInBasket)
this.itemsInBasket = []
await addOrder(accountStore.userAccount.id, this.itemsInBasket, this.usedPayment.id, this.usedAddress.id)
.then(async result => {
if (result.status == 201) {
await accountStore.refreshOrders()
await productStore.fetchAllProducts()
await accountStore.refreshOrders()
await productStore.fetchAllProducts()
this.itemsInBasket = []
feedbackStore.changeBanner(BannerStateEnum.ORDERPLACESUCCESSFUL)
} else {
feedbackStore.changeBanner(BannerStateEnum.ERROR)
}
})
}
}
})

View File

@@ -20,6 +20,7 @@ export const useFeedbackStore = defineStore("feedbackStore", {
actions: {
changeBanner(bannerState: BannerStateEnum) {
// Banner message
switch (bannerState) {
////////// System feedback //////////
@@ -107,6 +108,8 @@ export const useFeedbackStore = defineStore("feedbackStore", {
}
}
// Banner color
switch (bannerState) {
case BannerStateEnum.ERROR:
case BannerStateEnum.ACCOUNTLOGINERROR:
@@ -118,9 +121,9 @@ export const useFeedbackStore = defineStore("feedbackStore", {
case BannerStateEnum.PRODUCTDELETESUCCESSFUL:
case BannerStateEnum.PRODUCTDELETEERROR:
this.color = "red"
this.icon = "mdi-alert-circle"
break;
case BannerStateEnum.BASKETPRODUCTADDED:
case BannerStateEnum.DATABASERESETSUCCESSFUL:
case BannerStateEnum.ACCOUNTLOGINSUCCESSFUL:
case BannerStateEnum.ACCOUNTREGISTERSUCCESSFUL:
@@ -132,10 +135,74 @@ export const useFeedbackStore = defineStore("feedbackStore", {
case BannerStateEnum.PRODUCTCREATESUCCESSFUL:
case BannerStateEnum.PRODUCTCREATEERROR:
this.color = "green"
this.icon = "mdi-check-circle"
break
break;
case BannerStateEnum.BASKETPRODUCTREMOVED:
this.color = "blue"
}
// Banner icon
switch (bannerState) {
case BannerStateEnum.ERROR:
this.icon = "mdi-alert-circle"
break;
case BannerStateEnum.ACCOUNTLOGINERROR:
case BannerStateEnum.ACCOUNTLOGINWRONGLOGIN:
case BannerStateEnum.ACCOUNTREGISTERERROR:
case BannerStateEnum.ACCOUNTREGISTERUSERNAMEINUSE:
this.icon = "mdi-account"
break;
case BannerStateEnum.CATEGORYCREATEERROR:
case BannerStateEnum.CATEGORYDELETEERROR:
case BannerStateEnum.CATEGORYCREATESUCCESSFUL:
case BannerStateEnum.CATEGORYDELETESUCESSFUL:
this.icon = "mdi-label"
break;
case BannerStateEnum.PRODUCTDELETESUCCESSFUL:
case BannerStateEnum.PRODUCTDELETEERROR:
case BannerStateEnum.PRODUCTCREATESUCCESSFUL:
case BannerStateEnum.PRODUCTCREATEERROR:
this.icon = "mdi-store"
break;
case BannerStateEnum.DATABASERESETSUCCESSFUL:
this.icon = "mdi-database-refresh"
break;
case BannerStateEnum.BASKETPRODUCTADDED:
case BannerStateEnum.BASKETPRODUCTREMOVED:
this.icon = "mdi-basket"
break;
case BannerStateEnum.ORDERPLACESUCCESSFUL:
this.icon = "mdi-basket-check"
break;
case BannerStateEnum.ACCOUNTLOGOUTSUCCESSFUL:
this.icon = "mdi-logout"
break;
case BannerStateEnum.ACCOUNTLOGINSUCCESSFUL:
this.icon = "mdi-login"
break;
case BannerStateEnum.ACCOUNTREGISTERSUCCESSFUL:
this.icon = "mdi-account-plus"
break;
case BannerStateEnum.ACCOUNTUPDATESUCCESSFUL:
this.icon = "mdi-account-reactivate"
break;
}
this.showBanner = true
}
}

View File

@@ -66,8 +66,8 @@
"backToLogin": "Zurück zum Login",
"delete": "Account löschen",
"managingAccount": "Account verwalten",
"addresses": "Adressen",
"payments": "Bezahlarten",
"address": "Adresse | Addressen",
"payment": "Bezahlart | Bezahlarten",
"masterData": "Stammdaten",
"noAddresses": "Keine Adressen gefunden",
"noPayments": "Keine Bezahlarten gefunden",

View File

@@ -66,8 +66,8 @@
"register": "Create Account",
"delete": "Delete Account",
"managingAccount": "Managing Account",
"addresses": "Addresses",
"payments": "Payments",
"address": "Address | Addresses",
"payment": "Payment | Payments",
"masterData": "Master data",
"noAddresses": "No Addresses found",
"noPayments": "No payments found",

View File

@@ -9,12 +9,28 @@ const basketStore = useBasketStore()
const accountStore = useAccountStore()
const showDialog: ModelRef<boolean> = defineModel()
const orderingInProgress = ref(false)
const addressError = ref(false)
const paymentError = ref(false)
async function doOrder() {
orderingInProgress.value = true
await basketStore.takeOrder()
addressError.value = false
paymentError.value = false
if (basketStore.usedAddress == null) {
addressError.value = true
}
if (basketStore.usedPayment == null){
paymentError.value = true
}
if (basketStore.usedAddress != null && basketStore.usedPayment != null) {
await basketStore.takeOrder()
showDialog.value = false
}
showDialog.value = false
orderingInProgress.value = false
}
</script>
@@ -25,29 +41,65 @@ async function doOrder() {
icon="mdi-basket-check"
v-model="showDialog"
max-width="800"
persistent
>
<v-row>
<v-col>
Address
{{ $t('account.address', accountStore.userAccount.addresses.length) }}
</v-col>
</v-row>
<v-row>
<v-col>
<v-radio-group>
<v-radio-group
v-model="basketStore.usedAddress"
:error="addressError"
>
<v-radio
v-for="address in accountStore.userAccount.addresses"
:value="address"
:label="address.street + ' ' + address.houseNumber + ', ' + address.postalCode + ' ' + address.city"
/>
</v-radio-group>
</v-col>
</v-row>
<v-row>
<v-col>
{{ $t('account.payment', accountStore.userAccount.payments.length) }}
</v-col>
</v-row>
<v-row>
<v-col>
<v-radio-group
v-model="basketStore.usedPayment"
>
<v-radio
v-for="payment in accountStore.userAccount.payments"
:value="payment"
:label="payment.bankName + ': ' + payment.iban"
:error="paymentError"
/>
</v-radio-group>
</v-col>
</v-row>
<template #actions>
<outlined-button
@click="showDialog = false"
prepend-icon="mdi-close"
color="orange"
>
{{ $t('dialog.cancel') }}
</outlined-button>
<outlined-button
@click="doOrder"
:loading="orderingInProgress"
prepend-icon="mdi-send"
color="green"
>
{{ $t('ordering.takeOrder') }}
</outlined-button>

View File

@@ -36,46 +36,81 @@ function formatDateTimeString(string: string) {
:subtitle="$t('totalPrice') + ': ' + accountStore.getOrderTotalPrice(order.id) + ' €'"
>
<template #withoutContainer>
<v-timeline direction="horizontal" side="start" size="x-large">
<v-timeline-item :dot-color="getDotColor(order, 1)" icon="mdi-basket-check">
{{ $t('orders.ordered') }}
</v-timeline-item>
<v-row>
<v-col>
<v-timeline direction="horizontal" side="start" size="x-large">
<v-timeline-item :dot-color="getDotColor(order, 1)" icon="mdi-basket-check">
{{ $t('orders.ordered') }}
</v-timeline-item>
<v-timeline-item :dot-color="getDotColor(order, 2)" icon="mdi-package-variant">
{{ $t('orders.preparingForShipping') }}
</v-timeline-item>
<v-timeline-item :dot-color="getDotColor(order, 2)" icon="mdi-package-variant">
{{ $t('orders.preparingForShipping') }}
</v-timeline-item>
<v-timeline-item :dot-color="getDotColor(order, 3)" icon="mdi-send">
{{ $t('orders.shipped') }}
</v-timeline-item>
<v-timeline-item :dot-color="getDotColor(order, 3)" icon="mdi-send">
{{ $t('orders.shipped') }}
</v-timeline-item>
<v-timeline-item :dot-color="getDotColor(order, 4)" icon="mdi-truck-fast">
{{ $t('orders.inDelivery') }}
</v-timeline-item>
<v-timeline-item :dot-color="getDotColor(order, 4)" icon="mdi-truck-fast">
{{ $t('orders.inDelivery') }}
</v-timeline-item>
<v-timeline-item :dot-color="getDotColor(order, 5)" icon="mdi-package-check">
{{ $t('orders.delivered') }}
</v-timeline-item>
</v-timeline>
<v-timeline-item :dot-color="getDotColor(order, 5)" icon="mdi-package-check">
{{ $t('orders.delivered') }}
</v-timeline-item>
</v-timeline>
</v-col>
</v-row>
<v-table class="bg-surface-light">
<thead>
<tr>
<th>{{ $t('quantity') }}</th>
<th>{{ $t('product.brand') }}</th>
<th>{{ $t('product.productName') }}</th>
<th>{{ $t('product.productPrice') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="orderItem in order.orderItems">
<td>{{ orderItem.quantity }}x</td>
<td>{{ orderItem.product.brand.name }}</td>
<td>{{ orderItem.product.name }}</td>
<td>{{ orderItem.product.price }} </td>
</tr>
</tbody>
</v-table>
<v-row>
<v-col>
<v-card variant="outlined" class="ml-5 pa-3">
<div class="text-h6">
<v-icon icon="mdi-home" />
{{ $t('account.address', 1) }}
</div>
<div class="pl-9">{{ order.address.street }} {{ order.address.houseNumber }}</div>
<div class="pl-9">{{ order.address.postalCode }} {{ order.address.city }}</div>
</v-card>
</v-col>
<v-col>
<v-card variant="outlined" class="mr-5 pa-3">
<div class="text-h6">
<v-icon icon="mdi-currency-usd" />
{{ $t('account.payment', 1) }}
</div>
<div class="pl-9">{{ order.payment.bankName }}</div>
<div class="pl-9">{{ order.payment.iban }}</div>
</v-card>
</v-col>
</v-row>
<v-row>
<v-col>
<v-table class="bg-surface-light">
<thead>
<tr>
<th>{{ $t('quantity') }}</th>
<th>{{ $t('product.brand') }}</th>
<th>{{ $t('product.productName') }}</th>
<th>{{ $t('product.productPrice') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="orderItem in order.orderItems">
<td>{{ orderItem.quantity }}x</td>
<td>{{ orderItem.product.brand.name }}</td>
<td>{{ orderItem.product.name }}</td>
<td>{{ orderItem.product.price }} </td>
</tr>
</tbody>
</v-table>
</v-col>
</v-row>
</template>
</card-view>
</template>

View File

@@ -142,7 +142,7 @@ watch(() => props.product.images, () => {
v-model="nrOfArticles"
variant="outlined"
:min="1"
:max="10"
:max="product.inStock"
density="comfortable"
:hide-details="true"
:disabled="product.inStock == 0"