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

+ 10 - 12
app/coi/routers.py

4
     targeting separate databases.
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
     def db_for_read(self, model, **hints):
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
     def db_for_write(self, model, **hints):
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
     def allow_relation(self, obj1, obj2, **hints):
24
     def allow_relation(self, obj1, obj2, **hints):
27
         """
25
         """
28
         Allow relations if both objects belong to the same database.
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
         if obj1._state.db in db_set and obj2._state.db in db_set:
29
         if obj1._state.db in db_set and obj2._state.db in db_set:
32
             return True
30
             return True
33
         return None
31
         return None
37
         Ensure migrations are applied only to the appropriate database.
35
         Ensure migrations are applied only to the appropriate database.
38
         """
36
         """
39
         if app_label in self.route_app_labels:
37
         if app_label in self.route_app_labels:
40
-            return db == "OB2011DB"
38
+            return db == self.route_app_labels[app_label]
41
         return db == "default"
39
         return db == "default"

+ 12 - 0
app/coi/settings.py

145
             'extra_params':  'Encrypt=no',
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
 from django import forms
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
 class ReportForm(forms.ModelForm):
7
 class ReportForm(forms.ModelForm):
5
     class Meta:
8
     class Meta:
6
         model = Report
9
         model = Report
7
         fields = ['name', 'created_by', 'file']  # Include the fields you want in the form
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

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

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

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
         managed = False  # This model corresponds to a database view
463
         managed = False  # This model corresponds to a database view
464
         app_label = "legacy"
464
         app_label = "legacy"
465
         db_table = "rotate_broken_test"  # Matches SQL Server table name
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
 )
12
 )
13
 from django.core.paginator import Paginator
13
 from django.core.paginator import Paginator
14
 from core.models import MgMasterView, VMasterView, BelMasterView, EMasterView
14
 from core.models import MgMasterView, VMasterView, BelMasterView, EMasterView
15
+from pprint import pprint
15
 
16
 
16
 class ConfigurableCRUDView:
17
 class ConfigurableCRUDView:
17
     model = None
18
     model = None
44
         - Respects `config_field_orders` and `config_excludes`.
45
         - Respects `config_field_orders` and `config_excludes`.
45
         Returns field instances instead of field names.
46
         Returns field instances instead of field names.
46
         """
47
         """
48
+        pprint("get_fields")
47
         model_fields = {f.name: f for f in self.model._meta.get_fields()}
49
         model_fields = {f.name: f for f in self.model._meta.get_fields()}
48
 
50
 
49
         # Filter based on `config_fields` configuration
51
         # Filter based on `config_fields` configuration
65
         # Reorder fields to match the order specified in `config_field_orders`
67
         # Reorder fields to match the order specified in `config_field_orders`
66
         ordered_field_names = set(self.config_field_orders)
68
         ordered_field_names = set(self.config_field_orders)
67
         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))
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
         return ordered_fields + remaining_fields
70
         return ordered_fields + remaining_fields
70
 
71
 
71
 
72
 
226
         results.extend(queryset)
227
         results.extend(queryset)
227
     return results
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

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
                         </a>
46
                         </a>
47
                     {% elif field.name == 'file' and obj.file %}
47
                     {% elif field.name == 'file' and obj.file %}
48
                     <a href="{{ obj.file.url }}" target="_blank">View</a>
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
                     {% elif field.get_internal_type == "DateTimeField" %}
51
                     {% elif field.get_internal_type == "DateTimeField" %}
50
                         {{ obj|attr:field.name|date:"d/m/Y H:i" }}
52
                         {{ obj|attr:field.name|date:"d/m/Y H:i" }}
51
                     {% else %}
53
                     {% else %}

+ 30 - 1
app/report/filters.py

1
 import django_filters
1
 import django_filters
2
-from core.models import Report
2
+from core.models import Report, CustomerTemplateMapping, ProductDrawing
3
 
3
 
4
 class ReportFilter(django_filters.FilterSet):
4
 class ReportFilter(django_filters.FilterSet):
5
     name = django_filters.CharFilter(
5
     name = django_filters.CharFilter(
26
     class Meta:
26
     class Meta:
27
         model = Report
27
         model = Report
28
         fields = ['name', 'created_by', 'created_at']
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

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

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
 from django.urls import path
1
 from django.urls import path
2
 from . import views
2
 from . import views
3
-from .views import ReportCRUDView
3
+from .views import ReportCRUDView, CustomerTemplateCRUDView, ProductDrawingCRUDView
4
 
4
 
5
 report_crud = ReportCRUDView()
5
 report_crud = ReportCRUDView()
6
+customer_templates_crud = CustomerTemplateCRUDView()
7
+product_drawings_crud = ProductDrawingCRUDView()
6
 
8
 
7
 app_name = "report"  # Use this namespace for reverse URL lookups
9
 app_name = "report"  # Use this namespace for reverse URL lookups
8
 
10
 
16
     path('coi/', views.coi_view, name='coi-view'),
18
     path('coi/', views.coi_view, name='coi-view'),
17
     path('report/generate/', views.gen_report_view, name='gen_report'),
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
     # path('create/', views.create_report, name='create'),  # Create a new report
30
     # path('create/', views.create_report, name='create'),  # Create a new report
20
     # path('<int:pk>/', views.detail_report, name='detail'),  # View details of a specific report
31
     # path('<int:pk>/', views.detail_report, name='detail'),  # View details of a specific report
21
     # path('<int:pk>/update/', views.update_report, name='update'),  # Update a specific report
32
     # path('<int:pk>/update/', views.update_report, name='update'),  # Update a specific report

+ 84 - 16
app/report/views.py

1
 from django.shortcuts import render, redirect, get_object_or_404
1
 from django.shortcuts import render, redirect, get_object_or_404
2
 from django.core.paginator import Paginator
2
 from django.core.paginator import Paginator
3
 from django.contrib import messages
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
 from .forms import ExportOptionsForm
9
 from .forms import ExportOptionsForm
9
 from pprint import pprint
10
 from pprint import pprint
10
 
11
 
24
 
25
 
25
 from itertools import chain
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
 def index(request):
33
 def index(request):
29
     reports = Report.objects.all()
34
     reports = Report.objects.all()
886
     pprint(f"outputfile = {output_file}")
891
     pprint(f"outputfile = {output_file}")
887
     return report
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
 def get_fields(model):
894
 def get_fields(model):
902
     # model_fields = {f.name: f for f in model._meta.get_fields()}
895
     # model_fields = {f.name: f for f in model._meta.get_fields()}
903
     # fields = list(model_fields.values())
896
     # fields = list(model_fields.values())
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

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
                 <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>
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
                 <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>
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
                 <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>
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
                  <li>
71
                  <li>
70
                     <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">
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
                           <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">
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">