star-cloud/app/Models/Transaction/Order.php
sky121113 b7e8401a99 [FIX] 銷售紀錄未付款單顯示:已棄單改未支付、未出貨顯示 --
1. 付款狀態「已棄單(abandoned)」顯示文字改為「未支付」;新增 Unpaid 語系、移除孤兒 Abandoned key,三語系(zh_TW/en/ja)對齊排序。
2. 出貨狀態:非付款成功(未完成 pending/支付失敗 failed/未支付 abandoned)的單從未出貨,一律顯示「--」,避免 delivery_status 欄位 DB 預設值 1 誤顯示為「出貨成功」。
3. 判斷邏輯收斂至 Order::hasDeliveryOutcome(),桌機表格、手機卡片、明細面板與匯出 delivery_status_label accessor 共用同一基準。
4. 補齊匯出訂單狀態對照表原本缺漏的 failed/abandoned 標籤。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 17:14:29 +08:00

254 lines
7.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Models\Transaction;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Traits\TenantScoped;
use App\Models\Machine\Machine;
use App\Models\Member\Member;
class Order extends Model
{
use HasFactory, SoftDeletes, TenantScoped;
// 支付狀態
public const PAYMENT_STATUS_FAILED = 0;
public const PAYMENT_STATUS_SUCCESS = 1;
// 出貨狀態
public const DELIVERY_STATUS_FAILED = 0;
public const DELIVERY_STATUS_SUCCESS = 1;
public const DELIVERY_STATUS_PARTIAL = 2;
// 交易生命週期狀態status 欄位,字串)。
// pending進入付款、等待結果completed付款成功出貨另看 delivery_status
// failed付款失敗/逾時abandoned感應前取消由排程把逾時 pending 標記)。
// ⚠ 終態 = [completed, failed, abandoned],一律冪等不可覆蓋(保護線上 main 機台行為不變)。
public const STATUS_PENDING = 'pending';
public const STATUS_COMPLETED = 'completed';
public const STATUS_FAILED = 'failed';
public const STATUS_ABANDONED = 'abandoned';
/** 終態清單:到了這些狀態就不再變更(冪等)。 */
public const TERMINAL_STATUSES = [
self::STATUS_COMPLETED,
self::STATUS_FAILED,
self::STATUS_ABANDONED,
];
protected $fillable = [
'company_id',
'flow_id',
'order_no',
'machine_id',
'member_id',
'payment_type',
'total_amount',
'discount_amount',
'pay_amount',
'change_amount',
'points_used',
'original_amount',
'payment_status',
'payment_request',
'payment_response',
'payment_at',
'member_barcode',
'code_id',
'welcome_gift_id',
'invoice_info',
'machine_time',
'status',
'metadata',
'delivery_status',
'remark',
];
protected $casts = [
'total_amount' => 'decimal:2',
'discount_amount' => 'decimal:2',
'pay_amount' => 'decimal:2',
'change_amount' => 'decimal:2',
'original_amount' => 'decimal:2',
'payment_at' => 'datetime',
'machine_time' => 'datetime',
'metadata' => 'array',
'payment_type' => 'integer',
'delivery_status' => 'integer',
];
/**
* 取得所有支付類型的對照標籤
*/
public static function getPaymentTypeLabels(): array
{
return [
1 => __('Credit Card'),
2 => __('E-Ticket (EasyCard/iPass)'),
3 => __('QR Code Payment - E.SUN'), // 掃碼支付-玉山
4 => __('Bill Acceptor'),
5 => __('Pass Code'), // 使用者要求由「通關密碼」改為「通行碼」
6 => __('Pickup Code'),
7 => __('Welcome Gift'),
8 => __('Questionnaire'),
9 => __('Coin Acceptor'),
10 => __('Mobile Pay'), // NFC 手機感應(行動支付,信用卡軌道,依使用者按鈕分類)
11 => __('QR Code Payment - TayPay'), // 掃碼支付-TayPay
21 => __('Offline + 1'),
22 => __('Offline + 2'),
23 => __('Offline + 3'),
24 => __('Offline + 4'),
25 => __('Offline + 9'),
30 => __('LINE Pay'),
31 => __('JKO Pay'),
32 => __('Easy Wallet'),
33 => __('Pi Pay'),
34 => __('PlusPay'),
40 => __('Member Verify Pickup'),
41 => __('Staff Card'),
42 => __('Pickup Voucher'),
50 => __('Offline + LINE Pay'),
51 => __('Offline + JKO Pay'),
52 => __('Offline + Easy Wallet'),
53 => __('Offline + Pi Pay'),
54 => __('Offline + PlusPay'),
60 => __('Points/Voucher'),
61 => __('Member + 1'),
62 => __('Member + 2'),
63 => __('Member + 3'),
64 => __('Member + 4'),
65 => __('Member + 5'),
66 => __('Member + 6'),
67 => __('Member + 7'),
68 => __('Member + 8'),
69 => __('Member + 9'),
70 => __('LINE Pay (Official)'), // LINE Pay 官方直連(非 TapPay 底下的 30
90 => __('Member + LINE Pay'),
91 => __('Member + JKO Pay'),
92 => __('Member + Easy Wallet'),
93 => __('Member + Pi Pay'),
94 => __('Member + PlusPay'),
];
}
/**
* 取得目前的支付類型標籤 (Accessor)
*/
public function getPaymentTypeLabelAttribute(): string
{
return self::getPaymentTypeLabels()[$this->payment_type] ?? __('Unknown');
}
public function getMaskedPickupRecipientAttribute(): ?string
{
if (empty($this->member_barcode)) {
return null;
}
return self::maskPickupRecipientName($this->member_barcode);
}
public static function maskPickupRecipientName(string $value): string
{
$value = trim($value);
if ($value === '') {
return $value;
}
if (preg_match('/^([^\s(]+)(.*)$/us', $value, $matches) !== 1) {
return self::maskName($value);
}
return self::maskName($matches[1]) . ($matches[2] ?? '');
}
private static function maskName(string $name): string
{
$length = mb_strlen($name);
if ($length <= 1) {
return $name;
}
if ($length === 2) {
return mb_substr($name, 0, 1) . 'O';
}
return mb_substr($name, 0, 1)
. str_repeat('O', $length - 2)
. mb_substr($name, -1);
}
public function machine()
{
return $this->belongsTo(Machine::class);
}
public function member()
{
return $this->belongsTo(Member::class);
}
public function items()
{
return $this->hasMany(OrderItem::class);
}
public function invoice()
{
return $this->hasOne(Invoice::class);
}
public function dispenseRecords()
{
return $this->hasMany(DispenseRecord::class);
}
public function staffCardLog()
{
return $this->hasOne(\App\Models\StaffCardLog::class);
}
public function welcomeGift()
{
return $this->belongsTo(WelcomeGift::class);
}
/**
* 取得所有出貨狀態的對照標籤
*/
public static function getDeliveryStatusLabels(): array
{
return [
self::DELIVERY_STATUS_FAILED => __('Dispense Failed'),
self::DELIVERY_STATUS_SUCCESS => __('Dispense Success'),
self::DELIVERY_STATUS_PARTIAL => __('Partial Dispense Success'),
];
}
/**
* 出貨狀態是否有意義:唯有付款成功(completed/paid)、機台真的動作過的單才有出貨結果。
* pending(未完成)、failed(支付失敗)、abandoned(未支付)從未出貨,出貨狀態應顯示 '--'。
* ⚠ delivery_status 欄位 DB 預設為 1(出貨成功),這些單若直接讀值會誤顯示成「出貨成功」。
*/
public function hasDeliveryOutcome(): bool
{
return in_array($this->status, [self::STATUS_COMPLETED, 'paid'], true);
}
/**
* 取得目前出貨狀態標籤 (Accessor)
*/
public function getDeliveryStatusLabelAttribute(): string
{
if (! $this->hasDeliveryOutcome()) {
return '--';
}
return self::getDeliveryStatusLabels()[$this->delivery_status] ?? __('Unknown');
}
}