Đang tải...

Xây dựng Quản lý kho hàng (Listing Management) trong hệ thống Bất Động Sản

07/05/2026
5 phút đọc
Xây dựng Quản lý kho hàng (Listing Management) trong hệ thống Bất Động Sản
Trong ngành Bất động sản (Proptech), việc quản lý kho hàng (Listing Management) không đơn giản là CRUD mà là bài toán về Tính nhất quán dữ liệu và Quản lý trạng...

Trong ngành Bất động sản (Proptech), việc quản lý kho hàng (Listing Management) không đơn giản là CRUD mà là bài toán về Tính nhất quán dữ liệu và Quản lý trạng thái.

Nếu thiết kế kém, dữ liệu sẽ bị trùng lặp (nhiều môi giới cùng đăng một căn nhà) hoặc thông tin bị sai lệch (nhà đã bán nhưng vẫn hiện "Đang bán"). Chúng ta sẽ xây dựng module này với tư duy Enterprise: Sử dụng Spatial Data (tọa độ bản đồ), JSON Media, và Action Pattern để đảm bảo quy trình kiểm duyệtListing chặt chẽ.

Bước 1: Khởi tạo dự án và Thiết kế Cơ sở dữ liệu

Tạo dự án mới:

laravel new proptech-listings
cd proptech-listings

1. Tạo Model và Migration:

php artisan make:model Listing -m

2. Thiết kế bảng listings:

Chúng ta cần lưu trữ thông tin kỹ thuật, pháp lý và dữ liệu đa phương tiện (JSON).

// database/migrations/xxxx_create_listings_table.php
public function up(): void
{
 Schema::create('listings', function (Blueprint $table) {
 $table->id();
 $table->string('title');
 $table->string('slug')->unique();
 $table->text('description');
 
 // Thông số kỹ thuật
 $table->decimal('area', 10, 2); // Diện tích (m2)
 $table->decimal('price', 15, 2); // Giá bán
 
 // Vị trí & Bản đồ
 $table->string('address');
 $table->decimal('lat', 10, 8)->nullable(); // Vĩ độ
 $table->decimal('lng', 11, 8)->nullable(); // Kinh độ
 
 // Pháp lý & Phân loại
 $table->string('legal_status'); // Sổ hồng, sổ đỏ, đang chờ...
 $table->string('category'); // Nhà phố, Chung cư, Đất nền
 $table->string('status')->default('available'); // available, deposited, sold
 
 // Media (Lưu JSON các link ảnh/video 360)
 $table->json('media_assets')->nullable(); 

 $table->timestamps();
 });
}

Bước 2: Chuẩn hóa dữ liệu bằng PHP Enums

Để tránh việc nhập sai trạng thái (ví dụ: gõ nhầm 'da ban' thay vì 'sold'), ta dùng Enums.

namespace AppModels;

use IlluminateDatabaseEloquentModel;
use AppEnumsListingStatus;
use AppEnumsListingCategory;
use IlluminateSupportStr;

class Listing extends Model
{
 protected $fillable = [
 'title', 'slug', 'description', 'area', 'price', 'address', 
 'lat', 'lng', 'legal_status', 'category', 'status', 'media_assets'
 ];

 protected $casts = [
 'status' => ListingStatus::class,
 'category' => ListingCategory::class,
 'media_assets' => 'array', // Tự động convert JSON sang Array
 ];

 protected static function boot()
 {
 parent::boot();
 static::creating(fn ($listing) => $listing->slug = Str::slug($listing->title));
 }
}

Bước 3: Action Pattern - Tạo mới Bất động sản

Sử dụng Action giúp chúng ta dễ dàng thêm các logic kiểm tra trùng lặp vị trí hoặc tiêu đề sau này.

// app/Actions/CreateListingAction.php
namespace AppActions;

use AppModelsListing;
use IlluminateValidationValidationException;

class CreateListingAction
{
 public function execute(array $data): Listing
 {
 // Logic kiểm tra trùng lặp đơn giản (Ví dụ: Trùng địa chỉ chính xác)
 if (Listing::where('address', $data['address'])->exists()) {
 throw ValidationException::withMessages([
 'address' => 'Bất động sản tại địa chỉ này đã tồn tại trong kho hàng.'
 ]);
 }

 return Listing::create($data);
 }
}

Bước 4: API Resource - Định dạng dữ liệu trả về

Frontend cần dữ liệu đẹp, ví dụ: Diện tích phải có đơn vị "m2", giá tiền phải được format.

// app/Http/Resources/ListingResource.php
namespace AppHttpResources;

use IlluminateHttpResourcesJsonJsonResource;

class ListingResource extends JsonResource
{
 public function toArray($request): array
 {
 return [
 'id' => $this->id,
 'title' => $this->title,
 'slug' => $this->slug,
 'specifications' => [
 'area' => $this->area . ' m²',
 'price' => number_format($this->price) . ' VND',
 'category' => $this->category->value,
 ],
 'location' => [
 'full_address' => $this->address,
 'coords' => [
 'lat' => (float) $this->lat,
 'lng' => (float) $this->lng,
 ]
 ],
 'legal' => $this->legal_status,
 'status' => $this->status->value,
 'gallery' => $this->media_assets ?? [],
 'created_at' => $this->created_at->diffForHumans(),
 ];
 }
}

Bước 5: Controller & Routing

// app/Http/Controllers/Api/ListingController.php
namespace AppHttpControllersApi;

use AppHttpControllersController;
use IlluminateHttpRequest;
use AppActionsCreateListingAction;
use AppModelsListing;
use AppHttpResourcesListingResource;

class ListingController extends Controller
{
 public function index()
 {
 $listings = Listing::latest()->paginate(10);
 return ListingResource::collection($listings);
 }

 public function store(Request $request, CreateListingAction $action)
 {
 $data = $request->validate([
 'title' => 'required|string|max:255',
 'description' => 'required|string',
 'area' => 'required|numeric',
 'price' => 'required|numeric',
 'address' => 'required|string',
 'category' => 'required|string',
 'legal_status' => 'required|string',
 'media_assets' => 'nullable|array',
 'lat' => 'nullable|numeric',
 'lng' => 'nullable|numeric',
 ]);

 $listing = $action->execute($data);

 return response()->json([
 'success' => true,
 'data' => new ListingResource($listing)
 ], 201);
 }
}

Routes (routes/api.php):

use AppHttpControllersApiListingController;

Route::get('/listings', [ListingController::class, 'index']);
Route::post('/listings', [ListingController::class, 'store']);

Bước 6: Thử lửa với Postman

Khởi động server: php artisan serve

  1. Tạo Listing mới (POST):
  • URL: http://127.0.0.1:8000/api/listings
  • Body (JSON):
{
 "title": "Căn hộ Landmark 81 - View sông Sài Gòn",
 "description": "Căn hộ cao cấp đầy đủ nội thất, tầng cao thoáng mát.",
 "area": 85.5,
 "price": 7500000000,
 "address": "Landmark 81, Vinhomes Central Park, Bình Thạnh",
 "category": "apartment",
 "legal_status": "Sổ hồng riêng",
 "media_assets": [
 "https://example.com/img1.jpg",
 "https://example.com/tour360.mp4"
 ],
 "lat": 10.7948,
 "lng": 106.7218
}

2. Kết quả mong đợi:

Hệ thống trả về JSON đã được format qua Resource, slug tự động sinh ra là can-ho-landmark-81-view-song-sai-gon.

📚 Nguồn: Viblo

Bình luận

0 bình luận

Email không hiển thị công khai.

Chưa có bình luận nào. Hãy là người đầu tiên bình luận.

Chia sẻ bài viết

Cần tư vấn?

Liên hệ với chúng tôi để được hỗ trợ

Liên hệ ngay

Bài viết liên quan

1.0 Site To Buy Verified Bybit Account
21/06/2026

1.0 Site To Buy Verified Bybit Account

1.0 Site To Buy Verified Bybit Account 💬 Need Assistance? All Time Service, Just Talk To- ✨ Telegram: @usamarketit ✨ Email: usamarketit@gmail.com ✨ WhatsApp: +1(772)563-8300 ✨ Discord: usam...

Đọc thêm
VA88 MN
20/06/2026

VA88 MN

VA88 - Nhà cái cá cược thể thao, game đổi thưởng top #1 Châu Á 2026. Đa dạng các sản phẩm cá cược Bóng đá, Live Casino, Lô đề online, Bắn cá, Nổ hũ Địa ch?...

Đọc thêm
Best Sites to Buy Aged Reddit Accounts: Top 5 Trusted Providers
20/06/2026

Best Sites to Buy Aged Reddit Accounts: Top 5 Trusted Providers

# Old Reddit Accounts: Value, Benefits, Security, and Community Reputation ![](https://images.viblo.asia/bff98096-0daa-416e-9760-d90647fab604.jpg) Introduction Reddit is one of the world's largest onl...

Đọc thêm

Bắt đầu dự án của bạn

Hãy để Flash Dev đồng hành cùng bạn

Liên hệ ngay