Bladeren bron

genreport

tum 1 jaar geleden
bovenliggende
commit
4c91f0c519

+ 1 - 0
app/coi/settings.py

@@ -226,6 +226,7 @@ CRISPY_ALLOWED_TEMPLATE_PACKS = "tailwind"
226 226
 CRISPY_TEMPLATE_PACK = "tailwind"
227 227
 
228 228
 LOGIN_URL = '/sysadmin/login/'  # Replace with the path to your login view
229
+LOGIN_REDIRECT_URL = '/sysadmin/profile/'  # Optional
229 230
 
230 231
 DATABASE_ROUTERS = ["coi.routers.DatabaseRouter"]
231 232
 

+ 0 - 0
app/core/management/commands/__init__.py


+ 14 - 0
app/core/management/commands/create_profile.py

@@ -0,0 +1,14 @@
1
+from django.core.management.base import BaseCommand
2
+from django.contrib.auth.models import User
3
+from sysadmin.models import UserProfile
4
+
5
+class Command(BaseCommand):
6
+    help = "Create missing profiles for existing users"
7
+
8
+    def handle(self, *args, **kwargs):
9
+        users_without_profiles = User.objects.filter(profile__isnull=True)
10
+        for user in users_without_profiles:
11
+            UserProfile.objects.create(user=user)
12
+            self.stdout.write(f"Created profile for {user.username}")
13
+
14
+        self.stdout.write(self.style.SUCCESS("All missing profiles have been created!"))

+ 25 - 0
app/core/migrations/0005_delete_belmasterview_delete_emasterview_and_more.py

@@ -0,0 +1,25 @@
1
+# Generated by Django 4.2 on 2025-01-17 04:58
2
+
3
+from django.db import migrations
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('core', '0004_allproductaverageobminmaxview_and_more'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.DeleteModel(
14
+            name='BelMasterView',
15
+        ),
16
+        migrations.DeleteModel(
17
+            name='EMasterView',
18
+        ),
19
+        migrations.DeleteModel(
20
+            name='MgMasterView',
21
+        ),
22
+        migrations.DeleteModel(
23
+            name='VMasterView',
24
+        ),
25
+    ]

+ 300 - 0
app/legacy/migrations/0002_belmasterview_emasterview_mgmasterview_vmasterview.py

@@ -0,0 +1,300 @@
1
+# Generated by Django 4.2 on 2025-01-17 04:58
2
+
3
+from django.db import migrations, models
4
+
5
+
6
+class Migration(migrations.Migration):
7
+
8
+    dependencies = [
9
+        ('legacy', '0001_initial'),
10
+    ]
11
+
12
+    operations = [
13
+        migrations.CreateModel(
14
+            name='BelMasterView',
15
+            fields=[
16
+                ('PRO0', models.CharField(max_length=255, primary_key=True, serialize=False)),
17
+                ('PRO1', models.CharField(max_length=255, null=True)),
18
+                ('PRO1C', models.CharField(max_length=255, null=True)),
19
+                ('PRO2', models.CharField(max_length=255, null=True)),
20
+                ('PRO5', models.CharField(max_length=255, null=True)),
21
+                ('PRO8', models.CharField(max_length=255, null=True)),
22
+                ('PRO9', models.CharField(max_length=255, null=True)),
23
+                ('PRO10', models.CharField(max_length=255, null=True)),
24
+                ('PRO11', models.CharField(max_length=255, null=True)),
25
+                ('PRO12', models.CharField(max_length=255, null=True)),
26
+                ('PRO13', models.CharField(max_length=255, null=True)),
27
+                ('PRO14', models.CharField(max_length=255, null=True)),
28
+                ('PRO15', models.CharField(max_length=255, null=True)),
29
+                ('PRO16', models.CharField(max_length=255, null=True)),
30
+                ('PRO17', models.CharField(max_length=255, null=True)),
31
+                ('PRO18', models.CharField(max_length=255, null=True)),
32
+                ('PRO21', models.CharField(max_length=255, null=True)),
33
+                ('PRO25', models.CharField(max_length=255, null=True)),
34
+                ('PRO27', models.CharField(max_length=255, null=True)),
35
+                ('P2', models.CharField(max_length=255, null=True)),
36
+                ('PRO6', models.CharField(max_length=255, null=True)),
37
+                ('SPEED', models.CharField(max_length=255, null=True)),
38
+                ('PRO4', models.CharField(max_length=255, null=True)),
39
+                ('MC11', models.CharField(max_length=255, null=True)),
40
+                ('MC12', models.CharField(max_length=255, null=True)),
41
+                ('MC14', models.CharField(max_length=255, null=True)),
42
+                ('MC15', models.CharField(max_length=255, null=True)),
43
+                ('MC16', models.CharField(max_length=255, null=True)),
44
+                ('MC19', models.CharField(max_length=255, null=True)),
45
+                ('MC20', models.CharField(max_length=255, null=True)),
46
+                ('MC21', models.CharField(max_length=255, null=True)),
47
+                ('MC22', models.CharField(max_length=255, null=True)),
48
+                ('MC23', models.CharField(max_length=255, null=True)),
49
+                ('MC24', models.CharField(max_length=255, null=True)),
50
+                ('MP45', models.CharField(max_length=255, null=True)),
51
+                ('MP49', models.CharField(max_length=255, null=True)),
52
+                ('MI13', models.CharField(max_length=255, null=True)),
53
+                ('TC', models.CharField(max_length=255, null=True)),
54
+                ('MI14', models.CharField(max_length=255, null=True)),
55
+                ('MI15', models.CharField(max_length=255, null=True)),
56
+                ('MI16', models.CharField(max_length=255, null=True)),
57
+                ('MI17', models.CharField(max_length=255, null=True)),
58
+                ('MI18', models.CharField(max_length=255, null=True)),
59
+                ('MI19', models.CharField(max_length=255, null=True)),
60
+                ('MI20', models.CharField(max_length=255, null=True)),
61
+                ('MI21', models.CharField(max_length=255, null=True)),
62
+                ('MI22', models.CharField(max_length=255, null=True)),
63
+                ('MI23', models.CharField(max_length=255, null=True)),
64
+                ('MI31', models.CharField(max_length=255, null=True)),
65
+                ('MI33', models.CharField(max_length=255, null=True)),
66
+                ('INSAGM', models.CharField(max_length=255, null=True)),
67
+                ('MARAGM', models.CharField(max_length=255, null=True)),
68
+                ('MI53', models.CharField(max_length=255, null=True)),
69
+                ('MI55', models.CharField(max_length=255, null=True)),
70
+                ('MI36', models.CharField(max_length=255, null=True)),
71
+                ('MI39', models.CharField(max_length=255, null=True)),
72
+                ('MI24', models.CharField(max_length=255, null=True)),
73
+                ('Ind1', models.CharField(max_length=255, null=True)),
74
+                ('Ind2', models.CharField(max_length=255, null=True)),
75
+                ('Ind3', models.CharField(max_length=255, null=True)),
76
+                ('AGR1', models.CharField(max_length=255, null=True)),
77
+                ('AGR2', models.CharField(max_length=255, null=True)),
78
+                ('ob_Condition', models.CharField(max_length=255, null=True)),
79
+            ],
80
+            options={
81
+                'db_table': 'bel_master_view',
82
+                'managed': False,
83
+            },
84
+        ),
85
+        migrations.CreateModel(
86
+            name='EMasterView',
87
+            fields=[
88
+                ('PRO0', models.CharField(max_length=255, primary_key=True, serialize=False)),
89
+                ('PRO1', models.CharField(max_length=255, null=True)),
90
+                ('PRO1C', models.CharField(max_length=255, null=True)),
91
+                ('PRO2', models.CharField(max_length=255, null=True)),
92
+                ('PRO5', models.CharField(max_length=255, null=True)),
93
+                ('PRO8', models.CharField(max_length=255, null=True)),
94
+                ('PRO9', models.CharField(max_length=255, null=True)),
95
+                ('PRO10', models.CharField(max_length=255, null=True)),
96
+                ('PRO11', models.CharField(max_length=255, null=True)),
97
+                ('PRO12', models.CharField(max_length=255, null=True)),
98
+                ('PRO13', models.CharField(max_length=255, null=True)),
99
+                ('PRO14', models.CharField(max_length=255, null=True)),
100
+                ('PRO15', models.CharField(max_length=255, null=True)),
101
+                ('PRO16', models.CharField(max_length=255, null=True)),
102
+                ('PRO17', models.CharField(max_length=255, null=True)),
103
+                ('PRO18', models.CharField(max_length=255, null=True)),
104
+                ('PRO21', models.CharField(max_length=255, null=True)),
105
+                ('PRO25', models.CharField(max_length=255, null=True)),
106
+                ('PRO27', models.CharField(max_length=255, null=True)),
107
+                ('P2', models.CharField(max_length=255, null=True)),
108
+                ('PRO6', models.CharField(max_length=255, null=True)),
109
+                ('SPEED', models.CharField(max_length=255, null=True)),
110
+                ('PRO4', models.CharField(max_length=255, null=True)),
111
+                ('MC11', models.CharField(max_length=255, null=True)),
112
+                ('MC12', models.CharField(max_length=255, null=True)),
113
+                ('MC14', models.CharField(max_length=255, null=True)),
114
+                ('MC15', models.CharField(max_length=255, null=True)),
115
+                ('MC16', models.CharField(max_length=255, null=True)),
116
+                ('MC19', models.CharField(max_length=255, null=True)),
117
+                ('MC20', models.CharField(max_length=255, null=True)),
118
+                ('MC21', models.CharField(max_length=255, null=True)),
119
+                ('MC22', models.CharField(max_length=255, null=True)),
120
+                ('MC23', models.CharField(max_length=255, null=True)),
121
+                ('MC24', models.CharField(max_length=255, null=True)),
122
+                ('MP45', models.CharField(max_length=255, null=True)),
123
+                ('MP49', models.CharField(max_length=255, null=True)),
124
+                ('MI13', models.CharField(max_length=255, null=True)),
125
+                ('TC', models.CharField(max_length=255, null=True)),
126
+                ('MI14', models.CharField(max_length=255, null=True)),
127
+                ('MI15', models.CharField(max_length=255, null=True)),
128
+                ('MI16', models.CharField(max_length=255, null=True)),
129
+                ('MI17', models.CharField(max_length=255, null=True)),
130
+                ('MI18', models.CharField(max_length=255, null=True)),
131
+                ('MI19', models.CharField(max_length=255, null=True)),
132
+                ('MI20', models.CharField(max_length=255, null=True)),
133
+                ('MI21', models.CharField(max_length=255, null=True)),
134
+                ('MI22', models.CharField(max_length=255, null=True)),
135
+                ('MI23', models.CharField(max_length=255, null=True)),
136
+                ('MI31', models.CharField(max_length=255, null=True)),
137
+                ('MI33', models.CharField(max_length=255, null=True)),
138
+                ('INSAGM', models.CharField(max_length=255, null=True)),
139
+                ('MARAGM', models.CharField(max_length=255, null=True)),
140
+                ('MI53', models.CharField(max_length=255, null=True)),
141
+                ('MI55', models.CharField(max_length=255, null=True)),
142
+                ('MI36', models.CharField(max_length=255, null=True)),
143
+                ('MI39', models.CharField(max_length=255, null=True)),
144
+                ('MI24', models.CharField(max_length=255, null=True)),
145
+                ('Ind1', models.CharField(max_length=255, null=True)),
146
+                ('Ind2', models.CharField(max_length=255, null=True)),
147
+                ('Ind3', models.CharField(max_length=255, null=True)),
148
+                ('AGR1', models.CharField(max_length=255, null=True)),
149
+                ('AGR2', models.CharField(max_length=255, null=True)),
150
+                ('ob_Condition', models.CharField(max_length=255, null=True)),
151
+            ],
152
+            options={
153
+                'db_table': 'e_master_view',
154
+                'managed': False,
155
+            },
156
+        ),
157
+        migrations.CreateModel(
158
+            name='MgMasterView',
159
+            fields=[
160
+                ('PRO0', models.CharField(max_length=255, primary_key=True, serialize=False)),
161
+                ('PRO1', models.CharField(max_length=255, null=True)),
162
+                ('PRO1C', models.CharField(max_length=255, null=True)),
163
+                ('PRO2', models.CharField(max_length=255, null=True)),
164
+                ('PRO5', models.CharField(max_length=255, null=True)),
165
+                ('PRO8', models.CharField(max_length=255, null=True)),
166
+                ('PRO9', models.CharField(max_length=255, null=True)),
167
+                ('PRO10', models.CharField(max_length=255, null=True)),
168
+                ('PRO11', models.CharField(max_length=255, null=True)),
169
+                ('PRO12', models.CharField(max_length=255, null=True)),
170
+                ('PRO13', models.CharField(max_length=255, null=True)),
171
+                ('PRO14', models.CharField(max_length=255, null=True)),
172
+                ('PRO15', models.CharField(max_length=255, null=True)),
173
+                ('PRO16', models.CharField(max_length=255, null=True)),
174
+                ('PRO17', models.CharField(max_length=255, null=True)),
175
+                ('PRO18', models.CharField(max_length=255, null=True)),
176
+                ('PRO21', models.CharField(max_length=255, null=True)),
177
+                ('PRO25', models.CharField(max_length=255, null=True)),
178
+                ('PRO27', models.CharField(max_length=255, null=True)),
179
+                ('P2', models.CharField(max_length=255, null=True)),
180
+                ('SPEED', models.CharField(max_length=255, null=True)),
181
+                ('PRO4', models.CharField(max_length=255, null=True)),
182
+                ('MC11', models.CharField(max_length=255, null=True)),
183
+                ('MC12', models.CharField(max_length=255, null=True)),
184
+                ('MC14', models.CharField(max_length=255, null=True)),
185
+                ('MC15', models.CharField(max_length=255, null=True)),
186
+                ('MC16', models.CharField(max_length=255, null=True)),
187
+                ('MC19', models.CharField(max_length=255, null=True)),
188
+                ('MC20', models.CharField(max_length=255, null=True)),
189
+                ('MC21', models.CharField(max_length=255, null=True)),
190
+                ('MC22', models.CharField(max_length=255, null=True)),
191
+                ('MC23', models.CharField(max_length=255, null=True)),
192
+                ('MC24', models.CharField(max_length=255, null=True)),
193
+                ('MP45', models.CharField(max_length=255, null=True)),
194
+                ('MP49', models.CharField(max_length=255, null=True)),
195
+                ('MI13', models.CharField(max_length=255, null=True)),
196
+                ('TC', models.CharField(max_length=255, null=True)),
197
+                ('MI14', models.CharField(max_length=255, null=True)),
198
+                ('MI15', models.CharField(max_length=255, null=True)),
199
+                ('MI16', models.CharField(max_length=255, null=True)),
200
+                ('MI17', models.CharField(max_length=255, null=True)),
201
+                ('MI18', models.CharField(max_length=255, null=True)),
202
+                ('MI19', models.CharField(max_length=255, null=True)),
203
+                ('MI20', models.CharField(max_length=255, null=True)),
204
+                ('MI21', models.CharField(max_length=255, null=True)),
205
+                ('MI22', models.CharField(max_length=255, null=True)),
206
+                ('MI23', models.CharField(max_length=255, null=True)),
207
+                ('MI31', models.CharField(max_length=255, null=True)),
208
+                ('MI33', models.CharField(max_length=255, null=True)),
209
+                ('INSAGM', models.CharField(max_length=255, null=True)),
210
+                ('MARAGM', models.CharField(max_length=255, null=True)),
211
+                ('MI53', models.CharField(max_length=255, null=True)),
212
+                ('MI55', models.CharField(max_length=255, null=True)),
213
+                ('MI36', models.CharField(max_length=255, null=True)),
214
+                ('MI39', models.CharField(max_length=255, null=True)),
215
+                ('MI24', models.CharField(max_length=255, null=True)),
216
+                ('Ind1', models.CharField(max_length=255, null=True)),
217
+                ('Ind2', models.CharField(max_length=255, null=True)),
218
+                ('Ind3', models.CharField(max_length=255, null=True)),
219
+                ('AGR1', models.CharField(max_length=255, null=True)),
220
+                ('AGR2', models.CharField(max_length=255, null=True)),
221
+                ('ob_Condition', models.CharField(max_length=255, null=True)),
222
+            ],
223
+            options={
224
+                'db_table': 'mg_master_view',
225
+                'managed': False,
226
+            },
227
+        ),
228
+        migrations.CreateModel(
229
+            name='VMasterView',
230
+            fields=[
231
+                ('PRO0', models.CharField(max_length=255, primary_key=True, serialize=False)),
232
+                ('PRO1', models.CharField(max_length=255, null=True)),
233
+                ('PRO1C', models.CharField(max_length=255, null=True)),
234
+                ('PRO2', models.CharField(max_length=255, null=True)),
235
+                ('PRO5', models.CharField(max_length=255, null=True)),
236
+                ('PRO8', models.CharField(max_length=255, null=True)),
237
+                ('PRO9', models.CharField(max_length=255, null=True)),
238
+                ('PRO10', models.CharField(max_length=255, null=True)),
239
+                ('PRO11', models.CharField(max_length=255, null=True)),
240
+                ('PRO12', models.CharField(max_length=255, null=True)),
241
+                ('PRO13', models.CharField(max_length=255, null=True)),
242
+                ('PRO14', models.CharField(max_length=255, null=True)),
243
+                ('PRO15', models.CharField(max_length=255, null=True)),
244
+                ('PRO16', models.CharField(max_length=255, null=True)),
245
+                ('PRO17', models.CharField(max_length=255, null=True)),
246
+                ('PRO18', models.CharField(max_length=255, null=True)),
247
+                ('PRO21', models.CharField(max_length=255, null=True)),
248
+                ('PRO25', models.CharField(max_length=255, null=True)),
249
+                ('PRO27', models.CharField(max_length=255, null=True)),
250
+                ('P2', models.CharField(max_length=255, null=True)),
251
+                ('PRO6', models.CharField(max_length=255, null=True)),
252
+                ('SPEED', models.CharField(max_length=255, null=True)),
253
+                ('PRO4', models.CharField(max_length=255, null=True)),
254
+                ('MC11', models.CharField(max_length=255, null=True)),
255
+                ('MC12', models.CharField(max_length=255, null=True)),
256
+                ('MC14', models.CharField(max_length=255, null=True)),
257
+                ('MC15', models.CharField(max_length=255, null=True)),
258
+                ('MC16', models.CharField(max_length=255, null=True)),
259
+                ('MC19', models.CharField(max_length=255, null=True)),
260
+                ('MC20', models.CharField(max_length=255, null=True)),
261
+                ('MC21', models.CharField(max_length=255, null=True)),
262
+                ('MC22', models.CharField(max_length=255, null=True)),
263
+                ('MC23', models.CharField(max_length=255, null=True)),
264
+                ('MC24', models.CharField(max_length=255, null=True)),
265
+                ('MP34', models.CharField(max_length=255, null=True)),
266
+                ('MP39', models.CharField(max_length=255, null=True)),
267
+                ('MI13', models.CharField(max_length=255, null=True)),
268
+                ('TC', models.CharField(max_length=255, null=True)),
269
+                ('MI14', models.CharField(max_length=255, null=True)),
270
+                ('MI15', models.CharField(max_length=255, null=True)),
271
+                ('MI16', models.CharField(max_length=255, null=True)),
272
+                ('MI17', models.CharField(max_length=255, null=True)),
273
+                ('MI18', models.CharField(max_length=255, null=True)),
274
+                ('MI19', models.CharField(max_length=255, null=True)),
275
+                ('MI20', models.CharField(max_length=255, null=True)),
276
+                ('MI21', models.CharField(max_length=255, null=True)),
277
+                ('MI22', models.CharField(max_length=255, null=True)),
278
+                ('MI23', models.CharField(max_length=255, null=True)),
279
+                ('MI31', models.CharField(max_length=255, null=True)),
280
+                ('MI33', models.CharField(max_length=255, null=True)),
281
+                ('INSAGM', models.CharField(max_length=255, null=True)),
282
+                ('MARAGM', models.CharField(max_length=255, null=True)),
283
+                ('MI53', models.CharField(max_length=255, null=True)),
284
+                ('MI55', models.CharField(max_length=255, null=True)),
285
+                ('MI36', models.CharField(max_length=255, null=True)),
286
+                ('MI39', models.CharField(max_length=255, null=True)),
287
+                ('MI24', models.CharField(max_length=255, null=True)),
288
+                ('Ind1', models.CharField(max_length=255, null=True)),
289
+                ('Ind2', models.CharField(max_length=255, null=True)),
290
+                ('Ind3', models.CharField(max_length=255, null=True)),
291
+                ('AGR1', models.CharField(max_length=255, null=True)),
292
+                ('AGR2', models.CharField(max_length=255, null=True)),
293
+                ('ob_Condition', models.CharField(max_length=255, null=True)),
294
+            ],
295
+            options={
296
+                'db_table': 'v_master_view',
297
+                'managed': False,
298
+            },
299
+        ),
300
+    ]

BIN
app/report/checkbox_checked.jpg


BIN
app/report/checkbox_unchecked.jpg


BIN
app/report/coi_templates.xlsx


+ 168 - 0
app/report/gen_report.py

@@ -0,0 +1,168 @@
1
+
2
+from openpyxl import load_workbook, Workbook
3
+from datetime import datetime
4
+from openpyxl.drawing.image import Image
5
+import re
6
+
7
+from openpyxl.drawing.spreadsheet_drawing import AbsoluteAnchor
8
+from openpyxl.drawing.xdr import XDRPoint2D, XDRPositiveSize2D
9
+from openpyxl.utils.units import pixels_to_EMU
10
+from openpyxl.utils import get_column_letter
11
+
12
+def center_image_in_cell(ws, cell_address, img_path):
13
+    """
14
+    Center an image in a specific cell.
15
+
16
+    Args:
17
+        ws: The worksheet object.
18
+        cell_address (str): The cell address (e.g., "B2") where the image will be centered.
19
+        img_path (str): Path to the image file.
20
+
21
+    Returns:
22
+        None
23
+    """
24
+    # Load the image
25
+    img = Image(img_path)
26
+    img.width = img.height = 20
27
+
28
+    # Get the cell
29
+    cell = ws[cell_address]
30
+
31
+    # Approximate pixel dimensions of the cell
32
+    col_letter = get_column_letter(cell.column)
33
+    col_width = ws.column_dimensions[col_letter].width or 10  # Default width
34
+    row_height = ws.row_dimensions[cell.row].height or 15  # Default height
35
+
36
+    # Convert dimensions to pixels
37
+    col_width_pixels = col_width * 7  # Approximation: 1 Excel unit = ~7 pixels
38
+    row_height_pixels = row_height * 0.75  # Approximation: 1 Excel unit = ~0.75 pixels
39
+
40
+    # Calculate the center position
41
+    cell_left = pixels_to_EMU(cell.column - 1)  # Column start position in EMU
42
+    cell_top = pixels_to_EMU((cell.row - 1) * row_height_pixels)  # Row start position in EMU
43
+    x_center = cell_left + pixels_to_EMU((col_width_pixels - img.width) / 2)
44
+    y_center = cell_top + pixels_to_EMU((row_height_pixels - img.height) / 2)
45
+
46
+    # Set the image position and size
47
+    position = XDRPoint2D(x_center, y_center)
48
+    size = XDRPositiveSize2D(pixels_to_EMU(img.width), pixels_to_EMU(img.height))
49
+    img.anchor = AbsoluteAnchor(pos=position, ext=size)
50
+
51
+    # Add the image to the worksheet
52
+    ws.add_image(img)
53
+
54
+
55
+def gen_xlsx(template_file, selected_sheets, prefix_filename, data):
56
+    """
57
+    Generate an Excel file from a template, fill placeholders, and include only selected sheets.
58
+
59
+    Args:
60
+        template_file (str): Path to the Excel template file.
61
+        selected_sheets (list): List of sheet names to include in the output file.
62
+        prefix_filename (str): Prefix for the output filename.
63
+        data (dict): Data dictionary with sheet-specific keys and fallback keys.
64
+
65
+    Returns:
66
+        str: Path of the generated Excel file.
67
+    """
68
+    checked_image_path = "/app/report/checkbox_checked.jpg"  # Path to the checked checkbox image
69
+    unchecked_image_path = "/app/report/checkbox_unchecked.jpg"  # Path to the unchecked checkbox image
70
+
71
+    # Load the template workbook
72
+    workbook = load_workbook(template_file)
73
+
74
+    # Remove sheets not in selected_sheets
75
+    for sheet_name in workbook.sheetnames:
76
+        if sheet_name not in selected_sheets:
77
+            del workbook[sheet_name]
78
+
79
+    # Process the selected sheets
80
+    for sheet_name in selected_sheets:
81
+        if sheet_name not in workbook.sheetnames:
82
+            raise ValueError(f"Sheet '{sheet_name}' not found in the template.")
83
+
84
+        sheet = workbook[sheet_name]
85
+
86
+        # Replace placeholders with actual values
87
+        # Handle hiding rows based on patterns in data
88
+        
89
+        for row in sheet.iter_rows():
90
+            for cell in row:
91
+                if cell.value and isinstance(cell.value, str) and cell.value.startswith("<") and cell.value.endswith(">"):
92
+                    placeholder = cell.value.strip("<>")
93
+                    
94
+                    # Determine value priority: `sheet_name.key` > `key`
95
+                    value = None
96
+                    sheet_specific_key = f"{sheet_name}.{placeholder}"
97
+                    if sheet_specific_key in data:
98
+                        value = data[sheet_specific_key]
99
+                    elif placeholder in data:
100
+                        value = data[placeholder]
101
+
102
+                    if value is not None:
103
+                        if value is True:
104
+                            img = Image(checked_image_path)
105
+                            img.width = img.height = 20
106
+                            print(f"{cell.coordinate}")
107
+                            sheet.add_image(img, cell.coordinate)
108
+                            cell.value = None  # Remove the placeholder text
109
+                        elif value is False:
110
+                            img = Image(unchecked_image_path)
111
+                            img.width = img.height = 20
112
+                            sheet.add_image(img, cell.coordinate)
113
+                            # center_image_in_cell(sheet, cell.coordinate, unchecked_image_path)
114
+                            cell.value = None  # Remove the placeholder text
115
+                        else:
116
+                            # Insert the text value directly
117
+                            cell.value = value
118
+
119
+        for key, value in data.items():
120
+            if isinstance(value, str) and re.match(r"^\d+\[\d+:\d+\]$", value):
121
+                # Parse the prefix and row range
122
+                prefix, row_range = value.split("[")
123
+                row_start, row_end = map(int, row_range[:-1].split(":"))
124
+                
125
+                # Hide rows if the prefix matches the condition
126
+                if prefix == "0":  # Adjust the condition as needed
127
+                    sheet.row_dimensions.group(row_start, row_end, hidden=True)
128
+        
129
+
130
+    # Generate the output filename with a timestamp
131
+    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
132
+    output_path = f"{prefix_filename}_{timestamp}.xlsx"
133
+    workbook.save(output_path)
134
+
135
+    return output_path
136
+
137
+if __name__ == "__main__":
138
+    # Example usage
139
+    data = {
140
+        "customer": "Tum Coder",
141
+        "inspect_date": "2025-01-15",
142
+        "lot_no": "12345",
143
+        "staff_name":  "Tum 8888",
144
+        "man_name":  "Tum 999",
145
+        "size": "Large",
146
+        "pcs": "10 pcs",
147
+        "spec": "Spec-A",
148
+        "hardness.d1_act": "10",
149
+        "hardness.d2_act": "0[24:28]",  # Hide rows 24 to 28 if the prefix is "0"
150
+        "hardness.acc": True,  # Hide rows 24 to 28 if the prefix is "0"
151
+        "hardness.spe_acc": False,  # Hide rows 24 to 28 if the prefix is "0"
152
+        "dimension_app.d1_act": "33",
153
+        "dimension_app.d2_act": "0[26:32]",  # Hide rows 24 to 28 if the prefix is "0"
154
+        "dimension_app.acc": True,  # Hide rows 24 to 28 if the prefix is "0"
155
+        "dimension_app.spe_acc": True,  # Hide rows 24 to 28 if the prefix is "0"
156
+    }
157
+
158
+
159
+
160
+
161
+    output_file = gen_xlsx(
162
+        template_file="./hardness.xlsx",
163
+        selected_sheets=["hardness", "dimension_app"],  # Replace with your actual sheet names
164
+        prefix_filename="./output/output",
165
+        data=data
166
+    )
167
+
168
+    print(f"Generated file: {output_file}")

+ 42 - 0
app/report/views.py

@@ -8,6 +8,10 @@ from .filters import ReportFilter
8 8
 from .forms import ExportOptionsForm
9 9
 from pprint import pprint
10 10
 
11
+from .gen_report import gen_xlsx
12
+from django.core.files.base import File
13
+from pathlib import Path
14
+
11 15
 
12 16
 def index(request):
13 17
     reports = Report.objects.all()
@@ -61,6 +65,44 @@ class ReportCRUDView(ConfigurableCRUDView):
61 65
 def coi_view(request):
62 66
     pprint(f"xxxx method = xxx {request.method}")
63 67
     if request.method == "POST":
68
+        pprint(request.POST)
69
+        if 'export' in request.POST:
70
+
71
+            data = {
72
+                "customer": "Tum Coder",
73
+                "inspect_date": "2025-01-15",
74
+                "lot_no": "12345",
75
+                "staff_name":  "Tum 8888",
76
+                "man_name":  "Tum 999",
77
+                "size": "Large",
78
+                "pcs": "10 pcs",
79
+                "spec": "Spec-A",
80
+                "hardness.d1_act": "10",
81
+                "hardness.d2_act": "0[24:28]",  # Hide rows 24 to 28 if the prefix is "0"
82
+                "hardness.acc": True,  # Hide rows 24 to 28 if the prefix is "0"
83
+                "hardness.spe_acc": False,  # Hide rows 24 to 28 if the prefix is "0"
84
+                "dimension_app.d1_act": "33",
85
+                "dimension_app.d2_act": "0[26:32]",  # Hide rows 24 to 28 if the prefix is "0"
86
+                "dimension_app.acc": True,  # Hide rows 24 to 28 if the prefix is "0"
87
+                "dimension_app.spe_acc": True,  # Hide rows 24 to 28 if the prefix is "0"
88
+            }
89
+            output_file = gen_xlsx(
90
+                template_file="/app/report/coi_templates.xlsx",
91
+                selected_sheets=["hardness", "dimension_app"],  # Replace with your actual sheet names
92
+                prefix_filename="/app/media/coi",
93
+                data=data
94
+            )
95
+            report = Report.objects.create(
96
+                name=request.POST.get('lot_no','Untitled'),
97
+                created_by=request.user,
98
+                file=None  # Leave this as None or assign a file if required
99
+            )
100
+            output_file_path = Path(output_file)  # Convert to a Path object for convenience
101
+            with open(output_file_path, "rb") as f:
102
+                report.file.save(output_file_path.name, File(f), save=True)
103
+
104
+            pprint(f"outputfile = {output_file}")
105
+
64 106
         if 'search_lot' in request.POST:
65 107
             lot_no = request.POST.get('lot_no', None)
66 108
             if lot_no:

+ 3 - 0
app/sysadmin/apps.py

@@ -4,3 +4,6 @@ from django.apps import AppConfig
4 4
 class SysadminConfig(AppConfig):
5 5
     default_auto_field = 'django.db.models.BigAutoField'
6 6
     name = 'sysadmin'
7
+
8
+    def ready(self):
9
+        import sysadmin.signals

+ 8 - 0
app/sysadmin/forms.py

@@ -14,6 +14,8 @@ class CustomLoginForm(AuthenticationForm):
14 14
 from django.contrib.auth.forms import UserCreationForm
15 15
 from django.contrib.auth.models import User
16 16
 
17
+from .models import UserProfile
18
+
17 19
 class CustomUserCreationForm(UserCreationForm):
18 20
     class Meta:
19 21
         model = User
@@ -33,3 +35,9 @@ class CustomUserCreationForm(UserCreationForm):
33 35
         self.fields['password2'].widget.attrs.update({
34 36
             'placeholder': 'Confirm Password'
35 37
         })
38
+
39
+
40
+class UserProfileForm(forms.ModelForm):
41
+    class Meta:
42
+        model = UserProfile
43
+        fields = ['profile_picture', 'position', 'signed_picture']  # Include the fields you want to manage

+ 29 - 0
app/sysadmin/migrations/0001_initial.py

@@ -0,0 +1,29 @@
1
+# Generated by Django 4.2 on 2025-01-17 04:58
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
+    initial = True
11
+
12
+    dependencies = [
13
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14
+    ]
15
+
16
+    operations = [
17
+        migrations.CreateModel(
18
+            name='UserProfile',
19
+            fields=[
20
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21
+                ('bio', models.TextField(blank=True, null=True)),
22
+                ('profile_picture', models.ImageField(blank=True, null=True, upload_to='profile/%Y/%m/%d/')),
23
+                ('signed_picture', models.ImageField(blank=True, null=True, upload_to='signed/%Y/%m/%d/')),
24
+                ('email', models.EmailField(blank=True, max_length=254, null=True)),
25
+                ('position', models.CharField(blank=True, choices=[('QA_STAFF', 'QA Staff'), ('QA_MANAGER', 'QA Manager')], max_length=20, null=True)),
26
+                ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)),
27
+            ],
28
+        ),
29
+    ]

+ 17 - 0
app/sysadmin/models.py

@@ -1,3 +1,20 @@
1 1
 from django.db import models
2 2
 
3 3
 # Create your models here.
4
+
5
+from django.contrib.auth.models import User
6
+
7
+class UserProfile(models.Model):
8
+    POSITION_CHOICES = [
9
+        ('QA_STAFF', 'QA Staff'),
10
+        ('QA_MANAGER', 'QA Manager'),
11
+    ]
12
+    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
13
+    bio = models.TextField(blank=True, null=True)
14
+    profile_picture = models.ImageField(upload_to="profile/%Y/%m/%d/", blank=True, null=True)
15
+    signed_picture = models.ImageField(upload_to="signed/%Y/%m/%d/", blank=True, null=True)
16
+    email = models.EmailField(blank=True, null=True)  # New email field
17
+    position = models.CharField(max_length=20, choices=POSITION_CHOICES, blank=True, null=True)  # New position field
18
+
19
+    def __str__(self):
20
+        return self.user.username

+ 13 - 0
app/sysadmin/signals.py

@@ -0,0 +1,13 @@
1
+from django.db.models.signals import post_save
2
+from django.dispatch import receiver
3
+from django.contrib.auth.models import User
4
+from .models import UserProfile
5
+
6
+@receiver(post_save, sender=User)
7
+def create_user_profile(sender, instance, created, **kwargs):
8
+    if created:
9
+        UserProfile.objects.create(user=instance)
10
+
11
+@receiver(post_save, sender=User)
12
+def save_user_profile(sender, instance, **kwargs):
13
+    instance.profile.save()

+ 94 - 0
app/sysadmin/templates/sysadmin/profile.html

@@ -0,0 +1,94 @@
1
+{% extends "base.html" %}
2
+{% load tailwind_filters %}
3
+
4
+{% block title %}Report Dashboard{% endblock %}
5
+{% block content %}
6
+<div class="container mx-auto px-4 py-8">
7
+<h1 class="text-2xl font-bold text-gray-700 mb-6">Your Profile</h1>
8
+
9
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
10
+            <!-- Username -->
11
+            <div class="flex items-center bg-gray-50 p-4 rounded-lg shadow-sm border">
12
+                <div class="flex-shrink-0 bg-indigo-100 rounded-full h-12 w-12 flex items-center justify-center">
13
+                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6">
14
+  <path strokeLinecap="round" strokeLinejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
15
+</svg>
16
+
17
+                </div>
18
+                <div class="ml-4">
19
+                    <h3 class="text-lg font-medium text-gray-900">Username</h3>
20
+                    <p class="text-gray-600">{{ user.username }}</p>
21
+                </div>
22
+            </div>
23
+
24
+            <!-- Email -->
25
+            <div class="flex items-center bg-gray-50 p-4 rounded-lg shadow-sm border">
26
+                <div class="flex-shrink-0 bg-indigo-100 rounded-full h-12 w-12 flex items-center justify-center">
27
+                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
28
+  <path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75" />
29
+</svg>
30
+
31
+                </div>
32
+                <div class="ml-4">
33
+                    <h3 class="text-lg font-medium text-gray-900">Email</h3>
34
+                    <p class="text-gray-600">{{ user.email }}</p>
35
+                </div>
36
+            </div>
37
+        </div>
38
+<form method="POST" enctype="multipart/form-data" class='space-y-4'>
39
+    {% csrf_token %}
40
+              <!-- First Name -->
41
+              <div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
42
+
43
+                <div>
44
+                  <label for="first_name" class="block text-sm font-medium text-gray-700">First Name</label>
45
+                  <input
46
+                      type="text"
47
+                      name="first_name"
48
+                      id="first_name"
49
+                      value="{{ user.first_name }}"
50
+                      class="block w-full mt-2 rounded-md border-gray-300 shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
51
+                      />
52
+                </div>
53
+
54
+                <!-- Last Name -->
55
+                <div>
56
+                  <label for="last_name" class="block text-sm font-medium text-gray-700">Last Name</label>
57
+                  <input
58
+                      type="text"
59
+                      name="last_name"
60
+                      id="last_name"
61
+                      value="{{ user.last_name }}"
62
+                      class="block w-full mt-2 rounded-md border-gray-300 shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
63
+                      />
64
+                </div>
65
+              </div>
66
+    {% if form.instance.profile_picture %}
67
+    <!-- Show uploaded profile picture -->
68
+    <img src="{{ form.instance.profile_picture.url }}" 
69
+         alt="Profile Picture" 
70
+         class="w-32 h-32 rounded-full mb-4 shadow-md">
71
+    {% else %}
72
+    <!-- Placeholder image if no profile picture -->
73
+    <div class="w-32 h-32 rounded-full bg-gray-200 flex items-center justify-center text-gray-500 mb-4">
74
+      <span>No Image</span>
75
+    </div>
76
+    {% endif %}
77
+    {{ form.profile_picture }}
78
+    {% if form.instance.signed_picture %}
79
+    <!-- Show uploaded profile picture -->
80
+    <img src="{{ form.instance.signed_picture.url }}" 
81
+         alt="Profile Picture" 
82
+         class="w-32 mb-4 shadow-md">
83
+    {% else %}
84
+    <!-- Placeholder image if no profile picture -->
85
+    <div class="w-32 h-32 rounded-full bg-gray-200 flex items-center justify-center text-gray-500 mb-4">
86
+      <span>No Image</span>
87
+    </div>
88
+    {% endif %}
89
+    {{ form.signed_picture }}
90
+    {{ form.position | as_crispy_field }}
91
+    <button type="submit" class="px-6 py-2 bg-indigo-600 text-white font-medium rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">Save Changes</button>
92
+</form>
93
+</div>
94
+{% endblock %}

+ 2 - 0
app/sysadmin/urls.py

@@ -7,4 +7,6 @@ urlpatterns = [
7 7
     path('login/', views.login_view, name='login'),
8 8
     path('register/', views.register_view, name='register'),
9 9
     path('logout/', views.logout_view, name='logout'),
10
+    path('profile/', views.profile_view, name='profile'),  # Add profile view URL
11
+
10 12
 ]

+ 32 - 1
app/sysadmin/views.py

@@ -1,7 +1,10 @@
1 1
 # views.py
2 2
 from django.contrib.auth import authenticate, login, logout
3 3
 from django.shortcuts import render, redirect
4
-from .forms import CustomLoginForm, CustomUserCreationForm
4
+from .forms import CustomLoginForm, CustomUserCreationForm, UserProfileForm
5
+from .models import UserProfile
6
+from django.contrib.auth.decorators import login_required
7
+from django.contrib import messages
5 8
 
6 9
 def login_view(request):
7 10
     if request.method == "POST":
@@ -30,3 +33,31 @@ def register_view(request):
30 33
 def logout_view(request):
31 34
     logout(request)  # Logs out the user
32 35
     return redirect('sysadmin:login')  # Redirect to the login page after logout
36
+
37
+@login_required
38
+def profile_view(request):
39
+    try:
40
+        # Get the profile for the current user
41
+        profile = request.user.profile
42
+    except UserProfile.DoesNotExist:
43
+        # Create a profile if it doesn't exist
44
+        profile = UserProfile.objects.create(user=request.user)
45
+
46
+    if request.method == "POST":
47
+        form = UserProfileForm(request.POST, request.FILES, instance=profile)
48
+
49
+        user = request.user
50
+        user.first_name = request.POST.get('first_name', user.first_name)
51
+        user.last_name = request.POST.get('last_name', user.last_name)
52
+
53
+        if form.is_valid():
54
+            form.save()
55
+            user.save()
56
+            messages.success(request, "Profile Updated")
57
+            return redirect('sysadmin:profile')  # Redirect to the profile page after saving
58
+        else:
59
+            messages.error(request, form.errors)
60
+    else:
61
+        form = UserProfileForm(instance=profile)
62
+
63
+    return render(request, 'sysadmin/profile.html', {'form': form})

+ 2 - 2
app/templates/base.html

@@ -7,7 +7,7 @@
7 7
 <head>
8 8
     <meta charset="UTF-8">
9 9
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
10
-    <title>{% block title %}My Django App{% endblock %}</title>
10
+    <title>{% block title %}COI System{% endblock %}</title>
11 11
 
12 12
     <!-- TailwindCSS -->
13 13
     <!--
@@ -39,7 +39,7 @@
39 39
 
40 40
                 {% if user.is_authenticated %}
41 41
                     <!-- Logged-in User -->
42
-                    <span class="text-gray-600 dark:text-gray-300">Hello, {{ user.username }}</span>
42
+                    <a href="{% url 'sysadmin:profile' %}"><span class="text-gray-600 dark:text-gray-300">Hello, {{ user.username }}</span></a>
43 43
                     <a href="{% url 'sysadmin:logout' %}" class="text-blue-500 hover:underline">Logout</a>
44 44
                 {% else %}
45 45
                     <!-- Guest User -->

BIN
hardness.xlsx


+ 3 - 1
test_xlsx2.py

@@ -139,6 +139,8 @@ if __name__ == "__main__":
139 139
         "customer": "Tum Coder",
140 140
         "inspect_date": "2025-01-15",
141 141
         "lot_no": "12345",
142
+        "staff_name":  "Tum 8888",
143
+        "man_name":  "Tum 999",
142 144
         "size": "Large",
143 145
         "pcs": "10 pcs",
144 146
         "spec": "Spec-A",
@@ -149,7 +151,7 @@ if __name__ == "__main__":
149 151
         "dimension_app.d1_act": "33",
150 152
         "dimension_app.d2_act": "0[26:32]",  # Hide rows 24 to 28 if the prefix is "0"
151 153
         "dimension_app.acc": True,  # Hide rows 24 to 28 if the prefix is "0"
152
-        "dimension_app.spe_acc": False,  # Hide rows 24 to 28 if the prefix is "0"
154
+        "dimension_app.spe_acc": True,  # Hide rows 24 to 28 if the prefix is "0"
153 155
     }
154 156
 
155 157