Order.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. <?php
  2. namespace App\Models;
  3. use App\Helpers\Price;
  4. use App\Jobs\GenerateInstallationPack;
  5. use App\Models\Dictionary\Area;
  6. use App\Models\Dictionary\District;
  7. use App\Models\Scopes\YearScope;
  8. use Illuminate\Database\Eloquent\Attributes\ScopedBy;
  9. use Illuminate\Database\Eloquent\Casts\Attribute;
  10. use Illuminate\Database\Eloquent\Factories\HasFactory;
  11. use Illuminate\Database\Eloquent\Model;
  12. use Illuminate\Database\Eloquent\Relations\BelongsTo;
  13. use Illuminate\Database\Eloquent\Relations\BelongsToMany;
  14. use Illuminate\Database\Eloquent\Relations\HasMany;
  15. use Illuminate\Database\Eloquent\Relations\HasManyThrough;
  16. use Illuminate\Database\Eloquent\SoftDeletes;
  17. use Illuminate\Support\Facades\DB;
  18. #[ScopedBy([YearScope::class])]
  19. class Order extends Model
  20. {
  21. use HasFactory, SoftDeletes;
  22. const DEFAULT_SORT_BY = 'created_at';
  23. const STATUS_NEW = 1;
  24. const STATUS_NOT_READY = 2;
  25. const STATUS_READY_NO_MAF = 3;
  26. const STATUS_READY_TO_MOUNT = 4;
  27. const STATUS_IN_MOUNT = 5;
  28. const STATUS_DUTY = 6;
  29. const STATUS_READY_TO_HAND_OVER = 7;
  30. const STATUS_NOT_HANDED_OVER_WITH_NOTES = 8;
  31. const STATUS_HANDED_OVER_WITH_NOTES = 9;
  32. const STATUS_HANDED_OVER = 10;
  33. const STATUS_NO_MAF = 11;
  34. const STATUS_PROBLEM = 12;
  35. const STATUS_PAID = 13;
  36. //ПЛ не готова; ПЛ готова нет МАФ; Готов к монтажу; В монтаже; Долг;Готов к сдаче;Не сдан, замечания;Сдан с замечаниями;Сдан;Проблема
  37. const STATUS_NAMES = [
  38. self::STATUS_NEW => 'Новая',
  39. self::STATUS_NOT_READY => 'Не готова',
  40. self::STATUS_READY_NO_MAF => 'Готова, нет МАФ',
  41. self::STATUS_READY_TO_MOUNT => 'Готова к монтажу',
  42. self::STATUS_IN_MOUNT => 'В монтаже',
  43. self::STATUS_DUTY => 'Долг',
  44. self::STATUS_READY_TO_HAND_OVER => 'Готова к сдаче',
  45. self::STATUS_NOT_HANDED_OVER_WITH_NOTES => 'Не сдана, замечания',
  46. self::STATUS_HANDED_OVER_WITH_NOTES => 'Сдана с замечаниями',
  47. self::STATUS_HANDED_OVER => 'Сдана',
  48. self::STATUS_NO_MAF => 'Отсутствуют МАФ',
  49. self::STATUS_PROBLEM => 'Проблема',
  50. self::STATUS_PAID => 'Оплачено',
  51. ];
  52. const STATUS_COLOR = [
  53. self::STATUS_NEW => 'dark',
  54. self::STATUS_NOT_READY => 'dark',
  55. self::STATUS_READY_NO_MAF => 'light',
  56. self::STATUS_READY_TO_MOUNT => 'primary',
  57. self::STATUS_IN_MOUNT => 'info',
  58. self::STATUS_DUTY => 'warning',
  59. self::STATUS_READY_TO_HAND_OVER => 'primary',
  60. self::STATUS_NOT_HANDED_OVER_WITH_NOTES => 'warning',
  61. self::STATUS_HANDED_OVER_WITH_NOTES => 'info',
  62. self::STATUS_HANDED_OVER => 'success',
  63. self::STATUS_NO_MAF => 'secondary',
  64. self::STATUS_PROBLEM => 'danger',
  65. self::STATUS_PAID => 'success',
  66. ];
  67. public const BRIGADIER_HIDDEN_STATUS_IDS = [
  68. self::STATUS_HANDED_OVER,
  69. ];
  70. // set year attribute to current selected year
  71. protected static function boot(): void
  72. {
  73. parent::boot();
  74. static::creating(function($attributes) {
  75. if(!isset($attributes->year)) {
  76. $attributes->year = year();
  77. }
  78. });
  79. }
  80. protected $fillable = [
  81. 'year',
  82. 'name',
  83. 'user_id',
  84. 'district_id',
  85. 'area_id',
  86. 'object_address',
  87. 'object_type_id',
  88. 'comment',
  89. 'installation_date',
  90. 'ready_date',
  91. 'brigadier_id',
  92. 'order_status_id',
  93. 'tg_group_name',
  94. 'tg_group_link',
  95. 'ready_to_mount',
  96. 'install_days',
  97. ];
  98. public $appends = ['common_name', 'move_maf_name', 'products_with_count'];
  99. public function products_sku(): HasMany
  100. {
  101. return $this->hasMany(ProductSKU::class, 'order_id', 'id');
  102. }
  103. public function products(): HasManyThrough
  104. {
  105. return $this->hasManyThrough(
  106. Product::class,
  107. ProductSKU::class,
  108. 'order_id',
  109. 'id',
  110. 'id',
  111. 'product_id');
  112. }
  113. public function user(): BelongsTo
  114. {
  115. return $this->belongsTo(User::class, 'user_id', 'id');
  116. }
  117. public function district(): BelongsTo
  118. {
  119. return $this->belongsTo(District::class);
  120. }
  121. public function area(): BelongsTo
  122. {
  123. return $this->belongsTo(Area::class);
  124. }
  125. public function objectType(): BelongsTo
  126. {
  127. return $this->belongsTo(ObjectType::class);
  128. }
  129. public function brigadier(): BelongsTo
  130. {
  131. return $this->belongsTo(User::class, 'brigadier_id', 'id');
  132. }
  133. public function orderStatus(): BelongsTo
  134. {
  135. return $this->belongsTo(OrderStatus::class);
  136. }
  137. public function photos(): BelongsToMany
  138. {
  139. return $this->belongsToMany(File::class, 'order_photo');
  140. }
  141. public function documents(): BelongsToMany
  142. {
  143. return $this->belongsToMany(File::class, 'order_document');
  144. }
  145. public function statements(): BelongsToMany
  146. {
  147. return $this->belongsToMany(File::class, 'order_statement');
  148. }
  149. public function reclamations(): HasMany
  150. {
  151. return $this->hasMany(Reclamation::class);
  152. }
  153. public function chatMessages(): HasMany
  154. {
  155. return $this->hasMany(ChatMessage::class)->orderBy('created_at');
  156. }
  157. public static function visibleStatusIdsForBrigadier(): array
  158. {
  159. return array_values(array_diff(
  160. array_keys(self::STATUS_NAMES),
  161. self::BRIGADIER_HIDDEN_STATUS_IDS,
  162. ));
  163. }
  164. public function getNeeds(): array
  165. {
  166. $needs = [];
  167. foreach ($this->products_sku as $sku) {
  168. if($sku->maf_order) continue;
  169. $needs[$sku->product_id]['needs'] = (isset($needs[$sku->product_id])) ? $needs[$sku->product_id]['needs'] + 1 : 1;
  170. }
  171. foreach ($needs as $productId => $quantity) {
  172. $needs[$productId]['sku'] = MafOrder::query()
  173. ->where('maf_orders.product_id', $productId)
  174. ->sum('maf_orders.in_stock');
  175. }
  176. return $needs;
  177. }
  178. public function recalculateReadyToMount(): void
  179. {
  180. $result = true;
  181. foreach ($this->getNeeds() as $need) {
  182. if($need['sku'] < $need['needs']) {
  183. $result = false;
  184. break;
  185. }
  186. }
  187. $this->update(['ready_to_mount' => ($result) ? 'Да' : 'Нет']);
  188. }
  189. public function autoChangeStatus(): void
  190. {
  191. if(($this->products_sku->count() < 1)
  192. && ($this->order_status_id !== self::STATUS_NEW)
  193. ) {
  194. $this->update(['order_status_id' => self::STATUS_NO_MAF]);
  195. return;
  196. }
  197. if(($this->order_status_id === self::STATUS_READY_TO_MOUNT)
  198. && ($this->brigadier_id !== null)
  199. && ($this->installation_date !== null)
  200. ) {
  201. if ($this->canBeMovedToMountStatus()) {
  202. $this->update(['order_status_id' => self::STATUS_IN_MOUNT]);
  203. }
  204. }
  205. }
  206. public function commonName(): Attribute
  207. {
  208. return Attribute::make(
  209. get: fn($value) => (string) $this->object_address . ', ' . $this->area->name . ', ' . $this->district->shortname,
  210. );
  211. }
  212. public function moveMafName(): Attribute
  213. {
  214. return Attribute::make(
  215. get: fn ($value) => trim($this->common_name . ($this->name ? ' | ' . $this->name : '')),
  216. );
  217. }
  218. public function productsWithCount(): Attribute
  219. {
  220. $products = $this->products_sku;
  221. $ret = [];
  222. foreach ($products as $product) {
  223. if(isset($ret[$product->product->id])) {
  224. $ret[$product->product->id]['count'] += 1;
  225. } else {
  226. $ret[$product->product->id] = [
  227. 'name' => $product->product->article,
  228. 'count' => 1,
  229. ];
  230. }
  231. if($product->maf_order?->order_number) {
  232. $ret[$product->product->id]['order_numbers'][] = $product->maf_order?->order_number;
  233. }
  234. }
  235. $s = '';
  236. $openTag = '<div>';
  237. $closeTag = '</div>';
  238. foreach ($ret as $product) {
  239. $order_numbers = (isset($product['order_numbers'])) ? ' (' . implode(', ', array_unique($product['order_numbers'])) . ')' : '';
  240. $s .= $openTag . $product['name'] . ' - ' . $product['count'] . $order_numbers . $closeTag;
  241. }
  242. return Attribute::make(
  243. get: fn($value) => (string) $s,
  244. );
  245. }
  246. public function isAllMafConnected(): array
  247. {
  248. $errors = [];
  249. foreach($this->products_sku as $sku) {
  250. $orderNumber = trim((string) ($sku->maf_order?->order_number ?? ''));
  251. if($sku->maf_order_id && $orderNumber !== '') continue;
  252. $errors[] = 'МАФ ' . $sku->product->article . 'не имеет номера заказа МАФ!';
  253. }
  254. return $errors;
  255. }
  256. public function getMountStatusErrors(): array
  257. {
  258. foreach ($this->products_sku as $sku) {
  259. $orderNumber = trim((string) ($sku->maf_order?->order_number ?? ''));
  260. if ($sku->maf_order_id !== null && $orderNumber !== '') {
  261. continue;
  262. }
  263. return ['МАФ не привязан к заказу'];
  264. }
  265. return [];
  266. }
  267. public function canBeMovedToMountStatus(): bool
  268. {
  269. return $this->getMountStatusErrors() === [];
  270. }
  271. public function canCreateHandover(): array
  272. {
  273. $errors = [];
  274. if($this->photos->count() === 0) $errors[] = 'Не загружены фотографии!';
  275. foreach($this->products_sku as $sku) {
  276. if($sku->maf_order_id === null) $errors[] = 'МАФ ' . $sku->product->article . ' не имеет номера заказа!';
  277. if($sku->passport_id === null) $errors[] = 'МАФ ' . $sku->product->article . ' не загружен паспорт!';
  278. if($sku->rfid === null) $errors[] = 'МАФ ' . $sku->product->article . ' не имеет номера заказа!';
  279. if($sku->manufacture_date === null) $errors[] = 'МАФ ' . $sku->product->article . ' нет даты производства!';
  280. if($sku->factory_number === null) $errors[] = 'МАФ ' . $sku->product->article . ' нет заводского номера!';
  281. if($sku->product->passport_name === null) $errors[] = 'МАФ ' . $sku->product->article . ' нет паспорта!';
  282. if($sku->product->statement_name === null) $errors[] = 'МАФ ' . $sku->product->article . ' нет ведомости!';
  283. if($sku->product->certificate_id === null) $errors[] = 'МАФ ' . $sku->product->article . ' нет сертификата!';
  284. if($sku->product->service_life === null) $errors[] = 'МАФ ' . $sku->product->article . ' нет срока эксплуатации!';
  285. if($sku->product->certificate_number === null) $errors[] = 'МАФ ' . $sku->product->article . ' нет номера сертификата!';
  286. if($sku->product->certificate_date === null) $errors[] = 'МАФ ' . $sku->product->article . ' нет даты сертификата!';
  287. if($sku->product->certificate_issuer === null) $errors[] = 'МАФ ' . $sku->product->article . ' нет орана выпустившего сертификат!';
  288. if($sku->product->certificate_type === null) $errors[] = 'МАФ ' . $sku->product->article . ' нет типа сертификата!';
  289. }
  290. return $errors;
  291. }
  292. }