瀏覽代碼

Dockerfile

tum 11 月之前
父節點
當前提交
811230361d

+ 2 - 0
Dockerfile

@@ -1,5 +1,7 @@
1 1
 # Use Python 3.10-slim as a base
2 2
 FROM python:3.10-slim
3
+ENV PYTHONUNBUFFERED=1
4
+
3 5
 
4 6
 # Install system dependencies for MS SQL ODBC (comment out if not using MSSQL)
5 7
 # Install system dependencies for MS SQL ODBC and command-line tools

+ 1 - 1
app/legacy/templates/legacy/datacrud_list.html

@@ -30,7 +30,7 @@
30 30
     <thead>
31 31
         <tr class="bg-gray-100 text-left text-sm uppercase">
32 32
             {% for field in fields %}
33
-                <th class="border border-gray-200 px-4 py-2 text-left">{{ field.verbose_name }}</th>
33
+            <th class="border border-gray-200 px-4 py-2 text-left">{% firstof field.verbose_name field.name %} </th>
34 34
             {% endfor %}
35 35
             <th class="py-2 px-4 border-b">Actions</th>
36 36
         </tr>

二進制
app/report/coi_templates.xlsx


+ 143 - 43
app/report/gen_report.py

@@ -7,53 +7,133 @@ import re
7 7
 from openpyxl.drawing.spreadsheet_drawing import AbsoluteAnchor
8 8
 from openpyxl.drawing.xdr import XDRPoint2D, XDRPositiveSize2D
9 9
 from openpyxl.utils.units import pixels_to_EMU
10
-from openpyxl.utils import get_column_letter
10
+from openpyxl.utils import get_column_letter, column_index_from_string
11 11
 from django.db import models
12 12
 import os
13 13
 from django.db.models.fields.files import ImageFieldFile
14 14
 from pprint import pprint
15
+from PIL import Image as PILImage
16
+from openpyxl.drawing.xdr import XDRPoint2D, XDRPositiveSize2D
17
+from openpyxl.utils.units import pixels_to_EMU
18
+from openpyxl.drawing.spreadsheet_drawing import AnchorMarker, TwoCellAnchor
19
+
15 20
 
16
-def center_image_in_cell(ws, cell_address, img_path):
21
+def set_image_with_offset_old(sheet, img, cell_coordinate, offset_x=0, offset_y=0):
17 22
     """
18
-    Center an image in a specific cell.
23
+    Add an image to the sheet with an offset relative to the top-left corner of a cell.
19 24
 
20
-    Args:
21
-        ws: The worksheet object.
22
-        cell_address (str): The cell address (e.g., "B2") where the image will be centered.
23
-        img_path (str): Path to the image file.
25
+    :param sheet: The worksheet
26
+    :param img: The openpyxl Image object
27
+    :param cell_coordinate: Cell to place the image (e.g., "B2")
28
+    :param offset_x: Horizontal offset in pixels
29
+    :param offset_y: Vertical offset in pixels
30
+    """
31
+    col_letter = ''.join(filter(str.isalpha, cell_coordinate))  # Extract column letter
32
+    row_number = int(''.join(filter(str.isdigit, cell_coordinate)))  # Extract row number
24 33
 
25
-    Returns:
26
-        None
34
+    # Get column width and row height in pixels
35
+    col_width = sheet.column_dimensions[get_column_letter(column_index_from_string(col_letter))].width or 10
36
+    row_height = sheet.row_dimensions[row_number].height or 15
37
+
38
+    # Approximate conversion of Excel units to pixels
39
+    col_pixels = col_width * 7.5  # Excel's ~7.5 pixels per width unit
40
+    row_pixels = row_height * 0.75  # Approximation for row height in pixels
41
+
42
+    # Calculate absolute positions based on offsets
43
+    anchor_x = col_pixels + offset_x
44
+    anchor_y = row_pixels + offset_y
45
+
46
+    # Set the anchor for the image
47
+    img.anchor = cell_coordinate
48
+    img.anchor.dx = int(anchor_x * 9525)  # Convert to EMUs (Excel Measurement Units)
49
+    img.anchor.dy = int(anchor_y * 9525)  # Convert to EMUs (Excel Measurement Units)
50
+
51
+    sheet.add_image(img)
52
+
53
+
54
+def set_image_with_offset(sheet, img, cell_coordinate, offset_x=0, offset_y=0):
27 55
     """
28
-    # Load the image
29
-    img = Image(img_path)
30
-    img.width = img.height = 20
31
-
32
-    # Get the cell
33
-    cell = ws[cell_address]
34
-
35
-    # Approximate pixel dimensions of the cell
36
-    col_letter = get_column_letter(cell.column)
37
-    col_width = ws.column_dimensions[col_letter].width or 10  # Default width
38
-    row_height = ws.row_dimensions[cell.row].height or 15  # Default height
39
-
40
-    # Convert dimensions to pixels
41
-    col_width_pixels = col_width * 7  # Approximation: 1 Excel unit = ~7 pixels
42
-    row_height_pixels = row_height * 0.75  # Approximation: 1 Excel unit = ~0.75 pixels
43
-
44
-    # Calculate the center position
45
-    cell_left = pixels_to_EMU(cell.column - 1)  # Column start position in EMU
46
-    cell_top = pixels_to_EMU((cell.row - 1) * row_height_pixels)  # Row start position in EMU
47
-    x_center = cell_left + pixels_to_EMU((col_width_pixels - img.width) / 2)
48
-    y_center = cell_top + pixels_to_EMU((row_height_pixels - img.height) / 2)
49
-
50
-    # Set the image position and size
51
-    position = XDRPoint2D(x_center, y_center)
56
+    Add an image to the sheet with an offset relative to the top-left corner of a cell.
57
+
58
+    :param sheet: The worksheet
59
+    :param img: The openpyxl Image object
60
+    :param cell_coordinate: Cell to place the image (e.g., "B2")
61
+    :param offset_x: Horizontal offset in pixels
62
+    :param offset_y: Vertical offset in pixels
63
+    """
64
+    # Extract the column and row from the cell coordinate
65
+    col_letter = ''.join(filter(str.isalpha, cell_coordinate))  # Extract column letter
66
+    row_number = int(''.join(filter(str.isdigit, cell_coordinate)))  # Extract row number
67
+
68
+    # Get the zero-based indices for the cell
69
+    col_idx = column_index_from_string(col_letter) - 1
70
+    row_idx = row_number - 1
71
+
72
+    # Approximate column width and row height to pixels
73
+    col_width = sheet.column_dimensions[col_letter].width or 10  # Default column width
74
+    row_height = sheet.row_dimensions[row_number].height or 15  # Default row height
75
+
76
+    # Convert column width and row height to pixels
77
+    col_pixels = col_width * 7.5  # Approximation: ~7.5 pixels per width unit
78
+    row_pixels = row_height * 0.75  # Approximation: ~0.75 pixels per height unit
79
+
80
+    # Calculate the position in pixels for the top-left corner of the cell
81
+    cell_x = col_idx * col_pixels
82
+    cell_y = row_idx * row_pixels
83
+
84
+    # Apply the offsets
85
+    final_x = cell_x + offset_x
86
+    final_y = cell_y + offset_y
87
+
88
+    # Convert to EMUs
89
+    pos = XDRPoint2D(pixels_to_EMU(final_x), pixels_to_EMU(final_y))
52 90
     size = XDRPositiveSize2D(pixels_to_EMU(img.width), pixels_to_EMU(img.height))
53
-    img.anchor = AbsoluteAnchor(pos=position, ext=size)
91
+
92
+    # Set the image's anchor with the position and size
93
+    img.anchor = AbsoluteAnchor(pos=pos, ext=size)
54 94
 
55 95
     # Add the image to the worksheet
56
-    ws.add_image(img)
96
+    sheet.add_image(img)
97
+
98
+
99
+
100
+def center_image_in_cell(sheet, img, cell_coordinate):
101
+    """
102
+    Center an image inside a specified cell.
103
+
104
+    :param sheet: The worksheet
105
+    :param img: The openpyxl Image object
106
+    :param cell_coordinate: The cell to center the image in (e.g., "C3")
107
+    """
108
+    # Extract column and row from the cell coordinate
109
+    col_letter = ''.join(filter(str.isalpha, cell_coordinate))  # Extract column letter
110
+    row_number = int(''.join(filter(str.isdigit, cell_coordinate)))  # Extract row number
111
+    col_idx = column_index_from_string(col_letter) - 1  # Convert to zero-based column index
112
+
113
+    # Get cell dimensions
114
+    col_width = sheet.column_dimensions[col_letter].width or 10  # Default width if not set
115
+    row_height = sheet.row_dimensions[row_number].height or 15  # Default height if not set
116
+
117
+    # Convert dimensions to pixels (approximation)
118
+    col_pixels = col_width * 7.5  # 1 Excel column width unit ≈ 7.5 pixels
119
+    row_pixels = row_height * 0.75  # 1 Excel row height unit ≈ 0.75 pixels
120
+
121
+    # Get image dimensions
122
+    img_width, img_height = img.width, img.height
123
+
124
+    # Calculate offsets to center the image
125
+    offset_x = int((col_pixels - img_width) / 2 * pixels_to_EMU(1))  # Center horizontally
126
+    offset_y = int((row_pixels - img_height) / 2 * pixels_to_EMU(1))  # Center vertically
127
+
128
+    # Define the anchor for the image
129
+    _from = AnchorMarker(col=col_idx, row=row_number - 1, colOff=offset_x, rowOff=offset_y)
130
+    to = AnchorMarker(col=col_idx + 1, row=row_number, colOff=-offset_x, rowOff=-offset_y)
131
+
132
+    # Use TwoCellAnchor for positioning
133
+    img.anchor = TwoCellAnchor(editAs="oneCell", _from=_from, to=to)
134
+
135
+    # Add the image to the sheet
136
+    sheet.add_image(img)
57 137
 
58 138
 
59 139
 def gen_xlsx(template_file, selected_sheets, prefix_filename, data):
@@ -89,7 +169,7 @@ def gen_xlsx(template_file, selected_sheets, prefix_filename, data):
89 169
 
90 170
         # Replace placeholders with actual values
91 171
         # Handle hiding rows based on patterns in data
92
-        
172
+         
93 173
         for row in sheet.iter_rows():
94 174
             for cell in row:
95 175
                 if cell.value and isinstance(cell.value, str) and cell.value.startswith("<") and cell.value.endswith(">"):
@@ -108,21 +188,41 @@ def gen_xlsx(template_file, selected_sheets, prefix_filename, data):
108 188
                             pprint("ImageField")
109 189
                             image_path = value.path
110 190
                             if os.path.exists(image_path):
191
+                                # img = Image(image_path)
192
+                                # img.height = 40  # Adjust size as needed
193
+                                pil_img = PILImage.open(image_path)
194
+                                original_width, original_height = pil_img.size
195
+
196
+                                # Desired height (e.g., 40), calculate the new width to maintain aspect ratio
197
+                                desired_height = 40
198
+                                aspect_ratio = original_width / original_height
199
+                                new_width = int(desired_height * aspect_ratio)
200
+
201
+                                # Resize the image using Pillow (optional, for saving memory during export)
202
+                                resized_img = pil_img.resize((new_width, desired_height), PILImage.Resampling.LANCZOS)
203
+                                resized_img.save(image_path)  # Save the resized image back to the same path
204
+
205
+                                # Insert the resized image into the Excel sheet
111 206
                                 img = Image(image_path)
112
-                                img.height = 40  # Adjust size as needed
113
-                                sheet.add_image(img, cell.coordinate)
207
+                                img.width, img.height = new_width, desired_height  # Set the dimensions
208
+                                # sheet.add_image(img, cell.coordinate)
209
+                                center_image_in_cell(sheet, img, cell.coordinate, )
114 210
                                 cell.value = None  # Clear placeholder
211
+
115 212
                         elif value is True:
116 213
                             img = Image(checked_image_path)
117
-                            img.width = img.height = 20
214
+                            img.width = img.height = 10
118 215
                             print(f"{cell.coordinate}")
119
-                            sheet.add_image(img, cell.coordinate)
216
+                            # sheet.add_image(img, cell.coordinate)
217
+                            # set_image_with_offset(sheet, img, cell.coordinate, offset_x=100)
218
+                            center_image_in_cell(sheet,img, cell.coordinate, )
120 219
                             cell.value = None  # Remove the placeholder text
121 220
                         elif value is False:
122 221
                             img = Image(unchecked_image_path)
123
-                            img.width = img.height = 20
124
-                            sheet.add_image(img, cell.coordinate)
125
-                            # center_image_in_cell(sheet, cell.coordinate, unchecked_image_path)
222
+                            img.width = img.height = 10
223
+                            # sheet.add_image(img, cell.coordinate)
224
+                            # set_image_with_offset(sheet, img, cell.coordinate, offset_x=100)
225
+                            center_image_in_cell(sheet, img, cell.coordinate, )
126 226
                             cell.value = None  # Remove the placeholder text
127 227
                         else:
128 228
                             # Insert the text value directly

+ 8 - 8
app/report/templates/report/coi.html

@@ -8,12 +8,6 @@
8 8
   <h1 class="text-2xl font-bold text-gray-800">Export Center</h1>
9 9
   <form method='post'>
10 10
     {% csrf_token %}
11
-    <div class="flex items-center justify-between mb-4">
12
-      <h1 class="text-lg font-bold text-gray-800">TKX Certificate Issue</h1>
13
-      <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded hover:bg-gray-400">
14
-        รายละเอียด CheckedBy|ApproveBy
15
-      </button>
16
-    </div>
17 11
     <div class="flex items-center gap-2 mb-4">
18 12
       <label for="lot-number" class="text-gray-700 font-medium">Lot No. :</label>
19 13
       <input id="lot-number" type="text" class="border border-gray-300 rounded px-4 py-2 focus:outline-blue-500" placeholder="Enter Lot No." name='lot_no' required x-model="lot_no">
@@ -35,7 +29,7 @@
35 29
         <div class="grid grid-cols-2 gap-4">
36 30
           <div class="my-4">
37 31
             <label for="qa1" class="block mb-2 text-sm font-medium text-gray-900">Select QA.1</label>
38
-            <select id="qa1" name="qa1" class="block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
32
+            <select id="qa1" name="qa1" class="block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" x-model='qa1'>
39 33
               <option value="" disabled selected>Choose a user</option>
40 34
               {% for user in users %}
41 35
               <option value="{{ user.id }}">{{ user.profile }}</option>
@@ -44,7 +38,7 @@
44 38
           </div>
45 39
           <div class="my-4">
46 40
             <label for="qa2" class="block mb-2 text-sm font-medium text-gray-900">Select QA.2</label>
47
-            <select id="qa2" name="qa2" class="block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
41
+            <select id="qa2" name="qa2" class="block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" x-model='qa2'>
48 42
               <option value="" disabled selected>Choose a user</option>
49 43
               {% for user in users %}
50 44
               <option value="{{ user.id }}">{{ user.profile }}</option>
@@ -139,10 +133,14 @@
139 133
     return {
140 134
       lot_no: '', // Bind this to the input value
141 135
       exports: [],
136
+      qa1: null,
137
+      qa2: null,
142 138
       gen_report_url: '{% url "report:gen_report" %}', 
143 139
       downloadUrl: null, // Stores the download link after export success
144 140
       init() {
145 141
         //alert("COI Report");
142
+        this.qa1 = '';
143
+        this.qa2 = '';
146 144
       },
147 145
       async exportCOI() {
148 146
               if (!this.lot_no) {
@@ -155,6 +153,8 @@
155 153
                   const response = await axios.post(this.gen_report_url, {
156 154
                       lot_no: this.lot_no,
157 155
                       exports: this.exports,
156
+                      qa1: this.qa1,
157
+                      qa2: this.qa2
158 158
                   });
159 159
 
160 160
                   if (response.status === 200) {

+ 91 - 16
app/report/views.py

@@ -16,6 +16,7 @@ from django.http import JsonResponse, HttpResponseBadRequest
16 16
 import json
17 17
 from django.contrib.auth.decorators import login_required
18 18
 from django.contrib.auth.models import User
19
+from legacy.models import Data
19 20
 
20 21
 
21 22
 def index(request):
@@ -67,7 +68,76 @@ class ReportCRUDView(ConfigurableCRUDView):
67 68
     # config_edit_fields = ["lot_no", "code"]
68 69
     ordering = ["-created_at", "-id",]
69 70
 
70
-def create_coi_file(lot_no, sheets, user):
71
+def convert_sheet_data(sheet_data):
72
+    """
73
+    Convert sheet_data to the required form with prefixed keys.
74
+    
75
+    :param sheet_data: Dictionary with sheet names as keys and their data as values.
76
+    :return: Dictionary in the required key-value format.
77
+    """
78
+    converted_data = {}
79
+
80
+    for sheet_name, data in sheet_data.items():
81
+        for key, value in data.items():
82
+            # Prefix each key with the sheet name
83
+            converted_key = f"{sheet_name}.{key}"
84
+            converted_data[converted_key] = value
85
+
86
+    return converted_data
87
+
88
+def generate_hardness_out_values(lot_no):
89
+    """
90
+    Generate a dictionary of placeholder values for a given lot_no.
91
+
92
+    :param lot_no: The lot number to query data for.
93
+    :return: A dictionary with placeholders (e.g., v1_1, v1_2, ...) as keys and their respective values.
94
+    """
95
+    # Query the Data model for records matching the given lot_no
96
+    records = Data.objects.filter(lot_no=lot_no).order_by('row_no')
97
+    print(f"records {lot_no} = {records.values()}")
98
+    # Initialize an empty dictionary to store placeholder values
99
+    placeholders = {}
100
+
101
+    # Iterate over the records to populate placeholder values
102
+    for record_idx, record in enumerate(records, start=1):
103
+        placeholders[f'v{record_idx}_1'] = record.p1  # Checkpoint 1 value
104
+        placeholders[f'v{record_idx}_2'] = record.p2  # Checkpoint 2 value
105
+        placeholders[f'v{record_idx}_3'] = record.p3  # Checkpoint 3 value
106
+        placeholders[f'v{record_idx}_4'] = record.avg  # Average value
107
+        placeholders[f'v{record_idx}_5'] = record.rgrade  # Judgment value
108
+
109
+    return placeholders
110
+
111
+def merge_sheet_data_with_data(sheet_data, data):
112
+    """
113
+    Merge `sheet_data` with `data`.
114
+
115
+    :param sheet_data: Dictionary containing the sheet-specific data.
116
+    :param data: Dictionary containing general data.
117
+    :return: A merged dictionary combining both `sheet_data` and `data`.
118
+    """
119
+    # Merge dictionaries using unpacking
120
+    merged_data = {**data, **sheet_data}
121
+
122
+    return merged_data
123
+
124
+def create_coi_file(lot_no, sheets, user, md):
125
+    pprint("---- create_coi_file ---")
126
+    pprint(md)
127
+    qa1 = User.objects.get(pk=md['qa1'])
128
+    qa2 = User.objects.get(pk=md['qa2'])
129
+
130
+    pprint(qa1)
131
+    pprint(qa2)
132
+    
133
+    sheet_data = {}
134
+    for sheet_name in sheets:
135
+        if sheet_name == 'hardness_out':
136
+            sheet_data[sheet_name] = generate_hardness_out_values(lot_no)
137
+    converted_data = convert_sheet_data(sheet_data)
138
+
139
+    print(f"sheet_data \n {sheet_data}") 
140
+    print(f"converted_data \n {converted_data}")
71 141
 
72 142
     data = {
73 143
         "customer": "Tum Coder",
@@ -78,30 +148,32 @@ def create_coi_file(lot_no, sheets, user):
78 148
         "size": "Large",
79 149
         "pcs": "10 pcs",
80 150
         "spec": "Spec-A",
81
-        "hardness_out.d1_act": "10",
82
-        "hardness_out.d2_act": "0[24:28]",  # Hide rows 24 to 28 if the prefix is "0"
83 151
         "hardness_out.acc": True,  # Hide rows 24 to 28 if the prefix is "0"
84 152
         "hardness_out.spe_acc": False,  # Hide rows 24 to 28 if the prefix is "0"
85
-        "hardness_out.qa1": "Hello world",
86
-        "hardness_out.qa2": "Unknow person",
87
-        "hardness_out.v11": 5,
88
-        "hardness_out.v12": 15,
89
-        "hardness_out.v13": 10,
90
-        "hardness_out.v21": 9,
91
-        "hardness_out.v22": 8,
92
-        "hardness_out.v23": 7,
153
+        "acc": True,  # Hide rows 24 to 28 if the prefix is "0"
154
+        "spe_acc": True,  # Hide rows 24 to 28 if the prefix is "0"
155
+        # "hardness_out.qa1": f"{qa1.first_name} {qa1.last_name}",
156
+        # "hardness_out.qa2": f"{qa2.first_name} {qa2.last_name}",
157
+        "qa1": f"{qa1.first_name} {qa1.last_name}",
158
+        "qa2": f"{qa2.first_name} {qa2.last_name}",
93 159
         "dimension_app.d1_act": "33",
94 160
         "dimension_app.d2_act": "0[26:32]",  # Hide rows 24 to 28 if the prefix is "0"
95 161
         "dimension_app.acc": True,  # Hide rows 24 to 28 if the prefix is "0"
96 162
         "dimension_app.spe_acc": True,  # Hide rows 24 to 28 if the prefix is "0"
97
-        "sign1": user.profile.signed_picture,
98
-        "sign2": user.profile.signed_picture,
163
+        "sign1": qa1.profile.signed_picture,
164
+        "sign2": qa2.profile.signed_picture,
165
+        "pos1": qa1.profile.get_position_display(),
166
+        "pos2": qa2.profile.get_position_display()
99 167
     }
168
+    merged_data = merge_sheet_data_with_data(converted_data, data)
169
+    pprint(f"---- merged_data ---")
170
+    pprint(merged_data)
171
+
100 172
     output_file = gen_xlsx(
101 173
         template_file="/app/report/coi_templates.xlsx",
102 174
         selected_sheets=sheets,  # Replace with your actual sheet names
103 175
         prefix_filename="/app/media/coi",
104
-        data=data
176
+        data=merged_data
105 177
     )
106 178
     report = Report.objects.create(
107 179
         name=lot_no,
@@ -201,14 +273,17 @@ def gen_report_view(request):
201 273
         try:
202 274
             # Parse JSON data from the request body
203 275
             data = json.loads(request.body)
204
-            lot_no = data.get("lot_no")
276
+            lot_no = data.get("lot_no").strip()
205 277
             exports = data.get("exports")
278
+            qa1 = data.get('qa1')
279
+            qa2 = data.get('qa2')
280
+            print(f"data = {data}")
206 281
 
207 282
             if not lot_no:
208 283
                 return HttpResponseBadRequest("Missing 'lot_no' in request data")
209 284
 
210 285
             # Call the `create_coi_file` function with the provided lot_no
211
-            report = create_coi_file(lot_no, exports, request.user)
286
+            report = create_coi_file(lot_no, exports, request.user, {'qa1': qa1, 'qa2': qa2})
212 287
 
213 288
             # Return a success response with the report details
214 289
             return JsonResponse({

+ 2 - 1
app/sysadmin/models.py

@@ -19,4 +19,5 @@ class UserProfile(models.Model):
19 19
     position = models.CharField(max_length=20, choices=POSITION_CHOICES, blank=True, null=True)  # New position field
20 20
 
21 21
     def __str__(self):
22
-        return f"{self.user.username} / {self.user.first_name} {self.user.last_name} #{self.position}"
22
+        pos = self.get_position_display()
23
+        return f"{self.user.username} / {self.user.first_name} {self.user.last_name} #{pos}"