Mỗi model bên dưới là Mongoose schema, FE có thể dựa vào để map DTO, validate form và hiển thị. Tất cả schema đều có createdAt, updatedAt do { timestamps: true } bật sẵn.
- email: String, required, unique
- passwordHash: String, required
- role: String, required, enum:
DealerStaff | DealerManager | EVMStaff | Admin - dealer: ObjectId ref
Dealer(tùy chọn, khi user thuộc đại lý) - profile: { name: String, phone: String }
- status: String, enum:
active | locked, default:active
- name: String, required
- code: String, required, unique
- region: String
- address: String
- contacts: Array<{ name: String, phone: String, email: String }>
- contract: ObjectId ref
DealerContract(chưa có model tương ứng trong repo) - creditLimit: Number, default: 0
- salesTarget: Number, default: 0
- status: String, enum:
active | inactive, default:active
- fullName: String, required
- phone: String (indexed)
- email: String (indexed)
- idNumber: String
- address: String
- segment: String, enum:
retail | fleet, default:retail - notes: String
- ownerDealer: ObjectId ref
Dealer - ownerUser: ObjectId ref
User
- name: String, required
- brand: String, default:
EVM - segment: String
- description: String
- active: Boolean, default: true
- model: ObjectId ref
VehicleModel, required - trim: String, required
- battery: String
- range: Number
- motorPower: Number
- features: String[]
- msrp: Number, required
- images: String[]
- active: Boolean, default: true
- Unique index:
{ model, trim }
- name: String, required
- code: String
- hex: String
- extraPrice: Number, default: 0
- active: Boolean, default: true
- ownerType: String, required, enum:
EVM | Dealer - owner: ObjectId ref
Dealer(tùy chọn, khiownerType = Dealer) - variant: ObjectId ref
VehicleVariant, required - color: ObjectId ref
VehicleColor - quantity: Number, default: 0
- reserved: Number, default: 0
- vinList: String[]
- location: String
- Unique index:
{ ownerType, owner, variant, color }(sparse)
- items là mảng
QuoteItem(embedded, không _id) - dealer: ObjectId ref
Dealer, required - sales: ObjectId ref
User - customer: ObjectId ref
Customer, required - items: Array<{
- variant: ObjectId ref
VehicleVariant, required - color: ObjectId ref
VehicleColor - qty: Number, required, min: 1
- unitPrice: Number, required
- promotionApplied: ObjectId[] ref
Promotion}>
- variant: ObjectId ref
- subtotal: Number, default: 0
- discount: Number, default: 0
- promotionTotal: Number, default: 0
- fees: { registration: Number, plate: Number, delivery: Number } (mặc định 0)
- total: Number, default: 0
- validUntil: Date
- status: String, enum:
draft | sent | accepted | rejected, default:draft - notes: String
- items là mảng
OrderItem(embedded, không _id) - orderNo: String (indexed)
- dealer: ObjectId ref
Dealer, required - sales: ObjectId ref
User - customer: ObjectId ref
Customer, required - quote: ObjectId ref
Quote - items: Array<{
- variant: ObjectId ref
VehicleVariant, required - color: ObjectId ref
VehicleColor - qty: Number, required, min: 1
- unitPrice: Number, required
- vins: String[] }>
- variant: ObjectId ref
- paymentMethod: String, enum:
cash | finance, default:cash - deposit: Number, default: 0
- status: String, enum:
new | confirmed | allocated | invoiced | delivered | cancelled, default:new - expectedDelivery: Date
- actualDelivery: Date
- logs: Array<{ at: Date, by: String, action: String, note: String }>
- order: ObjectId ref
Order, required - type: String, enum:
deposit | balance | finance, required - amount: Number, required
- method: String, enum:
cash | bank | loan, required - transactionRef: String
- paidAt: Date
- status: String, enum:
pending | confirmed | failed, default:pending - notes: String
- name: String, required
- scope: String, enum:
global | byDealer | byVariant, default:global - dealers: ObjectId[] ref
Dealer - variants: ObjectId[] ref
VehicleVariant - type: String, enum:
cashback | accessory | finance, default:cashback - value: Number, default: 0
- stackable: Boolean, default: false
- validFrom: Date, required
- validTo: Date, required
- status: String, enum:
active | inactive, default:active
- order: ObjectId ref
Order, required - contractNo: String, required, indexed
- signedDate: Date
- files: String[] (đường dẫn/URL file)
- terms: String
- status: String, enum:
draft | signed | cancelled, default:draft
- customer: ObjectId ref
Customer, required - dealer: ObjectId ref
Dealer, required - variant: ObjectId ref
VehicleVariant, required - preferredTime: Date, required
- status: String, enum:
requested | confirmed | done | cancelled, default:requested - result: { feedback: String, interestRate: Number }
- assignedStaff: ObjectId ref
User
- order: ObjectId ref
Order, required - address: String
- scheduledAt: Date
- status: String, enum:
pending | in_progress | delivered, default:pending - notes: String
- customer: ObjectId ref
Customer - dealer: ObjectId ref
Dealer - createdBy: ObjectId ref
User - content: String, required
- status: String, enum:
new | in_progress | resolved, default:new
- ID các quan hệ dùng
string(Mongo ObjectId) khi gửi/nhận JSON. - Ngày giờ dùng ISO string (VD:
2025-10-31T12:00:00.000Z). - Các enum nên dùng select cố định theo danh sách ở trên.
- Các mảng ref (VD:
variantstrong Promotion,itemstrong Order/Quote) gửi dạng mảng ObjectId hoặc object chi tiết tùy API.