Skip to content

fix(catalog,orders,offers): resolve listings by id, unify uploads - #17

Merged
beanbocchi merged 1 commit into
mainfrom
fix/catalog-orders-offers-upload
Aug 11, 2026
Merged

fix(catalog,orders,offers): resolve listings by id, unify uploads#17
beanbocchi merged 1 commit into
mainfrom
fix/catalog-orders-offers-upload

Conversation

@beanbocchi

Copy link
Copy Markdown
Member

Vì sao sản phẩm hiện "không còn trong danh mục"

GET /listings?ids=style: form, explode: false. Dio mặc định gửi ngược lại (ids=a&ids=b&ids=c) và server đọc một giá trị cho một khoá, nên nó trả về đúng listing đầu tiên. Lịch sử đơn resolve cả trang bằng một lượt gọi đó → đơn ba dòng về với hai dòng không có listing → placeholder Sản phẩm không còn trong danh mục cho sản phẩm vẫn còn nguyên.

Kiểm chứng trên API thật:

Dạng gửi Trả về
ids=a,b,c 3 listing
ids=a&ids=b&ids=c 1 listing

Sửa bằng listFormat: ListFormat.csvBaseOptions — một chỗ cho cả ids, /tags?near, /categories?near, là toàn bộ array query param của API này.

Kèm theo: fallback offline của catalog đọc một khoá cached_listings toàn cục, ghi bởi lượt duyệt thành công gần nhất và phát lại cho lượt duyệt hỏng kế tiếp — một trang danh mục nhận 500 lại vẽ sản phẩm của danh mục khác. Giờ cache khoá theo bộ lọc và chỉ đứng ra thay khi request không tới được server.

Các thay đổi khác

  • Xác nhận đã nhận hàng: nút bị vô hiệu hoá câm khi chưa chọn ảnh → giờ luôn bấm được và nói rõ lý do; thêm nút vào cả màn chi tiết đơn.
  • Yêu cầu hoàn tiền: từ menu ba chấm xuống thanh dưới, chỉ hiện khi server thật sự nhận; đang có vụ mở thì dẫn tới vụ đó.
  • Thương lượng: chặn giá cao hơn giá niêm yết ở cả hai màn nhập giá. Cần shopnexus/server#feat-offer-not-above-asking.
  • Upload: ResourceUploader gộp ba bước cho cả năm module và giữ lại Resource của bước confirm — signed url chỉ được đưa ra đúng một lần, và bảy chỗ chép tay trước đây đều vứt nó đi. ImageUploadField là dải ảnh dùng chung, có thử lại từng ảnh. Spec: docs/superpowers/specs/2026-08-11-unified-resource-upload-design.md.
  • CI: 46 bài đỏ → 268 pass, flutter analyze sạch. Không bài nào là lỗi mới — fixture Order thiếu refund, còn lại là test mô tả những màn đã được thiết kế lại.

Lưu ý khi review

  • Diff lớn phần lớn là .g.dart chạy lại bằng Dart 3.12 (cú pháp mới của json_serializable). Code tay nằm ở lib/core/upload/, lib/core/network/dio_client.dart, lib/features/catalog/data/repositories/catalog_repository.dart, lib/features/account/.
  • ImageUploadField chưa có test — đã thử và bỏ vì widget đọc file thật rồi đi qua Dio; lý do ghi trong spec.
  • kyc_card_picker_widgetlisting_suggestion_screen giữ UI riêng (một cái là ba ô có nhãn, một cái gắn với luồng gợi ý AI) nhưng đã dùng chung ResourceUploader.

🤖 Generated with Claude Code

**"Sản phẩm không còn trong danh mục" cho sản phẩm vẫn còn**

`GET /listings?ids=` là `style: form, explode: false` — một tham số mang một
danh sách nối bằng dấu phẩy — và mọi array query param của API này đều thế.
Dio mặc định gửi ngược lại, `ids=a&ids=b&ids=c`, còn server đọc một giá trị
cho một khoá: nó trả về đúng listing đầu tiên. Lịch sử đơn resolve cả trang
bằng một lượt gọi đó, nên đơn ba dòng về với hai dòng không có listing — và
một dòng không có listing hiện đúng câu trên. `ListFormat.csv` đặt ở
`BaseOptions`, một chỗ cho cả ba tham số (`ids`, `/tags?near`,
`/categories?near`).

Fallback offline của catalog cũng trả nhầm: nó đọc một khoá `cached_listings`
toàn cục, ghi bởi lượt duyệt nào thành công gần nhất và phát lại cho lượt duyệt
nào hỏng kế tiếp — nên một trang danh mục nhận 500 lại vẽ sản phẩm của danh mục
khác, và vẽ chúng trần trụi vì bản cache dựng từ `RecentListing` vốn không mang
`category_id`. Giờ cache khoá theo bộ lọc, giữ nguyên `Listing`, và chỉ đứng ra
thay khi request không tới được server — 400/500 nổi lên như lỗi của chúng.

**Xác nhận đã nhận hàng**

Nút "Xác nhận" bị vô hiệu hoá khi chưa chọn ảnh, không kèm câu nào: bấm vào
không có gì xảy ra, không request nào rời máy. Giờ nút luôn bấm được và trả
lời. Màn chi tiết đơn cũng có nút này — trước đây phải quay lại danh sách mới
xác nhận được.

**Yêu cầu hoàn tiền**

Từ menu ba chấm — nơi nó hiện trên *mọi* đơn, kể cả đơn đã hủy và đơn đã có vụ
chưa xong, hai trường hợp server trả 409 — xuống thanh việc dưới cùng, và chỉ
hiện khi `POST /orders/{id}/refunds` thật sự nhận. Đang có vụ mở thì nút dẫn
tới vụ đó.

**Thương lượng không vượt giá niêm yết**

Chặn ở cả hai màn nhập giá, khớp với 422 `offer_above_asking` vừa thêm ở server.

**Gộp upload về một đường**

Bảy chỗ chép tay ba bước reserve → PUT → confirm, mỗi chỗ một module, và **cả
bảy đều vứt đi câu trả lời của bước confirm** — lần duy nhất server đưa ra
signed `url`, theo đúng hợp đồng. Sau khi tải lên, app chỉ còn một id, và không
route nào đổi id trần lấy ảnh: màn nào muốn khoe tấm ảnh vừa tải đều không có
gì để vẽ. `ResourceUploader` làm ba bước đó một lần cho cả năm module và trả về
`Resource` đã xác nhận; `ImageUploadField` vẽ file cục bộ cho tới khi có link
đã ký, báo lỗi từng ảnh và cho thử lại từng ảnh.

**CI**

46 bài đỏ, không bài nào là lỗi mới: fixture `Order` thiếu `refund` (đã thành
trường bắt buộc), và bốn nhóm test mô tả những màn đã được thiết kế lại — đơn
hàng giờ là sáu tab của riêng người mua, badge Tin nhắn chỉ đếm tin nhắn, trang
Tài khoản đổi nhãn, nút + mở thẳng biểu mẫu đăng bán. Test cập nhật theo thiết
kế hiện tại. 268 pass, `flutter analyze` sạch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@beanbocchi
beanbocchi merged commit 64a8185 into main Aug 11, 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.

1 participant