[RELEASE] demo → main:銷售紀錄流水號顯示優化上線

1. [FEAT] 銷售紀錄流水號顯示剝除機台序號前綴 — 新增 HasDisplayFlowId trait,訂單詳情與發票匯出改顯示乾淨流水號;DB 仍存完整 serial+flow_id 唯一值供去重/冪等/對帳判斷,含防禦性處理與測試。

備註:純顯示層變更,無資料庫遷移、無設定檔變更、無 RBAC 權限調整。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
sky121113 2026-06-19 09:32:41 +08:00
commit e3593a4ba8
6 changed files with 49 additions and 4 deletions

View File

@ -1030,7 +1030,7 @@ class SalesController extends Controller
$row = [
$inv->invoice_no ?: '---',
$inv->invoice_date ?: '---',
$inv->flow_id ?: '---',
$inv->display_flow_id ?: '---',
$inv->machine->name ?? ($inv->order->machine->name ?? 'Unknown'),
$inv->machine->serial_no ?? ($inv->order->machine->serial_no ?? '---'),
$inv->order->order_no ?? '---',

View File

@ -6,13 +6,14 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Traits\TenantScoped;
use App\Traits\HasDisplayFlowId;
class Invoice extends Model
{
// TenantScoped自動以登入者 company_id 過濾(含 route-model binding 與列表查詢),
// 防止跨公司讀取/操作他人發票。console佇列 Job / reconcile 命令)會自動跳過 scope
// 系統身分仍可跨公司開立/對帳。
use HasFactory, SoftDeletes, TenantScoped;
use HasFactory, SoftDeletes, TenantScoped, HasDisplayFlowId;
protected $fillable = [
'company_id',

View File

@ -6,12 +6,13 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Traits\TenantScoped;
use App\Traits\HasDisplayFlowId;
use App\Models\Machine\Machine;
use App\Models\Member\Member;
class Order extends Model
{
use HasFactory, SoftDeletes, TenantScoped;
use HasFactory, SoftDeletes, TenantScoped, HasDisplayFlowId;
// 支付狀態
public const PAYMENT_STATUS_FAILED = 0;

View File

@ -0,0 +1,28 @@
<?php
namespace App\Traits;
/**
* 提供「給人看的 flow_id」accessor去掉機台序號前綴顯示乾淨易讀的機台流水號。
*
* 資料庫仍儲存完整的 `serial_no . flow_id`(全域唯一值),供後台去重 / 冪等 / 對帳判斷使用;
* accessor 只用於畫面與匯出顯示,不改動任何 DB 值。
*
* flow_id 格式serial_no . YYYYMMDDHHMMSSXXXX無分隔符
* 套用此 trait Model 需具備 `flow_id` 欄位與 `machine`(含 serial_no關聯。
*/
trait HasDisplayFlowId
{
public function getDisplayFlowIdAttribute(): string
{
$flowId = (string) ($this->flow_id ?? '');
$serial = $this->machine?->serial_no;
// 僅在確實以該機台序號開頭時才剝除前綴;舊的未前綴資料原樣顯示(防禦)。
if ($serial !== null && $serial !== '' && str_starts_with($flowId, $serial)) {
return substr($flowId, strlen($serial));
}
return $flowId;
}
}

View File

@ -102,7 +102,8 @@
</div>
<div class="space-y-1">
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Machine Flow ID') }}</p>
<p class="text-xs font-black text-slate-400 dark:text-slate-500 font-mono uppercase tracking-widest">{{ $order->flow_id }}</p>
{{-- 顯示去前綴序號的乾淨流水號DB 仍存完整 serial+flow_id 唯一值供後台判斷) --}}
<p class="text-xs font-black text-slate-400 dark:text-slate-500 font-mono uppercase tracking-widest">{{ $order->display_flow_id }}</p>
</div>
</div>
</section>

View File

@ -101,6 +101,20 @@ class FinalizeIdempotencyTest extends TestCase
$this->assertEquals('2222222222' . $raw, $orderB->flow_id);
}
/** display_flow_id 顯示用 accessor:剝除機台序號前綴,DB 仍存完整 serial+raw 唯一值。 */
public function test_display_flow_id_strips_serial_prefix(): void
{
$serial = '2501000062';
$raw = '202606190839470011';
$this->makeMachine($serial);
$order = $this->service->finalizeTransaction($this->completedPayload($serial, $raw));
// DB 值維持完整 28 碼;顯示值剝掉序號前綴 → 乾淨 18 碼
$this->assertEquals($serial . $raw, $order->flow_id);
$this->assertEquals($raw, $order->fresh()->display_flow_id);
}
/** RelateNumber = serial + rawFlowId(無分隔符、≤30、與線上推導逐字等價,不可雙重 serial)。 */
public function test_relate_number_equals_serial_plus_raw_flow_id(): void
{