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');
|
||||
}
|
||||
|
||||
static public function getSubtotal($locationId = null)
|
||||
{
|
||||
$location = $locationId ?? session('location_id', 22);
|
||||
return Cart::where('user_id', auth()->user()->id)
|
||||
->where('location_id', $location)
|
||||
->get()
|
||||
->map(function($row){
|
||||
return $row->qty * $row->display_price;
|
||||
})
|
||||
->sum();
|
||||
}
|
||||
|
||||
static public function clearAll($locationId = null)
|
||||
{
|
||||
$location = $locationId ?? session('location_id', 22);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</div>
|
||||
<a class="btn btn-lg btn-primary w-100"
|
||||
href="{{ route('second', ['checkout', 'v1-delivery-1']) }}">
|
||||
href="{{ route('checkout.delivery') }}">
|
||||
Proceed to checkout
|
||||
<i class="ci-chevron-right fs-lg ms-1 me-n1"></i>
|
||||
</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'])
|
||||
|
||||
@section('content')
|
||||
<!-- Order preview offcanvas -->
|
||||
<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])
|
||||
<x-layout.header />
|
||||
|
||||
<!-- Page content -->
|
||||
<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"
|
||||
style="width: 2rem; height: 2rem; margin-top: -.125rem">1</div>
|
||||
<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">
|
||||
<p class="fs-sm mb-md-4">Add your Postcode to see the delivery and collection options
|
||||
available in your area.</p>
|
||||
<div class="d-flex flex-column flex-md-row align-items-md-end gap-3 gap-xl-4">
|
||||
<div class="w-100">
|
||||
<label for="postcode" class="form-label">Postcode</label>
|
||||
<input type="text" class="form-control form-control-lg" id="postcode"
|
||||
placeholder="e.g. H1 1AG">
|
||||
<p class="fs-sm mb-md-4">{{ __('checkout.choose_delivery_method') }}</p>
|
||||
|
||||
<!-- Delivery Method Selection -->
|
||||
<div class="delivery-method-selection mb-4">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<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>
|
||||
<a class="btn btn-lg btn-primary" href="{{ route('second', ['checkout', 'v1-delivery-2']) }}">
|
||||
Calculate cost and availability
|
||||
</label>
|
||||
</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>
|
||||
</a>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
</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"
|
||||
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 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">Payment</h2>
|
||||
<h2 class="h5 text-body-secondary ps-3 ps-md-4 mb-0">{{ __('checkout.payment') }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -113,74 +193,8 @@
|
|||
<!-- 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">
|
||||
<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">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>
|
||||
<x-checkout.order-summary :subtotal="$subtotal" :total="$total" :savings="0" :tax="0"
|
||||
:showEdit="true" :editUrl="route('second', ['checkout', 'v1-cart'])" />
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
|
@ -188,8 +202,364 @@
|
|||
</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
|
||||
|
|
|
|||
|
|
@ -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\Auth\ProfileController;
|
||||
use App\Http\Controllers\CartController;
|
||||
use App\Http\Controllers\CheckoutController;
|
||||
|
||||
Route::group(['prefix' => '/dummy'], function () {
|
||||
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::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