Browse Source

fix: report master

Muhammad Iqbal Afandi 3 years ago
parent
commit
66535e0b58

+ 72
- 0
app/Exports/PurchaseDetailsExport.php View File

@@ -0,0 +1,72 @@
1
+<?php
2
+
3
+namespace App\Exports;
4
+
5
+use Illuminate\Contracts\View\View;
6
+use Maatwebsite\Excel\Concerns\FromView;
7
+use Maatwebsite\Excel\Concerns\Exportable;
8
+use Maatwebsite\Excel\Concerns\WithStyles;
9
+use Illuminate\Contracts\Support\Responsable;
10
+use Maatwebsite\Excel\Concerns\ShouldAutoSize;
11
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
12
+use Maatwebsite\Excel\Concerns\WithPreCalculateFormulas;
13
+
14
+class PurchaseDetailsExport implements
15
+    FromView,
16
+    Responsable,
17
+    WithStyles,
18
+    ShouldAutoSize,
19
+    WithPreCalculateFormulas
20
+{
21
+    use Exportable;
22
+
23
+    private $fileName = "purchase-report.xlsx";
24
+
25
+    public function __construct(private array $data)
26
+    {
27
+    }
28
+
29
+    public function view(): View
30
+    {
31
+        ["purchases" => $purchases] = $this->data;
32
+
33
+        return view("Excel.Purchases.Export", compact("purchases"));
34
+    }
35
+
36
+    public function styles(Worksheet $sheet)
37
+    {
38
+        $lastRow = $sheet->getHighestDataRow();
39
+
40
+        $lastContent = $lastRow - 1;
41
+
42
+        $sheet->setCellValue("E$lastRow", "=SUM(E5:E$lastContent)");
43
+
44
+        $sheet
45
+            ->getStyle("E")
46
+            ->getNumberFormat()
47
+            ->setFormatCode("#,###");
48
+
49
+        return [
50
+            1 => [
51
+                "font" => ["bold" => true, "size" => 12],
52
+                "alignment" => [
53
+                    "vertical" => "center",
54
+                    "horizontal" => "center",
55
+                ],
56
+            ],
57
+            2 => [
58
+                "font" => ["bold" => true, "size" => 12],
59
+                "alignment" => [
60
+                    "vertical" => "center",
61
+                    "horizontal" => "center",
62
+                ],
63
+            ],
64
+            4 => [
65
+                "font" => ["bold" => true],
66
+            ],
67
+            $lastRow => [
68
+                "font" => ["bold" => true, "size" => 12],
69
+            ],
70
+        ];
71
+    }
72
+}

+ 36
- 2
app/Http/Controllers/PurchaseController.php View File

@@ -2,6 +2,7 @@
2 2
 
3 3
 namespace App\Http\Controllers;
4 4
 
5
+use App\Exports\PurchaseDetailsExport;
5 6
 use App\Models\Ppn;
6 7
 use Inertia\Inertia;
7 8
 use App\Models\Product;
@@ -357,11 +358,44 @@ class PurchaseController extends Controller
357 358
 
358 359
     public function report()
359 360
     {
360
-        return inertia("Purchases/Report");
361
+        return inertia("Purchases/Report", [
362
+            "initialFilters" => request()->only("start_date", "end_date"),
363
+            "purchases" => PurchaseDetail::filter(
364
+                request()->only("start_date", "end_date")
365
+            )
366
+                ->latest()
367
+                ->paginate(10)
368
+                ->withQueryString()
369
+                ->through(
370
+                    fn($purchaseDetail) => [
371
+                        "createdAt" => $purchaseDetail->created_at,
372
+                        "totalPrice" => FunctionService::rupiahFormat(
373
+                            $purchaseDetail->price * $purchaseDetail->qty
374
+                        ),
375
+                        "qty" => $purchaseDetail->qty,
376
+                        "status" => $purchaseDetail->purchase->status,
377
+                    ]
378
+                ),
379
+        ]);
361 380
     }
362 381
 
363 382
     public function reportExcel()
364 383
     {
365
-        //
384
+        return new PurchaseDetailsExport([
385
+            "purchases" => PurchaseDetail::filter(
386
+                request()->only("start_date", "end_date")
387
+            )
388
+                ->latest()
389
+                ->get()
390
+                ->map(
391
+                    fn($purchaseDetail) => [
392
+                        "createdAt" => $purchaseDetail->created_at,
393
+                        "qty" => $purchaseDetail->qty,
394
+                        "status" => $purchaseDetail->purchase->status,
395
+                        "price" =>
396
+                            $purchaseDetail->price * $purchaseDetail->qty,
397
+                    ]
398
+                ),
399
+        ]);
366 400
     }
367 401
 }

+ 17
- 18
app/Http/Controllers/SalesController.php View File

@@ -232,24 +232,23 @@ class SalesController extends Controller
232 232
     public function report()
233 233
     {
234 234
         return inertia("Sales/Report", [
235
-            "filters" => request()->only("start_date", "end_date"),
236
-            "sales" => Inertia::lazy(
237
-                fn() => SaleDetail::filter(
238
-                    request()->only("start_date", "end_date")
239
-                )
240
-                    ->latest()
241
-                    ->paginate(3)
242
-                    ->withQueryString()
243
-                    ->through(
244
-                        fn($saleDetail) => [
245
-                            "createdAt" => $saleDetail->created_at,
246
-                            "totalPrice" => FunctionService::rupiahFormat(
247
-                                $saleDetail->price * $saleDetail->qty
248
-                            ),
249
-                            "status" => $saleDetail->sale->status,
250
-                        ]
251
-                    )
252
-            ),
235
+            "initialFilters" => request()->only("start_date", "end_date"),
236
+            "sales" => SaleDetail::filter(
237
+                request()->only("start_date", "end_date")
238
+            )
239
+                ->latest()
240
+                ->paginate(10)
241
+                ->withQueryString()
242
+                ->through(
243
+                    fn($saleDetail) => [
244
+                        "createdAt" => $saleDetail->created_at,
245
+                        "totalPrice" => FunctionService::rupiahFormat(
246
+                            $saleDetail->price * $saleDetail->qty
247
+                        ),
248
+                        "qty" => $saleDetail->qty,
249
+                        "status" => $saleDetail->sale->status,
250
+                    ]
251
+                ),
253 252
         ]);
254 253
     }
255 254
 

+ 21
- 0
app/Models/PurchaseDetail.php View File

@@ -2,6 +2,7 @@
2 2
 
3 3
 namespace App\Models;
4 4
 
5
+use Carbon\Carbon;
5 6
 use App\Services\FunctionService;
6 7
 use Illuminate\Database\Eloquent\Model;
7 8
 use Illuminate\Database\Eloquent\Casts\Attribute;
@@ -13,6 +14,15 @@ class PurchaseDetail extends Model
13 14
 
14 15
     protected $fillable = ["price", "qty", "purchase_number", "product_number"];
15 16
 
17
+    protected function createdAt(): Attribute
18
+    {
19
+        return Attribute::make(
20
+            get: fn($value) => Carbon::parse($value)->translatedFormat(
21
+                "l d/m/y"
22
+            )
23
+        );
24
+    }
25
+
16 26
     protected function price(): Attribute
17 27
     {
18 28
         return Attribute::make(
@@ -51,4 +61,15 @@ class PurchaseDetail extends Model
51 61
                 });
52 62
             });
53 63
     }
64
+
65
+    public function scopeFilter($query, array $filters)
66
+    {
67
+        $query
68
+            ->when($filters["start_date"] ?? null, function ($query, $search) {
69
+                $query->whereDate("created_at", ">=", $search);
70
+            })
71
+            ->when($filters["end_date"] ?? null, function ($query, $search) {
72
+                $query->whereDate("created_at", "<=", $search);
73
+            });
74
+    }
54 75
 }

+ 27
- 33
resources/js/components/AppDateRangeFilter.vue View File

@@ -1,8 +1,9 @@
1 1
 <script setup>
2
-import { onMounted, reactive, watch } from 'vue'
2
+import { watch, ref } from 'vue'
3 3
 import { Inertia } from '@inertiajs/inertia'
4 4
 import dayjs from 'dayjs'
5 5
 import { pickBy } from 'lodash'
6
+import { computed } from '@vue/reactivity'
6 7
 
7 8
 const props = defineProps({
8 9
   initialFilter: {
@@ -20,51 +21,44 @@ const props = defineProps({
20 21
   },
21 22
 })
22 23
 
23
-const filters = reactive({
24
-  dates: null,
25
-  startDate: null,
26
-  endDate: null,
27
-})
28
-
29
-onMounted(() => {
24
+const initialFilter = computed(() => {
30 25
   if (props.initialFilter.start_date || props.initialFilter.end_date) {
31 26
     if (props.initialFilter.end_date) {
32
-      filters.dates = [
27
+      return [
33 28
         new Date(props.initialFilter.start_date),
34 29
         new Date(props.initialFilter.end_date),
35 30
       ]
36 31
     } else {
37
-      filters.dates = [new Date(props.initialFilter.start_date), null]
32
+      return [new Date(props.initialFilter.start_date), null]
38 33
     }
39 34
   }
40 35
 })
41 36
 
42
-watch(
43
-  () => filters.dates,
44
-  () => {
45
-    if (filters.dates[1]) {
46
-      filters.startDate = dayjs(filters.dates[0]).format('YYYY-MM-DD')
37
+const dates = ref(initialFilter.value)
47 38
 
48
-      filters.endDate = dayjs(filters.dates[1]).format('YYYY-MM-DD')
49
-    } else if (filters.dates[0]) {
50
-      filters.startDate = dayjs(filters.dates[0]).format('YYYY-MM-DD')
39
+watch(dates, (value) => {
40
+  if (value[1]) {
41
+    var start_date = dayjs(value[0]).format('YYYY-MM-DD')
51 42
 
52
-      filters.endDate = null
53
-    }
43
+    var end_date = dayjs(value[1]).format('YYYY-MM-DD')
44
+  } else if (value[0]) {
45
+    var start_date = dayjs(value[0]).format('YYYY-MM-DD')
54 46
 
55
-    Inertia.get(
56
-      props.url,
57
-      pickBy({
58
-        start_date: filters.startDate,
59
-        end_date: filters.endDate,
60
-      }),
61
-      {
62
-        preserveState: true,
63
-        only: [...props.refreshData],
64
-      }
65
-    )
47
+    var end_date = null
66 48
   }
67
-)
49
+
50
+  Inertia.get(
51
+    props.url,
52
+    pickBy({
53
+      start_date,
54
+      end_date,
55
+    }),
56
+    {
57
+      preserveState: true,
58
+      only: [...props.refreshData],
59
+    }
60
+  )
61
+})
68 62
 </script>
69 63
 
70 64
 <template>
@@ -73,6 +67,6 @@ watch(
73 67
     selection-mode="range"
74 68
     date-format="dd/mm/yy"
75 69
     :manual-input="false"
76
-    v-model="filters.dates"
70
+    v-model="dates"
77 71
   />
78 72
 </template>

+ 68
- 1
resources/js/pages/Purchases/Report.vue View File

@@ -1,9 +1,76 @@
1 1
 <script setup>
2
+import { reportTable, optionStatus } from './config'
3
+import AppDateRangeFilter from '@/components/AppDateRangeFilter.vue'
4
+import AppButtonLink from '@/components/AppButtonLink.vue'
5
+import AppResetFilter from '@/components/AppResetFilter.vue'
6
+import AppDropdown from '@/components/AppDropdown.vue'
7
+import AppPagination from '@/components/AppPagination.vue'
2 8
 import DashboardLayout from '@/layouts/Dashboard/DashboardLayout.vue'
9
+
10
+defineProps({
11
+  initialFilters: Object,
12
+  purchases: {
13
+    type: Object,
14
+    default: {
15
+      data: [],
16
+      links: [],
17
+      total: 0,
18
+    },
19
+  },
20
+})
21
+
22
+const exportExcel = () => {
23
+  return route('purchases.report.excel', location.search)
24
+}
3 25
 </script>
4 26
 
5 27
 <template>
6 28
   <DashboardLayout title="Laporan Pembelian">
7
-    <h1>Laporan Pembelian</h1>
29
+    <DataTable
30
+      responsive-layout="scroll"
31
+      column-resize-mode="expand"
32
+      :value="purchases.data"
33
+      :rowHover="true"
34
+      :stripedRows="true"
35
+    >
36
+      <template #header>
37
+        <h1>Laporan Pembelian</h1>
38
+
39
+        <div class="grid">
40
+          <div class="col-12 sm:col-6 lg:col-4">
41
+            <AppDateRangeFilter
42
+              placeholder="filter waktu..."
43
+              :url="route('purchases.report')"
44
+              :refresh-data="['purchases']"
45
+              :initial-filter="initialFilters"
46
+            />
47
+          </div>
48
+
49
+          <div class="col-12 sm:col-6 lg:col-4">
50
+            <AppResetFilter :url="route('purchases.report')" />
51
+          </div>
52
+
53
+          <div class="col-12">
54
+            <AppButtonLink
55
+              v-if="purchases.total"
56
+              label="Export excel"
57
+              class-button="p-button-outlined md:w-16rem"
58
+              icon="pi pi-file-excel"
59
+              :inertia-link="false"
60
+              :href="exportExcel()"
61
+            />
62
+          </div>
63
+        </div>
64
+      </template>
65
+
66
+      <Column
67
+        v-for="value in reportTable"
68
+        :field="value.field"
69
+        :header="value.header"
70
+        :key="value.field"
71
+      />
72
+    </DataTable>
73
+
74
+    <AppPagination :links="purchases.links" />
8 75
   </DashboardLayout>
9 76
 </template>

+ 7
- 0
resources/js/pages/Purchases/config.js View File

@@ -25,3 +25,10 @@ export const cartTable = [
25 25
   { field: 'qty', header: 'Kuantitas' },
26 26
   { field: 'unit', header: 'Satuan' },
27 27
 ]
28
+
29
+export const reportTable = [
30
+  { field: 'createdAt', header: 'Tanggal' },
31
+  { field: 'totalPrice', header: 'Total Harga' },
32
+  { field: 'qty', header: 'Kuantitas' },
33
+  { field: 'status', header: 'Status' },
34
+]

+ 2
- 2
resources/js/pages/Sales/Report.vue View File

@@ -7,7 +7,7 @@ import DashboardLayout from '@/layouts/Dashboard/DashboardLayout.vue'
7 7
 import AppResetFilter from '@/components/AppResetFilter.vue'
8 8
 
9 9
 defineProps({
10
-  filters: Object,
10
+  initialFilters: Object,
11 11
   sales: {
12 12
     type: Object,
13 13
     default: {
@@ -41,7 +41,7 @@ const exportExcel = () => {
41 41
               placeholder="filter waktu..."
42 42
               :url="route('sales.report')"
43 43
               :refresh-data="['sales']"
44
-              :initial-filter="filters"
44
+              :initial-filter="initialFilters"
45 45
             />
46 46
           </div>
47 47
 

+ 35
- 0
resources/views/Excel/Purchases/Export.blade.php View File

@@ -0,0 +1,35 @@
1
+<table>
2
+    <thead>
3
+        <tr>
4
+            <th colspan="5">{{ __('words.report_purchase') }}</th>
5
+        </tr>
6
+        <tr>
7
+            <th colspan="5" rowspan="2">{{ __('words.period', ['number' => '']) }}
8
+                {{ $purchases->first()['createdAt'] }}
9
+                -
10
+                {{ $purchases->last()['createdAt'] }} </th>
11
+        </tr>
12
+        <tr></tr>
13
+        <tr>
14
+            <th>#</th>
15
+            <th>{{ __('words.date') }}</th>
16
+            <th>{{ __('words.quantity') }}</th>
17
+            <th>{{ __('words.status') }}</th>
18
+            <th>{{ __('words.total_price') }}</th>
19
+        </tr>
20
+    </thead>
21
+    <tbody>
22
+        @foreach ($purchases as $index => $purchase)
23
+            <tr>
24
+                <td>{{ ++$index }}</td>
25
+                <td>{{ $purchase['createdAt'] }}</td>
26
+                <td>{{ $purchase['qty'] }}</td>
27
+                <td>{{ $purchase['status'] }}</td>
28
+                <td>{{ $purchase['price'] }}</td>
29
+            </tr>
30
+        @endforeach
31
+        <tr>
32
+            <td colspan="4">{{ __('words.total') }}</td>
33
+        </tr>
34
+    </tbody>
35
+</table>

+ 4
- 1
routes/web.php View File

@@ -69,7 +69,10 @@ Route::middleware(["auth", "verified", "checkBlocked"])->group(function () {
69 69
         "reportExcel",
70 70
     ])->name("purchases.report.excel");
71 71
 
72
-    Route::get("/purchases/report", [PurchaseController::class, "report"]);
72
+    Route::get("/purchases/report", [
73
+        PurchaseController::class,
74
+        "report",
75
+    ])->name("purchases.report");
73 76
 
74 77
     Route::resource("/purchases", PurchaseController::class);
75 78