added net weight, user, date etc. generates new document number with year as a prefix, restarts numbering every year

master
kikimor 1 week ago
parent 33b49e317d
commit 34769a84eb

@ -1,6 +1,6 @@
--- ---
name: python-pro 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 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 model: sonnet
--- ---

@ -1,6 +1,6 @@
--- ---
name: react-expert-dev 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<example>\\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<commentary>\\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</commentary>\\n</example>\\n\\n<example>\\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<commentary>\\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</commentary>\\n</example>\\n\\n<example>\\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<commentary>\\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</commentary>\\n</example>\\n\\n<example>\\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<commentary>\\nRefactoring a complex React context is a core task for the react-expert-dev agent.\\n</commentary>\\n</example>" 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 model: inherit
color: cyan color: cyan
memory: project memory: project

@ -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

@ -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)),
],
),
]

@ -1,4 +1,4 @@
from django.db import models from django.db import models, transaction
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.utils import timezone from django.utils import timezone
@ -57,3 +57,18 @@ class Report(models.Model):
def __str__(self): def __str__(self):
return self.name 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}"

@ -125,14 +125,19 @@ class VehicleSerializer(serializers.ModelSerializer):
extra = VehicleExtraSerializer(required=False, allow_null=True) extra = VehicleExtraSerializer(required=False, allow_null=True)
tare_user_name = serializers.CharField(source='tare_user.username', read_only=True, default=None) 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) 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: class Meta:
model = Vehicle model = Vehicle
fields = ['id', 'vehicle_number', 'trailer1_number', 'trailer2_number', 'driver_pid', fields = ['id', 'vehicle_number', 'trailer1_number', 'trailer2_number', 'driver_pid',
'tare', 'tare_date', 'tare_user', 'tare_user_name', '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', 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): def create(self, validated_data):
extra_data = validated_data.pop('extra', None) extra_data = validated_data.pop('extra', None)

@ -2,7 +2,7 @@ from rest_framework import viewsets, status
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny 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 ( from .serializers import (
ComPortReadingSerializer, UserSerializer, UserDetailSerializer, ComPortReadingSerializer, UserSerializer, UserDetailSerializer,
ChangePasswordSerializer, VehicleSerializer, NomenclatureSerializer, ChangePasswordSerializer, VehicleSerializer, NomenclatureSerializer,
@ -111,6 +111,16 @@ class ComPortReadingViewSet(viewsets.ModelViewSet):
serializer.save(source_ip=self.get_client_ip(self.request)) 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): class VehicleViewSet(viewsets.ModelViewSet):
""" """
API endpoint for vehicle management. API endpoint for vehicle management.
@ -183,6 +193,7 @@ class VehicleViewSet(viewsets.ModelViewSet):
vehicle.tare_date = timezone.now() vehicle.tare_date = timezone.now()
vehicle.tare_user = request.user vehicle.tare_user = request.user
vehicle.save() vehicle.save()
_maybe_finalize(vehicle, request.user)
serializer = self.get_serializer(vehicle) serializer = self.get_serializer(vehicle)
sse_broadcast_update('vehicle', 'updated', serializer.data) sse_broadcast_update('vehicle', 'updated', serializer.data)
@ -204,6 +215,7 @@ class VehicleViewSet(viewsets.ModelViewSet):
vehicle.gross_date = timezone.now() vehicle.gross_date = timezone.now()
vehicle.gross_user = request.user vehicle.gross_user = request.user
vehicle.save() vehicle.save()
_maybe_finalize(vehicle, request.user)
serializer = self.get_serializer(vehicle) serializer = self.get_serializer(vehicle)
sse_broadcast_update('vehicle', 'updated', serializer.data) sse_broadcast_update('vehicle', 'updated', serializer.data)

@ -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),
),
]

@ -13,9 +13,20 @@ class Vehicle(models.Model):
tare = models.IntegerField(null=True, blank=True) tare = models.IntegerField(null=True, blank=True)
tare_date = models.DateTimeField(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_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 = models.IntegerField(null=True, blank=True)
gross_date = models.DateTimeField(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_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): def __str__(self):
return f"{self.vehicle_number}" return f"{self.vehicle_number}"

@ -360,7 +360,7 @@ export default function Main() {
<button <button
className="weight-set-btn" className="weight-set-btn"
onClick={handleSetTare} onClick={handleSetTare}
disabled={currentWeight === null || isNaN(currentWeight)} disabled={currentWeight === null || isNaN(currentWeight) || selectedVehicle?.tare != null}
> >
Set Tare Set Tare
</button> </button>
@ -381,7 +381,7 @@ export default function Main() {
<button <button
className="weight-set-btn" className="weight-set-btn"
onClick={handleSetGross} onClick={handleSetGross}
disabled={currentWeight === null || isNaN(currentWeight)} disabled={currentWeight === null || isNaN(currentWeight) || selectedVehicle?.gross != null}
> >
Set Gross Set Gross
</button> </button>

Loading…
Cancel
Save