diff --git a/.forgejo/workflows/ci-cd.yml b/.forgejo/workflows/ci-cd.yml
index 62a125b..4c508ef 100644
--- a/.forgejo/workflows/ci-cd.yml
+++ b/.forgejo/workflows/ci-cd.yml
@@ -5,7 +5,7 @@ on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
- # - 'version-[0-9]+.[0-9]+.[0-9]+'
+ - 'version-[0-9]+.[0-9]+.[0-9]+'
workflow_dispatch:
env:
diff --git a/docs/test-cases-location.md b/docs/test-cases-location.md
deleted file mode 100644
index ed19dcd..0000000
--- a/docs/test-cases-location.md
+++ /dev/null
@@ -1,860 +0,0 @@
-# Test Cases: Location Features
-# HRMS Check-in/Check-out System
-
-## Document Information
-
-| Field | Value |
-|-------|-------|
-| **Document Version** | 1.0.0 |
-| **Last Updated** | 2025-03-09 |
-| **Project** | HRMS Check-in/Check-out |
-| **Module** | Location Features |
-| **Author** | QA Team |
-
----
-
-## 1. Overview (ภาพรวม)
-
-ระบบ HRMS Check-in/Check-out มีฟีเจอร์ Location ที่สำคัญในการลงเวลาเข้า-ออกงาน ฟีเจอร์ Location ประกอบด้วย:
-
-### 1.1 Location Features
-
-| Feature | Description |
-|---------|-------------|
-| **Location Services** | รับพิกัด GPS จากอุปกรณ์ผ่าน Geolocation API |
-| **Location Validation** | ตรวจสอบความถูกต้องของตำแหน่ง (Mock Location Detection) |
-| **POI Resolution** | แปลงพิกัดเป็นชื่อสถานที่ (Bangkok GIS / ArcGIS) |
-| **Check-in/Check-out** | บันทึกตำแหน่งพร้อมรูปถ่าย |
-| **Privacy Consent** | ขอความยินยอมการเข้าถึงตำแหน่ง |
-
-### 1.2 Related Files
-
-| File Path | Description |
-|-----------|-------------|
-| `/src/composables/useLocationValidation.ts` | Location validation logic |
-| `/src/views/HomeView.vue` | Check-in/Check-out page |
-| `/src/components/AscGISMap.vue` | Map component with location validation |
-| `/src/api/api.checkin.ts` | Check-in API endpoints |
-| `/src/stores/privacy.ts` | Privacy store for consent management |
-| `/src/composables/usePermissions.ts` | Permission checking utilities |
-
----
-
-## 2. Test Environment (สภาพแวดล้อมการทดสอบ)
-
-### 2.1 Devices (อุปกรณ์ทดสอบ)
-
-| Device Type | OS | Browser | Notes |
-|-------------|-------|---------|-------|
-| Mobile | iOS 17+ | Safari | Test with real GPS |
-| Mobile | Android 13+ | Chrome | Test with mock location apps |
-| Desktop | macOS 14+ | Chrome 121+ | Test with Developer Tools |
-| Desktop | Windows 11 | Edge 121+ | Test with Developer Tools |
-
-### 2.2 Tools (เครื่องมือทดสอบ)
-
-| Tool | Purpose |
-|------|---------|
-| Fake GPS Location (Android) | Mock location testing |
-| Location Changer (iOS) | Mock location testing |
-| Chrome DevTools | Sensor simulation |
-| Xcode Simulator | iOS location simulation |
-| Android Studio Emulator | Android location simulation |
-
-### 2.3 Test Data (ข้อมูลทดสอบ)
-
-| Data Type | Valid Value | Invalid Value |
-|-----------|-------------|---------------|
-| Latitude | 13.7563 (Bangkok) | 0, 91, -91 |
-| Longitude | 100.5018 (Bangkok) | 0, 181, -181 |
-| Accuracy (meters) | 10-50 | 101+ |
-| Timestamp | Current time | 60+ seconds old |
-
----
-
-## 3. Validation Rules (กฎการตรวจสอบ)
-
-### 3.1 Location Validation Rules
-
-| Rule | Threshold | Error Message (Thai) | Mock Indicator |
-|------|-----------|---------------------|----------------|
-| Valid Coordinates | -90 to 90 lat, -180 to 180 lon, not (0,0) | "พิกัดตำแหน่งไม่ถูกต้อง กรุณาลองใหม่" | +3 |
-| Fresh Timestamp | <= 60 seconds old | "ข้อมูลตำแหน่งเก่าเกินไป กรุณารับสัญญาณ GPS ใหม่" | +2 |
-| GPS Accuracy | <= 100 meters | "ความแม่นยำตำแหน่งต่ำเกินไป กรุณาตรวจสอบการรับสัญญาณ GPS" | +1 |
-| Movement Speed | <= 100 m/s (~360 km/h) | "ตรวจพบการเคลื่อนที่ด้วยความเร็วผิดปกติ อาจเป็นการจำลองตำแหน่ง" | +3 |
-| Mock Detection | >= 3 indicators | "ตรวจพบว่าตำแหน่ง GPS อาจไม่ถูกต้อง กรุณาปิดแอปจำลองตำแหน่งและลองใหม่" | - |
-
-### 3.2 Validation Configuration
-
-```typescript
-VALIDATION_CONFIG = {
- MAX_TIMESTAMP_AGE_MS: 60_000, // 60 seconds
- MAX_ACCURACY_METERS: 100, // 100 meters
- MAX_SPEED_MS: 100, // ~360 km/h
- POSITION_HISTORY_SIZE: 5, // positions for pattern detection
- MOCK_INDICATOR_THRESHOLD: 3, // indicators for mock detection
-}
-```
-
----
-
-## 4. Test Scenarios
-
-### 4.1 TC-LOC-01: Location Permission
-
-| Test Case | Description | Priority |
-|-----------|-------------|----------|
-| TC-LOC-01-01 | อนุญาตให้เข้าถึงตำแหน่ง | High |
-| TC-LOC-01-02 | ปฏิเสธการเข้าถึงตำแหน่ง | High |
-
-### 4.2 TC-LOC-02: Location Acquisition
-
-| Test Case | Description | Priority |
-|-----------|-------------|----------|
-| TC-LOC-02-01 | รับพิกัด GPS สำเร็จ (Outdoor) | High |
-| TC-LOC-02-02 | รับพิกัด GPS สำเร็จ (Indoor) | Medium |
-| TC-LOC-02-03 | รับพิกัด GPS ล้มเหลว (GPS ไม่ทำงาน) | High |
-| TC-LOC-02-04 | หมดเวลาขอรับพิกัด (Timeout) | Medium |
-
-### 4.3 TC-LOC-03: Location Validation
-
-| Test Case | Description | Priority |
-|-----------|-------------|----------|
-| TC-LOC-03-01 | พิกัดถูกต้อง (Valid coordinates) | High |
-| TC-LOC-03-02 | พิกัดไม่ถูกต้อง (Invalid coordinates - (0,0)) | High |
-| TC-LOC-03-03 | พิกัดนอกช่วง (Out of range) | Medium |
-| TC-LOC-03-04 | ข้อมูลตำแหน่งเก่าเกินไป (Stale timestamp) | High |
-| TC-LOC-03-05 | ความแม่นยำต่ำ (Poor accuracy) | Medium |
-| TC-LOC-03-06 | ความเร็วเคลื่อนที่ผิดปกติ (Impossible speed) | High |
-
-### 4.4 TC-LOC-04: Mock Location Detection
-
-| Test Case | Description | Priority |
-|-----------|-------------|----------|
-| TC-LOC-04-01 | ไม่พบ Mock Location (Normal GPS) | High |
-| TC-LOC-04-02 | พบ Mock Location - Fake GPS App | High |
-| TC-LOC-04-03 | พบ Mock Location - พิกัด (0,0) | High |
-| TC-LOC-04-04 | พบ Mock Location - ข้อมูลเก่าเกินไป | High |
-| TC-LOC-04-05 | พบ Mock Location - ความเร็วผิดปกติ | High |
-| TC-LOC-04-06 | พบ Mock Location - หลาย indicators (Confidence High) | High |
-
-### 4.5 TC-LOC-05: POI Resolution
-
-| Test Case | Description | Priority |
-|-----------|-------------|----------|
-| TC-LOC-05-01 | แปลงพิกัดเป็นชื่อสถานที่สำเร็จ (Bangkok GIS) | High |
-| TC-LOC-05-02 | แปลงพิกัดเป็นชื่อสถานที่สำเร็จ (ArcGIS Fallback) | Medium |
-| TC-LOC-05-03 | แปลงพิกัดล้มเหลว (ทั้งสอง service down) | Low |
-
-### 4.6 TC-LOC-06: Check-in with Location
-
-| Test Case | Description | Priority |
-|-----------|-------------|----------|
-| TC-LOC-06-01 | ลงเวลาเข้าสำเร็จ - ณ สถานที่ตั้ง (In-place) | High |
-| TC-LOC-06-02 | ลงเวลาเข้าสำเร็จ - นอกสถานที่ตั้ง (Off-site) | High |
-| TC-LOC-06-03 | ลงเวลาเข้าล้มเหลว - Mock Location detected | High |
-| TC-LOC-06-04 | ลงเวลาเข้าล้มเหลว - Location permission denied | High |
-
-### 4.7 TC-LOC-07: Check-out with Location
-
-| Test Case | Description | Priority |
-|-----------|-------------|----------|
-| TC-LOC-07-01 | ลงเวลาออกสำเร็จ | High |
-| TC-LOC-07-02 | ลงเวลาออกล้มเหลว - Mock Location detected | High |
-
-### 4.8 TC-LOC-08: Special Time Entry
-
-| Test Case | Description | Priority |
-|-----------|-------------|----------|
-| TC-LOC-08-01 | บันทึกเวลาพิเศษพร้อมตำแหน่งสำเร็จ | Medium |
-| TC-LOC-08-02 | บันทึกเวลาพิเศษล้มเหลว - ไม่ระบุตำแหน่ง | Medium |
-
-### 4.9 TC-LOC-09: Privacy Consent
-
-| Test Case | Description | Priority |
-|-----------|-------------|----------|
-| TC-LOC-09-01 | แสดง Privacy Modal ก่อนใช้ Location | High |
-| TC-LOC-09-02 | ยอมรับ Privacy Policy | High |
-| TC-LOC-09-03 | ปฏิเสธ Privacy Policy | High |
-
----
-
-## 5. Test Cases Details
-
-### Test Case Template
-
-```
-Test Case ID: TC-LOC-XX-XX
-Test Case Name: [ชื่อ Test Case]
-Description: [คำอธิบาย]
-Priority: High/Medium/Low
-Pre-conditions: [เงื่อนไขเบื้องต้น]
-Test Data: [ข้อมูลทดสอบ]
-Test Steps: [ขั้นตอนการทดสอบ]
-Expected Result: [ผลลัพธ์ที่คาดหวัง]
-Actual Result: [ผลลัพธ์จริง - ว่างไว้กรอก]
-Status: [Pass/Fail/Not Run]
-Tested By: [ผู้ทดสอบ]
-Test Date: [วันที่ทดสอบ]
-```
-
----
-
-### TC-LOC-01: Location Permission
-
-#### TC-LOC-01-01: อนุญาตให้เข้าถึงตำแหน่ง
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-01-01 |
-| **Test Case Name** | อนุญาตให้เข้าถึงตำแหน่ง |
-| **Description** | ผู้ใช้อนุญาตให้แอปเข้าถึงตำแหน่ง GPS |
-| **Priority** | High |
-| **Pre-conditions** | 1. ยอมรับ Privacy Policy แล้ว
2. เปิดแอปครั้งแรก หรือยังไม่เคยอนุญาต Location Permission |
-| **Test Data** | - |
-| **Test Steps** | 1. เปิดแอป HRMS
2. กดปุ่มขอตำแหน่ง (ถ้าจำเป็น)
3. เลือก "Allow" เมื่อระบบขอ Location Permission |
-| **Expected Result** | 1. แสดงแผนที่พร้อมตำแหน่งปัจจุบัน
2. แสดงชื่อสถานที่ใกล้เคียง
3. ปุ่มลงเวลาเข้า/ออกใช้งานได้ |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-01-02: ปฏิเสธการเข้าถึงตำแหน่ง
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-01-02 |
-| **Test Case Name** | ปฏิเสธการเข้าถึงตำแหน่ง |
-| **Description** | ผู้ใช้ปฏิเสธการเข้าถึงตำแหน่ง GPS |
-| **Priority** | High |
-| **Pre-conditions** | 1. ยอมรับ Privacy Policy แล้ว
2. เปิดแอปครั้งแรก หรือยังไม่เคยอนุญาต Location Permission |
-| **Test Data** | - |
-| **Test Steps** | 1. เปิดแอป HRMS
2. กดปุ่มขอตำแหน่ง (ถ้าจำเป็น)
3. เลือก "Deny" เมื่อระบบขอ Location Permission |
-| **Expected Result** | 1. แสดงข้อความแจ้งเตือน: "ไม่สามารถระบุตำแหน่งปัจจุบันได้ เนื่องจากคุณปฏิเสธการเข้าถึงตำแหน่ง กรุณาเปิดการเข้าถึงตำแหน่ง"
2. แผนที่ไม่แสดง
3. ปุ่มลงเวลาเข้า/ออกใช้งานไม่ได้ |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
----
-
-### TC-LOC-02: Location Acquisition
-
-#### TC-LOC-02-01: รับพิกัด GPS สำเร็จ (Outdoor)
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-02-01 |
-| **Test Case Name** | รับพิกัด GPS สำเร็จ (Outdoor) |
-| **Description** | รับพิกัด GPS สำเร็จในพื้นที่เปิดกว้าง |
-| **Priority** | High |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว |
-| **Test Data** | Location: อาคารรัฐสภา แขวง ถนนนครไชยศรี เขตดุสิต กรุงเทพมหานคร (13.7563, 100.5018) |
-| **Test Steps** | 1. เปิดแอป HRMS
2. อยู่ในพื้นที่เปิดกว้าง (Outdoor)
3. รอรับพิกัด GPS |
-| **Expected Result** | 1. รับพิกัด GPS สำเร็จ
2. แสดงแผนที่พร้อมตำแหน่ง
3. แสดงชื่อสถานที่ใกล้เคียง
4. locationGranted = true |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-02-02: รับพิกัด GPS สำเร็จ (Indoor)
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-02-02 |
-| **Test Case Name** | รับพิกัด GPS สำเร็จ (Indoor) |
-| **Description** | รับพิกัด GPS สำเร็จในพื้นที่ปิด |
-| **Priority** | Medium |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว |
-| **Test Data** | Location: ภายในอาคารสำนักงาน |
-| **Test Steps** | 1. เปิดแอป HRMS
2. อยู่ในพื้นที่ปิด (Indoor)
3. รอรับพิกัด GPS |
-| **Expected Result** | 1. รับพิกัด GPS สำเร็จ
2. แสดงแผนที่พร้อมตำแหน่ง
3. แสดงชื่อสถานที่ใกล้เคียง
4. อาจมีความแม่นยำต่ำกว่า Outdoor |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-02-03: รับพิกัด GPS ล้มเหลว (GPS ไม่ทำงาน)
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-02-03 |
-| **Test Case Name** | รับพิกัด GPS ล้มเหลว (GPS ไม่ทำงาน) |
-| **Description** | GPS ไม่ทำงาน หรืออยู่ในพื้นที่ที่ไม่มีสัญญาณ GPS |
-| **Priority** | High |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว |
-| **Test Data** | - |
-| **Test Steps** | 1. เปิดแอป HRMS
2. ปิด GPS ในอุปกรณ์
3. พยายามขอรับพิกัด |
-| **Expected Result** | 1. แสดงข้อความแจ้งเตือน: "ไม่สามารถระบุตำแหน่งปัจจุบันได้"
2. แผนที่ไม่แสดง |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-02-04: หมดเวลาขอรับพิกัด (Timeout)
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-02-04 |
-| **Test Case Name** | หมดเวลาขอรับพิกัด (Timeout) |
-| **Description** | การร้องขอตำแหน่งหมดเวลา |
-| **Priority** | Medium |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว |
-| **Test Data** | - |
-| **Test Steps** | 1. เปิดแอป HRMS
2. จำลองสภาวะที่ GPS ตอบสนองช้า (ใช้ Developer Tools)
3. รอจนหมดเวลา |
-| **Expected Result** | 1. แสดงข้อความแจ้งเตือน: "การร้องขอตำแหน่งหมดเวลา กรุณาลองอีกครั้ง" |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
----
-
-### TC-LOC-03: Location Validation
-
-#### TC-LOC-03-01: พิกัดถูกต้อง (Valid coordinates)
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-03-01 |
-| **Test Case Name** | พิกัดถูกต้อง (Valid coordinates) |
-| **Description** | ตรวจสอบพิกัดที่ถูกต้อง |
-| **Priority** | High |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว |
-| **Test Data** | Latitude: 13.7563, Longitude: 100.5018, Accuracy: 10m, Timestamp: current |
-| **Test Steps** | 1. เปิดแอป HRMS
2. รับพิกัด GPS ที่ถูกต้อง |
-| **Expected Result** | 1. validation = { isValid: true, isMockDetected: false }
2. locationGranted = true
3. ไม่มี error/warning |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-03-02: พิกัดไม่ถูกต้อง (Invalid coordinates - (0,0))
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-03-02 |
-| **Test Case Name** | พิกัดไม่ถูกต้อง (Invalid coordinates - (0,0)) |
-| **Description** | ตรวจสอบพิกัด (0,0) ซึ่งเป็นค่า default ของ mock location |
-| **Priority** | High |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว |
-| **Test Data** | Latitude: 0, Longitude: 0 |
-| **Test Steps** | 1. เปิดแอป HRMS
2. จำลองพิกัด (0,0) ด้วย Mock Location App |
-| **Expected Result** | 1. validation = { isValid: false, isMockDetected: true, confidence: 'high' }
2. แสดงข้อความ: "พิกัดตำแหน่งไม่ถูกต้อง กรุณาลองใหม่"
3. disabledBtn = true |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-03-03: พิกัดนอกช่วง (Out of range)
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-03-03 |
-| **Test Case Name** | พิกัดนอกช่วง (Out of range) |
-| **Description** | ตรวจสอบพิกัดที่อยู่นอกช่วงที่กำหนด |
-| **Priority** | Medium |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว |
-| **Test Data** | Latitude: 91, Longitude: 181 (out of range) |
-| **Test Steps** | 1. เปิดแอป HRMS
2. จำลองพิกัดนอกช่วงด้วย Developer Tools |
-| **Expected Result** | 1. validation = { isValid: false }
2. แสดงข้อความ: "พิกัดตำแหน่งไม่ถูกต้อง กรุณาลองใหม่" |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-03-04: ข้อมูลตำแหน่งเก่าเกินไป (Stale timestamp)
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-03-04 |
-| **Test Case Name** | ข้อมูลตำแหน่งเก่าเกินไป (Stale timestamp) |
-| **Description** | ตรวจสอบว่าข้อมูลตำแหน่งเก่าเกิน 60 วินาที |
-| **Priority** | High |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว |
-| **Test Data** | Timestamp: Date.now() - 70,000 ms (70 seconds old) |
-| **Test Steps** | 1. เปิดแอป HRMS
2. จำลอง timestamp เก่าเกิน 60 วินาที |
-| **Expected Result** | 1. validation.errors = ["ข้อมูลตำแหน่งเก่าเกินไป กรุณารับสัญญาณ GPS ใหม่"]
2. mockIndicators += 2 |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-03-05: ความแม่นยำต่ำ (Poor accuracy)
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-03-05 |
-| **Test Case Name** | ความแม่นยำต่ำ (Poor accuracy) |
-| **Description** | ตรวจสอบความแม่นยำของ GPS ต่ำกว่า 100 เมตร |
-| **Priority** | Medium |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว |
-| **Test Data** | Accuracy: 150 meters |
-| **Test Steps** | 1. เปิดแอป HRMS
2. จำลองความแม่นยำต่ำด้วย Developer Tools |
-| **Expected Result** | 1. validation.warnings = ["ความแม่นยำตำแหน่งต่ำเกินไป กรุณาตรวจสอบการรับสัญญาณ GPS"]
2. mockIndicators += 1 |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-03-06: ความเร็วเคลื่อนที่ผิดปกติ (Impossible speed)
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-03-06 |
-| **Test Case Name** | ความเร็วเคลื่อนที่ผิดปกติ (Impossible speed) |
-| **Description** | ตรวจสอบความเร็วเคลื่อนที่เกิน 100 m/s (360 km/h) |
-| **Priority** | High |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว
3. มีตำแหน่งเดิมใน history |
-| **Test Data** | Position 1: (13.7563, 100.5018) at t=0
Position 2: (15.8700, 100.9925) at t=60 (~180 km in 60s = 3000 km/h) |
-| **Test Steps** | 1. เปิดแอป HRMS
2. จำลองการเคลื่อนที่ด้วยความเร็วผิดปกติ |
-| **Expected Result** | 1. validation.errors = ["ตรวจพบการเคลื่อนที่ด้วยความเร็วผิดปกติ อาจเป็นการจำลองตำแหน่ง"]
2. mockIndicators += 3 |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
----
-
-### TC-LOC-04: Mock Location Detection
-
-#### TC-LOC-04-01: ไม่พบ Mock Location (Normal GPS)
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-04-01 |
-| **Test Case Name** | ไม่พบ Mock Location (Normal GPS) |
-| **Description** | การใช้งาน GPS ปกติ ไม่พบ mock location |
-| **Priority** | High |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว
3. ไม่ได้เปิด Mock Location App |
-| **Test Data** | Normal GPS data |
-| **Test Steps** | 1. เปิดแอป HRMS
2. ใช้งาน GPS ปกติ |
-| **Expected Result** | 1. validation = { isValid: true, isMockDetected: false }
2. locationGranted = true
3. isMockLocationDetected = false
4. ปุ่มลงเวลาเข้า/ออกใช้งานได้ |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-04-02: พบ Mock Location - Fake GPS App
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-04-02 |
-| **Test Case Name** | พบ Mock Location - Fake GPS App |
-| **Description** | ตรวจพบ mock location จาก Fake GPS App |
-| **Priority** | High |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว |
-| **Test Data** | Mock location from Fake GPS App |
-| **Test Steps** | 1. เปิด Fake GPS Location App
2. ตั้งค่าตำแหน่งปลอม
3. เปิดแอป HRMS
4. ขอรับพิกัด |
-| **Expected Result** | 1. ตรวจพบ mock location (indicators >= 3)
2. แสดงข้อความ: "ตรวจพบว่าตำแหน่ง GPS อาจไม่ถูกต้อง กรุณาปิดแอปจำลองตำแหน่งและลองใหม่"
3. isMockLocationDetected = true
4. disabledBtn = true |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-04-03: พบ Mock Location - พิกัด (0,0)
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-04-03 |
-| **Test Case Name** | พบ Mock Location - พิกัด (0,0) |
-| **Description** | ตรวจพบพิกัด (0,0) ซึ่งเป็นค่า default ของ mock location |
-| **Priority** | High |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว |
-| **Test Data** | Latitude: 0, Longitude: 0 |
-| **Test Steps** | 1. จำลองพิกัด (0,0) ด้วย Mock Location App
2. เปิดแอป HRMS
3. ขอรับพิกัด |
-| **Expected Result** | 1. mockIndicators = 3 (จาก invalid coordinates)
2. isMockDetected = true
3. confidence = 'medium'
4. แสดงข้อความแจ้งเตือน |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-04-04: พบ Mock Location - ข้อมูลเก่าเกินไป
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-04-04 |
-| **Test Case Name** | พบ Mock Location - ข้อมูลเก่าเกินไป |
-| **Description** | ตรวจพบข้อมูลตำแหน่งเก่าเกิน 60 วินาที |
-| **Priority** | High |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว |
-| **Test Data** | Timestamp: Date.now() - 70,000 ms |
-| **Test Steps** | 1. จำลอง timestamp เก่าเกิน 60 วินาที
2. เปิดแอป HRMS
3. ขอรับพิกัด |
-| **Expected Result** | 1. mockIndicators = 2 (จาก stale timestamp)
2. แสดง warning: "ข้อมูลตำแหน่งเก่าเกินไป กรุณารับสัญญาณ GPS ใหม่" |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-04-05: พบ Mock Location - ความเร็วผิดปกติ
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-04-05 |
-| **Test Case Name** | พบ Mock Location - ความเร็วผิดปกติ |
-| **Description** | ตรวจพบความเร็วเคลื่อนที่ผิดปกติ |
-| **Priority** | High |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว
3. มีตำแหน่งเดิมใน history |
-| **Test Data** | Speed > 100 m/s |
-| **Test Steps** | 1. จำลองการเคลื่อนที่ด้วยความเร็ว > 100 m/s
2. เปิดแอป HRMS |
-| **Expected Result** | 1. mockIndicators = 3 (จาก impossible speed)
2. isMockDetected = true
3. confidence = 'medium' |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-04-06: พบ Mock Location - หลาย indicators (Confidence High)
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-04-06 |
-| **Test Case Name** | พบ Mock Location - หลาย indicators (Confidence High) |
-| **Description** | ตรวจพบหลาย mock indicators พร้อมกัน |
-| **Priority** | High |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว |
-| **Test Data** | Combined: invalid coordinates (0,0) + stale timestamp + impossible speed |
-| **Test Steps** | 1. จำลอง multiple mock indicators
2. เปิดแอป HRMS |
-| **Expected Result** | 1. mockIndicators >= 5
2. isMockDetected = true
3. confidence = 'high'
4. แสดงข้อความแจ้งเตือนชัดเจน |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
----
-
-### TC-LOC-05: POI Resolution
-
-#### TC-LOC-05-01: แปลงพิกัดเป็นชื่อสถานที่สำเร็จ (Bangkok GIS)
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-05-01 |
-| **Test Case Name** | แปลงพิกัดเป็นชื่อสถานที่สำเร็จ (Bangkok GIS) |
-| **Description** | แปลงพิกัดเป็นชื่อสถานที่สำเร็จผ่าน Bangkok GIS API |
-| **Priority** | High |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว
3. รับพิกัดสำเร็จแล้ว |
-| **Test Data** | Latitude: 13.7563, Longitude: 100.5018 (Sanam Suea Pa, Bangkok) |
-| **Test Steps** | 1. เปิดแอป HRMS
2. รับพิกัด GPS
3. รอให้ระบบแปลงพิกัดเป็นชื่อสถานที่ |
-| **Expected Result** | 1. เรียก Bangkok GIS API: `https://bmagis.bangkok.go.th/...`
2. แสดงชื่อสถานที่ (POI name)
3. poiPlaceName มีค่า |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-05-02: แปลงพิกัดเป็นชื่อสถานที่สำเร็จ (ArcGIS Fallback)
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-05-02 |
-| **Test Case Name** | แปลงพิกัดเป็นชื่อสถานที่สำเร็จ (ArcGIS Fallback) |
-| **Description** | แปลงพิกัดเป็นชื่อสถานที่สำเร็จผ่าน ArcGIS API (เมื่อ Bangkok GIS fail) |
-| **Priority** | Medium |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว
3. Bangkok GIS API ล้มเหลว |
-| **Test Data** | Latitude: 13.7563, Longitude: 100.5018 |
-| **Test Steps** | 1. เปิดแอป HRMS
2. รับพิกัด GPS
3. จำลอง Bangkok GIS API fail
4. รอ fallback ไป ArcGIS |
-| **Expected Result** | 1. เรียก ArcGIS API: `https://geocode.arcgis.com/...`
2. แสดงชื่อสถานที่ (POI name)
3. poiPlaceName มีค่า |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-05-03: แปลงพิกัดล้มเหลว (ทั้งสอง service down)
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-05-03 |
-| **Test Case Name** | แปลงพิกัดล้มเหลว (ทั้งสอง service down) |
-| **Description** | แปลงพิกัดล้มเหลวเมื่อทั้ง Bangkok GIS และ ArcGIS down |
-| **Priority** | Low |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว |
-| **Test Data** | - |
-| **Test Steps** | 1. เปิดแอป HRMS
2. จำลองทั้งสอง API fail |
-| **Expected Result** | 1. แอปยังทำงานต่อไปได้
2. แผนที่แสดงตำแหน่งแต่ไม่มีชื่อสถานที่
3. formLocation.POI = '' |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
----
-
-### TC-LOC-06: Check-in with Location
-
-#### TC-LOC-06-01: ลงเวลาเข้าสำเร็จ - ณ สถานที่ตั้ง (In-place)
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-06-01 |
-| **Test Case Name** | ลงเวลาเข้าสำเร็จ - ณ สถานที่ตั้ง (In-place) |
-| **Description** | ลงเวลาเข้าสำเร็จเมื่ออยู่ ณ สถานที่ตั้ง |
-| **Priority** | High |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว
3. รับพิกัดและชื่อสถานที่สำเร็จ
4. ถ่ายรูปภาพแล้ว
5. statusCheckin = true (เข้างาน) |
-| **Test Data** | workplace: 'in-place', locationName: '', POI: 'สนามเสือป่า', img: (รูปถ่าย) |
-| **Test Steps** | 1. เลือก "ในสถานที่"
2. กด "ลงเวลาเข้างาน"
3. ยืนยันการลงเวลา |
-| **Expected Result** | 1. API call: POST `/leave/check-in`
2. formdata: { lat, lon, POI, isLocation: true, locationName: '', img, remark, checkInId }
3. แสดง modal ลงเวลาเข้างานสำเร็จ
4. statusCheckin = false |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-06-02: ลงเวลาเข้าสำเร็จ - นอกสถานที่ตั้ง (Off-site)
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-06-02 |
-| **Test Case Name** | ลงเวลาเข้าสำเร็จ - นอกสถานที่ตั้ง (Off-site) |
-| **Description** | ลงเวลาเข้าสำเร็จเมื่ออยู่นอกสถานที่ตั้ง |
-| **Priority** | High |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว
3. รับพิกัดและชื่อสถานที่สำเร็จ
4. ถ่ายรูปภาพแล้ว
5. statusCheckin = true (เข้างาน) |
-| **Test Data** | workplace: 'off-site', locationName: 'ปฏิบัติงานที่บ้าน (WFH)', POI: 'บ้าน', img: (รูปถ่าย) |
-| **Test Steps** | 1. เลือก "นอกสถานที่"
2. เลือก "ปฏิบัติงานที่บ้าน (WFH)"
3. กด "ลงเวลาเข้างาน"
4. ยืนยันการลงเวลา |
-| **Expected Result** | 1. API call: POST `/leave/check-in`
2. formdata: { lat, lon, POI, isLocation: false, locationName: 'ปฏิบัติงานที่บ้าน (WFH)', img, remark, checkInId }
3. แสดง modal ลงเวลาเข้างานสำเร็จ |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-06-03: ลงเวลาเข้าล้มเหลว - Mock Location detected
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-06-03 |
-| **Test Case Name** | ลงเวลาเข้าล้มเหลว - Mock Location detected |
-| **Description** | ลงเวลาเข้าล้มเหลวเมื่อตรวจพบ mock location |
-| **Priority** | High |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว
3. เปิด Mock Location App |
-| **Test Data** | Mock location enabled |
-| **Test Steps** | 1. เปิด Mock Location App
2. ตั้งค่าตำแหน่งปลอง
3. เปิดแอป HRMS
4. พยายามลงเวลาเข้างาน |
-| **Expected Result** | 1. แสดงข้อความ: "ตรวจพบว่าตำแหน่ง GPS อาจไม่ถูกต้อง..."
2. isMockLocationDetected = true
3. disabledBtn = true
4. ไม่สามารถลงเวลาเข้างานได้ |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-06-04: ลงเวลาเข้าล้มเหลว - Location permission denied
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-06-04 |
-| **Test Case Name** | ลงเวลาเข้าล้มเหลว - Location permission denied |
-| **Description** | ลงเวลาเข้าล้มเหลวเมื่อปฏิเสธ Location Permission |
-| **Priority** | High |
-| **Pre-conditions** | 1. ยอมรับ Privacy Policy แล้ว
2. ปฏิเสธ Location Permission |
-| **Test Data** | - |
-| **Test Steps** | 1. เปิดแอป HRMS
2. ปฏิเสธ Location Permission
3. พยายามลงเวลาเข้างาน |
-| **Expected Result** | 1. locationGranted = false
2. ปุ่มลงเวลาเข้า/ออก disabled
3. ไม่สามารถลงเวลาเข้างานได้ |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
----
-
-### TC-LOC-07: Check-out with Location
-
-#### TC-LOC-07-01: ลงเวลาออกสำเร็จ
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-07-01 |
-| **Test Case Name** | ลงเวลาออกสำเร็จ |
-| **Description** | ลงเวลาออกสำเร็จพร้อมตำแหน่ง |
-| **Priority** | High |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว
3. เคยลงเวลาเข้างานแล้ว
4. statusCheckin = false (ออกงาน) |
-| **Test Data** | POI: 'Office', img: (รูปถ่าย) |
-| **Test Steps** | 1. เปิดแอป HRMS
2. ถ่ายรูปภาพ
3. กด "ลงเวลาออกงาน" |
-| **Expected Result** | 1. API call: POST `/leave/check-in` (with checkInId)
2. แสดง modal ลงเวลาออกงานสำเร็จ
3. statusCheckin = true |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-07-02: ลงเวลาออกล้มเหลว - Mock Location detected
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-07-02 |
-| **Test Case Name** | ลงเวลาออกล้มเหลว - Mock Location detected |
-| **Description** | ลงเวลาออกล้มเหลวเมื่อตรวจพบ mock location |
-| **Priority** | High |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว
3. เคยลงเวลาเข้างานแล้ว
4. เปิด Mock Location App |
-| **Test Data** | Mock location enabled |
-| **Test Steps** | 1. เปิด Mock Location App
2. พยายามลงเวลาออกงาน |
-| **Expected Result** | 1. แสดงข้อความ: "ตรวจพบว่าตำแหน่ง GPS อาจไม่ถูกต้อง..."
2. disabledBtn = true
3. ไม่สามารถลงเวลาออกงานได้ |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
----
-
-### TC-LOC-08: Special Time Entry
-
-#### TC-LOC-08-01: บันทึกเวลาพิเศษพร้อมตำแหน่งสำเร็จ
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-08-01 |
-| **Test Case Name** | บันทึกเวลาพิเศษพร้อมตำแหน่งสำเร็จ |
-| **Description** | บันทึกเวลาพิเศษพร้อมตำแหน่งสำเร็จ |
-| **Priority** | Medium |
-| **Pre-conditions** | 1. อนุญาต Location Permission แล้ว
2. ยอมรับ Privacy Policy แล้ว
3. รับพิกัดสำเร็จ |
-| **Test Data** | POI: 'Meeting Room', img: (รูปถ่าย) |
-| **Test Steps** | 1. เปิดแอป HRMS
2. ไปที่หน้าบันทึกเวลาพิเศษ
3. กรอกข้อมูล
4. บันทึก |
-| **Expected Result** | 1. บันทึกเวลาพิเศษสำเร็จ
2. มีข้อมูลตำแหน่ง |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-08-02: บันทึกเวลาพิเศษล้มเหลว - ไม่ระบุตำแหน่ง
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-08-02 |
-| **Test Case Name** | บันทึกเวลาพิเศษล้มเหลว - ไม่ระบุตำแหน่ง |
-| **Description** | บันทึกเวลาพิเศษล้มเหลวเมื่อไม่ระบุตำแหน่ง |
-| **Priority** | Medium |
-| **Pre-conditions** | 1. ยอมรับ Privacy Policy แล้ว
2. ปฏิเสธ Location Permission |
-| **Test Data** | - |
-| **Test Steps** | 1. เปิดแอป HRMS
2. ปฏิเสธ Location Permission
3. พยายามบันทึกเวลาพิเศษ |
-| **Expected Result** | 1. แจ้งเตือนให้ระบุตำแหน่ง |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
----
-
-### TC-LOC-09: Privacy Consent
-
-#### TC-LOC-09-01: แสดง Privacy Modal ก่อนใช้ Location
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-09-01 |
-| **Test Case Name** | แสดง Privacy Modal ก่อนใช้ Location |
-| **Description** | แสดง Privacy Modal ก่อนใช้ Location |
-| **Priority** | High |
-| **Pre-conditions** | 1. เปิดแอปครั้งแรก
2. ยังไม่ยอมรับ Privacy Policy |
-| **Test Data** | - |
-| **Test Steps** | 1. เปิดแอป HRMS
2. กดปุ่มที่ต้องการ Location (แผนที่/กล้อง) |
-| **Expected Result** | 1. privacyStore.modalPrivacy = true
2. แสดง Privacy Modal
3. ไม่สามารถใช้งาน Location ได้จนกว่าจะยอมรับ |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-09-02: ยอมรับ Privacy Policy
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-09-02 |
-| **Test Case Name** | ยอมรับ Privacy Policy |
-| **Description** | ยอมรับ Privacy Policy และใช้งาน Location ได้ |
-| **Priority** | High |
-| **Pre-conditions** | 1. เปิดแอปครั้งแรก
2. Privacy Modal แสดงขึ้นมา |
-| **Test Data** | - |
-| **Test Steps** | 1. เปิดแอป HRMS
2. แสดง Privacy Modal
3. กด "ยอมรับ" |
-| **Expected Result** | 1. privacyStore.isAccepted = true
2. privacyStore.modalPrivacy = false
3. สามารถใช้งาน Location ได้
4. เรียก mapRef.value?.requestLocationPermission() |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
-#### TC-LOC-09-03: ปฏิเสธ Privacy Policy
-
-| Field | Value |
-|-------|-------|
-| **Test Case ID** | TC-LOC-09-03 |
-| **Test Case Name** | ปฏิเสธ Privacy Policy |
-| **Description** | ปฏิเสธ Privacy Policy และไม่สามารถใช้งาน Location |
-| **Priority** | High |
-| **Pre-conditions** | 1. เปิดแอปครั้งแรก
2. Privacy Modal แสดงขึ้นมา |
-| **Test Data** | - |
-| **Test Steps** | 1. เปิดแอป HRMS
2. แสดง Privacy Modal
3. กด "ปฏิเสธ" หรือปิด Modal |
-| **Expected Result** | 1. privacyStore.isAccepted = false
2. privacyStore.modalPrivacy = false
3. ไม่สามารถใช้งาน Location ได้
4. แจ้งเตือนเมื่อพยายามใช้งาน Location |
-| **Actual Result** | |
-| **Status** | Not Run |
-| **Tested By** | |
-| **Test Date** | |
-
----
-
-## 6. Test Summary
-
-### 6.1 Test Case Statistics
-
-| Category | Total | Critical | High | Medium | Low |
-|----------|-------|----------|------|--------|-----|
-| Location Permission | 2 | - | 2 | - | - |
-| Location Acquisition | 4 | - | 2 | 2 | - |
-| Location Validation | 6 | - | 4 | 2 | - |
-| Mock Location Detection | 6 | - | 6 | - | - |
-| POI Resolution | 3 | - | 1 | 1 | 1 |
-| Check-in with Location | 4 | - | 4 | - | - |
-| Check-out with Location | 2 | - | 2 | - | - |
-| Special Time Entry | 2 | - | - | 2 | - |
-| Privacy Consent | 3 | - | 3 | - | - |
-| **Total** | **32** | - | **24** | **7** | **1** |
-
-### 6.2 Execution Status
-
-| Status | Count | Percentage |
-|--------|-------|------------|
-| Not Run | 32 | 100% |
-| Pass | 0 | 0% |
-| Fail | 0 | 0% |
-| Blocked | 0 | 0% |
-
----
-
-## 7. References
-
-### 7.1 API Endpoints
-
-| Endpoint | Method | Description |
-|----------|--------|-------------|
-| `/leave/check-in` | POST | Check-in/Check-out with location data |
-| `/leave/check-time` | GET | Get check-in/check-out status |
-| `/leave/check-status` | GET | Get queue status |
-| `/leave/user/checkout-check/{isSeminar}` | GET | Check before check-out |
-
-### 7.2 Key Functions
-
-| Function | Location | Description |
-|----------|----------|-------------|
-| `validateLocation()` | `useLocationValidation.ts` | Main validation function |
-| `validateCoordinates()` | `useLocationValidation.ts` | Coordinate validation |
-| `validateTimestamp()` | `useLocationValidation.ts` | Timestamp validation |
-| `validateAccuracy()` | `useLocationValidation.ts` | Accuracy validation |
-| `validateSpeed()` | `useLocationValidation.ts` | Speed validation |
-| `haversineDistance()` | `useLocationValidation.ts` | Distance calculation |
-| `checkPrivacyAccepted()` | `usePermissions.ts` | Check privacy consent |
-| `requestLocationPermission()` | `AscGISMap.vue` | Request location permission |
-
-### 7.3 Related Files
-
-- `/src/composables/useLocationValidation.ts` - Location validation logic
-- `/src/views/HomeView.vue` - Check-in/Check-out page
-- `/src/components/AscGISMap.vue` - Map component with location validation
-- `/src/api/api.checkin.ts` - Check-in API endpoints
-- `/src/stores/privacy.ts` - Privacy store for consent management
-- `/src/composables/usePermissions.ts` - Permission checking utilities
-
----
-
-## 8. Revision History
-
-| Version | Date | Author | Changes |
-|---------|------|--------|---------|
-| 1.0.0 | 2025-03-09 | QA Team | Initial version - 32 test cases for Location features |
-
----
-
-## 9. Approval
-
-| Role | Name | Signature | Date |
-|------|------|-----------|------|
-| QA Lead | | | |
-| Developer | | | |
-| Product Owner | | | |
diff --git a/docs/test-steps-location.md b/docs/test-steps-location.md
deleted file mode 100644
index f62a2fa..0000000
--- a/docs/test-steps-location.md
+++ /dev/null
@@ -1,1026 +0,0 @@
-# Test Steps: Location Features
-# HRMS Check-in/Check-out System
-
-## Document Information
-
-| Field | Value |
-|-------|-------|
-| **Document Version** | 1.0.0 |
-| **Last Updated** | 2025-03-09 |
-| **Project** | HRMS Check-in/Check-out |
-| **Module** | Location Features |
-| **Author** | QA Team |
-
----
-
-## Table of Contents
-
-1. [Overview](#1-overview)
-2. [Test Environment Setup](#2-test-environment-setup)
-3. [Manual Testing Procedures](#3-manual-testing-procedures)
-4. [Mock Location Testing](#4-mock-location-testing)
-5. [Developer Tools Testing](#5-developer-tools-testing)
-6. [Device-Specific Testing](#6-device-specific-testing)
-7. [Test Data & Scenarios](#7-test-data--scenarios)
-8. [Troubleshooting](#8-troubleshooting)
-
----
-
-## 1. Overview
-
-เอกสารนี้ประกอบด้วยขั้นตอนการทดสอบ (Test Steps) แบบละเอียดสำหรับการทดสอบฟีเจอร์ Location ของระบบ HRMS Check-in/Check-out โดยเน้นการทดสอบแบบ Manual Testing และการจำลองสถานการณ์ต่าง ๆ
-
-### 1.1 Testing Scope
-
-| Area | Description |
-|------|-------------|
-| **Location Permission** | การขอและการจัดการสิทธิ์การเข้าถึงตำแหน่ง |
-| **Location Acquisition** | การรับพิกัด GPS จากอุปกรณ์ |
-| **Location Validation** | การตรวจสอบความถูกต้องของตำแหน่ง |
-| **Mock Detection** | การตรวจจับการจำลองตำแหน่ง |
-| **POI Resolution** | การแปลงพิกัดเป็นชื่อสถานที่ |
-| **Check-in/Check-out** | การลงเวลาเข้า-ออกงานพร้อมตำแหน่ง |
-| **Privacy Consent** | การขอความยินยอมการใช้ข้อมูล |
-
----
-
-## 2. Test Environment Setup
-
-### 2.1 Device Preparation
-
-#### 2.1.1 Android Device Setup
-
-1. **Enable Developer Options**
- - เปิด Settings > About Phone
- - แตะที่ Build Number 7 ครั้ง
- - กลับมาที่ Settings > Developer Options
-
-2. **Enable Mock Location (สำหรับการทดสอบ)**
- - เปิด Settings > Developer Options
- - เลือก "Select mock location app"
- - เลือก Fake GPS Location App ที่ติดตั้ง
-
-3. **Install Fake GPS Apps**
- - Fake GPS Location (by Lexa) - [Google Play](https://play.google.com/store/apps/details?id=com.lexa.fakegps)
- - GPS Joystick - [Google Play](https://play.google.com/store/apps/details?id=com.theappninjas.android.gpsjoke)
-
-#### 2.1.2 iOS Device Setup
-
-1. **Xcode Simulation (ต้องใช้ Mac)**
- - เปิด Xcode
- - เลือก Device > Debug Location > Custom Location...
- - ป้อน Latitude และ Longitude
-
-2. **Location Changer Apps**
- - Location Changer - [App Store](https://apps.apple.com/app/location-changer/id1383869695)
-
-#### 2.1.3 Desktop Browser Setup
-
-1. **Chrome DevTools**
- - กด F12 หรือ Cmd+Option+I (Mac) / Ctrl+Shift+I (Windows)
- - เลือก More tools > Sensors
- - ตั้งค่า Location ใน Sensors tab
-
-2. **Edge DevTools**
- - กด F12
- - เลือก More tools > Sensors
- - ตั้งค่า Location ใน Sensors tab
-
-### 2.2 Network Setup
-
-| Scenario | Setup |
-|----------|-------|
-| **Normal Network** | WiFi/4G/5G เชื่อมต่อปกติ |
-| **Slow Network** | Chrome DevTools > Network > Throttling > Slow 3G |
-| **Offline** | Chrome DevTools > Network > Offline |
-| **GPS Blocking** | Disable Location Service ในระบบ |
-
-### 2.3 Test Location Data
-
-| Location Name | Latitude | Longitude | Notes |
-|---------------|----------|-----------|-------|
-| Sanam Suea Pa (สนามเสือป่า) | 13.7563 | 100.5018 | Bangkok Government Office |
-| Suvarnabhumi Airport | 13.6900 | 100.7501 | Outdoor, high GPS accuracy |
-| Central World | 13.7944 | 100.5439 | Indoor shopping mall |
-| Null Island | 0 | 0 | Default mock location (invalid) |
-| Out of Range | 91 | 181 | Invalid coordinates |
-| Chiang Mai | 18.7883 | 98.9853 | Northern Thailand |
-
----
-
-## 3. Manual Testing Procedures
-
-### 3.1 TC-LOC-01: Location Permission Testing
-
-#### Test Case: TC-LOC-01-01 - อนุญาตให้เข้าถึงตำแหน่ง
-
-**Objective**: ทดสอบการอนุญาตให้เข้าถึงตำแหน่ง
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | เปิดแอป HRMS ครั้งแรก | แสดงหน้าแรกของแอป | [ ] |
-| 2 | แสดง Privacy Modal (ถ้ายังไม่ยอมรับ) | แสดง Privacy Policy Modal | [ ] |
-| 3 | กด "ยอมรับ" เพื่อยอมรับ Privacy Policy | Modal ปิด, `isAccepted = true` | [ ] |
-| 4 | กดปุ่มขอตำแหน่ง (mapRef.requestLocationPermission()) | ระบบขอ Location Permission | [ ] |
-| 5 | เลือก "Allow" เมื่อระบบขอ Location Permission | รับสิทธิ์สำเร็จ | [ ] |
-| 6 | รอรับพิกัด GPS | แสดง Skeleton loading | [ ] |
-| 7 | รับพิกัดสำเร็จ | แสดงแผนที่พร้อมตำแหน่งปัจจุบัน | [ ] |
-| 8 | ตรวจสอบชื่อสถานที่ใกล้เคียง | แสดงชื่อสถานที่ (POI) | [ ] |
-| 9 | ตรวจสอบสถานะปุ่มลงเวลา | ปุ่มใช้งานได้ (disabled = false) | [ ] |
-
-**Verification Checklist:**
-
-- [ ] แผนที่แสดงตำแหน่งปัจจุบันถูกต้อง
-- [ ] ชื่อสถานที่ใกล้เคียงแสดงถูกต้อง
-- [ ] `locationGranted = true`
-- [ ] ปุ่มลงเวลาเข้า/ออกใช้งานได้
-- [ ] ไม่มีข้อความ error แสดงขึ้น
-
----
-
-#### Test Case: TC-LOC-01-02 - ปฏิเสธการเข้าถึงตำแหน่ง
-
-**Objective**: ทดสอบการปฏิเสธการเข้าถึงตำแหน่ง
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | เปิดแอป HRMS ครั้งแรก | แสดงหน้าแรกของแอป | [ ] |
-| 2 | ยอมรับ Privacy Policy | Modal ปิด | [ ] |
-| 3 | กดปุ่มขอตำแหน่ง | ระบบขอ Location Permission | [ ] |
-| 4 | เลือก "Deny" เมื่อระบบขอ Location Permission | ปฏิเสธสิทธิ์ | [ ] |
-| 5 | ตรวจสอบ error message | แสดง "ไม่สามารถระบุตำแหน่งปัจจุบันได้ เนื่องจากคุณปฏิเสธการเข้าถึงตำแหน่ง กรุณาเปิดการเข้าถึงตำแหน่ง" | [ ] |
-| 6 | ตรวจสอบแผนที่ | แผนที่ไม่แสดง (poiPlaceName = '') | [ ] |
-| 7 | ตรวจสอบปุ่มลงเวลา | ปุ่มใช้งานไม่ได้ (disabled = true) | [ ] |
-
-**Verification Checklist:**
-
-- [ ] แสดง error message ถูกต้อง
-- [ ] แผนที่ไม่แสดง
-- [ ] `locationGranted = false`
-- [ ] ปุ่มลงเวลาเข้า/ออก disabled
-- [ ] ไม่สามารถลงเวลาได้
-
----
-
-### 3.2 TC-LOC-02: Location Acquisition Testing
-
-#### Test Case: TC-LOC-02-01 - รับพิกัด GPS สำเร็จ (Outdoor)
-
-**Objective**: ทดสอบการรับพิกัด GPS สำเร็จในพื้นที่เปิดกว้าง
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | ไปยังพื้นที่เปิดกว้าง (Outdoor) | อยู่กลางแจ้ง มีสัญญาณ GPS ดี | [ ] |
-| 2 | เปิดแอป HRMS | แสดงหน้าแรก | [ ] |
-| 3 | อนุญาต Location Permission | รับสิทธิ์สำเร็จ | [ ] |
-| 4 | กดปุ่มขอตำแหน่ง | เริ่มรับพิกัด GPS | [ ] |
-| 5 | รอรับพิกัด (ประมาณ 3-5 วินาที) | แสดง Skeleton loading | [ ] |
-| 6 | รับพิกัดสำเร็จ | แสดงแผนที่ | [ ] |
-| 7 | ตรวจสอบพิกัด | Latitude และ Longitude ถูกต้อง | [ ] |
-| 8 | ตรวจสอบความแม่นยำ | Accuracy <= 100 meters | [ ] |
-| 9 | ตรวจสอบ timestamp | Timestamp ภายใน 60 วินาที | [ ] |
-| 10 | ตรวจสอบชื่อสถานที่ | แสดงชื่อสถานที่ใกล้เคียง | [ ] |
-
-**Verification Checklist:**
-
-- [ ] รับพิกัดสำเร็จ
-- [ ] พิกัดอยู่ในช่วงที่ถูกต้อง (lat: -90 to 90, lon: -180 to 180)
-- [ ] ไม่ใช่ (0, 0)
-- [ ] Accuracy <= 100 meters
-- [ ] Timestamp ภายใน 60 วินาที
-- [ ] ชื่อสถานที่ใกล้เคียงแสดงถูกต้อง
-
----
-
-#### Test Case: TC-LOC-02-02 - รับพิกัด GPS สำเร็จ (Indoor)
-
-**Objective**: ทดสอบการรับพิกัด GPS สำเร็จในพื้นที่ปิด
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | ไปยังพื้นที่ปิด (Indoor) | อยู่ภายในอาคาร | [ ] |
-| 2 | เปิดแอป HRMS | แสดงหน้าแรก | [ ] |
-| 3 | อนุญาต Location Permission | รับสิทธิ์สำเร็จ | [ ] |
-| 4 | กดปุ่มขอตำแหน่ง | เริ่มรับพิกัด GPS | [ ] |
-| 5 | รอรับพิกัด (อาจนานกว่าปกติ) | แสดง Skeleton loading | [ ] |
-| 6 | รับพิกัดสำเร็จ | แสดงแผนที่ | [ ] |
-| 7 | ตรวจสอบพิกัด | Latitude และ Longitude ถูกต้อง | [ ] |
-| 8 | ตรวจสอบความแม่นยำ | อาจมีความแม่นยำต่ำกว่า Outdoor | [ ] |
-
-**Verification Checklist:**
-
-- [ ] รับพิกัดสำเร็จ
-- [ ] อาจมีความแม่นยำต่ำกว่า Outdoor
-- [ ] แผนที่แสดงถูกต้อง
-
----
-
-#### Test Case: TC-LOC-02-03 - รับพิกัด GPS ล้มเหลว (GPS ไม่ทำงาน)
-
-**Objective**: ทดสอบกรณี GPS ไม่ทำงาน
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | เปิดแอป HRMS | แสดงหน้าแรก | [ ] |
-| 2 | อนุญาต Location Permission | รับสิทธิ์สำเร็จ | [ ] |
-| 3 | ไปที่ Settings > Location > ปิด GPS | GPS ปิดอยู่ | [ ] |
-| 4 | กดปุ่มขอตำแหน่ง | เริ่มรับพิกัด GPS | [ ] |
-| 5 | ตรวจสอบ error message | แสดง "ไม่สามารถระบุตำแหน่งปัจจุบันได้" | [ ] |
-| 6 | ตรวจสอบแผนที่ | แผนที่ไม่แสดง | [ ] |
-
-**Verification Checklist:**
-
-- [ ] แสดง error message ถูกต้อง
-- [ ] แผนที่ไม่แสดง
-- [ ] ไม่สามารถรับพิกัดได้
-
----
-
-### 3.3 TC-LOC-03: Location Validation Testing
-
-#### Test Case: TC-LOC-03-01 - พิกัดถูกต้อง (Valid coordinates)
-
-**Objective**: ทดสอบการตรวจสอบพิกัดที่ถูกต้อง
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | เปิดแอป HRMS | แสดงหน้าแรก | [ ] |
-| 2 | อนุญาต Location Permission | รับสิทธิ์สำเร็จ | [ ] |
-| 3 | รับพิกัด GPS ปกติ | พิกัดถูกต้อง (เช่น 13.7563, 100.5018) | [ ] |
-| 4 | เปิด Console และตรวจสอบ validationResult | { isValid: true, isMockDetected: false } | [ ] |
-| 5 | ตรวจสอบ locationGranted | locationGranted = true | [ ] |
-| 6 | ตรวจสอบปุ่มลงเวลา | ปุ่มใช้งานได้ | [ ] |
-
-**Verification Checklist:**
-
-- [ ] `validationResult.isValid = true`
-- [ ] `validationResult.isMockDetected = false`
-- [ ] `validationResult.errors = []`
-- [ ] `locationGranted = true`
-- [ ] ไม่มี error/warning message
-
----
-
-#### Test Case: TC-LOC-03-02 - พิกัดไม่ถูกต้อง (Invalid coordinates - (0,0))
-
-**Objective**: ทดสอบการตรวจสอบพิกัด (0,0)
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | เปิด Fake GPS App | เปิดแอป | [ ] |
-| 2 | ตั้งค่าพิกัด (0, 0) | Latitude: 0, Longitude: 0 | [ ] |
-| 3 | เปิดแอป HRMS | แสดงหน้าแรก | [ ] |
-| 4 | กดปุ่มขอตำแหน่ง | เริ่มรับพิกัด GPS | [ ] |
-| 5 | ตรวจสอบ Console (validateCoordinates) | validateCoordinates(0, 0) = false | [ ] |
-| 6 | ตรวจสอบ validationResult | { isValid: false, isMockDetected: true, confidence: 'high' } | [ ] |
-| 7 | ตรวจสอบ error message | "พิกัดตำแหน่งไม่ถูกต้อง กรุณาลองใหม่" | [ ] |
-| 8 | ตรวจสอบ disabledBtn | disabledBtn = true | [ ] |
-
-**Verification Checklist:**
-
-- [ ] `validateCoordinates(0, 0) = false`
-- [ ] `mockIndicators = 3`
-- [ ] `isMockDetected = true`
-- [ ] `confidence = 'high'`
-- [ ] แสดง error message ถูกต้อง
-- [ ] ปุ่มลงเวลา disabled
-
----
-
-#### Test Case: TC-LOC-03-04 - ข้อมูลตำแหน่งเก่าเกินไป (Stale timestamp)
-
-**Objective**: ทดสอบการตรวจสอบ timestamp เก่าเกิน 60 วินาที
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | เปิด Chrome DevTools | F12 > Sensors | [ ] |
-| 2 | เลือก Custom Location | กำหนดพิกัด | [ ] |
-| 3 | เปิด Console และเขียน script | จำลอง timestamp เก่า | [ ] |
-| 4 | Run script: | | |
-| ```javascript | | |
-| const stalePosition = { | | |
-| coords: { latitude: 13.7563, longitude: 100.5018, accuracy: 10 }, | | |
-| timestamp: Date.now() - 70000 // 70 seconds ago | | |
-| } | | |
-| navigator.geolocation.getCurrentPosition(pos => console.log(pos)) | | |
-| ``` | | |
-| 5 | รีเฟรชแอป | รับพิกัด | [ ] |
-| 6 | ตรวจสอบ validationResult | mockIndicators += 2 | [ ] |
-| 7 | ตรวจสอบ error message | "ข้อมูลตำแหน่งเก่าเกินไป กรุณารับสัญญาณ GPS ใหม่" | [ ] |
-
-**Verification Checklist:**
-
-- [ ] `validateTimestamp(staleTimestamp) = false`
-- [ ] `mockIndicators += 2`
-- [ ] แสดง error message ถูกต้อง
-
----
-
-#### Test Case: TC-LOC-03-05 - ความแม่นยำต่ำ (Poor accuracy)
-
-**Objective**: ทดสอบการตรวจสอบความแม่นยำต่ำ
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | เปิด Chrome DevTools | F12 > Sensors | [ ] |
-| 2 | เลือก Custom Location | กำหนดพิกัด | [ ] |
-| 3 | เปิด Console และเขียน script | จำลอง accuracy ต่ำ | [ ] |
-| 4 | Run script: | | |
-| ```javascript | | |
-| const poorAccuracyPosition = { | | |
-| coords: { latitude: 13.7563, longitude: 100.5018, accuracy: 150 }, | | |
-| timestamp: Date.now() | | |
-| } | | |
-| ``` | | |
-| 5 | รีเฟรชแอป | รับพิกัด | [ ] |
-| 6 | ตรวจสอบ validationResult | warnings = ["ความแม่นยำตำแหน่งต่ำเกินไป..."] | [ ] |
-| 7 | ตรวจสอบ mockIndicators | mockIndicators += 1 | [ ] |
-
-**Verification Checklist:**
-
-- [ ] `validateAccuracy(150) = false`
-- [ ] `mockIndicators += 1`
-- [ ] แสดง warning message ถูกต้อง
-
----
-
-#### Test Case: TC-LOC-03-06 - ความเร็วเคลื่อนที่ผิดปกติ (Impossible speed)
-
-**Objective**: ทดสอบการตรวจสอบความเร็วเคลื่อนที่ผิดปกติ
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | เปิดแอป HRMS ที่ตำแหน่ง A (13.7563, 100.5018) | รับพิกัด A | [ ] |
-| 2 | รอสักครู่เพื่อให้บันทึกตำแหน่ง A ลง history | previousPositions มีค่า | [ ] |
-| 3 | เปิด Fake GPS App | เปิดแอป | [ ] |
-| 4 | ตั้งค่าพิกัด B (15.8700, 100.9925) ที่ห่างกัน ~180 กม. | พิกัด B | [ ] |
-| 5 | รีเฟรชแอปทันที (ภายใน 60 วินาที) | รับพิกัด B | [ ] |
-| 6 | ตรวจสอบ Console (calculateSpeed) | speed = ~3000 km/h (> 100 m/s) | [ ] |
-| 7 | ตรวจสอบ validationResult | mockIndicators += 3 | [ ] |
-| 8 | ตรวจสอบ error message | "ตรวจพบการเคลื่อนที่ด้วยความเร็วผิดปกติ..." | [ ] |
-
-**Verification Checklist:**
-
-- [ ] `calculateSpeed()` = ความเร็ว > 100 m/s
-- [ ] `mockIndicators += 3`
-- [ ] แสดง error message ถูกต้อง
-
----
-
-### 3.4 TC-LOC-04: Mock Location Detection Testing
-
-#### Test Case: TC-LOC-04-01 - ไม่พบ Mock Location (Normal GPS)
-
-**Objective**: ทดสอบการใช้งาน GPS ปกติ ไม่พบ mock location
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | ตรวจสอบว่าปิด Fake GPS App | ไม่มี Fake GPS App ทำงาน | [ ] |
-| 2 | เปิดแอป HRMS | แสดงหน้าแรก | [ ] |
-| 3 | อนุญาต Location Permission | รับสิทธิ์สำเร็จ | [ ] |
-| 4 | กดปุ่มขอตำแหน่ง | เริ่มรับพิกัด GPS | [ ] |
-| 5 | รับพิกัดสำเร็จ | แสดงแผนที่ | [ ] |
-| 6 | ตรวจสอบ Console (validationResult) | { isValid: true, isMockDetected: false } | [ ] |
-| 7 | ตรวจสอบ locationGranted | locationGranted = true | [ ] |
-| 8 | ตรวจสอบ isMockLocationDetected | isMockLocationDetected = false | [ ] |
-| 9 | ตรวจสอบปุ่มลงเวลา | ปุ่มใช้งานได้ | [ ] |
-
-**Verification Checklist:**
-
-- [ ] `isValid = true`
-- [ ] `isMockDetected = false`
-- [ ] `locationGranted = true`
-- [ ] `isMockLocationDetected = false`
-- [ ] ปุ่มลงเวลาใช้งานได้
-
----
-
-#### Test Case: TC-LOC-04-02 - พบ Mock Location - Fake GPS App
-
-**Objective**: ทดสอบการตรวจจับ mock location จาก Fake GPS App
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | เปิด Fake GPS Location App | เปิดแอป | [ ] |
-| 2 | ตั้งค่าตำแหน่งปลอง (เช่น 13.7563, 100.5018) | ตั้งค่าสำเร็จ | [ ] |
-| 3 | เปิด Developer Options > Select mock location app | เลือก Fake GPS App | [ ] |
-| 4 | เปิดแอป HRMS | แสดงหน้าแรก | [ ] |
-| 5 | กดปุ่มขอตำแหน่ง | เริ่มรับพิกัด GPS | [ ] |
-| 6 | รับพิกัดจาก Fake GPS | แสดงพิกัดปลอง | [ ] |
-| 7 | ตรวจสอบ Console (validationResult) | isMockDetected = true | [ ] |
-| 8 | ตรวจสอบ mockIndicators | mockIndicators >= 3 | [ ] |
-| 9 | ตรวจสอบ error message | "ตรวจพบว่าตำแหน่ง GPS อาจไม่ถูกต้อง..." | [ ] |
-| 10 | ตรวจสอบ isMockLocationDetected | isMockLocationDetected = true | [ ] |
-| 11 | ตรวจสอบ disabledBtn | disabledBtn = true | [ ] |
-| 12 | พยายามกดปุ่มลงเวลา | กดไม่ได้ | [ ] |
-
-**Verification Checklist:**
-
-- [ ] `isMockDetected = true`
-- [ ] `mockIndicators >= 3`
-- [ ] แสดง error message ถูกต้อง
-- [ ] `isMockLocationDetected = true`
-- [ ] `disabledBtn = true`
-- [ ] ไม่สามารถลงเวลาได้
-
----
-
-### 3.5 TC-LOC-05: POI Resolution Testing
-
-#### Test Case: TC-LOC-05-01 - แปลงพิกัดเป็นชื่อสถานที่สำเร็จ (Bangkok GIS)
-
-**Objective**: ทดสอบการแปลงพิกัดเป็นชื่อสถานที่สำเร็จผ่าน Bangkok GIS API
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | เปิดแอป HRMS | แสดงหน้าแรก | [ ] |
-| 2 | อนุญาต Location Permission | รับสิทธิ์สำเร็จ | [ ] |
-| 3 | กดปุ่มขอตำแหน่ง | เริ่มรับพิกัด GPS | [ ] |
-| 4 | รับพิกัดสำเร็จ | แสดงแผนที่ | [ ] |
-| 5 | เปิด Network Tab (DevTools) | แสดง requests | [ ] |
-| 6 | ตรวจสอบ request ไป Bangkok GIS | GET `https://bmagis.bangkok.go.th/...` | [ ] |
-| 7 | ตรวจสอบ response | status = 200, มี PlaceName | [ ] |
-| 8 | ตรวจสอบ poiPlaceName | poiPlaceName มีค่า | [ ] |
-| 9 | ตรวจสอบ formLocation.POI | formLocation.POI มีค่า | [ ] |
-| 10 | ตรวจสอบหน้าจอ | แสดงชื่อสถานที่ใกล้เคียง | [ ] |
-
-**Verification Checklist:**
-
-- [ ] เรียก Bangkok GIS API สำเร็จ
-- [ ] Response มี PlaceName
-- [ ] `poiPlaceName` มีค่า
-- [ ] แสดงชื่อสถานที่ใกล้เคียงถูกต้อง
-
----
-
-#### Test Case: TC-LOC-05-02 - แปลงพิกัดเป็นชื่อสถานที่สำเร็จ (ArcGIS Fallback)
-
-**Objective**: ทดสอบการแปลงพิกัดเป็นชื่อสถานที่สำเร็จผ่าน ArcGIS API (เมื่อ Bangkok GIS fail)
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | เปิด Chrome DevTools > Network Tab | แสดง requests | [ ] |
-| 2 | เปิด Throttling > Offline | จำลอง Bangkok GIS down | [ ] |
-| 3 | เปิดแอป HRMS | แสดงหน้าแรก | [ ] |
-| 4 | อนุญาต Location Permission | รับสิทธิ์สำเร็จ | [ ] |
-| 5 | กดปุ่มขอตำแหน่ง | เริ่มรับพิกัด GPS | [ ] |
-| 6 | รับพิกัดสำเร็จ | แสดงแผนที่ | [ ] |
-| 7 | ปิด Throttling (กลับสู่ Online) | Bangkok GIS ยังไม่พร้อม | [ ] |
-| 8 | ตรวจสอบ Console | Bangkok GIS API fail | [ ] |
-| 9 | ตรวจสอบ fallback | เรียก ArcGIS API | [ ] |
-| 10 | ตรวจสอบ response | GET `https://geocode.arcgis.com/...` | [ ] |
-| 11 | ตรวจสอบ poiPlaceName | poiPlaceName มีค่า | [ ] |
-
-**Verification Checklist:**
-
-- [ ] Bangkok GIS API fail
-- [ ] Fallback ไป ArcGIS API สำเร็จ
-- [ ] `poiPlaceName` มีค่า
-- [ ] แสดงชื่อสถานที่ใกล้เคียง
-
----
-
-### 3.6 TC-LOC-06: Check-in with Location Testing
-
-#### Test Case: TC-LOC-06-01 - ลงเวลาเข้าสำเร็จ - ณ สถานที่ตั้ง (In-place)
-
-**Objective**: ทดสอบการลงเวลาเข้าสำเร็จเมื่ออยู่ ณ สถานที่ตั้ง
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | เปิดแอป HRMS | แสดงหน้าแรก | [ ] |
-| 2 | ตรวจสอบ statusCheckin | statusCheckin = true (เข้างาน) | [ ] |
-| 3 | อนุญาต Location Permission | รับสิทธิ์สำเร็จ | [ ] |
-| 4 | กดปุ่มขอตำแหน่ง | รับพิกัดสำเร็จ | [ ] |
-| 5 | ถ่ายรูปภาพ | แสดงรูปที่ถ่าย | [ ] |
-| 6 | เลือก "ในสถานที่" | workplace = 'in-place' | [ ] |
-| 7 | กด "ลงเวลาเข้างาน" | แสดง dialog ยืนยัน | [ ] |
-| 8 | ตรวจสอบ dialog message | "ยืนยันการลงเวลาเข้างาน ในสถานที่..." | [ ] |
-| 9 | กด "ยืนยัน" | ส่งข้อมูลไป API | [ ] |
-| 10 | เปิด Network Tab | POST `/leave/check-in` | [ ] |
-| 11 | ตรวจสอบ payload | { lat, lon, POI, isLocation: true, locationName: '', img, remark } | [ ] |
-| 12 | ตรวจสอบ response | status = 200 | [ ] |
-| 13 | ตรวจสอบ modal | แสดง modal ลงเวลาเข้างานสำเร็จ | [ ] |
-| 14 | ตรวจสอบ statusCheckin | statusCheckin = false | [ ] |
-
-**Verification Checklist:**
-
-- [ ] API call ถูกต้อง
-- [ ] Payload ถูกต้อง (isLocation: true, locationName: '')
-- [ ] Response status 200
-- [ ] แสดง modal สำเร็จ
-- [ ] `statusCheckin = false`
-
----
-
-#### Test Case: TC-LOC-06-02 - ลงเวลาเข้าสำเร็จ - นอกสถานที่ตั้ง (Off-site)
-
-**Objective**: ทดสอบการลงเวลาเข้าสำเร็จเมื่ออยู่นอกสถานที่ตั้ง
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | เปิดแอป HRMS | แสดงหน้าแรก | [ ] |
-| 2 | ตรวจสอบ statusCheckin | statusCheckin = true (เข้างาน) | [ ] |
-| 3 | อนุญาต Location Permission | รับสิทธิ์สำเร็จ | [ ] |
-| 4 | กดปุ่มขอตำแหน่ง | รับพิกัดสำเร็จ | [ ] |
-| 5 | ถ่ายรูปภาพ | แสดงรูปที่ถ่าย | [ ] |
-| 6 | เลือก "นอกสถานที่" | workplace = 'off-site' | [ ] |
-| 7 | เลือก "ปฏิบัติงานที่บ้าน (WFH)" | model = 'ปฏิบัติงานที่บ้าน (WFH)' | [ ] |
-| 8 | ตรวจสอบ useLocation | useLocation = 'ปฏิบัติงานที่บ้าน (WFH)' | [ ] |
-| 9 | กด "ลงเวลาเข้างาน" | แสดง dialog ยืนยัน | [ ] |
-| 10 | ตรวจสอบ dialog message | "ยืนยันการลงเวลาเข้างาน นอกสถานที่ (ปฏิบัติงานที่บ้าน (WFH))..." | [ ] |
-| 11 | กด "ยืนยัน" | ส่งข้อมูลไป API | [ ] |
-| 12 | เปิด Network Tab | POST `/leave/check-in` | [ ] |
-| 13 | ตรวจสอบ payload | { lat, lon, POI, isLocation: false, locationName: 'ปฏิบัติงานที่บ้าน (WFH)', img, remark } | [ ] |
-| 14 | ตรวจสอบ response | status = 200 | [ ] |
-
-**Verification Checklist:**
-
-- [ ] API call ถูกต้อง
-- [ ] Payload ถูกต้อง (isLocation: false, locationName มีค่า)
-- [ ] Response status 200
-- [ ] แสดง modal สำเร็จ
-
----
-
-#### Test Case: TC-LOC-06-03 - ลงเวลาเข้าล้มเหลว - Mock Location detected
-
-**Objective**: ทดสอบการลงเวลาเข้าล้มเหลวเมื่อตรวจพบ mock location
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | เปิด Fake GPS Location App | เปิดแอป | [ ] |
-| 2 | ตั้งค่าตำแหน่งปลอง | ตั้งค่าสำเร็จ | [ ] |
-| 3 | เปิดแอป HRMS | แสดงหน้าแรก | [ ] |
-| 4 | อนุญาต Location Permission | รับสิทธิ์สำเร็จ | [ ] |
-| 5 | กดปุ่มขอตำแหน่ง | เริ่มรับพิกัด GPS | [ ] |
-| 6 | รับพิกัดจาก Fake GPS | ตรวจจับ mock location | [ ] |
-| 7 | ตรวจสอบ error message | "ตรวจพบว่าตำแหน่ง GPS อาจไม่ถูกต้อง..." | [ ] |
-| 8 | ตรวจสอบ isMockLocationDetected | isMockLocationDetected = true | [ ] |
-| 9 | ตรวจสอบ disabledBtn | disabledBtn = true | [ ] |
-| 10 | ถ่ายรูปภาพ | แสดงรูปที่ถ่าย | [ ] |
-| 11 | พยายามกด "ลงเวลาเข้างาน" | กดไม่ได้ (ปุ่ม disabled) | [ ] |
-
-**Verification Checklist:**
-
-- [ ] ตรวจจับ mock location สำเร็จ
-- [ ] `isMockLocationDetected = true`
-- [ ] `disabledBtn = true`
-- [ ] ไม่สามารถลงเวลาเข้างานได้
-
----
-
-### 3.7 TC-LOC-07: Check-out with Location Testing
-
-#### Test Case: TC-LOC-07-01 - ลงเวลาออกสำเร็จ
-
-**Objective**: ทดสอบการลงเวลาออกสำเร็จพร้อมตำแหน่ง
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | เปิดแอป HRMS | แสดงหน้าแรก | [ ] |
-| 2 | ตรวจสอบ statusCheckin | statusCheckin = false (ออกงาน) | [ ] |
-| 3 | อนุญาต Location Permission | รับสิทธิ์สำเร็จ | [ ] |
-| 4 | กดปุ่มขอตำแหน่ง | รับพิกัดสำเร็จ | [ ] |
-| 5 | ถ่ายรูปภาพ | แสดงรูปที่ถ่าย | [ ] |
-| 6 | กด "ลงเวลาออกงาน" | เรียก checkoutCheck API | [ ] |
-| 7 | เปิด Network Tab | GET `/leave/user/checkout-check/N` | [ ] |
-| 8 | ตรวจสอบ response | status = 'NORMAL' หรือ 'ABSENT' | [ ] |
-| 9 | ตรวจสอบ dialog | แสดง dialog ยืนยันการลงเวลาออกงาน | [ ] |
-| 10 | กด "ยืนยัน" | ส่งข้อมูลไป API | [ ] |
-| 11 | เปิด Network Tab | POST `/leave/check-in` | [ ] |
-| 12 | ตรวจสอบ payload | { lat, lon, POI, isLocation, locationName, img, remark, checkInId } | [ ] |
-| 13 | ตรวจสอบ response | status = 200 | [ ] |
-| 14 | ตรวจสอบ modal | แสดง modal ลงเวลาออกงานสำเร็จ | [ ] |
-| 15 | ตรวจสอบ statusCheckin | statusCheckin = true | [ ] |
-
-**Verification Checklist:**
-
-- [ ] checkoutCheck API ถูกต้อง
-- [ ] API call ถูกต้อง (มี checkInId)
-- [ ] Response status 200
-- [ ] แสดง modal สำเร็จ
-- [ ] `statusCheckin = true`
-
----
-
-### 3.8 TC-LOC-09: Privacy Consent Testing
-
-#### Test Case: TC-LOC-09-01 - แสดง Privacy Modal ก่อนใช้ Location
-
-**Objective**: ทดสอบการแสดง Privacy Modal ก่อนใช้ Location
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | เปิดแอป HRMS ครั้งแรก (หรือ clear localStorage) | แสดงหน้าแรก | [ ] |
-| 2 | ตรวจสอบ privacyStore.isAccepted | isAccepted = false | [ ] |
-| 3 | กดปุ่มที่ต้องการ Location (แผนที่/กล้อง) | checkPrivacyAccepted() return false | [ ] |
-| 4 | ตรวจสอบ privacyStore.modalPrivacy | modalPrivacy = true | [ ] |
-| 5 | ตรวจสอบ Privacy Modal | แสดง Privacy Modal | [ ] |
-| 6 | ตรวจสอบว่าไม่สามารถใช้งาน Location ได้ | แผนที่/กล้องไม่ทำงาน | [ ] |
-
-**Verification Checklist:**
-
-- [ ] `isAccepted = false`
-- [ ] `modalPrivacy = true`
-- [ ] แสดง Privacy Modal
-- [ ] ไม่สามารถใช้งาน Location ได้
-
----
-
-#### Test Case: TC-LOC-09-02 - ยอมรับ Privacy Policy
-
-**Objective**: ทดสอบการยอมรับ Privacy Policy
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | เปิดแอป HRMS ครั้งแรก | แสดงหน้าแรก | [ ] |
-| 2 | แสดง Privacy Modal | แสดง Privacy Modal | [ ] |
-| 3 | กด "ยอมรับ" | เรียก privacyStore.setAccepted(true) | [ ] |
-| 4 | ตรวจสอบ privacyStore.isAccepted | isAccepted = true | [ ] |
-| 5 | ตรวจสอบ privacyStore.modalPrivacy | modalPrivacy = false | [ ] |
-| 6 | ตรวจสอบ Privacy Modal | Modal ปิด | [ ] |
-| 7 | กดปุ่มขอตำแหน่ง | เรียก mapRef.value?.requestLocationPermission() | [ ] |
-| 8 | อนุญาต Location Permission | รับสิทธิ์สำเร็จ | [ ] |
-| 9 | รับพิกัดสำเร็จ | แสดงแผนที่ | [ ] |
-
-**Verification Checklist:**
-
-- [ ] `isAccepted = true`
-- [ ] `modalPrivacy = false`
-- [ ] Modal ปิด
-- [ ] สามารถใช้งาน Location ได้
-
----
-
-#### Test Case: TC-LOC-09-03 - ปฏิเสธ Privacy Policy
-
-**Objective**: ทดสอบการปฏิเสธ Privacy Policy
-
-| Step | Action | Expected Result | Screenshot |
-|------|--------|-----------------|------------|
-| 1 | เปิดแอป HRMS ครั้งแรก | แสดงหน้าแรก | [ ] |
-| 2 | แสดง Privacy Modal | แสดง Privacy Modal | [ ] |
-| 3 | กด "ปฏิเสธ" หรือปิด Modal | เรียก privacyStore.modalPrivacy = false | [ ] |
-| 4 | ตรวจสอบ privacyStore.isAccepted | isAccepted = false | [ ] |
-| 5 | ตรวจสอบ privacyStore.modalPrivacy | modalPrivacy = false | [ ] |
-| 6 | ตรวจสอบ Privacy Modal | Modal ปิด | [ ] |
-| 7 | กดปุ่มขอตำแหน่ง | checkPrivacyAccepted() return false | [ ] |
-| 8 | ตรวจสอบ | Privacy Modal แสดงอีกครั้ง | [ ] |
-
-**Verification Checklist:**
-
-- [ ] `isAccepted = false`
-- [ ] `modalPrivacy = false`
-- [ ] Modal ปิด
-- [ ] ไม่สามารถใช้งาน Location ได้
-- [ ] แจ้งเตือนเมื่อพยายามใช้งาน Location
-
----
-
-## 4. Mock Location Testing
-
-### 4.1 Fake GPS Apps for Testing
-
-#### 4.1.1 Android Fake GPS Apps
-
-| App Name | Link | Features |
-|----------|------|----------|
-| Fake GPS Location | [Play Store](https://play.google.com/store/apps/details?id=com.lexa.fakegps) | Simple location spoofing |
-| GPS Joystick | [Play Store](https://play.google.com/store/apps/details?id=com.theappninjas.android.gpsjoke) | Joystick control, route simulation |
-| Mock GPS | [Play Store](https://play.google.com/store/apps/details?id=com.incorporateapps.fakegps) | Multiple location saving |
-
-#### 4.1.2 iOS Fake GPS Apps
-
-| App Name | Link | Features |
-|----------|------|----------|
-| Location Changer | [App Store](https://apps.apple.com/app/location-changer/id1383869695) | Location spoofing (needs jailbreak) |
-| Xcode Simulation | - | Built-in location simulation |
-
-### 4.2 Mock Location Test Scenarios
-
-#### Scenario 1: Basic Mock Location
-
-1. **Setup**
- - เปิด Fake GPS Location App
- - ตั้งค่าตำแหน่ง: 13.7563, 100.5018 (Sanam Suea Pa)
- - ตั้งค่าความแม่นยำ: 10 meters
-
-2. **Execute**
- - เปิดแอป HRMS
- - กดปุ่มขอตำแหน่ง
- - ตรวจสอบผลลัพธ์
-
-3. **Expected**
- - ตรวจจับ mock location (indicators >= 3)
- - แสดง error message
- - ปุ่มลงเวลา disabled
-
-#### Scenario 2: Rapid Location Change
-
-1. **Setup**
- - เปิดแอป HRMS ที่ตำแหน่ง A (13.7563, 100.5018)
- - รอสักครู่เพื่อให้บันทึกตำแหน่ง A ลง history
-
-2. **Execute**
- - เปิด Fake GPS App
- - เปลี่ยนตำแหน่งไป B (15.8700, 100.9925)
- - รีเฟรชแอป HRMS ทันที (ภายใน 60 วินาที)
-
-3. **Expected**
- - คำนวณความเร็ว = ~3000 km/h (> 100 m/s)
- - mockIndicators += 3
- - แสดง error: "ตรวจพบการเคลื่อนที่ด้วยความเร็วผิดปกติ..."
-
-#### Scenario 3: Null Island (0,0)
-
-1. **Setup**
- - เปิด Fake GPS App
- - ตั้งค่าตำแหน่ง: 0, 0 (Null Island)
-
-2. **Execute**
- - เปิดแอป HRMS
- - กดปุ่มขอตำแหน่ง
-
-3. **Expected**
- - validateCoordinates(0, 0) = false
- - mockIndicators += 3
- - แสดง error: "พิกัดตำแหน่งไม่ถูกต้อง กรุณาลองใหม่"
-
----
-
-## 5. Developer Tools Testing
-
-### 5.1 Chrome DevTools Sensor Simulation
-
-#### 5.1.1 Enable Sensors
-
-1. เปิด Chrome DevTools (F12)
-2. เลือก More tools > Sensors
-3. ตั้งค่า Location
-
-#### 5.1.2 Pre-defined Locations
-
-| Location | Latitude | Longitude | Use Case |
-|----------|----------|-----------|----------|
-| Sanam Suea Pa | 13.7563 | 100.5018 | Bangkok office |
-| Suvarnabhumi Airport | 13.6900 | 100.7501 | Outdoor high accuracy |
-| Central World | 13.7944 | 100.5439 | Indoor |
-| Null Island | 0 | 0 | Invalid coordinates |
-| Out of Range | 91 | 181 | Out of range test |
-
-#### 5.1.3 Custom Location
-
-1. เลือก "Other..." ใน Sensors tab
-2. ป้อน Latitude และ Longitude
-3. กด Enter
-
-### 5.2 Network Throttling
-
-#### 5.2.1 Enable Throttling
-
-1. เปิด Chrome DevTools (F12)
-2. เลือก Network tab
-3. เลือก Throttling
-4. เลือก preset หรือ custom
-
-#### 5.2.2 Throttling Presets
-
-| Preset | Download | Upload | Latency | Use Case |
-|--------|----------|--------|---------|----------|
-| Offline | 0 | 0 | - | API failure test |
-| Slow 3G | 500 Kbps | 500 Kbps | 2000ms | Slow network |
-| Fast 3G | 1.6 Mbps | 750 Kbps | 100ms | Normal mobile |
-
-### 5.3 Console Testing
-
-#### 5.3.1 Test Location Validation
-
-```javascript
-// Test 1: Valid coordinates
-const validPosition = {
- coords: { latitude: 13.7563, longitude: 100.5018, accuracy: 10 },
- timestamp: Date.now()
-}
-console.log(validateLocation(validPosition))
-// Expected: { isValid: true, isMockDetected: false }
-
-// Test 2: Invalid coordinates (0,0)
-const invalidPosition = {
- coords: { latitude: 0, longitude: 0, accuracy: 10 },
- timestamp: Date.now()
-}
-console.log(validateLocation(invalidPosition))
-// Expected: { isValid: false, isMockDetected: true, confidence: 'high' }
-
-// Test 3: Stale timestamp
-const stalePosition = {
- coords: { latitude: 13.7563, longitude: 100.5018, accuracy: 10 },
- timestamp: Date.now() - 70000 // 70 seconds ago
-}
-console.log(validateLocation(stalePosition))
-// Expected: errors = ["ข้อมูลตำแหน่งเก่าเกินไป..."]
-
-// Test 4: Poor accuracy
-const poorAccuracyPosition = {
- coords: { latitude: 13.7563, longitude: 100.5018, accuracy: 150 },
- timestamp: Date.now()
-}
-console.log(validateLocation(poorAccuracyPosition))
-// Expected: warnings = ["ความแม่นยำตำแหน่งต่ำเกินไป..."]
-```
-
-#### 5.3.2 Test Distance Calculation
-
-```javascript
-// Test haversineDistance
-const distance = haversineDistance(13.7563, 100.5018, 15.8700, 100.9925)
-console.log(`Distance: ${distance} meters`)
-// Expected: ~180,000 meters (180 km)
-
-// Test speed calculation
-const pos1 = { latitude: 13.7563, longitude: 100.5018, timestamp: Date.now() - 60000 }
-const pos2 = { latitude: 15.8700, longitude: 100.9925, timestamp: Date.now() }
-const speed = calculateSpeed(pos1, pos2)
-console.log(`Speed: ${speed} m/s (${speed * 3.6} km/h)`)
-// Expected: ~3000 km/h (> 100 m/s = 360 km/h)
-```
-
----
-
-## 6. Device-Specific Testing
-
-### 6.1 Mobile Device Testing
-
-#### 6.1.1 Android Testing
-
-| Step | Action | Notes |
-|------|--------|-------|
-| 1 | Install Fake GPS App | Google Play Store |
-| 2 | Enable Developer Options | Settings > About Phone > Build Number (7 taps) |
-| 3 | Enable Mock Location | Settings > Developer Options > Select mock location app |
-| 4 | Set Fake Location | Fake GPS App > Set Location |
-| 5 | Open HRMS App | Test mock detection |
-
-#### 6.1.2 iOS Testing
-
-| Step | Action | Notes |
-|------|--------|-------|
-| 1 | Connect iPhone to Mac | USB cable |
-| 2 | Open Xcode | Mac |
-| 3 | Select Device > Debug Location | Xcode menu |
-| 4 | Set Custom Location | Enter lat/lon |
-| 5 | Open HRMS App | Test mock detection |
-
-### 6.2 Desktop Browser Testing
-
-#### 6.2.1 Chrome Testing
-
-| Step | Action | Notes |
-|------|--------|-------|
-| 1 | Open Chrome | - |
-| 2 | Open DevTools | F12 |
-| 3 | More tools > Sensors | - |
-| 4 | Set Location | Choose or custom |
-| 5 | Open HRMS App | Test location |
-
-#### 6.2.2 Edge Testing
-
-| Step | Action | Notes |
-|------|--------|-------|
-| 1 | Open Edge | - |
-| 2 | Open DevTools | F12 |
-| 3 | More tools > Sensors | - |
-| 4 | Set Location | Choose or custom |
-| 5 | Open HRMS App | Test location |
-
----
-
-## 7. Test Data & Scenarios
-
-### 7.1 Valid Locations
-
-| Name | Latitude | Longitude | Accuracy | Expected |
-|------|----------|-----------|----------|----------|
-| Sanam Suea Pa | 13.7563 | 100.5018 | <= 100m | Pass |
-| Suvarnabhumi | 13.6900 | 100.7501 | <= 100m | Pass |
-| Central World | 13.7944 | 100.5439 | <= 100m | Pass |
-| Chiang Mai | 18.7883 | 98.9853 | <= 100m | Pass |
-
-### 7.2 Invalid Locations
-
-| Name | Latitude | Longitude | Accuracy | Expected |
-|------|----------|-----------|----------|----------|
-| Null Island | 0 | 0 | Any | Fail (invalid coordinates) |
-| Out of Range (lat) | 91 | 100.5018 | Any | Fail (invalid coordinates) |
-| Out of Range (lon) | 13.7563 | 181 | Any | Fail (invalid coordinates) |
-| NaN | NaN | NaN | Any | Fail (invalid coordinates) |
-
-### 7.3 Edge Cases
-
-| Scenario | Latitude | Longitude | Accuracy | Timestamp | Expected |
-|----------|----------|-----------|----------|-----------|----------|
-| Stale Data | 13.7563 | 100.5018 | 10 | t - 70s | Fail (stale) |
-| Poor Accuracy | 13.7563 | 100.5018 | 150 | current | Warning |
-| Impossible Speed | 15.8700 | 100.9925 | 10 | t + 60s (from 13.7563, 100.5018) | Fail (speed) |
-
----
-
-## 8. Troubleshooting
-
-### 8.1 Common Issues
-
-| Issue | Cause | Solution |
-|-------|-------|----------|
-| ไม่ได้รับพิกัด GPS | GPS ปิดอยู่ | เปิด Location Service |
-| แผนที่ไม่แสดง | ยังไม่ยอมรับ Privacy Policy | ยอมรับ Privacy Policy |
-| ปุ่มลงเวลา disabled | Mock location detected | ปิด Fake GPS App และรีเฟรช |
-| POI ไม่แสดง | Bangkok GIS down | รอ fallback ไป ArcGIS |
-| ความแม่นยำต่ำ | อยู่ในอาคาร | ไปยังพื้นที่เปิดกว้าง |
-
-### 8.2 Debug Mode
-
-#### Enable Debug Mode
-
-1. เปิด Console ใน Browser
-2. ตรวจสอบค่าต่าง ๆ:
-
-```javascript
-// Check validation result
-console.log('Validation:', validationResult)
-
-// Check location granted
-console.log('Location Granted:', locationGranted)
-
-// Check mock detected
-console.log('Mock Detected:', isMockLocationDetected)
-
-// Check form location
-console.log('Form Location:', formLocation)
-
-// Check privacy accepted
-console.log('Privacy Accepted:', privacyStore.isAccepted)
-```
-
----
-
-## 9. Test Execution Checklist
-
-### 9.1 Pre-Test Checklist
-
-- [ ] ติดตั้ง Fake GPS App (สำหรับการทดสอบ mock location)
-- [ ] เปิด Developer Options (Android) หรือเตรียม Xcode (iOS)
-- [ ] เตรียม Test Data และ Test Scenarios
-- [ ] เตรียม Screenshot/Screen Recording tool
-- [ ] ตรวจสอบ Test Environment (Network, GPS, etc.)
-
-### 9.2 During Test Checklist
-
-- [ ] บันทึกผลลัพธ์ทุก Test Case
-- [ ] ถ่าย Screenshot สำคัญ
-- [ ] บันทึกข้อผิดพลาดที่พบ
-- [ ] ตรวจสอบ Console logs
-- [ ] ตรวจสอบ Network requests
-
-### 9.3 Post-Test Checklist
-
-- [ ] สรุปผลการทดสอบ
-- [ ] รายงาน bugs ที่พบ
-- [ ] อัปเดต Test Cases document
-- [ ] เตรียม Test Evidence (Screenshots, Videos)
-- [ ] ส่งรายงานให้ทีมที่เกี่ยวข้อง
-
----
-
-## 10. References
-
-### 10.1 Related Documents
-
-- [Test Cases: Location Features](./test-cases-location.md)
-- [Location Validation Logic](../src/composables/useLocationValidation.ts)
-- [Home View Component](../src/views/HomeView.vue)
-- [AscGISMap Component](../src/components/AscGISMap.vue)
-
-### 10.2 External Resources
-
-- [W3C Geolocation API](https://www.w3.org/TR/geolocation-API/)
-- [MDN Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API)
-- [Chrome DevTools Sensors](https://developer.chrome.com/docs/devtools/device-mode/override-device-metrics/)
-
----
-
-## 11. Revision History
-
-| Version | Date | Author | Changes |
-|---------|------|--------|---------|
-| 1.0.0 | 2025-03-09 | QA Team | Initial version - Detailed test steps for Location features |
-
----
-
-## 12. Approval
-
-| Role | Name | Signature | Date |
-|------|------|-----------|------|
-| QA Lead | | | |
-| Developer | | | |
-| Product Owner | | | |
diff --git a/src/components/AscGISMap.vue b/src/components/AscGISMap.vue
index 7651b7c..81da108 100644
--- a/src/components/AscGISMap.vue
+++ b/src/components/AscGISMap.vue
@@ -5,14 +5,19 @@ import axios from 'axios'
import { useCounterMixin } from '@/stores/mixin'
import { useQuasar } from 'quasar'
import { usePrivacyStore } from '@/stores/privacy'
+import { useLocationValidation } from '@/composables/useLocationValidation'
const mixin = useCounterMixin()
const { messageError } = mixin
const privacyStore = usePrivacyStore()
-const emit = defineEmits(['update:location'])
+// import type { LocationObject } from '@/interface/index/Main'
+const mapElement = ref(null)
+const emit = defineEmits(['update:location', 'locationStatus', 'mockDetected'])
const $q = useQuasar()
+const { validateLocation, showMockWarning } = useLocationValidation()
+
function updateLocation(latitude: number, longitude: number, namePOI: string) {
// ส่ง event ไปยัง parent component เพื่ออัพเดทค่า props
emit('update:location', latitude, longitude, namePOI)
@@ -221,8 +226,30 @@ const requestLocationPermission = () => {
navigator.geolocation.getCurrentPosition(
async (position) => {
- // Permission granted
- locationGranted.value = true
+ // Validate location first
+ const validationResult = validateLocation(position)
+
+ // Always emit mockDetected event (regardless of result)
+ if (validationResult.isMockDetected) {
+ showMockWarning(validationResult)
+ emit('mockDetected', validationResult)
+ }
+
+ // Check for critical errors (invalid coordinates) that prevent showing location
+ const hasCriticalErrors = validationResult.errors.some(error =>
+ error.includes('พิกัดตำแหน่งไม่ถูกต้อง')
+ )
+
+ if (hasCriticalErrors) {
+ locationGranted.value = false
+ emit('locationStatus', false)
+ messageError($q, '', validationResult.errors[0])
+ return
+ }
+
+ // Permission granted based on mock detection
+ locationGranted.value = !validationResult.isMockDetected
+ emit('locationStatus', !validationResult.isMockDetected)
const { latitude, longitude } = position.coords
// console.log('Current position:', latitude, longitude)
@@ -240,6 +267,7 @@ const requestLocationPermission = () => {
(error) => {
// Permission denied
locationGranted.value = false
+ emit('locationStatus', false)
switch (error.code) {
case error.PERMISSION_DENIED:
@@ -270,6 +298,7 @@ const requestLocationPermission = () => {
defineExpose({
requestLocationPermission,
+ locationGranted,
})
diff --git a/src/components/DialogDebug.vue b/src/components/DialogDebug.vue
index fb151e3..ab55a0e 100644
--- a/src/components/DialogDebug.vue
+++ b/src/components/DialogDebug.vue
@@ -176,7 +176,7 @@ function onClose() {
-
+
diff --git a/src/components/PopupPrivacy.vue b/src/components/PopupPrivacy.vue
index 4493fab..a72dde3 100644
--- a/src/components/PopupPrivacy.vue
+++ b/src/components/PopupPrivacy.vue
@@ -108,7 +108,6 @@ const checkIfScrollable = () => {
transition-show="slide-up"
transition-hide="slide-down"
:maximized="$q.screen.lt.sm"
- @show="checkIfScrollable"
>
diff --git a/src/composables/useLocationValidation.ts b/src/composables/useLocationValidation.ts
new file mode 100644
index 0000000..27222df
--- /dev/null
+++ b/src/composables/useLocationValidation.ts
@@ -0,0 +1,169 @@
+import { ref } from 'vue'
+import { useQuasar } from 'quasar'
+import { useCounterMixin } from '@/stores/mixin'
+
+export interface LocationValidationResult {
+ isValid: boolean
+ isMockDetected: boolean
+ confidence: 'low' | 'medium' | 'high'
+ warnings: string[]
+ errors: string[]
+}
+
+export interface PositionSnapshot {
+ latitude: number
+ longitude: number
+ timestamp: number
+}
+
+// Configuration constants - exported for documentation and testing purposes
+export const VALIDATION_CONFIG = {
+ MAX_TIMESTAMP_AGE_MS: 60_000, // 60 seconds - maximum acceptable age of location data
+ MAX_ACCURACY_METERS: 100, // 100 meters - maximum acceptable GPS accuracy
+ MAX_SPEED_MS: 100, // 100 m/s (~360 km/h) - maximum plausible movement speed
+ POSITION_HISTORY_SIZE: 5, // number of positions to keep for pattern detection
+ MOCK_INDICATOR_THRESHOLD: 3, // threshold for mock detection (indicators >= 3 = mock)
+} as const
+
+export function useLocationValidation() {
+ const $q = useQuasar()
+ const { messageError } = useCounterMixin()
+
+ // Thai error messages - exported for i18n consistency
+ const errorMessages = {
+ MOCK_DETECTED: 'ตรวจพบว่าตำแหน่ง GPS อาจไม่ถูกต้อง กรุณาปิดแอปจำลองตำแหน่งและลองใหม่',
+ INVALID_COORDINATES: 'พิกัดตำแหน่งไม่ถูกต้อง กรุณาลองใหม่',
+ STALE_TIMESTAMP: 'ข้อมูลตำแหน่งเก่าเกินไป กรุณารับสัญญาณ GPS ใหม่',
+ POOR_ACCURACY: 'ความแม่นยำตำแหน่งต่ำเกินไป กรุณาตรวจสอบการรับสัญญาณ GPS',
+ IMPOSSIBLE_SPEED: 'ตรวจพบการเคลื่อนที่ด้วยความเร็วผิดปกติ อาจเป็นการจำลองตำแหน่ง',
+ }
+
+ const previousPositions = ref([])
+
+ // คำนวณระยะห่างระหว่าง 2 จุด (Haversine formula)
+ const haversineDistance = (lat1: number, lon1: number, lat2: number, lon2: number): number => {
+ const R = 6371e3 // Earth's radius in meters
+ const φ1 = (lat1 * Math.PI) / 180
+ const φ2 = (lat2 * Math.PI) / 180
+ const Δφ = ((lat2 - lat1) * Math.PI) / 180
+ const Δλ = ((lon2 - lon1) * Math.PI) / 180
+
+ const a =
+ Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
+ Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2)
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
+
+ return R * c
+ }
+
+ // ตรวจสอบพิกัดถูกต้อง
+ const validateCoordinates = (lat: number, lon: number): boolean => {
+ return (
+ lat >= -90 && lat <= 90 &&
+ lon >= -180 && lon <= 180 &&
+ !isNaN(lat) && !isNaN(lon) &&
+ !(lat === 0 && lon === 0) // Mock มักใช้ 0,0
+ )
+ }
+
+ // ตรวจสอบความแม่นยำ
+ const validateAccuracy = (accuracy: number | null): boolean => {
+ if (accuracy === null) return true
+ return accuracy <= VALIDATION_CONFIG.MAX_ACCURACY_METERS
+ }
+
+ // ตรวจสอบ Timestamp
+ const validateTimestamp = (timestamp: number): boolean => {
+ const now = Date.now()
+ const age = Math.abs(now - timestamp)
+ return age <= VALIDATION_CONFIG.MAX_TIMESTAMP_AGE_MS
+ }
+
+ // คำนวณความเร็ว
+ const calculateSpeed = (pos1: PositionSnapshot, pos2: PositionSnapshot): number => {
+ const distance = haversineDistance(pos1.latitude, pos1.longitude, pos2.latitude, pos2.longitude)
+ const timeDiff = Math.abs(pos2.timestamp - pos1.timestamp) / 1000 // seconds
+ return timeDiff > 0 ? distance / timeDiff : 0
+ }
+
+ // ตรวจสอบความเร็วปกติ
+ const validateSpeed = (current: PositionSnapshot, previous: PositionSnapshot): boolean => {
+ const speed = calculateSpeed(previous, current)
+ return speed <= VALIDATION_CONFIG.MAX_SPEED_MS
+ }
+
+ // Main validation function
+ const validateLocation = (position: GeolocationPosition): LocationValidationResult => {
+ const warnings: string[] = []
+ const errors: string[] = []
+ let mockIndicators = 0
+
+ const { latitude, longitude, accuracy } = position.coords
+ const { timestamp } = position
+
+ // 1. Coordinate validation
+ if (!validateCoordinates(latitude, longitude)) {
+ errors.push(errorMessages.INVALID_COORDINATES)
+ mockIndicators += 3
+ }
+
+ // 2. Timestamp validation
+ if (!validateTimestamp(timestamp)) {
+ errors.push(errorMessages.STALE_TIMESTAMP)
+ mockIndicators += 2
+ }
+
+ // 3. Accuracy validation
+ if (!validateAccuracy(accuracy)) {
+ warnings.push(errorMessages.POOR_ACCURACY)
+ mockIndicators += 1
+ }
+
+ // 4. Compare with previous positions
+ if (previousPositions.value.length > 0) {
+ const previous = previousPositions.value[previousPositions.value.length - 1]
+
+ if (!validateSpeed({ latitude, longitude, timestamp }, previous)) {
+ errors.push(errorMessages.IMPOSSIBLE_SPEED)
+ mockIndicators += 3
+ }
+ }
+
+ // Store current position
+ previousPositions.value.push({ latitude, longitude, timestamp })
+ if (previousPositions.value.length > VALIDATION_CONFIG.POSITION_HISTORY_SIZE) {
+ previousPositions.value.shift()
+ }
+
+ // Determine result
+ const isMockDetected = mockIndicators >= VALIDATION_CONFIG.MOCK_INDICATOR_THRESHOLD
+ const isValid = errors.length === 0
+
+ let confidence: 'low' | 'medium' | 'high' = 'low'
+ if (mockIndicators >= 5) confidence = 'high'
+ else if (mockIndicators >= 3) confidence = 'medium'
+
+ return {
+ isValid,
+ isMockDetected,
+ confidence,
+ warnings,
+ errors,
+ }
+ }
+
+ const showMockWarning = (result: LocationValidationResult) => {
+ if (!result.isMockDetected) return
+ messageError($q, null, errorMessages.MOCK_DETECTED)
+ }
+
+ const resetValidation = () => {
+ previousPositions.value = []
+ }
+
+ return {
+ validateLocation,
+ showMockWarning,
+ resetValidation,
+ }
+}
diff --git a/src/stores/mixin.ts b/src/stores/mixin.ts
index fffa00c..3ff5636 100644
--- a/src/stores/mixin.ts
+++ b/src/stores/mixin.ts
@@ -5,56 +5,6 @@ import { logout } from '@/plugins/auth'
import { format, utcToZonedTime } from 'date-fns-tz'
export const useCounterMixin = defineStore('mixin', () => {
- let activeErrorDialog: any = null
-
- const clearActiveErrorDialog = () => {
- if (!activeErrorDialog) return
- try {
- if (typeof activeErrorDialog.hide === 'function') {
- activeErrorDialog.hide()
- } else if (typeof activeErrorDialog.destroy === 'function') {
- activeErrorDialog.destroy()
- }
- } finally {
- activeErrorDialog = null
- }
- }
-
- const openSingleErrorDialog = (
- q: any,
- message: string,
- onCancel?: () => void | Promise
- ) => {
- clearActiveErrorDialog()
-
- const dialog = q.dialog({
- component: CustomComponent,
- componentProps: {
- title: `พบข้อผิดพลาด`,
- message,
- icon: 'warning',
- color: 'red',
- onlycancel: true,
- },
- })
-
- activeErrorDialog = dialog
-
- dialog.onDismiss(() => {
- if (activeErrorDialog === dialog) {
- activeErrorDialog = null
- }
- })
-
- if (onCancel) {
- dialog.onCancel(() => {
- void onCancel()
- })
- }
-
- return dialog
- }
-
function date2Thai(srcDate: Date, isFullMonth = false, isTime = false) {
if (srcDate == null) {
return null
@@ -183,30 +133,53 @@ export const useCounterMixin = defineStore('mixin', () => {
}
const messageError = (q: any, e: any = '', msg: string = '') => {
- // Keep only one active warning popup to prevent dialog overlap.
+ // q.dialog.hide();
if (e.response !== undefined) {
if (e.response.data.status !== undefined) {
if (e.response.data.status == 401) {
//invalid_token
- openSingleErrorDialog(
- q,
- 'ล็อกอินหมดอายุ กรุณาล็อกอินใหม่อีกครั้ง',
- async () => {
- showLoader()
- await logout()
- setTimeout(() => {
- hideLoader()
- }, 1000)
- }
- )
+ q.dialog({
+ component: CustomComponent,
+ componentProps: {
+ title: `พบข้อผิดพลาด`,
+ message: `ล็อกอินหมดอายุ กรุณาล็อกอินใหม่อีกครั้ง`,
+ icon: 'warning',
+ color: 'red',
+ onlycancel: true,
+ },
+ }).onCancel(async () => {
+ showLoader()
+ await logout()
+ setTimeout(() => {
+ hideLoader()
+ }, 1000)
+ })
} else {
const message = e.response.data.result ?? e.response.data.message
- openSingleErrorDialog(q, `${message}`)
+ q.dialog({
+ component: CustomComponent,
+ componentProps: {
+ title: `พบข้อผิดพลาด`,
+ message: `${message}`,
+ icon: 'warning',
+ color: 'red',
+ onlycancel: true,
+ },
+ })
}
} else {
if (e.response.status == 401) {
if (msg !== '') {
- openSingleErrorDialog(q, msg, async () => {
+ q.dialog({
+ component: CustomComponent,
+ componentProps: {
+ title: `พบข้อผิดพลาด`,
+ message: msg,
+ icon: 'warning',
+ color: 'red',
+ onlycancel: true,
+ },
+ }).onCancel(async () => {
showLoader()
await logout()
setTimeout(() => {
@@ -215,35 +188,70 @@ export const useCounterMixin = defineStore('mixin', () => {
})
} else {
//invalid_token
- openSingleErrorDialog(
- q,
- 'ล็อกอินหมดอายุ กรุณาล็อกอินใหม่อีกครั้ง',
- async () => {
- showLoader()
- await logout()
- setTimeout(() => {
- hideLoader()
- }, 1000)
- }
- )
+ q.dialog({
+ component: CustomComponent,
+ componentProps: {
+ title: `พบข้อผิดพลาด`,
+ message: `ล็อกอินหมดอายุ กรุณาล็อกอินใหม่อีกครั้ง`,
+ icon: 'warning',
+ color: 'red',
+ onlycancel: true,
+ },
+ }).onCancel(async () => {
+ showLoader()
+ await logout()
+ setTimeout(() => {
+ hideLoader()
+ }, 1000)
+ })
}
} else if (e.response.data.successful === false) {
- openSingleErrorDialog(q, e.response.data.message)
+ q.dialog({
+ component: CustomComponent,
+ componentProps: {
+ title: `พบข้อผิดพลาด`,
+ message: e.response.data.message,
+ icon: 'warning',
+ color: 'red',
+ onlycancel: true,
+ },
+ })
} else {
- openSingleErrorDialog(
- q,
- 'ข้อมูลผิดพลาดทำให้เกิดการไม่ตอบสนองต่อการเรียกใช้งานดูเว็บไซต์'
- )
+ q.dialog({
+ component: CustomComponent,
+ componentProps: {
+ title: `พบข้อผิดพลาด`,
+ message: `ข้อมูลผิดพลาดทำให้เกิดการไม่ตอบสนองต่อการเรียกใช้งานดูเว็บไซต์`,
+ icon: 'warning',
+ color: 'red',
+ onlycancel: true,
+ },
+ })
}
}
} else {
if (msg !== '') {
- return openSingleErrorDialog(q, msg)
+ return q.dialog({
+ component: CustomComponent,
+ componentProps: {
+ title: `พบข้อผิดพลาด`,
+ message: msg,
+ icon: 'warning',
+ color: 'red',
+ onlycancel: true,
+ },
+ })
}
- openSingleErrorDialog(
- q,
- 'ข้อมูลผิดพลาดทำให้เกิดการไม่ตอบสนองต่อการเรียกใช้งานดูเว็บไซต์'
- )
+ q.dialog({
+ component: CustomComponent,
+ componentProps: {
+ title: `พบข้อผิดพลาด`,
+ message: `ข้อมูลผิดพลาดทำให้เกิดการไม่ตอบสนองต่อการเรียกใช้งานดูเว็บไซต์`,
+ icon: 'warning',
+ color: 'red',
+ onlycancel: true,
+ },
+ })
}
}
diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue
index 5df3c0c..379b970 100644
--- a/src/views/HomeView.vue
+++ b/src/views/HomeView.vue
@@ -19,7 +19,6 @@ const { date2Thai, showLoader, hideLoader, messageError, dialogConfirm } = mixin
const $q = useQuasar()
const { checkPrivacyAccepted } = usePermissions()
const privacyStore = usePrivacyStore()
-const MOCK_CHECK_DELAY_MS = 800
const modalTime = ref(false) // Dailog ลงเวลาเข้างานของคุณ
const checkStatus = ref('')
@@ -33,6 +32,8 @@ const endTimeAfternoon = ref('12:00:00') //เวลาเช็คเ
const isLoadingCheckTime = ref(false) // ตัวแปรสำหรับการโหลด
const disabledBtn = ref(false)
+const locationGranted = ref(false)
+const isMockLocationDetected = ref(false)
/**
* fetch เช็คเวลาต้องลงเวลาเข้าหรือออกงาน
@@ -114,40 +115,19 @@ async function updateLocation(
formLocation.POI = namePOI
}
-function resetLocationForRetry() {
- formLocation.lat = 0
- formLocation.lng = 0
- formLocation.POI = ''
+/**
+ * รับค่าสถานะ location จาก AscGISMap
+ */
+function onLocationStatus(status: boolean) {
+ locationGranted.value = status
}
-function getCurrentPositionAsync(options?: PositionOptions) {
- return new Promise((resolve, reject) => {
- navigator.geolocation.getCurrentPosition(resolve, reject, options)
- })
-}
-
-function wait(ms: number) {
- return new Promise((resolve) => setTimeout(resolve, ms))
-}
-
-async function getDelayedFreshPosition() {
- const firstPosition = await getCurrentPositionAsync({
- enableHighAccuracy: true,
- timeout: 10000,
- maximumAge: 0,
- })
-
- await wait(MOCK_CHECK_DELAY_MS)
-
- try {
- return await getCurrentPositionAsync({
- enableHighAccuracy: true,
- timeout: 10000,
- maximumAge: 0,
- })
- } catch (error) {
- return firstPosition
- }
+/**
+ * รับค่า mock location detection จาก AscGISMap
+ */
+function onMockDetected(result: any) {
+ isMockLocationDetected.value = true
+ disabledBtn.value = true
}
const location = ref('') // พื้นที่ใกล้เคียง
@@ -187,9 +167,6 @@ const cameraIsOn = ref(false)
const img = ref(undefined)
const photoWidth = ref(350)
const photoHeight = ref(350)
-const availableCameras = ref([])
-const currentCameraIndex = ref(0)
-const currentCameraType = ref<'front' | 'back' | 'unknown'>('unknown')
const intervalId = ref(undefined) // ต้องใช้ตัวแปรเก็บค่า interval
@@ -305,16 +282,6 @@ async function stopChecking() {
}
}
-function identifyCameraType(label: string): 'front' | 'back' | 'unknown' {
- const lowerLabel = label.toLowerCase()
- if (lowerLabel.includes('front') || lowerLabel.includes('user') || lowerLabel.includes('face')) {
- return 'front'
- } else if (lowerLabel.includes('back') || lowerLabel.includes('environment') || lowerLabel.includes('rear')) {
- return 'back'
- }
- return 'unknown'
-}
-
/** function เปิดกล้อง*/
async function openCamera() {
// เช็คสิทธิ์ privacy ก่อนเปิดกล้อง
@@ -328,11 +295,7 @@ async function openCamera() {
await camera.value?.stop()
} else {
await camera.value?.start()
- const devices: any = await camera.value?.devices(['videoinput'])
- if (devices) {
- availableCameras.value = devices
- await changeCamera()
- }
+ await changeCamera() // ต้องรอให้ start() เสร็จก่อน
}
cameraIsOn.value = !cameraIsOn.value
} else {
@@ -346,59 +309,10 @@ async function openCamera() {
}
/** change camera device*/
-async function changeCamera(targetCameraType?: 'front' | 'back') {
- try {
- const devices: any = await camera.value?.devices(['videoinput'])
-
- if (!devices || devices.length === 0) {
- console.warn('No cameras found')
- return
- }
-
- availableCameras.value = devices
-
- if (devices.length === 1 || !targetCameraType) {
- const device = devices[0]
- await camera.value?.changeCamera(device.deviceId)
- currentCameraIndex.value = 0
- currentCameraType.value = identifyCameraType(device.label || '')
- return
- }
-
- const matchingCameras = devices.filter((device: any) =>
- identifyCameraType(device.label || '') === targetCameraType
- )
-
- if (matchingCameras.length > 0) {
- const targetDevice = matchingCameras[0]
- await camera.value?.changeCamera(targetDevice.deviceId)
- currentCameraIndex.value = devices.indexOf(targetDevice)
- currentCameraType.value = targetCameraType
- } else {
- const nextIndex = (currentCameraIndex.value + 1) % devices.length
- const nextDevice = devices[nextIndex]
- await camera.value?.changeCamera(nextDevice.deviceId)
- currentCameraIndex.value = nextIndex
- currentCameraType.value = identifyCameraType(nextDevice.label || '')
- }
- } catch (error) {
- console.error('Error switching camera:', error)
- }
-}
-
-/** switch camera device*/
-async function switchCamera() {
- if (availableCameras.value.length <= 1) {
- return
- }
-
- // สลับแค่ระหว่างกล้อง 2 ตัวแรก (กล้องหน้าและหลังหลัก)
- const targetIndex = currentCameraIndex.value === 0 ? 1 : 0
- const targetDevice = availableCameras.value[targetIndex]
-
- await camera.value?.changeCamera(targetDevice.deviceId)
- currentCameraIndex.value = targetIndex
- currentCameraType.value = identifyCameraType(targetDevice.label || '')
+async function changeCamera() {
+ const devices: any = await camera.value?.devices(['videoinput'])
+ const device = await devices[0]
+ camera.value?.changeCamera(device.deviceId)
}
/** function ถ่ายรูป*/
@@ -434,9 +348,7 @@ const objectRef: FormRef = {
}
/** function ตรวจสอบค่าว่างของ input*/
-async function validateForm() {
- disabledBtn.value = true
-
+function validateForm() {
const hasError = []
for (const key in objectRef) {
if (Object.prototype.hasOwnProperty.call(objectRef, key)) {
@@ -463,15 +375,11 @@ async function validateForm() {
model.value === 'อื่นๆ' ? useLocation.value : model.value
})`
} คุณต้องการยืนยันการลงเวลาเข้างาน?`,
- () => {
- disabledBtn.value = false
- },
+ () => {},
'red',
'ยืนยัน'
)
}
- } else {
- disabledBtn.value = false
}
}
@@ -482,16 +390,14 @@ const timeChickin = ref('') //เวลาเข้างาน,เว
async function confirm() {
// เช็คสิทธิ์ privacy ก่อนใช้งานแผนที่และกล้อง
if (!checkPrivacyAccepted()) {
- disabledBtn.value = false
return
}
if (!formLocation.POI || !formLocation.lat || !formLocation.lng) {
- disabledBtn.value = false
mapRef.value?.requestLocationPermission()
return
}
-
+ disabledBtn.value = true
showLoader()
const isLocation = workplace.value === 'in-place' //*true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง
const locationName = workplace.value === 'in-place' ? '' : useLocation.value
@@ -534,7 +440,6 @@ async function confirm() {
async function getCheck() {
if (!formLocation.POI || !formLocation.lat || !formLocation.lng) {
- disabledBtn.value = false
mapRef.value?.requestLocationPermission()
return
}
@@ -567,9 +472,7 @@ async function getCheck() {
() => confirm(),
'ยืนยันการลงเวลาออกงาน',
`เวลาออกจากงานของคุณคือ ${endTimeAfternoonVal} แต่ขณะนี้เป็นเวลา ${timeVal} น. หากคุณออกจากงานในเวลานี้สถานะการลงเวลาจะเป็น "${res.data.result.statusText}" คุณแน่ใจว่าจะลงเวลาออกงานในตอนนี้ใช่หรือไม่?`,
- () => {
- disabledBtn.value = false
- },
+ () => {},
'red',
'ยืนยัน'
)
@@ -578,7 +481,6 @@ async function getCheck() {
}
})
.catch((e) => {
- disabledBtn.value = false
messageError($q, e)
})
.finally(() => {
@@ -710,6 +612,16 @@ watch(
}
}
)
+
+watch(
+ () => locationGranted.value,
+ (newVal) => {
+ // Removed auto-reset of isMockLocationDetected to prevent
+ // clearing mock detection state when permission is granted.
+ // Mock detection state should only be reset after explicit user action
+ // or after a successful validation without mock indicators.
+ }
+)
@@ -782,6 +694,8 @@ watch(
v-if="$q.screen.gt.xs"
ref="mapRef"
@update:location="updateLocation"
+ @location-status="onLocationStatus"
+ @mock-detected="onMockDetected"
/>
@@ -825,16 +739,6 @@ watch(
v-if="$q.screen.gt.xs"
class="absolute-bottom-right q-ma-md"
>
-
-
-
+
@@ -1117,7 +1016,7 @@ watch(
push
size="18px"
:class="$q.screen.gt.xs ? 'q-px-md' : 'full-width q-pa-sm'"
- :disable="disabledBtn ? true : camera && img ? false : true"
+ :disable="disabledBtn || !locationGranted || isMockLocationDetected ? true : camera && img ? false : true"
@click="validateForm"
:loading="inQueue"
/>
@@ -1232,7 +1131,7 @@ watch(
push
size="18px"
:class="$q.screen.gt.xs ? 'q-px-md' : 'full-width q-pa-sm'"
- :disable="disabledBtn ? true : camera && img ? false : true"
+ :disable="disabledBtn || !locationGranted || isMockLocationDetected ? true : camera && img ? false : true"
@click="validateForm"
:loading="inQueue"
/>
@@ -1369,4 +1268,4 @@ watch(
rgba(2, 169, 152, 1) 100%
);
}
-
\ No newline at end of file
+
diff --git a/src/views/MainView.vue b/src/views/MainView.vue
index c8bc1d8..e34ab53 100644
--- a/src/views/MainView.vue
+++ b/src/views/MainView.vue
@@ -564,10 +564,7 @@ onMounted(async () => {
-
+
@@ -723,8 +720,4 @@ onMounted(async () => {
background-color: #016987;
color: #fff;
}
-
-.hidden {
- display: none !important;
-}