← Home

Projects Deep — Interview Reference

Mock Q →

// 2026-05-19 — interview hôm nay 15:00 với Leaders Chuyên môn Base.vn

Phong's Project Portfolio

5 featured projects + AI Hackathon + Work Experience — based on REAL codebase scan

Summary Shiru TWD Agents Hub Twendee ERP MoneyFi Decot VCCI Go Claw CKForensics 🔥 Pain Points Talking Points Q to Ask

⚡ Quick Profile Snapshot

Experience: 2 years (Front-end + Full-stack)

Current: Twendee Limited Software (Feb 2025 - Now)

Background: 7+ FE projects · 5+ Web3 products (~$20M TX volume) · AI Agents Engineering

Education: PTIT, Electronics & Telecom, GPA 3.45 (Distinction)

Stack mạnh:

TypeScriptNext.jsReactNestJSPrismaPostgreSQLRedisBullMQTanStackWeb3Claude CodeMCP

"Em là Front-end Developer 2 năm kinh nghiệm, hiện tại đang shift sang Full-stack và AI Agentic Engineering tại Twendee. Em đã ship 5+ Web3 products với tổng transaction volume ~$20M, build full-stack platforms với NestJS + Prisma + PostgreSQL, và đang lead 1 AI Agent platform multi-tenant cho ERP."

🌟 Shiru — AI Advisor Web3 Application

Fullstack · Jan 2026-Now

Pitch 1 câu: AI-powered crypto portfolio platform với dual auth (SIWE + Email/PIN), KYC/KYB workflow, AI investment recommendations, portfolio tracking trên 6 EVM chains.

FE Stack

Next.jsTailwindCSSShadcn UITanStack Query

BE Stack (REAL từ package.json)

NestJS 9Prisma 7PostgreSQL@prisma/adapter-pgRedis (ioredis)BullMQJWT + PassportSendGrid@nestjs/throttler@nestjs/scheduleSwaggerMoralis SDKSumsubPM2Docker

🏗️ Architecture (REAL từ source scan):

🎯 Key Backend Decisions (Talk about these):

  1. Tại sao Repository pattern? → Tách Prisma logic khỏi service. Service không phụ thuộc ORM cụ thể. Dễ test (mock repository).
  2. Tại sao JWT refresh-rotation với Token table? → Refresh token reuse detection. Mỗi lần refresh, mark old token usedAt; nếu thấy reuse → revoke entire family (security).
  3. Tại sao BullMQ thay vì sync? → AI recommendation generation chậm (~5s/call), không block API. Portfolio sync chạy background. Retry built-in cho external API failures.
  4. Tại sao custom cache thay vì Redis trực tiếp? → Encapsulate TTL conventions, key naming, fallback to DB nếu Redis down.
  5. Tại sao separate cron-job process? → Crash isolation. Cron failure không kill main API. PM2 manage cả 2 process riêng.
  6. Tại sao Merkle tree cho whitelist? → Lưu 1 root hash on-chain (~32 bytes) thay vì N addresses (gas saving). User submit proof khi claim.

🔐 Auth Flow Deep (REAL từ src/modules/auth/auth.service.ts):

🤖 AI Integration Module (REAL submodules):

⏰ Cron Jobs (3 specific jobs):

🔥 Pain Points (REAL từ codebase scan):

🚀 Improvement ideas (nếu interviewer hỏi):

  1. OpenTelemetry tracing across NestJS → BullMQ jobs → external APIs để debug slow flows
  2. Prisma slow query logging + Datadog/Sentry integration cho N+1 detection
  3. Cursor pagination cho TransactionHistory thay offset (table sẽ lớn nhanh)
  4. Event sourcing cho KYB audit log — immutable history với optimistic concurrency
  5. Tag-based cache invalidation với meta convention (đã learn TanStack Query meta pattern — apply tương tự server side)
  6. Helmet + CSP headers nếu chưa có, plus signed cookies cho refresh token
  7. Database connection pool tuning — Prisma adapter-pg cho phép control pool size theo memory budget
  8. Health endpoint deeper checks — không chỉ DB/Redis mà còn Moralis quota, Sumsub responsive, SendGrid status

🤖 TWDAgentsHub — Multi-tenant AI Orchestrator

AI Agentic · 2026

Pitch 1 câu: Multi-tenant AI Orchestrator SaaS cho Vietnamese SMEs — 5 domain agents (Finance, HR, Sales, Supply, Compliance), multi-channel input (REST, Web Chat, Telegram), pluggable ERP connectors, proactive intelligence.

⚠️ CỰC kỳ relevant cho Base.vn — multi-tenant SaaS, Vietnamese market, HR agent, ERP-style. Có thể là bài tủ trong interview.

Stack

NestJSTypeScriptPostgreSQL 16 (RLS!)Redis 7Vercel AI SDKReactshadcn/uiTurborepopnpm workspacesBiomeDocker

Monorepo Layout

apps/
  api/         NestJS modular monolith
  admin-ui/    Vite + React + shadcn
  chat-widget/ Embeddable Vite lib
packages/
  shared-types/    TS contracts
  erp-adapters/    Pluggable ERP connectors
tooling/
  tsconfig/        Shared TS configs
  tailwind-config/ Shared Tailwind preset

📦 API Submodules (REAL từ apps/api/src/):

agents/ — 5 domain agents (Finance, HR, Sales, Supply, Compliance)
orchestrator/ — Agent orchestration layer
dynamic-agents/ — Runtime-created agents
mcp/ — Model Context Protocol servers
llm/ — Multi-provider LLM abstraction
memory/ — Agent memory (conversation context)
conversations/ — Chat conversations CRUD
messages/ — Message persistence
chat/ — Chat session orchestration
channels/ — Multi-channel routing
channel-bots/ — Telegram/Slack/Discord bots
lead-notifications/ — Proactive intelligence alerts
auth/ — Multi-tenant auth
database/ — Prisma + RLS setup
redis/ — Redis client wrapper
health/ — Health checks (db, redis)
common/ — Shared utilities
config/ — Environment config

→ 18 submodules. Modular monolith done right — clear bounded contexts trong cùng app.

🏗️ Key Architecture Decisions:

  1. Multi-tenant với PostgreSQL RLS (Row-Level Security) → từng tenant chỉ thấy data của mình, enforced tại DB layer (không phụ thuộc app code). Defense-in-depth.
  2. Modular monolith (không microservices) → 5 devs, 8-week MVP. Microservices premature cho scale này.
  3. Turborepo + pnpm workspaces → share TS types qua shared-types package, cache builds, parallel tasks.
  4. Pluggable ERP adapters → strategy pattern. Mỗi ERP (vd SAP, Odoo, custom) có adapter riêng implement common interface.
  5. Vercel AI SDK → unified abstraction cho multiple LLM providers (OpenAI, Anthropic, GLM). Streaming + tool calling built-in.
  6. Multi-channel input → REST API + Web Chat (embedded widget) + Telegram bot. Tất cả route vào cùng agent engine.
  7. Biome thay ESLint+Prettier → 1 tool, 10× faster, simpler config.
  8. Health endpoint pattern: {status, uptime, checks: {db, redis}} — Kubernetes/load balancer friendly.

🚀 Improvement ideas:

  1. Prompt evaluation harness — golden dataset, regression test khi đổi prompt
  2. Prompt caching (Anthropic) — cache system prompts, reduce 90% input tokens
  3. Cost dashboard per tenant — track LLM spend, enforce quotas
  4. Human-in-the-loop approval gates cho actions sensitive (finance/HR writes) — đã có trong CV
  5. Structured logging cho agent steps (LangFuse hoặc tự build) — debug AI decisions
  6. Async streaming responses cho long-running agents qua SSE
  7. Multi-tenant rate limiting — fair share resources, prevent noisy neighbor

🏢 Twendee ERP — Internal Business Platform

Fullstack · 2025

Pitch: Internal ERP cho Twendee gồm hr-system (backend NestJS + Prisma + Cerbos RBAC), new-erp-ui (frontend), erp-agents-presentation (AI agents UI). Multi-environment (dev/staging/prod) với Docker.

⚠️ Đặc biệt relevant Base.vn — đây là tương tự Base HRM (HR management).

🏗️ Architecture Highlights:

🎯 Key Decisions to discuss:

  1. Tại sao Cerbos thay vì hardcoded RBAC? → Policy as code. Non-dev (PM, HR) có thể đề xuất policy thay đổi. Audit trail của policy changes. Decouple authz logic khỏi business code.
  2. Tại sao tách hr-system khỏi new-erp-ui? → Separation FE/BE concern, độc lập deployment.
  3. Multi-env yml configs: 12-factor app principle — config in env, không hardcode.

🔥 Pain Points (REAL từ codebase scan):

🔄 Flow/UX Pain Points (Process Design Issues):

1. Onboarding Flow — Multi-Channel Chaos

HR tạo quy trình (Web) → Nhận link Telegram → Gửi link khác cho nhân viên mới
    ↓                         ↓                        ↓
Fill metadata         Bot gửi /profile#config    Fill onboarding form (link riêng)
    ↓                         ↓                        ↓
Assign responsible    User phải vào web link    Submit → HR nhận status update
        
  • Problem: 3 links khác nhau, user không biết link nào làm gì
  • Problem: Telegram chỉ thông báo, phải quay về web để action
  • Solution: Single onboarding link + Telegram inline buttons + progress checklist

2. Leave Request Flow — Approval Visibility Gap

Employee submit (Web) → Manager nhận email+Telegram → Phải vào web approve
    ↓                              ↓                         ↓
No in-app notification    "Click link to approve"    No timestamp ai approved khi nào
    ↓                              ↓                         ↓
Employee refresh page      No inline approve btn     No escalation nếu pending >3 ngày
        
  • Problem: Employee không biết "đang pending ai" trên dashboard
  • Problem: Approver nhận Telegram nhưng phải ra web để approve
  • Solution: Telegram /approve_[id] callback + approval timeline với timestamp

3. Payroll Flow — No Calculation Transparency

  • Problem: UI shows final amount, not breakdown (gross → deductions → net)
  • Problem: Approval status unclear ("CEO rejected" but no corrective action shown)
  • Problem: Manual export → email to bank → no audit trail
  • Solution: Payroll builder UI + reject with comments + one-click export với audit log

4. Cross-Channel Friction Matrix

StepWebTelegramEmail
Request createdForm submit ✓User notified (link)Approver notified
Approval neededView page → click"Approve via link" 🔴Link in email
Status updateRefresh page 🔴Message in chatNo push 🔴

→ Thiếu: Notification Hub (in-app bell), Telegram inline actions, auto-escalation

📈 Scaling Bottlenecks (1K → 100K users):

Area 1K 10K 100K Solution
Database ⚠️ 🔴 💀 Connection pool + .select() thay .include()
State (2,247 useState) ⚠️ 🔴 💀 TanStack Query + virtualization
Notifications ⚠️ 🔴 💀 Bull queue + exponential backoff
Caching ⚠️ 🔴 Redis cho roles/permissions/config
File Storage ⚠️ ⚠️ Streaming upload + presigned URLs
Multi-tenancy 🔴 💀 💀 tenantId + PostgreSQL RLS (rewrite)

🔥 Critical Findings:

  • User model có 80+ relations — all fetched via .include()
  • No connection pooling configured
  • Notifications sent synchronously trong request cycle
  • In-memory cache only (no Redis) — OOM risk
  • No tenantId — multi-org impossible

⏱️ Priority Fix Timeline:

  1. Hour 1: DB connection pooling
  2. Hour 2: Replace .include().select()
  3. Hour 4: Bull queues for notifications
  4. Hour 6: Redis caching layer
  5. Day 2: React state optimization

🔧 Process Extensibility (Adding New Workflow Types):

Current process types: ONBOARDING, OFFBOARDING, PURCHASE, CHANGE_SOCIAL_INSURANCE

Component Extensibility Current State What Breaks
Process Types 1/5 Hardcoded enum New type = enum + Prisma model + service + migration
Step Definitions 2/5 Helper functions per type New steps = new helper + hardcoded order
State Machine 1/5 Switch-case per type Transitions scattered, not pluggable
Forms 2/5 DTO per type New type = new DTO + new FE component
Notifications 2/5 Hardcoded in updateStatus() New type = new case statement
Templates (Non-dev) 0/5 Not supported PM/HR không thể tạo process type mới

🚨 Adding New Process Type Requires:

  1. Add enum value to ProcessType
  2. Create new Prisma model + migration
  3. Create new service + helper functions
  4. Add switch-case for transitions
  5. Create new DTO + FE form component
  6. Add notification rules manually

→ ~3-5 days per new process type

✅ True Extensibility Solution:

  • Move ProcessType to DB table (config-driven)
  • Generic step engine (store steps in DB)
  • BPMN-like rule engine for transitions
  • JSON schema for forms (dynamic rendering)
  • Event-driven notifications with templates
  • Process builder UI for PM/HR

→ ~2-3 weeks refactor

✅ Flexible Workflow Engine Solution:

Database-driven workflow definitions — PM/HR can create process types via UI

📊 CONFIG LAYER — Table Fields (click to expand)

1. ProcessDefinition — Workflow Templates

FieldTypePurpose
idString (CUID)Primary key
codeString UNIQUE"ONBOARDING", "LEAVE_REQUEST"
nameString"Employee Onboarding"
descriptionString?Mô tả cho PM/HR
categoryString"HR", "FINANCE", "IT"
iconString?"user-plus", "calendar"
formSchemaJsonJSON Schema cho dynamic form
isActiveBooleanEnable/disable
versionIntSchema versioning
createdBy, createdAt, updatedAtAuditTrack changes

2. StepDefinition — Generic Steps

FieldTypePurpose
idString (CUID)Primary key
processDefinitionIdFKBelongs to which process
codeString"DEPT_APPROVAL", "CEO_SIGN"
nameString"Department Approval"
orderIntExecution order (1, 2, 3...)
typeEnumACTION | APPROVAL | NOTIFICATION | CONDITION
assigneeTypeEnumROLE | USER | DEPT_HEAD | MANAGER | DYNAMIC
assigneeConfigJson?{"roleId": "...", "expression": "..."}
formSchemaJson?Step-specific form fields
timeoutHoursInt?Auto-escalate after N hours
escalationRoleIdFK?Escalate to this role
conditionExpressionString?"data.amount > 10000000"
isRequired, allowReject, allowDelegateBooleanStep behaviors

3. TransitionRule — State Machine

FieldTypePurpose
idString (CUID)Primary key
processDefinitionIdFKBelongs to which process
fromStepIdFK?null = START state
toStepIdFK?null = END state
triggerActionEnumAPPROVE | REJECT | COMPLETE | TIMEOUT
conditionExpressionString?Additional condition check
sideEffectsJson?[{type: "SEND_NOTIFICATION", ...}]

Example: Step1 --APPROVE--> Step2 --REJECT--> END

4. NotificationTemplate — Event Notifications

FieldTypePurpose
idString (CUID)Primary key
processDefinitionIdFKBelongs to which process
codeString"STEP_ASSIGNED", "COMPLETED"
triggerEventEnumSTEP_ASSIGNED | COMPLETED | REJECTED | TIMEOUT
triggerStepCodeString?Specific step or null = process-level
channelsEnum[][EMAIL, TELEGRAM, IN_APP]
recipientTypeEnumREQUESTER | ASSIGNEE | ROLE
subjectTemplateString"Leave #{{processId}} - Action Required"
bodyTemplateString"Hi {{recipientName}}, ..."
🏃 RUNTIME LAYER — Table Fields (click to expand)

5. ProcessInstance — Running Processes

FieldTypePurpose
idString (CUID)Primary key
processDefinitionIdFKWhich process template
referenceNumberString UNIQUE"ONB-2026-0001" (human-readable)
requesterIdFK UserWho created this request
statusEnumDRAFT | SUBMITTED | IN_PROGRESS | COMPLETED | CANCELLED
dataJsonProcess form data (validated by formSchema)
currentStepIdFK?Current active step
submittedAt, completedAt, cancelledAtDateTime?Lifecycle timestamps

6. StepInstance — Step Executions

FieldTypePurpose
idString (CUID)Primary key
processInstanceIdFKBelongs to which process run
stepDefinitionIdFKWhich step template
assigneeIdFK User?Resolved at runtime
statusEnumPENDING | ASSIGNED | IN_PROGRESS | COMPLETED | REJECTED | TIMEOUT
dataJson?Step-specific form data
decisionEnum?APPROVED | REJECTED | DELEGATED
decisionCommentString?"Approved, looks good"
dueAtDateTime?Calculated from timeoutHours
assignedAt, startedAt, completedAtDateTime?Step lifecycle

7. ProcessHistory — Audit Trail (Immutable)

FieldTypePurpose
idString (CUID)Primary key
processInstanceIdFKWhich process run
actorIdFK UserWho performed action
actionEnumCREATED | SUBMITTED | STEP_COMPLETED | REJECTED | CANCELLED
stepCodeString?Which step (if step-related)
fromStatus, toStatusString?Status transition
commentString?User comment
metadata, createdAtJson?, DateTimeExtra context + timestamp

Insert-only table — never update/delete for compliance

📋 ENUMS — All Enum Values (click to expand)

StepType

ACTION | APPROVAL | NOTIFICATION | CONDITION | PARALLEL

AssigneeType

ROLE | USER | DEPT_HEAD | REQUESTER_MANAGER | DYNAMIC

TriggerAction

APPROVE | REJECT | COMPLETE | TIMEOUT | CONDITION_TRUE

ProcessStatus

DRAFT | SUBMITTED | IN_PROGRESS | COMPLETED | CANCELLED | REJECTED

StepStatus

PENDING | ASSIGNED | IN_PROGRESS | COMPLETED | REJECTED | SKIPPED | TIMEOUT

NotificationChannel

EMAIL | TELEGRAM | IN_APP | SMS

🎯 USER FLOWS — Cách tạo và chạy quy trình (click to expand)

FLOW 1: PM/HR Tạo Workflow Template (Process Builder UI)

📝 Step 1: PM mở Process Builder, điền thông tin cơ bản:

// UI Form Input
{
  code: "LEAVE_REQUEST",
  name: "Đơn xin nghỉ phép",
  category: "HR",
  icon: "calendar",
  description: "Quy trình xin nghỉ phép..."
}

💾 API Call: POST /api/process-definitions

// INSERT INTO ProcessDefinition
{
  id: "cuid_abc123",
  code: "LEAVE_REQUEST",
  name: "Đơn xin nghỉ phép",
  category: "HR",
  formSchema: {}, // empty, fill later
  isActive: false, // draft mode
  version: 1,
  createdBy: "pm_user_id"
}

📋 Step 2: PM thiết kế Form Schema (drag-drop form builder):

// formSchema được update via PUT /api/process-definitions/:id
{
  "type": "object",
  "required": ["leaveType", "startDate", "endDate", "reason"],
  "properties": {
    "leaveType": {
      "type": "string",
      "enum": ["ANNUAL", "SICK", "UNPAID", "MATERNITY"],
      "title": "Loại nghỉ phép"
    },
    "startDate": {
      "type": "string",
      "format": "date",
      "title": "Ngày bắt đầu"
    },
    "endDate": {
      "type": "string",
      "format": "date",
      "title": "Ngày kết thúc"
    },
    "reason": {
      "type": "string",
      "maxLength": 500,
      "title": "Lý do"
    },
    "attachments": {
      "type": "array",
      "items": { "type": "string", "format": "uri" },
      "title": "Đính kèm (nếu có)"
    }
  }
}

🔄 Step 3: PM kéo thả Steps trong React Flow canvas:

// Step 1: Manager Approval
POST /api/step-definitions
{
  processDefinitionId: "cuid_abc123",
  code: "MANAGER_APPROVAL",
  name: "Quản lý duyệt",
  order: 1,
  type: "APPROVAL",
  assigneeType: "REQUESTER_MANAGER",
  timeoutHours: 48,
  escalationRoleId: "hr_admin_role"
}
// Step 2: HR Confirm
POST /api/step-definitions
{
  processDefinitionId: "cuid_abc123",
  code: "HR_CONFIRM",
  name: "HR xác nhận",
  order: 2,
  type: "APPROVAL",
  assigneeType: "ROLE",
  assigneeConfig: {
    "roleId": "hr_staff_role"
  },
  timeoutHours: 24
}
// Step 3: Notify Employee
POST /api/step-definitions
{
  processDefinitionId: "cuid_abc123",
  code: "NOTIFY_RESULT",
  name: "Thông báo kết quả",
  order: 3,
  type: "NOTIFICATION",
  assigneeType: "DYNAMIC",
  assigneeConfig: {
    "expression": "requester"
  }
}

➡️ Step 4: PM nối các Steps bằng Transitions:

// POST /api/transition-rules (batch)
[
  { fromStepId: null, toStepId: "step1_id", triggerAction: "COMPLETE" },  // START → Manager
  { fromStepId: "step1_id", toStepId: "step2_id", triggerAction: "APPROVE" },  // Manager ✓ → HR
  { fromStepId: "step1_id", toStepId: null, triggerAction: "REJECT" },  // Manager ✗ → END (rejected)
  { fromStepId: "step2_id", toStepId: "step3_id", triggerAction: "APPROVE" },  // HR ✓ → Notify
  { fromStepId: "step2_id", toStepId: null, triggerAction: "REJECT" },  // HR ✗ → END (rejected)
  { fromStepId: "step3_id", toStepId: null, triggerAction: "COMPLETE" }  // Notify → END (completed)
]

🔔 Step 5: PM cấu hình Notifications:

// POST /api/notification-templates
{
  processDefinitionId: "cuid_abc123",
  code: "STEP_ASSIGNED_MANAGER",
  triggerEvent: "STEP_ASSIGNED",
  triggerStepCode: "MANAGER_APPROVAL",
  channels: ["EMAIL", "TELEGRAM", "IN_APP"],
  recipientType: "ASSIGNEE",
  subjectTemplate: "🗓️ Đơn nghỉ phép #{{referenceNumber}} cần duyệt",
  bodyTemplate: "Xin chào {{recipientName}},\n\n{{requesterName}} đã gửi đơn xin nghỉ phép:\n- Loại: {{data.leaveType}}\n- Từ: {{data.startDate}} đến {{data.endDate}}\n- Lý do: {{data.reason}}\n\nVui lòng duyệt tại: {{approvalLink}}",
  telegramTemplate: "📋 Đơn nghỉ #{{referenceNumber}} từ {{requesterName}}. Duyệt: {{approvalLink}}"
}

Step 6: PM click "Publish" → isActive: true → Workflow sẵn sàng sử dụng!

FLOW 2: Nhân viên Tạo Đơn (Process Instance)

📝 Step 1: Nhân viên mở trang "Tạo đơn mới", chọn loại quy trình:

// GET /api/process-definitions?isActive=true&category=HR
// Response: list of available process types
[
  { id: "cuid_abc123", code: "LEAVE_REQUEST", name: "Đơn xin nghỉ phép", icon: "calendar" },
  { id: "cuid_def456", code: "EXPENSE_CLAIM", name: "Đơn thanh toán chi phí", icon: "receipt" }
]

📋 Step 2: Nhân viên click "Đơn xin nghỉ phép" → FE fetch formSchema → render dynamic form:

// GET /api/process-definitions/cuid_abc123
// Response includes formSchema → FE renders:
// - Select: Loại nghỉ phép (ANNUAL, SICK, UNPAID...)
// - DatePicker: Ngày bắt đầu
// - DatePicker: Ngày kết thúc
// - Textarea: Lý do
// - FileUpload: Đính kèm

💾 Step 3: Nhân viên điền form và click "Lưu nháp":

// POST /api/process-instances
{
  processDefinitionId: "cuid_abc123",
  data: {
    leaveType: "ANNUAL",
    startDate: "2026-06-01",
    endDate: "2026-06-03",
    reason: "Nghỉ phép năm, đi du lịch với gia đình",
    attachments: []
  }
}

// Response: ProcessInstance created
{
  id: "inst_xyz789",
  referenceNumber: "LV-2026-0042",  // auto-generated
  status: "DRAFT",
  requesterId: "employee_user_id",
  data: { ... },
  createdAt: "2026-05-22T10:30:00Z"
}

🚀 Step 4: Nhân viên click "Gửi đơn" → Workflow Engine bắt đầu:

// POST /api/process-instances/inst_xyz789/submit

// Backend WorkflowEngine.submitProcess() does:
// 1. Validate form data against formSchema (Zod)
// 2. Find first step (MANAGER_APPROVAL)
// 3. Resolve assignee (requester's manager → "manager_user_id")
// 4. Create StepInstance
// 5. Update ProcessInstance status
// 6. Trigger notifications

// INSERT INTO StepInstance
{
  id: "step_inst_001",
  processInstanceId: "inst_xyz789",
  stepDefinitionId: "step1_id",
  assigneeId: "manager_user_id",  // resolved from REQUESTER_MANAGER
  status: "ASSIGNED",
  assignedAt: "2026-05-22T10:31:00Z",
  dueAt: "2026-05-24T10:31:00Z"  // +48 hours
}

// UPDATE ProcessInstance
{
  status: "SUBMITTED" → "IN_PROGRESS",
  currentStepId: "step_inst_001",
  submittedAt: "2026-05-22T10:31:00Z"
}

// INSERT INTO ProcessHistory
{
  processInstanceId: "inst_xyz789",
  actorId: "employee_user_id",
  action: "SUBMITTED",
  fromStatus: "DRAFT",
  toStatus: "IN_PROGRESS"
}

📧 Step 5: Hệ thống gửi thông báo cho Manager:

// NotificationService triggered by STEP_ASSIGNED event
// Interpolate template với data:

Email to: manager@company.com
Subject: 🗓️ Đơn nghỉ phép #LV-2026-0042 cần duyệt
Body: Xin chào Nguyễn Văn A,

Trần Thị B đã gửi đơn xin nghỉ phép:
- Loại: Nghỉ phép năm
- Từ: 01/06/2026 đến 03/06/2026
- Lý do: Nghỉ phép năm, đi du lịch với gia đình

Vui lòng duyệt tại: https://erp.company.com/approvals/step_inst_001

// Telegram message also sent with inline button

FLOW 3: Manager Duyệt Đơn

👀 Step 1: Manager mở link từ email/Telegram → xem chi tiết đơn:

// GET /api/step-instances/step_inst_001
{
  id: "step_inst_001",
  status: "ASSIGNED",
  stepDefinition: { code: "MANAGER_APPROVAL", name: "Quản lý duyệt", allowReject: true },
  processInstance: {
    referenceNumber: "LV-2026-0042",
    requester: { name: "Trần Thị B", department: "Marketing" },
    data: { leaveType: "ANNUAL", startDate: "2026-06-01", ... }
  }
}

Step 2: Manager click "Duyệt" với comment:

// POST /api/step-instances/step_inst_001/complete
{
  decision: "APPROVED",
  comment: "Đồng ý, chúc em đi chơi vui vẻ!"
}

// Backend WorkflowEngine.completeStep() does:
// 1. Validate assignee matches current user
// 2. Validate step status is ASSIGNED or IN_PROGRESS
// 3. Update StepInstance
// 4. Find next transition (APPROVE → HR_CONFIRM)
// 5. Create next StepInstance
// 6. Trigger notifications

// UPDATE StepInstance (current)
{
  status: "COMPLETED",
  decision: "APPROVED",
  decisionComment: "Đồng ý, chúc em đi chơi vui vẻ!",
  decisionAt: "2026-05-22T14:00:00Z",
  completedAt: "2026-05-22T14:00:00Z"
}

// INSERT INTO StepInstance (next step)
{
  id: "step_inst_002",
  processInstanceId: "inst_xyz789",
  stepDefinitionId: "step2_id",  // HR_CONFIRM
  assigneeId: "hr_staff_user_id",  // resolved from ROLE
  status: "ASSIGNED",
  assignedAt: "2026-05-22T14:00:01Z",
  dueAt: "2026-05-23T14:00:01Z"  // +24 hours
}

// UPDATE ProcessInstance
{
  currentStepId: "step_inst_002"
}

// INSERT INTO ProcessHistory
{
  action: "STEP_COMPLETED",
  stepCode: "MANAGER_APPROVAL",
  comment: "Đồng ý, chúc em đi chơi vui vẻ!"
}

FLOW 4: Quy trình Hoàn thành

// Sau khi HR duyệt → Step NOTIFY_RESULT được tạo (type: NOTIFICATION)
// WorkflowEngine tự động complete notification step

// INSERT INTO StepInstance
{
  id: "step_inst_003",
  stepDefinitionId: "step3_id",  // NOTIFY_RESULT
  assigneeId: "employee_user_id",  // requester
  status: "COMPLETED",  // auto-completed for NOTIFICATION type
  completedAt: "2026-05-22T16:00:00Z"
}

// Transition COMPLETE → toStepId: null (END)
// WorkflowEngine.completeProcess()

// UPDATE ProcessInstance
{
  status: "COMPLETED",
  currentStepId: null,
  completedAt: "2026-05-22T16:00:00Z"
}

// INSERT INTO ProcessHistory
{
  action: "COMPLETED",
  fromStatus: "IN_PROGRESS",
  toStatus: "COMPLETED"
}

// Trigger PROCESS_COMPLETED notification to requester
// "🎉 Đơn nghỉ phép #LV-2026-0042 đã được duyệt!"

FLOW 5: Manager Từ chối Đơn (REJECT)

Step 1: Manager click "Từ chối" với lý do:

// POST /api/step-instances/step_inst_001/complete
{
  decision: "REJECTED",
  comment: "Không đủ nhân sự trong thời gian này. Đề nghị chọn ngày khác."
}

// Backend WorkflowEngine.completeStep() does:
// 1. Update StepInstance với decision: REJECTED
// 2. Find transition với triggerAction: REJECT
// 3. Execute transition (toStepId: null = END)

💾 Step 2: Database Updates:

// UPDATE StepInstance
{
  id: "step_inst_001",
  status: "REJECTED",           // ← not COMPLETED
  decision: "REJECTED",
  decisionComment: "Không đủ nhân sự trong thời gian này...",
  decisionAt: "2026-05-22T15:00:00Z",
  completedAt: "2026-05-22T15:00:00Z"
}

// Find TransitionRule
{
  fromStepId: "step1_id",       // MANAGER_APPROVAL
  toStepId: null,               // → END (no next step)
  triggerAction: "REJECT"
}

// UPDATE ProcessInstance
{
  status: "REJECTED",           // ← Process ends with REJECTED status
  currentStepId: null,
  completedAt: "2026-05-22T15:00:00Z"
}

// INSERT INTO ProcessHistory
{
  processInstanceId: "inst_xyz789",
  actorId: "manager_user_id",
  action: "STEP_REJECTED",
  stepCode: "MANAGER_APPROVAL",
  fromStatus: "IN_PROGRESS",
  toStatus: "REJECTED",
  comment: "Không đủ nhân sự trong thời gian này..."
}

📧 Step 3: Thông báo cho nhân viên:

// NotificationService triggered by STEP_REJECTED event
// Template: NotificationTemplate với triggerEvent: "STEP_REJECTED"

Email to: employee@company.com
Subject: ❌ Đơn nghỉ phép #LV-2026-0042 bị từ chối
Body: Xin chào Trần Thị B,

Đơn nghỉ phép của bạn đã bị từ chối bởi Nguyễn Văn A (Quản lý).

Lý do: Không đủ nhân sự trong thời gian này. Đề nghị chọn ngày khác.

Bạn có thể tạo đơn mới tại: https://erp.company.com/leave-request/new

// Telegram: "❌ Đơn #LV-2026-0042 bị từ chối. Lý do: ..."

🔄 Option: Cho phép tạo đơn mới (Resubmit):

// Nhân viên có thể:
// 1. Tạo đơn MỚI với ngày khác (recommended)
// 2. Hoặc nếu system cho phép REVISION:

// POST /api/process-instances/inst_xyz789/revise
{
  data: {
    leaveType: "ANNUAL",
    startDate: "2026-06-15",    // ← changed dates
    endDate: "2026-06-17",
    reason: "Nghỉ phép năm (đã đổi ngày theo góp ý của quản lý)"
  }
}

// This creates NEW ProcessInstance linked to original
{
  id: "inst_revision_001",
  referenceNumber: "LV-2026-0042-R1",  // revision suffix
  previousVersionId: "inst_xyz789",     // link to rejected version
  status: "DRAFT"
}

FLOW 6: Timeout & Auto-Escalation

Background: Cron job check timeout mỗi 5 phút:

// TimeoutCheckerCronJob runs every 5 minutes
// SELECT * FROM StepInstance
// WHERE status IN ('ASSIGNED', 'IN_PROGRESS')
//   AND dueAt < NOW()
//   AND dueAt IS NOT NULL

// Found: step_inst_001 with dueAt: "2026-05-24T10:31:00Z"
// Current time: "2026-05-24T12:00:00Z" (past due)

// StepDefinition has:
{
  timeoutHours: 48,
  escalationRoleId: "hr_admin_role"
}

🔔 Step 1: Send Reminder (nếu chưa escalate):

// First timeout: send reminder to original assignee
// Check: Is this first timeout or repeat?

// If first timeout (within grace period, e.g., +4 hours):
// NotificationService sends REMINDER

Email to: manager@company.com
Subject: ⚠️ NHẮC NHỞ: Đơn #LV-2026-0042 cần duyệt GẤP
Body: Xin chào Nguyễn Văn A,

Đơn nghỉ phép #LV-2026-0042 đã QUÁ HẠN duyệt.
- Đã chờ: 50 giờ (hạn: 48 giờ)
- Nhân viên: Trần Thị B
- Ngày nghỉ: 01/06 - 03/06/2026

Vui lòng duyệt NGAY: https://erp.company.com/approvals/step_inst_001

Nếu không có phản hồi trong 4 giờ, đơn sẽ được chuyển lên HR Admin.

// INSERT ProcessHistory
{
  action: "STEP_TIMEOUT",
  stepCode: "MANAGER_APPROVAL",
  metadata: { reminderSent: true, reminderAt: "2026-05-24T12:00:00Z" }
}

⬆️ Step 2: Auto-Escalate (nếu vẫn không response):

// After grace period (e.g., +4 hours), still no action
// TimeoutCheckerCronJob escalates

// Option A: Reassign to escalation role
// UPDATE StepInstance
{
  id: "step_inst_001",
  assigneeId: "hr_admin_user_id",  // ← reassigned from manager
  status: "ASSIGNED",               // ← reset to ASSIGNED
  assignedAt: "2026-05-24T16:00:00Z",
  dueAt: "2026-05-25T16:00:00Z",   // ← new deadline (+24h)
  metadata: {
    escalatedFrom: "manager_user_id",
    escalatedAt: "2026-05-24T16:00:00Z",
    escalationReason: "TIMEOUT"
  }
}

// INSERT ProcessHistory
{
  action: "STEP_ESCALATED",
  stepCode: "MANAGER_APPROVAL",
  metadata: {
    fromAssignee: "manager_user_id",
    toAssignee: "hr_admin_user_id",
    reason: "TIMEOUT after 52 hours"
  }
}

// Notify original assignee (manager)
Email: "⚠️ Đơn #LV-2026-0042 đã được chuyển lên HR Admin do quá hạn."

// Notify new assignee (HR Admin)
Email: "📋 Đơn #LV-2026-0042 cần xử lý GẤP (escalated từ Nguyễn Văn A)"

🚫 Option B: Auto-Reject (nếu config):

// Alternative: StepDefinition có timeoutAction: "AUTO_REJECT"
// Instead of escalating, system auto-rejects

// Find TransitionRule
{
  fromStepId: "step1_id",
  toStepId: null,                    // → END
  triggerAction: "TIMEOUT"           // ← special trigger
}

// UPDATE StepInstance
{
  status: "TIMEOUT",
  decision: null,                    // no human decision
  decisionComment: "Tự động từ chối do quá hạn duyệt",
  completedAt: "2026-05-24T16:00:00Z"
}

// UPDATE ProcessInstance
{
  status: "CANCELLED",               // or "TIMEOUT"
  completedAt: "2026-05-24T16:00:00Z"
}

// Notify requester
Email: "⏰ Đơn #LV-2026-0042 đã hết hạn do không được duyệt trong 48h.
        Vui lòng tạo đơn mới."

📊 Timeout Configuration Summary:

ConfigFieldExample
Timeout durationStepDefinition.timeoutHours48 (hours)
Grace periodStepDefinition.gracePeriodHours4 (hours before escalate)
Escalation targetStepDefinition.escalationRoleIdFK to Role table
Timeout actionStepDefinition.timeoutActionESCALATE | AUTO_REJECT | REMIND_ONLY
Max escalationsStepDefinition.maxEscalations2 (then auto-reject)

FLOW 7: Delegation (Ủy quyền)

🔄 Use case: Manager đi công tác, delegate cho Deputy:

// POST /api/step-instances/step_inst_001/delegate
{
  delegateToUserId: "deputy_manager_id",
  reason: "Đi công tác đến 25/05, ủy quyền cho Phó phòng"
}

// Validate: StepDefinition.allowDelegate == true

// UPDATE StepInstance
{
  assigneeId: "deputy_manager_id",    // ← new assignee
  delegatedFromId: "manager_user_id", // ← track original
  status: "ASSIGNED",
  assignedAt: "2026-05-22T11:00:00Z"  // ← reset assignment time
}

// INSERT ProcessHistory
{
  action: "STEP_DELEGATED",
  stepCode: "MANAGER_APPROVAL",
  actorId: "manager_user_id",
  metadata: {
    delegatedTo: "deputy_manager_id",
    reason: "Đi công tác đến 25/05..."
  }
}

// Notify deputy
Email: "📋 Nguyễn Văn A đã ủy quyền đơn #LV-2026-0042 cho bạn xử lý."

// Notify requester (optional)
Email: "📌 Đơn của bạn đã được chuyển từ Nguyễn Văn A sang Trần Văn C."

📊 Visual Flow Summary:

┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   DRAFT     │────▶│  SUBMITTED  │────▶│ IN_PROGRESS │────▶│  COMPLETED  │
│             │     │             │     │             │     │             │
│ Employee    │     │ Create      │     │ Steps       │     │ All steps   │
│ fills form  │     │ Step 1      │     │ executing   │     │ done        │
└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘
                           │                   │
                           ▼                   ▼
                    ┌─────────────┐     ┌─────────────┐
                    │ StepInstance│     │ StepInstance│
                    │ MANAGER     │────▶│ HR_CONFIRM  │────▶ ... ────▶ END
                    │ APPROVAL    │     │             │
                    │ assignee:   │     │ assignee:   │
                    │ manager_id  │     │ hr_staff_id │
                    └─────────────┘     └─────────────┘
                           │
                           ▼ (if REJECT)
                    ┌─────────────┐
                    │  REJECTED   │
                    │             │
                    │ Process ends│
                    │ with reject │
                    └─────────────┘
            

Dynamic Forms

formSchema: Json field → JSON Schema → Zod validation → render động

Assignee Resolution

AssigneeType: ROLE, USER, DEPT_HEAD, MANAGER, DYNAMIC (expression)

Process Builder UI

React Flow drag-drop → PM define steps, transitions, notifications

Migration Strategy (5-6 weeks):

  1. Week 1-2: Create parallel tables, seed existing 4 types
  2. Week 3: Dual-write (old primary, new shadow)
  3. Week 4: Read from new, validate matches old
  4. Week 5-6: Deprecate old code, keep tables for rollback

🚀 Improvement ideas:

  1. Migration tooling consolidation — fix-*.js scripts → unified migration framework (Prisma Migrate flow)
  2. Cerbos policy versioning + audit log mỗi policy change
  3. API Gateway layer nếu có nhiều internal services (rate limit, auth aggregation)
  4. Observability stack — OpenTelemetry + Grafana cho HR workflow latency tracking

💰 MoneyFi — Multi-chain DeFi (Universal/Aptos)

FE · Feb 2025-Now

Pitch: Multi-chain DeFi automation platform — 3 sub-projects: moneyFi-dapp (user dapp), moneyFi-admin (admin), moneyfi-SDK-example (published SDK demo). Deposit/withdraw/earn yield across Aptos + 5 EVM networks via cross-chain.

ReactJSTypeScriptMaterial UITanStack QueryAptos Web3web3.jsWagmiPrivyD3Chart.jsSentryGA4OpenAPI codegenReact Hook Form + Zod

🏗️ Architecture:

🎯 Key Decisions:

  1. Adapter pattern cho chain abstraction → component code không biết Aptos vs EVM, chỉ work với common interface
  2. OpenAPI codegen → BE thay schema → FE auto-regenerate types → compile error cảnh báo break change
  3. Privy cho EVM social login → bypass MetaMask UX friction cho non-crypto users
  4. CommonJS + ESM dual build cho SDK → support cả Node.js (CJS) và modern bundlers (ESM)

🔥 Pain Points (REAL từ codebase scan):

🚀 Improvement ideas:

  1. Code-split 45+ vault components với React.lazy + Suspense — reduce initial bundle
  2. Web Workers cho heavy D3 computations — không block main thread
  3. WebSocket cho real-time portfolio thay vì TanStack Query polling
  4. Storybook cho 45+ components — design system documentation + visual regression
  5. E2E tests Playwright cho critical wallet flows (login, deposit, withdraw)
  6. SDK examples ecosystem — Next.js example, Vue example, Vanilla JS example

📝 Decot — Privacy-Preserving CLM on Sui

FE Maintainer · Oct 2025-Feb 2026

Pitch: Contract Lifecycle Management (CLM) platform với e-signatures + on-chain verification trên Sui blockchain, ZKLogin để privacy-preserving auth.

Next.jsReactRedux ToolkitStyled ComponentsAnt DesignSui BlockchainZKLoginMysten Dapp-kitAmazon Cognito OAuthSocket.IORedux PersistCypressDocker (dev/staging/prod)

🏗️ Highlights:

🎯 Discuss khi được hỏi:

  1. Tại sao ZKLogin? → User auth qua OAuth (Google/Cognito) NHƯNG vẫn có on-chain identity, không expose private key. Zero-knowledge proof của OAuth credential.
  2. Tại sao Redux Toolkit (project legacy)? → State global cho document state + collaboration. Persist cho draft.
  3. DocumentViewer 40KB — bundle implication: nên code-split.

🚀 Improvement ideas:

  1. Migrate Redux Toolkit → TanStack Query + Zustand — server state vs client state separation
  2. Code-split DocumentViewer 40KB với dynamic import (only mount khi user opens contract)
  3. Virtual scrolling cho long PDFs — react-window/react-virtuoso
  4. Document version control + diff visualization — feature giá trị
  5. Operational Transform (OT) hoặc CRDT cho real-time collaboration thay Socket.IO broadcasting

🏛️ VCCI Member Management Platform

FE · Jul-Dec 2025

Pitch: Membership registration + lifecycle management cho Vietnam Chamber of Commerce and Industry. 40+ form fields, two-tier registration (Company vs Association), full Vietnamese localization.

Vite 7ReactJSTanStack RouterTanStack QueryHeroUIRadix UITailwind CSSReact Hook Form + ZodFramer Motioni18nextinput-otp

🏗️ Highlights:

🎯 Talking points:

  1. Form 40+ fields perf: RHF uncontrolled → field-level re-render → form vẫn smooth
  2. Zod discriminated union cho conditional schemas (Company schema vs Association schema)
  3. TanStack Router — type-safe routes, code-splitting

🏆 Go Claw — AI Agentic for HR Management

3rd Prize · Twendee Hackathon

Pitch: AI Agentic application cho HR Management. Team "Claude Code Never Sleep" — 3rd Prize. Reference code trong Twd/goclaw-ref.

⚠️ PERFECT alignment với Base.vn HRM. Mention story này trong "tại sao Base.vn?".

Story key:

→ Base.vn pitch angle: "Em build Go Claw cho HR Management trong hackathon → win Top 3. Đó là lý do em hứng thú với Base.vn — Base HRM là exactly cùng problem space em đã solve ở scale prototype. Em muốn áp dụng experience đó vào production scale tại Base."

🔍 CKForensics — Open Source CLI Tool

Side Project · Open Source

Pitch: Forensic analysis tool cho Claude Code sessions — token cost, context map, audit manifest, skill recommendations. Phân tích "Claude Code đã touch những file gì, tokens đâu đi, session cost bao nhiêu".

Tech:

TypeScriptBunCLIOpen Source (MIT)gitleakseslintConventional Commits

Why mention this:

🔥 Pain Points Summary — Interview Cheat Sheet

Cross-project patterns để articulate trong interview — based on REAL codebase scan 2026-05-22

📊 Quick Stats

ProjectCodeFlowCriticalTop Issue
Shiru10-364 as any, monolithic services
Twendee ERP843Multi-channel chaos, no notification hub
MoneyFi8-24 state systems, chain duplication

🚨 Code Red Flags

  • No centralized tokens → rebranding impossible
  • 2,000+ LOC services → untestable, unscalable
  • as any everywhere → TypeScript useless
  • No tests → refactoring blocked

🔄 Flow Red Flags

  • Multi-channel confusion → 3 links cho 1 flow
  • Telegram chỉ notify → phải về web approve
  • No notification hub → phải refresh page

📈 Scale Red Flags

  • 80+ relations .include() → N+1
  • Sync notifications → blocked
  • No tenantId → single-tenant

🔧 Extend Red Flags

  • Hardcoded ProcessType enum
  • Service-per-type → 3-5 days/type
  • No process builder UI

🌟 Shiru Pain → Answer

Q: "Technical challenge khó nhất?"

A: "KYC Sumsub ↔ Auth circular dependency. Auth cần Sumsub verify, Sumsub cần Auth get user. Dùng forwardRef nhưng đó là workaround. Nếu làm lại: KYC module riêng, communicate qua events/queue."

🏢 ERP Pain → Answer

Q: "Handle legacy code thế nào?"

A: "Nhận codebase 2,247 useState + components 3,000 LOC. Không thể rewrite. Approach: identify highest-risk areas (payroll), extract domain logic vào hooks, add tests incrementally."

💰 MoneyFi Pain → Answer

Q: "State management philosophy?"

A: "4 systems cạnh tranh — debug phải check cả 4. Solution: Server state = React Query (caching, refetch). UI state = Zustand (simple, granular). Kill Redux."

🔄 ERP Flow Pain → Answer

Q: "Thách thức UX/process design?"

A: "Onboarding flow có 3 links khác nhau: HR tạo process → nhận Telegram link → gửi link khác cho nhân viên mới. User không biết link nào làm gì. Solution: single onboarding link + Telegram inline approve buttons + unified notification hub."

🔄 Cross-Channel Pain → Answer

Q: "Sao approval flow chậm?"

A: "Telegram chỉ notify 'có request mới' nhưng phải về web approve. No inline action buttons. Request pending 3 ngày không có escalation. Solution: Telegram /approve_[id] callback + auto-escalate rule."

📈 ERP Scale 10K → Answer

Q: "Scale ERP lên 10K users thì sao?"

A: "3 bottlenecks chính: (1) User model có 80+ relations fetched via .include() — N+1 queries. (2) Notifications gửi sync trong request — block 30s+. (3) No Redis — cache trong memory sẽ OOM. Fix: connection pool + .select() + Bull queue + Redis layer."

📈 Multi-tenant → Answer

Q: "Multi-org support thế nào?"

A: "Hiện tại không có tenantId — single-tenant design. Scale 100K+ cần: (1) Add tenantId FK to all models (migration). (2) PostgreSQL Row-Level Security policies. (3) Tenant middleware filter mọi query. Đây là rewrite, không phải refactor."

🔧 New Process Type → Answer

Q: "Thêm workflow type mới thế nào?"

A: "Hiện tại ProcessType là hardcoded enum. Thêm type mới cần: enum + Prisma model + service + helper + DTO + FE form + notification cases. ~3-5 days per type. Solution: database-driven process definitions + generic step engine + JSON form schema. Refactor ~2-3 weeks."

🔧 PM/HR Create Process → Answer

Q: "Non-dev có thể tạo process type?"

A: "Hiện tại: không. Zero end-user flexibility. Cần: (1) Process builder UI. (2) Step definitions stored in DB. (3) BPMN-like rule engine cho transitions. (4) Event-driven notifications với templates. Đây là feature lớn, không quick fix."

✅ Schema Design → Answer

Q: "Schema design cho flexible workflow?"

A: "3 layers: (1) Config tables — ProcessDefinition, StepDefinition, TransitionRule. (2) Runtime tables — ProcessInstance, StepInstance, ProcessHistory. (3) Dynamic forms — JSON Schema trong formSchema field, validate bằng Zod, render động."

✅ Migration Strategy → Answer

Q: "Migrate từ hardcoded → flexible?"

A: "4 phases, 5-6 weeks: (1) Parallel tables + seed existing types. (2) Dual-write — old primary, new shadow. (3) Read from new, validate matches old. (4) Deprecate old, keep for rollback 30 days. Zero downtime."

⏱️ Improvement Timeline I Would Propose

WeekFocusImpact
1-2Design tokens + strict tsconfigFoundation — stop bleeding
3-4Extract hooks from giant componentsTestability — unblock refactor
5-6Add critical path tests (>60%)Confidence — ship with safety
7-8State management consolidationDebug speed — single source of truth
9-10API layer standardizationReliability — retry, error handling

🎤 Master Talking Points (memorize)

"Giới thiệu bản thân"

"Em là Phong, Front-end Developer 2 năm kinh nghiệm, hiện đang shift sang Full-stack và AI Agentic Engineering tại Twendee. Em đã ship 7+ FE projects, 5+ Web3 products với tổng transaction ~$20M, và đang lead 1 AI Orchestrator platform multi-tenant. Top 3 Twendee AI Hackathon với idea Go Claw cho HR Management — em rất hứng thú với hệ sản phẩm của Base, đặc biệt Base HRM."

"Dự án ấn tượng nhất?"

"Em mention 2: Shiru — fullstack AI advisor với NestJS+Prisma+PostgreSQL+BullMQ+Redis, em ownership JWT refresh-rotation và BullMQ async architecture. TWDAgentsHub — multi-tenant AI Orchestrator với PostgreSQL RLS, Turborepo, 5 domain agents. Đây là evolution từ hackathon Go Claw."

"Thách thức kỹ thuật khó nhất?"

"Em chọn multi-tenancy với RLS trong TWDAgentsHub. Trước em chỉ biết tenant_id check ở app level. Học PostgreSQL Row-Level Security → defense-in-depth, enforced at DB. Trade-off: complexity setup nhưng security guarantee mạnh hơn nhiều. Apply pattern này khi build cho Vietnamese SMEs vì compliance/data isolation critical."

"Đề xuất cải tiến dự án?"

"Shiru: thêm OpenTelemetry tracing — debug BullMQ jobs đang gặp khó. Migrate transaction history pagination offset → cursor (table sắp lớn). TWDAgentsHub: prompt evaluation harness — golden dataset regression test. Anthropic prompt caching giảm 90% input tokens. MoneyFi: code-split 45+ components giảm initial bundle."

"Tại sao Base.vn?"

"3 lý do. (1) Base HRM — em đã build Go Claw cho HR ở hackathon scale, muốn áp dụng vào production multi-tenant của Base. (2) Hệ sản phẩm 31 products phục vụ 11k+ doanh nghiệp VN — challenge multi-tenant + multi-product compelling. (3) Engineering culture — Base focus product, UX clean, engineering quality cao — match expectation của em về team."

"Mindset làm việc?"

"3 nguyên tắc. (1) Bug-first thinking — pattern nào prevent bug nào, không cargo-cult. (2) AI Agentic workflow — Claude Code orchestrate review/test/refactor để focus vào decisions, không chỉ typing. (3) Continuous learning — bài Async event loop em mới deep-dive tuần trước, useMemo+React Compiler là next exploration."

❓ 12 Smart Questions to Ask Base.vn

Cuối interview, interviewer sẽ hỏi "Em có câu hỏi gì cho bên anh/chị không?". KHÔNG bỏ qua — đây là cơ hội (1) show engagement + research, (2) extract info để decide nếu được offer, (3) close interview mạnh. Hỏi 3-4 câu thôi, pick từ list theo flow.

🚀 Role & Growth (chọn 1-2)

Q1 · Signal cao — onboarding pattern

"Anh/chị thấy engineer mới gia nhập Base làm tốt trong 3 tháng đầu thường có pattern gì? Ngược lại, ai struggle thì lý do thường là gì?"

→ Show: bro nghĩ proactively về success. Extract: expectation thực tế, common pitfalls.

Q2 · Specific product interest

"Em đang quan tâm tới Base HRM. Engineer mới có cơ hội ownership feature gì trong 6 tháng đầu không, hay sẽ rotation qua nhiều product?"

→ Show: research kỹ, biết Base có nhiều products. Extract: ownership structure, career path.

Q3 · Career path

"Path development cho engineer tại Base ra sao? Có level rõ ràng (Junior → Mid → Senior → Staff) hay flat structure?"

→ Senior signal: thinking about progression. Important info cho offer evaluation.

🏗️ Engineering Culture (chọn 1-2)

Q4 · AI workflow (Base có Vibe Code Engineer)

"Em nghe Base có Vibe Code Engineer role. Engineer ở Base dùng AI tools (Claude Code, Cursor) trong daily workflow ra sao? Có guideline nào về responsible AI usage không?"

→ ⭐ Highly relevant — bro đang là AI Agentic Engineer. Show alignment với culture. Extract: actual AI maturity.

Q5 · Code review process

"Code review process tại Base như thế nào? Trung bình 1 PR mất bao lâu từ open → merge? Có pair programming hay async review?"

→ Show: care about quality + collaboration. Extract: PR velocity (signal về team health).

Q6 · Tech debt vs new feature

"Engineer ở Base làm sao balance giữa shipping feature mới và technical debt? Có dedicated time cho refactor/improvement không?"

→ Mature signal. Extract: real culture (most companies SAY they value tech debt but don't budget time).

Q7 · Testing & deployment

"Testing strategy của Base ra sao — unit/integration/E2E proportion? Cadence release thế nào, có CI/CD pipeline cho mỗi product?"

→ Show production mindset. Extract: engineering maturity.

👥 Team & Product (chọn 1)

Q8 · Team structure

"Team Engineering của product anh/chị đang lead có bao nhiêu người? Cấu trúc — squad theo product hay function?"

→ Personal touch (asking về interviewer's team). Extract: team size + structure.

Q9 · Tech challenge lớn nhất

"Phía Base đang đối mặt với technical challenge lớn nhất hiện tại là gì? Multi-tenancy, scale, integration giữa 31 products, hay AI adoption?"

→ ⭐ Senior signal — thinking about real problems. Show breadth + curiosity. Họ thường share genuine.

Q10 · Cross-product platform

"Base có 31+ products. Engineering team handle scale này như thế nào — shared platform team hay mỗi product team độc lập tech stack?"

→ Show: understand business complexity. Extract: architecture decisions.

💖 Personal + Next Steps (chọn 1, ALWAYS hỏi)

Q11 · Personal story

"Anh/chị thấy điều gì khiến anh/chị work ở Base lâu nhất? Hoặc khoảnh khắc nào anh/chị thấy proud về Base nhất?"

→ Họ kể story → bro listen → build rapport. Họ remember bro = senior emotional intelligence.

Q12 · ⭐ MUST ASK · Next steps

"Dạ sau buổi này, next steps là gì? Timeline kết quả khoảng bao lâu? Có gì em có thể chuẩn bị thêm cho round tiếp theo (nếu có)?"

Luôn hỏi cuối cùng. Show interest in moving forward. Set expectation về timeline.

🚫 KHÔNG hỏi những câu này với Tech Lead

🎯 Suggested Flow — 3 câu hỏi

  1. OPENER (technical/cultural): Q4 (AI workflow) HOẶC Q9 (tech challenge) — chọn 1 dựa vào flow interview
  2. MIDDLE (role/team): Q1 (onboarding) HOẶC Q8 (team structure) — depend on rapport
  3. CLOSER (always): Q12 (next steps) — bắt buộc

3 câu là đủ. Quá nhiều = drag interview. Quá ít (0-1) = không engaged. Sweet spot 3-4.

📧 Sau khi xong interview — 24h follow-up

Gửi thank-you email tới HR (cc tech lead nếu có email) trong vòng 24h:

Subject: Cảm ơn buổi PV Base.vn ngày 19/05 — Trần Nam Phong

Dạ chị [HR name],

Em cảm ơn anh/chị [tech lead names] đã dành thời gian trao đổi với em
chiều nay. Em đặc biệt thấy hứng thú với phần thảo luận về [1 SPECIFIC
TOPIC discussed, vd: multi-tenancy approach cho Base HRM / engineering
culture với AI tools / tech challenge X].

Em đã đọc thêm về [related topic / link] sau buổi PV, em có thêm 1
suggestion là [brief idea — show continued thinking].

Em mong nhận được phản hồi từ phía Base. Có gì em hỗ trợ thêm,
chị cứ ping em ạ.

Em chân thành cảm ơn,
Phong
[phone] · [email] · [linkedin]

1 specific reference từ buổi PV (không generic) + 1 idea bro nghĩ sau = show genuine engagement. Senior signal.

✅ Interview Day Checklist