# 10 — Cronjob định kỳ

> Toàn bộ Console Command trong `src/Command/Cronjob/` — chạy theo lịch (crontab), **khác** với các script bảo trì một lần ở file 11 (đừng nhầm 2 loại này). Xem file 09 để hiểu sâu hơn về từng tích hợp bên ngoài liên quan.
>
> ⚠️ **Cảnh báo chung quan trọng:** 2 cronjob `cronjob:deco_clone_order` và `cronjob:import-down-payment` hiện có lệnh `dd()` (dump-and-die của Symfony) còn sót lại trên đường đi thành công/lỗi — nghĩa là **khi chạy thật dưới cron, tiến trình sẽ dừng đột ngột thay vì thoát sạch (exit code chuẩn)**. Đây nhiều khả năng là code debug chưa dọn trước khi đưa vào lịch chạy, cần được sửa trước khi tin tưởng hoàn toàn vào 2 job này.

## 0. Bảng tổng hợp

| Lệnh | Điều kiện kích hoạt | Log | An toàn khi chạy lại? |
|---|---|---|---|
| `cronjob:deco_clone_order` | Không lọc theo Channel — luôn gọi API DecoNetwork lấy đơn "hôm qua" | Không có (chỉ `dd()`) | ⚠️ Không sạch — dừng bằng `dd()` |
| `cronjob:import-down-payment` | Không lọc — luôn kết nối SFTP Svea | `app_logs/down_payment_import/{ngày}.txt` | Service an toàn, nhưng lệnh dừng bằng `dd()` |
| `order:import_1239_blavitt_pod` | Không lọc — job riêng cho đối tác Blavitt | Monolog `critical` | An toàn (file đã xử lý được archive) |
| `order:import_boltic` | Không lọc — job riêng cho đối tác Boltic | Monolog `critical` | An toàn (archive phía server) |
| `order:import_nakata` | Không lọc — job riêng cho đối tác Nakata | Monolog `critical` | An toàn (archive phía server) |
| `kickbackInvoice:create` | `Channel.availableForKickbackInvoice=yes` (+ check lại `kickbackInvoiceActive`) | Chỉ console output | Cần xác minh có chặn tạo trùng hoá đơn cùng kỳ hay không |
| `cronjob:resync-wp-completed-order` | Không lọc — quét toàn bộ bảng `OrderResyncWp` | Không có | Cần xác minh bảng có được dọn sau khi xử lý xong |
| `cronjob:shopify_clone_order` | `shopifySyncOrder`+`active`+có credential Shopify | `shopify_logs/clone_order_{ngày}.txt` | An toàn |
| `cronjob:shopify_clone_product` | `shopifySyncProduct`+`active`+có credential | `shopify_logs/clone_product_{ngày}.txt` | An toàn |
| `cronjob:shopify_clone_product_stock_stock` | như trên | `shopify_logs/clone_product_stock_{ngày}.txt` | An toàn (⚠️ tên lệnh có lỗi chính tả) |
| `cronjob:export-stock-to-woo --slot=` | `wooSyncStockActive`+`active`+khớp slot | `woo_stock_logs/export_{ngày}.txt` | An toàn, thiết kế để chạy nhiều lần/ngày |

---

## 1. `cronjob:deco_clone_order` — Nhập đơn từ DecoNetwork

**File:** `src/Command/Cronjob/DecoOrderCommand.php`

- **Kích hoạt:** không kiểm tra `Channel` nào — luôn tính khoảng thời gian "cả ngày hôm qua" (UTC) và gọi thẳng API DecoNetwork với **thông tin đăng nhập hard-code ngay trong code** (`username=Liem`, mật khẩu dạng plaintext).
- **Xử lý:** nếu có đơn trả về, gọi `OrderService::importFromDecoNetwork()` — tự tạo/dùng 1 `Channel` riêng (`synId = testDeco`) để chứa các đơn import kiểu này, khớp đơn theo `channelId+orderNr`, tự tạo `Customer`/loại thanh toán nếu chưa có.
- ⚠️ **Vấn đề nghiêm trọng:** cả nhánh thành công lẫn nhánh lỗi HTTP đều gọi `dd(...)` — chương trình **luôn dừng đột ngột**, không bao giờ thoát sạch. Đây rõ ràng là code debug chưa được dọn. Kết hợp với credential hard-code trong source, **khuyến nghị rà soát và sửa trước khi coi đây là job production ổn định**.
- **Log:** không có file log nào — chỉ có output của `dd()`.

## 2. `cronjob:import-down-payment` — Nhập file đối soát thanh toán

**File:** `src/Command/Cronjob/ImportDownPaymentCommand.php`

- **Kích hoạt:** không lọc theo `Channel` — luôn kết nối SFTP (host Svea) mỗi lần chạy.
- **Xử lý:** gọi `DownPaymentFileService::importDownPaymentGetFileFTP()` — tải file CSV mới, bỏ qua file không phải `.csv`, bỏ qua file đã tồn tại (đối chiếu theo `fileName` trong DB, chống nhập trùng), parse và tạo `DownPaymentFile`/`DownPaymentRow`, sau đó **di chuyển file gốc trên SFTP vào thư mục `arkiv`** (đã xử lý xong).
- **Bản thân service an toàn/idempotent** để chạy lại nhiều lần. Tuy nhiên **lệnh command lại gọi `dd($result)`** ở cuối — cùng vấn đề như mục 1, cần dọn trước khi tin tưởng hoàn toàn vào lịch chạy tự động.
- **Log:** `app_logs/down_payment_import/{ngày}.txt` — JSON theo dòng cho từng file (bỏ qua/lỗi/thành công) + 1 dòng tổng kết cuối.

## 3. `order:import_1239_blavitt_pod`, `order:import_boltic`, `order:import_nakata` — Nhập đơn từ đối tác qua (S)FTP

3 lệnh này có cấu trúc gần như giống hệt nhau, chỉ khác đối tác/host/định dạng cột — mỗi lệnh gắn với **1 partner riêng biệt**, không lọc theo `Channel` (tự tạo/dùng 1 kênh riêng theo `synId` cố định: `boltic`, `nakata`).

| Lệnh | Đối tác | Giao thức | Đường dẫn từ xa |
|---|---|---|---|
| `order:import_1239_blavitt_pod` | Blavitt POD | FTP thuần (`ftp_*`) | thư mục gốc tài khoản FTP |
| `order:import_boltic` | Boltic Bandy | SFTP | `/home/integration/Boltic` |
| `order:import_nakata` | Nakata | SFTP | `/home/integration/Nakata` |

- **Xử lý chung:** tải các file mới (bỏ qua `.`/`..`/`archive`) → parse CSV bằng PhpSpreadsheet (cột cố định theo từng đối tác) → gọi `OrderService::importCsvData()` (Boltic/Nakata) hoặc `importOrder1239BlavittPOD()` (Blavitt) → **di chuyển file đã xử lý vào thư mục `archive/` phía server**, tránh xử lý lại lần sau.
- Lệnh `order:import_1239_blavitt_pod` nhận tham số tuỳ chọn `removeFile` — để trống (mặc định) thì file được xoá/archive sau khi xử lý; truyền bất kỳ giá trị nào thì **giữ nguyên file** (dùng cho môi trường dev/test — cẩn thận nếu vô tình bật chế độ này ở production sẽ khiến file bị xử lý lặp lại mỗi lần chạy).
- **Không có bước kiểm tra trùng `orderNr` trước khi insert** trong đoạn code được rà soát — nếu vô tình đưa 1 file đã xử lý (archive) trở lại thư mục gốc, hệ thống **có thể tạo đơn trùng**. Vì file được archive phía server ngay sau khi xử lý thành công, rủi ro này thấp trong vận hành bình thường nhưng cần lưu ý khi thao tác thủ công.
- **Log:** chỉ qua PSR `LoggerInterface::critical()` (Monolog, không phải file log riêng theo ngày) — ghi khi không tìm thấy file hoặc khi parse lỗi. Riêng Boltic/Nakata, lỗi parse còn kèm `dd()` trong khối catch — dừng tiến trình giữa chừng khi gặp file lỗi định dạng.

## 4. `kickbackInvoice:create` — Tạo hoá đơn hoa hồng định kỳ

**File:** `src/Command/Cronjob/KickbackInvoiceCreateCommand.php`

- **Kích hoạt:** lọc `Channel` theo `availableForKickbackInvoice = yes` — điều kiện này thực chất là `kickbackInvoicePeriodInterval > 0 AND kickbackInvoiceNrOfMonth > 0` (không kiểm tra `active` ngoài mặc định loại trừ kênh đã xoá). Bên trong `KickbackInvoiceService::create()` còn kiểm tra lại **chặt hơn**: `kickbackInvoiceActive = true` — nếu không đạt, trả lỗi và job coi như bỏ qua kênh đó.
- **Xử lý:** tính kỳ báo cáo = tháng theo `kickbackInvoicePeriodInterval` của **năm hiện tại** (không phải "tháng trước" tương đối) → gom báo cáo đơn "complete" trong kỳ → tạo 1 hoặc nhiều `KickbackInvoice` (tách theo brand nếu bật `kickbackInvoiceSeparateByBrand`).
- **Log:** chỉ in ra console (`writeln`), **không có file log** — nếu cần theo dõi lịch sử chạy, phải tự redirect output khi cấu hình crontab.
- ⚠️ **Cần xác minh trước khi coi là an toàn tuyệt đối để chạy lại nhiều lần trong cùng kỳ**: không thấy rõ cơ chế chặn tạo trùng hoá đơn cho cùng 1 kênh/kỳ báo cáo trong đoạn code đã rà soát — nếu chạy lệnh này 2 lần trong cùng tháng, có khả năng sinh 2 hoá đơn hoa hồng trùng kỳ. Nên xác minh kỹ `KickbackInvoiceService::add()` trước khi lên lịch chạy nhiều lần/tháng.

## 5. `cronjob:resync-wp-completed-order` — Retry đồng bộ đơn hoàn tất

**File:** `src/Command/Cronjob/ResyncWpCompletedOrderCommand.php`

- **Kích hoạt:** không lọc theo `Channel` — quét **toàn bộ** bảng `OrderResyncWp` (hàng đợi lỗi tích luỹ từ Shopify/WordPress/Deco, xem file 04 mục 4.3 và file 09).
- **Xử lý:** với mỗi dòng, load lại `Order` tương ứng và gọi lại `OrderService::postOrderUpdateComplete($order)` — thử lại đúng logic đồng bộ ngược đã thất bại trước đó.
- **Log:** không có logger nào được gọi trong bản thân command này — log (nếu có) nằm trong `postOrderUpdateComplete()`.
- ⚠️ Cần xác nhận rằng khi xử lý thành công, dòng `OrderResyncWp` tương ứng **có thực sự bị xoá** (qua `resyncToWpRemoveLog()` bên trong `postOrderUpdateComplete`) — nếu không, job sẽ retry vô hạn cả các dòng đã xử lý xong.

## 6. `cronjob:shopify_clone_order` / `_product` / `_product_stock_stock`

Xem chi tiết đầy đủ ở file **09 — Tích hợp bên thứ ba, mục 1**. Tóm tắt điều kiện kích hoạt: `Channel.active=true`, cờ đồng bộ tương ứng (`shopifySyncOrder`/`shopifySyncProduct`) bật, và đã cấu hình `shopifyAccessToken`+`shopifyUrl`.

⚠️ Lưu ý tên lệnh `cronjob:shopify_clone_product_stock_stock` **có lỗi chính tả** (lặp từ "stock") — đây là tên lệnh **thật sự đang chạy trong production**, phải giữ nguyên khi cấu hình/sửa crontab, không tự ý "sửa lỗi chính tả" vì sẽ làm mất lịch chạy hiện có.

## 7. `cronjob:export-stock-to-woo --slot=night|noon|afternoon`

Xem chi tiết đầy đủ ở file **09 — Tích hợp bên thứ ba, mục 2.2**. Đây là cronjob có chất lượng code/log tốt nhất trong nhóm — xử lý lỗi theo từng kênh riêng biệt (`RuntimeException` không làm dừng cả batch), log rõ ràng theo cấu trúc JSON mỗi ngày.

Lịch đề xuất (comment sẵn trong code):
```
0  2 * * *  php bin/console cronjob:export-stock-to-woo --slot=night
0 11 * * *  php bin/console cronjob:export-stock-to-woo --slot=noon
0 15 * * *  php bin/console cronjob:export-stock-to-woo --slot=afternoon
```

---

## 8. Khuyến nghị vận hành

1. **Ưu tiên sửa 2 lệnh có `dd()`** (`cronjob:deco_clone_order`, `cronjob:import-down-payment`) trước khi tin tưởng để chạy tự động lâu dài — hiện tại chúng "trông như chạy được" nhưng sẽ luôn dừng bất thường khi thực sự xử lý dữ liệu hoặc gặp lỗi.
2. **Không nên đặt lịch cho `garp:monday`** (xem file 08 mục 6) — chưa hoàn thiện, cũng dính lỗi `dd()`.
3. Với `kickbackInvoice:create` và `cronjob:resync-wp-completed-order`, nên **kiểm tra kỹ log/DB sau vài lần chạy đầu** để xác nhận không có hiện tượng tạo trùng hoá đơn hoặc retry vô hạn, trước khi hoàn toàn "quên" và để job tự chạy dài hạn.
4. Các job có log dạng `{loại}_logs/{ngày}.txt` (Shopify, WooCommerce, DownPayment) nên có **chính sách dọn log cũ định kỳ** — hiện chưa thấy cơ chế tự xoá log cũ nào trong code.
