coin price and coin exchange done
parent
74b8848e15
commit
e44b9e74a6
@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Customer;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Customer;
|
||||||
|
use App\Models\Location;
|
||||||
|
use App\Models\Sale;
|
||||||
|
use App\Models\Voucher;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class CoinExchangeController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$locations = Location::get();
|
||||||
|
$vouchers = Voucher::with(['location'])
|
||||||
|
->where(function ($q) {
|
||||||
|
$q->where('price_coin', '!=', 0)
|
||||||
|
->where('price_coin', '!=', null);
|
||||||
|
})
|
||||||
|
->where('is_sold', Voucher::UNSOLD)
|
||||||
|
->groupBy('batch_id')
|
||||||
|
->orderBy('updated_at', 'desc');
|
||||||
|
|
||||||
|
if ($request->location_id != '') {
|
||||||
|
$vouchers->where('location_id', $request->location_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return inertia('Coin/Exchange', [
|
||||||
|
'locations' => $locations,
|
||||||
|
'vouchers' => tap($vouchers->paginate(10))->setHidden(['username', 'password']),
|
||||||
|
'_location_id' => $request->location_id ?? '',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exchange(Voucher $voucher)
|
||||||
|
{
|
||||||
|
$batchCount = $voucher->count_unsold();
|
||||||
|
if ($batchCount < 1) {
|
||||||
|
return redirect()->route('customer.coin.exchange')
|
||||||
|
->with('message', ['type' => 'error', 'message' => 'transaksi gagal, voucher sedang tidak tersedia']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$customer = Customer::find(auth()->id());
|
||||||
|
|
||||||
|
if ($customer->coin_balance < $voucher->price_coin) {
|
||||||
|
return redirect()->route('customer.coin.exchange')
|
||||||
|
->with('message', ['type' => 'error', 'message' => 'koin kamu tidak cukup untuk ditukar voucher ini']);
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::beginTransaction();
|
||||||
|
$sale = $customer->sales()->create([
|
||||||
|
'code' => 'Tukar Coin ' . str()->upper(str()->random(5)),
|
||||||
|
'date_time' => now(),
|
||||||
|
'amount' => 0,
|
||||||
|
'payed_with' => Sale::PAYED_WITH_COIN,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$voucher = $voucher->shuffle_unsold();
|
||||||
|
$sale->items()->create([
|
||||||
|
'entity_type' => $voucher::class,
|
||||||
|
'entity_id' => $voucher->id,
|
||||||
|
'price' => $voucher->price_coin,
|
||||||
|
'quantity' => 1,
|
||||||
|
'additional_info_json' => json_encode([
|
||||||
|
'id' => $voucher->id,
|
||||||
|
'quantity' => 1,
|
||||||
|
'voucher' => $voucher->load(['location'])
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$voucher->update(['is_sold' => Voucher::SOLD]);
|
||||||
|
$voucher->check_stock_notification();
|
||||||
|
|
||||||
|
$sale->create_notification();
|
||||||
|
|
||||||
|
$coin = $customer->coins()->create([
|
||||||
|
'credit' => $voucher->price_coin,
|
||||||
|
'description' => $sale->code,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$coin->update_customer_balance();
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return redirect()->route('transactions.show', $sale)
|
||||||
|
->with('message', ['type' => 'success', 'message' => 'penukaran berhasil']);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,121 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import { Head, router } from '@inertiajs/react'
|
||||||
|
|
||||||
|
import CustomerLayout from '@/Layouts/CustomerLayout'
|
||||||
|
import VoucherCard from './VoucherCard'
|
||||||
|
|
||||||
|
const EmptyHere = () => {
|
||||||
|
return (
|
||||||
|
<div className="w-full px-5 text-center flex flex-col my-auto">
|
||||||
|
<div className="font-bold text-xl">Voucher segera tersedia</div>
|
||||||
|
<div className="text-gray-400">
|
||||||
|
Yuk, share referral kamu untuk tingkatkan coinnya
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Exhange(props) {
|
||||||
|
const {
|
||||||
|
locations,
|
||||||
|
vouchers: { data, next_page_url },
|
||||||
|
_location_id,
|
||||||
|
} = props
|
||||||
|
|
||||||
|
const [locId, setLocId] = useState(_location_id)
|
||||||
|
const [v, setV] = useState(data)
|
||||||
|
|
||||||
|
const handleSelectLoc = (loc) => {
|
||||||
|
if (loc.id === locId) {
|
||||||
|
setLocId('')
|
||||||
|
fetch('')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setLocId(loc.id)
|
||||||
|
fetch(loc.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNextPage = () => {
|
||||||
|
router.get(
|
||||||
|
next_page_url,
|
||||||
|
{
|
||||||
|
location_id: locId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
replace: true,
|
||||||
|
preserveState: true,
|
||||||
|
only: ['vouchers'],
|
||||||
|
onSuccess: (res) => {
|
||||||
|
setV(v.concat(res.props.vouchers.data))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetch = (locId) => {
|
||||||
|
router.get(
|
||||||
|
route(route().current()),
|
||||||
|
{ location_id: locId },
|
||||||
|
{
|
||||||
|
replace: true,
|
||||||
|
preserveState: true,
|
||||||
|
onSuccess: (res) => {
|
||||||
|
setV(res.props.vouchers.data)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CustomerLayout>
|
||||||
|
<Head title="Coin" />
|
||||||
|
<div className="flex flex-col min-h-[calc(95dvh)]">
|
||||||
|
<div className="pt-5 text-2xl px-5 font-bold">Tukar Coin</div>
|
||||||
|
<div className="px-5 text-gray-400 text-sm">
|
||||||
|
tukarkan coin anda dengan voucher manarik
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{v.length <= 0 ? (
|
||||||
|
<EmptyHere />
|
||||||
|
) : (
|
||||||
|
<div className="w-full flex flex-col pt-5">
|
||||||
|
{/* chips */}
|
||||||
|
<div className="w-full flex flex-row overflow-y-scroll space-x-2 px-2">
|
||||||
|
{locations.map((location) => (
|
||||||
|
<div
|
||||||
|
onClick={() => handleSelectLoc(location)}
|
||||||
|
key={location.id}
|
||||||
|
className={`px-2 py-1 rounded-2xl ${
|
||||||
|
location.id === locId
|
||||||
|
? 'text-white bg-blue-600 border border-blue-800'
|
||||||
|
: 'bg-blue-100 border border-blue-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{location.name}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* voucher */}
|
||||||
|
<div className="flex flex-col w-full px-3 mt-3 space-y-2">
|
||||||
|
{v.map((voucher) => (
|
||||||
|
<VoucherCard
|
||||||
|
key={voucher.id}
|
||||||
|
voucher={voucher}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{next_page_url !== null && (
|
||||||
|
<div
|
||||||
|
onClick={handleNextPage}
|
||||||
|
className="w-full text-center px-2 py-1 border mt-5 hover:bg-blue-600 hover:text-white"
|
||||||
|
>
|
||||||
|
Load more
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CustomerLayout>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
import { formatIDR } from '@/utils'
|
||||||
|
import { router } from '@inertiajs/react'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
const ExchangeModal = ({ show, voucher, setShow }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`fixed z-10 top-0 left-0 h-full w-full -mt-4 ${
|
||||||
|
show ? '' : 'invisible'
|
||||||
|
} `}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="max-w-md mx-auto h-full bg-gray-500 bg-opacity-70 -mt-2"
|
||||||
|
onClick={() => setShow(false)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col h-full my-auto justify-center px-4">
|
||||||
|
<div className="px-3 py-1 shadow-md rounded border bg-white border-gray-100 hover:bg-gray-50">
|
||||||
|
<div className="text-base font-bold">
|
||||||
|
{voucher.location.name}
|
||||||
|
</div>
|
||||||
|
<div className="w-full border border-dashed"></div>
|
||||||
|
<div className="flex flex-row justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-400 py-1">
|
||||||
|
{voucher.profile}
|
||||||
|
</div>
|
||||||
|
<div className="text-xl font-bold">
|
||||||
|
{formatIDR(voucher.price_coin)} Coin
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col justify-end text-right">
|
||||||
|
<div className="text-3xl font-bold">
|
||||||
|
{voucher.display_quota}
|
||||||
|
</div>
|
||||||
|
<div className="text-gray-400 ">
|
||||||
|
{voucher.display_expired}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row space-x-3">
|
||||||
|
<div
|
||||||
|
className="w-full mt-2 px-3 py-1 shadow-md rounded border-blue-700 bg-blue-600 text-white hover:bg-white hover:text-black"
|
||||||
|
onClick={() =>
|
||||||
|
router.get(
|
||||||
|
route(
|
||||||
|
'customer.coin.exchange.process',
|
||||||
|
voucher
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Tukarkan
|
||||||
|
</div>
|
||||||
|
<div className="w-full mt-2 px-3 py-1 shadow-md rounded border-white bg-white hover:bg-gray-200">
|
||||||
|
Batal
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function VoucherCard({ voucher }) {
|
||||||
|
const [show, setShow] = useState(false)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className="px-3 py-1 shadow-md rounded border border-gray-100 hover:bg-gray-50"
|
||||||
|
onClick={() => setShow(true)}
|
||||||
|
>
|
||||||
|
<div className="text-base font-bold">
|
||||||
|
{voucher.location.name}
|
||||||
|
</div>
|
||||||
|
<div className="w-full border border-dashed"></div>
|
||||||
|
<div className="flex flex-row justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-400 py-1">
|
||||||
|
{voucher.profile}
|
||||||
|
</div>
|
||||||
|
<div className="text-xl font-bold">
|
||||||
|
{formatIDR(voucher.price_coin)} Coin
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col justify-end text-right">
|
||||||
|
<div className="text-3xl font-bold">
|
||||||
|
{voucher.display_quota}
|
||||||
|
</div>
|
||||||
|
<div className="text-gray-400 ">
|
||||||
|
{voucher.display_expired}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ExchangeModal voucher={voucher} show={show} setShow={setShow} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -1,64 +1,64 @@
|
|||||||
import moment from "moment";
|
import moment from 'moment'
|
||||||
import { isEmpty } from "lodash";
|
import { isEmpty } from 'lodash'
|
||||||
|
|
||||||
export const formatDate = (date) => {
|
export const formatDate = (date) => {
|
||||||
return moment(date).format("DD/MM/yyyy");
|
return moment(date).format('DD/MM/yyyy')
|
||||||
};
|
}
|
||||||
|
|
||||||
export const formatDateTime = (date) => {
|
export const formatDateTime = (date) => {
|
||||||
return moment(date).format("DD/MM/yyyy HH:mm:ss");
|
return moment(date).format('DD/MM/yyyy HH:mm:ss')
|
||||||
};
|
}
|
||||||
|
|
||||||
export const dateToString = (date) => {
|
export const dateToString = (date) => {
|
||||||
return moment(date).format("MM/DD/yyyy");
|
return moment(date).format('MM/DD/yyyy')
|
||||||
};
|
}
|
||||||
|
|
||||||
export const converToDate = (date) => {
|
export const converToDate = (date) => {
|
||||||
if (isEmpty(date) == false) {
|
if (isEmpty(date) == false) {
|
||||||
return new Date(date);
|
return new Date(date)
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return ''
|
||||||
};
|
}
|
||||||
|
|
||||||
export function formatIDR(amount) {
|
export function formatIDR(amount) {
|
||||||
const idFormatter = new Intl.NumberFormat("id-ID",{
|
const idFormatter = new Intl.NumberFormat('id-ID', {
|
||||||
minimumFractionDigits: 0,
|
minimumFractionDigits: 0,
|
||||||
maximumFractionDigits: 2,
|
maximumFractionDigits: 2,
|
||||||
});
|
})
|
||||||
return idFormatter.format(amount);
|
return idFormatter.format(amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formatIDDate = (date) => {
|
export const formatIDDate = (date) => {
|
||||||
const month = [
|
const month = [
|
||||||
"Januari",
|
'Januari',
|
||||||
"Februari",
|
'Februari',
|
||||||
"Maret",
|
'Maret',
|
||||||
"April",
|
'April',
|
||||||
"Mei",
|
'Mei',
|
||||||
"Juni",
|
'Juni',
|
||||||
"Juli",
|
'Juli',
|
||||||
"Agustus",
|
'Agustus',
|
||||||
"September",
|
'September',
|
||||||
"Oktober",
|
'Oktober',
|
||||||
"November",
|
'November',
|
||||||
"Desember",
|
'Desember',
|
||||||
];
|
]
|
||||||
date = new Date(date);
|
date = new Date(date)
|
||||||
|
|
||||||
return `${date.getDate()} ${month[date.getMonth()]} ${date.getFullYear()}`;
|
return `${date.getDate()} ${month[date.getMonth()]} ${date.getFullYear()}`
|
||||||
};
|
}
|
||||||
|
|
||||||
export const hasPermission = (auth, permission) => {
|
export const hasPermission = (auth, permission) => {
|
||||||
const { user } = auth
|
const { user } = auth
|
||||||
if (+user.is_superadmin === 1) {
|
if (+user.is_superadmin === 1) {
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
let has = user.role.permissions.find(item => item.name === permission)
|
let has = user.role.permissions.find((item) => item.name === permission)
|
||||||
|
|
||||||
if(has) {
|
if (has) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue