
## custom/Espo/Modules/HybReport/Controllers/CReportWidget.php
52: throw new \Espo\Core\Exceptions\BadRequest("Thiếu thông số id báo cáo.");
64: throw new \Espo\Core\Exceptions\Forbidden("Bạn không có quyền thao tác Báo cáo này.");

## custom/Espo/Modules/HybReport/Controllers/CReportDashboard.php
16: throw new BadRequest("Thiếu ID của Báo cáo (Dashboard ID).");
25: throw new BadRequest("Không tìm thấy Dashboard.");
155: throw new Forbidden("Bạn không có quyền tạo Widget.");
168: throw new BadRequest("Thiếu dashboardId.");
171: throw new BadRequest("Thiếu tên widget.");
177: throw new NotFound("Không tìm thấy Dashboard: $dashboardId");
203: throw new Forbidden("Bạn không có quyền chỉnh sửa Widget.");
210: throw new BadRequest("Thiếu widgetId.");
215: throw new NotFound("Không tìm thấy Widget: $widgetId");
241: throw new Forbidden("Bạn không có quyền xóa Widget.");
248: throw new BadRequest("Thiếu widgetId.");
253: throw new NotFound("Không tìm thấy Widget: $widgetId");

## custom/Espo/Modules/HybReport/Hooks/CReportDashboard/SyncToWidgets.php
55: 'name' => $reportName // Đồng bộ cả tên nếu cần

## custom/Espo/Modules/HybReport/Hooks/CReportWidget/ClearMeasureField.php
34: throw new BadRequest("Bảo mật: Định dạng JSON của trường {$fieldName} không hợp lệ.");
37: throw new BadRequest("Bảo mật: Dữ liệu của trường {$fieldName} bắt buộc phải là Array.");
49: throw new BadRequest("Bảo mật: Mảng phép tính (metrics) bị sai định dạng.");
65: throw new BadRequest("Bảo mật: Định dạng JSON của Data Matrix không hợp lệ.");
75: throw new BadRequest("Bảo mật: Tiêu chí nhóm [{$node['groupByField']}] chứa ký tự nghi ngờ mã độc.");
102: throw new BadRequest("Lỗi Nghiệp vụ BI: Không được phép nhóm lặp lại trường [{$node['groupByField']}] ở lớp con vì lớp cha đã sử dụng. Điều này sẽ phá vỡ cấu trúc ma trận báo cáo.");
117: throw new BadRequest("Bảo mật: Hệ thống phát hiện hàm tính toán không được phép ({$func}).");
121: throw new BadRequest("Bảo mật: Tên trường tính toán [{$metric['field']}] chứa ký tự nghi ngờ mã độc.");

## custom/Espo/Modules/HybReport/HybReport/Controllers/CReportWidget.php
52: throw new \Espo\Core\Exceptions\BadRequest("Thiếu thông số id báo cáo.");
64: throw new \Espo\Core\Exceptions\Forbidden("Bạn không có quyền thao tác Báo cáo này.");

## custom/Espo/Modules/HybReport/HybReport/Controllers/CReportDashboard.php
15: throw new BadRequest("Thiếu ID của Báo cáo (Dashboard ID).");
24: throw new BadRequest("Không tìm thấy Dashboard.");

## custom/Espo/Modules/HybReport/HybReport/Hooks/CReportDashboard/SyncToWidgets.php
55: 'name' => $reportName // Đồng bộ cả tên nếu cần

## custom/Espo/Modules/HybReport/HybReport/Hooks/CReportWidget/ClearMeasureField.php
34: throw new BadRequest("Bảo mật: Định dạng JSON của trường {$fieldName} không hợp lệ.");
37: throw new BadRequest("Bảo mật: Dữ liệu của trường {$fieldName} bắt buộc phải là Array.");
49: throw new BadRequest("Bảo mật: Mảng phép tính (metrics) bị sai định dạng.");
65: throw new BadRequest("Bảo mật: Định dạng JSON của Data Matrix không hợp lệ.");
75: throw new BadRequest("Bảo mật: Tiêu chí nhóm [{$node['groupByField']}] chứa ký tự nghi ngờ mã độc.");
102: throw new BadRequest("Lỗi Nghiệp vụ BI: Không được phép nhóm lặp lại trường [{$node['groupByField']}] ở lớp con vì lớp cha đã sử dụng. Điều này sẽ phá vỡ cấu trúc ma trận báo cáo.");
117: throw new BadRequest("Bảo mật: Hệ thống phát hiện hàm tính toán không được phép ({$func}).");
121: throw new BadRequest("Bảo mật: Tên trường tính toán [{$metric['field']}] chứa ký tự nghi ngờ mã độc.");

## custom/Espo/Modules/HybReport/HybReport/Services/CReportWidget.php
27: $this->config  // [TIMEZONE FIX] Truyền Config để PayloadParser đọc đúng timezone
55: $this->logError("compilePreview", $e);
56: throw new Error("Lỗi biên dịch Report: " . $e->getMessage());
63: $enableChart = true; // default safe: trả về để backward compat
104: $this->logError("getMatrixData", $e);
105: throw new Error("Lỗi nạp Matrix: " . $e->getMessage());
145: $this->logError("getDetailData", $e);
146: throw new Error("Lỗi nạp Chi tiết: " . $e->getMessage());
178: $this->logError("getChartData", $e);
179: throw new Error("Lỗi nạp Biểu đồ: " . $e->getMessage());
191: throw new BadRequest("Thiếu thông số id báo cáo.");
196: throw new BadRequest("Không tìm thấy báo cáo với ID: $id");
219: $this->logError("getDrillDownData", $e);
220: throw new Error("Lỗi nạp Drill-Down: " . $e->getMessage());
232: throw new BadRequest("Không tìm thấy báo cáo với ID: $id");
257: $this->logError("runSavedReport", $e);
258: throw new Error("Lỗi thực thi API Báo cáo: " . $e->getMessage());
282: throw new Error("Vượt quá giới hạn API (500 yêu cầu / giờ). Vui lòng thử lại sau.");
302: throw new BadRequest("JSON không hợp lệ ở trường '$fieldName': " . json_last_error_msg());

## custom/Espo/Modules/HybReport/HybReport/Services/CReportDashboard.php
22: if (!$dashboard) throw new NotFound("Không tìm thấy Báo cáo.");
25: throw new Forbidden("Bạn không có quyền xem báo cáo này.");
48: throw new Forbidden("Báo cáo riêng tư. Chỉ Admin mới được xem.");
52: throw new Forbidden("Bạn không thuộc Team được phân quyền.");

## custom/Espo/Modules/HybReport/HybReport/Tools/Report/MatrixQueryBuilder.php
41: $tagToMetricIdMap = []; // Bản đồ lưu trữ Tên Tag -> ID thực tế
328: if ($valA === '[Chưa phân bổ]' || $valA === '') return 1;
329: if ($valB === '[Chưa phân bổ]' || $valB === '') return -1;
487: $field = 'Global'; $vStr = 'Tổng hợp';
543: error_log("[HybReport Pivot] Lỗi SQL Unified Tree: " . $e->getMessage());

## custom/Espo/Modules/HybReport/HybReport/Tools/Report/PayloadParser.php
113: throw new \InvalidArgumentException('Unsafe math expression blocked.');
267: $this->applyFiltersToBuilder($selectBuilder, $filters, $aliases, $dataSources); // [#23 Gap 0]: Apply filters vào Pivot/Chart
403: 'timeMode' => $timeMode, // [#30]: cần biết extract vs continuous để sort đúng
798: $this->applyFiltersToBuilder($selectBuilder, $filters, $aliases, $prunedDataSources); // [#23 Gap 0-Detail]: Apply filters vào Detail Grid
945: 'continuous', // detail không dùng timeMode
1044: $entity = $revMatch[1]; // Override entity từ pattern
1154: $single   = null; // không dùng single cho range
1176: continue; // Skip: operator cần input nhưng chưa có value
2130: if (isset($seen[$key])) throw new \Espo\Core\Exceptions\BadRequest("Duplicate field: " . $key);
2140: if (isset($seenMetrics[$metricKey])) throw new \Espo\Core\Exceptions\BadRequest("Duplicate metric: " . $metricKey);

## custom/Espo/Modules/HybReport/Services/CReportWidget.php
27: $this->config  // [TIMEZONE FIX] Truyền Config để PayloadParser đọc đúng timezone
55: $this->logError("compilePreview", $e);
56: throw new Error("Lỗi biên dịch Report: " . $e->getMessage());
63: $enableChart = true; // default safe: trả về để backward compat
104: $this->logError("getMatrixData", $e);
105: throw new Error("Lỗi nạp Matrix: " . $e->getMessage());
145: $this->logError("getDetailData", $e);
146: throw new Error("Lỗi nạp Chi tiết: " . $e->getMessage());
178: $this->logError("getChartData", $e);
179: throw new Error("Lỗi nạp Biểu đồ: " . $e->getMessage());
191: throw new BadRequest("Thiếu thông số id báo cáo.");
196: throw new BadRequest("Không tìm thấy báo cáo với ID: $id");
219: $this->logError("getDrillDownData", $e);
220: throw new Error("Lỗi nạp Drill-Down: " . $e->getMessage());
232: throw new BadRequest("Không tìm thấy báo cáo với ID: $id");
257: $this->logError("runSavedReport", $e);
258: throw new Error("Lỗi thực thi API Báo cáo: " . $e->getMessage());
282: throw new Error("Vượt quá giới hạn API (500 yêu cầu / giờ). Vui lòng thử lại sau.");
302: throw new BadRequest("JSON không hợp lệ ở trường '$fieldName': " . json_last_error_msg());

## custom/Espo/Modules/HybReport/Services/CReportDashboard.php
22: if (!$dashboard) throw new NotFound("Không tìm thấy Báo cáo.");
25: throw new Forbidden("Bạn không có quyền xem báo cáo này.");
48: throw new Forbidden("Báo cáo riêng tư. Chỉ Admin mới được xem.");
52: throw new Forbidden("Bạn không thuộc Team được phân quyền.");

## custom/Espo/Modules/HybReport/Tools/Report/MatrixQueryBuilder.php
41: $tagToMetricIdMap = []; // Bản đồ lưu trữ Tên Tag -> ID thực tế
328: if ($valA === '[Chưa phân bổ]' || $valA === '') return 1;
329: if ($valB === '[Chưa phân bổ]' || $valB === '') return -1;
505: $field = 'Global'; $vStr = 'Tổng hợp';
561: error_log("[HybReport Pivot] Lỗi SQL Unified Tree: " . $e->getMessage());

## custom/Espo/Modules/HybReport/Tools/Report/PayloadParser.php
113: throw new \InvalidArgumentException('Unsafe math expression blocked.');
352: $this->applyFiltersToBuilder($selectBuilder, $filters, $aliases, $dataSources); // [#23 Gap 0]: Apply filters vào Pivot/Chart
488: 'timeMode' => $timeMode, // [#30]: cần biết extract vs continuous để sort đúng
890: $this->applyFiltersToBuilder($selectBuilder, $filters, $aliases, $prunedDataSources); // [#23 Gap 0-Detail]: Apply filters vào Detail Grid
1037: 'continuous', // detail không dùng timeMode
1140: $entity = $revMatch[1]; // Override entity từ pattern
1277: $single   = null; // không dùng single cho range
1299: continue; // Skip: operator cần input nhưng chưa có value
2260: if (isset($seen[$key])) throw new \Espo\Core\Exceptions\BadRequest("Duplicate field: " . $key);
2270: if (isset($seenMetrics[$metricKey])) throw new \Espo\Core\Exceptions\BadRequest("Duplicate metric: " . $metricKey);
