Przeglądaj źródła

feat: master sales

rodzic
commit
917892e03c

+ 23
- 2
app/Http/Controllers/SalesController.php Wyświetl plik

@@ -19,7 +19,24 @@ class SalesController extends Controller
19 19
      */
20 20
     public function index()
21 21
     {
22
-        return inertia('Sales/Index.vue');
22
+        return inertia('Sales/Index', [
23
+            'initialSearch' => request('search'),
24
+            'sales' => Sale::filter(request()->only('search'))
25
+                ->latest()
26
+                ->paginate(10)
27
+                ->withQueryString()
28
+                ->through(fn($sale) => [
29
+                    'id' => $sale->id,
30
+                    'updatedAt' => $sale->updated_at,
31
+                    'number' => $sale->number,
32
+                    'status' => $sale->status,
33
+                    'price' => $sale->saleDetails->price,
34
+                    'ppn' => $sale->saleDetails->ppn,
35
+                    'qty' => $sale->saleDetails->qty,
36
+                    'productName' => $sale->product->name,
37
+                    'productNumber' => $sale->product->number
38
+                ])
39
+        ]);
23 40
     }
24 41
 
25 42
     /**
@@ -29,7 +46,11 @@ class SalesController extends Controller
29 46
      */
30 47
     public function create()
31 48
     {
32
-        //
49
+        // return inertia('Sales/Create', [
50
+        //     'customers' => fn () => [
51
+        //             'name' =>
52
+        //     ]
53
+        // ]);
33 54
     }
34 55
 
35 56
     /**

+ 10
- 0
app/Models/Sale.php Wyświetl plik

@@ -15,4 +15,14 @@ class Sale extends Model
15 15
         'customers_id',
16 16
         'user_id'
17 17
     ];
18
+
19
+    public function scopeFilter($query, array $filters)
20
+    {
21
+        $query->when($filters['search'] ?? null, function ($query, $search) {
22
+            $query->where(function ($query) use ($search) {
23
+                $query->where('number', 'like', '%' . $search . '%')
24
+                    ->orWhere('status', 'like', '%' . $search . '%');
25
+            });
26
+        });
27
+    }
18 28
 }

+ 1
- 1
database/migrations/2022_06_16_091443_create_sales_table.php Wyświetl plik

@@ -17,7 +17,7 @@ return new class extends Migration
17 17
             $table->id();
18 18
             $table->string('number');
19 19
             $table->enum('status', ['pending', 'success']);
20
-            $table->foreignId('customers_id')->constrained();
20
+            $table->foreignId('customer_id')->constrained();
21 21
             $table->foreignId('user_id')->constrained();
22 22
             $table->timestamps();
23 23
         });

+ 2647
- 0
public/js/resources_js_pages_Sales_Create_vue.js
Plik diff jest za duży
Wyświetl plik


+ 17779
- 42
public/js/resources_js_pages_Sales_Index_vue.js
Plik diff jest za duży
Wyświetl plik


+ 39
- 0
public/js/resources_js_pages_Sales_tableHeader_js.js Wyświetl plik

@@ -0,0 +1,39 @@
1
+"use strict";
2
+(self["webpackChunk"] = self["webpackChunk"] || []).push([["resources_js_pages_Sales_tableHeader_js"],{
3
+
4
+/***/ "./resources/js/pages/Sales/tableHeader.js":
5
+/*!*************************************************!*\
6
+  !*** ./resources/js/pages/Sales/tableHeader.js ***!
7
+  \*************************************************/
8
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
9
+
10
+__webpack_require__.r(__webpack_exports__);
11
+/* harmony export */ __webpack_require__.d(__webpack_exports__, {
12
+/* harmony export */   "default": () => (__WEBPACK_DEFAULT_EXPORT__)
13
+/* harmony export */ });
14
+/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ([{
15
+  field: 'updatedAt',
16
+  header: 'Tanggal'
17
+}, {
18
+  field: 'number',
19
+  header: 'Nomor Penjualan'
20
+}, {
21
+  field: 'status',
22
+  header: 'Status'
23
+}, {
24
+  field: 'price',
25
+  header: 'Harga'
26
+}, {
27
+  field: 'qty',
28
+  header: 'Kuantitas'
29
+}, {
30
+  field: 'productName',
31
+  header: 'Nama Produk'
32
+}, {
33
+  field: 'productNumber',
34
+  header: 'Nomor Produk'
35
+}]);
36
+
37
+/***/ })
38
+
39
+}]);

+ 17
- 1
public/js/vue.js Wyświetl plik

@@ -58364,6 +58364,14 @@ var map = {
58364 58364
 		"./resources/js/pages/Purchases/Index.vue",
58365 58365
 		"resources_js_pages_Purchases_Index_vue"
58366 58366
 	],
58367
+	"./Sales/Create": [
58368
+		"./resources/js/pages/Sales/Create.vue",
58369
+		"resources_js_pages_Sales_Create_vue"
58370
+	],
58371
+	"./Sales/Create.vue": [
58372
+		"./resources/js/pages/Sales/Create.vue",
58373
+		"resources_js_pages_Sales_Create_vue"
58374
+	],
58367 58375
 	"./Sales/Index": [
58368 58376
 		"./resources/js/pages/Sales/Index.vue",
58369 58377
 		"resources_js_pages_Sales_Index_vue"
@@ -58372,6 +58380,14 @@ var map = {
58372 58380
 		"./resources/js/pages/Sales/Index.vue",
58373 58381
 		"resources_js_pages_Sales_Index_vue"
58374 58382
 	],
58383
+	"./Sales/tableHeader": [
58384
+		"./resources/js/pages/Sales/tableHeader.js",
58385
+		"resources_js_pages_Sales_tableHeader_js"
58386
+	],
58387
+	"./Sales/tableHeader.js": [
58388
+		"./resources/js/pages/Sales/tableHeader.js",
58389
+		"resources_js_pages_Sales_tableHeader_js"
58390
+	],
58375 58391
 	"./StockProducts/Index": [
58376 58392
 		"./resources/js/pages/StockProducts/Index.vue",
58377 58393
 		"resources_js_pages_StockProducts_Index_vue"
@@ -58569,7 +58585,7 @@ module.exports = JSON.parse('{"name":"axios","version":"0.21.4","description":"P
58569 58585
 /******/ 		// This function allow to reference async chunks
58570 58586
 /******/ 		__webpack_require__.u = (chunkId) => {
58571 58587
 /******/ 			// return url for filenames based on template
58572
-/******/ 			return "js/" + chunkId + ".js?id=" + {"node_modules_chart_js_auto_auto_esm_js":"9296b829a7757dee","resources_js_pages_Auth_Login_vue":"3660adc6dce187d8","resources_js_pages_Customers_Create_vue":"a28f01f843162dc3","resources_js_pages_Customers_Edit_vue":"888355cbc87c499f","resources_js_pages_Customers_Index_vue":"8b937b36c08abd77","resources_js_pages_Customers_tableHeader_js":"7a40a3d5ad60171c","resources_js_pages_Dashboards_Index_vue":"c5d472522e3c56ea","resources_js_pages_Error_Index_vue":"4119ff1c60260652","resources_js_pages_Purchases_Index_vue":"78384af131366665","resources_js_pages_Sales_Index_vue":"8437f395f6c9a021","resources_js_pages_StockProducts_Index_vue":"fddc59b8e7d0e040","resources_js_pages_Suppliers_Create_vue":"d2de74a83347ac1a","resources_js_pages_Suppliers_Edit_vue":"5d6c8bf19ca55905","resources_js_pages_Suppliers_Index_vue":"fe5c7495dc6faf71","resources_js_pages_Suppliers_tableHeader_js":"500bc29ff0dbddce","resources_js_pages_Users_Create_vue":"e7a11c529b06b795","resources_js_pages_Users_Edit_vue":"b79f9b44dc4c7ab3","resources_js_pages_Users_Index_vue":"b55debdc9cbaf227","resources_js_pages_Users_Show_vue":"9ee8ea844b0d1b72","resources_js_pages_Users_tableHeader_js":"48f19bd820caf015"}[chunkId] + "";
58588
+/******/ 			return "js/" + chunkId + ".js?id=" + {"node_modules_chart_js_auto_auto_esm_js":"9296b829a7757dee","resources_js_pages_Auth_Login_vue":"3660adc6dce187d8","resources_js_pages_Customers_Create_vue":"a28f01f843162dc3","resources_js_pages_Customers_Edit_vue":"888355cbc87c499f","resources_js_pages_Customers_Index_vue":"8b937b36c08abd77","resources_js_pages_Customers_tableHeader_js":"7a40a3d5ad60171c","resources_js_pages_Dashboards_Index_vue":"c5d472522e3c56ea","resources_js_pages_Error_Index_vue":"4119ff1c60260652","resources_js_pages_Purchases_Index_vue":"78384af131366665","resources_js_pages_Sales_Create_vue":"0bac2c72f834d4f8","resources_js_pages_Sales_Index_vue":"47a48a9d387ea71b","resources_js_pages_Sales_tableHeader_js":"a7aefd03020f1bdf","resources_js_pages_StockProducts_Index_vue":"fddc59b8e7d0e040","resources_js_pages_Suppliers_Create_vue":"d2de74a83347ac1a","resources_js_pages_Suppliers_Edit_vue":"5d6c8bf19ca55905","resources_js_pages_Suppliers_Index_vue":"fe5c7495dc6faf71","resources_js_pages_Suppliers_tableHeader_js":"500bc29ff0dbddce","resources_js_pages_Users_Create_vue":"e7a11c529b06b795","resources_js_pages_Users_Edit_vue":"b79f9b44dc4c7ab3","resources_js_pages_Users_Index_vue":"b55debdc9cbaf227","resources_js_pages_Users_Show_vue":"9ee8ea844b0d1b72","resources_js_pages_Users_tableHeader_js":"48f19bd820caf015"}[chunkId] + "";
58573 58589
 /******/ 		};
58574 58590
 /******/ 	})();
58575 58591
 /******/ 	

+ 150
- 0
resources/js/pages/Sales/Create.vue Wyświetl plik

@@ -0,0 +1,150 @@
1
+<script setup>
2
+import { useForm, Head } from '@inertiajs/inertia-vue3'
3
+import { useFormErrorReset } from '@/components/useFormErrorReset'
4
+import AppInputText from '@/components/AppInputText.vue'
5
+import AppInputNumber from '@/components/AppInputNumber.vue'
6
+import AppDropdown from '@/components/AppDropdown.vue'
7
+import AppAutocompleteBasic from '@/components/AppAutocompleteBasic.vue'
8
+import DashboardLayout from '@/layouts/DashboardLayout.vue'
9
+
10
+defineProps({
11
+  customers: Object,
12
+})
13
+
14
+const form = useForm({
15
+  number: null,
16
+  status: null,
17
+  price: null,
18
+  qty: null,
19
+  customer_id: null,
20
+})
21
+
22
+useFormErrorReset(form)
23
+
24
+const optionStatus = [
25
+  {
26
+    label: 'Pending',
27
+    value: 'pending',
28
+  },
29
+  {
30
+    label: 'Success',
31
+    value: 'success',
32
+  },
33
+]
34
+
35
+const customerOnComplete = (event) => {
36
+  Inertia.reload({
37
+    data: { search: event.query },
38
+    only: ['customers'],
39
+  })
40
+}
41
+
42
+const customerOnSelected = (event) => {
43
+  form.customer = event.value
44
+}
45
+
46
+const onSubmit = () => {
47
+  form.post(route('sales.store'), { onSuccess: () => form.reset() })
48
+}
49
+</script>
50
+
51
+<template>
52
+  <Head title="Tambah Penjualan" />
53
+
54
+  <DashboardLayout>
55
+    <div class="grid">
56
+      <div class="col-12 lg:col-8">
57
+        <Card>
58
+          <template #title> Tambah Penjualan </template>
59
+          <template #content>
60
+            <div class="grid">
61
+              <div class="col-12 md:col-6">
62
+                <AppInputText
63
+                  label="Nomor Penjualan"
64
+                  placeholder="nomor penjualan"
65
+                  type="number"
66
+                  :error="form.errors.number"
67
+                  v-model="form.number"
68
+                />
69
+              </div>
70
+
71
+              <div class="col-12 md:col-6">
72
+                <!-- <AppDropdown
73
+                  label="Status"
74
+                  placeholder="status"
75
+                  :option="optionStatus"
76
+                  :error="form.errors.status"
77
+                  v-model="form.status"
78
+                /> -->
79
+              </div>
80
+
81
+              <div class="col-12 md:col-6">
82
+                <AppInputNumber
83
+                  label="Harga"
84
+                  placeholder="harga"
85
+                  :error="form.errors.price"
86
+                  v-model="form.price"
87
+                />
88
+              </div>
89
+
90
+              <div class="col-12 md:col-6">
91
+                <AppInputText
92
+                  label="Kuantitas"
93
+                  placeholder="kuantitas"
94
+                  :error="form.errors.qty"
95
+                  v-model="form.qty"
96
+                />
97
+              </div>
98
+
99
+              <div class="col-12 md:col-6">
100
+                <AppAutocompleteBasic
101
+                  label="Pelanggan"
102
+                  placeholder="pelanggan"
103
+                  :error="form.errors.customer_id"
104
+                  v-model="form.customer_id"
105
+                  @suggestions="customers"
106
+                  @complete="customerOnComplete"
107
+                  @item-select="customerOnSelected"
108
+                >
109
+                  <template #item="slotProps">
110
+                    <template v-if="slotProps.item">
111
+                      <div class="flex flex-column">
112
+                        <span>{{ slotProps.item.name }}</span>
113
+                        <span>{{ slotProps.item.type }}</span>
114
+                        <span class="font-bold">{{
115
+                          slotProps.item.platNumber
116
+                        }}</span>
117
+                      </div>
118
+                    </template>
119
+                  </template>
120
+
121
+                  <template #empty>
122
+                    <span
123
+                      class="cursor-pointer"
124
+                      style="color: var(--primary-color)"
125
+                      @click="gotoMember"
126
+                    >
127
+                      Tambah Produk
128
+                    </span>
129
+                  </template>
130
+                </AppAutocompleteBasic>
131
+              </div>
132
+            </div>
133
+          </template>
134
+
135
+          <template #footer>
136
+            <div class="flex flex-column md:flex-row justify-content-end">
137
+              <Button
138
+                label="Simpan"
139
+                icon="pi pi-check"
140
+                class="p-button-outlined"
141
+                :disabled="form.processing"
142
+                @click="onSubmit"
143
+              />
144
+            </div>
145
+          </template>
146
+        </Card>
147
+      </div>
148
+    </div>
149
+  </DashboardLayout>
150
+</template>

+ 70
- 2
resources/js/pages/Sales/Index.vue Wyświetl plik

@@ -1,10 +1,78 @@
1 1
 <script setup>
2
+import { watch } from 'vue'
3
+import { Inertia } from '@inertiajs/inertia'
2 4
 import { Head } from '@inertiajs/inertia-vue3'
5
+import { pickBy } from 'lodash'
6
+import tableHeader from './tableHeader'
7
+import { useSearchText } from '@/components/useSearchText'
3 8
 import DashboardLayout from '@/layouts/DashboardLayout.vue'
9
+import AppButtonLink from '@/components/AppButtonLink.vue'
10
+import AppPagination from '@/components/AppPagination.vue'
11
+
12
+const props = defineProps({
13
+  sales: Object,
14
+  initialSearch: String,
15
+})
16
+
17
+const { search } = useSearchText(props)
18
+
19
+watch(search, () => {
20
+  Inertia.get('/sales', pickBy({ search: search.value }), {
21
+    preserveState: true,
22
+  })
23
+})
4 24
 </script>
5 25
 
6 26
 <template>
7
-  <Head title="Halaman Penjualan" />
27
+  <Head title="Daftar Penjualan" />
28
+
29
+  <DashboardLayout>
30
+    <DataTable
31
+      responsiveLayout="scroll"
32
+      columnResizeMode="expand"
33
+      :value="sales.data"
34
+      :rowHover="true"
35
+      :stripedRows="true"
36
+    >
37
+      <template #header>
38
+        <h1>Penjualan</h1>
39
+
40
+        <div class="grid">
41
+          <div class="col-12 md:col-8">
42
+            <div class="flex align-items-center">
43
+              <InputText
44
+                class="w-full md:w-27rem"
45
+                placeholder="cari, contoh: P0xx, Pending, Success"
46
+                v-model="search"
47
+              />
48
+            </div>
49
+          </div>
50
+
51
+          <div
52
+            class="col-12 md:col-4 flex flex-column md:flex-row justify-content-end"
53
+          >
54
+            <AppButtonLink
55
+              label="Tambah Penjualan"
56
+              icon="pi pi-pencil"
57
+              class="p-button-outlined"
58
+              :href="route('sales.create')"
59
+            />
60
+          </div>
61
+        </div>
62
+      </template>
63
+
64
+      <Column
65
+        v-for="value in tableHeader"
66
+        :field="value.field"
67
+        :header="value.header"
68
+        :key="value.field"
69
+      />
70
+
71
+      <Column>
72
+        <template #body="{ data }"> </template>
73
+      </Column>
74
+    </DataTable>
8 75
 
9
-  <DashboardLayout> Halaman Penjualan </DashboardLayout>
76
+    <AppPagination :links="sales.links" />
77
+  </DashboardLayout>
10 78
 </template>

+ 9
- 0
resources/js/pages/Sales/tableHeader.js Wyświetl plik

@@ -0,0 +1,9 @@
1
+export default [
2
+  { field: 'updatedAt', header: 'Tanggal' },
3
+  { field: 'number', header: 'Nomor Penjualan' },
4
+  { field: 'status', header: 'Status' },
5
+  { field: 'price', header: 'Harga' },
6
+  { field: 'qty', header: 'Kuantitas' },
7
+  { field: 'productName', header: 'Nama Produk' },
8
+  { field: 'productNumber', header: 'Nomor Produk' },
9
+]