tum 23 godzin temu
rodzic
commit
bf7012e325

+ 1 - 1
app/core/forms.py

@@ -19,7 +19,7 @@ class CustomerTemplateMappingForm(forms.ModelForm):
19 19
 
20 20
     class Meta:
21 21
         model = CustomerTemplateMapping
22
-        fields = ['customer_name', 'template_names']
22
+        fields = ['customer_name', 'export_template', 'template_names']
23 23
 
24 24
     def clean_template_names(self):
25 25
         # convert data to list of selected template keys

+ 160 - 35
app/core/management/commands/import_customer_templates.py

@@ -1,53 +1,178 @@
1 1
 import csv
2
+import json
3
+import re
4
+from pathlib import Path
5
+
6
+from django.contrib.auth.models import User
2 7
 from django.core.management.base import BaseCommand
8
+
3 9
 from core.models import CustomerTemplateMapping
4
-from django.contrib.auth.models import User
5
-import json
10
+from core.utils import SHEET_NAMES
11
+
12
+
13
+SKIPPED_SHEET_KEYS = {"hardness_out_in_4"}
14
+INDEXED_SHEET_KEYS = [
15
+    key for key in SHEET_NAMES.keys() if key not in SKIPPED_SHEET_KEYS
16
+]
17
+
6 18
 
7 19
 class Command(BaseCommand):
8
-    help = "Import CustomerTemplateMapping records from a CSV file"
20
+    help = "Import CustomerTemplateMapping records from legacy JSON CSV or tabular template-index data"
9 21
 
10 22
     def add_arguments(self, parser):
11
-        parser.add_argument('csv_path', type=str, help='Path to the CSV file')
12
-        parser.add_argument('--created_by', type=int, help='User ID to assign as created_by')
23
+        parser.add_argument("csv_path", type=str, help="Path to the source file")
24
+        parser.add_argument("--created_by", type=int, help="User ID to assign as created_by")
25
+        parser.add_argument(
26
+            "--export-template",
27
+            dest="export_template",
28
+            choices=[choice[0] for choice in CustomerTemplateMapping.TEMPLATE_TYPE_CHOICES],
29
+            default="asean",
30
+            help="Export template value to assign to imported rows",
31
+        )
13 32
 
14 33
     def handle(self, *args, **options):
15
-        csv_path = options['csv_path']
16
-        created_by = None
34
+        source_path = Path(options["csv_path"])
35
+        if not source_path.exists():
36
+            self.stderr.write(self.style.ERROR(f"Source file not found: {source_path}"))
37
+            return
17 38
 
18
-        if options.get('created_by'):
19
-            try:
20
-                created_by = User.objects.get(id=options['created_by'])
21
-            except User.DoesNotExist:
22
-                self.stdout.write(self.style.ERROR(f"User with ID {options['created_by']} does not exist."))
23
-                return
39
+        created_by = self.get_created_by(options.get("created_by"))
40
+        if options.get("created_by") and created_by is None:
41
+            return
42
+
43
+        rows = self.read_rows(source_path)
44
+        if not rows:
45
+            self.stderr.write(self.style.WARNING(f"No rows found in {source_path}"))
46
+            return
24 47
 
25
-        with open(csv_path, newline='', encoding='utf-8') as csvfile:
26
-            reader = csv.DictReader(csvfile)
27
-            count = 0
48
+        if self.is_legacy_json_format(rows):
49
+            count = self.import_legacy_json_rows(
50
+                rows,
51
+                created_by=created_by,
52
+                export_template=options["export_template"],
53
+            )
54
+        else:
55
+            count = self.import_tabular_rows(
56
+                rows,
57
+                created_by=created_by,
58
+                export_template=options["export_template"],
59
+            )
60
+
61
+        self.stdout.write(self.style.SUCCESS(f"Imported {count} customer template mappings."))
28 62
 
29
-            for row in reader:
30
-                name = row.get("customer_name")
31
-                templates = row.get("template_names")
63
+    def get_created_by(self, user_id):
64
+        if not user_id:
65
+            return None
66
+        try:
67
+            return User.objects.get(id=user_id)
68
+        except User.DoesNotExist:
69
+            self.stdout.write(self.style.ERROR(f"User with ID {user_id} does not exist."))
70
+            return None
32 71
 
33
-                if not name or not templates:
34
-                    self.stderr.write(f"Skipping incomplete row: {row}")
35
-                    continue
72
+    def read_rows(self, source_path):
73
+        with source_path.open(newline="", encoding="utf-8-sig") as handle:
74
+            sample = handle.read(2048)
75
+            handle.seek(0)
36 76
 
77
+            if "\t" in sample:
78
+                dialect = csv.excel_tab
79
+            else:
37 80
                 try:
38
-                    template_list = json.loads(templates)
39
-                except json.JSONDecodeError:
40
-                    self.stderr.write(f"Skipping row with invalid JSON: {templates}")
41
-                    continue
81
+                    dialect = csv.Sniffer().sniff(sample, delimiters=",")
82
+                except csv.Error:
83
+                    dialect = csv.excel
42 84
 
43
-                CustomerTemplateMapping.objects.update_or_create(
44
-                    customer_name=name,
45
-                    defaults={
46
-                        "template_names": template_list,
47
-                        "created_by": created_by,
48
-                    }
49
-                )
85
+            reader = csv.reader(handle, dialect)
86
+            return [row for row in reader if any(cell.strip() for cell in row)]
50 87
 
51
-                count += 1
88
+    def is_legacy_json_format(self, rows):
89
+        header = [cell.strip() for cell in rows[0]]
90
+        return "customer_name" in header and "template_names" in header
52 91
 
53
-        self.stdout.write(self.style.SUCCESS(f"Imported {count} customer template mappings."))
92
+    def import_legacy_json_rows(self, rows, created_by, export_template):
93
+        header = [cell.strip() for cell in rows[0]]
94
+        count = 0
95
+
96
+        for row in rows[1:]:
97
+            data = dict(zip(header, row))
98
+            customer_name = data.get("customer_name", "").strip()
99
+            template_names = data.get("template_names", "").strip()
100
+
101
+            if not customer_name or not template_names:
102
+                self.stderr.write(f"Skipping incomplete row: {data}")
103
+                continue
104
+
105
+            try:
106
+                template_list = json.loads(template_names)
107
+            except json.JSONDecodeError:
108
+                self.stderr.write(f"Skipping row with invalid JSON: {template_names}")
109
+                continue
110
+
111
+            CustomerTemplateMapping.objects.update_or_create(
112
+                customer_name=customer_name,
113
+                defaults={
114
+                    "template_names": template_list,
115
+                    "export_template": export_template,
116
+                    "created_by": created_by,
117
+                },
118
+            )
119
+            count += 1
120
+
121
+        return count
122
+
123
+    def import_tabular_rows(self, rows, created_by, export_template):
124
+        merged = {}
125
+
126
+        for row in rows:
127
+            if len(row) < 6:
128
+                self.stderr.write(f"Skipping incomplete row: {row}")
129
+                continue
130
+
131
+            customer_name = self.normalize_customer_name(row[1])
132
+            template_index = row[3].strip()
133
+
134
+            if not customer_name:
135
+                self.stderr.write(f"Skipping row with empty customer_name: {row}")
136
+                continue
137
+
138
+            try:
139
+                template_key = self.template_key_from_index(template_index)
140
+            except ValueError as exc:
141
+                self.stderr.write(f"Skipping row {row}: {exc}")
142
+                continue
143
+
144
+            merged.setdefault(customer_name, set()).add(template_key)
145
+
146
+        count = 0
147
+        for customer_name, template_names in merged.items():
148
+            ordered_templates = [
149
+                key for key in INDEXED_SHEET_KEYS if key in template_names
150
+            ]
151
+            CustomerTemplateMapping.objects.update_or_create(
152
+                customer_name=customer_name,
153
+                defaults={
154
+                    "template_names": ordered_templates,
155
+                    "export_template": export_template,
156
+                    "created_by": created_by,
157
+                },
158
+            )
159
+            count += 1
160
+
161
+        return count
162
+
163
+    def normalize_customer_name(self, value):
164
+        cleaned = re.sub(r"\s+", " ", value or "")
165
+        return cleaned.strip()
166
+
167
+    def template_key_from_index(self, raw_value):
168
+        try:
169
+            index = int(raw_value)
170
+        except (TypeError, ValueError) as exc:
171
+            raise ValueError(f"invalid template index '{raw_value}'") from exc
172
+
173
+        if index < 1 or index > len(INDEXED_SHEET_KEYS):
174
+            raise ValueError(
175
+                f"template index '{raw_value}' is out of range 1..{len(INDEXED_SHEET_KEYS)}"
176
+            )
177
+
178
+        return INDEXED_SHEET_KEYS[index - 1]

+ 20 - 0
app/core/migrations/0010_customertemplatemapping_export_template.py

@@ -0,0 +1,20 @@
1
+from django.db import migrations, models
2
+
3
+
4
+class Migration(migrations.Migration):
5
+
6
+    dependencies = [
7
+        ("core", "0009_mkscodemap"),
8
+    ]
9
+
10
+    operations = [
11
+        migrations.AddField(
12
+            model_name="customertemplatemapping",
13
+            name="export_template",
14
+            field=models.CharField(
15
+                choices=[("japanese", "Japanese"), ("asean", "ASEAN")],
16
+                default="japanese",
17
+                max_length=20,
18
+            ),
19
+        ),
20
+    ]

+ 10 - 0
app/core/models.py

@@ -493,7 +493,17 @@ TEMPLATE_CHOICES = [
493 493
 ]
494 494
 
495 495
 class CustomerTemplateMapping(models.Model):
496
+    TEMPLATE_TYPE_CHOICES = [
497
+        ("japanese", "Japanese"),
498
+        ("asean", "ASEAN"),
499
+    ]
500
+
496 501
     customer_name = models.CharField(max_length=255)
502
+    export_template = models.CharField(
503
+        max_length=20,
504
+        choices=TEMPLATE_TYPE_CHOICES,
505
+        default="japanese",
506
+    )
497 507
     template_names = models.JSONField(default=list, blank=True, null=True)  # stores list of template names
498 508
 
499 509
     created_by = models.ForeignKey(

+ 75 - 0
app/customer_template_mapping_import.tsv

@@ -0,0 +1,75 @@
1
+1	TAK SPRING INDUSTRY CO.,LTD.	4366866770	10	Paper&E mail	Dimensin and Hardness
2
+2	TAK SPRING INDUSTRY CO.,LTD.	2366845379	10	Paper&E mail	Dimensin and Hardness
3
+3	TT FUJI TOOL SUPPORT CO., LTD.	1370235390	1	Paper&E mail	Hardness
4
+4	Y.S.S. (THAILAND) PUBLIC CO., LTD.	2366866139	10	Paper&E mail	Hardness& Balance
5
+5	OKAMOTO (THAI) CO.,LTD.	1370020050	1	Paper&E mail	Hardness
6
+6	MITSUBISHI MATERIALS (THAILAND) CO., LTD.	1366810629	13	Paper&E mail	Centering
7
+7	MAZDA POWERTRAIN MANUFACTURING (THAILAND) CO., LTD.	1370110091	13	Paper&E mail	Dimensin and Centering
8
+8	Y.S.S. (THAILAND) PUBLIC CO., LTD.	4370091656	10	Paper&E mail	Dimensin and Hardness
9
+9	YANGCHING ENTERPRISE CO., LTD.	2366860025	1	Paper&E mail	Hardness
10
+10	NHK PRECISION (THAILAND) CO., LTD.	5366802066	1	Paper&E mail	Hardness
11
+11	SIAM CHITA CO.,LTD.	2366866201	1	Paper&E mail	Hardness
12
+12	NHK PRECISION (THAILAND) CO., LTD.	5366802023	1	Paper&E mail	Hardness
13
+13	TAKAHASHI SPRING (THAILAND) CO., LTD.	2366825041	1	Paper&E mail	Hardness
14
+14	SIAM CHITA CO.,LTD.	2366861390	1	Paper&E mail	Hardness
15
+14	SIAM CHITA CO.,LTD.	1366816228	1	Paper&E mail	Hardness
16
+14	THAI KYOWA GMB CO., LTD.	1370645858	13	Paper&E mail	Thickness(mm) 8 Point
17
+15	STARCORE CO.,LTD.	4370091710	1	Paper&E mail	Hardness
18
+16	THAI KJK CO.,LTD.	4370161778	7	Paper&E mail	Dimension(mm) &Appearance
19
+17	THAI KJK CO.,LTD.	4370061811	7	Paper&E mail	Dimension(mm) &Appearance
20
+18	THAI COLD ROLLED STE	4370091273	1	Paper&E mail	Hardness
21
+19	HIGHLY MARELLI (THAILAND) CO.,LTD.	1370951550	4	Paper&E mail	Dimension(mm)
22
+20	YANGCHING ENTERPRISE CO., LTD.	4366860160	1	Paper&E mail	Hardness
23
+21	YANGCHING ENTERPRISE CO., LTD.	4370140711	1	Paper&E mail	Hardness
24
+22	YANGCHING ENTERPRISE CO., LTD.	4370140738	1	Paper&E mail	Hardness
25
+23	YANGCHING ENTERPRISE CO., LTD.	4370140690	1	Paper&E mail	Hardness
26
+24	THAI KJK CO.,LTD.	4370561492	7	Paper&E mail	Dimension(mm) &Appearance
27
+25	THAI KJK CO.,LTD	1371261727	7	Paper&E mail	Dimension(mm) &Appearance
28
+26	TAK SPRING INDUSTRY CO.,LTD.	2366845395	10	Paper&E mail	Dimensin and Hardness
29
+27	JTEKT (THAILAND) CO., LTD.	1371145620	1	Paper&E mail	Hardness
30
+28	TT FUJI TOOL SUPPORT CO., LTD.	1370535254	1	Paper&E mail	Hardness
31
+29	SIAM MOLEX CO.,LTD	1371340830	1	Paper&E mail	Hardness
32
+30	JFE STEEL GALVANIZING (THAILAND) LTD.	4366875095	1	Paper&E mail	Hardness
33
+31	STARCORE CO.,LTD.	4370091460	1	Paper&E mail	Hardness
34
+32	THAI KYOWA GMB CO., LTD.	1371140962	13	Paper&E mail	Thickness(mm) 8 Point
35
+33	AA FINE BLANKING CO.,LTD.	1366843810	1	Paper&E mail	Hardness
36
+34	SIAM METAL TECHNOLOGY CO.,LTD.	1371261654	1	Paper&E mail	Hardness
37
+35	SIAM CHITA CO.,LTD.	4366866711	1	Paper&E mail	Hardness
38
+36	STARCORE CO.,LTD.	4370091567	3	Paper&E mail	Hardness_both_size
39
+37	THAI KJK CO.,LTD	1371261727	7	Paper&E mail	Dimension(mm) &Appearance
40
+38	TT FUJI TOOL SUPPORT CO., LTD.	1371651665	1	Paper&E mail	Hardness
41
+39	TT FUJI TOOL SUPPORT CO., LTD.	1371651657	1	Paper&E mail	Hardness
42
+39	TT FUJI TOOL SUPPORT CO., LTD.	1371935353	1	Paper&E mail	Hardness
43
+40	TT FUJI TOOL SUPPORT CO., LTD.	1371640493	1	Paper&E mail	Hardness
44
+41	JTEKT (THAILAND) CO., LTD.	1370002590	1	Paper&E mail	Hardness
45
+42	JTEKT (THAILAND) CO., LTD.	1370002582	1	Paper&E mail	Hardness
46
+43	JTEKT (THAILAND) CO., LTD.	1371245675	1	Paper&E mail	Hardness
47
+44	JTEKT (THAILAND) CO., LTD.	1371261832	1	Paper&E mail	Hardness
48
+45	JTEKT (THAILAND) CO., LTD.	1371261840	1	Paper&E mail	Hardness
49
+46	JTEKT (THAILAND) CO., LTD.	1371245705	1	Paper&E mail	Hardness
50
+48	JTEKT (THAILAND) CO., LTD.	1371245691	1	Paper&E mail	Hardness
51
+49	JTEKT (THAILAND) CO., LTD.	1371430740	1	Paper&E mail	Hardness
52
+50	JTEKT (THAILAND) CO., LTD.	1371245799	1	Paper&E mail	Hardness
53
+51	JTEKT (THAILAND) CO., LTD.	1371245780	1	Paper&E mail	Hardness
54
+52	JTEKT (THAILAND) CO., LTD.	1371245772	1	Paper&E mail	Hardness
55
+53	JTEKT (THAILAND) CO., LTD.	1371245837	1	Paper&E mail	Hardness
56
+54	JTEKT (THAILAND) CO., LTD.	1371245829	1	Paper&E mail	Hardness
57
+55	JTEKT (THAILAND) CO., LTD.	1371651789	1	Paper&E mail	Hardness
58
+56	JTEKT (THAILAND) CO., LTD.	1371245764	1	Paper&E mail	Hardness
59
+57	JTEKT (THAILAND) CO., LTD.	1371245802	1	Paper&E mail	Hardness
60
+58	JTEKT (THAILAND) CO., LTD.	1371245810	1	Paper&E mail	Hardness
61
+59	JTEKT (THAILAND) CO., LTD.	1371245861	1	Paper&E mail	Hardness
62
+60	JTEKT (THAILAND) CO., LTD.	1371245888	1	Paper&E mail	Hardness
63
+61	JTEKT (THAILAND) CO., LTD.	1371245870	1	Paper&E mail	Hardness
64
+62	JTEKT (THAILAND) CO., LTD.	1371261930	1	Paper&E mail	Hardness
65
+63	JTEKT (THAILAND) CO., LTD.	1371245993	1	Paper&E mail	Hardness
66
+64	JTEKT (THAILAND) CO., LTD.	1371245985	1	Paper&E mail	Hardness
67
+65	TT FUJI TOOL SUPPORT CO., LTD.	1370225190	1	Paper&E mail	Hardness
68
+66	MITSUBISHI MATERIALS (THAILAND) CO., LTD.	1366810637	13	Paper&E mail	Centering
69
+67	NIHON PARTS (THAILAND) CO., LTD.	4366838211	10	Paper&E mail	Dimensin and Hardness
70
+68	SAHAVIRIYA STEEL INDUSTRIES PUBLIC CO., LTD.	4370091117	1	Paper&E mail	Hardness A,B
71
+69	AIZEN SB (THAILAND) CO.,LTD.	5366876183	1	Paper&E mail	Hardness
72
+70	SIAM CHITA CO.,LTD.	2366861382	1	Paper&E mail	Hardness
73
+71	E & H PRECISION (THAILAND) CO., LTD.	1370530716	1	Paper&E mail	Hardness
74
+72	SIAM CHITA CO.,LTD.	4370145454	1	Paper&E mail	Hardness
75
+73	MITSUBISHI MATERIALS (THAILAND) CO., LTD.	1370012154	13	Paper&E mail	Centering

BIN
app/report/coi_templates.xlsx


BIN
app/report/coi_templates_en.xlsx


+ 6 - 1
app/report/filters.py

@@ -34,6 +34,11 @@ class CustomerTemplateFilter(django_filters.FilterSet):
34 34
         lookup_expr='icontains',
35 35
         label='Customer Name'
36 36
     )
37
+    export_template = django_filters.ChoiceFilter(
38
+        field_name='export_template',
39
+        choices=CustomerTemplateMapping.TEMPLATE_TYPE_CHOICES,
40
+        label='Export Template'
41
+    )
37 42
 
38 43
     template_names = django_filters.CharFilter(
39 44
         method='filter_template_names',
@@ -42,7 +47,7 @@ class CustomerTemplateFilter(django_filters.FilterSet):
42 47
 
43 48
     class Meta:
44 49
         model = CustomerTemplateMapping
45
-        fields = ['customer_name', 'template_names']
50
+        fields = ['customer_name', 'export_template', 'template_names']
46 51
 
47 52
     def filter_template_names(self, queryset, name, value):
48 53
         return queryset.filter(template_names__icontains=value)

+ 2 - 0
app/report/gen_report.py

@@ -244,6 +244,8 @@ def gen_xlsx(template_file, selected_sheets, prefix_filename, data):
244 244
                     if value is not None:
245 245
                         if isinstance(value, ImageFieldFile):
246 246
                             pprint("ImageField")
247
+                            if not value.name:
248
+                                continue
247 249
                             image_path = value.path
248 250
                             if os.path.exists(image_path):
249 251
                                 # img = Image(image_path)

+ 70 - 47
app/report/templates/report/coi.html

@@ -7,30 +7,35 @@
7 7
 {% block content %}
8 8
 <div class="container mx-auto px-4 py-8" x-data="COIReport">
9 9
   <h1 class="text-2xl font-bold text-gray-800">Export Center</h1>
10
-  <form method='post'>
11
-    {% csrf_token %}
12
-    <div class="flex items-center gap-2 mb-4">
13
-      <label for="lot-number" class="text-gray-700 font-medium">Lot No. :</label>
14
-      <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" value="{{ lot_no }}">
15
-      <button class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" type="submit" name="search_lot">
16
-        🔍
17
-      </button>
10
+  <div class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm">
11
+    <form method='post'>
12
+      {% csrf_token %}
13
+      <div class="flex flex-col gap-3 sm:flex-row sm:items-center">
14
+        <label for="lot-number" class="text-sm font-semibold text-gray-700 sm:min-w-20">Lot No.</label>
15
+        <input id="lot-number" type="text" class="w-full rounded-md border border-gray-300 px-3 py-2 focus:outline-blue-500" placeholder="Enter Lot No." name='lot_no' required x-model="lot_no" value="{{ lot_no }}">
16
+        <button class="shrink-0 rounded-md bg-blue-500 px-4 py-2 text-white hover:bg-blue-600" type="submit" name="search_lot">
17
+          Search
18
+        </button>
19
+      </div>
20
+    </form>
21
+
22
+    <div class="mt-4">
23
+      <div class="mb-2 inline-flex rounded-md bg-blue-100 px-3 py-1 text-sm font-medium text-blue-700">
24
+        Export Options
25
+      </div>
26
+      <div class="grid grid-cols-1 gap-x-4 gap-y-2 md:grid-cols-2 xl:grid-cols-4">
27
+      {% for key,value in SHEET_NAMES.items %}
28
+      <label class="flex items-center gap-2 text-sm text-gray-700">
29
+        <input type="checkbox" class="rounded" name='exports' value='{{ key }}'
30
+        x-model='exports' required>
31
+        <span>{{ value }}</span>
32
+      </label>
33
+      {% endfor %}
34
+      </div>
18 35
     </div>
19
-  </form>
20
-      <button class="bg-blue-100 text-blue-700 px-4 py-2 rounded hover:bg-blue-200">
21
-        Option for Export :
22
-      </button>
23
-        <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
24
-        {% for key,value in SHEET_NAMES.items %}
25
-        <label class="flex items-center space-x-1">
26
-          <input type="checkbox" class="rounded" name='exports' value='{{ key }}'
27
-          x-model='exports' required>
28
-          <span>{{ value }}</span>
29
-        </label>
30
-        {% endfor %}
31
-        </div>
32
-        <div class="grid grid-cols-2 gap-4">
33
-          <div class="my-4">
36
+
37
+    <div class="mt-4 grid grid-cols-1 gap-3 lg:grid-cols-3">
38
+      <div>
34 39
             <label for="qa1" class="block mb-2 text-sm font-medium text-gray-900">Select QA.1</label>
35 40
             <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' required>
36 41
               <option value="" disabled selected>Choose a user</option>
@@ -38,8 +43,8 @@
38 43
               <option value="{{ user.id }}">{{ user.profile }}</option>
39 44
               {% endfor %}
40 45
             </select>
41
-          </div>
42
-          <div class="my-4">
46
+      </div>
47
+      <div>
43 48
             <label for="qa2" class="block mb-2 text-sm font-medium text-gray-900">Select QA.2</label>
44 49
             <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' required>
45 50
               <option value="" disabled selected>Choose a user</option>
@@ -47,36 +52,52 @@
47 52
               <option value="{{ user.id }}">{{ user.profile }}</option>
48 53
               {% endfor %}
49 54
             </select>
50
-          </div>
55
+      </div>
56
+      <div class="rounded-lg border border-gray-200 bg-gray-50 px-4 py-3">
57
+        <div class="mb-2 text-sm font-medium text-gray-900">Accept Status</div>
58
+        <div class="flex flex-wrap items-center gap-4">
59
+          <label class="flex items-center gap-2 text-sm text-gray-700">
60
+            <input type="radio" name="status" id="accepted" value="accepted" 
61
+              class="rounded" x-model="acceptStatus" />
62
+            <span>Accepted</span>
63
+          </label>
64
+          <label class="flex items-center gap-2 text-sm text-gray-700">
65
+            <input type="radio" name="status" id="special_accepted" value="special_accepted" 
66
+              class="rounded" x-model="acceptStatus" />
67
+            <span>Special Accepted</span>
68
+          </label>
51 69
         </div>
52
-        <div class="flex justify-end my-3 space-x-4">
53
-          <div class="flex items-center space-x-4 my-4">
54
-            <!-- Radio button for 'Accepted' -->
55
-            <label class="flex items-center space-x-2">
56
-              <input type="radio" name="status" id="accepted" value="accepted" 
57
-                class="rounded" x-model="acceptStatus" />
58
-              <span>Accepted</span>
59
-            </label>
70
+      </div>
71
+    </div>
60 72
 
61
-            <!-- Radio button for 'Special Accepted' -->
62
-            <label class="flex items-center space-x-2">
63
-              <input type="radio" name="status" id="special_accepted" value="special_accepted" 
64
-                class="rounded" x-model="acceptStatus" />
65
-              <span>Special Accepted</span>
73
+    <div class="mt-4 flex justify-end">
74
+      <div class="w-full rounded-lg border border-blue-100 bg-blue-50 px-4 py-3 lg:w-auto lg:min-w-[28rem]">
75
+        <div class="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
76
+          <div class="flex flex-wrap items-center gap-4">
77
+            <span class="text-sm font-medium text-gray-900">Export Template</span>
78
+            <label class="flex items-center gap-2 text-sm text-gray-700">
79
+              <input type="radio" name="export_template" value="japanese" class="rounded" x-model="exportTemplate" />
80
+              <span>Japanese</span>
81
+            </label>
82
+            <label class="flex items-center gap-2 text-sm text-gray-700">
83
+              <input type="radio" name="export_template" value="asean" class="rounded" x-model="exportTemplate" />
84
+              <span>ASEAN</span>
66 85
             </label>
67 86
           </div>
68
-          <div>
69
-            <button  type='button' class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" 
70
-                                   @click="exportCOI">
87
+          <div class="flex flex-wrap items-center justify-center gap-3 lg:justify-end">
88
+            <button  type='button' class="rounded-md bg-blue-500 px-4 py-2 text-white hover:bg-blue-600" 
89
+                                     @click="exportCOI">
71 90
               Export Excel
72 91
             </button>
73
-          </div>
74
-          <!-- TODO: add download here -->
75
-          <div class=" text-center" x-show="downloadUrl">
76
-            <a :href="downloadUrl"  class="block px-4 py-2 bg-green-600 text-white  rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2" download x-text="downText">
77
-            </a>
92
+            <div class="text-center" x-show="downloadUrl">
93
+              <a :href="downloadUrl"  class="block rounded-md bg-green-600 px-4 py-2 text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2" download x-text="downText">
94
+              </a>
95
+            </div>
78 96
           </div>
79 97
         </div>
98
+      </div>
99
+    </div>
100
+  </div>
80 101
   
81 102
   <div class="bg-white shadow-md rounded-md p-4">
82 103
 
@@ -153,6 +174,7 @@ th, td {
153 174
     return {
154 175
       lot_no: '{{ lot_no }}', // Bind this to the input value
155 176
       exports: {{ selected_templates|default:"[]"|safe }},
177
+      exportTemplate: '{{ export_template|default:"japanese" }}',
156 178
       qa1: null,
157 179
       qa2: null,
158 180
       gen_report_url: '{% url "report:gen_report" %}', 
@@ -187,6 +209,7 @@ th, td {
187 209
                   const response = await axios.post(this.gen_report_url, {
188 210
                       lot_no: this.lot_no,
189 211
                       exports: this.exports,
212
+                      export_template: this.exportTemplate,
190 213
                       qa1: this.qa1,
191 214
                       qa2: this.qa2,
192 215
                       acceptStatus: this.acceptStatus

+ 47 - 12
app/report/views.py

@@ -32,6 +32,26 @@ from django.db.models import Q, Func, Value, F
32 32
 from django.views.generic import (
33 33
     ListView,)
34 34
 
35
+COI_TEMPLATE_FILES = {
36
+    "japanese": "coi_templates.xlsx",
37
+    "asean": "coi_templates_en.xlsx",
38
+}
39
+
40
+
41
+def get_coi_template_file(template_name):
42
+    template_key = (template_name or "japanese").lower()
43
+    filename = COI_TEMPLATE_FILES.get(template_key, COI_TEMPLATE_FILES["japanese"])
44
+    return f"{settings.BASE_DIR}/report/{filename}"
45
+
46
+
47
+def get_customer_template_mapping(master_row):
48
+    if not master_row or not master_row.PRO1C:
49
+        return None
50
+    return CustomerTemplateMapping.objects.filter(
51
+        customer_name__icontains=master_row.PRO1C
52
+    ).first()
53
+
54
+
35 55
 def index(request):
36 56
     reports = Report.objects.all()
37 57
     report_filter = ReportFilter(request.GET, queryset=reports)
@@ -975,7 +995,7 @@ def generate_centering_values(lot_no, code):
975 995
     # Prepare placeholders
976 996
 
977 997
     # placeholders = {}
978
-    placeholders = clear_values(10,3)
998
+    placeholders = clear_values(20, 3)
979 999
     # for i in range(1,7):
980 1000
         # for j in range(1,4):
981 1001
             # placeholders[f'v{i}_{j}'] = 0
@@ -986,12 +1006,17 @@ def generate_centering_values(lot_no, code):
986 1006
 
987 1007
     # Ensure that we map each manualSize entry to a corresponding DataMs entry
988 1008
     inspect_date = None
1009
+    record_count = 0
989 1010
     for i,r in enumerate(data_ms_records, start=1):
1011
+        record_count = i
990 1012
         if i == 1:
991 1013
             inspect_date = r.created_at
992 1014
         placeholders[f'v{i}_2'] = r.censize
993 1015
         placeholders[f'v{i}_3'] = r.censizeok
994 1016
 
1017
+    if record_count <= 10:
1018
+        hide_con(placeholders, "v11_2", "47:93")
1019
+
995 1020
     placeholders['inspect_date'] = inspect_date.strftime('%Y/%m/%d') if inspect_date else "-"
996 1021
     return placeholders
997 1022
 
@@ -1220,8 +1245,9 @@ def create_coi_file(lot_no, sheets, user, md):
1220 1245
     pprint(f"---- merged_data ---")
1221 1246
     pprint(merged_data)
1222 1247
 
1248
+    template_file = get_coi_template_file(md.get("export_template"))
1223 1249
     output_file = gen_xlsx(
1224
-        template_file=f"{settings.BASE_DIR}/report/coi_templates.xlsx",
1250
+        template_file=template_file,
1225 1251
         selected_sheets=sheets,  # Replace with your actual sheet names
1226 1252
         prefix_filename=f"{settings.BASE_DIR}/media/coi_{lot_no}_",
1227 1253
         data=merged_data
@@ -1274,11 +1300,13 @@ def filter_by_lot_no(lot_no):
1274 1300
 def coi_view(request):
1275 1301
     pprint(f"xxxx method = xxx {request.method}")
1276 1302
     users = User.objects.all()
1303
+    export_template = "japanese"
1277 1304
 
1278 1305
     if request.method == "POST":
1279 1306
         pprint(request.POST)
1280 1307
         exports = request.POST.getlist("exports")  # Retrieve the list of selected values
1281 1308
         pprint(f"Selected Export Options: {exports}")
1309
+        export_template = request.POST.get("export_template", "japanese")
1282 1310
         
1283 1311
         if 'export' in request.POST:
1284 1312
 
@@ -1301,7 +1329,7 @@ def coi_view(request):
1301 1329
                 "dimension_app.spe_acc": True,  # Hide rows 24 to 28 if the prefix is "0"
1302 1330
             }
1303 1331
             output_file = gen_xlsx(
1304
-                template_file="/app/report/coi_templates.xlsx",
1332
+                template_file=get_coi_template_file(export_template),
1305 1333
                 selected_sheets=exports,  # Replace with your actual sheet names
1306 1334
                 prefix_filename="/app/media/coi",
1307 1335
                 data=data
@@ -1332,11 +1360,9 @@ def coi_view(request):
1332 1360
                 if first_result:
1333 1361
                     size_str = f"{first_result.PRO10}x{first_result.PRO11}x{first_result.PRO12}";
1334 1362
                     spec = f"{first_result.PRO13} {first_result.PRO14} {first_result.PRO15} {first_result.PRO16} {first_result.PRO17} {first_result.PRO18}"
1335
-                    #first_result.PRO1C = "TUM"
1336
-                    #selected_templates  = CustomerTemplateMapping.objects.filter(customer_name=first_result.PRO1C).first().template_names
1337
-                    # first_result.PRO1C = 'OSAKA SEIMITSU'
1338
-                    mapping = CustomerTemplateMapping.objects.filter(customer_name__icontains=first_result.PRO1C).first()
1363
+                    mapping = get_customer_template_mapping(first_result)
1339 1364
                     selected_templates = mapping.template_names if mapping else []
1365
+                    export_template = mapping.export_template if mapping else export_template
1340 1366
 
1341 1367
                     mgt_code = first_result.PRO1 if first_result else "-"
1342 1368
                     mks_code = convert_mgt_to_mks(mgt_code)
@@ -1367,11 +1393,13 @@ def coi_view(request):
1367 1393
                                                            'size_str': size_str,
1368 1394
                                                            'lot_no': lot_no,
1369 1395
                                                            'spec': spec, 'users': users, 'SHEET_NAMES': SHEET_NAMES, 
1370
-                                                           'results': results, 'fields': fields, 'selected_templates': selected_templates, 'code': code})
1396
+                                                           'results': results, 'fields': fields, 'selected_templates': selected_templates, 'code': code,
1397
+                                                           'export_template': export_template})
1371 1398
 
1372 1399
         messages.success(request, "Request Sent")
1373 1400
         return redirect(request.path_info)
1374
-    return render(request, 'report/coi.html', {'SHEET_NAMES': SHEET_NAMES, 'users': users})
1401
+    return render(request, 'report/coi.html', {'SHEET_NAMES': SHEET_NAMES, 'users': users,
1402
+                                               'export_template': export_template})
1375 1403
 
1376 1404
 
1377 1405
 @csrf_exempt  # Disable CSRF for API requests (ensure this is secure in production)
@@ -1385,14 +1413,21 @@ def gen_report_view(request):
1385 1413
             exports = data.get("exports")
1386 1414
             qa1 = data.get('qa1')
1387 1415
             qa2 = data.get('qa2')
1416
+            export_template = data.get("export_template")
1388 1417
             print(f"data = {data}")
1389 1418
 
1390 1419
             if not lot_no:
1391 1420
                 return HttpResponseBadRequest("Missing 'lot_no' in request data")
1392 1421
 
1422
+            if not export_template:
1423
+                results = queryFromMaster(lot_no)
1424
+                first_result = results[0] if results else None
1425
+                mapping = get_customer_template_mapping(first_result)
1426
+                export_template = mapping.export_template if mapping else "japanese"
1427
+
1393 1428
             # Call the `create_coi_file` function with the provided lot_no
1394 1429
             report = create_coi_file(lot_no, exports, request.user, {'qa1': qa1, 'qa2': qa2, \
1395
-                    'acceptStatus': data.get('acceptStatus')})
1430
+                    'acceptStatus': data.get('acceptStatus'), 'export_template': export_template})
1396 1431
 
1397 1432
             # Return a success response with the report details
1398 1433
             return JsonResponse({
@@ -1426,8 +1461,8 @@ class CustomerTemplateCRUDView(ConfigurableCRUDView):
1426 1461
     create_url_name = 'report:customer_templates-create'
1427 1462
     update_url_name = 'report:customer_templates-update'
1428 1463
     delete_url_name = 'report:customer_templates-delete'
1429
-    config_fields = ["id", "customer_name", "template_names", "created_at"] 
1430
-    config_field_orders = ["id", "customer_name", "template_names", "created_at",  "created_by"]
1464
+    config_fields = ["id", "customer_name", "export_template", "template_names", "created_at"] 
1465
+    config_field_orders = ["id", "customer_name", "export_template", "template_names", "created_at",  "created_by"]
1431 1466
     # config_readonly_fields = ["lot_no"]
1432 1467
     config_edit_fields = None
1433 1468
     ordering = ["-created_at", "-id",]

BIN
app/report/~$coi_templates.xlsx