Преглед изворни кода

feat: add QA.2 default user, auto-select QA fields in COI, boolean icon UI

- Add qa2_default field to UserProfile with single-user enforcement via save()
- Add migration for qa2_default field
- Expose qa2_default in UserCustomForm and UserCRUDView list table (after Superuser Status column)
- Auto-select current logged-in user as QA.1 in COI view
- Auto-select qa2_default user as QA.2 in COI view (persists after lot search)
- Default export template changed from Japanese to ASEAN
- Render boolean columns as centered check/cross SVG icons in datacrud_list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
tum пре 5 часа
родитељ
комит
02a8e14f85

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

@@ -50,6 +50,14 @@
50 50
                     <a href="{{ obj.drawing.url }}" target="_blank">View</a>
51 51
                     {% elif field.get_internal_type == "DateTimeField" %}
52 52
                         {{ obj|attr:field.name|date:"d/m/Y H:i" }}
53
+                    {% elif obj|attr:field.name|is_bool %}
54
+                        <div class="flex justify-center">
55
+                        {% if obj|attr:field.name %}
56
+                            <svg class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
57
+                        {% else %}
58
+                            <svg class="w-5 h-5 text-gray-300" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
59
+                        {% endif %}
60
+                        </div>
53 61
                     {% else %}
54 62
                         {{ obj|attr:field.name | safe_floatformat:2 }}
55 63
                     {% endif %}

+ 4 - 0
app/legacy/templatetags/legacy_filters.py

@@ -48,3 +48,7 @@ def safe_floatformat(value, decimal_places):
48 48
 @register.filter
49 49
 def get_item(dictionary, key):
50 50
     return dictionary.get(key, None)
51
+
52
+@register.filter
53
+def is_bool(value):
54
+    return isinstance(value, bool)

+ 6 - 10
app/report/templates/report/coi.html

@@ -174,18 +174,14 @@ th, td {
174 174
     return {
175 175
       lot_no: '{{ lot_no }}', // Bind this to the input value
176 176
       exports: {{ selected_templates|default:"[]"|safe }},
177
-      exportTemplate: '{{ export_template|default:"japanese" }}',
178
-      qa1: null,
179
-      qa2: null,
180
-      gen_report_url: '{% url "report:gen_report" %}', 
181
-      acceptStatus: 'accepted', 
177
+      exportTemplate: '{{ export_template|default:"asean" }}',
178
+      qa1: '{{ current_user_id }}',
179
+      qa2: '{{ qa2_default_user_id }}',
180
+      gen_report_url: '{% url "report:gen_report" %}',
181
+      acceptStatus: 'accepted',
182 182
       downloadUrl: null, // Stores the download link after export success
183 183
       downText: "Download Excel",
184
-      init() {
185
-        //alert("COI Report");
186
-        this.qa1 = '';
187
-        this.qa2 = '';
188
-      },
184
+      init() {},
189 185
       async exportCOI() {
190 186
               if (!this.lot_no) {
191 187
                   alert("Please enter a Lot No.");

+ 12 - 5
app/report/views.py

@@ -20,6 +20,7 @@ from django.http import JsonResponse, HttpResponseBadRequest
20 20
 import json
21 21
 from django.contrib.auth.decorators import login_required
22 22
 from django.contrib.auth.models import User
23
+from sysadmin.models import UserProfile
23 24
 from legacy.models import Data
24 25
 from django.conf import settings
25 26
 
@@ -1301,6 +1302,8 @@ def coi_view(request):
1301 1302
     pprint(f"xxxx method = xxx {request.method}")
1302 1303
     users = User.objects.all()
1303 1304
     export_template = "japanese"
1305
+    qa2_profile = UserProfile.objects.filter(qa2_default=True).select_related('user').first()
1306
+    qa2_default_user_id = qa2_profile.user.id if qa2_profile else ''
1304 1307
 
1305 1308
     if request.method == "POST":
1306 1309
         pprint(request.POST)
@@ -1388,18 +1391,22 @@ def coi_view(request):
1388 1391
 
1389 1392
 
1390 1393
 
1391
-                return render(request, 'report/coi.html', {'result': first_result, 
1392
-                                                           'pcs':pcs, 
1394
+                return render(request, 'report/coi.html', {'result': first_result,
1395
+                                                           'pcs':pcs,
1393 1396
                                                            'size_str': size_str,
1394 1397
                                                            'lot_no': lot_no,
1395
-                                                           'spec': spec, 'users': users, 'SHEET_NAMES': SHEET_NAMES, 
1398
+                                                           'spec': spec, 'users': users, 'SHEET_NAMES': SHEET_NAMES,
1396 1399
                                                            'results': results, 'fields': fields, 'selected_templates': selected_templates, 'code': code,
1397
-                                                           'export_template': export_template})
1400
+                                                           'export_template': export_template,
1401
+                                                           'current_user_id': request.user.id,
1402
+                                                           'qa2_default_user_id': qa2_default_user_id})
1398 1403
 
1399 1404
         messages.success(request, "Request Sent")
1400 1405
         return redirect(request.path_info)
1401 1406
     return render(request, 'report/coi.html', {'SHEET_NAMES': SHEET_NAMES, 'users': users,
1402
-                                               'export_template': export_template})
1407
+                                               'export_template': export_template,
1408
+                                               'current_user_id': request.user.id,
1409
+                                               'qa2_default_user_id': qa2_default_user_id})
1403 1410
 
1404 1411
 
1405 1412
 @csrf_exempt  # Disable CSRF for API requests (ensure this is secure in production)

+ 5 - 2
app/sysadmin/forms.py

@@ -48,10 +48,11 @@ class UserCustomForm(forms.ModelForm):
48 48
     profile_picture = forms.ImageField(required=False, label="Profile Picture")
49 49
     signed_picture = forms.ImageField(required=False, label="Signed Picture")
50 50
     position = forms.ChoiceField(
51
-        choices=UserProfile.POSITION_CHOICES, 
52
-        required=False, 
51
+        choices=UserProfile.POSITION_CHOICES,
52
+        required=False,
53 53
         label="Position"
54 54
     )
55
+    qa2_default = forms.BooleanField(required=False, label="QA.2 Default")
55 56
 
56 57
     class Meta:
57 58
         model = User
@@ -74,6 +75,7 @@ class UserCustomForm(forms.ModelForm):
74 75
             self.fields['profile_picture'].initial = profile_instance.profile_picture
75 76
             self.fields['signed_picture'].initial = profile_instance.signed_picture
76 77
             self.fields['position'].initial = profile_instance.position
78
+            self.fields['qa2_default'].initial = profile_instance.qa2_default
77 79
 
78 80
     def save(self, commit=True):
79 81
         # Save the User instance first
@@ -82,6 +84,7 @@ class UserCustomForm(forms.ModelForm):
82 84
             'profile_picture': self.cleaned_data.get('profile_picture'),
83 85
             'signed_picture': self.cleaned_data.get('signed_picture'),
84 86
             'position': self.cleaned_data.get('position'),
87
+            'qa2_default': self.cleaned_data.get('qa2_default'),
85 88
         }
86 89
 
87 90
         # Ensure profile exists for the user

+ 18 - 0
app/sysadmin/migrations/0003_userprofile_qa2_default.py

@@ -0,0 +1,18 @@
1
+# Generated by Django 4.2 on 2026-03-23 00:17
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('sysadmin', '0002_alter_userprofile_position'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.AddField(
14
+            model_name='userprofile',
15
+            name='qa2_default',
16
+            field=models.BooleanField(default=False),
17
+        ),
18
+    ]

+ 6 - 0
app/sysadmin/models.py

@@ -17,6 +17,12 @@ class UserProfile(models.Model):
17 17
     signed_picture = models.ImageField(upload_to="signed/%Y/%m/%d/", blank=True, null=True)
18 18
     email = models.EmailField(blank=True, null=True)  # New email field
19 19
     position = models.CharField(max_length=20, choices=POSITION_CHOICES, blank=True, null=True)  # New position field
20
+    qa2_default = models.BooleanField(default=False)
21
+
22
+    def save(self, *args, **kwargs):
23
+        if self.qa2_default:
24
+            UserProfile.objects.exclude(pk=self.pk).filter(qa2_default=True).update(qa2_default=False)
25
+        super().save(*args, **kwargs)
20 26
 
21 27
     def __str__(self):
22 28
         pos = self.get_position_display()

+ 24 - 0
app/sysadmin/views.py

@@ -8,6 +8,8 @@ from django.contrib.auth.decorators import login_required
8 8
 from django.contrib import messages
9 9
 from core.utils import ConfigurableCRUDView
10 10
 from django.contrib.auth.models import User
11
+from django.db.models import F
12
+from types import SimpleNamespace
11 13
 
12 14
 def login_view(request):
13 15
     if request.method == "POST":
@@ -87,3 +89,25 @@ class UserCRUDView(ConfigurableCRUDView):
87 89
     form_class = UserCustomForm
88 90
     config_edit_fields = None
89 91
     ordering = ["-id"]
92
+
93
+    def get_list_view(self):
94
+        ViewClass = super().get_list_view()
95
+        outer = self
96
+
97
+        class UserListView(ViewClass):
98
+            def get_queryset(self):
99
+                qs = super().get_queryset()
100
+                return qs.select_related('profile').annotate(
101
+                    qa2_default=F('profile__qa2_default')
102
+                )
103
+
104
+            def get_context_data(self, **kwargs):
105
+                context = super().get_context_data(**kwargs)
106
+                qa2_field = SimpleNamespace(name='qa2_default', verbose_name='QA.2 Default')
107
+                fields = list(context['fields'])
108
+                idx = next((i for i, f in enumerate(fields) if f.name == 'is_superuser'), len(fields) - 1)
109
+                fields.insert(idx + 1, qa2_field)
110
+                context['fields'] = fields
111
+                return context
112
+
113
+        return UserListView