diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index a5ed392..9cc4b9b 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index e061640..ed3824b 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2a36fd6..a09b86d 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index 8c38f3f..118f471 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,3 +1,45 @@ from django.contrib import admin +from .models import Plant, Category, Product, Inventory, Machine, WorkOrder, Inspection -# Register your models here. +@admin.register(Plant) +class PlantAdmin(admin.ModelAdmin): + list_display = ('name', 'location') + search_fields = ('name', 'location') + +@admin.register(Category) +class CategoryAdmin(admin.ModelAdmin): + list_display = ('name',) + +@admin.register(Product) +class ProductAdmin(admin.ModelAdmin): + list_display = ('name', 'sku', 'category') + search_fields = ('name', 'sku') + list_filter = ('category',) + +@admin.register(Inventory) +class InventoryAdmin(admin.ModelAdmin): + list_display = ('product', 'plant', 'quantity', 'lot_number') + list_filter = ('plant', 'product') + search_fields = ('lot_number',) + +@admin.register(Machine) +class MachineAdmin(admin.ModelAdmin): + list_display = ('name', 'plant', 'status', 'last_maintenance') + list_filter = ('plant', 'status') + search_fields = ('name',) + +class InspectionInline(admin.TabularInline): + model = Inspection + extra = 1 + +@admin.register(WorkOrder) +class WorkOrderAdmin(admin.ModelAdmin): + list_display = ('order_number', 'plant', 'product', 'quantity', 'status', 'start_date') + list_filter = ('plant', 'status', 'product') + search_fields = ('order_number',) + inlines = [InspectionInline] + +@admin.register(Inspection) +class InspectionAdmin(admin.ModelAdmin): + list_display = ('work_order', 'result', 'inspector', 'timestamp') + list_filter = ('result', 'timestamp') \ No newline at end of file diff --git a/core/management/__init__.py b/core/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/management/__pycache__/__init__.cpython-311.pyc b/core/management/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..e003507 Binary files /dev/null and b/core/management/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/management/commands/__init__.py b/core/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/management/commands/__pycache__/__init__.cpython-311.pyc b/core/management/commands/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..2e0c69f Binary files /dev/null and b/core/management/commands/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/management/commands/__pycache__/seed_data.cpython-311.pyc b/core/management/commands/__pycache__/seed_data.cpython-311.pyc new file mode 100644 index 0000000..7219d50 Binary files /dev/null and b/core/management/commands/__pycache__/seed_data.cpython-311.pyc differ diff --git a/core/management/commands/seed_data.py b/core/management/commands/seed_data.py new file mode 100644 index 0000000..72861f6 --- /dev/null +++ b/core/management/commands/seed_data.py @@ -0,0 +1,75 @@ +from django.core.management.base import BaseCommand +from django.contrib.auth.models import User +from core.models import Plant, Category, Product, Inventory, Machine, WorkOrder, Inspection +import random +from datetime import date, timedelta + +class Command(BaseCommand): + help = 'Seeds the database with initial demo data for the ERP' + + def handle(self, *args, **kwargs): + self.stdout.write('Seeding data...') + + # Ensure we have an admin user + if not User.objects.filter(username='admin').exists(): + User.objects.create_superuser('admin', 'admin@example.com', 'admin') + self.stdout.write('Superuser "admin" created.') + + # Plants + p1, _ = Plant.objects.get_or_create(name='Istanbul Facility', location='Istanbul, TR') + p2, _ = Plant.objects.get_or_create(name='Ankara Facility', location='Ankara, TR') + p3, _ = Plant.objects.get_or_create(name='Izmir Facility', location='Izmir, TR') + + # Categories + cat_raw, _ = Category.objects.get_or_create(name='Raw Materials') + cat_final, _ = Category.objects.get_or_create(name='Finished Goods') + cat_comp, _ = Category.objects.get_or_create(name='Components') + + # Products + prod1, _ = Product.objects.get_or_create(name='Steel Plate', sku='RAW-001', category=cat_raw) + prod2, _ = Product.objects.get_or_create(name='Microchip A1', sku='COMP-101', category=cat_comp) + prod3, _ = Product.objects.get_or_create(name='Smart Widget', sku='FIN-505', category=cat_final) + + # Inventory + for p in [p1, p2, p3]: + for prod in [prod1, prod2, prod3]: + Inventory.objects.get_or_create( + plant=p, + product=prod, + lot_number=f"LOT-{random.randint(1000, 9999)}", + defaults={'quantity': random.uniform(10, 500)} + ) + + # Machines + m1, _ = Machine.objects.get_or_create(name='Laser Cutter X1', plant=p1, status='active') + m2, _ = Machine.objects.get_or_create(name='Assembly Line 3', plant=p2, status='active') + m3, _ = Machine.objects.get_or_create(name='Quality Tester T8', plant=p3, status='maintenance') + + # Work Orders + wo1, _ = WorkOrder.objects.get_or_create( + order_number='WO-2026-001', + plant=p1, + product=prod3, + quantity=100, + machine=m1, + status='in_progress' + ) + wo2, _ = WorkOrder.objects.get_or_create( + order_number='WO-2026-002', + plant=p2, + product=prod3, + quantity=50, + machine=m2, + status='planned' + ) + + # Inspections + admin_user = User.objects.get(username='admin') + Inspection.objects.get_or_create( + work_order=wo1, + inspector=admin_user, + result='pass', + notes='All systems nominal.' + ) + + self.stdout.write(self.style.SUCCESS('Successfully seeded demo data.')) diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..0a7ef4d --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,94 @@ +# Generated by Django 5.2.7 on 2026-02-05 08:46 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Category', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ], + options={ + 'verbose_name_plural': 'Categories', + }, + ), + migrations.CreateModel( + name='Plant', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('location', models.CharField(blank=True, max_length=255)), + ], + ), + migrations.CreateModel( + name='Machine', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('status', models.CharField(choices=[('active', 'Active'), ('maintenance', 'Maintenance'), ('broken', 'Broken')], default='active', max_length=20)), + ('last_maintenance', models.DateField(blank=True, null=True)), + ('plant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='machines', to='core.plant')), + ], + ), + migrations.CreateModel( + name='Product', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('sku', models.CharField(max_length=100, unique=True)), + ('description', models.TextField(blank=True)), + ('category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.category')), + ], + ), + migrations.CreateModel( + name='WorkOrder', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('order_number', models.CharField(max_length=50, unique=True)), + ('quantity', models.PositiveIntegerField()), + ('status', models.CharField(choices=[('planned', 'Planned'), ('in_progress', 'In Progress'), ('completed', 'Completed'), ('cancelled', 'Cancelled')], default='planned', max_length=20)), + ('start_date', models.DateTimeField(blank=True, null=True)), + ('end_date', models.DateTimeField(blank=True, null=True)), + ('machine', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.machine')), + ('plant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.plant')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.product')), + ], + ), + migrations.CreateModel( + name='Inspection', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('result', models.CharField(choices=[('pass', 'Pass'), ('fail', 'Fail')], max_length=10)), + ('notes', models.TextField(blank=True)), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('inspector', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ('work_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inspections', to='core.workorder')), + ], + ), + migrations.CreateModel( + name='Inventory', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity', models.DecimalField(decimal_places=2, default=0, max_digits=12)), + ('lot_number', models.CharField(blank=True, max_length=100)), + ('plant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inventory', to='core.plant')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.product')), + ], + options={ + 'verbose_name_plural': 'Inventory', + 'unique_together': {('plant', 'product', 'lot_number')}, + }, + ), + ] diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..4b21515 Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 71a8362..6ed3c2b 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,87 @@ from django.db import models +from django.contrib.auth.models import User -# Create your models here. +class Plant(models.Model): + name = models.CharField(max_length=255) + location = models.CharField(max_length=255, blank=True) + + def __str__(self): + return self.name + +class Category(models.Model): + name = models.CharField(max_length=100) + + class Meta: + verbose_name_plural = "Categories" + + def __str__(self): + return self.name + +class Product(models.Model): + name = models.CharField(max_length=255) + sku = models.CharField(max_length=100, unique=True) + category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True) + description = models.TextField(blank=True) + + def __str__(self): + return f"{self.name} ({self.sku})" + +class Inventory(models.Model): + plant = models.ForeignKey(Plant, on_delete=models.CASCADE, related_name='inventory') + product = models.ForeignKey(Product, on_delete=models.CASCADE) + quantity = models.DecimalField(max_digits=12, decimal_places=2, default=0) + lot_number = models.CharField(max_length=100, blank=True) + + class Meta: + verbose_name_plural = "Inventory" + unique_together = ('plant', 'product', 'lot_number') + + def __str__(self): + return f"{self.product.name} at {self.plant.name} - Lot: {self.lot_number or 'N/A'}" + +class Machine(models.Model): + STATUS_CHOICES = [ + ('active', 'Active'), + ('maintenance', 'Maintenance'), + ('broken', 'Broken'), + ] + name = models.CharField(max_length=255) + plant = models.ForeignKey(Plant, on_delete=models.CASCADE, related_name='machines') + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active') + last_maintenance = models.DateField(null=True, blank=True) + + def __str__(self): + return f"{self.name} ({self.plant.name})" + +class WorkOrder(models.Model): + STATUS_CHOICES = [ + ('planned', 'Planned'), + ('in_progress', 'In Progress'), + ('completed', 'Completed'), + ('cancelled', 'Cancelled'), + ] + order_number = models.CharField(max_length=50, unique=True) + plant = models.ForeignKey(Plant, on_delete=models.CASCADE) + product = models.ForeignKey(Product, on_delete=models.CASCADE) + quantity = models.PositiveIntegerField() + machine = models.ForeignKey(Machine, on_delete=models.SET_NULL, null=True, blank=True) + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='planned') + start_date = models.DateTimeField(null=True, blank=True) + end_date = models.DateTimeField(null=True, blank=True) + + def __str__(self): + return self.order_number + +class Inspection(models.Model): + RESULT_CHOICES = [ + ('pass', 'Pass'), + ('fail', 'Fail'), + ] + work_order = models.ForeignKey(WorkOrder, on_delete=models.CASCADE, related_name='inspections') + inspector = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) + result = models.CharField(max_length=10, choices=RESULT_CHOICES) + notes = models.TextField(blank=True) + timestamp = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"Inspection for {self.work_order.order_number} - {self.result}" \ No newline at end of file diff --git a/core/templates/base.html b/core/templates/base.html index 1e7e5fb..1b89ad8 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -1,25 +1,166 @@ - - - {% block title %}Knowledge Base{% endblock %} - {% if project_description %} - - - - {% endif %} - {% if project_image_url %} - - - {% endif %} - {% load static %} - - {% block head %}{% endblock %} + + + {% block title %}Multi-Plant ERP{% endblock %} + {% load static %} + + + {% block head %}{% endblock %} - - {% block content %}{% endblock %} - + - +
+
+
+ {{ user.username }} +
+
+
+ +
+ {% block content %}{% endblock %} +
+
+ + \ No newline at end of file diff --git a/core/templates/core/index.html b/core/templates/core/index.html index faec813..2afffe5 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,145 +1,146 @@ {% extends "base.html" %} -{% block title %}{{ project_name }}{% endblock %} +{% block title %}Dashboard | Multi-Plant ERP{% endblock %} -{% block head %} - - - +{% block extra_css %} {% endblock %} {% block content %} -
-
-

Analyzing your requirements and generating your app…

-
- Loading… +
+
+

Operational Dashboard

+

Real-time manufacturing insights across all facilities.

-

AppWizzy AI is collecting your requirements and applying the first changes.

-

This page will refresh automatically as the plan is implemented.

-

- Runtime: Django {{ django_version }} · Python {{ python_version }} - — UTC {{ current_time|date:"Y-m-d H:i:s" }} -

-
-
- +
+ + Add New Order +
+ + +
+
+
Active Facilities
+
{{ stats.plants_count }}
+
+
+
Total Stock Items
+
{{ stats.inventory_count }}
+
+
+
Production Machines
+
{{ stats.machines_count }}
+
+
+
Running Work Orders
+
{{ stats.active_work_orders }}
+
+
+ +
+
+

Recent Quality Inspections

+ View All +
+ + {% if stats.recent_inspections %} + + + + + + + + + + + + {% for inspection in stats.recent_inspections %} + + + + + + + + {% endfor %} + +
Work OrderFacilityResultInspectorDate
{{ inspection.work_order.order_number }}{{ inspection.work_order.plant.name }} + + {{ inspection.result|upper }} + + {{ inspection.inspector.username }}{{ inspection.timestamp|date:"M d, Y H:i" }}
+ {% else %} +

No recent inspections recorded.

+ {% endif %} +
{% endblock %} \ No newline at end of file diff --git a/core/views.py b/core/views.py index c9aed12..c24423b 100644 --- a/core/views.py +++ b/core/views.py @@ -4,22 +4,31 @@ import platform from django import get_version as django_version from django.shortcuts import render from django.utils import timezone - +from .models import Plant, Product, Inventory, Machine, WorkOrder, Inspection def home(request): - """Render the landing screen with loader and environment details.""" + """Render the ERP dashboard with summary statistics.""" host_name = request.get_host().lower() agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" now = timezone.now() + # ERP Stats + stats = { + "plants_count": Plant.objects.count(), + "products_count": Product.objects.count(), + "inventory_count": Inventory.objects.count(), + "machines_count": Machine.objects.count(), + "active_work_orders": WorkOrder.objects.filter(status='in_progress').count(), + "recent_inspections": Inspection.objects.order_by('-timestamp')[:5], + } + context = { - "project_name": "New Style", + "project_name": "Multi-Plant ERP", "agent_brand": agent_brand, "django_version": django_version(), "python_version": platform.python_version(), "current_time": now, "host_name": host_name, - "project_description": os.getenv("PROJECT_DESCRIPTION", ""), - "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), + "stats": stats, } - return render(request, "core/index.html", context) + return render(request, "core/index.html", context) \ No newline at end of file