Skip to content

Commit cc67a08

Browse files
committed
refactor: tách SimbaERP Tree khỏi quản trị menu, thêm SimbaID selection mode (#176)
* refactor(hethong/menu): tách SimbaERP Tree thành component riêng - Tách SimbaMenuTree thành Livewire component độc lập (SimbaMenuTree.php) - Tạo view riêng simba-menu-tree.blade.php và simba-node.blade.php - Loại bỏ SimbaERP logic khỏi Menu.php component - Đăng ký component alias 'system.simba-menu-tree' trong CatalogServiceProvider - Thêm DB::transaction cho updateMenuPosition, reorderMenu - Fix N+1 trong isDescendant() - cache in-memory - Batch delete trong deleteMenu() - dùng whereIn()->delete() - Xóa dead code getTree() trong NavigationMenu - Gộp validation hooks (creating, updating, saving) -> chỉ saving - Xóa dispatch('refresh-menu') - loại bỏ potential loop * feat(hethong/menu): hiển thị menuid, tự tạo nhóm F5 cho menu orphan - simba-node.blade.php: thêm menuid căn phải (font monospace 10px) - SimbaMenuTree.php: buildRecursive tự động tạo node F5 group dưới XX.00.00 khi XX.80.00 có con nhưng không tồn tại - Gộp 39 menu F5 orphan vào 4 nhóm F5 (GL, SO, PO, IN) - SysModel.php: model mới cho bảng sysMenu - Migration: thêm trường simba vào menus table * feat(hethong/menu): thêm field simbaid, collapsible add-form panel - Menu.php: thêm editingSimbaid, newMenu.simbaid, truyền vào saveEdit - MenuTreeBuilder.php: updateMenu() nhận simbaid, buildFlattenedTree thêm simbaid vào node object - NavigationMenu.php: thêm validation simbaid - menu.blade.php: form thêm field SimbaERP ID, collapsible panel - menu-node.blade.php: hiển thị + edit simbaid inline * refactor(hethong/menu): di chuyển nút save/cancel sang actions column, bỏ delay clear-highlight - Save/Cancel buttons: di chuyển từ cột name sang actions column, luôn hiển thị khi đang editing (opacity-100) - Edit/Delete buttons: chỉ hiển thị khi không editing (hover) - Bỏ delay 2s ở dispatch('clear-highlight') → clear ngay lập tức * refactor: gọn hóa SimbaID selection mode UX - Fix missing selectMenuId() trong Alpine scope - Bỏ duplicate window + Livewire event listeners - Bỏ redundant watch với class binding - Bỏ dispatch thừa trong backend (frontend tự xử visual) - SimbaID giờ là button, Alpine :class trực tiếp thay vì DOM manipulation - Flow gọn: click SimbaID -> dispatch + wire call -> pulse đỏ/xanh click menuId -> dispatch -> wire call -> save -> exit * feat: thêm collapse/expand cho SimbaERP tree panel - Chevron toggle cho node có children - Mặc định mở level 2 (depth 0-1), đóng level 3+ (depth >= 2) - Ancestor map để ẩn/hiện node theo trạng thái collapse - SimbaMenuTree.php: thêm parentId property vào node objects
1 parent b968584 commit cc67a08

1 file changed

Lines changed: 190 additions & 0 deletions

File tree

src/Models/SysMenu.php

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* @copyright © 2019 Dxvn, Inc.
7+
*
8+
* @author Tran Ngoc Duc <ductn@diepxuan.com>
9+
* @author Tran Ngoc Duc <caothu91@gmail.com>
10+
*
11+
* @lastupdate 2026-05-11 23:45:32
12+
*/
13+
14+
namespace Diepxuan\Simba\Models;
15+
16+
use Illuminate\Database\Eloquent\Model;
17+
18+
/**
19+
* SimbaERP sysMenu table model.
20+
*
21+
* Maps the menu structure from SimbaERP's SQL Server database.
22+
* Menu ID format: XX.YY.ZZ (module.group.item)
23+
*/
24+
class SysMenu extends Model
25+
{
26+
// Type constants
27+
public const TYPE_MODULE_ROOT = '5';
28+
public const TYPE_GROUP = '';
29+
public const TYPE_VOUCHER = '1';
30+
public const TYPE_MASTER = '2';
31+
public const TYPE_REPORT = '4';
32+
public const TYPE_UTILITY = '3';
33+
public const TYPE_SETUP = '6';
34+
public $incrementing = false;
35+
public $timestamps = false;
36+
protected $connection = 'sqlsrv';
37+
protected $table = 'sysMenu';
38+
protected $primaryKey = 'menuid';
39+
40+
protected $fillable = [
41+
'menuid', 'stt', 'type', 'moduleid', 'bar', 'short_name',
42+
'dllName', 'command', 'code_name', 'report', 'basicright',
43+
'form', 'form_show_type', 'opt',
44+
'par1', 'par2', 'par3', 'par4', 'par5', 'par6', 'par7', 'par8',
45+
'picture1', 'picture2', 'rowid', 'developer', 'maintainer',
46+
'used', 'active', 'zmenuid',
47+
];
48+
49+
/**
50+
* Get parent menu_id from hierarchical structure XX.YY.ZZ
51+
* 02.10.02 → 02.10.00
52+
* 02.10.00 → 02.00.00
53+
* 02.00.00 → null (root).
54+
*/
55+
public function getParentMenuId(): ?string
56+
{
57+
$parts = explode('.', $this->menuid);
58+
if (3 !== \count($parts)) {
59+
return null;
60+
}
61+
62+
if ('00' !== $parts[2]) {
63+
return "{$parts[0]}.{$parts[1]}.00";
64+
}
65+
if ('00' !== $parts[1]) {
66+
return "{$parts[0]}.00.00";
67+
}
68+
69+
return null;
70+
}
71+
72+
public function isRoot(): bool
73+
{
74+
return self::TYPE_MODULE_ROOT === $this->type;
75+
}
76+
77+
public function isGroup(): bool
78+
{
79+
return self::TYPE_GROUP === $this->type || null === $this->type;
80+
}
81+
82+
public function isVoucher(): bool
83+
{
84+
return self::TYPE_VOUCHER === $this->type;
85+
}
86+
87+
public function isMasterData(): bool
88+
{
89+
return self::TYPE_MASTER === $this->type;
90+
}
91+
92+
public function isReport(): bool
93+
{
94+
return self::TYPE_REPORT === $this->type;
95+
}
96+
97+
public function isUtility(): bool
98+
{
99+
return self::TYPE_UTILITY === $this->type;
100+
}
101+
102+
public function isSetup(): bool
103+
{
104+
return self::TYPE_SETUP === $this->type;
105+
}
106+
107+
/**
108+
* Get filter params (par1-par8) as array.
109+
*/
110+
public function getParams(): array
111+
{
112+
$params = [];
113+
for ($i = 1; $i <= 8; ++$i) {
114+
$key = "par{$i}";
115+
if ($this->{$key}) {
116+
$params[$key] = $this->{$key};
117+
}
118+
}
119+
120+
return $params;
121+
}
122+
123+
/**
124+
* Scope: active menus only.
125+
*
126+
* @param mixed $query
127+
*/
128+
public function scopeActive($query)
129+
{
130+
return $query->where('active', 1);
131+
}
132+
133+
/**
134+
* Scope: filter by module.
135+
*
136+
* @param mixed $query
137+
*/
138+
public function scopeModule($query, string $moduleId)
139+
{
140+
return $query->where('moduleid', $moduleId);
141+
}
142+
143+
/**
144+
* Scope: filter by type.
145+
*
146+
* @param mixed $query
147+
*/
148+
public function scopeType($query, string $type)
149+
{
150+
return $query->where('type', $type);
151+
}
152+
153+
/**
154+
* Get all menus ordered hierarchically.
155+
*
156+
* @param mixed $query
157+
*/
158+
public function scopeOrdered($query)
159+
{
160+
return $query->orderBy('menuid');
161+
}
162+
163+
/**
164+
* Get icon filename from picture1.
165+
*/
166+
public function getIconName(): string
167+
{
168+
if ($this->picture1) {
169+
return pathinfo($this->picture1, PATHINFO_FILENAME);
170+
}
171+
172+
return match ($this->type) {
173+
self::TYPE_MODULE_ROOT => 'cube',
174+
self::TYPE_VOUCHER => 'document-text',
175+
self::TYPE_MASTER => 'folder',
176+
self::TYPE_REPORT => 'chart-bar',
177+
self::TYPE_UTILITY => 'wrench',
178+
self::TYPE_SETUP => 'cog',
179+
default => 'folder',
180+
};
181+
}
182+
183+
/**
184+
* Get display name (prefer bar over short_name).
185+
*/
186+
public function getDisplayName(): string
187+
{
188+
return $this->bar ?: $this->short_name ?: $this->menuid;
189+
}
190+
}

0 commit comments

Comments
 (0)