{{ __('checkout.delivery_method') }}
+ + @if ($delivery_method == 'shipping') +{{ __('checkout.delivery') }}
+ @if ($address) +{{ $address->location }}
+ @endif + @else +{{ __('checkout.pickup') }}
+ @endif +diff --git a/app/Http/Controllers/CheckoutController.php b/app/Http/Controllers/CheckoutController.php
new file mode 100644
index 0000000..ca79eb8
--- /dev/null
+++ b/app/Http/Controllers/CheckoutController.php
@@ -0,0 +1,159 @@
+merge(['location_id' => $request->input('location_id', session('location_id', 22))]);
+
+ $store = Location::find($request->input('location_id'));
+
+ $subtotal = $memberCartRepository->getSubtotal($request->input('location_id'));
+
+ $address_list = Address::where('user_id', $request->user()->id)->orderBy('is_primary','desc')->get();
+
+
+ $total = $subtotal;
+ return view('checkout.v1-delivery-1', [
+ 'carts' => $request->user()->carts,
+ 'subtotal' => $subtotal,
+ 'total' => $total,
+ 'store' => $store,
+ 'address_list' => $address_list,
+ ]);
+ }
+
+ public function indexProcess(Request $request)
+ {
+ $delivery_method = $request->input('delivery_method') ?? 'shipping';
+ $address_id = $request->input('address_id');
+
+ if ($address_id == null) {
+ $address_list = Address::where('user_id', $request->user()->id)->orderBy('is_primary','desc')->get();
+ $address_id = $address_list->first()->id;
+ }
+
+ if ($delivery_method == null || $address_id == null) {
+ return redirect()->back()->with('error', 'Delivery method or address is required');
+ }
+
+
+ if ($delivery_method == 'shipping') {
+ session(['checkout_delivery_method' => $delivery_method]);
+ session(['checkout_address_id' => $address_id]);
+ return redirect()->route('checkout.shipping');
+ }
+
+ if ($delivery_method == 'pickup') {
+ session(['checkout_delivery_method' => $delivery_method]);
+ session(['checkout_address_id' => null]);
+ return redirect()->route('checkout.payment');
+ }
+
+ return redirect()->back()->with('error', 'Delivery method is not valid');
+ }
+
+
+ public function chooseShipping(Request $request, MemberCartRepository $memberCartRepository)
+ {
+ try {
+ $delivery_method = $request->input('delivery_method');
+ $address_id = $request->input('address_id');
+
+ $subtotal = $memberCartRepository->getSubtotal($request->input('location_id'));
+ $total = $subtotal;
+
+
+
+ $shipping_list = [
+ [
+ 'courier' => 'JNE',
+ 'service' => 'REG',
+ 'title' => 'JNE Regular',
+ 'cost' => 10000,
+ ],
+ [
+ 'courier' => 'JNE',
+ 'service' => 'YES',
+ 'title' => 'JNE YES (Same Day)',
+ 'cost' => 25000,
+ ],
+ [
+ 'courier' => 'J&T',
+ 'service' => 'EZ',
+ 'title' => 'J&T Express',
+ 'cost' => 12000,
+ ],
+ [
+ 'courier' => 'SiCepat',
+ 'service' => 'REG',
+ 'title' => 'SiCepat Regular',
+ 'cost' => 11000,
+ ],
+ [
+ 'courier' => 'Gojek',
+ 'service' => 'Same Day',
+ 'title' => 'Gojek Same Day',
+ 'cost' => 20000,
+ ],
+ [
+ 'courier' => 'Grab',
+ 'service' => 'Instant',
+ 'title' => 'Grab Instant',
+ 'cost' => 22000,
+ ],
+ ];
+
+ return view('checkout.v1-delivery-1-shipping', [
+ 'carts' => $request->user()->carts,
+ 'subtotal' => $subtotal,
+ 'total' => $total,
+ 'delivery_method' => $delivery_method,
+ 'address_id' => $address_id,
+ 'address' => Address::find($address_id),
+ 'shipping_list' => $shipping_list,
+ ]);
+ } catch (\Exception $e) {
+ return redirect()->route('checkout.delivery')->with('error', 'Invalid checkout data');
+ }
+ }
+
+ public function chooseShippingProcess(Request $request)
+ {
+ $shipping_option = $request->input('shipping_option');
+
+ // Parse shipping option (format: courier|service|cost)
+ $shipping_data = explode('|', $shipping_option);
+ $courier = $shipping_data[0] ?? '';
+ $service = $shipping_data[1] ?? '';
+ $cost = $shipping_data[2] ?? 0;
+
+ session(['checkout_courier' => $courier]);
+ session(['checkout_service' => $service]);
+ session(['checkout_shipping_cost' => $cost]);
+
+ return redirect()->route('checkout.payment');
+ }
+
+ public function choosePayment(Request $request)
+ {
+ try {
+
+ // proses checkout
+
+
+ // proses payment
+
+
+ } catch (\Exception $e) {
+ return redirect()->route('checkout.delivery')->with('error', 'Invalid checkout data');
+ }
+ }
+}
diff --git a/app/Http/Requests/Member/Transaction/CancelRequest.php b/app/Http/Requests/Member/Transaction/CancelRequest.php
new file mode 100644
index 0000000..4e56fcf
--- /dev/null
+++ b/app/Http/Requests/Member/Transaction/CancelRequest.php
@@ -0,0 +1,31 @@
+transaction;
+ $user = auth()->user();
+ $isAdmin = auth()->user()->role->permissions->contains(function($value){
+ return $value->code == "transaction.online";
+ });
+ $isOwner = (@$transaction->customer->user->id == @$user->id) && ($transaction->status == 'WAIT_PAYMENT' || $transaction->status == 'WAIT_PROCESS');
+ return $isAdmin || $isOwner;
+ }
+
+ public function rules()
+ {
+ return [
+ 'note' => 'nullable|string',
+ ];
+ }
+}
diff --git a/app/Http/Requests/Member/Transaction/CloseRequest.php b/app/Http/Requests/Member/Transaction/CloseRequest.php
new file mode 100644
index 0000000..8e6b7bb
--- /dev/null
+++ b/app/Http/Requests/Member/Transaction/CloseRequest.php
@@ -0,0 +1,31 @@
+transaction;
+ $user = auth()->user();
+ $isAdmin = auth()->user()->role->permissions->contains(function($value){
+ return $value->code == "transaction.online";
+ });
+ $isOwner = (@$transaction->customer->user->id == @$user->id) && ($transaction->status == 'WAIT_PAYMENT' || $transaction->status == 'WAIT_PROCESS');
+ return $isAdmin || $isOwner;
+ }
+
+ public function rules()
+ {
+ return [
+ 'note' => 'nullable|string',
+ ];
+ }
+}
diff --git a/app/Http/Requests/Member/Transaction/DeliverRequest.php b/app/Http/Requests/Member/Transaction/DeliverRequest.php
new file mode 100644
index 0000000..c27bf70
--- /dev/null
+++ b/app/Http/Requests/Member/Transaction/DeliverRequest.php
@@ -0,0 +1,30 @@
+transaction;
+ $user = auth()->user();
+ $isAdmin = auth()->user()->role->permissions->contains(function($value){
+ return $value->code == "transaction.online";
+ });
+ return $isAdmin;
+ }
+
+ public function rules()
+ {
+ return [
+ 'note' => 'nullable|string',
+ ];
+ }
+}
diff --git a/app/Http/Requests/Member/Transaction/DetailRequest.php b/app/Http/Requests/Member/Transaction/DetailRequest.php
new file mode 100644
index 0000000..c38ef38
--- /dev/null
+++ b/app/Http/Requests/Member/Transaction/DetailRequest.php
@@ -0,0 +1,31 @@
+transaction;
+ $user = auth()->user();
+ $isAdmin = auth()->user()->role->permissions->contains(function($value){
+ return $value->code == "transaction.online";
+ });
+ $isOwner = (@$transaction->customer->user->id == @$user->id);
+ return $isAdmin || $isOwner;
+ }
+
+ public function rules()
+ {
+ return [
+ 'note' => 'nullable|string',
+ ];
+ }
+}
diff --git a/app/Http/Requests/Member/Transaction/ProcessRequest.php b/app/Http/Requests/Member/Transaction/ProcessRequest.php
new file mode 100644
index 0000000..5788c7d
--- /dev/null
+++ b/app/Http/Requests/Member/Transaction/ProcessRequest.php
@@ -0,0 +1,30 @@
+transaction;
+ $user = auth()->user();
+ $isAdmin = auth()->user()->role->permissions->contains(function($value){
+ return $value->code == "transaction.online";
+ });
+ return $isAdmin;
+ }
+
+ public function rules()
+ {
+ return [
+ 'note' => 'nullable|string',
+ ];
+ }
+}
diff --git a/app/Http/Requests/Member/Transaction/TransactionRequest.php b/app/Http/Requests/Member/Transaction/TransactionRequest.php
new file mode 100644
index 0000000..ab26f98
--- /dev/null
+++ b/app/Http/Requests/Member/Transaction/TransactionRequest.php
@@ -0,0 +1,34 @@
+ 'required|integer|exists:address,id',
+ 'location_id' => 'required|integer',
+ 'courier_company' => 'required|string',
+ 'courier_type' => 'required|string',
+ 'vouchers' => 'nullable|array',
+ 'vouchers.*' => 'required|integer',
+ 'items' => 'nullable|array',
+ 'items.*.item_reference_id' => 'required|integer',
+ 'items.*.qty' => 'required|integer',
+ 'use_customer_points' => 'nullable|integer',
+ ];
+ }
+}
diff --git a/app/Models/LuckyWheel.php b/app/Models/LuckyWheel.php
new file mode 100644
index 0000000..a5c32f9
--- /dev/null
+++ b/app/Models/LuckyWheel.php
@@ -0,0 +1,23 @@
+hasMany(LuckyWheelPrize::class);
+ }
+
+ public function gets(){
+ return $this->hasMany(LuckyWheelGet::class);
+ }
+
+ public function tickets(){
+ return $this->hasMany(LuckyWheelTicket::class);
+ }
+}
diff --git a/app/Models/LuckyWheelGet.php b/app/Models/LuckyWheelGet.php
new file mode 100644
index 0000000..4c64e2b
--- /dev/null
+++ b/app/Models/LuckyWheelGet.php
@@ -0,0 +1,21 @@
+belongsTo(LuckyWheelPrize::class,'prize_id');
+ }
+
+ public function voucher(){
+ return $this->belongsTo(Voucher::class,'voucher_id');
+ }
+}
\ No newline at end of file
diff --git a/app/Models/LuckyWheelPrize.php b/app/Models/LuckyWheelPrize.php
new file mode 100644
index 0000000..d4c1269
--- /dev/null
+++ b/app/Models/LuckyWheelPrize.php
@@ -0,0 +1,28 @@
+belongsTo(Voucher::class);
+ }
+
+ public function voucherEvent(){
+ return $this->belongsTo(VoucherEvent::class);
+ }
+
+ public function gets(){
+ return $this->hasMany(LuckyWheelGet::class,"prize_id")
+ ->whereDate("created_at", Carbon::now());
+ }
+
+}
diff --git a/app/Models/LuckyWheelTicket.php b/app/Models/LuckyWheelTicket.php
new file mode 100644
index 0000000..a70dc51
--- /dev/null
+++ b/app/Models/LuckyWheelTicket.php
@@ -0,0 +1,38 @@
+belongsTo(LuckyWheel::class);
+ }
+
+ public function customer()
+ {
+ return $this->belongsTo(Customer::class);
+ }
+
+ public function prize()
+ {
+ return $this->hasOne(LuckyWheelPrize::class,"ticket_id")->with('voucher');
+ }
+
+ public function gets()
+ {
+ return $this->hasMany(LuckyWheelGet::class,"ticket_id")->with("prize","voucher")->orderBy("created_at","desc");
+ }
+
+ public function get()
+ {
+ return $this->hasOne(LuckyWheelGet::class,"ticket_id")->whereNotNull("redeem_at")->with("voucher","prize");
+ }
+}
\ No newline at end of file
diff --git a/app/Models/PosInvoice.php b/app/Models/PosInvoice.php
new file mode 100644
index 0000000..5049db3
--- /dev/null
+++ b/app/Models/PosInvoice.php
@@ -0,0 +1,82 @@
+hasOne(LuckyWheelTicket::class, 'invoice_id')->with("prize","gets");
+ }
+
+ public function customer(){
+ return $this->belongsTo(Customer::class);
+ }
+
+ public function sales(){
+ return $this->belongsTo(Sales::class);
+ }
+
+ public function sales2(){
+ return $this->belongsTo(Sales::class, 'sales2_id', 'id');
+ }
+
+ public function location(){
+ return $this->belongsTo(Location::class);
+ }
+
+ public function user(){
+ return $this->belongsTo(User::class);
+ }
+
+ public function details(){
+ return $this->hasMany(PosInvoiceDetail::class,"invoice_id")
+ ->orderBy("line_no","asc");
+ }
+
+ public function payments(){
+ return $this->hasMany(PosInvoicePayment::class,"invoice_id");
+ }
+
+ public function canceledBy(){
+ return $this->belongsTo(User::class,"canceled_by");
+ }
+
+ public function vouchers(){
+ return $this->morphMany(Voucher::class,'reference_used');
+ }
+
+ public function discountVouchers(){
+ return $this->hasMany(PosInvoiceVoucher::class, 'pos_invoice_id');
+ }
+
+ public function getPoint() {
+ $total = CustomerPoint::where('reference_type', 'App\Models\PosInvoice')
+ ->where('reference_id', $this->id)
+ ->sum('point');
+
+ return $total;
+ }
+
+ public function feedback(){
+ return $this->hasOne(SurveyFeedback::class,"invoice_id");
+ }
+}
diff --git a/app/Models/PosInvoiceDetail.php b/app/Models/PosInvoiceDetail.php
new file mode 100644
index 0000000..783e304
--- /dev/null
+++ b/app/Models/PosInvoiceDetail.php
@@ -0,0 +1,35 @@
+belongsTo(ItemReference::class);
+ }
+
+ public function item()
+ {
+ return $this->belongsTo(Items::class);
+ }
+
+ public function itemVariant()
+ {
+ return $this->belongsTo(ItemVariant::class);
+ }
+
+ public function invoice()
+ {
+ return $this->belongsTo(PosInvoice::class, 'invoice_id');
+ }
+}
diff --git a/app/Models/PosInvoicePayment.php b/app/Models/PosInvoicePayment.php
new file mode 100644
index 0000000..4c14c0e
--- /dev/null
+++ b/app/Models/PosInvoicePayment.php
@@ -0,0 +1,18 @@
+belongsTo(Bank::class, 'bank_id', 'id');
+ }
+}
diff --git a/app/Models/PosInvoiceVoucher.php b/app/Models/PosInvoiceVoucher.php
new file mode 100644
index 0000000..0f480c8
--- /dev/null
+++ b/app/Models/PosInvoiceVoucher.php
@@ -0,0 +1,29 @@
+belongsTo(PosInvoice::class, 'pos_invoice_id');
+ }
+
+ public function voucher()
+ {
+ return $this->belongsTo(Voucher::class, 'voucher_id');
+ }
+}
diff --git a/app/Models/Sales.php b/app/Models/Sales.php
new file mode 100644
index 0000000..70ce2a8
--- /dev/null
+++ b/app/Models/Sales.php
@@ -0,0 +1,70 @@
+ [
+ 'source' => 'number'
+ ]
+ ];
+ }
+
+ public function scopeFilter(Builder $query, array $filters)
+ {
+ $query->when($filters['search'] ?? false, function ($query, $search) {
+ return $query
+ ->where('name', 'iLIKE', '%' . $search . '%')
+ ->orWhere('phone', 'LIKE', '%' . $search . '%');
+ });
+ }
+
+ public function locations()
+ {
+ return $this->belongsTo(Location::class, 'location_id', 'id');
+ }
+
+ public function customerGroup()
+ {
+ return $this->belongsTo(CustomerGroup::class, 'customer_group_id', 'id');
+ }
+}
diff --git a/app/Models/SerialNumber.php b/app/Models/SerialNumber.php
new file mode 100644
index 0000000..49498cd
--- /dev/null
+++ b/app/Models/SerialNumber.php
@@ -0,0 +1,25 @@
+hasMany(SerialNumberDetail::class, 'sn_batch_id', 'id');
+ }
+
+ public function user() {
+ return $this->belongsTo(User::class);
+ }
+}
diff --git a/app/Models/SerialNumberDetail.php b/app/Models/SerialNumberDetail.php
new file mode 100644
index 0000000..ad7bd30
--- /dev/null
+++ b/app/Models/SerialNumberDetail.php
@@ -0,0 +1,27 @@
+belongsTo(SerialNumber::class,"sn_batch_id");
+ }
+
+ public function invoice(){
+ return $this->belongsTo(PosInvoice::class,"invoice_id");
+ }
+
+ public function activatedBy(){
+ return $this->belongsTo(User::class,"activated_by");
+ }
+}
diff --git a/app/Models/SerialNumberLog.php b/app/Models/SerialNumberLog.php
new file mode 100644
index 0000000..5e9fecb
--- /dev/null
+++ b/app/Models/SerialNumberLog.php
@@ -0,0 +1,14 @@
+hasMany(SurveyQuestion::class);
+ }
+
+ public function voucherEvent() {
+ return $this->belongsTo(VoucherEvent::class);
+ }
+}
diff --git a/app/Models/SurveyFeedback.php b/app/Models/SurveyFeedback.php
new file mode 100644
index 0000000..10a514c
--- /dev/null
+++ b/app/Models/SurveyFeedback.php
@@ -0,0 +1,29 @@
+hasMany(SurveyFeedbackDetail::class);
+ }
+
+ public function survey() {
+ return $this->belongsTo(Survey::class);
+ }
+
+ public function customer() {
+ return $this->belongsTo(Customer::class);
+ }
+
+ public function invoice() {
+ return $this->belongsTo(PosInvoice::class);
+ }
+}
diff --git a/app/Models/SurveyFeedbackDetail.php b/app/Models/SurveyFeedbackDetail.php
new file mode 100644
index 0000000..198dcf3
--- /dev/null
+++ b/app/Models/SurveyFeedbackDetail.php
@@ -0,0 +1,13 @@
+morphOne(TransactionPayment::class,"method");
+ }
+}
diff --git a/app/Notifications/FcmChannel.php b/app/Notifications/FcmChannel.php
new file mode 100644
index 0000000..042c2db
--- /dev/null
+++ b/app/Notifications/FcmChannel.php
@@ -0,0 +1,57 @@
+toFcm($notifiable);
+
+ // if ($notifiable->fcm_token != null){
+ // $this->sendNotification($payload, $notifiable->fcm_token, $notifiable);
+ // }
+
+ // foreach($notifiable->devices as $device){
+ // if ($device->fcm_token == $notifiable->fcm_token)
+ // continue;
+
+ // $this->sendNotification($payload, $device->fcm_token, $device);
+ // sleep(0.5);
+ // }
+ }
+
+ private function sendNotification($payload, $fcm_token, $device){
+
+ // if (!$fcm_token)
+ // return;
+
+ // try{
+ // $payload["token"] = $fcm_token;
+ // $messaging = app('firebase.messaging');
+ // $message = CloudMessage::fromArray($payload);
+ // $result = $messaging->send($message);
+ // }catch(\Kreait\Firebase\Exception\Messaging\NotFound $e){
+ // if (get_class($device) == UserDevice::class){
+ // $device->fcm_token = null;
+ // $device->save();
+ // }else if (get_class($device) == User::class){
+ // $device->fcm_token = null;
+ // $device->save();
+ // }
+ // }catch(\Kreait\Firebase\Exception\Messaging\InvalidMessage $e){
+ // \Log::info([$fcm_token, $e->getMessage()]);
+ // }catch(\Exception $e){
+ // report($e);
+ // }
+
+ }
+}
diff --git a/app/Notifications/Member/Transaction/NewOrder.php b/app/Notifications/Member/Transaction/NewOrder.php
new file mode 100644
index 0000000..b8ec578
--- /dev/null
+++ b/app/Notifications/Member/Transaction/NewOrder.php
@@ -0,0 +1,83 @@
+transaction = $transaction;
+ }
+
+ /**
+ * Get the notification's delivery channels.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function via($notifiable)
+ {
+ return ['database',FcmChannel::class];
+ }
+
+ /**
+ * Get the mail representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return \Illuminate\Notifications\Messages\MailMessage
+ */
+ public function toMail($notifiable)
+ {
+ return (new MailMessage)
+ ->line('The introduction to the notification.')
+ ->action('Notification Action', url('/'))
+ ->line('Thank you for using our application!');
+ }
+
+ /**
+ * Get the array representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function toArray($notifiable)
+ {
+ $transaction = $this->transaction;
+ return [
+ "title" => "Ada Pesanan baru masuk!",
+ "body" => "Mohon segera proses pesanan nomor $transaction->number",
+ "type" => "Info",
+ "data" => $this->transaction,
+ "model" => get_class($this->transaction)
+ ];
+ }
+
+ public function toFcm($notifiable)
+ {
+ $payload = $this->toArray($notifiable);
+
+ return [
+ "notification" => [
+ "title" => $payload["title"],
+ "body" => $payload["body"],
+ ]
+ ];
+ }
+}
diff --git a/app/Notifications/Member/Transaction/OrderCanceled.php b/app/Notifications/Member/Transaction/OrderCanceled.php
new file mode 100644
index 0000000..54fa21e
--- /dev/null
+++ b/app/Notifications/Member/Transaction/OrderCanceled.php
@@ -0,0 +1,84 @@
+transaction = $transaction;
+ }
+
+ /**
+ * Get the notification's delivery channels.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function via($notifiable)
+ {
+ return ['database',FcmChannel::class];
+ }
+
+ /**
+ * Get the mail representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return \Illuminate\Notifications\Messages\MailMessage
+ */
+ public function toMail($notifiable)
+ {
+ return (new MailMessage)
+ ->line('The introduction to the notification.')
+ ->action('Notification Action', url('/'))
+ ->line('Thank you for using our application!');
+ }
+
+ /**
+ * Get the array representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function toArray($notifiable)
+ {
+ $transaction = $this->transaction;
+ $status = $transaction->statuses()->where("status","CANCELED")->first();
+ return [
+ "title" => "Pesanan telah dibatalkan!",
+ "body" => "Pesanan $transaction->number telah dibatalkan $status->note",
+ "type" => "Info",
+ "data" => $this->transaction,
+ "model" => get_class($this->transaction)
+ ];
+ }
+
+ public function toFcm($notifiable)
+ {
+ $payload = $this->toArray($notifiable);
+
+ return [
+ "notification" => [
+ "title" => $payload["title"],
+ "body" => $payload["body"],
+ ]
+ ];
+ }
+}
diff --git a/app/Notifications/Member/Transaction/OrderDelivered.php b/app/Notifications/Member/Transaction/OrderDelivered.php
new file mode 100644
index 0000000..3330934
--- /dev/null
+++ b/app/Notifications/Member/Transaction/OrderDelivered.php
@@ -0,0 +1,83 @@
+transaction = $transaction;
+ }
+
+ /**
+ * Get the notification's delivery channels.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function via($notifiable)
+ {
+ return ['database',FcmChannel::class];
+ }
+
+ /**
+ * Get the mail representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return \Illuminate\Notifications\Messages\MailMessage
+ */
+ public function toMail($notifiable)
+ {
+ return (new MailMessage)
+ ->line('The introduction to the notification.')
+ ->action('Notification Action', url('/'))
+ ->line('Thank you for using our application!');
+ }
+
+ /**
+ * Get the array representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function toArray($notifiable)
+ {
+ $transaction = $this->transaction;
+ return [
+ "title" => "Pesanan sudah tiba!",
+ "body" => "Pesanan $transaction->number sudah tiba di alamat tujuan",
+ "type" => "Info",
+ "data" => $this->transaction,
+ "model" => get_class($this->transaction)
+ ];
+ }
+
+ public function toFcm($notifiable)
+ {
+ $payload = $this->toArray($notifiable);
+
+ return [
+ "notification" => [
+ "title" => $payload["title"],
+ "body" => $payload["body"],
+ ]
+ ];
+ }
+}
diff --git a/app/Notifications/Member/Transaction/OrderOnDelivery.php b/app/Notifications/Member/Transaction/OrderOnDelivery.php
new file mode 100644
index 0000000..e4cc50a
--- /dev/null
+++ b/app/Notifications/Member/Transaction/OrderOnDelivery.php
@@ -0,0 +1,83 @@
+transaction = $transaction;
+ }
+
+ /**
+ * Get the notification's delivery channels.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function via($notifiable)
+ {
+ return ['database',FcmChannel::class];
+ }
+
+ /**
+ * Get the mail representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return \Illuminate\Notifications\Messages\MailMessage
+ */
+ public function toMail($notifiable)
+ {
+ return (new MailMessage)
+ ->line('The introduction to the notification.')
+ ->action('Notification Action', url('/'))
+ ->line('Thank you for using our application!');
+ }
+
+ /**
+ * Get the array representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function toArray($notifiable)
+ {
+ $transaction = $this->transaction;
+ return [
+ "title" => "Pesanan sedang dalam pengiriman!",
+ "body" => "Pesanan $transaction->number sedang dikirim menuju alamat tujuan",
+ "type" => "Info",
+ "data" => $this->transaction,
+ "model" => get_class($this->transaction)
+ ];
+ }
+
+ public function toFcm($notifiable)
+ {
+ $payload = $this->toArray($notifiable);
+
+ return [
+ "notification" => [
+ "title" => $payload["title"],
+ "body" => $payload["body"],
+ ]
+ ];
+ }
+}
diff --git a/app/Notifications/Member/Transaction/OrderPaid.php b/app/Notifications/Member/Transaction/OrderPaid.php
new file mode 100644
index 0000000..a603a95
--- /dev/null
+++ b/app/Notifications/Member/Transaction/OrderPaid.php
@@ -0,0 +1,83 @@
+transaction = $transaction;
+ }
+
+ /**
+ * Get the notification's delivery channels.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function via($notifiable)
+ {
+ return ['database',FcmChannel::class];
+ }
+
+ /**
+ * Get the mail representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return \Illuminate\Notifications\Messages\MailMessage
+ */
+ public function toMail($notifiable)
+ {
+ return (new MailMessage)
+ ->line('The introduction to the notification.')
+ ->action('Notification Action', url('/'))
+ ->line('Thank you for using our application!');
+ }
+
+ /**
+ * Get the array representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function toArray($notifiable)
+ {
+ $transaction = $this->transaction;
+ return [
+ "title" => "Pembayaran Berhasil",
+ "body" => "Terima kasih, Pesanan $transaction->number akan segera kami proses",
+ "type" => "Info",
+ "data" => $this->transaction,
+ "model" => get_class($this->transaction)
+ ];
+ }
+
+ public function toFcm($notifiable)
+ {
+ $payload = $this->toArray($notifiable);
+
+ return [
+ "notification" => [
+ "title" => $payload["title"],
+ "body" => $payload["body"],
+ ]
+ ];
+ }
+}
diff --git a/app/Notifications/Member/Transaction/OrderProcessed.php b/app/Notifications/Member/Transaction/OrderProcessed.php
new file mode 100644
index 0000000..edda130
--- /dev/null
+++ b/app/Notifications/Member/Transaction/OrderProcessed.php
@@ -0,0 +1,83 @@
+transaction = $transaction;
+ }
+
+ /**
+ * Get the notification's delivery channels.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function via($notifiable)
+ {
+ return ['database',FcmChannel::class];
+ }
+
+ /**
+ * Get the mail representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return \Illuminate\Notifications\Messages\MailMessage
+ */
+ public function toMail($notifiable)
+ {
+ return (new MailMessage)
+ ->line('The introduction to the notification.')
+ ->action('Notification Action', url('/'))
+ ->line('Thank you for using our application!');
+ }
+
+ /**
+ * Get the array representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function toArray($notifiable)
+ {
+ $transaction = $this->transaction;
+ return [
+ "title" => "Pesanan telah diproses!",
+ "body" => "Pesanan $transaction->number telah diproses akan segera dikirim",
+ "type" => "Info",
+ "data" => $this->transaction,
+ "model" => get_class($this->transaction)
+ ];
+ }
+
+ public function toFcm($notifiable)
+ {
+ $payload = $this->toArray($notifiable);
+
+ return [
+ "notification" => [
+ "title" => $payload["title"],
+ "body" => $payload["body"],
+ ]
+ ];
+ }
+}
diff --git a/app/Notifications/Member/Transaction/OrderWaitPayment.php b/app/Notifications/Member/Transaction/OrderWaitPayment.php
new file mode 100644
index 0000000..8542b94
--- /dev/null
+++ b/app/Notifications/Member/Transaction/OrderWaitPayment.php
@@ -0,0 +1,95 @@
+transaction = $transaction;
+ }
+
+ /**
+ * Get the notification's delivery channels.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function via($notifiable)
+ {
+ if ($this->dontSend($notifiable)) {
+ return [];
+ }
+
+ return ['database',
+ FcmChannel::class,
+
+ ];
+ }
+
+ public function dontSend($notifiable)
+ {
+ return $this->transaction->status != 'WAIT_PAYMENT';
+ }
+
+ /**
+ * Get the mail representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return \Illuminate\Notifications\Messages\MailMessage
+ */
+ public function toMail($notifiable)
+ {
+ return (new MailMessage)
+ ->line('The introduction to the notification.')
+ ->action('Notification Action', url('/'))
+ ->line('Thank you for using our application!');
+ }
+
+ /**
+ * Get the array representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function toArray($notifiable)
+ {
+ $transaction = $this->transaction;
+
+ return [
+ 'title' => 'Menunggu Pembayaran',
+ 'body' => "Silahkan lakukan pembayaran untuk pesanan $transaction->number",
+ 'type' => 'Info',
+ 'data' => $this->transaction,
+ 'model' => get_class($this->transaction),
+ ];
+ }
+
+ public function toFcm($notifiable)
+ {
+ $payload = $this->toArray($notifiable);
+
+ return [
+ 'notification' => [
+ 'title' => $payload['title'],
+ 'body' => $payload['body'],
+ ],
+ ];
+ }
+}
diff --git a/app/Repositories/Crm/SurveyRepository.php b/app/Repositories/Crm/SurveyRepository.php
new file mode 100644
index 0000000..7c087cd
--- /dev/null
+++ b/app/Repositories/Crm/SurveyRepository.php
@@ -0,0 +1,219 @@
+voucherEventRepository = $voucherEventRepository;
+ }
+
+ public function getList(array $params = [])
+ {
+ $limit = @$params["limit"] ? (int) @$params["limit"] : 10;
+ $sortColumn = @$params["sort"]["column"] ? $params["sort"]["column"] : "id";
+ $sortDir = @$params["sort"]["dir"] ? $params["sort"]["dir"] : "desc";
+
+ $results = Survey::when(@$params["search"], function($query) use ($params){
+ $query->where("name","ilike","%". $params["search"] . "%")
+ ->where("code","ilike","%". $params["search"] . "%");
+ })
+ ->orderBy($sortColumn, $sortDir)
+ ->paginate($limit);
+
+ return $results;
+ }
+
+ public function getListDetail(Survey $survey, array $params = [])
+ {
+ $limit = @$params["limit"] ? (int) @$params["limit"] : 10;
+ $sortColumn = @$params["sort"]["column"] ? $params["sort"]["column"] : "id";
+ $sortDir = @$params["sort"]["dir"] ? $params["sort"]["dir"] : "desc";
+
+ $results = SurveyQuestion::where('survey_id', $survey->id)
+ ->when(@$params["search"], function($query) use ($params){
+ $query->where("description","ilike","%". $params["search"] . "%");
+ })
+ ->orderBy($sortColumn, $sortDir)
+ ->paginate($limit);
+
+ return $results;
+ }
+
+ public function createFeedback(Survey $survey, array $data) {
+ do {
+ $code = md5(Str::random(40));
+ } while(SurveyFeedback::where('code', $code)->first());
+
+ $feedback = SurveyFeedback::create([
+ 'code' => $code,
+ 'survey_id' => $survey->id,
+ 'channel' => @$data['channel'],
+ 'customer_id' => @$data['customer_id'],
+ 'invoice_id' => @$data['invoice_id'],
+ 'ip_address' => @$data['ip_address'],
+ 'agent' => @$data['agent'],
+ 'time' => Carbon::now()
+ ]);
+
+ foreach($data['details'] as $detail) {
+ $surveyDetail = $survey->detail->where('id', $detail['survey_question_id'])->first();
+
+ $feedback->detail()->create([
+ 'description' => @$surveyDetail->description,
+ 'value' => $detail['value'],
+ 'survey_question_id' => $detail['survey_question_id']
+ ]);
+ }
+
+ if ($feedback->customer){
+ $notif = new SurveyRespond($feedback);
+ $feedback->customer->notify($notif);
+ }
+
+ return $feedback;
+ }
+
+ public function generateFeedback(Survey $survey, array $data) {
+ do {
+ $code = md5(Str::random(40));
+ } while(SurveyFeedback::where('code', $code)->first());
+
+ if (@$data['customer_id'])
+ {
+ $last_feedback = SurveyFeedback::where("customer_id", @$data['customer_id'])
+ ->whereDate("created_at",">=", Carbon::now()->subMonth(1))
+ ->first();
+ if ($last_feedback)
+ {
+ return null;
+ }
+ }
+
+ $feedback = SurveyFeedback::create([
+ 'code' => $code,
+ 'survey_id' => $survey->id,
+ 'channel' => @$data['channel'],
+ 'customer_id' => @$data['customer_id'],
+ 'invoice_id' => @$data['invoice_id']
+ ]);
+
+ return $feedback;
+ }
+
+ public function create(array $data)
+ {
+ do {
+ $code = md5(Str::random(40));
+ } while(Survey::where('code', $code)->first());
+
+ $item = Survey::create([
+ 'name' => $data['name'],
+ 'code' => md5(Str::random(40)),
+ 'title' => @$data['title'],
+ 'content' => @$data['content'],
+ 'voucher_event_id' => @$data['voucher_event_id']
+ ]);
+
+ return $item;
+ }
+
+ public function createDetail(Survey $survey, array $data)
+ {
+ $item = SurveyQuestion::create([
+ 'survey_id' => $survey->id,
+ 'description' => $data['description'],
+ 'type' => $data['type'],
+ 'data' => $data['data'],
+ 'order' => $data['order']
+ ]);
+
+ return $item;
+ }
+
+ public function update(Survey $item, array $data)
+ {
+ $item->update($data);
+ return $item;
+ }
+
+ public function updateDetail(SurveyQuestion $item, array $data)
+ {
+ $item->update($data);
+ return $item;
+ }
+
+ public function updateFeedbackDetail(SurveyFeedback $item, array $data)
+ {
+ if ($item->time == null){
+ $invoice = @$item->invoice;
+ $voucherEvent = @$item->survey->voucherEvent;
+ if ($invoice && $voucherEvent){
+ $expired = Carbon::now()->addDay(30);
+ $voucher = $this->voucherEventRepository->createVoucher($voucherEvent, $invoice->customer, $invoice->customer->user, "FREE VOUCHER SURVEY GIFT", $expired, 400000);
+
+ if ($voucher){
+
+ $notif = new SurveyRespond($item, $voucher);
+ $invoice->customer->notify($notif);
+ }
+ }
+ }
+ $item->update([
+ "time" => Carbon::now(),
+ 'ip_address' => @$data['ip_address'],
+ 'agent' => @$data['agent'],
+ ]);
+
+ foreach($data['details'] as $detail) {
+ $feedbackDetail = $item->detail()->where('survey_question_id', $detail['survey_question_id'])->first();
+ $surveyDetail = SurveyQuestion::where('id', $detail['survey_question_id'])->first();
+
+ if($feedbackDetail) {
+ $feedbackDetail->update([
+ 'description' => @$surveyDetail->description,
+ 'value' => $detail['value']
+ ]);
+ } else {
+ $item->detail()->create([
+ 'survey_question_id' => $detail['survey_question_id'],
+ 'description' => @$surveyDetail->description,
+ 'value' => $detail['value']
+ ]);
+ }
+ }
+
+ return $item;
+ }
+
+ public function delete(Survey $item)
+ {
+ $item->delete();
+ }
+
+ public function deleteDetail(SurveyQuestion $item)
+ {
+ $item->delete();
+ }
+
+ public function findBy($column, $value)
+ {
+ $item = Survey::where($column, $value)->firstOrFail();
+ return $item;
+ }
+
+}
diff --git a/app/Repositories/Member/Cart/MemberCartRepository.php b/app/Repositories/Member/Cart/MemberCartRepository.php
index ad986d2..2c4b7a0 100644
--- a/app/Repositories/Member/Cart/MemberCartRepository.php
+++ b/app/Repositories/Member/Cart/MemberCartRepository.php
@@ -16,6 +16,18 @@ class MemberCartRepository
->sum('qty');
}
+ static public function getSubtotal($locationId = null)
+ {
+ $location = $locationId ?? session('location_id', 22);
+ return Cart::where('user_id', auth()->user()->id)
+ ->where('location_id', $location)
+ ->get()
+ ->map(function($row){
+ return $row->qty * $row->display_price;
+ })
+ ->sum();
+ }
+
static public function clearAll($locationId = null)
{
$location = $locationId ?? session('location_id', 22);
diff --git a/app/Repositories/Member/ShippingRepository.php b/app/Repositories/Member/ShippingRepository.php
new file mode 100644
index 0000000..9de053d
--- /dev/null
+++ b/app/Repositories/Member/ShippingRepository.php
@@ -0,0 +1,255 @@
+biteship = $biteship;
+ }
+
+ public function calcTotal($params){
+ $user = auth()->user();
+ $location = Location::findOrFail(@$params["location_id"]);
+ $items = Cart::where("user_id", $user->id)
+ ->where("location_id", $location->id)
+ ->get();
+ $total_amount = 0;
+ foreach($items as $detail){
+ $convertion = 1;
+ if ($detail->item->display_unit and $detail->item->display_unit != $detail->item->unit){
+ $convertions = DB::select("select to_qty / from_qty as conv from item_convertions where from_unit = ? and to_unit = ?",
+[$detail->item->display_unit, $detail->item->unit]);
+ $convertion = max((float) @$convertions[0]->conv,1);
+ }
+ $d_price = @$detail->itemReference->discount->price ?? 0;
+ $s_price = @$detail->itemReference->price->price ?? 0;
+ $price = ( $s_price ? $s_price : $detail->item->net_price) * $convertion;
+ $price = ( $d_price ? $d_price : $price) * $convertion;
+ $total = $detail->qty * $price;
+ $total_amount = $total_amount + $total;
+ }
+ return $total_amount;
+
+ }
+ public function getList($params)
+ {
+ $biteship = $this->biteship;
+ $location = Location::findOrFail(@$params["location_id"]);
+ $address = Address::findOrFail(@$params["address_id"]);
+
+ if (!$location->postal_code){
+ throw ValidationException::withMessages([
+ "location_id" => "Data gudang tidak memiliki informasi kode pos"
+ ]);
+ }
+
+ if (!$location->latitude || !$location->longitude){
+ throw ValidationException::withMessages([
+ "location_id" => "Data gudang tidak memiliki informasi lat long"
+ ]);
+ }
+
+ $hasLatLong = $address->latitude != null && $address->longitude != null;
+ $items = collect(@$params["items"] ?? []);
+
+
+ $user = auth()->user();
+
+ $items = collect(@$data["items"] ?? []);
+
+ $items = Cart::where("user_id", $user->id)
+ ->where("location_id", $location->id)
+ ->when(count($items) > 0, function($query) use ($items){
+ $query->whereIn("item_reference_id", $items->pluck("item_reference_id"));
+ })
+ ->get();
+
+ $items = $items->map(function($cart){
+ if (((float) @$cart->item->weight) <= 0){
+ throw ValidationException::withMessages([
+ "location_id" => "Berat ada yang kosong"
+ ]);
+ }
+ return [
+ "weight" => max(@$cart->item->weight,0.001),
+ "quantity" => @$cart->qty,
+ "value" => @$cart->item->net_price,
+ "description" => @$cart->itemVariant->description ?? @$cart->item->name,
+ "name" => @$cart->item->category->name ?? "GOODS"
+ ];
+ });
+
+ if ($hasLatLong){
+ return $biteship->rateByLatLong([
+ "origin_latitude" => $location->latitude,
+ "origin_longitude" => $location->longitude,
+ "destination_latitude" => $address->latitude,
+ "destination_longitude" => $address->longitude,
+ "items" => $items
+ ]);
+ }else{
+ return $biteship->rateByPostal([
+ "origin_postal_code" => $location->postal_code,
+ "destination_postal_code" => $address->postal_code,
+ "items" => $items
+ ]);
+ }
+ }
+
+
+ public function order(Transaction $transaction)
+ {
+ $biteship = $this->biteship;
+ $location = $transaction->location;
+ $address = $transaction->address;
+
+ if (!$location->postal_code){
+ throw ValidationException::withMessages([
+ "location_id" => "Data gudang tidak memiliki informasi kode pos"
+ ]);
+ }
+
+ if (!$location->latitude || !$location->longitude){
+ throw ValidationException::withMessages([
+ "location_id" => "Data gudang tidak memiliki informasi lat long"
+ ]);
+ }
+
+ $hasLatLong = $address->latitude != null && $address->longitude != null;
+ $user = auth()->user();
+ $items = $transaction->details;
+ $items = $items->map(function($cart){
+ if (((float) @$cart->item->weight) == 0){
+ throw ValidationException::withMessages([
+ "items" => "Berat item wajib diisi!"
+ ]);
+ }
+
+ return [
+ "weight" => max(@$cart->item->weight,0.001),
+ "quantity" => @$cart->qty,
+ "value" => @$cart->item->net_price,
+ "description" => @$cart->itemVariant->description ?? @$cart->item->name,
+ "name" => @$cart->item->category->name ?? "GOODS"
+ ];
+ });
+ $subtotal = $items->reduce(function($acc, $cart){
+ return $acc + ( @$cart->qty * @$cart->item->net_price);
+ },0);
+
+ if ($hasLatLong){
+ return $biteship->orderByLatLong(
+ [
+ "origin_contact_name" => $location->display_name,
+ "origin_contact_phone" => $location->phone,
+ "origin_address" => $location->address,
+ "origin_latitude" => $location->latitude,
+ "origin_longitude" => $location->longitude,
+
+ "destination_contact_name" => $address->name,
+ "destination_contact_phone" => $address->phone,
+ "destination_latitude" => $address->latitude,
+ "destination_longitude" => $address->longitude,
+ "destination_address" => $address->address,
+
+ "reference_id" => $transaction->number,
+
+ "courier_insurance" => $subtotal,
+ "courier_company" => $transaction->courier_company,
+ "courier_type" => $transaction->courier_type,
+ "items" => $items
+ ]
+ );
+ }else{
+ $destination_address = $address->address;
+ if (@$address->subdistrict->name)
+ $destination_address .= "," .@$address->subdistrict->name;
+ if (@$address->district->name)
+ $destination_address .= "," .@$address->district->name;
+ if (@$address->city->name)
+ $destination_address .= "," .@$address->city->name;
+ if (@$address->province->name)
+ $destination_address .= "," .@$address->province->name;
+ return $biteship->orderByPostal(
+ [
+ "origin_contact_name" => $location->display_name,
+ "origin_contact_phone" => $location->phone,
+ "origin_address" => $location->address,
+ "origin_postal_code" => $location->postal_code,
+ "origin_latitude" => $location->latitude,
+ "origin_longitude" => $location->longitude,
+
+ "destination_contact_name" => $address->name,
+ "destination_contact_phone" => $address->phone,
+ "destination_address" => $destination_address,
+ "destination_postal_code" => $address->postal_code,
+
+ "reference_id" => $transaction->number,
+ "courier_insurance" => $subtotal,
+
+ "courier_company" => $transaction->courier_company,
+ "courier_type" => $transaction->courier_type,
+ "items" => $items
+ ]
+ );
+ }
+ }
+
+ public function tracking(Transaction $transaction, $waybill = true){
+ $biteship = $this->biteship;
+ $shipping = $transaction->shipping;
+
+ if (!@$shipping){
+
+ throw ValidationException::withMessages([
+ "shipping" => "Belum ada process pengiriman!"
+ ]);
+
+ if ($waybill){
+
+ $data = (array) $biteship->trackingByWaybill([
+ "id" => $shipping->tracking_id,
+ "waybill_id" => $shipping->waybill_id,
+ "courier" => $shipping->courier,
+ ]);
+ }else{
+
+ $data = (array) $biteship->trackingById([
+ "id" => $shipping->tracking_id,
+ "waybill_id" => $shipping->waybill_id,
+ "courier" => $shipping->courier,
+ ]);
+ }
+
+ $shipping->tracks()->delete();
+ foreach($data["history"] as $history){
+ $history = (array) $history;
+ $shipping->tracks()->create([
+ "status" => $history["status"],
+ "note" => $history["note"],
+ "created_at" => @$history["updated_at"]
+ ]);
+ }
+ $shipping->fill([
+ "status" => $data["status"]
+ ]);
+ $shipping->update();
+
+ }
+ }
+}
diff --git a/app/Repositories/Member/Transaction/CheckoutController.php b/app/Repositories/Member/Transaction/CheckoutController.php
new file mode 100644
index 0000000..5cb9a3e
--- /dev/null
+++ b/app/Repositories/Member/Transaction/CheckoutController.php
@@ -0,0 +1,26 @@
+validated();
+ $item = $repository->create($data);
+
+ $notification = new OrderWaitPayment($item);
+ $user = auth()->user();
+ $user->notify($notification->delay(now()->addMinutes(1)));
+
+ return new CheckoutResource($item);
+ }
+
+}
diff --git a/app/Repositories/Member/Transaction/TransactionRepository.php b/app/Repositories/Member/Transaction/TransactionRepository.php
new file mode 100644
index 0000000..92e8ec7
--- /dev/null
+++ b/app/Repositories/Member/Transaction/TransactionRepository.php
@@ -0,0 +1,872 @@
+shippingRepository = $shippingRepository;
+ $this->invoiceRepository = $invoiceRepository;
+ $this->roleRepository = $roleRepository;
+ $this->voucherRepository = $voucherRepository;
+ $this->xendit = $xendit;
+ }
+
+ public function getList($params = [])
+ {
+ $customColumns = [
+ 'user.name' => 'users.name',
+ ];
+
+ $limit = @$params["limit"] ? (int) @$params["limit"] : 10;
+ $sortColumn = @$params["sort"]["column"] ? (@$customColumns[$params["sort"]["column"]] ? $customColumns[$params["sort"]["column"]] : $params["sort"]["column"]) : "id";
+ $sortDir = @$params["sort"]["dir"] ? $params["sort"]["dir"] : "desc";
+
+ $model = Transaction::select('transactions.*')
+ ->leftJoin('address', 'address.id', 'transactions.address_id')
+ ->leftJoin('users', 'users.id', 'transactions.user_id')
+ ->when(@!$params['is_admin'], function ($query) {
+ $query->where('transactions.user_id', auth()->user()->id);
+ })
+ ->when(@$params['status'], function ($query) use ($params) {
+ $query->where('transactions.status', $params['status']);
+ })
+ ->when(@$params['filter']['multiple_status'] && !empty($params['filter']['multiple_status']), function($query) use ($params){
+ $query->whereIn("transactions.status", $params['filter']['multiple_status']);
+ })
+ ->when(@$params['filter']['except'], function($query) use ($params){
+ $except = @$params['filter']['except'];
+ if (is_array($except)){
+ $query->whereNotIn("status", $except);
+ }else{
+ $query->where("status","<>", $except);
+ }
+ })
+ ->when(@$params['filter']['start'], function($query) use ($params){
+ $query->whereDate("transactions.time", ">=", $params['filter']['start']);
+ })
+ ->when(@$params['filter']['end'], function($query) use ($params){
+ $query->whereDate("transactions.time", "<=", $params['filter']['end']);
+ })
+ ->when(@$params['search'], function($query) use ($params) {
+ $query->where(function($query) use ($params) {
+ $query->where('transactions.number', 'ilike', '%' . $params['search'] . '%')
+ ->orWhere('users.name', 'ilike', '%' . $params['search'] . '%')
+ ->orWhere('address.name', 'ilike', '%' . $params['search'] . '%');
+ });
+ })
+ ->orderBy($sortColumn, $sortDir)
+ ->paginate($limit);
+
+ return $model;
+ }
+
+ public function getListForExport($params = [])
+ {
+ $customColumns = [
+ 'user.name' => 'users.name',
+ ];
+
+ $limit = @$params["limit"] ? (int) @$params["limit"] : 10;
+ $sortColumn = @$params["sort"]["column"] ? (@$customColumns[$params["sort"]["column"]] ? $customColumns[$params["sort"]["column"]] : $params["sort"]["column"]) : "transactions.id";
+ $sortDir = @$params["sort"]["dir"] ? $params["sort"]["dir"] : "desc";
+
+ return Transaction::selectRaw("
+ transactions.*,
+ transaction_details.*,
+ coalesce(item_variants.description, items.name) as product_name,
+ item_reference.number as item_reference_number
+ ")
+ ->leftJoin('address', 'address.id', 'transactions.address_id')
+ ->leftJoin('users', 'users.id', 'transactions.user_id')
+ ->leftJoin('transaction_details', 'transaction_details.transaction_id', 'transactions.id')
+ ->leftJoin('item_reference', 'item_reference.id', 'transaction_details.item_reference_id')
+ ->leftJoin('items', 'items.id', 'item_reference.item_id')
+ ->leftJoin('item_variants', 'item_variants.id', 'transaction_details.item_variant_id')
+ ->when(@!$params['is_admin'], function ($query) {
+ $query->where('transactions.user_id', auth()->user()->id);
+ })
+ ->when(@$params['status'], function ($query) use ($params) {
+ $query->where('transactions.status', $params['status']);
+ })
+ ->when(@$params['filter']['multiple_status'] && !empty($params['filter']['multiple_status']), function($query) use ($params){
+ $query->whereIn("transactions.status", $params['filter']['multiple_status']);
+ })
+ ->when(@$params['filter']['except'], function($query) use ($params){
+ $except = @$params['filter']['except'];
+ if (is_array($except)){
+ $query->whereNotIn("status", $except);
+ }else{
+ $query->where("status","<>", $except);
+ }
+ })
+ ->when(@$params['filter']['start'], function($query) use ($params){
+ $query->whereDate("transactions.time", ">=", $params['filter']['start']);
+ })
+ ->when(@$params['filter']['end'], function($query) use ($params){
+ $query->whereDate("transactions.time", "<=", $params['filter']['end']);
+ })
+ ->when(@$params['search'], function($query) use ($params) {
+ $query->where(function($query) use ($params) {
+ $query->where('transactions.number', 'ilike', '%' . $params['search'] . '%')
+ ->orWhere('users.name', 'ilike', '%' . $params['search'] . '%')
+ ->orWhere('address.name', 'ilike', '%' . $params['search'] . '%');
+ });
+ })
+ ->orderBy($sortColumn, $sortDir)
+ ->get();
+ }
+
+ public function getCount($params = [])
+ {
+ $model = Transaction::select('transactions.*')
+ ->leftJoin('address', 'address.id', 'transactions.address_id')
+ ->leftJoin('users', 'users.id', 'transactions.user_id')
+ ->when(@!$params['is_admin'], function ($query) {
+ $query->where('transactions.user_id', auth()->user()->id);
+ })
+ ->when(@$params['status'], function ($query) use ($params) {
+ $query->where('transactions.status', $params['status']);
+ })
+ ->when(@$params['filter']['multiple_status'] && !empty($params['filter']['multiple_status']), function($query) use ($params){
+ $query->whereIn("transactions.status", $params['filter']['multiple_status']);
+ })
+ ->when(@$params['filter']['except'], function($query) use ($params){
+ $except = @$params['filter']['except'];
+ if (is_array($except)){
+ $query->whereNotIn("status", $except);
+ }else{
+ $query->where("status","<>", $except);
+ }
+ })
+ ->select("status")
+ ->addSelect(DB::raw("COUNT(*) as count"))
+ ->groupBy("status")
+ ->get();
+
+ return $model;
+ }
+
+ public function getlistItem(Transaction $transaction, $params = []) {
+
+ $limit = @$params["limit"] ? (int) @$params["limit"] : 10;
+ $sortColumn = @$params["sort"]["column"] ? $params["sort"]["column"] : "id";
+ $sortDir = @$params["sort"]["dir"] ? $params["sort"]["dir"] : "desc";
+
+ return TransactionDetail::select('transaction_details.*')
+ ->leftJoin('item_reference', 'item_reference.id', 'transaction_details.item_reference_id')
+ ->leftJoin('item_variants', 'item_variants.id', 'transaction_details.item_variant_id')
+ ->where('transaction_id', $transaction->id)
+ ->when(@$params['search'], function($query) use ($params) {
+ $query->where(function($query) use ($params) {
+ $query->where('item_variants.description', 'ilike', '%' . $params['search'] . '%')
+ ->orWhere('item_reference.number', 'ilike', '%' . $params['search'] . '%');
+ });
+ })
+ ->orderBy($sortColumn, $sortDir)
+ ->paginate($limit);
+ }
+
+ public function setStatusTransaction($transactionId, $status, $note = '')
+ {
+ $user = auth()->user();
+ TransactionStatus::create([
+ 'transaction_id' => $transactionId,
+ 'time' => now(),
+ 'note' => $note,
+ 'status' => $status,
+ 'user_id' => @$user->id ?? 0,
+ ]);
+ }
+
+ public function getAmount($items)
+ {
+ $amount = 0;
+
+ foreach ($items as $detail)
+ {
+ $itemReference = ItemReference::findOrFail($detail['item_reference_id']);
+ $item = Items::findOrFail($itemReference->item_id);
+ $amount += $detail['qty'] * $item->unit_price;
+ }
+
+ return $amount;
+ }
+
+
+ public function createPayment($model){
+
+ $fees = [];
+ if ($model->shipping_price){
+ $fees[] =
+ [
+ "type" => "SHIPPING",
+ "value" => $model->shipping_price
+ ];
+ }
+ if ($model->discount){
+ $fees[] =
+ [
+ "type" => "DISCOUNT",
+ "value" => $model->discount
+ ];
+ }
+
+ $remaining_amount = ($model->subtotal + $model->shipping_price) - $model->discount;
+
+ $xendit_response = $this->xendit->createPaymentLink([
+ "external_id" => $model->number,
+ "amount" => $remaining_amount,
+ "items" => $model->details->map(function($cart){
+ return [
+ "name" => $cart->item->name,
+ "category" => @$cart->item->category->name ?? "GOODS",
+ "price" => $cart->unit_price,
+ "quantity" => $cart->qty
+ ];
+ }),
+ "fees" => $fees
+ ]);
+
+
+ if ($xendit_response){
+ $xendit_link = XenditLink::create([
+ "uid" => $xendit_response["id"],
+ "invoice_url" => $xendit_response["invoice_url"],
+ "user_id" => $xendit_response["user_id"],
+ "external_id" => $xendit_response["external_id"],
+ "status" => $xendit_response["status"],
+ "amount" => $xendit_response["amount"],
+ "expiry_date" => $xendit_response["expiry_date"]
+ ]);
+ $payment = TransactionPayment::create([
+ "amount" => $remaining_amount,
+ "status" => "PENDING",
+ "transaction_id" => $model->id,
+ "method_type" => get_class($xendit_link),
+ "method_id" => $xendit_link->id
+ ]);
+ }
+ $this->setStatusTransaction($model->id, 'WAIT_PAYMENT');
+ return $xendit_response;
+ }
+
+
+ public function create($data)
+ {
+ $model = DB::transaction(function () use ($data) {
+ $user = auth()->user();
+ $customer = Customer::where("user_id", $user->id)->first();
+ $location_id = $data["location_id"];
+
+
+ // validasi voucher
+ if ( @$data["vouchers"]){
+ $used_voucher = Voucher::whereIn("id", $data["vouchers"])->whereNotNull("used_at")->count();
+ if ($used_voucher){
+ throw ValidationException::withMessages([
+ "voucher" => "Voucher Telah digunakan "
+ ]);
+ }
+ }
+
+ // get numbering
+ $auto = new AutoNumbering([
+ "type" => "TRX",
+ "location_id" => 0,
+ "prefix" => "TRX",
+ "pad" => 12
+ ]);
+ $number = $auto->getCurrent();
+ $data["number"] = $number;
+
+ $items = collect(@$data["items"] ?? []);
+
+ $carts = Cart::where('user_id', $user->id)
+ ->where("location_id", $data["location_id"])
+->when(count($items) > 0, function($query) use ($items) {
+ $query->whereIn("item_reference_id", $items->pluck("item_reference_id"));
+})
+ ->get();
+ $carts = $carts->map(function($cart) use ($items){
+ $item = $items->firstWhere("item_reference_id", $cart->item_reference_id);
+ if ($item){
+ $cart->new_qty = $cart->qty - $item["qty"];
+ $cart->qty = $item["qty"];
+ }
+ return $cart;
+ });
+
+ if (count($carts) == 0){
+ throw ValidationException::withMessages([
+ "items" => "Tidak ada item di keranjang"
+ ]);
+ }
+
+ // detail data process
+ $subtotal = 0;
+ $details = [];
+ foreach ($carts as $detail)
+ {
+ $convertion = 1;
+ if (@$detail->item->display_unit and @$detail->item->display_unit != @$detail->item->unit){
+ $convertions = DB::select("select to_qty / from_qty as conv from item_convertions where from_unit = ? and to_unit = ?", [$detail->item->display_unit, $detail->item->unit] );
+ $convertion = max((float) @$convertions[0]->conv, 1);
+ }
+ $d_price = @$detail->itemReference->discount->price ?? 0;
+ $s_price = @$detail->itemReference->price->price ?? 0;
+ $price = ( $s_price ? $s_price : $detail->item->net_price) * $convertion;
+ $price = ( $d_price ? $d_price : $price) * $convertion;
+ $total = $detail->qty * $price;
+ $details[] = [
+ 'item_id' => $detail->item_id,
+ 'item_variant_id' => $detail->item_variant_id,
+ 'item_reference_id' => $detail->item_reference_id,
+ 'qty' => $detail->qty,
+ 'unit' => ($detail->item->display_unit ?? $detail->item->unit),
+ 'unit_price' => $price,
+ 'unit_cost' => $detail->item->unit_cost,
+ 'total' => $total
+ ];
+ $subtotal += $total;
+ }
+
+
+ // validasi voucher
+ if ( @$data["vouchers"]){
+ $used_vouchers = Voucher::whereIn("id", $data["vouchers"])->get();
+ foreach($used_vouchers as $v){
+ if ($subtotal < ((float) $v->min_sales)){
+ throw ValidationException::withMessages([
+ "voucher" => "Voucher minimal belanja ".$v->min_sales
+ ]);
+ }
+ }
+ }
+
+
+ // get shipping_price
+ $list_shipping = $this->shippingRepository->getList($data);
+ $filtered_shipping = collect($list_shipping["pricing"])->filter(function($pricing) use ($data){
+ return $pricing["company"] == $data["courier_company"] &&
+ $pricing["type"] == $data["courier_type"];
+ })->values();
+ if (count($filtered_shipping) == 0){
+ throw ValidationException::withMessages([
+ "courier_company" => "Pilihan kurir tidak ditemukan"
+ ]);
+ }
+if ($subtotal >= 1000000){
+ $shipping_price = max($filtered_shipping[0]["price"] - 50000, 0);
+}else{
+ $shipping_price = $filtered_shipping[0]["price"];
+}
+ $valid_vouchers = $this->voucherRepository->getValidByItems($carts, $customer->id);
+ $vouchers = @$data["vouchers"] ? Voucher::whereIn("id", $data["vouchers"])
+ ->whereIn("id",$valid_vouchers)
+ ->get(): collect([]);
+
+ // discount
+ $discount = $vouchers->reduce(function($acc, $voucher){
+ return $acc + ($voucher->nominal);
+ },0);
+
+ $subtotal2 = $subtotal + $shipping_price;
+ $amount = max(0, $subtotal2 - $discount);
+ $discount = min($subtotal2, $discount);
+
+
+ // discount points
+ $discount_point = 0;
+ $use_customer_points = @$data["use_customer_points"] ?? 0;
+ if ($use_customer_points > 0){
+ $current_point = auth()->user()->customer->point;
+
+ // use_customer_points
+ if ($use_customer_points > $current_point){
+ throw ValidationException::withMessages([
+ "use_customer_points" => "Point tidak cukup. Poin anda " . number_format($current_point, 0, ",", ".")
+ ]);
+ }
+
+
+ // convert point to amount
+ $point_to_amount = $use_customer_points * 1000;
+
+ $discount_point = min($point_to_amount, $amount);
+
+ $amount = max(0, $amount - $discount_point);
+ }
+
+
+ $model = Transaction::create([
+ 'number' => $data['number'],
+ 'address_id' => $data['address_id'],
+ 'location_id' => $data['location_id'],
+ 'courier_company' => $data['courier_company'],
+ 'courier_type' => $data['courier_type'],
+ 'shipping_price' => $shipping_price,
+ 'user_id' => auth()->user()->id,
+ 'customer_id' => @$customer->id,
+ 'time' => now(),
+ 'subtotal' => $subtotal,
+ 'discount' => $discount,
+ 'point' => $discount_point,
+ 'amount' => $amount,
+ 'status' => 'WAIT_PAYMENT',
+ ]);
+ $model->details()->createMany($details);
+
+
+ $subtotal_remaining = $subtotal2;
+ foreach ($vouchers as $voucher) {
+ if ($subtotal_remaining <= 0){
+ continue;
+ }
+
+ $voucher->fill([
+ "used_at" => Carbon::now(),
+ "reference_used_id" => $model->id,
+ "reference_used_type" => get_class($model)
+ ]);
+ $voucher->save();
+
+ TransactionPayment::create([
+ "amount" => min($voucher->nominal, $subtotal_remaining),
+ "status" => "PAID",
+ "transaction_id" => $model->id,
+ "method_type" => get_class($voucher),
+ "method_id" => $voucher->id
+ ]);
+
+ $subtotal_remaining = max(0,$subtotal_remaining - $voucher->nominal);
+ }
+
+ if ($use_customer_points > 0){
+ $cp = CustomerPoint::create([
+ "customer_id" => $model->customer_id,
+ "point" => -1 * $use_customer_points,
+ "reference_type" => get_class($model),
+ "reference_id" => $model->id,
+ "description" => "Discount Point",
+ ]);
+
+ $current_point = auth()->user()->customer->point;
+
+ if ($current_point < 0){
+ throw ValidationException::withMessages([
+ "use_customer_points" => "Point anda telah habis"
+ ]);
+ }
+
+ TransactionPayment::create([
+ "amount" => $discount_point,
+ "status" => "PAID",
+ "transaction_id" => $model->id,
+ "method_type" => get_class($cp),
+ "method_id" => $cp->id
+ ]);
+ }
+
+ // create payment
+ $fees = [
+ [
+ "type" => "SHIPPING",
+ "value" => $shipping_price
+ ]
+ ];
+ if ($discount){
+ $fees[] =
+ [
+ "type" => "DISCOUNT",
+ "value" => $discount
+ ];
+ }
+ $remaining_amount = $amount;
+ if ($amount > 0){
+ $xendit_response = $this->xendit->createPaymentLink([
+ "external_id" => $model->number,
+ "amount" => $remaining_amount,
+ "items" => $carts->map(function($cart){
+$detail = $cart;
+ $convertion = 1;
+ if (@$detail->item->display_unit and @$detail->item->display_unit != @$detail->item->unit){
+ $convertions = DB::select("select to_qty / from_qty as conv from item_convertions where from_unit = ? and to_unit = ?", [$detail->item->display_unit, $detail->item->unit] );
+ $convertion = max((float) @$convertions[0]->conv, 1);
+ }
+
+ $d_price = @$detail->itemReference->discount->price ?? 0;
+ $s_price = @$detail->itemReference->price->price ?? 0;
+ $price = ( $s_price ? $s_price : $detail->item->net_price) * $convertion;
+ $price = ( $d_price ? $d_price : $price) * $convertion;
+
+ return [
+ "name" => $cart->item->name,
+ "category" => @$cart->item->category->name ?? "GOODS",
+ "price" => $price,
+ "quantity" => $cart->qty
+ ];
+ }),
+ "fees" => $fees
+ ]);
+
+ if ($xendit_response){
+
+ $xendit_link = XenditLink::create([
+ "uid" => $xendit_response["id"],
+ "invoice_url" => $xendit_response["invoice_url"],
+ "user_id" => $xendit_response["user_id"],
+ "external_id" => $xendit_response["external_id"],
+ "status" => $xendit_response["status"],
+ "amount" => $xendit_response["amount"],
+ "expiry_date" => $xendit_response["expiry_date"]
+ ]);
+
+ $payment = TransactionPayment::create([
+ "amount" => $remaining_amount,
+ "status" => "PENDING",
+ "transaction_id" => $model->id,
+ "method_type" => get_class($xendit_link),
+ "method_id" => $xendit_link->id
+ ]);
+ }
+
+ $this->setStatusTransaction($model->id, 'WAIT_PAYMENT');
+ }else{
+ $model->status = 'WAIT_PROCESS';
+ $model->save();
+ $this->setStatusTransaction($model->id, 'WAIT_PROCESS');
+ }
+
+
+ // clear cart
+
+ foreach($carts as $cart){
+ if ($cart->new_qty <= 0){
+ $cart->delete();
+ }else{
+ $new_qty = $cart->new_qty;
+ unset($cart->new_qty);
+ $cart->update([
+ "qty" => $new_qty
+ ]);
+ }
+ }
+
+ return $model;
+ });
+
+ return $model;
+ }
+
+ public function webhookPayment($data)
+ {
+ $paymentLink = XenditLink::where("uid", $data["id"])->firstOrFail();
+ $paymentLink->fill([
+ "payment_id" => @$data["payment_id"],
+ "paid_amount" => @$data["paid_amount"],
+ "payment_method" => @$data["payment_method"],
+ "bank_code" => @$data["bank_code"],
+ "payment_channel" => @$data["payment_channel"],
+ "payment_destination" => @$data["payment_destination"],
+ "received_amount" => @$data["adjusted_received_amount"],
+ "status" => @$data["status"],
+ "paid_at" => @$data["paid_at"]
+ ]);
+ $paymentLink->save();
+ $paymentLink->payment()->update([
+ "status" => $paymentLink->status,
+ ]);
+
+ $transaction = Transaction::find($paymentLink->payment->transaction_id);
+ if ($transaction && @$data["status"] == "PAID"){
+
+ if ($transaction->status != "WAIT_PROCESS"){ // once notify for first
+ $roleRepository = $this->roleRepository;
+ $users = $roleRepository->findUserByPermission("transaction.online");
+ $notification = new NewOrder($transaction);
+ Notification::send($users, $notification);
+
+ $notification2 = new OrderPaid($transaction);
+ $transaction->customer->user->notify($notification2);
+ }
+
+ $this->setStatusTransaction($transaction->id,'WAIT_PROCESS');
+ $transaction->status = 'WAIT_PROCESS';
+ $transaction->save();
+ }else if ($transaction && @$data["status"] == "EXPIRED"){
+
+ if ($transaction->status != "CANCELED"){
+ $this->cancel($transaction->id,"Oleh System");
+ }
+ }
+
+ return $paymentLink;
+ }
+
+ public function payment($id)
+ {
+ $model = DB::transaction(function () use ($id) {
+
+ $model = Transaction::findOrfail($id);
+
+ TransactionPayment::create([
+ 'transaction_id' => $model->id,
+ 'amount' => $model->amount,
+ 'status' => 'PAID',
+ ]);
+
+ $this->setStatusTransaction($model->id, 'WAIT_PROCESS');
+
+ $model->status = 'WAIT_PROCESS';
+ $model->save();
+
+ return $model;
+ });
+
+ return $model;
+ }
+
+ public function process($id)
+ {
+ $repository = $this->invoiceRepository;
+ $model = DB::transaction(function () use ($id, $repository) {
+
+ $model = Transaction::findOrfail($id);
+
+ $this->setStatusTransaction($model->id, 'ON_PROCESS');
+
+ $model->status = 'ON_PROCESS';
+ $model->save();
+
+ $invoice = $this->invoiceRepository->createFromOnline($model);
+
+ $vouchers = $model->vouchers;
+ foreach ($vouchers as $voucher) {
+ $repository->checkAffiliatorVoucher($invoice, $voucher);
+ }
+
+ return $model;
+ });
+
+ $user = @$model->customer->user;
+ if ($user){
+ $notification2 = new OrderProcessed($model);
+ $user->notify($notification2);
+ }
+
+ return $model;
+ }
+
+ public function deliver($id)
+ {
+ $model = DB::transaction(function () use ($id) {
+
+ $model = Transaction::findOrfail($id);
+
+ $shipping = TransactionShipping::where("transaction_id",$model->id)->first();
+ if ($shipping){
+ return $model;
+ }
+ // order shippping
+ $data = $this->shippingRepository->order($model);
+
+ if(empty($data)) {
+ throw new Exception('error failed create order');
+ }
+
+ $weight_total = collect($data["items"])->reduce(function($acc, $item){
+ $item = (object) $item;
+ return $acc + ($item->weight * $item->quantity);
+ },0);
+
+ $shipping = TransactionShipping::create([
+ "transaction_id" => $model->id,
+
+ "origin_address" => $data["origin"]["address"],
+ "origin_name" => $data["origin"]["contact_name"],
+ "origin_phone" => $data["origin"]["contact_phone"],
+ "origin_postal_code" => $data["origin"]["postal_code"],
+ "origin_latitude" => $data["origin"]["coordinate"]["latitude"],
+ "origin_longitude" => $data["origin"]["coordinate"]["longitude"],
+ "origin_note" => $data["origin"]["note"],
+
+
+ "destination_address" => @$data["destination"]["address"],
+ "destination_name" => @$data["destination"]["contact_name"],
+ "destination_phone" => @$data["destination"]["contact_phone"],
+ "destination_postal_code" => @$data["destination"]["postal_code"],
+ "destination_latitude" => @$data["destination"]["coordinate"]["latitude"],
+ "destination_longitude" => @$data["destination"]["coordinate"]["longitude"],
+ "destination_note" => @$data["destination"]["note"],
+
+ "shipper_name" => @$data["shipper"]["name"],
+ "shipper_phone" => @$data["shipper"]["phone"],
+ "shipper_email" => @$data["shipper"]["email"],
+
+ "uid" => $data["id"],
+ "tracking_id" => @$data["courier"]["tracking_id"],
+ "waybill_id" => @$data["courier"]["waybill_id"],
+ "company" => @$data["courier"]["company"],
+ "driver_name" => @$data["courier"]["driver_name"],
+ "driver_phone" => @$data["courier"]["driver_phone"],
+ "driver_photo_url" => @$data["courier"]["driver_photo_url"],
+ "driver_plate_number" => @$data["courier"]["driver_plate_number"],
+ "type" => @$data["courier"]["type"],
+ "insurance_amount" => @$data["insurance"]["amount"],
+ "insurance_fee" => @$data["insurance"]["fee"],
+ "weight_total" => $weight_total,
+ "price" => @$data["price"],
+ "note" => @$data["note"],
+ "status" => @$data["status"]
+ ]);
+
+ $this->setStatusTransaction($model->id, 'ON_DELIVERY');
+
+ $model->status = 'ON_DELIVERY';
+ $model->save();
+
+
+ return $model;
+ });
+
+
+ $user = @$model->customer->user;
+ if ($user){
+ $notification2 = new OrderOnDelivery($model);
+ $user->notify($notification2);
+ }
+
+ return $model;
+ }
+
+ public function close($id)
+ {
+ $model = DB::transaction(function () use ($id) {
+
+ $model = Transaction::findOrfail($id);
+
+ $this->setStatusTransaction($model->id, 'CLOSED');
+
+ $model->status = 'CLOSED';
+ $model->save();
+
+ return $model;
+ });
+
+ return $model;
+ }
+
+ public function delivered($id)
+ {
+ $model = DB::transaction(function () use ($id) {
+
+ $model = Transaction::findOrfail($id);
+
+ $this->setStatusTransaction($model->id, 'DELIVERED');
+
+ $model->status = 'DELIVERED';
+ $model->save();
+
+ return $model;
+ });
+
+ $user = @$model->customer->user;
+ if ($user){
+ $notification2 = new OrderDelivered($model);
+ $user->notify($notification2);
+ }
+
+ return $model;
+ }
+
+ public function cancel($id, $note = "")
+ {
+ $model = DB::transaction(function () use ($id, $note) {
+
+ $model = Transaction::findOrfail($id);
+ $model->vouchers()->update([
+ "used_at" => null,
+ "reference_used_id" => null,
+ "reference_used_type" => null
+ ]);
+
+ $this->setStatusTransaction($model->id, 'CANCELED', $note);
+
+ $model->status = 'CANCELED';
+ $model->save();
+
+
+
+
+ $customer_point = CustomerPoint::where("customer_id", $model->customer_id)
+ ->where("reference_id", $model->id)
+ ->where("reference_type", get_class($model))
+ ->orderBy("id", "asc")
+ ->first();
+
+ if ($customer_point){
+ CustomerPoint::create([
+ "customer_id" => $model->customer_id,
+ "point" => -1 * $customer_point->point,
+ "reference_type" => get_class($model),
+ "reference_id" => $model->id,
+ "description" => "Refund Discount Point",
+ ]);
+ }
+
+
+ return $model;
+ });
+
+ $user = @$model->customer->user;
+ if ($user){
+ $notification2 = new OrderCanceled($model);
+ $user->notify($notification2);
+ }
+
+ return $model;
+ }
+}
diff --git a/app/Repositories/Pos/InvoiceRepository.php b/app/Repositories/Pos/InvoiceRepository.php
new file mode 100644
index 0000000..a29155b
--- /dev/null
+++ b/app/Repositories/Pos/InvoiceRepository.php
@@ -0,0 +1,842 @@
+surveyRepository = $surveyRepository;
+ }
+
+ public function getList(array $params = [], $all = false)
+ {
+ $user = auth()->user();
+ $location_id = @$user->employee->location_id;
+
+ $sortColumn = [
+ 'number' => 'pos_invoices.number',
+ 'time' => 'pos_invoices.time',
+ 'total' => 'pos_invoices.total',
+ 'location.name' => 'locations.name',
+ 'customer' => 'customers.name',
+ 'sales.name' => 'sales.name',
+ 'sales2.name' => 'sales.name',
+ 'user.name' => 'users.name'
+ ];
+
+ $limit = @$params["limit"] ? (int) @$params["limit"] : 10;
+ $sortColumn = @$params["sort"]["column"] ? $sortColumn[$params["sort"]["column"]] : "pos_invoices.id";
+ $sortDir = @$params["sort"]["dir"] ? $params["sort"]["dir"] : "desc";
+
+ $results = PosInvoice::selectRaw("
+ pos_invoices.*,
+ CASE WHEN a.first_transaction is not null THEN 1 ELSE 0 END as new_customer,
+ CASE WHEN (a.first_transaction IS NOT NULL AND customers.number NOT ILIKE 'CAG%') OR customers.number = 'NONAME' THEN 'online' ELSE 'offline' END as type"
+ )
+ ->leftJoin(DB::raw("(select min(pos_invoices.id) as first_transaction from pos_invoices group by customer_id) as a"), 'a.first_transaction', 'pos_invoices.id')
+ ->leftJoin('transactions', 'transactions.invoice_id', 'pos_invoices.id')
+ ->leftJoin('locations', 'locations.id', 'pos_invoices.location_id')
+ ->leftJoin('customers', 'customers.id', 'pos_invoices.customer_id')
+ ->leftJoin('users', 'users.id', 'pos_invoices.user_id')
+ ->leftJoin('sales', 'sales.id', 'pos_invoices.sales_id')
+ ->leftJoin('sales as sales2', 'sales2.id', 'pos_invoices.sales2_id')
+ ->when(@$params["search"], function ($query) use ($params) {
+ $query->where(function ($query) use ($params) {
+ $query->where("pos_invoices.number", "ilike", "%" . $params["search"] . "%")
+ ->orWhere("sales.name", "ilike", "%" . $params["search"] . "%")
+ ->orWhere("sales2.name", "ilike", "%" . $params["search"] . "%")
+ ->orWhere("users.name", "ilike", "%" . $params["search"] . "%")
+ ->orWhere("customers.name", "ilike", "%" . $params["search"] . "%")
+ ->orWhere("customers.phone", "ilike", "%" . $params["search"] . "%");
+ /* ->orWhereExists(function($subquery) use ($params) {
+ $subquery->select(DB::raw(1))
+ ->from('pos_invoice_details')
+ ->leftJoin('items', 'pos_invoice_details.item_id', 'items.id')
+ ->leftJoin('item_variants', 'item_variants.id', 'pos_invoice_details.item_variant_id')
+ ->leftJoin('item_reference', 'item_reference.id', 'pos_invoice_details.item_reference_id')
+ ->whereColumn('pos_invoice_details.invoice_id', 'pos_invoices.id')
+ ->where(function($q) use ($params) {
+ $q->where('item_reference.number', 'ilike', '%' . $params['search'] . '%')
+ ->orWhere('items.number', 'ilike', '%' . $params['search'] . '%')
+ ->orWhere('items.name', 'ilike', '%' . $params['search'] . '%')
+ ->orWhere('item_variants.description', 'ilike', '%' . $params['search'] . '%')
+ ->orWhere('item_variants.code', 'ilike', '%' . $params['search'] . '%');
+ });
+ }); */
+ });
+ })
+ ->when(!$all, function ($query) use ($location_id) {
+ $query->where("pos_invoices.location_id", $location_id);
+ })
+ ->when(@$params['active'], function($query) {
+ $query->whereNull('canceled_at');
+ })
+ ->when(@$params['filter'], function ($query) use ($params) {
+ foreach ($params['filter'] as $filter) {
+ if ($filter['column'] == 'start') {
+ $query->whereDate("pos_invoices.time", ">=", $filter["query"]);
+ } else if ($filter['column'] == 'end') {
+ $query->whereDate("pos_invoices.time", "<=", $filter["query"]);
+ } else if ($filter['column'] == 'sync') {
+ if ($filter['query'] == 'BERHASIL') {
+ $query->whereNotNull('pos_invoices.sync_at');
+ }
+
+ if ($filter['query'] == 'GAGAL') {
+ $query->whereNull('pos_invoices.sync_at')
+ ->whereNotNull('pos_invoices.sync_log');
+ }
+ } else if ($filter['column'] == 'payment_method') {
+ $query->whereRaw("EXISTS (SELECT id FROM pos_invoice_payments WHERE invoice_id = pos_invoices.id AND method = '" . $filter['query'] . "' AND amount > 0)");
+ } else {
+ $query->where('pos_invoices.' . $filter['column'], $filter['query']);
+ }
+ }
+ })
+ ->orderBy($sortColumn, $sortDir)
+ ->paginate($limit);
+ return $results;
+ }
+
+ public function getUnPosted()
+ {
+ return PosInvoice::whereNull("sync_at")
+ ->whereNull("canceled_at")
+ ->take(1000)->get();
+ }
+
+ public function createFromOnline($model)
+ {
+ DB::beginTransaction();
+ try{
+ $sales = Sales::where("name","ECOMMERCE")->first();
+ $user = auth()->user();
+ $posInvoice = PosInvoice::find((int) $model->invoice_id);
+ if ($posInvoice)
+ return;
+
+ $location = $model->location;
+ $auto = new AutoNumbering([
+ "type" => "POS",
+ "prefix" => str_replace(" ", "", $location->code),
+ "location_id" => $location->id,
+ "pad" => 12
+ ]);
+ $number = $auto->getCurrent();
+
+ $invoice = new PosInvoice([
+ "number" => $number,
+ "user_id" => $user->id,
+ "customer_id" => $model->customer_id,
+ "time" => $model->time,
+ "location_id" => $location->id,
+ "sales_id" => (int) @$sales->id,
+ "note" => @$model->note,
+ "subtotal" => $model->subtotal,
+ "voucher" => 0,
+ "discount" => 0,
+ "tax" => 0,
+ "total" => $model->subtotal + $model->shipping_price,
+ "vouchers" => "",
+ "note_internal" => $model->number,
+ ]);
+ $invoice->save();
+ $model->invoice_id = $invoice->id;
+ $model->save();
+
+ foreach ($model->details as $row) {
+ $invoice->details()->create([
+ 'item_reference_id' => $row->item_reference_id,
+ 'item_id' => $row->item_id,
+ 'item_variant_id' => $row->item_variant_id,
+ 'note' => '',
+ 'qty' => $row->qty,
+ 'unit_price'=> $row->unit_price,
+ 'unit'=> $row->unit,
+ 'reference'=> $row->reference->number,
+ 'discount'=> (int) @$row->discount,
+ 'vat'=> 0,
+ 'total'=> $row->total,
+ 'unit_cost'=> $row->unit_cost
+ ]);
+ }
+
+ // find biaya expedisi + asuransi
+ $item = ItemReference::where("number","9CBYEXPDAS")->first();
+
+ if ($model->shipping_price && $item){
+ $invoice->details()->create([
+ 'item_reference_id' => $item->id,
+ 'item_id' => $item->item_id,
+ 'item_variant_id' => $item->item_variant_id,
+ 'note' => $model->courier_company." ".$model->courier_type,
+ 'qty' => 1,
+ 'unit_price' => $model->shipping_price,
+ 'unit' => 'PCS',
+ 'reference' => $item->number,
+ 'discount' => 0,
+ 'vat' => 0,
+ 'total'=> $model->shipping_price,
+ 'unit_cost'=> $model->shipping_price
+ ]);
+ }
+
+ foreach ($model->payments as $row) {
+
+ if ($row->method_type == XenditLink::class){
+ if ($row->status == "PAID"){
+
+ $invoice->payments()->create([
+ "method" => "XENDIT",
+ "amount" => $row->amount,
+ "bank" => "XENDIT",
+ "card_number" => $row->method->uid,
+ "remarks" => @$row->method->payment_method." ".@$row->method->bank_code
+ ]);
+ }
+ }
+ }
+
+ foreach($model->vouchers as $voucher){
+
+ $invoice->payments()->create([
+ "method" => "VOUCHER",
+ "amount" => $voucher->nominal,
+ "bank" => "VOUCHER",
+ "card_number" => $voucher->number,
+ "remarks" => ""
+ ]);
+ }
+ $model->invoice_id = $invoice->id;
+ $model->save();
+
+ $survey = Survey::where("name","AFTER PURCHASE")->first();
+ if ($survey){
+ $data = [
+ "channel" => "pos",
+ "customer_id" => $invoice->customer_id,
+ "invoice_id" => $invoice->id
+ ];
+ $this->surveyRepository->generateFeedback($survey, $data);
+ }
+
+ DB::commit();
+
+ return $invoice;
+ }catch(\Exception $e){
+ DB::rollback();
+ throw $e;
+ }
+ }
+
+ public function create(array $data)
+ {
+ return DB::transaction(function () use ($data) {
+
+ $numbering = (array) @DB::select("SELECT id, transaction, location_id, prefix, pad, current
+ FROM numbering
+ WHERE transaction = ? AND location_id = ?
+ FOR UPDATE
+ ", ["POS", $data["location_id"]])[0];
+
+ $location = Location::findOrFail($data["location_id"]);
+ if ($numbering == null) {
+ $numbering = DB::table("numbering")->insert([
+ "transaction" => "POS",
+ "location_id" => $data["location_id"],
+ "prefix" => str_replace(" ", "", $location->code),
+ "pad" => 12,
+ "current" => 0
+ ]);
+
+ $numbering = (array) DB::select("SELECT id, transaction, location_id, prefix, pad, current
+ FROM numbering
+ WHERE id = ?
+ FOR UPDATE
+ ", [DB::getPdo()->lastInsertId()])[0];
+ }
+
+ if (Carbon::now()->gte("2024-01-22")) {
+ $prefix_number = $numbering["prefix"];
+ $next_number = $numbering["current"] + 1;
+ $pad_number = $numbering["pad"] - strlen($prefix_number);
+ $number = $prefix_number . str_pad($next_number, $pad_number, 0, STR_PAD_LEFT);
+ DB::statement("UPDATE numbering SET current = current+1 WHERE id = ?", [$numbering["id"]]);
+ } else {
+ $count = DB::select("select last_value from pos_invoices_id_seq")[0]->last_value;
+ $number = "POS" . str_pad($count + 1, 6, 0, STR_PAD_LEFT);
+ }
+
+
+ // $count = DB::select("select last_value from pos_invoices_id_seq")[0]->last_value;
+ $data["user_id"] = auth()->user()->id;
+ $data["voucher"] = (float) @$data["voucher"];
+ $data["time"] = Carbon::now();
+ $data["vouchers"] = @$data["vouchers"] ? implode(",", @$data["vouchers"]):"";
+ $data["number"] = $number;
+ $subtotal = (float) @$data["subtotal"];
+ $discount = (float) @$data["discount"];
+ $invoice = PosInvoice::create($data);
+
+ $exclude_item_points = [];
+
+ $i = 0;
+ foreach ($data["details"] as $row) {
+ $i++;
+
+ $item = Items::find($row["item_id"]);
+ $row["line_no"] = $i;
+ $row["unit_price"] = (float) @$row["unit_price"];
+ $row["total"] = (float) @$row["total"];
+ $row["discount"] = (float) @$row["discount"];
+ $row["vat"] = (float) @$row["vat"];
+ $row["unit_cost"] = @$item ? $item->unit_cost : 0;
+ $row["line_discount"] = @$row["discount"] * @$row["qty"];
+ $row["invoice_discount"] = $subtotal == 0 ? 0: ($row["total"] / $subtotal) * $discount;
+ $row["net_price"] = @$row['net_price'];
+
+ if (@$row['serial_number']){
+ $this->activateSerialNumber($invoice, @$row['serial_number'],[
+ "reference" => @$row["reference"],
+ "description" => @$row["description"],
+ "item_number" => @$row["item_number"],
+ "variant_code" => @$row["variant_code"],
+ "brand" => @$item->dimension->brand
+ ]);
+ }
+ $row["pricelist_discount"] = @$row['pricelist_discount'];
+ if (strpos($item->name, "VOUCHER") > -1) {
+ $exp = $row["unit_price"] == 0 ? 3 : 6;
+ $row["reference"] = $this->issueVoucher($invoice, $item->name, $row["reference"], (float) @$row["qty"], $exp);
+ }
+ $invoice->details()->create($row);
+ }
+
+ // Handle DISCOUNT vouchers - save to pos_invoice_vouchers table
+ if (@$data["discount_vouchers"]) {
+ $this->useDiscountVoucher($invoice, $data["discount_vouchers"]);
+ }
+
+ // Handle SALE vouchers (payment vouchers) - legacy behavior
+ if (@$data["vouchers"])
+ $exclude_item_points = $this->useVoucher($invoice, $data["vouchers"]);
+
+ $this->checkPoint($invoice, $exclude_item_points);
+
+ foreach ($data["payments"] as $row) {
+
+ if ($row["amount"] <= 0)
+ continue;
+
+ $invoice->payments()->create($row);
+
+ if ($row['method'] == "POINT"){
+
+ DB::table("customer_points")
+ ->insert([
+ "description" => "Use points for payment transaction " . $invoice->number,
+ "point" => ($row["amount"] / 1000) * -1,
+ "customer_id" => $invoice->customer_id,
+ "reference_id" => $invoice->id,
+ "reference_type" => get_class($invoice),
+ "created_at" => Carbon::now()
+ ]);
+ }else if ($row['method'] == "VOUCHER"){
+
+ $voucher = Voucher::where('number', $row['card_number'])->first();
+ if ($voucher){
+ // Validate voucher is SALE type for payment method
+ if (($voucher->calculation_type ?? 'SALE') != 'SALE') {
+ throw ValidationException::withMessages([
+ "voucher" => "Voucher ini adalah Voucher Discount, bukan Voucher Belanja. Tidak bisa digunakan sebagai pembayaran."
+ ]);
+ }
+
+ $voucher->used_at = Carbon::now();
+ $voucher->reference_used_id = $invoice->id;
+ $voucher->reference_used_type = get_class($invoice);
+ $voucher->save();
+ }
+ }
+ }
+
+ $luckyWheel = LuckyWheel::where("valid_at","<=", Carbon::now())
+ ->where("expired_at",">=", Carbon::now())
+ ->where(function($query) use ($location){
+ $query->whereNull("location_id")
+ ->orWhere("location_id", @$location->id);
+ })
+ ->where("min_sales_total","<", $invoice->total)
+ ->first();
+
+ if ($luckyWheel){
+ $ticket_counts = $luckyWheel->tickets()->count();
+ if ($ticket_counts < $luckyWheel->max_ticket || $luckyWheel->max_ticket == -1){
+ $max_prize = $luckyWheel->max_prize;
+ $current_prize = $luckyWheel->gets()->whereNotNull("redeem_at")->sum("nominal");
+
+// if ($max_prize > $current_prize){
+
+ // $ticket2 = $luckyWheel->tickets()->where("customer_id", $invoice->customer_id)->first();
+ // if ($ticket2 == null){
+ $ticket = $luckyWheel->tickets()->create([
+ "invoice_id" => $invoice->id,
+ "customer_id" => $invoice->customer_id,
+ "max_times" => 10
+ ]);
+ // }
+ // }
+ }
+ }
+
+ $survey = Survey::where("name","AFTER PURCHASE")->first();
+ if ($survey){
+ $data = [
+ "channel" => "pos",
+ "customer_id" => $invoice->customer_id,
+ "invoice_id" => $invoice->id
+ ];
+ $feedback = $this->surveyRepository->generateFeedback($survey, $data);
+ if ($feedback){
+ $notif = new SurveyBroadcast($feedback);
+ $invoice->customer->notify($notif);
+ }
+ }
+
+ return $invoice;
+ });
+ }
+
+ public function deactivateSerialNumber($invoice, $sn){
+ if (!@$sn)
+ return;
+
+ $arr = explode(",",$sn);
+ SerialNumberDetail::whereIn("number",$arr)->update([
+ "activated_at" => null
+ ]);
+ SerialNumberLog::whereIn('number',$arr)->delete();
+ }
+
+ public function activateSerialNumber($invoice, $sn, $data){
+ if (!@$sn)
+ return;
+
+ $user = auth()->user();
+ $arr = explode(",",$sn);
+ foreach($arr as $row){
+ $snDetail = SerialNumberDetail::where("number",$row)->first();
+ if ($snDetail){
+ $snDetail->activated_at = Carbon::now();
+ $snDetail->activated_by = $user->id;
+ $snDetail->invoice_id = $invoice->id;
+ $snDetail->save();
+ }
+
+ $snLog = new SerialNumberLog;
+ $snLog->number = $row;
+ $snLog->activated_at = Carbon::now();
+ $snLog->reference_number = $data["reference"] ?? "";
+ $snLog->item_number = $data["item_number"] ?? "";
+ $snLog->variant_code = $data["variant_code"] ?? "";
+ $snLog->description = $data["description"] ?? "";
+ $snLog->brand = $data["brand"] ?? "";
+ $snLog->invoice_no = $invoice->number;
+ $snLog->save();
+ }
+ }
+
+ public function checkPoint($invoice, $item_ids)
+ {
+
+ $point = 0;
+ foreach ($invoice->details as $detail) {
+ $discount_items = DB::select("select apply_point from discounts left join discount_items on discounts.id = discount_items.discount_id
+ where item_reference_id = ? and valid_at <= NOW() and expired_at >= NOW() order by discounts.id desc", [@$detail->item_reference_id]);
+
+ $apply_point = count($discount_items) > 0 ? $discount_items[0]->apply_point : false;
+ $discount = (float) @$detail->discount;
+ $qty = (float) @$detail->qty;
+
+ if(!$apply_point) {
+
+ if ($discount > 0){
+ continue;
+ }
+
+ if ($detail->unit_price < $detail->item->net_price){
+ continue;
+ }
+
+ // skip is has discount from voucher
+ if (in_array($detail->item_reference_id, $item_ids)){
+ continue;
+ }
+ }
+
+ $rows = DB::select(
+ "select point from customer_point_rules WHERE item_reference_id = ? AND qty = ?",
+ [$detail->item_reference_id, $detail->qty]
+ );
+ if (count($rows)) {
+
+ $row_point = (float) @$rows[0]->point;
+ $detail->point = $row_point * $qty;
+ $detail->save();
+ $point += $detail->point;
+ } else {
+
+ $rows_general = DB::select(
+ "select point from customer_point_rules WHERE item_reference_id = ? AND (qty is null or qty = 0)",
+ [$detail->item_reference_id]
+ );
+
+ if (count($rows_general)) {
+ $row_point = (float) @$rows_general[0]->point;
+ $detail->point = $row_point * $qty;
+ $detail->save();
+ $point += $detail->point;
+ }
+ }
+ }
+
+ if ($point > 0 && $invoice->location->code != "IND") {
+ DB::table("customer_points")
+ ->insert([
+ "description" => "Get points from transaction " . $invoice->number,
+ "point" => $point,
+ "customer_id" => $invoice->customer_id,
+ "reference_id" => $invoice->id,
+ "reference_type" => get_class($invoice),
+ "created_at" => Carbon::now()
+ ]);
+ }
+ }
+
+ private function useVoucher($invoice, $vouchers)
+ {
+ $exclude_item_points = [];
+ $arr = explode(",", $vouchers);
+ foreach ($arr as $voucher) {
+ $item = Voucher::where("number", $voucher)->first();
+ if (!$item)
+ continue;
+
+ $item_id = $this->checkAffiliatorVoucher($invoice, $item);
+ if (count($item_id) > 0)
+ {
+ $exclude_item_points = array_merge($exclude_item_points, $item_id);
+ }
+
+ if (@$item->event){
+ $item_reference_ids = $item->event->items->pluck("id")->toArray();
+ $exclude_item_points = array_merge($exclude_item_points, $item_reference_ids);
+ }
+
+ $item->used_at = Carbon::now();
+ $item->reference_used_id = $invoice->id;
+ $item->reference_used_type = get_class($invoice);
+ $item->save();
+ }
+
+ return $exclude_item_points;
+ }
+
+ private function useDiscountVoucher($invoice, $discount_vouchers)
+ {
+ // discount_vouchers is an array of voucher codes
+ foreach ($discount_vouchers as $voucher_code) {
+ $voucher = Voucher::where("number", $voucher_code)->first();
+ if (!$voucher)
+ continue;
+
+ // Validate it's a DISCOUNT type voucher
+ if (($voucher->calculation_type ?? 'SALE') != 'DISCOUNT') {
+ throw ValidationException::withMessages([
+ "discount_voucher" => "Voucher {$voucher_code} bukan Voucher Discount."
+ ]);
+ }
+
+ // Create pos_invoice_vouchers record
+ \App\Models\PosInvoiceVoucher::create([
+ 'pos_invoice_id' => $invoice->id,
+ 'voucher_id' => $voucher->id,
+ 'nominal' => $voucher->nominal
+ ]);
+
+ // Mark voucher as used
+ $voucher->used_at = Carbon::now();
+ $voucher->reference_used_id = $invoice->id;
+ $voucher->reference_used_type = get_class($invoice);
+ $voucher->save();
+ }
+ }
+
+ private function findDetail($invoice, $item_reference_ids){
+ return $invoice->details->filter(function($detail) use ($item_reference_ids){
+ return in_array($detail->item_reference_id , $item_reference_ids);
+ })->reduce(function($acc, $detail){
+ return $acc + ($detail->unit_price - $detail->unit_cost);
+ },0);
+ }
+
+ public function checkAffiliatorVoucher($invoice, $voucher){
+ if ($voucher->reference_issued_type != VoucherClaim::class){
+ return [];
+ }
+ $issued = $voucher->referenceIssued;
+
+ if ($issued->claimable_type != AffiliatorItemCode::class){
+ return [];
+ }
+
+ $claimable = $issued->claimable;
+ if ($claimable == null){
+ return [];
+ }
+
+ $affiliator = $claimable->affiliator;
+
+ if ($claimable->affiliatorItem != null){
+ $affiliatorItem = $claimable->affiliatorItem;
+
+ $itemReference = $affiliatorItem->item;
+ $item_reference_ids = [$itemReference->id];
+ $gross = $this->findDetail($invoice, $item_reference_ids);
+ $fee_nominal = $affiliatorItem->fee ? $affiliatorItem->fee : $gross * ($affiliator->fee_percentage/100);
+
+ }else if ($claimable->codeable_type == Affiliatoritem::class){
+
+ $affiliatorItem = $claimable->codeable;
+ $itemReference = $affiliatorItem->item;
+ $item_reference_ids = [$itemReference->id];
+ $gross = $this->findDetail($invoice, $item_reference_ids);
+ $fee_nominal = $affiliatorItem->fee ? $affiliatorItem->fee : $gross * ($affiliator->fee_percentage/100);
+
+ }else if ($claimable->codeable_type == VoucherEvent::class){
+ $voucherEvent = $claimable->codeable;
+ $item_reference_ids = $voucherEvent->items->pluck("id")->toArray();
+ $gross = $this->findDetail($invoice, $item_reference_ids);
+ $fee_nominal = $gross * ($affiliator->fee_percentage/100);
+ }else{
+ return [];
+ }
+
+
+ $incentive = new Incentive;
+ $incentive->fill([
+ "employee_id" => 0,
+ "person_id" => $affiliator->id,
+ "person_type" => get_class($affiliator),
+ "nominal" => $fee_nominal,
+ "date" => Carbon::now(),
+ "reference_id" => $voucher->id,
+ "reference_type" => get_class($voucher),
+ "status" => "OPEN"
+ ]);
+ $incentive->save();
+
+ return $item_reference_ids;
+ }
+
+ private function generateVoucher($nominal)
+ {
+ $code = "BLJ";
+ if ($nominal == 2000000) {
+ $code = "2JT";
+ } else if ($nominal == 1000000) {
+ $code = "1JT";
+ } else if ($nominal == 1500000) {
+ $code = "1.5JT";
+ } else if ($nominal == 2500000) {
+ $code = "2.5JT";
+ } else if ($nominal == 500000) {
+ $code = "500RB";
+ } else if ($nominal == 750000) {
+ $code = "750RB";
+ } else if ($nominal == 250000) {
+ $code = "250RB";
+ } else if ($nominal == 100000) {
+ $code = "100RB";
+ } else if ($nominal == 50000) {
+ $code = "50RB";
+ } else if ($nominal == 3000000) {
+ $code = "3JT";
+ }
+ $voucher = null;
+ $iter = 0;
+ while ($voucher == null) {
+ $new_code = strtoupper("EV/" . $code . "/" . date("Y") . "/" . bin2hex(openssl_random_pseudo_bytes(3)));
+ $exists = Voucher::where("number", $new_code)->first();
+ $voucher = $exists ? null : $new_code;
+ }
+ return $voucher;
+ }
+
+ private function issueVoucher($invoice, $name, $vouchers, $qty, $exp)
+ {
+
+ $nominal = str_replace(".", "", $name);
+ preg_match("/[0-9]++/", $nominal, $match);
+ $nominal = (float) @$match[0];
+
+ $evoucher = true;
+ if ($vouchers == "") {
+ $arr = [];
+ for ($i = 0; $i < $qty; $i++) {
+ $arr[] = $this->generateVoucher($nominal);
+ }
+
+ $evoucher = true;
+ } else {
+ $arr = explode(",", $vouchers);
+
+ $evoucher = false;
+ }
+ foreach ($arr as $voucher) {
+ $voucher = trim($voucher);
+ $item = Voucher::where("number", $voucher)->firstOrNew();
+ $item->customer_id = $invoice->customer_id;
+ $item->evoucher = $evoucher;
+ $item->number = $voucher;
+ $item->nominal = $nominal;
+ $item->issued_at = Carbon::now();
+ $item->expired_at = Carbon::now()->addMonth($exp);
+ $item->reference_issued_id = $invoice->id;
+ $item->reference_issued_type = get_class($invoice);
+ $item->save();
+ }
+
+ return implode(",", $arr);
+ }
+
+ public function update(PosInvoice $invoice, array $data)
+ {
+ if (!$this->isAdmin()) {
+ $isCreatedToday = Carbon::now()->diffInMinutes(Carbon::parse($invoice->time)) <= (60 * 24);
+ if (!$isCreatedToday) {
+ return abort(403, 'hanya invoice yang belum 24 jam yang bisa di edit');
+ }
+ }
+
+ $data["vouchers"] = implode(",", $data["vouchers"]);
+
+ $invoice->update($data);
+
+ $ids = [];
+ foreach ($data["details"] as $row) {
+ $detail = $invoice->details()->find(@$row["id"]);
+ if (!$detail){
+ $item = Items::find($row["item_id"]);
+ $row["unit_price"] = (float) @$row["unit_price"];
+ $row["total"] = (float) @$row["total"];
+ $row["unit_cost"] = @$item ? $item->unit_cost : 0;
+ $detail = $invoice->details()->create($row);
+ }else{
+ $detail->update([
+ "qty" => $row["qty"],
+ "serial_number" => $row["serial_number"],
+ "total" => $row["total"],
+ "unit_price" => $row["unit_price"],
+ "discount" => $row["discount"],
+ ]);
+ $item = $detail->item;
+ }
+
+ $this->deactivateSerialNumber($invoice, @$row['serial_number']);
+
+ $this->activateSerialNumber($invoice, @$row['serial_number'],[
+ "reference" => @$row["reference"],
+ "description" => @$row["description"],
+ "item_number" => @$row["item_number"],
+ "variant_code" => @$row["variant_code"],
+ "brand" => @$item->dimension->brand
+ ]);
+
+ $ids[] = $detail->id;
+ }
+ $invoice->details()->whereNotIn("id",$ids)->delete($ids);
+
+ $ids = [];
+ foreach ($data["payments"] as $row) {
+ if ($row["amount"] <= 0)
+ continue;
+
+ $detail = $invoice->details()->find(@$row["id"]);
+ if (!$detail){
+ $detail = $invoice->payments()->create($row);
+ }else{
+ $detail->update($row);
+ }
+ $ids[] = $detail->id;
+ }
+ $invoice->payments()->whereNotIn("id",$ids)->delete();
+
+ return $invoice;
+ }
+
+ public function cancel(PosInvoice $item, $data)
+ {
+ $data["canceled_by"] = auth()->user()->id;
+ $data["canceled_at"] = Carbon::now();
+ $item->fill($data);
+ $item->save();
+
+ DB::table("customer_points")->where("reference_id", $item->id)
+ ->where("reference_type", get_class($item))->delete();
+
+ DB::table("vouchers")
+ ->where("reference_used_id", $item->id)
+ ->where("reference_used_type", get_class($item))
+ ->update([
+ "used_at" => null
+ ]);
+ }
+
+ // public function delete(PosInvoice $item)
+ // {
+ // $item->delete();
+ // }
+
+ public function findBy($column, $value)
+ {
+ $item = PosInvoice::where($column, $value)->firstOrFail();
+ return $item;
+ }
+
+ public function isAdmin()
+ {
+ $admin = Role::where('name', 'ADMIN')->first();
+ $user = auth()->user();
+
+ return $user->role_id == $admin->id;
+ }
+}
diff --git a/app/ThirdParty/Biteship/Biteship.php b/app/ThirdParty/Biteship/Biteship.php
new file mode 100644
index 0000000..054f5b1
--- /dev/null
+++ b/app/ThirdParty/Biteship/Biteship.php
@@ -0,0 +1,40 @@
+rate = new Rate;
+ $this->order = new Order;
+ $this->tracking = new Tracking;
+ }
+
+ public function trackingByWaybill($params){
+ return $this->tracking->byWaybill($params);
+ }
+
+ public function rateByPostal($params){
+ return $this->rate->byPostal($params);
+ }
+
+ public function rateByLatlong($params){
+ return $this->rate->byLatLong($params);
+ }
+
+ public function orderByPostal($params){
+ return $this->order->byPostal($params);
+ }
+
+ public function orderByLatLong($params){
+ return $this->order->byLatLong($params);
+ }
+
+ public function trackingById($params){
+ return $this->tracking->byId($params);
+ }
+}
diff --git a/app/ThirdParty/Biteship/Order.php b/app/ThirdParty/Biteship/Order.php
new file mode 100644
index 0000000..7b88974
--- /dev/null
+++ b/app/ThirdParty/Biteship/Order.php
@@ -0,0 +1,116 @@
+ $key
+ ])
+ ->withBody(json_encode([
+ "origin_contact_name" => $params["origin_contact_name"],
+ "origin_contact_phone" => $params["origin_contact_phone"],
+ "origin_address" => $params["origin_address"],
+ "origin_postal_code" => $params["origin_postal_code"],
+ "origin_coordinate" => [
+ "latitude" => $params["origin_latitude"],
+ "longitude" => $params["origin_longitude"],
+ ],
+
+ "destination_contact_name" => $params["destination_contact_name"],
+ "destination_contact_phone" => $params["destination_contact_phone"],
+ "destination_address" => $params["destination_address"],
+ "destination_postal_code" => $params["destination_postal_code"],
+
+ "reference_id" => $params["reference_id"],
+ "courier_insurance" => $params["courier_insurance"],
+
+ "courier_company" => $params["courier_company"],
+ "courier_type" => $params["courier_type"],
+ "delivery_type" => "now",
+ "items" => $params["items"]
+
+ ]), 'application/json')
+ ->post($url."/v1/orders");
+
+ if ($res->status() == 200)
+ return $res->json();
+ else{
+ Log::error("Biteship order error", $res->json());
+ throw new Exception($res->json()['error']);
+ }
+
+ return null;
+ }
+
+ public function byLatLong($params){
+
+ $url = env("BITESHIP_URL");
+ $key = env("BITESHIP_KEY");
+ $res = Http::withHeaders([
+ "authorization" => $key
+ ])
+ ->withBody(json_encode([
+ "origin_contact_name" => $params["origin_contact_name"],
+ "origin_contact_phone" => $params["origin_contact_phone"],
+ "origin_address" => $params["origin_address"],
+ "origin_coordinate" => [
+ "latitude" => $params["origin_latitude"],
+ "longitude" => $params["origin_longitude"],
+ ],
+
+ "destination_contact_name" => $params["destination_contact_name"],
+ "destination_contact_phone" => $params["destination_contact_phone"],
+ "destination_address" => $params["destination_address"],
+ "destination_coordinate" => [
+ "latitude" => $params["destination_latitude"],
+ "longitude" => $params["destination_longitude"],
+ ],
+
+ "courier_company" => $params["courier_company"],
+ "courier_type" => $params["courier_type"],
+ "delivery_type" => "now",
+ "items" => $params["items"]
+
+ ]), 'application/json')
+ ->post($url."/v1/orders");
+
+ if ($res->status() == 200)
+ return $res->json();
+ else{
+ Log::error("Biteship order error", [$res->json()]);
+ throw new Exception($res->json()['error']);
+ }
+
+ return null;
+
+ }
+
+
+ public function confirm($id){
+
+ $url = env("BITESHIP_URL");
+ $key = env("BITESHIP_KEY");
+ $res = Http::withHeaders([
+ "authorization" => $key
+ ])
+ ->post($url."/v1/orders/".$id."/confirm");
+
+ if ($res->status() == 200)
+ return $res->json();
+ else
+ Log::error("Biteship order error", [$res->json()]);
+
+ return null;
+ }
+
+}
diff --git a/app/ThirdParty/Biteship/Rate.php b/app/ThirdParty/Biteship/Rate.php
new file mode 100644
index 0000000..7e72ec2
--- /dev/null
+++ b/app/ThirdParty/Biteship/Rate.php
@@ -0,0 +1,81 @@
+ $key
+ ])
+ ->withBody(json_encode([
+ "origin_latitude" => $origin_latitude,
+ "origin_longitude" => $origin_longitude,
+ "destination_latitude" => $destination_latitude,
+ "destination_longitude" => $destination_longitude,
+ "couriers" => env("BITESHIP_COURIER_ALL","grab,gojek,tiki,jnt,anteraja"),
+ "items" => $items
+ ]), 'application/json')
+ ->post($url."/v1/rates/couriers");
+
+ if ($res->status() == 200)
+ return $res->json();
+
+ return null;
+ });
+ }
+
+ public function byPostal($params)
+ {
+ $destination_postal_code = $params["destination_postal_code"];
+ $origin_postal_code = $params["origin_postal_code"];
+ $items = $params["items"];
+ $sha1 = sha1(json_encode($items));
+
+ $key = $origin_postal_code."_".$destination_postal_code."_".$sha1;
+ return Cache::remember("rates_".$key, 60 * 60 * 24, function() use (
+ $origin_postal_code,
+ $destination_postal_code,
+ $items) {
+ $url = env("BITESHIP_URL");
+ $key = env("BITESHIP_KEY");
+ $res = Http::withHeaders([
+ "authorization" => $key
+ ])
+ ->withBody(json_encode([
+ "origin_postal_code" => $origin_postal_code,
+ "destination_postal_code" => $destination_postal_code,
+ "couriers" => env("BITESHIP_COURIER","tiki,jnt,anteraja"),
+ "items" => $items
+ ]), 'application/json')
+ ->post($url."/v1/rates/couriers");
+
+ if ($res->status() == 200)
+ return $res->json();
+
+ return null;
+ });
+ }
+}
diff --git a/app/ThirdParty/Biteship/Tracking.php b/app/ThirdParty/Biteship/Tracking.php
new file mode 100644
index 0000000..027c5c7
--- /dev/null
+++ b/app/ThirdParty/Biteship/Tracking.php
@@ -0,0 +1,54 @@
+ $key
+ ])
+ ->get($url."/v1/trackings/".$waybill_id."/couriers/".$courier);
+
+ if ($res->status() == 200)
+ return $res->json();
+
+ return null;
+ });
+ }
+
+
+ public function byId($params)
+ {
+ $id = $params["id"];
+
+ $key = $id ;
+ return Cache::remember("tracking_id_".$key, 60 * 60 * 24, function() use ($id) {
+ $url = env("BITESHIP_URL");
+ $key = env("BITESHIP_KEY");
+ $res = Http::withHeaders([
+ "authorization" => $key
+ ])
+ ->get($url."/v1/trackings/".$id);
+
+ if ($res->status() == 200)
+ return $res->json();
+
+ return null;
+ });
+ }
+
+}
diff --git a/lang/en/cart_summary.php b/lang/en/cart_summary.php
new file mode 100644
index 0000000..75ba172
--- /dev/null
+++ b/lang/en/cart_summary.php
@@ -0,0 +1,15 @@
+ 'Order summary',
+ 'edit' => 'Edit',
+ 'subtotal' => 'Subtotal',
+ 'items_count' => ':count items',
+ 'savings' => 'Saving',
+ 'tax_collected' => 'Tax collected',
+ 'shipping' => 'Shipping',
+ 'calculated_at_checkout' => 'Calculated at checkout',
+ 'estimated_total' => 'Estimated total',
+ 'bonuses_earned' => 'Congratulations! You have earned :count bonuses',
+ 'create_account' => 'Create an account',
+ 'and_get' => 'and get',
+];
\ No newline at end of file
diff --git a/lang/en/checkout.php b/lang/en/checkout.php
new file mode 100644
index 0000000..c3cfea7
--- /dev/null
+++ b/lang/en/checkout.php
@@ -0,0 +1,47 @@
+ 'Delivery Method',
+ 'choose_delivery_method' => 'Choose how you want to receive your order',
+ 'delivery' => 'Delivery',
+ 'delivery_description' => 'We deliver to your address',
+ 'store_pickup' => 'Store Pickup',
+ 'pickup_description' => 'Pick up from our store',
+ 'postcode' => 'Postcode',
+ 'postcode_placeholder' => 'e.g. 12345',
+ 'calculate_shipping' => 'Calculate Shipping',
+ 'calculating' => 'Calculating...',
+ 'shipping_address' => 'Shipping Address',
+ 'first_name' => 'First Name',
+ 'last_name' => 'Last Name',
+ 'address' => 'Address',
+ 'address2' => 'Address Line 2',
+ 'optional' => 'optional',
+ 'city' => 'City',
+ 'state' => 'State',
+ 'select_state' => 'Select State',
+ 'zip' => 'ZIP Code',
+ 'phone' => 'Phone',
+ 'select_store' => 'Select Store',
+ 'asia_golf_jakarta' => 'Asia Golf Jakarta',
+ 'asia_golf_bandung' => 'Asia Golf Bandung',
+ 'store_address' => 'Address',
+ 'hours' => 'Hours',
+ 'continue_to_payment' => 'Continue to Payment',
+ 'free' => 'FREE',
+ 'shipping' => 'Shipping',
+ 'enter_postcode' => 'Please enter your postcode',
+ 'calculate_shipping_first' => 'Please calculate shipping first',
+ 'fill_required_fields' => 'Please fill in all required fields',
+ 'select_store' => 'Please select a store',
+ 'select_saved_address' => 'Select Saved Address',
+ 'choose_address' => 'Choose an address',
+ 'add_new_address' => 'Add New Address',
+ 'primary' => 'Primary',
+ 'continue_to_shipping' => 'Continue to Shipping',
+ 'choose_shipping' => 'Choose Shipping',
+ 'payment' => 'Payment',
+ 'delivery' => 'Delivery',
+ 'pickup' => 'Pickup',
+ 'pickup_ready' => 'Your order will be ready for pickup at the selected store',
+ 'continue_to_payment' => 'Continue to Payment',
+];
diff --git a/lang/id/cart_summary.php b/lang/id/cart_summary.php
new file mode 100644
index 0000000..41b63a5
--- /dev/null
+++ b/lang/id/cart_summary.php
@@ -0,0 +1,15 @@
+ 'Ringkasan Pesanan',
+ 'edit' => 'Edit',
+ 'subtotal' => 'Subtotal',
+ 'items_count' => ':count barang',
+ 'savings' => 'Hemat',
+ 'tax_collected' => 'Pajak yang dipungut',
+ 'shipping' => 'Pengiriman',
+ 'calculated_at_checkout' => 'Dihitung saat checkout',
+ 'estimated_total' => 'Total perkiraan',
+ 'bonuses_earned' => 'Selamat! Anda telah mendapatkan :count bonus',
+ 'create_account' => 'Buat akun',
+ 'and_get' => 'dan dapatkan',
+];
\ No newline at end of file
diff --git a/lang/id/checkout.php b/lang/id/checkout.php
new file mode 100644
index 0000000..744cbae
--- /dev/null
+++ b/lang/id/checkout.php
@@ -0,0 +1,36 @@
+ 'Metode Pengiriman',
+ 'choose_delivery_method' => 'Pilih bagaimana Anda ingin menerima pesanan Anda',
+ 'delivery' => 'Pengiriman',
+ 'delivery_description' => 'Kami mengirim ke alamat Anda',
+ 'store_pickup' => 'Ambil di Toko',
+ 'pickup_description' => 'Ambil dari toko kami',
+ 'postcode' => 'Kode Pos',
+ 'postcode_placeholder' => 'contoh: 12345',
+ 'calculate_shipping' => 'Hitung Ongkir',
+ 'calculating' => 'Menghitung...',
+ 'shipping_address' => 'Alamat Pengiriman',
+ 'first_name' => 'Nama Depan',
+ 'last_name' => 'Nama Belakang',
+ 'address' => 'Alamat',
+ 'address2' => 'Alamat Baris 2',
+ 'optional' => 'opsional',
+ 'city' => 'Kota',
+ 'state' => 'Provinsi',
+ 'select_state' => 'Pilih Provinsi',
+ 'zip' => 'Kode Pos',
+ 'phone' => 'Telepon',
+ 'select_store' => 'Pilih Toko',
+ 'asia_golf_jakarta' => 'Asia Golf Jakarta',
+ 'asia_golf_bandung' => 'Asia Golf Bandung',
+ 'store_address' => 'Alamat',
+ 'hours' => 'Jam',
+ 'continue_to_payment' => 'Lanjut ke Pembayaran',
+ 'free' => 'GRATIS',
+ 'shipping' => 'Pengiriman',
+ 'enter_postcode' => 'Silakan masukkan kode pos Anda',
+ 'calculate_shipping_first' => 'Silakan hitung ongkir terlebih dahulu',
+ 'fill_required_fields' => 'Silakan isi semua field yang wajib diisi',
+ 'select_store' => 'Silakan pilih toko',
+];
diff --git a/resources/views/checkout/v1-cart.blade.php b/resources/views/checkout/v1-cart.blade.php
index 8011e6f..baa7593 100644
--- a/resources/views/checkout/v1-cart.blade.php
+++ b/resources/views/checkout/v1-cart.blade.php
@@ -202,7 +202,7 @@
$0.00
+ href="{{ route('checkout.delivery') }}">
Proceed to checkout
diff --git a/resources/views/checkout/v1-delivery-1-shipping.blade.php b/resources/views/checkout/v1-delivery-1-shipping.blade.php
new file mode 100644
index 0000000..c2d2428
--- /dev/null
+++ b/resources/views/checkout/v1-delivery-1-shipping.blade.php
@@ -0,0 +1,460 @@
+@extends('layouts.landing', ['title' => 'Checkout v.1 - Delivery Info Step 1'])
+
+@section('content')
+ {{ __('checkout.delivery') }} {{ $address->location }} {{ __('checkout.pickup') }}{{ __('checkout.delivery_method') }}
+
+ @if ($delivery_method == 'shipping')
+ {{ __('checkout.choose_shipping') }}
+
+
+ {{ __('checkout.payment') }}
+
Add your Postcode to see the delivery and collection options - available in your area.
-{{ __('checkout.choose_delivery_method') }}
+ + +