# 06 — API Bundle (REST API `/api/v1`)

> Toàn bộ endpoint dưới đây phục vụ ứng dụng React admin/backoffice và các hệ thống tích hợp bên ngoài. Xem file `12-bao-mat-xac-thuc-cau-hinh.md` để hiểu chi tiết cơ chế token; xem file `07-webshop-bundle.md` cho phần webshop công khai (khác hoàn toàn, không dùng API này).

## 1. Cách API được kết nối vào ứng dụng

```yaml
# config/routes.yaml
api:
    resource: '@ApplicationApiBundle/Resources/config/routing.yaml'
    prefix: /api/v1
```

- Mỗi resource (channel, order, invoice...) có **1 file YAML riêng** trong `src/Application/ApiBundle/Resources/config/route/`, được include vào `routing.yaml` gốc của bundle.
- Controller: `src/Application/ApiBundle/Controller/*.php` — **rất mỏng**, không chứa logic nghiệp vụ, chỉ parse request và gọi Service.
- `config/packages/security.yaml` **không khai báo firewall/access_control** cho `/api` — tức là **Symfony Security không tham gia** vào việc bảo vệ các route này. Toàn bộ xác thực nằm thủ công trong từng action.

## 2. Cơ chế xác thực (tóm tắt — chi tiết ở file 12)

Mọi action (trừ vài endpoint public rõ ràng) đều bắt đầu bằng:

```php
$authorization = $request->headers->get('Authorization');
$token = $apiService->getToken($authorization);
if (empty($token)) {
    return $this->json(['message' => 'Invalid token authorization'], 401);
}
```

- Header bắt buộc: `Authorization: Bearer <token 32 ký tự hex thường>`.
- Token được cấp khi đăng nhập (`/users/*/login`), hiệu lực **24 giờ**, không refresh tự động.
- Một số action gọi `getToken($authorization, true, false, true)` để lấy về **toàn bộ đối tượng `User`** (không chỉ token) khi cần biết `role`/`brandId`/`channelId` của người gọi.
- Endpoint dùng cho tích hợp ngoài (import đơn hàng, webhook Shopify...) gọi `getToken($authorization, true, true)` — cờ thứ 3 `true` cho phép **tài khoản `lockForOnlyIntegration`** đăng nhập được (tài khoản này bị khoá khỏi luồng UI thường).
- Mọi Service trả về khuôn dạng `['data' => ..., 'status_code' => int]`; Controller chỉ forward nguyên trạng thành JSON.

---

## 3. Người dùng & xác thực — `UserController`, `UserCustomLinkController`

Base: `/api/v1/users`, `/api/v1/user-custom-links`

| Method | Path | Mô tả |
|---|---|---|
| POST | `/users/integration/login` | Đăng nhập dành cho hệ thống tích hợp ngoài |
| POST | `/users/app/login` | Đăng nhập ứng dụng React admin/backoffice |
| POST | `/users/forgot-password` | Gửi email đặt lại mật khẩu |
| POST | `/users/reset-password/{resetPasswordToken}` | Đặt mật khẩu mới bằng token reset |
| POST | `/users/channel/login` | Đăng nhập cổng khách hàng B2B (Channel) |
| POST | `/users/channel/login/reset-otp` | Yêu cầu gửi mã OTP đăng nhập (Channel) |
| POST | `/users/channel/login/login-by-otp` | Hoàn tất đăng nhập bằng OTP |
| ANY | `/users/channel/force-login` | Admin **giả lập đăng nhập thay** cho 1 tài khoản Channel (không cần mật khẩu) |
| GET | `/users/` | Danh sách người dùng |
| POST | `/users/` | Tạo người dùng |
| GET/POST/DELETE | `/users/{id}` | Xem/sửa/xoá người dùng |
| GET/POST/DELETE | `/user-custom-links/...` | CRUD link tắt cá nhân hoá |

## 4. Kênh bán hàng — `ChannelController`

Base: `/api/v1/channels`

| Method | Path | Mô tả |
|---|---|---|
| GET | `/channels/` | Danh sách kênh |
| POST | `/channels/` | Tạo kênh — hỗ trợ upload đa file (logo, banner 1-3, banner mobile, favicon, icon link, ảnh banner set) |
| GET/POST/DELETE | `/channels/{id}` | Xem/sửa (kèm upload)/xoá kênh |
| POST | `/channels/{id}/get-prepaid-current-sum` | Tính số dư trả trước hiện tại của kênh |
| GET | `/channels/{id}/prepaid-orders` | Log các đơn bị trừ vào số dư trả trước (`limit`/`offset`), kèm số dư tính lại tại thời điểm gọi. Chỉ đọc — không ghi log, không gửi email. `ROLE_CHANNEL` chỉ xem kênh của mình khi kênh bật `guestLoginShowPrepaid` (TSHIRTORDER-1633) |

## 5. Đơn hàng — `OrderController`, `BulkOrderController`, `ReturnOrderController`, `CreditController`

Base: `/api/v1/orders`, `/bulk-orders`, `/return-orders`, `/credits`

**Đơn hàng (Order)**

| Method | Path | Mô tả |
|---|---|---|
| GET | `/orders/` | Danh sách đơn hàng |
| GET | `/orders/product-history-list` | Lịch sử bán theo sản phẩm |
| POST | `/orders/import-deco/test` | Import đơn từ DecoNetwork (token tích hợp) |
| POST | `/orders/import-json` | Import đơn từ WooCommerce/WordPress (token tích hợp) |
| POST | `/orders/update-sort-orders` | Cập nhật thứ tự sắp xếp hàng loạt |
| POST | `/orders/massedit` | Sửa/in hàng loạt nhiều đơn cùng lúc |
| GET | `/orders/count` | Đếm số đơn theo trạng thái |
| POST | `/orders/` | Tạo đơn hàng (kèm upload file/korrektur) |
| GET | `/orders/detail-by-count-nr/{countNr}` | Tra đơn theo số chạy nội bộ |
| GET | `/orders/production-type/{productionTypeId}` | Danh sách đơn theo loại sản xuất |
| GET/POST/DELETE | `/orders/{id}` | Xem/sửa/xoá đơn |
| POST | `/orders/{id}/refresh-product` | Đồng bộ lại giá/tên sản phẩm hiện tại vào đơn |
| GET | `/orders/{id}/copy` | Nhân bản đơn hàng |
| POST | `/orders/{id}/production-type/{productionTypeId}/change-status` | Chuyển bước sản xuất **mức đơn hàng** |
| POST | `/orders/{id}/orderProduct/{orderProductId}/change-status` | Chuyển bước sản xuất **mức dòng sản phẩm** |
| GET | `/orders/{id}/print-packing-slip` | PDF phiếu sản xuất |
| GET | `/orders/{id}/print-delivery-label` | PDF nhãn giao hàng |
| GET | `/orders/{id}/print-order` | PDF xác nhận đơn |
| GET | `/orders/{id}/print-delivery` | PDF phiếu giao hàng ("följesedel") |
| GET | `/orders/{id}/print-offert` | PDF báo giá |
| POST | `/orders/{id}/postnord/create-label` | Tạo nhãn vận chuyển PostNord |
| POST | `/orders/{id}/postnord/show-label` | Lấy lại nhãn PostNord đã tạo |
| POST | `/orders/{id}/send-mail` | Gửi email liên quan đơn hàng |
| POST | `/orders/{orderId}/item/{orderProductId}` | Sửa 1 dòng sản phẩm trong đơn |
| DELETE | `/orders/{id}/remove-file/{fileId}` | Xoá file đính kèm đơn hàng |

**Gộp đơn (BulkOrder)**

| Method | Path | Mô tả |
|---|---|---|
| GET/POST | `/bulk-orders/` | Danh sách / tạo bulk order |
| GET/POST/DELETE | `/bulk-orders/{id}` | Xem/sửa/xoá |
| POST | `/bulk-orders/{id}/create-invoice` | Xuất hoá đơn gộp cho bulk order |
| POST | `/bulk-orders/{id}/postnord/create-label` | Tạo nhãn PostNord cho lô giao gộp |
| POST | `/bulk-orders/{id}/postnord/show-label` | Xem lại nhãn |
| GET | `/bulk-orders/{id}/print-foljesedel` | PDF phiếu giao gộp |
| POST | `/bulk-orders/{id}/send-notification-email` | Gửi email thông báo bulk order |

**Trả hàng / Credit note**

| Method | Path | Mô tả |
|---|---|---|
| GET | `/return-orders/` | Danh sách yêu cầu trả hàng |
| POST | `/return-orders/create-from-order/{orderId}` | Tạo yêu cầu trả hàng từ đơn |
| POST/GET/DELETE | `/return-orders/{id}` | CRUD |
| POST | `/return-orders/{id}/refund` | Xử lý hoàn tiền |
| GET | `/credits/` | Danh sách credit note |
| POST | `/credits/create-from-order/{orderId}` | Tạo credit từ đơn hàng |
| POST | `/credits/create-from-invoice/{invoiceId}` | Tạo credit từ hoá đơn |
| POST | `/credits/create-from-invoice/{creditId}/force-create` | Kích hoạt lại credit đang ở trạng thái nháp |
| POST | `/credits/{id}/create-invoice` | Xuất hoá đơn hoá credit (thành hoá đơn âm) |
| GET/POST/DELETE | `/credits/{id}` | CRUD |

## 6. Hoá đơn & đối soát — `InvoiceController`, `InvoiceJournalController`, `InvoiceReminderController`, `KickbackInvoiceController`, `DownPayment*Controller`

Base: `/api/v1/invoices`, `/invoice-journals`, `/invoice-reminders`, `/kickback-invoices`, `/down-payment-files`, `/down-payment-file-journals`, `/down-payment-rows`

| Method | Path | Mô tả |
|---|---|---|
| GET | `/invoices/` | Danh sách hoá đơn |
| GET | `/invoices/total-sum-by-year/{year}` | Tổng doanh thu hoá đơn theo năm |
| GET | `/invoices/reminder-list` | Danh sách hoá đơn cần nhắc nợ |
| GET | `/invoices/{id}/pdf` | PDF hoá đơn |
| POST | `/invoices/ ` (tạo) `/create-from-multi-orders` / `/create-from-multi-orders-individual` | Tạo hoá đơn gộp / tạo riêng từng hoá đơn từ nhiều đơn |
| POST | `/invoices/export-pdf-not-paid-1200` | Xuất báo cáo công nợ chưa thu (PDF hàng loạt) |
| GET/POST/DELETE | `/invoices/{id}` | CRUD |
| POST | `/invoices/{id}/refresh-customer-info` | Đồng bộ lại thông tin khách hàng vào hoá đơn |
| POST | `/invoices/{id}/send-mail` | Gửi hoá đơn qua email |
| GET/POST | `/invoice-journals/` | Danh sách/tạo lô hoá đơn kế toán |
| GET | `/invoice-journals/{id}/pdf` | PDF lô |
| GET/POST/DELETE | `/invoice-journals/{id}` | CRUD |
| GET | `/invoice-reminders/` | Danh sách nhắc nợ |
| POST | `/invoice-reminders/add-from-multi-invoices` | Tạo nhắc nợ hàng loạt |
| GET/POST/DELETE | `/invoice-reminders/{id}` | CRUD, `.../pdf`, `.../send-mail` |
| GET/POST | `/kickback-invoices/` | Danh sách / tạo hoá đơn hoa hồng |
| GET | `/kickback-invoices/{id}/pdf` | PDF |
| POST | `/kickback-invoices/{id}/send-mail` | Gửi email |
| GET/POST | `/down-payment-files/` | Danh sách/tạo lô file đối soát |
| POST | `/down-payment-files/export` | Xuất báo cáo lô |
| GET/POST | `/down-payment-file-journals/` | Danh sách/tạo journal đối soát |
| GET/POST | `/down-payment-rows/` | Danh sách/tạo dòng giao dịch |
| POST | `/down-payment-rows/add-multi-rows` | Thêm hàng loạt dòng |
| POST | `/down-payment-rows/add-apply-multi-invoices` | Thêm + áp dụng ngay vào nhiều hoá đơn |
| POST | `/down-payment-rows/apply-multi` | Áp dụng đối soát |
| POST | `/down-payment-rows/confirm-apply-multi` | Xem trước kết quả đối soát (không lưu) |

## 7. Sản phẩm / Catalogue — `ProductController`, `ProductModelController`, `ProductCategoryController`, `BrandController`, `ProductionController`, `StatusListController`

Base: `/api/v1/products`, `/product-models`, `/product-categories`, `/brands`, `/productions`, `/status-list`

| Method | Path | Mô tả |
|---|---|---|
| GET | `/products/` | Danh sách sản phẩm |
| GET | `/products/export-excel` | Xuất Excel catalogue |
| GET | `/products/list-no-kickback` | Sản phẩm không tính hoa hồng |
| GET/POST | `/products/list-price` | Bảng giá đặc biệt (xem + sửa hàng loạt) |
| POST | `/products/import-wp-product` | Import sản phẩm từ WooCommerce (token tích hợp) |
| POST | `/products/import-wp-stock` | Import tồn kho từ WooCommerce (token tích hợp) |
| POST | `/products/quick-update` | Sửa nhanh từng phần (VD sửa trực tiếp trên bảng lưới) |
| POST | `/products/` | Tạo sản phẩm (multipart: ảnh, thumbnail, file in) |
| GET/POST/DELETE | `/products/{id}` | Xem/sửa/xoá |
| POST | `/products/{id}/copy-to-new` | Nhân bản sản phẩm |
| GET | `/products/{id}/sale-log` | Lịch sử bán của sản phẩm |
| GET/POST/DELETE | `/product-models/...` | CRUD mẫu sản phẩm (kèm upload thumbnail) |
| GET | `/product-categories/` | Danh sách danh mục (admin) |
| GET | `/product-categories/ecom/{channelId}/list` | Danh mục hiển thị trên webshop của 1 kênh |
| GET/POST/DELETE | `/product-categories/{id}...` | CRUD |
| GET/POST/DELETE | `/brands/...` | CRUD thương hiệu (kèm upload logo) |
| GET/POST/DELETE | `/productions/...` | CRUD loại hình sản xuất/in ấn |
| GET/POST/DELETE | `/status-list/...` | CRUD danh mục "enum" dùng chung; `list` cũng phục vụ tra cứu tĩnh (`?type=addon_product_type/down_payment_file_type/country_code/user_role/channel_ecom_template`) |

## 8. Khách hàng — `CustomerController`

Base: `/api/v1/customers`

| Method | Path | Mô tả |
|---|---|---|
| GET | `/customers/` | Danh sách khách hàng |
| GET | `/customers/default-customer-for-guest-order` | Khách hàng mặc định dùng cho đơn ecom khách vãng lai (không cần đăng nhập) |
| GET | `/customers/{userId}/default-customer-by-user-for-guest-order` | Khách hàng mặc định theo tài khoản Channel cụ thể |
| POST | `/customers/` | Tạo khách hàng |
| GET/POST/DELETE | `/customers/{id}` | Xem/sửa/xoá |

## 9. Marketing / Mã giảm giá — `CouponCampaignController`, `CouponCodeController`

Base: `/api/v1/coupon/campaigns`, `/api/v1/coupon/codes`

| Method | Path | Mô tả |
|---|---|---|
| GET/POST/DELETE | `/coupon/campaigns/...` | CRUD chương trình khuyến mãi |
| GET | `/coupon/codes/` | Danh sách mã |
| POST | `/coupon/codes/add-multi-code` | Thêm hàng loạt mã cụ thể |
| POST | `/coupon/codes/auto-generate-code/{campaignId}/{nrCode}` | Tự sinh N mã ngẫu nhiên cho 1 campaign |
| GET/POST/DELETE | `/coupon/codes/{id}` | CRUD |

## 10. Tích hợp Shopify — `ShopifyController`

Base: `/api/v1/shopify`

| Method | Path | Mô tả |
|---|---|---|
| ANY | `/shopify/webhook-customer-erasure-data` | Webhook GDPR "xoá dữ liệu khách hàng" từ Shopify (token tích hợp) |
| ANY | `/shopify/webhook-data-request` | Webhook GDPR "yêu cầu dữ liệu khách hàng" từ Shopify (token tích hợp) |

## 11. Nội dung / CMS — `EmailTemplateController`, `WikiPageController`, `WebshopMenuController`

Base: `/api/v1/email-template`, `/wiki-pages`, `/webshop-menu`

| Method | Path | Mô tả |
|---|---|---|
| GET/POST/DELETE | `/email-template/...` | CRUD mẫu email (kèm upload logo trong nội dung) |
| GET/POST/DELETE | `/wiki-pages/...` | CRUD trang nội dung tĩnh hiển thị trên webshop |
| GET/POST/DELETE | `/webshop-menu/...` | CRUD mục menu điều hướng webshop |

## 12. Báo cáo / Tiện ích / Nhật ký — `OtherController`, `ActivityLogController`

Base: `/api/v1/other`, `/api/v1/activity-logs`

| Method | Path | Mô tả |
|---|---|---|
| ANY | `/other/type-status/{name}` | Tra cứu enum tĩnh (`channel_type`, `customer_type`) |
| ANY | `/other/next-count/{type}` | Lấy trước số thứ tự tiếp theo (từ `AutoCount`) |
| ANY | `/other/dashboard` | Số liệu tổng hợp cho trang chủ backoffice |
| ANY | `/other/report/order` (+ `/export-excel`) | Báo cáo đơn hàng |
| ANY | `/other/report/order-product` (+ `/export-excel`) | Báo cáo theo dòng sản phẩm |
| ANY | `/other/report/order-product-kickback/export-excel` | Báo cáo hoa hồng |
| ANY | `/other/report/order-product-supplier/export-excel` | Báo cáo theo nhà cung cấp |
| GET | `/activity-logs/` | Nhật ký thao tác (lọc theo entity/entityId/userEmail/khoảng ngày) |

**Route debug (không có tiền tố nhóm, khai báo thẳng trong `routing.yaml` gốc):** `GET /test1221`, `/test1221/send-mail`, `/test1221/olamerlin_import` — dùng để xem trước email/test import, **nên loại bỏ hoặc chặn ở môi trường production**.

> Lưu ý kỹ thuật: trong các file `order.yaml`, `coupon_code.yaml`, `down_payment_row.yaml`, các route dạng chuỗi cố định (VD `/count`, `/massedit`) **luôn được khai báo trước** route `/{id}` để Symfony không hiểu nhầm đoạn cố định thành tham số ID.

---

## 13. Ví dụ tình huống — một phiên làm việc điển hình của nhân viên bán hàng qua API

> 1. `POST /users/app/login` → nhận `accessToken`.
> 2. `GET /channels/` → chọn kênh đang làm việc.
> 3. `GET /customers/?channelId=12&q=acme` → tìm khách hàng "ACME".
> 4. `POST /orders/` → tạo đơn hàng mới cho khách, kèm `newFiles` (file thiết kế khách gửi).
> 5. `GET /orders/{id}/print-order` → in PDF xác nhận đơn gửi khách duyệt.
> 6. Sau khi sản xuất xong: `POST /orders/{id}/postnord/create-label` → tạo vận đơn, đơn tự chuyển "complete".
> 7. Cuối tháng: `POST /invoices/create-from-multi-orders` → gộp hoá đơn cho khách "ACME".
> 8. `GET /activity-logs/?entityId={orderId}&entity=Order` → tra soát lịch sử thao tác trên đơn khi cần.
