Skip to content

Release sprint 2026-07-29 - #59

Merged
ThienlocTran merged 108 commits into
mainfrom
release/sprint-2026-07-29
Jul 30, 2026
Merged

ThienlocTran merged 108 commits into
mainfrom
release/sprint-2026-07-29

Conversation

@ThienlocTran

@ThienlocTran ThienlocTran commented Jul 29, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Package current sprint frontend changes from dev.
  • Includes merged frontend PRs through T201 loading/empty/error states.

Validation

  • No build/test run by request policy.
  • Release branch and tag pushed: sprint-2026-07-29.

Notes

  • User will merge into main manually.

Summary by CodeRabbit

  • Tính năng mới

    • Bổ sung đầy đủ luồng phiếu xuất kho: danh sách, tạo/sửa, xem chi tiết, lưu nháp, gửi duyệt và hủy.
    • Thêm màn hình phiếu xuất chờ duyệt với phân trang, xem nhanh, duyệt và từ chối kèm lý do.
    • Bổ sung quy trình nhập dữ liệu Excel theo từng bước, kiểm tra lỗi và xác nhận áp dụng.
    • Dashboard và cảnh báo tồn kho nay hiển thị dữ liệu thực tế, bộ lọc và trạng thái tải/lỗi.
  • Cải tiến

    • Chuẩn hóa phân quyền điều hướng, trạng thái và huy hiệu hiển thị.
    • Hỗ trợ lịch sử thao tác cho cả phiếu nhập và phiếu xuất.
    • Bổ sung tài liệu đặc tả, kế hoạch triển khai và kịch bản kiểm thử E2E.

ThienlocTran and others added 30 commits July 7, 2026 16:35
- add export receipt API client for drafts, items, availability and workflow

- implement export receipt create, edit, list and detail screens

- display current stock, line totals and over-stock warnings

- integrate export receipts into level 1 and level 2 approval UI

- load dedicated export approval history instead of import history
Preserve latest dev import workflow, permissions and confirmation dialogs while retaining export receipt list, form, detail, approval and history integration.
…eipt-ui

feat(export-receipt): complete Sprint 3 UI T125-T136
…eipt-ui

fix: update approval level display labels
25ThienNgoc and others added 25 commits July 23, 2026 22:21
…ng-List-UI

commit: T186 Inventory Warning UI
…ng-Filter-UI

commit: T187 Inventory warning filter UI
…t-pending

Feature/t198 inventory widget pending
…cessed-ui

commit: T188 mark viewed processed UI
…severity-badges

commit: T189 warning status badge ui
…ndation-ui

commit: T190 reoder recommendation ui
…rror-states

Feature/t201 loading empty error states
@coderabbitai

coderabbitai Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Luồng phiếu xuất kho

Layer / File(s) Summary
Service và đặc tả nghiệp vụ
docs/README_T121.md, docs/README_T122.md, docs/README_T123.md, docs/README_T124.md, export-receipt-plan.md, src/services/exportReceiptService.js, src/services/stockOutApprovalService.js
Bổ sung đặc tả, API tạo/sửa/lấy/gửi duyệt/hủy và API phê duyệt hoặc từ chối phiếu xuất.
Form và danh sách phiếu xuất
src/components/ExportReceiptForm.vue, src/components/ExportReceiptList.vue, src/views/StockDocumentCreateView.vue, src/views/StockDocumentsView.vue
Triển khai tạo/sửa nháp, kiểm tra tồn kho, lọc/phân trang, xác nhận thao tác và điều hướng theo loại phiếu.
Chi tiết và phê duyệt
src/components/ExportReceiptDetailPanel.vue, src/components/StockOutApprovalDetail.vue, src/views/PendingExportApprovalsView.vue, src/views/StockDocumentDetailView.vue, src/views/ApprovalsView.vue
Hiển thị chi tiết, lý do từ chối, gửi duyệt, hủy, duyệt và từ chối phiếu xuất; thêm các route phê duyệt.
Tích hợp lịch sử và trạng thái
src/components/ImportReceiptHistoryModal.vue, src/components/ImportInspectionPanel.vue, src/components/StatusBadge.vue
Cho phép tải lịch sử theo loại chứng từ và chuẩn hóa nhãn/badge trạng thái.
Phân quyền và thành phần dùng chung
src/services/permissionService.js, src/router/index.js, src/components/AppSidebar.vue, src/components/DataTable.vue
Tập trung kiểm tra quyền route, lọc sidebar và bổ sung tương tác hàng cho DataTable.

Luồng vận hành tồn kho

Layer / File(s) Summary
Cảnh báo tồn kho
src/services/replenishmentService.js, src/views/AlertsView.vue
Thêm API gợi ý bổ sung và màn hình cảnh báo có bộ lọc, phân trang, loading và error state.
Dashboard
src/views/DashboardView.vue
Thay dashboard placeholder bằng dữ liệu KPI, phiếu chờ duyệt, cảnh báo và truy cập nhanh theo quyền.
Import Excel
src/services/excelImportService.js, src/views/ImportExcelView.vue
Triển khai workflow sáu bước từ tải file mẫu đến validate, xác nhận và apply dữ liệu Excel.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive Tiêu đề quá chung chung, chỉ nêu ngày sprint release mà không cho biết thay đổi chính của PR. Đổi sang tiêu đề ngắn nhưng cụ thể, nêu trọng tâm như thêm luồng phiếu xuất, dashboard và các màn hình liên quan.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/sprint-2026-07-29

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 34

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/views/StockDocumentsView.vue (1)

303-304: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Nút "Xem" bị ẩn sai cho Employee ở trạng thái NHAP/TU_CHOI — giờ áp dụng luôn cho phiếu xuất.

canViewImportReceipt trả false khi role không phải MANAGER và hasWorkflowAction(status) đúng (tức khi đã có Sửa/Gửi duyệt/Hủy) → với Employee ở NHAP/TU_CHOI, nút Xem sẽ biến mất. Điều này ngược với docs/README_T124.md TC-UI-A02 (NHAP phải có đủ Sửa/Gửi duyệt/Hủy/Xem) và ExportReceiptList.vue (Xem luôn hiện). Việc gộp template cho cả in/out (bỏ nhánh v-else cũ) khiến bug tồn tại từ trước giờ ảnh hưởng luôn phiếu xuất.

🔧 Đề xuất fix
-function canViewImportReceipt(status) {
-  return currentRole.value === 'MANAGER' || !hasWorkflowAction(status)
-}
+function canViewImportReceipt() {
+  return true
+}
As per path instructions StockSense: "UI phải phản ánh đúng quyền".

Also applies to: 342-342

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/views/StockDocumentsView.vue` around lines 303 - 304, Cập nhật điều kiện
hiển thị nút “Xem” trong template dùng chung của StockDocumentsView để Employee
vẫn luôn thấy nút này ở trạng thái NHAP và TU_CHOI, không bị loại bởi
canViewImportReceipt hoặc hasWorkflowAction. Đồng bộ hành vi với
ExportReceiptList.vue và giữ các quyền Sửa/Gửi duyệt/Hủy hiện có.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/AppSidebar.vue`:
- Around line 9-33: The permission tags in the items definition are unused
because visibleItems delegates access control entirely to canAccessRoute. Remove
the fourth "manage", "approval", or "admin" fields from the affected items,
keeping each item's label, route, and icon unchanged.
- Around line 64-74: Update the .sidebar styles to restore vertical scrolling
for content exceeding the fixed viewport height, using overflow-y: auto while
preserving the existing fixed positioning and layout. Apply the same adjustment
to the corresponding sidebar style block referenced by the additional location.

In `@src/components/DataTable.vue`:
- Around line 2-17: Preserve the minWidth contract in DataTable by declaring and
applying the minWidth prop to the table sizing behavior, rather than allowing it
to fall through to the wrapper. Reuse the existing min-width values passed by
views such as Inventory, CategoryList, and Warehouse; only remove the prop after
updating every caller consistently.

In `@src/components/ExportReceiptDetailPanel.vue`:
- Around line 166-181: Remove the local statusOptions, getStatusLabel, and
getStatusClass mappings from ExportReceiptDetailPanel.vue and reuse the shared
StatusBadge.vue component for status labels and badge classes. Pass the receipt
status through the standardized component so status rendering remains consistent
with other voucher views and future backend status changes require only one
mapping update.
- Around line 67-81: Replace the duplicated role checks in canEdit, canSubmit,
and canCancel at src/components/ExportReceiptDetailPanel.vue:67-81 and
canManageApproval at src/components/StockOutApprovalDetail.vue:34-36 with a
shared permission service/helper, analogous to permissionService.canAccessRoute.
Centralize the allowed-role rules there and have both components reuse it while
preserving their existing status checks and behavior.
- Around line 112-131: Update executeSubmit to call submitExportReceipt instead
of submitForApproval and pass receipt.value.version directly as the version
argument. Ensure the receipt detail response model/spec exposes version when the
backend does not currently return it, preserving the version required for
inventory validation and optimistic locking.

In `@src/components/ExportReceiptForm.vue`:
- Around line 54-78: Update loadWarehouses and loadAvailableProducts to detect
401 responses and redirect the user to /login, matching the existing behavior in
loadReceiptDetails. Preserve the current generic error messages and empty-list
fallback for non-401 failures.
- Around line 295-299: Update the approval submission call in the draft flow to
use the actual `submitExportReceipt` signature and pass `draftRes.version ?? 0`
as the raw second argument, not as a `{ version: ... }` object. Apply the same
correction to the additional call at the referenced location so the backend
receives a scalar version value.
- Around line 1-17: Update the named imports in the ExportReceiptForm setup to
use the service’s actual exports: createExportReceipt, updateExportReceipt,
getExportReceipt, and submitExportReceipt. Replace the outdated createDraft,
updateDraft, getExportReceiptDetails, and submitForApproval references
throughout the component while preserving their existing behavior.
- Around line 120-135: Update onWarehouseChange and proceedChangeWarehouse to
convert the selected warehouse value back to the existing warehouseId type
before assigning it to receipt.warehouseId or
confirmWarehouseChange.newWarehouseId, preserving numeric IDs from the API and
keeping create/edit payloads type-consistent.

In `@src/components/ExportReceiptList.vue`:
- Around line 59-87: Update the finally block in fetchReceipts so it
conditionally sets isLoading.value to false only when requestId matches
fetchReceiptsRequestId, without returning from finally. Preserve the existing
stale-request guard for the loading state while allowing exceptions from the
catch block to propagate.
- Around line 1-9: Update the imports in ExportReceiptList.vue to use the
exported exportReceiptService symbols cancelExportReceipt and
submitExportReceipt instead of cancelDraft and submitForApproval, then update
their usages in the component to match the renamed functions.

In `@src/components/ImportReceiptHistoryModal.vue`:
- Around line 15-18: Update the watcher in ImportReceiptHistoryModal to observe
both receiptId and documentType so changing the document type reloads history
even when the receipt ID is unchanged. Add a request sequence or equivalent
stale-request guard, and only update the history, loading, and error state when
the response belongs to the latest request; preserve the existing watcher
behavior and state transitions otherwise.

In `@src/components/StockOutApprovalDetail.vue`:
- Around line 94-101: Thay thế cơ chế xác nhận native bằng modal custom trong
handleApprove, đồng bộ với luồng openRejectModal/confirmReject và mẫu sử dụng
trong ExportReceiptDetailPanel.vue. Giữ nguyên việc dừng xử lý khi người dùng
hủy, đồng thời tái sử dụng component, state và cơ chế confirm dialog hiện có
thay vì tạo kiểu xác nhận riêng.

In `@src/router/index.js`:
- Line 65: Update the route permission logic used by canAccessRoute so stock-out
create/edit paths are checked before detail routes, and narrow the detail
matcher to exclude create and edit rather than relying on a generic [^/]+
segment. Preserve API-side authorization responsibilities; this change is only
for frontend router behavior.

In `@src/services/excelImportService.js`:
- Around line 6-8: Configure a finite, sufficiently long timeout on the axios
client created in excelImportService.js for file upload requests, or override it
explicitly for the apply and validate operations. Ensure timeout failures
propagate through the existing loading and error handling so the UI does not
remain stuck in the applying spinner.

In `@src/services/exportReceiptService.js`:
- Around line 4-7: Thêm timeout 15 giây vào cấu hình axios client được tạo trong
exportReceiptService, đồng bộ với stockOutApprovalService. Đảm bảo lỗi
timeout/network/HTTP được truyền về luồng gọi để trạng thái loading của các thao
tác Lưu/Gửi duyệt luôn kết thúc và hiển thị lỗi cho người dùng.
- Around line 35-42: Chuẩn hoá API approval xuất giữa exportReceiptService và
stockOutApprovalService bằng cách dùng chung mapping trạng thái, bao gồm
CHO_DUYET_CAP_1 và CHO_DUYET_CAP_2 cùng cách hiển thị CHO_DUYET, đồng thời thống
nhất trường rejectReason. Cập nhật các hàm getPendingExportReceipts,
approveExportReceipt và rejectExportReceipt hoặc service dùng chung tương ứng để
mọi view nhận dữ liệu đã chuẩn hoá.

In `@src/services/permissionService.js`:
- Around line 56-66: Reorder the route checks in canAccessRoute so the
create/edit regex /^\/stock-(in|out)\/(?:create|[^/]+\/edit)$/ is evaluated
before the stock-in and stock-out detail-view rules. Preserve the existing role
lists, ensuring /stock-out/create is restricted to ADMIN and EMPLOYEE rather
than allowing MANAGER.

In `@src/services/stockOutApprovalService.js`:
- Around line 55-64: Update normalizeReceipt to map receipt.details instead of
receipt.items when normalizing export receipt products. Preserve the existing
item-field normalization and fallback behavior, ensuring
getPendingExportApprovalDetail displays the API response’s details list.

In `@src/views/AlertsView.vue`:
- Around line 142-158: Update the warehouse select handler to call
applyWarehouseFilter instead of applyFilter, so changing warehouseId does not
reload dropdowns. Keep applyFilter for warehouse-status changes, including its
loadDropdowns and validation behavior, and ensure both handlers still fetch
alerts appropriately.

In `@src/views/ApprovalsView.vue`:
- Line 14: Unify the approval and rejection flow used by ApprovalsView.vue and
StockOutApprovalDetail.vue by selecting one shared implementation for
approveExportReceipt and rejectExportReceipt. Update the relevant service
imports and callers so both screens use the same normalization and
returned-receipt behavior, while preserving their existing UI updates.
- Around line 58-64: Update the status filter options and handling in
ApprovalsView so selecting “Chờ duyệt” includes both CHO_DUYET_CAP_1 and
CHO_DUYET_CAP_2 when querying the backend. Add the missing CHO_DUYET_CAP_1
option or normalize the selected filter to transmit both statuses, while
preserving the existing statusLabels display text.

In `@src/views/DashboardView.vue`:
- Line 19: Remove the unused isRetrying state and all assignments to it within
loadDashboardData, since retry behavior is already controlled by isLoading and
no template or logic consumes isRetrying.
- Around line 143-146: Replace the duplicate-load guard in loadDashboardData
with a dedicated in-flight flag such as isFetching, separate from the UI-facing
isLoading state. Set and clear this flag around the dashboard data request so
concurrent calls are blocked even when forceReload is true, while preserving
isLoading for display state.
- Around line 260-267: Căn chỉnh điều kiện tải dữ liệu trong luồng chứa
`loadLowStockItems` với điều kiện hiển thị section cảnh báo tại phần dùng
`canSeeAlerts`, để role có quyền `/alerts` nhưng không có `/inventory` vẫn nhận
được dữ liệu đúng. Kiểm tra UI theo các role ADMIN, MANAGER và EMPLOYEE, đồng
thời giữ nguyên trạng thái empty/error cho trường hợp không được phép xem cảnh
báo.
- Around line 83-87: Remove the redundant typeof checks from hasWarningLoadError
and hasStockLoadError in DashboardView.vue, since warningCountFailed and
stockTotalFailed are always-defined refs. Keep each condition based directly on
its ref’s value being true.
- Around line 306-328: Update loadStockTotal to obtain the authoritative
aggregate inventory value from the backend’s standard total-stock field or
dedicated aggregate endpoint, rather than falling back to list-response fields
such as total or page metadata. Preserve the existing finite, non-negative
validation and error propagation, but do not return 0 when the aggregate value
is unavailable; use the backend aggregate contract so the “Tổng tồn” KPI
represents all inventory.
- Around line 268-281: Điều chỉnh khối catch trong luồng tải dữ liệu của
DashboardView để chỉ đặt các cờ pendingImportFailed, pendingExportFailed và
lowStockFailed cho những section mà người dùng hiện tại được phép xem/tải, sử
dụng các điều kiện quyền hiện có. Không bật cờ lỗi cho section bị ẩn theo role,
để hasAnyApiError không hiển thị banner lỗi không liên quan.

In `@src/views/ImportExcelView.vue`:
- Around line 116-133: Update the file validation around the selected file in
the import flow to reject files exceeding the client-side size limit before
upload, set an appropriate file error, clear the selected file, reset the input,
and return. Also update formatFileSize to clamp the calculated unit index to the
available sizes array so sizes[i] is never undefined.
- Around line 405-414: Cập nhật watcher importType để đặt lại
selectedWarehouseId khi người dùng chuyển khỏi PRODUCT_WITH_OPENING_STOCK, bảo
đảm handleCreateSession và handleValidateSession không gửi giá trị kho đã chọn
trước đó; giữ nguyên giá trị khi vẫn ở loại import cần kho.

In `@src/views/StockDocumentCreateView.vue`:
- Around line 98-99: Cập nhật computed `canCancel` trong
`StockDocumentCreateView.vue` để chỉ cho phép hủy khi `receiptStatus.value` là
`NHAP`, loại bỏ `TU_CHOI`; giữ nguyên điều kiện kiểm tra `receiptId.value` và
không thay đổi `canSubmit`.

In `@src/views/StockDocumentDetailView.vue`:
- Around line 29-35: Cập nhật template của StockDocumentDetailView để khi type
=== 'out' thì render ExportReceiptDetailPanel, thay vì luôn dùng
StockOutApprovalDetail. Giữ StockOutApprovalDetail cho luồng quản lý duyệt nhằm
bảo toàn giao diện hiện tại, đồng thời duy trì nhánh hasReceiptId và thông báo
lỗi khi thiếu mã phiếu.

In `@src/views/StockDocumentsView.vue`:
- Around line 63-77: Cập nhật statusOptions trong StockDocumentsView để sử dụng
bộ trạng thái phù hợp với loại phiếu đang xem: loại bỏ CHO_HANG_VE và
CHO_KIEM_HANG khỏi bộ lọc phiếu xuất, đồng thời thêm CHO_DUYET_CAP_1 để có thể
lọc trạng thái này. Giữ statusLabels đồng bộ với các option hiển thị và bảo toàn
các trạng thái dùng chung.

---

Outside diff comments:
In `@src/views/StockDocumentsView.vue`:
- Around line 303-304: Cập nhật điều kiện hiển thị nút “Xem” trong template dùng
chung của StockDocumentsView để Employee vẫn luôn thấy nút này ở trạng thái NHAP
và TU_CHOI, không bị loại bởi canViewImportReceipt hoặc hasWorkflowAction. Đồng
bộ hành vi với ExportReceiptList.vue và giữ các quyền Sửa/Gửi duyệt/Hủy hiện có.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ccb8549e-32f1-44e6-91f4-0ed83933f6f2

📥 Commits

Reviewing files that changed from the base of the PR and between c99a015 and f630ce2.

📒 Files selected for processing (29)
  • docs/README_T121.md
  • docs/README_T122.md
  • docs/README_T123.md
  • docs/README_T124.md
  • docs/superpowers/specs/2026-06-28-import-receipt-ux-ui-design.md
  • export-receipt-plan.md
  • src/components/AppSidebar.vue
  • src/components/DataTable.vue
  • src/components/ExportReceiptDetailPanel.vue
  • src/components/ExportReceiptForm.vue
  • src/components/ExportReceiptList.vue
  • src/components/ImportInspectionPanel.vue
  • src/components/ImportReceiptHistoryModal.vue
  • src/components/StatusBadge.vue
  • src/components/StockOutApprovalDetail.vue
  • src/router/index.js
  • src/services/excelImportService.js
  • src/services/exportReceiptService.js
  • src/services/permissionService.js
  • src/services/replenishmentService.js
  • src/services/stockOutApprovalService.js
  • src/views/AlertsView.vue
  • src/views/ApprovalsView.vue
  • src/views/DashboardView.vue
  • src/views/ImportExcelView.vue
  • src/views/PendingExportApprovalsView.vue
  • src/views/StockDocumentCreateView.vue
  • src/views/StockDocumentDetailView.vue
  • src/views/StockDocumentsView.vue

Comment on lines 9 to +33
const items = [
['Tổng quan', '/dashboard', 'mdi-view-dashboard-outline'],
['Nhân viên', '/employees', 'mdi-account-group-outline', 'admin'],
['Sản phẩm', '/products', 'mdi-package-variant-closed'],
['Nhà cung cấp', '/partners', 'mdi-truck-delivery-outline', 'manage'],
['Danh mục', '/categories', 'mdi-shape-outline'],
['Kho hàng', '/warehouses', 'mdi-warehouse'],
['Tồn kho', '/inventory', 'mdi-clipboard-list-outline'],
['Lịch sử giao dịch', '/inventory-transactions', 'mdi-history'],
['Phiếu nhập kho', '/stock-in', 'mdi-tray-arrow-down', 'stock-in'],
['Phiếu xuất kho', '/stock-out', 'mdi-tray-arrow-up', 'employee'],
['Chờ duyệt', '/approvals', 'mdi-check-decagram-outline', 'approval'],
['Import Excel', '/import-excel', 'mdi-file-excel-outline'],
['Cảnh báo tồn kho', '/alerts', 'mdi-alert-outline'],
]
const authStore = useAuthStore()
const currentRole = computed(() => normalizeRole(authStore.currentRole))
const visibleItems = computed(() => items.filter(item => {
const role = currentRole.value
if (item[3] === 'admin') return canManageEmployees(role)
if (item[3] === 'manage') return role === 'ADMIN' || role === 'MANAGER'
if (item[3] === 'approval') return role === 'ADMIN' || role === 'MANAGER'
if (item[3] === 'stock-in') return role === 'ADMIN' || role === 'MANAGER' || role === 'EMPLOYEE'
if (item[3] === 'employee') return role === 'ADMIN' || role === 'EMPLOYEE'
return true
}))
["Tổng quan", "/dashboard", "mdi-view-dashboard-outline"],
["Sản phẩm", "/products", "mdi-package-variant-closed"],
["Nhà cung cấp", "/partners", "mdi-truck-delivery-outline", "manage"],
["Danh mục", "/categories", "mdi-shape-outline"],
["Kho hàng", "/warehouses", "mdi-warehouse"],
["Tồn kho", "/inventory", "mdi-clipboard-list-outline"],
["Lịch sử giao dịch", "/inventory-transactions", "mdi-history"],
["Phiếu nhập kho", "/stock-in", "mdi-tray-arrow-down"],
["Phiếu xuất kho", "/stock-out", "mdi-tray-arrow-up"],
["Chờ duyệt", "/approvals", "mdi-check-decagram-outline", "approval"],
[
"Phiếu xuất chờ duyệt",
"/pending-export-approvals",
"mdi-file-clock-outline",
"approval",
],
["Import Excel", "/import-excel", "mdi-file-excel-outline", "admin"],
["Cảnh báo tồn kho", "/alerts", "mdi-alert-outline"],
["Nhân viên", "/employees", "mdi-account-group-outline", "admin"],
["Nhân viên & phân quyền", "/users", "mdi-account-cog-outline", "admin"],
];
const visibleItems = computed(() =>
items.filter((item) => canAccessRoute(item[1], currentRole.value)),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Tag quyền ("manage", "approval", "admin") trong items không được dùng.

visibleItems chỉ filter bằng canAccessRoute(item[1], currentRole.value) — phần tử thứ 4 của mỗi item hoàn toàn không được đọc. Không gây lỗi hiện tại (vì canAccessRoute tự xử lý đúng theo path), nhưng dễ gây hiểu lầm cho người sau tưởng tag này điều khiển quyền.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/AppSidebar.vue` around lines 9 - 33, The permission tags in
the items definition are unused because visibleItems delegates access control
entirely to canAccessRoute. Remove the fourth "manage", "approval", or "admin"
fields from the affected items, keeping each item's label, route, and icon
unchanged.

Comment on lines +64 to +74
.sidebar {
position: fixed;
inset: 0 auto 0 0;
width: 260px;
background: #0f172a;
color: #e5e7eb;
padding: 18px 14px;
display: flex;
flex-direction: column;
z-index: 20;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Sidebar bỏ overflow/scroll, danh sách nav lại dài hơn (14 mục) — có thể tràn trên màn hình thấp.

.sidebar dùng position: fixed; inset: 0 auto 0 0 (chiều cao = viewport) nhưng không còn overflow-y: auto. Với role ADMIN thấy đủ 14 mục nav + brand + note, trên viewport thấp (laptop nhỏ) các mục cuối (vd "Nhân viên & phân quyền") có thể bị đẩy ra ngoài vùng nhìn thấy mà không cuộn được.

🔧 Fix đề xuất
 .sidebar {
   position: fixed;
   inset: 0 auto 0 0;
   width: 260px;
   background: `#0f172a`;
   color: `#e5e7eb`;
   padding: 18px 14px;
   display: flex;
   flex-direction: column;
+  overflow-y: auto;
   z-index: 20;
 }

Also applies to: 103-107

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/AppSidebar.vue` around lines 64 - 74, Update the .sidebar
styles to restore vertical scrolling for content exceeding the fixed viewport
height, using overflow-y: auto while preserving the existing fixed positioning
and layout. Apply the same adjustment to the corresponding sidebar style block
referenced by the additional location.

Comment on lines +2 to +17
const props = defineProps({
columns: { type: Array, required: true },
rows: { type: Array, required: true },
emptyText: { type: String, default: 'Chưa có dữ liệu' },
minWidth: { type: String, default: '920px' },
})
emptyText: { type: String, default: "Chưa có dữ liệu" },
clickable: { type: Boolean, default: false },
});

const emit = defineEmits(["row-click"]);
</script>

<template>
<div class="table-wrap card">
<table class="data-table" :style="{ minWidth }">
<table class="data-table">
<thead>
<tr>
<th v-for="column in columns" :key="column.key" :class="column.class">
<th v-for="column in columns" :key="column.key">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Kiểm tra các view khác có dùng column.class hoặc :min-width với DataTable không
rg -n "class:" -g '*.vue' -B2 -A2 src/views src/components | rg -B3 -A1 "columns"
rg -n "min-width|minWidth" -g '*.vue' src/views src/components

Repository: ThienlocTran/sme-stocksense-frontend

Length of output: 4002


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== DataTable.vue outline/lines =="
wc -l src/components/DataTable.vue
sed -n '1,120p' src/components/DataTable.vue

echo
echo "== all DataTable usages =="
rg -n "DataTable|min-width|column\.class|\.class:" src/views src/components | sed -n '1,220p'

echo
echo "== relevant view column definitions =="
for f in src/views/InventoryTransactionsView.vue src/views/InventoryView.vue src/views/AlertsView.vue src/views/categories/CategoryListView.vue src/views/WarehousesView.vue src/views/employees/EmployeeListView.vue; do
  echo "--- $f ---"
  sed -n '1,70p' "$f" || true
  echo "--- class uses in $f ---"
  rg -n "\bclass\b|\bcolumn\.class\b|employee-actions-column|cell-compact" "$f" || true
done

echo
echo "== stylesheet definitions for related classes =="
rg -n "\.employee-actions-column|\.cell-compact|\.employee-name-column|\.employee-email-column|\.employee-date-column|table-wrap|data-table" src -g '*.{vue,css}'

Repository: ThienlocTran/sme-stocksense-frontend

Length of output: 47999


Không nên bỏ minWidth khi nhiều view đang truyền nó xuống DataTable.

Những view như Inventory, InventoryTransactions, CategoryList, Warehouse, Alert, Employee... đang truyền min-width="..." vào DataTable; khi DataTable đổi contract không nhận minWidth, những size này sẽ thành attribute rơi vào wrapper và không áp dụng đúng như trước. Nếu muốn bỏ nữa thì cần cập nhật/clear những chỗ gọi này.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/DataTable.vue` around lines 2 - 17, Preserve the minWidth
contract in DataTable by declaring and applying the minWidth prop to the table
sizing behavior, rather than allowing it to fall through to the wrapper. Reuse
the existing min-width values passed by views such as Inventory, CategoryList,
and Warehouse; only remove the prop after updating every caller consistently.

Comment on lines +67 to +81
function canEdit() {
if (!receipt.value) return false
if (!['ADMIN', 'EMPLOYEE'].includes(currentRole.value)) return false
return receipt.value.status === 'NHAP' || receipt.value.status === 'TU_CHOI'
}

function canSubmit() {
return canEdit()
}

function canCancel() {
if (!receipt.value) return false
if (!['ADMIN', 'EMPLOYEE'].includes(currentRole.value)) return false
return receipt.value.status === 'NHAP'
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Phân quyền hardcode lặp lại ở nhiều component thay vì dùng service chung. Cả 2 nơi tự check mảng role bằng tay ngay trong component, giống cách route guard cũ trước khi được gom về permissionService.canAccessRoute.

  • src/components/ExportReceiptDetailPanel.vue#L67-L81: canEdit/canSubmit/canCancel tự check ['ADMIN', 'EMPLOYEE'].
  • src/components/StockOutApprovalDetail.vue#L34-L36: canManageApproval tự check ['ADMIN', 'MANAGER'].

Nên gom vào 1 hàm/service dùng chung (tương tự permissionService) để tránh 2 nơi lệch nhau khi quy tắc phân quyền thay đổi.

📍 Affects 2 files
  • src/components/ExportReceiptDetailPanel.vue#L67-L81 (this comment)
  • src/components/StockOutApprovalDetail.vue#L34-L36
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ExportReceiptDetailPanel.vue` around lines 67 - 81, Replace
the duplicated role checks in canEdit, canSubmit, and canCancel at
src/components/ExportReceiptDetailPanel.vue:67-81 and canManageApproval at
src/components/StockOutApprovalDetail.vue:34-36 with a shared permission
service/helper, analogous to permissionService.canAccessRoute. Centralize the
allowed-role rules there and have both components reuse it while preserving
their existing status checks and behavior.

Source: Path instructions

Comment on lines +112 to +131
async function executeSubmit() {
actionState.isProcessing = true
actionState.currentAction = 'submit'
actionMessage.value = ''
actionErrorMessage.value = ''

try {
await submitForApproval(receipt.value.id, { version: receipt.value.version })
actionMessage.value = 'Gửi duyệt phiếu xuất thành công.'
// Tự động reload data để cập nhật Badge Status (ẩn nút thao tác)
await loadReceiptDetails()
} catch (err) {
actionErrorMessage.value = err.message || 'Lỗi khi gửi duyệt.'
// Hết phiên -> redirect về login
if (err.status === 401) router.replace('/login')
} finally {
actionState.isProcessing = false
actionState.currentAction = ''
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "version" src/services/exportReceiptService.js

Repository: ThienlocTran/sme-stocksense-frontend

Length of output: 352


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
git ls-files | rg '(README_T123\.md|spec.*T122|T122|exportReceiptService\.js|ExportReceiptDetailPanel\.vue)' || true

echo
echo "== service outline and relevant sections =="
wc -l src/services/exportReceiptService.js
cat -n src/services/exportReceiptService.js

echo
echo "== panel relevant sections =="
wc -l src/components/ExportReceiptDetailPanel.vue
sed -n '1,180p' src/components/ExportReceiptDetailPanel.vue | cat -n

echo
echo "== version mentions in relevant docs/spec files =="
for f in $(git ls-files | rg '(README_T123\.md|T122|spec|docs|README' || true); do
  if rg -q "version|T122|getExportReceiptDetails|submitForApproval|export-receipts|Submit|SubmitForApproval|2\.2|phiển|phiên|optimistic" "$f"; then
    echo "--- $f ---"
    rg -n -C 2 "version|T122|getExportReceiptDetails|submitForApproval|export-receipts|SubmitForApproval|2\.2|phiển|phiên|optimistic" "$f" || true
  fi
done

Repository: ThienlocTran/sme-stocksense-frontend

Length of output: 10197


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== exportReceiptService aliases around getExportReceiptDetails/submitForApproval =="
rg -n "getExportReceiptDetails|submitForApproval" src/services/exportReceiptService.js

echo
echo "== T122 relevant sections =="
sed -n '1,260p' docs/README_T122.md | cat -n

echo
echo "== T123 relevant sections =="
sed -n '1,260p' docs/README_T123.md | cat -n

echo
echo "== behavioral probe for current component/service call shape =="
node - <<'JS'
const versionInReceipt = undefined
const expectedPayloadForService = (receiptVersion) => ({ version: { version: receiptVersion } })
const actualSentBySubmitForApproval = (receiptVersion) => ({ version: receiptVersion })
console.log('current versionInReceipt:', versionInReceipt)
console.log('current send object:', actualSentBySubmitForApproval(versionInReceipt))
console.log('current JSON omitted version:', JSON.stringify(actualSentBySubmitForApproval(versionInReceipt)))
console.log('expecting service to receive object in version parameter:', expectedPayloadForService(versionInReceipt))
JS

Repository: ThienlocTran/sme-stocksense-frontend

Length of output: 258


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== all aliases in exportReceiptService.js =="
sed -n '1,90p' src/services/exportReceiptService.js | cat -n

echo
echo "== T122 relevant sections =="
sed -n '1,260p' docs/README_T122.md | cat -n

echo
echo "== T123 relevant sections =="
sed -n '1,260p' docs/README_T123.md | cat -n

echo
echo "== current component call-shape probe =="
node - <<'JS'
const currentPayload = { version: undefined }
const serviceReceiveShape = (id, version) => `id=${id}, version=${version}, payload=${JSON.stringify({ version })}`

console.log('current executeSubmit payload:', JSON.stringify(currentPayload))
console.log('submitForApproval currently receives:', serviceReceiveShape(1, currentPayload))
console.log('submitExportReceipt service receives:', serviceReceiveShape(1, currentPayload.version))
JS

Repository: ThienlocTran/sme-stocksense-frontend

Length of output: 10084


Cần giữ version khi gửi duyệt phiều xuất.

executeSubmit hiện đang gọi sai hàm: truyền { version: receipt.value.version } vào submitForApproval, nhưng hàm thực tế là submitExportReceipt chỉ nhận version trực tiếp. Nếu backend không gửi lại version ở API chi tiết, thì đây cũng cần thêm vào response model/spec, vì T122 yêu cầu backend kiểm tồn kho/optimistic lock trước khi duyệt.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ExportReceiptDetailPanel.vue` around lines 112 - 131, Update
executeSubmit to call submitExportReceipt instead of submitForApproval and pass
receipt.value.version directly as the version argument. Ensure the receipt
detail response model/spec exposes version when the backend does not currently
return it, preserving the version required for inventory validation and
optimistic locking.

Comment on lines +116 to +133
const file = files[0]
if (!file.name.toLowerCase().endsWith('.xlsx')) {
fileError.value = 'Chỉ chấp nhận file định dạng .xlsx'
selectedFile.value = null
event.target.value = ''
return
}

selectedFile.value = file
}

function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes'
const k = 1024
const sizes = ['Bytes', 'KB', 'MB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Nên chặn file quá lớn ngay ở client.

Hiện chỉ kiểm tra đuôi .xlsx. File hàng chục MB sẽ upload rồi mới bị backend từ chối (thường 413/multipart limit) — người dùng chờ lâu và nhận thông báo khó hiểu. Thêm ngưỡng dung lượng, và clamp i trong formatFileSize để tránh sizes[i] là undefined.

🔧 Đề xuất patch nhỏ
   const file = files[0]
   if (!file.name.toLowerCase().endsWith('.xlsx')) {
     fileError.value = 'Chỉ chấp nhận file định dạng .xlsx'
     selectedFile.value = null
     event.target.value = ''
     return
   }
+
+  const MAX_FILE_SIZE = 10 * 1024 * 1024
+  if (file.size > MAX_FILE_SIZE) {
+    fileError.value = 'File vượt quá dung lượng cho phép (10MB).'
+    selectedFile.value = null
+    event.target.value = ''
+    return
+  }
 
   selectedFile.value = file
 }
 
 function formatFileSize(bytes) {
-  if (bytes === 0) return '0 Bytes'
+  if (!bytes) return '0 Bytes'
   const k = 1024
-  const sizes = ['Bytes', 'KB', 'MB']
-  const i = Math.floor(Math.log(bytes) / Math.log(k))
+  const sizes = ['Bytes', 'KB', 'MB', 'GB']
+  const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1)
   return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
 }

As per path instructions, "Kiểm tra loading, empty state, error state, form validation".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const file = files[0]
if (!file.name.toLowerCase().endsWith('.xlsx')) {
fileError.value = 'Chỉ chấp nhận file định dạng .xlsx'
selectedFile.value = null
event.target.value = ''
return
}
selectedFile.value = file
}
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes'
const k = 1024
const sizes = ['Bytes', 'KB', 'MB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
const file = files[0]
if (!file.name.toLowerCase().endsWith('.xlsx')) {
fileError.value = 'Chỉ chấp nhận file định dạng .xlsx'
selectedFile.value = null
event.target.value = ''
return
}
const MAX_FILE_SIZE = 10 * 1024 * 1024
if (file.size > MAX_FILE_SIZE) {
fileError.value = 'File vượt quá dung lượng cho phép (10MB).'
selectedFile.value = null
event.target.value = ''
return
}
selectedFile.value = file
}
function formatFileSize(bytes) {
if (!bytes) return '0 Bytes'
const k = 1024
const sizes = ['Bytes', 'KB', 'MB', 'GB']
const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1)
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/views/ImportExcelView.vue` around lines 116 - 133, Update the file
validation around the selected file in the import flow to reject files exceeding
the client-side size limit before upload, set an appropriate file error, clear
the selected file, reset the input, and return. Also update formatFileSize to
clamp the calculated unit index to the available sizes array so sizes[i] is
never undefined.

Source: Path instructions

Comment on lines +405 to +414
<div v-if="importType === 'PRODUCT_WITH_OPENING_STOCK'" class="field">
<label>Kho nhận hàng đầu kỳ</label>
<select v-model="selectedWarehouseId" class="select" :disabled="isLoadingWarehouses || isCreatingSession || isValidating || isConfirming || isApplying || applyResult">
<option value="">-- Chọn kho hàng nhận tồn đầu kỳ --</option>
<option v-for="w in warehouses" :key="w.id" :value="w.id">{{ w.tenKho }} ({{ w.maKho }})</option>
</select>
<small class="text-slate-400 mt-1 block">Có thể để trống nếu file tồn đầu kỳ đã có mã kho theo từng dòng.</small>
<small v-if="isLoadingWarehouses" class="text-slate-400">Đang tải danh sách kho hàng...</small>
<small v-if="warehouseError" class="text-red-600 font-semibold mt-1 block">{{ warehouseError }}</small>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

selectedWarehouseId không được xóa khi đổi lại importType.

Select kho chỉ hiển thị với PRODUCT_WITH_OPENING_STOCK, nhưng khi người dùng chọn kho rồi đổi về PRODUCT_ONLY, giá trị cũ vẫn còn trong state và vẫn được gửi lên ở handleCreateSession (Line 205) / handleValidateSession (Line 234) dù UI không còn hiển thị. Nên clear trong watcher importType.

🔧 Patch nhỏ
-watch([importType, selectedWarehouseId], () => {
+watch(importType, () => {
+  if (importType.value !== 'PRODUCT_WITH_OPENING_STOCK') {
+    selectedWarehouseId.value = ''
+  }
+})
+
+watch([importType, selectedWarehouseId], () => {
   if (importId.value) {
     resetWorkflowState('Cấu hình import đã thay đổi, vui lòng khởi tạo phiên và validate lại từ đầu.')
   }
 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/views/ImportExcelView.vue` around lines 405 - 414, Cập nhật watcher
importType để đặt lại selectedWarehouseId khi người dùng chuyển khỏi
PRODUCT_WITH_OPENING_STOCK, bảo đảm handleCreateSession và handleValidateSession
không gửi giá trị kho đã chọn trước đó; giữ nguyên giá trị khi vẫn ở loại import
cần kho.

Comment on lines +98 to +99
const canSubmit = computed(() => receiptId.value && ['NHAP', 'TU_CHOI'].includes(receiptStatus.value) && hasValidItems.value)
const canCancel = computed(() => receiptId.value && ['NHAP', 'TU_CHOI'].includes(receiptStatus.value))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

canCancel cho phép huỷ cả phiếu TU_CHOI, sai với spec và màn hình danh sách.

Theo docs/README_T124.md (TC-UI-A02): trạng thái TU_CHOI chỉ có Sửa/Gửi duyệt lại/Xem, không có Hủy. ExportReceiptList.vue's canCancelExportReceipt cũng chỉ cho phép NHAP. Ở đây canCancel include cả TU_CHOI → 2 màn hình cho cùng 1 phiếu sẽ hiện nút khác nhau.

🔧 Đề xuất fix
-const canCancel = computed(() => receiptId.value && ['NHAP', 'TU_CHOI'].includes(receiptStatus.value))
+const canCancel = computed(() => receiptId.value && receiptStatus.value === 'NHAP')
As per path instructions StockSense: "chỉ phiếu hoàn thành mới làm thay đổi tồn kho; ... UI phải phản ánh đúng quyền" — hành vi hủy cần nhất quán giữa các màn hình.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const canSubmit = computed(() => receiptId.value && ['NHAP', 'TU_CHOI'].includes(receiptStatus.value) && hasValidItems.value)
const canCancel = computed(() => receiptId.value && ['NHAP', 'TU_CHOI'].includes(receiptStatus.value))
const canSubmit = computed(() => receiptId.value && ['NHAP', 'TU_CHOI'].includes(receiptStatus.value) && hasValidItems.value)
const canCancel = computed(() => receiptId.value && receiptStatus.value === 'NHAP')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/views/StockDocumentCreateView.vue` around lines 98 - 99, Cập nhật
computed `canCancel` trong `StockDocumentCreateView.vue` để chỉ cho phép hủy khi
`receiptStatus.value` là `NHAP`, loại bỏ `TU_CHOI`; giữ nguyên điều kiện kiểm
tra `receiptId.value` và không thay đổi `canSubmit`.

Source: Path instructions

Comment on lines +29 to +35
<StockOutApprovalDetail v-if="hasReceiptId" :receiptId="id" />
<div v-else class="card card-pad">
<p class="form-alert form-alert-error mb-0">Không tìm thấy mã phiếu xuất để tải chi tiết.</p>
</div>
</template>
</div>
</template>
</template> No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "ExportReceiptDetailPanel" -g '*.vue' -g '*.js'

Repository: ThienlocTran/sme-stocksense-frontend

Length of output: 174


🏁 Script executed:

#!/bin/bash
set -e
echo "== files =="
git ls-files | rg '(^src/views/|StockDocumentDetailView|ExportReceiptDetailPanel|StockOutApprovalDetail|README_T123|PendingExportApprovalsView)' || true

echo
echo "== relevant occurrences =="
rg -n "ExportReceiptDetailPanel|StockOutApprovalDetail|StockDocumentDetailView|PendingExportApprovalsView|stock-out|pending export|CHO_DUYET|Duyệt|Hủy|Sửa|Gửi duyệt" src . --glob '!node_modules' || true

echo
echo "== StockDocumentDetailView outline/section =="
wc -l src/views/StockDocumentDetailView.vue 2>/dev/null || true
sed -n '1,120p' src/views/StockDocumentDetailView.vue

echo
echo "== pending approvals view relevant section =="
file="$(git ls-files | rg 'PendingExportApprovalsView\.vue' | head -n1)"
[ -n "$file" ] && { wc -l "$file"; sed -n '1,180p' "$file"; }

Repository: ThienlocTran/sme-stocksense-frontend

Length of output: 39690


Gắn ExportReceiptDetailPanel vào route chi tiết phiếu xuất.

/stock-out/:id là route dùng cho cả việc nhân viên xem chi tiết phiếu xuất và manager duyệt, nhưng mọi link hiện tại đều render StockOutApprovalDetail. Kết quả là route này chỉ hiển thị hành động duyệt/phê duyệt, trong khi luồng T123 yêu cầu nhân viên thấy nút Sửa/Gửi duyệt/Hủy ngay từ chi tiết. Cần gọi <ExportReceiptDetailPanel /> cho type === 'out', và để luồng duyệt quản lý không bị phá giao diện nếu muốn vẫn dùng cùng route.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/views/StockDocumentDetailView.vue` around lines 29 - 35, Cập nhật
template của StockDocumentDetailView để khi type === 'out' thì render
ExportReceiptDetailPanel, thay vì luôn dùng StockOutApprovalDetail. Giữ
StockOutApprovalDetail cho luồng quản lý duyệt nhằm bảo toàn giao diện hiện tại,
đồng thời duy trì nhánh hasReceiptId và thông báo lỗi khi thiếu mã phiếu.

Comment on lines 63 to 77
const statusOptions = [
{ value: 'NHAP', label: 'Nháp' },
{ value: 'CHO_DUYET_CAP_1', label: 'Chờ quản lý duyệt' },
{ value: 'CHO_DUYET_CAP_2', label: 'Chờ quản lý duyệt' },
{ value: 'CHO_DUYET_CAP_2', label: 'Chờ duyệt' },
{ value: 'CHO_HANG_VE', label: 'Chờ hàng về' },
{ value: 'CHO_KIEM_HANG', label: 'Chờ kiểm hàng' },
{ value: 'HOAN_THANH', label: 'Hoàn thành' },
{ value: 'TU_CHOI', label: 'Từ chối' },
{ value: 'HUY', label: 'Hủy' },
]

const statusLabels = Object.fromEntries(statusOptions.map(status => [status.value, status.label]))
const statusLabels = {
...Object.fromEntries(statusOptions.map(status => [status.value, status.label])),
CHO_DUYET_CAP_1: 'Chờ duyệt',
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

statusOptions dùng chung cho cả nhập/xuất, sai bộ lọc khi xem phiếu xuất.

Danh sách gồm CHO_HANG_VE, CHO_KIEM_HANG (chỉ có ở phiếu nhập) và thiếu CHO_DUYET_CAP_1 (trạng thái thực tế của phiếu xuất, theo ExportReceiptList.vue). Khi xem phiếu xuất, dropdown lọc sẽ hiện sai option và không thể lọc theo "Chờ duyệt cấp 1".

🔧 Đề xuất fix
-const statusOptions = [
+const statusOptions = computed(() => isOut.value ? [
+  { value: 'NHAP', label: 'Nháp' },
+  { value: 'CHO_DUYET_CAP_1', label: 'Chờ duyệt' },
+  { value: 'CHO_DUYET_CAP_2', label: 'Chờ duyệt' },
+  { value: 'HOAN_THANH', label: 'Hoàn thành' },
+  { value: 'TU_CHOI', label: 'Từ chối' },
+  { value: 'HUY', label: 'Hủy' },
+] : [
   { value: 'NHAP', label: 'Nháp' },
   { value: 'CHO_DUYET_CAP_2', label: 'Chờ duyệt' },
   { value: 'CHO_HANG_VE', label: 'Chờ hàng về' },
   { value: 'CHO_KIEM_HANG', label: 'Chờ kiểm hàng' },
   { value: 'HOAN_THANH', label: 'Hoàn thành' },
   { value: 'TU_CHOI', label: 'Từ chối' },
   { value: 'HUY', label: 'Hủy' },
-]
+])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/views/StockDocumentsView.vue` around lines 63 - 77, Cập nhật
statusOptions trong StockDocumentsView để sử dụng bộ trạng thái phù hợp với loại
phiếu đang xem: loại bỏ CHO_HANG_VE và CHO_KIEM_HANG khỏi bộ lọc phiếu xuất,
đồng thời thêm CHO_DUYET_CAP_1 để có thể lọc trạng thái này. Giữ statusLabels
đồng bộ với các option hiển thị và bảo toàn các trạng thái dùng chung.

@ThienlocTran
ThienlocTran merged commit f4cf220 into main Jul 30, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants