Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.19% covered (success)
92.19%
59 / 64
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
PickingOrderFinishService
92.19% covered (success)
92.19%
59 / 64
0.00% covered (danger)
0.00%
0 / 4
16.12
0.00% covered (danger)
0.00%
0 / 1
 finish
81.82% covered (warning)
81.82%
9 / 11
0.00% covered (danger)
0.00%
0 / 1
3.05
 markSeparatedExpeditionProducts
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
5.01
 processLinkedRequests
95.83% covered (success)
95.83%
23 / 24
0.00% covered (danger)
0.00%
0 / 1
3
 processStatusPickingOrder
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
5.01
1<?php
2
3namespace Domain\PickingOrder\Services;
4
5use App\Models\PickingOrder\PickingOrderProduct;
6use App\Models\Request\RequestItem;
7use App\Models\StockIO\StockIOProduct;
8use Carbon\Carbon;
9
10use App\Models\PickingOrder\PickingOrder;
11use App\Models\Request\Request;
12
13use Domain\PickingOrder\Enums\PickingOrderStatusEnum;
14use Domain\Request\Enums\RequestItemStatusEnum;
15use Domain\Request\Enums\RequestStatusEnum;
16use Domain\PickingOrder\Exception\PickingOrderCanceledException;
17use Domain\Shared\Enums\SectorEnum;
18use Domain\StockIO\Enums\StockProductStatusEnum;
19use Domain\StockIO\Services\StockOutputFinishService;
20
21readonly class PickingOrderFinishService
22{
23    public function finish(PickingOrder $pickingOrder): void
24    {
25        if ($pickingOrder->isCanceled()) {
26            throw new PickingOrderCanceledException(__('Picking order is canceled.'));
27        }
28
29        if ($pickingOrder->isPaused()) {
30            (new PickingOrderStartService())->resumeCurrentPause($pickingOrder);
31        }
32
33        $pickingOrder->load(['stockIOProducts', 'stockIO.stockIOProducts']);
34
35        $this->markSeparatedExpeditionProducts($pickingOrder);
36        $this->processLinkedRequests($pickingOrder);
37
38        $this->processStatusPickingOrder($pickingOrder);
39        $pickingOrder->setAttribute('end_date', Carbon::now());
40        $pickingOrder->save();
41
42        (new StockOutputFinishService)->processOutputFinish($pickingOrder);
43    }
44
45    private function markSeparatedExpeditionProducts(PickingOrder $pickingOrder): void
46    {
47        if ($pickingOrder->stockIO?->sector !== SectorEnum::EXPEDITION) {
48            return;
49        }
50
51        $pickingOrder->loadMissing('pickingOrderProduct.stockIOProduct');
52
53        $pickingOrder->pickingOrderProduct->each(function (PickingOrderProduct $pickingOrderProduct) {
54            if ($pickingOrderProduct->status !== PickingOrderStatusEnum::SEPARATED) {
55                return;
56            }
57
58            $stockIOProduct = $pickingOrderProduct->stockIOProduct;
59
60            if (!$stockIOProduct || $stockIOProduct->status === StockProductStatusEnum::CANCELED) {
61                return;
62            }
63
64            $stockIOProduct->setAttribute('status', StockProductStatusEnum::SEPARATED);
65            $stockIOProduct->save();
66        });
67    }
68
69    private function processLinkedRequests(PickingOrder $pickingOrder): void
70    {
71        $pickingOrder->pickingOrderProduct
72            ->pluck('stockIOProduct.requestItem.request')
73            ->filter()
74            ->unique('id')
75            ->each(function (Request $request) {
76                $statusCounts = $request->requestItems()
77                    ->selectRaw('status, COUNT(*) as total')
78                    ->groupBy('status')
79                    ->pluck('total', 'status');
80
81                $get = fn ($status) => $statusCounts[$status] ?? 0;
82
83                $countAll = $statusCounts
84                    ->reject(fn ($_, $status) => $status == RequestItemStatusEnum::CANCELED->value)
85                    ->sum();
86
87                if ($countAll === 0) {
88                    return;
89                }
90
91                $separated = $get(RequestItemStatusEnum::SEPARATED->value);
92
93                $request->setAttribute(
94                    'status',
95                    $separated === $countAll
96                        ? RequestStatusEnum::SEPARATED
97                        : RequestStatusEnum::SEPARATED_PARTIAL
98                );
99                $request->save();
100            });
101    }
102
103    private function processStatusPickingOrder(PickingOrder $pickingOrder): void
104    {
105        $statusCounts = $pickingOrder->pickingOrderProduct()
106            ->selectRaw('status, COUNT(*) as total')
107            ->groupBy('status')
108            ->pluck('total', 'status');
109
110        $get = fn($status) => $statusCounts[$status] ?? 0;
111
112        $countAll = $statusCounts
113            ->reject(fn($_, $status) => $status == PickingOrderStatusEnum::CANCELED->value)
114            ->sum();
115
116        $separated = $get(PickingOrderStatusEnum::SEPARATED->value);
117        $separatedPartial = $get(PickingOrderStatusEnum::SEPARATED_PARTIAL->value);
118
119        if ($countAll === 0) {
120            return;
121        }
122
123        if ($separated === $countAll) {
124            $pickingOrder->setAttribute('status', PickingOrderStatusEnum::SEPARATED->value);
125
126            return;
127        }
128
129        if ($separatedPartial > 0 || $separated > 0) {
130            $pickingOrder->setAttribute('status', PickingOrderStatusEnum::SEPARATED_PARTIAL->value);
131        }
132    }
133}