pembayaran hutang done

dev
Aji Kamaludin 1 year ago
parent bdf9ea7a49
commit 394e4bb40b
No known key found for this signature in database
GPG Key ID: 19058F67F0083AD3

@ -39,6 +39,6 @@
- [x] menu mitra wbb ada tombol tambah mitra untuk registrasi dengan full form
- [x] menu pembayaran hutang [admin dapat melakukan pembayaran hutang dari customer, atau mengkonfirmasi pembayaran yang dilakukan custoemr]
- [x] ubah filter di mitra list dan customer list menjadi seperti di sale index
- [ ] untuk detail mitra nanti akan ada button untuk (transaksi mitra dengan cakupan: pembelian voucher, pembayaran hutang,
- [x] untuk detail mitra nanti akan ada button untuk (transaksi mitra dengan cakupan: pembelian voucher, pembayaran hutang,
topuplimit, penambahan batas bayar, history deposit)
- [ ] tambah floating button untuk notifikasi deposit (angka saja), di dashboard tambahkan list deposit terbaru

@ -35,8 +35,17 @@ class CustomerHistoryController extends Controller
]);
}
public function paylater()
public function paylater(Customer $customer)
{
$query = PaylaterHistory::with(['editor'])
->where('customer_id', $customer->id)
// ->where('type', PaylaterHistory::TYPE_REPAYMENT)
->orderBy('created_at', 'desc');
return inertia('CustomerHistory/PaylaterHistory', [
'query' => $query->paginate(),
'customer' => $customer,
]);
}
public function paylater_limit(Customer $customer)

@ -42,10 +42,6 @@ class DepositController extends Controller
$deposits->where('is_valid', $request->status);
}
if ($request->customer_id != '') {
$deposits->where('is_valid', $request->customer_id);
}
$customers = Customer::with(['paylater'])->orderBy('deposit_balance', 'desc');
$stats = [
@ -96,7 +92,7 @@ class DepositController extends Controller
'debit' => 'required|numeric',
]);
if ($request->status == DepositHistory::STATUS_REJECT) {
if ($request->is_valid == DepositHistory::STATUS_REJECT) {
$request->validate(['reject_reason' => 'required|string']);
}
@ -107,12 +103,13 @@ class DepositController extends Controller
'note' => $request->reject_reason,
]);
if ($request->status == DepositHistory::STATUS_VALID) {
if ($request->is_valid == DepositHistory::STATUS_VALID) {
$deposit->update_customer_balance();
$deposit->create_notification_user();
}
DB::commit();
session()->flash('message', ['type' => 'success', 'message' => 'Item has beed updated']);
return redirect()->route('deposit.index')
->with('message', ['type' => 'success', 'message' => 'Item has beed updated']);
}
}

@ -4,27 +4,105 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Customer;
use App\Models\DepositHistory;
use App\Models\PaylaterHistory;
use App\Services\GeneralService;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
class PaylaterController extends Controller
{
public function index()
public function index(Request $request)
{
// TODO show list of paylater repaymeny
$deposits = DepositHistory::with(['customer', 'account', 'depositLocation', 'editor'])
->where('credit', 0)
->where('type', DepositHistory::TYPE_REPAYMENT);
if ($request->q != '') {
$deposits->where(function ($query) use ($request) {
$query->where('description', 'ilike', "%$request->q%")
->orWhereHas('customer', function ($query) use ($request) {
$query->where('fullname', 'ilike', "%$request->q%")
->orWhere('email', 'ilike', "%$request->q%")
->orWhere('phone', 'ilike', "%$request->q%");
});
});
}
$sortBy = 'updated_at';
$sortRule = 'desc';
if ($request->sortBy != '' && $request->sortRule != '') {
$sortBy = $request->sortBy;
$sortRule = $request->sortRule;
}
$deposits->orderBy($sortBy, $sortRule);
if ($request->status != '') {
$deposits->where('is_valid', $request->status);
}
return inertia('Paylater/Index', [
'deposits' => $deposits->paginate(),
'_q' => $request->q,
'_sortBy' => $sortBy,
'_sortRule' => $sortRule,
]);
}
public function edit()
public function show(PaylaterHistory $paylater)
{
// TODO show detail repayment and confirmation
return inertia('Paylater/Detail', [
'paylater' => $paylater->load(['customer'])
]);
}
public function update()
public function edit(DepositHistory $deposit)
{
// TODO store update detail of repayment
return inertia('Paylater/Form', [
'deposit' => $deposit->load(['customer', 'account', 'depositLocation', 'editor']),
]);
}
public function update(Request $request, DepositHistory $deposit)
{
$request->validate([
'is_valid' => [
'required',
Rule::in([DepositHistory::STATUS_VALID, DepositHistory::STATUS_REJECT]),
],
'debit' => 'required|numeric',
]);
if ($request->is_valid == DepositHistory::STATUS_REJECT) {
$request->validate(['reject_reason' => 'required|string']);
}
DB::beginTransaction();
$deposit->update([
'debit' => $request->debit,
'is_valid' => $request->is_valid,
'note' => $request->reject_reason,
]);
$paylater = $deposit->paylater;
$paylater->update([
'credit' => $request->debit,
'is_valid' => $request->is_valid,
'note' => $request->reject_reason,
]);
if ($request->is_valid == DepositHistory::STATUS_VALID) {
$paylater->update_customer_paylater();
$paylater->create_notification_user();
}
DB::commit();
return redirect()->route('paylater.index')
->with('message', ['type' => 'success', 'message' => 'Item has beed updated']);
}
public function limit()

@ -102,6 +102,7 @@ class DepositController extends Controller
'direct' => $request->direct,
'bank_admin_fee' => Setting::getByKey('ADMINFEE_MANUAL_TRANSFER'),
'cash_admin_fee' => Setting::getByKey('ADMINFEE_CASH_DEPOSIT'),
'back' => $request->back ?? 'transactions.deposit.index'
]);
}
@ -156,6 +157,7 @@ class DepositController extends Controller
if ($is_valid == DepositHistory::STATUS_VALID) {
$deposit->update_customer_balance();
}
// TODO: update for paylater
DB::commit();
@ -188,6 +190,7 @@ class DepositController extends Controller
$deposit->save();
}
// TODO: update for paylater
DB::commit();

@ -3,8 +3,10 @@
namespace App\Http\Controllers\Customer;
use App\Http\Controllers\Controller;
use App\Models\Account;
use App\Models\Customer;
use App\Models\DepositHistory;
use App\Models\DepositLocation;
use App\Models\PaylaterHistory;
use App\Models\Setting;
use App\Services\GeneralService;
@ -27,6 +29,17 @@ class PaylaterController extends Controller
public function show(Request $request, PaylaterHistory $paylater)
{
if ($paylater->type == PaylaterHistory::TYPE_REPAYMENT) {
$deposit = DepositHistory::where('related_id', $paylater->id)->first();
if (!in_array($deposit->is_valid, [DepositHistory::STATUS_VALID])) {
return redirect()->route('transactions.deposit.show', [
'deposit' => $deposit,
'back' => 'customer.paylater.index'
]);
}
}
return inertia('Paylater/Detail', [
'paylater' => $paylater,
]);
@ -52,6 +65,27 @@ class PaylaterController extends Controller
$customer = $request->user('customer');
// validate amount
if ($customer->paylater->usage < $request->amount) {
return redirect()->back()
->with('message', ['type' => 'error', 'message' => 'Nominal Tagihan tidak boleh lebih dari tagihan']);
}
// only 1 repayment at a time
$repayment = DepositHistory::query()
->where([
['customer_id', '=', $customer->id],
['type', '=', DepositHistory::TYPE_REPAYMENT]
])->where(function ($query) {
$query->where('is_valid', '!=', DepositHistory::STATUS_VALID)
->where('is_valid', '!=', DepositHistory::STATUS_REJECT)
->where('is_valid', '!=', DepositHistory::STATUS_EXPIRED);
})->first();
if ($repayment != null) {
return redirect()->back()
->with('message', ['type' => 'error', 'message' => 'Selesaikan pembayaran tagihan sebelumnya']);
}
DB::beginTransaction();
$code = GeneralService::generateDepositRepayCode();
@ -93,6 +127,10 @@ class PaylaterController extends Controller
DB::commit();
return redirect()->route('transactions.deposit.show', ['deposit' => $deposit->id, 'direct' => 'true']);
return redirect()->route('transactions.deposit.show', [
'deposit' => $deposit->id,
'direct' => 'true',
'back' => 'customer.paylater.index'
]);
}
}

@ -3,6 +3,7 @@
namespace App\Jobs;
use App\Models\DepositHistory;
use App\Models\PaylaterHistory;
use App\Models\Setting;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -41,6 +42,8 @@ class ExpiredDepositHistoryJob implements ShouldQueue
])
->get();
$paylaterIds = [];
info(self::class, ['deposit' => $deposits->count()]);
foreach ($deposits as $deposit) {
$lastUpdated = Carbon::parse($deposit->updated_at);
@ -51,14 +54,22 @@ class ExpiredDepositHistoryJob implements ShouldQueue
// Check if the time difference is more than 2 hours
if ($timeDifference > $maxTimeout) {
$expiredIds[] = $deposit->id;
if ($deposit->related_id != null && $deposit->type == DepositHistory::TYPE_REPAYMENT) {
$paylaterIds[] = $deposit->related_id;
}
}
}
info(self::class, ['deposit_to_expired' => count($expiredIds)]);
info(self::class, ['deposit_to_expired' => count($expiredIds)]);
if (count($expiredIds) > 0) {
DepositHistory::whereIn('id', $expiredIds)->update(['is_valid' => DepositHistory::STATUS_EXPIRED]);
}
info(self::class, ['paylater_to_expired' => count($paylaterIds)]);
if (count($paylaterIds) > 0) {
PaylaterHistory::whereIn('id', $paylaterIds)->update(['is_valid' => PaylaterHistory::STATUS_EXPIRED]);
}
DB::commit();
info(self::class, ['done']);
}

@ -52,6 +52,7 @@ class DepositHistory extends Model
'format_created_at',
'amount',
'image_prove_url',
'admin_fee'
];
protected static function booted(): void
@ -112,6 +113,23 @@ class DepositHistory extends Model
});
}
public function adminFee(): Attribute
{
return Attribute::make(get: function () {
if ($this->account_id != null) {
return Setting::getByKey('ADMINFEE_MANUAL_TRANSFER');
}
if ($this->deposit_location_id != null) {
return Setting::getByKey('ADMINFEE_CASH_DEPOSIT');
}
if ($this->payment_token != null) {
return Setting::getByKey('MIDTRANS_ADMIN_FEE');
}
});
}
public function customer()
{
return $this->belongsTo(Customer::class);
@ -127,6 +145,11 @@ class DepositHistory extends Model
return $this->belongsTo(DepositLocation::class, 'deposit_location_id');
}
public function paylater()
{
return $this->hasOne(PaylaterHistory::class, 'id', 'related_id');
}
public function update_customer_balance()
{
$customer = Customer::find($this->customer_id);

@ -44,21 +44,13 @@ class PaylaterHistory extends Model
'format_human_created_at',
'format_created_at',
'amount',
'status',
'status_text',
];
public function update_customer_paylater()
public function customer()
{
$customer = Customer::find($this->customer_id);
$paylater = $customer->paylater;
if ($paylater->day_deadline_at == null) {
$paylater->day_deadline_at = now()->addDays($paylater->day_deadline);
}
$paylater->update([
'usage' => $paylater->usage + $this->debit - $this->credit,
'day_deadline_at' => $paylater->day_deadline_at,
]);
return $this->belongsTo(Customer::class);
}
public function formatHumanCreatedAt(): Attribute
@ -85,4 +77,67 @@ class PaylaterHistory extends Model
return '-Rp ' . number_format($this->credit, is_float($this->credit) ? 2 : 0, ',', '.');
});
}
public function status(): Attribute
{
return Attribute::make(get: function () {
if ($this->type == self::TYPE_REPAYMENT && $this->is_valid == self::STATUS_REJECT) {
return 'Reject';
}
if ($this->type == self::TYPE_REPAYMENT && $this->is_valid == self::STATUS_EXPIRED) {
return 'Expired';
}
if ($this->type == self::TYPE_REPAYMENT && $this->is_valid != self::STATUS_VALID) {
return 'Menunggu pembayaran';
}
return '';
});
}
public function statusText(): Attribute
{
return Attribute::make(get: function () {
return [
self::STATUS_VALID => ['text' => 'Success', 'color' => 'bg-green-600', 'text_color' => 'text-green-600'],
self::STATUS_WAIT_UPLOAD => ['text' => 'Upload bukti bayar', 'color' => 'bg-red-600', 'text_color' => 'text-red-600'],
self::STATUS_WAIT_APPROVE => ['text' => 'Menunggu Approve', 'color' => 'bg-green-600', 'text_color' => 'text-green-600'],
self::STATUS_WAIT_PAYMENT => ['text' => 'Menunggu Pembayaran', 'color' => 'bg-green-600', 'text_color' => 'text-green-600'],
self::STATUS_INVALID => ['text' => 'Error', 'color' => 'bg-red-600', 'text_color' => 'text-red-600'],
self::STATUS_REJECT => ['text' => 'Reject', 'color' => 'bg-red-600', 'text_color' => 'text-red-600'],
self::STATUS_EXPIRED => ['text' => 'Expired', 'color' => 'bg-red-600', 'text_color' => 'text-red-600'],
][$this->is_valid];
});
}
public function create_notification_user()
{
Notification::create([
'entity_id' => $this->customer_id,
'description' => 'Pembayaran ' . $this->description . ' sebesar ' . $this->amount . ' sudah sukses diterima',
]);
}
public function update_customer_paylater()
{
$customer = Customer::find($this->customer_id);
$paylater = $customer->paylater;
if ($paylater->day_deadline_at == null) {
$paylater->day_deadline_at = now()->addDays($paylater->day_deadline);
}
$usage = $paylater->usage + $this->debit - $this->credit;
if ($usage == 0) {
$paylater->day_deadline_at = null;
}
$paylater->update([
'usage' => $usage,
'day_deadline_at' => $paylater->day_deadline_at,
]);
}
}

@ -9,7 +9,7 @@
"dyrynda/laravel-cascade-soft-deletes": "^4.3",
"guzzlehttp/guzzle": "^7.7.0",
"inertiajs/inertia-laravel": "^0.6.9",
"laravel/framework": "^10.13.5",
"laravel/framework": "^10.14.1",
"laravel/sanctum": "^3.2.5",
"laravel/socialite": "^5.6.3",
"laravel/tinker": "^2.8.1",
@ -28,7 +28,7 @@
"mockery/mockery": "^1.6.2",
"nunomaduro/collision": "^6.4",
"phpunit/phpunit": "^9.6.9",
"spatie/laravel-ignition": "^2.1.3"
"spatie/laravel-ignition": "^2.2.0"
},
"autoload": {
"psr-4": {

79
composer.lock generated

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "9a5a4aa44ec60adc8aa5223f1cdf6e32",
"content-hash": "db8739a9b80108312e32b7bcb4417196",
"packages": [
{
"name": "brick/math",
@ -1094,16 +1094,16 @@
},
{
"name": "laravel/framework",
"version": "v10.13.5",
"version": "v10.14.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "03106ae9ba2ec4b36dc973b7bdca6fad81e032b4"
"reference": "6f89a2b74b232d8bf2e1d9ed87e311841263dfcb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/03106ae9ba2ec4b36dc973b7bdca6fad81e032b4",
"reference": "03106ae9ba2ec4b36dc973b7bdca6fad81e032b4",
"url": "https://api.github.com/repos/laravel/framework/zipball/6f89a2b74b232d8bf2e1d9ed87e311841263dfcb",
"reference": "6f89a2b74b232d8bf2e1d9ed87e311841263dfcb",
"shasum": ""
},
"require": {
@ -1290,7 +1290,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2023-06-08T20:25:36+00:00"
"time": "2023-06-28T14:25:16+00:00"
},
{
"name": "laravel/sanctum",
@ -2182,16 +2182,16 @@
},
{
"name": "nesbot/carbon",
"version": "2.67.0",
"version": "2.68.1",
"source": {
"type": "git",
"url": "https://github.com/briannesbitt/Carbon.git",
"reference": "c1001b3bc75039b07f38a79db5237c4c529e04c8"
"reference": "4f991ed2a403c85efbc4f23eb4030063fdbe01da"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/c1001b3bc75039b07f38a79db5237c4c529e04c8",
"reference": "c1001b3bc75039b07f38a79db5237c4c529e04c8",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/4f991ed2a403c85efbc4f23eb4030063fdbe01da",
"reference": "4f991ed2a403c85efbc4f23eb4030063fdbe01da",
"shasum": ""
},
"require": {
@ -2280,7 +2280,7 @@
"type": "tidelift"
}
],
"time": "2023-05-25T22:09:47+00:00"
"time": "2023-06-20T18:29:04+00:00"
},
{
"name": "nette/schema",
@ -8538,16 +8538,16 @@
},
{
"name": "spatie/backtrace",
"version": "1.4.1",
"version": "1.5.3",
"source": {
"type": "git",
"url": "https://github.com/spatie/backtrace.git",
"reference": "47794d19e3215ace9e005a8f200cd7cc7be52572"
"reference": "483f76a82964a0431aa836b6ed0edde0c248e3ab"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/backtrace/zipball/47794d19e3215ace9e005a8f200cd7cc7be52572",
"reference": "47794d19e3215ace9e005a8f200cd7cc7be52572",
"url": "https://api.github.com/repos/spatie/backtrace/zipball/483f76a82964a0431aa836b6ed0edde0c248e3ab",
"reference": "483f76a82964a0431aa836b6ed0edde0c248e3ab",
"shasum": ""
},
"require": {
@ -8584,7 +8584,7 @@
"spatie"
],
"support": {
"source": "https://github.com/spatie/backtrace/tree/1.4.1"
"source": "https://github.com/spatie/backtrace/tree/1.5.3"
},
"funding": [
{
@ -8596,26 +8596,27 @@
"type": "other"
}
],
"time": "2023-06-13T14:35:04+00:00"
"time": "2023-06-28T12:59:17+00:00"
},
{
"name": "spatie/flare-client-php",
"version": "1.3.6",
"version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/flare-client-php.git",
"reference": "530ac81255af79f114344286e4275f8869c671e2"
"reference": "82138174d5fe2829a7f085a6bdb2a06f6def9f7a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/flare-client-php/zipball/530ac81255af79f114344286e4275f8869c671e2",
"reference": "530ac81255af79f114344286e4275f8869c671e2",
"url": "https://api.github.com/repos/spatie/flare-client-php/zipball/82138174d5fe2829a7f085a6bdb2a06f6def9f7a",
"reference": "82138174d5fe2829a7f085a6bdb2a06f6def9f7a",
"shasum": ""
},
"require": {
"illuminate/pipeline": "^8.0|^9.0|^10.0",
"nesbot/carbon": "^2.62.1",
"php": "^8.0",
"spatie/backtrace": "^1.2",
"spatie/backtrace": "^1.5.2",
"symfony/http-foundation": "^5.0|^6.0",
"symfony/mime": "^5.2|^6.0",
"symfony/process": "^5.2|^6.0",
@ -8632,7 +8633,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.1.x-dev"
"dev-main": "1.3.x-dev"
}
},
"autoload": {
@ -8657,7 +8658,7 @@
],
"support": {
"issues": "https://github.com/spatie/flare-client-php/issues",
"source": "https://github.com/spatie/flare-client-php/tree/1.3.6"
"source": "https://github.com/spatie/flare-client-php/tree/1.4.0"
},
"funding": [
{
@ -8665,28 +8666,28 @@
"type": "github"
}
],
"time": "2023-04-12T07:57:12+00:00"
"time": "2023-06-28T11:08:09+00:00"
},
{
"name": "spatie/ignition",
"version": "1.8.1",
"version": "1.9.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/ignition.git",
"reference": "d8eb8ea1ed27f48a694405cff363746ffd37f13e"
"reference": "de24ff1e01814d5043bd6eb4ab36a5a852a04973"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/ignition/zipball/d8eb8ea1ed27f48a694405cff363746ffd37f13e",
"reference": "d8eb8ea1ed27f48a694405cff363746ffd37f13e",
"url": "https://api.github.com/repos/spatie/ignition/zipball/de24ff1e01814d5043bd6eb4ab36a5a852a04973",
"reference": "de24ff1e01814d5043bd6eb4ab36a5a852a04973",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-mbstring": "*",
"php": "^8.0",
"spatie/backtrace": "^1.4",
"spatie/flare-client-php": "^1.1",
"spatie/backtrace": "^1.5.3",
"spatie/flare-client-php": "^1.4.0",
"symfony/console": "^5.4|^6.0",
"symfony/var-dumper": "^5.4|^6.0"
},
@ -8698,7 +8699,7 @@
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"psr/simple-cache-implementation": "*",
"symfony/cache": "^6.2",
"symfony/cache": "^6.0",
"symfony/process": "^5.4|^6.0",
"vlucas/phpdotenv": "^5.5"
},
@ -8748,20 +8749,20 @@
"type": "github"
}
],
"time": "2023-06-06T14:14:58+00:00"
"time": "2023-06-28T13:24:59+00:00"
},
{
"name": "spatie/laravel-ignition",
"version": "2.1.3",
"version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-ignition.git",
"reference": "35711943d4725aa80f8033e4f1cb3a6775530b25"
"reference": "dd15fbe82ef5392798941efae93c49395a87d943"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/35711943d4725aa80f8033e4f1cb3a6775530b25",
"reference": "35711943d4725aa80f8033e4f1cb3a6775530b25",
"url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/dd15fbe82ef5392798941efae93c49395a87d943",
"reference": "dd15fbe82ef5392798941efae93c49395a87d943",
"shasum": ""
},
"require": {
@ -8771,7 +8772,7 @@
"illuminate/support": "^10.0",
"php": "^8.1",
"spatie/flare-client-php": "^1.3.5",
"spatie/ignition": "^1.5.0",
"spatie/ignition": "^1.9",
"symfony/console": "^6.2.3",
"symfony/var-dumper": "^6.2.3"
},
@ -8840,7 +8841,7 @@
"type": "github"
}
],
"time": "2023-05-25T11:30:27+00:00"
"time": "2023-06-28T13:51:52+00:00"
},
{
"name": "symfony/yaml",

@ -51,7 +51,7 @@ const ActionSection = ({ deposit }) => {
)
}
export default function Detail({ deposit }) {
export default function Detail({ deposit, back }) {
return (
<CustomerLayout>
<Head title="Top Up" />
@ -59,7 +59,7 @@ export default function Detail({ deposit }) {
<div
className="w-full px-5 py-5"
onClick={() => {
router.get(route('transactions.deposit.index'))
router.get(route(back))
}}
>
<HiChevronLeft className="font-bold h-5 w-5" />

@ -8,7 +8,7 @@ import CustomerLayout from '@/Layouts/CustomerLayout'
export default function Detail({ paylater }) {
return (
<CustomerLayout>
<Head title="poin" />
<Head title="Mitra WBB" />
<div className="flex flex-col min-h-[calc(95dvh)]">
<div
className="w-full px-5 py-5"
@ -40,6 +40,11 @@ export default function Detail({ paylater }) {
{paylater.note}
</div>
)}
{isEmpty(paylater.status) === false && (
<div className="bg-red-50 text-red-700 p-3 border rounded-md">
{paylater.status}
</div>
)}
</div>
</div>
</div>

@ -26,7 +26,7 @@ export default function Index({
return (
<CustomerLayout>
<Head title="Top Up" />
<Head title="Mitra WBB" />
<div className="flex flex-col w-full min-h-[calc(90dvh)]">
<div className="w-full pt-10 px-5">
<div className="text-base">{user.fullname}</div>
@ -47,14 +47,18 @@ export default function Index({
</div>
</div>
<div>
<div
className="px-3 py-2 border rounded-full bg-blue-700 text-white hover:bg-transparent hover:text-black"
onClick={() =>
router.get(route('customer.paylater.repay'))
}
>
Bayar Tagihan
</div>
{+user.paylater.usage !== 0 && (
<div
className="px-3 py-2 border rounded-full bg-blue-700 text-white hover:bg-transparent hover:text-black"
onClick={() =>
router.get(
route('customer.paylater.repay')
)
}
>
Bayar Tagihan
</div>
)}
</div>
</div>
<div
@ -91,6 +95,13 @@ export default function Index({
<div className="font-bold text-lg">
{history.amount}
</div>
{history.status !== '' && (
<div
className={`text-xs px-2 py-1 rounded-full border text-white bg-red-600`}
>
{history.status}
</div>
)}
</div>
</div>
))}

@ -164,8 +164,8 @@ export default [
name: 'Pembayaran Hutang', // daftar pembayaran hutang yang perlu di konfirmasi , dan ada tombol add untuk pembayaran hutang oleh admin
show: true,
icon: HiCash,
route: route('setting.affilate'),
active: 'setting.affilate',
route: route('paylater.index'),
active: 'paylater.*',
permission: 'view-paylater-repayment',
},
{

@ -0,0 +1,172 @@
import React from 'react'
import { Link, router } from '@inertiajs/react'
import { Head } from '@inertiajs/react'
import { HiEye } from 'react-icons/hi2'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import Pagination from '@/Components/Pagination'
import { formatIDR } from '@/utils'
import Button from '@/Components/Button'
import { HiRefresh } from 'react-icons/hi'
export default function PaylaterHistory(props) {
const {
query: { links, data },
customer,
} = props
return (
<AuthenticatedLayout
page={'Mitra WBB'}
action={[customer.name, 'Riwayat Pembayaran Hutang']}
parent={route('mitra.edit', customer)}
>
<Head title="Pembayaran Hutang" />
<div>
<div className="mx-auto sm:px-6 lg:px-8 ">
<div className="p-6 overflow-hidden shadow-sm sm:rounded-lg bg-gray-200 dark:bg-gray-800 space-y-4">
<div className="w-full flex flex-row justify-end">
<Button
onClick={() =>
router.visit(
route(route().current(), customer)
)
}
>
<HiRefresh className="w-5 h-5" />
</Button>
</div>
<div className="overflow-auto">
<div>
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-400 mb-4">
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th
scope="col"
className="py-3 px-6"
>
#
</th>
<th
scope="col"
className="py-3 px-6"
>
Customer
</th>
<th
scope="col"
className="py-3 px-3"
>
Debit
</th>
<th
scope="col"
className="py-3 px-3"
>
Credit
</th>
<th
scope="col"
className="py-3 px-6"
>
Tanggal
</th>
<th
scope="col"
className="py-3 px-6"
>
Note
</th>
<th
scope="col"
className="py-3 px-6"
>
Status
</th>
<th
scope="col"
className="py-3 px-6"
>
Approver
</th>
<th
scope="col"
className="py-3 px-6"
/>
</tr>
</thead>
<tbody>
{data.map((paylater) => (
<tr
className="bg-white border-b dark:bg-gray-800 dark:border-gray-700"
key={paylater.id}
>
<td
scope="row"
className="py-4 px-6 font-medium text-gray-900 whitespace-nowrap dark:text-white"
>
{paylater.description}
</td>
<td className="py-4 px-6">
<Link
className="hover:underline"
href={route(
'mitra.edit',
customer.id
)}
>
{customer.name}
</Link>
</td>
<td className="py-4 px-3">
{`Rp ${formatIDR(
paylater.credit
)}`}
</td>
<td className="py-4 px-3">
{`Rp ${formatIDR(
paylater.debit
)}`}
</td>
<td className="py-4 px-6">
{paylater.format_created_at}
</td>
<td className="py-4 px-6">
{paylater.note}
</td>
<td
className={`py-4 px-6 ${paylater.status_text.text_color}`}
>
{paylater.status_text.text}
</td>
<td className="py-4 px-6">
{paylater.editor?.name}
</td>
<td className="py-4 px-6 flex justify-center">
<Link
href={route(
'paylater.show',
paylater
)}
className="flex space-x-1 items-center hover:underline"
>
<HiEye />
<div>Lihat</div>
</Link>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="w-full flex items-center justify-center">
<Pagination links={links} />
</div>
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
)
}

@ -229,7 +229,12 @@ export default function Form(props) {
Riwayat Pembelian
</FButton>
</Link>
<Link href="#">
<Link
href={route(
'mitra.history.paylater',
customer
)}
>
<FButton
size="xs"
color="primary"

@ -13,13 +13,14 @@ import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import FormInput from '@/Components/FormInput'
import Button from '@/Components/Button'
import FormInputNumeric from '@/Components/FormInputNumeric'
import { formatIDR } from '@/utils'
export default function Form(props) {
const { deposit } = props
const { data, setData, post, processing, errors } = useForm({
debit: 0,
is_valid: 0,
is_valid: '',
reject_reason: '',
})
@ -56,7 +57,7 @@ export default function Form(props) {
return (
<AuthenticatedLayout
page={'Deposit'}
action={'Form'}
action={deposit.description}
parent={route('deposit.index')}
>
<Head title="Deposit" />
@ -64,7 +65,9 @@ export default function Form(props) {
<div>
<div className="mx-auto sm:px-6 lg:px-8">
<div className="overflow-hidden p-4 shadow-sm sm:rounded-lg bg-white dark:bg-gray-800 flex flex-col ">
<div className="text-xl font-bold mb-4">Deposit</div>
<div className="text-xl font-bold mb-4">
{deposit.description}
</div>
<table className="relative w-full overflow-x-auto p-2 rounded">
<tbody>
<tr>
@ -81,11 +84,6 @@ export default function Form(props) {
</Link>
</td>
</tr>
<tr>
<td className="font-bold">Deskripsi</td>
<td>:</td>
<td>{deposit.description}</td>
</tr>
<tr>
<td className="font-bold">
Metode Pembayaran
@ -117,6 +115,11 @@ export default function Form(props) {
<td>:</td>
<td>{deposit.amount}</td>
</tr>
<tr>
<td className="font-bold">Admin Fee</td>
<td>:</td>
<td>Rp {formatIDR(deposit.admin_fee)}</td>
</tr>
<tr>
<td className="font-bold">Status</td>
<td>:</td>
@ -138,6 +141,11 @@ export default function Form(props) {
<td>{deposit.editor.name}</td>
</tr>
)}
<tr>
<td className="font-bold">Tanggal</td>
<td>:</td>
<td>{deposit.format_created_at}</td>
</tr>
</tbody>
</table>
@ -170,7 +178,7 @@ export default function Form(props) {
<select
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
onChange={handleOnChange}
value={+data.is_valid}
value={data.is_valid}
name="is_valid"
>
<option value="">
@ -184,9 +192,14 @@ export default function Form(props) {
Reject
</option>
</select>
{errors.status && (
{errors.is_valid && (
<div className="text-sm text-red-500">
{errors.is_valid}
</div>
)}
{errors.reject_reason && (
<div className="text-sm text-red-500">
{errors.status}
{errors.reject_reason}
</div>
)}
</div>

@ -0,0 +1,67 @@
import React from 'react'
import { Head, Link } from '@inertiajs/react'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
export default function Detail(props) {
const { paylater } = props
return (
<AuthenticatedLayout
page={'Pembayaran Hutang'}
action={paylater.description}
parent={route('paylater.index')}
>
<Head title="Pembayaran Hutang" />
<div>
<div className="mx-auto sm:px-6 lg:px-8">
<div className="overflow-hidden p-4 shadow-sm sm:rounded-lg bg-white dark:bg-gray-800 flex flex-col ">
<div className="text-xl font-bold mb-4">
{paylater.description}
</div>
<table className="relative w-full overflow-x-auto p-2 rounded">
<tbody>
<tr>
<td className="font-bold">Customer</td>
<td>:</td>
<td>
<Link
href={route('customer.edit', {
customer: paylater.customer,
})}
className="hover:underline"
>
{paylater.customer.name}
</Link>
</td>
</tr>
<tr>
<td className="font-bold">Jumlah</td>
<td>:</td>
<td>{paylater.amount}</td>
</tr>
<tr>
<td className="font-bold">Status</td>
<td>:</td>
<td
className={
paylater.status_text.text_color
}
>
{paylater.status_text.text}
</td>
</tr>
<tr>
<td className="font-bold">Tanggal</td>
<td>:</td>
<td>{paylater.format_created_at}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</AuthenticatedLayout>
)
}

@ -0,0 +1,230 @@
import React, { useEffect } from 'react'
import { Head, Link, useForm } from '@inertiajs/react'
import { isEmpty } from 'lodash'
import {
STATUS_APPROVE,
STATUS_REJECT,
STATUS_WAIT_APPROVE,
STATUS_WAIT_UPLOAD,
} from '@/constant'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import FormInput from '@/Components/FormInput'
import Button from '@/Components/Button'
import FormInputNumeric from '@/Components/FormInputNumeric'
import { formatIDR } from '@/utils'
export default function Form(props) {
const { deposit } = props
const { data, setData, post, processing, errors } = useForm({
debit: 0,
is_valid: 0,
reject_reason: '',
})
const handleOnChange = (event) => {
setData(
event.target.name,
event.target.type === 'checkbox'
? event.target.checked
? 1
: 0
: event.target.value
)
}
const showForm =
+deposit.is_valid === STATUS_WAIT_APPROVE ||
+deposit.is_valid === STATUS_WAIT_UPLOAD
const handleSubmit = () => {
post(route('paylater.update', deposit))
}
useEffect(() => {
if (isEmpty(deposit) === false) {
setData({
debit: deposit.debit,
is_valid: deposit.is_valid,
reject_reason: deposit.reject_reason,
})
return
}
}, [deposit])
return (
<AuthenticatedLayout
page={'Pembayaran Hutang'}
action={deposit.description}
parent={route('paylater.index')}
>
<Head title="Pembayaran Hutang" />
<div>
<div className="mx-auto sm:px-6 lg:px-8">
<div className="overflow-hidden p-4 shadow-sm sm:rounded-lg bg-white dark:bg-gray-800 flex flex-col ">
<div className="text-xl font-bold mb-4">
{deposit.description}
</div>
<table className="relative w-full overflow-x-auto p-2 rounded">
<tbody>
<tr>
<td className="font-bold">Customer</td>
<td>:</td>
<td>
<Link
href={route('customer.edit', {
customer: deposit.customer,
})}
className="hover:underline"
>
{deposit.customer.name}
</Link>
</td>
</tr>
<tr>
<td className="font-bold">
Metode Pembayaran
</td>
<td>:</td>
<td>{deposit.payment_channel}</td>
</tr>
{deposit.account !== null && (
<tr>
<td className="font-bold">Bank Akun</td>
<td>:</td>
<td>
{deposit.account.name} (
{deposit.account.bank_name})
</td>
</tr>
)}
{deposit.deposit_location !== null && (
<tr>
<td className="font-bold">
Lokasi Cash / Setor Tunai
</td>
<td>:</td>
<td>{deposit.deposit_location.name}</td>
</tr>
)}
<tr>
<td className="font-bold">Jumlah</td>
<td>:</td>
<td>{deposit.amount}</td>
</tr>
<tr>
<td className="font-bold">Admin Fee</td>
<td>:</td>
<td>Rp {formatIDR(+deposit.admin_fee)}</td>
</tr>
<tr>
<td className="font-bold">Status</td>
<td>:</td>
<td className={deposit.status.text_color}>
{deposit.status.text}
</td>
</tr>
<tr>
<td className="font-bold">
Alasan Penolakan
</td>
<td>:</td>
<td>{deposit.reject_reason}</td>
</tr>
{isEmpty(deposit.editor) === false && (
<tr>
<td className="font-bold">Approver</td>
<td>:</td>
<td>{deposit.editor.name}</td>
</tr>
)}
<tr>
<td className="font-bold">Tanggal</td>
<td>:</td>
<td>{deposit.format_created_at}</td>
</tr>
</tbody>
</table>
{isEmpty(deposit.image_prove_url) === false && (
<div>
<a
href={deposit.image_prove_url}
target="_blank"
>
<img
src={deposit.image_prove_url}
className="w-full object-fill h-96"
loading="lazy"
/>
</a>
</div>
)}
{showForm && (
<>
<div className="my-4">
<FormInputNumeric
type="number"
label="Jumlah Deposit"
name="debit"
onChange={handleOnChange}
value={data.debit}
error={errors.debit}
/>
<div className="mb-1 text-sm">Status</div>
<select
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
onChange={handleOnChange}
value={+data.is_valid}
name="is_valid"
>
<option value="">
{' '}
-- pilih status --{' '}
</option>
<option value={STATUS_APPROVE}>
Approve
</option>
<option value={STATUS_REJECT}>
Reject
</option>
</select>
{errors.status && (
<div className="text-sm text-red-500">
{errors.status}
</div>
)}
{errors.reject_reason && (
<div className="text-sm text-red-500">
{errors.reject_reason}
</div>
)}
</div>
{+data.is_valid === STATUS_REJECT && (
<FormInput
label="Alasan penolakan"
name="reject_reason"
value={data.reject_reason}
onChange={handleOnChange}
error={errors.reject_reason}
/>
)}
<div className="mt-8">
<Button
onClick={handleSubmit}
processing={processing}
>
Simpan
</Button>
</div>
</>
)}
</div>
</div>
</div>
</AuthenticatedLayout>
)
}

@ -0,0 +1,218 @@
import React, { useEffect, useState } from 'react'
import { Head, Link, router } from '@inertiajs/react'
import { usePrevious } from 'react-use'
import { HiEye } from 'react-icons/hi2'
import { formatIDR, hasPermission } from '@/utils'
import { DEPOSIT_STATUSES } from '@/constant'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import Pagination from '@/Components/Pagination'
import SearchInput from '@/Components/SearchInput'
import ThSort from '@/Components/ThSortComponent'
export default function Index(props) {
const {
deposits: { links, data },
auth,
_search,
_sortBy,
_sortRule,
} = props
const [search, setSearch] = useState({
q: _search,
sortBy: _sortBy,
sortRule: _sortRule,
})
const [status, setStatus] = useState('')
const preValue = usePrevious(`${search}${status}`)
const handleChangeSearch = (e) => {
setSearch({
...search,
q: e.target.value,
})
}
const sort = (key, sort = null) => {
if (sort !== null) {
setSearch({
...search,
sortBy: key,
sortRule: sort,
})
return
}
setSearch({
...search,
sortBy: key,
sortRule: search.sortRule == 'asc' ? 'desc' : 'asc',
})
}
const params = { ...search, status: status }
useEffect(() => {
if (preValue) {
router.get(
route(route().current()),
{ ...search, status: status },
{
replace: true,
preserveState: true,
}
)
}
}, [search, status])
const canUpdate = hasPermission(auth, 'update-paylater-repayment')
return (
<AuthenticatedLayout page={'Pembayaran Hutang'} action={''}>
<Head title="Pembayaran Hutang" />
<div>
<div className="mx-auto sm:px-6 lg:px-8 ">
<div className="p-6 overflow-hidden shadow-sm sm:rounded-lg bg-gray-200 dark:bg-gray-800 space-y-4">
<div className="flex flex-col lg:flex-row gap-1 justify-between">
<div className="flex flex-row space-x-2">
<SearchInput
onChange={handleChangeSearch}
value={search.q}
/>
</div>
<div className="flex flex-row space-x-2">
<select
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
onChange={(e) => setStatus(e.target.value)}
>
<option value="">Semua Status</option>
{DEPOSIT_STATUSES.map((status) => (
<option
value={status.value}
key={status.value}
>
{status.key}
</option>
))}
</select>
</div>
</div>
<div className="overflow-auto">
<div>
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-400 mb-4">
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<ThSort
sort={sort}
label={'description'}
search={search}
>
#
</ThSort>
<ThSort
sort={sort}
label={'customer_id'}
search={search}
>
Customer
</ThSort>
<ThSort
sort={sort}
label={'debit'}
search={search}
>
Deposit
</ThSort>
<ThSort
sort={sort}
label={'updated_at'}
search={search}
>
Tanggal
</ThSort>
<ThSort
sort={sort}
label={'is_valid'}
search={search}
>
Status
</ThSort>
<ThSort
sort={sort}
label={'updated_by'}
search={search}
>
Approver
</ThSort>
<th
scope="col"
className="py-3 px-6"
/>
</tr>
</thead>
<tbody>
{data.map((deposit) => (
<tr
className="bg-white border-b dark:bg-gray-800 dark:border-gray-700"
key={deposit.id}
>
<td
scope="row"
className="py-4 px-6 font-medium text-gray-900 whitespace-nowrap dark:text-white"
>
{deposit.description}
</td>
<td className="py-4 px-6">
<Link
className="hover:underline"
href={route(
'mitra.edit',
deposit.customer.id
)}
>
{deposit.customer.name}
</Link>
</td>
<td className="py-4 px-6">
{deposit.amount}
</td>
<td className="py-4 px-6">
{deposit.format_created_at}
</td>
<td
className={`py-4 px-6 ${deposit.status.text_color}`}
>
{deposit.status.text}
</td>
<td className="py-4 px-6">
{deposit.editor?.name}
</td>
<td className="py-4 px-6 flex justify-center">
{canUpdate && (
<Link
href={route(
'paylater.edit',
deposit
)}
className="flex space-x-1 items-center hover:underline"
>
<HiEye />
<div>Lihat</div>
</Link>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="w-full flex items-center justify-center">
<Pagination links={links} params={params} />
</div>
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
)
}

@ -41,7 +41,11 @@ export default function Detail(props) {
const { sale } = props
return (
<AuthenticatedLayout page={`Sale`} action={`${sale.code}`}>
<AuthenticatedLayout
page={`Sale`}
action={`${sale.code}`}
parent={route('sale.index')}
>
<Head title={`${sale.code}`} />
<div>
@ -69,7 +73,7 @@ export default function Detail(props) {
Metode Pembayaran
</td>
<td>:</td>
<td>{sale.payed_with}</td>
<td>{sale.payment_with}</td>
</tr>
<tr>
<td className="font-bold">Total</td>

@ -139,6 +139,7 @@ Route::middleware(['http_secure_aware', 'inertia.admin'])
// mitra history
Route::get('/mitra/{customer}/deposit', [CustomerHistoryController::class, 'deposit'])->name('mitra.history.deposit');
Route::get('/mitra/{customer}/sale', [CustomerHistoryController::class, 'sale'])->name('mitra.history.sale');
Route::get('/mitra/{customer}/paylater', [CustomerHistoryController::class, 'paylater'])->name('mitra.history.paylater');
Route::get('/mitra/{customer}/paylater_deadline', [CustomerHistoryController::class, 'paylater_deadline'])->name('mitra.history.paylater_deadline');
Route::get('/mitra/{customer}/paylater_limit', [CustomerHistoryController::class, 'paylater_limit'])->name('mitra.history.paylater_limit');
@ -168,6 +169,12 @@ Route::middleware(['http_secure_aware', 'inertia.admin'])
Route::get('/deposites/{deposit}', [DepositController::class, 'edit'])->name('deposit.edit');
Route::post('/deposites/{deposit}', [DepositController::class, 'update'])->name('deposit.update');
// repayment
Route::get('/paylater', [PaylaterController::class, 'index'])->name('paylater.index');
Route::get('/paylater/{paylater}', [PaylaterController::class, 'show'])->name('paylater.show');
Route::get('/paylater/{deposit}/edit', [PaylaterController::class, 'edit'])->name('paylater.edit');
Route::post('/paylater/{deposit}', [PaylaterController::class, 'update'])->name('paylater.update');
// poin rewared
Route::get('/bonus-poin', [PoinRewardController::class, 'index'])->name('poin-reward.index');
Route::post('/bonus-poin', [PoinRewardController::class, 'store'])->name('poin-reward.store');

@ -53,7 +53,7 @@ Route::middleware(['http_secure_aware', 'guard_should_customer', 'inertia.custom
Route::get('paylater/repay', [PaylaterController::class, 'create'])->name('customer.paylater.repay');
Route::post('paylater/repay', [PaylaterController::class, 'store']);
// deposite
// deposit
Route::get('trx/deposit', [DepositController::class, 'index'])->name('transactions.deposit.index');
Route::get('trx/deposit/topup', [DepositController::class, 'create'])->name('transactions.deposit.topup');
Route::post('trx/deposit/topup', [DepositController::class, 'store']);

Loading…
Cancel
Save