PageSourceSearch

https://getabf.net/qrscan/js/excel.js

js getabf.net collected 2026-10-04 20:21:47 UTC 18,229 bytes, 496 lines download raw bytes

1// Excel Module - SheetJS Operations
2const ExcelModule = (() => {
3    // Global Excel data
4    let workbook = null;
5    let worksheet = null;
6    let excelData = [];
7    let currentFileName = '';
8    let currentSheetName = '';
9    
10    // Initialize
11    function init() {
12        console.log('Excel module initialized');
13    }
14    
15    // Parse uploaded Excel file
16    function parseExcelFile(file, callback) {
17        if (!file) {
18            callback({ success: false, message: 'Không có file được chọn' });
19            return;
20        }
21        
22        // Check file size
23        const maxSize = 50 * 1024 * 1024; // 50MB
24        if (file.size > maxSize) {
25            callback({ 
26                success: false, 
27                message: `File quá lớn (${(file.size / 1024 / 1024).toFixed(2)}MB). Giới hạn: 50MB` 
28            });
29            return;
30        }
31        
32        // Check file extension
33        const validExtensions = ['.xlsx', '.xls', '.xlsm', '.xlsb'];
34        const fileExt = '.' + file.name.split('.').pop().toLowerCase();
35        if (!validExtensions.includes(fileExt)) {
36            callback({ 
37                success: false, 
38                message: `Định dạng file không hợp lệ. Chỉ chấp nhận: ${validExtensions.join(', ')}` 
39            });
40            return;
41        }
42        
43        const reader = new FileReader();
44        
45        reader.onload = function(e) {
46            try {
47                const data = new Uint8Array(e.target.result);
48                workbook = XLSX.read(data, { 
49                    type: 'array',
50                    cellDates: true,
51                    cellStyles: true
52                });
53                
54                // Try to find 'Data' sheet (case insensitive)
55                currentSheetName = 'Data';
56                if (!workbook.Sheets[currentSheetName]) {
57                    currentSheetName = Object.keys(workbook.Sheets).find(name => 
58                        name.toLowerCase() === 'data'
59                    ) || workbook.SheetNames[0];
60                }
61                
62                worksheet = workbook.Sheets[currentSheetName];
63                
64                // Convert to JSON with headers
65                excelData = XLSX.utils.sheet_to_json(worksheet, { 
66                    header: 1,
67                    defval: '',
68                    blankrows: false
69                });
70                
71                // Validate data
72                if (excelData.length <= 1) {
73                    callback({ 
74                        success: false, 
75                        message: 'File Excel không có dữ liệu hoặc chỉ có header' 
76                    });
77                    return;
78                }
79                
80                // Check if column A exists (ID column)
81                if (!excelData[0] || excelData[0].length === 0) {
82                    callback({ 
83                        success: false, 
84                        message: 'File không có cột dữ liệu. Vui lòng kiểm tra định dạng file.' 
85                    });
86                    return;
87                }
88                
89                // Update global variables
90                window.excelData = excelData;
91                currentFileName = file.name;
92                window.currentFileName = currentFileName;
93                
94                // Calculate file info
95                const fileInfo = calculateFileInfo(excelData);
96                
97                // 🔴 PHÁT HIỆN VÀ ĐỌC CỘT SCAN_STATUS NẾU CÓ
98                const scanStatusInfo = detectAndReadScanStatus(excelData);
99                
100                callback({ 
101                    success: true, 
102                    data: excelData,
103                    sheetName: currentSheetName,
104                    fileName: currentFileName,
105                    fileInfo: fileInfo,
106                    scanStatusInfo: scanStatusInfo // 🔴 THÊM THÔNG TIN NÀY
107                });
108                
109            } catch (error) {
110                console.error('Error parsing Excel:', error);
111                callback({ 
112                    success: false, 
113                    message: `Lỗi khi đọc file Excel: ${error.message}` 
114                });
115            }
116        };
117        
118        reader.onerror = function() {
119            callback({ 
120                success: false, 
121                message: 'Lỗi khi đọc file. Vui lòng thử lại.' 
122            });
123        };
124        
125        reader.readAsArrayBuffer(file);
126    }
127    
128    // 🔴 HÀM MỚI: Phát hiện và đọc cột SCAN_STATUS
129    function detectAndReadScanStatus(data) {
130        if (!data || data.length <= 1) {
131            return {
132                hasScanStatus: false,
133                scanStatusColumn: -1,
134                scannedIds: [],
135                count: 0,
136                columnName: '',
137                rawData: []
138            };
139        }
140        
141        const headers = data[0];
142        let scanStatusColumn = -1;
143        
144        // Tìm cột SCAN_STATUS
145        const possibleNames = [
146            'SCAN_STATUS', 'SCAN STATUS', 'STATUS', 
147            'Trạng thái quét', 'Đã quét', 'QUET_STATUS',
148            'ScanStatus', 'scan_status', 'status',
149            'TRANG_THAI_QUET', 'Trạng thái', 'Trang thai quet'
150        ];
151        
152        // Tìm theo tên cột
153        for (let i = 0; i < headers.length; i++) {
154            const header = String(headers[i] || '').trim().toLowerCase();
155            for (const name of possibleNames) {
156                if (header === name.toLowerCase()) {
157                    scanStatusColumn = i;
158                    break;
159                }
160            }
161            if (scanStatusColumn !== -1) break;
162        }
163        
164        // Nếu không tìm thấy, kiểm tra cột Y (index 24 - thứ tự 25)
165        if (scanStatusColumn === -1 && headers.length >= 25) {
166            scanStatusColumn = 24; // Cột Y
167        }
168        
169        // Nếu vẫn không tìm thấy, tìm cột cuối cùng
170        if (scanStatusColumn === -1) {
171            const lastColumn = headers.length - 1;
172            // Nếu cột cuối cùng trống hoặc có tên đặc biệt
173            const lastColHeader = String(headers[lastColumn] || '').trim().toLowerCase();
174            if (lastColHeader === '' || possibleNames.some(name => 
175                lastColHeader.includes(name.toLowerCase()))) {
176                scanStatusColumn = lastColumn;
177            }
178        }
179        
180        // Nếu KHÔNG tìm thấy cột SCAN_STATUS
181        if (scanStatusColumn === -1) {
182            return {
183                hasScanStatus: false,
184                scanStatusColumn: -1,
185                scannedIds: [],
186                count: 0,
187                columnName: '',
188                rawData: []
189            };
190        }
191        
192        // Đọc dữ liệu từ cột SCAN_STATUS
193        const scannedIds = [];
194        const rawStatusData = [];
195        let count = 0;
196        
197        // Pattern để phát hiện đã quét
198        const scanPatterns = [
199            /đã quét/i,
200            /scanned/i,
201            /đã scan/i,
202            /completed/i,
203            /done/i,
204            /xong/i,
205            /finished/i,
206            /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/, // dd/mm/yyyy
207            /\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/, // yyyy/mm/dd
208            /\d{1,2}:\d{1,2}(:\d{1,2})?/, // hh:mm:ss
209            /\d{1,2}\s*[\/\-]\s*\d{1,2}\s*[\/\-]\s*\d{2,4}/, // dd / mm / yyyy
210        ];
211        
212        for (let i = 1; i < data.length; i++) {
213            const row = data[i];
214            if (row && row.length > scanStatusColumn) {
215                const status = String(row[scanStatusColumn] || '').trim();
216                const id = row[0] ? String(row[0]).trim() : null;
217                
218                rawStatusData.push({
219                    rowIndex: i,
220                    id: id,
221                    status: status,
222                    hasValue: status !== '',
223                    isScanned: false
224                });
225                
226                // Kiểm tra nếu đã quét
227                let isScanned = false;
228                
229                if (status !== '') {
230                    // Kiểm tra các pattern
231                    for (const pattern of scanPatterns) {
232                        if (pattern.test(status)) {
233                            isScanned = true;
234                            break;
235                        }
236                    }
237                    
238                    // Hoặc nếu có từ "Đã" ở đầu
239                    if (status.toLowerCase().startsWith('đã')) {
240                        isScanned = true;
241                    }
242                    
243                    // Hoặc nếu có từ "Scanned"
244                    if (status.toLowerCase().includes('scanned')) {
245                        isScanned = true;
246                    }
247                    
248                    // Hoặc nếu độ dài > 3 ký tự và không phải là "no", "null", "false"
249                    const lowerStatus = status.toLowerCase();
250                    const notScannedWords = ['no', 'null', 'false', '0', 'không', 'chưa', ''];
251                    if (status.length > 3 && !notScannedWords.includes(lowerStatus)) {
252                        // Nếu có chứa số (có thể là timestamp)
253                        if (/\d/.test(status)) {
254                            isScanned = true;
255                        }
256                    }
257                }
258                
259                if (isScanned && id && id !== '') {
260                    scannedIds.push(id);
261                    count++;
262                    rawStatusData[rawStatusData.length - 1].isScanned = true;
263                }
264            }
265        }
266        
267        return {
268            hasScanStatus: true,
269            scanStatusColumn: scanStatusColumn,
270            scannedIds: scannedIds,
271            count: count,
272            columnName: headers[scanStatusColumn] || `Cột ${String.fromCharCode(65 + scanStatusColumn)}`,
273            rawData: rawStatusData,
274            totalRows: data.length - 1
275        };
276    }
277    
278    // Calculate file information
279    function calculateFileInfo(data) {
280        if (!data || data.length === 0) {
281            return null;
282        }
283        
284        const totalRows = data.length - 1;
285        const totalColumns = data[0] ? data[0].length : 0;
286        
287        // Estimate size
288        const estimatedSize = JSON.stringify(data).length;
289        const sizeInKB = (estimatedSize / 1024).toFixed(2);
290        const sizeInMB = (estimatedSize / 1024 / 1024).toFixed(2);
291        
292        // Check if file is suitable for auto-save
293        let saveSuitability = 'good';
294        let recommendation = '';
295        
296        if (totalRows > 50000) {
297            saveSuitability = 'poor';
298            recommendation = 'File rất lớn (>50,000 dòng). Không nên lưu tự động tiến trình.';
299        } else if (totalRows > 10000) {
300            saveSuitability = 'fair';
301            recommendation = 'File lớn (>10,000 dòng). Chế độ lưu nhẹ được kích hoạt.';
302        } else {
303            saveSuitability = 'good';
304            recommendation = 'File phù hợp. Tự động lưu tiến trình đầy đủ.';
305        }
306        
307        // Check for required columns
308        const headers = data[0] || [];
309        const hasIdColumn = headers.length > 0;
310        const idColumnName = headers[0] || 'Column A';
311        
312        return {
313            totalRows,
314            totalColumns,
315            sizeInKB,
316            sizeInMB,
317            saveSuitability,
318            recommendation,
319            hasIdColumn,
320            idColumnName,
321            headers: headers.slice(0, 10)
322        };
323    }
324    
325    // Find row by ID
326    function findRowById(id) {
327        if (!excelData || excelData.length === 0) {
328            return null;
329        }
330        
331        // ID is in column A (index 0)
332        for (let i = 1; i < excelData.length; i++) {
333            const row = excelData[i];
334            if (row && row[0] && row[0].toString().trim() === id.toString().trim()) {
335                return {
336                    rowIndex: i,
337                    rowData: row,
338                    headers: excelData[0]
339                };
340            }
341        }
342        
343        return null;
344    }
345    
346    // Update scan status in Excel data
347    function updateScanStatus(rowIndex, timestamp = null) {
348        if (!excelData || rowIndex < 1 || rowIndex >= excelData.length) {
349            return false;
350        }
351        
352        try {
353            const headers = excelData[0];
354            let statusColIndex = -1;
355            
356            // Find SCAN_STATUS column
357            for (let i = 0; i < headers.length; i++) {
358                if (headers[i] === 'SCAN_STATUS' || headers[i] === 'Trạng thái quét') {
359                    statusColIndex = i;
360                    break;
361                }
362            }
363            
364            // If column doesn't exist, add it
365            if (statusColIndex === -1) {
366                statusColIndex = headers.length;
367                headers[statusColIndex] = 'SCAN_STATUS';
368                
369                // Initialize for all rows
370                for (let i = 1; i < excelData.length; i++) {
371                    if (!excelData[i][statusColIndex]) {
372                        excelData[i][statusColIndex] = '';
373                    }
374                }
375            }
376            
377            // Update status with timestamp
378            const scanTime = timestamp || new Date();
379            const formattedTime = scanTime.toLocaleString('vi-VN');
380            excelData[rowIndex][statusColIndex] = `Đã quét: ${formattedTime}`;
381            
382            // Mark as changed
383            window.hasUnsavedChanges = true;
384            
385            return true;
386            
387        } catch (error) {
388            console.error('Error updating scan status:', error);
389            return false;
390        }
391    }
392    
393    // Export updated Excel file
394    function exportExcel(includeAllSheets = false) {
395        if (!workbook || !excelData || excelData.length === 0) {
396            return { success: false, message: 'Không có dữ liệu để xuất' };
397        }
398        
399        try {
400            // Update worksheet with current data
401            const newWorksheet = XLSX.utils.aoa_to_sheet(excelData);
402            
403            // Update workbook
404            workbook.Sheets[currentSheetName] = newWorksheet;
405            
406            // Generate Excel file
407            const excelBuffer = XLSX.write(workbook, { 
408                bookType: 'xlsx', 
409                type: 'array',
410                bookSST: false
411            });
412            
413            // Create blob
414            const blob = new Blob([excelBuffer], { 
415                type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' 
416            });
417            
418            // Create filename
419            const now = new Date();
420            const dateStr = now.toISOString().slice(0, 10).replace(/-/g, '');
421            const timeStr = now.toTimeString().slice(0, 8).replace(/:/g, '');
422            const originalName = currentFileName.replace(/\.[^/.]+$/, "");
423            const exportName = `${originalName}_scanned_${dateStr}_${timeStr}.xlsx`;
424            
425            return {
426                success: true,
427                blob: blob,
428                fileName: exportName,
429                size: (excelBuffer.length / 1024 / 1024).toFixed(2) + ' MB',
430                scannedCount: window.scannedIds ? window.scannedIds.size : 0
431            };
432            
433        } catch (error) {
434            console.error('Error exporting Excel:', error);
435            return { 
436                success: false, 
437                message: `Lỗi khi xuất file: ${error.message}` 
438            };
439        }
440    }
441    
442    // Create sample data for testing
443    function createSampleData() {
444        const sampleData = [
445            ["index", "sheet", "length", "width", "cutting-length", "cutting-width", "thickness", "grain", "material", "Code N.1", "Code N.2", "Code N.3", "label", "SCAN_STATUS"],
446            [8, 1, 2000, 350, 1998, 348, 17, "theo cạnh dài", "17mm_101SH", "Tầng 2", "phòng ngủ 1", "kệ sách", "Hông Phải", "Đã quét: 15/01/2024 14:30"],
447            [39, 3, 766, 307, 766, 306, 17, "theo cạnh dài", "17mm_101SH", "Tầng 1", "phòng ngủ", "tủ kín", "Kệ/ Đợt Tầng Trên", ""],
448            [42, 1, 1200, 450, 1198, 448, 18, "theo cạnh ngắn", "18mm_202SH", "Tầng 3", "phòng khách", "tủ TV", "Mặt Trước", "Đã quét: 16/01/2024 10:15"],
449            [15, 2, 800, 600, 798, 598, 15, "theo cạnh dài", "15mm_303SH", "Tầng 1", "nhà bá
449º¿p", "tá»§ chén", "Cánh Trái", ""],
450            [27, 1, 1500, 400, 1498, 398, 16, "theo cạnh dài", "16mm_404SH", "Tầng 2", "phòng làm việc", "bàn làm việc", "Mặt Bàn", "Scanned: 2024-01-14"]
451        ];
452        
453        excelData = sampleData;
454        window.excelData = excelData;
455        currentFileName = 'sample_data_with_status.xlsx';
456        window.currentFileName = currentFileName;
457        
458        return sampleData;
459    }
460    
461    // Get current Excel data
462    function getData() {
463        return excelData;
464    }
465    
466    // Set Excel data (for restoration)
467    function setData(data, fileName = '') {
468        excelData = data;
469        window.excelData = data;
470        currentFileName = fileName || currentFileName;
471        window.currentFileName = currentFileName;
472        
473        // Recreate workbook from data
474        if (data && data.length > 0) {
475            worksheet = XLSX.utils.aoa_to_sheet(data);
476            workbook = XLSX.utils.book_new();
477            currentSheetName = 'Data';
478            XLSX.utils.book_append_sheet(workbook, worksheet, currentSheetName);
479        }
480    }
481    
482    // Public API
483    return {
484        init,
485        parseExcelFile,
486        calculateFileInfo,
487        detectAndReadScanStatus, // 🔴 THÊM HÀM NÀY
488        findRowById,
489        updateScanStatus,
490        exportExcel,
491        createSampleData,
492        getData,
493        setData,
494        getFileName: () => currentFileName
495    };
496})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.