From 7e0887015bda1b157ff71c35f5e530bf6ae58d84 Mon Sep 17 00:00:00 2001 From: Aji Kamaludin Date: Sun, 4 Jun 2023 02:14:50 +0700 Subject: [PATCH] customer purchase done --- TODO.md | 4 +- app/Http/Controllers/AccountController.php | 2 +- app/Http/Controllers/BannerController.php | 4 +- .../Controllers/Customer/AuthController.php | 4 +- .../Controllers/Customer/CartController.php | 159 ++++++++++++++++++ .../Customer/DepositController.php | 14 +- .../Controllers/Customer/HomeController.php | 5 +- .../Customer/ProfileController.php | 4 +- .../Customer/TransactionController.php | 26 +++ app/Http/Controllers/CustomerController.php | 4 +- .../Controllers/CustomerLevelController.php | 5 +- app/Http/Controllers/DepositController.php | 8 +- app/Http/Controllers/GeneralController.php | 1 - app/Http/Controllers/SettingController.php | 2 +- app/Http/Controllers/VoucherController.php | 2 +- app/Http/Middleware/Authenticate.php | 3 +- .../HandleInertiaCustomerRequests.php | 10 +- app/Models/Banner.php | 2 +- app/Models/Customer.php | 13 +- app/Models/DepositHistory.php | 9 +- app/Models/Sale.php | 37 ++++ app/Models/SaleItem.php | 11 ++ app/Models/Voucher.php | 18 +- app/Services/GeneralService.php | 13 +- app/Services/MidtransService.php | 4 +- .../2023_05_24_130715_create_sales_table.php | 1 + ...3_05_24_130718_create_sale_items_table.php | 1 + database/seeders/DummySeeder.php | 11 +- resources/js/Layouts/CustomerLayout.jsx | 28 ++- .../js/Pages/DepositHistory/FormModal.jsx | 1 - resources/js/Pages/Home/Cart/Index.jsx | 80 +++++++++ resources/js/Pages/Home/Cart/VoucherCard.jsx | 74 ++++++++ resources/js/Pages/Home/Index/VoucherCard.jsx | 13 +- resources/js/Pages/Home/Trx/Detail.jsx | 39 +++++ resources/js/Pages/Home/Trx/Index.jsx | 71 ++++++++ resources/js/Pages/Home/Trx/VoucherCard.jsx | 73 ++++++++ routes/api.php | 2 +- routes/web.php | 12 +- 38 files changed, 708 insertions(+), 62 deletions(-) create mode 100644 app/Http/Controllers/Customer/CartController.php create mode 100644 app/Http/Controllers/Customer/TransactionController.php create mode 100644 resources/js/Pages/Home/Cart/Index.jsx create mode 100644 resources/js/Pages/Home/Cart/VoucherCard.jsx create mode 100644 resources/js/Pages/Home/Trx/Detail.jsx create mode 100644 resources/js/Pages/Home/Trx/Index.jsx create mode 100644 resources/js/Pages/Home/Trx/VoucherCard.jsx diff --git a/TODO.md b/TODO.md index ef2d0fd..059345d 100644 --- a/TODO.md +++ b/TODO.md @@ -31,9 +31,9 @@ - [x] Customer Edit Profile - [x] Customer Deposit Manual - [x] Customer Deposit Payment Gateway -- [ ] Customer Purchase Voucher -- [ ] Register Refferal +- [x] Customer Purchase Voucher - [ ] Customer Share Buyed Voucher, via WA dll +- [ ] Register Refferal - [ ] Customer View Coin History - [ ] Verified Akun - [ ] Notification (purchase success, deposit success) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 21e9e4e..d9712f7 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -12,7 +12,7 @@ class AccountController extends Controller $query = Account::orderBy('updated_at', 'desc')->paginate(); return inertia('Account/Index', [ - 'query' => $query + 'query' => $query, ]); } diff --git a/app/Http/Controllers/BannerController.php b/app/Http/Controllers/BannerController.php index 04ba868..afb3471 100644 --- a/app/Http/Controllers/BannerController.php +++ b/app/Http/Controllers/BannerController.php @@ -12,7 +12,7 @@ class BannerController extends Controller $query = Banner::orderBy('updated_at', 'desc')->paginate(); return inertia('Banner/Index', [ - 'query' => $query + 'query' => $query, ]); } @@ -45,7 +45,7 @@ class BannerController extends Controller public function edit(Banner $banner) { return inertia('Banner/Form', [ - 'banner' => $banner + 'banner' => $banner, ]); } diff --git a/app/Http/Controllers/Customer/AuthController.php b/app/Http/Controllers/Customer/AuthController.php index 642a0b5..993785e 100644 --- a/app/Http/Controllers/Customer/AuthController.php +++ b/app/Http/Controllers/Customer/AuthController.php @@ -4,8 +4,6 @@ namespace App\Http\Controllers\Customer; use App\Http\Controllers\Controller; use App\Models\Customer; -use App\Models\CustomerLevel; -use App\Models\CustomerLevelHistory; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; @@ -72,7 +70,7 @@ class AuthController extends Controller 'fullname' => $user->name, 'name' => $user->nickname, 'email' => $user->email, - 'username' => Str::slug($user->name . '_' . Str::random(5), '_'), + 'username' => Str::slug($user->name.'_'.Str::random(5), '_'), 'google_id' => $user->id, 'google_oauth_response' => json_encode($user), ]); diff --git a/app/Http/Controllers/Customer/CartController.php b/app/Http/Controllers/Customer/CartController.php new file mode 100644 index 0000000..3696eeb --- /dev/null +++ b/app/Http/Controllers/Customer/CartController.php @@ -0,0 +1,159 @@ + deposit, coin, paylater + * + */ + public function index() + { + $carts = collect(session('carts') ?? []); + $total = $carts->sum(function ($item) { + return $item['quantity'] * $item['voucher']->price; + }); + + return inertia('Home/Cart/Index', [ + 'carts' => $carts, + 'total' => $total, + ]); + } + + /** + * handle cart add, remove or sub + * + */ + public function store(Request $request, Voucher $voucher) + { + $operator = $request->param ?? 'add'; + $voucher->load(['location']); + + $carts = collect(session('carts') ?? []); + if ($carts->count() > 0) { + $item = $carts->firstWhere('id', $voucher->id); + if ($item == null) { + $carts->add(['id' => $voucher->id, 'quantity' => 1, 'voucher' => $voucher]); + session(['carts' => $carts->toArray()]); + session()->flash('message', ['type' => 'success', 'message' => 'voucher added to cart']); + } else { + $carts = $carts->map(function ($item) use ($voucher, $operator) { + if ($item['id'] == $voucher->id) { + if ($operator == 'delete') { + return ['id' => null]; + } + if ($operator == 'add') { + $quantity = $item['quantity'] + 1; + } + if ($operator == 'sub') { + $quantity = $item['quantity'] - 1; + if ($quantity <= 0) { + $quantity = 1; + } + } + + return [ + ...$item, + 'quantity' => $quantity, + ]; + } + + return $item; + }); + $carts = $carts->whereNotNull('id')->toArray(); + session(['carts' => $carts]); + } + + return; + } + + session(['carts' => [ + ['id' => $voucher->id, 'quantity' => 1, 'voucher' => $voucher], + ]]); + session()->flash('message', ['type' => 'success', 'message' => 'voucher added to cart']); + } + + /** + * find correct voucher , reject if cant be found + * create sale and item sale + * credit deposit + * redirect to show detail + */ + public function purchase() + { + DB::beginTransaction(); + $carts = collect(session('carts')); + + // validate voucher is available + $vouchers = Voucher::whereIn('id', $carts->pluck('id')->toArray())->get(); + $carts = $carts->map(function ($item) use ($vouchers) { + $voucher = $vouchers->firstWhere('id', $item['id']); + if ($voucher->is_sold == Voucher::SOLD) { + $voucher = $voucher->shuffle_unsold(); + // rare happen + if ($voucher == null) { + session()->remove('carts'); + return redirect()->route('home.index') + ->with('message', ['type' => 'error', 'message' => 'transaksi gagal, voucher sedang tidak tersedia']); + } + } + + return [ + ...$item, + 'voucher' => $voucher + ]; + }); + + $total = $carts->sum(function ($item) { + return $item['quantity'] * $item['voucher']->price; + }); + + $customer = Customer::find(auth()->id()); + $sale = $customer->sales()->create([ + 'code' => Str::random(5), + 'date_time' => now(), + 'amount' => $total, + 'payed_with' => Sale::PAYED_WITH_DEPOSIT, + ]); + + foreach ($carts as $item) { + $sale->items()->create([ + 'entity_type' => $item['voucher']::class, + 'entity_id' => $item['voucher']->id, + 'price' => $item['voucher']->price, + 'quantity' => $item['quantity'], + 'additional_info_json' => json_encode($item), + ]); + + $item['voucher']->update(['is_sold' => Voucher::SOLD]); + } + + $deposit = $customer->deposites()->create([ + 'credit' => $total, + 'description' => 'Pembayaran #' . $sale->code, + 'related_type' => $sale::class, + 'related_id' => $sale->id, + 'is_valid' => DepositHistory::STATUS_VALID, + ]); + $deposit->update_customer_balance(); + + DB::commit(); + + session()->remove('carts'); + + return redirect()->route('transactions.show', $sale) + ->with('message', ['type' => 'success', 'message' => 'pembelian berhasil']); + } +} diff --git a/app/Http/Controllers/Customer/DepositController.php b/app/Http/Controllers/Customer/DepositController.php index d59969f..3473c99 100644 --- a/app/Http/Controllers/Customer/DepositController.php +++ b/app/Http/Controllers/Customer/DepositController.php @@ -22,7 +22,7 @@ class DepositController extends Controller ->orderBy('is_valid', 'desc'); return inertia('Home/Deposit/Index', [ - 'histories' => $histories->paginate(20) + 'histories' => $histories->paginate(20), ]); } @@ -39,15 +39,15 @@ class DepositController extends Controller 'amount' => 'required|numeric|min:10000', 'payment' => [ 'required', - Rule::in([Setting::PAYMENT_MANUAL, Setting::PAYMENT_MIDTRANS]) - ] + Rule::in([Setting::PAYMENT_MANUAL, Setting::PAYMENT_MIDTRANS]), + ], ]); DB::beginTransaction(); $deposit = DepositHistory::make([ 'customer_id' => auth()->id(), 'debit' => $request->amount, - 'description' => 'Top Up #' . Str::random(5), + 'description' => 'Top Up #'.Str::random(5), 'payment_channel' => $request->payment, ]); @@ -77,7 +77,7 @@ class DepositController extends Controller 'accounts' => Account::get(), 'midtrans_client_key' => Setting::getByKey('MIDTRANS_CLIENT_KEY'), 'is_production' => app()->isProduction(), - 'direct' => $request->direct + 'direct' => $request->direct, ]); } @@ -93,10 +93,10 @@ class DepositController extends Controller $deposit->update([ 'image_prove' => $file->hashName('uploads'), 'account_id' => $request->account_id, - 'is_valid' => DepositHistory::STATUS_WAIT_APPROVE + 'is_valid' => DepositHistory::STATUS_WAIT_APPROVE, ]); - session()->flash('message', ['type' => 'success', 'message' => 'Upload berhasil, silahkan tunggu untuk approve']);; + session()->flash('message', ['type' => 'success', 'message' => 'Upload berhasil, silahkan tunggu untuk approve']); } public function midtrans_payment(Request $request, DepositHistory $deposit) diff --git a/app/Http/Controllers/Customer/HomeController.php b/app/Http/Controllers/Customer/HomeController.php index f4f6631..b443a8d 100644 --- a/app/Http/Controllers/Customer/HomeController.php +++ b/app/Http/Controllers/Customer/HomeController.php @@ -17,6 +17,7 @@ class HomeController extends Controller $banners = Banner::orderBy('updated_at', 'desc')->get(); $locations = Location::get(); $vouchers = Voucher::with(['location']) + ->where('is_sold', Voucher::UNSOLD) ->groupBy('batch_id') ->orderBy('updated_at', 'desc'); @@ -28,8 +29,8 @@ class HomeController extends Controller 'infos' => $infos, 'banners' => $banners, 'locations' => $locations, - 'vouchers' => $vouchers->paginate(10), - '_location_id' => $request->location_id ?? '' + 'vouchers' => tap($vouchers->paginate(10))->setHidden(['username', 'password']), + '_location_id' => $request->location_id ?? '', ]); } diff --git a/app/Http/Controllers/Customer/ProfileController.php b/app/Http/Controllers/Customer/ProfileController.php index 9a82aa8..c1657fe 100644 --- a/app/Http/Controllers/Customer/ProfileController.php +++ b/app/Http/Controllers/Customer/ProfileController.php @@ -26,7 +26,7 @@ class ProfileController extends Controller 'name' => 'string|required', 'address' => 'string|required', 'phone' => 'string|required|numeric', - 'username' => 'string|required|min:5|alpha_dash|unique:customers,username,' . $customer->id, + 'username' => 'string|required|min:5|alpha_dash|unique:customers,username,'.$customer->id, 'password' => 'nullable|string|min:8|confirmed', 'image' => 'nullable|image', ]); @@ -51,6 +51,6 @@ class ProfileController extends Controller 'password' => $customer->password, ]); - session()->flash('message', ['type' => 'success', 'message' => 'profile updateded']); + session()->flash('message', ['type' => 'success', 'message' => 'profile updated']); } } diff --git a/app/Http/Controllers/Customer/TransactionController.php b/app/Http/Controllers/Customer/TransactionController.php new file mode 100644 index 0000000..95e5bd0 --- /dev/null +++ b/app/Http/Controllers/Customer/TransactionController.php @@ -0,0 +1,26 @@ +id()) + ->orderBy('created_at', 'desc'); + + return inertia('Home/Trx/Index', [ + 'query' => $query->paginate(), + ]); + } + + public function show(Sale $sale) + { + return inertia('Home/Trx/Detail', [ + 'sale' => $sale->load(['items.voucher.location']) + ]); + } +} diff --git a/app/Http/Controllers/CustomerController.php b/app/Http/Controllers/CustomerController.php index 2025941..9b04f5c 100644 --- a/app/Http/Controllers/CustomerController.php +++ b/app/Http/Controllers/CustomerController.php @@ -57,14 +57,14 @@ class CustomerController extends Controller public function edit(Customer $customer) { return inertia('Customer/Form', [ - 'customer' => $customer + 'customer' => $customer, ]); } public function update(Request $request, Customer $customer) { $request->validate([ - 'username' => 'required|string|min:5|alpha_dash|unique:customers,username,' . $customer->id, + 'username' => 'required|string|min:5|alpha_dash|unique:customers,username,'.$customer->id, 'password' => 'nullable|string|min:8', 'name' => 'required|string', 'fullname' => 'required|string', diff --git a/app/Http/Controllers/CustomerLevelController.php b/app/Http/Controllers/CustomerLevelController.php index f3ccd79..1bc80fc 100644 --- a/app/Http/Controllers/CustomerLevelController.php +++ b/app/Http/Controllers/CustomerLevelController.php @@ -10,8 +10,9 @@ class CustomerLevelController extends Controller public function index() { $query = CustomerLevel::query(); + return inertia('CustomerLevel/Index', [ - 'query' => $query->paginate() + 'query' => $query->paginate(), ]); } @@ -21,7 +22,7 @@ class CustomerLevelController extends Controller 'name' => 'required|string', 'description' => 'nullable|string', 'min_amount' => 'required|numeric|min:0', - 'max_amount' => 'required|numeric|min:0' + 'max_amount' => 'required|numeric|min:0', ]); $customerLevel->update([ diff --git a/app/Http/Controllers/DepositController.php b/app/Http/Controllers/DepositController.php index 78549cc..b10dab1 100644 --- a/app/Http/Controllers/DepositController.php +++ b/app/Http/Controllers/DepositController.php @@ -36,7 +36,7 @@ class DepositController extends Controller } return inertia('DepositHistory/Index', [ - 'query' => $query->paginate() + 'query' => $query->paginate(), ]); } @@ -45,15 +45,15 @@ class DepositController extends Controller $request->validate([ 'status' => [ 'required', - Rule::in([DepositHistory::STATUS_VALID, DepositHistory::STATUS_REJECT]) - ] + Rule::in([DepositHistory::STATUS_VALID, DepositHistory::STATUS_REJECT]), + ], ]); DB::beginTransaction(); $deposit->update([ 'is_valid' => $request->status, ]); - if ($request->status === DepositHistory::STATUS_VALID) { + if ($request->status == DepositHistory::STATUS_VALID) { $deposit->update_customer_balance(); } DB::commit(); diff --git a/app/Http/Controllers/GeneralController.php b/app/Http/Controllers/GeneralController.php index 5e8209c..4ea2aa5 100644 --- a/app/Http/Controllers/GeneralController.php +++ b/app/Http/Controllers/GeneralController.php @@ -23,7 +23,6 @@ class GeneralController extends Controller $file = $request->file('image'); $file->store('uploads', 'public'); - return response()->json([ 'id' => Str::ulid(), 'name' => $file->getClientOriginalName(), diff --git a/app/Http/Controllers/SettingController.php b/app/Http/Controllers/SettingController.php index 9e8804f..d71dc52 100644 --- a/app/Http/Controllers/SettingController.php +++ b/app/Http/Controllers/SettingController.php @@ -14,7 +14,7 @@ class SettingController extends Controller return inertia('Setting/Index', [ 'setting' => $setting, - 'midtrans_notification_url' => route('api.midtrans.notification') + 'midtrans_notification_url' => route('api.midtrans.notification'), ]); } diff --git a/app/Http/Controllers/VoucherController.php b/app/Http/Controllers/VoucherController.php index abd38b2..58eb385 100644 --- a/app/Http/Controllers/VoucherController.php +++ b/app/Http/Controllers/VoucherController.php @@ -21,7 +21,7 @@ class VoucherController extends Controller } return inertia('Voucher/Index', [ - 'query' => $query->paginate() + 'query' => $query->paginate(), ]); } diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 418ce73..d9b09d7 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -14,11 +14,12 @@ class Authenticate extends Middleware */ protected function redirectTo($request) { - if (!$request->expectsJson()) { + if (! $request->expectsJson()) { $uri = $request->getRequestUri(); if (str_contains($uri, 'admin')) { return route('admin.login'); } + return route('customer.login'); } } diff --git a/app/Http/Middleware/HandleInertiaCustomerRequests.php b/app/Http/Middleware/HandleInertiaCustomerRequests.php index 88855fd..9f6998c 100644 --- a/app/Http/Middleware/HandleInertiaCustomerRequests.php +++ b/app/Http/Middleware/HandleInertiaCustomerRequests.php @@ -29,6 +29,14 @@ class HandleInertiaCustomerRequests extends Middleware */ public function share(Request $request): array { + $carts = collect(session('carts') ?? []); + $cart_count = 0; + if ($carts->count() > 0) { + foreach ($carts as $cart) { + $cart_count += $cart['quantity']; + } + } + return array_merge(parent::share($request), [ 'app_name' => env('APP_NAME', 'App Name'), 'auth' => [ @@ -38,7 +46,7 @@ class HandleInertiaCustomerRequests extends Middleware 'message' => fn () => $request->session()->get('message') ?? ['type' => null, 'message' => null], ], 'notification_count' => 0, - 'carts' => [] + 'cart_count' => $cart_count, ]); } } diff --git a/app/Models/Banner.php b/app/Models/Banner.php index b895462..b0525df 100644 --- a/app/Models/Banner.php +++ b/app/Models/Banner.php @@ -15,7 +15,7 @@ class Banner extends Model ]; protected $appends = [ - 'image_url' + 'image_url', ]; protected function imageUrl(): Attribute diff --git a/app/Models/Customer.php b/app/Models/Customer.php index f60b84e..d660538 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -43,7 +43,7 @@ class Customer extends Authenticatable 'image_url', 'display_deposit', 'display_coin', - 'display_phone' + 'display_phone', ]; protected static function booted(): void @@ -103,6 +103,7 @@ class Customer extends Authenticatable if ($this->phone === null) { return ' - '; } + return '+62' . $this->phone; }); } @@ -125,4 +126,14 @@ class Customer extends Authenticatable { return $this->belongsTo(CustomerLevel::class, 'customer_level_id'); } + + public function sales() + { + return $this->hasMany(Sale::class); + } + + public function deposites() + { + return $this->hasMany(DepositHistory::class); + } } diff --git a/app/Models/DepositHistory.php b/app/Models/DepositHistory.php index 4d1e738..2766d68 100644 --- a/app/Models/DepositHistory.php +++ b/app/Models/DepositHistory.php @@ -41,7 +41,7 @@ class DepositHistory extends Model 'format_human_created_at', 'format_created_at', 'amount', - 'image_prove_url' + 'image_prove_url', ]; public function status(): Attribute @@ -61,14 +61,14 @@ class DepositHistory extends Model public function formatHumanCreatedAt(): Attribute { return Attribute::make(get: function () { - return Carbon::parse($this->created_at)->locale('id')->format('d F Y'); + return Carbon::parse($this->created_at)->locale('id')->translatedFormat('d F Y'); }); } public function formatCreatedAt(): Attribute { return Attribute::make(get: function () { - return Carbon::parse($this->created_at)->locale('id')->format('d M Y H:i:s'); + return Carbon::parse($this->created_at)->locale('id')->translatedFormat('d F Y H:i:s'); }); } @@ -78,6 +78,7 @@ class DepositHistory extends Model if ($this->credit == 0) { return 'Rp' . number_format($this->debit, 0, ',', '.'); } + return '-Rp' . number_format($this->credit, 0, ',', '.'); }); } @@ -102,6 +103,6 @@ class DepositHistory extends Model public function update_customer_balance() { $customer = Customer::find($this->customer_id); - $customer->update(['deposit_balance' => $customer->deposit_balance + $this->debit]); + $customer->update(['deposit_balance' => $customer->deposit_balance + $this->debit - $this->credit]); } } diff --git a/app/Models/Sale.php b/app/Models/Sale.php index 0108872..44171b3 100644 --- a/app/Models/Sale.php +++ b/app/Models/Sale.php @@ -2,6 +2,9 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Casts\Attribute; +use Illuminate\Support\Carbon; + class Sale extends Model { const PAYED_WITH_MIDTRANS = 'midtrans'; @@ -13,6 +16,7 @@ class Sale extends Model const PAYED_WITH_COIN = 'coin'; protected $fillable = [ + 'code', 'customer_id', 'date_time', 'amount', @@ -23,4 +27,37 @@ class Sale extends Model 'payment_channel', 'payment_type', ]; + + + protected $appends = [ + 'format_human_created_at', + 'format_created_at', + 'display_amount', + ]; + + public function items() + { + return $this->hasMany(SaleItem::class); + } + + public function formatHumanCreatedAt(): Attribute + { + return Attribute::make(get: function () { + return Carbon::parse($this->created_at)->locale('id')->translatedFormat('d F Y'); + }); + } + + public function formatCreatedAt(): Attribute + { + return Attribute::make(get: function () { + return Carbon::parse($this->created_at)->locale('id')->translatedFormat('d F Y H:i:s'); + }); + } + + public function displayAmount(): Attribute + { + return Attribute::make(get: function () { + return 'Rp' . number_format($this->amount, 0, ',', '.'); + }); + } } diff --git a/app/Models/SaleItem.php b/app/Models/SaleItem.php index d9d9aa5..00d3f57 100644 --- a/app/Models/SaleItem.php +++ b/app/Models/SaleItem.php @@ -9,6 +9,17 @@ class SaleItem extends Model 'entity_type', 'entity_id', 'price', + 'quantity', 'additional_info_json', ]; + + public function related() + { + return $this->belongsTo($this->entity_type, 'entity_id'); + } + + public function voucher() + { + return $this->belongsTo(Voucher::class, 'entity_id'); + } } diff --git a/app/Models/Voucher.php b/app/Models/Voucher.php index fe363d2..7889f93 100644 --- a/app/Models/Voucher.php +++ b/app/Models/Voucher.php @@ -7,6 +7,10 @@ use Illuminate\Support\Str; class Voucher extends Model { + const UNSOLD = 0; + + const SOLD = 1; + protected $fillable = [ 'name', 'description', @@ -21,8 +25,8 @@ class Voucher extends Model 'comment', 'expired', 'expired_unit', - 'is_sold', //menandakan sudah terjual atau belum - // batch pada saat import , jadi ketika user ingin beli akan tetapi sudah sold , + 'is_sold', //menandakan sudah terjual atau belum + // batch pada saat import , jadi ketika user ingin beli akan tetapi sudah sold , // maka akan dicarikan voucher lain dari batch yang sama 'batch_id', ]; @@ -73,4 +77,14 @@ class Voucher extends Model { return $this->belongsTo(Location::class)->withTrashed(); } + + public function shuffle_unsold() + { + $voucher = Voucher::where([ + ['is_sold', '=', self::UNSOLD], + ['batch_id', '=', $this->batch_id] + ])->first(); + + return $voucher; + } } diff --git a/app/Services/GeneralService.php b/app/Services/GeneralService.php index b8be990..c2f0978 100644 --- a/app/Services/GeneralService.php +++ b/app/Services/GeneralService.php @@ -56,16 +56,21 @@ class GeneralService public static function getEnablePayment() { $payment = [ - ['name' => Setting::PAYMENT_MANUAL, 'logo' => null, 'display_name' => 'Transfer Manual'] + ['name' => Setting::PAYMENT_MANUAL, 'logo' => null, 'display_name' => 'Transfer Manual'], ]; $midtrans_enable = Setting::getByKey('MIDTRANS_ENABLED'); if ($midtrans_enable == 1) { - $payment[] = ['name' => Setting::PAYMENT_MIDTRANS, 'logo' => Setting::getByKey('MIDTRANS_LOGO')]; + $payment[] = ['name' => Setting::PAYMENT_MIDTRANS, 'logo' => Setting::getByKey('MIDTRANS_LOGO')]; } - // Paylater - return $payment; } + + public static function getCartEnablePayment() + { + // deposit + // coin + // paylater + } } diff --git a/app/Services/MidtransService.php b/app/Services/MidtransService.php index cd583d8..4f5df54 100644 --- a/app/Services/MidtransService.php +++ b/app/Services/MidtransService.php @@ -40,8 +40,8 @@ class MidtransService 'address' => $this->deposit->customer->address, ], 'callbacks' => [ - 'finish' => route('customer.deposit.show', ['deposit' => $this->deposit->id]) - ] + 'finish' => route('customer.deposit.show', ['deposit' => $this->deposit->id]), + ], ]; $snapToken = Snap::getSnapToken($params); diff --git a/database/migrations/2023_05_24_130715_create_sales_table.php b/database/migrations/2023_05_24_130715_create_sales_table.php index d1f4e16..8d8772b 100644 --- a/database/migrations/2023_05_24_130715_create_sales_table.php +++ b/database/migrations/2023_05_24_130715_create_sales_table.php @@ -14,6 +14,7 @@ return new class extends Migration Schema::create('sales', function (Blueprint $table) { $table->ulid('id')->primary(); + $table->string('code')->nullable(); $table->ulid('customer_id')->nullable(); $table->timestamp('date_time')->nullable(); $table->decimal('amount', 20, 2)->default(0); diff --git a/database/migrations/2023_05_24_130718_create_sale_items_table.php b/database/migrations/2023_05_24_130718_create_sale_items_table.php index a019eb1..a6f69fd 100644 --- a/database/migrations/2023_05_24_130718_create_sale_items_table.php +++ b/database/migrations/2023_05_24_130718_create_sale_items_table.php @@ -18,6 +18,7 @@ return new class extends Migration $table->string('entity_type')->nullable(); $table->ulid('entity_id')->nullable(); $table->decimal('price', 20, 2)->default(0); + $table->integer('quantity')->default(0); $table->text('additional_info_json')->nullable(); $table->timestamps(); diff --git a/database/seeders/DummySeeder.php b/database/seeders/DummySeeder.php index 4eed431..6696939 100644 --- a/database/seeders/DummySeeder.php +++ b/database/seeders/DummySeeder.php @@ -41,9 +41,9 @@ class DummySeeder extends Seeder $images = ['1.webp', '2.webp', '3.webp']; foreach ($images as $index => $image) { Banner::create([ - 'title' => 'Banner ' . $index, - 'image' => 'sample/' . $image, - 'description' => '

Banner

' + 'title' => 'Banner '.$index, + 'image' => 'sample/'.$image, + 'description' => '

Banner

', ]); } } @@ -52,7 +52,7 @@ class DummySeeder extends Seeder { $banks = [ ['name' => 'BRI', 'bank_name' => 'Bank Rakyat Indonesia', 'holder_name' => 'Aji Kamaludin', 'account_number' => '187391738129'], - ['name' => 'Jago', 'bank_name' => 'Bank Jago', 'holder_name' => 'Aji Kamaludin', 'account_number' => '718297389172'] + ['name' => 'Jago', 'bank_name' => 'Bank Jago', 'holder_name' => 'Aji Kamaludin', 'account_number' => '718297389172'], ]; foreach ($banks as $bank) { @@ -72,7 +72,7 @@ class DummySeeder extends Seeder foreach ($locations as $location) { Location::create([ 'name' => $location, - 'description' => '-' + 'description' => '-', ]); } } @@ -82,7 +82,6 @@ class DummySeeder extends Seeder $vouchers = GeneralService::script_parser(file_get_contents(public_path('example.md'))); - DB::beginTransaction(); foreach ([1, 2] as $loop) { $batchId = Str::ulid(); diff --git a/resources/js/Layouts/CustomerLayout.jsx b/resources/js/Layouts/CustomerLayout.jsx index c270919..32e8329 100644 --- a/resources/js/Layouts/CustomerLayout.jsx +++ b/resources/js/Layouts/CustomerLayout.jsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect } from 'react' import { ToastContainer, toast } from 'react-toastify' import { router, usePage } from '@inertiajs/react' @@ -13,8 +13,11 @@ export default function CustomerLayout({ children }) { const { props: { auth: { user }, + cart_count, + flash, }, } = usePage() + const handleOnClick = (r) => { router.get(route(r)) } @@ -27,6 +30,12 @@ export default function CustomerLayout({ children }) { return 'text-gray-600' } + useEffect(() => { + if (flash.message !== null) { + toast(flash.message.message, { type: flash.message.type }) + } + }, [flash]) + return (
@@ -42,18 +51,29 @@ export default function CustomerLayout({ children }) {
Beranda
-
+ +
handleOnClick('cart.index')} + >
- 1 + {cart_count}
Keranjang
-
+
handleOnClick('transactions.index')} + >
Transaksi
diff --git a/resources/js/Pages/DepositHistory/FormModal.jsx b/resources/js/Pages/DepositHistory/FormModal.jsx index bd8a132..68eca19 100644 --- a/resources/js/Pages/DepositHistory/FormModal.jsx +++ b/resources/js/Pages/DepositHistory/FormModal.jsx @@ -22,7 +22,6 @@ export default function FormModal(props) { }, payment_channel: '', is_valid: 0, - status: '', status_text: '', text_color: '', customer_name: '', diff --git a/resources/js/Pages/Home/Cart/Index.jsx b/resources/js/Pages/Home/Cart/Index.jsx new file mode 100644 index 0000000..61b5914 --- /dev/null +++ b/resources/js/Pages/Home/Cart/Index.jsx @@ -0,0 +1,80 @@ +import React from 'react' +import { Head, router } from '@inertiajs/react' +import CustomerLayout from '@/Layouts/CustomerLayout' +import VoucherCard from './VoucherCard' +import { formatIDR } from '@/utils' + +const EmptyHere = () => { + return ( +
+
+ Wah, keranjang belanjamu kosong +
+
+ Yuk, pilih paket voucher terbaik mu! +
+
+ ) +} + +export default function Index({ auth: { user }, carts, total }) { + const canPay = +user.deposit_balance >= +total + + const handleSubmit = () => { + router.post(route('cart.purchase')) + } + + const handleTopUp = () => { + router.get(route('customer.deposit.topup')) + } + + return ( + + +
+
Keranjang
+ + {carts.length > 0 ? ( + <> +
+ {carts.map((item) => ( + + ))} +
+
+
+ Saldo: {formatIDR(user.deposit_balance)} +
+
+
TOTAL
+
{formatIDR(total)}
+
+ {canPay ? ( +
+ Bayar +
+ ) : ( +
+
+ Saldo tidak cukup +
+
+ Top Up +
+
+ )} +
+ + ) : ( + + )} +
+
+ ) +} diff --git a/resources/js/Pages/Home/Cart/VoucherCard.jsx b/resources/js/Pages/Home/Cart/VoucherCard.jsx new file mode 100644 index 0000000..48a065e --- /dev/null +++ b/resources/js/Pages/Home/Cart/VoucherCard.jsx @@ -0,0 +1,74 @@ +import { formatIDR } from '@/utils' +import { router } from '@inertiajs/react' +import { HiMinusCircle, HiPlusCircle, HiTrash } from 'react-icons/hi2' + +export default function VoucherCard({ item: { voucher, quantity } }) { + const handleDelete = () => { + router.post(route('cart.store', { voucher: voucher, param: 'delete' })) + } + + const handleAdd = () => { + router.post(route('cart.store', { voucher: voucher, param: 'add' })) + } + + const handleSub = () => { + router.post(route('cart.store', { voucher: voucher, param: 'sub' })) + } + + return ( +
+
{voucher.location.name}
+
+
+
+
+ {voucher.profile} +
+
+ IDR {formatIDR(voucher.price)} +
+ {+voucher.discount !== 0 && ( +
+
+ {voucher.discount}% +
+
+ {formatIDR(voucher.display_price)} +
+
+ )} +
+
+
+ {voucher.display_quota} +
+
+ {voucher.display_expired} +
+
+
+
+
+
{formatIDR(voucher.price)}
+
x
+
{quantity}
+
{formatIDR(+voucher.price * +quantity)}
+
+
+ + +
{quantity}
+ +
+
+ ) +} diff --git a/resources/js/Pages/Home/Index/VoucherCard.jsx b/resources/js/Pages/Home/Index/VoucherCard.jsx index 32b00a9..4e2cf3d 100644 --- a/resources/js/Pages/Home/Index/VoucherCard.jsx +++ b/resources/js/Pages/Home/Index/VoucherCard.jsx @@ -1,8 +1,15 @@ import { formatIDR } from '@/utils' +import { router } from '@inertiajs/react' export default function VoucherCard({ voucher }) { + const addCart = () => { + router.post(route('cart.store', voucher)) + } return ( -
+
{voucher.location.name}
@@ -24,11 +31,11 @@ export default function VoucherCard({ voucher }) {
)}
-
+
{voucher.display_quota}
-
+
{voucher.display_expired}
diff --git a/resources/js/Pages/Home/Trx/Detail.jsx b/resources/js/Pages/Home/Trx/Detail.jsx new file mode 100644 index 0000000..e8407d3 --- /dev/null +++ b/resources/js/Pages/Home/Trx/Detail.jsx @@ -0,0 +1,39 @@ +import React from 'react' +import { Head, router } from '@inertiajs/react' +import CustomerLayout from '@/Layouts/CustomerLayout' +import VoucherCard from './VoucherCard' +import { HiChevronLeft } from 'react-icons/hi2' + +export default function Detail({ sale }) { + return ( + + +
+
{ + router.get(route('transactions.index')) + }} + > + +
+
+ Transaksi #{sale.code} +
+
{sale.format_created_at}
+ +
+ {sale.items.map((item) => ( + + ))} +
+
+
+
TOTAL
+
{sale.display_amount}
+
+
+
+
+ ) +} diff --git a/resources/js/Pages/Home/Trx/Index.jsx b/resources/js/Pages/Home/Trx/Index.jsx new file mode 100644 index 0000000..b7f87ac --- /dev/null +++ b/resources/js/Pages/Home/Trx/Index.jsx @@ -0,0 +1,71 @@ +import React, { useState } from 'react' +import { Head, router } from '@inertiajs/react' +import CustomerLayout from '@/Layouts/CustomerLayout' + +export default function Index({ query: { data, next_page_url } }) { + const [sales, setSales] = useState(data) + + const handleNextPage = () => { + router.get( + next_page_url, + {}, + { + replace: true, + preserveState: true, + only: ['query'], + onSuccess: (res) => { + setSales(sales.concat(res.props.query.data)) + }, + } + ) + } + + return ( + + +
+
+ Transaksi Pembelian +
+
+
+ {sales.map((sale) => ( +
+ router.get( + route('transactions.show', sale.id) + ) + } + > +
+
{sale.format_human_created_at}
+
+ Invoice{' '} + + #{sale.code} + +
+
+
+
+ {sale.display_amount} +
+
+
+ ))} + {next_page_url !== null && ( +
+ Load more +
+ )} +
+
+
+
+ ) +} diff --git a/resources/js/Pages/Home/Trx/VoucherCard.jsx b/resources/js/Pages/Home/Trx/VoucherCard.jsx new file mode 100644 index 0000000..d9b94ce --- /dev/null +++ b/resources/js/Pages/Home/Trx/VoucherCard.jsx @@ -0,0 +1,73 @@ +import { formatIDR } from '@/utils' +import { HiShare } from 'react-icons/hi2' + +export default function VoucherCard({ item: { voucher, quantity } }) { + return ( +
+
+
+ {voucher.location.name} +
+
{ + navigator.share({ + title: 'Hello World', + text: 'Hai Hai', + }) + }} + > + +
+
+
+
+
+
+ {voucher.profile} +
+
+ IDR {formatIDR(voucher.price)} +
+ {+voucher.discount !== 0 && ( +
+
+ {voucher.discount}% +
+
+ {formatIDR(voucher.display_price)} +
+
+ )} +
+
+
+ {voucher.display_quota} +
+
+ {voucher.display_expired} +
+
+
+
+
+
{formatIDR(voucher.price)}
+
x
+
{quantity}
+
{formatIDR(+voucher.price * +quantity)}
+
+
+
+
+ Username :{' '} + {voucher.username} +
+
+ Password :{' '} + {voucher.password} +
+
+
+
+ ) +} diff --git a/routes/api.php b/routes/api.php index 99854bf..1e1d5b2 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,7 +1,7 @@ name('customer.deposit.show'); Route::post('deposit/trx/{deposit}', [DepositController::class, 'update'])->name('customer.deposit.update'); - }); + // cart + Route::get('cart', [CartController::class, 'index'])->name('cart.index'); + Route::post('cart/process', [CartController::class, 'purchase'])->name('cart.purchase'); + Route::post('cart/{voucher}', [CartController::class, 'store'])->name('cart.store'); + + // transaction + Route::get('sale/trx', [TransactionController::class, 'index'])->name('transactions.index'); + Route::get('sale/trx/{sale}', [TransactionController::class, 'show'])->name('transactions.show'); + }); Route::middleware('guest:customer')->group(function () { // login