'boolean', 'meta' => 'object', ]; public function __construct(array $attributes = []) { parent::__construct($attributes); $this->setTable(config('shop.tables.order_notes', 'order_notes')); } protected static function booted() { static::creating(function (OrderNote $note) { // Set default type if (empty($note->type)) { $note->type = self::TYPE_NOTE; } // Default to internal note if (!isset($note->is_customer_note)) { $note->is_customer_note = false; } }); } // ========================================================================= // RELATIONSHIPS // ========================================================================= /** * The order this note belongs to. */ public function order(): BelongsTo { return $this->belongsTo(config('shop.models.order', Order::class), 'order_id'); } /** * The author of this note (user, admin, system). */ public function author(): MorphTo { return $this->morphTo(); } // ========================================================================= // COMPUTED ATTRIBUTES // ========================================================================= /** * Get human-readable type label. */ public function getTypeLabelAttribute(): string { return match ($this->type) { self::TYPE_NOTE => 'Note', self::TYPE_STATUS_CHANGE => 'Status Change', self::TYPE_PAYMENT => 'Payment', self::TYPE_REFUND => 'Refund', self::TYPE_SHIPPING => 'Shipping', self::TYPE_CUSTOMER => 'Customer Message', self::TYPE_SYSTEM => 'System', self::TYPE_EMAIL => 'Email', self::TYPE_WEBHOOK => 'Webhook', default => ucfirst($this->type), }; } /** * Get icon for the note type (for UI purposes). */ public function getTypeIconAttribute(): string { return match ($this->type) { self::TYPE_NOTE => 'pencil', self::TYPE_STATUS_CHANGE => 'arrow-path', self::TYPE_PAYMENT => 'credit-card', self::TYPE_REFUND => 'arrow-uturn-left', self::TYPE_SHIPPING => 'truck', self::TYPE_CUSTOMER => 'user', self::TYPE_SYSTEM => 'cog', self::TYPE_EMAIL => 'envelope', self::TYPE_WEBHOOK => 'bolt', default => 'information-circle', }; } /** * Get color for the note type (for UI purposes). */ public function getTypeColorAttribute(): string { return match ($this->type) { self::TYPE_NOTE => 'gray', self::TYPE_STATUS_CHANGE => 'blue', self::TYPE_PAYMENT => 'green', self::TYPE_REFUND => 'red', self::TYPE_SHIPPING => 'purple', self::TYPE_CUSTOMER => 'yellow', self::TYPE_SYSTEM => 'indigo', self::TYPE_EMAIL => 'teal', self::TYPE_WEBHOOK => 'orange', default => 'gray', }; } // ========================================================================= // SCOPES // ========================================================================= /** * Scope to get customer-visible notes. */ public function scopeForCustomer($query) { return $query->where('is_customer_note', true); } /** * Scope to get internal notes only. */ public function scopeInternal($query) { return $query->where('is_customer_note', false); } /** * Scope to filter by type. */ public function scopeOfType($query, string $type) { return $query->where('type', $type); } /** * Scope to filter by multiple types. */ public function scopeOfTypes($query, array $types) { return $query->whereIn('type', $types); } /** * Scope to get system notes. */ public function scopeSystem($query) { return $query->where('type', self::TYPE_SYSTEM); } /** * Scope to get payment-related notes. */ public function scopePaymentRelated($query) { return $query->whereIn('type', [self::TYPE_PAYMENT, self::TYPE_REFUND]); } // ========================================================================= // META HELPERS // ========================================================================= /** * Get a value from the meta object. */ public function getMeta(?string $key = null, $default = null) { if ($key === null) { return $this->meta ?? new \stdClass(); } return $this->meta?->{$key} ?? $default; } /** * Update a key in the meta object. */ public function updateMetaKey(string $key, $value): self { $meta = (array) ($this->meta ?? new \stdClass()); $meta[$key] = $value; $this->meta = (object) $meta; $this->save(); return $this; } // ========================================================================= // FACTORY METHODS // ========================================================================= /** * Create a system note. */ public static function createSystemNote(Order $order, string $content, ?array $meta = null): self { return $order->notes()->create([ 'content' => $content, 'type' => self::TYPE_SYSTEM, 'is_customer_note' => false, 'meta' => $meta ? (object) $meta : null, ]); } /** * Create a customer note (visible to customer). */ public static function createCustomerNote(Order $order, string $content, $author = null): self { return $order->notes()->create([ 'content' => $content, 'type' => self::TYPE_CUSTOMER, 'is_customer_note' => true, 'author_type' => $author ? get_class($author) : null, 'author_id' => $author?->getKey(), ]); } /** * Create a payment note. */ public static function createPaymentNote( Order $order, string $content, ?string $reference = null, ?int $amount = null ): self { $meta = []; if ($reference) { $meta['payment_reference'] = $reference; } if ($amount !== null) { $meta['amount'] = $amount; } return $order->notes()->create([ 'content' => $content, 'type' => self::TYPE_PAYMENT, 'is_customer_note' => false, 'meta' => !empty($meta) ? (object) $meta : null, ]); } /** * Create a shipping note. */ public static function createShippingNote( Order $order, string $content, ?string $trackingNumber = null, ?string $carrier = null ): self { $meta = []; if ($trackingNumber) { $meta['tracking_number'] = $trackingNumber; } if ($carrier) { $meta['carrier'] = $carrier; } return $order->notes()->create([ 'content' => $content, 'type' => self::TYPE_SHIPPING, 'is_customer_note' => true, // Shipping info should be visible to customer 'meta' => !empty($meta) ? (object) $meta : null, ]); } }