Compare commits
No commits in common. "4183ec1e36969602ca3c9f88dbde721b375fd1e8" and "783a0f6dda088f7ecf44e2168ee2149beedc3a75" have entirely different histories.
4183ec1e36
...
783a0f6dda
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Admin\Finances;
|
namespace App\Http\Controllers\Admin\Finances;
|
||||||
|
|
||||||
use App\Enums\StudentStatus;
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\Admin\Finances\TuitionInvoiceRequest;
|
use App\Http\Requests\Admin\Finances\TuitionInvoiceRequest;
|
||||||
use App\Http\Requests\PaginatedRequest;
|
use App\Http\Requests\PaginatedRequest;
|
||||||
@ -29,7 +28,7 @@ public function index(PaginatedRequest $request): Response
|
|||||||
...$request->validatedWithDefaults(),
|
...$request->validatedWithDefaults(),
|
||||||
academicTermId: $request->validated('academic_term_id'),
|
academicTermId: $request->validated('academic_term_id'),
|
||||||
),
|
),
|
||||||
'students' => $this->studentService->getAllForSelect(status: StudentStatus::Active->value),
|
'students' => $this->studentService->getAllForSelect(),
|
||||||
'academicTerms' => $this->academicTermService->getAllForSelect(),
|
'academicTerms' => $this->academicTermService->getAllForSelect(),
|
||||||
'filters' => $request->only(['academic_term_id']),
|
'filters' => $request->only(['academic_term_id']),
|
||||||
]);
|
]);
|
||||||
@ -37,7 +36,7 @@ public function index(PaginatedRequest $request): Response
|
|||||||
|
|
||||||
public function store(TuitionInvoiceRequest $request): RedirectResponse
|
public function store(TuitionInvoiceRequest $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$this->service->createMany($request->validated('student_ids'), $request->validated());
|
$this->service->create($request->validated());
|
||||||
|
|
||||||
Inertia::flash('toast', ['type' => 'success', 'message' => 'Tagihan berhasil ditambahkan.']);
|
Inertia::flash('toast', ['type' => 'success', 'message' => 'Tagihan berhasil ditambahkan.']);
|
||||||
|
|
||||||
|
|||||||
@ -14,19 +14,11 @@ public function authorize(): bool
|
|||||||
|
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
$rules = [
|
return [
|
||||||
|
'student_id' => ['required', 'integer', Rule::exists('students', 'id')],
|
||||||
'academic_term_id' => ['required', 'integer', Rule::exists('academic_terms', 'id')],
|
'academic_term_id' => ['required', 'integer', Rule::exists('academic_terms', 'id')],
|
||||||
'amount_due' => ['required', 'numeric', 'min:0'],
|
'amount_due' => ['required', 'numeric', 'min:0'],
|
||||||
'due_date' => ['required', 'date'],
|
'due_date' => ['nullable', 'date'],
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($this->isMethod('post')) {
|
|
||||||
$rules['student_ids'] = ['required', 'array', 'min:1'];
|
|
||||||
$rules['student_ids.*'] = ['integer', Rule::exists('students', 'id')];
|
|
||||||
} else {
|
|
||||||
$rules['student_id'] = ['required', 'integer', Rule::exists('students', 'id')];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $rules;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
use App\Models\TuitionInvoice;
|
use App\Models\TuitionInvoice;
|
||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
class TuitionInvoiceService
|
class TuitionInvoiceService
|
||||||
{
|
{
|
||||||
@ -22,18 +21,14 @@ public function paginated(int $perPage = 25, string $search = '', ?int $academic
|
|||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createMany(array $studentIds, array $data): void
|
public function create(array $data): TuitionInvoice
|
||||||
{
|
{
|
||||||
DB::transaction(function () use ($studentIds, $data) {
|
return TuitionInvoice::create([
|
||||||
foreach ($studentIds as $studentId) {
|
'student_id' => $data['student_id'],
|
||||||
TuitionInvoice::create([
|
'academic_term_id' => $data['academic_term_id'],
|
||||||
'student_id' => $studentId,
|
'amount_due' => $data['amount_due'],
|
||||||
'academic_term_id' => $data['academic_term_id'],
|
'due_date' => $data['due_date'] ?? null,
|
||||||
'amount_due' => $data['amount_due'],
|
]);
|
||||||
'due_date' => $data['due_date'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(TuitionInvoice $invoice, array $data): TuitionInvoice
|
public function update(TuitionInvoice $invoice, array $data): TuitionInvoice
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
class StudentService
|
class StudentService
|
||||||
{
|
{
|
||||||
public function getAllForSelect(?string $status = null): Collection
|
public function getAllForSelect(): Collection
|
||||||
{
|
{
|
||||||
return Student::select(['id', 'user_id', 'student_number', 'department_id'])
|
return Student::select(['id', 'user_id', 'student_number', 'department_id'])
|
||||||
->with([
|
->with([
|
||||||
@ -20,7 +20,6 @@ public function getAllForSelect(?string $status = null): Collection
|
|||||||
'user.profile:id,user_id,full_name',
|
'user.profile:id,user_id,full_name',
|
||||||
'department:id,name',
|
'department:id,name',
|
||||||
])
|
])
|
||||||
->when($status, fn ($q) => $q->where('status', $status))
|
|
||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,18 +13,6 @@ import InputError from '@/components/input-error';
|
|||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { RupiahInput } from '@/components/rupiah-input';
|
import { RupiahInput } from '@/components/rupiah-input';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
|
||||||
Combobox,
|
|
||||||
ComboboxChip,
|
|
||||||
ComboboxChips,
|
|
||||||
ComboboxChipsInput,
|
|
||||||
ComboboxContent,
|
|
||||||
ComboboxEmpty,
|
|
||||||
ComboboxInput,
|
|
||||||
ComboboxItem,
|
|
||||||
ComboboxList,
|
|
||||||
useComboboxAnchor,
|
|
||||||
} from '@/components/ui/combobox';
|
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
@ -221,15 +209,6 @@ function CreateForm({
|
|||||||
academicTerms: AcademicTermOption[];
|
academicTerms: AcademicTermOption[];
|
||||||
}) {
|
}) {
|
||||||
const [dueDate, setDueDate] = useState<Date | undefined>();
|
const [dueDate, setDueDate] = useState<Date | undefined>();
|
||||||
const [selectedStudents, setSelectedStudents] = useState<
|
|
||||||
TuitionInvoiceStudent[]
|
|
||||||
>([]);
|
|
||||||
const studentAnchor = useComboboxAnchor();
|
|
||||||
|
|
||||||
function reset() {
|
|
||||||
setDueDate(undefined);
|
|
||||||
setSelectedStudents([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormDialog
|
<FormDialog
|
||||||
@ -240,84 +219,33 @@ function CreateForm({
|
|||||||
resetOnSuccess
|
resetOnSuccess
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
reset();
|
setDueDate(undefined);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ errors }) => (
|
{({ errors }) => (
|
||||||
<div className="grid gap-4">
|
<div className="grid gap-4">
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<div className="flex items-center justify-between">
|
<Label>
|
||||||
<Label>
|
Mahasiswa{' '}
|
||||||
Mahasiswa{' '}
|
<span className="text-destructive">*</span>
|
||||||
<span className="text-destructive">*</span>
|
</Label>
|
||||||
</Label>
|
<input type="hidden" name="student_id" />
|
||||||
<button
|
<Select name="student_id">
|
||||||
type="button"
|
<SelectTrigger className="w-full">
|
||||||
className="text-xs text-primary underline underline-offset-4 hover:text-primary/80"
|
<SelectValue placeholder="Pilih mahasiswa" />
|
||||||
onClick={() =>
|
</SelectTrigger>
|
||||||
setSelectedStudents(
|
<SelectContent>
|
||||||
selectedStudents.length ===
|
{students.map((student) => (
|
||||||
students.length
|
<SelectItem
|
||||||
? []
|
|
||||||
: students,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{selectedStudents.length === students.length
|
|
||||||
? 'Batalkan Semua'
|
|
||||||
: 'Pilih Semua'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{selectedStudents.map((student) => (
|
|
||||||
<input
|
|
||||||
key={student.id}
|
|
||||||
type="hidden"
|
|
||||||
name="student_ids[]"
|
|
||||||
value={student.id}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
<Combobox
|
|
||||||
items={students}
|
|
||||||
multiple
|
|
||||||
value={selectedStudents}
|
|
||||||
onValueChange={setSelectedStudents}
|
|
||||||
itemToStringLabel={studentLabel}
|
|
||||||
isItemEqualToValue={(a, b) => a.id === b.id}
|
|
||||||
>
|
|
||||||
<ComboboxChips ref={studentAnchor}>
|
|
||||||
{selectedStudents.map((student) => (
|
|
||||||
<ComboboxChip
|
|
||||||
key={student.id}
|
key={student.id}
|
||||||
aria-label={studentLabel(student)}
|
value={String(student.id)}
|
||||||
>
|
>
|
||||||
{studentLabel(student)}
|
{studentLabel(student)}
|
||||||
</ComboboxChip>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<ComboboxChipsInput
|
</SelectContent>
|
||||||
placeholder={
|
</Select>
|
||||||
selectedStudents.length === 0
|
<InputError message={errors.student_id} />
|
||||||
? 'Pilih mahasiswa (aktif)'
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ComboboxChips>
|
|
||||||
<ComboboxContent anchor={studentAnchor}>
|
|
||||||
<ComboboxEmpty>
|
|
||||||
Mahasiswa tidak ditemukan.
|
|
||||||
</ComboboxEmpty>
|
|
||||||
<ComboboxList>
|
|
||||||
{students.map((student) => (
|
|
||||||
<ComboboxItem
|
|
||||||
key={student.id}
|
|
||||||
value={student}
|
|
||||||
>
|
|
||||||
{studentLabel(student)}
|
|
||||||
</ComboboxItem>
|
|
||||||
))}
|
|
||||||
</ComboboxList>
|
|
||||||
</ComboboxContent>
|
|
||||||
</Combobox>
|
|
||||||
<InputError message={errors.student_ids} />
|
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>
|
<Label>
|
||||||
@ -356,10 +284,7 @@ function CreateForm({
|
|||||||
<InputError message={errors.amount_due} />
|
<InputError message={errors.amount_due} />
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>
|
<Label>Jatuh Tempo</Label>
|
||||||
Jatuh Tempo{' '}
|
|
||||||
<span className="text-destructive">*</span>
|
|
||||||
</Label>
|
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
name="due_date"
|
name="due_date"
|
||||||
@ -394,9 +319,6 @@ function EditForm({
|
|||||||
const [dueDate, setDueDate] = useState<Date | undefined>(
|
const [dueDate, setDueDate] = useState<Date | undefined>(
|
||||||
editing?.due_date ? new Date(editing.due_date) : undefined,
|
editing?.due_date ? new Date(editing.due_date) : undefined,
|
||||||
);
|
);
|
||||||
const [student, setStudent] = useState<TuitionInvoiceStudent | null>(
|
|
||||||
students.find((s) => s.id === editing?.student_id) ?? null,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormDialog
|
<FormDialog
|
||||||
@ -415,38 +337,24 @@ function EditForm({
|
|||||||
Mahasiswa{' '}
|
Mahasiswa{' '}
|
||||||
<span className="text-destructive">*</span>
|
<span className="text-destructive">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<input
|
<Select
|
||||||
type="hidden"
|
|
||||||
name="student_id"
|
name="student_id"
|
||||||
value={student?.id ?? ''}
|
defaultValue={String(editing.student_id)}
|
||||||
/>
|
|
||||||
<Combobox
|
|
||||||
items={students}
|
|
||||||
value={student}
|
|
||||||
onValueChange={setStudent}
|
|
||||||
itemToStringLabel={studentLabel}
|
|
||||||
isItemEqualToValue={(a, b) => a.id === b.id}
|
|
||||||
>
|
>
|
||||||
<ComboboxInput
|
<SelectTrigger className="w-full">
|
||||||
placeholder="Pilih mahasiswa (aktif)"
|
<SelectValue placeholder="Pilih mahasiswa" />
|
||||||
className="w-full"
|
</SelectTrigger>
|
||||||
/>
|
<SelectContent>
|
||||||
<ComboboxContent>
|
{students.map((student) => (
|
||||||
<ComboboxEmpty>
|
<SelectItem
|
||||||
Mahasiswa tidak ditemukan.
|
key={student.id}
|
||||||
</ComboboxEmpty>
|
value={String(student.id)}
|
||||||
<ComboboxList>
|
>
|
||||||
{students.map((option) => (
|
{studentLabel(student)}
|
||||||
<ComboboxItem
|
</SelectItem>
|
||||||
key={option.id}
|
))}
|
||||||
value={option}
|
</SelectContent>
|
||||||
>
|
</Select>
|
||||||
{studentLabel(option)}
|
|
||||||
</ComboboxItem>
|
|
||||||
))}
|
|
||||||
</ComboboxList>
|
|
||||||
</ComboboxContent>
|
|
||||||
</Combobox>
|
|
||||||
<InputError message={errors.student_id} />
|
<InputError message={errors.student_id} />
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
@ -488,10 +396,7 @@ function EditForm({
|
|||||||
<InputError message={errors.amount_due} />
|
<InputError message={errors.amount_due} />
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>
|
<Label>Jatuh Tempo</Label>
|
||||||
Jatuh Tempo{' '}
|
|
||||||
<span className="text-destructive">*</span>
|
|
||||||
</Label>
|
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
name="due_date"
|
name="due_date"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user