Added proper agents config for mcp servers and skills.

Added the menu category delete route.
Fix: Show blank vouchers in tables
Fix: In rare cases, the old settements can stick around. fixed it.
This commit is contained in:
2026-08-10 08:25:12 +00:00
parent 2e5543a2eb
commit 38a8656008
19 changed files with 193 additions and 84 deletions

31
.agents/mcp_config.json Normal file
View File

@ -0,0 +1,31 @@
{
"mcpServers": {
"postgres-db": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-postgres",
"${POSTGRES_URL}"
],
"env": {
"POSTGRES_URL": "${env:SQLALCHEMY_DATABASE_URI}"
}
},
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"."
]
},
"angular-cli": {
"command": "npx",
"args": [
"-y",
"@angular/cli",
"mcp"
]
}
}
}

30
.agents/rules.md Normal file
View File

@ -0,0 +1,30 @@
# Project Architecture & Guidelines
## Monorepo Layout
- **Backend**: `./barker` (Python, managed via `uv`)
- **Frontend**: `./bookie` (Angular, managed via `npm`/`ng`)
- **Environment**: Root `.env` file containing shared/database variables.
---
## Execution Rules & Commands
### Backend (`/barker`)
- **Working Directory**: Always execute backend commands within `./barker` OR pass `--directory barker` to `uv`.
- **Package & Script Management**:
- Use `uv` exclusively. Never run bare `pip` or global `python`.
- Run scripts/tests: `uv run python <script>` or `uv run pytest` from inside `./barker`.
- Manage dependencies: `uv add <package>` or `uv remove <package>`.
- **Environment**: The root `.env` file (`../.env` relative to `barker`) contains the database connection string and secret keys.
### Frontend (`/bookie`)
- **Working Directory**: Always execute frontend commands within `./bookie`.
- **Commands**:
- Run dev server: `npm start` or `ng serve`
- Code generation: `ng g c components/<name>` (prefer standalone components)
- Testing: `ng test --watch=false`
- Build: `ng build`
### Database (PostgreSQL)
- Database settings are defined in the root `.env`.
- Inspect schema, run migrations, or check types strictly via database tools or `uv run` scripts inside `./barker`.

View File

@ -0,0 +1,17 @@
---
name: angular-signals
description: Writes modern Angular code using Standalone Components and Signals. Use whenever generating frontend UI or state logic.
---
# Modern Angular Standards
## Architecture
- NEVER generate or use `NgModule`.
- ALL components, directives, and pipes must be `standalone: true`.
## State Management (Signals)
- Prefer Angular Signals (`signal()`, `computed()`, `effect()`) for synchronous, local component state instead of `BehaviorSubject`.
- Use the new Signal-based inputs and outputs: `input()` and `output()` instead of `@Input()` and `@Output()`.
- Use standard RxJS strictly for asynchronous streams (e.g., HTTP requests, router events).
## Control Flow
- Use the new built-in control flow (`@if`, `@for`, `@switch`) instead of structural directives (`*ngIf`, `*ngFor`).

View File

@ -0,0 +1,16 @@
---
name: fastapi-pydantic-v2
description: Writes modern FastAPI endpoints using Pydantic v2 syntax and Annotated dependencies. Use whenever generating or refactoring API routes.
---
# FastAPI & Pydantic v2 Standards
## Pydantic v2 Rules
- NEVER use `.dict()` or `.json()`. ALWAYS use `.model_dump()` and `.model_dump_json()`.
- NEVER use `@validator` or `@root_validator`. ALWAYS use `@field_validator` and `@model_validator(mode="before|after")`.
- NEVER use `schema_extra`. Use `json_schema_extra` inside the `model_config` dict.
- Use `pydantic_settings.BaseSettings` for all environment variable configurations.
## FastAPI Rules
- ALWAYS use `typing.Annotated` for dependency injection (e.g., `db: Annotated[AsyncSession, Depends(get_db)]`).
- Do not use `def` for endpoints that perform I/O; always use `async def`.
- Raise `fastapi.HTTPException` for expected client errors instead of returning standard dictionaries.

View File

@ -0,0 +1,19 @@
---
name: sqlalchemy-async
description: Writes SQLAlchemy 2.0 async database queries using Psycopg3. Use whenever interacting with the PostgreSQL database.
---
# SQLAlchemy 2.0 Async Standards
## Model Definitions
- ALWAYS use SQLAlchemy 2.0 Declarative Base with `Mapped` and `mapped_column`.
- Example: `id: Mapped[int] = mapped_column(primary_key=True)`
- NEVER use the old `Column(Integer, primary_key=True)` syntax.
## Querying
- NEVER use `session.query()`. This is legacy 1.x syntax.
- ALWAYS use `sqlalchemy.select()` combined with `await session.execute(stmt)`.
- Extract results using `.scalars().all()` or `.scalar_one_or_none()`.
## Session Management
- Use `sqlalchemy.ext.asyncio.AsyncSession`.
- Ensure all database I/O is awaited.

View File

@ -0,0 +1,30 @@
---
name: strict-mypy
description: Enforces strict MyPy type checking conventions matching the project's pyproject.toml configuration. Use whenever writing or modifying Python code.
---
# Strict MyPy Typing Standards
This project uses a highly strict MyPy configuration (`strict = true`). When writing or refactoring Python code, you MUST adhere to the following rules to ensure the code passes type checking:
## 1. Absolute Signature Completeness
- EVERY function and method must have fully typed arguments and a return type (`disallow_untyped_defs`, `disallow_incomplete_defs`).
- ALWAYS specify `-> None` for functions and methods that do not return a value.
- NEVER leave type hints partially defined (e.g., typing only some arguments).
- Decorators must be completely type-hinted using `typing.Callable` or `typing.ParamSpec` (`disallow_untyped_decorators`).
## 2. No Implicit Optionals
- `arg: str = None` is strictly forbidden.
- You MUST explicitly union with None: `arg: str | None = None` (`no_implicit_optional`).
## 3. Strict Generics
- NEVER use bare collections as types.
- ALWAYS specify the inner types: use `list[str]`, `dict[str, int]`, or `tuple[int, ...]` instead of bare `list`, `dict`, or `tuple` (`disallow_any_generics`).
## 4. Restrictions on `Any` and Subclassing
- Do not subclass from untyped external libraries or `Any` (`disallow_subclassing_any`).
- Avoid using external types that have no stubs and resolve to `Any` (`disallow_any_unimported`).
- Refrain from returning `Any` to avoid contaminating the type inference of calling functions (`warn_return_any`).
## 5. Pydantic Strictness
- `pydantic.mypy` is active with strict initialization.
- When instantiating Pydantic models, ensure you are passing exactly the expected typed arguments, as extra or untyped arguments will fail type checks (`init_typed = true`, `init_forbid_extra = true`).

4
.vscode/launch.json vendored
View File

@ -6,14 +6,14 @@
"configurations": [
{
"name": "ng serve",
"type": "pwa-chrome",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: start",
"url": "http://localhost:4200/"
},
{
"name": "Python: FastAPI",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "barker",
"args": [],

View File

@ -9,7 +9,7 @@
"python.analysis.extraPaths": [
"./barker"
],
// Ruff (you have ruff installed via poetry)
// Ruff (you have ruff installed via uv)
"editor.formatOnSave": true,
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff"

3
.vscode/tasks.json vendored
View File

@ -27,6 +27,9 @@
"type": "npm",
"script": "test",
"isBackground": true,
"options": {
"cwd": "${workspaceFolder}/bookie"
},
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",

View File

@ -32,8 +32,8 @@ RUN \
FROM python:3.14-slim AS runner
LABEL maintainer="Amritanshu <docker@tanshu.com>"
RUN apt update \
&& apt install -y --no-install-recommends curl \
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
# Install uv
@ -70,16 +70,13 @@ COPY --from=builder /frontend/browser /app/static
# Sync the project
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=barker/uv.lock,target=uv.lock \
--mount=type=bind,source=barker/pyproject.toml,target=pyproject.toml \
uv sync --locked
ENV PYTHONPATH=/app
EXPOSE 80
RUN chmod 777 /app/docker-entrypoint.sh \
&& ln -s /app/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh \
&& ln -s /app/docker-entrypoint.sh /
RUN chmod +x /app/docker-entrypoint.sh \
&& ln -s /app/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["docker-entrypoint.sh"]
# at the end of your Dockerfile, before CMD or after EXPOSE

View File

@ -20,8 +20,9 @@ build-check: ## Multi-arch build without push (compile check)
.PHONY: build-check-local
build-check-local: ## Multi-arch build without push (compile check)
@git archive --format=tar HEAD | docker buildx build \
--platform linux/amd64,linux/arm64/v8 \
--platform linux/amd64 \
--tag barker:test \
--pull \
--progress=plain \
--load \
-

View File

@ -1,6 +1,6 @@
# Installation (Ubuntu)
# Installation linux
This project uses **pyenv** for Python version management and **Poetry** for dependency and virtual environment management.
This project uses **uv** for Python version, dependency and virtual environment management.
---

View File

@ -1,4 +1,4 @@
"""inculded roles
"""role includes
Revision ID: 5cb65066be86
Revises: 367ecf7b898f

View File

@ -4,9 +4,8 @@ import uvicorn
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
# from fastapi.staticfiles import StaticFiles
from starlette.middleware.sessions import SessionMiddleware
from .core.config import settings
@ -149,7 +148,7 @@ app.include_router(split.router, prefix="/api", tags=["voucher"])
app.include_router(change.router, prefix="/api/voucher", tags=["voucher"])
app.include_router(health.router, prefix="/health", tags=["health"])
# app.mount("/static", StaticFiles(directory="static"), name="static")
app.mount("/static", StaticFiles(directory="static"), name="static")
def init() -> None:

View File

@ -4,7 +4,7 @@ from datetime import date
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, Security, status
from sqlalchemy import distinct, select, update
from sqlalchemy import distinct, func, select, update
from ..core.security import get_current_active_user as get_user
from ..db.session import SessionDep
@ -64,21 +64,26 @@ def update_route(
return menu_category_info(item)
@router.delete("/{id_}", response_model=schemas.MenuCategoryBlank)
@router.delete("/{id_}", response_model=None)
def delete_route(
id_: uuid.UUID, user: Annotated[UserToken, Security(get_user, scopes=["products"])], db: SessionDep
) -> schemas.MenuCategoryBlank:
) -> None:
item: MenuCategory = db.execute(select(MenuCategory).where(MenuCategory.id == id_)).scalar_one()
if item.is_fixture:
raise HTTPException(
status_code=status.HTTP_423_LOCKED,
detail=f"{item.name} is a fixture and cannot be edited or deleted.",
)
else:
product_count = db.execute(
select(func.count(distinct(SkuVersion.sku_id))).where(SkuVersion.menu_category_id == id_)
).scalar_one()
if product_count > 0:
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
detail="Menu Category deletion not implemented",
detail="Menu Category has products and cannot be deleted.",
)
db.delete(item)
db.commit()
@router.get("", response_model=schemas.MenuCategoryBlank)

View File

@ -4,7 +4,7 @@ from datetime import datetime
from decimal import Decimal
from fastapi import HTTPException, status
from sqlalchemy import ColumnElement, func, select
from sqlalchemy import ColumnElement, delete, func, select
from sqlalchemy.orm import Session, contains_eager
from sqlalchemy.sql import expression
@ -126,9 +126,11 @@ def do_update_settlements(voucher: Voucher, others: list[SettleSchema], db: Sess
voucher.settlements.append(ns)
db.add(ns)
for removable in (os for os in voucher.settlements if os.settled not in [x.id_ for x in settlements]):
voucher.settlements.remove(removable)
db.delete(removable)
db.execute(
delete(Settlement).where(
Settlement.voucher_id == voucher.id, Settlement.settled.notin_([x.id_ for x in settlements])
)
)
return fully_settled
@ -199,12 +201,12 @@ def get_voucher(
select(Voucher)
.join(Voucher.food_table)
.join(Voucher.customer, isouter=True)
.join(Voucher.kots)
.join(Kot.inventories)
.join(Inventory.sku)
.join(SkuVersion, onclause=sku_version_onclause)
.join(StockKeepingUnit.product)
.join(ProductVersion, onclause=product_version_onclause)
.join(Voucher.kots, isouter=True)
.join(Kot.inventories, isouter=True)
.join(Inventory.sku, isouter=True)
.join(SkuVersion, onclause=sku_version_onclause, isouter=True)
.join(StockKeepingUnit.product, isouter=True)
.join(ProductVersion, onclause=product_version_onclause, isouter=True)
.where(Voucher.id == voucher_id)
.order_by(Kot.code, Inventory.sort_order)
.options(

View File

@ -82,12 +82,12 @@ def from_bill(id_: str, user: Annotated[UserToken, Security(get_user)], db: Sess
.join(Bill.regime)
.join(Voucher.food_table)
.join(Voucher.customer, isouter=True)
.join(Voucher.kots)
.join(Kot.inventories)
.join(Inventory.sku)
.join(SkuVersion, onclause=sku_version_onclause)
.join(StockKeepingUnit.product)
.join(ProductVersion, onclause=product_version_onclause)
.join(Voucher.kots, isouter=True)
.join(Kot.inventories, isouter=True)
.join(Inventory.sku, isouter=True)
.join(SkuVersion, onclause=sku_version_onclause, isouter=True)
.join(StockKeepingUnit.product, isouter=True)
.join(ProductVersion, onclause=product_version_onclause, isouter=True)
.where(Regime.prefix == match.group(1), Bill.bill_number == int(match.group(2)))
.order_by(Kot.code)
.options(

View File

@ -1,41 +0,0 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true, // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/bookie'),
subdir: '.',
reporters: [{ type: 'html' }, { type: 'text-summary' }],
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true,
});
};

10
lint.sh
View File

@ -9,8 +9,8 @@ npx prettier --write src/app
npx ng lint --fix
cd "$parent_path/barker" || exit
ruff format .
ruff check --fix .
bandit --recursive barker
bandit --recursive .
safety check
uv run ruff format .
uv run ruff check .
uv run bandit --recursive barker
uv run bandit --recursive .
uv run safety check