the browser-facing portion of osu!
at master 4.6 kB view raw
1<?php 2 3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 4// See the LICENCE file in the repository root for full licence text. 5 6namespace App\Models\Store; 7 8use App\Exceptions\InvariantException; 9use App\Exceptions\ValidationException; 10use App\Libraries\ChangeUsername; 11use App\Models\SupporterTag; 12use App\Traits\Validatable; 13use Illuminate\Database\Eloquent\SoftDeletes; 14 15/** 16 * @property float|null $cost 17 * @property \Carbon\Carbon $created_at 18 * @property \Carbon\Carbon|null $deleted_at 19 * @property ExtraDataBase|null $extra_data 20 * @property string|null $extra_info 21 * @property int $id 22 * @property Order $order 23 * @property int $order_id 24 * @property Product $product 25 * @property int $product_id 26 * @property int $quantity 27 * @property bool $reserved 28 * @property \Carbon\Carbon|null $updated_at 29 */ 30class OrderItem extends Model 31{ 32 use SoftDeletes, Validatable; 33 34 protected $primaryKey = 'id'; 35 36 protected $casts = [ 37 'cost' => 'float', 38 'extra_data' => ExtraDataBase::class, 39 'reserved' => 'boolean', 40 ]; 41 42 public function scopeHasShipping($query) 43 { 44 return $query->whereHas('product', function ($q) { 45 return $q->hasShipping(); 46 }); 47 } 48 49 public function isValid() 50 { 51 $this->validationErrors()->reset(); 52 53 if ($this->quantity < 0) { 54 $this->validationErrors()->add('quantity', 'not_negative'); 55 } 56 57 if ($this->cost < 0) { 58 $this->validationErrors()->add('cost', 'not_negative'); 59 } 60 61 return $this->validationErrors()->isEmpty(); 62 } 63 64 public function delete() 65 { 66 if (!$this->order->isCart()) { 67 throw new InvariantException("Delete not allowed on Order ({$this->order->getKey()})."); 68 } 69 70 parent::delete(); 71 } 72 73 public function save(array $options = []) 74 { 75 $skipValidations = $options['skipValidations'] ?? false; 76 if (!$skipValidations && !$this->isValid()) { 77 // FIXME: Simpler to just throw instead of fixing all the save() calls right now. 78 throw new ValidationException($this->validationErrors()); 79 } 80 81 return parent::save($options); 82 } 83 84 public function subtotal() 85 { 86 return $this->cost * $this->quantity; 87 } 88 89 public function order() 90 { 91 return $this->belongsTo(Order::class, 'order_id'); 92 } 93 94 public function product() 95 { 96 return $this->belongsTo(Product::class, 'product_id'); 97 } 98 99 public function scopeCustomClass($query, $name) 100 { 101 return $query->whereHas('product', function ($q) use ($name) { 102 $q->customClass($name); 103 }); 104 } 105 106 public function refreshCost() 107 { 108 if ($this->product->cost === null) { 109 return; 110 } 111 $this->cost = $this->product->cost; 112 } 113 114 public function getCustomClassInstance() 115 { 116 // only one for now 117 if ($this->product->custom_class === 'username-change') { 118 return new ChangeUsername($this->order->user, $this->extra_info ?? ''); 119 } 120 } 121 122 public function getDisplayName(bool $html = false) 123 { 124 switch ($this->product->custom_class) { 125 case Product::SUPPORTER_TAG_NAME: 126 return SupporterTag::getDisplayName($this, $html); 127 default: 128 return $this->product->name.($this->extra_info !== null ? " ({$this->extra_info})" : ''); 129 } 130 } 131 132 public function getSubtext() 133 { 134 $extraData = $this->extra_data; 135 if ($extraData instanceof ExtraDataSupporterTag && $extraData->message !== null) { 136 return trans('store.order.item.subtext.supporter_tag', ['message' => $extraData->message]); 137 } 138 139 return null; 140 } 141 142 public function releaseProduct() 143 { 144 if ($this->reserved) { 145 $this->product->release($this->quantity); 146 $this->reserved = false; 147 $this->saveOrExplode(); 148 } 149 } 150 151 public function reserveProduct() 152 { 153 if (!$this->reserved) { 154 $this->product->reserve($this->quantity); 155 $this->reserved = true; 156 $this->saveOrExplode(); 157 } 158 } 159 160 public function validationErrorsTranslationPrefix(): string 161 { 162 return 'store.order_item'; 163 } 164 165 public function __get($key) 166 { 167 // TODO: remove this after no more things are queued with old $casts 168 $this->casts['extra_data'] = ExtraDataBase::class; 169 170 return parent::__get($key); 171 } 172}