tum пре 8 месеци
родитељ
комит
ba8f8f05d9

+ 10 - 12
app/coi/routers.py

@@ -4,30 +4,28 @@ class DatabaseRouter:
4 4
     targeting separate databases.
5 5
     """
6 6
 
7
-    # Define app labels that use the secondary database
8
-    route_app_labels = {"legacy"}
7
+    route_app_labels = {
8
+        "legacy": "OB2011DB",
9
+        "trans": "MGT_Trans",
10
+    }
9 11
 
10 12
     def db_for_read(self, model, **hints):
11 13
         """
12
-        Direct read operations for legacy_app models to OB2011DB.
14
+        Direct read operations to the appropriate database.
13 15
         """
14
-        if model._meta.app_label in self.route_app_labels:
15
-            return "OB2011DB"
16
-        return "default"
16
+        return self.route_app_labels.get(model._meta.app_label, "default")
17 17
 
18 18
     def db_for_write(self, model, **hints):
19 19
         """
20
-        Direct write operations for legacy_app models to OB2011DB.
20
+        Direct write operations to the appropriate database.
21 21
         """
22
-        if model._meta.app_label in self.route_app_labels:
23
-            return "OB2011DB"
24
-        return "default"
22
+        return self.route_app_labels.get(model._meta.app_label, "default")
25 23
 
26 24
     def allow_relation(self, obj1, obj2, **hints):
27 25
         """
28 26
         Allow relations if both objects belong to the same database.
29 27
         """
30
-        db_set = {"default", "OB2011DB"}
28
+        db_set = {"default", "OB2011DB", "MGT_Trans"}
31 29
         if obj1._state.db in db_set and obj2._state.db in db_set:
32 30
             return True
33 31
         return None
@@ -37,5 +35,5 @@ class DatabaseRouter:
37 35
         Ensure migrations are applied only to the appropriate database.
38 36
         """
39 37
         if app_label in self.route_app_labels:
40
-            return db == "OB2011DB"
38
+            return db == self.route_app_labels[app_label]
41 39
         return db == "default"

+ 12 - 0
app/coi/settings.py

@@ -145,6 +145,18 @@ DATABASES = {
145 145
             'extra_params':  'Encrypt=no',
146 146
         },
147 147
     },
148
+    'MGT_Trans': {
149
+        'ENGINE': DB2_ENGINE,
150
+        'NAME': 'MGT_Trans',
151
+        'USER': DB2_USER,
152
+        'PASSWORD': DB2_PASSWORD,
153
+        'HOST': DB2_HOST,
154
+        #'PORT': DB2_PORT,
155
+        'OPTIONS': {
156
+            'driver': DB2_DRIVER,
157
+            'extra_params':  'Encrypt=no',
158
+        },
159
+    },
148 160
 }
149 161
 
150 162
 

+ 25 - 1
app/core/forms.py

@@ -1,7 +1,31 @@
1 1
 from django import forms
2
-from .models import Report
2
+from .models import Report, CustomerTemplateMapping, ProductDrawing
3
+
4
+
5
+from core.utils import SHEET_NAMES
3 6
 
4 7
 class ReportForm(forms.ModelForm):
5 8
     class Meta:
6 9
         model = Report
7 10
         fields = ['name', 'created_by', 'file']  # Include the fields you want in the form
11
+
12
+class CustomerTemplateMappingForm(forms.ModelForm):
13
+    template_names = forms.MultipleChoiceField(
14
+        choices=[(key, label) for key, label in SHEET_NAMES.items()],
15
+        widget=forms.CheckboxSelectMultiple,
16
+        required=False,
17
+        label="Templates"
18
+    )
19
+
20
+    class Meta:
21
+        model = CustomerTemplateMapping
22
+        fields = ['customer_name', 'template_names']
23
+
24
+    def clean_template_names(self):
25
+        # convert data to list of selected template keys
26
+        return self.cleaned_data['template_names']
27
+
28
+class ProductDrawingForm(forms.ModelForm):
29
+    class Meta:
30
+        model = ProductDrawing
31
+        fields = ['code_no', 'code_no_mks',  'lot_no', 'drawing', 'description']

+ 30 - 0
app/core/migrations/0006_customertemplatemapping_and_more.py

@@ -0,0 +1,30 @@
1
+# Generated by Django 4.2 on 2025-05-06 04:36
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('core', '0005_delete_belmasterview_delete_emasterview_and_more'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.CreateModel(
14
+            name='CustomerTemplateMapping',
15
+            fields=[
16
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
17
+                ('customer_name', models.CharField(max_length=255)),
18
+                ('template_names', models.JSONField(blank=True, default=list, null=True)),
19
+            ],
20
+        ),
21
+        migrations.DeleteModel(
22
+            name='AllProductAverageObMinMaxView',
23
+        ),
24
+        migrations.DeleteModel(
25
+            name='AllProductDimensionForInsProcess',
26
+        ),
27
+        migrations.DeleteModel(
28
+            name='AllProductPressPositionPressWeight',
29
+        ),
30
+    ]

+ 33 - 0
app/core/migrations/0007_customertemplatemapping_created_at_and_more.py

@@ -0,0 +1,33 @@
1
+# Generated by Django 4.2 on 2025-05-06 04:52
2
+
3
+from django.conf import settings
4
+from django.db import migrations, models
5
+import django.db.models.deletion
6
+import django.utils.timezone
7
+
8
+
9
+class Migration(migrations.Migration):
10
+
11
+    dependencies = [
12
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13
+        ('core', '0006_customertemplatemapping_and_more'),
14
+    ]
15
+
16
+    operations = [
17
+        migrations.AddField(
18
+            model_name='customertemplatemapping',
19
+            name='created_at',
20
+            field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
21
+            preserve_default=False,
22
+        ),
23
+        migrations.AddField(
24
+            model_name='customertemplatemapping',
25
+            name='created_by',
26
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
27
+        ),
28
+        migrations.AddField(
29
+            model_name='customertemplatemapping',
30
+            name='updated_at',
31
+            field=models.DateTimeField(auto_now=True),
32
+        ),
33
+    ]

+ 30 - 0
app/core/migrations/0008_productdrawing.py

@@ -0,0 +1,30 @@
1
+# Generated by Django 4.2 on 2025-05-06 08:47
2
+
3
+from django.conf import settings
4
+from django.db import migrations, models
5
+import django.db.models.deletion
6
+
7
+
8
+class Migration(migrations.Migration):
9
+
10
+    dependencies = [
11
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12
+        ('core', '0007_customertemplatemapping_created_at_and_more'),
13
+    ]
14
+
15
+    operations = [
16
+        migrations.CreateModel(
17
+            name='ProductDrawing',
18
+            fields=[
19
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20
+                ('code_no', models.CharField(blank=True, max_length=100, null=True)),
21
+                ('code_no_mks', models.CharField(blank=True, max_length=100, null=True)),
22
+                ('lot_no', models.CharField(blank=True, max_length=100, null=True)),
23
+                ('drawing', models.ImageField(upload_to='drawings/')),
24
+                ('description', models.TextField(blank=True, null=True)),
25
+                ('created_at', models.DateTimeField(auto_now_add=True)),
26
+                ('updated_at', models.DateTimeField(auto_now=True)),
27
+                ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
28
+            ],
29
+        ),
30
+    ]

+ 58 - 0
app/core/models.py

@@ -463,3 +463,61 @@ class RotateBrokenTest(models.Model):
463 463
         managed = False  # This model corresponds to a database view
464 464
         app_label = "legacy"
465 465
         db_table = "rotate_broken_test"  # Matches SQL Server table name
466
+
467
+class LotType(models.Model):
468
+    lot_no = models.CharField(primary_key=True,max_length=20, db_column='LotNo')
469
+    group = models.CharField(max_length=10, db_column='Group')
470
+    sub_group = models.CharField(max_length=10, db_column='SubGroup')
471
+
472
+    class Meta:
473
+        db_table = 'T-LotType'
474
+        managed = False  # Since it's an existing table in SQL Server
475
+        app_label = "trans"
476
+
477
+
478
+TEMPLATE_CHOICES = [
479
+    "hardness_out",
480
+    "hardness_out_in",
481
+    "hardness_both_size",
482
+    "dimension",
483
+    "dimension_app",
484
+    "dimension_bal_weight",
485
+    "dim_bal_app_hard",
486
+    "dim_bal_app_rot_hard",
487
+    "thickness_8_point",
488
+    "centering"
489
+]
490
+
491
+class CustomerTemplateMapping(models.Model):
492
+    customer_name = models.CharField(max_length=255)
493
+    template_names = models.JSONField(default=list, blank=True, null=True)  # stores list of template names
494
+
495
+    created_by = models.ForeignKey(
496
+        User,
497
+        on_delete=models.SET_NULL,
498
+        null=True,
499
+        blank=True, 
500
+    )  # Reference to the user who created the report
501
+    created_at = models.DateTimeField(auto_now_add=True)  # Automatically set when created
502
+    updated_at = models.DateTimeField(auto_now=True)  # Automatically updated when modified
503
+    def __str__(self):
504
+        return self.customer_name
505
+
506
+class ProductDrawing(models.Model):
507
+    code_no = models.CharField(max_length=100, null=True, blank=True)
508
+    code_no_mks = models.CharField(max_length=100, null=True, blank=True)
509
+    lot_no = models.CharField(max_length=100, null=True, blank=True)
510
+    drawing = models.ImageField(upload_to='drawings/')
511
+    description = models.TextField(null=True, blank=True)
512
+    created_at = models.DateTimeField(auto_now_add=True)
513
+    updated_at = models.DateTimeField(auto_now=True)
514
+
515
+    created_by = models.ForeignKey(
516
+        User,
517
+        on_delete=models.SET_NULL,
518
+        null=True,
519
+        blank=True, 
520
+    )  # Reference to the user who created the report
521
+    
522
+    def __str__(self):
523
+        return f"{self.code_no} - {self.lot_no}"

+ 14 - 1
app/core/utils.py

@@ -12,6 +12,7 @@ from django.views.generic import (
12 12
 )
13 13
 from django.core.paginator import Paginator
14 14
 from core.models import MgMasterView, VMasterView, BelMasterView, EMasterView
15
+from pprint import pprint
15 16
 
16 17
 class ConfigurableCRUDView:
17 18
     model = None
@@ -44,6 +45,7 @@ class ConfigurableCRUDView:
44 45
         - Respects `config_field_orders` and `config_excludes`.
45 46
         Returns field instances instead of field names.
46 47
         """
48
+        pprint("get_fields")
47 49
         model_fields = {f.name: f for f in self.model._meta.get_fields()}
48 50
 
49 51
         # Filter based on `config_fields` configuration
@@ -65,7 +67,6 @@ class ConfigurableCRUDView:
65 67
         # Reorder fields to match the order specified in `config_field_orders`
66 68
         ordered_field_names = set(self.config_field_orders)
67 69
         ordered_fields.sort(key=lambda f: self.config_field_orders.index(f.name) if f.name in ordered_field_names else len(ordered_field_names))
68
-
69 70
         return ordered_fields + remaining_fields
70 71
 
71 72
 
@@ -226,3 +227,15 @@ def queryFromMaster(lot_no):
226 227
         results.extend(queryset)
227 228
     return results
228 229
 
230
+SHEET_NAMES = {
231
+    'hardness_out': 'Hardness Out',
232
+    'hardness_out_in': 'Hardness Out/In', 
233
+    'hardness_both_size': 'Hardness Both Size',
234
+    'dimension': 'Dimension',
235
+    'dimension_app': 'Dimension Appearance',
236
+    'dimension_bal_weight': 'Dimension Balance/Weight',
237
+    'dim_bal_app_hard': 'Dimension Balance/Appearance/Hardness',
238
+    'dim_bal_app_rot_hard': 'Dimension Balance/Appearance/Rotation/Hardness',
239
+    'thickness_8_point': 'Thickness 8 Points',
240
+    'centering': 'Centering',
241
+}

+ 104 - 0
app/legacy/migrations/0003_allproductaverageobminmaxview_and_more.py

@@ -0,0 +1,104 @@
1
+# Generated by Django 4.2 on 2025-05-06 04:36
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('legacy', '0002_belmasterview_emasterview_mgmasterview_vmasterview'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.CreateModel(
14
+            name='AllProductAverageObMinMaxView',
15
+            fields=[
16
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
17
+                ('ProductCode', models.CharField(max_length=255, null=True)),
18
+                ('out_min', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
19
+                ('out_max', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
20
+                ('in_min', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
21
+                ('in_max', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
22
+            ],
23
+            options={
24
+                'db_table': 'AllProduct_Average_OB_MIN_MAX_view',
25
+                'managed': False,
26
+            },
27
+        ),
28
+        migrations.CreateModel(
29
+            name='AllProductDimensionForInsProcess',
30
+            fields=[
31
+                ('ProdType', models.CharField(max_length=255, null=True)),
32
+                ('ProductCode', models.CharField(max_length=255, primary_key=True, serialize=False)),
33
+                ('Size_Id', models.CharField(max_length=255, null=True)),
34
+                ('Size_Name', models.CharField(max_length=255, null=True)),
35
+                ('Std', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
36
+                ('TolUn', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
37
+                ('TolUp', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
38
+            ],
39
+            options={
40
+                'db_table': 'AllProduct_Dimension_ForInsProcess',
41
+                'managed': False,
42
+            },
43
+        ),
44
+        migrations.CreateModel(
45
+            name='AllProductPressPositionPressWeight',
46
+            fields=[
47
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
48
+                ('ProductCode', models.CharField(max_length=255, null=True)),
49
+                ('Lot_No', models.CharField(max_length=255, null=True)),
50
+                ('PO_Qty', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
51
+                ('UWeight', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
52
+                ('Current_ProNo', models.CharField(max_length=255, null=True)),
53
+                ('Press_Time', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
54
+                ('PressType_1', models.CharField(max_length=255, null=True)),
55
+                ('PressWeight_1', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
56
+                ('PressType_2', models.CharField(max_length=255, null=True)),
57
+                ('PressWeight_2', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
58
+                ('Press_Ton', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
59
+                ('Press_T', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
60
+                ('Press_T_Tol', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
61
+                ('Mold_D', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
62
+                ('Mold_T', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
63
+                ('SegMold_T', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
64
+                ('SegMold_D', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
65
+                ('Center_D', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
66
+                ('Center_T', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
67
+                ('LowerPlate_T', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
68
+                ('StudPlate_T', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
69
+                ('UpperPlate_T', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
70
+                ('PinPlate_T', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
71
+                ('TopConcave_T', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
72
+                ('BottomConcave_T', models.DecimalField(decimal_places=2, max_digits=10, null=True)),
73
+            ],
74
+            options={
75
+                'db_table': 'AllProduct_PressPosition_PressWeight',
76
+                'managed': False,
77
+            },
78
+        ),
79
+        migrations.CreateModel(
80
+            name='RotateBrokenTest',
81
+            fields=[
82
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
83
+                ('row_no', models.IntegerField(blank=True, null=True)),
84
+                ('speed_spec', models.FloatField(blank=True, null=True)),
85
+                ('speed_test', models.FloatField(blank=True, null=True)),
86
+                ('speedOk', models.CharField(blank=True, max_length=4, null=True)),
87
+                ('qty', models.IntegerField()),
88
+                ('station_no', models.IntegerField()),
89
+                ('created_at', models.DateTimeField(blank=True, null=True)),
90
+                ('updated_at', models.DateTimeField(blank=True, null=True)),
91
+                ('lot_no', models.CharField(blank=True, max_length=50, null=True)),
92
+                ('machine_id', models.IntegerField(blank=True, null=True)),
93
+                ('code', models.CharField(blank=True, max_length=50, null=True)),
94
+                ('emp_id', models.IntegerField()),
95
+                ('devid', models.CharField(blank=True, max_length=40, null=True)),
96
+                ('mode', models.CharField(blank=True, max_length=10, null=True)),
97
+                ('cal_mode', models.IntegerField(blank=True, null=True)),
98
+            ],
99
+            options={
100
+                'db_table': 'rotate_broken_test',
101
+                'managed': False,
102
+            },
103
+        ),
104
+    ]

+ 2 - 0
app/legacy/templates/legacy/datacrud_list.html

@@ -46,6 +46,8 @@
46 46
                         </a>
47 47
                     {% elif field.name == 'file' and obj.file %}
48 48
                     <a href="{{ obj.file.url }}" target="_blank">View</a>
49
+                    {% elif field.name == 'drawing' and obj.drawing %}
50
+                    <a href="{{ obj.drawing.url }}" target="_blank">View</a>
49 51
                     {% elif field.get_internal_type == "DateTimeField" %}
50 52
                         {{ obj|attr:field.name|date:"d/m/Y H:i" }}
51 53
                     {% else %}

+ 30 - 1
app/report/filters.py

@@ -1,5 +1,5 @@
1 1
 import django_filters
2
-from core.models import Report
2
+from core.models import Report, CustomerTemplateMapping, ProductDrawing
3 3
 
4 4
 class ReportFilter(django_filters.FilterSet):
5 5
     name = django_filters.CharFilter(
@@ -26,3 +26,32 @@ class ReportFilter(django_filters.FilterSet):
26 26
     class Meta:
27 27
         model = Report
28 28
         fields = ['name', 'created_by', 'created_at']
29
+
30
+
31
+class CustomerTemplateFilter(django_filters.FilterSet):
32
+    customer_name = django_filters.CharFilter(
33
+        field_name='customer_name',
34
+        lookup_expr='icontains',
35
+        label='Customer Name'
36
+    )
37
+
38
+    template_names = django_filters.CharFilter(
39
+        method='filter_template_names',
40
+        label='Template Name Contains'
41
+    )
42
+
43
+    class Meta:
44
+        model = CustomerTemplateMapping
45
+        fields = ['customer_name', 'template_names']
46
+
47
+    def filter_template_names(self, queryset, name, value):
48
+        return queryset.filter(template_names__icontains=value)
49
+
50
+class ProductDrawingFilter(django_filters.FilterSet):
51
+    code_no = django_filters.CharFilter(lookup_expr='icontains', label='Code No')
52
+    code_no_mks = django_filters.CharFilter(lookup_expr='icontains', label='Code No (MKS)')
53
+    lot_no = django_filters.CharFilter(lookup_expr='icontains', label='Lot No')
54
+
55
+    class Meta:
56
+        model = ProductDrawing
57
+        fields = ['code_no', 'code_no_mks', 'lot_no',]

+ 42 - 0
app/report/templates/report/customer_template_form.html

@@ -0,0 +1,42 @@
1
+{% extends "base.html" %}
2
+{% load legacy_filters %}
3
+{% load tailwind_filters %}
4
+
5
+{% block title %}
6
+    {% if view.title %}
7
+        {{ view.title }}
8
+    {% else %}
9
+        {{ view|class_name }}
10
+    {% endif %}
11
+{% endblock %}
12
+
13
+{% block content %}
14
+<div class="container mx-auto px-4 py-6">
15
+    <h1 class="text-2xl font-bold mb-6">
16
+        {% if view.title %}
17
+            {{ view.title }}
18
+        {% elif view|class_name == "CreateViewClass" %}
19
+            Create {{ model_verbose_name }}
20
+        {% else %}
21
+            Update {{ model_verbose_name }}
22
+        {% endif %}
23
+    </h1>
24
+
25
+    <!-- Render the Form -->
26
+    <form method="post" enctype="multipart/form-data" >
27
+        {% csrf_token %}
28
+        <div>
29
+          
30
+        {{ form|crispy }}
31
+        </div>
32
+        <div class='mt-4'>
33
+            <button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
34
+                Save
35
+            </button>
36
+            <a href="{% url list_url_name %}" class="bg-gray-300 text-gray-800 px-4 py-2 rounded hover:bg-gray-400">
37
+                Cancel
38
+            </a>
39
+        </div>
40
+    </form>
41
+</div>
42
+{% endblock %}

+ 126 - 0
app/report/templates/report/customer_template_list.html

@@ -0,0 +1,126 @@
1
+{% extends "base.html" %}
2
+{% load legacy_filters %}
3
+{% load tailwind_filters %}
4
+{% block title %}{{ page_title }}{% endblock %}
5
+
6
+{% block content %}
7
+<div class="container mx-auto px-4 py-6">
8
+    <h1 class="text-3xl font-bold text-gray-800 mb-4">{{ page_title }}</h1>
9
+
10
+    <!-- Filter Form -->
11
+    <form method="get" class="flex items-center space-x-4 mb-4">
12
+        {{ filter.form | crispy }}
13
+        <button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">Filter</button>
14
+        <a href="?" class="bg-gray-300 text-gray-800 px-4 py-2 rounded hover:bg-gray-400">Reset</a>
15
+    </form>
16
+
17
+    <!-- Create Button -->
18
+    <div class="my-4 flex">
19
+        <p class="text-gray-600 mb-4 mr-auto">
20
+            Total Records: {{ page_obj.paginator.count }}
21
+        </p>
22
+        <a href="{% url create_url %}" class="ml-auto bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600">
23
+            Create New Data
24
+        </a>
25
+    </div>
26
+
27
+    <!-- Data Table -->
28
+    <div class="clear-both bg-white shadow rounded-lg overflow-x-auto">
29
+<table class="w-full border-collapse border border-gray-200">
30
+    <thead>
31
+        <tr class="bg-gray-100 text-left text-sm uppercase">
32
+            {% for field in fields %}
33
+            <th class="border border-gray-200 px-4 py-2 text-left">{% firstof field.verbose_name field.name %} </th>
34
+            {% endfor %}
35
+            <th class="py-2 px-4 border-b">Actions</th>
36
+        </tr>
37
+    </thead>
38
+    <tbody>
39
+        {% for obj in page_obj %}
40
+            <tr class="hover:bg-gray-50">
41
+                {% for field in fields %}
42
+                <td class="border border-gray-200 px-4 py-2">
43
+                    {% if field.name == 'id' %}
44
+                        <a href="{% url update_url obj.pk %}" class="text-blue-500 hover:underline">
45
+                            {{ obj|attr:field.name }}
46
+                        </a>
47
+                    {% elif field.name == 'template_names' %}
48
+
49
+                      {% with template_list=obj|attr:field.name %}
50
+                          {% for t in template_list %}
51
+                              {{ sheet_names | get_item:t }}
52
+                              {% if not forloop.last %}, {% endif %}
53
+                          {% endfor %}
54
+                      {% endwith %}
55
+                    {% elif field.name == 'file' and obj.file %}
56
+                    <a href="{{ obj.file.url }}" target="_blank">View</a>
57
+                    {% elif field.get_internal_type == "DateTimeField" %}
58
+                        {{ obj|attr:field.name|date:"d/m/Y H:i" }}
59
+                    {% else %}
60
+                        {{ obj|attr:field.name | safe_floatformat:2 }}
61
+                    {% endif %}
62
+                </td>
63
+                {% endfor %}
64
+                <td class="py-2 px-4 border-b">
65
+                    <a href="{% url update_url obj.pk %}" 
66
+                       class="bg-blue-500 text-white px-3 py-2 rounded hover:bg-blue-600">Edit</a>
67
+                    <a href="{% url delete_url obj.pk %}" 
68
+                       class="bg-red-500 text-white px-3 py-2 rounded hover:bg-red-600">Delete</a>
69
+                </td>
70
+            </tr>
71
+        {% empty %}
72
+            <tr>
73
+                <td colspan="5" class="py-4 px-4 text-center text-gray-600">No data available.</td>
74
+            </tr>
75
+        {% endfor %}
76
+    </tbody>
77
+</table>
78
+    </div>
79
+
80
+    <!-- Pagination -->
81
+    <div class="mt-6 flex justify-between items-center">
82
+        <div>
83
+            <span class="text-sm text-gray-600">
84
+                Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
85
+            </span>
86
+        </div>
87
+        <div class="space-x-2">
88
+            {% if page_obj.has_previous %}
89
+                <a href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value|urlencode }}&{% endif %}{% endfor %}page=1" 
90
+                   class="text-blue-500 hover:underline">First</a>
91
+                <a href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value|urlencode }}&{% endif %}{% endfor %}page={{ page_obj.previous_page_number }}" 
92
+                   class="text-blue-500 hover:underline">Previous</a>
93
+            {% endif %}
94
+            {% for page_num in page_obj.paginator.page_range %}
95
+                {% if page_num == page_obj.number %}
96
+                    <span class="font-bold text-gray-700">{{ page_num }}</span>
97
+                {% elif page_num == 1 or page_num == page_obj.paginator.num_pages or page_num >= page_obj.number|add:"-2" and page_num <= page_obj.number|add:"2" %}
98
+                    <a href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value|urlencode }}&{% endif %}{% endfor %}page={{ page_num }}" 
99
+                       class="text-blue-500 hover:underline">{{ page_num }}</a>
100
+                {% elif page_num == page_obj.number|add:-3 or page_num == page_obj.number|add:3 %}
101
+                    <span class="mx-1">...</span>
102
+                {% endif %}
103
+            {% endfor %}
104
+            {% if page_obj.has_next %}
105
+                <a href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value|urlencode }}&{% endif %}{% endfor %}page={{ page_obj.next_page_number }}" 
106
+                   class="text-blue-500 hover:underline">Next</a>
107
+                <a href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value|urlencode }}&{% endif %}{% endfor %}page={{ page_obj.paginator.num_pages }}" 
108
+                   class="text-blue-500 hover:underline">Last</a>
109
+            {% endif %}
110
+        </div>
111
+    </div>
112
+</div>
113
+<style>
114
+/* Remove fixed layout and allow columns to grow dynamically */
115
+table {
116
+    table-layout: auto; /* Default is auto, can be explicitly set */
117
+    width: 100%; /* Ensures table spans available space */
118
+}
119
+
120
+th, td {
121
+    white-space: nowrap; /* Prevents text wrapping */
122
+    text-overflow: ellipsis; /* Adds ellipsis for overflowed content if combined with max-width */
123
+    vertical-align: top; /* Aligns content to the top */
124
+}
125
+</style>
126
+{% endblock %}

+ 12 - 1
app/report/urls.py

@@ -1,8 +1,10 @@
1 1
 from django.urls import path
2 2
 from . import views
3
-from .views import ReportCRUDView
3
+from .views import ReportCRUDView, CustomerTemplateCRUDView, ProductDrawingCRUDView
4 4
 
5 5
 report_crud = ReportCRUDView()
6
+customer_templates_crud = CustomerTemplateCRUDView()
7
+product_drawings_crud = ProductDrawingCRUDView()
6 8
 
7 9
 app_name = "report"  # Use this namespace for reverse URL lookups
8 10
 
@@ -16,6 +18,15 @@ urlpatterns = [
16 18
     path('coi/', views.coi_view, name='coi-view'),
17 19
     path('report/generate/', views.gen_report_view, name='gen_report'),
18 20
 
21
+    path('customer_templates/', customer_templates_crud.get_list_view().as_view(), name='customer_templates-list'),
22
+    path('customer_templates/create/', customer_templates_crud.get_create_view().as_view(), name='customer_templates-create'),
23
+    path('customer_templates/<str:pk>/update/', customer_templates_crud.get_update_view().as_view(), name='customer_templates-update'),
24
+    path('customer_templates/<str:pk>/delete/', customer_templates_crud.get_delete_view().as_view(), name='customer_templates-delete'),
25
+    
26
+    path('product_drawings/', product_drawings_crud.get_list_view().as_view(), name='product_drawings-list'),
27
+    path('product_drawings/create/', product_drawings_crud.get_create_view().as_view(), name='product_drawings-create'),
28
+    path('product_drawings/<str:pk>/update/', product_drawings_crud.get_update_view().as_view(), name='product_drawings-update'),
29
+    path('product_drawings/<str:pk>/delete/', product_drawings_crud.get_delete_view().as_view(), name='product_drawings-delete'),
19 30
     # path('create/', views.create_report, name='create'),  # Create a new report
20 31
     # path('<int:pk>/', views.detail_report, name='detail'),  # View details of a specific report
21 32
     # path('<int:pk>/update/', views.update_report, name='update'),  # Update a specific report

+ 84 - 16
app/report/views.py

@@ -1,10 +1,11 @@
1 1
 from django.shortcuts import render, redirect, get_object_or_404
2 2
 from django.core.paginator import Paginator
3 3
 from django.contrib import messages
4
-from core.models import Report, AllProductDimensionForInsProcess
5
-from core.forms import ReportForm
6
-from core.utils import ConfigurableCRUDView, queryFromMaster
7
-from .filters import ReportFilter
4
+from core.models import Report, AllProductDimensionForInsProcess, CustomerTemplateMapping, \
5
+                ProductDrawing
6
+from core.forms import ReportForm, CustomerTemplateMappingForm, ProductDrawingForm
7
+from core.utils import ConfigurableCRUDView, queryFromMaster, SHEET_NAMES
8
+from .filters import ReportFilter, CustomerTemplateFilter, ProductDrawingFilter
8 9
 from .forms import ExportOptionsForm
9 10
 from pprint import pprint
10 11
 
@@ -24,6 +25,10 @@ from django.conf import settings
24 25
 
25 26
 from itertools import chain
26 27
 
28
+from django_filters.views import FilterView
29
+
30
+from django.views.generic import (
31
+    ListView,)
27 32
 
28 33
 def index(request):
29 34
     reports = Report.objects.all()
@@ -886,18 +891,6 @@ def create_coi_file(lot_no, sheets, user, md):
886 891
     pprint(f"outputfile = {output_file}")
887 892
     return report
888 893
 
889
-SHEET_NAMES = {
890
-    'hardness_out': 'Hardness Out',
891
-    'hardness_out_in': 'Hardness Out/In', 
892
-    'hardness_both_size': 'Hardness Both Size',
893
-    'dimension': 'Dimension',
894
-    'dimension_app': 'Dimension Appearance',
895
-    'dimension_bal_weight': 'Dimension Balance/Weight',
896
-    'dim_bal_app_hard': 'Dimension Balance/Appearance/Hardness',
897
-    'dim_bal_app_rot_hard': 'Dimension Balance/Appearance/Rotation/Hardness',
898
-    'thickness_8_point': 'Thickness 8 Points',
899
-    'centering': 'Centering',
900
-}
901 894
 def get_fields(model):
902 895
     # model_fields = {f.name: f for f in model._meta.get_fields()}
903 896
     # fields = list(model_fields.values())
@@ -1051,3 +1044,78 @@ def gen_report_view(request):
1051 1044
 
1052 1045
 
1053 1046
 
1047
+class CustomerTemplateCRUDView(ConfigurableCRUDView):
1048
+    model = CustomerTemplateMapping
1049
+    list_template_name = 'report/customer_template_list.html'
1050
+    detail_template_name = 'legacy/datacrud_detail.html'
1051
+    form_template_name = 'report/customer_template_form.html'
1052
+    confirm_delete_template_name = 'legacy/datacrud_confirm_delete.html'
1053
+    filterset_class = CustomerTemplateFilter
1054
+
1055
+    page_title = "Customer Template Mapping"
1056
+
1057
+    # URL name mappings
1058
+    list_url_name = 'report:customer_templates-list'
1059
+    create_url_name = 'report:customer_templates-create'
1060
+    update_url_name = 'report:customer_templates-update'
1061
+    delete_url_name = 'report:customer_templates-delete'
1062
+    config_fields = ["id", "customer_name", "template_names", "created_at"] 
1063
+    config_field_orders = ["id", "customer_name", "template_names", "created_at",  "created_by"]
1064
+    # config_readonly_fields = ["lot_no"]
1065
+    config_edit_fields = None
1066
+    ordering = ["-created_at", "-id",]
1067
+    form_class = CustomerTemplateMappingForm
1068
+
1069
+
1070
+    def get_list_view(self):
1071
+        class ListViewClass(FilterView, ListView):
1072
+            model = self.model
1073
+            template_name = self.list_template_name
1074
+            paginate_by = self.paginate_by
1075
+            filterset_class = self.filterset_class
1076
+            ordering = self.ordering
1077
+
1078
+            def get_context_data(inner_self, **kwargs):
1079
+                context = super().get_context_data(**kwargs)
1080
+                fields = self.get_fields()
1081
+                context.update({
1082
+                    'fields': [f for f in fields],
1083
+                    'sheet_names': SHEET_NAMES,
1084
+
1085
+                    # 'fields': [field for field in self.model._meta.get_fields()],
1086
+                    'page_title': self.page_title,
1087
+                    'list_url': self.list_url_name,
1088
+                    'create_url': self.create_url_name,
1089
+                    'update_url': self.update_url_name,
1090
+                    'delete_url': self.delete_url_name,
1091
+                    'bs': self.get_breadcrumbs('list'),
1092
+                })
1093
+                return context
1094
+
1095
+        return ListViewClass
1096
+
1097
+
1098
+
1099
+class ProductDrawingCRUDView(ConfigurableCRUDView):
1100
+    model = ProductDrawing
1101
+    list_template_name = 'legacy/datacrud_list.html'
1102
+    detail_template_name = 'legacy/datacrud_detail.html'
1103
+    form_template_name = 'legacy/datacrud_form.html'
1104
+    confirm_delete_template_name = 'legacy/datacrud_confirm_delete.html'
1105
+    filterset_class = ProductDrawingFilter
1106
+
1107
+    page_title = "Product Drawing"
1108
+
1109
+    # URL name mappings
1110
+    list_url_name = 'report:product_drawings-list'
1111
+    create_url_name = 'report:product_drawings-create'
1112
+    update_url_name = 'report:product_drawings-update'
1113
+    delete_url_name = 'report:product_drawings-delete'
1114
+    config_fields = ["id", "code_no", "code_no_mks", "lot_no", "drawing", "description", "created_at"] 
1115
+    #config_field_orders = ["id", "customer_name", "template_names", "created_at",  "created_by"]
1116
+    # config_readonly_fields = ["lot_no"]
1117
+    config_edit_fields = None
1118
+    ordering = ["-created_at", "-id",]
1119
+    form_class = ProductDrawingForm
1120
+
1121
+


+ 18 - 0
app/sysadmin/migrations/0002_alter_userprofile_position.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 4.2 on 2025-05-06 04:36
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('sysadmin', '0001_initial'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AlterField(
14
+            model_name='userprofile',
15
+            name='position',
16
+            field=models.CharField(blank=True, choices=[('QA_STAFF', 'QA Staff'), ('QA_MANAGER', 'QA. MG.'), ('QA_AST_MANAGER', 'QA. Asst. MG.'), ('QA_ENGINEER', 'QA. Engineer')], max_length=20, null=True),
17
+        ),
18
+    ]

+ 2 - 0
app/templates/base.html

@@ -66,6 +66,8 @@
66 66
                 <li><a href="/dashboard/" class="flex items-center p-2 text-gray-900 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-white"><span class="ml-3">Dashboard</span></a></li>
67 67
                 <li><a href="{% url "report:coi-view" %}" class="flex items-center p-2 text-gray-900 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-white"><span class="ml-3">COI</span></a></li>
68 68
                 <li><a href="{% url "report:report-list" %}" class="flex items-center p-2 text-gray-900 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-white"><span class="ml-3">Reports</span></a></li>
69
+                <li><a href="{% url "report:customer_templates-list" %}" class="flex items-center p-2 text-gray-900 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-white"><span class="ml-3">Customer Templates</span></a></li>
70
+                <li><a href="{% url "report:product_drawings-list" %}" class="flex items-center p-2 text-gray-900 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-white"><span class="ml-3">Drawing</span></a></li>
69 71
                  <li>
70 72
                     <button type="button" class="flex items-center w-full p-2 text-base text-gray-900 transition duration-75 rounded-lg group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700" aria-controls="dropdown-example" data-collapse-toggle="dropdown-example">
71 73
                           <svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">