initial basic server web app
This commit is contained in:
commit
89238722b3
29 changed files with 2015 additions and 0 deletions
6
CHANGELOG.md
Normal file
6
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
# Changelog
|
||||
All notable changes to WuttaPOS will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||
35
README.md
Normal file
35
README.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
# WuttaPOS
|
||||
|
||||
This project includes two primary components:
|
||||
|
||||
- web app and related daemons, to run on the server
|
||||
- standalone GUI app, to run on the lanes
|
||||
|
||||
This project is in the very early stages and is not yet documented.
|
||||
It is based on an earlier effort, which used Rattail:
|
||||
[rattail/wuttapos](https://forgejo.wuttaproject.org/rattail/wuttapos)
|
||||
|
||||
However this project uses Wutta Framework, has no Rattail
|
||||
dependencies, and "starts over" for (mostly) everything.
|
||||
|
||||
|
||||
## Server
|
||||
|
||||
Make a virtual environment and install the app:
|
||||
|
||||
python3 -m venv wuttapos
|
||||
source wuttapos/bin/activate
|
||||
pip install WuttaPOS[server]
|
||||
wuttapos install
|
||||
|
||||
For more info see
|
||||
https://docs.wuttaproject.org/wuttjamaican/narr/install/index.html
|
||||
|
||||
|
||||
## Terminal
|
||||
|
||||
It uses [Flet](https://flet.dev/) for the GUI toolkit. The intended
|
||||
use case is to run as a proper standalone GUI app on the lane desktop.
|
||||
|
||||
(Code for this app has not yet been added.)
|
||||
83
pyproject.toml
Normal file
83
pyproject.toml
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
|
||||
[project]
|
||||
name = "WuttaPOS"
|
||||
# nb. 0.3.0 was the last release of the previous rattail WuttaPOS app.
|
||||
# pretty sure this will bump to 0.4.0 and then i can remove this note.
|
||||
version = "0.3.0"
|
||||
description = "Point of Sale system based on Wutta Framework"
|
||||
readme = "README.md"
|
||||
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
|
||||
classifiers = [
|
||||
# TODO: remove this if you intend to publish your project
|
||||
# (it's here by default, to prevent accidental publishing)
|
||||
"Private :: Do Not Upload",
|
||||
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Environment :: Win32 (MS Windows)",
|
||||
"Environment :: X11 Applications",
|
||||
"Framework :: Pyramid",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
||||
"Natural Language :: English",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Topic :: Office/Business",
|
||||
]
|
||||
dependencies = [
|
||||
"psycopg2",
|
||||
"WuttJamaican[db]>=0.28.2",
|
||||
"WuttaSync",
|
||||
]
|
||||
|
||||
|
||||
[project.optional-dependencies]
|
||||
server = ["WuttaWeb[continuum]"]
|
||||
terminal = ["flet[all]<0.80.0"]
|
||||
|
||||
|
||||
[project.scripts]
|
||||
"wuttapos" = "wuttapos.cli:wuttapos_typer"
|
||||
|
||||
[project.entry-points."paste.app_factory"]
|
||||
"main" = "wuttapos.server.app:main"
|
||||
|
||||
[project.entry-points."wutta.app.providers"]
|
||||
wuttapos = "wuttapos.app:WuttaPosAppProvider"
|
||||
|
||||
[project.entry-points."wutta.config.extensions"]
|
||||
"wuttapos" = "wuttapos.config:WuttaPosConfigExtension"
|
||||
|
||||
# TODO: (why) is this needed again?
|
||||
[project.entry-points."wutta.web.menus"]
|
||||
"wuttapos" = "wuttapos.server.menus:serverMenuHandler"
|
||||
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://wuttaproject.org/"
|
||||
Repository = "https://forgejo.wuttaproject.org/wutta/wuttapos"
|
||||
Issues = "https://forgejo.wuttaproject.org/wutta/wuttapos/issues"
|
||||
Changelog = "https://forgejo.wuttaproject.org/wutta/wuttapos/src/branch/master/CHANGELOG.md"
|
||||
|
||||
|
||||
[tool.commitizen]
|
||||
version_provider = "pep621"
|
||||
tag_format = "v$version"
|
||||
update_changelog_on_bump = true
|
||||
|
||||
# [tool.hatch.build.targets.sdist]
|
||||
# exclude = [
|
||||
# "htmlcov/",
|
||||
# ]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/wuttapos"]
|
||||
27
src/wuttapos/__init__.py
Normal file
27
src/wuttapos/__init__.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# WuttaPOS -- Point of Sale system based on Wutta Framework
|
||||
# Copyright © 2026 Lance Edgar
|
||||
#
|
||||
# This file is part of WuttaPOS.
|
||||
#
|
||||
# WuttaPOS is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
WuttaPOS - package root
|
||||
"""
|
||||
|
||||
from ._version import __version__
|
||||
6
src/wuttapos/_version.py
Normal file
6
src/wuttapos/_version.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
|
||||
from importlib.metadata import version
|
||||
|
||||
|
||||
__version__ = version("WuttaPOS")
|
||||
33
src/wuttapos/app.py
Normal file
33
src/wuttapos/app.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# WuttaPOS -- Point of Sale system based on Wutta Framework
|
||||
# Copyright © 2026 Lance Edgar
|
||||
#
|
||||
# This file is part of WuttaPOS.
|
||||
#
|
||||
# WuttaPOS is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
WuttaPOS app
|
||||
"""
|
||||
|
||||
from wuttjamaican import app as base
|
||||
|
||||
|
||||
class WuttaPosAppProvider(base.AppProvider):
|
||||
"""
|
||||
Custom :term:`app provider` for WuttaPOS.
|
||||
"""
|
||||
30
src/wuttapos/cli/__init__.py
Normal file
30
src/wuttapos/cli/__init__.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# WuttaPOS -- Point of Sale system based on Wutta Framework
|
||||
# Copyright © 2026 Lance Edgar
|
||||
#
|
||||
# This file is part of WuttaPOS.
|
||||
#
|
||||
# WuttaPOS is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
WuttaPOS - ``wuttapos`` subcommands
|
||||
"""
|
||||
|
||||
from .base import wuttapos_typer
|
||||
|
||||
# nb. must bring in all modules for discovery to work
|
||||
from . import install
|
||||
32
src/wuttapos/cli/base.py
Normal file
32
src/wuttapos/cli/base.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# WuttaPOS -- Point of Sale system based on Wutta Framework
|
||||
# Copyright © 2026 Lance Edgar
|
||||
#
|
||||
# This file is part of WuttaPOS.
|
||||
#
|
||||
# WuttaPOS is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
``wuttapos`` command
|
||||
"""
|
||||
|
||||
from wuttjamaican.cli import make_typer
|
||||
|
||||
|
||||
wuttapos_typer = make_typer(
|
||||
name="wuttapos", help="Point of Sale system based on Wutta Framework"
|
||||
)
|
||||
47
src/wuttapos/cli/install.py
Normal file
47
src/wuttapos/cli/install.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# WuttaPOS -- Point of Sale system based on Wutta Framework
|
||||
# Copyright © 2026 Lance Edgar
|
||||
#
|
||||
# This file is part of WuttaPOS.
|
||||
#
|
||||
# WuttaPOS is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
WuttaPOS CLI
|
||||
"""
|
||||
|
||||
import typer
|
||||
|
||||
from wuttapos.cli import wuttapos_typer
|
||||
|
||||
|
||||
@wuttapos_typer.command()
|
||||
def install(
|
||||
ctx: typer.Context,
|
||||
):
|
||||
"""
|
||||
Install the server app
|
||||
"""
|
||||
config = ctx.parent.wutta_config
|
||||
app = config.get_app()
|
||||
install = app.get_install_handler(
|
||||
pkg_name="wuttapos",
|
||||
app_title="WuttaPOS",
|
||||
pypi_name="WuttaPOS",
|
||||
egg_name="WuttaPOS",
|
||||
)
|
||||
install.run()
|
||||
58
src/wuttapos/config.py
Normal file
58
src/wuttapos/config.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# WuttaPOS -- Point of Sale system based on Wutta Framework
|
||||
# Copyright © 2026 Lance Edgar
|
||||
#
|
||||
# This file is part of WuttaPOS.
|
||||
#
|
||||
# WuttaPOS is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
WuttaPOS config extension
|
||||
"""
|
||||
|
||||
from wuttjamaican.conf import WuttaConfigExtension
|
||||
|
||||
|
||||
class WuttaPosConfigExtension(WuttaConfigExtension):
|
||||
"""
|
||||
The :term`config extension` for WuttaPOS.
|
||||
"""
|
||||
|
||||
key = "wuttapos"
|
||||
|
||||
def configure(self, config):
|
||||
|
||||
# app info
|
||||
config.setdefault(f"{config.appname}.app_title", "WuttaPOS")
|
||||
config.setdefault(f"{config.appname}.app_dist", "WuttaPOS")
|
||||
|
||||
# app model
|
||||
config.setdefault(f"{config.appname}.model_spec", "wuttapos.db.model")
|
||||
|
||||
# # auth handler
|
||||
# config.setdefault(
|
||||
# f"{config.appname}.auth.handler", "wuttapos.auth:WuttaPosAuthHandler"
|
||||
# )
|
||||
|
||||
# server menu handler
|
||||
config.setdefault(
|
||||
f"{config.appname}.web.menus.handler.spec",
|
||||
"wuttapos.server.menus:serverMenuHandler",
|
||||
)
|
||||
|
||||
# # web app libcache
|
||||
# #config.setdefault('wuttaweb.static_libcache.module', 'wuttapos.server.static')
|
||||
0
src/wuttapos/db/__init__.py
Normal file
0
src/wuttapos/db/__init__.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
"""add Departments
|
||||
|
||||
Revision ID: 148d701e4ac7
|
||||
Revises:
|
||||
Create Date: 2026-01-01 17:26:20.695393
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import wuttjamaican.db.util
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "148d701e4ac7"
|
||||
down_revision: Union[str, None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = ("wuttapos",)
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
|
||||
# department
|
||||
op.create_table(
|
||||
"department",
|
||||
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||
sa.Column("department_id", sa.String(length=20), nullable=False),
|
||||
sa.Column("name", sa.String(length=100), nullable=False),
|
||||
sa.Column("for_products", sa.Boolean(), nullable=False),
|
||||
sa.Column("for_personnel", sa.Boolean(), nullable=False),
|
||||
sa.Column("exempt_from_gross_sales", sa.Boolean(), nullable=True),
|
||||
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_department")),
|
||||
)
|
||||
op.create_table(
|
||||
"department_version",
|
||||
sa.Column(
|
||||
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"department_id", sa.String(length=20), autoincrement=False, nullable=True
|
||||
),
|
||||
sa.Column("name", sa.String(length=100), autoincrement=False, nullable=True),
|
||||
sa.Column("for_products", sa.Boolean(), autoincrement=False, nullable=True),
|
||||
sa.Column("for_personnel", sa.Boolean(), autoincrement=False, nullable=True),
|
||||
sa.Column(
|
||||
"exempt_from_gross_sales", sa.Boolean(), 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_department_version")
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_department_version_end_transaction_id"),
|
||||
"department_version",
|
||||
["end_transaction_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_department_version_operation_type"),
|
||||
"department_version",
|
||||
["operation_type"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_department_version_transaction_id"),
|
||||
"department_version",
|
||||
["transaction_id"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
# department
|
||||
op.drop_index(
|
||||
op.f("ix_department_version_transaction_id"), table_name="department_version"
|
||||
)
|
||||
op.drop_index(
|
||||
op.f("ix_department_version_operation_type"), table_name="department_version"
|
||||
)
|
||||
op.drop_index(
|
||||
op.f("ix_department_version_end_transaction_id"),
|
||||
table_name="department_version",
|
||||
)
|
||||
op.drop_table("department_version")
|
||||
op.drop_table("department")
|
||||
239
src/wuttapos/db/alembic/versions/6f02663c2220_add_products.py
Normal file
239
src/wuttapos/db/alembic/versions/6f02663c2220_add_products.py
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
"""add Products
|
||||
|
||||
Revision ID: 6f02663c2220
|
||||
Revises: 148d701e4ac7
|
||||
Create Date: 2026-01-01 18:18:22.958598
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import wuttjamaican.db.util
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "6f02663c2220"
|
||||
down_revision: Union[str, None] = "148d701e4ac7"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
|
||||
# product
|
||||
op.create_table(
|
||||
"product",
|
||||
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||
sa.Column("product_id", sa.String(length=20), nullable=False),
|
||||
sa.Column("brand_name", sa.String(length=100), nullable=True),
|
||||
sa.Column("description", sa.String(length=255), nullable=False),
|
||||
sa.Column("size", sa.String(length=30), nullable=True),
|
||||
sa.Column("sold_by_weight", sa.Boolean(), nullable=False),
|
||||
sa.Column("department_uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||
sa.Column("special_order", sa.Boolean(), nullable=True),
|
||||
sa.Column("case_size", sa.Numeric(precision=9, scale=4), nullable=True),
|
||||
sa.Column("unit_cost", sa.Numeric(precision=9, scale=5), nullable=True),
|
||||
sa.Column("unit_price_reg", sa.Numeric(precision=8, scale=3), nullable=True),
|
||||
sa.Column("notes", sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["department_uuid"],
|
||||
["department.uuid"],
|
||||
name=op.f("fk_product_department_uuid_department"),
|
||||
),
|
||||
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_product")),
|
||||
)
|
||||
op.create_table(
|
||||
"product_version",
|
||||
sa.Column(
|
||||
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"product_id", sa.String(length=20), autoincrement=False, nullable=True
|
||||
),
|
||||
sa.Column(
|
||||
"brand_name", sa.String(length=100), autoincrement=False, nullable=True
|
||||
),
|
||||
sa.Column(
|
||||
"description", sa.String(length=255), autoincrement=False, nullable=True
|
||||
),
|
||||
sa.Column("size", sa.String(length=30), autoincrement=False, nullable=True),
|
||||
sa.Column("sold_by_weight", sa.Boolean(), autoincrement=False, nullable=True),
|
||||
sa.Column(
|
||||
"department_uuid",
|
||||
wuttjamaican.db.util.UUID(),
|
||||
autoincrement=False,
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column("special_order", sa.Boolean(), autoincrement=False, nullable=True),
|
||||
sa.Column(
|
||||
"case_size",
|
||||
sa.Numeric(precision=9, scale=4),
|
||||
autoincrement=False,
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column(
|
||||
"unit_cost",
|
||||
sa.Numeric(precision=9, scale=5),
|
||||
autoincrement=False,
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column(
|
||||
"unit_price_reg",
|
||||
sa.Numeric(precision=8, scale=3),
|
||||
autoincrement=False,
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column("notes", sa.Text(), 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_product_version")
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_product_version_end_transaction_id"),
|
||||
"product_version",
|
||||
["end_transaction_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_product_version_operation_type"),
|
||||
"product_version",
|
||||
["operation_type"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_product_version_transaction_id"),
|
||||
"product_version",
|
||||
["transaction_id"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
# product_inventory
|
||||
op.create_table(
|
||||
"product_inventory",
|
||||
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||
sa.Column("product_uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||
sa.Column("on_hand", sa.Numeric(precision=9, scale=4), nullable=True),
|
||||
sa.Column("on_order", sa.Numeric(precision=9, scale=4), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["product_uuid"],
|
||||
["product.uuid"],
|
||||
name=op.f("fk_product_inventory_product_uuid_product"),
|
||||
),
|
||||
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_product_inventory")),
|
||||
)
|
||||
|
||||
# inventory_adjustment_type
|
||||
op.create_table(
|
||||
"inventory_adjustment_type",
|
||||
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||
sa.Column("type_code", sa.Integer(), nullable=False),
|
||||
sa.Column("name", sa.String(length=100), nullable=False),
|
||||
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_inventory_adjustment_type")),
|
||||
sa.UniqueConstraint(
|
||||
"type_code", name=op.f("uq_inventory_adjustment_type_type_code")
|
||||
),
|
||||
)
|
||||
op.create_table(
|
||||
"inventory_adjustment_type_version",
|
||||
sa.Column(
|
||||
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
|
||||
),
|
||||
sa.Column("type_code", sa.Integer(), autoincrement=False, nullable=True),
|
||||
sa.Column("name", sa.String(length=100), 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_inventory_adjustment_type_version")
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_inventory_adjustment_type_version_end_transaction_id"),
|
||||
"inventory_adjustment_type_version",
|
||||
["end_transaction_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_inventory_adjustment_type_version_operation_type"),
|
||||
"inventory_adjustment_type_version",
|
||||
["operation_type"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_inventory_adjustment_type_version_transaction_id"),
|
||||
"inventory_adjustment_type_version",
|
||||
["transaction_id"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
# inventory_adjustment
|
||||
op.create_table(
|
||||
"inventory_adjustment",
|
||||
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||
sa.Column("inventory_uuid", wuttjamaican.db.util.UUID(), nullable=True),
|
||||
sa.Column("adjusted", sa.DateTime(), nullable=False),
|
||||
sa.Column("effective_date", sa.Date(), nullable=False),
|
||||
sa.Column("adjustment_type_uuid", wuttjamaican.db.util.UUID(), nullable=True),
|
||||
sa.Column("amount", sa.Numeric(precision=9, scale=4), nullable=False),
|
||||
sa.Column("source", sa.String(length=100), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["adjustment_type_uuid"],
|
||||
["inventory_adjustment_type.uuid"],
|
||||
name=op.f(
|
||||
"fk_inventory_adjustment_adjustment_type_uuid_inventory_adjustment_type"
|
||||
),
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["inventory_uuid"],
|
||||
["product_inventory.uuid"],
|
||||
name=op.f("fk_inventory_adjustment_inventory_uuid_product_inventory"),
|
||||
),
|
||||
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_inventory_adjustment")),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
# inventory_adjustment
|
||||
op.drop_table("inventory_adjustment")
|
||||
|
||||
# inventory_adjustment_type
|
||||
op.drop_index(
|
||||
op.f("ix_inventory_adjustment_type_version_transaction_id"),
|
||||
table_name="inventory_adjustment_type_version",
|
||||
)
|
||||
op.drop_index(
|
||||
op.f("ix_inventory_adjustment_type_version_operation_type"),
|
||||
table_name="inventory_adjustment_type_version",
|
||||
)
|
||||
op.drop_index(
|
||||
op.f("ix_inventory_adjustment_type_version_end_transaction_id"),
|
||||
table_name="inventory_adjustment_type_version",
|
||||
)
|
||||
op.drop_table("inventory_adjustment_type_version")
|
||||
op.drop_table("inventory_adjustment_type")
|
||||
|
||||
# product_inventory
|
||||
op.drop_table("product_inventory")
|
||||
|
||||
# product
|
||||
op.drop_index(
|
||||
op.f("ix_product_version_transaction_id"), table_name="product_version"
|
||||
)
|
||||
op.drop_index(
|
||||
op.f("ix_product_version_operation_type"), table_name="product_version"
|
||||
)
|
||||
op.drop_index(
|
||||
op.f("ix_product_version_end_transaction_id"), table_name="product_version"
|
||||
)
|
||||
op.drop_table("product_version")
|
||||
op.drop_table("product")
|
||||
35
src/wuttapos/db/model/__init__.py
Normal file
35
src/wuttapos/db/model/__init__.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# WuttaPOS -- Point of Sale system based on Wutta Framework
|
||||
# Copyright © 2026 Lance Edgar
|
||||
#
|
||||
# This file is part of WuttaPOS.
|
||||
#
|
||||
# WuttaPOS is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
WuttaPOS - data model
|
||||
"""
|
||||
|
||||
from wuttjamaican.db.model import *
|
||||
|
||||
from .departments import Department
|
||||
from .products import (
|
||||
Product,
|
||||
ProductInventory,
|
||||
InventoryAdjustmentType,
|
||||
InventoryAdjustment,
|
||||
)
|
||||
88
src/wuttapos/db/model/departments.py
Normal file
88
src/wuttapos/db/model/departments.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# WuttaPOS -- Point of Sale system based on Wutta Framework
|
||||
# Copyright © 2026 Lance Edgar
|
||||
#
|
||||
# This file is part of WuttaPOS.
|
||||
#
|
||||
# WuttaPOS is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
Model for Departments
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import orm
|
||||
|
||||
from wuttjamaican.db import model
|
||||
|
||||
|
||||
class Department(model.Base):
|
||||
"""
|
||||
Represents an organizational department, for products and/or personnel.
|
||||
"""
|
||||
|
||||
__tablename__ = "department"
|
||||
__versioned__ = {}
|
||||
__wutta_hint__ = {
|
||||
"model_title": "Department",
|
||||
"model_title_plural": "Departments",
|
||||
}
|
||||
|
||||
uuid = model.uuid_column()
|
||||
|
||||
department_id = sa.Column(
|
||||
sa.String(length=20),
|
||||
nullable=False,
|
||||
doc="""
|
||||
Unique identifier for the department.
|
||||
""",
|
||||
)
|
||||
|
||||
name = sa.Column(
|
||||
sa.String(length=100),
|
||||
nullable=False,
|
||||
doc="""
|
||||
Name of the department.
|
||||
""",
|
||||
)
|
||||
|
||||
for_products = sa.Column(
|
||||
sa.Boolean(),
|
||||
nullable=False,
|
||||
doc="""
|
||||
Indicates the department exists to organize products.
|
||||
""",
|
||||
)
|
||||
|
||||
for_personnel = sa.Column(
|
||||
sa.Boolean(),
|
||||
nullable=False,
|
||||
doc="""
|
||||
Indicates the department exists to organize personnel.
|
||||
""",
|
||||
)
|
||||
|
||||
exempt_from_gross_sales = sa.Column(
|
||||
sa.Boolean(),
|
||||
nullable=True,
|
||||
doc="""
|
||||
Indicates products in this department do not count toward gross sales.
|
||||
""",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name or ""
|
||||
283
src/wuttapos/db/model/products.py
Normal file
283
src/wuttapos/db/model/products.py
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
"""
|
||||
Model definition for Products
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import orm
|
||||
|
||||
from wuttjamaican.db import model
|
||||
from wuttjamaican.db.util import UUID
|
||||
|
||||
|
||||
class Product(model.Base):
|
||||
"""
|
||||
Represents an item for sale (usually).
|
||||
"""
|
||||
|
||||
__tablename__ = "product"
|
||||
__versioned__ = {}
|
||||
__wutta_hint__ = {
|
||||
"model_title": "Product",
|
||||
"model_title_plural": "Products",
|
||||
}
|
||||
|
||||
uuid = model.uuid_column()
|
||||
|
||||
product_id = sa.Column(
|
||||
sa.String(length=20),
|
||||
nullable=False,
|
||||
doc="""
|
||||
Unique identifier for the product.
|
||||
""",
|
||||
)
|
||||
|
||||
brand_name = sa.Column(
|
||||
sa.String(length=100),
|
||||
nullable=True,
|
||||
doc="""
|
||||
Brand name for the product, if applicable.
|
||||
""",
|
||||
)
|
||||
|
||||
description = sa.Column(
|
||||
sa.String(length=255),
|
||||
nullable=False,
|
||||
doc="""
|
||||
Description of the product.
|
||||
""",
|
||||
)
|
||||
|
||||
size = sa.Column(
|
||||
sa.String(length=30),
|
||||
nullable=True,
|
||||
doc="""
|
||||
Size of the product.
|
||||
""",
|
||||
)
|
||||
|
||||
sold_by_weight = sa.Column(
|
||||
sa.Boolean(),
|
||||
nullable=False,
|
||||
doc="""
|
||||
Indicates the item is sold by weight, vs. by single units.
|
||||
""",
|
||||
)
|
||||
|
||||
department_uuid = model.uuid_fk_column("department.uuid", nullable=False)
|
||||
department = orm.relationship(
|
||||
"Department",
|
||||
doc="""
|
||||
Department to which the product belongs.
|
||||
""",
|
||||
)
|
||||
|
||||
special_order = sa.Column(
|
||||
sa.Boolean(),
|
||||
nullable=True,
|
||||
doc="""
|
||||
Indicates the item is not normally carried, must be ordered specially.
|
||||
""",
|
||||
)
|
||||
|
||||
case_size = sa.Column(
|
||||
sa.Numeric(precision=9, scale=4),
|
||||
nullable=True,
|
||||
doc="""
|
||||
Number of units in a case for this item, if applicable.
|
||||
""",
|
||||
)
|
||||
|
||||
unit_cost = sa.Column(
|
||||
sa.Numeric(precision=9, scale=5),
|
||||
nullable=True,
|
||||
doc="""
|
||||
Current cost (from the vendor, to the retailer) for one unit of the item.
|
||||
""",
|
||||
)
|
||||
|
||||
unit_price_reg = sa.Column(
|
||||
sa.Numeric(precision=8, scale=3),
|
||||
nullable=True,
|
||||
doc="""
|
||||
Regular price (to the customer) for one unit of product.
|
||||
""",
|
||||
)
|
||||
|
||||
notes = sa.Column(
|
||||
sa.Text(),
|
||||
nullable=True,
|
||||
doc="""
|
||||
Arbitrary notes for the product.
|
||||
""",
|
||||
)
|
||||
|
||||
inventory = orm.relationship(
|
||||
"ProductInventory",
|
||||
doc="""
|
||||
Reference to the live inventory record for this product.
|
||||
""",
|
||||
uselist=False,
|
||||
back_populates="product",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
@property
|
||||
def full_description(self):
|
||||
fields = [self.brand_name or "", self.description or "", self.size or ""]
|
||||
fields = [f.strip() for f in fields if f.strip()]
|
||||
return " ".join(fields)
|
||||
|
||||
def __str__(self):
|
||||
return self.full_description
|
||||
|
||||
|
||||
class ProductInventory(model.Base):
|
||||
"""
|
||||
Contains the live inventory counts for products.
|
||||
"""
|
||||
|
||||
__tablename__ = "product_inventory"
|
||||
__wutta_hint__ = {
|
||||
"model_title": "Product Inventory",
|
||||
"model_title_plural": "Product Inventory",
|
||||
}
|
||||
|
||||
uuid = model.uuid_column()
|
||||
|
||||
product_uuid = model.uuid_fk_column("product.uuid", nullable=False)
|
||||
product = orm.relationship(
|
||||
"Product",
|
||||
doc="""
|
||||
Reference to the product.
|
||||
""",
|
||||
back_populates="inventory",
|
||||
)
|
||||
|
||||
on_hand = sa.Column(
|
||||
sa.Numeric(precision=9, scale=4),
|
||||
nullable=True,
|
||||
doc="""
|
||||
Unit quantity of product which is currently on hand.
|
||||
""",
|
||||
)
|
||||
|
||||
on_order = sa.Column(
|
||||
sa.Numeric(precision=9, scale=4),
|
||||
nullable=True,
|
||||
doc="""
|
||||
Unit quantity of product which is currently on order.
|
||||
""",
|
||||
)
|
||||
|
||||
adjustments = orm.relationship(
|
||||
"InventoryAdjustment",
|
||||
back_populates="inventory",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.product or "")
|
||||
|
||||
|
||||
class InventoryAdjustmentType(model.Base):
|
||||
"""
|
||||
Possible types of inventory adjustments.
|
||||
"""
|
||||
|
||||
__tablename__ = "inventory_adjustment_type"
|
||||
__versioned__ = {}
|
||||
__wutta_hint__ = {
|
||||
"model_title": "Inventory Adjustment Type",
|
||||
"model_title_plural": "Inventory Adjustment Types",
|
||||
}
|
||||
|
||||
uuid = model.uuid_column()
|
||||
|
||||
type_code = sa.Column(
|
||||
sa.Integer(),
|
||||
nullable=False,
|
||||
unique=True,
|
||||
doc="""
|
||||
Code indicating the type of inventory adjustment.
|
||||
""",
|
||||
)
|
||||
|
||||
name = sa.Column(
|
||||
sa.String(length=100),
|
||||
nullable=False,
|
||||
doc="""
|
||||
Name for the adjustment type.
|
||||
""",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name or ""
|
||||
|
||||
|
||||
class InventoryAdjustment(model.Base):
|
||||
"""
|
||||
Represents any adjustment to inventory.
|
||||
"""
|
||||
|
||||
__tablename__ = "inventory_adjustment"
|
||||
__wutta_hint__ = {
|
||||
"model_title": "Inventory Adjustment",
|
||||
"model_title_plural": "Inventory Adjustments",
|
||||
}
|
||||
|
||||
uuid = model.uuid_column()
|
||||
|
||||
inventory_uuid = model.uuid_fk_column("product_inventory.uuid", nullable=True)
|
||||
inventory = orm.relationship(
|
||||
"ProductInventory",
|
||||
doc="""
|
||||
Reference to the product inventory record.
|
||||
""",
|
||||
back_populates="adjustments",
|
||||
)
|
||||
|
||||
adjusted = sa.Column(
|
||||
sa.DateTime(),
|
||||
nullable=False,
|
||||
doc="""
|
||||
Date and time (in UTC) when the adjustment occurred.
|
||||
""",
|
||||
)
|
||||
|
||||
effective_date = sa.Column(
|
||||
sa.Date(),
|
||||
nullable=False,
|
||||
doc="""
|
||||
Effective date (in local time zone) for the adjustment.
|
||||
""",
|
||||
)
|
||||
|
||||
adjustment_type_uuid = model.uuid_fk_column(
|
||||
"inventory_adjustment_type.uuid", nullable=True
|
||||
)
|
||||
adjustment_type = orm.relationship(
|
||||
"InventoryAdjustmentType",
|
||||
doc="""
|
||||
Reference to the adjustment type record.
|
||||
""",
|
||||
)
|
||||
|
||||
amount = sa.Column(
|
||||
sa.Numeric(precision=9, scale=4),
|
||||
nullable=False,
|
||||
doc="""
|
||||
Amount of the adjustment; may be positive or negative.
|
||||
""",
|
||||
)
|
||||
|
||||
source = sa.Column(
|
||||
sa.String(length=100),
|
||||
nullable=True,
|
||||
doc="""
|
||||
Arbitrary string identifying the source of the adjustment, if applicable.
|
||||
""",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.inventory or "")
|
||||
0
src/wuttapos/server/__init__.py
Normal file
0
src/wuttapos/server/__init__.py
Normal file
66
src/wuttapos/server/app.py
Normal file
66
src/wuttapos/server/app.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# WuttaPOS -- Point of Sale system based on Wutta Framework
|
||||
# Copyright © 2026 Lance Edgar
|
||||
#
|
||||
# This file is part of WuttaPOS.
|
||||
#
|
||||
# WuttaPOS is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
WuttaPOS server web app
|
||||
"""
|
||||
|
||||
from wuttaweb import app as base
|
||||
|
||||
|
||||
def main(global_config, **settings):
|
||||
"""
|
||||
Make and return the WSGI app (Paste entry point).
|
||||
"""
|
||||
# prefer wuttapos templates over wuttaweb
|
||||
settings.setdefault(
|
||||
"mako.directories",
|
||||
[
|
||||
"wuttapos.server:templates",
|
||||
"wuttaweb:templates",
|
||||
],
|
||||
)
|
||||
|
||||
# make config objects
|
||||
wutta_config = base.make_wutta_config(settings)
|
||||
pyramid_config = base.make_pyramid_config(settings)
|
||||
|
||||
# bring in the rest of wuttapos
|
||||
pyramid_config.include("wuttapos.server.static")
|
||||
pyramid_config.include("wuttapos.server.subscribers")
|
||||
pyramid_config.include("wuttapos.server.views")
|
||||
|
||||
return pyramid_config.make_wsgi_app()
|
||||
|
||||
|
||||
def make_wsgi_app():
|
||||
"""
|
||||
Make and return the WSGI app (generic entry point).
|
||||
"""
|
||||
return base.make_wsgi_app(main)
|
||||
|
||||
|
||||
def make_asgi_app():
|
||||
"""
|
||||
Make and return the ASGI app (generic entry point).
|
||||
"""
|
||||
return base.make_asgi_app(main)
|
||||
0
src/wuttapos/server/forms/__init__.py
Normal file
0
src/wuttapos/server/forms/__init__.py
Normal file
96
src/wuttapos/server/forms/schema.py
Normal file
96
src/wuttapos/server/forms/schema.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# WuttaPOS -- Point of Sale system based on Wutta Framework
|
||||
# Copyright © 2026 Lance Edgar
|
||||
#
|
||||
# This file is part of WuttaPOS.
|
||||
#
|
||||
# WuttaPOS is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
Form schema types
|
||||
"""
|
||||
|
||||
from wuttaweb.forms.schema import ObjectRef
|
||||
|
||||
|
||||
class DepartmentRef(ObjectRef):
|
||||
"""
|
||||
Schema type for a
|
||||
:class:`~wuttapos.db.model.departments.Department` reference field.
|
||||
|
||||
This is a subclass of
|
||||
:class:`~wuttaweb:wuttaweb.forms.schema.ObjectRef`.
|
||||
"""
|
||||
|
||||
@property
|
||||
def model_class(self):
|
||||
model = self.app.model
|
||||
return model.Department
|
||||
|
||||
def sort_query(self, query):
|
||||
return query.order_by(self.model_class.name)
|
||||
|
||||
def get_object_url(self, obj):
|
||||
department = obj
|
||||
return self.request.route_url("departments.view", uuid=department.uuid)
|
||||
|
||||
|
||||
class ProductRef(ObjectRef):
|
||||
"""
|
||||
Schema type for a
|
||||
:class:`~wuttapos.db.model.products.Product` reference field.
|
||||
|
||||
This is a subclass of
|
||||
:class:`~wuttaweb:wuttaweb.forms.schema.ObjectRef`.
|
||||
"""
|
||||
|
||||
@property
|
||||
def model_class(self):
|
||||
model = self.app.model
|
||||
return model.Product
|
||||
|
||||
def sort_query(self, query):
|
||||
return query.order_by(self.model_class.description)
|
||||
|
||||
def get_object_url(self, obj):
|
||||
product = obj
|
||||
return self.request.route_url("products.view", uuid=product.uuid)
|
||||
|
||||
|
||||
class InventoryAdjustmentTypeRef(ObjectRef):
|
||||
"""
|
||||
Schema type for a
|
||||
:class:`~wuttapos.db.model.products.InventoryAdjustmentType`
|
||||
reference field.
|
||||
|
||||
This is a subclass of
|
||||
:class:`~wuttaweb:wuttaweb.forms.schema.ObjectRef`.
|
||||
"""
|
||||
|
||||
@property
|
||||
def model_class(self):
|
||||
model = self.app.model
|
||||
return model.InventoryAdjustmentType
|
||||
|
||||
def sort_query(self, query):
|
||||
return query.order_by(self.model_class.name)
|
||||
|
||||
def get_object_url(self, obj):
|
||||
adjustment_type = obj
|
||||
return self.request.route_url(
|
||||
"inventory_adjustment_types.view", uuid=adjustment_type.uuid
|
||||
)
|
||||
83
src/wuttapos/server/menus.py
Normal file
83
src/wuttapos/server/menus.py
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# WuttaPOS -- Point of Sale system based on Wutta Framework
|
||||
# Copyright © 2026 Lance Edgar
|
||||
#
|
||||
# This file is part of WuttaPOS.
|
||||
#
|
||||
# WuttaPOS is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
WuttaPOS server menu
|
||||
"""
|
||||
|
||||
from wuttaweb import menus as base
|
||||
|
||||
|
||||
class WuttaPosMenuHandler(base.MenuHandler):
|
||||
"""
|
||||
WuttaPOS menu handler
|
||||
"""
|
||||
|
||||
def make_menus(self, request, **kwargs):
|
||||
|
||||
# nb. the products menu is just an example; you should
|
||||
# replace it and add more as needed
|
||||
|
||||
return [
|
||||
self.make_products_menu(request),
|
||||
self.make_admin_menu(request, include_people=True),
|
||||
]
|
||||
|
||||
def make_products_menu(self, request):
|
||||
return {
|
||||
"title": "Products",
|
||||
"type": "menu",
|
||||
"items": [
|
||||
{
|
||||
"title": "Products",
|
||||
"route": "products",
|
||||
"perm": "products.list",
|
||||
},
|
||||
{"type": "sep"},
|
||||
{
|
||||
"title": "Departments",
|
||||
"route": "departments",
|
||||
"perm": "departments.list",
|
||||
},
|
||||
{"type": "sep"},
|
||||
{
|
||||
"title": "Inventory Adjustments",
|
||||
"route": "inventory_adjustments",
|
||||
"perm": "inventory_adjustments.list",
|
||||
},
|
||||
{
|
||||
"title": "New Inventory Adjustment",
|
||||
"route": "inventory_adjustments.create",
|
||||
"perm": "inventory_adjustments.create",
|
||||
},
|
||||
{
|
||||
"title": "Inventory Adjustment Types",
|
||||
"route": "inventory_adjustment_types",
|
||||
"perm": "inventory_adjustment_types.list",
|
||||
},
|
||||
# {
|
||||
# "title": "Vendors",
|
||||
# "route": "vendors",
|
||||
# "perm": "vendors.list",
|
||||
# },
|
||||
],
|
||||
}
|
||||
47
src/wuttapos/server/static/__init__.py
Normal file
47
src/wuttapos/server/static/__init__.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# WuttaPOS -- Point of Sale system based on Wutta Framework
|
||||
# Copyright © 2026 Lance Edgar
|
||||
#
|
||||
# This file is part of WuttaPOS.
|
||||
#
|
||||
# WuttaPOS is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
Static assets
|
||||
"""
|
||||
|
||||
# from fanstatic import Library, Resource
|
||||
|
||||
|
||||
# # libcache
|
||||
# libcache = Library('wuttapos_libcache', 'libcache')
|
||||
# bb_vue_js = Resource(libcache, 'vue.esm-browser-3.3.11.prod.js')
|
||||
# bb_oruga_js = Resource(libcache, 'oruga-0.8.10.js')
|
||||
# bb_oruga_bulma_js = Resource(libcache, 'oruga-bulma-0.3.0.js')
|
||||
# bb_oruga_bulma_css = Resource(libcache, 'oruga-bulma-0.3.0.css')
|
||||
# bb_fontawesome_svg_core_js = Resource(libcache, 'fontawesome-svg-core-6.5.2.js')
|
||||
# bb_free_solid_svg_icons_js = Resource(libcache, 'free-solid-svg-icons-6.5.2.js')
|
||||
# bb_vue_fontawesome_js = Resource(libcache, 'vue-fontawesome-3.0.6.index.es.js')
|
||||
|
||||
|
||||
def includeme(config):
|
||||
config.include("wuttaweb.static")
|
||||
config.add_static_view(
|
||||
"wuttapos",
|
||||
"wuttapos.server:static",
|
||||
cache_max_age=3600,
|
||||
)
|
||||
2
src/wuttapos/server/static/libcache/README
Normal file
2
src/wuttapos/server/static/libcache/README
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Place files in this folder, which correspond to the Resource()
|
||||
definitions found in `wuttapos/server/static/__init__.py`
|
||||
37
src/wuttapos/server/subscribers.py
Normal file
37
src/wuttapos/server/subscribers.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# WuttaPOS -- Point of Sale system based on Wutta Framework
|
||||
# Copyright © 2026 Lance Edgar
|
||||
#
|
||||
# This file is part of WuttaPOS.
|
||||
#
|
||||
# WuttaPOS is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
Pyramid event subscribers
|
||||
"""
|
||||
|
||||
import wuttapos
|
||||
|
||||
|
||||
def add_wuttapos_to_context(event):
|
||||
renderer_globals = event
|
||||
renderer_globals["wuttapos"] = wuttapos
|
||||
|
||||
|
||||
def includeme(config):
|
||||
config.include("wuttaweb.subscribers")
|
||||
config.add_subscriber(add_wuttapos_to_context, "pyramid.events.BeforeRender")
|
||||
16
src/wuttapos/server/templates/base_meta.mako
Normal file
16
src/wuttapos/server/templates/base_meta.mako
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<%inherit file="wuttaweb:templates/base_meta.mako" />
|
||||
|
||||
## TODO: you can override parent template as needed below, or you
|
||||
## can simply delete this file if no customizations are needed
|
||||
|
||||
<%def name="favicon()">
|
||||
${parent.favicon()}
|
||||
</%def>
|
||||
|
||||
<%def name="header_logo()">
|
||||
${parent.header_logo()}
|
||||
</%def>
|
||||
|
||||
<%def name="footer()">
|
||||
${parent.footer()}
|
||||
</%def>
|
||||
36
src/wuttapos/server/views/__init__.py
Normal file
36
src/wuttapos/server/views/__init__.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# WuttaPOS -- Point of Sale system based on Wutta Framework
|
||||
# Copyright © 2026 Lance Edgar
|
||||
#
|
||||
# This file is part of WuttaPOS.
|
||||
#
|
||||
# WuttaPOS is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
WuttaPOS server views
|
||||
"""
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
# wuttaweb
|
||||
config.include("wuttaweb.views.essential")
|
||||
|
||||
# wuttapos
|
||||
config.include("wuttapos.server.views.departments")
|
||||
config.include("wuttapos.server.views.products")
|
||||
config.include("wuttapos.server.views.inventory_adjustments")
|
||||
126
src/wuttapos/server/views/departments.py
Normal file
126
src/wuttapos/server/views/departments.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# WuttaPOS -- Point of Sale system based on Wutta Framework
|
||||
# Copyright © 2026 Lance Edgar
|
||||
#
|
||||
# This file is part of WuttaPOS.
|
||||
#
|
||||
# WuttaPOS is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
Master view for Departments
|
||||
"""
|
||||
|
||||
from wuttaweb.views import MasterView
|
||||
|
||||
from wuttapos.db.model import Department, Product
|
||||
|
||||
|
||||
class DepartmentView(MasterView):
|
||||
"""
|
||||
Master view for Departments
|
||||
"""
|
||||
|
||||
model_class = Department
|
||||
model_title = "Department"
|
||||
model_title_plural = "Departments"
|
||||
|
||||
route_prefix = "departments"
|
||||
url_prefix = "/departments"
|
||||
|
||||
creatable = True
|
||||
editable = True
|
||||
deletable = True
|
||||
|
||||
labels = {
|
||||
"department_id": "Department ID",
|
||||
}
|
||||
|
||||
grid_columns = [
|
||||
"department_id",
|
||||
"name",
|
||||
"for_products",
|
||||
"for_personnel",
|
||||
"exempt_from_gross_sales",
|
||||
]
|
||||
|
||||
form_fields = [
|
||||
"department_id",
|
||||
"name",
|
||||
"for_products",
|
||||
"for_personnel",
|
||||
"exempt_from_gross_sales",
|
||||
]
|
||||
|
||||
has_rows = True
|
||||
row_model_class = Product
|
||||
|
||||
row_grid_columns = [
|
||||
"product_id",
|
||||
"brand_name",
|
||||
"description",
|
||||
"size",
|
||||
"sold_by_weight",
|
||||
"case_size",
|
||||
"special_order",
|
||||
"unit_price_reg",
|
||||
]
|
||||
|
||||
def configure_grid(self, grid):
|
||||
g = grid
|
||||
super().configure_grid(g)
|
||||
|
||||
# name
|
||||
g.set_link("name")
|
||||
|
||||
def get_row_grid_data(self, obj):
|
||||
department = obj
|
||||
model = self.app.model
|
||||
session = self.app.get_session(department)
|
||||
|
||||
return session.query(model.Product).filter(
|
||||
model.Product.department == department
|
||||
)
|
||||
|
||||
def configure_row_grid(self, grid):
|
||||
g = grid
|
||||
super().configure_row_grid(g)
|
||||
|
||||
# links
|
||||
g.set_link("product_id")
|
||||
g.set_link("brand_name")
|
||||
g.set_link("description")
|
||||
g.set_link("size")
|
||||
|
||||
# currency
|
||||
g.set_renderer("unit_price_reg", "currency")
|
||||
|
||||
# view action
|
||||
def view_url(product, i):
|
||||
return self.request.route_url("products.view", uuid=product.uuid)
|
||||
|
||||
g.add_action("view", url=view_url, icon="eye")
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
||||
DepartmentView = kwargs.get("DepartmentView", base["DepartmentView"])
|
||||
DepartmentView.defaults(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
defaults(config)
|
||||
266
src/wuttapos/server/views/inventory_adjustments.py
Normal file
266
src/wuttapos/server/views/inventory_adjustments.py
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# WuttaPOS -- Point of Sale system based on Wutta Framework
|
||||
# Copyright © 2026 Lance Edgar
|
||||
#
|
||||
# This file is part of WuttaPOS.
|
||||
#
|
||||
# WuttaPOS is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
Master view for Inventory Adjustments
|
||||
"""
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from wuttaweb.views import MasterView
|
||||
from wuttaweb.forms import widgets
|
||||
from wuttaweb.forms.schema import WuttaQuantity
|
||||
|
||||
from wuttapos.db.model.products import (
|
||||
InventoryAdjustment,
|
||||
InventoryAdjustmentType,
|
||||
)
|
||||
from wuttapos.server.forms.schema import ProductRef, InventoryAdjustmentTypeRef
|
||||
|
||||
|
||||
def render_grid_product(adjustment, field, value):
|
||||
product = adjustment.inventory.product
|
||||
return str(product)
|
||||
|
||||
|
||||
class InventoryAdjustmentTypeView(MasterView):
|
||||
"""
|
||||
Master view for Inventory Adjustment Types
|
||||
"""
|
||||
|
||||
model_class = InventoryAdjustmentType
|
||||
model_title = "Inventory Adjustment Type"
|
||||
model_title_plural = "Inventory Adjustment Types"
|
||||
|
||||
route_prefix = "inventory_adjustment_types"
|
||||
url_prefix = "/inventory/adjustment-types"
|
||||
|
||||
creatable = True
|
||||
editable = True
|
||||
deletable = True
|
||||
|
||||
grid_columns = [
|
||||
"type_code",
|
||||
"name",
|
||||
]
|
||||
|
||||
form_fields = [
|
||||
"type_code",
|
||||
"name",
|
||||
]
|
||||
|
||||
has_rows = True
|
||||
row_model_class = InventoryAdjustment
|
||||
row_model_title_plural = "Inventory Adjustments"
|
||||
|
||||
row_grid_columns = [
|
||||
"product",
|
||||
"adjusted",
|
||||
"effective_date",
|
||||
"adjustment_type",
|
||||
"amount",
|
||||
"source",
|
||||
]
|
||||
|
||||
rows_sort_defaults = ("adjusted", "desc")
|
||||
|
||||
def configure_grid(self, grid):
|
||||
g = grid
|
||||
super().configure_grid(g)
|
||||
|
||||
# links
|
||||
g.set_link("type_code")
|
||||
g.set_link("name")
|
||||
|
||||
def get_row_grid_data(self, obj):
|
||||
adjustment_type = obj
|
||||
model = self.app.model
|
||||
session = self.app.get_session(adjustment_type)
|
||||
|
||||
return session.query(model.InventoryAdjustment).filter(
|
||||
model.InventoryAdjustment.adjustment_type == adjustment_type
|
||||
)
|
||||
|
||||
def configure_row_grid(self, grid):
|
||||
g = grid
|
||||
super().configure_row_grid(g)
|
||||
model = self.app.model
|
||||
session = self.Session()
|
||||
|
||||
# product
|
||||
g.set_renderer("product", render_grid_product)
|
||||
g.set_link("product")
|
||||
|
||||
# view action
|
||||
def view_url(adjustment, i):
|
||||
return self.request.route_url(
|
||||
"inventory_adjustments.view", uuid=adjustment.uuid
|
||||
)
|
||||
|
||||
g.add_action("view", url=view_url, icon="eye")
|
||||
|
||||
|
||||
class InventoryAdjustmentView(MasterView):
|
||||
"""
|
||||
Master view for Inventory Adjustments
|
||||
"""
|
||||
|
||||
model_class = InventoryAdjustment
|
||||
model_title = "Inventory Adjustment"
|
||||
model_title_plural = "Inventory Adjustments"
|
||||
|
||||
route_prefix = "inventory_adjustments"
|
||||
url_prefix = "/inventory/adjustments"
|
||||
|
||||
creatable = True
|
||||
editable = False
|
||||
deletable = False
|
||||
|
||||
grid_columns = [
|
||||
"product",
|
||||
"adjusted",
|
||||
"effective_date",
|
||||
"adjustment_type",
|
||||
"amount",
|
||||
"source",
|
||||
]
|
||||
|
||||
form_fields = [
|
||||
"product",
|
||||
"effective_date",
|
||||
"adjusted",
|
||||
"adjustment_type",
|
||||
"amount",
|
||||
"source",
|
||||
]
|
||||
|
||||
def configure_grid(self, grid):
|
||||
g = grid
|
||||
super().configure_grid(g)
|
||||
model = self.app.model
|
||||
session = self.Session()
|
||||
|
||||
# product
|
||||
g.set_renderer("product", render_grid_product)
|
||||
g.set_link("product")
|
||||
|
||||
# adjustment_type
|
||||
g.set_joiner(
|
||||
"adjustment_type",
|
||||
lambda q: q.outerjoin(
|
||||
model.InventoryAdjustmentType,
|
||||
model.InventoryAdjustmentType.type_code
|
||||
== self.model_class.adjustment_type_code,
|
||||
),
|
||||
)
|
||||
g.set_sorter("adjustment_type", model.InventoryAdjustmentType.name)
|
||||
g.remove_filter("adjustment_type_code")
|
||||
types = session.query(model.InventoryAdjustmentType).order_by(
|
||||
model.InventoryAdjustmentType.name
|
||||
)
|
||||
choices = OrderedDict([(typ.type_code, typ.name) for typ in types])
|
||||
g.set_filter(
|
||||
"adjustment_type",
|
||||
model.InventoryAdjustmentType.type_code,
|
||||
verbs=["equal", "not_equal"],
|
||||
choices=choices,
|
||||
)
|
||||
|
||||
def configure_form(self, form):
|
||||
f = form
|
||||
super().configure_form(f)
|
||||
model = self.app.model
|
||||
session = self.Session()
|
||||
adjustment = f.model_instance
|
||||
|
||||
# product
|
||||
f.set_node("product", ProductRef(self.request))
|
||||
if self.creating:
|
||||
if uuid := self.request.GET.get("product"):
|
||||
if product := session.get(model.Product, uuid):
|
||||
f.set_default("product", product)
|
||||
f.fields.insert_after("product", "on_hand")
|
||||
f.set_node("on_hand", WuttaQuantity(self.request))
|
||||
f.set_readonly("on_hand")
|
||||
f.set_default(
|
||||
"on_hand",
|
||||
product.inventory.on_hand if product.inventory else None,
|
||||
)
|
||||
else:
|
||||
f.set_default("product", adjustment.inventory.product)
|
||||
|
||||
# adjustment_type
|
||||
f.set_node(
|
||||
"adjustment_type",
|
||||
InventoryAdjustmentTypeRef(self.request, empty_option=True),
|
||||
)
|
||||
f.set_required("adjustment_type", False)
|
||||
|
||||
# adjusted
|
||||
if self.creating:
|
||||
f.remove("adjusted")
|
||||
|
||||
# effective_date
|
||||
if self.creating:
|
||||
f.remove("effective_date")
|
||||
|
||||
# amount
|
||||
f.set_node("amount", WuttaQuantity(self.request))
|
||||
|
||||
def objectify(self, form):
|
||||
model = self.app.model
|
||||
adjustment = super().objectify(form)
|
||||
|
||||
if self.creating:
|
||||
|
||||
inventory = form.validated["product"].inventory
|
||||
if not inventory:
|
||||
inventory = model.ProductInventory(product=form.validated["product"])
|
||||
|
||||
adjustment.inventory = inventory
|
||||
adjustment.adjusted = self.app.make_utc()
|
||||
adjustment.effective_date = self.app.localtime().date()
|
||||
|
||||
if adjustment.adjustment_type == -99:
|
||||
adjustment.adjustment_type = None
|
||||
|
||||
inventory.on_hand = (inventory.on_hand or 0) + adjustment.amount
|
||||
|
||||
return adjustment
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
||||
InventoryAdjustmentTypeView = kwargs.get(
|
||||
"InventoryAdjustmentTypeView", base["InventoryAdjustmentTypeView"]
|
||||
)
|
||||
InventoryAdjustmentTypeView.defaults(config)
|
||||
|
||||
InventoryAdjustmentView = kwargs.get(
|
||||
"InventoryAdjustmentView", base["InventoryAdjustmentView"]
|
||||
)
|
||||
InventoryAdjustmentView.defaults(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
defaults(config)
|
||||
145
src/wuttapos/server/views/products.py
Normal file
145
src/wuttapos/server/views/products.py
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# WuttaPOS -- Point of Sale system based on Wutta Framework
|
||||
# Copyright © 2026 Lance Edgar
|
||||
#
|
||||
# This file is part of WuttaPOS.
|
||||
#
|
||||
# WuttaPOS is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
Master view for Products
|
||||
"""
|
||||
|
||||
from wuttaweb.views import MasterView
|
||||
from wuttaweb.forms.schema import WuttaQuantity, WuttaMoney
|
||||
|
||||
from wuttapos.db.model.products import Product
|
||||
from wuttapos.server.forms.schema import DepartmentRef
|
||||
|
||||
|
||||
class ProductView(MasterView):
|
||||
"""
|
||||
Master view for Products
|
||||
"""
|
||||
|
||||
model_class = Product
|
||||
model_title = "Product"
|
||||
model_title_plural = "Products"
|
||||
|
||||
route_prefix = "products"
|
||||
url_prefix = "/products"
|
||||
|
||||
creatable = True
|
||||
editable = True
|
||||
deletable = True
|
||||
|
||||
labels = {
|
||||
"product_id": "Product ID",
|
||||
}
|
||||
|
||||
grid_columns = [
|
||||
"product_id",
|
||||
"brand_name",
|
||||
"description",
|
||||
"size",
|
||||
"sold_by_weight",
|
||||
"case_size",
|
||||
"department",
|
||||
"special_order",
|
||||
"unit_price_reg",
|
||||
]
|
||||
|
||||
form_fields = [
|
||||
"product_id",
|
||||
"brand_name",
|
||||
"description",
|
||||
"size",
|
||||
"department",
|
||||
"sold_by_weight",
|
||||
"case_size",
|
||||
"special_order",
|
||||
"unit_cost",
|
||||
"unit_price_reg",
|
||||
"notes",
|
||||
"on_hand",
|
||||
"on_order",
|
||||
]
|
||||
|
||||
def configure_grid(self, grid):
|
||||
g = grid
|
||||
super().configure_grid(g)
|
||||
|
||||
# links
|
||||
g.set_link("product_id")
|
||||
g.set_link("brand_name")
|
||||
g.set_link("description")
|
||||
g.set_link("size")
|
||||
|
||||
# currency
|
||||
g.set_renderer("unit_cost", "currency", scale=4)
|
||||
g.set_renderer("unit_price_reg", "currency")
|
||||
|
||||
def configure_form(self, form):
|
||||
f = form
|
||||
super().configure_form(f)
|
||||
product = f.model_instance
|
||||
|
||||
# department
|
||||
f.set_node("department", DepartmentRef(self.request))
|
||||
|
||||
# case_size
|
||||
f.set_node("case_size", WuttaQuantity(self.request))
|
||||
|
||||
# unit_cost
|
||||
f.set_node("unit_cost", WuttaMoney(self.request, scale=4))
|
||||
|
||||
# unit_price_reg
|
||||
f.set_node("unit_price_reg", WuttaMoney(self.request))
|
||||
|
||||
# notes
|
||||
f.set_widget("notes", "notes")
|
||||
|
||||
# on_hand
|
||||
f.set_node("on_hand", WuttaQuantity(self.request))
|
||||
if self.creating or self.editing:
|
||||
f.remove("on_hand")
|
||||
else:
|
||||
f.set_default(
|
||||
"on_hand",
|
||||
product.inventory.on_hand if product.inventory else None,
|
||||
)
|
||||
|
||||
# on_order
|
||||
f.set_node("on_order", WuttaQuantity(self.request))
|
||||
if self.creating or self.editing:
|
||||
f.remove("on_order")
|
||||
else:
|
||||
f.set_default(
|
||||
"on_order",
|
||||
product.inventory.on_order if product.inventory else None,
|
||||
)
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
||||
ProductView = kwargs.get("ProductView", base["ProductView"])
|
||||
ProductView.defaults(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
defaults(config)
|
||||
Loading…
Add table
Add a link
Reference in a new issue