Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a2ce30edd | ||
|
|
4484195ddb |
No files matched your search
@@ -26,7 +26,6 @@ npm start
|
||||
- improve product listing (light purple background)
|
||||
- Stats: products over time
|
||||
- inventory views
|
||||
- Stats: Implement users tab
|
||||
|
||||
|
||||
## TODO-old
|
||||
|
||||
@@ -7,9 +7,16 @@ class ProductAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class CardAdmin(admin.ModelAdmin):
|
||||
list_display = ("name", "user", "card_id", "active")
|
||||
list_filter = ("active",)
|
||||
search_fields = ("user__username", "name")
|
||||
|
||||
|
||||
admin.site.register(Product, ProductAdmin)
|
||||
admin.site.register(InventoryTransaction)
|
||||
admin.site.register(Order)
|
||||
admin.site.register(OrderLine)
|
||||
admin.site.register(Transaction)
|
||||
admin.site.register(UserProfile)
|
||||
admin.site.register(Card, CardAdmin)
|
||||
@@ -66,7 +66,7 @@ def create_order(customer_id: ID, order_lines: List[OrderLineInput], is_external
|
||||
product.inventory_amount -= ol.amount
|
||||
product.save()
|
||||
|
||||
if not is_external and user.profile.balance < order_sum:
|
||||
if not is_external and user.profile.balance <= order_sum:
|
||||
return InsufficientFunds(
|
||||
message="Du har ikke nok penger på bok.", amount_lacking=abs(order_sum - user.profile.balance)
|
||||
)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
from decimal import Decimal
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
@@ -26,7 +25,7 @@ class UserProfile:
|
||||
balance: auto
|
||||
image: auto
|
||||
last_purchase_date: datetime
|
||||
order_sum_total: Decimal
|
||||
order_sum_total: float
|
||||
|
||||
@strawberry.field()
|
||||
def top_months(self) -> List[TopMonth]:
|
||||
|
||||
+1
-3
@@ -2,11 +2,9 @@ from django import forms
|
||||
|
||||
from bbs.models import InventoryTransaction, Product
|
||||
|
||||
MAX_DIGITS = 9
|
||||
DECIMAL_PLACES = 1
|
||||
|
||||
class InventoryTransactionForm(forms.ModelForm):
|
||||
unit_price = forms.DecimalField(max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES, required=True, localize=True)
|
||||
unit_price = forms.FloatField(required=True, localize=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Only show active products
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
# Generated by Django 4.0.2 on 2022-03-05 19:26
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bbs", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="inventorytransaction",
|
||||
name="amount",
|
||||
field=models.IntegerField(verbose_name="Antall"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="inventorytransaction",
|
||||
name="unit_price",
|
||||
field=models.DecimalField(decimal_places=1, max_digits=9, verbose_name="Enhetspris"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="orderline",
|
||||
name="unit_price",
|
||||
field=models.DecimalField(decimal_places=1, max_digits=9, verbose_name="Enhetspris"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="product",
|
||||
name="alcohol_percent",
|
||||
field=models.DecimalField(decimal_places=1, max_digits=9, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="product",
|
||||
name="inventory_amount",
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="product",
|
||||
name="sale_price_ext",
|
||||
field=models.DecimalField(decimal_places=1, max_digits=9),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="product",
|
||||
name="sale_price_int",
|
||||
field=models.DecimalField(decimal_places=1, max_digits=9),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="product",
|
||||
name="volume_liter",
|
||||
field=models.DecimalField(decimal_places=1, max_digits=9, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="transaction",
|
||||
name="amount",
|
||||
field=models.DecimalField(decimal_places=1, max_digits=9),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="userprofile",
|
||||
name="balance",
|
||||
field=models.DecimalField(decimal_places=1, default=0, max_digits=9),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 4.0.2 on 2023-06-28 19:11
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bbs', '0002_alter_inventorytransaction_amount_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='order',
|
||||
name='order_sum',
|
||||
field=models.DecimalField(db_column='sum', decimal_places=1, max_digits=9),
|
||||
),
|
||||
]
|
||||
+25
-20
@@ -2,7 +2,6 @@ import hashlib
|
||||
from datetime import datetime
|
||||
from itertools import groupby
|
||||
from typing import Type
|
||||
from decimal import Decimal
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
@@ -18,8 +17,6 @@ from django.db.models.functions import (
|
||||
)
|
||||
|
||||
EXTERNAL_USER = "Ekstern"
|
||||
MAX_DIGITS = 9
|
||||
DECIMAL_PLACES = 1
|
||||
|
||||
|
||||
def _format_counts(per_user):
|
||||
@@ -39,15 +36,14 @@ def _format_counts(per_user):
|
||||
|
||||
class Product(models.Model):
|
||||
name = models.CharField(max_length=64)
|
||||
sale_price_int = models.DecimalField(max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES)
|
||||
sale_price_ext = models.DecimalField(max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES)
|
||||
sale_price_int = models.FloatField()
|
||||
sale_price_ext = models.FloatField()
|
||||
description = models.CharField(max_length=256, blank=True)
|
||||
volume_liter = models.DecimalField(max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES, null=True)
|
||||
alcohol_percent = models.DecimalField(max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES, null=True)
|
||||
volume_liter = models.FloatField(null=True)
|
||||
alcohol_percent = models.FloatField(null=True)
|
||||
image = models.ImageField(upload_to="products", blank=True)
|
||||
customer_support = models.CharField(max_length=32, blank=True)
|
||||
# FIXME: rename to inventory_quantity
|
||||
inventory_amount = models.IntegerField(default=0)
|
||||
inventory_amount = models.FloatField(default=0)
|
||||
active = models.BooleanField(default=True)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
updated = models.DateTimeField(auto_now=True)
|
||||
@@ -113,7 +109,7 @@ class Order(models.Model):
|
||||
blank=True,
|
||||
related_name="orders",
|
||||
)
|
||||
order_sum = models.DecimalField(db_column="sum", max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES)
|
||||
order_sum = models.FloatField(db_column="sum")
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
objects = OrderManager()
|
||||
@@ -152,10 +148,8 @@ class OrderLineManager(models.QuerySet):
|
||||
class OrderLine(models.Model):
|
||||
order = models.ForeignKey("bbs.Order", on_delete=models.CASCADE, related_name="orderlines")
|
||||
product = models.ForeignKey("bbs.Product", on_delete=models.CASCADE, related_name="orderlines")
|
||||
# FIXME: rename to quantity
|
||||
amount = models.IntegerField()
|
||||
# FIXME: rename to price
|
||||
unit_price = models.DecimalField(max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES, verbose_name="Enhetspris")
|
||||
unit_price = models.FloatField(verbose_name="Enhetspris")
|
||||
|
||||
objects = OrderLineManager.as_manager()
|
||||
|
||||
@@ -174,7 +168,7 @@ class Transaction(models.Model):
|
||||
"""Money"""
|
||||
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="transactions")
|
||||
amount = models.DecimalField(max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES)
|
||||
amount = models.FloatField()
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
@@ -195,10 +189,8 @@ class InventoryTransaction(models.Model):
|
||||
related_name="transactions",
|
||||
verbose_name="Produkt",
|
||||
)
|
||||
# FIXME: rename to quantity
|
||||
amount = models.IntegerField(verbose_name="Antall")
|
||||
# FIXME: rename to price
|
||||
unit_price = models.DecimalField(max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES, verbose_name="Enhetspris")
|
||||
amount = models.FloatField(verbose_name="Antall")
|
||||
unit_price = models.FloatField(verbose_name="Enhetspris")
|
||||
comment = models.TextField(verbose_name="Kommentar", blank=True)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@@ -212,7 +204,7 @@ class InventoryTransaction(models.Model):
|
||||
|
||||
class UserProfile(models.Model):
|
||||
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="profile")
|
||||
balance = models.DecimalField(max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES, default=0)
|
||||
balance = models.FloatField(default=0.0)
|
||||
image = models.URLField(blank=True)
|
||||
|
||||
def last_purchase(self):
|
||||
@@ -235,7 +227,7 @@ class UserProfile(models.Model):
|
||||
def product_order_stats(self):
|
||||
return OrderLine.objects.filter(order__customer__pk=self.pk).product_order_stats()
|
||||
|
||||
def order_sum_total(self) -> Decimal:
|
||||
def order_sum_total(self) -> float:
|
||||
return Order.objects.filter(customer_id=self.user_id).aggregate(sum=Sum("order_sum"))["sum"] or 0
|
||||
|
||||
def image_url(self) -> str:
|
||||
@@ -253,3 +245,16 @@ class UserProfile(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user}"
|
||||
|
||||
|
||||
class Card(models.Model):
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="cards")
|
||||
card_id = models.CharField(max_length=32)
|
||||
name = models.CharField(max_length=64)
|
||||
# pin = models.IntegerField(max_length=4, blank=True, null=True)
|
||||
# valid_until = models.DateTimeField(blank=True, null=True)
|
||||
active = models.BooleanField(default=True)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}"
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/assets/brick.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&display=swap" rel="stylesheet">
|
||||
|
||||
@@ -51,11 +51,11 @@ const Log = () => {
|
||||
<td>
|
||||
{(order.orderlines ?? []).map((line) => (
|
||||
<div key={line.id}>
|
||||
{Number(line.amount)}x {line.product.name}
|
||||
{line.amount}x {line.product.name}
|
||||
</div>
|
||||
))}
|
||||
</td>
|
||||
<td>{Number(order.orderSum)}</td>
|
||||
<td>{order.orderSum}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@@ -104,7 +104,7 @@ const Log = () => {
|
||||
<td>
|
||||
{trans.user.firstName} {trans.user.lastName}
|
||||
</td>
|
||||
<td>{Number(trans.amount)}</td>
|
||||
<td>{trans.amount}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useState } from "react";
|
||||
import { useToast } from "../../components/ToastProvider";
|
||||
import { AllProductsDocument, AllUsersDocument, CreateOrderDocument, OrderLineInput, Product } from "../../types";
|
||||
import { activeUserFilter } from "../../utils/users";
|
||||
import {formatCurrency} from "../../utils/currency";
|
||||
|
||||
const USER_EXTERNAL = "external";
|
||||
|
||||
@@ -54,7 +53,7 @@ export const useRegister = () => {
|
||||
setOrder(newOrder);
|
||||
};
|
||||
|
||||
const total = formatCurrency(sumTotal(products, order, selectedUser === USER_EXTERNAL));
|
||||
const total = sumTotal(products, order, selectedUser === USER_EXTERNAL);
|
||||
|
||||
const onSubmit = async () => {
|
||||
let res;
|
||||
|
||||
@@ -25,7 +25,7 @@ export const StatsNav = () => (
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink className="disabled" to="/stats/users" title="TODO" hidden={true}>
|
||||
<NavLink className="disabled" to="/stats/users" title="TODO">
|
||||
Folk
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
@@ -3,7 +3,6 @@ import React, { MouseEventHandler } from "react";
|
||||
|
||||
import { AllUsersQuery } from "../../types";
|
||||
import styles from "./UserCard.module.css";
|
||||
import {formatCurrency} from "../../utils/currency";
|
||||
|
||||
type UserCardProps = {
|
||||
user: AllUsersQuery["allUsers"][0] | null;
|
||||
@@ -14,7 +13,6 @@ type UserCardProps = {
|
||||
/** If user is null, then it's the special external user */
|
||||
export const UserCard = ({ user, onUser, active = false }: UserCardProps): JSX.Element => {
|
||||
const classes = clsx(styles.user, { [styles.active]: active });
|
||||
const userBalanceString = formatCurrency(Number(user?.profile?.balance));
|
||||
return (
|
||||
<button className={classes} onClick={onUser} type="button" title={user?.firstName || "Ekstern"}>
|
||||
<div className={styles.avatarWrap}>
|
||||
@@ -29,7 +27,7 @@ export const UserCard = ({ user, onUser, active = false }: UserCardProps): JSX.E
|
||||
</div>
|
||||
<div className={styles.meta}>
|
||||
{user?.firstName || "Ekstern"}
|
||||
<span className={styles.balance}>{userBalanceString}</span>
|
||||
<span className={styles.balance}>{user?.profile?.balance || ""}</span>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export function formatCurrency(value: number) {
|
||||
if (isNaN(value)) {
|
||||
return "";
|
||||
} else {
|
||||
return value.toFixed(2).replace(/\.00$/, '');
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user