Repository navigation
Release sprint 2026-07-29 - #59
Conversation
T164 ExcelImport admin UI
- 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
…dashboard auth error
…s, and dashboard error state flags
…ng-List-UI commit: T186 Inventory Warning UI
…ng-Filter-UI commit: T187 Inventory warning filter UI
…d-ui commit: T196 layout dashboard UI
…ards Feature/t197 inventory kpi cards
…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
Feature/t199 low stock table UI
…mpty-error-states
Feature/t200 quick access UI
…mpty-error-states
…rror-states Feature/t201 loading empty error states
📝 WalkthroughWalkthroughChangesLuồng phiếu xuất kho
Luồng vận hành tồn kho
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 winNú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.
canViewImportReceipttrảfalsekhi 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ớidocs/README_T124.mdTC-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ánhv-elsecũ) khiến bug tồn tại từ trước giờ ảnh hưởng luôn phiếu xuất.As per path instructions StockSense: "UI phải phản ánh đúng quyền".🔧 Đề xuất fix
-function canViewImportReceipt(status) { - return currentRole.value === 'MANAGER' || !hasWorkflowAction(status) -} +function canViewImportReceipt() { + return true +}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
📒 Files selected for processing (29)
docs/README_T121.mddocs/README_T122.mddocs/README_T123.mddocs/README_T124.mddocs/superpowers/specs/2026-06-28-import-receipt-ux-ui-design.mdexport-receipt-plan.mdsrc/components/AppSidebar.vuesrc/components/DataTable.vuesrc/components/ExportReceiptDetailPanel.vuesrc/components/ExportReceiptForm.vuesrc/components/ExportReceiptList.vuesrc/components/ImportInspectionPanel.vuesrc/components/ImportReceiptHistoryModal.vuesrc/components/StatusBadge.vuesrc/components/StockOutApprovalDetail.vuesrc/router/index.jssrc/services/excelImportService.jssrc/services/exportReceiptService.jssrc/services/permissionService.jssrc/services/replenishmentService.jssrc/services/stockOutApprovalService.jssrc/views/AlertsView.vuesrc/views/ApprovalsView.vuesrc/views/DashboardView.vuesrc/views/ImportExcelView.vuesrc/views/PendingExportApprovalsView.vuesrc/views/StockDocumentCreateView.vuesrc/views/StockDocumentDetailView.vuesrc/views/StockDocumentsView.vue
| 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)), | ||
| ); |
There was a problem hiding this comment.
📐 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.
| .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; | ||
| } |
There was a problem hiding this comment.
🎯 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.
| 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"> |
There was a problem hiding this comment.
🗄️ 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/componentsRepository: 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.
| 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' | ||
| } |
There was a problem hiding this comment.
📐 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/canCanceltự check['ADMIN', 'EMPLOYEE'].src/components/StockOutApprovalDetail.vue#L34-L36:canManageApprovaltự 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
| 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 = '' | ||
| } | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n "version" src/services/exportReceiptService.jsRepository: 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
doneRepository: 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))
JSRepository: 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))
JSRepository: 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.
| 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] | ||
| } |
There was a problem hiding this comment.
🎯 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.
| 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
| <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> |
There was a problem hiding this comment.
🎯 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.
| const canSubmit = computed(() => receiptId.value && ['NHAP', 'TU_CHOI'].includes(receiptStatus.value) && hasValidItems.value) | ||
| const canCancel = computed(() => receiptId.value && ['NHAP', 'TU_CHOI'].includes(receiptStatus.value)) |
There was a problem hiding this comment.
🎯 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')📝 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.
| 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
| <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 |
There was a problem hiding this comment.
🎯 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.
| 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', | ||
| } | ||
|
|
There was a problem hiding this comment.
🎯 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.
Summary
Validation
Notes
Summary by CodeRabbit
Tính năng mới
Cải tiến