diff --git a/pyproject.toml b/pyproject.toml index e758f15..a1584f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ wuttapos = "wuttapos.app:WuttaPosAppProvider" # TODO: (why) is this needed again? [project.entry-points."wutta.web.menus"] -"wuttapos" = "wuttapos.server.menus:serverMenuHandler" +"wuttapos" = "wuttapos.server.menus:WuttaPosMenuHandler" [project.urls] diff --git a/src/wuttapos/config.py b/src/wuttapos/config.py index 99d6c1f..4157f58 100644 --- a/src/wuttapos/config.py +++ b/src/wuttapos/config.py @@ -51,7 +51,7 @@ class WuttaPosConfigExtension(WuttaConfigExtension): # server menu handler config.setdefault( f"{config.appname}.web.menus.handler.spec", - "wuttapos.server.menus:serverMenuHandler", + "wuttapos.server.menus:WuttaPosMenuHandler", ) # # web app libcache diff --git a/src/wuttapos/db/alembic/versions/32e965f42f0f_add_customers.py b/src/wuttapos/db/alembic/versions/32e965f42f0f_add_customers.py new file mode 100644 index 0000000..1f9e584 --- /dev/null +++ b/src/wuttapos/db/alembic/versions/32e965f42f0f_add_customers.py @@ -0,0 +1,104 @@ +"""add Customers + +Revision ID: 32e965f42f0f +Revises: 6f02663c2220 +Create Date: 2026-01-01 19:51:21.769137 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import wuttjamaican.db.util + + +# revision identifiers, used by Alembic. +revision: str = "32e965f42f0f" +down_revision: Union[str, None] = "6f02663c2220" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + + # customer + op.create_table( + "customer", + sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("customer_id", sa.String(length=20), nullable=False), + sa.Column("name", sa.String(length=100), nullable=False), + sa.Column("account_holder_uuid", wuttjamaican.db.util.UUID(), nullable=True), + sa.Column("phone_number", sa.String(length=20), nullable=True), + sa.Column("email_address", sa.String(length=255), nullable=True), + sa.ForeignKeyConstraint( + ["account_holder_uuid"], + ["person.uuid"], + name=op.f("fk_customer_account_holder_uuid_person"), + ), + sa.PrimaryKeyConstraint("uuid", name=op.f("pk_customer")), + ) + op.create_table( + "customer_version", + sa.Column( + "uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False + ), + sa.Column( + "customer_id", sa.String(length=20), autoincrement=False, nullable=True + ), + sa.Column("name", sa.String(length=100), autoincrement=False, nullable=True), + sa.Column( + "account_holder_uuid", + wuttjamaican.db.util.UUID(), + autoincrement=False, + nullable=True, + ), + sa.Column( + "phone_number", sa.String(length=20), autoincrement=False, nullable=True + ), + sa.Column( + "email_address", sa.String(length=255), autoincrement=False, nullable=True + ), + sa.Column( + "transaction_id", sa.BigInteger(), autoincrement=False, nullable=False + ), + sa.Column("end_transaction_id", sa.BigInteger(), nullable=True), + sa.Column("operation_type", sa.SmallInteger(), nullable=False), + sa.PrimaryKeyConstraint( + "uuid", "transaction_id", name=op.f("pk_customer_version") + ), + ) + op.create_index( + op.f("ix_customer_version_end_transaction_id"), + "customer_version", + ["end_transaction_id"], + unique=False, + ) + op.create_index( + op.f("ix_customer_version_operation_type"), + "customer_version", + ["operation_type"], + unique=False, + ) + op.create_index( + op.f("ix_customer_version_transaction_id"), + "customer_version", + ["transaction_id"], + unique=False, + ) + + +def downgrade() -> None: + + # customer + op.drop_index( + op.f("ix_customer_version_transaction_id"), table_name="customer_version" + ) + op.drop_index( + op.f("ix_customer_version_operation_type"), table_name="customer_version" + ) + op.drop_index( + op.f("ix_customer_version_end_transaction_id"), table_name="customer_version" + ) + op.drop_table("customer_version") + op.drop_table("customer") diff --git a/src/wuttapos/db/model/__init__.py b/src/wuttapos/db/model/__init__.py index c99b602..98e0ab8 100644 --- a/src/wuttapos/db/model/__init__.py +++ b/src/wuttapos/db/model/__init__.py @@ -26,6 +26,7 @@ WuttaPOS - data model from wuttjamaican.db.model import * +from .customers import Customer from .departments import Department from .products import ( Product, diff --git a/src/wuttapos/db/model/customers.py b/src/wuttapos/db/model/customers.py new file mode 100644 index 0000000..67dfc21 --- /dev/null +++ b/src/wuttapos/db/model/customers.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8; -*- +""" +Model definition for Customers +""" + +import sqlalchemy as sa +from sqlalchemy import orm + +from wuttjamaican.db import model + + +class Customer(model.Base): + """ + Technically a customer account, but relates to a person (account holder). + """ + + __tablename__ = "customer" + __versioned__ = {} + __wutta_hint__ = { + "model_title": "Customer", + "model_title_plural": "Customers", + } + + uuid = model.uuid_column() + + customer_id = sa.Column( + sa.String(length=20), + nullable=False, + doc=""" + Unique identifier for the customer account. + """, + ) + + name = sa.Column( + sa.String(length=100), + nullable=False, + doc=""" + Name for the customer account. + """, + ) + + account_holder_uuid = model.uuid_fk_column("person.uuid", nullable=True) + account_holder = orm.relationship( + "Person", + doc=""" + Reference to the account holder, if applicable. + """, + ) + + phone_number = sa.Column( + sa.String(length=20), + nullable=True, + doc=""" + Phone number for the customer. + """, + ) + + email_address = sa.Column( + sa.String(length=255), + nullable=True, + doc=""" + Email address for the customer. + """, + ) + + def __str__(self): + return self.name or "" diff --git a/src/wuttapos/server/menus.py b/src/wuttapos/server/menus.py index 2bfcad4..a007fb2 100644 --- a/src/wuttapos/server/menus.py +++ b/src/wuttapos/server/menus.py @@ -38,10 +38,24 @@ class WuttaPosMenuHandler(base.MenuHandler): # replace it and add more as needed return [ + self.make_customers_menu(request), self.make_products_menu(request), self.make_admin_menu(request, include_people=True), ] + def make_customers_menu(self, request): + return { + "title": "Customers", + "type": "menu", + "items": [ + { + "title": "Customers", + "route": "customers", + "perm": "customers.list", + }, + ], + } + def make_products_menu(self, request): return { "title": "Products", diff --git a/src/wuttapos/server/views/__init__.py b/src/wuttapos/server/views/__init__.py index b0800d5..90e9371 100644 --- a/src/wuttapos/server/views/__init__.py +++ b/src/wuttapos/server/views/__init__.py @@ -34,3 +34,4 @@ def includeme(config): config.include("wuttapos.server.views.departments") config.include("wuttapos.server.views.products") config.include("wuttapos.server.views.inventory_adjustments") + config.include("wuttapos.server.views.customers") diff --git a/src/wuttapos/server/views/customers.py b/src/wuttapos/server/views/customers.py new file mode 100644 index 0000000..9352625 --- /dev/null +++ b/src/wuttapos/server/views/customers.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8; -*- +""" +Master view for Customers +""" + +from wuttaweb.views import MasterView +from wuttaweb.forms.schema import PersonRef + +from wuttapos.db.model.customers import Customer + + +class CustomerView(MasterView): + """ + Master view for Customers + """ + + model_class = Customer + model_title = "Customer" + model_title_plural = "Customers" + + route_prefix = "customers" + url_prefix = "/customers" + + creatable = True + editable = True + deletable = True + + labels = { + "customer_id": "Customer ID", + } + + grid_columns = [ + "customer_id", + "name", + "phone_number", + "email_address", + ] + + form_fields = [ + "customer_id", + "name", + "account_holder", + "phone_number", + "email_address", + ] + + def configure_grid(self, grid): + g = grid + super().configure_grid(g) + + # links + g.set_link("customer_id") + g.set_link("name") + + def configure_form(self, form): + f = form + super().configure_form(f) + + # account_holder + f.set_node("account_holder", PersonRef(self.request)) + f.set_required("account_holder", False) + + +def defaults(config, **kwargs): + base = globals() + + CustomerView = kwargs.get("CustomerView", base["CustomerView"]) + CustomerView.defaults(config) + + +def includeme(config): + defaults(config)