FieldFlow
Hệ thống quản lý phân phối trực tuyến cho doanh nghiệp nhỏ — gồm ứng dụng di động cho nhân viên thị trường và bảng điều khiển web cho quản lý.
Doanh nghiệp phân phối nhỏ đang gặp khó khăn gì?
Doanh nghiệp phân phối nhỏ (5–30 nhân viên thị trường) đối mặt với 4 vấn đề cốt lõi:
Không biết nhân viên có đi làm không — không kiểm soát được lộ trình, thời gian làm việc.
Không biết tồn kho tại điểm bán — hàng có đang còn không, sắp hết không, ai bán được nhiều.
Ghi giấy, gọi điện, nhắn tin → thất thoát, nhầm lẫn thông tin đơn hàng.
Tổng hợp cuối tuần/tháng → ra quyết định chậm, bỏ lỡ cơ hội kinh doanh.
Nền tảng: Web dashboard
Công việc: Tổng quan hệ thống, báo cáo, ra quyết định chiến lược.
Nền tảng: Web + Mobile
Công việc: Theo dõi đội ngũ, duyệt đơn hàng, giám sát tuyến bán.
Nền tảng: Mobile app
Công việc: Đi tuyến, check-in, nhập tồn kho, chụp hình, tạo đơn hàng.
6 Flow nghiệp vụ chính
Flow 1: Setup ban đầu
Quản lý thiết lập hệ thống lần đầu.
Nhập thông tin: tên, địa chỉ, loại hình
Excel: SKU, tên, giá, đơn vị, danh mục
Tên, địa chỉ, GPS, loại hình, SĐT
Tên tuyến, danh sách điểm bán, ngày chạy
Tên, SĐT, tuyến phụ trách
Flow 2: Điểm bán mới từ nhân viên (field)
Nhân viên phát hiện điểm bán mới khi đi thị trường.
NV mở app → Nhập tên, địa chỉ
Tạp hóa / Siêu thị mini / Nhà thuốc / Khác
Tọa độ tự động + 1-2 hình mặt tiền
Quản lý duyệt trên dashboard
Flow 3: Check-in tại điểm bán (CORE FLOW)
Tuyến hôm nay → Nhấn "Check-in"
≤200m: OK. >200m: Force + lý do
Tồn kho + Chụp hình + Đơn hàng + Ghi chú
Ghi thời gian kết thúc → Chuyển điểm tiếp
Chi tiết thao tác tại điểm bán (thứ tự bất kỳ)
- Hiển thị danh sách sản phẩm
- NV nhập số lượng từng sản phẩm
- Highlight sản phẩm tồn kho = 0 (hết hàng)
- Lưu snapshot tại thời điểm ghé thăm
- Chụp hình trưng bày, kệ hàng, biển bảng
- Tối đa 5 hình / 1 lần ghé thăm
- Xem lại và chụp lại trước khi lưu
- Chọn sản phẩm + số lượng
- Tự động tính thành tiền
- Nhập ghi chú ("giao chiều", "gọi trước")
- Gửi đơn → xuất hiện trên dashboard
- Nhập text tự do
- "Chủ nói hết bia", "Đối thủ giảm giá"
- "Cửa hàng đang sửa"
Flow 4: Xử lý đơn hàng (Quản lý)
Hiện trên Dashboard
NV, điểm bán, SP, SL, thành tiền
Duyệt / Từ chối / Chỉnh sửa
Mới → Đã duyệt → Đang giao → Hoàn thành
Flow 5: Giám sát hàng ngày (Quản lý)
- Số NV đang đi tuyến
- Số điểm bán đã ghé / tổng
- Số đơn hàng + tổng doanh số
- Bản đồ: vị trí tất cả NV (real-time)
- Lộ trình trên bản đồ
- Dòng thời gian check-in/out
- Đơn hàng, hình ảnh, tồn kho
- Điểm bán chưa ghé
- NV chưa check-out
- Force check-in cần xem lại
- Doanh số theo ngày/tuần/tháng
- Tồn kho tại điểm bán
- Hiệu suất nhân viên
- Xuất file Excel / PDF
Flow 6: Báo cáo cuối ngày (Tự động)
- Số NV đi tuyến / tổng NV
- Số điểm bán ghé thăm / tổng điểm bán
- Số đơn hàng + tổng doanh số
- Danh sách điểm bán chưa ghé (cảnh báo)
- Danh sách NV chưa check-out
- Gửi thông báo tóm tắt cho Quản lý
Ứng dụng di động (Nhân viên thị trường)
Tổng MVP: 20/28 chức năng.
| Mã | Chức năng | Mô tả | MVP |
|---|---|---|---|
| A. Đăng nhập & Tài khoản | |||
M1 | Đăng nhập | SĐT + mật khẩu | MVP |
M2 | Thông tin cá nhân | Xem tên, vai trò, tuyến phụ trách | MVP |
M3 | Đổi mật khẩu | Tự đổi mật khẩu | MVP |
M4 | Thông báo đẩy | Nhận thông báo từ quản lý | MVP |
| B. Tuyến bán hàng | |||
M5 | Tuyến hôm nay | Danh sách điểm bán theo thứ tự, trạng thái | MVP |
M6 | Chi tiết điểm bán | Tên, địa chỉ, SĐT, lịch sử ghé gần nhất | MVP |
M7 | Điều hướng | Mở app bản đồ dẫn đường đến điểm bán | MVP |
M8 | Thêm điểm bán mới | Tạo nhanh tại field, GPS tự động lấy tọa độ | MVP |
M9 | Xem tuyến trên bản đồ | Hiển thị tất cả điểm bán trên bản đồ | v2 |
| C. Check-in / Check-out | |||
M10 | Check-in GPS | Bán kính 200m, ghi thời gian + tọa độ | MVP |
M11 | Force check-in | Ngoài bán kính: cho phép + nhập lý do | MVP |
M12 | Check-out | Kết thúc visit, tính tổng thời gian | MVP |
M13 | Lịch sử check-in | Xem lại các lần ghé thăm | v2 |
| D. Tồn kho | |||
M14 | Nhập tồn kho | Danh sách SP → nhập số lượng nhanh | MVP |
M15 | Cảnh báo hết hàng | Highlight SP tồn kho = 0 | MVP |
M16 | So sánh với lần trước | Chênh lệch (+/-) so với visit trước | v2 |
M17 | Quét mã vạch | Quét barcode tìm SP nhanh | v2 |
| E. Hình ảnh | |||
M18 | Chụp hình | Trực tiếp trong app, tối đa 5 hình/lần ghé | MVP |
M19 | Xem lại hình | Preview trước khi lưu, chụp lại nếu mờ | MVP |
M20 | Thư viện hình | Xem lại hình ảnh lịch sử tại điểm bán | v2 |
| F. Đơn hàng | |||
M21 | Tạo đơn hàng | Chọn SP + SL, tự tính thành tiền | MVP |
M22 | Ghi chú đơn hàng | Text tự do (giao chiều, gọi trước...) | MVP |
M23 | Trạng thái đơn | Mới/Đã duyệt/Đang giao/Hoàn thành | MVP |
M24 | Lịch sử đơn hàng | Danh sách đơn đã tạo, filter theo ngày | v2 |
M25 | Gợi ý đặt hàng | Gợi ý dựa trên lịch sử tiêu thụ | v3 |
| G. Khác | |||
M26 | Ghi chú tại điểm bán | Text tự do, "Chủ nói hết bia" | MVP |
M27 | Tổng kết ngày | NV xem: ghé mấy điểm, tạo mấy đơn, doanh số | MVP |
M28 | Offline mode | Làm việc không mạng, đồng bộ khi có mạng | v2 |
Bảng điều khiển web (Quản lý)
Tổng MVP: 30/34 chức năng.
| Mã | Chức năng | Mô tả | MVP |
|---|---|---|---|
| A. Tài khoản & Phân quyền | |||
W1 | Đăng ký / Đăng nhập | Tạo tài khoản công ty | MVP |
W2 | Thông tin công ty | Tên, địa chỉ, logo, loại hình | MVP |
W3 | Phân quyền | Quản lý / Giám sát / Nhân viên — khác quyền xem | MVP |
| B. Quản lý Sản phẩm | |||
W4 | Danh sách sản phẩm | Bảng: tên, SKU, giá, đơn vị, danh mục. Tìm, filter | MVP |
W5 | Thêm/Sửa/Xóa SP | Form nhập thông tin sản phẩm | MVP |
W6 | Import từ Excel | Import hàng loạt sản phẩm từ file | MVP |
W7 | Phân nhóm danh mục | FMCG, bia, nước, bánh kẹo... | MVP |
| C. Quản lý Điểm bán | |||
W8 | Danh sách điểm bán | Bảng: tên, địa chỉ, loại hình, trạng thái | MVP |
W9 | Thêm/Sửa điểm bán | Form: tên, địa chỉ, tọa độ bản đồ, SĐT, loại hình | MVP |
W10 | Import từ Excel | Import hàng loạt điểm bán | MVP |
W11 | Duyệt điểm bán mới | NV tạo ở field → quản lý duyệt/từ chối | MVP |
W12 | Xem trên bản đồ | Hiển thị tất cả điểm bán trên bản đồ | v2 |
| D. Quản lý Tuyến | |||
W13 | Tạo tuyến | Tên tuyến + chọn điểm bán + sắp xếp thứ tự | MVP |
W14 | Gán tuyến cho NV | 1 NV có thể có nhiều tuyến (khác ngày) | MVP |
W15 | Lịch trình tuần | Cấu hình: tuyến nào chạy thứ mấy | MVP |
W16 | Tối ưu tuyến | Tự động sắp xếp theo khoảng cách | v2 |
| E. Quản lý Nhân viên | |||
W17 | Danh sách NV | Bảng: tên, SĐT, tuyến, trạng thái | MVP |
W18 | Tạo tài khoản NV | Tạo tài khoản cho NV đăng nhập app | MVP |
W19 | Reset mật khẩu | Quản lý reset mật khẩu cho NV | MVP |
W20 | Vị trí GPS real-time | Xem vị trí hiện tại tất cả NV trên bản đồ | MVP |
| F. Giám sát & Dashboard | |||
W21 | Dashboard tổng quan | NV active, điểm đã ghé, đơn hàng, doanh số | MVP |
W22 | Bản đồ theo dõi | Vị trí NV + lộ trình đã đi | MVP |
W23 | Dòng thời gian NV | Click NV → xem timeline ghé thăm | MVP |
W24 | Chi tiết lần ghé thăm | Tồn kho, hình ảnh, đơn hàng, ghi chú | MVP |
W25 | Cảnh báo | Chưa ghé, chưa check-out, force check-in | MVP |
| G. Đơn hàng | |||
W26 | Danh sách đơn hàng | Filter: ngày, NV, trạng thái, điểm bán | MVP |
W27 | Chi tiết đơn | Sản phẩm, SL, thành tiền, ghi chú | MVP |
W28 | Duyệt/Từ chối | Duyệt hoặc từ chối + lý do | MVP |
W29 | Cập nhật trạng thái | Mới → Đã duyệt → Đang giao → Hoàn thành | MVP |
| H. Báo cáo | |||
W30 | Báo cáo doanh số | Theo ngày/tuần/tháng, NV/khu vực, biểu đồ | MVP |
W31 | Báo cáo tồn kho | Tồn kho tại điểm bán, SP sắp hết | MVP |
W32 | Báo cáo hiệu suất NV | Số điểm ghé, số đơn, doanh số, thời gian | MVP |
W33 | Báo cáo tần suất ghé thăm | Outlet ghé thường xuyên, outlet bỏ lỡ | v2 |
W34 | Xuất báo cáo | Xuất file Excel / PDF | MVP |
ER Diagram — Business Data Model
erDiagram
Company ||--o{ User : "1:N"
Company ||--o{ Product : "1:N"
Company ||--o{ Outlet : "1:N"
Company ||--o{ Route : "1:N"
User ||--o{ Route : "assigned"
Route ||--o{ RouteOutlet : "1:N"
Outlet ||--o{ RouteOutlet : "1:N"
User ||--o{ Visit : "1:N"
Outlet ||--o{ Visit : "1:N"
Route ||--o{ Visit : "1:N"
Visit ||--o{ Inventory : "1:N"
Visit ||--o{ VisitPhoto : "1:N"
Visit ||--o{ Order : "1:N"
Order ||--o{ OrderItem : "1:N"
Product ||--o{ Inventory : "1:N"
Product ||--o{ OrderItem : "1:N"
Company {
uuid id PK
text name
text address
text industry
text logo_url
text plan
timestamp created_at
}
User {
uuid id PK
uuid company_id FK
text full_name
text phone
text email
enum role
boolean is_active
}
Product {
uuid id PK
uuid company_id FK
text sku
text name
text unit
decimal unit_price
text category
boolean is_active
}
Outlet {
uuid id PK
uuid company_id FK
text name
text address
decimal lat
decimal lng
enum outlet_type
text contact_phone
enum status
boolean approved
}
Route {
uuid id PK
uuid company_id FK
text name
uuid user_id FK
int_array day_of_week
boolean is_active
}
Visit {
uuid id PK
uuid user_id FK
uuid outlet_id FK
uuid route_id FK
timestamp checkin_time
decimal checkin_lat
decimal checkin_lng
timestamp checkout_time
boolean is_forced
text force_reason
text notes
int duration_minutes
}
Order {
uuid id PK
uuid visit_id FK
uuid user_id FK
uuid outlet_id FK
text order_number
decimal total_amount
enum status
text notes
uuid approved_by FK
}
OrderItem {
uuid id PK
uuid order_id FK
uuid product_id FK
decimal quantity
decimal unit_price
decimal total_price
}
Inventory {
uuid id PK
uuid visit_id FK
uuid product_id FK
decimal quantity
text unit
}
VisitPhoto {
uuid id PK
uuid visit_id FK
text photo_url
text caption
timestamp taken_at
}
Dữ liệu mẫu (Ví dụ 1 ngày hoạt động)
Công ty TNHH Phân Phối ABC — 8 NV, 120 outlets, 45 SKUs
Giả định MVP
Yêu cầu mạng khi sử dụng
Dữ liệu cách ly giữa các công ty
Giá bán thiết lập ở mức công ty
Chỉ ghi nhận tại thời điểm NV nhập
Có thể cấu hình sau
Mỗi SP tự quy định đơn vị
Trade marketing = v2
Không bao gồm trong MVP
High-level Architecture
Mỗi DN = 1 VPS riêng, DB riêng
Module chia theo nghiệp vụ (DMS, kho, chấm công...)
1 process, module giao tiếp qua event bus
Màu sắc, logo, layout per deployment
Vue 3 SPA → Capacitor Android/iOS
API chạy PM2, chỉ PostgreSQL dùng Docker
graph TB
subgraph VPS["VPS — 1 Doanh nghiệp"]
subgraph Caddy["Caddy Reverse Proxy"]
direction LR
HTTPS["Auto HTTPS
Let's Encrypt"]
STATIC["Static File Server
Vue SPA"]
PROXY["Reverse Proxy
/api/* → :3000"]
end
subgraph PM2["PM2 Process Manager"]
subgraph API["Fastify API Server :3000"]
subgraph CORE["Core Layer"]
AUTH["Auth"]
USERS["Users"]
COMP["Companies"]
PRODS["Products"]
STORE["Storage"]
NOTIF["Notifications"]
end
subgraph BUS["Module Registry + Event Bus"]
direction LR
end
subgraph MODS["Business Modules"]
DMS["DMS
Phân phối"]
INV["Inventory
Tồn kho"]
MAT["Materials
Nguyên vật liệu"]
ATT["Attendance
Chấm công"]
end
CORE --> BUS
BUS --> MODS
end
end
subgraph DOCK["Docker Compose"]
PG["PostgreSQL 16
Shared DB, module-owned tables"]
end
end
Caddy --> PM2
API --> PG
Module Communication — 3 Patterns
Fire-and-forget. Module A làm xong → thông báo modules khác.
Implementation: In-process EventEmitter (ZERO infra).
DMS → publish('dms.order.created') → Inventory, Debt-Tracking, Reports subscribe
Module A cần data từ Module B ngay lập tức.
Implementation: Service Registry.
DMS → registry.getService('inventory','checkStock') → trả về đồng bộ
Data dùng chung (users, products, companies).
Implementation: Core module expose services.
Products = shared: DMS bán products, Inventory track
Frontend (Web + Mobile)
| Component | Technology | Lý do |
|---|---|---|
| Framework | Vue 3 | Composition API, reactive |
| Build tool | Vite | Fast HMR |
| Routing | Vue Router 4 | SPA, dynamic module routes |
| State | Pinia | TypeScript-friendly |
| UI Components | shadcn-vue | Customizable, accessible |
| CSS | Tailwind CSS 3 | Utility-first, theme-able |
| Mobile | Capacitor 6 | SPA → native Android/iOS |
| Maps | Leaflet + OSM | Free, không giới hạn |
| HTTP Client | ofetch | Modern, type-safe |
| Forms | VeeValidate + Zod | Validation schema |
| Charts | Apache ECharts | Powerful reports |
| Tables | TanStack Table | Server-side pagination |
Backend
| Component | Technology | Lý do |
|---|---|---|
| Runtime | Node.js 20 LTS | Same language FE/BE |
| Framework | Fastify 5 | Fast, plugin architecture |
| Language | TypeScript | Type safety across stack |
| ORM | Drizzle ORM | Lightweight, type-safe |
| Database | PostgreSQL 16 | Reliable, JSON support |
| Cache | In-memory | → Redis khi cần (YAGNI) |
| Storage | Filesystem | → MinIO khi cần (YAGNI) |
| Auth | bcrypt + JWT | Access + refresh tokens |
| Validation | Zod | Shared schemas API + Web |
| Push | Firebase Admin SDK | FCM notifications |
| Excel | ExcelJS | Import/export Excel |
PDFKit | Xuất báo cáo PDF | |
| Images | Sharp | Resize/compress uploads |
| Testing | Vitest | Fast, native ESM |
Infrastructure
| Component | Technology | Lý do |
|---|---|---|
| Database | PostgreSQL 16 (Docker) | Đóng gói, dễ quản lý, isolated |
| Reverse Proxy | Caddy (bare-metal) | Auto HTTPS, config minimal |
| Process Manager | PM2 (bare-metal) | Auto restart, zero-downtime reload |
| SSL | Caddy auto-TLS | Let's Encrypt, zero config |
| CI/CD | Git pull + pm2 reload | Simple cho SMB |
| Monitoring | PM2 logs + health endpoint | Đủ cho SMB scale |
| Backup | Host cron: pg_dump → gzip | Automated daily backup |
Business Modules
| Module | Label | Mô tả | Dependencies | API Prefix |
|---|---|---|---|---|
dms | Quản lý phân phối | Outlets, routes, visits, sales orders, GPS tracking, photos | inventory | /api/v1/dms/ |
inventory | Quản lý tồn kho | Warehouses, stock items, stock movements, adjustments | — | /api/v1/inventory/ |
materials | Quản lý nguyên vật liệu | Raw materials, suppliers, purchase orders, BOM | inventory | /api/v1/materials/ |
attendance | Quản lý chấm công | Time entries, schedules, shifts, GPS check-in/out | — | /api/v1/attendance/ |
promotions | Quản lý khuyến mãi | Campaigns, discounts, pricing rules, gift-with-purchase | dms | /api/v1/promotions/ |
debt-tracking | Quản lý công nợ | Credit limits, payments, aging reports, collection | dms | /api/v1/debt-tracking/ |
reports | Báo cáo | Cross-module aggregation, sales/inventory/attendance | all enabled | /api/v1/reports/ |
Cross-Module Events
dms.order.createddms.order.approveddms.order.completeddms.order.cancelleddms.visit.checkedindms.visit.completeddms.outlet.created
inventory.stock.lowinventory.movement.createdinventory.stock.adjustedattendance.checkedinattendance.checkedoutattendance.late
Module Config (per deployment)
| Module | Enabled | Key Config |
|---|---|---|
dms | Enabled | GPS polling 30s, requirePhoto: true, geofence: 100m |
inventory | Enabled | FIFO, lowStockAlert: true, threshold: 10 |
materials | Disabled | For manufacturing companies, trackBOM: true |
attendance | Enabled | Geofence: 200m, manual checkin: true, 08:00-17:30 |
promotions | Disabled | — |
debt-tracking | Disabled | — |
reports | Enabled | Cross-module reporting |
Cross-Module Communication Examples
Example 1: Tạo đơn hàng (DMS → Inventory → Reports)
- Mobile app →
POST /api/v1/dms/orders - [Service Call]
registry.getService('inventory','checkStock')→ kiểm tra tồn kho. Nếu thiếu → error. - [Service Call]
registry.getService('core','getProductPrices')→ lấy giá hiện tại. - Tạo order + order_items trong DB.
- [Event]
publish('dms.order.created')- Inventory module nhận → tạo stock_movement (xuất kho) + update stock. Nếu stock < threshold →
publish('inventory.stock.low') - Reports module nhận → update cached daily sales aggregate
- Inventory module nhận → tạo stock_movement (xuất kho) + update stock. Nếu stock < threshold →
Example 2: Check-in chấm công (Attendance → DMS)
- Mobile app →
POST /api/v1/attendance/checkin - Tạo time_entry trong DB.
- [Event]
publish('attendance.checkedin')→ DMS module nhận (if enabled) → update GPS tracking view trên dashboard.
Example 3: Báo cáo doanh số (Reports → multiple modules)
- Web app →
GET /api/v1/reports/sales?from=...&to=... - [Service Call]
registry.getService('dms','getOrderStats') - [Service Call]
registry.getService('inventory','getMovementStats') - [Service Call]
registry.getService('attendance','getAttendanceStats') - Aggregate + return combined report.
Schema Ownership — 1 DB, Module-Owned Tables
graph LR
subgraph PG["PostgreSQL 16"]
subgraph CORE["Core Schema"]
CO["companies"]
US["users"]
PR["products"]
NO["notifications"]
end
subgraph DMS["DMS Module"]
OU["outlets"]
RO["routes"]
ROO["route_outlets"]
VI["visits"]
PH["photos"]
SO["sales_orders"]
OI["order_items"]
GL["gps_logs"]
end
subgraph INV["Inventory Module"]
WH["warehouses"]
SI["stock_items"]
SM["stock_movements"]
end
subgraph MAT["Materials Module"]
RM["material_details"]
SU["suppliers"]
PO["purchase_orders"]
POI["purchase_order_items"]
BOM["bill_of_materials"]
end
subgraph ATT["Attendance Module"]
TE["time_entries"]
SC["schedules"]
SH["shifts"]
GF["geofences"]
end
end
Core Tables: companies, users, products, notifications
| Table | Key Fields |
|---|---|
companies | id, name, slug, address, industry, logo_url, phone, email, settings (JSONB), plan, is_active |
users | id, company_id, full_name, phone, email, password_hash (bcrypt), role (admin/supervisor/sales_rep/warehouse_staff), is_active, fcm_token |
products | id, company_id, sku, name, description, unit, unit_price, category, image_url, product_type (finished/raw/semi_finished), metadata (JSONB) |
notifications | id, company_id, user_id, type, title, body, data (JSONB), is_read |
DMS Module Tables: outlets, routes, visits, orders, photos, gps_logs
| Table | Key Fields |
|---|---|
outlets | id, company_id, name, address, lat, lng, outlet_type (grocery/mini_market/pharmacy/restaurant/other), contact_name, contact_phone, status (active/paused/closed), approved, created_by |
routes | id, company_id, name, user_id, day_of_week (int[]), is_active |
route_outlets | id, route_id, outlet_id, sort_order |
visits | id, company_id, user_id, outlet_id, route_id, checkin_time, checkin_lat/lng, checkout_time, checkout_lat/lng, is_forced, force_reason, notes, duration_minutes, status (in_progress/completed) |
photos | id, visit_id, photo_url, thumbnail_url, caption, taken_at |
sales_orders | id, company_id, visit_id, user_id, outlet_id, order_number, total_amount, status (pending/approved/delivering/completed/cancelled), notes, approved_by, cancelled_reason |
order_items | id, order_id, product_id, quantity, unit_price, total_price |
gps_logs | id, company_id, user_id, lat, lng, accuracy, battery_level, recorded_at — partitioned by month |
Inventory Module Tables: warehouses, stock_items, stock_movements
| Table | Key Fields |
|---|---|
warehouses | id, company_id, name, address, is_active |
stock_items | id, warehouse_id, product_id, quantity, reserved_quantity — UNIQUE(warehouse_id, product_id) |
stock_movements | id, company_id, warehouse_id, product_id, movement_type (in/out/transfer/adjustment/return), quantity, reference_type, reference_id, notes, created_by |
Materials Module Tables: suppliers, material_details, BOM, purchase_orders
| Table | Key Fields |
|---|---|
suppliers | id, company_id, name, contact_name, contact_phone, email, address |
material_details | id, product_id, supplier_id, min_order_quantity, lead_time_days, unit_cost, reorder_point |
bill_of_materials | id, company_id, finished_product_id, material_product_id, quantity_required, unit |
purchase_orders | id, company_id, supplier_id, po_number, status (draft/sent/partial/received/cancelled), total_amount, order_date, expected_date |
purchase_order_items | id, po_id, product_id, quantity, unit_cost, total_cost, received_quantity |
Attendance Module Tables: schedules, shifts, time_entries, geofences
| Table | Key Fields |
|---|---|
schedules | id, company_id, name, start_time, end_time, late_threshold (minutes), is_active |
shifts | id, user_id, schedule_id, date — UNIQUE(user_id, date) |
time_entries | id, company_id, user_id, date, checkin_time, checkin_lat/lng, checkout_time, checkout_lat/lng, is_manual, notes, total_hours, status (in_progress/completed/missing_checkout) |
geofences | id, company_id, name, lat, lng, radius (meters), is_active |
REST API Structure
Base URL: /api/v1
POST /auth/loginPOST /auth/refreshPOST /auth/logoutPOST /auth/change-password
GET/POST /usersGET/PUT/DELETE /users/:idPOST /users/:id/reset-password
GET/POST /productsGET/PUT/DELETE /products/:idPOST /products/importGET /products/export
GET/PUT /settings/companyGET/PUT /settings/themeGET/PUT /settings/modulesGET /notificationsPATCH /notifications/:id/read
DMS Module Endpoints
| Method | Endpoint | Mô tả |
|---|---|---|
GET | /dms/outlets | Danh sách điểm bán |
POST | /dms/outlets | Tạo điểm bán mới |
POST | /dms/outlets/import | Import Excel |
PATCH | /dms/outlets/:id/approve | Duyệt điểm bán |
GET | /dms/routes | Danh sách tuyến |
GET | /dms/routes/today | Tuyến hôm nay |
POST | /dms/visits/checkin | Check-in GPS |
PUT | /dms/visits/:id/checkout | Check-out |
GET | /dms/visits/user/:userId/timeline | Timeline NV |
POST | /dms/orders | Tạo đơn hàng |
PATCH | /dms/orders/:id/approve | Duyệt đơn |
PATCH | /dms/orders/:id/reject | Từ chối đơn |
POST | /dms/gps/log | Ghi GPS log |
GET | /dms/gps/employees | Vị trí NV real-time |
GET | /dms/dashboard/summary | Tổng quan dashboard |
GET | /dms/dashboard/employee-map | Bản đồ NV |
API Response Format
Authentication & Authorization
phone/email + password
JWT, 15min TTL
JWT, 7day TTL, DB-stored
Bearer accessToken
Refresh Token Strategy
- Token hashed SHA-256 trước khi lưu DB
- Max 5 active refresh tokens/user
- Oldest auto-revoked khi vượt giới hạn
- Mỗi refresh tạo NEW token (old xóa)
- Reuse detected → revoke ALL tokens cho user
- Web: httpOnly cookie. Mobile: Capacitor Preferences API
RBAC Permission Matrix
| Module | admin | supervisor | sales_rep | warehouse_staff |
|---|---|---|---|---|
| Core | Full access | users:read, products:read | products:read | products:read |
| DMS | Full access | outlets:manage, orders:approve, visits:read | outlets:read, routes:read, visits:create, orders:create | orders:read |
| Inventory | Full access | read | — | manage |
| Materials | Full access | read | — | purchase:manage |
| Attendance | Full access | read, schedules:manage | checkin:create | checkin:create |
| Reports | Full access | read, export | read:own | read:inventory |
Runtime Stack trên VPS
graph TB
subgraph VPS["VPS Ubuntu/Debian"]
subgraph CADDY["Caddy — port 80/443"]
HTTPS["Auto HTTPS
Let's Encrypt"]
STATIC2["Vue SPA static files"]
APIPROXY["Reverse proxy
/api/* → localhost:3000"]
UPLOADS["File uploads
/var/www/fieldflow/uploads"]
end
subgraph PM2RUN["PM2"]
API2["fieldflow-api
Node.js cluster mode
:3000"]
end
subgraph DOCKER["Docker Compose"]
PG2["PostgreSQL 16
port 5432 internal"]
end
CODE["/opt/fieldflow/ — git repo"]
WEBFILES["/var/www/fieldflow/web/ — built SPA"]
UPFILE["/var/www/fieldflow/uploads/ — uploaded files"]
end
CADDY --> PM2RUN
API2 --> PG2
Deployment Flow (update)
Pull latest code
Build API
Run migrations
Zero-downtime
Copy to web dir
Common Operations
| Thao tác | Lệnh |
|---|---|
| Xem API logs | pm2 logs fieldflow-api |
| Restart (zero-downtime) | pm2 reload fieldflow-api |
| Hard restart | pm2 restart fieldflow-api |
| Monitor | pm2 monit |
| Caddy logs | journalctl -u caddy -f |
| Reload Caddy | systemctl reload caddy |
| Backup (daily 2am cron) | /opt/fieldflow/deploy/scripts/backup.sh |
| GPS logs cleanup (weekly) | /opt/fieldflow/deploy/scripts/cleanup-gps-logs.sh |
Lộ trình triển khai
- Tuần 1-2: Project setup, DB schema, Core layer (auth, users, products), module system skeleton
- Tuần 3-4: DMS module: Outlet CRUD + Import Excel, Route management
- Tuần 5-6: DMS module: basic Vue web dashboard (responsive), module routing
- Tuần 7-8: Visit check-in/out GPS, Leaflet maps, event bus wiring
- Tuần 9-10: Photo upload, Inventory module (basic: warehouse, stock items)
- Tuần 11-12: Sales order creation (mobile) + approval (web), DMS ↔ Inventory service calls
- Tuần 13-14: Inventory: stock movements, adjustments, low stock alerts
- Tuần 15-16: Attendance module: check-in/out, schedules, timesheets
- Tuần 17-18: Reports module: sales report, inventory report, attendance report
- Tuần 19-20: Capacitor mobile build, FCM push notifications, Theme system
- Tuần 21: Testing, bug fixes, deployment automation
- Tuần 22-23: Materials module: raw materials, suppliers, BOM, purchase orders
- Tuần 24: Additional modules per customer needs
Key Technical Decisions & Trade-offs
| Decision | Choice | Trade-off |
|---|---|---|
| Mobile approach | Capacitor (web wrapper) | Nhanh hơn, nhưng native features hạn chế vs RN/Flutter |
| Maps | Leaflet + OSM | Free, không giới hạn, nhưng ít polished hơn Google Maps |
| Real-time | HTTP polling | Đơn giản, nhưng không instant. Chấp nhận được cho SMB |
| ORM | Drizzle | Lightweight, type-safe, nhưng ít features hơn Prisma |
| UI | shadcn-vue + Tailwind | Full control, copy-paste, nhưng nhiều setup hơn Vuetify |
| Auth | JWT + refresh (DB-stored) | Simple, mobile-friendly, secure rotation |
| Module comm | In-process EventEmitter + Service Registry | Simple, zero infra, nhưng không cross-process isolation |
| DB | Shared DB, module-owned tables | Simple cross-module queries, nhưng modules share DB load |
| Process manager | PM2 (bare-metal) | Simple, zero-downtime reload, nhưng không container isolation |
| Reverse proxy | Caddy (bare-metal) | Auto HTTPS, minimal config, nhưng ít ecosystem hơn Nginx |
| DB hosting | PostgreSQL in Docker | Easy management, isolated, nhưng slight overhead |
| Cache | In-memory first | Zero infra, nhưng mất khi restart. Migrate Redis khi cần |
| Storage | Filesystem first | Simple cho 1-server, migrate MinIO multi-server |
| Module arch | Domain-driven (business capability) | Match business mental model, nhưng larger individual modules |
| GPS logs | Table partitioning by month | Efficient queries + easy retention management |
Open Questions (Future Decisions)
- E-invoice integration: Which Vietnam e-invoice provider? (Viettel, MISA, etc.) — post-MVP
- Payment integration: Payment gateway for order collection? (MoMo, VNPay, bank transfer) — post-MVP
- GPS logs retention: Confirm 90-day raw retention policy? Aggregated data retention?
- Offline-ready architecture: Queue API calls when offline, sync when online — Phase 4/5
- Multi-deployment management: How to manage updates across many deployments? — when >5 deployments
- Vietnamese full-text search: PostgreSQL FTS config for Vietnamese tokenization — Phase 3
- Materials module scope: For manufacturing companies only? Or also for distribution with simple BOM?
FieldFlow — Architecture Proposal v2.1 — June 2026
Generated from fieldflow-dms-business-report.md + fieldflow-architecture-proposal.md