From 34769a84eb43c2a14a83e242768e3d2599437f44 Mon Sep 17 00:00:00 2001 From: kikimor Date: Sun, 22 Feb 2026 13:20:18 +0200 Subject: [PATCH] added net weight, user, date etc. generates new document number with year as a prefix, restarts numbering every year --- .claude/agents/python-expert-dev.md | 2 +- .claude/agents/react-expert-dev.md | 2 +- .claude/skills/dispatch/SKILL.md | 41 +++++++++++++++ .../api/migrations/0003_documentcounter.py | 29 +++++++++++ backend/api/models.py | 17 +++++- backend/api/serializers.py | 9 +++- backend/api/views.py | 14 ++++- ...hicle_gross_manual_vehicle_net_and_more.py | 52 +++++++++++++++++++ backend/vehicles/models.py | 11 ++++ frontend/src/components/Main.jsx | 4 +- 10 files changed, 173 insertions(+), 8 deletions(-) create mode 100644 .claude/skills/dispatch/SKILL.md create mode 100644 backend/api/migrations/0003_documentcounter.py create mode 100644 backend/vehicles/migrations/0004_vehicle_doc_number_vehicle_gross_manual_vehicle_net_and_more.py diff --git a/.claude/agents/python-expert-dev.md b/.claude/agents/python-expert-dev.md index d29f819..a058bdc 100644 --- a/.claude/agents/python-expert-dev.md +++ b/.claude/agents/python-expert-dev.md @@ -1,6 +1,6 @@ --- name: python-pro -description: An expert Python developer specializing in writing clean, performant, and idiomatic code. Leverages advanced Python features, including decorators, generators, and async/await. Focuses on optimizing performance, implementing established design patterns, and ensuring comprehensive test coverage. Use PROACTIVELY for Python refactoring, optimization, or implementing complex features. +description: Specialized Django/Python backend agent used exclusively by the /dispatch skill. Handles Django models, views, serializers, migrations, and all changes within the backend/ directory. Do not invoke this agent directly — always route through the dispatch orchestrator. tools: Read, Write, Edit, MultiEdit, Grep, Glob, Bash, LS, WebSearch, WebFetch, TodoWrite, Task, mcp__context7__resolve-library-id, mcp__context7__get-library-docs, mcp__sequential-thinking__sequentialthinking model: sonnet --- diff --git a/.claude/agents/react-expert-dev.md b/.claude/agents/react-expert-dev.md index bc5e059..8879dca 100644 --- a/.claude/agents/react-expert-dev.md +++ b/.claude/agents/react-expert-dev.md @@ -1,6 +1,6 @@ --- name: react-expert-dev -description: "Use this agent when developing new React components, refactoring existing React code, solving complex UI challenges, optimizing component performance, implementing state management patterns, or architecting frontend solutions. This agent should be invoked proactively whenever React code is being written or modified.\\n\\n\\nContext: The user is working on the ScalesApp frontend and needs a new component.\\nuser: \"I need a component that displays the vehicle weight history as a chart\"\\nassistant: \"I'll use the react-expert-dev agent to design and implement this component properly.\"\\n\\nSince the user is requesting a new React component, proactively launch the react-expert-dev agent to handle the implementation with proper patterns, hooks, and performance considerations.\\n\\n\\n\\n\\nContext: The user notices their vehicle list is re-rendering too frequently.\\nuser: \"The vehicle list in Main.jsx seems sluggish when there are many vehicles\"\\nassistant: \"Let me invoke the react-expert-dev agent to diagnose and optimize the rendering performance.\"\\n\\nA performance issue in a React component is exactly the domain of the react-expert-dev agent. Launch it proactively to analyze and fix the issue.\\n\\n\\n\\n\\nContext: The user is adding a new feature to the report editor.\\nuser: \"Add a copy/paste feature for report elements in the ReportEditor\"\\nassistant: \"I'll use the react-expert-dev agent to implement this feature following the existing ReportEditor patterns.\"\\n\\nNew React functionality in an existing complex component should be handled by the react-expert-dev agent to ensure it aligns with the existing architecture.\\n\\n\\n\\n\\nContext: User wants to refactor a context provider.\\nuser: \"The NomenclatureContext is getting too large and hard to maintain\"\\nassistant: \"I'll launch the react-expert-dev agent to analyze the context and propose a clean refactoring strategy.\"\\n\\nRefactoring a complex React context is a core task for the react-expert-dev agent.\\n\\n" +description: "Specialized React frontend agent used exclusively by the /dispatch skill. Handles React components, JSX, hooks, state management, performance optimization, and all changes within the frontend/ directory. Do not invoke this agent directly — always route through the dispatch orchestrator." model: inherit color: cyan memory: project diff --git a/.claude/skills/dispatch/SKILL.md b/.claude/skills/dispatch/SKILL.md new file mode 100644 index 0000000..87a1599 --- /dev/null +++ b/.claude/skills/dispatch/SKILL.md @@ -0,0 +1,41 @@ +--- +name: dispatch +description: "Development orchestrator for ScalesApp. Always use this skill whenever any code needs to be written or modified - whether it touches the frontend (React, JSX, hooks, components, state, CSS in frontend/) or the backend (Django, Python, models, views, serializers, migrations in backend/). Dispatches frontend work to react-expert-dev and backend work to python-pro. Runs both agents in parallel for full-stack tasks." +--- + +You are the **development orchestrator** for this project. Do not implement changes yourself — always dispatch to the appropriate specialized agent. + +## Routing + +| Work involves... | Agent | +|-------------------------------------------------------------------|-------------------| +| `frontend/` — React, JSX, hooks, components, state, CSS | `react-expert-dev` | +| `backend/` — Django, Python, models, views, serializers, migrations | `python-pro` | +| Both layers | Both, in parallel | + +## Rules + +1. Each agent is called **exactly once** — never call the same agent twice. +2. For tasks that span both layers, launch both agents in a **single message** using two parallel Task tool calls. +3. Every agent prompt must include the no-subagent constraint (see below). + +## Dispatch Steps + +1. **Analyze** — identify which directories and layers the task touches. +2. **Compose** a detailed prompt for each relevant agent: + - Full task description and context + - Relevant file paths to examine + - Clear acceptance criteria + - Append this constraint verbatim at the end: + + > **CONSTRAINT: Do NOT use the Task tool. Do not spawn any subagents. Complete all work directly using your available file and shell tools.** + +3. **Launch** via the Task tool: + - Frontend → `subagent_type: react-expert-dev` + - Backend → `subagent_type: python-pro` + - Both → two calls in a single message (parallel) + +4. **Report** — after all agents complete, summarize: + - What each agent did + - Any required follow-up steps (e.g., run migrations, restart services) + - Any issues or pre-existing problems the agents flagged diff --git a/backend/api/migrations/0003_documentcounter.py b/backend/api/migrations/0003_documentcounter.py new file mode 100644 index 0000000..ef01b51 --- /dev/null +++ b/backend/api/migrations/0003_documentcounter.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2.8 on 2026-02-22 10:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("api", "0002_add_report_model"), + ] + + operations = [ + migrations.CreateModel( + name="DocumentCounter", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("year", models.IntegerField(unique=True)), + ("last_number", models.IntegerField(default=0)), + ], + ), + ] diff --git a/backend/api/models.py b/backend/api/models.py index 4e1e57f..01922eb 100644 --- a/backend/api/models.py +++ b/backend/api/models.py @@ -1,4 +1,4 @@ -from django.db import models +from django.db import models, transaction from django.contrib.auth.models import AbstractUser from django.utils import timezone @@ -57,3 +57,18 @@ class Report(models.Model): def __str__(self): return self.name + + +class DocumentCounter(models.Model): + year = models.IntegerField(unique=True) + last_number = models.IntegerField(default=0) + + +def generate_doc_number(year): + with transaction.atomic(): + counter, _ = DocumentCounter.objects.select_for_update().get_or_create( + year=year, defaults={'last_number': 0} + ) + counter.last_number += 1 + counter.save() + return f"{year}-{counter.last_number:06d}" diff --git a/backend/api/serializers.py b/backend/api/serializers.py index 54929ff..eea9deb 100644 --- a/backend/api/serializers.py +++ b/backend/api/serializers.py @@ -125,14 +125,19 @@ class VehicleSerializer(serializers.ModelSerializer): extra = VehicleExtraSerializer(required=False, allow_null=True) tare_user_name = serializers.CharField(source='tare_user.username', read_only=True, default=None) gross_user_name = serializers.CharField(source='gross_user.username', read_only=True, default=None) + net_user_name = serializers.CharField(source='net_user.username', read_only=True, default=None) class Meta: model = Vehicle fields = ['id', 'vehicle_number', 'trailer1_number', 'trailer2_number', 'driver_pid', 'tare', 'tare_date', 'tare_user', 'tare_user_name', - 'gross', 'gross_date', 'gross_user', 'gross_user_name', 'extra'] + 'gross', 'gross_date', 'gross_user', 'gross_user_name', + 'net', 'net_date', 'net_user', 'net_user_name', + 'doc_number', 'extra'] read_only_fields = ['id', 'tare_date', 'tare_user', 'tare_user_name', - 'gross_date', 'gross_user', 'gross_user_name'] + 'gross_date', 'gross_user', 'gross_user_name', + 'net', 'net_date', 'net_user', 'net_user_name', + 'doc_number'] def create(self, validated_data): extra_data = validated_data.pop('extra', None) diff --git a/backend/api/views.py b/backend/api/views.py index 9f32db7..2dd3f4f 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -2,7 +2,7 @@ from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated, AllowAny -from .models import ComPortReading, User, Report +from .models import ComPortReading, User, Report, generate_doc_number from .serializers import ( ComPortReadingSerializer, UserSerializer, UserDetailSerializer, ChangePasswordSerializer, VehicleSerializer, NomenclatureSerializer, @@ -111,6 +111,16 @@ class ComPortReadingViewSet(viewsets.ModelViewSet): serializer.save(source_ip=self.get_client_ip(self.request)) +def _maybe_finalize(vehicle, user): + """If both tare and gross are set and net hasn't been calculated yet, compute net and assign doc_number.""" + if vehicle.tare is not None and vehicle.gross is not None and vehicle.net is None: + vehicle.net = vehicle.gross - vehicle.tare + vehicle.net_date = timezone.now() + vehicle.net_user = user + vehicle.doc_number = generate_doc_number(timezone.now().year) + vehicle.save() + + class VehicleViewSet(viewsets.ModelViewSet): """ API endpoint for vehicle management. @@ -183,6 +193,7 @@ class VehicleViewSet(viewsets.ModelViewSet): vehicle.tare_date = timezone.now() vehicle.tare_user = request.user vehicle.save() + _maybe_finalize(vehicle, request.user) serializer = self.get_serializer(vehicle) sse_broadcast_update('vehicle', 'updated', serializer.data) @@ -204,6 +215,7 @@ class VehicleViewSet(viewsets.ModelViewSet): vehicle.gross_date = timezone.now() vehicle.gross_user = request.user vehicle.save() + _maybe_finalize(vehicle, request.user) serializer = self.get_serializer(vehicle) sse_broadcast_update('vehicle', 'updated', serializer.data) diff --git a/backend/vehicles/migrations/0004_vehicle_doc_number_vehicle_gross_manual_vehicle_net_and_more.py b/backend/vehicles/migrations/0004_vehicle_doc_number_vehicle_gross_manual_vehicle_net_and_more.py new file mode 100644 index 0000000..5809c42 --- /dev/null +++ b/backend/vehicles/migrations/0004_vehicle_doc_number_vehicle_gross_manual_vehicle_net_and_more.py @@ -0,0 +1,52 @@ +# Generated by Django 4.2.8 on 2026-02-22 10:11 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("vehicles", "0003_vehicle_driver_pid_vehicle_trailer1_number_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="vehicle", + name="doc_number", + field=models.CharField(blank=True, max_length=20, null=True, unique=True), + ), + migrations.AddField( + model_name="vehicle", + name="gross_manual", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="vehicle", + name="net", + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name="vehicle", + name="net_date", + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name="vehicle", + name="net_user", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="user_vehicle_net", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="vehicle", + name="tare_manual", + field=models.BooleanField(default=False), + ), + ] diff --git a/backend/vehicles/models.py b/backend/vehicles/models.py index b756b76..5102c72 100644 --- a/backend/vehicles/models.py +++ b/backend/vehicles/models.py @@ -13,9 +13,20 @@ class Vehicle(models.Model): tare = models.IntegerField(null=True, blank=True) tare_date = models.DateTimeField(null=True, blank=True) tare_user = models.ForeignKey(User, null=True, blank=True, related_name='user_vehicle_tare', on_delete=models.SET_NULL) + tare_manual = models.BooleanField(default=False) + gross = models.IntegerField(null=True, blank=True) gross_date = models.DateTimeField(null=True, blank=True) gross_user = models.ForeignKey(User, null=True, blank=True, related_name='user_vehicle_gross', on_delete=models.SET_NULL) + gross_manual = models.BooleanField(default=False) + + net = models.IntegerField(null=True, blank=True) + net_date = models.DateTimeField(null=True, blank=True) + net_user = models.ForeignKey(User, null=True, blank=True, related_name='user_vehicle_net', on_delete=models.SET_NULL) + + doc_number = models.CharField(max_length=20, null=True, blank=True, unique=True) + + def __str__(self): return f"{self.vehicle_number}" diff --git a/frontend/src/components/Main.jsx b/frontend/src/components/Main.jsx index 43fcc6f..ade6676 100644 --- a/frontend/src/components/Main.jsx +++ b/frontend/src/components/Main.jsx @@ -360,7 +360,7 @@ export default function Main() { @@ -381,7 +381,7 @@ export default function Main() {