2 İşlemeler

Yazar SHA1 Mesaj Tarih
  Muhammad Iqbal Afandi 010719f9c5 feat: breadcrumbs customer history purchase 3 yıl önce
  Muhammad Iqbal Afandi 86c26442aa fix: components 3 yıl önce

+ 24
- 0
app/Http/Controllers/CustomerController.php Dosyayı Görüntüle

@@ -2,6 +2,8 @@
2 2
 
3 3
 namespace App\Http\Controllers;
4 4
 
5
+use App\Models\Ppn;
6
+use App\Models\Sale;
5 7
 use App\Models\Customer;
6 8
 use App\Models\SaleDetail;
7 9
 use App\Exports\CustomerHistoryExport;
@@ -137,6 +139,28 @@ class CustomerController extends Controller
137 139
         return back()->with("success", __("messages.success.destroy.customer"));
138 140
     }
139 141
 
142
+    public function historyPurchase(Sale $sale)
143
+    {
144
+        return inertia("Customers/HistoryPurchase", [
145
+            "id" => $sale->id,
146
+            "number" => $sale->number,
147
+            "ppn" => Ppn::first()->ppn,
148
+            "status" => $sale->status,
149
+            "ppnChecked" => $sale->ppn ? true : false,
150
+            "customer" => $sale->customer,
151
+            "saleDetail" => $sale->saleDetail->transform(
152
+                fn($sale) => [
153
+                    "id" => $sale->id,
154
+                    "number" => $sale->product_number,
155
+                    "name" => $sale->product->name,
156
+                    "price" => $sale->getRawOriginal("price"),
157
+                    "qty" => $sale->qty,
158
+                    "unit" => $sale->product->unit,
159
+                ]
160
+            ),
161
+        ]);
162
+    }
163
+
140 164
     public function historyPurchaseExcel()
141 165
     {
142 166
         $this->authorize("viewAny", Customer::class);

resources/js/pages/Purchases/Components/Cart.vue → resources/js/pages/Components/Cart.vue Dosyayı Görüntüle


resources/js/pages/Sales/Composables/useCart.js → resources/js/pages/Composables/useCart.js Dosyayı Görüntüle


+ 133
- 0
resources/js/pages/Customers/HistoryPurchase.vue Dosyayı Görüntüle

@@ -0,0 +1,133 @@
1
+<script setup>
2
+import { Link } from '@inertiajs/inertia-vue3'
3
+import { useForm } from '@/composables/useForm'
4
+import { IDRCurrencyFormat } from '@/utils/helpers'
5
+import { cartTable } from './config'
6
+import Cart from '@/pages/Components/Cart.vue'
7
+import { useCart } from '@/pages/Composables/useCart'
8
+import AppButtonLink from '@/components/AppButtonLink.vue'
9
+import DashboardLayout from '@/layouts/Dashboard/DashboardLayout.vue'
10
+
11
+const props = defineProps({
12
+  id: Number,
13
+  number: String,
14
+  ppn: Number,
15
+  status: String,
16
+  ppnChecked: Boolean,
17
+  customer: Object,
18
+  saleDetail: Array,
19
+})
20
+
21
+const form = useForm({
22
+  status: props.status,
23
+  customer: props.customer,
24
+  ppn: props.ppn,
25
+  checkedPpn: props.ppnChecked,
26
+})
27
+
28
+const { cart, totalCartPrice } = useCart(form, props.saleDetail)
29
+
30
+const back = () => history.back()
31
+</script>
32
+
33
+<template>
34
+  <DashboardLayout title="Detail Penjualan">
35
+    <DynamicDialog />
36
+
37
+    <div class="grid">
38
+      <div class="col-12 flex flex-column md:flex-row md:align-items-center">
39
+        <Link class="text-primary text-5xl font-bold" @click="back">
40
+          Pelanggan
41
+        </Link>
42
+
43
+        <span class="text-5xl font-bold mx-2"> / </span>
44
+
45
+        <span class="text-5xl font-bold">Detail Pembelian</span>
46
+      </div>
47
+
48
+      <div class="col-12">
49
+        <div class="grid">
50
+          <div class="col-12">
51
+            <Card>
52
+              <template #title>
53
+                <h2 class="text-2xl font-bold">Detail Penjualan</h2>
54
+              </template>
55
+              <template #content>
56
+                <div class="grid">
57
+                  <div class="col-12">
58
+                    <div class="grid">
59
+                      <div class="col">
60
+                        <h3 class="text-base">Nama</h3>
61
+                        <span>{{ customer.name }}</span>
62
+                      </div>
63
+                      <div class="col">
64
+                        <h3 class="text-base">Alamat</h3>
65
+                        <span>{{ customer.address }}</span>
66
+                      </div>
67
+                      <div class="col">
68
+                        <h3 class="text-base">No HP</h3>
69
+                        <span>{{ customer.phone }}</span>
70
+                      </div>
71
+                      <div class="col">
72
+                        <h3 class="text-base">NPWP</h3>
73
+                        <span>{{ customer.npwp }}</span>
74
+                      </div>
75
+                    </div>
76
+                  </div>
77
+                  <div class="col-12">
78
+                    <div class="grid">
79
+                      <div class="col">
80
+                        <h3 class="text-base">Nomor Pembelian</h3>
81
+                        <span>{{ number }}</span>
82
+                      </div>
83
+                      <div class="col">
84
+                        <h3 class="text-base">Status Pembelian</h3>
85
+                        <span>{{ status }}</span>
86
+                      </div>
87
+                      <div class="col">
88
+                        <h3 class="text-base">Total Harga</h3>
89
+                        <span>{{ IDRCurrencyFormat(totalCartPrice()) }}</span>
90
+                      </div>
91
+                      <div class="col"></div>
92
+                    </div>
93
+                  </div>
94
+                </div>
95
+              </template>
96
+            </Card>
97
+          </div>
98
+          <div class="col-12">
99
+            <Cart
100
+              title="Keranjang Produk"
101
+              :cart="cart"
102
+              :header-table="cartTable"
103
+              :btn-ppn-disabled="true"
104
+              :btn-delete-show="false"
105
+              :btn-edit-show="false"
106
+              v-model:checked-ppn="form.checkedPpn"
107
+            />
108
+          </div>
109
+        </div>
110
+      </div>
111
+
112
+      <div class="col-12 flex justify-content-end">
113
+        <AppButtonLink
114
+          label="Cetak Invoice"
115
+          icon="pi pi-print"
116
+          target="_blank"
117
+          :inertia-link="false"
118
+          :href="route('sales.pdf.invoice', id)"
119
+        />
120
+      </div>
121
+
122
+      <div class="col-12 flex justify-content-end">
123
+        <AppButtonLink
124
+          label="Cetak Delivery Order"
125
+          icon="pi pi-print"
126
+          target="_blank"
127
+          :inertia-link="false"
128
+          :href="route('sales.pdf.do', id)"
129
+        />
130
+      </div>
131
+    </div>
132
+  </DashboardLayout>
133
+</template>

+ 7
- 7
resources/js/pages/Customers/Index.vue Dosyayı Görüntüle

@@ -74,13 +74,6 @@ const onDelete = (data) => {
74 74
       <Column>
75 75
         <template #body="{ data }">
76 76
           <div class="grid gap-2">
77
-            <AppButtonLink
78
-              icon="pi pi-list"
79
-              class="p-button-icon-only p-button-rounded p-button-text"
80
-              v-tooltip.bottom="'History Pembelian'"
81
-              :href="route('customers.show', data.id)"
82
-            />
83
-
84 77
             <AppButtonLink
85 78
               icon="pi pi-pencil"
86 79
               class="p-button-icon-only p-button-rounded p-button-text"
@@ -95,6 +88,13 @@ const onDelete = (data) => {
95 88
               v-tooltip.bottom="'Hapus Pelanggan'"
96 89
               @click="onDelete(data)"
97 90
             />
91
+
92
+            <AppButtonLink
93
+              icon="pi pi-chevron-right"
94
+              class="p-button-icon-only p-button-rounded p-button-text"
95
+              v-tooltip.bottom="'History Pembelian'"
96
+              :href="route('customers.show', data.id)"
97
+            />
98 98
           </div>
99 99
         </template>
100 100
       </Column>

+ 3
- 3
resources/js/pages/Customers/Show.vue Dosyayı Görüntüle

@@ -26,7 +26,7 @@ const exportExcel = () => {
26 26
 </script>
27 27
 
28 28
 <template>
29
-  <DashboardLayout title="">
29
+  <DashboardLayout title="Detail Pembelian">
30 30
     <div class="grid">
31 31
       <div class="col-12">
32 32
         <Card>
@@ -113,10 +113,10 @@ const exportExcel = () => {
113 113
           <Column>
114 114
             <template #body="{ data }">
115 115
               <AppButtonLink
116
-                icon="pi pi-eye"
116
+                icon="pi pi-chevron-right"
117 117
                 class="p-button-icon-only p-button-rounded p-button-text"
118 118
                 v-tooltip.bottom="'Lihat Detail Penjualan'"
119
-                :href="route('sales.show', data.id)"
119
+                :href="route('customers.history-purchases', data.id)"
120 120
               />
121 121
             </template>
122 122
           </Column>

+ 8
- 0
resources/js/pages/Customers/config.js Dosyayı Görüntüle

@@ -11,3 +11,11 @@ export const detailTable = [
11 11
   { field: 'ppn', header: 'PPN' },
12 12
   { field: 'status', header: 'Status' },
13 13
 ]
14
+
15
+export const cartTable = [
16
+  { field: 'number', header: 'Nomor Produk' },
17
+  { field: 'name', header: 'Produk' },
18
+  { field: 'price', header: 'Harga' },
19
+  { field: 'qty', header: 'Kuantitas' },
20
+  { field: 'unit', header: 'Satuan' },
21
+]

+ 0
- 109
resources/js/pages/Purchases/Composables/useCart.js Dosyayı Görüntüle

@@ -1,109 +0,0 @@
1
-import { reactive } from 'vue'
2
-import { FormValidationError, ppn } from '@/utils/helpers'
3
-
4
-export function useCart(form, initialProducts = []) {
5
-  const cart = reactive(initialProducts)
6
-
7
-  const cartDeleted = reactive([])
8
-
9
-  const cartErrors = reactive([])
10
-
11
-  const cartValidation = () => {
12
-    onClearCartErrors()
13
-
14
-    const itemExists = cart.find(
15
-      (product) => product.number === form.product.number
16
-    )
17
-
18
-    if (itemExists) {
19
-      cartErrors.push({
20
-        message: 'Produk sudah ada dikeranjang',
21
-        field: 'product',
22
-      })
23
-    }
24
-
25
-    if (cartErrors.length) {
26
-      throw new FormValidationError('form error', cartErrors)
27
-    }
28
-  }
29
-
30
-  const onAddCart = () => {
31
-    try {
32
-      form.clearErrors('product', 'qty')
33
-
34
-      cartValidation()
35
-
36
-      cart.push({
37
-        label: 'add',
38
-        number: form.product.number,
39
-        name: form.product.name,
40
-        price: form.price,
41
-        qty: Number(form.qty),
42
-        unit: form.product.unit,
43
-      })
44
-
45
-      form.reset('product', 'price', 'qty')
46
-    } catch (e) {
47
-      e.errors.forEach((error) => {
48
-        form.setError(error.field, error.message)
49
-      })
50
-    }
51
-  }
52
-
53
-  const onDeleteCart = (index) => {
54
-    if (cart[index]?.id) {
55
-      cartDeleted.push({
56
-        ...cart[index],
57
-        label: 'delete',
58
-      })
59
-    }
60
-
61
-    cart.splice(index, 1)
62
-  }
63
-
64
-  const onClearCart = () => {
65
-    cart.splice(0)
66
-  }
67
-
68
-  const onClearCartDelete = () => {
69
-    cartDeleted.splice(0)
70
-  }
71
-
72
-  const onClearCartErrors = () => {
73
-    cartErrors.splice(0)
74
-  }
75
-
76
-  const totalCartPrice = () => {
77
-    const itemPrices = cart.map((product) => {
78
-      return form.checkedPpn
79
-        ? ppn(product.price, form.ppn) * product.qty
80
-        : product.price * product.qty
81
-    })
82
-
83
-    return itemPrices.reduce(
84
-      (prevPrice, currentPrice) => prevPrice + currentPrice,
85
-      0
86
-    )
87
-  }
88
-
89
-  const onEditCart = (event) => {
90
-    const { newData, index } = event
91
-
92
-    cart[index] = {
93
-      ...newData,
94
-      label: 'edit',
95
-    }
96
-  }
97
-
98
-  return {
99
-    cart,
100
-    cartDeleted,
101
-    cartErrors,
102
-    onClearCart,
103
-    onClearCartDelete,
104
-    onAddCart,
105
-    onEditCart,
106
-    onDeleteCart,
107
-    totalCartPrice,
108
-  }
109
-}

+ 2
- 2
resources/js/pages/Purchases/Create.vue Dosyayı Görüntüle

@@ -2,9 +2,9 @@
2 2
 import { computed, provide } from 'vue'
3 3
 import { optionStatus } from './config'
4 4
 import { cartTable } from './config'
5
+import Cart from '@/pages/Components/Cart.vue'
6
+import { useCart } from '@/pages/Composables/useCart'
5 7
 import Details from './Components/Details.vue'
6
-import Cart from './Components/Cart.vue'
7
-import { useCart } from './Composables/useCart'
8 8
 import { useDialog } from './Composables/useDialog'
9 9
 import { useForm } from '@/composables/useForm'
10 10
 import HistoryProduct from './Components/HistoryProduct.vue'

+ 2
- 2
resources/js/pages/Purchases/Edit.vue Dosyayı Görüntüle

@@ -2,9 +2,9 @@
2 2
 import { computed } from 'vue'
3 3
 import { optionStatus } from './config'
4 4
 import { cartTable } from './config'
5
+import Cart from '@/pages/Components/Cart.vue'
6
+import { useCart } from '@/pages/Composables/useCart'
5 7
 import Details from './Components/Details.vue'
6
-import Cart from './Components/Cart.vue'
7
-import { useCart } from './Composables/useCart'
8 8
 import { useDialog } from './Composables/useDialog'
9 9
 import { useForm } from '@/composables/useForm'
10 10
 import HistoryProduct from './Components/HistoryProduct.vue'

+ 1
- 1
resources/js/pages/Purchases/Index.vue Dosyayı Görüntüle

@@ -64,7 +64,7 @@ defineProps({
64 64
 
65 65
           <AppButtonLink
66 66
             v-else
67
-            icon="pi pi-eye"
67
+            icon="pi pi-chevron-right"
68 68
             class="p-button-icon-only p-button-rounded p-button-text"
69 69
             v-tooltip.bottom="'Lihat Detail Pembelian'"
70 70
             :href="route('purchases.show', data.id)"

+ 2
- 2
resources/js/pages/Purchases/Show.vue Dosyayı Görüntüle

@@ -2,8 +2,8 @@
2 2
 import { useForm } from '@/composables/useForm'
3 3
 import { IDRCurrencyFormat } from '@/utils/helpers'
4 4
 import { cartTable } from './config'
5
-import Cart from './Components/Cart.vue'
6
-import { useCart } from './Composables/useCart'
5
+import Cart from '@/pages/Components/Cart.vue'
6
+import { useCart } from '@/pages/Composables/useCart'
7 7
 import AppButtonLink from '@/components/AppButtonLink.vue'
8 8
 import DashboardLayout from '@/layouts/Dashboard/DashboardLayout.vue'
9 9
 

+ 0
- 110
resources/js/pages/Sales/Components/Cart.vue Dosyayı Görüntüle

@@ -1,110 +0,0 @@
1
-<script setup>
2
-import { ref } from 'vue'
3
-import { IDRCurrencyFormat } from '@/utils/helpers'
4
-import AppInputNumber from '@/components/AppInputNumber.vue'
5
-import AppInputText from '@/components/AppInputText.vue'
6
-
7
-defineProps({
8
-  title: String,
9
-  headerTable: {
10
-    required: true,
11
-    type: Array,
12
-  },
13
-  cart: {
14
-    required: true,
15
-    type: Array,
16
-  },
17
-  btnEditShow: {
18
-    default: true,
19
-    type: Boolean,
20
-  },
21
-  btnDeleteShow: {
22
-    default: true,
23
-    type: Boolean,
24
-  },
25
-  btnPpnDisabled: {
26
-    default: false,
27
-    type: Boolean,
28
-  },
29
-  checkedPpn: Boolean,
30
-})
31
-
32
-const editingRows = ref([])
33
-</script>
34
-
35
-<template>
36
-  <DataTable
37
-    responsiveLayout="scroll"
38
-    columnResizeMode="expand"
39
-    edit-mode="row"
40
-    data-key="number"
41
-    :value="cart"
42
-    :rowHover="true"
43
-    :stripedRows="true"
44
-    v-model:editing-rows="editingRows"
45
-    @row-edit-save="$emit('edit', $event)"
46
-  >
47
-    <template #header>
48
-      <h2 class="text-2xl font-bold">{{ title }}</h2>
49
-
50
-      <div class="field-checkbox flex justify-content-end gap-2">
51
-        <label class="text-sm" for="ppn">
52
-          Semua produk dikenakan PPN {{ $page.props.ppn }}%
53
-        </label>
54
-        <input
55
-          type="checkbox"
56
-          id="ppn"
57
-          :disabled="btnPpnDisabled"
58
-          :checked="checkedPpn"
59
-          @input="$emit('update:checkedPpn', $event.target.checked)"
60
-        />
61
-      </div>
62
-    </template>
63
-
64
-    <Column
65
-      v-for="value in headerTable"
66
-      :key="value.field"
67
-      :field="value.field"
68
-      :header="value.header"
69
-    >
70
-      <template #body="{ data, field }">
71
-        <template v-if="field == 'price'">
72
-          {{ IDRCurrencyFormat(data[field]) }}
73
-        </template>
74
-
75
-        <template v-else> {{ data[field] }} </template>
76
-      </template>
77
-
78
-      <template #editor="{ data, field }">
79
-        <AppInputNumber
80
-          v-if="field == 'price'"
81
-          label="Harga"
82
-          placeholder="harga"
83
-          v-model="data[field]"
84
-        />
85
-
86
-        <AppInputText
87
-          v-if="field == 'qty'"
88
-          label="Kuantitas"
89
-          placeholder="kuantitas"
90
-          type="number"
91
-          v-model="data[field]"
92
-        />
93
-      </template>
94
-    </Column>
95
-
96
-    <Column v-if="btnEditShow" :row-editor="true" />
97
-
98
-    <Column>
99
-      <template #body="{ index }">
100
-        <Button
101
-          v-if="btnDeleteShow"
102
-          icon="pi pi-trash"
103
-          class="p-button-icon-only p-button-rounded p-button-text"
104
-          v-tooltip.bottom="'hapus'"
105
-          @click="$emit('delete', index)"
106
-        />
107
-      </template>
108
-    </Column>
109
-  </DataTable>
110
-</template>

+ 2
- 2
resources/js/pages/Sales/Create.vue Dosyayı Görüntüle

@@ -3,9 +3,9 @@ import { computed, watchEffect } from 'vue'
3 3
 import { profit, ppn as ppnUtils } from '@/utils/helpers'
4 4
 import { optionStatus } from './config'
5 5
 import { cartTable } from './config'
6
+import Cart from '@/pages/Components/Cart.vue'
7
+import { useCart } from '@/pages/Composables/useCart'
6 8
 import Details from './Components/Details.vue'
7
-import Cart from './Components/Cart.vue'
8
-import { useCart } from './Composables/useCart'
9 9
 import { useDialog } from './Composables/useDialog'
10 10
 import { useForm } from '@/composables/useForm'
11 11
 import AppInputText from '@/components/AppInputText.vue'

+ 1
- 1
resources/js/pages/Sales/Index.vue Dosyayı Görüntüle

@@ -55,7 +55,7 @@ defineProps({
55 55
       <Column>
56 56
         <template #body="{ data }">
57 57
           <AppButtonLink
58
-            icon="pi pi-eye"
58
+            icon="pi pi-chevron-right"
59 59
             class="p-button-icon-only p-button-rounded p-button-text"
60 60
             v-tooltip.bottom="'Lihat Detail Penjualan'"
61 61
             :href="route('sales.show', data.id)"

+ 2
- 3
resources/js/pages/Sales/Show.vue Dosyayı Görüntüle

@@ -1,10 +1,9 @@
1 1
 <script setup>
2
-import { Inertia } from '@inertiajs/inertia'
3 2
 import { useForm } from '@/composables/useForm'
4 3
 import { IDRCurrencyFormat } from '@/utils/helpers'
5 4
 import { cartTable } from './config'
6
-import Cart from './Components/Cart.vue'
7
-import { useCart } from './Composables/useCart'
5
+import Cart from '@/pages/Components/Cart.vue'
6
+import { useCart } from '@/pages/Composables/useCart'
8 7
 import AppButtonLink from '@/components/AppButtonLink.vue'
9 8
 import DashboardLayout from '@/layouts/Dashboard/DashboardLayout.vue'
10 9
 

+ 5
- 0
routes/web.php Dosyayı Görüntüle

@@ -57,6 +57,11 @@ Route::middleware(["auth", "verified", "checkBlocked"])->group(function () {
57 57
         "historyPurchaseExcel",
58 58
     ])->name("customers.history-purchase.excel");
59 59
 
60
+    Route::get("/customers/history-purchase/{sale}", [
61
+        CustomerController::class,
62
+        "historyPurchase",
63
+    ])->name("customers.history-purchases");
64
+
60 65
     Route::resource("/customers", CustomerController::class);
61 66
 
62 67
     Route::get("/purchases/pdf/invoice/{purchase}", [