DRAFT — June 2026

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ý.

50+
Chức năng MVP
7
Business Modules
~21
Tuần MVP
4
Giai đoạn
01 — Vấn Đề

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 kiểm soát nhân viên

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.

Tồn kho mù

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.

Đơn hàng chậm, sai sót

Ghi giấy, gọi điện, nhắn tin → thất thoát, nhầm lẫn thông tin đơn hàng.

Báo cáo chậm

Tổng hợp cuối tuần/tháng → ra quyết định chậm, bỏ lỡ cơ hội kinh doanh.

02 — Đối Tượng Sử Dụng
Quản lý / Giám đốc

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.

Giám sát

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.

Nhân viên thị trường

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.

03 — Luồng Nghiệp Vụ

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.

Tạo tài khoản công ty
Nhập thông tin: tên, địa chỉ, loại hình
Import sản phẩm
Excel: SKU, tên, giá, đơn vị, danh mục
Tạo điểm bán
Tên, địa chỉ, GPS, loại hình, SĐT
Tạo tuyến
Tên tuyến, danh sách điểm bán, ngày chạy
Tạo NV & gán tuyến
Tên, SĐT, tuyến phụ trách
Kết quả
Hệ thống sẵn sàng. NV mở app thấy tuyến hôm nay.

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.

Thêm điểm bán mới
NV mở app → Nhập tên, địa chỉ
Chọn loại hình
Tạp hóa / Siêu thị mini / Nhà thuốc / Khác
GPS + Chụp hình
Tọa độ tự động + 1-2 hình mặt tiền
Gửi → Chờ duyệt
Quản lý duyệt trên dashboard

Flow 3: Check-in tại điểm bán (CORE FLOW)

Luồng quan trọng nhất
Diễn ra hàng ngày. Thời gian mục tiêu cho 1 điểm bán: 5–15 phút.
Chọn điểm bán
Tuyến hôm nay → Nhấn "Check-in"
Kiểm tra GPS
≤200m: OK. >200m: Force + lý do
Nghiệp vụ
Tồn kho + Chụp hình + Đơn hàng + Ghi chú
Check-out
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ỳ)

Nhập tồn kho
  • 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
  • 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
Tạo đơn hàng
  • 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
Ghi chú chung
  • 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ý)

Đơn từ NV
Hiện trên Dashboard
Xem chi tiết
NV, điểm bán, SP, SL, thành tiền
Phê duyệt
Duyệt / Từ chối / Chỉnh sửa
Cập nhật
Mới → Đã duyệt → Đang giao → Hoàn thành

Flow 5: Giám sát hàng ngày (Quản lý)

Tổng quan hôm nay
  • 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)
Chi tiết từng NV
  • Lộ trình trên bản đồ
  • Dòng thời gian check-in/out
  • Đơn hàng, hình ảnh, tồn kho
Cảnh báo
  • Điểm bán chưa ghé
  • NV chưa check-out
  • Force check-in cần xem lại
Báo cáo
  • 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)

Cuối ngày (18:00) — Hệ thống tự động tổng hợp
  • 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ý
04 — Chức Năng Mobile

Ứng dụng di động (Nhân viên thị trường)

Tổng MVP: 20/28 chức năng.

Chức năngMô tảMVP
A. Đăng nhập & Tài khoản
M1Đăng nhậpSĐT + mật khẩuMVP
M2Thông tin cá nhânXem tên, vai trò, tuyến phụ tráchMVP
M3Đổi mật khẩuTự đổi mật khẩuMVP
M4Thông báo đẩyNhận thông báo từ quản lýMVP
B. Tuyến bán hàng
M5Tuyến hôm nayDanh sách điểm bán theo thứ tự, trạng tháiMVP
M6Chi tiết điểm bánTên, địa chỉ, SĐT, lịch sử ghé gần nhấtMVP
M7Điều hướngMở app bản đồ dẫn đường đến điểm bánMVP
M8Thêm điểm bán mớiTạo nhanh tại field, GPS tự động lấy tọa độMVP
M9Xem 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
M10Check-in GPSBán kính 200m, ghi thời gian + tọa độMVP
M11Force check-inNgoài bán kính: cho phép + nhập lý doMVP
M12Check-outKết thúc visit, tính tổng thời gianMVP
M13Lịch sử check-inXem lại các lần ghé thămv2
D. Tồn kho
M14Nhập tồn khoDanh sách SP → nhập số lượng nhanhMVP
M15Cảnh báo hết hàngHighlight SP tồn kho = 0MVP
M16So sánh với lần trướcChênh lệch (+/-) so với visit trướcv2
M17Quét mã vạchQuét barcode tìm SP nhanhv2
E. Hình ảnh
M18Chụp hìnhTrực tiếp trong app, tối đa 5 hình/lần ghéMVP
M19Xem lại hìnhPreview trước khi lưu, chụp lại nếu mờMVP
M20Thư viện hìnhXem lại hình ảnh lịch sử tại điểm bánv2
F. Đơn hàng
M21Tạo đơn hàngChọn SP + SL, tự tính thành tiềnMVP
M22Ghi chú đơn hàngText tự do (giao chiều, gọi trước...)MVP
M23Trạng thái đơnMới/Đã duyệt/Đang giao/Hoàn thànhMVP
M24Lịch sử đơn hàngDanh sách đơn đã tạo, filter theo ngàyv2
M25Gợi ý đặt hàngGợi ý dựa trên lịch sử tiêu thụv3
G. Khác
M26Ghi chú tại điểm bánText tự do, "Chủ nói hết bia"MVP
M27Tổng kết ngàyNV xem: ghé mấy điểm, tạo mấy đơn, doanh sốMVP
M28Offline modeLàm việc không mạng, đồng bộ khi có mạngv2
05 — Chức Năng Web

Bảng điều khiển web (Quản lý)

Tổng MVP: 30/34 chức năng.

Chức năngMô tảMVP
A. Tài khoản & Phân quyền
W1Đăng ký / Đăng nhậpTạo tài khoản công tyMVP
W2Thông tin công tyTên, địa chỉ, logo, loại hìnhMVP
W3Phân quyềnQuản lý / Giám sát / Nhân viên — khác quyền xemMVP
B. Quản lý Sản phẩm
W4Danh sách sản phẩmBảng: tên, SKU, giá, đơn vị, danh mục. Tìm, filterMVP
W5Thêm/Sửa/Xóa SPForm nhập thông tin sản phẩmMVP
W6Import từ ExcelImport hàng loạt sản phẩm từ fileMVP
W7Phân nhóm danh mụcFMCG, bia, nước, bánh kẹo...MVP
C. Quản lý Điểm bán
W8Danh sách điểm bánBảng: tên, địa chỉ, loại hình, trạng tháiMVP
W9Thêm/Sửa điểm bánForm: tên, địa chỉ, tọa độ bản đồ, SĐT, loại hìnhMVP
W10Import từ ExcelImport hàng loạt điểm bánMVP
W11Duyệt điểm bán mớiNV tạo ở field → quản lý duyệt/từ chốiMVP
W12Xem 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
W13Tạo tuyếnTên tuyến + chọn điểm bán + sắp xếp thứ tựMVP
W14Gán tuyến cho NV1 NV có thể có nhiều tuyến (khác ngày)MVP
W15Lịch trình tuầnCấu hình: tuyến nào chạy thứ mấyMVP
W16Tối ưu tuyếnTự động sắp xếp theo khoảng cáchv2
E. Quản lý Nhân viên
W17Danh sách NVBảng: tên, SĐT, tuyến, trạng tháiMVP
W18Tạo tài khoản NVTạo tài khoản cho NV đăng nhập appMVP
W19Reset mật khẩuQuản lý reset mật khẩu cho NVMVP
W20Vị trí GPS real-timeXem vị trí hiện tại tất cả NV trên bản đồMVP
F. Giám sát & Dashboard
W21Dashboard tổng quanNV active, điểm đã ghé, đơn hàng, doanh sốMVP
W22Bản đồ theo dõiVị trí NV + lộ trình đã điMVP
W23Dòng thời gian NVClick NV → xem timeline ghé thămMVP
W24Chi tiết lần ghé thămTồn kho, hình ảnh, đơn hàng, ghi chúMVP
W25Cảnh báoChưa ghé, chưa check-out, force check-inMVP
G. Đơn hàng
W26Danh sách đơn hàngFilter: ngày, NV, trạng thái, điểm bánMVP
W27Chi tiết đơnSản phẩm, SL, thành tiền, ghi chúMVP
W28Duyệt/Từ chốiDuyệt hoặc từ chối + lý doMVP
W29Cập nhật trạng tháiMới → Đã duyệt → Đang giao → Hoàn thànhMVP
H. Báo cáo
W30Báo cáo doanh sốTheo ngày/tuần/tháng, NV/khu vực, biểu đồMVP
W31Báo cáo tồn khoTồn kho tại điểm bán, SP sắp hếtMVP
W32Báo cáo hiệu suất NVSố điểm ghé, số đơn, doanh số, thời gianMVP
W33Báo cáo tần suất ghé thămOutlet ghé thường xuyên, outlet bỏ lỡv2
W34Xuất báo cáoXuất file Excel / PDFMVP
06 — Mô Hình Dữ Liệu

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

60-80
Visits / ngày
800-1.2k
Inventory records
200-400
Photos
15-30
Orders
80-150
Order Items

Giả định MVP

Online-first
Yêu cầu mạng khi sử dụng
1 công ty = 1 tenant
Dữ liệu cách ly giữa các công ty
Đơn giá cố định
Giá bán thiết lập ở mức công ty
Tồn kho = snapshot
Chỉ ghi nhận tại thời điểm NV nhập
Bán kính 200m
Có thể cấu hình sau
Đơn vị theo SP
Mỗi SP tự quy định đơn vị
Không khuyến mãi
Trade marketing = v2

Không bao gồm trong MVP

Công nợ / thanh toán
Kho tổng (chỉ kho điểm bán)
Tích hợp ERP / kế toán
Khuyến mãi / chiết khấu
Offline mode
AI gợi ý đơn hàng
07 — Kiến Trúc Hệ Thống

High-level Architecture

Nguyên tắc thiết kế
Single-tenant
Mỗi DN = 1 VPS riêng, DB riêng
Domain-driven modules
Module chia theo nghiệp vụ (DMS, kho, chấm công...)
Modular monolith
1 process, module giao tiếp qua event bus
Theme + Layout customizable
Màu sắc, logo, layout per deployment
One source, multi-platform
Vue 3 SPA → Capacitor Android/iOS
Bare-metal first
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

Event Bus (Async)

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

Service Calls (Sync)

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ộ

Shared Core Data

Data dùng chung (users, products, companies).

Implementation: Core module expose services.

Products = shared: DMS bán products, Inventory track

08 — Technology Stack

Frontend (Web + Mobile)

ComponentTechnologyLý do
FrameworkVue 3Composition API, reactive
Build toolViteFast HMR
RoutingVue Router 4SPA, dynamic module routes
StatePiniaTypeScript-friendly
UI Componentsshadcn-vueCustomizable, accessible
CSSTailwind CSS 3Utility-first, theme-able
MobileCapacitor 6SPA → native Android/iOS
MapsLeaflet + OSMFree, không giới hạn
HTTP ClientofetchModern, type-safe
FormsVeeValidate + ZodValidation schema
ChartsApache EChartsPowerful reports
TablesTanStack TableServer-side pagination

Backend

ComponentTechnologyLý do
RuntimeNode.js 20 LTSSame language FE/BE
FrameworkFastify 5Fast, plugin architecture
LanguageTypeScriptType safety across stack
ORMDrizzle ORMLightweight, type-safe
DatabasePostgreSQL 16Reliable, JSON support
CacheIn-memory→ Redis khi cần (YAGNI)
StorageFilesystem→ MinIO khi cần (YAGNI)
Authbcrypt + JWTAccess + refresh tokens
ValidationZodShared schemas API + Web
PushFirebase Admin SDKFCM notifications
ExcelExcelJSImport/export Excel
PDFPDFKitXuất báo cáo PDF
ImagesSharpResize/compress uploads
TestingVitestFast, native ESM

Infrastructure

ComponentTechnologyLý do
DatabasePostgreSQL 16 (Docker)Đóng gói, dễ quản lý, isolated
Reverse ProxyCaddy (bare-metal)Auto HTTPS, config minimal
Process ManagerPM2 (bare-metal)Auto restart, zero-downtime reload
SSLCaddy auto-TLSLet's Encrypt, zero config
CI/CDGit pull + pm2 reloadSimple cho SMB
MonitoringPM2 logs + health endpointĐủ cho SMB scale
BackupHost cron: pg_dump → gzipAutomated daily backup
09 — Module System

Business Modules

ModuleLabelMô tảDependenciesAPI Prefix
dmsQuản lý phân phốiOutlets, routes, visits, sales orders, GPS tracking, photosinventory/api/v1/dms/
inventoryQuản lý tồn khoWarehouses, stock items, stock movements, adjustments/api/v1/inventory/
materialsQuản lý nguyên vật liệuRaw materials, suppliers, purchase orders, BOMinventory/api/v1/materials/
attendanceQuản lý chấm côngTime entries, schedules, shifts, GPS check-in/out/api/v1/attendance/
promotionsQuản lý khuyến mãiCampaigns, discounts, pricing rules, gift-with-purchasedms/api/v1/promotions/
debt-trackingQuản lý công nợCredit limits, payments, aging reports, collectiondms/api/v1/debt-tracking/
reportsBáo cáoCross-module aggregation, sales/inventory/attendanceall enabled/api/v1/reports/

Cross-Module Events

DMS Events
  • dms.order.created
  • dms.order.approved
  • dms.order.completed
  • dms.order.cancelled
  • dms.visit.checkedin
  • dms.visit.completed
  • dms.outlet.created
Other Events
  • inventory.stock.low
  • inventory.movement.created
  • inventory.stock.adjusted
  • attendance.checkedin
  • attendance.checkedout
  • attendance.late

Module Config (per deployment)

ModuleEnabledKey Config
dmsEnabledGPS polling 30s, requirePhoto: true, geofence: 100m
inventoryEnabledFIFO, lowStockAlert: true, threshold: 10
materialsDisabledFor manufacturing companies, trackBOM: true
attendanceEnabledGeofence: 200m, manual checkin: true, 08:00-17:30
promotionsDisabled
debt-trackingDisabled
reportsEnabledCross-module reporting

Cross-Module Communication Examples

Example 1: Tạo đơn hàng (DMS → Inventory → Reports)
  1. Mobile appPOST /api/v1/dms/orders
  2. [Service Call] registry.getService('inventory','checkStock') → kiểm tra tồn kho. Nếu thiếu → error.
  3. [Service Call] registry.getService('core','getProductPrices') → lấy giá hiện tại.
  4. Tạo order + order_items trong DB.
  5. [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
Example 2: Check-in chấm công (Attendance → DMS)
  1. Mobile appPOST /api/v1/attendance/checkin
  2. Tạo time_entry trong DB.
  3. [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)
  1. Web appGET /api/v1/reports/sales?from=...&to=...
  2. [Service Call] registry.getService('dms','getOrderStats')
  3. [Service Call] registry.getService('inventory','getMovementStats')
  4. [Service Call] registry.getService('attendance','getAttendanceStats')
  5. Aggregate + return combined report.
10 — Database Design

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
TableKey Fields
companiesid, name, slug, address, industry, logo_url, phone, email, settings (JSONB), plan, is_active
usersid, company_id, full_name, phone, email, password_hash (bcrypt), role (admin/supervisor/sales_rep/warehouse_staff), is_active, fcm_token
productsid, company_id, sku, name, description, unit, unit_price, category, image_url, product_type (finished/raw/semi_finished), metadata (JSONB)
notificationsid, company_id, user_id, type, title, body, data (JSONB), is_read
DMS Module Tables: outlets, routes, visits, orders, photos, gps_logs
TableKey Fields
outletsid, 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
routesid, company_id, name, user_id, day_of_week (int[]), is_active
route_outletsid, route_id, outlet_id, sort_order
visitsid, 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)
photosid, visit_id, photo_url, thumbnail_url, caption, taken_at
sales_ordersid, company_id, visit_id, user_id, outlet_id, order_number, total_amount, status (pending/approved/delivering/completed/cancelled), notes, approved_by, cancelled_reason
order_itemsid, order_id, product_id, quantity, unit_price, total_price
gps_logsid, company_id, user_id, lat, lng, accuracy, battery_level, recorded_at — partitioned by month
Inventory Module Tables: warehouses, stock_items, stock_movements
TableKey Fields
warehousesid, company_id, name, address, is_active
stock_itemsid, warehouse_id, product_id, quantity, reserved_quantity — UNIQUE(warehouse_id, product_id)
stock_movementsid, 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
TableKey Fields
suppliersid, company_id, name, contact_name, contact_phone, email, address
material_detailsid, product_id, supplier_id, min_order_quantity, lead_time_days, unit_cost, reorder_point
bill_of_materialsid, company_id, finished_product_id, material_product_id, quantity_required, unit
purchase_ordersid, company_id, supplier_id, po_number, status (draft/sent/partial/received/cancelled), total_amount, order_date, expected_date
purchase_order_itemsid, po_id, product_id, quantity, unit_cost, total_cost, received_quantity
Attendance Module Tables: schedules, shifts, time_entries, geofences
TableKey Fields
schedulesid, company_id, name, start_time, end_time, late_threshold (minutes), is_active
shiftsid, user_id, schedule_id, date — UNIQUE(user_id, date)
time_entriesid, 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)
geofencesid, company_id, name, lat, lng, radius (meters), is_active
11 — API Design

REST API Structure

Base URL: /api/v1

Auth
  • POST /auth/login
  • POST /auth/refresh
  • POST /auth/logout
  • POST /auth/change-password
Users
  • GET/POST /users
  • GET/PUT/DELETE /users/:id
  • POST /users/:id/reset-password
Products
  • GET/POST /products
  • GET/PUT/DELETE /products/:id
  • POST /products/import
  • GET /products/export
Settings & Notifications
  • GET/PUT /settings/company
  • GET/PUT /settings/theme
  • GET/PUT /settings/modules
  • GET /notifications
  • PATCH /notifications/:id/read

DMS Module Endpoints

MethodEndpointMô tả
GET/dms/outletsDanh sách điểm bán
POST/dms/outletsTạo điểm bán mới
POST/dms/outlets/importImport Excel
PATCH/dms/outlets/:id/approveDuyệt điểm bán
GET/dms/routesDanh sách tuyến
GET/dms/routes/todayTuyến hôm nay
POST/dms/visits/checkinCheck-in GPS
PUT/dms/visits/:id/checkoutCheck-out
GET/dms/visits/user/:userId/timelineTimeline NV
POST/dms/ordersTạo đơn hàng
PATCH/dms/orders/:id/approveDuyệt đơn
PATCH/dms/orders/:id/rejectTừ chối đơn
POST/dms/gps/logGhi GPS log
GET/dms/gps/employeesVị trí NV real-time
GET/dms/dashboard/summaryTổng quan dashboard
GET/dms/dashboard/employee-mapBản đồ NV

API Response Format

// Success { "success": true, "data": { ... }, "meta": { "page": 1, "pageSize": 20, "total": 150, "totalPages": 8 } }
// Error { "success": false, "error": { "code": "VALIDATION_ERROR", "message": "Số điện thoại không hợp lệ", "details": [ ... ] } }
12 — Auth & RBAC

Authentication & Authorization

Login
phone/email + password
accessToken
JWT, 15min TTL
refreshToken
JWT, 7day TTL, DB-stored
Every API call
Bearer accessToken

Refresh Token Strategy

Storage
  • 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
Rotation
  • 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

Moduleadminsupervisorsales_repwarehouse_staff
CoreFull accessusers:read, products:readproducts:readproducts:read
DMSFull accessoutlets:manage, orders:approve, visits:readoutlets:read, routes:read, visits:create, orders:createorders:read
InventoryFull accessreadmanage
MaterialsFull accessreadpurchase:manage
AttendanceFull accessread, schedules:managecheckin:createcheckin:create
ReportsFull accessread, exportread:ownread:inventory
13 — Deployment

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)

git pull
Pull latest code
npm ci + build
Build API
db:migrate
Run migrations
pm2 reload
Zero-downtime
Build SPA
Copy to web dir

Common Operations

Thao tácLệnh
Xem API logspm2 logs fieldflow-api
Restart (zero-downtime)pm2 reload fieldflow-api
Hard restartpm2 restart fieldflow-api
Monitorpm2 monit
Caddy logsjournalctl -u caddy -f
Reload Caddysystemctl reload caddy
Backup (daily 2am cron)/opt/fieldflow/deploy/scripts/backup.sh
GPS logs cleanup (weekly)/opt/fieldflow/deploy/scripts/cleanup-gps-logs.sh
14 — MVP Phasing

Lộ trình triển khai

Total MVP (Phase 1-4): ~21 tuần (~5 tháng)
Với Materials (Phase 5): ~24 tuần (~6 tháng).
Phase 1 — Foundation (4-6 tuần)
Core + Auth + 1 Module (DMS basic)
  • 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
Phase 2 — Core DMS Flows (4-6 tuần)
Check-in/out, orders, maps
  • 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
Phase 3 — Inventory + Attendance (4-6 tuần)
Full inventory flows, attendance module
  • 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
Phase 4 — Polish & Mobile (2-3 tuần)
Mobile app, notifications, theme
  • Tuần 19-20: Capacitor mobile build, FCM push notifications, Theme system
  • Tuần 21: Testing, bug fixes, deployment automation
Phase 5 — Materials + Future (2-3 tuần)
Materials module, promotions/debt-tracking
  • Tuần 22-23: Materials module: raw materials, suppliers, BOM, purchase orders
  • Tuần 24: Additional modules per customer needs
15 — Quyết Định Kỹ Thuật

Key Technical Decisions & Trade-offs

DecisionChoiceTrade-off
Mobile approachCapacitor (web wrapper)Nhanh hơn, nhưng native features hạn chế vs RN/Flutter
MapsLeaflet + OSMFree, không giới hạn, nhưng ít polished hơn Google Maps
Real-timeHTTP pollingĐơn giản, nhưng không instant. Chấp nhận được cho SMB
ORMDrizzleLightweight, type-safe, nhưng ít features hơn Prisma
UIshadcn-vue + TailwindFull control, copy-paste, nhưng nhiều setup hơn Vuetify
AuthJWT + refresh (DB-stored)Simple, mobile-friendly, secure rotation
Module commIn-process EventEmitter + Service RegistrySimple, zero infra, nhưng không cross-process isolation
DBShared DB, module-owned tablesSimple cross-module queries, nhưng modules share DB load
Process managerPM2 (bare-metal)Simple, zero-downtime reload, nhưng không container isolation
Reverse proxyCaddy (bare-metal)Auto HTTPS, minimal config, nhưng ít ecosystem hơn Nginx
DB hostingPostgreSQL in DockerEasy management, isolated, nhưng slight overhead
CacheIn-memory firstZero infra, nhưng mất khi restart. Migrate Redis khi cần
StorageFilesystem firstSimple cho 1-server, migrate MinIO multi-server
Module archDomain-driven (business capability)Match business mental model, nhưng larger individual modules
GPS logsTable partitioning by monthEfficient queries + easy retention management

Open Questions (Future Decisions)

  1. E-invoice integration: Which Vietnam e-invoice provider? (Viettel, MISA, etc.) — post-MVP
  2. Payment integration: Payment gateway for order collection? (MoMo, VNPay, bank transfer) — post-MVP
  3. GPS logs retention: Confirm 90-day raw retention policy? Aggregated data retention?
  4. Offline-ready architecture: Queue API calls when offline, sync when online — Phase 4/5
  5. Multi-deployment management: How to manage updates across many deployments? — when >5 deployments
  6. Vietnamese full-text search: PostgreSQL FTS config for Vietnamese tokenization — Phase 3
  7. 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