Author SHA1 Message Date
leanderandCopilot 53968fa949 Integrate compatible tests-and-bumps updates
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-22 21:13:26 +02:00
leanderandCopilot d979558905 Toggle selected users in register and deposit
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-22 20:57:27 +02:00
leanderandCopilot 78d5b06258 Add user purchase statistics tab
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-22 05:39:45 +02:00
leanderandCopilot 4848dda4a4 Migrate inventory and reports to the SPA
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-22 05:38:49 +02:00
leanderandCopilot 1d507d1976 Refresh README TODOs and product card styling
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-22 05:22:47 +02:00
leanderandCopilot ec5e8dc791 Prevent rotating background from shifting layout
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-22 05:12:17 +02:00
leanderandCopilot ac2ceddf78 Improve mobile responsiveness across pages
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-22 05:05:44 +02:00
leanderandCopilot c05820f61b Adjust chart x-axis legend placement
Move the date legend inward to align with the y-axis label on overview and user detail charts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-22 04:59:14 +02:00
leanderandCopilot 4d64126581 Keep statistics charts clear of the footer
Use a full-height flex layout and normal footer flow so long chart content scrolls without overlapping the footer. Keep the page gradient fixed while scrolling.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-22 04:54:28 +02:00
leanderandCopilot 9ad7b8aa28 Keep deposit entry visible while selecting a user
Place the amount entry and actions in a clear adjacent panel on larger screens and above the scrollable user list on mobile.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-22 04:50:02 +02:00
leanderandCopilot 64b77217f4 Improve chart text contrast
Use a brighter shared chart theme and a dark styled tooltip, with high-contrast pie slice labels.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-22 04:47:27 +02:00
leanderandCopilot 65e532be30 Fix user detail GraphQL query
Return monthly purchase totals with the correct GraphQL scalar and align frontend queries and generated types with the upgraded schema.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-22 04:40:53 +02:00
leanderandCopilot 407523b61a Upgrade vulnerable Python dependencies
Raise the supported Python floor to 3.11 for patched dependency releases and update CI/runtime metadata.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-22 04:33:15 +02:00
leanderandCopilot e23cbb1d27 Make local frontend backend configurable
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-22 04:21:44 +02:00
leanderandCopilot 098c6701f2 Add repeatable demo database seed
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-22 04:00:46 +02:00
leanderandCopilot 18da8335a8 Upgrade frontend dependencies and tooling
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-22 04:00:31 +02:00
89 changed files with 8194 additions and 17320 deletions

No files matched your search

+1
View File
@@ -15,3 +15,4 @@ static/assets
static/index.html static/index.html
.python-version .python-version
.env .env
.coverage
+1 -1
View File
@@ -2,7 +2,7 @@ variables:
PIP_CACHE_DIR: "${CI_PROJECT_DIR}/.cache/pip" PIP_CACHE_DIR: "${CI_PROJECT_DIR}/.cache/pip"
default: default:
image: nikolaik/python-nodejs:python3.9-nodejs20 image: nikolaik/python-nodejs:python3.11-nodejs20
cache: cache:
paths: paths:
- .cache/pip - .cache/pip
+1 -1
View File
@@ -9,4 +9,4 @@ repos:
rev: "1.24.0" rev: "1.24.0"
hooks: hooks:
- id: django-upgrade - id: django-upgrade
args: [--target-version, "4.2"] args: [--target-version, "5.2"]
+29 -15
View File
@@ -1,31 +1,45 @@
# Getting started # Getting started
Before starting, make sure you have a working python and nodejs setup with pyenv and nvm. Before starting, make sure you have Python 3.11 or newer and Node.js 20 available.
```bash ```bash
apt-get install libmysqlclient-dev
pre-commit install
uv run manage.py migrate uv run manage.py migrate
uv run bin/run
cp .env-default .env cp .env-default .env
uv run manage.py seed_demo_data
# in another terminal # Optional: create an admin login
cd frontend uv run manage.py createsuperuser
npm install
npm start # Install frontend packages once
cd frontend && npm install
```
Start the backend and frontend in separate terminals from the project root:
```bash
uv run bin/run
```
```bash
uv run bin/run-frontend
```
Vite prints the frontend URL when it starts (normally <http://localhost:5173/>; if that port is busy, it selects another). Django runs at <http://localhost:3000/>; open the admin directly at <http://localhost:3000/admin/> or through the frontend dev proxy at the printed Vite URL followed by `/admin/`. The GraphQL API is at <http://localhost:3000/graphql/>; the frontend uses Vite's proxy for development API requests.
To test the frontend against a different backend, set `VITE_DEV_API_PROXY` when starting Vite, for example `VITE_DEV_API_PROXY=https://bbs.neuf.no uv run bin/run-frontend`. This affects the development proxy only; use caution with a live backend, since app changes and submissions may modify production data.
To create the admin during data seeding instead, set `BBS_SEED_ADMIN_PASSWORD` for that command; the command only creates a local `admin` superuser if one does not already exist.
## Testing
```bash
mise run test
``` ```
## References ## References
- We use JS code generation to work with queries/mutations in our graphql API: <https://github.com/dotansimha/graphql-typed-document-node> - We use JS code generation to work with queries/mutations in our graphql API: <https://github.com/dotansimha/graphql-typed-document-node>
## TODO
- improve product listing (light purple background)
- Stats: products over time
- inventory views
- Stats: Implement users tab
## TODO-old ## TODO-old
- Larger pictures - Larger pictures
+63
View File
@@ -0,0 +1,63 @@
from decimal import Decimal
from typing import Annotated
import strawberry
from django.db import transaction
from strawberry import ID, Info
from bbs import models
from bbs.api.errors import FieldError, FormErrors
from bbs.api.types.inventory import InventoryTransaction
@strawberry.input
class InventoryTransactionInput:
product_id: ID
amount: int
unit_price: Decimal
comment: str = ""
@strawberry.type
class CreateInventoryTransactionSuccess:
inventory_transaction: InventoryTransaction
CreateInventoryTransactionResponse = Annotated[
CreateInventoryTransactionSuccess | FormErrors,
strawberry.union("CreateInventoryTransactionResponse"),
]
@strawberry.mutation
@transaction.atomic
def create_inventory_transaction(
info: Info,
data: InventoryTransactionInput,
) -> CreateInventoryTransactionResponse:
user = info.context.request.user
if not user.is_authenticated:
message = "Logg inn for å registrere varetransaksjoner."
return FormErrors(message=message, fields=[FieldError(field="user", message=message)])
try:
product = models.Product.objects.get(pk=data.product_id, active=True)
except (models.Product.DoesNotExist, ValueError):
message = "Velg et aktivt produkt."
return FormErrors(message=message, fields=[FieldError(field="product_id", message=message)])
product.inventory_amount += data.amount
product.save()
inventory_transaction = models.InventoryTransaction.objects.create(
user=user,
product=product,
amount=data.amount,
unit_price=data.unit_price,
comment=data.comment,
)
return CreateInventoryTransactionSuccess(inventory_transaction=inventory_transaction)
@strawberry.type
class InventoryMutations:
create_inventory_transaction = create_inventory_transaction
+105 -1
View File
@@ -1,9 +1,113 @@
from datetime import datetime
from decimal import Decimal
import strawberry import strawberry
import strawberry_django import strawberry_django
from django.db.models import F, Sum
from django.utils import timezone
from bbs.api.types.inventory import Transaction, TransactionOrdering from bbs import models
from bbs.api.types.inventory import (
InventoryReport,
InventoryReportProduct,
InventoryTransaction,
InventoryTransactionOrdering,
Transaction,
TransactionOrdering,
)
@strawberry.type @strawberry.type
class InventoryQueries: class InventoryQueries:
transaction_list: list[Transaction] = strawberry_django.field(pagination=True, ordering=TransactionOrdering) transaction_list: list[Transaction] = strawberry_django.field(pagination=True, ordering=TransactionOrdering)
inventory_transaction_list: list[InventoryTransaction] = strawberry_django.field(
pagination=True,
ordering=InventoryTransactionOrdering,
)
@strawberry.field
def inventory_report(self, start_time: datetime, end_time: datetime) -> InventoryReport:
if timezone.is_naive(start_time):
start_time = timezone.make_aware(start_time)
if timezone.is_naive(end_time):
end_time = timezone.make_aware(end_time)
if start_time > end_time:
raise ValueError("The report start date must not be after the end date.")
purchases = (
models.InventoryTransaction.objects.filter(created__range=(start_time, end_time))
.values("product_id", "product__name")
.annotate(units=Sum("amount"), value=Sum(F("amount") * F("unit_price")))
)
sales = (
models.OrderLine.objects.filter(order__created__range=(start_time, end_time))
.values("product_id", "product__name")
.annotate(units=Sum("amount"), value=Sum(F("amount") * F("unit_price")))
)
product_totals = {}
for product in purchases:
product_totals[product["product_id"]] = {
"product_id": product["product_id"],
"product_name": product["product__name"],
"purchase_units": product["units"] or 0,
"purchase_value": product["value"] or Decimal(0),
"sales_units": 0,
"sales_value": Decimal(0),
}
for product in sales:
product_data = product_totals.setdefault(
product["product_id"],
{
"product_id": product["product_id"],
"product_name": product["product__name"],
"purchase_units": 0,
"purchase_value": Decimal(0),
"sales_units": 0,
"sales_value": Decimal(0),
},
)
product_data["sales_units"] = product["units"] or 0
product_data["sales_value"] = product["value"] or Decimal(0)
product_data = sorted(product_totals.values(), key=lambda product: product["product_name"])
inventory_purchases = sum((product["purchase_value"] for product in product_data), Decimal(0))
product_sales = sum((product["sales_value"] for product in product_data), Decimal(0))
deposits = (
models.Transaction.objects.filter(created__range=(start_time, end_time)).aggregate(total=Sum("amount"))[
"total"
]
or Decimal(0)
)
order_purchases = (
models.Order.objects.filter(created__range=(start_time, end_time)).aggregate(total=Sum("order_sum"))[
"total"
]
or Decimal(0)
)
deposits_start = (
models.Transaction.objects.filter(created__lt=start_time).aggregate(total=Sum("amount"))["total"]
or Decimal(0)
)
orders_start = (
models.Order.objects.filter(created__lt=start_time).aggregate(total=Sum("order_sum"))["total"] or Decimal(0)
)
deposits_end = (
models.Transaction.objects.filter(created__lte=end_time).aggregate(total=Sum("amount"))["total"]
or Decimal(0)
)
orders_end = (
models.Order.objects.filter(created__lte=end_time).aggregate(total=Sum("order_sum"))["total"] or Decimal(0)
)
return InventoryReport(
products=[InventoryReportProduct(**product) for product in product_data],
inventory_purchases=inventory_purchases,
product_sales=product_sales,
deposits=deposits,
order_purchases=order_purchases,
balance_at_start=deposits_start - orders_start,
balance_at_end=deposits_end - orders_end,
)
+3 -2
View File
@@ -1,5 +1,4 @@
import strawberry import strawberry
import strawberry_django
from django.contrib.auth.models import User as DjangoUser from django.contrib.auth.models import User as DjangoUser
from strawberry import ID from strawberry import ID
@@ -8,7 +7,9 @@ from bbs.api.types.users import User
@strawberry.type @strawberry.type
class UsersQueries: class UsersQueries:
all_users: list[User] = strawberry_django.field() @strawberry.field
def all_users(self) -> list[User]:
return DjangoUser.objects.filter(profile__isnull=False)
@strawberry.field @strawberry.field
def user(self, user_id: ID) -> User: def user(self, user_id: ID) -> User:
+4 -2
View File
@@ -2,6 +2,7 @@ import strawberry
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from strawberry.extensions import SchemaExtension from strawberry.extensions import SchemaExtension
from bbs.api.mutations.inventory import InventoryMutations
from bbs.api.mutations.shop import ShopMutations from bbs.api.mutations.shop import ShopMutations
from bbs.api.mutations.users import UsersMutations from bbs.api.mutations.users import UsersMutations
from bbs.api.queries.inventory import InventoryQueries from bbs.api.queries.inventory import InventoryQueries
@@ -15,12 +16,13 @@ class Query(InventoryQueries, ShopQueries, UsersQueries):
@strawberry.type @strawberry.type
class Mutation(ShopMutations, UsersMutations): class Mutation(InventoryMutations, ShopMutations, UsersMutations):
pass pass
class HandleNotFound(SchemaExtension): class HandleNotFound(SchemaExtension):
def on_request_end(self): def on_operation(self):
yield
result = self.execution_context.result result = self.execution_context.result
if result.errors: if result.errors:
for error in result.errors: for error in result.errors:
+40 -1
View File
@@ -1,12 +1,14 @@
from decimal import Decimal
from typing import TYPE_CHECKING, Annotated from typing import TYPE_CHECKING, Annotated
import strawberry import strawberry
import strawberry_django import strawberry_django
from strawberry import auto from strawberry import ID, auto
from bbs import models from bbs import models
if TYPE_CHECKING: if TYPE_CHECKING:
from .shop import Product
from .users import User from .users import User
@@ -21,3 +23,40 @@ class Transaction:
user: Annotated["User", strawberry.lazy(".users")] user: Annotated["User", strawberry.lazy(".users")]
amount: auto amount: auto
created: auto created: auto
@strawberry_django.type(models.InventoryTransaction)
class InventoryTransaction:
id: auto
user: Annotated["User", strawberry.lazy(".users")]
product: Annotated["Product", strawberry.lazy(".shop")]
amount: auto
unit_price: auto
comment: auto
created: auto
@strawberry_django.order_type(models.InventoryTransaction)
class InventoryTransactionOrdering:
created: auto
@strawberry.type
class InventoryReportProduct:
product_id: ID
product_name: str
purchase_units: int
purchase_value: Decimal
sales_units: int
sales_value: Decimal
@strawberry.type
class InventoryReport:
products: list[InventoryReportProduct]
inventory_purchases: Decimal
product_sales: Decimal
deposits: Decimal
order_purchases: Decimal
balance_at_start: Decimal
balance_at_end: Decimal
+9 -3
View File
@@ -1,11 +1,12 @@
from decimal import Decimal
from typing import TYPE_CHECKING, Annotated from typing import TYPE_CHECKING, Annotated
import strawberry import strawberry
import strawberry_django import strawberry_django
from strawberry import auto from strawberry import auto
from strawberry.scalars import JSON
from bbs import models from bbs import models
from bbs.api.types.utils import JSONScalar
if TYPE_CHECKING: if TYPE_CHECKING:
from .users import User from .users import User
@@ -24,8 +25,13 @@ class Product:
image_url: str image_url: str
sale_price_int: auto sale_price_int: auto
sale_price_ext: auto sale_price_ext: auto
active: auto
inventory_amount: auto
updated: auto
wholesale_unit_price: Decimal
wholesale_value: Decimal
# FIXME: stricter types # FIXME: stricter types
user_counts: JSONScalar user_counts: JSON
@strawberry_django.order_type(models.Order) @strawberry_django.order_type(models.Order)
@@ -36,7 +42,7 @@ class OrderOrdering:
@strawberry_django.type(models.Order) @strawberry_django.type(models.Order)
class Order: class Order:
id: auto id: auto
customer: Annotated["User", strawberry.lazy(".users")] customer: Annotated["User", strawberry.lazy(".users")] | None
order_sum: auto order_sum: auto
created: auto created: auto
orderlines: list["OrderLine"] orderlines: list["OrderLine"]
+6 -3
View File
@@ -14,7 +14,7 @@ from bbs.api.types.shop import Point, ProductOrderStat
class TopMonth: class TopMonth:
period: str period: str
count: int count: int
sum: int sum: float
@strawberry_django.type(models.UserProfile) @strawberry_django.type(models.UserProfile)
@@ -27,7 +27,10 @@ class UserProfile:
@strawberry.field() @strawberry.field()
def top_months(self) -> list[TopMonth]: def top_months(self) -> list[TopMonth]:
return [TopMonth(**month) for month in self.top_months()] return [
TopMonth(period=month["period"], count=month["count"], sum=float(month["sum"]))
for month in self.top_months()
]
@strawberry.field() @strawberry.field()
def product_order_stats(self) -> list[ProductOrderStat]: def product_order_stats(self) -> list[ProductOrderStat]:
@@ -45,4 +48,4 @@ class User:
id: auto id: auto
first_name: auto first_name: auto
last_name: auto last_name: auto
profile: UserProfile profile: UserProfile | None
-11
View File
@@ -1,11 +0,0 @@
import json
from typing import Any, NewType
import strawberry
JSONScalar = strawberry.scalar(
NewType("JSONScalar", Any),
serialize=lambda v: v,
parse_value=lambda v: json.loads(v),
description="The GenericScalar scalar type represents a generic GraphQL scalar value that could be: List or Object.",
)
+151
View File
@@ -0,0 +1,151 @@
import os
from datetime import datetime, time, timedelta
from decimal import Decimal
from django.contrib.auth import get_user_model
from django.core.management import BaseCommand
from django.db.models import Sum
from django.utils import timezone
from bbs.models import InventoryTransaction, Order, OrderLine, Product, Transaction, UserProfile
DEMO_USERS = (
("demo-ada", "Ada", "Nord", "ada@example.test", Decimal("300.0")),
("demo-bjørn", "Bjørn", "Berg", "bjorn@example.test", Decimal("250.0")),
("demo-casey", "Casey", "Sol", "casey@example.test", Decimal("200.0")),
)
DEMO_PRODUCTS = (
("Demo Cola", Decimal("25.0"), Decimal("30.0"), 30),
("Demo Kaffe", Decimal("15.0"), Decimal("20.0"), 30),
("Demo Sjokolade", Decimal("20.0"), Decimal("25.0"), 30),
("Demo Energidrikk", Decimal("35.0"), Decimal("40.0"), 30),
)
class Command(BaseCommand):
help = "Create repeatable demo users, products, deposits, and orders for local testing."
def handle(self, *args, **options): # noqa: ARG002
users = self.seed_users()
products = self.seed_products(users[0][0])
self.seed_deposits(users)
self.seed_orders(users, products)
self.update_balances(users)
self.create_admin()
self.stdout.write(self.style.SUCCESS("Demo data is ready."))
if not os.environ.get("BBS_SEED_ADMIN_PASSWORD"):
self.stdout.write("Create an admin login with: uv run manage.py createsuperuser")
@staticmethod
def seed_users():
user_model = get_user_model()
users = []
for username, first_name, last_name, email, deposit_amount in DEMO_USERS:
user, _ = user_model.objects.get_or_create(
username=username,
defaults={"first_name": first_name, "last_name": last_name, "email": email},
)
UserProfile.objects.get_or_create(user=user)
users.append((user, deposit_amount))
return users
@staticmethod
def seed_products(stock_user):
products = {}
for name, sale_price_int, sale_price_ext, stock in DEMO_PRODUCTS:
product, created = Product.objects.get_or_create(
name=name,
defaults={
"sale_price_int": sale_price_int,
"sale_price_ext": sale_price_ext,
"inventory_amount": stock,
"description": "Demo product for local development.",
},
)
products[name] = product
if created:
InventoryTransaction.objects.get_or_create(
user=stock_user,
product=product,
comment="Demo opening stock",
defaults={"amount": stock, "unit_price": Decimal("10.0")},
)
return products
def seed_deposits(self, users):
for index, (user, deposit_amount) in enumerate(users):
created_at = self.timestamp(days_ago=32 + index)
deposit, created = Transaction.objects.get_or_create(
user=user,
created=created_at,
defaults={"amount": deposit_amount},
)
if created:
deposit.created = created_at
deposit.save(update_fields=["created"])
def seed_orders(self, users, products):
orders = (
(0, 24, (("Demo Cola", 2), ("Demo Sjokolade", 1))),
(1, 19, (("Demo Kaffe", 2), ("Demo Sjokolade", 1))),
(2, 11, (("Demo Energidrikk", 1),)),
(0, 4, (("Demo Kaffe", 1), ("Demo Cola", 1))),
(1, 1, (("Demo Cola", 2),)),
)
for user_index, days_ago, lines in orders:
user = users[user_index][0]
created_at = self.timestamp(days_ago=days_ago)
total = sum(
(products[name].sale_price_int * amount for name, amount in lines),
start=Decimal("0"),
)
order, created = Order.objects.get_or_create(
customer=user,
created=created_at,
defaults={"order_sum": total},
)
if created:
order.created = created_at
order.save(update_fields=["created"])
for name, amount in lines:
product = products[name]
OrderLine.objects.create(
order=order,
product=product,
amount=amount,
unit_price=product.sale_price_int,
)
product.inventory_amount -= amount
product.save(update_fields=["inventory_amount"])
@staticmethod
def update_balances(users):
for user, _ in users:
deposits = Transaction.objects.filter(user=user).aggregate(total=Sum("amount"))["total"] or Decimal("0")
purchases = Order.objects.filter(customer=user).aggregate(total=Sum("order_sum"))["total"] or Decimal("0")
profile = user.profile
profile.balance = deposits - purchases
profile.save(update_fields=["balance"])
def create_admin(self):
admin_password = os.environ.get("BBS_SEED_ADMIN_PASSWORD")
if not admin_password:
return
user_model = get_user_model()
if user_model.objects.filter(username="admin").exists():
self.stdout.write("Existing 'admin' account left unchanged.")
else:
user_model.objects.create_superuser(
username="admin",
email="admin@example.test",
password=admin_password,
)
self.stdout.write("Created superuser 'admin'.")
@staticmethod
def timestamp(days_ago: int) -> datetime:
date = timezone.localdate() - timedelta(days=days_ago)
return timezone.make_aware(datetime.combine(date, time(hour=12)))
+2 -2
View File
@@ -1,5 +1,5 @@
import hashlib # noqa: EXE002 import hashlib # noqa: EXE002
from datetime import datetime from datetime import UTC, datetime
from decimal import Decimal from decimal import Decimal
from itertools import groupby from itertools import groupby
@@ -224,7 +224,7 @@ class UserProfile(models.Model):
def last_purchase_date(self) -> datetime: def last_purchase_date(self) -> datetime:
last = self.last_purchase() last = self.last_purchase()
return last.created if last else datetime.min # noqa: DTZ901 return last.created if last else datetime.min.replace(tzinfo=UTC)
def top_months(self, limit=5): def top_months(self, limit=5):
return ( return (
View File
Whitespace-only changes.
+38
View File
@@ -0,0 +1,38 @@
from bbs.forms import DateRangeForm, InventoryTransactionForm
from bbs.models import Product
def test_inventory_form_filters_active_products(db): # noqa: ARG001
active = Product.objects.create(name="active", sale_price_int=10, sale_price_ext=12, active=True)
inactive = Product.objects.create(name="inactive", sale_price_int=10, sale_price_ext=12, active=False)
product_queryset = InventoryTransactionForm().fields["product"].queryset
assert active in product_queryset
assert inactive not in product_queryset
def test_inventory_form_valid(db): # noqa: ARG001
product = Product.objects.create(name="borg", sale_price_ext=35, sale_price_int=30)
form = InventoryTransactionForm(
data={"product": product.pk, "amount": 5, "unit_price": "12.5", "comment": ""},
)
assert form.is_valid()
def test_inventory_form_invalid_missing_required_fields(db): # noqa: ARG001
form = InventoryTransactionForm(data={})
assert not form.is_valid()
assert "product" in form.errors
def test_date_range_form_allows_missing_dates():
assert DateRangeForm(data={}).is_valid()
def test_date_range_form_accepts_dates():
form = DateRangeForm(data={"start_time": "2026-01-01 00:00:00", "end_time": "2026-01-31 23:59:59"})
assert form.is_valid()
assert form.cleaned_data["start_time"] is not None
+60
View File
@@ -0,0 +1,60 @@
import pytest
from django.contrib.auth import get_user_model
from django.core.management import call_command
from bbs.models import UserProfile
@pytest.fixture
def user(db): # noqa: ARG001
user = get_user_model().objects.create_user(username="profile-image-user", email="image@example.com")
UserProfile.objects.create(user=user)
return user
def test_profile_images_updates_missing_image(user):
UserProfile.objects.filter(pk=user.profile.pk).update(image="")
call_command("profile_images")
user.profile.refresh_from_db()
assert user.profile.image != ""
def test_profile_images_dry_run_skips_update(user):
UserProfile.objects.filter(pk=user.profile.pk).update(image="")
call_command("profile_images", dry_run=True)
user.profile.refresh_from_db()
assert user.profile.image == ""
def test_profile_images_force_overwrites_existing_image(user):
UserProfile.objects.filter(pk=user.profile.pk).update(image="https://example.com/old.jpg")
call_command("profile_images", force=True)
user.profile.refresh_from_db()
assert user.profile.image not in ("", "https://example.com/old.jpg")
@pytest.mark.django_db
def test_export_schema_writes_file(tmp_path, settings):
settings.BASE_DIR = str(tmp_path)
call_command("export_schema")
schema_file = tmp_path / "schema.graphql"
assert schema_file.exists()
assert "type Query" in schema_file.read_text()
@pytest.mark.django_db
def test_export_schema_stdout_does_not_write_file(tmp_path, settings, capsys):
settings.BASE_DIR = str(tmp_path)
call_command("export_schema", filename="-")
assert not (tmp_path / "schema.graphql").exists()
assert capsys.readouterr().out == ""
+12
View File
@@ -0,0 +1,12 @@
from datetime import UTC, datetime
from django.contrib.auth import get_user_model
from bbs.models import UserProfile
def test_last_purchase_date_without_orders_is_timezone_aware(db): # noqa: ARG001
user = get_user_model().objects.create_user(username="no-purchases")
profile = UserProfile.objects.create(user=user)
assert profile.last_purchase_date() == datetime.min.replace(tzinfo=UTC)
+158 -2
View File
@@ -1,8 +1,10 @@
import pytest # noqa: INP001 import pytest
from django.contrib.auth import get_user_model
from django.test import Client from django.test import Client
from django.utils import timezone
from strawberry.django.test import GraphQLTestClient from strawberry.django.test import GraphQLTestClient
from bbs.models import Order, Product from bbs.models import InventoryTransaction, Order, OrderLine, Product, Transaction
@pytest.fixture(name="gql_client") @pytest.fixture(name="gql_client")
@@ -15,6 +17,26 @@ def product_fix(db): # noqa: ARG001
return Product.objects.create(name="borg", sale_price_ext=35, sale_price_int=30) return Product.objects.create(name="borg", sale_price_ext=35, sale_price_int=30)
def test_user_not_found(db, gql_client): # noqa: ARG001
result = gql_client.query(
"""
query User($userId: ID!) {
user(userId: $userId) {
id
}
}
""",
{"userId": "999"},
assert_no_errors=False,
)
assert result.errors
error = result.errors[0]
assert error["path"] == ["user"]
assert error["extensions"]["code"] == "NOT_FOUND"
assert error["extensions"]["model"] == "User"
def test_create_order(product, gql_client): def test_create_order(product, gql_client):
assert Order.objects.count() == 0 assert Order.objects.count() == 0
variables = { variables = {
@@ -50,3 +72,137 @@ def test_create_order(product, gql_client):
assert Order.objects.count() == 1 assert Order.objects.count() == 1
o = Order.objects.first() o = Order.objects.first()
assert o.orderlines.first().product.id == product.id assert o.orderlines.first().product.id == product.id
def test_create_inventory_transaction_requires_login(product, gql_client):
result = gql_client.query(
"""
mutation CreateInventoryTransaction($data: InventoryTransactionInput!) {
createInventoryTransaction(data: $data) {
... on FormErrors { message }
}
}
""",
variables={"data": {"productId": str(product.id), "amount": 5, "unitPrice": "12.5"}},
)
assert not result.errors
assert result.data["createInventoryTransaction"]["message"] == "Logg inn for å registrere varetransaksjoner."
product.refresh_from_db()
assert product.inventory_amount == 0
assert InventoryTransaction.objects.count() == 0
def test_create_inventory_transaction_updates_stock(product, gql_client, client):
user = get_user_model().objects.create_user(username="stock-manager")
client.force_login(user)
result = gql_client.query(
"""
mutation CreateInventoryTransaction($data: InventoryTransactionInput!) {
createInventoryTransaction(data: $data) {
... on CreateInventoryTransactionSuccess {
inventoryTransaction {
amount
unitPrice
product { id inventoryAmount }
user { id }
}
}
}
}
""",
variables={
"data": {
"productId": str(product.id),
"amount": 5,
"unitPrice": "12.5",
"comment": "Restock",
}
},
)
assert not result.errors
transaction = result.data["createInventoryTransaction"]["inventoryTransaction"]
assert transaction["amount"] == 5
assert transaction["unitPrice"] == "12.5"
assert transaction["product"]["inventoryAmount"] == 5
assert transaction["user"]["id"] == str(user.id)
product.refresh_from_db()
assert product.inventory_amount == 5
def test_inventory_report_aggregates_period_data(product, gql_client):
user = get_user_model().objects.create_user(username="report-user")
InventoryTransaction.objects.create(user=user, product=product, amount=5, unit_price=10)
transaction = Transaction.objects.create(user=user, amount=100)
order = Order.objects.create(order_sum=30)
OrderLine.objects.create(order=order, product=product, amount=2, unit_price=15)
start_time = timezone.make_naive(timezone.now() - timezone.timedelta(days=1))
end_time = timezone.make_naive(timezone.now() + timezone.timedelta(days=1))
result = gql_client.query(
"""
query InventoryReport($startTime: DateTime!, $endTime: DateTime!) {
inventoryReport(startTime: $startTime, endTime: $endTime) {
products {
productId
purchaseUnits
purchaseValue
salesUnits
salesValue
}
inventoryPurchases
productSales
deposits
orderPurchases
balanceAtStart
balanceAtEnd
}
}
""",
variables={"startTime": start_time.isoformat(), "endTime": end_time.isoformat()},
)
assert not result.errors
report = result.data["inventoryReport"]
assert report["products"] == [
{
"productId": str(product.id),
"purchaseUnits": 5,
"purchaseValue": "50",
"salesUnits": 2,
"salesValue": "30",
}
]
assert report["inventoryPurchases"] == "50"
assert report["productSales"] == "30"
assert report["deposits"] == "100"
assert report["orderPurchases"] == "30"
assert report["balanceAtStart"] == "0"
assert report["balanceAtEnd"] == "70"
assert transaction.amount == 100
products_result = gql_client.query(
"""
query InventoryProducts {
allProducts {
id
active
inventoryAmount
wholesaleUnitPrice
wholesaleValue
}
inventoryTransactionList(ordering: [{created: DESC}], pagination: {limit: 20, offset: 0}) {
amount
unitPrice
product { name }
user { firstName lastName }
}
}
"""
)
assert not products_result.errors
assert products_result.data["allProducts"][0]["active"]
assert products_result.data["inventoryTransactionList"][0]["amount"] == 5
+65
View File
@@ -0,0 +1,65 @@
import pytest
from django.contrib.auth import get_user_model
from django.core.management import call_command
from django.test import Client
from strawberry.django.test import GraphQLTestClient
from bbs.models import InventoryTransaction, Order, Product, Transaction
@pytest.mark.django_db
def test_seed_demo_data_is_repeatable():
call_command("seed_demo_data")
call_command("seed_demo_data")
assert get_user_model().objects.filter(username__startswith="demo-").count() == 3
assert Product.objects.filter(name__startswith="Demo ").count() == 4
assert Order.objects.count() == 5
assert Transaction.objects.count() == 3
assert InventoryTransaction.objects.count() == 4
assert sum(product.inventory_amount for product in Product.objects.filter(name__startswith="Demo ")) == 109
@pytest.mark.django_db
def test_demo_data_is_available_when_an_admin_has_no_profile():
user_model = get_user_model()
user_model.objects.create_superuser(username="admin", email="admin@example.test", password="password")
call_command("seed_demo_data")
gql_client = GraphQLTestClient(Client())
result = gql_client.query(
"""
{
allProducts(filters: {active: true}) { name }
allUsers { firstName lastName profile { balance } }
}
"""
)
assert not result.errors
assert result.data
assert len(result.data["allProducts"]) == 4
assert len(result.data["allUsers"]) == 3
ada = user_model.objects.get(username="demo-ada")
detail_result = gql_client.query(
"""
query UserDetail($userId: ID!) {
user(userId: $userId) {
firstName
profile {
balance
lastPurchaseDate
orderSumTotal
topMonths { period sum count }
productOrderStats { productName data { x y } }
}
}
}
""",
variables={"userId": str(ada.pk)},
)
assert not detail_result.errors
assert detail_result.data["user"]["profile"]["topMonths"]
assert isinstance(detail_result.data["user"]["profile"]["topMonths"][0]["sum"], float)
+10
View File
@@ -0,0 +1,10 @@
import pytest
from django.test import Client
@pytest.mark.parametrize("path", ["/inventory", "/inventory/add", "/report"])
def test_inventory_pages_serve_the_spa_entry(client: Client, path: str):
response = client.get(path)
assert response.status_code == 200
assert b'<div id="root"></div>' in b"".join(response.streaming_content)
+21
View File
@@ -0,0 +1,21 @@
from bbs.utils import format_username
def test_format_username():
assert format_username("Ada", "Lovelace") == "adalovela"
def test_format_username_truncates_names():
assert format_username("Aleksandar", "Vukovic") == "aleksavukovi"
def test_format_username_removes_spaces():
assert format_username("Mary Jane", "van Dijk") == "maryjavandij"
def test_format_username_drops_non_ascii_characters():
assert format_username("Ångström", "Ø") == "ngstr"
def test_format_username_allows_empty_names():
assert format_username("", "") == ""
+7 -5
View File
@@ -7,15 +7,17 @@ from django.views.decorators.csrf import csrf_exempt
from strawberry.django.views import GraphQLView from strawberry.django.views import GraphQLView
from bbs.api.schema import schema from bbs.api.schema import schema
from bbs.views.reports import inventory, inventory_add, report from bbs.views.spa import spa_index
admin.autodiscover() admin.autodiscover()
urlpatterns = [ urlpatterns = [
# TODO: migrate reports to SPA path("inventory", spa_index, name="inventory"),
path("inventory/", inventory, name="inventory"), path("inventory/", spa_index),
path("inventory/add/", inventory_add, name="inventory-add"), path("inventory/add", spa_index, name="inventory-add"),
path("report", report, name="report"), path("inventory/add/", spa_index),
path("report", spa_index, name="report"),
path("report/", spa_index),
path("accounts/login/", LoginView.as_view(), name="django.contrib.auth.views.login"), path("accounts/login/", LoginView.as_view(), name="django.contrib.auth.views.login"),
path( path(
"accounts/logout/", "accounts/logout/",
+5 -4
View File
@@ -1,8 +1,9 @@
from datetime import datetime, timedelta from datetime import timedelta
from itertools import groupby from itertools import groupby
from django.contrib import messages from django.contrib import messages
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.utils import timezone
from bbs.forms import DateRangeForm, InventoryTransactionForm from bbs.forms import DateRangeForm, InventoryTransactionForm
from bbs.models import InventoryTransaction, Order, Product, Transaction, UserProfile from bbs.models import InventoryTransaction, Order, Product, Transaction, UserProfile
@@ -10,12 +11,12 @@ from bbs.models import InventoryTransaction, Order, Product, Transaction, UserPr
def report(request): def report(request):
# FIXME: split this up in smaller pieces # FIXME: split this up in smaller pieces
datetime_now = datetime.now() # noqa: DTZ005 datetime_now = timezone.localtime()
start_time = datetime.now() - timedelta(days=30) # noqa: DTZ005 start_time = timezone.localtime() - timedelta(days=30)
# start of day, 1 month # start of day, 1 month
start_time = start_time.replace(hour=0, minute=0, second=0, microsecond=0) start_time = start_time.replace(hour=0, minute=0, second=0, microsecond=0)
# end of day # end of day
end_time = datetime.now().replace(hour=23, minute=59, second=59, microsecond=999) # noqa: DTZ005 end_time = timezone.localtime().replace(hour=23, minute=59, second=59, microsecond=999)
if request.GET.get("start_time", False) or request.GET.get("end_time", False): if request.GET.get("start_time", False) or request.GET.get("end_time", False):
form = DateRangeForm(request.GET) form = DateRangeForm(request.GET)
+11
View File
@@ -0,0 +1,11 @@
from pathlib import Path
from django.contrib.staticfiles import finders
from django.http import FileResponse, Http404
def spa_index(_request):
index_path = finders.find("index.html")
if not index_path:
raise Http404("The frontend has not been built.")
return FileResponse(Path(index_path).open("rb"), content_type="text/html")
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash #!/bin/bash
python manage.py runserver exec python manage.py runserver 127.0.0.1:3000
+1 -2
View File
@@ -1,4 +1,3 @@
#!/bin/bash #!/bin/bash
cd frontend && npm run start cd frontend && npm run start -- --host 127.0.0.1
+1
View File
@@ -19,6 +19,7 @@ module.exports = {
rules: { rules: {
"no-console": "off", "no-console": "off",
"prefer-destructuring": "off", "prefer-destructuring": "off",
"import/no-unresolved": ["error", { ignore: ["\\?react$"] }],
"import/no-extraneous-dependencies": [ "import/no-extraneous-dependencies": [
"error", "error",
{ {
+24 -2
View File
@@ -3,6 +3,28 @@ documents: ./src/**/*.graphql
generates: generates:
./src/types.ts: ./src/types.ts:
plugins: plugins:
- typescript - typescript:
- typescript-operations typesPrefix: Schema
scalars:
DateTime:
input: string
output: string
Decimal:
input: string
output: string
JSON:
input: unknown
output: "Array<{ name: string; count: number }>"
- typescript-operations:
nonOptionalTypename: true
scalars:
DateTime:
input: string
output: string
Decimal:
input: string
output: string
JSON:
input: unknown
output: "Array<{ name: string; count: number }>"
- typed-document-node - typed-document-node
+5225 -16611
View File
File diff suppressed because it is too large. Load diff
+30 -35
View File
@@ -7,59 +7,54 @@
"check-types": "tsc", "check-types": "tsc",
"dev": "vite", "dev": "vite",
"format:js": "eslint --fix src", "format:js": "eslint --fix src",
"format:styles": "stylelint --fix src", "format:styles": "prettier --write \"src/**/*.css\"",
"generate": "graphql-codegen", "generate": "node scripts/generate-types.mjs",
"lint:js": "eslint src", "lint:js": "eslint src",
"lint:styles": "stylelint src", "lint:styles": "prettier --check \"src/**/*.css\"",
"preview": "vite preview", "preview": "vite preview",
"start": "vite", "start": "vite",
"test": "jest" "test": "node --test"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.5.10", "@apollo/client": "^3.14.1",
"@nivo/bar": "^0.79.1", "@nivo/bar": "^0.99.0",
"@nivo/line": "^0.79.1", "@nivo/line": "^0.99.0",
"@nivo/pie": "^0.79.1", "@nivo/pie": "^0.99.0",
"clsx": "^1.1.1", "clsx": "^1.1.1",
"date-fns": "^2.28.0", "date-fns": "^2.28.0",
"graphql": "^16.3.0", "graphql": "^16.3.0",
"react": "^17.0.2", "react": "^18.3.1",
"react-dom": "^17.0.2", "react-dom": "^18.3.1",
"react-hook-form": "^7.30.0", "react-hook-form": "^7.30.0",
"react-router-dom": "^6.3.0", "react-router-dom": "^7.18.4",
"use-url-search-params": "^2.5.1" "use-url-search-params": "^2.5.1"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "^2.6.2", "@graphql-codegen/core": "^6.2.2",
"@graphql-codegen/typed-document-node": "^2.2.8", "@graphql-codegen/typescript": "^6.1.2",
"@graphql-codegen/typescript-operations": "^2.3.5", "@graphql-codegen/typed-document-node": "^7.2.0",
"@graphql-codegen/typescript-operations": "^6.1.10",
"@graphql-typed-document-node/core": "^3.1.1", "@graphql-typed-document-node/core": "^3.1.1",
"@types/react": "^17.0.44", "@types/react": "^18.3.28",
"@types/react-dom": "^17.0.15", "@types/react-dom": "^18.3.7",
"@types/react-router-dom": "^5.3.3", "@typescript-eslint/eslint-plugin": "^8.72.0",
"@typescript-eslint/eslint-plugin": "^5.19.0", "@typescript-eslint/parser": "^8.72.0",
"@typescript-eslint/parser": "^5.19.0", "eslint": "^8.57.1",
"@vitejs/plugin-react": "^1.3.1", "eslint-config-prettier": "^10.1.8",
"@vitejs/plugin-react-refresh": "^1.3.6",
"eslint": "^8.13.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^5.5.7",
"eslint-plugin-react": "^7.29.4", "eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.4.0", "eslint-plugin-react-hooks": "^4.4.0",
"jest": "^27.5.1", "prettier": "^3.6.2",
"prettier": "^2.6.2", "typescript": "^5.9.3",
"stylelint": "^14.7.0", "vite": "^6.4.5",
"stylelint-config-idiomatic-order": "^8.1.0", "vite-plugin-svgr": "^5.2.0",
"stylelint-config-prettier": "^9.0.3", "yaml": "^2.8.1"
"stylelint-config-recommended": "^7.0.0",
"stylelint-order": "^5.0.0",
"stylelint-prettier": "^2.0.0",
"typescript": "^4.6.3",
"vite": "^2.9.5",
"vite-plugin-svgr": "^1.1.0"
}, },
"engines": { "engines": {
"node": "20" "node": "20"
},
"overrides": {
"@whatwg-node/promise-helpers": "^1.3.2"
} }
} }
+66
View File
@@ -0,0 +1,66 @@
import { readdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { codegen } from "@graphql-codegen/core";
import { plugin as typedDocumentNodePlugin } from "@graphql-codegen/typed-document-node";
import { plugin as typescriptOperationsPlugin } from "@graphql-codegen/typescript-operations";
import { plugin as typescriptPlugin } from "@graphql-codegen/typescript";
import { buildASTSchema, parse } from "graphql";
import { parse as parseYaml } from "yaml";
const frontendRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const configPath = path.join(frontendRoot, "codegen.yml");
const config = parseYaml(await readFile(configPath, "utf8"));
const schemaDocument = parse(await readFile(path.resolve(frontendRoot, config.schema), "utf8"));
const outputEntries = Object.entries(config.generates ?? {});
if (outputEntries.length !== 1) {
throw new Error(`Expected one GraphQL Codegen output in ${configPath}`);
}
const [outputName, outputConfig] = outputEntries[0];
const outputPath = path.resolve(frontendRoot, outputName);
const sourceRoot = path.join(frontendRoot, "src");
const plugins = outputConfig.plugins.map((plugin) =>
typeof plugin === "string" ? { [plugin]: {} } : plugin,
);
async function findGraphqlFiles(directory) {
const entries = await readdir(directory, { withFileTypes: true });
const nestedFiles = await Promise.all(
entries.map((entry) => {
const entryPath = path.join(directory, entry.name);
if (entry.isDirectory()) return findGraphqlFiles(entryPath);
return entry.isFile() && entry.name.endsWith(".graphql") ? [entryPath] : [];
}),
);
return nestedFiles.flat();
}
const documents = await Promise.all(
(await findGraphqlFiles(sourceRoot))
.sort()
.map(async (location) => ({
location,
document: parse(await readFile(location, "utf8")),
})),
);
const pluginMap = {
typescript: { plugin: typescriptPlugin },
"typescript-operations": { plugin: typescriptOperationsPlugin },
"typed-document-node": { plugin: typedDocumentNodePlugin },
};
const generated = await codegen({
filename: outputPath,
schema: schemaDocument,
schemaAst: buildASTSchema(schemaDocument),
documents,
config: {},
plugins,
pluginMap,
});
await writeFile(outputPath, generated);
+2 -1
View File
@@ -1,4 +1,5 @@
.app { .app {
flex: 1 0 auto;
width: min(100% - 2rem, 1220px);
margin: 1rem auto; margin: 1rem auto;
max-width: 1220px;
} }
+8
View File
@@ -4,11 +4,15 @@ import { Route, Routes as ReactRouterRoutes } from "react-router-dom";
const Splash = React.lazy(() => import("./components/Splash")); const Splash = React.lazy(() => import("./components/Splash"));
const UserCreate = React.lazy(() => import("./features/auth/UserCreate")); const UserCreate = React.lazy(() => import("./features/auth/UserCreate"));
const Deposit = React.lazy(() => import("./features/deposit/Deposit")); const Deposit = React.lazy(() => import("./features/deposit/Deposit"));
const Inventory = React.lazy(() => import("./features/inventory/Inventory"));
const InventoryAdd = React.lazy(() => import("./features/inventory/InventoryAdd"));
const InventoryReport = React.lazy(() => import("./features/inventory/InventoryReport"));
const Log = React.lazy(() => import("./features/log/Log")); const Log = React.lazy(() => import("./features/log/Log"));
const Register = React.lazy(() => import("./features/register/Register")); const Register = React.lazy(() => import("./features/register/Register"));
const OrderStats = React.lazy(() => import("./features/stats/OrderStats")); const OrderStats = React.lazy(() => import("./features/stats/OrderStats"));
const ProductStats = React.lazy(() => import("./features/stats/ProductStats")); const ProductStats = React.lazy(() => import("./features/stats/ProductStats"));
const Stats = React.lazy(() => import("./features/stats/Stats")); const Stats = React.lazy(() => import("./features/stats/Stats"));
const UserStats = React.lazy(() => import("./features/stats/UserStats"));
const UserDetail = React.lazy(() => import("./features/users/UserDetail")); const UserDetail = React.lazy(() => import("./features/users/UserDetail"));
const UserList = React.lazy(() => import("./features/users/UserList")); const UserList = React.lazy(() => import("./features/users/UserList"));
@@ -16,10 +20,14 @@ export const Routes = (): JSX.Element => (
<React.Suspense fallback={<div>Loading...</div>}> <React.Suspense fallback={<div>Loading...</div>}>
<ReactRouterRoutes> <ReactRouterRoutes>
<Route path="/deposit" element={<Deposit />} /> <Route path="/deposit" element={<Deposit />} />
<Route path="/inventory" element={<Inventory />} />
<Route path="/inventory/add" element={<InventoryAdd />} />
<Route path="/report" element={<InventoryReport />} />
<Route path="/log" element={<Log />} /> <Route path="/log" element={<Log />} />
<Route path="/stats" element={<Stats />} /> <Route path="/stats" element={<Stats />} />
<Route path="/stats/orders" element={<OrderStats />} /> <Route path="/stats/orders" element={<OrderStats />} />
<Route path="/stats/products" element={<ProductStats />} /> <Route path="/stats/products" element={<ProductStats />} />
<Route path="/stats/users" element={<UserStats />} />
<Route path="/users" element={<UserList />} /> <Route path="/users" element={<UserList />} />
<Route path="/users/create" element={<UserCreate />} /> <Route path="/users/create" element={<UserCreate />} />
<Route path="/user/:userId" element={<UserDetail />} /> <Route path="/user/:userId" element={<UserDetail />} />
+1 -1
View File
@@ -1,7 +1,7 @@
import { ApolloClient, InMemoryCache } from "@apollo/client"; import { ApolloClient, InMemoryCache } from "@apollo/client";
const client = new ApolloClient({ const client = new ApolloClient({
uri: `${import.meta.env.VITE_API_URL}/graphql/`, uri: import.meta.env.DEV ? "/graphql/" : `${import.meta.env.VITE_API_URL}/graphql/`,
cache: new InMemoryCache(), cache: new InMemoryCache(),
}); });
+2 -4
View File
@@ -1,8 +1,6 @@
.footer { .footer {
position: absolute; flex-shrink: 0;
right: 0; padding: 0 0 1rem;
bottom: 1rem;
left: 0;
height: 20px; height: 20px;
color: rgba(255, 255, 255, 0.5); color: rgba(255, 255, 255, 0.5);
font-size: 0.8rem; font-size: 0.8rem;
+1 -1
View File
@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { ReactComponent as Brick } from "../assets/brick.svg"; import Brick from "../assets/brick.svg?react";
import styles from "./Footer.module.css"; import styles from "./Footer.module.css";
export const Footer = (): JSX.Element => { export const Footer = (): JSX.Element => {
+7 -5
View File
@@ -1,9 +1,11 @@
.errorMessage { .errorMessage {
font-family: Rocket Rinder, monospace;
font-size: 3rem;
font-style: italic;
color: var(--brand);
text-align: center;
width: 100%; width: 100%;
margin-top: 30vh; margin-top: 30vh;
color: var(--brand);
font-family:
Rocket Rinder,
monospace;
font-size: 3rem;
font-style: italic;
text-align: center;
} }
+56 -8
View File
@@ -1,12 +1,15 @@
.header { .header {
margin: 1rem auto; width: min(100% - 2rem, 1220px);
max-width: 1220px; max-width: 1220px;
margin: 1rem auto;
} }
.nav { .nav {
display: flex; display: flex;
height: 5rem; min-height: 5rem;
align-items: center; align-items: center;
flex-wrap: wrap;
gap: 0.5rem 1rem;
text-transform: uppercase; text-transform: uppercase;
} }
@@ -17,14 +20,21 @@
display: block; display: block;
height: 1px; height: 1px;
background-color: rgba(243, 33, 61, 0.4); background-color: rgba(243, 33, 61, 0.4);
box-shadow: 0 0 4px rgba(243, 33, 61, 0.4), 0 0 11px rgba(243, 33, 61, 0.4), 0 0 19px rgba(243, 33, 61, 0.4); box-shadow:
0 0 4px rgba(243, 33, 61, 0.4),
0 0 11px rgba(243, 33, 61, 0.4),
0 0 19px rgba(243, 33, 61, 0.4);
content: ""; content: "";
transform: rotate(-2.5deg); transform: rotate(-2.5deg);
} }
.navLink:hover:after { .navLink:hover:after {
background-color: var(--brand); background-color: var(--brand);
box-shadow: 0 0 4px var(--brand), 0 0 11px var(--brand), 0 0 19px var(--brand), 0 0 30px var(--brand); box-shadow:
0 0 4px var(--brand),
0 0 11px var(--brand),
0 0 19px var(--brand),
0 0 30px var(--brand);
} }
.logoNav { .logoNav {
@@ -34,6 +44,8 @@
.navItems { .navItems {
display: flex; display: flex;
align-items: center; align-items: center;
flex: 1;
flex-wrap: wrap;
padding: 0; padding: 0;
margin: 0; margin: 0;
list-style: none; list-style: none;
@@ -51,16 +63,47 @@
color: white; color: white;
} }
@media (max-width: 640px) {
.nav {
align-items: flex-start;
}
.logoNav {
margin: 0 auto;
}
.navItems {
justify-content: center;
gap: 0.25rem;
}
.navLink {
display: inline-block;
padding: 0.5rem;
font-size: 0.8rem;
}
.brand {
padding-left: 0;
font-size: 2.5rem;
}
}
/* FIXME: Duplicates Splash styles */ /* FIXME: Duplicates Splash styles */
.brand { .brand {
padding-left: 1rem; padding-left: 1rem;
color: var(--brand); color: var(--brand);
font-family: Rocket Rinder, monospace; font-family:
Rocket Rinder,
monospace;
font-size: 3rem; font-size: 3rem;
font-style: italic; font-style: italic;
letter-spacing: 2px; letter-spacing: 2px;
line-height: 1; line-height: 1;
text-shadow: 0 0 4px rgba(243, 33, 61, 0.8), 0 0 11px rgba(243, 33, 61, 0.8), 0 0 19px rgba(243, 33, 61, 0.8); text-shadow:
0 0 4px rgba(243, 33, 61, 0.8),
0 0 11px rgba(243, 33, 61, 0.8),
0 0 19px rgba(243, 33, 61, 0.8);
} }
.subtitle { .subtitle {
@@ -68,7 +111,9 @@
left: -8px; left: -8px;
animation: flicker 1.5s infinite alternate; animation: flicker 1.5s infinite alternate;
color: var(--interlace); color: var(--interlace);
font-family: Rocket Clouds DEMO, serif; font-family:
Rocket Clouds DEMO,
serif;
font-size: 10px; font-size: 10px;
font-weight: bold; font-weight: bold;
line-height: 1; line-height: 1;
@@ -83,7 +128,10 @@
53%, 53%,
57%, 57%,
100% { 100% {
text-shadow: 0 0 4px rgba(121, 152, 238, 0.8), 0 0 11px rgba(121, 152, 238, 0.8), 0 0 19px rgba(121, 152, 238, 0.8); text-shadow:
0 0 4px rgba(121, 152, 238, 0.8),
0 0 11px rgba(121, 152, 238, 0.8),
0 0 19px rgba(121, 152, 238, 0.8);
} }
20%, 20%,
+10
View File
@@ -32,6 +32,16 @@ export const Header = (): JSX.Element => {
Logg Logg
</NavLink> </NavLink>
</li> </li>
<li>
<NavLink to="/inventory" className={navLinkStyles}>
Varebeholdning
</NavLink>
</li>
<li>
<NavLink to="/report" className={navLinkStyles}>
Rapport
</NavLink>
</li>
<li> <li>
<NavLink to="/stats" className={navLinkStyles}> <NavLink to="/stats" className={navLinkStyles}>
Statistikk Statistikk
+20 -6
View File
@@ -18,22 +18,31 @@
.sun { .sun {
width: clamp(100px, 65vw, 600px); width: clamp(100px, 65vw, 600px);
height: clamp(100px, 65vw, 600px); height: clamp(100px, 65vw, 600px);
border-radius: 50% 50%;
margin: 0 auto; margin: 0 auto;
background: linear-gradient(var(--sun) 50%, var(--sun-end) 90%); background: linear-gradient(var(--sun) 50%, var(--sun-end) 90%);
border-radius: 50% 50%; box-shadow:
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun), 0 0 30px var(--sun); 0 0 4px var(--sun),
0 0 11px var(--sun),
0 0 19px var(--sun),
0 0 30px var(--sun);
} }
.brand { .brand {
position: relative; position: relative;
left: -0.6rem; left: -0.6rem;
padding-top: 3rem; padding-top: 3rem;
color: var(--brand); color: var(--brand);
font-family: Rocket Rinder, monospace; font-family:
Rocket Rinder,
monospace;
font-size: clamp(20px, 20vw, 200px); font-size: clamp(20px, 20vw, 200px);
font-style: italic; font-style: italic;
letter-spacing: 1rem; letter-spacing: 1rem;
line-height: 1; line-height: 1;
text-shadow: 0 0 4px rgba(243, 33, 61, 0.8), 0 0 11px rgba(243, 33, 61, 0.8), 0 0 19px rgba(243, 33, 61, 0.8); text-shadow:
0 0 4px rgba(243, 33, 61, 0.8),
0 0 11px rgba(243, 33, 61, 0.8),
0 0 19px rgba(243, 33, 61, 0.8);
} }
.subtitle { .subtitle {
@@ -42,7 +51,9 @@
left: 0rem; left: 0rem;
animation: flicker 1.5s infinite alternate; animation: flicker 1.5s infinite alternate;
color: var(--interlace); color: var(--interlace);
font-family: Rocket Clouds DEMO, serif; font-family:
Rocket Clouds DEMO,
serif;
font-size: clamp(20px, 5vw, 50px); font-size: clamp(20px, 5vw, 50px);
font-weight: bold; font-weight: bold;
line-height: 1; line-height: 1;
@@ -57,7 +68,10 @@
53%, 53%,
57%, 57%,
100% { 100% {
text-shadow: 0 0 4px rgba(121, 152, 238, 0.8), 0 0 11px rgba(121, 152, 238, 0.8), 0 0 19px rgba(121, 152, 238, 0.8); text-shadow:
0 0 4px rgba(121, 152, 238, 0.8),
0 0 11px rgba(121, 152, 238, 0.8),
0 0 19px rgba(121, 152, 238, 0.8);
} }
20%, 20%,
+8 -2
View File
@@ -16,11 +16,17 @@
.primary { .primary {
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%); background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun); box-shadow:
0 0 4px var(--sun),
0 0 11px var(--sun),
0 0 19px var(--sun);
} }
.danger { .danger {
background: var(--brand); background: var(--brand);
box-shadow: 0 0 4px rgba(243, 33, 61, 0.8), 0 0 11px rgba(243, 33, 61, 0.8), 0 0 19px rgba(243, 33, 61, 0.8); box-shadow:
0 0 4px rgba(243, 33, 61, 0.8),
0 0 11px rgba(243, 33, 61, 0.8),
0 0 19px rgba(243, 33, 61, 0.8);
} }
.close { .close {
+22 -8
View File
@@ -1,33 +1,47 @@
.decoration {
position: absolute;
z-index: -1;
top: 0;
right: 0;
left: 0;
height: 100vh;
overflow: clip;
pointer-events: none;
}
.wireframe { .wireframe {
position: absolute; position: absolute;
bottom: calc(22% - 512px); bottom: calc(22% - 512px);
left: calc(50% - 512px); left: calc(50% - 512px);
display: grid;
width: 1024px; width: 1024px;
height: 1024px; height: 1024px;
display: grid; animation-duration: 10s;
grid-template-columns: repeat(8, 128px);
animation-iteration-count: infinite; animation-iteration-count: infinite;
animation-name: isometric-rotate; animation-name: isometric-rotate;
animation-duration: 10s;
animation-timing-function: linear; animation-timing-function: linear;
pointer-events: none; grid-template-columns: repeat(8, 128px);
z-index: -1;
} }
.cell { .cell {
border-bottom: 2px solid var(--sun);
border-right: 2px solid var(--sun); border-right: 2px solid var(--sun);
border-bottom: 2px solid var(--sun);
} }
.cell:nth-child(8n) { .cell:nth-child(8n) {
border-right: none; border-right: none;
} }
.cell:nth-child(n+57) { .cell:nth-child(n + 57) {
border-bottom: none; border-bottom: none;
} }
@media (max-width: 640px) {
.decoration {
display: none;
}
}
@keyframes isometric-rotate { @keyframes isometric-rotate {
0% { 0% {
transform: rotateX(70deg) rotateY(0deg) rotateZ(-45deg); transform: rotateX(70deg) rotateY(0deg) rotateZ(-45deg);
+2
View File
@@ -3,9 +3,11 @@ import React from "react";
import styles from "./Wireframe.module.css"; import styles from "./Wireframe.module.css";
export const Wireframe = () => ( export const Wireframe = () => (
<div className={styles.decoration}>
<div className={styles.wireframe}> <div className={styles.wireframe}>
{[...Array(64)].map((_, idx) => ( {[...Array(64)].map((_, idx) => (
<div key={idx} className={styles.cell} /> <div key={idx} className={styles.cell} />
))} ))}
</div> </div>
</div>
); );
@@ -3,3 +3,8 @@
max-width: 500px; max-width: 500px;
gap: 1rem; gap: 1rem;
} }
.form input {
width: 100%;
min-width: 0;
}
@@ -1,17 +1,113 @@
.deposit {
display: grid;
align-items: start;
gap: 1rem 2rem;
grid-template:
"heading amount" auto
"search amount" auto
"users amount" 1fr
/ minmax(0, 1fr) minmax(260px, 320px);
}
.heading {
grid-area: heading;
margin-bottom: 0;
}
.search {
display: flex;
align-items: center;
gap: 0.75rem;
grid-area: search;
}
.search input {
width: min(100%, 420px);
}
.userList { .userList {
display: grid; display: grid;
height: 800px; max-height: min(62vh, 680px);
padding-top: 2rem; padding: 0.5rem 0.5rem 0.5rem 0;
grid-template-columns: repeat(5, 220px); gap: 1rem;
grid-area: users;
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
overflow-y: auto; overflow-y: auto;
column-gap: 16px;
row-gap: 24px;
} }
.amountSection { .amountSection {
margin-top: 32px; position: sticky;
top: 1rem;
padding: 1.25rem;
border: 1px solid rgba(221, 81, 127, 0.45);
border-radius: 8px;
background: rgba(35, 15, 41, 0.84);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.24);
grid-area: amount;
}
.amountSection h2 {
margin-top: 0;
}
.selectedUser {
min-height: 1.5em;
margin: 0 0 1.25rem;
color: rgba(255, 255, 255, 0.78);
text-shadow: none;
}
.selectedUser strong {
color: var(--text);
}
.amountLabel {
display: block;
margin-bottom: 0.4rem;
color: var(--sun);
}
.amountInput {
display: flex;
align-items: center;
gap: 0.75rem;
}
.amountInput input {
width: 100%;
min-width: 0;
font-size: 1.8rem;
}
.amountInput span {
color: rgba(255, 255, 255, 0.8);
text-shadow: none;
} }
.checkout { .checkout {
margin-top: 32px; display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 1.5rem;
}
@media (max-width: 760px) {
.deposit {
gap: 0.75rem;
grid-template:
"heading" auto
"search" auto
"amount" auto
"users" auto
/ minmax(0, 1fr);
}
.amountSection {
position: static;
}
.userList {
max-height: 45vh;
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
}
} }
+43 -20
View File
@@ -8,12 +8,13 @@ const Deposit = (): JSX.Element => {
const { const {
loading, loading,
selectedUser, selectedUser,
setSelectedUser, toggleSelectedUser,
amount, amount,
setAmount, setAmount,
userQuery, userQuery,
setUserQuery, setUserQuery,
filteredUsers, filteredUsers,
selectedUserDetails,
reset, reset,
onSubmit, onSubmit,
} = useDeposit(); } = useDeposit();
@@ -23,45 +24,67 @@ const Deposit = (): JSX.Element => {
} }
return ( return (
<div> <div className={styles.deposit}>
<section> <h2 className={styles.heading}>Hvem skal sette inn penger?</h2>
<h2>Hvem skal sette inn penger?</h2> <div className={styles.search}>
<input <input
name="userQuery" name="userQuery"
onChange={(event: React.FormEvent<HTMLInputElement>) => setUserQuery(event.currentTarget.value)} onChange={(event: React.FormEvent<HTMLInputElement>) => setUserQuery(event.currentTarget.value)}
value={userQuery} value={userQuery}
placeholder="Filtrér på navn" placeholder="Filtrér på navn"
aria-label="Filtrér personer på navn"
/> />
{userQuery !== "" && ( {userQuery !== "" && (
<button type="button" onClick={() => setUserQuery("")}> <button type="button" onClick={() => setUserQuery("")}>
Vis alle Vis alle
</button> </button>
)} )}
</div>
<section className={styles.amountSection} aria-label="Innskudd">
<h2>Hvor mye?</h2>
{selectedUserDetails ? (
<p className={styles.selectedUser}>
Innskudd til{" "}
<strong>
{selectedUserDetails.firstName} {selectedUserDetails.lastName}
</strong>
</p>
) : (
<p className={styles.selectedUser}>Velg en person fra listen</p>
)}
<label className={styles.amountLabel} htmlFor="deposit-amount">
Beløp
</label>
<div className={styles.amountInput}>
<input
id="deposit-amount"
name="amount"
inputMode="decimal"
value={amount}
onChange={(event) => setAmount(event.target.value)}
placeholder="0"
/>
<span>kr</span>
</div>
<div className={styles.checkout}>
<button type="button" onClick={onSubmit} className="primary">
Sett inn
</button>
<button type="button" onClick={reset}>
Start på nytt
</button>
</div>
</section>
<div className={styles.userList}> <div className={styles.userList}>
{filteredUsers.map((user) => ( {filteredUsers.map((user) => (
<UserCard <UserCard
key={user.id} key={user.id}
active={selectedUser === user.id} active={selectedUser === user.id}
onUser={() => { onUser={() => toggleSelectedUser(user.id)}
setSelectedUser(user.id);
}}
user={user} user={user}
/> />
))} ))}
</div> </div>
</section>
<section className={styles.amountSection}>
<h2>Hvor mye?</h2>
<input name="amount" value={amount} onChange={(event) => setAmount(event.target.value)} placeholder="0" />
</section>
<section className={styles.checkout}>
<button type="button" onClick={onSubmit} className="primary">
Sett inn
</button>{" "}
<button type="button" onClick={reset}>
Start på nytt
</button>
</section>
</div> </div>
); );
}; };
+9 -4
View File
@@ -10,9 +10,13 @@ export const useDeposit = () => {
const [selectedUser, setSelectedUser] = useState(""); const [selectedUser, setSelectedUser] = useState("");
const [amount, setAmount] = useState(""); const [amount, setAmount] = useState("");
const [userQuery, setUserQuery] = useState(""); const [userQuery, setUserQuery] = useState("");
const toggleSelectedUser = (userId: string) => {
setSelectedUser((currentUserId) => (currentUserId === userId ? "" : userId));
};
const filteredUsers = (data?.allUsers || []).filter((user) => const filteredUsers = (data?.allUsers || []).filter((user) =>
`${user.firstName.toLowerCase()} ${user.lastName.toLowerCase()}`.includes(userQuery.toLowerCase()) `${user.firstName.toLowerCase()} ${user.lastName.toLowerCase()}`.includes(userQuery.toLowerCase()),
); );
const selectedUserDetails = data?.allUsers.find((user) => user.id === selectedUser);
const { showToast } = useToast(); const { showToast } = useToast();
const onSubmit = async () => { const onSubmit = async () => {
@@ -30,7 +34,7 @@ export const useDeposit = () => {
if (res.data?.createDeposit?.__typename === "CreateDepositSuccess") { if (res.data?.createDeposit?.__typename === "CreateDepositSuccess") {
const { transaction } = res.data?.createDeposit; const { transaction } = res.data?.createDeposit;
showToast( showToast(
`${transaction.user.firstName} har satt inn${transaction.amount}. Ny saldo er ${transaction.user.profile?.balance}.` `${transaction.user.firstName} har satt inn${transaction.amount}. Ny saldo er ${transaction.user.profile?.balance}.`,
); );
reset(); reset();
return; return;
@@ -42,7 +46,7 @@ export const useDeposit = () => {
if (res.data?.createDeposit?.__typename === "AboveMaxSaldo") { if (res.data?.createDeposit?.__typename === "AboveMaxSaldo") {
showToast( showToast(
`${res.data.createDeposit.message}. Ny saldo ville vært ${res.data.createDeposit.above} mer enn maksimalt ${res.data.createDeposit.maxSaldo}.`, `${res.data.createDeposit.message}. Ny saldo ville vært ${res.data.createDeposit.above} mer enn maksimalt ${res.data.createDeposit.maxSaldo}.`,
"danger" "danger",
); );
} }
}; };
@@ -55,12 +59,13 @@ export const useDeposit = () => {
return { return {
loading, loading,
selectedUser, selectedUser,
setSelectedUser, toggleSelectedUser,
amount, amount,
setAmount, setAmount,
userQuery, userQuery,
setUserQuery, setUserQuery,
filteredUsers, filteredUsers,
selectedUserDetails,
reset, reset,
onSubmit, onSubmit,
}; };
@@ -0,0 +1,71 @@
.section {
margin-top: 2rem;
}
.tableWrap {
max-width: 100%;
overflow-x: auto;
}
.tableWrap table {
width: 100%;
}
.inactive {
opacity: 0.65;
}
.transactions {
margin-top: 2rem;
}
.pagination {
display: flex;
gap: 0.75rem;
margin-top: 1rem;
}
.form {
display: grid;
max-width: 36rem;
gap: 1rem;
}
.form label {
display: grid;
gap: 0.35rem;
}
.form textarea,
.form select {
padding: 0.5rem;
border: 1px solid var(--border);
border-radius: 4px;
background: rgb(35, 15, 41);
color: var(--text);
font: inherit;
}
.form textarea {
min-height: 6rem;
}
.summary {
display: flex;
flex-wrap: wrap;
gap: 1rem 2rem;
}
.report {
margin-top: 2rem;
}
.error {
color: var(--sun);
}
@media (max-width: 640px) {
.tableWrap table {
min-width: 42rem;
}
}
@@ -0,0 +1,126 @@
import { useQuery } from "@apollo/client";
import React, { useState } from "react";
import { Link } from "react-router-dom";
import { GQLError } from "../../components/GQLError";
import { InventoryPageDocument } from "../../types";
import { formatCurrency } from "../../utils/currency";
import { formatTime } from "../../utils/time";
import styles from "./Inventory.module.css";
const PAGE_SIZE = 20;
const Inventory = () => {
const [offset, setOffset] = useState(0);
const { data, loading, error } = useQuery(InventoryPageDocument, {
variables: { limit: PAGE_SIZE, offset },
});
if (loading) return <div>Loading...</div>;
if (error) return <GQLError error={error} />;
const products = [...(data?.allProducts || [])].sort(
(a, b) =>
Number(b.active) - Number(a.active) || a.inventoryAmount - b.inventoryAmount || a.name.localeCompare(b.name),
);
const activeProducts = products.filter((product) => product.active);
const transactions = data?.inventoryTransactionList || [];
return (
<div>
<h2>Varebeholdning</h2>
<div className={styles.summary}>
<div>Antall aktive varer: {activeProducts.reduce((total, product) => total + product.inventoryAmount, 0)}</div>
<div>
Innkjøpsverdi:{" "}
{formatCurrency(activeProducts.reduce((total, product) => total + Number(product.wholesaleValue), 0))} kr
</div>
</div>
<section className={styles.section}>
<div className={styles.summary}>
<h3>Varer</h3>
<Link className="btn-primary" to="/inventory/add">
Registrer varetransaksjon
</Link>
<Link to="/report">Åpne rapport</Link>
</div>
<div className={styles.tableWrap}>
<table>
<thead>
<tr>
<th scope="col">Vare</th>
<th scope="col">Beholdning</th>
<th scope="col">Innkjøpspris</th>
<th scope="col">Verdi</th>
<th scope="col">I salg</th>
<th scope="col">Sist oppdatert</th>
</tr>
</thead>
<tbody>
{products.map((product) => (
<tr key={product.id} className={!product.active ? styles.inactive : undefined}>
<td>{product.name}</td>
<td>{product.inventoryAmount}</td>
<td>{formatCurrency(Number(product.wholesaleUnitPrice))} kr</td>
<td>{formatCurrency(Number(product.wholesaleValue))} kr</td>
<td>{product.active ? "Ja" : "Nei"}</td>
<td>{formatTime(product.updated)}</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
<section className={styles.transactions}>
<h3>Varetransaksjoner</h3>
<div className={styles.tableWrap}>
<table>
<thead>
<tr>
<th scope="col">Når</th>
<th scope="col">Vare</th>
<th scope="col">Antall</th>
<th scope="col">Enhetspris</th>
<th scope="col">Hvem</th>
<th scope="col">Kommentar</th>
</tr>
</thead>
<tbody>
{transactions.map((transaction) => (
<tr key={transaction.id}>
<td>{formatTime(transaction.created)}</td>
<td>{transaction.product.name}</td>
<td>{transaction.amount}</td>
<td>{formatCurrency(Number(transaction.unitPrice))} kr</td>
<td>
{transaction.user.firstName} {transaction.user.lastName}
</td>
<td>{transaction.comment}</td>
</tr>
))}
{!transactions.length && (
<tr>
<td colSpan={6}>Ingen varetransaksjoner ennå.</td>
</tr>
)}
</tbody>
</table>
</div>
<div className={styles.pagination}>
<button type="button" disabled={offset === 0} onClick={() => setOffset(Math.max(0, offset - PAGE_SIZE))}>
Forrige
</button>
<button
type="button"
disabled={transactions.length < PAGE_SIZE}
onClick={() => setOffset(offset + PAGE_SIZE)}
>
Neste
</button>
</div>
</section>
</div>
);
};
export default Inventory;
@@ -0,0 +1,102 @@
import { useMutation, useQuery } from "@apollo/client";
import React, { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { GQLError } from "../../components/GQLError";
import { useToast } from "../../components/ToastProvider";
import { CreateInventoryTransactionDocument, InventoryProductsDocument } from "../../types";
import styles from "./Inventory.module.css";
const InventoryAdd = () => {
const navigate = useNavigate();
const { showToast } = useToast();
const { data, loading, error } = useQuery(InventoryProductsDocument);
const [createInventoryTransaction, { loading: saving }] = useMutation(CreateInventoryTransactionDocument);
const [productId, setProductId] = useState("");
const [amount, setAmount] = useState("");
const [unitPrice, setUnitPrice] = useState("");
const [comment, setComment] = useState("");
const products = data?.allProducts || [];
if (loading) return <div>Loading...</div>;
if (error) return <GQLError error={error} />;
const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
try {
const result = await createInventoryTransaction({
variables: {
data: {
productId,
amount: Number(amount),
unitPrice,
comment,
},
},
});
const response = result.data?.createInventoryTransaction;
if (response?.__typename === "CreateInventoryTransactionSuccess") {
showToast(`Registrerte ${amount} stk ${response.inventoryTransaction.product.name}.`);
navigate("/inventory");
} else if (response?.__typename === "FormErrors") {
showToast(response.message, "danger");
} else {
showToast("Kunne ikke registrere varetransaksjonen.", "danger");
}
} catch (mutationError) {
showToast(
mutationError instanceof Error ? mutationError.message : "Kunne ikke registrere varetransaksjonen.",
"danger",
);
}
};
return (
<div>
<h2>Registrer varetransaksjon</h2>
<p>
Du må være innlogget for å registrere varer. <a href="/accounts/login/">Logg inn</a>.
</p>
<form className={styles.form} onSubmit={onSubmit}>
<label>
Vare
<select required value={productId} onChange={(event) => setProductId(event.target.value)}>
<option value="">Velg vare</option>
{products.map((product) => (
<option key={product.id} value={product.id}>
{product.name} ({product.inventoryAmount} på lager)
</option>
))}
</select>
</label>
<label>
Antall
<input required type="number" step="1" value={amount} onChange={(event) => setAmount(event.target.value)} />
</label>
<label>
Innkjøpspris per enhet (kr)
<input
required
step="0.1"
type="number"
value={unitPrice}
onChange={(event) => setUnitPrice(event.target.value)}
/>
</label>
<label>
Kommentar
<textarea value={comment} onChange={(event) => setComment(event.target.value)} />
</label>
<div className={styles.summary}>
<button type="submit" className="primary" disabled={saving || !products.length}>
{saving ? "Lagrer..." : "Registrer"}
</button>
<Link to="/inventory">Avbryt</Link>
</div>
</form>
{!products.length && <p>Ingen aktive varer å registrere.</p>}
</div>
);
};
export default InventoryAdd;
@@ -0,0 +1,138 @@
import { useQuery } from "@apollo/client";
import { format, subDays } from "date-fns";
import React, { useState } from "react";
import { GQLError } from "../../components/GQLError";
import { InventoryReportDocument } from "../../types";
import { formatCurrency } from "../../utils/currency";
import styles from "./Inventory.module.css";
const today = () => format(new Date(), "yyyy-MM-dd");
const monthAgo = () => format(subDays(new Date(), 30), "yyyy-MM-dd");
const timestampAt = (date: string, endOfDay = false) => `${date}T${endOfDay ? "23:59:59.999999" : "00:00:00"}`;
const InventoryReport = () => {
const [startDate, setStartDate] = useState(monthAgo);
const [endDate, setEndDate] = useState(today);
const [appliedRange, setAppliedRange] = useState({ startDate: monthAgo(), endDate: today() });
const invalidRange = startDate > endDate;
const { data, loading, error } = useQuery(InventoryReportDocument, {
variables: {
startTime: timestampAt(appliedRange.startDate),
endTime: timestampAt(appliedRange.endDate, true),
},
});
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (!invalidRange) setAppliedRange({ startDate, endDate });
};
if (error) return <GQLError error={error} />;
const report = data?.inventoryReport;
const balanceChange = report ? Number(report.balanceAtEnd) - Number(report.balanceAtStart) : 0;
const inventoryChange = report ? Number(report.inventoryPurchases) - Number(report.productSales) : 0;
return (
<div>
<h2>Varerapport</h2>
<form className={styles.summary} onSubmit={onSubmit}>
<label>
Fra
<input type="date" required value={startDate} onChange={(event) => setStartDate(event.target.value)} />
</label>
<label>
Til
<input type="date" required value={endDate} onChange={(event) => setEndDate(event.target.value)} />
</label>
<button type="submit" disabled={invalidRange}>
Oppdater
</button>
</form>
{invalidRange && <p className={styles.error}>Startdato må være før eller lik sluttdato.</p>}
{loading && <div>Loading...</div>}
{report && (
<>
<section className={styles.report}>
<h3>Beholdningsendring</h3>
<div className={styles.tableWrap}>
<table>
<thead>
<tr>
<th scope="col">Vare</th>
<th scope="col">Innkjøp</th>
<th scope="col">Salg</th>
<th scope="col">Endring verdi</th>
</tr>
</thead>
<tbody>
{report.products.map((product) => (
<tr key={product.productId}>
<td>{product.productName}</td>
<td>
{formatCurrency(Number(product.purchaseValue))} kr ({product.purchaseUnits} stk)
</td>
<td>
{formatCurrency(Number(product.salesValue))} kr ({product.salesUnits} stk)
</td>
<td>{formatCurrency(Number(product.purchaseValue) - Number(product.salesValue))} kr</td>
</tr>
))}
{!report.products.length && (
<tr>
<td colSpan={4}>Ingen varetransaksjoner i perioden.</td>
</tr>
)}
<tr>
<th scope="row">Totalt</th>
<td>{formatCurrency(Number(report.inventoryPurchases))} kr</td>
<td>{formatCurrency(Number(report.productSales))} kr</td>
<td>{formatCurrency(inventoryChange)} kr</td>
</tr>
</tbody>
</table>
</div>
</section>
<section className={styles.report}>
<h3>Innskudd og kjøp</h3>
<div className={styles.tableWrap}>
<table>
<thead>
<tr>
<th scope="col">Post</th>
<th scope="col">Saldo</th>
<th scope="col">Innskudd</th>
<th scope="col">Kjøp</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Saldo ved periodestart</th>
<td>{formatCurrency(Number(report.balanceAtStart))} kr</td>
<td />
<td />
</tr>
<tr>
<th scope="row">Endring i perioden</th>
<td>{formatCurrency(balanceChange)} kr</td>
<td>{formatCurrency(Number(report.deposits))} kr</td>
<td>{formatCurrency(Number(report.orderPurchases))} kr</td>
</tr>
<tr>
<th scope="row">Saldo ved periodeslutt</th>
<td>{formatCurrency(Number(report.balanceAtEnd))} kr</td>
<td />
<td />
</tr>
</tbody>
</table>
</div>
</section>
</>
)}
</div>
);
};
export default InventoryReport;
@@ -0,0 +1,20 @@
mutation createInventoryTransaction($data: InventoryTransactionInput!) {
createInventoryTransaction(data: $data) {
... on CreateInventoryTransactionSuccess {
inventoryTransaction {
id
product {
name
inventoryAmount
}
}
}
... on FormErrors {
message
fields {
field
message
}
}
}
}
@@ -0,0 +1,25 @@
query inventoryPage($limit: Int = 20, $offset: Int = 0) {
allProducts {
id
name
active
inventoryAmount
updated
wholesaleUnitPrice
wholesaleValue
}
inventoryTransactionList(ordering: [{ created: DESC }], pagination: { limit: $limit, offset: $offset }) {
id
created
amount
unitPrice
comment
product {
name
}
user {
firstName
lastName
}
}
}
@@ -0,0 +1,7 @@
query inventoryProducts {
allProducts(filters: { active: true }) {
id
name
inventoryAmount
}
}
@@ -0,0 +1,18 @@
query inventoryReport($startTime: DateTime!, $endTime: DateTime!) {
inventoryReport(startTime: $startTime, endTime: $endTime) {
products {
productId
productName
purchaseUnits
purchaseValue
salesUnits
salesValue
}
inventoryPurchases
productSales
deposits
orderPurchases
balanceAtStart
balanceAtEnd
}
}
+29 -2
View File
@@ -1,7 +1,17 @@
.log { .log {
display: grid; display: grid;
min-width: 0;
gap: 1rem; gap: 1rem;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, minmax(0, 1fr));
}
.tableWrap {
max-width: 100%;
overflow-x: auto;
}
.tableWrap table {
width: 100%;
} }
.disabled, .disabled,
.disabled a:link { .disabled a:link {
@@ -20,8 +30,8 @@
.pagination a { .pagination a {
padding: 0.4rem 0.8rem; padding: 0.4rem 0.8rem;
border: 1px solid var(--border); border: 1px solid var(--border);
margin-top: 1rem;
border-radius: 4px; border-radius: 4px;
margin-top: 1rem;
} }
.pagination a:hover { .pagination a:hover {
color: white; color: white;
@@ -30,3 +40,20 @@
.disabled a:hover { .disabled a:hover {
color: rgba(121, 152, 238, 0.5); color: rgba(121, 152, 238, 0.5);
} }
@media (max-width: 760px) {
.log {
grid-template-columns: minmax(0, 1fr);
}
}
@media (max-width: 480px) {
.pagination {
gap: 0 0.5rem;
}
.pagination a {
display: inline-block;
padding: 0.6rem 0.8rem;
}
}
+4
View File
@@ -34,6 +34,7 @@ const Log = () => {
<div className={styles.log}> <div className={styles.log}>
<div> <div>
<h2>Ordre</h2> <h2>Ordre</h2>
<div className={styles.tableWrap}>
<table> <table>
<thead> <thead>
<tr> <tr>
@@ -60,6 +61,7 @@ const Log = () => {
))} ))}
</tbody> </tbody>
</table> </table>
</div>
<nav aria-label="Order pagination"> <nav aria-label="Order pagination">
<ul className={styles.pagination}> <ul className={styles.pagination}>
<li className={clsx({ [styles.disabled]: orderOffset <= 0 })}> <li className={clsx({ [styles.disabled]: orderOffset <= 0 })}>
@@ -89,6 +91,7 @@ const Log = () => {
</div> </div>
<div> <div>
<h2>Transaksjoner</h2> <h2>Transaksjoner</h2>
<div className={styles.tableWrap}>
<table> <table>
<thead> <thead>
<tr> <tr>
@@ -109,6 +112,7 @@ const Log = () => {
))} ))}
</tbody> </tbody>
</table> </table>
</div>
<nav aria-label="Transaction pagination"> <nav aria-label="Transaction pagination">
<ul className={styles.pagination}> <ul className={styles.pagination}>
<li className={clsx({ [styles.disabled]: transactionOffset <= 0 })}> <li className={clsx({ [styles.disabled]: transactionOffset <= 0 })}>
+1 -1
View File
@@ -1,5 +1,5 @@
query orderList($limit: Int = 10, $offset: Int = 0) { query orderList($limit: Int = 10, $offset: Int = 0) {
orderList(order: {created: DESC}, pagination: {limit: $limit, offset: $offset}) { orderList(ordering: [{created: DESC}], pagination: {limit: $limit, offset: $offset}) {
id id
isExternal isExternal
created created
@@ -1,5 +1,5 @@
query transactionList($limit: Int = 10, $offset: Int = 0) { query transactionList($limit: Int = 10, $offset: Int = 0) {
transactionList(order: {created: DESC}, pagination: {limit: $limit, offset: $offset}) { transactionList(ordering: [{created: DESC}], pagination: {limit: $limit, offset: $offset}) {
id id
created created
amount amount
@@ -1,51 +1,56 @@
.product { .product {
display: flex;
border: none;
background: rgb(35, 15, 41);
border-radius: 4px;
transition: background-color 0.1s ease-out, border 0.1s ease-out;
position: relative; position: relative;
display: flex;
align-items: flex-start; align-items: flex-start;
padding: 1rem; padding: 1rem;
border: none;
border-radius: 4px;
background: rgb(88, 59, 99);
transition:
background-color 0.1s ease-out,
border 0.1s ease-out;
} }
.product:hover, .product:hover,
.active.product { .active.product {
background-color: var(--background); background-color: var(--background);
box-shadow: 0 0 4px var(--background), 0 0 11px var(--background), 0 0 19px var(--background); box-shadow:
0 0 4px var(--background),
0 0 11px var(--background),
0 0 19px var(--background);
} }
.productText { .productText {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
font-size: 1.2rem;
text-shadow: none;
color: white; color: white;
font-size: 1.2rem;
text-overflow: ellipsis;
text-shadow: none;
white-space: nowrap;
} }
.amount { .amount {
position: absolute; position: absolute;
z-index: 1; z-index: 1;
top: 0; top: 0;
bottom: 0;
right: 0; right: 0;
bottom: 0;
display: none;
padding: 12px 12px 8px 8px; padding: 12px 12px 8px 8px;
border-left: 1px solid transparent; border-left: 1px solid transparent;
background-color: transparent;
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
color: transparent; color: transparent;
font-size: 2rem; font-size: 2rem;
font-weight: bold; font-weight: bold;
line-height: 1; line-height: 1;
background-color: transparent;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
display: none;
} }
.active .amount { .active .amount {
display: block; display: block;
color: var(--background);
text-shadow: none;
border-left-color: rgb(35, 15, 41); border-left-color: rgb(35, 15, 41);
background-color: var(--sun-end); background-color: var(--sun-end);
color: var(--background);
text-shadow: none;
} }
@@ -1,11 +1,11 @@
import clsx from "clsx"; import clsx from "clsx";
import React, { MouseEventHandler } from "react"; import React, { MouseEventHandler } from "react";
import { Product } from "../../types"; import { AllProductsQuery } from "../../types";
import styles from "./ProductCard.module.css"; import styles from "./ProductCard.module.css";
type ProductCardProps = { type ProductCardProps = {
product: Omit<Product, "userCounts">; product: AllProductsQuery["allProducts"][number];
onProduct: MouseEventHandler; onProduct: MouseEventHandler;
active: boolean; active: boolean;
amount: number; amount: number;
@@ -1,8 +1,7 @@
.userList { .userList {
display: grid; display: grid;
grid-template-columns: repeat(5, 220px); gap: 1rem;
column-gap: 16px; grid-template-columns: repeat(auto-fill, minmax(min(100%, 190px), 1fr));
row-gap: 24px;
} }
.showAll { .showAll {
@@ -11,9 +10,8 @@
.productList { .productList {
display: grid; display: grid;
grid-template-columns: repeat(5, 220px); gap: 1rem;
column-gap: 16px; grid-template-columns: repeat(auto-fill, minmax(min(100%, 190px), 1fr));
row-gap: 24px;
} }
.productListSection { .productListSection {
@@ -26,9 +24,23 @@
.total { .total {
display: inline-block; display: inline-block;
min-width: 200px; min-width: min(200px, 100%);
padding: 4px 8px; padding: 4px 8px;
margin-right: 8px; margin-right: 8px;
color: white; color: white;
font-size: 1.8rem; font-size: 1.8rem;
} }
.checkout {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.75rem;
}
@media (max-width: 480px) {
.total {
width: 100%;
font-size: 1.4rem;
}
}
+5 -5
View File
@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { OrderLineInput, Product } from "../../types"; import { AllProductsQuery, OrderLineInput } from "../../types";
import { UserCard } from "../users/UserCard"; import { UserCard } from "../users/UserCard";
import { ProductCard } from "./ProductCard"; import { ProductCard } from "./ProductCard";
import styles from "./Register.module.css"; import styles from "./Register.module.css";
@@ -15,7 +15,7 @@ const Register = (): JSX.Element => {
loading, loading,
showAll, showAll,
setShowAll, setShowAll,
setSelectedUser, toggleSelectedUser,
selectedUser, selectedUser,
addToOrder, addToOrder,
order, order,
@@ -36,14 +36,14 @@ const Register = (): JSX.Element => {
<UserCard <UserCard
key={USER_EXTERNAL} key={USER_EXTERNAL}
user={null} user={null}
onUser={() => setSelectedUser(USER_EXTERNAL)} onUser={() => toggleSelectedUser(USER_EXTERNAL)}
active={selectedUser == USER_EXTERNAL} active={selectedUser == USER_EXTERNAL}
/> />
{users.map((user) => ( {users.map((user) => (
<UserCard <UserCard
key={user.id} key={user.id}
user={user} user={user}
onUser={() => setSelectedUser(user.id)} onUser={() => toggleSelectedUser(user.id)}
active={selectedUser == user.id} active={selectedUser == user.id}
/> />
))} ))}
@@ -55,7 +55,7 @@ const Register = (): JSX.Element => {
<section className={styles.productListSection}> <section className={styles.productListSection}>
<h2>Kjøper hva?</h2> <h2>Kjøper hva?</h2>
<div className={styles.productList}> <div className={styles.productList}>
{products.map((product: Omit<Product, "userCounts">) => ( {products.map((product: AllProductsQuery["allProducts"][number]) => (
<ProductCard <ProductCard
key={product.id} key={product.id}
product={product} product={product}
+19 -7
View File
@@ -2,17 +2,25 @@ import { useMutation, useQuery } from "@apollo/client";
import { useState } from "react"; import { useState } from "react";
import { useToast } from "../../components/ToastProvider"; import { useToast } from "../../components/ToastProvider";
import { AllProductsDocument, AllUsersDocument, CreateOrderDocument, OrderLineInput, Product } from "../../types"; import {
AllProductsDocument,
AllProductsQuery,
AllUsersDocument,
CreateOrderDocument,
OrderLineInput,
} from "../../types";
import { formatCurrency } from "../../utils/currency";
import { activeUserFilter } from "../../utils/users"; import { activeUserFilter } from "../../utils/users";
import {formatCurrency} from "../../utils/currency";
const USER_EXTERNAL = "external"; const USER_EXTERNAL = "external";
const sumTotal = (products: Omit<Product, "userCounts">[], order: OrderLineInput[], isExternal: boolean) => { type Product = AllProductsQuery["allProducts"][number];
const sumTotal = (products: Product[], order: OrderLineInput[], isExternal: boolean) => {
const prices = order.map((orderLine: OrderLineInput) => { const prices = order.map((orderLine: OrderLineInput) => {
const product = products.find((product: Omit<Product, "userCounts">) => product.id === orderLine.productId); const product = products.find((product) => product.id === orderLine.productId);
if (!product) return 0; if (!product) return 0;
return orderLine.amount * (isExternal ? product.salePriceExt : product.salePriceInt); return orderLine.amount * Number(isExternal ? product.salePriceExt : product.salePriceInt);
}); });
return prices.reduce((partialSum, a) => partialSum + a, 0); return prices.reduce((partialSum, a) => partialSum + a, 0);
}; };
@@ -39,6 +47,10 @@ export const useRegister = () => {
setSelectedUser(""); setSelectedUser("");
}; };
const toggleSelectedUser = (userId: string) => {
setSelectedUser((currentUserId) => (currentUserId === userId ? "" : userId));
};
const addToOrder = (productId: string) => { const addToOrder = (productId: string) => {
let added = false; let added = false;
const newOrder = order.map((orderLine: OrderLineInput) => { const newOrder = order.map((orderLine: OrderLineInput) => {
@@ -60,7 +72,7 @@ export const useRegister = () => {
let res; let res;
try { try {
res = await createOrderMutation(); res = await createOrderMutation();
} catch (e) { } catch {
showToast("Whoops"); showToast("Whoops");
console.log("woops"); console.log("woops");
return; return;
@@ -93,7 +105,7 @@ export const useRegister = () => {
products, products,
showAll, showAll,
setShowAll, setShowAll,
setSelectedUser, toggleSelectedUser,
selectedUser, selectedUser,
addToOrder, addToOrder,
order, order,
+14 -10
View File
@@ -1,9 +1,11 @@
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Bar, BarDatum } from "@nivo/bar"; import { BarDatum, ResponsiveBar } from "@nivo/bar";
import React from "react"; import React from "react";
import { OrderStatsDocument, OrderStatsByTime } from "../../types"; import { OrderStatsDocument, SchemaOrderStatsByTime } from "../../types";
import styles from "./Stats.module.css";
import { StatsView } from "./StatsView"; import { StatsView } from "./StatsView";
import { chartTheme } from "./chartTheme";
const days = { 1: "Mandag", 2: "Tirsdag", 3: "Onsdag", 4: "Torsdag", 5: "Fredag", 6: "Lørdag", 7: "Søndag" }; const days = { 1: "Mandag", 2: "Tirsdag", 3: "Onsdag", 4: "Torsdag", 5: "Fredag", 6: "Lørdag", 7: "Søndag" };
@@ -23,16 +25,18 @@ const months = {
}; };
const OrderBar = ({ data }: { data: BarDatum[] }) => ( const OrderBar = ({ data }: { data: BarDatum[] }) => (
<Bar <div className={styles.chart}>
<ResponsiveBar
data={data} data={data}
margin={{ left: 60, bottom: 40 }} theme={chartTheme}
width={1200} margin={{ top: 10, right: 10, bottom: 70, left: 60 }}
height={400}
colorBy="indexValue" colorBy="indexValue"
enableLabel={false} enableLabel={false}
tooltipLabel={(d) => d.id.toString()} tooltipLabel={(d) => d.id.toString()}
axisLeft={{ legend: "Ordre", legendPosition: "middle", legendOffset: -50 }} axisLeft={{ legend: "Ordre", legendPosition: "middle", legendOffset: -50 }}
axisBottom={{ tickRotation: -35 }}
/> />
</div>
); );
const OrderStats = () => { const OrderStats = () => {
@@ -42,7 +46,7 @@ const OrderStats = () => {
<StatsView loading={loading}> <StatsView loading={loading}>
<h3>Ordre per time</h3> <h3>Ordre per time</h3>
<OrderBar <OrderBar
data={(data?.orderStats?.hourly || []).map((stat: OrderStatsByTime) => ({ data={(data?.orderStats?.hourly || []).map((stat: SchemaOrderStatsByTime) => ({
id: `${stat.period.toString().padStart(2, "0")}:00`, id: `${stat.period.toString().padStart(2, "0")}:00`,
label: stat.period, label: stat.period,
value: stat.count, value: stat.count,
@@ -50,7 +54,7 @@ const OrderStats = () => {
/> />
<h3>Ordre per ukedag</h3> <h3>Ordre per ukedag</h3>
<OrderBar <OrderBar
data={(data?.orderStats?.daily || []).map((stat: OrderStatsByTime) => ({ data={(data?.orderStats?.daily || []).map((stat: SchemaOrderStatsByTime) => ({
/* @ts-expect-error Here, stat.period can only be a key in days range. */ /* @ts-expect-error Here, stat.period can only be a key in days range. */
id: days[stat.period], id: days[stat.period],
label: stat.period, label: stat.period,
@@ -59,7 +63,7 @@ const OrderStats = () => {
/> />
<h3>Ordre per måned</h3> <h3>Ordre per måned</h3>
<OrderBar <OrderBar
data={(data?.orderStats?.monthly || []).map((stat: OrderStatsByTime) => ({ data={(data?.orderStats?.monthly || []).map((stat: SchemaOrderStatsByTime) => ({
/* @ts-expect-error Here, stat.period can only be a key in months range. */ /* @ts-expect-error Here, stat.period can only be a key in months range. */
id: months[stat.period], id: months[stat.period],
label: stat.period, label: stat.period,
@@ -68,7 +72,7 @@ const OrderStats = () => {
/> />
<h3>Ordre per år</h3> <h3>Ordre per år</h3>
<OrderBar <OrderBar
data={(data?.orderStats?.yearly || []).map((stat: OrderStatsByTime) => ({ data={(data?.orderStats?.yearly || []).map((stat: SchemaOrderStatsByTime) => ({
id: stat.period, id: stat.period,
label: stat.period, label: stat.period,
value: stat.count, value: stat.count,
+9 -5
View File
@@ -1,10 +1,11 @@
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Pie } from "@nivo/pie"; import { ResponsivePie } from "@nivo/pie";
import React from "react"; import React from "react";
import { AllProductStatsDocument } from "../../types"; import { AllProductStatsDocument } from "../../types";
import styles from "./Stats.module.css"; import styles from "./Stats.module.css";
import { StatsView } from "./StatsView"; import { StatsView } from "./StatsView";
import { chartTheme } from "./chartTheme";
type UserCount = { type UserCount = {
name: string; name: string;
@@ -22,17 +23,20 @@ const ProductStats = () => {
{(data?.allProducts || []).map((product) => ( {(data?.allProducts || []).map((product) => (
<div key={product.id}> <div key={product.id}>
{product.name} {product.name}
<Pie <div className={styles.productChart}>
margin={{ top: 40, right: 120, bottom: 40, left: 120 }} <ResponsivePie
margin={{ top: 30, right: 50, bottom: 30, left: 50 }}
data={product.userCounts.map((uc: UserCount) => ({ data={product.userCounts.map((uc: UserCount) => ({
id: uc.name, id: uc.name,
label: uc.name, label: uc.name,
value: uc.count, value: uc.count,
}))} }))}
width={400} theme={chartTheme}
height={300} arcLabelsTextColor="#17121a"
arcLabelsSkipAngle={8}
/> />
</div> </div>
</div>
))} ))}
</div> </div>
</StatsView> </StatsView>
+23 -3
View File
@@ -1,8 +1,17 @@
.products { .products {
display: grid; display: grid;
max-width: 1200px; max-width: 100%;
gap: 1rem; gap: 1.5rem 1rem;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
}
.productChart {
height: 300px;
}
.chart {
width: 100%;
height: 360px;
} }
.statsNav { .statsNav {
@@ -27,3 +36,14 @@
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 4px; border-radius: 4px;
} }
@media (max-width: 640px) {
.statsNav ul {
flex-wrap: wrap;
gap: 0.5rem;
}
.chart {
height: 300px;
}
}
+19 -22
View File
@@ -1,29 +1,30 @@
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Line } from "@nivo/line"; import { ResponsiveLine } from "@nivo/line";
import React from "react"; import React from "react";
import { ProductStatsDocument } from "../../types"; import { ProductStatsDocument } from "../../types";
import styles from "./Stats.module.css";
import { StatsView } from "./StatsView"; import { StatsView } from "./StatsView";
import { chartTheme, chartTooltipStyle } from "./chartTheme";
const Stats = () => { const Stats = () => {
const { data, loading } = useQuery(ProductStatsDocument); const { data, loading } = useQuery(ProductStatsDocument);
const series: any[] = []; const series = (data?.productStats || []).map(({ productName: id, data }) => ({
(data?.productStats || [])?.forEach(({ productName: id, data }) => { id,
const lineData: any[] = []; data: data.map((point, index, points) => ({
data.forEach((point, index) => { x: point.x,
lineData.push({ x: point.x, y: point.y + (lineData?.[index - 1]?.y || 0) }); y: point.y + (points[index - 1]?.y || 0),
}); })),
series.push({ id, data: lineData }); }));
});
return ( return (
<StatsView loading={loading}> <StatsView loading={loading}>
<h2>Kjøp over tid</h2> <h2>Kjøp over tid</h2>
<Line <div className={styles.chart}>
<ResponsiveLine
data={series} data={series}
width={1000} theme={chartTheme}
height={400}
margin={{ top: 50, right: 110, bottom: 50, left: 60 }} margin={{ top: 50, right: 110, bottom: 50, left: 60 }}
xScale={{ xScale={{
type: "time", type: "time",
@@ -43,28 +44,24 @@ const Stats = () => {
axisBottom={{ axisBottom={{
format: "%b %Y", format: "%b %Y",
legend: "Date", legend: "Date",
legendOffset: -40, legendPosition: "middle",
legendOffset: -20,
}} }}
useMesh useMesh
pointLabelYOffset={0} pointLabelYOffset={0}
tooltip={({ point }) => ( tooltip={({ point }) => (
<div <div style={chartTooltipStyle}>
style={{
background: "white",
padding: "9px 12px",
border: "1px solid #ccc",
}}
>
<div> <div>
<strong>{point.data.xFormatted}</strong> <strong>{point.data.xFormatted}</strong>
</div> </div>
<div> <div>
<span style={{ background: point.serieColor, width: 13, height: 13, display: "inline-block" }} />{" "} <span style={{ background: point.seriesColor, width: 13, height: 13, display: "inline-block" }} />{" "}
<strong>{point.data.yFormatted}</strong> {point.serieId} <strong>{String(point.data.yFormatted)}</strong> {point.seriesId}
</div> </div>
</div> </div>
)} )}
/> />
</div>
</StatsView> </StatsView>
); );
}; };
+1 -1
View File
@@ -25,7 +25,7 @@ export const StatsNav = () => (
</NavLink> </NavLink>
</li> </li>
<li> <li>
<NavLink className="disabled" to="/stats/users" title="TODO" hidden={true}> <NavLink className={navLinkStyles} to="/stats/users">
Folk Folk
</NavLink> </NavLink>
</li> </li>
+55
View File
@@ -0,0 +1,55 @@
import { useQuery } from "@apollo/client";
import { ResponsiveBar } from "@nivo/bar";
import React from "react";
import { GQLError } from "../../components/GQLError";
import { UserStatsDocument } from "../../types";
import styles from "./Stats.module.css";
import { StatsView } from "./StatsView";
import { chartTheme } from "./chartTheme";
const UserStats = () => {
const { data, loading, error } = useQuery(UserStatsDocument);
if (error) return <GQLError error={error} />;
const users = (data?.allUsers || [])
.filter((user) => Number(user.profile?.orderSumTotal || 0) > 0)
.map((user) => ({
name: `${user.firstName}${user.lastName ? ` ${user.lastName[0]}.` : ""}`,
total: Number(user.profile?.orderSumTotal || 0),
}))
.sort((a, b) => b.total - a.total)
.slice(0, 10);
return (
<StatsView loading={loading}>
<h3>Topp 10 etter total kjøpesum</h3>
{users.length ? (
<div className={styles.chart} style={{ height: Math.max(320, users.length * 44) }}>
<ResponsiveBar
data={users}
keys={["total"]}
indexBy="name"
layout="horizontal"
theme={chartTheme}
margin={{ top: 10, right: 35, bottom: 40, left: 100 }}
padding={0.3}
enableLabel={false}
axisBottom={{
legend: "Kjøp totalt (kr)",
legendPosition: "middle",
legendOffset: 32,
}}
axisLeft={null}
valueFormat={(value) => `${Number(value).toLocaleString("no")} kr`}
/>
</div>
) : (
<p>Ingen kjøp å vise ennå.</p>
)}
</StatsView>
);
};
export default UserStats;
+60
View File
@@ -0,0 +1,60 @@
export const chartTheme = {
background: "transparent",
text: {
fill: "#f7effa",
fontFamily: '"IBM Plex Mono", monospace',
fontSize: 13,
textShadow: "none",
},
axis: {
domain: {
line: { stroke: "#b9a3c5", strokeWidth: 1 },
},
ticks: {
line: { stroke: "#b9a3c5", strokeWidth: 1 },
text: { fill: "#f7effa", fontSize: 12, textShadow: "none" },
},
legend: {
text: { fill: "#f7effa", fontSize: 13, textShadow: "none" },
},
},
grid: {
line: { stroke: "rgba(221, 81, 127, 0.24)", strokeWidth: 1 },
},
legends: {
text: { fill: "#f7effa", fontSize: 12, textShadow: "none" },
title: {
text: { fill: "#f7effa", fontSize: 13, textShadow: "none" },
},
},
labels: {
text: {
fill: "#ffffff",
fontSize: 13,
textShadow: "none",
},
},
tooltip: {
container: {
background: "#2b1733",
color: "#ffffff",
fontFamily: '"IBM Plex Mono", monospace',
fontSize: 13,
border: "1px solid #dd517f",
borderRadius: 4,
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.45)",
},
},
};
export const chartTooltipStyle = {
background: "#2b1733",
color: "#ffffff",
padding: "9px 12px",
border: "1px solid #dd517f",
borderRadius: 4,
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.45)",
fontFamily: '"IBM Plex Mono", monospace',
fontSize: 13,
textShadow: "none",
};
@@ -0,0 +1,10 @@
query userStats {
allUsers {
id
firstName
lastName
profile {
orderSumTotal
}
}
}
+17 -12
View File
@@ -1,14 +1,16 @@
.user { .user {
display: flex; display: flex;
max-height: 82px;
align-items: center; align-items: center;
padding: 0; padding: 0;
border: none; border: none;
border-radius: 4px;
border-left: 4px solid transparent; border-left: 4px solid transparent;
background: rgb(35, 15, 41); background: rgb(35, 15, 41);
border-radius: 4px;
color: var(--text); color: var(--text);
transition: background-color 0.1s ease-out, border 0.1s ease-out; transition:
max-height: 82px; background-color 0.1s ease-out,
border 0.1s ease-out;
} }
.user:hover { .user:hover {
@@ -17,16 +19,19 @@
.user:hover, .user:hover,
.active.user { .active.user {
background-color: var(--background); background-color: var(--background);
box-shadow: 0 0 4px var(--background), 0 0 11px var(--background), 0 0 19px var(--background); box-shadow:
0 0 4px var(--background),
0 0 11px var(--background),
0 0 19px var(--background);
} }
.active.user { .active.user {
border-left: 4px solid var(--sun) border-left: 4px solid var(--sun);
} }
.avatar { .avatar {
margin-left: 1rem;
width: 50px; width: 50px;
height: 50px; height: 50px;
margin-left: 1rem;
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
} }
.avatarWrap { .avatarWrap {
@@ -35,10 +40,10 @@
} }
.noAvatar { .noAvatar {
margin-left: 1rem;
display: block; display: block;
width: 50px; width: 50px;
height: 50px; height: 50px;
margin-left: 1rem;
background-color: black; background-color: black;
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
content: ""; content: "";
@@ -46,16 +51,16 @@
.meta { .meta {
display: flex; display: flex;
padding: 1rem; overflow: hidden;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
white-space: nowrap; padding: 1rem;
text-overflow: ellipsis;
overflow: hidden;
font-size: 1.2rem; font-size: 1.2rem;
text-overflow: ellipsis;
text-shadow: none; text-shadow: none;
white-space: nowrap;
} }
.balance { .balance {
color: var(--sun) color: var(--sun);
} }
+1 -1
View File
@@ -2,8 +2,8 @@ import clsx from "clsx";
import React, { MouseEventHandler } from "react"; import React, { MouseEventHandler } from "react";
import { AllUsersQuery } from "../../types"; import { AllUsersQuery } from "../../types";
import { formatCurrency } from "../../utils/currency";
import styles from "./UserCard.module.css"; import styles from "./UserCard.module.css";
import {formatCurrency} from "../../utils/currency";
type UserCardProps = { type UserCardProps = {
user: AllUsersQuery["allUsers"][0] | null; user: AllUsersQuery["allUsers"][0] | null;
@@ -1,5 +1,21 @@
.userDetail { .userDetail {
display: grid; display: grid;
min-width: 0;
gap: 1rem; gap: 1rem;
grid-template-columns: 400px 1fr; grid-template-columns: minmax(0, 400px) minmax(0, 1fr);
}
.chart {
width: 100%;
height: 360px;
}
@media (max-width: 760px) {
.userDetail {
grid-template-columns: minmax(0, 1fr);
}
.chart {
height: 300px;
}
} }
+21 -26
View File
@@ -1,11 +1,12 @@
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Line } from "@nivo/line"; import { ResponsiveLine } from "@nivo/line";
import React from "react"; import React from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { GQLError } from "../../components/GQLError"; import { GQLError } from "../../components/GQLError";
import { User, UserDetailDocument, UserProfile } from "../../types"; import { SchemaUser as User, UserDetailDocument, SchemaUserProfile as UserProfile } from "../../types";
import { formatMonth, formatTime } from "../../utils/time"; import { formatMonth, formatTime } from "../../utils/time";
import { chartTheme, chartTooltipStyle } from "../stats/chartTheme";
import styles from "./UserDetail.module.css"; import styles from "./UserDetail.module.css";
const UserDetail = () => { const UserDetail = () => {
@@ -18,15 +19,13 @@ const UserDetail = () => {
const user = data?.user as User; const user = data?.user as User;
const profile = user?.profile as UserProfile; const profile = user?.profile as UserProfile;
// FIXME: types const series = (profile?.productOrderStats || []).map(({ productName: id, data }) => ({
const series: any[] = []; id,
(profile?.productOrderStats || [])?.forEach(({ productName: id, data }) => { data: data.map((point, index, points) => ({
const lineData: any[] = []; x: point.x,
data.forEach((point, index) => { y: point.y + (points[index - 1]?.y || 0),
lineData.push({ x: point.x, y: point.y + (lineData?.[index - 1]?.y || 0) }); })),
}); }));
series.push({ id, data: lineData });
});
return ( return (
<div className={styles.userDetail}> <div className={styles.userDetail}>
<div> <div>
@@ -37,13 +36,13 @@ const UserDetail = () => {
<div>Stats</div> <div>Stats</div>
<ul> <ul>
<li> <li>
Saldo: <strong>{profile.balance.toLocaleString("no")} kr</strong> Saldo: <strong>{Number(profile.balance).toLocaleString("no")} kr</strong>
</li> </li>
<li> <li>
Siste kjøp: <strong>{formatTime(profile.lastPurchaseDate)}</strong> Siste kjøp: <strong>{formatTime(profile.lastPurchaseDate)}</strong>
</li> </li>
<li> <li>
Kjøp totalt: <strong>{profile.orderSumTotal.toLocaleString("no")} kr</strong> Kjøp totalt: <strong>{Number(profile.orderSumTotal).toLocaleString("no")} kr</strong>
</li> </li>
</ul> </ul>
</div> </div>
@@ -62,10 +61,10 @@ const UserDetail = () => {
</div> </div>
<div> <div>
<h2>Kjøp over tid</h2> <h2>Kjøp over tid</h2>
<Line <div className={styles.chart}>
<ResponsiveLine
data={series} data={series}
width={1000} theme={chartTheme}
height={400}
margin={{ top: 50, right: 110, bottom: 50, left: 60 }} margin={{ top: 50, right: 110, bottom: 50, left: 60 }}
xScale={{ xScale={{
type: "time", type: "time",
@@ -85,30 +84,26 @@ const UserDetail = () => {
axisBottom={{ axisBottom={{
format: "%b %Y", format: "%b %Y",
legend: "Date", legend: "Date",
legendOffset: -12, legendPosition: "middle",
legendOffset: -20,
}} }}
useMesh useMesh
pointLabelYOffset={0} pointLabelYOffset={0}
tooltip={({ point }) => ( tooltip={({ point }) => (
<div <div style={chartTooltipStyle}>
style={{
background: "white",
padding: "9px 12px",
border: "1px solid #ccc",
}}
>
<div> <div>
<strong>{point.data.xFormatted}</strong> <strong>{point.data.xFormatted}</strong>
</div> </div>
<div> <div>
<span style={{ background: point.serieColor, width: 13, height: 13, display: "inline-block" }} />{" "} <span style={{ background: point.seriesColor, width: 13, height: 13, display: "inline-block" }} />{" "}
<strong>{point.data.yFormatted}</strong> {point.serieId} <strong>{String(point.data.yFormatted)}</strong> {point.seriesId}
</div> </div>
</div> </div>
)} )}
/> />
</div> </div>
</div> </div>
</div>
); );
}; };
export default UserDetail; export default UserDetail;
+41 -11
View File
@@ -14,26 +14,41 @@
html, html,
body { body {
width: 100%; width: 100%;
height: 100%; min-height: 100%;
padding: 0; padding: 0;
margin: 0; margin: 0;
} }
html { html {
box-sizing: border-box; box-sizing: border-box;
} }
#root {
display: flex;
min-height: 100vh;
flex-direction: column;
}
*, *,
*:before, *:before,
*:after { *:after {
box-sizing: inherit; box-sizing: inherit;
} }
@media (min-width: 641px) {
html {
scrollbar-gutter: stable;
}
}
/* Body and interitable styles */ /* Body and interitable styles */
body { body {
background: linear-gradient(rgba(0, 0, 0, 0.8) 60%, var(--background) 100%); background: linear-gradient(rgba(0, 0, 0, 0.8) 60%, var(--background) 100%);
background-attachment: fixed;
background-color: var(--background); background-color: var(--background);
color: var(--text); color: var(--text);
font-family: var(--font-family); font-family: var(--font-family);
text-shadow: 0 0 4px currentColor, 0 0 11px currentColor, 0 0 19px currentColor; text-shadow:
0 0 4px currentColor,
0 0 11px currentColor,
0 0 19px currentColor;
} }
/* Links */ /* Links */
@@ -63,9 +78,12 @@ h5 {
a.btn-primary { a.btn-primary {
padding: 0.4rem 1rem; padding: 0.4rem 1rem;
border: 1px solid transparent; border: 1px solid transparent;
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
border-radius: 4px; border-radius: 4px;
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun); background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
box-shadow:
0 0 4px var(--sun),
0 0 11px var(--sun),
0 0 19px var(--sun);
color: var(--text); color: var(--text);
} }
@@ -73,11 +91,14 @@ a.btn-primary {
button { button {
padding: 0.4rem 1rem; padding: 0.4rem 1rem;
border: 1px solid var(--border); border: 1px solid var(--border);
background: transparent;
border-radius: 4px; border-radius: 4px;
background: transparent;
color: var(--border); color: var(--border);
font-family: var(--font-family); font-family: var(--font-family);
text-shadow: 0 0 4px currentColor, 0 0 11px currentColor, 0 0 19px currentColor; text-shadow:
0 0 4px currentColor,
0 0 11px currentColor,
0 0 19px currentColor;
} }
button:hover { button:hover {
color: var(--text); color: var(--text);
@@ -86,7 +107,10 @@ button:hover {
button.primary { button.primary {
border: 1px solid transparent; border: 1px solid transparent;
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%); background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun); box-shadow:
0 0 4px var(--sun),
0 0 11px var(--sun),
0 0 19px var(--sun);
color: var(--text); color: var(--text);
} }
@@ -94,12 +118,15 @@ button.primary {
input { input {
padding: 0.4rem 0.4rem; padding: 0.4rem 0.4rem;
border: 1px solid var(--border); border: 1px solid var(--border);
background: transparent;
border-radius: 4px; border-radius: 4px;
background: transparent;
color: var(--text); color: var(--text);
font-family: var(--font-family); font-family: var(--font-family);
font-size: 1.5rem; font-size: 1.5rem;
text-shadow: 0 0 4px currentColor, 0 0 11px currentColor, 0 0 19px currentColor; text-shadow:
0 0 4px currentColor,
0 0 11px currentColor,
0 0 19px currentColor;
} }
input:focus { input:focus {
background: rgba(85, 109, 200, 0.1); background: rgba(85, 109, 200, 0.1);
@@ -108,9 +135,12 @@ input:focus {
input.btn-primary { input.btn-primary {
padding: 0.4rem 1rem; padding: 0.4rem 1rem;
border: 1px solid transparent; border: 1px solid transparent;
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
border-radius: 4px; border-radius: 4px;
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun); background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
box-shadow:
0 0 4px var(--sun),
0 0 11px var(--sun),
0 0 19px var(--sun);
color: var(--text); color: var(--text);
} }
::placeholder { ::placeholder {
+2 -2
View File
@@ -1,9 +1,9 @@
import { useCallback, useEffect, useRef } from "react"; import { useCallback, useEffect, useRef } from "react";
export function usePrevious(value: any) { export function usePrevious<T>(value: T) {
// The ref object is a generic container whose current property is mutable ... // The ref object is a generic container whose current property is mutable ...
// ... and can hold any value, similar to an instance property on a class // ... and can hold any value, similar to an instance property on a class
const ref = useRef(); const ref = useRef<T | undefined>(undefined);
// Store current value in ref // Store current value in ref
useEffect(() => { useEffect(() => {
+1 -1
View File
@@ -9,5 +9,5 @@ ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>,
document.getElementById("root") document.getElementById("root"),
); );
+367 -223
View File
@@ -1,339 +1,483 @@
/** Internal type. DO NOT USE DIRECTLY. */
type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
/** Internal type. DO NOT USE DIRECTLY. */
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'; import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type Maybe<T> = T | null; export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>; export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
/** All built-in and custom scalars, mapped to their actual values */ /** All built-in and custom scalars, mapped to their actual values */
export type Scalars = { export type Scalars = {
ID: string; ID: { input: string; output: string; }
String: string; String: { input: string; output: string; }
Boolean: boolean; Boolean: { input: boolean; output: boolean; }
Int: number; Int: { input: number; output: number; }
Float: number; Float: { input: number; output: number; }
/** Date with time (isoformat) */ /** Date with time (isoformat) */
DateTime: any; DateTime: { input: string; output: string; }
/** The GenericScalar scalar type represents a generic GraphQL scalar value that could be: List or Object. */ /** Decimal (fixed-point) */
JSONScalar: any; Decimal: { input: string; output: string; }
/** The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf). */
JSON: { input: unknown; output: Array<{ name: string; count: number }>; }
}; };
export type AboveMaxSaldo = Error & { export type SchemaAboveMaxSaldo = SchemaError & {
__typename?: 'AboveMaxSaldo'; __typename?: 'AboveMaxSaldo';
above: Scalars['Float']; message: Scalars['String']['output'];
error: Scalars['Boolean']; error: Scalars['Boolean']['output'];
maxSaldo: Scalars['Float']; maxSaldo: Scalars['Float']['output'];
message: Scalars['String']; above: Scalars['Float']['output'];
}; };
export type CreateDepositResponse = AboveMaxSaldo | CreateDepositSuccess | FormErrors; export type SchemaCreateDepositResponse = SchemaCreateDepositSuccess | SchemaAboveMaxSaldo | SchemaFormErrors;
export type CreateDepositSuccess = { export type SchemaCreateDepositSuccess = {
__typename?: 'CreateDepositSuccess'; __typename?: 'CreateDepositSuccess';
transaction: Transaction; transaction: SchemaTransaction;
}; };
export type CreateOrderResponse = CreateOrderSuccess | FormErrors | InsufficientFunds; export type SchemaCreateInventoryTransactionResponse = SchemaCreateInventoryTransactionSuccess | SchemaFormErrors;
export type CreateOrderSuccess = { export type SchemaCreateInventoryTransactionSuccess = {
__typename?: 'CreateInventoryTransactionSuccess';
inventoryTransaction: SchemaInventoryTransaction;
};
export type SchemaCreateOrderResponse = SchemaCreateOrderSuccess | SchemaInsufficientFunds | SchemaFormErrors;
export type SchemaCreateOrderSuccess = {
__typename?: 'CreateOrderSuccess'; __typename?: 'CreateOrderSuccess';
order: Order; order: SchemaOrder;
}; };
export type Error = { export type SchemaError = {
error: Scalars['Boolean']; message: Scalars['String']['output'];
message: Scalars['String']; error: Scalars['Boolean']['output'];
}; };
export type FieldError = Error & { export type SchemaFieldError = SchemaError & {
__typename?: 'FieldError'; __typename?: 'FieldError';
error: Scalars['Boolean']; message: Scalars['String']['output'];
field: Scalars['String']; error: Scalars['Boolean']['output'];
message: Scalars['String']; field: Scalars['String']['output'];
}; };
export type FormErrors = { export type SchemaFormErrors = {
__typename?: 'FormErrors'; __typename?: 'FormErrors';
error: Scalars['Boolean']; fields: Array<SchemaFieldError>;
fields: Array<FieldError>; message: Scalars['String']['output'];
message: Scalars['String']; error: Scalars['Boolean']['output'];
}; };
export type InsufficientFunds = Error & { export type SchemaInsufficientFunds = SchemaError & {
__typename?: 'InsufficientFunds'; __typename?: 'InsufficientFunds';
amountLacking: Scalars['Float']; message: Scalars['String']['output'];
error: Scalars['Boolean']; error: Scalars['Boolean']['output'];
message: Scalars['String']; amountLacking: Scalars['Float']['output'];
}; };
export type Mutation = { export type SchemaInventoryReport = {
__typename?: 'InventoryReport';
products: Array<SchemaInventoryReportProduct>;
inventoryPurchases: Scalars['Decimal']['output'];
productSales: Scalars['Decimal']['output'];
deposits: Scalars['Decimal']['output'];
orderPurchases: Scalars['Decimal']['output'];
balanceAtStart: Scalars['Decimal']['output'];
balanceAtEnd: Scalars['Decimal']['output'];
};
export type SchemaInventoryReportProduct = {
__typename?: 'InventoryReportProduct';
productId: Scalars['ID']['output'];
productName: Scalars['String']['output'];
purchaseUnits: Scalars['Int']['output'];
purchaseValue: Scalars['Decimal']['output'];
salesUnits: Scalars['Int']['output'];
salesValue: Scalars['Decimal']['output'];
};
export type SchemaInventoryTransaction = {
__typename?: 'InventoryTransaction';
id: Scalars['ID']['output'];
user: SchemaUser;
product: SchemaProduct;
amount: Scalars['Int']['output'];
unitPrice: Scalars['Decimal']['output'];
comment: Scalars['String']['output'];
created: Scalars['DateTime']['output'];
};
export type SchemaInventoryTransactionInput = {
productId: Scalars['ID']['input'];
amount: Scalars['Int']['input'];
unitPrice: Scalars['Decimal']['input'];
comment?: Scalars['String']['input'];
};
export type SchemaInventoryTransactionOrdering =
{ created: SchemaOrdering; };
export type SchemaMutation = {
__typename?: 'Mutation'; __typename?: 'Mutation';
createDeposit: CreateDepositResponse; createInventoryTransaction: SchemaCreateInventoryTransactionResponse;
createOrder: CreateOrderResponse; createOrder: SchemaCreateOrderResponse;
createUser: User; createDeposit: SchemaCreateDepositResponse;
createUser: SchemaUser;
}; };
export type MutationCreateDepositArgs = { export type SchemaMutationCreateInventoryTransactionArgs = {
amount: Scalars['Int']; data: SchemaInventoryTransactionInput;
userId: Scalars['ID'];
}; };
export type MutationCreateOrderArgs = { export type SchemaMutationCreateOrderArgs = {
customerId: Scalars['ID']; customerId: Scalars['ID']['input'];
isExternal?: Scalars['Boolean']; orderLines: Array<SchemaOrderLineInput>;
orderLines: Array<OrderLineInput>; isExternal?: Scalars['Boolean']['input'];
}; };
export type MutationCreateUserArgs = { export type SchemaMutationCreateDepositArgs = {
input: UserInput; userId: Scalars['ID']['input'];
amount: Scalars['Int']['input'];
}; };
export type OffsetPaginationInput = {
limit?: Scalars['Int']; export type SchemaMutationCreateUserArgs = {
offset?: Scalars['Int']; input: SchemaUserInput;
}; };
export type Order = { export type SchemaOffsetPaginationInput = {
offset?: Scalars['Int']['input'];
limit?: InputMaybe<Scalars['Int']['input']>;
};
export type SchemaOrder = {
__typename?: 'Order'; __typename?: 'Order';
created: Scalars['DateTime']; id: Scalars['ID']['output'];
customer?: Maybe<User>; customer?: Maybe<SchemaUser>;
id: Scalars['ID']; orderSum: Scalars['Decimal']['output'];
isExternal: Scalars['Boolean']; created: Scalars['DateTime']['output'];
orderSum: Scalars['Float']; orderlines: Array<SchemaOrderLine>;
orderlines?: Maybe<Array<OrderLine>>; isExternal: Scalars['Boolean']['output'];
}; };
export type OrderLine = { export type SchemaOrderLine = {
__typename?: 'OrderLine'; __typename?: 'OrderLine';
amount: Scalars['Int']; id: Scalars['ID']['output'];
id: Scalars['ID']; order: SchemaOrder;
order: Order; product: SchemaProduct;
product: Product; amount: Scalars['Int']['output'];
unitPrice: Scalars['Float']; unitPrice: Scalars['Decimal']['output'];
};
export type SchemaOrderLineInput = {
productId: Scalars['ID']['input'];
amount: Scalars['Int']['input'];
};
export type SchemaOrderOrdering =
{ created: SchemaOrdering; };
export type SchemaOrderStats = {
__typename?: 'OrderStats';
yearly: Array<SchemaOrderStatsByTime>;
monthly: Array<SchemaOrderStatsByTime>;
daily: Array<SchemaOrderStatsByTime>;
hourly: Array<SchemaOrderStatsByTime>;
};
export type SchemaOrderStatsByTime = {
__typename?: 'OrderStatsByTime';
period: Scalars['Int']['output'];
count: Scalars['String']['output'];
};
export enum SchemaOrdering {
Asc = 'ASC',
AscNullsFirst = 'ASC_NULLS_FIRST',
AscNullsLast = 'ASC_NULLS_LAST',
Desc = 'DESC',
DescNullsFirst = 'DESC_NULLS_FIRST',
DescNullsLast = 'DESC_NULLS_LAST'
}
export type SchemaPoint = {
__typename?: 'Point';
x: Scalars['String']['output'];
y: Scalars['Int']['output'];
};
export type SchemaProduct = {
__typename?: 'Product';
id: Scalars['ID']['output'];
name: Scalars['String']['output'];
imageUrl: Scalars['String']['output'];
salePriceInt: Scalars['Decimal']['output'];
salePriceExt: Scalars['Decimal']['output'];
active: Scalars['Boolean']['output'];
inventoryAmount: Scalars['Int']['output'];
updated: Scalars['DateTime']['output'];
wholesaleUnitPrice: Scalars['Decimal']['output'];
wholesaleValue: Scalars['Decimal']['output'];
userCounts: Scalars['JSON']['output'];
};
export type SchemaProductFilter = {
id?: InputMaybe<Scalars['ID']['input']>;
active?: InputMaybe<Scalars['Boolean']['input']>;
AND?: InputMaybe<SchemaProductFilter>;
OR?: InputMaybe<SchemaProductFilter>;
NOT?: InputMaybe<SchemaProductFilter>;
DISTINCT?: InputMaybe<Scalars['Boolean']['input']>;
};
export type SchemaProductOrderStat = {
__typename?: 'ProductOrderStat';
productName: Scalars['String']['output'];
data: Array<SchemaPoint>;
};
export type SchemaQuery = {
__typename?: 'Query';
transactionList: Array<SchemaTransaction>;
inventoryTransactionList: Array<SchemaInventoryTransaction>;
inventoryReport: SchemaInventoryReport;
allProducts: Array<SchemaProduct>;
allOrders: Array<SchemaOrder>;
orderList: Array<SchemaOrder>;
orderStats: SchemaOrderStats;
productStats: Array<SchemaProductOrderStat>;
allUsers: Array<SchemaUser>;
user: SchemaUser;
};
export type SchemaQueryTransactionListArgs = {
ordering?: Array<SchemaTransactionOrdering>;
pagination?: InputMaybe<SchemaOffsetPaginationInput>;
};
export type SchemaQueryInventoryTransactionListArgs = {
ordering?: Array<SchemaInventoryTransactionOrdering>;
pagination?: InputMaybe<SchemaOffsetPaginationInput>;
};
export type SchemaQueryInventoryReportArgs = {
startTime: Scalars['DateTime']['input'];
endTime: Scalars['DateTime']['input'];
};
export type SchemaQueryAllProductsArgs = {
filters?: InputMaybe<SchemaProductFilter>;
};
export type SchemaQueryOrderListArgs = {
ordering?: Array<SchemaOrderOrdering>;
pagination?: InputMaybe<SchemaOffsetPaginationInput>;
};
export type SchemaQueryUserArgs = {
userId: Scalars['ID']['input'];
};
export type SchemaTopMonth = {
__typename?: 'TopMonth';
period: Scalars['String']['output'];
count: Scalars['Int']['output'];
sum: Scalars['Float']['output'];
};
export type SchemaTransaction = {
__typename?: 'Transaction';
id: Scalars['ID']['output'];
user: SchemaUser;
amount: Scalars['Decimal']['output'];
created: Scalars['DateTime']['output'];
};
export type SchemaTransactionOrdering =
{ created: SchemaOrdering; };
export type SchemaUser = {
__typename?: 'User';
id: Scalars['ID']['output'];
firstName: Scalars['String']['output'];
lastName: Scalars['String']['output'];
profile?: Maybe<SchemaUserProfile>;
};
export type SchemaUserInput = {
email?: InputMaybe<Scalars['String']['input']>;
firstName?: InputMaybe<Scalars['String']['input']>;
lastName?: InputMaybe<Scalars['String']['input']>;
};
export type SchemaUserProfile = {
__typename?: 'UserProfile';
id: Scalars['ID']['output'];
balance: Scalars['Decimal']['output'];
image: Scalars['String']['output'];
lastPurchaseDate: Scalars['DateTime']['output'];
orderSumTotal: Scalars['Decimal']['output'];
topMonths: Array<SchemaTopMonth>;
productOrderStats: Array<SchemaProductOrderStat>;
};
export type InventoryTransactionInput = {
productId: string | number;
amount: number;
unitPrice: string;
comment?: string;
}; };
export type OrderLineInput = { export type OrderLineInput = {
amount: Scalars['Int']; productId: string | number;
productId: Scalars['ID']; amount: number;
};
export type OrderOrdering = {
created?: InputMaybe<Ordering>;
};
export type OrderStats = {
__typename?: 'OrderStats';
daily: Array<OrderStatsByTime>;
hourly: Array<OrderStatsByTime>;
monthly: Array<OrderStatsByTime>;
yearly: Array<OrderStatsByTime>;
};
export type OrderStatsByTime = {
__typename?: 'OrderStatsByTime';
count: Scalars['String'];
period: Scalars['Int'];
};
export enum Ordering {
Asc = 'ASC',
Desc = 'DESC'
}
export type Point = {
__typename?: 'Point';
x: Scalars['String'];
y: Scalars['Int'];
};
export type Product = {
__typename?: 'Product';
id: Scalars['ID'];
imageUrl: Scalars['String'];
name: Scalars['String'];
salePriceExt: Scalars['Float'];
salePriceInt: Scalars['Float'];
userCounts: Scalars['JSONScalar'];
};
export type ProductFilter = {
active?: InputMaybe<Scalars['Boolean']>;
id?: InputMaybe<Scalars['ID']>;
};
export type ProductOrderStat = {
__typename?: 'ProductOrderStat';
data: Array<Point>;
productName: Scalars['String'];
};
export type Query = {
__typename?: 'Query';
allOrders: Array<Order>;
allProducts: Array<Product>;
allUsers: Array<User>;
orderList: Array<Order>;
orderStats: OrderStats;
productStats: Array<ProductOrderStat>;
transactionList: Array<Transaction>;
user: User;
};
export type QueryAllProductsArgs = {
filters?: InputMaybe<ProductFilter>;
};
export type QueryOrderListArgs = {
order?: InputMaybe<OrderOrdering>;
pagination?: InputMaybe<OffsetPaginationInput>;
};
export type QueryTransactionListArgs = {
order?: InputMaybe<TransactionOrdering>;
pagination?: InputMaybe<OffsetPaginationInput>;
};
export type QueryUserArgs = {
userId: Scalars['ID'];
};
export type TopMonth = {
__typename?: 'TopMonth';
count: Scalars['Int'];
period: Scalars['String'];
sum: Scalars['Int'];
};
export type Transaction = {
__typename?: 'Transaction';
amount: Scalars['Float'];
created: Scalars['DateTime'];
id: Scalars['ID'];
user: User;
};
export type TransactionOrdering = {
created?: InputMaybe<Ordering>;
};
export type User = {
__typename?: 'User';
firstName: Scalars['String'];
id: Scalars['ID'];
lastName: Scalars['String'];
profile?: Maybe<UserProfile>;
};
export type UserInput = {
email?: InputMaybe<Scalars['String']>;
firstName?: InputMaybe<Scalars['String']>;
lastName?: InputMaybe<Scalars['String']>;
};
export type UserProfile = {
__typename?: 'UserProfile';
balance: Scalars['Float'];
id: Scalars['ID'];
image: Scalars['String'];
lastPurchaseDate: Scalars['DateTime'];
orderSumTotal: Scalars['Float'];
productOrderStats: Array<ProductOrderStat>;
topMonths: Array<TopMonth>;
}; };
export type CreateUserMutationVariables = Exact<{ export type CreateUserMutationVariables = Exact<{
firstName: Scalars['String']; firstName: string;
lastName: Scalars['String']; lastName: string;
email: Scalars['String']; email: string;
}>; }>;
export type CreateUserMutation = { __typename?: 'Mutation', createUser: { __typename?: 'User', id: string, firstName: string, lastName: string, profile?: { __typename?: 'UserProfile', id: string } | null } }; export type CreateUserMutation = { __typename: 'Mutation', createUser: { __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string } | null } };
export type CreateDepositMutationVariables = Exact<{ export type CreateDepositMutationVariables = Exact<{
userId: Scalars['ID']; userId: string | number;
amount: Scalars['Int']; amount: number;
}>; }>;
export type CreateDepositMutation = { __typename?: 'Mutation', createDeposit: { __typename?: 'AboveMaxSaldo', error: boolean, message: string, maxSaldo: number, above: number } | { __typename?: 'CreateDepositSuccess', transaction: { __typename?: 'Transaction', id: string, amount: number, user: { __typename?: 'User', id: string, firstName: string, lastName: string, profile?: { __typename?: 'UserProfile', id: string, balance: number } | null } } } | { __typename?: 'FormErrors', error: boolean, message: string, fields: Array<{ __typename?: 'FieldError', field: string, message: string }> } }; export type CreateDepositMutation = { __typename: 'Mutation', createDeposit:
| { __typename: 'CreateDepositSuccess', transaction: { __typename: 'Transaction', id: string, amount: string, user: { __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string, balance: string } | null } } }
| { __typename: 'AboveMaxSaldo', error: boolean, message: string, maxSaldo: number, above: number }
| { __typename: 'FormErrors', error: boolean, message: string, fields: Array<{ __typename: 'FieldError', field: string, message: string }> }
};
export type CreateInventoryTransactionMutationVariables = Exact<{
data: InventoryTransactionInput;
}>;
export type CreateInventoryTransactionMutation = { __typename: 'Mutation', createInventoryTransaction:
| { __typename: 'CreateInventoryTransactionSuccess', inventoryTransaction: { __typename: 'InventoryTransaction', id: string, product: { __typename: 'Product', name: string, inventoryAmount: number } } }
| { __typename: 'FormErrors', message: string, fields: Array<{ __typename: 'FieldError', field: string, message: string }> }
};
export type InventoryPageQueryVariables = Exact<{
limit?: number | null | undefined;
offset?: number | null | undefined;
}>;
export type InventoryPageQuery = { __typename: 'Query', allProducts: Array<{ __typename: 'Product', id: string, name: string, active: boolean, inventoryAmount: number, updated: string, wholesaleUnitPrice: string, wholesaleValue: string }>, inventoryTransactionList: Array<{ __typename: 'InventoryTransaction', id: string, created: string, amount: number, unitPrice: string, comment: string, product: { __typename: 'Product', name: string }, user: { __typename: 'User', firstName: string, lastName: string } }> };
export type InventoryProductsQueryVariables = Exact<{ [key: string]: never; }>;
export type InventoryProductsQuery = { __typename: 'Query', allProducts: Array<{ __typename: 'Product', id: string, name: string, inventoryAmount: number }> };
export type InventoryReportQueryVariables = Exact<{
startTime: string;
endTime: string;
}>;
export type InventoryReportQuery = { __typename: 'Query', inventoryReport: { __typename: 'InventoryReport', inventoryPurchases: string, productSales: string, deposits: string, orderPurchases: string, balanceAtStart: string, balanceAtEnd: string, products: Array<{ __typename: 'InventoryReportProduct', productId: string, productName: string, purchaseUnits: number, purchaseValue: string, salesUnits: number, salesValue: string }> } };
export type OrderListQueryVariables = Exact<{ export type OrderListQueryVariables = Exact<{
limit?: InputMaybe<Scalars['Int']>; limit?: number | null | undefined;
offset?: InputMaybe<Scalars['Int']>; offset?: number | null | undefined;
}>; }>;
export type OrderListQuery = { __typename?: 'Query', orderList: Array<{ __typename?: 'Order', id: string, isExternal: boolean, created: any, orderSum: number, customer?: { __typename?: 'User', id: string, firstName: string, lastName: string, profile?: { __typename?: 'UserProfile', id: string, balance: number } | null } | null, orderlines?: Array<{ __typename?: 'OrderLine', id: string, amount: number, unitPrice: number, product: { __typename?: 'Product', id: string, name: string, salePriceExt: number, salePriceInt: number } }> | null }> }; export type OrderListQuery = { __typename: 'Query', orderList: Array<{ __typename: 'Order', id: string, isExternal: boolean, created: string, orderSum: string, customer: { __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string, balance: string } | null } | null, orderlines: Array<{ __typename: 'OrderLine', id: string, amount: number, unitPrice: string, product: { __typename: 'Product', id: string, name: string, salePriceExt: string, salePriceInt: string } }> }> };
export type TransactionListQueryVariables = Exact<{ export type TransactionListQueryVariables = Exact<{
limit?: InputMaybe<Scalars['Int']>; limit?: number | null | undefined;
offset?: InputMaybe<Scalars['Int']>; offset?: number | null | undefined;
}>; }>;
export type TransactionListQuery = { __typename?: 'Query', transactionList: Array<{ __typename?: 'Transaction', id: string, created: any, amount: number, user: { __typename?: 'User', id: string, firstName: string, lastName: string, profile?: { __typename?: 'UserProfile', id: string, balance: number } | null } }> }; export type TransactionListQuery = { __typename: 'Query', transactionList: Array<{ __typename: 'Transaction', id: string, created: string, amount: string, user: { __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string, balance: string } | null } }> };
export type AllProductsQueryVariables = Exact<{ export type AllProductsQueryVariables = Exact<{
active: Scalars['Boolean']; active: boolean;
}>; }>;
export type AllProductsQuery = { __typename?: 'Query', allProducts: Array<{ __typename?: 'Product', id: string, name: string, imageUrl: string, salePriceExt: number, salePriceInt: number }> }; export type AllProductsQuery = { __typename: 'Query', allProducts: Array<{ __typename: 'Product', id: string, name: string, imageUrl: string, salePriceExt: string, salePriceInt: string }> };
export type AllUsersQueryVariables = Exact<{ [key: string]: never; }>; export type AllUsersQueryVariables = Exact<{ [key: string]: never; }>;
export type AllUsersQuery = { __typename?: 'Query', allUsers: Array<{ __typename?: 'User', id: string, firstName: string, lastName: string, profile?: { __typename?: 'UserProfile', id: string, balance: number, image: string, lastPurchaseDate: any } | null }> }; export type AllUsersQuery = { __typename: 'Query', allUsers: Array<{ __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string, balance: string, image: string, lastPurchaseDate: string } | null }> };
export type CreateOrderMutationVariables = Exact<{ export type CreateOrderMutationVariables = Exact<{
customerId: Scalars['ID']; customerId: string | number;
orderLines: Array<OrderLineInput> | OrderLineInput; orderLines: Array<OrderLineInput> | OrderLineInput;
isExternal: Scalars['Boolean']; isExternal: boolean;
}>; }>;
export type CreateOrderMutation = { __typename?: 'Mutation', createOrder: { __typename?: 'CreateOrderSuccess', order: { __typename?: 'Order', id: string, isExternal: boolean, orderSum: number, customer?: { __typename?: 'User', id: string, firstName: string, lastName: string, profile?: { __typename?: 'UserProfile', id: string, balance: number } | null } | null, orderlines?: Array<{ __typename?: 'OrderLine', id: string, amount: number, unitPrice: number, product: { __typename?: 'Product', id: string, name: string, salePriceExt: number, salePriceInt: number } }> | null } } | { __typename?: 'FormErrors', error: boolean, message: string, fields: Array<{ __typename?: 'FieldError', field: string, message: string }> } | { __typename?: 'InsufficientFunds', error: boolean, message: string, amountLacking: number } }; export type CreateOrderMutation = { __typename: 'Mutation', createOrder:
| { __typename: 'CreateOrderSuccess', order: { __typename: 'Order', id: string, isExternal: boolean, orderSum: string, customer: { __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string, balance: string } | null } | null, orderlines: Array<{ __typename: 'OrderLine', id: string, amount: number, unitPrice: string, product: { __typename: 'Product', id: string, name: string, salePriceExt: string, salePriceInt: string } }> } }
| { __typename: 'InsufficientFunds', error: boolean, message: string, amountLacking: number }
| { __typename: 'FormErrors', error: boolean, message: string, fields: Array<{ __typename: 'FieldError', field: string, message: string }> }
};
export type AllProductStatsQueryVariables = Exact<{ export type AllProductStatsQueryVariables = Exact<{
active?: InputMaybe<Scalars['Boolean']>; active?: boolean | null | undefined;
}>; }>;
export type AllProductStatsQuery = { __typename?: 'Query', allProducts: Array<{ __typename?: 'Product', id: string, name: string, userCounts: any }> }; export type AllProductStatsQuery = { __typename: 'Query', allProducts: Array<{ __typename: 'Product', id: string, name: string, userCounts: Array<{ name: string; count: number }> }> };
export type OrderStatsQueryVariables = Exact<{ [key: string]: never; }>; export type OrderStatsQueryVariables = Exact<{ [key: string]: never; }>;
export type OrderStatsQuery = { __typename?: 'Query', orderStats: { __typename?: 'OrderStats', yearly: Array<{ __typename?: 'OrderStatsByTime', period: number, count: string }>, monthly: Array<{ __typename?: 'OrderStatsByTime', period: number, count: string }>, daily: Array<{ __typename?: 'OrderStatsByTime', period: number, count: string }>, hourly: Array<{ __typename?: 'OrderStatsByTime', period: number, count: string }> } }; export type OrderStatsQuery = { __typename: 'Query', orderStats: { __typename: 'OrderStats', yearly: Array<{ __typename: 'OrderStatsByTime', period: number, count: string }>, monthly: Array<{ __typename: 'OrderStatsByTime', period: number, count: string }>, daily: Array<{ __typename: 'OrderStatsByTime', period: number, count: string }>, hourly: Array<{ __typename: 'OrderStatsByTime', period: number, count: string }> } };
export type ProductStatsQueryVariables = Exact<{ [key: string]: never; }>; export type ProductStatsQueryVariables = Exact<{ [key: string]: never; }>;
export type ProductStatsQuery = { __typename?: 'Query', productStats: Array<{ __typename?: 'ProductOrderStat', productName: string, data: Array<{ __typename?: 'Point', x: string, y: number }> }> }; export type ProductStatsQuery = { __typename: 'Query', productStats: Array<{ __typename: 'ProductOrderStat', productName: string, data: Array<{ __typename: 'Point', x: string, y: number }> }> };
export type UserStatsQueryVariables = Exact<{ [key: string]: never; }>;
export type UserStatsQuery = { __typename: 'Query', allUsers: Array<{ __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', orderSumTotal: string } | null }> };
export type UserDetailQueryVariables = Exact<{ export type UserDetailQueryVariables = Exact<{
userId: Scalars['ID']; userId: string | number;
}>; }>;
export type UserDetailQuery = { __typename?: 'Query', user: { __typename?: 'User', id: string, firstName: string, lastName: string, profile?: { __typename?: 'UserProfile', id: string, balance: number, image: string, lastPurchaseDate: any, orderSumTotal: number, topMonths: Array<{ __typename?: 'TopMonth', period: string, sum: number, count: number }>, productOrderStats: Array<{ __typename?: 'ProductOrderStat', productName: string, data: Array<{ __typename?: 'Point', x: string, y: number }> }> } | null } }; export type UserDetailQuery = { __typename: 'Query', user: { __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string, balance: string, image: string, lastPurchaseDate: string, orderSumTotal: string, topMonths: Array<{ __typename: 'TopMonth', period: string, sum: number, count: number }>, productOrderStats: Array<{ __typename: 'ProductOrderStat', productName: string, data: Array<{ __typename: 'Point', x: string, y: number }> }> } | null } };
export const CreateUserDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateUser"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"firstName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"lastName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"email"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createUser"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"firstName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"firstName"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"lastName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"lastName"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"email"},"value":{"kind":"Variable","name":{"kind":"Name","value":"email"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode<CreateUserMutation, CreateUserMutationVariables>; export const CreateUserDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateUser"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"firstName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"lastName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"email"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createUser"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"firstName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"firstName"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"lastName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"lastName"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"email"},"value":{"kind":"Variable","name":{"kind":"Name","value":"email"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode<CreateUserMutation, CreateUserMutationVariables>;
export const CreateDepositDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateDeposit"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"amount"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createDeposit"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"userId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userId"}}},{"kind":"Argument","name":{"kind":"Name","value":"amount"},"value":{"kind":"Variable","name":{"kind":"Name","value":"amount"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CreateDepositSuccess"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"transaction"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}}]}}]}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AboveMaxSaldo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"maxSaldo"}},{"kind":"Field","name":{"kind":"Name","value":"above"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FormErrors"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"fields"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]}}]} as unknown as DocumentNode<CreateDepositMutation, CreateDepositMutationVariables>; export const CreateDepositDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateDeposit"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"amount"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createDeposit"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"userId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userId"}}},{"kind":"Argument","name":{"kind":"Name","value":"amount"},"value":{"kind":"Variable","name":{"kind":"Name","value":"amount"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CreateDepositSuccess"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"transaction"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}}]}}]}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AboveMaxSaldo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"maxSaldo"}},{"kind":"Field","name":{"kind":"Name","value":"above"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FormErrors"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"fields"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]}}]} as unknown as DocumentNode<CreateDepositMutation, CreateDepositMutationVariables>;
export const OrderListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"orderList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"10"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"orderList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"order"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"created"},"value":{"kind":"EnumValue","value":"DESC"}}]}},{"kind":"Argument","name":{"kind":"Name","value":"pagination"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isExternal"}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"orderSum"}},{"kind":"Field","name":{"kind":"Name","value":"customer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"orderlines"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"unitPrice"}},{"kind":"Field","name":{"kind":"Name","value":"product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceExt"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceInt"}}]}}]}}]}}]}}]} as unknown as DocumentNode<OrderListQuery, OrderListQueryVariables>; export const CreateInventoryTransactionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"createInventoryTransaction"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"InventoryTransactionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createInventoryTransaction"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CreateInventoryTransactionSuccess"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"inventoryTransaction"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"inventoryAmount"}}]}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FormErrors"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"fields"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]}}]} as unknown as DocumentNode<CreateInventoryTransactionMutation, CreateInventoryTransactionMutationVariables>;
export const TransactionListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"transactionList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"10"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"transactionList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"order"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"created"},"value":{"kind":"EnumValue","value":"DESC"}}]}},{"kind":"Argument","name":{"kind":"Name","value":"pagination"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}}]}}]}}]}}]}}]} as unknown as DocumentNode<TransactionListQuery, TransactionListQueryVariables>; export const InventoryPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"inventoryPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"20"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allProducts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"active"}},{"kind":"Field","name":{"kind":"Name","value":"inventoryAmount"}},{"kind":"Field","name":{"kind":"Name","value":"updated"}},{"kind":"Field","name":{"kind":"Name","value":"wholesaleUnitPrice"}},{"kind":"Field","name":{"kind":"Name","value":"wholesaleValue"}}]}},{"kind":"Field","name":{"kind":"Name","value":"inventoryTransactionList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"ordering"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"created"},"value":{"kind":"EnumValue","value":"DESC"}}]}]}},{"kind":"Argument","name":{"kind":"Name","value":"pagination"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"unitPrice"}},{"kind":"Field","name":{"kind":"Name","value":"comment"}},{"kind":"Field","name":{"kind":"Name","value":"product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}}]}}]}}]} as unknown as DocumentNode<InventoryPageQuery, InventoryPageQueryVariables>;
export const InventoryProductsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"inventoryProducts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allProducts"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"active"},"value":{"kind":"BooleanValue","value":true}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"inventoryAmount"}}]}}]}}]} as unknown as DocumentNode<InventoryProductsQuery, InventoryProductsQueryVariables>;
export const InventoryReportDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"inventoryReport"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"startTime"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DateTime"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"endTime"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DateTime"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"inventoryReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"startTime"},"value":{"kind":"Variable","name":{"kind":"Name","value":"startTime"}}},{"kind":"Argument","name":{"kind":"Name","value":"endTime"},"value":{"kind":"Variable","name":{"kind":"Name","value":"endTime"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"products"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productId"}},{"kind":"Field","name":{"kind":"Name","value":"productName"}},{"kind":"Field","name":{"kind":"Name","value":"purchaseUnits"}},{"kind":"Field","name":{"kind":"Name","value":"purchaseValue"}},{"kind":"Field","name":{"kind":"Name","value":"salesUnits"}},{"kind":"Field","name":{"kind":"Name","value":"salesValue"}}]}},{"kind":"Field","name":{"kind":"Name","value":"inventoryPurchases"}},{"kind":"Field","name":{"kind":"Name","value":"productSales"}},{"kind":"Field","name":{"kind":"Name","value":"deposits"}},{"kind":"Field","name":{"kind":"Name","value":"orderPurchases"}},{"kind":"Field","name":{"kind":"Name","value":"balanceAtStart"}},{"kind":"Field","name":{"kind":"Name","value":"balanceAtEnd"}}]}}]}}]} as unknown as DocumentNode<InventoryReportQuery, InventoryReportQueryVariables>;
export const OrderListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"orderList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"10"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"orderList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"ordering"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"created"},"value":{"kind":"EnumValue","value":"DESC"}}]}]}},{"kind":"Argument","name":{"kind":"Name","value":"pagination"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isExternal"}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"orderSum"}},{"kind":"Field","name":{"kind":"Name","value":"customer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"orderlines"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"unitPrice"}},{"kind":"Field","name":{"kind":"Name","value":"product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceExt"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceInt"}}]}}]}}]}}]}}]} as unknown as DocumentNode<OrderListQuery, OrderListQueryVariables>;
export const TransactionListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"transactionList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"10"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"transactionList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"ordering"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"created"},"value":{"kind":"EnumValue","value":"DESC"}}]}]}},{"kind":"Argument","name":{"kind":"Name","value":"pagination"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}}]}}]}}]}}]}}]} as unknown as DocumentNode<TransactionListQuery, TransactionListQueryVariables>;
export const AllProductsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"allProducts"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"active"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allProducts"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"active"},"value":{"kind":"Variable","name":{"kind":"Name","value":"active"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"imageUrl"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceExt"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceInt"}}]}}]}}]} as unknown as DocumentNode<AllProductsQuery, AllProductsQueryVariables>; export const AllProductsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"allProducts"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"active"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allProducts"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"active"},"value":{"kind":"Variable","name":{"kind":"Name","value":"active"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"imageUrl"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceExt"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceInt"}}]}}]}}]} as unknown as DocumentNode<AllProductsQuery, AllProductsQueryVariables>;
export const AllUsersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"allUsers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allUsers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"lastPurchaseDate"}}]}}]}}]}}]} as unknown as DocumentNode<AllUsersQuery, AllUsersQueryVariables>; export const AllUsersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"allUsers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allUsers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"lastPurchaseDate"}}]}}]}}]}}]} as unknown as DocumentNode<AllUsersQuery, AllUsersQueryVariables>;
export const CreateOrderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOrder"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"customerId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orderLines"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"OrderLineInput"}}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isExternal"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOrder"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"customerId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"customerId"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderLines"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orderLines"}}},{"kind":"Argument","name":{"kind":"Name","value":"isExternal"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isExternal"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CreateOrderSuccess"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"order"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isExternal"}},{"kind":"Field","name":{"kind":"Name","value":"customer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"orderlines"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"unitPrice"}},{"kind":"Field","name":{"kind":"Name","value":"product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceExt"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceInt"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"orderSum"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"InsufficientFunds"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"amountLacking"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FormErrors"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"fields"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]}}]} as unknown as DocumentNode<CreateOrderMutation, CreateOrderMutationVariables>; export const CreateOrderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOrder"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"customerId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orderLines"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"OrderLineInput"}}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isExternal"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOrder"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"customerId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"customerId"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderLines"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orderLines"}}},{"kind":"Argument","name":{"kind":"Name","value":"isExternal"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isExternal"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CreateOrderSuccess"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"order"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isExternal"}},{"kind":"Field","name":{"kind":"Name","value":"customer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"orderlines"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"unitPrice"}},{"kind":"Field","name":{"kind":"Name","value":"product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceExt"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceInt"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"orderSum"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"InsufficientFunds"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"amountLacking"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FormErrors"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"fields"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]}}]} as unknown as DocumentNode<CreateOrderMutation, CreateOrderMutationVariables>;
export const AllProductStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"allProductStats"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"active"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allProducts"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"active"},"value":{"kind":"Variable","name":{"kind":"Name","value":"active"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"userCounts"}}]}}]}}]} as unknown as DocumentNode<AllProductStatsQuery, AllProductStatsQueryVariables>; export const AllProductStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"allProductStats"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"active"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allProducts"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"active"},"value":{"kind":"Variable","name":{"kind":"Name","value":"active"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"userCounts"}}]}}]}}]} as unknown as DocumentNode<AllProductStatsQuery, AllProductStatsQueryVariables>;
export const OrderStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"orderStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"orderStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"yearly"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"monthly"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"daily"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"hourly"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]} as unknown as DocumentNode<OrderStatsQuery, OrderStatsQueryVariables>; export const OrderStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"orderStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"orderStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"yearly"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"monthly"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"daily"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"hourly"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]} as unknown as DocumentNode<OrderStatsQuery, OrderStatsQueryVariables>;
export const ProductStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"productStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productName"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"x"}},{"kind":"Field","name":{"kind":"Name","value":"y"}}]}}]}}]}}]} as unknown as DocumentNode<ProductStatsQuery, ProductStatsQueryVariables>; export const ProductStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"productStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productName"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"x"}},{"kind":"Field","name":{"kind":"Name","value":"y"}}]}}]}}]}}]} as unknown as DocumentNode<ProductStatsQuery, ProductStatsQueryVariables>;
export const UserStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"userStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allUsers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"orderSumTotal"}}]}}]}}]}}]} as unknown as DocumentNode<UserStatsQuery, UserStatsQueryVariables>;
export const UserDetailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"userDetail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"userId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"lastPurchaseDate"}},{"kind":"Field","name":{"kind":"Name","value":"topMonths"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"sum"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"productOrderStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productName"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"x"}},{"kind":"Field","name":{"kind":"Name","value":"y"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"orderSumTotal"}}]}}]}}]}}]} as unknown as DocumentNode<UserDetailQuery, UserDetailQueryVariables>; export const UserDetailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"userDetail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"userId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"lastPurchaseDate"}},{"kind":"Field","name":{"kind":"Name","value":"topMonths"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"sum"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"productOrderStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productName"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"x"}},{"kind":"Field","name":{"kind":"Name","value":"y"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"orderSumTotal"}}]}}]}}]}}]} as unknown as DocumentNode<UserDetailQuery, UserDetailQueryVariables>;
+2 -5
View File
@@ -1,8 +1,5 @@
/* svgs */ /// <reference types="vite-plugin-svgr/client" />
declare module "*.svg" {
import { ReactElement, SVGProps } from "react";
export const ReactComponent: (props: SVGProps<SVGElement>) => ReactElement;
}
/* CSS modules */ /* CSS modules */
declare module "*.module.css"; declare module "*.module.css";
+1 -1
View File
@@ -2,6 +2,6 @@ export function formatCurrency(value: number) {
if (isNaN(value)) { if (isNaN(value)) {
return ""; return "";
} else { } else {
return value.toFixed(2).replace(/\.00$/, ''); return value.toFixed(2).replace(/\.00$/, "");
} }
} }
+3 -2
View File
@@ -7,7 +7,8 @@ const activeLimitDays = 365 / 2;
type FilterableUser = AllUsersQuery["allUsers"][0]; type FilterableUser = AllUsersQuery["allUsers"][0];
export const activeUserFilter = (user: FilterableUser) => { export const activeUserFilter = (user: FilterableUser) => {
const balance = user.profile?.balance || 0; const balance = Number(user.profile?.balance) || 0;
const active = parseISO(user.profile?.lastPurchaseDate) >= subDays(new Date(), activeLimitDays); const active =
!!user.profile?.lastPurchaseDate && parseISO(user.profile.lastPurchaseDate) >= subDays(new Date(), activeLimitDays);
return balance >= 15 && active; return balance >= 15 && active;
}; };
+8
View File
@@ -9,5 +9,13 @@ export default defineConfig({
__DEV__: process.env.NODE_ENV !== "production", __DEV__: process.env.NODE_ENV !== "production",
}, },
envDir: "..", envDir: "..",
server: {
host: "127.0.0.1",
proxy: Object.fromEntries(
["/admin", "/accounts", "/static", "/media", "/graphql"].map(
(path) => [path, process.env.VITE_DEV_API_PROXY || "http://127.0.0.1:3000"],
),
),
},
build: { outDir: "../static", emptyOutDir: false }, build: { outDir: "../static", emptyOutDir: false },
}); });
+4 -1
View File
@@ -1,3 +1,6 @@
[tools] [tools]
python = "3.9" python = "3.11"
uv = "latest" uv = "latest"
[tasks.test]
run = "uv run pytest --cov"
+19 -10
View File
@@ -3,35 +3,44 @@ name = "bbs"
version = "0.1.0" version = "0.1.0"
description = "" description = ""
authors = [{ name = "Nikolai R Kristiansen", email = "nikolaik@gmail.com" }] authors = [{ name = "Nikolai R Kristiansen", email = "nikolaik@gmail.com" }]
requires-python = ">=3.9,<3.10" requires-python = ">=3.11"
license = "MIT" license = "MIT"
dependencies = [ dependencies = [
"django-cors-headers>=3.10.0,<4", "django-cors-headers>=4.9.0,<5",
"django-extensions>=3.1.3,<4", "django-extensions>=4.1,<5",
"django-rename-app>=0.1.3,<0.2", "django-rename-app>=0.1.3,<0.2",
"Django==4.2.*", "Django==5.2.*",
"easy-thumbnails>=2.8.1,<3", "easy-thumbnails>=2.8.1,<3",
"gunicorn>=20.1.0,<21", "gunicorn>=22.0.0,<27",
"mysqlclient>=2.0.3,<3", "mysqlclient>=2.0.3,<3",
"Pillow>=9.0.0,<10", "Pillow>=13.0.0,<14",
"sentry-sdk>=1.5.5,<2", "sentry-sdk>=1.5.5,<2",
"strawberry-graphql-django==0.60.*", "strawberry-graphql-django>=0.93,<0.94",
"strawberry-graphql==0.270.*", "strawberry-graphql>=0.330.3,<0.337",
] ]
[tool.pytest.ini_options] [tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "bbs.settings" DJANGO_SETTINGS_MODULE = "bbs.settings"
[tool.coverage.run]
source = ["bbs"]
omit = ["bbs/tests/*", "**/test_*.py", "manage.py", "bbs/wsgi.py"]
[tool.coverage.report]
skip_covered = true
show_missing = true
[dependency-groups] [dependency-groups]
dev = [ dev = [
"ruff", "ruff==0.11.7",
"pytest-django", "pytest-django",
"pytest", "pytest",
"pytest-cov>=7.1.0",
] ]
[tool.ruff] [tool.ruff]
line-length = 120 line-length = 120
target-version = "py310" target-version = "py311"
[tool.ruff.lint] [tool.ruff.lint]
select = ["ALL"] select = ["ALL"]
+85 -18
View File
@@ -1,3 +1,5 @@
directive @oneOf on INPUT_OBJECT
type AboveMaxSaldo implements Error { type AboveMaxSaldo implements Error {
message: String! message: String!
error: Boolean! error: Boolean!
@@ -11,6 +13,12 @@ type CreateDepositSuccess {
transaction: Transaction! transaction: Transaction!
} }
union CreateInventoryTransactionResponse = CreateInventoryTransactionSuccess | FormErrors
type CreateInventoryTransactionSuccess {
inventoryTransaction: InventoryTransaction!
}
union CreateOrderResponse = CreateOrderSuccess | InsufficientFunds | FormErrors union CreateOrderResponse = CreateOrderSuccess | InsufficientFunds | FormErrors
type CreateOrderSuccess { type CreateOrderSuccess {
@@ -20,6 +28,9 @@ type CreateOrderSuccess {
"""Date with time (isoformat)""" """Date with time (isoformat)"""
scalar DateTime scalar DateTime
"""Decimal (fixed-point)"""
scalar Decimal
interface Error { interface Error {
message: String! message: String!
error: Boolean! error: Boolean!
@@ -43,12 +54,53 @@ type InsufficientFunds implements Error {
amountLacking: Float! amountLacking: Float!
} }
type InventoryReport {
products: [InventoryReportProduct!]!
inventoryPurchases: Decimal!
productSales: Decimal!
deposits: Decimal!
orderPurchases: Decimal!
balanceAtStart: Decimal!
balanceAtEnd: Decimal!
}
type InventoryReportProduct {
productId: ID!
productName: String!
purchaseUnits: Int!
purchaseValue: Decimal!
salesUnits: Int!
salesValue: Decimal!
}
type InventoryTransaction {
id: ID!
user: User!
product: Product!
amount: Int!
unitPrice: Decimal!
comment: String!
created: DateTime!
}
input InventoryTransactionInput {
productId: ID!
amount: Int!
unitPrice: Decimal!
comment: String! = ""
}
input InventoryTransactionOrdering @oneOf {
created: Ordering
}
""" """
The GenericScalar scalar type represents a generic GraphQL scalar value that could be: List or Object. The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf).
""" """
scalar JSONScalar scalar JSON @specifiedBy(url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf")
type Mutation { type Mutation {
createInventoryTransaction(data: InventoryTransactionInput!): CreateInventoryTransactionResponse!
createOrder(customerId: ID!, orderLines: [OrderLineInput!]!, isExternal: Boolean! = false): CreateOrderResponse! createOrder(customerId: ID!, orderLines: [OrderLineInput!]!, isExternal: Boolean! = false): CreateOrderResponse!
createDeposit(userId: ID!, amount: Int!): CreateDepositResponse! createDeposit(userId: ID!, amount: Int!): CreateDepositResponse!
createUser(input: UserInput!): User! createUser(input: UserInput!): User!
@@ -56,15 +108,15 @@ type Mutation {
input OffsetPaginationInput { input OffsetPaginationInput {
offset: Int! = 0 offset: Int! = 0
limit: Int! = -1 limit: Int
} }
type Order { type Order {
id: ID! id: ID!
customer: User customer: User
orderSum: Float! orderSum: Decimal!
created: DateTime! created: DateTime!
orderlines: [OrderLine!] orderlines: [OrderLine!]!
isExternal: Boolean! isExternal: Boolean!
} }
@@ -73,7 +125,7 @@ type OrderLine {
order: Order! order: Order!
product: Product! product: Product!
amount: Int! amount: Int!
unitPrice: Float! unitPrice: Decimal!
} }
input OrderLineInput { input OrderLineInput {
@@ -81,7 +133,7 @@ input OrderLineInput {
amount: Int! amount: Int!
} }
input OrderOrdering { input OrderOrdering @oneOf {
created: Ordering created: Ordering
} }
@@ -99,7 +151,11 @@ type OrderStatsByTime {
enum Ordering { enum Ordering {
ASC ASC
ASC_NULLS_FIRST
ASC_NULLS_LAST
DESC DESC
DESC_NULLS_FIRST
DESC_NULLS_LAST
} }
type Point { type Point {
@@ -111,14 +167,23 @@ type Product {
id: ID! id: ID!
name: String! name: String!
imageUrl: String! imageUrl: String!
salePriceInt: Float! salePriceInt: Decimal!
salePriceExt: Float! salePriceExt: Decimal!
userCounts: JSONScalar! active: Boolean!
inventoryAmount: Int!
updated: DateTime!
wholesaleUnitPrice: Decimal!
wholesaleValue: Decimal!
userCounts: JSON!
} }
input ProductFilter { input ProductFilter {
id: ID id: ID
active: Boolean active: Boolean
AND: ProductFilter
OR: ProductFilter
NOT: ProductFilter
DISTINCT: Boolean
} }
type ProductOrderStat { type ProductOrderStat {
@@ -127,10 +192,12 @@ type ProductOrderStat {
} }
type Query { type Query {
transactionList(pagination: OffsetPaginationInput, order: TransactionOrdering): [Transaction!]! transactionList(ordering: [TransactionOrdering!]! = [], pagination: OffsetPaginationInput): [Transaction!]!
inventoryTransactionList(ordering: [InventoryTransactionOrdering!]! = [], pagination: OffsetPaginationInput): [InventoryTransaction!]!
inventoryReport(startTime: DateTime!, endTime: DateTime!): InventoryReport!
allProducts(filters: ProductFilter): [Product!]! allProducts(filters: ProductFilter): [Product!]!
allOrders: [Order!]! allOrders: [Order!]!
orderList(pagination: OffsetPaginationInput, order: OrderOrdering): [Order!]! orderList(ordering: [OrderOrdering!]! = [], pagination: OffsetPaginationInput): [Order!]!
orderStats: OrderStats! orderStats: OrderStats!
productStats: [ProductOrderStat!]! productStats: [ProductOrderStat!]!
allUsers: [User!]! allUsers: [User!]!
@@ -140,17 +207,17 @@ type Query {
type TopMonth { type TopMonth {
period: String! period: String!
count: Int! count: Int!
sum: Int! sum: Float!
} }
type Transaction { type Transaction {
id: ID! id: ID!
user: User! user: User!
amount: Float! amount: Decimal!
created: DateTime! created: DateTime!
} }
input TransactionOrdering { input TransactionOrdering @oneOf {
created: Ordering created: Ordering
} }
@@ -169,10 +236,10 @@ input UserInput {
type UserProfile { type UserProfile {
id: ID! id: ID!
balance: Float! balance: Decimal!
image: String! image: String!
lastPurchaseDate: DateTime! lastPurchaseDate: DateTime!
orderSumTotal: Float! orderSumTotal: Decimal!
productOrderStats: [ProductOrderStat!]!
topMonths: [TopMonth!]! topMonths: [TopMonth!]!
productOrderStats: [ProductOrderStat!]!
} }
Generated
+356 -152
View File
@@ -1,17 +1,14 @@
version = 1 version = 1
revision = 2 revision = 5
requires-python = "==3.9.*" requires-python = ">=3.11"
[[package]] [[package]]
name = "asgiref" name = "asgiref"
version = "3.8.1" version = "3.12.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ sdist = { url = "https://files.pythonhosted.org/packages/e6/26/3b59f2bdae5f640389becb1f673cded775287f5fc4f816309d9ca9a3f93d/asgiref-3.12.1.tar.gz", hash = "sha256:59dcb51c272ad209d59bed5708a64a333083e86017d7fcdd67498eeab7784340", size = 42378, upload-time = "2026-07-14T09:56:18.087Z" }
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/29/38/b3395cc9ad1b56d2ddac9970bc8f4141312dbaec28bc7c218b0dfafd0f42/asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590", size = 35186, upload-time = "2024-03-22T14:39:36.863Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828, upload-time = "2024-03-22T14:39:34.521Z" }, { url = "https://files.pythonhosted.org/packages/c0/1b/54f4ad77cd8a584fa70746c47df988e002cf1ee1eba43364d46f87803647/asgiref-3.12.1-py3-none-any.whl", hash = "sha256:fe386d1c2bff7259ea95929266d12a8cf9a8b5a1c2598402967d8792e7a7c094", size = 25478, upload-time = "2026-07-14T09:56:16.926Z" },
] ]
[[package]] [[package]]
@@ -35,39 +32,41 @@ dependencies = [
[package.dev-dependencies] [package.dev-dependencies]
dev = [ dev = [
{ name = "pytest" }, { name = "pytest" },
{ name = "pytest-cov" },
{ name = "pytest-django" }, { name = "pytest-django" },
{ name = "ruff" }, { name = "ruff" },
] ]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "django", specifier = "==4.2.*" }, { name = "django", specifier = "==5.2.*" },
{ name = "django-cors-headers", specifier = ">=3.10.0,<4" }, { name = "django-cors-headers", specifier = ">=4.9.0,<5" },
{ name = "django-extensions", specifier = ">=3.1.3,<4" }, { name = "django-extensions", specifier = ">=4.1,<5" },
{ name = "django-rename-app", specifier = ">=0.1.3,<0.2" }, { name = "django-rename-app", specifier = ">=0.1.3,<0.2" },
{ name = "easy-thumbnails", specifier = ">=2.8.1,<3" }, { name = "easy-thumbnails", specifier = ">=2.8.1,<3" },
{ name = "gunicorn", specifier = ">=20.1.0,<21" }, { name = "gunicorn", specifier = ">=22.0.0,<27" },
{ name = "mysqlclient", specifier = ">=2.0.3,<3" }, { name = "mysqlclient", specifier = ">=2.0.3,<3" },
{ name = "pillow", specifier = ">=9.0.0,<10" }, { name = "pillow", specifier = ">=13.0.0,<14" },
{ name = "sentry-sdk", specifier = ">=1.5.5,<2" }, { name = "sentry-sdk", specifier = ">=1.5.5,<2" },
{ name = "strawberry-graphql", specifier = "==0.270.*" }, { name = "strawberry-graphql", specifier = ">=0.330.3,<0.337" },
{ name = "strawberry-graphql-django", specifier = "==0.60.*" }, { name = "strawberry-graphql-django", specifier = ">=0.93,<0.94" },
] ]
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "pytest" }, { name = "pytest" },
{ name = "pytest-cov", specifier = ">=7.1.0" },
{ name = "pytest-django" }, { name = "pytest-django" },
{ name = "ruff" }, { name = "ruff", specifier = "==0.11.7" },
] ]
[[package]] [[package]]
name = "certifi" name = "certifi"
version = "2025.4.26" version = "2026.10.19"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" } sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/1436c50cc46699de0230fbaacc1d1de7bfec413176099949e9d5112e88f2/certifi-2026.10.19.tar.gz", hash = "sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae", size = 137598, upload-time = "2026-10-20T02:24:16.604Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, { url = "https://files.pythonhosted.org/packages/c4/51/29046e48f693c54e5b7412216d447a1d8362c9653148d98c02cdf65fa733/certifi-2026.10.19-py3-none-any.whl", hash = "sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b", size = 136464, upload-time = "2026-10-20T02:24:15.072Z" },
] ]
[[package]] [[package]]
@@ -79,42 +78,181 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
] ]
[[package]]
name = "coverage"
version = "7.17.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d2/79/e78bd8f75a9b8e32233ce92d4607f0a21ce88a9156cace2884e7175a82f5/coverage-7.17.0.tar.gz", hash = "sha256:cc010a0a7fbdd9bb8bdfd0d3f04a5151275860213cbd7e2b876b29778489c712", size = 991021, upload-time = "2026-10-22T16:06:50.009Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/75/25/79a66272edd250031aeee6db87d500a0bf76390d0d5f8c2f431fe354715f/coverage-7.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:99a7b390cbccc6bf029ca4da3f0f607c218bb4085e9cd6b58a90d14ca12993c3", size = 225837, upload-time = "2026-10-22T16:03:55.622Z" },
{ url = "https://files.pythonhosted.org/packages/52/a6/f5f6fefb2fc1305363927edad1d0a60ce6ef80d6c1a74a79e825f0f670eb/coverage-7.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f72640e02a470b57b15cd655820133f6892760209283a363a60e2df932d5f0fe", size = 226257, upload-time = "2026-10-22T16:03:57.164Z" },
{ url = "https://files.pythonhosted.org/packages/28/61/72889bac8eb44c51f8043edb227df551dfdca1fc4819a6c6d67b3f69427b/coverage-7.17.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2dc9233f39149b5a4f4aba17a4b6bc5cca22e52cfc4b06fdb07d014439b8b2ea", size = 258282, upload-time = "2026-10-22T16:03:58.521Z" },
{ url = "https://files.pythonhosted.org/packages/f4/e4/03e4a5644df9b73de0d44d1b82acd18b06c42b5abc3f1433be8363fa983d/coverage-7.17.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e8113d3852e9377421a61de1917df27b3abb25404a4ecf69f9e64612c4345b5", size = 260506, upload-time = "2026-10-22T16:04:00.116Z" },
{ url = "https://files.pythonhosted.org/packages/6f/4e/949022286df7e59e8633a3a7e6635e0e6b53af7d57074cb0bf8f500373fb/coverage-7.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c86a39539d10d666bb819c16c94d7edfc1aa2e0531b937f7c549b83d15177c6e", size = 262600, upload-time = "2026-10-22T16:04:01.49Z" },
{ url = "https://files.pythonhosted.org/packages/27/cf/7d67f60df081006315699b7edb9402c1b47142caf1383f4e56ae8ff1a501/coverage-7.17.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:559f748446ee799803686b5784e3deb54c70d655ac386a860e06b11672504ba7", size = 264609, upload-time = "2026-10-22T16:04:03.286Z" },
{ url = "https://files.pythonhosted.org/packages/e1/e0/9293d94afbc099990a085e88229a51c79b00828aba0f2adda32dc4cc16d1/coverage-7.17.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34d65c9e8e257f48a654908005046fbf1bc48d9540ae3776ad08c0acd010e10c", size = 258382, upload-time = "2026-10-22T16:04:04.804Z" },
{ url = "https://files.pythonhosted.org/packages/24/c8/bcd28efedf262c074dae76f61098739eb02b15f3f716f80cc86f9885b8a9/coverage-7.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b7fa0997b8eba7378ac66f31ec7f70164490353ab79b9667d37c0d365e50177", size = 260299, upload-time = "2026-10-22T16:04:06.291Z" },
{ url = "https://files.pythonhosted.org/packages/b8/dc/503f55e5e2200933403c90d619d9f779b5060ddd338b1bdc63cd235592cb/coverage-7.17.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6ae0453a3390e67f7bfbe34417bbdf5b4ebfc21db23fb446a0a41115eeeca6ca", size = 258314, upload-time = "2026-10-22T16:04:07.729Z" },
{ url = "https://files.pythonhosted.org/packages/bb/b1/f1a57c36077cc516240636f7eff0d1427939ed795f096dd2c7769213a56c/coverage-7.17.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:96151262832f3c230ebae23b2d6911ad549e1738465946c97004913ff06112ba", size = 262239, upload-time = "2026-10-22T16:04:09.116Z" },
{ url = "https://files.pythonhosted.org/packages/a8/4f/c2e59d4a132936c7c207b9b69ab99c0f27b14c3c93266e9922aa3b562b37/coverage-7.17.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:cd7397de6cc782f00fe5ac1d75e9b306bcfa49c8b6b8dd6ed1f7709393ad40b3", size = 257841, upload-time = "2026-10-22T16:04:10.509Z" },
{ url = "https://files.pythonhosted.org/packages/05/04/50742c0ee8b6c85826ff11737c93ed957fea4c9acf4b4b99804bdac1dd84/coverage-7.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9d1fdf82c6fea57b5e9dada509f9ce9638c8334664e2c98c7f6f1ccf3d143ad2", size = 259039, upload-time = "2026-10-22T16:04:11.916Z" },
{ url = "https://files.pythonhosted.org/packages/8e/cd/83ad4bbbfcb6d045a33c14cdca313f6c9275737c67f3b7300222e3239a37/coverage-7.17.0-cp311-cp311-win32.whl", hash = "sha256:38c86e5d74bbfd88a393fa3f91257c1225c63aa6db6d5918de3e49ab04f91c6e", size = 227687, upload-time = "2026-10-22T16:04:13.537Z" },
{ url = "https://files.pythonhosted.org/packages/ca/c8/dcc5a7effa79707eb8a61e699b141cff7c9b46036771274cfb48ea7b765f/coverage-7.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:2ed3097c6817bc1a7fc8ae0a6131db74aa1150d863cccc308237ff362d3226b2", size = 228290, upload-time = "2026-10-22T16:04:14.899Z" },
{ url = "https://files.pythonhosted.org/packages/d1/58/a8553e0dcd38d8136df63964638d2b588e0a4a916bff1bca996244fda693/coverage-7.17.0-cp311-cp311-win_arm64.whl", hash = "sha256:6052e878b628abcd38fa5f2b64abebc686eed4e2267da42434017a9c6d24f241", size = 228199, upload-time = "2026-10-22T16:04:16.351Z" },
{ url = "https://files.pythonhosted.org/packages/0a/25/989fd3b495c4274ac0cf54cb6664326415e60183a7f8a73dd9bbb0b11e34/coverage-7.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0cfdc938ca3319d873d1f1252e60620a39aee4ebf9dbd7cc58238109ebda9c51", size = 225938, upload-time = "2026-10-22T16:04:18.113Z" },
{ url = "https://files.pythonhosted.org/packages/db/cb/14726426bf3241657facbf0f7df7babd8f7d3fcf8e4609b7929f9dceecbc/coverage-7.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d523da3c2e4bca49417768bcdb1a443c5b359dc44b8b93a3102e118fce912c40", size = 226166, upload-time = "2026-10-22T16:04:19.524Z" },
{ url = "https://files.pythonhosted.org/packages/8e/97/f95ed32cd3cfbe261a3598e694cb770cefc7b2d13ff013418d6f70a44dde/coverage-7.17.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:adbf3450c0f3f2b2854e282a8758f2b3eddf787eeff24ec9c9bc2cbc38328cc1", size = 258462, upload-time = "2026-10-22T16:04:21.079Z" },
{ url = "https://files.pythonhosted.org/packages/91/ac/eb4c31f741509a9ef48cf6f28385e98e9de0785599282252eee252ff8af7/coverage-7.17.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:85932227e9dd38e62cd29c17fefdfecfb919eead6af1e3259522d03cf85de983", size = 261789, upload-time = "2026-10-22T16:04:23.037Z" },
{ url = "https://files.pythonhosted.org/packages/e8/3e/d9cd006d1a3cc5deab07beb29b86cb4a10dd84d66b330820363fb8d310d6/coverage-7.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30f5477aac9c9e4a0a641c55f8f8fddd7f3bd91176c912c50f40ffced96b4034", size = 262322, upload-time = "2026-10-22T16:04:24.94Z" },
{ url = "https://files.pythonhosted.org/packages/c0/3a/4c872c6ae0762b09cdb8e856891bb3f584848cb60b6dda2bacb85fe2f3f0/coverage-7.17.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0b8bb651a4406fb6a983a905309fec1783443c65e1771b41e92740a0f4a1176b", size = 264600, upload-time = "2026-10-22T16:04:26.444Z" },
{ url = "https://files.pythonhosted.org/packages/df/98/1d41a2373fa98b423bcd70adb91d75e38eafb417955426dbb920b10016f3/coverage-7.17.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0edeb6594f080c8de1263cccb239cb316ef77aeb959e2e0f38f2953fcc72a49e", size = 258678, upload-time = "2026-10-22T16:04:27.832Z" },
{ url = "https://files.pythonhosted.org/packages/92/6a/fe04b5e63e76506daa63d90bdfb00a53767fe5df18546b437d6226918d6b/coverage-7.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da94ec0484e84fce5c7bf8d0f54b97767a90d992653a129b35634699d516e0a5", size = 260210, upload-time = "2026-10-22T16:04:29.275Z" },
{ url = "https://files.pythonhosted.org/packages/4f/c3/5c5a78dc19dd4799be9c6ec022316fb25ababaeacfaf0f52f24c56a57b43/coverage-7.17.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a25cc5e8c23a590815c6a7aa43624234d25cb23b21b484adb8a0daed59860db5", size = 258375, upload-time = "2026-10-22T16:04:30.647Z" },
{ url = "https://files.pythonhosted.org/packages/ed/e7/4c1d8eaff4d756fefd15d58c4953a78ab656782fd0e32c5a3465dba61aa0/coverage-7.17.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db645c81cf4d6bcb392ea1894aad65d55d1588a8472c834b25ca575da3029a56", size = 262422, upload-time = "2026-10-22T16:04:32.053Z" },
{ url = "https://files.pythonhosted.org/packages/a6/2a/0b17c3e2e7b6ff1a5d92eb58915c0d1026668928f961d14dac73cf1b6096/coverage-7.17.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6a80a11b90c4a03df1502304cf24ff953255b6b5d076ef79066ecf88baaff31f", size = 257875, upload-time = "2026-10-22T16:04:33.824Z" },
{ url = "https://files.pythonhosted.org/packages/d3/2d/eb0cbde0535342df0d17a667163030a3afb67e5eb9e3ad94af9540041584/coverage-7.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e4eaf11d6b0f2147f46cfbf06fe7edcca6f095d0120da00ddc2458995ee869fc", size = 260361, upload-time = "2026-10-22T16:04:35.661Z" },
{ url = "https://files.pythonhosted.org/packages/2e/47/1be2a6db7eb655d0a1d899217c3f2469f9121cbbeddac35c1c8173b1482e/coverage-7.17.0-cp312-cp312-win32.whl", hash = "sha256:912a29f338798541d795e2e25903c6fe7a99dee70a40fe4706a9a124649b523e", size = 227747, upload-time = "2026-10-22T16:04:37.102Z" },
{ url = "https://files.pythonhosted.org/packages/11/c5/201c894c57de81875d8b8cc527ba222aa350e8cfb183eb3f6df45d06ad8e/coverage-7.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:88a1a9fb0075b83389dd9b403bb3fce976031b3c2aa0e05e3e57143cf639de69", size = 228325, upload-time = "2026-10-22T16:04:38.989Z" },
{ url = "https://files.pythonhosted.org/packages/0b/5d/196095339b19758cf83f507fb598bc42820ae124745feb3084662729e17a/coverage-7.17.0-cp312-cp312-win_arm64.whl", hash = "sha256:2c680e838ff9e68d491158625ca2c1ddb17d78d791bba96a31f8b839b4990438", size = 228131, upload-time = "2026-10-22T16:04:40.654Z" },
{ url = "https://files.pythonhosted.org/packages/54/e6/1f01afbc9f4fa8ca5e1fa6fdf537a5c58b5765d1b4167ee284cddb9acc72/coverage-7.17.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:84538c040f4e6214f663459d89372ce4e6b2bbdd9d397ef0dca7b3ecd56b8eb2", size = 227080, upload-time = "2026-10-22T16:04:42.325Z" },
{ url = "https://files.pythonhosted.org/packages/92/7a/bdb7e6260c4a211389aeac9b89dc31662ebe122153ad0ecfd156897912df/coverage-7.17.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:6abfb6a4cf1cb1b67bc8fccdde177e2d2b781d0d34449e7078156547025140e5", size = 227152, upload-time = "2026-10-22T16:04:43.737Z" },
{ url = "https://files.pythonhosted.org/packages/37/4e/276e7d5b3cfb124871bdb544c27b004a8b4e270e6bf8e7414149c54b5e5b/coverage-7.17.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:ef65e96f0fc1a7c8b7644c7011d47a8228555e7888ac4d6246f715a7f3d7c398", size = 225397, upload-time = "2026-10-22T16:04:45.292Z" },
{ url = "https://files.pythonhosted.org/packages/b3/7d/dac073db12f3c131598a6693253aded581e4d7d622a89864f11bfb0ca2f5/coverage-7.17.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:55939ea4c097fda2edabf80fac5e49bef15597b950a9f29fc56292a895245feb", size = 225705, upload-time = "2026-10-22T16:04:46.603Z" },
{ url = "https://files.pythonhosted.org/packages/00/45/877b0a9248e921080ae91d115c13192d17e308f5aee86bcc7f7bc2a54ed1/coverage-7.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bfb6f868af552c0ceb2d19220b8cbcc2be096ca26163ac30e682a13041433429", size = 225828, upload-time = "2026-10-22T16:04:48.065Z" },
{ url = "https://files.pythonhosted.org/packages/4f/f5/2c48d47abec0e4590b302920334be5ee3854f96bbd22524feabb8d92b220/coverage-7.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8d823d66afd59566030539c49c7a6acaf3083996682be2dda760c63555842094", size = 226088, upload-time = "2026-10-22T16:04:49.513Z" },
{ url = "https://files.pythonhosted.org/packages/a8/a0/ec0442f3f372c7fd742f49b3022edd392f79e37a0048e1fa333f56d01ec3/coverage-7.17.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:50678474e714c684b82c108e63fbb8f1bcf1266d8293a49980f1d08526f093b5", size = 257257, upload-time = "2026-10-22T16:04:50.982Z" },
{ url = "https://files.pythonhosted.org/packages/92/e2/eb0927ba93809f76eee69fa1e47999825fc7dbbabc3e0e1a2744f2677acb/coverage-7.17.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:045c1eeb19fb4eae949850a761102fffb3a5f638b97f977912f95843e54f6669", size = 260165, upload-time = "2026-10-22T16:04:52.409Z" },
{ url = "https://files.pythonhosted.org/packages/9a/a2/8d4c6055efb32099b15c9cc33d007b8aabebc4832f372a3274de5ba0afc5/coverage-7.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfb12891a773cbc2132349c77c690a045a882fe3f98b4bda50dfabb01893f9bd", size = 261098, upload-time = "2026-10-22T16:04:53.763Z" },
{ url = "https://files.pythonhosted.org/packages/2c/1e/93412288688a7b03f152a734c8ef086e395108a0a6a746985d3967e220f2/coverage-7.17.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3067608b4429629f640c1b23d2f69b05d2fd45e00ebb6ee9e8fad220a6c11722", size = 263164, upload-time = "2026-10-22T16:04:55.386Z" },
{ url = "https://files.pythonhosted.org/packages/3c/20/7eb8d805c4f1a212245cef20181dfbf39535357985073d49730ee9cf08d4/coverage-7.17.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a1762e85cbbe3b5ff9c6dcbcc1b89d5c72320094292b7b9572bf882c85e428b7", size = 257287, upload-time = "2026-10-22T16:04:56.976Z" },
{ url = "https://files.pythonhosted.org/packages/1a/c1/1df29630b4ed17e135c76390afcc6bd273c3dc5ba2a21a76813a600fe98c/coverage-7.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:62bb77987d3f72a0aaa77c567ee7f9806bc2e748c8f46fafbe1fdb90dd148398", size = 259256, upload-time = "2026-10-22T16:04:58.545Z" },
{ url = "https://files.pythonhosted.org/packages/a1/79/2365d1008d28aafe468a450a24c6c63363de794b5140223d67685cd0e205/coverage-7.17.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6cc31f27240d4e080437e3b8229d92f1d809c9a4f233607385373b73974e183e", size = 257178, upload-time = "2026-10-22T16:05:00.103Z" },
{ url = "https://files.pythonhosted.org/packages/c0/9f/f002952d295d772688dffce0d991e3822795b1e3aad158073712a5a5e86e/coverage-7.17.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:61cde5222f1933d476647c5a25ed1a8678b7703098cb5a15f4a0aa39967d8c28", size = 261275, upload-time = "2026-10-22T16:05:01.462Z" },
{ url = "https://files.pythonhosted.org/packages/40/fe/f9e15fbb426be97cd679ee5d359151e046f103bcd76b5092c318c26ed596/coverage-7.17.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:11f5f7fb2d97a0fe865f5e7bad4eb0619b957637f676bc088d13844adf26ba95", size = 257029, upload-time = "2026-10-22T16:05:02.8Z" },
{ url = "https://files.pythonhosted.org/packages/c2/4a/3c398f38694387334ff4c9f71afc8e15b4fa7db7bc0bea4d5b6032eb0df0/coverage-7.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e09612b1b82c9df1d2b6c85dee88d5aad3e73d77a8ec0b16ac6a7322cbdf5d9", size = 259109, upload-time = "2026-10-22T16:05:04.241Z" },
{ url = "https://files.pythonhosted.org/packages/bd/a9/908a3cf1a2335aec1c788682a06c2f742a62a4f987033f080de1c5fd1997/coverage-7.17.0-cp313-cp313-win32.whl", hash = "sha256:bd3e9eb493631ff1ca34bf5efac25b5c66724f837bf6182c4fc771c55946423d", size = 227691, upload-time = "2026-10-22T16:05:05.636Z" },
{ url = "https://files.pythonhosted.org/packages/13/f7/8cdd7bfd8a63b3e6b133fc395ecfc7b1cb6f561926e7e2ad050de85b83e7/coverage-7.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:f1fc9a7cea899c51d8a53e66c2f082a3d9e461ece32418d3ef0a0b90c32bfd3d", size = 228418, upload-time = "2026-10-22T16:05:07.057Z" },
{ url = "https://files.pythonhosted.org/packages/09/cf/0b3fc57f735cd3831ca13d0be5312a5477d5934066683c5d34018cab7541/coverage-7.17.0-cp313-cp313-win_arm64.whl", hash = "sha256:e65a1e61a68493fbfbe44d8bec71b3231d81b9917f62d3dafe0e105072147e83", size = 228142, upload-time = "2026-10-22T16:05:08.435Z" },
{ url = "https://files.pythonhosted.org/packages/4d/e1/722462fe79912ebebc101497bd16c42413cde2dbae771017f1ec0acb84c2/coverage-7.17.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:dc5936491e306f2f6f095bd5fba593de11eb0375cee787ca1f245c3a333a523a", size = 227197, upload-time = "2026-10-22T16:05:09.846Z" },
{ url = "https://files.pythonhosted.org/packages/b7/74/826b9c09f2ebfea803bccb391d935688a25309bff8e819fc446bc629c108/coverage-7.17.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:968c5fb6e0c83cab752d79a70209648ce4d90cae815c60fa229b59e264601062", size = 227314, upload-time = "2026-10-22T16:05:11.422Z" },
{ url = "https://files.pythonhosted.org/packages/6d/19/c8514d7f5e0659a96ff2bc7377119dca6d420e2761c5fc69ae37618734f8/coverage-7.17.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:12cb8a20ad904a5811e6660d829a586b216e5254b9b34b76dde8a4d65d827dc6", size = 225477, upload-time = "2026-10-22T16:05:12.761Z" },
{ url = "https://files.pythonhosted.org/packages/81/74/ef9433ce599f07d8633b4ce6e6d6921505c4ef3f7f9006f8eb66e2c278cf/coverage-7.17.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:bb5ca66937c55bc85d1a5e9c5900a3e4693a8edf2280a9b3672b7b2661a84b95", size = 225787, upload-time = "2026-10-22T16:05:14.394Z" },
{ url = "https://files.pythonhosted.org/packages/d8/66/26e58981940e8b3944c6a9941bec5c7f50840e7009a046712e3f3bcd9e6a/coverage-7.17.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5cc15faaf318ee5040d835a62c75bc6f122d837274f3b5efd47bf9fbd4ac5b85", size = 226019, upload-time = "2026-10-22T16:05:15.798Z" },
{ url = "https://files.pythonhosted.org/packages/e9/63/fe175310815cc8357b83b773c9b4c20943c269f217dd825a37c4b4bfe324/coverage-7.17.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7d6d9d396c8b7bcaf76d774a10140b218111484e54c73a5f2a1b75655381748e", size = 226169, upload-time = "2026-10-22T16:05:17.188Z" },
{ url = "https://files.pythonhosted.org/packages/2c/b0/ac84761f5b4bd226696b0ec4606903d2ef7208abda0aec06c14a4218def4/coverage-7.17.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:42e6f170d1ac04b7eb3363310a6c41cdfd82c1fe6bf9ffbc4a86f380bdf091c7", size = 258092, upload-time = "2026-10-22T16:05:18.691Z" },
{ url = "https://files.pythonhosted.org/packages/e9/58/d8bdd65a661913bc12eaaa3e59742b2cb8b94f2e7f78e62184f0a58ca493/coverage-7.17.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:acdb5f3c96046efff9dc6c572a6acb2b8d0da09372d7b9a9ed7249cf7d17434f", size = 261096, upload-time = "2026-10-22T16:05:20.089Z" },
{ url = "https://files.pythonhosted.org/packages/7b/15/5d82e2f0a75ea149e0a6412e137e1d610538772fdfd7199737db7fe234da/coverage-7.17.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3393209f6894713161ee82a9b0e673e58237e0f9733c6638e7a6c0c620ef0b33", size = 262080, upload-time = "2026-10-22T16:05:21.568Z" },
{ url = "https://files.pythonhosted.org/packages/0f/cc/e3ebfd295d1dd47c7bc84a17d73f150465a37ab388dd68fe82b495dc79a0/coverage-7.17.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e9494d9e99389d39e6a96227749af898d8924ff591cb63944f8a98e68582bf0e", size = 264558, upload-time = "2026-10-22T16:05:22.906Z" },
{ url = "https://files.pythonhosted.org/packages/e0/9d/e2adff76d6544bbc6df7366d74967923c2de37ba889a006bfb875bf5857b/coverage-7.17.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:032d4c2db4c677eb71d43f2f8b80bf64e9346f7a3f7b6ebb13f1782f3cc6c274", size = 257855, upload-time = "2026-10-22T16:05:24.343Z" },
{ url = "https://files.pythonhosted.org/packages/d9/fd/ecd1e9cddf1c36b5d03c438568b1f53e63db84bb3656539d71b454484c7d/coverage-7.17.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4343a9378b964eb3cb80fa90406385de91c7e982a769ca9314d848f93bc0d3f2", size = 259933, upload-time = "2026-10-22T16:05:25.79Z" },
{ url = "https://files.pythonhosted.org/packages/40/b6/e17a19dccdbcad494c64691f71c1dba3e55c30625bc29ffbb88ac5a0aa8b/coverage-7.17.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:b609df3d6d900464207532c5170a65c6ede8d2e0b91f151f90008a866395cd76", size = 257972, upload-time = "2026-10-22T16:05:27.185Z" },
{ url = "https://files.pythonhosted.org/packages/6a/0d/37dac534709a3fa43a937c8b0604839bb44b8670b83d143a4b78fec20d1e/coverage-7.17.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a01ef92caeedfa4f2ad171a3a8e6fece4565de871d8bca428151bf9929f41048", size = 262271, upload-time = "2026-10-22T16:05:28.619Z" },
{ url = "https://files.pythonhosted.org/packages/cb/17/7c05186925b4d64a46c271dfd944e3f0c1ad89db9095c31a6df157a75df2/coverage-7.17.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:a3914e2c98c34b94c26d2add3d2a61ccfd3534fc4eaa879ee290bdbb5184ba6c", size = 257604, upload-time = "2026-10-22T16:05:30.07Z" },
{ url = "https://files.pythonhosted.org/packages/95/65/c241cdee66a0ff86c68f6d37328e735d10e3d30cd6ff66f356039eb688d2/coverage-7.17.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8a72b26dc5c9a9422e45472b1d67ba5caf273c524c8668cc04b23062663755e", size = 259714, upload-time = "2026-10-22T16:05:31.402Z" },
{ url = "https://files.pythonhosted.org/packages/db/09/e93233fe2405574ce0a31ed201c0cba3a860e085db5d2828bf12b7dc0790/coverage-7.17.0-cp314-cp314-win32.whl", hash = "sha256:03baf0688e14a5a44ffd1eefa8928dfc92b14565e521174e072b31b971aa1657", size = 227914, upload-time = "2026-10-22T16:05:32.78Z" },
{ url = "https://files.pythonhosted.org/packages/d7/77/04979eb217edb94b94afabb4bcc179f60c746f68d3fd0b12688ba9195730/coverage-7.17.0-cp314-cp314-win_amd64.whl", hash = "sha256:3a4459b0f389dd1e51aac3dbfc4ca35b30cae7dc99caa3c0a086d1997a985709", size = 228543, upload-time = "2026-10-22T16:05:34.118Z" },
{ url = "https://files.pythonhosted.org/packages/a0/38/155f2f2c6769f8c43eca8a5e581443faa0908828650090b97103b4a41376/coverage-7.17.0-cp314-cp314-win_arm64.whl", hash = "sha256:a32ee5831b15ba239bef5d272ae586ca8acea4a661b5f2941f5a0d380e518b23", size = 228396, upload-time = "2026-10-22T16:05:35.951Z" },
{ url = "https://files.pythonhosted.org/packages/52/55/d6751572d9445485c4c5a51ec6ae11caa5c11b7ef2448cdccc884f7f54d9/coverage-7.17.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9c083e253e96fdeeaf48133fe836cac86ace2db215d235700e7f7532420a200c", size = 226486, upload-time = "2026-10-22T16:05:37.363Z" },
{ url = "https://files.pythonhosted.org/packages/2f/a8/82d37760e537bfdb9fdee285fa1e1b45bc153293d23e2331c7befedd1990/coverage-7.17.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b88fb4d4d7608cbcebdc7ff7dbffc2115e3b81d6c33a79147cd79d54fcb080f4", size = 226861, upload-time = "2026-10-22T16:05:38.777Z" },
{ url = "https://files.pythonhosted.org/packages/24/96/70ec00a9db6d7bdd3f914eba12057360769f5cb78a98456e71fe1937deb5/coverage-7.17.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2bf3ee38a40c0570c316b9e7507c472469586906a20f289705898528c31aaea8", size = 266849, upload-time = "2026-10-22T16:05:40.285Z" },
{ url = "https://files.pythonhosted.org/packages/77/d8/d99be3bfbb208512f039c80b31e404138a86678fc929a17acd5b7135a6ae/coverage-7.17.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7c570293f001ea91598b90e90cb9b98fddee833e90bbede432cbd218331ccd8e", size = 269402, upload-time = "2026-10-22T16:05:41.585Z" },
{ url = "https://files.pythonhosted.org/packages/82/50/6cbe8b5a5060edbd21ce8109c4e900f8b7656e8b72ede6dc4d82dc0b8bfb/coverage-7.17.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:791f9ee09814dd30d9562b4f4eb037a371485ec1c830b53999d2320d20c4e764", size = 272025, upload-time = "2026-10-22T16:05:42.921Z" },
{ url = "https://files.pythonhosted.org/packages/bd/05/d3633add01e5518dfdacfdbd1be41cb5c581412dd8bccd663edf6ef7fec8/coverage-7.17.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c7c5672ecf1b497de7a0b37743bef40e3b58e3207e9fe2ed6682cb0420836a88", size = 273515, upload-time = "2026-10-22T16:05:44.324Z" },
{ url = "https://files.pythonhosted.org/packages/57/d9/b83387780271bf9ea9f96a3aeb01c1c302c4494a927923c4fa70cd170a3f/coverage-7.17.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:337f87e62e1c5f43c8198d879f677c342d152f84d3f157add12558853cbb8eaf", size = 266725, upload-time = "2026-10-22T16:05:45.713Z" },
{ url = "https://files.pythonhosted.org/packages/e4/be/8e80b29764ae3f4d70d44952eee3f3927eb36897490fe799e400adb7f5da/coverage-7.17.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c8a58baa8eb6154c6645eadcc19b6c089deab78c24a612a83206f2a8a5992a7c", size = 269312, upload-time = "2026-10-22T16:05:47.054Z" },
{ url = "https://files.pythonhosted.org/packages/b8/c9/e702f588fe8d83ca844f133270ad0a49bf650c8cf260551ff6f11a6b6bbf/coverage-7.17.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fb5745048ee20f796b0e3ab61831049e749acff188563e2af32f58ede957ac1b", size = 266740, upload-time = "2026-10-22T16:05:48.343Z" },
{ url = "https://files.pythonhosted.org/packages/8a/11/ee13ad14b714d75ef07c490b6e43969574a8b8b8c6d67bc741fcbacbf9c3/coverage-7.17.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:7b5abcae74750302ac4bc77351eaee87c450e4abd3b6225eacde3858e260f8b2", size = 271036, upload-time = "2026-10-22T16:05:49.727Z" },
{ url = "https://files.pythonhosted.org/packages/e2/bc/854e8912c11c54aaf416e74602000589c6efbbe1f251dc90202fdabddcb6/coverage-7.17.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4e153f0bd6509dbf837fd585f489b3328bfd98f2a718a0b15ae63032148b285c", size = 266158, upload-time = "2026-10-22T16:05:51.141Z" },
{ url = "https://files.pythonhosted.org/packages/a8/dd/96432830ddf63bc42654de54eee77a0cd6949cc65769bda06f353323f23a/coverage-7.17.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e42a835271922fe7bf98c7a05c78d4032f4e52bf5b07fafa1eadfdfc2f0ad22f", size = 268088, upload-time = "2026-10-22T16:05:52.92Z" },
{ url = "https://files.pythonhosted.org/packages/ce/ee/230c6ec065a6629d41cc89323ceb54d37e2650c104f70fc108f4ee7c5051/coverage-7.17.0-cp314-cp314t-win32.whl", hash = "sha256:5900055351ec89fdffb34b27898815106529ded29a9496464c1ebd6313fd40fa", size = 228229, upload-time = "2026-10-22T16:05:54.255Z" },
{ url = "https://files.pythonhosted.org/packages/f0/1d/23eb6aadcb3c00764ff2961c0ff55f5dfa2543856d932b949bbc04fa68e9/coverage-7.17.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5ee9c8bd13a59e301dc2aefe7aa8384633406808de051b1982775667094eb673", size = 229154, upload-time = "2026-10-22T16:05:55.682Z" },
{ url = "https://files.pythonhosted.org/packages/3e/f1/7df117e8e016b7a3ee459e833b3456f3610505afefd071f8727702d98439/coverage-7.17.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a9cb9a0fb58e6fff5442969af36094ecd6976f4907f179e33b27e15355d8f74", size = 228726, upload-time = "2026-10-22T16:05:57.179Z" },
{ url = "https://files.pythonhosted.org/packages/9f/a1/f9c24911755f91e6f451ca2e2c1804fbcbe9fecbc3310e341bbbbfa1d85b/coverage-7.17.0-cp315-cp315-android_24_arm64_v8a.whl", hash = "sha256:adabc1079ba844b096799ba8638d8df434aca1613f95bb7d5791af320cf05559", size = 227196, upload-time = "2026-10-22T16:05:58.543Z" },
{ url = "https://files.pythonhosted.org/packages/b0/a5/2b665b40c626e6bb40e5e8e23d91170b48b5f2f57186f46b07f2fac42386/coverage-7.17.0-cp315-cp315-android_24_x86_64.whl", hash = "sha256:6b4593d2871c4c801656dd5861e759f8f5156febd6b6061b02f6a72d64618b7b", size = 227315, upload-time = "2026-10-22T16:06:00.409Z" },
{ url = "https://files.pythonhosted.org/packages/3d/8c/22b518d61b930b8a4d62905d046c76a03675ef19b466462011dfb16439a3/coverage-7.17.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:c2c48a36bb970b7265b196032bfd4237c0b373f2b25ef7012d7e905b5b35c19c", size = 225490, upload-time = "2026-10-22T16:06:01.743Z" },
{ url = "https://files.pythonhosted.org/packages/08/5b/d3b141455944e030cd4987f055f8c84a44a1a0b28d81ae93389f44051691/coverage-7.17.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f8bc2cd4c6aa58be1ca243fb8cddb6c3c42031fe4403991ad9cf2b3baed03d9f", size = 225797, upload-time = "2026-10-22T16:06:03.423Z" },
{ url = "https://files.pythonhosted.org/packages/03/24/aab2156983f7022361352640c583d08f0238e4cd7463eb921172001b4cfe/coverage-7.17.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:cbf0b83ce927de6053279ba61342d9f8892afde1385fcba9144cd05d4023d109", size = 226025, upload-time = "2026-10-22T16:06:04.76Z" },
{ url = "https://files.pythonhosted.org/packages/87/db/94697b9ccf4ec94cdad07bf94e2a21b9702247cb212d2d5f18d2b9c3d0de/coverage-7.17.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:66efc0fb99cee34626fc17d3adac9c3cd816bfdad563bb1e983c3ef432f566d4", size = 226193, upload-time = "2026-10-22T16:06:06.167Z" },
{ url = "https://files.pythonhosted.org/packages/a0/fd/db1c4ade4219831d590fff5aa5b7d9ceaf84586e8e60da6fff71b6182251/coverage-7.17.0-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:58a304efdf2253fb2ea03d45d0a04cc174a92a0cd5279c28c5bb893ce7410968", size = 258046, upload-time = "2026-10-22T16:06:07.51Z" },
{ url = "https://files.pythonhosted.org/packages/95/ee/84ed138990ed752abd73db4adffc1842d6152017d8e21809cc109a413fda/coverage-7.17.0-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aed06129a9f530a948928260d7d2d81c47261355b0127e3c782251b38958463c", size = 261735, upload-time = "2026-10-22T16:06:08.861Z" },
{ url = "https://files.pythonhosted.org/packages/64/ad/c1e4367bbd1e7d562d6bc979c82b542f2040544e95a8cd995b694caf0e1a/coverage-7.17.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6cdb47a9be2d3313829bed1d296086287682a3ee901ff987b3432b45705b771", size = 262819, upload-time = "2026-10-22T16:06:10.49Z" },
{ url = "https://files.pythonhosted.org/packages/b5/3a/e8e8509b0940403eab15d86f70022790a01fce3e971a161919c544953f7a/coverage-7.17.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:804fbaf81d09b34e2f8b1159d01e211a50713d743d32fd9b2ca6cf0ae507a66f", size = 265194, upload-time = "2026-10-22T16:06:11.847Z" },
{ url = "https://files.pythonhosted.org/packages/1f/77/d64bbdbb734b1059ebcde2b5df567f142a9948079e51af9b0abf8c9ec17b/coverage-7.17.0-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b2afebb96da9a1bbca142f8cc12320c14e5c215e0a49cded4f191f5a31506a28", size = 258410, upload-time = "2026-10-22T16:06:13.234Z" },
{ url = "https://files.pythonhosted.org/packages/e2/29/7cf81f08a254532c074eb52cfe6b12bb753d08f2997269c9fac8de359541/coverage-7.17.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:bd04fd23206be9ad3de13b0b39313094fafa66a1f6baf70f5471c004a9fac47d", size = 260697, upload-time = "2026-10-22T16:06:14.595Z" },
{ url = "https://files.pythonhosted.org/packages/7f/56/bacea6dee2d5ccb31ab0cfffcee87ca7efa80ff048840209c8013aeaf02e/coverage-7.17.0-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:5ebfbc87836529a31d25c2dec1262c38eea852ec1c46c0f690a515a7339d9c5b", size = 258649, upload-time = "2026-10-22T16:06:16.265Z" },
{ url = "https://files.pythonhosted.org/packages/a4/97/fd8a6a63740f9ee177c53b59e2f4d0752369bb0fd154086a1e69aa1fb255/coverage-7.17.0-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:a59c3b470ac3b2432af3598a996affbeb10236cd899a005c1c4e517fcff8b592", size = 262989, upload-time = "2026-10-22T16:06:17.85Z" },
{ url = "https://files.pythonhosted.org/packages/c1/37/7cd3d3520d45db3788456452b65446182c08f2a7c276f40c9dd11966be02/coverage-7.17.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:7fef12cec4b96f59e09761cbd4fc9bdf4788062d1ae435b920241fc16c8a9549", size = 257955, upload-time = "2026-10-22T16:06:19.563Z" },
{ url = "https://files.pythonhosted.org/packages/c1/b9/80343a55e07d854927a915cc21a52e350ae1db9ead33f0248507b960785f/coverage-7.17.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:a14e8d69464da9cbe1dc29e7116dbd37296d1d51a695327c478e250349085089", size = 260512, upload-time = "2026-10-22T16:06:21.055Z" },
{ url = "https://files.pythonhosted.org/packages/f3/32/e4c816f0cb3efdc6392bb276ff88359729f9dec605bdd3c1c7a44776f759/coverage-7.17.0-cp315-cp315-win32.whl", hash = "sha256:9a66074231799d8884d267234d49f60c90b43a945dfe069646b72f3058cd4e67", size = 227913, upload-time = "2026-10-22T16:06:22.407Z" },
{ url = "https://files.pythonhosted.org/packages/43/14/283120142ac281834808584249b89957de997c28573ddd65ae106b622121/coverage-7.17.0-cp315-cp315-win_amd64.whl", hash = "sha256:79cf71e5f9a48877ccfed5baa2c72f45af4feeaa596249e23eb15b473134830e", size = 228532, upload-time = "2026-10-22T16:06:23.756Z" },
{ url = "https://files.pythonhosted.org/packages/b9/bc/83dbc6c3bd58042afaefcb0cfd7d7a2e3076989a2f3d9bc6e6edac819784/coverage-7.17.0-cp315-cp315-win_arm64.whl", hash = "sha256:09e563b49c81cd0a33734e926d7a4a6ef1778d74304cde2dcc3aff8b30ea1036", size = 228393, upload-time = "2026-10-22T16:06:25.163Z" },
{ url = "https://files.pythonhosted.org/packages/00/ed/9463d1cb1766a2570343a372c2871c7fb4d561e5051f029399f4d57ce947/coverage-7.17.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:a5e2e0625dfc789af0b2e10acfc106b352f314fffbd19e6392a7961c8cbe5d9a", size = 226481, upload-time = "2026-10-22T16:06:26.563Z" },
{ url = "https://files.pythonhosted.org/packages/ae/59/6536c7fa87be13236b4184fc395f14d18f622a5b8ef7448a6bfdf5f24ec0/coverage-7.17.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:194562ab1eff0d85aee8a17ce4c5c0bc1ebf70a629a7145257123156ad100fd6", size = 226876, upload-time = "2026-10-22T16:06:27.921Z" },
{ url = "https://files.pythonhosted.org/packages/b0/bc/4ffae6038f78382c9ab6a4969cb5fc01d50b083d119851085807629abe88/coverage-7.17.0-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:85682ca89bc4601125dcdff23fa70e6274e13c9ad502fba845e6c274ab2c6569", size = 267241, upload-time = "2026-10-22T16:06:29.356Z" },
{ url = "https://files.pythonhosted.org/packages/c0/60/cae4b708b4217d33ebf66b9c57f8910a257346b3eaf054a78d73605ddc4c/coverage-7.17.0-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0d0fac604a962ff546beb038cba9a5a09d88e6e3f01612fb46f7bd4fe5dc8dca", size = 270079, upload-time = "2026-10-22T16:06:30.736Z" },
{ url = "https://files.pythonhosted.org/packages/a9/84/1225ab04e55a6a2e483e903a5eb0f8f081cb5325df833594534aa288d91c/coverage-7.17.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72edd4f9483ae30cd1ba4f230687840ef7b58a16afb1c9e81131fd6d64655f36", size = 272775, upload-time = "2026-10-22T16:06:32.247Z" },
{ url = "https://files.pythonhosted.org/packages/94/ad/567f8dd9246a553aa8753732c41f0e0654e98e9a617d6282e64eae0e6aa8/coverage-7.17.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:91e487ad07c511da77b574820141a7c2da09bc2a4b4090b5be2424295d100697", size = 274110, upload-time = "2026-10-22T16:06:33.859Z" },
{ url = "https://files.pythonhosted.org/packages/66/5f/e0ee76895df40601e4aedffb6b8e71ea48e276a629fb4488b92cffffca14/coverage-7.17.0-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1cabb31531002fef5197de5df3c84730aef01e750e25154b9813a2583435aba2", size = 267775, upload-time = "2026-10-22T16:06:35.193Z" },
{ url = "https://files.pythonhosted.org/packages/19/6d/d19c6e45e940e5b9ba697b7be9a8e63cef73d90bdea45b670a59df466717/coverage-7.17.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:c9ac886242bbd50fb35a2242ba79555a99891e657da33f98b7ca968e3488a350", size = 270268, upload-time = "2026-10-22T16:06:36.597Z" },
{ url = "https://files.pythonhosted.org/packages/27/a3/b2731bb7aef2db800b330603675c3fa58a6497a05d5b5f8625f3b6285f68/coverage-7.17.0-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:d45cdc495671fc99268ff3653e0da4244972e9fc0e1fc3ea9de18781ad28963a", size = 267298, upload-time = "2026-10-22T16:06:37.973Z" },
{ url = "https://files.pythonhosted.org/packages/50/75/d0c90a81b6b6607933109914dc8b0ef0a5f4cacf9482ca868ae773ee5d0b/coverage-7.17.0-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:12961f738d717a046d4bf3f8d1727eb3ebe2ed5478ff96cd6233d4dfc39323d3", size = 271899, upload-time = "2026-10-22T16:06:39.389Z" },
{ url = "https://files.pythonhosted.org/packages/64/1e/f038f3a2614e9ef839a40a184713aca40b8ed79e626ab5cdc388771045df/coverage-7.17.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:1ee0a982b0e1230b3edf768a7dd022bd2377303763895327ac62deb5efacee03", size = 267380, upload-time = "2026-10-22T16:06:40.97Z" },
{ url = "https://files.pythonhosted.org/packages/c9/30/8d636e639b2d8b1934128573402a7caf66c8cfbc794fa02061d84f115c34/coverage-7.17.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:460679f53893081edb94eeed011d26be37228ba649b853fd2aed7e4394fb1f35", size = 268993, upload-time = "2026-10-22T16:06:42.361Z" },
{ url = "https://files.pythonhosted.org/packages/73/7a/219f6b391cb0636242d06c390a8add5af91afa3e92afd0380dbedd34f487/coverage-7.17.0-cp315-cp315t-win32.whl", hash = "sha256:600e121e412cd6bdb52ec105b95c6a04da0c9710d1534f6e64a42db667907802", size = 228216, upload-time = "2026-10-22T16:06:43.714Z" },
{ url = "https://files.pythonhosted.org/packages/f0/3f/514d294319c5eac11494394545de209079f33993e14526c220eab29b2a96/coverage-7.17.0-cp315-cp315t-win_amd64.whl", hash = "sha256:c3e11b81faafb1f063248735c3e2c5c4183fc1db2c7f46782170dd7760dffcbc", size = 229146, upload-time = "2026-10-22T16:06:45.262Z" },
{ url = "https://files.pythonhosted.org/packages/f6/82/c0d8bb0a67df838fc7bc6f4060ffc404fd1154fd18e624d83e6abc41aeef/coverage-7.17.0-cp315-cp315t-win_arm64.whl", hash = "sha256:d472ee1c64868a917a47f02ffb4ac8c22b477f7589d57a3eb0c58b1ad4f0137c", size = 228716, upload-time = "2026-10-22T16:06:47.097Z" },
{ url = "https://files.pythonhosted.org/packages/c9/17/48032919343afa2906112d458aceda2043afdb24a2ee8eb4c60904d8ddf1/coverage-7.17.0-py3-none-any.whl", hash = "sha256:bac487dce0d2aff28ddf1a533a916f771416eee0e493960f611e49037b878e9d", size = 217383, upload-time = "2026-10-22T16:06:48.471Z" },
]
[[package]]
name = "cross-web"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/4a/a0/bdb8370215987cc5bde497cb8b976b17ab14841566ecef2aab6ae3e6c7b0/cross_web-0.7.0.tar.gz", hash = "sha256:15fbc8b9a824a055db8127fd6e43e0773074f620fdecb6b2b587d3d0a2bdd459", size = 332407, upload-time = "2026-05-19T14:18:48.849Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7a/4a/78b52ec2edcbd9b123638f4e0421fd8699ebe653ebf63ac5f82abd2665bc/cross_web-0.7.0-py3-none-any.whl", hash = "sha256:ddea9be3c68b48eaf16561847a5831a559786949c544b3701432e00a4e8d19d9", size = 25207, upload-time = "2026-05-19T14:18:47.614Z" },
]
[[package]] [[package]]
name = "django" name = "django"
version = "4.2.21" version = "5.2.18"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "asgiref" }, { name = "asgiref" },
{ name = "sqlparse" }, { name = "sqlparse" },
{ name = "tzdata", marker = "sys_platform == 'win32'" }, { name = "tzdata", marker = "sys_platform == 'win32'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/c1/bb/2fad5edc1af2945cb499a2e322ac28e4714fc310bd5201ed1f5a9f73a342/django-4.2.21.tar.gz", hash = "sha256:b54ac28d6aa964fc7c2f7335138a54d78980232011e0cd2231d04eed393dcb0d", size = 10424638, upload-time = "2025-05-07T14:07:07.992Z" } sdist = { url = "https://files.pythonhosted.org/packages/85/fe/79e692b430c7721bf4b812c328bfe687e73e3f28f56611a6a18a97e887cc/django-5.2.18.tar.gz", hash = "sha256:461c5dd06d2ea16bd5ca37d3f46e4def1d6b0fe7588c6f4e2119517bb0af8b2d", size = 10910238, upload-time = "2026-10-06T13:01:09.371Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/4f/aeaa3098da18b625ed672f3da6d1cd94e188d1b2cc27c2c841b2f9666282/django-4.2.21-py3-none-any.whl", hash = "sha256:1d658c7bf5d31c7d0cac1cab58bc1f822df89255080fec81909256c30e6180b3", size = 7993839, upload-time = "2025-05-07T14:07:01.318Z" }, { url = "https://files.pythonhosted.org/packages/d6/01/6568ec52b26548ca1b83d33249f7274db3bf74698f42c0da42ec9db1a24a/django-5.2.18-py3-none-any.whl", hash = "sha256:92ed81d500be6408ecd704d7bd1366c534f30427bffcc63c5fefb129561aec7c", size = 8316071, upload-time = "2026-10-06T13:01:04.413Z" },
] ]
[[package]] [[package]]
name = "django-cors-headers" name = "django-cors-headers"
version = "3.14.0" version = "4.9.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "asgiref" },
{ name = "django" }, { name = "django" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/bc/f4/8b8c3d5e9a0aeea43576fbe623599052a7699abb54378ddb44adb1ef1ed3/django_cors_headers-3.14.0.tar.gz", hash = "sha256:5fbd58a6fb4119d975754b2bc090f35ec160a8373f276612c675b00e8a138739", size = 24892, upload-time = "2023-02-25T07:20:18.41Z" } sdist = { url = "https://files.pythonhosted.org/packages/21/39/55822b15b7ec87410f34cd16ce04065ff390e50f9e29f31d6d116fc80456/django_cors_headers-4.9.0.tar.gz", hash = "sha256:fe5d7cb59fdc2c8c646ce84b727ac2bca8912a247e6e68e1fb507372178e59e8", size = 21458, upload-time = "2025-09-18T10:40:52.326Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/45/f1/972b3a183192841b811e4b7496de0a31c09ae1d83a5391743c8ae0cbd86e/django_cors_headers-3.14.0-py3-none-any.whl", hash = "sha256:684180013cc7277bdd8702b80a3c5a4b3fcae4abb2bf134dceb9f5dfe300228e", size = 13187, upload-time = "2023-02-25T07:20:16.376Z" }, { url = "https://files.pythonhosted.org/packages/30/d8/19ed1e47badf477d17fb177c1c19b5a21da0fd2d9f093f23be3fb86c5fab/django_cors_headers-4.9.0-py3-none-any.whl", hash = "sha256:15c7f20727f90044dcee2216a9fd7303741a864865f0c3657e28b7056f61b449", size = 12809, upload-time = "2025-09-18T10:40:50.843Z" },
] ]
[[package]] [[package]]
name = "django-extensions" name = "django-extensions"
version = "3.2.3" version = "4.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "django" }, { name = "django" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/8a/f1/318684c9466968bf9a9c221663128206e460c1a67f595055be4b284cde8a/django-extensions-3.2.3.tar.gz", hash = "sha256:44d27919d04e23b3f40231c4ab7af4e61ce832ef46d610cc650d53e68328410a", size = 277216, upload-time = "2023-06-05T17:09:01.447Z" } sdist = { url = "https://files.pythonhosted.org/packages/6d/b3/ed0f54ed706ec0b54fd251cc0364a249c6cd6c6ec97f04dc34be5e929eac/django_extensions-4.1.tar.gz", hash = "sha256:7b70a4d28e9b840f44694e3f7feb54f55d495f8b3fa6c5c0e5e12bcb2aa3cdeb", size = 283078, upload-time = "2025-04-11T01:15:39.617Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/7e/ba12b9660642663f5273141018d2bec0a1cae1711f4f6d1093920e157946/django_extensions-3.2.3-py3-none-any.whl", hash = "sha256:9600b7562f79a92cbf1fde6403c04fee314608fefbb595502e34383ae8203401", size = 229868, upload-time = "2023-06-05T17:08:58.197Z" }, { url = "https://files.pythonhosted.org/packages/64/96/d967ca440d6a8e3861120f51985d8e5aec79b9a8bdda16041206adfe7adc/django_extensions-4.1-py3-none-any.whl", hash = "sha256:0699a7af28f2523bf8db309a80278519362cd4b6e1fd0a8cd4bf063e1e023336", size = 232980, upload-time = "2025-04-11T01:15:37.701Z" },
] ]
[[package]] [[package]]
@@ -131,178 +269,261 @@ wheels = [
[[package]] [[package]]
name = "easy-thumbnails" name = "easy-thumbnails"
version = "2.10" version = "2.10.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "django" }, { name = "django" },
{ name = "pillow" }, { name = "pillow" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/86/3b/5fe5c2c20cb08a34759cbd1ca9b8394b121785ebcb033c010a4b27b65a7a/easy_thumbnails-2.10.tar.gz", hash = "sha256:d009462fcd791edf2496e5ed46d044712e1a06b7d4600e4cf2812002e9904ef5", size = 80721, upload-time = "2024-09-12T12:41:00.361Z" } sdist = { url = "https://files.pythonhosted.org/packages/04/2f/71f17c3ce65b25dd8fc8e513738c8496eadbd8bab94f39509a511ff99704/easy_thumbnails-2.10.1.tar.gz", hash = "sha256:a50aa5f99387546c35ab5ba1ea9b3cbbc5658e65601cd34949f62137c32c222e", size = 81446, upload-time = "2025-08-17T20:49:11.308Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/22/ef/de1f3a7421d7a9ce2314ee7bc0d37a8264a50872e2cd5af1240798b0bbba/easy_thumbnails-2.10-py3-none-any.whl", hash = "sha256:9e446fb9da1ca0cbc99bdd1fe8fdd5c38784323d64393a37e16d13cd736980b2", size = 78939, upload-time = "2024-09-12T12:40:59.135Z" }, { url = "https://files.pythonhosted.org/packages/b0/90/be137d0592501ec08d4a00c8430121e95dfe6c8114a1d4436b5cdd6b9803/easy_thumbnails-2.10.1-py3-none-any.whl", hash = "sha256:24462d63dd31543ef1585538b2bfefe0db96d3409bb431c70b81548fb2cfc5be", size = 79695, upload-time = "2025-08-17T20:49:09.119Z" },
]
[[package]]
name = "exceptiongroup"
version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
] ]
[[package]] [[package]]
name = "graphql-core" name = "graphql-core"
version = "3.2.6" version = "3.3.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ sdist = { url = "https://files.pythonhosted.org/packages/fa/90/dfade6d16a55abb45e41b215fcdc940e4f119a6ac7d87430d45d020b659f/graphql_core-3.3.0.tar.gz", hash = "sha256:fd3424e88af3f3211931c6ff96350f1cd9069cf0f1a31b9972899e35d39136b5", size = 726439, upload-time = "2026-09-27T14:50:14.57Z" }
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c4/16/7574029da84834349b60ed71614d66ca3afe46e9bf9c7b9562102acb7d4f/graphql_core-3.2.6.tar.gz", hash = "sha256:c08eec22f9e40f0bd61d805907e3b3b1b9a320bc606e23dc145eebca07c8fbab", size = 505353, upload-time = "2025-01-26T16:36:27.374Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/ae/4f/7297663840621022bc73c22d7d9d80dbc78b4db6297f764b545cd5dd462d/graphql_core-3.2.6-py3-none-any.whl", hash = "sha256:78b016718c161a6fb20a7d97bbf107f331cd1afe53e45566c59f776ed7f0b45f", size = 203416, upload-time = "2025-01-26T16:36:24.868Z" }, { url = "https://files.pythonhosted.org/packages/0c/13/03fb01b3581134cc30d7dd3fb8a9c429267574ace881a9e72c2f57896ee9/graphql_core-3.3.0-py3-none-any.whl", hash = "sha256:d37fac6ef4dfc3eaa5daa59dcb498d7cbb118439d240993c68fddc4cb1bade44", size = 347906, upload-time = "2026-09-27T14:50:12.905Z" },
] ]
[[package]] [[package]]
name = "gunicorn" name = "gunicorn"
version = "20.1.0" version = "26.2.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ sdist = { url = "https://files.pythonhosted.org/packages/d9/8a/e4ef6ee11701b6cd64702848415ffb69eeff85cb388a3c6c7fe86f22f3f8/gunicorn-26.2.0.tar.gz", hash = "sha256:62b864895d9ebff0b2f9867ba04fe811c93121596540830c9c916d0769668447", size = 787921, upload-time = "2026-08-24T15:05:59.3Z" }
{ name = "setuptools" },
]
sdist = { url = "https://files.pythonhosted.org/packages/28/5b/0d1f0296485a6af03366604142ea8f19f0833894db3512a40ed07b2a56dd/gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8", size = 370601, upload-time = "2021-03-27T01:54:37.202Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/e4/dd/5b190393e6066286773a67dfcc2f9492058e9b57c4867a95f1ba5caf0a83/gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", size = 79531, upload-time = "2021-04-27T12:16:23.375Z" }, { url = "https://files.pythonhosted.org/packages/fe/85/7522a52e5e2f42faf1a129113ab63e548c42e103e9af395b7bfe65e403e2/gunicorn-26.2.0-py3-none-any.whl", hash = "sha256:bd249d0b3f7972f7432f0a6b6ff3b3ee2d129f70cd1ff6c09a9dd9e29a2b88e3", size = 228389, upload-time = "2026-08-24T15:05:57.67Z" },
] ]
[[package]] [[package]]
name = "iniconfig" name = "iniconfig"
version = "2.1.0" version = "2.3.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } sdist = { url = "https://files.pythonhosted.org/packages/01/e1/2069291243c926a2ff1cd706c7f3eeb9b62144bf60f77c9fb9ff2fb26bd3/iniconfig-2.3.1.tar.gz", hash = "sha256:67f4b9c50da0dedf52af349e7749a80a9057a5031199791b906c3bb3ae878960", size = 21209, upload-time = "2026-10-06T22:48:38.076Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, { url = "https://files.pythonhosted.org/packages/56/43/4ca9e49d27a1fcf6bece6f6aec0ea46bb9112489b93d4b688fb415457bdb/iniconfig-2.3.1-py3-none-any.whl", hash = "sha256:9121e2c1fdb355232495be3194c8dfe87ccc2d5dee45947b78e68f499790d7a7", size = 7552, upload-time = "2026-10-06T22:48:36.959Z" },
] ]
[[package]] [[package]]
name = "mysqlclient" name = "mysqlclient"
version = "2.2.7" version = "2.3.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/61/68/810093cb579daae426794bbd9d88aa830fae296e85172d18cb0f0e5dd4bc/mysqlclient-2.2.7.tar.gz", hash = "sha256:24ae22b59416d5fcce7e99c9d37548350b4565baac82f95e149cac6ce4163845", size = 91383, upload-time = "2025-01-10T12:06:00.763Z" } sdist = { url = "https://files.pythonhosted.org/packages/ef/8f/b9488795d21a76c1520905feba5afb6233f16510797a28b51f2b2688c93e/mysqlclient-2.3.0.tar.gz", hash = "sha256:bea8294964266f6486f1ca514ccfcdbc54d4fe0d32882b38c1d4594df870be8b", size = 102185, upload-time = "2026-09-14T15:38:30.784Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/e0/524b0777524e0d410f71987f556dd6a0e3273fdb06cd6e91e96afade7220/mysqlclient-2.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:199dab53a224357dd0cb4d78ca0e54018f9cee9bf9ec68d72db50e0a23569076", size = 207857, upload-time = "2025-01-10T11:56:33.666Z" }, { url = "https://files.pythonhosted.org/packages/bd/9a/49c257f71e187f37ec24cc4a7c071b1788a819054ee3b3133332c074b2cb/mysqlclient-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6a691aab1a6d22fb04aa08977ced9ecf0c1c781d0d985f7814972c3626db9390", size = 217316, upload-time = "2026-09-14T15:38:15.847Z" },
{ url = "https://files.pythonhosted.org/packages/20/40/b5d03494c1caa8f4da171db41d8d9d5b0d8959f893761597d97420083362/mysqlclient-2.2.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:977e35244fe6ef44124e9a1c2d1554728a7b76695598e4b92b37dc2130503069", size = 207965, upload-time = "2025-01-10T11:56:37.252Z" }, { url = "https://files.pythonhosted.org/packages/87/91/cdf42e08f3188ec4efb5e89883afb78cda3db50cd68616bce5814a27b769/mysqlclient-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:8b3a5ebc0a2983bc252116dd132ab4c746085fa727b5c98c09edcda09dc09d0f", size = 208447, upload-time = "2026-09-14T15:38:16.867Z" },
{ url = "https://files.pythonhosted.org/packages/61/48/5474ffb47d1f27600737ae6b4860fc4bbc17b7e7f1dec9b8b765a75d66e5/mysqlclient-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:effb81eb6d1f1df6c1d95f63f117bec5007e9c64487e9fa17d5696f2a5338358", size = 217531, upload-time = "2026-09-14T15:38:17.871Z" },
{ url = "https://files.pythonhosted.org/packages/a3/37/9205f687a60196e77d9bc4dd94fdb3c88a24b5cb920fdaf1acf363791bdd/mysqlclient-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:7c3c4b3edcc7dc50d23fb5634e0e82bce8ab7de5b83d03fddc1c9979c18607a4", size = 208548, upload-time = "2026-09-14T15:38:18.884Z" },
{ url = "https://files.pythonhosted.org/packages/1d/12/cf11b4df3ee7ca9957487a5e4c99f33611119b086b1d94e14a2ceb21797e/mysqlclient-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:60365cce6765b94eeb621aa0f9505044ec9b4ea191cd68500f0a35b2d6d2758b", size = 217521, upload-time = "2026-09-14T15:38:19.87Z" },
{ url = "https://files.pythonhosted.org/packages/6c/ed/4ef9c56dd78a5b6f4ede1574629e532e3f2b97bdd764a6d172fb7ed20fc4/mysqlclient-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:a6beb9ca67a9224ff4b45f7f9118f0932038fa38d59ca2e38ae35b5225984d7d", size = 208546, upload-time = "2026-09-14T15:38:21.073Z" },
{ url = "https://files.pythonhosted.org/packages/6e/5f/f62d1263a942e97ca34ea8bf151c13a638fea7a5a544aa9db3d6dad7671d/mysqlclient-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:5d4c53eb9c5625dd68b6fed32c8cf00ba19cd1c3073645dec309a9d16bd029e2", size = 222543, upload-time = "2026-09-14T15:38:22.036Z" },
{ url = "https://files.pythonhosted.org/packages/9d/e5/1de3e1fc27009a6da30a52167e98f52ab4277d089eaff7152f6156745af9/mysqlclient-2.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:cfe14103280d5a4968fe8a3ace2a8939ef68a1d881aec9872d06746106b49f7f", size = 213120, upload-time = "2026-09-14T15:38:23.063Z" },
{ url = "https://files.pythonhosted.org/packages/b3/ea/d7ebc53af9d3341e5c049795ec5e88946d83cbd26ee468475ae0c3b21d7f/mysqlclient-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:673891700dafbc66a6a8df12059b53a65905ec6e8dec615392fb984299880c0d", size = 222969, upload-time = "2026-09-14T15:38:24.168Z" },
{ url = "https://files.pythonhosted.org/packages/4b/42/9ff798c066e33df9f7b58822a3eaf26185c3cb7e61a8186a56243b006066/mysqlclient-2.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:fe27c63ba9088b28467bf276f93f0b4a9062beabaeeb9692593e774d1146cce2", size = 213680, upload-time = "2026-09-14T15:38:25.259Z" },
{ url = "https://files.pythonhosted.org/packages/d2/0f/29185111b7c0dd264e910c1250f2fd9a452ebad7fb272e750d8ffb18e67c/mysqlclient-2.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:3c601984c286c51080e0d3e9a857bc014713e6338b5f9c0a5df453a341b14022", size = 222537, upload-time = "2026-09-14T15:38:26.308Z" },
{ url = "https://files.pythonhosted.org/packages/32/7b/4a050453adb5d07ee5f979f17409f93be545baae786dd58698f82b08a3e1/mysqlclient-2.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:3d39527a5525b4ebff99721d063f4fe9e459b9e437c7b7698374966d92d4355d", size = 213112, upload-time = "2026-09-14T15:38:27.329Z" },
{ url = "https://files.pythonhosted.org/packages/a2/13/a046e9df6d69778b7de66f3d2ad83e563045992959efafb7b2ef62af0560/mysqlclient-2.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:24164ba592065ae5ff0149bb5707d05772335935059474fb75d864e5d0f94d63", size = 222969, upload-time = "2026-09-14T15:38:28.468Z" },
{ url = "https://files.pythonhosted.org/packages/f8/13/cf40c2957bebe95fa109feb8a28fe0871ba4bd5e37c77b6128114ec20712/mysqlclient-2.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:f1ec49f73dad7df8f2da4d9de0f875f03c8bd44fbe77878522204c0646822f63", size = 213723, upload-time = "2026-09-14T15:38:29.69Z" },
] ]
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "25.0" version = "26.3"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
] ]
[[package]] [[package]]
name = "pillow" name = "pillow"
version = "9.5.0" version = "13.0.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/00/d5/4903f310765e0ff2b8e91ffe55031ac6af77d982f0156061e20a4d1a8b2d/Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1", size = 50488147, upload-time = "2023-04-01T09:31:37.159Z" } sdist = { url = "https://files.pythonhosted.org/packages/ce/a4/9cc30690a394a8305e8be4c9036aa16d0dfddadd09079860b582f562496d/pillow-13.0.0.tar.gz", hash = "sha256:cdcd1ba258178f7356d74170c9a81ba4eab6a5d55748bc402a5a2a6fe6be3510", size = 47058631, upload-time = "2026-10-16T22:18:09.718Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/64/46/672289c0ff87733fb93854dedf3a8d65642a25c0bfc88e7f6d722f9161a5/Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66", size = 3395750, upload-time = "2023-04-01T09:30:10.542Z" }, { url = "https://files.pythonhosted.org/packages/6c/d7/af6af1b8cc55e07cb0489d5b0950bf5a9bb1461eeba825700b6d8d0e26ea/pillow-13.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:6f38f3f885a09cafc93fd667b1173a92f379e43785d635e7983c893f0da319fb", size = 5592324, upload-time = "2026-10-16T22:15:24.672Z" },
{ url = "https://files.pythonhosted.org/packages/a9/70/9259e93534d01f846f7d0501f19bb7d8cc1751741bc20826fc8d3a20fe32/Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e", size = 3077133, upload-time = "2023-04-01T09:30:13.386Z" }, { url = "https://files.pythonhosted.org/packages/0b/d2/2c12cb4fbb5d95f38d1001ff3d95fafcb830077018cba40e56a8f932dfbc/pillow-13.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37290fbef0c73e2d51d594eb78fded7dc7170d1c3ad0a890269e6fd8c8c4e061", size = 4856298, upload-time = "2026-10-16T22:15:27.669Z" },
{ url = "https://files.pythonhosted.org/packages/95/62/8a943681db5f6588498ed86aa1568dd31c63f6afdabe50841589fc662c68/Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115", size = 3112534, upload-time = "2023-04-01T09:30:15.616Z" }, { url = "https://files.pythonhosted.org/packages/5c/ee/229b27472483a4801ea94990815e710391836afdf987b4b722b62bd75db6/pillow-13.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c88c10dd432f3b39ccdafc9ed81d0c21b8fd634502574da76d1bfa4fe2c76d0", size = 6517304, upload-time = "2026-10-16T22:15:29.43Z" },
{ url = "https://files.pythonhosted.org/packages/f2/43/0892913d499c8df2c88dee69d59e77de19e0c51754a9be82023880641c09/Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3", size = 3386725, upload-time = "2023-04-01T09:30:19.221Z" }, { url = "https://files.pythonhosted.org/packages/36/46/f509b74787f40ce0627501089d630b2c741b63b244c1f40b640b3b01e181/pillow-13.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a625df9a2a5597fa5e5266e01aefc82604bb85d291e3547981e863560d75da6c", size = 7140667, upload-time = "2026-10-16T22:15:31.368Z" },
{ url = "https://files.pythonhosted.org/packages/ff/fc/48a51c0fe2a00d5def57b9981a1e0f8339b516351da7a51500383d833bc8/Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef", size = 3308605, upload-time = "2023-04-01T09:30:29.279Z" }, { url = "https://files.pythonhosted.org/packages/aa/a4/5a398b8ec2c37145397d6461c0b551c23ec03072aa64f357aed25e582b00/pillow-13.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:10b5019883c98f1dd819ca48ffc6f33a91cee04948fe637c605f4345c7cafd6f", size = 6610583, upload-time = "2026-10-16T22:15:34.025Z" },
{ url = "https://files.pythonhosted.org/packages/af/b7/f9faf80e3c93b02712c5748f10c75a8948e74eca61ec2408f7e1d4c9dd16/Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705", size = 3164057, upload-time = "2023-04-01T09:30:32.484Z" }, { url = "https://files.pythonhosted.org/packages/4f/7b/f2f1109b230fd4e2d57ab2305de31adc37ea9b4ff8719436c623cb484415/pillow-13.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f391dc92a015c478745192fe4f2e120c2af2aba18d9fe0616341cbf537599806", size = 7218241, upload-time = "2026-10-16T22:15:36.214Z" },
{ url = "https://files.pythonhosted.org/packages/3b/2b/57915b8af178e2c20bfd403ffed4521947881f9dbbfbaba48210dc59b9d7/Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1", size = 3415613, upload-time = "2023-04-01T09:30:34.978Z" }, { url = "https://files.pythonhosted.org/packages/ab/d3/069edb9083b267746baf59756282cacef904d3475c5288c7216fb5195a98/pillow-13.0.0-cp311-cp311-win32.whl", hash = "sha256:6a0920cb55df5a4d3f6862964908078c0bd59332bdd1bcd20d14e8c70ef27cc5", size = 6515278, upload-time = "2026-10-16T22:15:38.129Z" },
{ url = "https://files.pythonhosted.org/packages/e7/2a/f3ed578595f8486ee2cc07434460097d89aedd406a3db849b890ca8ec416/Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a", size = 3350667, upload-time = "2023-04-01T09:30:37.849Z" }, { url = "https://files.pythonhosted.org/packages/7d/f6/6e2d96413e4c9dbec12ea584d9e646d602877d4cce3ea669099f959deac6/pillow-13.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:d7710eb676e9bb68965cb045be0d333b4c0d7f9dfbaa7fbf121385b1588683d3", size = 7278471, upload-time = "2026-10-16T22:15:40.286Z" },
{ url = "https://files.pythonhosted.org/packages/28/a2/f2d0d584d45100a5419fd70a1233ade8f12469ffe6e8e3acd40364beaadb/Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865", size = 3414552, upload-time = "2023-04-01T09:30:48.682Z" }, { url = "https://files.pythonhosted.org/packages/70/e7/baff21f8cb60496c8c250609501b88919eeedb4088d3da106f6c62f3f3c0/pillow-13.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:12fbb851cf323cc19aac3eea1df5ad17b57e91e9fe3cfd06100dba7bb8208c13", size = 2698981, upload-time = "2026-10-16T22:15:42.637Z" },
{ url = "https://files.pythonhosted.org/packages/51/3a/a6701b987007aaa43559b7d8510629845b25686f09a0eb29f8946a62d767/Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964", size = 2229361, upload-time = "2023-04-01T09:30:51.739Z" }, { url = "https://files.pythonhosted.org/packages/7a/b8/58d79332229482eb0d27c82973850808c19d37e5bdc14443d3417bd373f5/pillow-13.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:67c8d44ad89fa7eb88de5d6ef6199b1e03dd24974f85a67639fc8badb801fe79", size = 5568643, upload-time = "2026-10-16T22:15:44.474Z" },
{ url = "https://files.pythonhosted.org/packages/69/72/48cc52bff8731cf72bc4101e34dc44807a410c171f921afb582a511da50e/Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d", size = 2538580, upload-time = "2023-04-01T09:30:54.345Z" }, { url = "https://files.pythonhosted.org/packages/39/4b/f2ed53d83aa44ac19599ac0a54897f867fafb95f406980d1e24083f8cbe3/pillow-13.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc481f742ccda20e9ed760b8f978dff50f2a4e9bc253ac167fb0a05b0783ef52", size = 4852329, upload-time = "2026-10-16T22:15:46.284Z" },
{ url = "https://files.pythonhosted.org/packages/24/35/92032a00f41bea9bf93f19d48f15daac27d1365c0038fe22dc4e7fc7c8b0/Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572", size = 3349772, upload-time = "2023-04-01T09:31:13.438Z" }, { url = "https://files.pythonhosted.org/packages/3b/bb/9e30e8ed18c20800ee327a12107d5249ad8dc444867b3adfcc21654e3c71/pillow-13.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8bf295fe459343bc6093c8e5e0df60bfa189e0fcb775a22e2c2488fdeccc6c0d", size = 6519309, upload-time = "2026-10-16T22:15:48.14Z" },
{ url = "https://files.pythonhosted.org/packages/50/ce/d39869c22904558ce32e664904cf72f13a9d47703b72392e881d9e7b6082/Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe", size = 3281583, upload-time = "2023-04-01T09:31:16.7Z" }, { url = "https://files.pythonhosted.org/packages/e6/99/fdf65cc539e266ab93546c261bdf98411fe165c6e4e13f9dbf597097192b/pillow-13.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42d63be3af4f9b951b199b0a9d23365d37ab14687e6d77b74940432c8515c4ca", size = 7144542, upload-time = "2026-10-16T22:15:50.093Z" },
{ url = "https://files.pythonhosted.org/packages/7a/75/4a382d1567efc6f4e3054f693167f8ce2d1ad939c5f6f12aa5c50f74b997/Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1", size = 3222603, upload-time = "2023-04-01T09:31:19.548Z" }, { url = "https://files.pythonhosted.org/packages/26/9b/f38b7c4d8f599537e2d1a6d546cec9beef7536ff8f5c49a3f9c78179d8ac/pillow-13.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bd024d567eca931b668978d672dba914d4b8e2628e2c3073a2ad178de8a2620", size = 6618042, upload-time = "2026-10-16T22:15:52.054Z" },
{ url = "https://files.pythonhosted.org/packages/51/d2/c10f72c44e000d08e41f822083cf322bb59afa7ed01ae7e3e47875b47600/Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7", size = 3298174, upload-time = "2023-04-01T09:31:23.005Z" }, { url = "https://files.pythonhosted.org/packages/a8/7c/45ee28531f89ee4582967d8a17f7a26d71ca64cd902b93c269590cd60a00/pillow-13.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:23edf4ec4b6f4f240e8df6e4985e9ee2ced86474632433e55fb1e2548744bf40", size = 7228137, upload-time = "2026-10-16T22:15:54.009Z" },
{ url = "https://files.pythonhosted.org/packages/02/4a/d362f7f44f1e5801c6726f0eaaeaf869d0d43c554b717072b2c5540cefb4/Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799", size = 2538628, upload-time = "2023-04-01T09:31:26.082Z" }, { url = "https://files.pythonhosted.org/packages/01/bc/9c2a969cb1b70201d04fa17fd5aedf02a732870b8da86bceaaa30b0ca385/pillow-13.0.0-cp312-cp312-win32.whl", hash = "sha256:02464da86f561f410e13b061ec37c3068a9cb557a095494157742d07b3c24a27", size = 6515423, upload-time = "2026-10-16T22:15:56.25Z" },
{ url = "https://files.pythonhosted.org/packages/ff/cd/73a79ecdc822076002eb24b5e1e4a530b0bb5310fcb6d239abbec877110b/pillow-13.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:ff3fe9eb621fbb9db4c4be33ab946f6bac5f98ab50ca8d3260503653656c961b", size = 7272166, upload-time = "2026-10-16T22:15:58.597Z" },
{ url = "https://files.pythonhosted.org/packages/4a/a3/be5a1236efd085b3984e4305da8f179ae9dab80c991d2ad78f2bd5c4c843/pillow-13.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:c3bc8ee0271387e0b1e3ad0ff893645990a656e79bf9c42f4f734a3642b11d51", size = 2698943, upload-time = "2026-10-16T22:16:00.637Z" },
{ url = "https://files.pythonhosted.org/packages/82/e2/9132d38df320e8099e2642d9fa538f18a7dfebf68a875a31725dc2a115aa/pillow-13.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e98b4f91f99b24b1da72e10621ba181f7281922384a7403094debb4199de6c00", size = 4190620, upload-time = "2026-10-16T22:16:02.527Z" },
{ url = "https://files.pythonhosted.org/packages/cb/be/436dac0996876f65ef1916ffb711c5ef4af2ebc57414b6f16642e07f6b47/pillow-13.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:34fb3db1149c879343861831728c243c432d4a5afc8f1878ca7612ef8de2a033", size = 4290286, upload-time = "2026-10-16T22:16:04.861Z" },
{ url = "https://files.pythonhosted.org/packages/60/16/7c1007162e4169ea0d5bec47e2e0b2174f56c50cf5b8c02a1dc23e41037e/pillow-13.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:2d50500a4a3bc6469adcdb319cf05d688b08ca3bf0f22a05339f3b25b433a599", size = 3723169, upload-time = "2026-10-16T22:16:06.956Z" },
{ url = "https://files.pythonhosted.org/packages/08/bc/578ab2eaa04705a8978bd9be0fe15c3d43a8fc95097c4f7e39540e79e4fa/pillow-13.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ef5fe6b7db3f501c56000ba8fe7c7e1a35ca1d45824fbeb95a67daa85cff4dbe", size = 5568469, upload-time = "2026-10-16T22:16:08.664Z" },
{ url = "https://files.pythonhosted.org/packages/98/c8/ccede3dcc4f274b80fb49199eed1ef34ba05566c8b71976b9d1f111da2e3/pillow-13.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f11a3c1358bb3c68012a7b1f41b223c3db9f7bfde90b481df028cfe1c3363313", size = 4852187, upload-time = "2026-10-16T22:16:10.693Z" },
{ url = "https://files.pythonhosted.org/packages/fb/3c/cd0770e96d20bfd990db024bcbd43897ff3e88da1f4782cf90ea231b1d4f/pillow-13.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19b66ca0a4d25cc0200b381ce824c7ea090db61427315e73cfb05dbf1b35c369", size = 6515908, upload-time = "2026-10-16T22:16:12.896Z" },
{ url = "https://files.pythonhosted.org/packages/ef/fe/f5cdaa2d1c4086e757430f368c271a58ee29cfe7378e7ae7b9d964015c25/pillow-13.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5f56499c1a74d2b862beb0c33b8016f99d095858455df9d21583bbbc1c15b582", size = 7142544, upload-time = "2026-10-16T22:16:14.844Z" },
{ url = "https://files.pythonhosted.org/packages/fb/b5/a54f6d44adbcd23737c8c12953f42be107e4cca64e247c0ca3a5b563061c/pillow-13.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a2a14cd73d5e6c927f644b0110ee81f59c4ef506259ec9183904747f2caacc81", size = 6613097, upload-time = "2026-10-16T22:16:16.893Z" },
{ url = "https://files.pythonhosted.org/packages/e6/b4/9e6c1207c21b2e91db0d15c16d30f3701ce5add1bd4da0e787761e66cb6e/pillow-13.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:08b826a9bf1265c76de04a42061d61ea6b109418b7826f28ff1ecdfaa26c5502", size = 7224846, upload-time = "2026-10-16T22:16:19.195Z" },
{ url = "https://files.pythonhosted.org/packages/78/9f/d3c3cb0518f3dd1eee0ef501535f53d15597a53c5c4118d178086143397a/pillow-13.0.0-cp313-cp313-win32.whl", hash = "sha256:12b0fa15879fe5a9d9eebe66e4e2d74128a773b1a4448010c9c714f45e04b9d8", size = 6516013, upload-time = "2026-10-16T22:16:21.389Z" },
{ url = "https://files.pythonhosted.org/packages/d7/ed/1b16fb66a37e1b9e41370b93b2c614c7da761a5b2a41dc333f7af4639962/pillow-13.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:9dda0788a9932776a75d8bcc020076edc308ce2508b51f6739a45e3158b3fc42", size = 7284777, upload-time = "2026-10-16T22:16:23.393Z" },
{ url = "https://files.pythonhosted.org/packages/87/7d/8d02eba9f7ecc87f8e90f731e032cc4773802f2a843763afd0c66a535ff4/pillow-13.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:40a266371ece581e5de13b6635ef78d7487824af80561b6ca27694a88c19bc0f", size = 2698826, upload-time = "2026-10-16T22:16:25.382Z" },
{ url = "https://files.pythonhosted.org/packages/8e/45/fbad9c7f5e2f1745c373a434d46072bc84eddfefaa28b852425ac957fda6/pillow-13.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:44629d4498c9cb6b2051806155f22141df0d8a929c56134b166655232dfb2f8c", size = 4190721, upload-time = "2026-10-16T22:16:27.524Z" },
{ url = "https://files.pythonhosted.org/packages/a2/bf/2074902a259a90692c5dbf132885d21671644cbbd3f690047516243a55db/pillow-13.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:bf97ba1b5542050e9b0c74e632323b6d8136cc99d64e193abebee22fe6013c76", size = 4290236, upload-time = "2026-10-16T22:16:29.562Z" },
{ url = "https://files.pythonhosted.org/packages/ff/a5/3d1bb93b8ea38d15d21356c1b8e9f50971ac592aaa248f7fb71739094c0e/pillow-13.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:aa694c49787040943570d61c9b8fc01271bd2a2cfdd55f0d896f8aecdce1a981", size = 3723093, upload-time = "2026-10-16T22:16:31.249Z" },
{ url = "https://files.pythonhosted.org/packages/84/a1/be060135d85187b25fa8b2573786c0e45c62ae27527fff5257675eb97f4a/pillow-13.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:49c12af515073e9bf511e4ef736743bf70d531a643668722279314b937742ba4", size = 5572253, upload-time = "2026-10-16T22:16:33.103Z" },
{ url = "https://files.pythonhosted.org/packages/b5/f4/0bf6f1ac9ba7ee2ffe9914aeb410a1035a0c1a3c42f122388d7fc950bc4b/pillow-13.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d9b0a2bee3f3e03e9f29057aedb807dbf1e1d96ee1b9d0f422d9db6c8583271d", size = 4852269, upload-time = "2026-10-16T22:16:35.102Z" },
{ url = "https://files.pythonhosted.org/packages/83/16/b603274617b3e6728da3a0133d4fba1ea8d7fbe0c4ea5fef9cbffa398b8b/pillow-13.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5933fcea5a5ba64c686f55c886a115cbfd92f2900460784a1e79909fdbafd7b6", size = 6516058, upload-time = "2026-10-16T22:16:37.228Z" },
{ url = "https://files.pythonhosted.org/packages/1d/f2/1371707ce438ba9a43f9d24089828494ec6f9396e28cd7903aa704dd6306/pillow-13.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e91c2b1cbeae768c98752e67e717e283d68dae45717b41f2d91eaee648e37b7d", size = 7142574, upload-time = "2026-10-16T22:16:39.172Z" },
{ url = "https://files.pythonhosted.org/packages/b9/a9/62cf68587f910bc87c5951bcb6e9430c7643a65076adf5f18b4bf350e2bd/pillow-13.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f6299e494e46253d40c34653cac369412f44ca53b5f85d848d3f95b1ed0546c", size = 6614295, upload-time = "2026-10-16T22:16:41.117Z" },
{ url = "https://files.pythonhosted.org/packages/6c/c9/9e9b48178649730299be0bafbf5f810a5b6977538f5a92d47e4ba5f5d608/pillow-13.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f9bd7d260d986f3553518c5d723544346fa48031f8aef3265d786aeb59845bc0", size = 7224841, upload-time = "2026-10-16T22:16:43.118Z" },
{ url = "https://files.pythonhosted.org/packages/8f/48/c44e91384b1b6c072a714a63d00608acc16a6a968735e478e4be4384e5ac/pillow-13.0.0-cp314-cp314-win32.whl", hash = "sha256:2e31c4e68cffd7b1f116127626813b82fcba19edf6b940a7b0c266d6484d0055", size = 6508694, upload-time = "2026-10-16T22:16:45.883Z" },
{ url = "https://files.pythonhosted.org/packages/50/e2/7ad17747517e4c813dca78257d6bb593466f8231baf1384bc98d92745edb/pillow-13.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:f7cbb7a64e7ec664775195e4f81923bc8a93d9451b963f844e59118f63147a7a", size = 7281830, upload-time = "2026-10-16T22:16:47.725Z" },
{ url = "https://files.pythonhosted.org/packages/41/9f/e5b51f2ce8ef4563215334035403ce61a244f93a2e65c24a940131618f87/pillow-13.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:a6aaa7871aebca081eb5c45f83e845bbd9712ae99fa9286dbfdcee835e63f4b4", size = 2695468, upload-time = "2026-10-16T22:16:49.515Z" },
{ url = "https://files.pythonhosted.org/packages/97/73/a53502f731f6c84805a4ce87a8881e86d07a65aee64728b900d112dc703e/pillow-13.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7b999816b5850a35ada08443e6e03945f1f6488144a83afe2d37b240a552aafa", size = 5578196, upload-time = "2026-10-16T22:16:51.291Z" },
{ url = "https://files.pythonhosted.org/packages/21/50/7e949959a56b5559c78ef172b70fc6025706acb289f3f648a15329e4fa6f/pillow-13.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:67871a13cf47275201553b536c025da777f997fa562a19eb798af4ad4b97c18f", size = 4857276, upload-time = "2026-10-16T22:16:53.399Z" },
{ url = "https://files.pythonhosted.org/packages/c9/52/43ded0ac08e77e39d17bd6d894162572201b5e8fdc4a022b14c55a1960a4/pillow-13.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e8ca8f69b7c77fa96dad99f8638c1fc4f1c0bed4acaa1baf7a3c31bdb80a0ab1", size = 6607008, upload-time = "2026-10-16T22:16:55.525Z" },
{ url = "https://files.pythonhosted.org/packages/0d/2e/4a57ed216d5bf877387cba4d44c02aa87abe51ad2e06404aa6b597c4da39/pillow-13.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bce6a29c3898191f4c9998e4d4e8f69790caeabde46e54da4bc235ce5bde683", size = 7224281, upload-time = "2026-10-16T22:16:57.738Z" },
{ url = "https://files.pythonhosted.org/packages/5c/3d/2929b248e135752d63aaeabf53f6de797e905b7b6263f9c1e2adade20aba/pillow-13.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:816752a5ef76a80484c8c441c85593b7ea9304f6231fc5776ad1fbff7e773058", size = 6695600, upload-time = "2026-10-16T22:16:59.842Z" },
{ url = "https://files.pythonhosted.org/packages/bc/33/fb0da992cf084ed1ee6ee05e96b6191e35c351eb707ca2df874feb24ce47/pillow-13.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5bdb38ba220b61b4ec31dd1046dea6b016d3c6850a23f43ed8b58332b2fc668e", size = 7300589, upload-time = "2026-10-16T22:17:01.686Z" },
{ url = "https://files.pythonhosted.org/packages/bb/af/c30dd9ef6a3dce84ecb1b2393cac629047a32e66279056c6cb46c3ffd65c/pillow-13.0.0-cp314-cp314t-win32.whl", hash = "sha256:06903aac074d00c988e77a1fc435286dc498506d4ca8d6f69df9babddf4ecc15", size = 6517204, upload-time = "2026-10-16T22:17:03.722Z" },
{ url = "https://files.pythonhosted.org/packages/85/d5/ef3a8ae7609c8ab26570abf18194882534b480d245a9148b895a2262c004/pillow-13.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9a635068d9793c7f0d929f39f18b58f1bd86c236e8e7b64167b86af9232ebca0", size = 7286695, upload-time = "2026-10-16T22:17:05.543Z" },
{ url = "https://files.pythonhosted.org/packages/84/3c/0861db0c69a03689ebe3e7599d35668a414deb5aaafd8255215e4e62e3d3/pillow-13.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0d1a9cf7bc6917538b20f8d06b9ab2147e542f68849e96fc58eb66bdf6acc692", size = 2698098, upload-time = "2026-10-16T22:17:07.674Z" },
{ url = "https://files.pythonhosted.org/packages/6e/eb/e86a41962a3a31ad495a3d6a912d9002b01bba4a6fa71c28703abdca7f69/pillow-13.0.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7d5e097eabd5d4915c27ac9f6e9325bb1e01a7c94d7348b5c4451d3fc30e8521", size = 4191213, upload-time = "2026-10-16T22:17:09.449Z" },
{ url = "https://files.pythonhosted.org/packages/dd/04/7e7445e706afec84ef06dc25b2c77f9cecff242c23730c62928ad1089d4a/pillow-13.0.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:51ebb529fc5e8abdd55302f6c3f8943037348ec2384329a3e55677f69ae2fdfc", size = 4290650, upload-time = "2026-10-16T22:17:11.196Z" },
{ url = "https://files.pythonhosted.org/packages/46/85/d534089068ac40c0f61f650389a296c812eab745a8a733f81feec39ae955/pillow-13.0.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:c87a04e7f152ee4db5dbe15f0bfa3fe628a1aeedbe66b81c43b87f52c813f9ac", size = 3723078, upload-time = "2026-10-16T22:17:12.772Z" },
{ url = "https://files.pythonhosted.org/packages/ce/2b/00b5c3b401df538b5dbe311d3899730fae5ba1e21cb8b80a128887ac6747/pillow-13.0.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:20722cd5375533ad754053363a80b8fb30c79d37930a6f7228030e237ce3bd5d", size = 5572292, upload-time = "2026-10-16T22:17:14.539Z" },
{ url = "https://files.pythonhosted.org/packages/72/f5/a6b0610f946e9b54ae888ba7ad29d6e2f8d9b11f69e212d70416b7186c09/pillow-13.0.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:04a63720f79e22fd54d897c233a68314c50ca0603360f54ce2bd5db8f0f43782", size = 4852222, upload-time = "2026-10-16T22:17:16.521Z" },
{ url = "https://files.pythonhosted.org/packages/1e/d0/c0ff25bb0427ec70f32aac995453321fc3c4494a46c7292ff02d9d260b04/pillow-13.0.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f8db54177035a2b5f717f94475b74907e12a96dc74f6d487f3966b0a4694071", size = 6527349, upload-time = "2026-10-16T22:17:18.534Z" },
{ url = "https://files.pythonhosted.org/packages/85/11/e46fa0bcc0fc117f1eacc206b3d51f04d583fab328b4b740672d0438d83f/pillow-13.0.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f36aa77f74b14a2103622b196402c71330e4e7cf4122ee45c854e9a01e0eecf", size = 7152873, upload-time = "2026-10-16T22:17:20.415Z" },
{ url = "https://files.pythonhosted.org/packages/7c/53/2bdeca705f3a58679fa9201233193c86a30238340ca29b3a152c3fcc719b/pillow-13.0.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:149a1c323ba366f90b48e6f1a6db6582eb50512df374588de56fc909abc0f0f3", size = 6626871, upload-time = "2026-10-16T22:17:22.361Z" },
{ url = "https://files.pythonhosted.org/packages/14/de/3829030f209722ade3753bb456b805a0e7f885cf885b483407365c82e9ca/pillow-13.0.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e892354a424b1e82a78ba16785822bdd40de06f19446a3d4a28047df1a4f8932", size = 7236439, upload-time = "2026-10-16T22:17:24.352Z" },
{ url = "https://files.pythonhosted.org/packages/44/b1/67fc37388382bc036a74f04da5bb6fcbf90ec3ac1f45893e06176c373fd3/pillow-13.0.0-cp315-cp315-win32.whl", hash = "sha256:8e459b613cf31f8980f584387b4fe47bf08e076e288d61aa8eba1f7fd4ddbb81", size = 6508688, upload-time = "2026-10-16T22:17:26.314Z" },
{ url = "https://files.pythonhosted.org/packages/44/05/fdb6a79aee10861165526b41b7b440e36ee1ff58613b2fbefe3acbf06d4f/pillow-13.0.0-cp315-cp315-win_amd64.whl", hash = "sha256:d4737f50d23ddd11d6685faacbb9252887dde0bc7997127757247983d74399d6", size = 7281107, upload-time = "2026-10-16T22:17:28.198Z" },
{ url = "https://files.pythonhosted.org/packages/e6/9c/6c6f7dc2a566467baf121394e83de25ac287cffe34cc030726beb4be0224/pillow-13.0.0-cp315-cp315-win_arm64.whl", hash = "sha256:2310e2adfc13c6d457d7d0f2ffdd5c56a74a317650cf765887df8d515a647ac0", size = 2695513, upload-time = "2026-10-16T22:17:30.013Z" },
{ url = "https://files.pythonhosted.org/packages/23/16/e9120defdb8bff693b582d035312acc6f530cdc2bc5238a87f410efccd82/pillow-13.0.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:21c32061a5512dc13f87cc7303e120e4d77c2f70df89a35af0e3ce246e5ac57f", size = 5578129, upload-time = "2026-10-16T22:17:31.642Z" },
{ url = "https://files.pythonhosted.org/packages/47/22/76faf2bb2448f48a84cf894c1e2bb1090d7d2b1aa521ece131cb2f05d631/pillow-13.0.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:1c774b9c0ba0d0b356f71530677b39028878a2cae5a93e0a13779cfd17120f88", size = 4857428, upload-time = "2026-10-16T22:17:33.43Z" },
{ url = "https://files.pythonhosted.org/packages/b3/64/0861ea4e03c50b14af9ed9dd8b9f388ab42f445c623a9fcbab19aef7aa55/pillow-13.0.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20822140c9a2ba9c5e42df342b0a892ab2e102fc0e3db967a133bb9ce3f898bb", size = 6612582, upload-time = "2026-10-16T22:17:35.415Z" },
{ url = "https://files.pythonhosted.org/packages/5c/20/cd1e9b3f60a9f3d5ebe46da36955c26004092a501f7f2c42f9fcc26611bc/pillow-13.0.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffea6b2986a797c84b6d1d27d4f43e0b83c73fc16281a930891cd237cf0d08fe", size = 7230176, upload-time = "2026-10-16T22:17:37.362Z" },
{ url = "https://files.pythonhosted.org/packages/b6/ef/ffb0a2fc85690714fb7ac662956949645d57f15bf4426f520da361e7b6e3/pillow-13.0.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:05cd2cd39055d0b9650060e4513ef4a9a0aea870083950bd046a0d6490f93a22", size = 6703640, upload-time = "2026-10-16T22:17:39.313Z" },
{ url = "https://files.pythonhosted.org/packages/e0/c2/6402ee3ccb3363aeed8fdb3f9fe56703ac5377a539f0b81b53999d7e121f/pillow-13.0.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:c6fd031333dedcad7ce3e03d244093f43504f209e86925ebdb2b868fa4037630", size = 7309231, upload-time = "2026-10-16T22:17:41.021Z" },
{ url = "https://files.pythonhosted.org/packages/fc/96/d6b82e8b8c06ad143b66b12e0b31051e4bf489b4fabca1f390a14fe1ebde/pillow-13.0.0-cp315-cp315t-win32.whl", hash = "sha256:c50c80e3591e5cf9a8dfa6007d4f10732f02e32263850999286e56ccd650cfb2", size = 6517200, upload-time = "2026-10-16T22:17:42.886Z" },
{ url = "https://files.pythonhosted.org/packages/60/d4/0062e6005e9b270b165015aafd343364ac378ec5a685431ea42baad33357/pillow-13.0.0-cp315-cp315t-win_amd64.whl", hash = "sha256:adb3c2124c68e480f4632d06cb7caa3b48a51678475f483b4edb1c6dee29ea43", size = 7287488, upload-time = "2026-10-16T22:17:45.56Z" },
{ url = "https://files.pythonhosted.org/packages/e4/81/b8eb349746be7bce47dd95337c6634e84910af91b59aaa9159c034c614fa/pillow-13.0.0-cp315-cp315t-win_arm64.whl", hash = "sha256:4a25756244bdcce3b50580baadf320fa48297f0eeb8e114ba64139a40d4d0fc8", size = 2698073, upload-time = "2026-10-16T22:17:47.556Z" },
{ url = "https://files.pythonhosted.org/packages/68/c0/fb7b1caf25ed5ac5d46116f09b8b0706b9559833c65c667cecb1a53153f2/pillow-13.0.0-pp311-pypy311_pp80-macosx_10_15_x86_64.whl", hash = "sha256:405be6058e206a86fedce2f715ecc2343f94072d7925251632dffa2c05f70dca", size = 5521630, upload-time = "2026-10-16T22:17:50.008Z" },
{ url = "https://files.pythonhosted.org/packages/1a/e6/4d11d5b1137f4fc080605cce410cfaae4f303ad400bdec75d27d3083fa29/pillow-13.0.0-pp311-pypy311_pp80-macosx_11_0_arm64.whl", hash = "sha256:b84be41387fc00db28c13c02e30d42b02d8d8f6a4a741a1aee6012ca7f1f6039", size = 4806023, upload-time = "2026-10-16T22:17:51.932Z" },
{ url = "https://files.pythonhosted.org/packages/f1/45/0923202a658e8ff6aafd72c1a4395ebadcdebaa182c437d8595d3dd3db32/pillow-13.0.0-pp311-pypy311_pp80-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a192360d512305fe92d3053e4f7627bbad1f7708f1f4ad38cccd9336acabe9ad", size = 5464831, upload-time = "2026-10-16T22:17:53.742Z" },
{ url = "https://files.pythonhosted.org/packages/07/52/d3e38f0cad1dadea2a658b9a7d6ba8a04048f5ae14f011b806e5c3bd6bcb/pillow-13.0.0-pp311-pypy311_pp80-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83b37abd87b590acd8f47ca8a61f70893322b11834d36e07c2298dacad4d14d2", size = 6043133, upload-time = "2026-10-16T22:17:55.625Z" },
{ url = "https://files.pythonhosted.org/packages/c4/f9/2ed7685980c1d64f8131a7d87a19c369424747577152622b73ddeff163b7/pillow-13.0.0-pp311-pypy311_pp80-win_amd64.whl", hash = "sha256:73ee25cf8cf8b75d5a5aeed7cb6588f5e5dbfbb0fde51707b630bbb23897645c", size = 7365208, upload-time = "2026-10-16T22:17:57.509Z" },
{ url = "https://files.pythonhosted.org/packages/60/da/f6d644e631c5b370c5d0b899f3ccadd2780aab070b29a110527cf4e1fd2c/pillow-13.0.0-pp312-pypy312_pp80-macosx_10_15_x86_64.whl", hash = "sha256:8619685cad8efc4c38c66d3b36aad854523297c43d748b3b6f0cc03c90ba3463", size = 5523188, upload-time = "2026-10-16T22:17:59.334Z" },
{ url = "https://files.pythonhosted.org/packages/e4/b0/2594f202d743a35d4fe80682bfd96f277e5ca7aecb27cf64ef5bfc1d000b/pillow-13.0.0-pp312-pypy312_pp80-macosx_11_0_arm64.whl", hash = "sha256:7e91e97a929232cdb828889ee6ad0e48d7f40f4ec38818f4e4a8968b2b1af22e", size = 4806808, upload-time = "2026-10-16T22:18:01.188Z" },
{ url = "https://files.pythonhosted.org/packages/8e/ed/d504165ef79af6fafe25c76726cf30f544cc236002f7ed054f1d51adc4ff/pillow-13.0.0-pp312-pypy312_pp80-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93ff9c9c3663ce23aaa5d0b6b6abac55359e2a0c06c7e30f3771829af816be63", size = 5464358, upload-time = "2026-10-16T22:18:02.779Z" },
{ url = "https://files.pythonhosted.org/packages/7b/fe/a10aba5503ca13e9e009b8bd0a1a4ce5584bdce246245fd6f3a83701369c/pillow-13.0.0-pp312-pypy312_pp80-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e65dd6e75de67b2362a4fa3e16aa627595f65054dc4ec73d64680c84d592fa61", size = 6043514, upload-time = "2026-10-16T22:18:04.538Z" },
{ url = "https://files.pythonhosted.org/packages/c0/37/66b12dc476041fe523bca29c9d58fbf1ea92a4eaf73acfb9e993948f1191/pillow-13.0.0-pp312-pypy312_pp80-win_amd64.whl", hash = "sha256:567f20f1c75897d7663830da6ea8af719e03d712b1d9ee7f813319f9ece42e5a", size = 7366030, upload-time = "2026-10-16T22:18:06.638Z" },
] ]
[[package]] [[package]]
name = "pluggy" name = "pluggy"
version = "1.6.0" version = "1.7.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } sdist = { url = "https://files.pythonhosted.org/packages/bf/db/7fc19e6f2dc92a966727031389fc2e08b558f0f25eb7403c1119ad4713cd/pluggy-1.7.0.tar.gz", hash = "sha256:d1eaa46ebb595891b860ab086b4d09c8588af65ebd4361b8e8f4bb8920b90ba8", size = 123304, upload-time = "2026-10-15T09:50:58.343Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, { url = "https://files.pythonhosted.org/packages/40/9e/2b38731e0fc536806f16490e1a12d7f0dc2a1235aa8cc07bcc75416a7daa/pluggy-1.7.0-py3-none-any.whl", hash = "sha256:7dd7b0d8832ba3cb632c306926ded123429211b83641b35dc5c41ad2d34f9bec", size = 27082, upload-time = "2026-10-15T09:50:56.808Z" },
]
[[package]]
name = "pygments"
version = "2.21.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
] ]
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "8.3.5" version = "9.1.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" }, { name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "exceptiongroup" },
{ name = "iniconfig" }, { name = "iniconfig" },
{ name = "packaging" }, { name = "packaging" },
{ name = "pluggy" }, { name = "pluggy" },
{ name = "tomli" }, { name = "pygments" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
]
[[package]]
name = "pytest-cov"
version = "7.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "coverage" },
{ name = "pluggy" },
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" },
] ]
[[package]] [[package]]
name = "pytest-django" name = "pytest-django"
version = "4.11.1" version = "4.14.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "pytest" }, { name = "pytest" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/b1/fb/55d580352db26eb3d59ad50c64321ddfe228d3d8ac107db05387a2fadf3a/pytest_django-4.11.1.tar.gz", hash = "sha256:a949141a1ee103cb0e7a20f1451d355f83f5e4a5d07bdd4dcfdd1fd0ff227991", size = 86202, upload-time = "2025-04-03T18:56:09.338Z" } sdist = { url = "https://files.pythonhosted.org/packages/44/f6/3851312120c2bf2f19cafff931e75059aad1ba670703cd751e2fde9bc942/pytest_django-4.14.0.tar.gz", hash = "sha256:26787dd3f422cfbab8f55b80a776e2edea7a11092cb74e960bef1312515708ef", size = 94700, upload-time = "2026-08-10T14:13:08.319Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/be/ac/bd0608d229ec808e51a21044f3f2f27b9a37e7a0ebaca7247882e67876af/pytest_django-4.11.1-py3-none-any.whl", hash = "sha256:1b63773f648aa3d8541000c26929c1ea63934be1cfa674c76436966d73fe6a10", size = 25281, upload-time = "2025-04-03T18:56:07.678Z" }, { url = "https://files.pythonhosted.org/packages/9c/03/850bffad2b581c440ca51c039d74504d5a422c94bda0bdb8a8ba5068d48b/pytest_django-4.14.0-py3-none-any.whl", hash = "sha256:c533b08d89cc675efcd5398eea270b34547e35f9a3608e2c9748dd88428ea187", size = 27067, upload-time = "2026-08-10T14:13:06.998Z" },
] ]
[[package]] [[package]]
name = "python-dateutil" name = "python-dateutil"
version = "2.9.0.post0" version = "3.0.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "six" }, { name = "six" },
{ name = "tzdata" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } sdist = { url = "https://files.pythonhosted.org/packages/0e/df/f15f80383f152452d8985b36d95cb8751f901a3e0ff4cd27f7e118f168e2/python_dateutil-3.0.0.tar.gz", hash = "sha256:7ebcb3c29a92f3ceafe79a7a48a5c45677e9e004bbeec5742ac0bbb00560b7a2", size = 271066, upload-time = "2026-10-21T23:41:45.075Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, { url = "https://files.pythonhosted.org/packages/5a/17/cebee01d1c0c1441174cf0456ddc91b280d79acb65340c05e41d77866ee8/python_dateutil-3.0.0-py2.py3-none-any.whl", hash = "sha256:3c79630202e4152c6add678446984236efe4f57e84d8e892aab0637bab82e9fd", size = 94659, upload-time = "2026-10-21T23:41:43.431Z" },
] ]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.11.12" version = "0.11.7"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/15/0a/92416b159ec00cdf11e5882a9d80d29bf84bba3dbebc51c4898bfbca1da6/ruff-0.11.12.tar.gz", hash = "sha256:43cf7f69c7d7c7d7513b9d59c5d8cafd704e05944f978614aa9faff6ac202603", size = 4202289, upload-time = "2025-05-29T13:31:40.037Z" } sdist = { url = "https://files.pythonhosted.org/packages/5b/89/6f9c9674818ac2e9cc2f2b35b704b7768656e6b7c139064fc7ba8fbc99f1/ruff-0.11.7.tar.gz", hash = "sha256:655089ad3224070736dc32844fde783454f8558e71f501cb207485fe4eee23d4", size = 4054861, upload-time = "2025-04-24T18:49:37.007Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/60/cc/53eb79f012d15e136d40a8e8fc519ba8f55a057f60b29c2df34efd47c6e3/ruff-0.11.12-py3-none-linux_armv6l.whl", hash = "sha256:c7680aa2f0d4c4f43353d1e72123955c7a2159b8646cd43402de6d4a3a25d7cc", size = 10285597, upload-time = "2025-05-29T13:30:57.539Z" }, { url = "https://files.pythonhosted.org/packages/b4/ec/21927cb906c5614b786d1621dba405e3d44f6e473872e6df5d1a6bca0455/ruff-0.11.7-py3-none-linux_armv6l.whl", hash = "sha256:d29e909d9a8d02f928d72ab7837b5cbc450a5bdf578ab9ebee3263d0a525091c", size = 10245403, upload-time = "2025-04-24T18:48:40.459Z" },
{ url = "https://files.pythonhosted.org/packages/e7/d7/73386e9fb0232b015a23f62fea7503f96e29c29e6c45461d4a73bac74df9/ruff-0.11.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2cad64843da9f134565c20bcc430642de897b8ea02e2e79e6e02a76b8dcad7c3", size = 11053154, upload-time = "2025-05-29T13:31:00.865Z" }, { url = "https://files.pythonhosted.org/packages/e2/af/fec85b6c2c725bcb062a354dd7cbc1eed53c33ff3aa665165871c9c16ddf/ruff-0.11.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dd1fb86b168ae349fb01dd497d83537b2c5541fe0626e70c786427dd8363aaee", size = 11007166, upload-time = "2025-04-24T18:48:44.742Z" },
{ url = "https://files.pythonhosted.org/packages/4e/eb/3eae144c5114e92deb65a0cb2c72326c8469e14991e9bc3ec0349da1331c/ruff-0.11.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9b6886b524a1c659cee1758140138455d3c029783d1b9e643f3624a5ee0cb0aa", size = 10403048, upload-time = "2025-05-29T13:31:03.413Z" }, { url = "https://files.pythonhosted.org/packages/31/9a/2d0d260a58e81f388800343a45898fd8df73c608b8261c370058b675319a/ruff-0.11.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d3d7d2e140a6fbbc09033bce65bd7ea29d6a0adeb90b8430262fbacd58c38ada", size = 10378076, upload-time = "2025-04-24T18:48:47.918Z" },
{ url = "https://files.pythonhosted.org/packages/29/64/20c54b20e58b1058db6689e94731f2a22e9f7abab74e1a758dfba058b6ca/ruff-0.11.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc3a3690aad6e86c1958d3ec3c38c4594b6ecec75c1f531e84160bd827b2012", size = 10597062, upload-time = "2025-05-29T13:31:05.539Z" }, { url = "https://files.pythonhosted.org/packages/c2/c4/9b09b45051404d2e7dd6d9dbcbabaa5ab0093f9febcae664876a77b9ad53/ruff-0.11.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4809df77de390a1c2077d9b7945d82f44b95d19ceccf0c287c56e4dc9b91ca64", size = 10557138, upload-time = "2025-04-24T18:48:51.707Z" },
{ url = "https://files.pythonhosted.org/packages/29/3a/79fa6a9a39422a400564ca7233a689a151f1039110f0bbbabcb38106883a/ruff-0.11.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f97fdbc2549f456c65b3b0048560d44ddd540db1f27c778a938371424b49fe4a", size = 10155152, upload-time = "2025-05-29T13:31:07.986Z" }, { url = "https://files.pythonhosted.org/packages/5e/5e/f62a1b6669870a591ed7db771c332fabb30f83c967f376b05e7c91bccd14/ruff-0.11.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3a0c2e169e6b545f8e2dba185eabbd9db4f08880032e75aa0e285a6d3f48201", size = 10095726, upload-time = "2025-04-24T18:48:54.243Z" },
{ url = "https://files.pythonhosted.org/packages/e5/a4/22c2c97b2340aa968af3a39bc38045e78d36abd4ed3fa2bde91c31e712e3/ruff-0.11.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74adf84960236961090e2d1348c1a67d940fd12e811a33fb3d107df61eef8fc7", size = 11723067, upload-time = "2025-05-29T13:31:10.57Z" }, { url = "https://files.pythonhosted.org/packages/45/59/a7aa8e716f4cbe07c3500a391e58c52caf665bb242bf8be42c62adef649c/ruff-0.11.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49b888200a320dd96a68e86736cf531d6afba03e4f6cf098401406a257fcf3d6", size = 11672265, upload-time = "2025-04-24T18:48:57.639Z" },
{ url = "https://files.pythonhosted.org/packages/bc/cf/3e452fbd9597bcd8058856ecd42b22751749d07935793a1856d988154151/ruff-0.11.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b56697e5b8bcf1d61293ccfe63873aba08fdbcbbba839fc046ec5926bdb25a3a", size = 12460807, upload-time = "2025-05-29T13:31:12.88Z" }, { url = "https://files.pythonhosted.org/packages/dd/e3/101a8b707481f37aca5f0fcc3e42932fa38b51add87bfbd8e41ab14adb24/ruff-0.11.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2b19cdb9cf7dae00d5ee2e7c013540cdc3b31c4f281f1dacb5a799d610e90db4", size = 12331418, upload-time = "2025-04-24T18:49:00.697Z" },
{ url = "https://files.pythonhosted.org/packages/2f/ec/8f170381a15e1eb7d93cb4feef8d17334d5a1eb33fee273aee5d1f8241a3/ruff-0.11.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d47afa45e7b0eaf5e5969c6b39cbd108be83910b5c74626247e366fd7a36a13", size = 12063261, upload-time = "2025-05-29T13:31:15.236Z" }, { url = "https://files.pythonhosted.org/packages/dd/71/037f76cbe712f5cbc7b852e4916cd3cf32301a30351818d32ab71580d1c0/ruff-0.11.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64e0ee994c9e326b43539d133a36a455dbaab477bc84fe7bfbd528abe2f05c1e", size = 11794506, upload-time = "2025-04-24T18:49:03.545Z" },
{ url = "https://files.pythonhosted.org/packages/0d/bf/57208f8c0a8153a14652a85f4116c0002148e83770d7a41f2e90b52d2b4e/ruff-0.11.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bf9603fe1bf949de8b09a2da896f05c01ed7a187f4a386cdba6760e7f61be", size = 11329601, upload-time = "2025-05-29T13:31:18.68Z" }, { url = "https://files.pythonhosted.org/packages/ca/de/e450b6bab1fc60ef263ef8fcda077fb4977601184877dce1c59109356084/ruff-0.11.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bad82052311479a5865f52c76ecee5d468a58ba44fb23ee15079f17dd4c8fd63", size = 13939084, upload-time = "2025-04-24T18:49:07.159Z" },
{ url = "https://files.pythonhosted.org/packages/c3/56/edf942f7fdac5888094d9ffa303f12096f1a93eb46570bcf5f14c0c70880/ruff-0.11.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08033320e979df3b20dba567c62f69c45e01df708b0f9c83912d7abd3e0801cd", size = 11522186, upload-time = "2025-05-29T13:31:21.216Z" }, { url = "https://files.pythonhosted.org/packages/0e/2c/1e364cc92970075d7d04c69c928430b23e43a433f044474f57e425cbed37/ruff-0.11.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7940665e74e7b65d427b82bffc1e46710ec7f30d58b4b2d5016e3f0321436502", size = 11450441, upload-time = "2025-04-24T18:49:11.41Z" },
{ url = "https://files.pythonhosted.org/packages/ed/63/79ffef65246911ed7e2290aeece48739d9603b3a35f9529fec0fc6c26400/ruff-0.11.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:929b7706584f5bfd61d67d5070f399057d07c70585fa8c4491d78ada452d3bef", size = 10449032, upload-time = "2025-05-29T13:31:23.417Z" }, { url = "https://files.pythonhosted.org/packages/9d/7d/1b048eb460517ff9accd78bca0fa6ae61df2b276010538e586f834f5e402/ruff-0.11.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:169027e31c52c0e36c44ae9a9c7db35e505fee0b39f8d9fca7274a6305295a92", size = 10441060, upload-time = "2025-04-24T18:49:14.184Z" },
{ url = "https://files.pythonhosted.org/packages/88/19/8c9d4d8a1c2a3f5a1ea45a64b42593d50e28b8e038f1aafd65d6b43647f3/ruff-0.11.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7de4a73205dc5756b8e09ee3ed67c38312dce1aa28972b93150f5751199981b5", size = 10129370, upload-time = "2025-05-29T13:31:25.777Z" }, { url = "https://files.pythonhosted.org/packages/3a/57/8dc6ccfd8380e5ca3d13ff7591e8ba46a3b330323515a4996b991b10bd5d/ruff-0.11.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:305b93f9798aee582e91e34437810439acb28b5fc1fee6b8205c78c806845a94", size = 10058689, upload-time = "2025-04-24T18:49:17.559Z" },
{ url = "https://files.pythonhosted.org/packages/bc/0f/2d15533eaa18f460530a857e1778900cd867ded67f16c85723569d54e410/ruff-0.11.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2635c2a90ac1b8ca9e93b70af59dfd1dd2026a40e2d6eebaa3efb0465dd9cf02", size = 11123529, upload-time = "2025-05-29T13:31:28.396Z" }, { url = "https://files.pythonhosted.org/packages/23/bf/20487561ed72654147817885559ba2aa705272d8b5dee7654d3ef2dbf912/ruff-0.11.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a681db041ef55550c371f9cd52a3cf17a0da4c75d6bd691092dfc38170ebc4b6", size = 11073703, upload-time = "2025-04-24T18:49:20.247Z" },
{ url = "https://files.pythonhosted.org/packages/4f/e2/4c2ac669534bdded835356813f48ea33cfb3a947dc47f270038364587088/ruff-0.11.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d05d6a78a89166f03f03a198ecc9d18779076ad0eec476819467acb401028c0c", size = 11577642, upload-time = "2025-05-29T13:31:30.647Z" }, { url = "https://files.pythonhosted.org/packages/9d/27/04f2db95f4ef73dccedd0c21daf9991cc3b7f29901a4362057b132075aa4/ruff-0.11.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:07f1496ad00a4a139f4de220b0c97da6d4c85e0e4aa9b2624167b7d4d44fd6b6", size = 11532822, upload-time = "2025-04-24T18:49:23.765Z" },
{ url = "https://files.pythonhosted.org/packages/a7/9b/c9ddf7f924d5617a1c94a93ba595f4b24cb5bc50e98b94433ab3f7ad27e5/ruff-0.11.12-py3-none-win32.whl", hash = "sha256:f5a07f49767c4be4772d161bfc049c1f242db0cfe1bd976e0f0886732a4765d6", size = 10475511, upload-time = "2025-05-29T13:31:32.917Z" }, { url = "https://files.pythonhosted.org/packages/e1/72/43b123e4db52144c8add336581de52185097545981ff6e9e58a21861c250/ruff-0.11.7-py3-none-win32.whl", hash = "sha256:f25dfb853ad217e6e5f1924ae8a5b3f6709051a13e9dad18690de6c8ff299e26", size = 10362436, upload-time = "2025-04-24T18:49:27.377Z" },
{ url = "https://files.pythonhosted.org/packages/fd/d6/74fb6d3470c1aada019ffff33c0f9210af746cca0a4de19a1f10ce54968a/ruff-0.11.12-py3-none-win_amd64.whl", hash = "sha256:5a4d9f8030d8c3a45df201d7fb3ed38d0219bccd7955268e863ee4a115fa0832", size = 11523573, upload-time = "2025-05-29T13:31:35.782Z" }, { url = "https://files.pythonhosted.org/packages/c5/a0/3e58cd76fdee53d5c8ce7a56d84540833f924ccdf2c7d657cb009e604d82/ruff-0.11.7-py3-none-win_amd64.whl", hash = "sha256:0a931d85959ceb77e92aea4bbedfded0a31534ce191252721128f77e5ae1f98a", size = 11566676, upload-time = "2025-04-24T18:49:30.938Z" },
{ url = "https://files.pythonhosted.org/packages/44/42/d58086ec20f52d2b0140752ae54b355ea2be2ed46f914231136dd1effcc7/ruff-0.11.12-py3-none-win_arm64.whl", hash = "sha256:65194e37853158d368e333ba282217941029a28ea90913c67e558c611d04daa5", size = 10697770, upload-time = "2025-05-29T13:31:38.009Z" }, { url = "https://files.pythonhosted.org/packages/68/ca/69d7c7752bce162d1516e5592b1cc6b6668e9328c0d270609ddbeeadd7cf/ruff-0.11.7-py3-none-win_arm64.whl", hash = "sha256:778c1e5d6f9e91034142dfd06110534ca13220bfaad5c3735f6cb844654f6177", size = 10677936, upload-time = "2025-04-24T18:49:34.392Z" },
] ]
[[package]] [[package]]
@@ -318,15 +539,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/9f/105366a122efa93f0cb1914f841747d160788e4d022d0488d2d44c2ba26c/sentry_sdk-1.45.1-py2.py3-none-any.whl", hash = "sha256:608887855ccfe39032bfd03936e3a1c4f4fc99b3a4ac49ced54a4220de61c9c1", size = 267163, upload-time = "2024-07-26T13:48:29.38Z" }, { url = "https://files.pythonhosted.org/packages/fe/9f/105366a122efa93f0cb1914f841747d160788e4d022d0488d2d44c2ba26c/sentry_sdk-1.45.1-py2.py3-none-any.whl", hash = "sha256:608887855ccfe39032bfd03936e3a1c4f4fc99b3a4ac49ced54a4220de61c9c1", size = 267163, upload-time = "2024-07-26T13:48:29.38Z" },
] ]
[[package]]
name = "setuptools"
version = "80.9.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" },
]
[[package]] [[package]]
name = "six" name = "six"
version = "1.17.0" version = "1.17.0"
@@ -338,74 +550,66 @@ wheels = [
[[package]] [[package]]
name = "sqlparse" name = "sqlparse"
version = "0.5.3" version = "0.6.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999, upload-time = "2024-12-10T12:05:30.728Z" } sdist = { url = "https://files.pythonhosted.org/packages/5f/d3/3f06a1006f2261d1342aefb3c71eed02f5d4ca5bdbecd86ebc12ad38306e/sqlparse-0.6.0.tar.gz", hash = "sha256:113c35c75365ab9cc9c7231d68c6428fb11c085fc8e9eb1ad659b7ddbf6cd2b9", size = 178477, upload-time = "2026-08-13T19:16:06.396Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415, upload-time = "2024-12-10T12:05:27.824Z" }, { url = "https://files.pythonhosted.org/packages/d9/50/f00935da0ec7cbf325f8dc4f772ae46fbc7b672dd62876e73f0a94adda57/sqlparse-0.6.0-py3-none-any.whl", hash = "sha256:b861c0288ce2fa56209a9a6412d2e066ac664b3873b89c26c9d8415e8e32996f", size = 50070, upload-time = "2026-08-13T19:16:04.062Z" },
] ]
[[package]] [[package]]
name = "strawberry-graphql" name = "strawberry-graphql"
version = "0.270.4" version = "0.336.2"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "cross-web" },
{ name = "graphql-core" }, { name = "graphql-core" },
{ name = "packaging" }, { name = "packaging" },
{ name = "python-dateutil" }, { name = "python-dateutil" },
{ name = "typing-extensions" }, { name = "typing-extensions" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/87/07/b4c411266038f9d367ce3307cf28cac74772771dfe6b4a87219311d59c90/strawberry_graphql-0.270.4.tar.gz", hash = "sha256:cca958a6070bcc2aca73a938cd80c0c7b4d39aecc8140a228608025896be990c", size = 207381, upload-time = "2025-05-29T10:40:31.544Z" } sdist = { url = "https://files.pythonhosted.org/packages/21/ad/6e4db854b7fc75b0b1fe0cda86839fced39f82af5497219a9499ceace844/strawberry_graphql-0.336.2.tar.gz", hash = "sha256:766a22ff3ec4ca82162c7088f77771e8e4e5fa0594e4c14e113f9e171d94a685", size = 267682, upload-time = "2026-10-20T12:19:11.843Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/13/de/3c7bbd77374fe814d671c607a61d6ba6cc6c25983e7cda6fa391e5c8450b/strawberry_graphql-0.270.4-py3-none-any.whl", hash = "sha256:93c6bd3bde85641ec81655c939b2daa09583e41048f94984a523539c20a8550d", size = 301421, upload-time = "2025-05-29T10:40:28.811Z" }, { url = "https://files.pythonhosted.org/packages/ab/ef/9709408bdf7652aa4dd99ee34a570fec800da4e499d725466be1d9fbcd55/strawberry_graphql-0.336.2-py3-none-any.whl", hash = "sha256:5d0514e0e9a4c5be1f009c668d53ef7e50d4e3d908d0656377cf5e81fbede7bf", size = 358961, upload-time = "2026-10-20T12:19:09.95Z" },
] ]
[[package]] [[package]]
name = "strawberry-graphql-django" name = "strawberry-graphql-django"
version = "0.60.0" version = "0.93.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "asgiref" }, { name = "asgiref" },
{ name = "django" }, { name = "django" },
{ name = "strawberry-graphql" }, { name = "strawberry-graphql" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/09/83/a0a902db2080469ea5f468d8077063d1c81cca99c89ae0bce6411e2e1bed/strawberry_graphql_django-0.60.0.tar.gz", hash = "sha256:a5d210bd4458be298f86d8b1b8d01ce0208291b5b6b227cd0cfd97c51cf71ee8", size = 84511, upload-time = "2025-05-24T10:01:20.863Z" } sdist = { url = "https://files.pythonhosted.org/packages/f2/ad/6e6785017be2ff5950de6e919a95c74f9818ac81d84009c6ae242604ac36/strawberry_graphql_django-0.93.0.tar.gz", hash = "sha256:f3ee0d36f09d308451c68c652803e4be10f064a8dc4a6c92a1d2657a66e1ae8d", size = 97428, upload-time = "2026-10-10T14:28:28.999Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/34/a7/86c098eb3407170230fe95053e1ee0283e5bac5323d90f04e6544fdd6692/strawberry_graphql_django-0.60.0-py3-none-any.whl", hash = "sha256:6c37e9e0fe43a947732f15b43900720171e77f2f66915456a54e18bb32779772", size = 105807, upload-time = "2025-05-24T10:01:19.178Z" }, { url = "https://files.pythonhosted.org/packages/6e/a8/fd73bcc2ac2cccc13a3e06ac2b373bd24d210a9b76c515391f44ebccc7de/strawberry_graphql_django-0.93.0-py3-none-any.whl", hash = "sha256:d0bb99865b808b444d7f87297e7e111ef4f36b39a968ea23e8222a30793284da", size = 121245, upload-time = "2026-10-10T14:28:27.456Z" },
]
[[package]]
name = "tomli"
version = "2.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
] ]
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.13.2" version = "4.16.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
] ]
[[package]] [[package]]
name = "tzdata" name = "tzdata"
version = "2025.2" version = "2026.5"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } sdist = { url = "https://files.pythonhosted.org/packages/d9/68/f1b440335057bfce71b6e50a9d09445aa2ecbd08359a337976627b8409e7/tzdata-2026.5.tar.gz", hash = "sha256:8cc73c0a0bfca7dbfa59235d60b2eff82231dee33f53d206db1acd9173cfc0a7", size = 200404, upload-time = "2026-10-03T09:23:14.143Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, { url = "https://files.pythonhosted.org/packages/94/21/1e5995a1c920cce14e4bffae20c665ec10e7ed03ab25e006cd741092b718/tzdata-2026.5-py2.py3-none-any.whl", hash = "sha256:b683bd1b6659ddcd810ff02ad09ba821d4bf1065072805063eb35c49617905ac", size = 347996, upload-time = "2026-10-03T09:23:12.535Z" },
] ]
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "2.4.0" version = "2.8.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" },
] ]