Browse Source

added auto status PAID for order

Alexander Musikhin 11 hours ago
parent
commit
00a29bcb30

+ 5 - 2
app/Http/Controllers/OrderController.php

@@ -29,6 +29,7 @@ use App\Models\User;
 use App\Services\ContractorSpecificationService;
 use App\Services\FileService;
 use App\Services\NotificationService;
+use App\Services\OrderPaymentStatusService;
 use Carbon\Carbon;
 use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
@@ -83,7 +84,7 @@ class OrderController extends Controller
         $this->data['districts'] = District::query()->get()->pluck('name', 'id');
         $this->data['areas'] = Area::query()->get()->pluck('name', 'id');
         $this->data['objectTypes'] = ObjectType::query()->get()->pluck('name', 'id');
-        $this->data['orderStatuses'] =OrderStatus::query()->get()->pluck('name', 'id');
+        $this->data['orderStatuses'] = OrderStatus::query()->orderBy('id')->get()->pluck('name', 'id');
         $this->data['brigadiers'] = User::query()->where('role', Role::BRIGADIER)->get()->pluck('name', 'id');
         $this->data['users'] = User::query()->whereIn('role', [Role::MANAGER, Role::ADMIN])->get()->pluck('name', 'id');
     }
@@ -128,7 +129,7 @@ class OrderController extends Controller
             Order::where('id', $order->id)->first()?->recalculateReadyToMount();
         }
 
-        $this->data['statuses'] = OrderStatus::query()->get()->pluck('name', 'id');
+        $this->data['statuses'] = OrderStatus::query()->orderBy('id')->get()->pluck('name', 'id');
         $this->data['nav'] = $nav;
 
         return view('orders.index', $this->data);
@@ -809,6 +810,8 @@ class OrderController extends Controller
             return redirect()->back()->with(['danger' => 'Выбранные МАФ не найдены на этой площадке.']);
         }
 
+        app(OrderPaymentStatusService::class)->markPaidIfAllMafsHavePaymentData($orderModel);
+
         return redirect()->back()->with(['success' => "Ведомость добавлена к МАФ: {$updated}."]);
     }
 

+ 4 - 0
app/Http/Controllers/ProductSKUController.php

@@ -11,6 +11,7 @@ use App\Models\MafView;
 use App\Models\ProductSKU;
 use App\Models\Role;
 use App\Services\FileService;
+use App\Services\OrderPaymentStatusService;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Storage;
@@ -101,6 +102,8 @@ class ProductSKUController extends Controller
         );
 
         $product_sku->update($request->validated());
+        app(OrderPaymentStatusService::class)->markPaidIfAllMafsHavePaymentData($product_sku->order_id);
+
         return redirect($url);
     }
 
@@ -125,6 +128,7 @@ class ProductSKUController extends Controller
         }
 
         $product_sku->update([$field => $value]);
+        app(OrderPaymentStatusService::class)->markPaidIfAllMafsHavePaymentData($product_sku->order_id);
 
         return response()->json([
             'field' => $field,

+ 16 - 7
app/Http/Controllers/ReportController.php

@@ -32,9 +32,14 @@ class ReportController extends Controller
             Order::STATUS_NOT_HANDED_OVER_WITH_NOTES,
             Order::STATUS_HANDED_OVER_WITH_NOTES,
             Order::STATUS_HANDED_OVER,
+            Order::STATUS_PAID,
         ];
-        $doneStatuses = [9, 10];
-        $handedOverStatus = Order::STATUS_HANDED_OVER;
+        $doneStatuses = [
+            Order::STATUS_HANDED_OVER_WITH_NOTES,
+            Order::STATUS_HANDED_OVER,
+            Order::STATUS_PAID,
+        ];
+        $paidStatus = Order::STATUS_PAID;
         $objectTypes = ObjectType::query()->get()->pluck('name', 'id')->toArray();
         $this->data['objectTypes'] = $objectTypes;
         $user_ids = Order::query()->distinct()->get('user_id')->pluck('user_id')->toArray();
@@ -65,9 +70,9 @@ class ReportController extends Controller
             })
             ->count();
 
-        // сумма сданных МАФ по площадкам со статусом "Сдана"
+        // сумма сданных МАФ по площадкам со сданными и оплаченными статусами
         $this->data['totalHandedOverSum'] = Price::format(
-            $this->getDoneSumByStatus($handedOverStatus)
+            $this->getDoneSumByStatus($doneStatuses)
         );
 
         // done by managers
@@ -173,7 +178,7 @@ class ReportController extends Controller
 
 
         // svod
-        $orderStatuses = OrderStatus::query()->get()->pluck('name', 'id')->toArray();
+        $orderStatuses = OrderStatus::query()->orderBy('id')->get()->pluck('name', 'id')->toArray();
         foreach ($orderStatuses as $orderStatusId => $orderStatus) {
             $this->data['orderStatuses'][$orderStatus] = Order::query()->where('order_status_id', '=', $orderStatusId)->count();
             $this->data['orderMafStatuses'][$orderStatus]     = ProductSKU::query()->
@@ -190,7 +195,10 @@ class ReportController extends Controller
         );
         // общая сумма done
         $this->data['totalDoneSum'] = Price::format(
-            $this->getDoneSumByStatus($handedOverStatus)
+            $this->getDoneSumByStatus($doneStatuses)
+        );
+        $this->data['totalPaidSum'] = Price::format(
+            $this->getDoneSumByStatus($paidStatus)
         );
 
         $districts = District::query()->get(['id', 'shortname']);
@@ -234,7 +242,8 @@ class ReportController extends Controller
             $this->data['byDistrict'][$districtId] = [
                 'name'  => $district,
                 'totalSum' => $this->getDistrictSum($districtId),
-                'doneSum' => $this->getDistrictSum($districtId, [$handedOverStatus]),
+                'doneSum' => $this->getDistrictSum($districtId, $doneStatuses),
+                'paidSum' => $this->getDistrictSum($districtId, $paidStatus),
                 'totalOrders' => $totalOrders,
                 'totalMafs' => $totalMafs,
                 'readyOrders' => $readyOrders,

+ 3 - 0
app/Models/Order.php

@@ -36,6 +36,7 @@ class Order extends Model
     const STATUS_HANDED_OVER = 10;
     const STATUS_NO_MAF = 11;
     const STATUS_PROBLEM = 12;
+    const STATUS_PAID = 13;
 
     //ПЛ не готова; ПЛ готова нет МАФ; Готов к монтажу; В монтаже; Долг;Готов к сдаче;Не сдан, замечания;Сдан с замечаниями;Сдан;Проблема
 
@@ -52,6 +53,7 @@ class Order extends Model
         self::STATUS_HANDED_OVER => 'Сдана',
         self::STATUS_NO_MAF => 'Отсутствуют МАФ',
         self::STATUS_PROBLEM => 'Проблема',
+        self::STATUS_PAID => 'Оплачено',
     ];
 
     const STATUS_COLOR = [
@@ -67,6 +69,7 @@ class Order extends Model
         self::STATUS_HANDED_OVER => 'success',
         self::STATUS_NO_MAF => 'secondary',
         self::STATUS_PROBLEM => 'danger',
+        self::STATUS_PAID => 'success',
     ];
 
     public const BRIGADIER_HIDDEN_STATUS_IDS = [

+ 5 - 0
app/Services/ExportMafRegistryService.php

@@ -78,6 +78,11 @@ class ExportMafRegistryService
                 ->whereIn('id', $mafs->pluck('id'))
                 ->update(['upd_number' => $updNumber]);
 
+            $paymentStatusService = app(OrderPaymentStatusService::class);
+            foreach ($mafs->pluck('order_id')->filter()->unique() as $orderId) {
+                $paymentStatusService->markPaidIfAllMafsHavePaymentData((int) $orderId);
+            }
+
             File::query()->create([
                 'link' => url('/storage/' . $path),
                 'path' => $path,

+ 7 - 0
app/Services/Import/ImportYearDataService.php

@@ -448,6 +448,7 @@ class ImportYearDataService
         $headerMap = array_flip($headers);
 
         $count = 0;
+        $touchedOrderIds = [];
         foreach ($rows as $row) {
             if (empty($row[$headerMap['id']])) {
                 continue;
@@ -707,10 +708,16 @@ class ImportYearDataService
 
             $sku = ProductSKU::withoutGlobalScope(\App\Models\Scopes\YearScope::class)->create($skuData);
             $this->productSkuIdMapping[$oldId] = $sku->id;
+            $touchedOrderIds[$newOrderId] = $newOrderId;
 
             $count++;
         }
 
+        $paymentStatusService = app(\App\Services\OrderPaymentStatusService::class);
+        foreach ($touchedOrderIds as $orderId) {
+            $paymentStatusService->markPaidIfAllMafsHavePaymentData((int) $orderId);
+        }
+
         $this->log("Импортировано SKU: {$count}");
     }
 

+ 2 - 0
app/Services/ImportMafsService.php

@@ -8,6 +8,7 @@ use App\Models\MafOrder;
 use App\Models\Order;
 use App\Models\Product;
 use App\Models\ProductSKU;
+use App\Services\OrderPaymentStatusService;
 use Exception;
 
 class ImportMafsService extends ImportBaseService
@@ -88,6 +89,7 @@ class ImportMafsService extends ImportBaseService
                     $logMessage .= 'Found and updated product with id: ' . $productSKU->id;
                     $productSKU->update($data);
                     $productSKU->save();
+                    app(OrderPaymentStatusService::class)->markPaidIfAllMafsHavePaymentData($productSKU->order_id);
                 } else {
                     echo $this->import->log('Product sku with id ' . $id . ' NOT FOUND!', 'WARNING');
                 }

+ 7 - 0
app/Services/ImportOrdersService.php

@@ -77,6 +77,7 @@ class ImportOrdersService extends ImportBaseService
             'mafOrdersCreated' => 0,
             'productsSkuCreated' => 0,
         ];
+        $touchedOrderIds = [];
         foreach($this->rowIterator as $row){
             $strNumber++;
             $r = $this->rowToArray($row);
@@ -258,6 +259,7 @@ class ImportOrdersService extends ImportBaseService
                     'upd_number' => $r['products_sku.upd_number'],
                 ]);
                 $result['productsSkuCreated'] += 1;
+                $touchedOrderIds[$order->id] = $order->id;
                 $logMessage .= 'Created product sku: ' . $productSKU->id . '. ';
 
                 echo $this->import->log($logMessage);
@@ -268,6 +270,11 @@ class ImportOrdersService extends ImportBaseService
 
         }
 
+        $paymentStatusService = app(OrderPaymentStatusService::class);
+        foreach ($touchedOrderIds as $orderId) {
+            $paymentStatusService->markPaidIfAllMafsHavePaymentData((int) $orderId);
+        }
+
         // Вывод результатов
         echo $this->import->log(print_r($result, true));
 

+ 53 - 0
app/Services/OrderPaymentStatusService.php

@@ -0,0 +1,53 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\Order;
+use App\Models\ProductSKU;
+use App\Models\Scopes\YearScope;
+
+class OrderPaymentStatusService
+{
+    public function markPaidIfAllMafsHavePaymentData(Order|int|null $order): bool
+    {
+        if ($order === null) {
+            return false;
+        }
+
+        $order = $order instanceof Order
+            ? $order
+            : Order::query()->withoutGlobalScope(YearScope::class)->find($order);
+
+        if (!$order) {
+            return false;
+        }
+
+        $mafsQuery = ProductSKU::query()
+            ->withoutGlobalScope(YearScope::class)
+            ->where('order_id', $order->id);
+
+        if (!(clone $mafsQuery)->exists()) {
+            return false;
+        }
+
+        $hasIncompleteMafs = (clone $mafsQuery)
+            ->where(function ($query): void {
+                $query
+                    ->whereNull('statement_number')
+                    ->orWhereRaw("TRIM(statement_number) = ''")
+                    ->orWhereNull('upd_number')
+                    ->orWhereRaw("TRIM(upd_number) = ''");
+            })
+            ->exists();
+
+        if ($hasIncompleteMafs) {
+            return false;
+        }
+
+        if ((int) $order->order_status_id !== Order::STATUS_PAID) {
+            $order->update(['order_status_id' => Order::STATUS_PAID]);
+        }
+
+        return true;
+    }
+}

+ 8 - 0
resources/views/reports/index.blade.php

@@ -206,6 +206,14 @@
                         Сдано МАФ на сумму
                     </div>
                 </div>
+                <div class="col">
+                    <div class="text-center text-success fs-4 fw-bold">
+                        {!! $totalPaidSum !!}
+                    </div>
+                    <div class="text-center">
+                        Оплачено на сумму
+                    </div>
+                </div>
 
             </div>
 

+ 2 - 0
resources/views/reports/svod.blade.php

@@ -5,6 +5,7 @@
                 <th>Округ</th>
                 <th>Сумма</th>
                 <th>Сдано на сумму</th>
+                <th>Оплачено на сумму</th>
                 <th colspan="2">Всего</th>
                 <th colspan="2">Готов к монтажу</th>
                 <th colspan="2">В монтаже</th>
@@ -25,6 +26,7 @@
                 <th></th>
                 <th></th>
                 <th></th>
+                <th></th>
                 <th>Адр</th>
                 <th>МАФ</th>
                 <th>Адр</th>

+ 34 - 0
tests/Feature/OrderControllerTest.php

@@ -595,6 +595,40 @@ class OrderControllerTest extends TestCase
         ]);
     }
 
+    public function test_add_statement_to_all_mafs_marks_order_paid_when_upd_exists(): void
+    {
+        $order = Order::factory()->create([
+            'order_status_id' => Order::STATUS_HANDED_OVER,
+        ]);
+
+        $firstSku = ProductSKU::factory()->create([
+            'order_id' => $order->id,
+            'statement_number' => null,
+            'statement_date' => null,
+            'upd_number' => 'UPD-001',
+        ]);
+        $secondSku = ProductSKU::factory()->create([
+            'order_id' => $order->id,
+            'statement_number' => null,
+            'statement_date' => null,
+            'upd_number' => 'UPD-002',
+        ]);
+
+        $response = $this->actingAs($this->adminUser)
+            ->post(route('order.add-statement-to-mafs', $order), [
+                'statement_number' => 'STAT-2026-05',
+                'statement_date' => '2026-05-19',
+                'skus' => [$firstSku->id, $secondSku->id],
+            ]);
+
+        $response->assertRedirect();
+
+        $this->assertDatabaseHas('orders', [
+            'id' => $order->id,
+            'order_status_id' => Order::STATUS_PAID,
+        ]);
+    }
+
     public function test_add_statement_to_mafs_requires_field_permissions(): void
     {
         $order = Order::factory()->create();

+ 62 - 0
tests/Feature/ProductSKUControllerTest.php

@@ -476,6 +476,68 @@ class ProductSKUControllerTest extends TestCase
         ]);
     }
 
+    public function test_inline_update_marks_order_paid_when_all_mafs_have_statement_and_upd(): void
+    {
+        $order = Order::factory()->create([
+            'order_status_id' => Order::STATUS_HANDED_OVER,
+        ]);
+
+        ProductSKU::factory()->create([
+            'order_id' => $order->id,
+            'statement_number' => 'STAT-001',
+            'upd_number' => 'UPD-001',
+        ]);
+
+        $targetSku = ProductSKU::factory()->create([
+            'order_id' => $order->id,
+            'statement_number' => 'STAT-002',
+            'upd_number' => null,
+        ]);
+
+        $response = $this->actingAs($this->adminUser)
+            ->postJson(route('product_sku.inline-update', $targetSku), [
+                'field' => 'upd_number',
+                'value' => 'UPD-002',
+            ]);
+
+        $response->assertOk();
+
+        $this->assertDatabaseHas('orders', [
+            'id' => $order->id,
+            'order_status_id' => Order::STATUS_PAID,
+        ]);
+    }
+
+    public function test_inline_update_does_not_roll_back_paid_order_when_upd_is_removed(): void
+    {
+        $order = Order::factory()->create([
+            'order_status_id' => Order::STATUS_PAID,
+        ]);
+
+        $sku = ProductSKU::factory()->create([
+            'order_id' => $order->id,
+            'statement_number' => 'STAT-001',
+            'upd_number' => 'UPD-001',
+        ]);
+
+        $response = $this->actingAs($this->adminUser)
+            ->postJson(route('product_sku.inline-update', $sku), [
+                'field' => 'upd_number',
+                'value' => '',
+            ]);
+
+        $response->assertOk();
+
+        $this->assertDatabaseHas('products_sku', [
+            'id' => $sku->id,
+            'upd_number' => null,
+        ]);
+        $this->assertDatabaseHas('orders', [
+            'id' => $order->id,
+            'order_status_id' => Order::STATUS_PAID,
+        ]);
+    }
+
     public function test_manager_cannot_inline_update_product_sku_field(): void
     {
         $sku = ProductSKU::factory()->create(['upd_number' => 'UPD-OLD']);

+ 24 - 6
tests/Feature/ReportControllerTest.php

@@ -165,9 +165,10 @@ class ReportControllerTest extends TestCase
         $response = $this->actingAs($this->adminUser)->get(route('reports.index'));
         $response->assertViewHas('totalSum');
         $response->assertViewHas('totalDoneSum');
+        $response->assertViewHas('totalPaidSum');
     }
 
-    public function test_reports_index_counts_done_sum_only_for_handed_over_sites(): void
+    public function test_reports_index_counts_done_sum_with_paid_and_paid_sum_separately(): void
     {
         $handedOverOrder = Order::factory()->create([
             'user_id' => $this->adminUser->id,
@@ -205,14 +206,31 @@ class ReportControllerTest extends TestCase
             ->forProduct($handedOverWithNotesProduct)
             ->create();
 
+        $paidOrder = Order::factory()->create([
+            'user_id' => $this->adminUser->id,
+            'district_id' => $handedOverOrder->district_id,
+            'order_status_id' => Order::STATUS_PAID,
+        ]);
+        $paidProduct = Product::factory()->create([
+            'total_price' => 4000,
+        ]);
+        ProductSKU::factory()
+            ->forOrder($paidOrder)
+            ->forProduct($paidProduct)
+            ->create();
+
         $response = $this->actingAs($this->adminUser)->get(route('reports.index'));
 
-        $expectedSum = Price::format(1000);
+        $expectedDoneSum = Price::format(8000);
+        $expectedPaidSum = Price::format(4000);
+        $expectedDistrictDoneSum = Price::format(5000);
 
-        $response->assertViewHas('totalHandedOverSum', $expectedSum);
-        $response->assertViewHas('totalDoneSum', $expectedSum);
-        $response->assertViewHas('byDistrict', function (array $byDistrict) use ($handedOverOrder, $expectedSum) {
-            return ($byDistrict[$handedOverOrder->district_id]['doneSum'] ?? null) === $expectedSum;
+        $response->assertViewHas('totalHandedOverSum', $expectedDoneSum);
+        $response->assertViewHas('totalDoneSum', $expectedDoneSum);
+        $response->assertViewHas('totalPaidSum', $expectedPaidSum);
+        $response->assertViewHas('byDistrict', function (array $byDistrict) use ($handedOverOrder, $expectedDistrictDoneSum, $expectedPaidSum) {
+            return ($byDistrict[$handedOverOrder->district_id]['doneSum'] ?? null) === $expectedDistrictDoneSum
+                && ($byDistrict[$handedOverOrder->district_id]['paidSum'] ?? null) === $expectedPaidSum;
         });
     }
 }

+ 4 - 0
tests/Unit/Models/OrderTest.php

@@ -35,6 +35,7 @@ class OrderTest extends TestCase
         $this->assertEquals(10, Order::STATUS_HANDED_OVER);
         $this->assertEquals(11, Order::STATUS_NO_MAF);
         $this->assertEquals(12, Order::STATUS_PROBLEM);
+        $this->assertEquals(13, Order::STATUS_PAID);
     }
 
     public function test_status_names_array_contains_all_statuses(): void
@@ -42,15 +43,18 @@ class OrderTest extends TestCase
         $this->assertArrayHasKey(Order::STATUS_NEW, Order::STATUS_NAMES);
         $this->assertArrayHasKey(Order::STATUS_HANDED_OVER, Order::STATUS_NAMES);
         $this->assertArrayHasKey(Order::STATUS_PROBLEM, Order::STATUS_NAMES);
+        $this->assertArrayHasKey(Order::STATUS_PAID, Order::STATUS_NAMES);
 
         $this->assertEquals('Новая', Order::STATUS_NAMES[Order::STATUS_NEW]);
         $this->assertEquals('Сдана', Order::STATUS_NAMES[Order::STATUS_HANDED_OVER]);
+        $this->assertEquals('Оплачено', Order::STATUS_NAMES[Order::STATUS_PAID]);
     }
 
     public function test_status_colors_array_exists(): void
     {
         $this->assertArrayHasKey(Order::STATUS_NEW, Order::STATUS_COLOR);
         $this->assertArrayHasKey(Order::STATUS_HANDED_OVER, Order::STATUS_COLOR);
+        $this->assertArrayHasKey(Order::STATUS_PAID, Order::STATUS_COLOR);
         $this->assertEquals('success', Order::STATUS_COLOR[Order::STATUS_HANDED_OVER]);
         $this->assertEquals('danger', Order::STATUS_COLOR[Order::STATUS_PROBLEM]);
     }