DESINO

📦 Module Sản Phẩm (Product Module) Specification & Blueprint Template

Phiên bản: 2.0 Nền tảng: JAM No-Code Platform Tài liệu: Thiết kế Schema, Quan hệ, CMS Layout & Blueprint Cấu hình mẫu cho Module Sản Phẩm.


1. Tổng Quan Kiến Trúc & Cấu Trúc Thực Thể (Entities)

Module Sản phẩm được thiết kế chuẩn theo kiến trúc JAM No-Code Platform bao gồm các Model chính:

  • Product: Thực thể Sản phẩm chính (Thông tin mô tả, giá cả, mã SKU, trạng thái, ảnh đại diện, bộ sưu tập).
  • Category: Danh mục sản phẩm (Cấu trúc phân cấp Tree: Danh mục cha - con).
  • Brand: Thương hiệu / Hãng sản xuất sản phẩm.
  • ProductVariant: Biến thể sản phẩm (Màu sắc, kích thước, dung lượng, giá riêng, SKU riêng).
  • ProductReview: Đánh giá & Phản hồi của khách hàng về sản phẩm.

2. Quy Tắc Cấu Hình YAML Blueprint (JAM Standards)

  1. Thứ tự Cột Base: id luôn ở đầu tiên. created_at, updated_at, published_at luôn ở cuối danh sách columns.
  2. Kích hoạt Link Action: is_action: true được gán vào cột name của sản phẩm để tạo đường dẫn điều hướng trong CMS table.
  3. Cột Slug: Tự động sinh bởi hệ thống — không khai báo slug trong layout: form / table / tree.
  4. Quan hệ N - N (Pivot Table): Dùng cú pháp params: TargetModel::class|pivot_table_name với tên bảng pivot ở dạng số nhiều (vd: category_products).
  5. CMS Sidebar Children Ordering: Quy tắc khai báo mảng children trong YAML phải sắp xếp theo thứ tự ngược lại (Phụ lên trước, Chính xuống dưới cùng) để UI hiển thị theo đúng thứ tự ưu tiên (Sản phẩm -> Danh mục -> Thương hiệu -> Đánh giá).
  6. CMS Table Widths & Dot Notation:
    • id: 150
    • status, price, sku, position: 150
    • Hiển thị quan hệ belongsTo: Dùng dot notation trỏ tới trường tên (ví dụ: category.name, brand.name) với table_width: 150.
    • Quan hệ hasMany / belongsToMany (DataTable, MultiSelect): table_width: 400.
  7. Form Section Widths: Dùng field_width: md cho các trường ngắn như status, price, sku, category_id, brand_id để hiển thị 2 cột sóng đôi đẹp mắt.

3. Mã Cấu Hình YAML Blueprint Mẫu (config/tenants/blueprint_product.yaml)

1version: 2 2 3models: 4 # 1. Thương hiệu (Brand) 5 Brand: 6 columns: 7 id: { type: increments, config: { ui: Numbering } } 8 name: 9 { 10 type: string, 11 config: 12 { ui: Text, is_action: true, validate: "required|string|max:255" }, 13 } 14 slug: 15 { 16 type: string, 17 config: { ui: Text, validate: "nullable|string|max:255" }, 18 } 19 logo: { type: string, config: { ui: SingleAttachment } } 20 description: 21 { type: text, config: { ui: Textarea, validate: "nullable|string" } } 22 status: 23 type: enum 24 config: 25 ui: RadioButton 26 default: active 27 options: 28 - { value: active, label: "Hoạt động", color: green } 29 - { value: inactive, label: "Tạm ẩn", color: red } 30 validate: "required|in:active,inactive" 31 position: 32 { 33 type: integer, 34 config: { default: 0, ui: Number, validate: "nullable|integer" }, 35 } 36 created_at: { type: timestamp, config: { ui: Date } } 37 updated_at: { type: timestamp, config: { ui: Date } } 38 relationships: 39 hasMany: 40 - { model: Product, config: { ui: DataTable } } 41 42 # 2. Danh mục sản phẩm (Category - Tree structure) 43 Category: 44 columns: 45 id: { type: increments, config: { ui: Numbering } } 46 parent_id: 47 { 48 type: foreignId:categories, 49 config: { ui: Lookup, on_delete: set null }, 50 } 51 name: 52 { 53 type: string, 54 config: 55 { ui: Text, is_action: true, validate: "required|string|max:255" }, 56 } 57 slug: 58 { 59 type: string, 60 config: { ui: Text, validate: "nullable|string|max:255" }, 61 } 62 cover: { type: string, config: { ui: SingleAttachment } } 63 description: 64 { type: text, config: { ui: Textarea, validate: "nullable|string" } } 65 status: 66 type: enum 67 config: 68 ui: RadioButton 69 default: active 70 options: 71 - { value: active, label: "Hoạt động", color: green } 72 - { value: inactive, label: "Tạm ẩn", color: red } 73 validate: "required|in:active,inactive" 74 position: 75 { 76 type: integer, 77 config: { default: 0, ui: Number, validate: "nullable|integer" }, 78 } 79 created_at: { type: timestamp, config: { ui: Date } } 80 updated_at: { type: timestamp, config: { ui: Date } } 81 relationships: 82 belongsTo: 83 - { model: Category, name: parent, config: { ui: SingleSelect } } 84 hasMany: 85 - { model: Category, name: children, config: { ui: DataTable } } 86 - { model: Product, config: { ui: DataTable } } 87 88 # 3. Sản phẩm (Product) 89 Product: 90 columns: 91 id: { type: increments, config: { ui: Numbering } } 92 brand_id: 93 { type: foreignId:brands, config: { ui: Lookup, on_delete: set null } } 94 category_id: 95 { 96 type: foreignId:categories, 97 config: { ui: Lookup, on_delete: set null }, 98 } 99 name: 100 { 101 type: string, 102 config: 103 { ui: Text, is_action: true, validate: "required|string|max:255" }, 104 } 105 slug: 106 { 107 type: string, 108 config: { ui: Text, validate: "nullable|string|max:255" }, 109 } 110 sku: 111 { 112 type: string, 113 config: { ui: Text, validate: "nullable|string|max:100" }, 114 } 115 cover: { type: string, config: { ui: SingleAttachment } } 116 images: { type: json, config: { ui: MultipleAttachments } } 117 summary: 118 { type: text, config: { ui: Textarea, validate: "nullable|string" } } 119 content: 120 { type: text, config: { ui: Editor, validate: "nullable|string" } } 121 price: 122 { 123 type: decimal, 124 config: 125 { default: 0, ui: Currency, validate: "required|numeric|min:0" }, 126 } 127 compare_at_price: 128 { 129 type: decimal, 130 config: 131 { default: 0, ui: Currency, validate: "nullable|numeric|min:0" }, 132 } 133 is_featured: 134 { 135 type: boolean, 136 config: { default: false, ui: Checkbox, validate: "boolean" }, 137 } 138 status: 139 type: enum 140 config: 141 ui: RadioButton 142 default: active 143 options: 144 - { value: active, label: "Đang bán", color: green } 145 - { value: draft, label: "Bản nháp", color: orange } 146 - { value: archived, label: "Lưu trữ", color: red } 147 validate: "required|in:active,draft,archived" 148 position: 149 { 150 type: integer, 151 config: { default: 0, ui: Number, validate: "nullable|integer" }, 152 } 153 created_at: { type: timestamp, config: { ui: Date } } 154 updated_at: { type: timestamp, config: { ui: Date } } 155 published_at: 156 { type: timestamp, config: { ui: Date, validate: "nullable|date" } } 157 relationships: 158 belongsTo: 159 - { model: Brand, config: { ui: SingleSelect } } 160 - { model: Category, config: { ui: SingleSelect } } 161 hasMany: 162 - { model: ProductVariant, config: { ui: DataTable } } 163 - { model: ProductReview, config: { ui: DataTable } } 164 belongsToMany: 165 - { 166 model: Category, 167 params: "Category::class|category_products", 168 config: { ui: MultiSelect }, 169 } 170 171 # 4. Biến thể sản phẩm (ProductVariant) 172 ProductVariant: 173 columns: 174 id: { type: increments, config: { ui: Numbering } } 175 product_id: 176 { type: foreignId:products, config: { ui: Lookup, on_delete: cascade } } 177 title: 178 { 179 type: string, 180 config: 181 { ui: Text, is_action: true, validate: "required|string|max:255" }, 182 } 183 sku: 184 { 185 type: string, 186 config: { ui: Text, validate: "nullable|string|max:100" }, 187 } 188 image: { type: string, config: { ui: SingleAttachment } } 189 price: 190 { 191 type: decimal, 192 config: 193 { default: 0, ui: Currency, validate: "required|numeric|min:0" }, 194 } 195 compare_at_price: 196 { 197 type: decimal, 198 config: 199 { default: 0, ui: Currency, validate: "nullable|numeric|min:0" }, 200 } 201 stock_quantity: 202 { 203 type: integer, 204 config: 205 { default: 0, ui: Number, validate: "required|integer|min:0" }, 206 } 207 option1: { 208 type: string, 209 config: { ui: Text, validate: "nullable|string|max:100" }, 210 } # ví dụ: Màu sắc 211 option2: { 212 type: string, 213 config: { ui: Text, validate: "nullable|string|max:100" }, 214 } # ví dụ: Kích thước 215 created_at: { type: timestamp, config: { ui: Date } } 216 updated_at: { type: timestamp, config: { ui: Date } } 217 relationships: 218 belongsTo: 219 - { model: Product, config: { ui: SingleSelect } } 220 221 # 5. Đánh giá sản phẩm (ProductReview) 222 ProductReview: 223 columns: 224 id: { type: increments, config: { ui: Numbering } } 225 product_id: 226 { type: foreignId:products, config: { ui: Lookup, on_delete: cascade } } 227 reviewer_name: 228 { 229 type: string, 230 config: 231 { ui: Text, is_action: true, validate: "required|string|max:255" }, 232 } 233 rating: 234 { 235 type: integer, 236 config: 237 { 238 default: 5, 239 ui: Number, 240 validate: "required|integer|min:1|max:5", 241 }, 242 } 243 comment: 244 { type: text, config: { ui: Textarea, validate: "nullable|string" } } 245 status: 246 type: enum 247 config: 248 ui: RadioButton 249 default: pending 250 options: 251 - { value: approved, label: "Đã duyệt", color: green } 252 - { value: pending, label: "Chờ duyệt", color: orange } 253 - { value: rejected, label: "Từ chối", color: red } 254 validate: "required|in:approved,pending,rejected" 255 created_at: { type: timestamp, config: { ui: Date } } 256 updated_at: { type: timestamp, config: { ui: Date } } 257 relationships: 258 belongsTo: 259 - { model: Product, config: { ui: SingleSelect } } 260 261# ------------------------------------------------------------------------------ 262# CMS Apps & Sidebar Navigation Config 263# ------------------------------------------------------------------------------ 264apps: 265 CMS: 266 roles: [admin, superadmin] 267 sidebar: 268 top: 269 - label: "Quản lý Sản phẩm" 270 icon: "tabler:package" 271 # Lưu ý: Các item con được định nghĩa theo thứ tự đảo ngược (Reverse Order) 272 # để UI hiển thị theo thứ tự mong muốn: Sản phẩm -> Danh mục -> Thương hiệu -> Đánh giá 273 children: 274 - label: "Đánh giá" 275 icon: "tabler:star" 276 tab_bar: 277 - model: ProductReview 278 hidden: false 279 layout: table 280 form_type: page 281 fields: 282 - { name: id, table_width: 100 } 283 - { name: reviewer_name, table_width: 200 } 284 - { name: product.name, table_width: 250 } 285 - { name: rating, table_width: 100 } 286 - { name: status, table_width: 150 } 287 - { name: created_at, table_width: 150 } 288 - model: ProductReview 289 hidden: true 290 layout: form 291 form_type: popup 292 form_layout: block 293 sections: 294 - name: "Chi tiết đánh giá" 295 fields: 296 - reviewer_name 297 - { name: product_id, field_width: md } 298 - { name: rating, field_width: md } 299 - { name: status, field_width: md } 300 - comment 301 302 - label: "Thương hiệu" 303 icon: "tabler:building-store" 304 tab_bar: 305 - model: Brand 306 hidden: false 307 layout: table 308 form_type: page 309 fields: 310 - { name: id, table_width: 100 } 311 - logo 312 - { name: name, table_width: 300 } 313 - { name: status, table_width: 150 } 314 - { name: position, table_width: 150 } 315 - { name: created_at, table_width: 150 } 316 - model: Brand 317 hidden: true 318 layout: form 319 form_type: page 320 sections: 321 - name: "Thông tin chung" 322 fields: 323 - logo 324 - name 325 - { name: status, field_width: md } 326 - { name: position, field_width: md } 327 - description 328 329 - label: "Danh mục" 330 icon: "tabler:category" 331 tab_bar: 332 - model: Category 333 hidden: false 334 layout: tree 335 form_type: page 336 fields: [id, cover, name, status, position, created_at] 337 - model: Category 338 hidden: true 339 layout: form 340 form_type: page 341 sections: 342 - name: "Thông tin danh mục" 343 fields: 344 - cover 345 - name 346 - { name: parent_id, field_width: md } 347 - { name: status, field_width: md } 348 - { name: position, field_width: md } 349 - description 350 351 - label: "Sản phẩm" 352 icon: "tabler:box" 353 tab_bar: 354 - model: Product 355 hidden: false 356 layout: table 357 form_type: page 358 fields: 359 - { name: id, table_width: 100 } 360 - cover 361 - { name: name, table_width: 300 } 362 - { name: sku, table_width: 150 } 363 - { name: price, table_width: 150 } 364 - { name: category.name, table_width: 150 } 365 - { name: brand.name, table_width: 150 } 366 - { name: status, table_width: 150 } 367 - { name: created_at, table_width: 150 } 368 - model: Product 369 hidden: true 370 layout: form 371 form_type: page 372 sections: 373 - name: "Thông tin chính" 374 fields: 375 - cover 376 - name 377 - { name: sku, field_width: md } 378 - { name: status, field_width: md } 379 - { name: category_id, field_width: md } 380 - { name: brand_id, field_width: md } 381 - { name: published_at, field_width: md } 382 - summary 383 - name: "Giá & Khuyến mãi" 384 fields: 385 - { name: price, field_width: md } 386 - { name: compare_at_price, field_width: md } 387 - { name: is_featured, field_width: md } 388 - name: "Mô tả chi tiết" 389 fields: 390 - images 391 - content 392 - name: "Danh mục phụ" 393 fields: 394 - categories

4. Hướng Dẫn Kích Hoạt Đồng Bộ (App Sync & Init)

Sau khi tạo hoặc thêm file blueprint này vào thư mục config/tenants/, bạn chạy lệnh đồng bộ hệ thống:

1./.agents/skills/jam-tenant-blueprint/scripts/app-init.sh

Lệnh này sẽ thực hiện tự động:

  1. php artisan app:init: Khởi tạo Schema Database, tự động tạo Eloquent Models, Relationships và Mutators.
  2. php artisan generate:translation: Đồng bộ hệ thống đa ngôn ngữ cho CMS và API.