Commit 39ee2678 authored by DevPilot's avatar DevPilot

fix(inventory): fixed assets had no name, and no way back in to edit them

Two things I got wrong when I opened up direct asset registration.

**There was nowhere to type what the asset IS.** The register carried a tag, a
category and a serial — the name was borrowed from `inventory_items.name_ar`,
which works only while every asset comes from stock. A building, a court or a
transformer has no stock row, so the detail screen showed an empty cell and the
form never asked. The register is meant to be read by someone doing a physical
count: "أثاث ومعدات" tells them nothing, "مكيف سبليت ٣ حصان — قاعة الاجتماعات"
tells them exactly what to look for. `asset_name` is now a required field,
backfilled from the linked item where there is one and from the category
otherwise, so no existing row loses the name it was already showing. It is also
searchable.

**The edit route had no button.** `/inventory/assets/{id}/edit` existed and
worked; nothing linked to it, so once an asset was saved there was no way back
in from the UI. Buttons on both the detail page and the register rows, hidden
for disposed assets since those are history.

Also corrected the labels that direct registration made wrong: the column header
and search hint still said "الصنف", and the empty state still claimed assets are
only created automatically when stock of type "أصل" is received.

Verified on a production clone: name saves, displays on both screens, is
findable by search, both edit buttons render, 1503 routes resolve.
Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
parent 9bb0c943
...@@ -157,6 +157,7 @@ class AssetController extends Controller ...@@ -157,6 +157,7 @@ class AssetController extends Controller
'category_id' => $data['category_id'], 'category_id' => $data['category_id'],
'warehouse_id' => $data['warehouse_id'], 'warehouse_id' => $data['warehouse_id'],
'asset_tag' => $data['asset_tag'], 'asset_tag' => $data['asset_tag'],
'asset_name' => $data['asset_name'],
'serial_number' => $data['serial_number'], 'serial_number' => $data['serial_number'],
'purchase_date' => $data['purchase_date'], 'purchase_date' => $data['purchase_date'],
'purchase_cost' => $data['purchase_cost'], 'purchase_cost' => $data['purchase_cost'],
...@@ -239,6 +240,7 @@ class AssetController extends Controller ...@@ -239,6 +240,7 @@ class AssetController extends Controller
)['c'] > 0; )['c'] > 0;
$fields = [ $fields = [
'asset_name' => $data['asset_name'],
'category_id' => $data['category_id'], 'category_id' => $data['category_id'],
'warehouse_id' => $data['warehouse_id'], 'warehouse_id' => $data['warehouse_id'],
'serial_number' => $data['serial_number'], 'serial_number' => $data['serial_number'],
...@@ -341,6 +343,9 @@ class AssetController extends Controller ...@@ -341,6 +343,9 @@ class AssetController extends Controller
if ($tag === '') { if ($tag === '') {
return ['error' => 'رقم الأصل مطلوب']; return ['error' => 'رقم الأصل مطلوب'];
} }
if (trim((string) $request->post('asset_name', '')) === '') {
return ['error' => 'اسم الأصل مطلوب — ده اللي هيتقري في الجرد'];
}
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) { if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
return ['error' => 'تاريخ الشراء مطلوب بصيغة صحيحة']; return ['error' => 'تاريخ الشراء مطلوب بصيغة صحيحة'];
} }
...@@ -368,6 +373,7 @@ class AssetController extends Controller ...@@ -368,6 +373,7 @@ class AssetController extends Controller
return [ return [
'asset_tag' => $tag, 'asset_tag' => $tag,
'asset_name' => mb_substr(trim((string) $request->post('asset_name', '')), 0, 200),
'category_id' => $categoryId, 'category_id' => $categoryId,
'item_id' => ((int) $request->post('item_id', 0)) ?: null, 'item_id' => ((int) $request->post('item_id', 0)) ?: null,
'warehouse_id' => ((int) $request->post('warehouse_id', 0)) ?: null, 'warehouse_id' => ((int) $request->post('warehouse_id', 0)) ?: null,
......
...@@ -19,6 +19,7 @@ class AssetRegister extends Model ...@@ -19,6 +19,7 @@ class AssetRegister extends Model
'item_id', 'item_id',
'warehouse_id', 'warehouse_id',
'asset_tag', 'asset_tag',
'asset_name',
'serial_number', 'serial_number',
'purchase_date', 'purchase_date',
'purchase_cost', 'purchase_cost',
...@@ -77,7 +78,8 @@ class AssetRegister extends Model ...@@ -77,7 +78,8 @@ class AssetRegister extends Model
if (!empty($filters['q'])) { if (!empty($filters['q'])) {
$search = '%' . $filters['q'] . '%'; $search = '%' . $filters['q'] . '%';
$where .= ' AND (ar.`asset_tag` LIKE ? OR i.`name_ar` LIKE ? OR c.`name_ar` LIKE ? OR ar.`serial_number` LIKE ?)'; $where .= ' AND (ar.`asset_tag` LIKE ? OR ar.`asset_name` LIKE ? OR i.`name_ar` LIKE ? OR c.`name_ar` LIKE ? OR ar.`serial_number` LIKE ?)';
$params[] = $search;
$params[] = $search; $params[] = $search;
$params[] = $search; $params[] = $search;
$params[] = $search; $params[] = $search;
......
...@@ -34,6 +34,17 @@ $__template->layout('Layout.main'); ...@@ -34,6 +34,17 @@ $__template->layout('Layout.main');
<h3 style="margin:0;color:#0D7377;font-size:15px;">بيانات الأصل</h3> <h3 style="margin:0;color:#0D7377;font-size:15px;">بيانات الأصل</h3>
</div> </div>
<div style="padding:20px;"> <div style="padding:20px;">
<div class="form-group" style="margin-bottom:15px;">
<label class="form-label">اسم الأصل <span style="color:#DC2626;">*</span></label>
<input type="text" name="asset_name" class="form-input" required
value="<?= e(old('asset_name', (string) ($asset['asset_name'] ?? ''))) ?>"
placeholder="مثال: مكيف سبليت ٣ حصان — قاعة الاجتماعات">
<small style="color:#6B7280;">
ده الاسم اللي هيتقري في الجرد وفي كشوف الأصول. اكتبه بالتفصيل اللي
يخلّي حد تاني يعرف يلاقي الأصل من غير ما يسأل.
</small>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:20px;"> <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:20px;">
<div class="form-group"> <div class="form-group">
<label class="form-label">رقم الأصل <span style="color:#DC2626;">*</span></label> <label class="form-label">رقم الأصل <span style="color:#DC2626;">*</span></label>
......
...@@ -38,7 +38,7 @@ $depMethodLabels = [ ...@@ -38,7 +38,7 @@ $depMethodLabels = [
<form method="GET" action="/inventory/assets" style="display:flex;gap:10px;flex-wrap:wrap;align-items:end;"> <form method="GET" action="/inventory/assets" style="display:flex;gap:10px;flex-wrap:wrap;align-items:end;">
<div style="flex:1;min-width:200px;"> <div style="flex:1;min-width:200px;">
<label class="form-label" style="font-size:12px;">بحث</label> <label class="form-label" style="font-size:12px;">بحث</label>
<input type="text" name="q" value="<?= e($filters['q'] ?? '') ?>" placeholder="رقم الأصل، اسم الصنف..." class="form-input"> <input type="text" name="q" value="<?= e($filters['q'] ?? '') ?>" placeholder="رقم الأصل، الاسم، الرقم التسلسلي..." class="form-input">
</div> </div>
<div style="min-width:160px;"> <div style="min-width:160px;">
<label class="form-label" style="font-size:12px;">الحالة</label> <label class="form-label" style="font-size:12px;">الحالة</label>
...@@ -71,7 +71,7 @@ $depMethodLabels = [ ...@@ -71,7 +71,7 @@ $depMethodLabels = [
<thead> <thead>
<tr> <tr>
<th>رقم الأصل</th> <th>رقم الأصل</th>
<th>الصنف</th> <th>اسم الأصل</th>
<th>المخزن</th> <th>المخزن</th>
<th>تكلفة الشراء</th> <th>تكلفة الشراء</th>
<th>القيمة الدفترية</th> <th>القيمة الدفترية</th>
...@@ -97,7 +97,7 @@ $depMethodLabels = [ ...@@ -97,7 +97,7 @@ $depMethodLabels = [
<!-- A fixed asset need not be a stock item, so fall back to <!-- A fixed asset need not be a stock item, so fall back to
its category rather than showing an empty cell. --> its category rather than showing an empty cell. -->
<td style="font-weight:600;"> <td style="font-weight:600;">
<?= e((string) ($asset['item_name'] ?: $asset['category_name'] ?? '')) ?> <?= e((string) ($asset['asset_name'] ?: ($asset['item_name'] ?: $asset['category_name'] ?? ''))) ?>
</td> </td>
<td><?= e((string) ($asset['warehouse_name'] ?: '—')) ?></td> <td><?= e((string) ($asset['warehouse_name'] ?: '—')) ?></td>
<td style="font-weight:700;direction:ltr;text-align:left;"><?= money($asset['purchase_cost'] ?? 0) ?></td> <td style="font-weight:700;direction:ltr;text-align:left;"><?= money($asset['purchase_cost'] ?? 0) ?></td>
...@@ -108,10 +108,15 @@ $depMethodLabels = [ ...@@ -108,10 +108,15 @@ $depMethodLabels = [
<?= e($asStInfo['label']) ?> <?= e($asStInfo['label']) ?>
</span> </span>
</td> </td>
<td> <td style="white-space:nowrap;">
<a href="/inventory/assets/<?= (int) $asset['id'] ?>" class="btn btn-sm btn-outline" style="font-size:12px;padding:4px 10px;"> <a href="/inventory/assets/<?= (int) $asset['id'] ?>" class="btn btn-sm btn-outline" style="font-size:12px;padding:4px 10px;">
<i data-lucide="eye" style="width:13px;height:13px;vertical-align:middle;"></i> عرض <i data-lucide="eye" style="width:13px;height:13px;vertical-align:middle;"></i> عرض
</a> </a>
<?php if (can('asset.manage') && $asset['status'] === 'active'): ?>
<a href="/inventory/assets/<?= (int) $asset['id'] ?>/edit" class="btn btn-sm btn-outline" style="font-size:12px;padding:4px 10px;">
<i data-lucide="pencil" style="width:13px;height:13px;vertical-align:middle;"></i> تعديل
</a>
<?php endif; ?>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
...@@ -136,7 +141,8 @@ $depMethodLabels = [ ...@@ -136,7 +141,8 @@ $depMethodLabels = [
<?php if (!empty($filters['q']) || !empty($filters['status']) || !empty($filters['warehouse_id'])): ?> <?php if (!empty($filters['q']) || !empty($filters['status']) || !empty($filters['warehouse_id'])): ?>
لا توجد نتائج مطابقة لبحثك. جرب تغيير معايير البحث. لا توجد نتائج مطابقة لبحثك. جرب تغيير معايير البحث.
<?php else: ?> <?php else: ?>
يتم إنشاء الأصول تلقائيا عند استلام أصناف من نوع "أصل". ابدأ بتسجيل أصول النادي من زرار «تسجيل أصل ثابت» فوق — والأصول اللي موجودة
في الدفاتر من قبل سجّلها كـ«رصيد افتتاحي» عشان تبدأ تتهلك.
<?php endif; ?> <?php endif; ?>
</p> </p>
</div> </div>
......
...@@ -2,6 +2,11 @@ ...@@ -2,6 +2,11 @@
<?php $__template->section('title'); ?>أصل <?= e($asset['asset_tag']) ?><?php $__template->endSection(); ?> <?php $__template->section('title'); ?>أصل <?= e($asset['asset_tag']) ?><?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?> <?php $__template->section('page_actions'); ?>
<?php if (can('asset.manage') && $asset['status'] === 'active'): ?>
<a href="/inventory/assets/<?= (int) $asset['id'] ?>/edit" class="btn btn-primary">
<i data-lucide="pencil" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i> تعديل الأصل
</a>
<?php endif; ?>
<a href="/inventory/assets" class="btn btn-outline"><i data-lucide="arrow-right" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i> العودة للقائمة</a> <a href="/inventory/assets" class="btn btn-outline"><i data-lucide="arrow-right" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i> العودة للقائمة</a>
<?php $__template->endSection(); ?> <?php $__template->endSection(); ?>
...@@ -50,19 +55,42 @@ $depPercent = $depreciableAmount > 0 ? min(100, round(($accumulatedDep / $deprec ...@@ -50,19 +55,42 @@ $depPercent = $depreciableAmount > 0 ? min(100, round(($accumulatedDep / $deprec
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="padding:10px 0;color:#6B7280;">الصنف</td> <td style="padding:10px 0;color:#6B7280;">اسم الأصل</td>
<td style="padding:10px 0;font-weight:600;"><?= e($asset['item_name'] ?? '') ?></td> <td style="padding:10px 0;font-weight:600;">
<?php
// A fixed asset need not be a stock item — a building, a court
// and a transformer have no SKU. Reading item_name alone left
// the name blank on every asset registered directly, which is
// most of them. Fall back to the category, then the tag.
$assetName = $asset['asset_name'] ?: ($asset['item_name'] ?: ($asset['category_name'] ?? ''));
echo e((string) ($assetName ?: $asset['asset_tag']));
?>
</td>
</tr>
<?php if (!empty($asset['category_name']) && !empty($asset['item_name'])): ?>
<tr>
<td style="padding:10px 0;color:#6B7280;">الفئة</td>
<td style="padding:10px 0;"><?= e((string) $asset['category_name']) ?></td>
</tr> </tr>
<?php endif; ?>
<?php if (!empty($asset['sku'])): ?>
<tr> <tr>
<td style="padding:10px 0;color:#6B7280;">SKU</td> <td style="padding:10px 0;color:#6B7280;">SKU</td>
<td style="padding:10px 0;"> <td style="padding:10px 0;">
<code style="font-size:11px;background:#F3F4F6;padding:2px 6px;border-radius:4px;"><?= e($asset['sku'] ?? '—') ?></code> <code style="font-size:11px;background:#F3F4F6;padding:2px 6px;border-radius:4px;"><?= e($asset['sku']) ?></code>
</td> </td>
</tr> </tr>
<?php endif; ?>
<tr> <tr>
<td style="padding:10px 0;color:#6B7280;">المخزن</td> <td style="padding:10px 0;color:#6B7280;">المخزن</td>
<td style="padding:10px 0;font-weight:600;"><?= e($asset['warehouse_name'] ?? '') ?></td> <td style="padding:10px 0;font-weight:600;"><?= e((string) ($asset['warehouse_name'] ?: '— لا ينطبق')) ?></td>
</tr> </tr>
<?php if (!empty($asset['site_location'])): ?>
<tr>
<td style="padding:10px 0;color:#6B7280;">الموقع</td>
<td style="padding:10px 0;"><?= e((string) $asset['site_location']) ?></td>
</tr>
<?php endif; ?>
<?php if (!empty($asset['serial_number'])): ?> <?php if (!empty($asset['serial_number'])): ?>
<tr> <tr>
<td style="padding:10px 0;color:#6B7280;">الرقم التسلسلي</td> <td style="padding:10px 0;color:#6B7280;">الرقم التسلسلي</td>
......
<?php
declare(strict_types=1);
use App\Core\Database;
/**
* Gives a fixed asset a name of its own.
*
* `asset_register` had a tag, a serial and a category — and no name. That was
* survivable while every asset came from an inventory item and borrowed
* `inventory_items.name_ar`. Once an asset could be registered directly (a
* building, a court, a transformer — none of which are stock items) there was
* nowhere to type what the thing actually IS, and the detail screen showed an
* empty cell.
*
* The register is meant to be read by someone doing a physical count. "أثاث
* ومعدات" tells them nothing; "مكيف سبليت ٣ حصان — الدور الأول" tells them
* exactly what to look for.
*
* Backfilled from the linked inventory item where there is one, so existing
* rows keep the name they were already displaying.
*/
return static function (Database $db): void {
$exists = $db->selectOne(
"SELECT 1 AS x FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'asset_register'
AND column_name = 'asset_name'"
);
if (!$exists) {
$db->raw(
"ALTER TABLE `asset_register`
ADD COLUMN `asset_name` VARCHAR(200) NULL
COMMENT 'اسم الأصل زي ما هيتقري في الجرد' AFTER `asset_tag`"
);
}
// Keep whatever name each existing asset was already showing.
$db->query(
"UPDATE asset_register a
JOIN inventory_items i ON i.id = a.item_id
SET a.asset_name = i.name_ar
WHERE a.asset_name IS NULL AND i.name_ar IS NOT NULL"
);
// Anything left has no item to borrow from — fall back to its category so no
// row is nameless.
$db->query(
"UPDATE asset_register a
JOIN asset_categories c ON c.id = a.category_id
SET a.asset_name = c.name_ar
WHERE a.asset_name IS NULL"
);
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment