checkout page, fill address, choose shipping, calculation
This commit is contained in:
parent
9a23e75f5b
commit
d298649398
|
|
@ -0,0 +1,159 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Address;
|
||||||
|
use App\Models\Location;
|
||||||
|
use App\Repositories\Member\Cart\MemberCartRepository;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class CheckoutController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request, MemberCartRepository $memberCartRepository)
|
||||||
|
{
|
||||||
|
$request->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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Member\Transaction;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class CancelRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
$transaction = $this->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',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Member\Transaction;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class CloseRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
$transaction = $this->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',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Member\Transaction;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class DeliverRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
$transaction = $this->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',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Member\Transaction;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class DetailRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
$transaction = $this->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',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Member\Transaction;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class ProcessRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
$transaction = $this->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',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Member\Transaction;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class TransactionRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'address_id' => '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',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class LuckyWheel extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
public function prizes(){
|
||||||
|
return $this->hasMany(LuckyWheelPrize::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function gets(){
|
||||||
|
return $this->hasMany(LuckyWheelGet::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tickets(){
|
||||||
|
return $this->hasMany(LuckyWheelTicket::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class LuckyWheelGet extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = ['prize_id','lucky_wheel_id','voucher_id','nominal','redeem_at'];
|
||||||
|
|
||||||
|
public function prize(){
|
||||||
|
return $this->belongsTo(LuckyWheelPrize::class,'prize_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function voucher(){
|
||||||
|
return $this->belongsTo(Voucher::class,'voucher_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class LuckyWheelPrize extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = ['name','voucher_event_id'];
|
||||||
|
|
||||||
|
public function voucher(){
|
||||||
|
return $this->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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class LuckyWheelTicket extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = ['customer_id','invoice_id','max_times'];
|
||||||
|
|
||||||
|
public function luckyWheel()
|
||||||
|
{
|
||||||
|
return $this->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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Spatie\Activitylog\Traits\LogsActivity;
|
||||||
|
use Spatie\Activitylog\LogOptions;
|
||||||
|
|
||||||
|
class PosInvoice extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use LogsActivity;
|
||||||
|
|
||||||
|
public function getActivitylogOptions(): LogOptions
|
||||||
|
{
|
||||||
|
return LogOptions::defaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $fillable = ["location_id","customer_id","sales_id","sales2_id",
|
||||||
|
"user_id","time","number",
|
||||||
|
"note","subtotal", "voucher", "discount","tax","total",
|
||||||
|
"vouchers","canceled_at","canceled_note","canceled_by", "note_internal"];
|
||||||
|
|
||||||
|
public function ticket()
|
||||||
|
{
|
||||||
|
return $this->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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class PosInvoiceDetail extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = ['item_reference_id','point', 'invoice_id', 'item_id','item_variant_id','note','qty','unit_price','unit','reference','discount','vat','total','unit_cost','line_no',
|
||||||
|
'description','item_number','variant_code','invoice_discount', 'line_discount', 'net_price', 'pricelist_discount','serial_number'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function itemReference()
|
||||||
|
{
|
||||||
|
return $this->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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class PosInvoicePayment extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = ['method', 'amount', 'bank', 'card_number', 'remarks', 'installment', 'bank_id'];
|
||||||
|
|
||||||
|
public function bank_raw()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Bank::class, 'bank_id', 'id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class PosInvoiceVoucher extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'pos_invoice_vouchers';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'pos_invoice_id',
|
||||||
|
'voucher_id',
|
||||||
|
'nominal'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function invoice()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(PosInvoice::class, 'pos_invoice_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function voucher()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Voucher::class, 'voucher_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Cviebrock\EloquentSluggable\Sluggable;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Spatie\Activitylog\Traits\LogsActivity;
|
||||||
|
use Spatie\Activitylog\LogOptions;
|
||||||
|
|
||||||
|
class Sales extends Model
|
||||||
|
{
|
||||||
|
use HasFactory, SoftDeletes, Sluggable;
|
||||||
|
use LogsActivity;
|
||||||
|
|
||||||
|
public function getActivitylogOptions(): LogOptions
|
||||||
|
{
|
||||||
|
return LogOptions::defaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $table = 'sales';
|
||||||
|
protected $primaryKey = 'id';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'id',
|
||||||
|
'number',
|
||||||
|
'name',
|
||||||
|
'phone',
|
||||||
|
'email',
|
||||||
|
'address',
|
||||||
|
'country',
|
||||||
|
'province_id',
|
||||||
|
'city_id',
|
||||||
|
'district_id',
|
||||||
|
'village_id',
|
||||||
|
'postal_code',
|
||||||
|
'customer_group_id',
|
||||||
|
'location_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function sluggable(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'number' => [
|
||||||
|
'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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
class SerialNumber extends Model
|
||||||
|
{
|
||||||
|
use HasFactory, SoftDeletes;
|
||||||
|
|
||||||
|
protected $table = 'sn_batches';
|
||||||
|
|
||||||
|
protected $fillable = ['item_reference_id', 'reference_number', 'item_number', 'variant_code',
|
||||||
|
'description', 'user_id', 'qty','status','closed_at'];
|
||||||
|
|
||||||
|
public function details() {
|
||||||
|
return $this->hasMany(SerialNumberDetail::class, 'sn_batch_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user() {
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class SerialNumberDetail extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'sn_batch_details';
|
||||||
|
|
||||||
|
protected $fillable = ['sn_batch_id', 'number','activated_at','invoice_id','activated_by'];
|
||||||
|
|
||||||
|
public function data(){
|
||||||
|
return $this->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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
class SerialNumberLog extends Model
|
||||||
|
{
|
||||||
|
use HasFactory, SoftDeletes;
|
||||||
|
|
||||||
|
protected $fillable = ['reference_number','item_number','variant_code','description','brand','activated_at','invoice_no'];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
class Survey extends Model
|
||||||
|
{
|
||||||
|
use HasFactory, SoftDeletes;
|
||||||
|
|
||||||
|
protected $table = 'survey';
|
||||||
|
|
||||||
|
protected $fillable = ['code', 'name', 'title', 'content', 'voucher_event_id'];
|
||||||
|
|
||||||
|
public function detail() {
|
||||||
|
return $this->hasMany(SurveyQuestion::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function voucherEvent() {
|
||||||
|
return $this->belongsTo(VoucherEvent::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class SurveyFeedback extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = ['code','survey_id', 'channel', 'time', 'customer_id', 'invoice_id', 'ip_address', 'agent'];
|
||||||
|
|
||||||
|
public function detail() {
|
||||||
|
return $this->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class SurveyFeedbackDetail extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = ['survey_question_id', 'survey_feedback_id', 'value', 'description'];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
class SurveyQuestion extends Model
|
||||||
|
{
|
||||||
|
use HasFactory, SoftDeletes;
|
||||||
|
|
||||||
|
protected $fillable = ['survey_id', 'type', 'data', 'description', 'order'];
|
||||||
|
|
||||||
|
public $timestamps = false;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class XenditLink extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
"uid",
|
||||||
|
"invoice_url",
|
||||||
|
"user_id",
|
||||||
|
"external_id",
|
||||||
|
"status",
|
||||||
|
"amount",
|
||||||
|
"received_amount",
|
||||||
|
"expiry_date",
|
||||||
|
"payment_id",
|
||||||
|
"paid_amount",
|
||||||
|
"payment_method",
|
||||||
|
"bank_code",
|
||||||
|
"payment_channel",
|
||||||
|
"payment_destination",
|
||||||
|
"paid_at"
|
||||||
|
];
|
||||||
|
|
||||||
|
public function payment()
|
||||||
|
{
|
||||||
|
return $this->morphOne(TransactionPayment::class,"method");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications;
|
||||||
|
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
use Kreait\Firebase\Messaging\CloudMessage;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\UserDevice;
|
||||||
|
|
||||||
|
class FcmChannel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Send the given notification.
|
||||||
|
*/
|
||||||
|
public function send(object $notifiable, Notification $notification): void
|
||||||
|
{
|
||||||
|
// $payload = $notification->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);
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications\Member\Transaction;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
use App\Models\Transaction;
|
||||||
|
use App\Notifications\FcmChannel;
|
||||||
|
|
||||||
|
|
||||||
|
class NewOrder extends Notification implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
protected $transaction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new notification instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(Transaction $transaction)
|
||||||
|
{
|
||||||
|
$this->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"],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications\Member\Transaction;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
use App\Models\Transaction;
|
||||||
|
use App\Notifications\FcmChannel;
|
||||||
|
|
||||||
|
|
||||||
|
class OrderCanceled extends Notification implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
protected $transaction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new notification instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(Transaction $transaction)
|
||||||
|
{
|
||||||
|
$this->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"],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications\Member\Transaction;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
use App\Models\Transaction;
|
||||||
|
use App\Notifications\FcmChannel;
|
||||||
|
|
||||||
|
|
||||||
|
class OrderDelivered extends Notification implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
protected $transaction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new notification instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(Transaction $transaction)
|
||||||
|
{
|
||||||
|
$this->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"],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications\Member\Transaction;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
use App\Models\Transaction;
|
||||||
|
use App\Notifications\FcmChannel;
|
||||||
|
|
||||||
|
|
||||||
|
class OrderOnDelivery extends Notification implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
protected $transaction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new notification instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(Transaction $transaction)
|
||||||
|
{
|
||||||
|
$this->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"],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications\Member\Transaction;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
use App\Models\Transaction;
|
||||||
|
use App\Notifications\FcmChannel;
|
||||||
|
|
||||||
|
|
||||||
|
class OrderPaid extends Notification implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
protected $transaction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new notification instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(Transaction $transaction)
|
||||||
|
{
|
||||||
|
$this->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"],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications\Member\Transaction;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
use App\Models\Transaction;
|
||||||
|
use App\Notifications\FcmChannel;
|
||||||
|
|
||||||
|
|
||||||
|
class OrderProcessed extends Notification implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
protected $transaction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new notification instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(Transaction $transaction)
|
||||||
|
{
|
||||||
|
$this->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"],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications\Member\Transaction;
|
||||||
|
|
||||||
|
use App\Models\Transaction;
|
||||||
|
use App\Notifications\FcmChannel;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
|
||||||
|
class OrderWaitPayment extends Notification implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
protected $transaction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new notification instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(Transaction $transaction)
|
||||||
|
{
|
||||||
|
$this->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'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repositories\Crm;
|
||||||
|
|
||||||
|
use App\Models\Survey;
|
||||||
|
use App\Models\SurveyFeedback;
|
||||||
|
use App\Models\SurveyFeedbackDetail;
|
||||||
|
use App\Models\SurveyQuestion;
|
||||||
|
use App\Notifications\Crm\SurveyRespond;
|
||||||
|
use App\Repositories\Member\VoucherEvent\VoucherEventRepository;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use DB;
|
||||||
|
|
||||||
|
class SurveyRepository
|
||||||
|
{
|
||||||
|
|
||||||
|
var $voucherEventRepository;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
VoucherEventRepository $voucherEventRepository) {
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,18 @@ class MemberCartRepository
|
||||||
->sum('qty');
|
->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)
|
static public function clearAll($locationId = null)
|
||||||
{
|
{
|
||||||
$location = $locationId ?? session('location_id', 22);
|
$location = $locationId ?? session('location_id', 22);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,255 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repositories\Member;
|
||||||
|
|
||||||
|
use App\ThirdParty\Biteship\Biteship;
|
||||||
|
|
||||||
|
use App\Models\Cart;
|
||||||
|
use App\Models\Location;
|
||||||
|
use App\Models\Address;
|
||||||
|
use App\Models\Transaction;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class ShippingRepository
|
||||||
|
{
|
||||||
|
|
||||||
|
var $biteship;
|
||||||
|
|
||||||
|
public function __construct(Biteship $biteship) {
|
||||||
|
$this->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();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Member\Transaction;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\Member\Transaction\TransactionRequest;
|
||||||
|
use App\Http\Resources\Member\Transaction\CheckoutResource;
|
||||||
|
use App\Repositories\Member\Transaction\TransactionRepository;
|
||||||
|
use App\Notifications\Member\Transaction\OrderWaitPayment;
|
||||||
|
use Illuminate\Support\Facades\Notification;
|
||||||
|
|
||||||
|
class CheckoutController extends Controller
|
||||||
|
{
|
||||||
|
public function index(TransactionRequest $request, TransactionRepository $repository)
|
||||||
|
{
|
||||||
|
$data = $request->validated();
|
||||||
|
$item = $repository->create($data);
|
||||||
|
|
||||||
|
$notification = new OrderWaitPayment($item);
|
||||||
|
$user = auth()->user();
|
||||||
|
$user->notify($notification->delay(now()->addMinutes(1)));
|
||||||
|
|
||||||
|
return new CheckoutResource($item);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,872 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repositories\Member\Transaction;
|
||||||
|
|
||||||
|
use App\Repositories\Member\Voucher\VoucherRepository;
|
||||||
|
use App\Models\Transaction;
|
||||||
|
use App\Models\Customer;
|
||||||
|
use App\Models\TransactionDetail;
|
||||||
|
use App\Models\TransactionStatus;
|
||||||
|
use App\Models\TransactionPayment;
|
||||||
|
use App\Models\XenditLink;
|
||||||
|
use App\Models\Location;
|
||||||
|
use App\Models\Items;
|
||||||
|
use App\Models\ItemReference;
|
||||||
|
use App\Models\Voucher;
|
||||||
|
use App\Models\Cart;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use App\Helpers\AutoNumbering;
|
||||||
|
use App\Models\CustomerPoint;
|
||||||
|
use App\Repositories\Member\ShippingRepository;
|
||||||
|
use App\Repositories\Pos\InvoiceRepository;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use App\ThirdParty\Xendit\Xendit;
|
||||||
|
use App\Models\TransactionShipping;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Exception;
|
||||||
|
use App\Repositories\Auth\RoleRepository;
|
||||||
|
use Illuminate\Support\Facades\Notification;
|
||||||
|
use App\Notifications\Member\Transaction\NewOrder;
|
||||||
|
use App\Notifications\Member\Transaction\OrderPaid;
|
||||||
|
use App\Notifications\Member\Transaction\OrderProcessed;
|
||||||
|
use App\Notifications\Member\Transaction\OrderOnDelivery;
|
||||||
|
use App\Notifications\Member\Transaction\OrderDelivered;
|
||||||
|
use App\Notifications\Member\Transaction\OrderCanceled;
|
||||||
|
|
||||||
|
class TransactionRepository
|
||||||
|
{
|
||||||
|
|
||||||
|
var $shippingRepository;
|
||||||
|
var $invoiceRepository;
|
||||||
|
var $roleRepository;
|
||||||
|
var $voucherRepository;
|
||||||
|
var $xendit;
|
||||||
|
|
||||||
|
public function __construct(ShippingRepository $shippingRepository,
|
||||||
|
InvoiceRepository $invoiceRepository,
|
||||||
|
VoucherRepository $voucherRepository,
|
||||||
|
RoleRepository $roleRepository,
|
||||||
|
Xendit $xendit) {
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,842 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repositories\Pos;
|
||||||
|
|
||||||
|
use App\Models\Bank;
|
||||||
|
use App\Models\Survey;
|
||||||
|
use App\Models\PosInvoice;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use App\Models\Items;
|
||||||
|
use App\Models\Location;
|
||||||
|
use App\Models\Role;
|
||||||
|
use App\Models\Sales;
|
||||||
|
use App\Models\Voucher;
|
||||||
|
use App\Models\VoucherClaim;
|
||||||
|
use App\Models\AffiliatorItem;
|
||||||
|
use App\Models\VoucherEvent;
|
||||||
|
use App\Models\AffiliatorItemCode;
|
||||||
|
use App\Models\Incentive;
|
||||||
|
use App\Helpers\AutoNumbering;
|
||||||
|
use App\Models\XenditLink;
|
||||||
|
use App\Models\ItemReference;
|
||||||
|
use App\Models\LuckyWheel;
|
||||||
|
use App\Models\SerialNumberDetail;
|
||||||
|
use App\Models\SerialNumberLog;
|
||||||
|
use App\Notifications\Crm\SurveyBroadcast;
|
||||||
|
use App\Repositories\Crm\SurveyRepository;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class InvoiceRepository
|
||||||
|
{
|
||||||
|
|
||||||
|
var $surveyRepository;
|
||||||
|
|
||||||
|
public function __construct(SurveyRepository $surveyRepository){
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\ThirdParty\Biteship;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
class Biteship
|
||||||
|
{
|
||||||
|
public function __construct() {
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\ThirdParty\Biteship;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
class Order
|
||||||
|
{
|
||||||
|
public function byPostal($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_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\ThirdParty\Biteship;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
class Rate
|
||||||
|
{
|
||||||
|
|
||||||
|
public function byLatLong($params)
|
||||||
|
{
|
||||||
|
$origin_latitude = $params["origin_latitude"];
|
||||||
|
$origin_longitude = $params["origin_longitude"];
|
||||||
|
$destination_latitude = $params["destination_latitude"];
|
||||||
|
$destination_longitude = $params["destination_longitude"];
|
||||||
|
$items = $params["items"];
|
||||||
|
$sha1 = sha1(json_encode($items));
|
||||||
|
|
||||||
|
$key = implode("_", [$origin_latitude, $origin_longitude, $destination_latitude, $destination_longitude, $sha1]);
|
||||||
|
return Cache::remember("rates_".$key, 60 * 60 * 24, function()
|
||||||
|
use ($origin_latitude,
|
||||||
|
$origin_longitude,
|
||||||
|
$destination_latitude,
|
||||||
|
$destination_longitude,
|
||||||
|
$items) {
|
||||||
|
$url = env("BITESHIP_URL");
|
||||||
|
$key = env("BITESHIP_KEY");
|
||||||
|
$res = Http::withHeaders([
|
||||||
|
"authorization" => $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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\ThirdParty\Biteship;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
class Tracking
|
||||||
|
{
|
||||||
|
|
||||||
|
public function byWaybill($params)
|
||||||
|
{
|
||||||
|
$waybill_id = $params["waybill_id"];
|
||||||
|
$courier = $params["courier"];
|
||||||
|
|
||||||
|
$key = $waybill_id ." ". $courier;
|
||||||
|
return Cache::remember("tracking_waybill_".$key, 60 * 60 * 24, function() use ($waybill_id, $courier) {
|
||||||
|
$url = env("BITESHIP_URL");
|
||||||
|
$key = env("BITESHIP_KEY");
|
||||||
|
$res = Http::withHeaders([
|
||||||
|
"authorization" => $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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
return [
|
||||||
|
'title' => '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',
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
return [
|
||||||
|
'delivery_method' => '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',
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
return [
|
||||||
|
'title' => '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',
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
return [
|
||||||
|
'delivery_method' => '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',
|
||||||
|
];
|
||||||
|
|
@ -202,7 +202,7 @@
|
||||||
<span class="h5 mb-0" id="cart-estimated-total">$0.00</span>
|
<span class="h5 mb-0" id="cart-estimated-total">$0.00</span>
|
||||||
</div>
|
</div>
|
||||||
<a class="btn btn-lg btn-primary w-100"
|
<a class="btn btn-lg btn-primary w-100"
|
||||||
href="{{ route('second', ['checkout', 'v1-delivery-1']) }}">
|
href="{{ route('checkout.delivery') }}">
|
||||||
Proceed to checkout
|
Proceed to checkout
|
||||||
<i class="ci-chevron-right fs-lg ms-1 me-n1"></i>
|
<i class="ci-chevron-right fs-lg ms-1 me-n1"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,460 @@
|
||||||
|
@extends('layouts.landing', ['title' => 'Checkout v.1 - Delivery Info Step 1'])
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<x-layout.header />
|
||||||
|
|
||||||
|
<!-- Page content -->
|
||||||
|
<main class="content-wrapper">
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="row pt-1 pt-sm-3 pt-lg-4 pb-2 pb-md-3 pb-lg-4 pb-xl-5">
|
||||||
|
|
||||||
|
<!-- Delivery info (Step 1) -->
|
||||||
|
<div class="col-lg-8 col-xl-7 mb-5 mb-lg-0">
|
||||||
|
<div class="d-flex flex-column gap-5 pe-lg-4 pe-xl-0">
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class="d-flex align-items-center justify-content-center bg-body-secondary text-body-secondary rounded-circle fs-sm fw-semibold lh-1 flex-shrink-0"
|
||||||
|
style="width: 2rem; height: 2rem; margin-top: -.125rem">1</div>
|
||||||
|
<div class="w-100 ps-3 ps-md-4">
|
||||||
|
<h2 class="h5 text-body-secondary mb-md-4">{{ __('checkout.delivery_method') }}</h2>
|
||||||
|
|
||||||
|
@if ($delivery_method == 'shipping')
|
||||||
|
<p>{{ __('checkout.delivery') }}</p>
|
||||||
|
@if ($address)
|
||||||
|
<p>{{ $address->location }}</p>
|
||||||
|
@endif
|
||||||
|
@else
|
||||||
|
<p>{{ __('checkout.pickup') }}</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-start" id="shippingAddressStep">
|
||||||
|
<div class="d-flex align-items-center justify-content-center bg-primary text-white rounded-circle fs-sm fw-semibold lh-1 flex-shrink-0"
|
||||||
|
style="width: 2rem; height: 2rem; margin-top: -.125rem">2</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="w-100 ps-3 ps-md-4">
|
||||||
|
<h1 class="h5 mb-12">{{ __('checkout.choose_shipping') }}
|
||||||
|
</h1>
|
||||||
|
<form action="{{ route('checkout.shipping.process') }}" method="post">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="delivery_method" value="{{ $delivery_method }}">
|
||||||
|
<input type="hidden" name="address_id" value="{{ $address_id }}">
|
||||||
|
|
||||||
|
@if ($delivery_method == 'shipping')
|
||||||
|
@foreach ($shipping_list as $shipping)
|
||||||
|
<div class="form-check mb-3">
|
||||||
|
<input class="form-check-input" type="radio" name="shipping_option"
|
||||||
|
id="shipping_{{ $loop->index }}"
|
||||||
|
value="{{ $shipping['courier'] }}|{{ $shipping['service'] }}|{{ $shipping['cost'] }}"
|
||||||
|
{{ $loop->first ? 'checked' : '' }}>
|
||||||
|
<label
|
||||||
|
class="form-check-label d-flex justify-content-between align-items-center"
|
||||||
|
for="shipping_{{ $loop->index }}">
|
||||||
|
<div>
|
||||||
|
<strong>{{ $shipping['title'] }}</strong>
|
||||||
|
<div class="text-muted small">{{ $shipping['courier'] }} -
|
||||||
|
{{ $shipping['service'] }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-primary fw-bold">
|
||||||
|
Rp {{ number_format($shipping['cost'], 0, ',', '.') }}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
@else
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="ci-store me-2"></i>
|
||||||
|
{{ __('checkout.pickup_ready') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-lg btn-primary w-100 mt-4">
|
||||||
|
<span>{{ __('checkout.continue_to_payment') }}</span>
|
||||||
|
<i class="ci-chevron-right fs-lg ms-1 me-n1"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class="d-flex align-items-center justify-content-center bg-body-secondary text-body-secondary rounded-circle fs-sm fw-semibold lh-1 flex-shrink-0"
|
||||||
|
style="width: 2rem; height: 2rem; margin-top: -.125rem">3</div>
|
||||||
|
<h2 class="h5 text-body-secondary ps-3 ps-md-4 mb-0">{{ __('checkout.payment') }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Order summary (sticky sidebar) -->
|
||||||
|
<aside class="col-lg-4 offset-xl-1" style="margin-top: -100px">
|
||||||
|
<div class="position-sticky top-0" style="padding-top: 100px">
|
||||||
|
<x-checkout.order-summary :subtotal="$subtotal" :total="$total" :savings="0" :tax="0"
|
||||||
|
:showEdit="true" :editUrl="route('second', ['checkout', 'v1-cart'])" />
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
@include('layouts.partials/footer')
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('scripts')
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Delivery method selection
|
||||||
|
const deliveryOption = document.getElementById('deliveryOption');
|
||||||
|
const pickupOption = document.getElementById('pickupOption');
|
||||||
|
const deliveryOptions = document.getElementById('deliveryOptions');
|
||||||
|
const pickupOptions = document.getElementById('pickupOptions');
|
||||||
|
const shippingAddress = document.getElementById('shippingAddress');
|
||||||
|
const continueButton = document.getElementById('continueButton');
|
||||||
|
|
||||||
|
// Handle delivery method change
|
||||||
|
deliveryOption.addEventListener('change', function() {
|
||||||
|
if (this.checked) {
|
||||||
|
deliveryOptions.style.display = 'block';
|
||||||
|
pickupOptions.style.display = 'none';
|
||||||
|
resetPickupSelection();
|
||||||
|
// Update button text for delivery
|
||||||
|
document.getElementById('continueButtonText').textContent =
|
||||||
|
'{{ __('checkout.continue_to_shipping') }}';
|
||||||
|
// Update hidden input values
|
||||||
|
document.getElementById('deliveryMethodInput').value = 'delivery';
|
||||||
|
// Update address ID from selected address
|
||||||
|
const addressSelect = document.getElementById('addressSelect');
|
||||||
|
if (addressSelect && addressSelect.value) {
|
||||||
|
document.getElementById('addressIdInput').value = addressSelect.value;
|
||||||
|
}
|
||||||
|
// Show shipping address step and shipping row
|
||||||
|
const shippingAddressStep = document.getElementById('shippingAddressStep');
|
||||||
|
const shippingRow = document.getElementById('shipping-row');
|
||||||
|
if (shippingAddressStep) {
|
||||||
|
console.log("Showing shipping address step");
|
||||||
|
shippingAddressStep.style.visibility = 'visible';
|
||||||
|
shippingAddressStep.style.height = 'auto';
|
||||||
|
shippingAddressStep.style.overflow = 'visible';
|
||||||
|
shippingAddressStep.style.margin = '';
|
||||||
|
shippingAddressStep.style.padding = '';
|
||||||
|
}
|
||||||
|
if (shippingRow) {
|
||||||
|
console.log("Showing shipping row");
|
||||||
|
shippingRow.style.visibility = 'visible';
|
||||||
|
shippingRow.style.height = 'auto';
|
||||||
|
shippingRow.style.overflow = 'visible';
|
||||||
|
shippingRow.style.margin = '';
|
||||||
|
shippingRow.style.padding = '';
|
||||||
|
} else {
|
||||||
|
console.log("Shipping row not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pickupOption.addEventListener('change', function() {
|
||||||
|
if (this.checked) {
|
||||||
|
deliveryOptions.style.display = 'none';
|
||||||
|
pickupOptions.style.display = 'block';
|
||||||
|
resetDeliverySelection();
|
||||||
|
// Update button text for pickup
|
||||||
|
document.getElementById('continueButtonText').textContent =
|
||||||
|
'{{ __('checkout.continue_to_payment') }}';
|
||||||
|
// Update hidden input values
|
||||||
|
document.getElementById('deliveryMethodInput').value = 'pickup';
|
||||||
|
document.getElementById('addressIdInput').value = '';
|
||||||
|
// Hide shipping address step and shipping row
|
||||||
|
const shippingAddressStep = document.getElementById('shippingAddressStep');
|
||||||
|
const shippingRow = document.getElementById('shipping-row');
|
||||||
|
console.log("Elements found:", shippingAddressStep, shippingRow);
|
||||||
|
if (shippingAddressStep) {
|
||||||
|
console.log("Hiding shipping address step");
|
||||||
|
shippingAddressStep.style.visibility = 'hidden';
|
||||||
|
shippingAddressStep.style.height = '0';
|
||||||
|
shippingAddressStep.style.overflow = 'hidden';
|
||||||
|
shippingAddressStep.style.margin = '0';
|
||||||
|
shippingAddressStep.style.padding = '0';
|
||||||
|
}
|
||||||
|
if (shippingRow) {
|
||||||
|
console.log("Hiding shipping row");
|
||||||
|
shippingRow.style.visibility = 'hidden';
|
||||||
|
shippingRow.style.height = '0';
|
||||||
|
shippingRow.style.overflow = 'hidden';
|
||||||
|
shippingRow.style.margin = '0';
|
||||||
|
shippingRow.style.padding = '0';
|
||||||
|
} else {
|
||||||
|
console.log("Shipping row not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Address selection handler
|
||||||
|
const addressSelect = document.getElementById('addressSelect');
|
||||||
|
if (addressSelect) {
|
||||||
|
// Auto-populate form with first selected address on page load
|
||||||
|
function populateAddressForm() {
|
||||||
|
const selectedOption = addressSelect.options[addressSelect.selectedIndex];
|
||||||
|
|
||||||
|
if (selectedOption && selectedOption.value) {
|
||||||
|
// Populate all shipping address form fields
|
||||||
|
document.getElementById('firstName').value = selectedOption.dataset.firstName || '';
|
||||||
|
document.getElementById('lastName').value = selectedOption.dataset.lastName || '';
|
||||||
|
document.getElementById('address').value = selectedOption.dataset.address || '';
|
||||||
|
document.getElementById('city').value = selectedOption.dataset.city || '';
|
||||||
|
document.getElementById('state').value = selectedOption.dataset.state || '';
|
||||||
|
document.getElementById('zip').value = selectedOption.dataset.postcode || '';
|
||||||
|
document.getElementById('phone').value = selectedOption.dataset.phone || '';
|
||||||
|
document.getElementById('postcode').value = selectedOption.dataset.postcode || '';
|
||||||
|
|
||||||
|
// Update order summary with postcode
|
||||||
|
if (selectedOption.dataset.postcode) {
|
||||||
|
updateOrderSummaryWithShipping();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate on page load
|
||||||
|
populateAddressForm();
|
||||||
|
// Set initial address ID
|
||||||
|
if (addressSelect.value) {
|
||||||
|
document.getElementById('addressIdInput').value = addressSelect.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
addressSelect.addEventListener('change', function() {
|
||||||
|
const selectedOption = this.options[this.selectedIndex];
|
||||||
|
|
||||||
|
if (this.value === 'new' || this.value === '') {
|
||||||
|
// Clear form fields for new address
|
||||||
|
document.getElementById('firstName').value = '';
|
||||||
|
document.getElementById('lastName').value = '';
|
||||||
|
document.getElementById('address').value = '';
|
||||||
|
document.getElementById('city').value = '';
|
||||||
|
document.getElementById('state').value = '';
|
||||||
|
document.getElementById('zip').value = '';
|
||||||
|
document.getElementById('phone').value = '';
|
||||||
|
document.getElementById('postcode').value = '';
|
||||||
|
// Clear address ID input
|
||||||
|
document.getElementById('addressIdInput').value = '';
|
||||||
|
} else {
|
||||||
|
// Populate form fields with selected address
|
||||||
|
document.getElementById('firstName').value = selectedOption.dataset.firstName || '';
|
||||||
|
document.getElementById('lastName').value = selectedOption.dataset.lastName || '';
|
||||||
|
document.getElementById('address').value = selectedOption.dataset.address || '';
|
||||||
|
document.getElementById('city').value = selectedOption.dataset.city || '';
|
||||||
|
document.getElementById('state').value = selectedOption.dataset.state || '';
|
||||||
|
document.getElementById('zip').value = selectedOption.dataset.postcode || '';
|
||||||
|
document.getElementById('phone').value = selectedOption.dataset.phone || '';
|
||||||
|
document.getElementById('postcode').value = selectedOption.dataset.postcode || '';
|
||||||
|
// Update address ID input
|
||||||
|
document.getElementById('addressIdInput').value = this.value;
|
||||||
|
|
||||||
|
// Update order summary with postcode
|
||||||
|
if (selectedOption.dataset.postcode) {
|
||||||
|
updateOrderSummaryWithShipping();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-update order summary when postcode changes
|
||||||
|
document.getElementById('postcode').addEventListener('input', function() {
|
||||||
|
if (this.value) {
|
||||||
|
updateOrderSummaryWithShipping();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store selection for pickup
|
||||||
|
const storeRadios = document.querySelectorAll('input[name="store"]');
|
||||||
|
storeRadios.forEach(radio => {
|
||||||
|
radio.addEventListener('change', function() {
|
||||||
|
if (this.checked) {
|
||||||
|
updateOrderSummaryForPickup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Form validation before continue
|
||||||
|
continueButton.addEventListener('click', function(e) {
|
||||||
|
const selectedMethod = document.querySelector('input[name="deliveryMethod"]:checked').value;
|
||||||
|
|
||||||
|
if (selectedMethod === 'delivery') {
|
||||||
|
if (!validateDeliveryForm()) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (selectedMethod === 'pickup') {
|
||||||
|
if (!validatePickupForm()) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function resetDeliverySelection() {
|
||||||
|
resetShippingCalculation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetPickupSelection() {
|
||||||
|
document.querySelectorAll('input[name="store"]').forEach(radio => {
|
||||||
|
radio.checked = false;
|
||||||
|
});
|
||||||
|
resetShippingCalculation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetShippingCalculation() {
|
||||||
|
// Reset order summary to original state
|
||||||
|
const shippingElement = document.querySelector('[data-shipping-cost]');
|
||||||
|
if (shippingElement) {
|
||||||
|
shippingElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalElement = document.getElementById('cart-estimated-total');
|
||||||
|
if (totalElement && window.originalTotal) {
|
||||||
|
totalElement.textContent = `Rp ${window.originalTotal}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOrderSummaryWithShipping() {
|
||||||
|
// Simulate shipping cost calculation based on postcode
|
||||||
|
const shippingCost = calculateShippingCost(document.getElementById('postcode').value);
|
||||||
|
|
||||||
|
// Update order summary
|
||||||
|
const subtotalElement = document.getElementById('cart-subtotal');
|
||||||
|
const currentSubtotal = parseFloat(subtotalElement.textContent.replace(/[^\d]/g, ''));
|
||||||
|
const newTotal = currentSubtotal + shippingCost;
|
||||||
|
|
||||||
|
// Store original total
|
||||||
|
if (!window.originalTotal) {
|
||||||
|
window.originalTotal = subtotalElement.textContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update total
|
||||||
|
const totalElement = document.getElementById('cart-estimated-total');
|
||||||
|
totalElement.textContent = `Rp ${number_format(newTotal, 0, ',', '.')}`;
|
||||||
|
|
||||||
|
// Show shipping cost in summary
|
||||||
|
showShippingCost(shippingCost);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOrderSummaryForPickup() {
|
||||||
|
// Pickup is usually free
|
||||||
|
const subtotalElement = document.getElementById('cart-subtotal');
|
||||||
|
const currentSubtotal = parseFloat(subtotalElement.textContent.replace(/[^\d]/g, ''));
|
||||||
|
|
||||||
|
// Store original total
|
||||||
|
if (!window.originalTotal) {
|
||||||
|
window.originalTotal = subtotalElement.textContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update total (same as subtotal for pickup)
|
||||||
|
const totalElement = document.getElementById('cart-estimated-total');
|
||||||
|
totalElement.textContent = subtotalElement.textContent;
|
||||||
|
|
||||||
|
// Show free shipping
|
||||||
|
showShippingCost(0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateShippingCost(postcode) {
|
||||||
|
// Simple shipping cost calculation based on postcode
|
||||||
|
// In real implementation, this would call an API
|
||||||
|
const jakartaPostcodes = ['10000', '10110', '10220', '10310', '10410'];
|
||||||
|
const bandungPostcodes = ['40111', '40112', '40113', '40114', '40115'];
|
||||||
|
|
||||||
|
if (jakartaPostcodes.includes(postcode)) {
|
||||||
|
return 15000; // Jakarta: Rp 15,000
|
||||||
|
} else if (bandungPostcodes.includes(postcode)) {
|
||||||
|
return 25000; // Bandung: Rp 25,000
|
||||||
|
} else {
|
||||||
|
return 35000; // Other areas: Rp 35,000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showShippingCost(cost, isFree = false) {
|
||||||
|
// Find or create shipping cost element in order summary
|
||||||
|
let shippingElement = document.querySelector('[data-shipping-cost]');
|
||||||
|
|
||||||
|
if (!shippingElement) {
|
||||||
|
const orderSummary = document.querySelector('.list-unstyled');
|
||||||
|
const shippingLi = document.createElement('li');
|
||||||
|
shippingLi.className = 'd-flex justify-content-between';
|
||||||
|
shippingLi.setAttribute('data-shipping-cost', '');
|
||||||
|
shippingLi.innerHTML = `
|
||||||
|
<span>{{ __('checkout.shipping') }}:</span>
|
||||||
|
<span class="text-dark-emphasis fw-medium" id="shipping-cost">Rp 0</span>
|
||||||
|
`;
|
||||||
|
orderSummary.appendChild(shippingLi);
|
||||||
|
shippingElement = shippingLi;
|
||||||
|
}
|
||||||
|
|
||||||
|
const costElement = document.getElementById('shipping-cost');
|
||||||
|
if (isFree) {
|
||||||
|
costElement.textContent = '{{ __('checkout.free') }}';
|
||||||
|
costElement.className = 'text-success fw-medium';
|
||||||
|
} else {
|
||||||
|
costElement.textContent = `Rp ${number_format(cost, 0, ',', '.')}`;
|
||||||
|
costElement.className = 'text-dark-emphasis fw-medium';
|
||||||
|
}
|
||||||
|
|
||||||
|
shippingElement.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateDeliveryForm() {
|
||||||
|
const postcode = document.getElementById('postcode').value;
|
||||||
|
const firstName = document.getElementById('firstName').value;
|
||||||
|
const address = document.getElementById('address').value;
|
||||||
|
const city = document.getElementById('city').value;
|
||||||
|
const phone = document.getElementById('phone').value;
|
||||||
|
|
||||||
|
if (!postcode) {
|
||||||
|
alert('{{ __('checkout.enter_postcode') }}');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!firstName || !address || !city || !phone) {
|
||||||
|
alert('{{ __('checkout.fill_required_fields') }}');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validatePickupForm() {
|
||||||
|
const selectedStore = document.querySelector('input[name="store"]:checked');
|
||||||
|
|
||||||
|
if (!selectedStore) {
|
||||||
|
alert('{{ __('checkout.select_store') }}');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number formatting helper
|
||||||
|
function number_format(number, decimals, dec_point, thousands_sep) {
|
||||||
|
number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
|
||||||
|
var n = !isFinite(+number) ? 0 : +number;
|
||||||
|
var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
|
||||||
|
var sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep;
|
||||||
|
var dec = (typeof dec_point === 'undefined') ? '.' : dec_point;
|
||||||
|
|
||||||
|
var s = '';
|
||||||
|
var toFixedFix = function(n, prec) {
|
||||||
|
var k = Math.pow(10, prec);
|
||||||
|
return '' + Math.round(n * k) / k;
|
||||||
|
};
|
||||||
|
|
||||||
|
s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
|
||||||
|
if (thousands_sep) {
|
||||||
|
var re = /(-?\d+)(\d{3})/;
|
||||||
|
while (re.test(s[0])) {
|
||||||
|
s[0] = s[0].replace(re, '$1' + sep + '$2');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((s[1] || '').length < prec) {
|
||||||
|
s[1] = s[1] || '';
|
||||||
|
s[1] += new Array(prec - s[1].length + 1).join('0');
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.join(dec);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endsection
|
||||||
|
|
@ -1,70 +1,7 @@
|
||||||
@extends('layouts.landing', ['title' => 'Checkout v.1 - Delivery Info Step 1'])
|
@extends('layouts.landing', ['title' => 'Checkout v.1 - Delivery Info Step 1'])
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<!-- Order preview offcanvas -->
|
<x-layout.header />
|
||||||
<div class="offcanvas offcanvas-end pb-sm-2 px-sm-2" id="orderPreview" tabindex="-1" aria-labelledby="orderPreviewLabel"
|
|
||||||
style="width: 500px">
|
|
||||||
<div class="offcanvas-header py-3 pt-lg-4">
|
|
||||||
<h4 class="offcanvas-title" id="orderPreviewLabel">Your order</h4>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="offcanvas-body d-flex flex-column gap-3 py-2">
|
|
||||||
|
|
||||||
<!-- Item -->
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<a class="flex-shrink-0" href="{{ route('second', ['shop', 'product-general-electronics']) }}">
|
|
||||||
<img src="/img/shop/electronics/thumbs/08.png" width="110" alt="iPhone 14">
|
|
||||||
</a>
|
|
||||||
<div class="w-100 min-w-0 ps-2 ps-sm-3">
|
|
||||||
<h5 class="d-flex animate-underline mb-2">
|
|
||||||
<a class="d-block fs-sm fw-medium text-truncate animate-target"
|
|
||||||
href="{{ route('second', ['shop', 'product-general-electronics']) }}">Apple iPhone 14 128GB White</a>
|
|
||||||
</h5>
|
|
||||||
<div class="h6 mb-0">$899.00</div>
|
|
||||||
<div class="fs-xs pt-2">Qty: 1</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Item -->
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<a class="position-relative flex-shrink-0" href="{{ route('second', ['shop', 'product-general-electronics']) }}">
|
|
||||||
<span class="badge text-bg-danger position-absolute top-0 start-0">-10%</span>
|
|
||||||
<img src="/img/shop/electronics/thumbs/09.png" width="110" alt="iPad Pro">
|
|
||||||
</a>
|
|
||||||
<div class="w-100 min-w-0 ps-2 ps-sm-3">
|
|
||||||
<h5 class="d-flex animate-underline mb-2">
|
|
||||||
<a class="d-block fs-sm fw-medium text-truncate animate-target"
|
|
||||||
href="{{ route('second', ['shop', 'product-general-electronics']) }}">Tablet Apple iPad Pro M2</a>
|
|
||||||
</h5>
|
|
||||||
<div class="h6 mb-0">$989.00 <del class="text-body-tertiary fs-xs fw-normal">$1,099.00</del></div>
|
|
||||||
<div class="fs-xs pt-2">Qty: 1</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Item -->
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<a class="flex-shrink-0" href="{{ route('second', ['shop', 'product-general-electronics']) }}">
|
|
||||||
<img src="/img/shop/electronics/thumbs/01.png" width="110" alt="Smart Watch">
|
|
||||||
</a>
|
|
||||||
<div class="w-100 min-w-0 ps-2 ps-sm-3">
|
|
||||||
<h5 class="d-flex animate-underline mb-2">
|
|
||||||
<a class="d-block fs-sm fw-medium text-truncate animate-target"
|
|
||||||
href="{{ route('second', ['shop', 'product-general-electronics']) }}">Smart Watch Series 7, White</a>
|
|
||||||
</h5>
|
|
||||||
<div class="h6 mb-0">$429.00</div>
|
|
||||||
<div class="fs-xs pt-2">Qty: 1</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="offcanvas-header">
|
|
||||||
<a class="btn btn-lg btn-outline-secondary w-100" href="checkout-v1-cart']) }}">Edit cart</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@include('layouts.partials/offcanvas')
|
|
||||||
|
|
||||||
@include('layouts.partials/navbar', ['wishlist' => true])
|
|
||||||
|
|
||||||
<!-- Page content -->
|
<!-- Page content -->
|
||||||
<main class="content-wrapper">
|
<main class="content-wrapper">
|
||||||
|
|
@ -78,33 +15,176 @@
|
||||||
<div class="d-flex align-items-center justify-content-center bg-primary text-white rounded-circle fs-sm fw-semibold lh-1 flex-shrink-0"
|
<div class="d-flex align-items-center justify-content-center bg-primary text-white rounded-circle fs-sm fw-semibold lh-1 flex-shrink-0"
|
||||||
style="width: 2rem; height: 2rem; margin-top: -.125rem">1</div>
|
style="width: 2rem; height: 2rem; margin-top: -.125rem">1</div>
|
||||||
<div class="w-100 ps-3 ps-md-4">
|
<div class="w-100 ps-3 ps-md-4">
|
||||||
<h1 class="h5 mb-md-4">Delivery information</h1>
|
<h1 class="h5 mb-md-4">{{ __('checkout.delivery_method') }}</h1>
|
||||||
<div class="ms-n5 ms-sm-0">
|
<div class="ms-n5 ms-sm-0">
|
||||||
<p class="fs-sm mb-md-4">Add your Postcode to see the delivery and collection options
|
<p class="fs-sm mb-md-4">{{ __('checkout.choose_delivery_method') }}</p>
|
||||||
available in your area.</p>
|
|
||||||
<div class="d-flex flex-column flex-md-row align-items-md-end gap-3 gap-xl-4">
|
<!-- Delivery Method Selection -->
|
||||||
<div class="w-100">
|
<div class="delivery-method-selection mb-4">
|
||||||
<label for="postcode" class="form-label">Postcode</label>
|
<div class="row">
|
||||||
<input type="text" class="form-control form-control-lg" id="postcode"
|
<div class="col-md-6">
|
||||||
placeholder="e.g. H1 1AG">
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="radio" name="deliveryMethod"
|
||||||
|
id="deliveryOption" value="delivery" checked>
|
||||||
|
<label class="form-check-label d-flex align-items-center"
|
||||||
|
for="deliveryOption">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="fw-medium">
|
||||||
|
|
||||||
|
|
||||||
|
{{ __('checkout.delivery') }}</div>
|
||||||
|
<div class="text-muted small">
|
||||||
|
{{ __('checkout.delivery_description') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<a class="btn btn-lg btn-primary" href="{{ route('second', ['checkout', 'v1-delivery-2']) }}">
|
</label>
|
||||||
Calculate cost and availability
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="radio" name="deliveryMethod"
|
||||||
|
id="pickupOption" value="pickup">
|
||||||
|
<label class="form-check-label d-flex align-items-center"
|
||||||
|
for="pickupOption">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="fw-medium">{{ __('checkout.store_pickup') }}</div>
|
||||||
|
<div class="text-muted small">
|
||||||
|
{{ __('checkout.pickup_description') }}</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Delivery Options -->
|
||||||
|
<div id="deliveryOptions" class="delivery-options">
|
||||||
|
|
||||||
|
{{-- dropdown address --}}
|
||||||
|
@if ($address_list->count() > 0)
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="addressSelect"
|
||||||
|
class="form-label">{{ __('checkout.select_saved_address') }}</label>
|
||||||
|
<select class="form-select form-select-lg" id="addressSelect">
|
||||||
|
@foreach ($address_list as $key => $address)
|
||||||
|
<option value="{{ $address->id }}"
|
||||||
|
data-first-name="{{ $address->name }}" data-last-name=""
|
||||||
|
data-address="{{ $address->address }}"
|
||||||
|
data-city="{{ $address->city_name }}"
|
||||||
|
data-state="{{ $address->province_name }}"
|
||||||
|
data-postcode="{{ $address->postal_code }}"
|
||||||
|
data-phone="{{ $address->phone }}"
|
||||||
|
{{ $address->is_primary || $key === 0 ? 'selected' : '' }}>
|
||||||
|
{{ $address->label }} - {{ $address->name }},
|
||||||
|
{{ $address->address }}, {{ $address->city_name }},
|
||||||
|
{{ $address->province_name }} {{ $address->postal_code }}
|
||||||
|
@if ($address->is_primary)
|
||||||
|
<span
|
||||||
|
class="badge bg-primary ms-2">{{ __('checkout.primary') }}</span>
|
||||||
|
@endif
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Shipping Address (shown automatically) -->
|
||||||
|
<div id="shippingAddress" class="shipping-address mt-4" style="display: block;">
|
||||||
|
<h6 class="mb-3">{{ __('checkout.shipping_address') }}</h6>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="firstName"
|
||||||
|
class="form-label">{{ __('checkout.first_name') }}</label>
|
||||||
|
<input type="text" class="form-control" id="firstName" readonly
|
||||||
|
style="border: none; background-color: #f8f9fa;">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="lastName"
|
||||||
|
class="form-label">{{ __('checkout.last_name') }}</label>
|
||||||
|
<input type="text" class="form-control" id="lastName" readonly
|
||||||
|
style="border: none; background-color: #f8f9fa;">
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label for="address"
|
||||||
|
class="form-label">{{ __('checkout.address') }}</label>
|
||||||
|
<input type="text" class="form-control" id="address" readonly
|
||||||
|
style="border: none; background-color: #f8f9fa;">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="city"
|
||||||
|
class="form-label">{{ __('checkout.city') }}</label>
|
||||||
|
<input type="text" class="form-control" id="city" readonly
|
||||||
|
style="border: none; background-color: #f8f9fa;">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label for="state"
|
||||||
|
class="form-label">{{ __('checkout.state') }}</label>
|
||||||
|
<input type="text" class="form-control" id="state" readonly
|
||||||
|
style="border: none; background-color: #f8f9fa;">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label for="zip"
|
||||||
|
class="form-label">{{ __('checkout.zip') }}</label>
|
||||||
|
<input type="text" class="form-control" id="zip" readonly
|
||||||
|
style="border: none; background-color: #f8f9fa;">
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label for="phone"
|
||||||
|
class="form-label">{{ __('checkout.phone') }}</label>
|
||||||
|
<input type="tel" class="form-control" id="phone" readonly
|
||||||
|
style="border: none; background-color: #f8f9fa;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pickup Options -->
|
||||||
|
<div id="pickupOptions" class="pickup-options" style="display: none;">
|
||||||
|
<div class="store-list">
|
||||||
|
<div class="form-check mb-3">
|
||||||
|
<input type="hidden" name="store" value="{{ $store->id }}">
|
||||||
|
<label class="form-check-label" style="margin-left: 0px;" for="store1">
|
||||||
|
<div class="fw-medium">{{ $store->display_name }}</div>
|
||||||
|
<div class="text-muted small">
|
||||||
|
{{ __('checkout.store_address') }}: {{ $store->address }}<br>
|
||||||
|
{{ __('checkout.phone') }}: {{ $store->phone }}<br>
|
||||||
|
{{ __('checkout.hours') }}: {{ $store->hours }}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Continue Button -->
|
||||||
|
<div class="mt-4">
|
||||||
|
<form action="{{ route('checkout.delivery.process') }}" method="post">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="delivery_method" id="deliveryMethodInput" value="shipping">
|
||||||
|
<input type="hidden" name="address_id" id="addressIdInput" value="">
|
||||||
|
<button type="submit" class="btn btn-lg btn-primary w-100"
|
||||||
|
id="continueButton">
|
||||||
|
<span
|
||||||
|
id="continueButtonText">{{ __('checkout.continue_to_shipping') }}</span>
|
||||||
<i class="ci-chevron-right fs-lg ms-1 me-n1"></i>
|
<i class="ci-chevron-right fs-lg ms-1 me-n1"></i>
|
||||||
</a>
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-start">
|
<div class="d-flex align-items-start" id="shippingAddressStep">
|
||||||
<div class="d-flex align-items-center justify-content-center bg-body-secondary text-body-secondary rounded-circle fs-sm fw-semibold lh-1 flex-shrink-0"
|
<div class="d-flex align-items-center justify-content-center bg-body-secondary text-body-secondary rounded-circle fs-sm fw-semibold lh-1 flex-shrink-0"
|
||||||
style="width: 2rem; height: 2rem; margin-top: -.125rem">2</div>
|
style="width: 2rem; height: 2rem; margin-top: -.125rem">2</div>
|
||||||
<h2 class="h5 text-body-secondary ps-3 ps-md-4 mb-0">Shipping address</h2>
|
<h2 class="h5 text-body-secondary ps-3 ps-md-4 mb-0">{{ __('checkout.choose_shipping') }}
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-start">
|
<div class="d-flex align-items-start">
|
||||||
<div class="d-flex align-items-center justify-content-center bg-body-secondary text-body-secondary rounded-circle fs-sm fw-semibold lh-1 flex-shrink-0"
|
<div class="d-flex align-items-center justify-content-center bg-body-secondary text-body-secondary rounded-circle fs-sm fw-semibold lh-1 flex-shrink-0"
|
||||||
style="width: 2rem; height: 2rem; margin-top: -.125rem">3</div>
|
style="width: 2rem; height: 2rem; margin-top: -.125rem">3</div>
|
||||||
<h2 class="h5 text-body-secondary ps-3 ps-md-4 mb-0">Payment</h2>
|
<h2 class="h5 text-body-secondary ps-3 ps-md-4 mb-0">{{ __('checkout.payment') }}</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -113,74 +193,8 @@
|
||||||
<!-- Order summary (sticky sidebar) -->
|
<!-- Order summary (sticky sidebar) -->
|
||||||
<aside class="col-lg-4 offset-xl-1" style="margin-top: -100px">
|
<aside class="col-lg-4 offset-xl-1" style="margin-top: -100px">
|
||||||
<div class="position-sticky top-0" style="padding-top: 100px">
|
<div class="position-sticky top-0" style="padding-top: 100px">
|
||||||
<div class="bg-body-tertiary rounded-5 p-4 mb-3">
|
<x-checkout.order-summary :subtotal="$subtotal" :total="$total" :savings="0" :tax="0"
|
||||||
<div class="p-sm-2 p-lg-0 p-xl-2">
|
:showEdit="true" :editUrl="route('second', ['checkout', 'v1-cart'])" />
|
||||||
<div class="border-bottom pb-4 mb-4">
|
|
||||||
<div class="d-flex align-items-center justify-content-between mb-4">
|
|
||||||
<h5 class="mb-0">Order summary</h5>
|
|
||||||
<div class="nav">
|
|
||||||
<a class="nav-link text-decoration-underline p-0"
|
|
||||||
href="{{ route('second', ['checkout', 'v1-cart']) }}">Edit</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<a class="d-flex align-items-center gap-2 text-decoration-none" href="#orderPreview"
|
|
||||||
data-bs-toggle="offcanvas">
|
|
||||||
<div class="ratio ratio-1x1" style="max-width: 64px">
|
|
||||||
<img src="/img/shop/electronics/thumbs/08.png" class="d-block p-1"
|
|
||||||
alt="iPhone">
|
|
||||||
</div>
|
|
||||||
<div class="ratio ratio-1x1" style="max-width: 64px">
|
|
||||||
<img src="/img/shop/electronics/thumbs/09.png" class="d-block p-1"
|
|
||||||
alt="iPad Pro">
|
|
||||||
</div>
|
|
||||||
<div class="ratio ratio-1x1" style="max-width: 64px">
|
|
||||||
<img src="/img/shop/electronics/thumbs/01.png" class="d-block p-1"
|
|
||||||
alt="Smart Watch">
|
|
||||||
</div>
|
|
||||||
<i class="ci-chevron-right text-body fs-xl p-0 ms-auto"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<ul class="list-unstyled fs-sm gap-3 mb-0">
|
|
||||||
<li class="d-flex justify-content-between">
|
|
||||||
Subtotal (3 items):
|
|
||||||
<span class="text-dark-emphasis fw-medium">$2,427.00</span>
|
|
||||||
</li>
|
|
||||||
<li class="d-flex justify-content-between">
|
|
||||||
Saving:
|
|
||||||
<span class="text-danger fw-medium">-$110.00</span>
|
|
||||||
</li>
|
|
||||||
<li class="d-flex justify-content-between">
|
|
||||||
Tax collected:
|
|
||||||
<span class="text-dark-emphasis fw-medium">$73.40</span>
|
|
||||||
</li>
|
|
||||||
<li class="d-flex justify-content-between">
|
|
||||||
Shipping:
|
|
||||||
<span class="text-dark-emphasis fw-medium">Calculated at checkout</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div class="border-top pt-4 mt-4">
|
|
||||||
<div class="d-flex justify-content-between mb-3">
|
|
||||||
<span class="fs-sm">Estimated total:</span>
|
|
||||||
<span class="h5 mb-0">$2,390.40</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-body-tertiary rounded-5 p-4">
|
|
||||||
<div class="d-flex align-items-center px-sm-2 px-lg-0 px-xl-2">
|
|
||||||
<svg class="text-warning flex-shrink-0" xmlns="http://www.w3.org/2000/svg" width="16"
|
|
||||||
height="16" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M1.333 9.667H7.5V16h-5c-.64 0-1.167-.527-1.167-1.167V9.667zm13.334 0v5.167c0 .64-.527 1.167-1.167 1.167h-5V9.667h6.167zM0 5.833V7.5c0 .64.527 1.167 1.167 1.167h.167H7.5v-1-3H1.167C.527 4.667 0 5.193 0 5.833zm14.833-1.166H8.5v3 1h6.167.167C15.473 8.667 16 8.14 16 7.5V5.833c0-.64-.527-1.167-1.167-1.167z" />
|
|
||||||
<path
|
|
||||||
d="M8 5.363a.5.5 0 0 1-.495-.573C7.752 3.123 9.054-.03 12.219-.03c1.807.001 2.447.977 2.447 1.813 0 1.486-2.069 3.58-6.667 3.58zM12.219.971c-2.388 0-3.295 2.27-3.595 3.377 1.884-.088 3.072-.565 3.756-.971.949-.563 1.287-1.193 1.287-1.595 0-.599-.747-.811-1.447-.811z" />
|
|
||||||
<path
|
|
||||||
d="M8.001 5.363c-4.598 0-6.667-2.094-6.667-3.58 0-.836.641-1.812 2.448-1.812 3.165 0 4.467 3.153 4.713 4.819a.5.5 0 0 1-.495.573zM3.782.971c-.7 0-1.448.213-1.448.812 0 .851 1.489 2.403 5.042 2.566C7.076 3.241 6.169.971 3.782.971z" />
|
|
||||||
</svg>
|
|
||||||
<div class="text-dark-emphasis fs-sm ps-2 ms-1">Congratulations! You have earned <span
|
|
||||||
class="fw-semibold">239 bonuses</span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -188,8 +202,364 @@
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
@include('layouts.partials/footer')
|
@include('layouts.partials/footer')
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Delivery method selection
|
||||||
|
const deliveryOption = document.getElementById('deliveryOption');
|
||||||
|
const pickupOption = document.getElementById('pickupOption');
|
||||||
|
const deliveryOptions = document.getElementById('deliveryOptions');
|
||||||
|
const pickupOptions = document.getElementById('pickupOptions');
|
||||||
|
const shippingAddress = document.getElementById('shippingAddress');
|
||||||
|
const continueButton = document.getElementById('continueButton');
|
||||||
|
|
||||||
|
// Handle delivery method change
|
||||||
|
deliveryOption.addEventListener('change', function() {
|
||||||
|
if (this.checked) {
|
||||||
|
deliveryOptions.style.display = 'block';
|
||||||
|
pickupOptions.style.display = 'none';
|
||||||
|
resetPickupSelection();
|
||||||
|
// Update button text for delivery
|
||||||
|
document.getElementById('continueButtonText').textContent = '{{ __("checkout.continue_to_shipping") }}';
|
||||||
|
// Update hidden input values
|
||||||
|
document.getElementById('deliveryMethodInput').value = 'delivery';
|
||||||
|
// Update address ID from selected address
|
||||||
|
const addressSelect = document.getElementById('addressSelect');
|
||||||
|
if (addressSelect && addressSelect.value) {
|
||||||
|
document.getElementById('addressIdInput').value = addressSelect.value;
|
||||||
|
}
|
||||||
|
// Show shipping address step and shipping row
|
||||||
|
const shippingAddressStep = document.getElementById('shippingAddressStep');
|
||||||
|
const shippingRow = document.getElementById('shipping-row');
|
||||||
|
if (shippingAddressStep) {
|
||||||
|
console.log("Showing shipping address step");
|
||||||
|
shippingAddressStep.style.visibility = 'visible';
|
||||||
|
shippingAddressStep.style.height = 'auto';
|
||||||
|
shippingAddressStep.style.overflow = 'visible';
|
||||||
|
shippingAddressStep.style.margin = '';
|
||||||
|
shippingAddressStep.style.padding = '';
|
||||||
|
}
|
||||||
|
if (shippingRow) {
|
||||||
|
console.log("Showing shipping row");
|
||||||
|
shippingRow.style.visibility = 'visible';
|
||||||
|
shippingRow.style.height = 'auto';
|
||||||
|
shippingRow.style.overflow = 'visible';
|
||||||
|
shippingRow.style.margin = '';
|
||||||
|
shippingRow.style.padding = '';
|
||||||
|
} else {
|
||||||
|
console.log("Shipping row not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pickupOption.addEventListener('change', function() {
|
||||||
|
if (this.checked) {
|
||||||
|
deliveryOptions.style.display = 'none';
|
||||||
|
pickupOptions.style.display = 'block';
|
||||||
|
resetDeliverySelection();
|
||||||
|
// Update button text for pickup
|
||||||
|
document.getElementById('continueButtonText').textContent =
|
||||||
|
'{{ __('checkout.continue_to_payment') }}';
|
||||||
|
// Update hidden input values
|
||||||
|
document.getElementById('deliveryMethodInput').value = 'pickup';
|
||||||
|
document.getElementById('addressIdInput').value = '';
|
||||||
|
// Hide shipping address step and shipping row
|
||||||
|
const shippingAddressStep = document.getElementById('shippingAddressStep');
|
||||||
|
const shippingRow = document.getElementById('shipping-row');
|
||||||
|
console.log("Elements found:", shippingAddressStep, shippingRow);
|
||||||
|
if (shippingAddressStep) {
|
||||||
|
console.log("Hiding shipping address step");
|
||||||
|
shippingAddressStep.style.visibility = 'hidden';
|
||||||
|
shippingAddressStep.style.height = '0';
|
||||||
|
shippingAddressStep.style.overflow = 'hidden';
|
||||||
|
shippingAddressStep.style.margin = '0';
|
||||||
|
shippingAddressStep.style.padding = '0';
|
||||||
|
}
|
||||||
|
if (shippingRow) {
|
||||||
|
console.log("Hiding shipping row");
|
||||||
|
shippingRow.style.visibility = 'hidden';
|
||||||
|
shippingRow.style.height = '0';
|
||||||
|
shippingRow.style.overflow = 'hidden';
|
||||||
|
shippingRow.style.margin = '0';
|
||||||
|
shippingRow.style.padding = '0';
|
||||||
|
} else {
|
||||||
|
console.log("Shipping row not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Address selection handler
|
||||||
|
const addressSelect = document.getElementById('addressSelect');
|
||||||
|
if (addressSelect) {
|
||||||
|
// Auto-populate form with first selected address on page load
|
||||||
|
function populateAddressForm() {
|
||||||
|
const selectedOption = addressSelect.options[addressSelect.selectedIndex];
|
||||||
|
|
||||||
|
if (selectedOption && selectedOption.value) {
|
||||||
|
// Populate all shipping address form fields
|
||||||
|
document.getElementById('firstName').value = selectedOption.dataset.firstName || '';
|
||||||
|
document.getElementById('lastName').value = selectedOption.dataset.lastName || '';
|
||||||
|
document.getElementById('address').value = selectedOption.dataset.address || '';
|
||||||
|
document.getElementById('city').value = selectedOption.dataset.city || '';
|
||||||
|
document.getElementById('state').value = selectedOption.dataset.state || '';
|
||||||
|
document.getElementById('zip').value = selectedOption.dataset.postcode || '';
|
||||||
|
document.getElementById('phone').value = selectedOption.dataset.phone || '';
|
||||||
|
document.getElementById('postcode').value = selectedOption.dataset.postcode || '';
|
||||||
|
|
||||||
|
// Update order summary with postcode
|
||||||
|
if (selectedOption.dataset.postcode) {
|
||||||
|
updateOrderSummaryWithShipping();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate on page load
|
||||||
|
populateAddressForm();
|
||||||
|
// Set initial address ID
|
||||||
|
if (addressSelect.value) {
|
||||||
|
document.getElementById('addressIdInput').value = addressSelect.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
addressSelect.addEventListener('change', function() {
|
||||||
|
const selectedOption = this.options[this.selectedIndex];
|
||||||
|
|
||||||
|
if (this.value === 'new' || this.value === '') {
|
||||||
|
// Clear form fields for new address
|
||||||
|
document.getElementById('firstName').value = '';
|
||||||
|
document.getElementById('lastName').value = '';
|
||||||
|
document.getElementById('address').value = '';
|
||||||
|
document.getElementById('city').value = '';
|
||||||
|
document.getElementById('state').value = '';
|
||||||
|
document.getElementById('zip').value = '';
|
||||||
|
document.getElementById('phone').value = '';
|
||||||
|
document.getElementById('postcode').value = '';
|
||||||
|
// Clear address ID input
|
||||||
|
document.getElementById('addressIdInput').value = '';
|
||||||
|
} else {
|
||||||
|
// Populate form fields with selected address
|
||||||
|
document.getElementById('firstName').value = selectedOption.dataset.firstName || '';
|
||||||
|
document.getElementById('lastName').value = selectedOption.dataset.lastName || '';
|
||||||
|
document.getElementById('address').value = selectedOption.dataset.address || '';
|
||||||
|
document.getElementById('city').value = selectedOption.dataset.city || '';
|
||||||
|
document.getElementById('state').value = selectedOption.dataset.state || '';
|
||||||
|
document.getElementById('zip').value = selectedOption.dataset.postcode || '';
|
||||||
|
document.getElementById('phone').value = selectedOption.dataset.phone || '';
|
||||||
|
document.getElementById('postcode').value = selectedOption.dataset.postcode || '';
|
||||||
|
// Update address ID input
|
||||||
|
document.getElementById('addressIdInput').value = this.value;
|
||||||
|
|
||||||
|
// Update order summary with postcode
|
||||||
|
if (selectedOption.dataset.postcode) {
|
||||||
|
updateOrderSummaryWithShipping();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-update order summary when postcode changes
|
||||||
|
document.getElementById('postcode').addEventListener('input', function() {
|
||||||
|
if (this.value) {
|
||||||
|
updateOrderSummaryWithShipping();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store selection for pickup
|
||||||
|
const storeRadios = document.querySelectorAll('input[name="store"]');
|
||||||
|
storeRadios.forEach(radio => {
|
||||||
|
radio.addEventListener('change', function() {
|
||||||
|
if (this.checked) {
|
||||||
|
updateOrderSummaryForPickup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Form validation before continue
|
||||||
|
continueButton.addEventListener('click', function(e) {
|
||||||
|
const selectedMethod = document.querySelector('input[name="deliveryMethod"]:checked').value;
|
||||||
|
|
||||||
|
if (selectedMethod === 'delivery') {
|
||||||
|
if (!validateDeliveryForm()) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (selectedMethod === 'pickup') {
|
||||||
|
if (!validatePickupForm()) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function resetDeliverySelection() {
|
||||||
|
resetShippingCalculation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetPickupSelection() {
|
||||||
|
document.querySelectorAll('input[name="store"]').forEach(radio => {
|
||||||
|
radio.checked = false;
|
||||||
|
});
|
||||||
|
resetShippingCalculation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetShippingCalculation() {
|
||||||
|
// Reset order summary to original state
|
||||||
|
const shippingElement = document.querySelector('[data-shipping-cost]');
|
||||||
|
if (shippingElement) {
|
||||||
|
shippingElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalElement = document.getElementById('cart-estimated-total');
|
||||||
|
if (totalElement && window.originalTotal) {
|
||||||
|
totalElement.textContent = `Rp ${window.originalTotal}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOrderSummaryWithShipping() {
|
||||||
|
// Simulate shipping cost calculation based on postcode
|
||||||
|
const shippingCost = calculateShippingCost(document.getElementById('postcode').value);
|
||||||
|
|
||||||
|
// Update order summary
|
||||||
|
const subtotalElement = document.getElementById('cart-subtotal');
|
||||||
|
const currentSubtotal = parseFloat(subtotalElement.textContent.replace(/[^\d]/g, ''));
|
||||||
|
const newTotal = currentSubtotal + shippingCost;
|
||||||
|
|
||||||
|
// Store original total
|
||||||
|
if (!window.originalTotal) {
|
||||||
|
window.originalTotal = subtotalElement.textContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update total
|
||||||
|
const totalElement = document.getElementById('cart-estimated-total');
|
||||||
|
totalElement.textContent = `Rp ${number_format(newTotal, 0, ',', '.')}`;
|
||||||
|
|
||||||
|
// Show shipping cost in summary
|
||||||
|
showShippingCost(shippingCost);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOrderSummaryForPickup() {
|
||||||
|
// Pickup is usually free
|
||||||
|
const subtotalElement = document.getElementById('cart-subtotal');
|
||||||
|
const currentSubtotal = parseFloat(subtotalElement.textContent.replace(/[^\d]/g, ''));
|
||||||
|
|
||||||
|
// Store original total
|
||||||
|
if (!window.originalTotal) {
|
||||||
|
window.originalTotal = subtotalElement.textContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update total (same as subtotal for pickup)
|
||||||
|
const totalElement = document.getElementById('cart-estimated-total');
|
||||||
|
totalElement.textContent = subtotalElement.textContent;
|
||||||
|
|
||||||
|
// Show free shipping
|
||||||
|
showShippingCost(0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateShippingCost(postcode) {
|
||||||
|
// Simple shipping cost calculation based on postcode
|
||||||
|
// In real implementation, this would call an API
|
||||||
|
const jakartaPostcodes = ['10000', '10110', '10220', '10310', '10410'];
|
||||||
|
const bandungPostcodes = ['40111', '40112', '40113', '40114', '40115'];
|
||||||
|
|
||||||
|
if (jakartaPostcodes.includes(postcode)) {
|
||||||
|
return 15000; // Jakarta: Rp 15,000
|
||||||
|
} else if (bandungPostcodes.includes(postcode)) {
|
||||||
|
return 25000; // Bandung: Rp 25,000
|
||||||
|
} else {
|
||||||
|
return 35000; // Other areas: Rp 35,000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showShippingCost(cost, isFree = false) {
|
||||||
|
// Find or create shipping cost element in order summary
|
||||||
|
let shippingElement = document.querySelector('[data-shipping-cost]');
|
||||||
|
|
||||||
|
if (!shippingElement) {
|
||||||
|
const orderSummary = document.querySelector('.list-unstyled');
|
||||||
|
const shippingLi = document.createElement('li');
|
||||||
|
shippingLi.className = 'd-flex justify-content-between';
|
||||||
|
shippingLi.setAttribute('data-shipping-cost', '');
|
||||||
|
shippingLi.innerHTML = `
|
||||||
|
<span>{{ __('checkout.shipping') }}:</span>
|
||||||
|
<span class="text-dark-emphasis fw-medium" id="shipping-cost">Rp 0</span>
|
||||||
|
`;
|
||||||
|
orderSummary.appendChild(shippingLi);
|
||||||
|
shippingElement = shippingLi;
|
||||||
|
}
|
||||||
|
|
||||||
|
const costElement = document.getElementById('shipping-cost');
|
||||||
|
if (isFree) {
|
||||||
|
costElement.textContent = '{{ __('checkout.free') }}';
|
||||||
|
costElement.className = 'text-success fw-medium';
|
||||||
|
} else {
|
||||||
|
costElement.textContent = `Rp ${number_format(cost, 0, ',', '.')}`;
|
||||||
|
costElement.className = 'text-dark-emphasis fw-medium';
|
||||||
|
}
|
||||||
|
|
||||||
|
shippingElement.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateDeliveryForm() {
|
||||||
|
const postcode = document.getElementById('postcode').value;
|
||||||
|
const firstName = document.getElementById('firstName').value;
|
||||||
|
const address = document.getElementById('address').value;
|
||||||
|
const city = document.getElementById('city').value;
|
||||||
|
const phone = document.getElementById('phone').value;
|
||||||
|
|
||||||
|
if (!postcode) {
|
||||||
|
alert('{{ __('checkout.enter_postcode') }}');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!firstName || !address || !city || !phone) {
|
||||||
|
alert('{{ __('checkout.fill_required_fields') }}');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validatePickupForm() {
|
||||||
|
const selectedStore = document.querySelector('input[name="store"]:checked');
|
||||||
|
|
||||||
|
if (!selectedStore) {
|
||||||
|
alert('{{ __('checkout.select_store') }}');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number formatting helper
|
||||||
|
function number_format(number, decimals, dec_point, thousands_sep) {
|
||||||
|
number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
|
||||||
|
var n = !isFinite(+number) ? 0 : +number;
|
||||||
|
var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
|
||||||
|
var sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep;
|
||||||
|
var dec = (typeof dec_point === 'undefined') ? '.' : dec_point;
|
||||||
|
|
||||||
|
var s = '';
|
||||||
|
var toFixedFix = function(n, prec) {
|
||||||
|
var k = Math.pow(10, prec);
|
||||||
|
return '' + Math.round(n * k) / k;
|
||||||
|
};
|
||||||
|
|
||||||
|
s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
|
||||||
|
if (thousands_sep) {
|
||||||
|
var re = /(-?\d+)(\d{3})/;
|
||||||
|
while (re.test(s[0])) {
|
||||||
|
s[0] = s[0].replace(re, '$1' + sep + '$2');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((s[1] || '').length < prec) {
|
||||||
|
s[1] = s[1] || '';
|
||||||
|
s[1] += new Array(prec - s[1].length + 1).join('0');
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.join(dec);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
@props([
|
||||||
|
'subtotal' => 0,
|
||||||
|
'total' => 0,
|
||||||
|
'savings' => 0,
|
||||||
|
'tax' => 0,
|
||||||
|
'showEdit' => false,
|
||||||
|
'editUrl' => null,
|
||||||
|
'showItems' => false,
|
||||||
|
'items' => [],
|
||||||
|
])
|
||||||
|
|
||||||
|
<div class="bg-body-tertiary rounded-5 p-4 mb-3">
|
||||||
|
<div class="p-sm-2 p-lg-0 p-xl-2">
|
||||||
|
<div class="border-bottom pb-4 mb-4">
|
||||||
|
<div class="d-flex align-items-center justify-content-between mb-4">
|
||||||
|
<h5 class="mb-0">{{ __('cart_summary.title') }}</h5>
|
||||||
|
@if ($showEdit && $editUrl)
|
||||||
|
<div class="nav">
|
||||||
|
<a class="nav-link text-decoration-underline p-0"
|
||||||
|
href="{{ $editUrl }}">{{ __('cart_summary.edit') }}</a>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@if ($showItems && count($items) > 0)
|
||||||
|
<a class="d-flex align-items-center gap-2 text-decoration-none" href="#orderPreview"
|
||||||
|
data-bs-toggle="offcanvas">
|
||||||
|
@foreach ($items->take(3) as $item)
|
||||||
|
<div class="ratio ratio-1x1" style="max-width: 64px">
|
||||||
|
<img src="{{ $item->image_url ?? '/img/shop/electronics/thumbs/08.png' }}"
|
||||||
|
class="d-block p-1" alt="{{ $item->name ?? 'Product' }}">
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
<i class="ci-chevron-right text-body fs-xl p-0 ms-auto"></i>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<ul class="list-unstyled fs-sm gap-3 mb-0">
|
||||||
|
<li class="d-flex justify-content-between">
|
||||||
|
<div>{{ __('cart_summary.subtotal') }} (
|
||||||
|
{{ __('cart_summary.items_count', ['count' => auth()->check() ? \App\Repositories\Member\Cart\MemberCartRepository::getCount() : 0]) }}):
|
||||||
|
</div>
|
||||||
|
<span class="text-dark-emphasis fw-medium" id="cart-subtotal">Rp
|
||||||
|
{{ number_format($subtotal, 0, ',', '.') }}</span>
|
||||||
|
</li>
|
||||||
|
<li class="d-flex justify-content-between">
|
||||||
|
{{ __('cart_summary.savings') }}:
|
||||||
|
<span class="text-danger fw-medium">Rp {{ number_format($savings, 0, ',', '.') }}</span>
|
||||||
|
</li>
|
||||||
|
<li class="d-flex justify-content-between" id="tax-row"
|
||||||
|
@if ($tax <= 0) style="display: none;" @endif>
|
||||||
|
{{ __('cart_summary.tax_collected') }}:
|
||||||
|
<span class="text-dark-emphasis fw-medium">Rp {{ number_format($tax, 0, ',', '.') }}</span>
|
||||||
|
</li>
|
||||||
|
<li class="d-flex justify-content-between" id="shipping-row">
|
||||||
|
{{ __('cart_summary.shipping') }}:
|
||||||
|
<span class="text-dark-emphasis fw-medium">{{ __('cart_summary.calculated_at_checkout') }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="border-top pt-4 mt-4">
|
||||||
|
<div class="d-flex justify-content-between mb-3">
|
||||||
|
<span class="fs-sm">{{ __('cart_summary.estimated_total') }}:</span>
|
||||||
|
<span class="h5 mb-0" id="cart-estimated-total">Rp {{ number_format($total, 0, ',', '.') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -14,6 +14,7 @@ use App\Http\Controllers\SearchController;
|
||||||
use App\Http\Controllers\ComponentController;
|
use App\Http\Controllers\ComponentController;
|
||||||
use App\Http\Controllers\Auth\ProfileController;
|
use App\Http\Controllers\Auth\ProfileController;
|
||||||
use App\Http\Controllers\CartController;
|
use App\Http\Controllers\CartController;
|
||||||
|
use App\Http\Controllers\CheckoutController;
|
||||||
|
|
||||||
Route::group(['prefix' => '/dummy'], function () {
|
Route::group(['prefix' => '/dummy'], function () {
|
||||||
Route::get('', [RoutingController::class, 'index'])->name('root');
|
Route::get('', [RoutingController::class, 'index'])->name('root');
|
||||||
|
|
@ -95,3 +96,15 @@ Route::middleware(['auth'])->prefix('/cart')->group(function () {
|
||||||
Route::delete('/{id}', [CartController::class, 'delete'])->name('cart.delete');
|
Route::delete('/{id}', [CartController::class, 'delete'])->name('cart.delete');
|
||||||
Route::get('/count', [CartController::class, 'count'])->name('cart.count');
|
Route::get('/count', [CartController::class, 'count'])->name('cart.count');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::middleware(['auth'])->prefix('/checkout')->group(function () {
|
||||||
|
|
||||||
|
|
||||||
|
Route::get('/', [CheckoutController::class, 'index'])->name('checkout.delivery');
|
||||||
|
Route::post('/', [CheckoutController::class, 'indexProcess'])->name('checkout.delivery.process');
|
||||||
|
|
||||||
|
Route::get('/shipping', [CheckoutController::class, 'chooseShipping'])->name('checkout.shipping');
|
||||||
|
Route::post('/shipping', [CheckoutController::class, 'chooseShippingProcess'])->name('checkout.shipping.process');
|
||||||
|
Route::get('/payment', [CheckoutController::class, 'choosePayment'])->name('checkout.payment');
|
||||||
|
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue