added net weight, user, date etc. generates new document number with year as a prefix, restarts numbering every year
This commit is contained in:
@@ -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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
+16
-1
@@ -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)
|
||||||
|
|||||||
+13
-1
@@ -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)
|
||||||
|
|||||||
+52
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user