diff --git a/app/Http/Controllers/BranchController.php b/app/Http/Controllers/BranchController.php index 12912ac..5447a4c 100644 --- a/app/Http/Controllers/BranchController.php +++ b/app/Http/Controllers/BranchController.php @@ -111,19 +111,9 @@ public function update(Request $request, $id) ]); if ($validated['status'] === 'Inactive') { - $staffAction = $request->input('staff_action'); - if ($staffAction === 'move') { - $targetBranchId = $request->input('move_to_branch_id'); - if ($targetBranchId) { - \App\Models\Staff::where('branch_id', $id) - ->where('status', 'Active') - ->update(['branch_id' => $targetBranchId]); - } - } elseif ($staffAction === 'inactivate') { - \App\Models\Staff::where('branch_id', $id) - ->where('status', 'Active') - ->update(['status' => 'Inactive']); - } + \App\Models\Staff::where('branch_id', $id) + ->where('status', 'Active') + ->update(['status' => 'Inactive']); } // Process new documents if provided diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index b7a7a81..766f296 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -26,7 +26,7 @@ public function getProfitReport(Request $request) $perPage = $request->query('per_page', 10); // Base Query from Account table (Ledger) - $query = Account::query()->with('accountable'); + $query = Account::query()->with(['accountable', 'branch']); if ($branchId) { $query->where('branch_id', $branchId); @@ -41,6 +41,19 @@ public function getProfitReport(Request $request) // Stats from the same filtered query $totalCredits = (clone $query)->sum('credit'); $totalDebits = (clone $query)->sum('debit'); + + // Today's Stats (always based on today's date, but respecting branch filter) + $todayIncome = Account::where('date', Carbon::today()->toDateString()) + ->when($branchId, fn($q) => $q->where('branch_id', $branchId)) + ->sum('credit'); + + // Monthly Stats (always based on current month, but respecting branch filter) + $monthlyExpense = Account::whereBetween('date', [ + Carbon::now()->startOfMonth()->toDateString(), + Carbon::now()->endOfMonth()->toDateString() + ]) + ->when($branchId, fn($q) => $q->where('branch_id', $branchId)) + ->sum('debit'); // Fetch Paginated Transactions from the same filtered query $allTransactions = $query->where(function($q) { @@ -73,7 +86,7 @@ public function getProfitReport(Request $request) 'category' => $a->type, 'description' => $a->description, 'amount' => $a->credit > 0 ? $a->credit : $a->debit, - 'branch' => 'N/A', // Branch name if needed can be added via relation + 'branch' => $a->branch->name ?? 'N/A', 'is_adjusted' => $isAdjusted, 'original_amount' => $originalAmount, 'remarks' => $remarks @@ -95,13 +108,12 @@ public function getProfitReport(Request $request) $monthStart = Carbon::now()->subMonths($i)->startOfMonth(); $monthEnd = Carbon::now()->subMonths($i)->endOfMonth(); - $monthIncome = Account::where('branch_id', $branchId ?: '!=', 0) + $monthIncome = Account::query() ->when($branchId, fn($q) => $q->where('branch_id', $branchId)) ->whereBetween('date', [$monthStart->toDateString(), $monthEnd->toDateString()]) ->sum('credit'); - // For trend, we can also use Account table for expenses - $monthExpense = Account::where('branch_id', $branchId ?: '!=', 0) + $monthExpense = Account::query() ->when($branchId, fn($q) => $q->where('branch_id', $branchId)) ->whereBetween('date', [$monthStart->toDateString(), $monthEnd->toDateString()]) ->sum('debit'); @@ -118,6 +130,8 @@ public function getProfitReport(Request $request) return response()->json([ 'total_income' => $totalCredits, 'total_expense' => $totalDebits, + 'today_income' => $todayIncome, + 'monthly_expense' => $monthlyExpense, 'net_profit' => $totalCredits - $totalDebits, 'low_stock_count' => $lowStockCount, 'transactions' => $paginatedTransactions, diff --git a/app/Http/Controllers/StaffController.php b/app/Http/Controllers/StaffController.php index 296dee8..4f68dc7 100644 --- a/app/Http/Controllers/StaffController.php +++ b/app/Http/Controllers/StaffController.php @@ -380,13 +380,36 @@ public function getSettlementDetails(Request $request, $id) if ($user && $user->isReceptionist() && $staff->branch_id != $user->branch_id) { return response()->json(['message' => 'Unauthorized'], 403); } - $targetMonthKey = $request->query('month'); + $targetMonthKey = $request->input('month') ?? $request->query('month') ?? $request->input('settlement_month'); $branch = $staff->branch; + // Find the next unpaid month logically for chronological check + $lastPayment = \App\Models\StaffPayment::where('staff_id', $id) + ->where('payment_type', 'Salary Settlement') + ->orderBy('settlement_month', 'desc') + ->first(); + + $joiningDate = Carbon::parse($staff->joining_date); + if ($lastPayment) { + $logicalNextMonth = Carbon::parse($lastPayment->settlement_month . '-01')->addMonth(); + } else { + $logicalNextMonth = $joiningDate->copy()->startOfMonth(); + } + if ($targetMonthKey) { - $nextSettlementMonth = Carbon::parse($targetMonthKey . '-01'); + $requestedMonth = Carbon::parse($targetMonthKey . '-01'); - // Verify if already paid + // 1. Chronological Check: Cannot settle a future month if a previous one is unpaid + if ($requestedMonth->startOfMonth()->greaterThan($logicalNextMonth->startOfMonth())) { + return response()->json([ + 'can_settle' => false, + 'message' => "Please settle " . $logicalNextMonth->format('F Y') . " before settling " . $requestedMonth->format('F Y') . ".", + ]); + } + + $nextSettlementMonth = $requestedMonth; + + // 2. Verify if already paid $existing = \App\Models\StaffPayment::where('staff_id', $id) ->where('payment_type', 'Salary Settlement') ->where('settlement_month', $targetMonthKey) @@ -399,31 +422,15 @@ public function getSettlementDetails(Request $request, $id) ]); } } else { - // Find the next unpaid month logically - $lastPayment = \App\Models\StaffPayment::where('staff_id', $id) - ->where('payment_type', 'Salary Settlement') - ->orderBy('settlement_month', 'desc') - ->first(); + $nextSettlementMonth = $logicalNextMonth; + } - $joiningDate = Carbon::parse($staff->joining_date); - $currentMonth = Carbon::now()->startOfMonth(); - - if ($lastPayment) { - $nextSettlementMonth = Carbon::parse($lastPayment->settlement_month . '-01')->addMonth(); - } else { - $nextSettlementMonth = $joiningDate->copy()->startOfMonth(); - } - - // Only allow settlement if the generation date for the month has arrived - $genDay = $branch->salary_generation_day ?? 2; - $generationDate = $nextSettlementMonth->copy()->addMonth()->day(min($genDay, $nextSettlementMonth->copy()->addMonth()->daysInMonth)); - - if (Carbon::now()->lessThan($generationDate)) { - return response()->json([ - 'can_settle' => false, - 'message' => "Salary for {$nextSettlementMonth->format('F Y')} will be available for settlement on " . $generationDate->format('jS F Y'), - ]); - } + // 3. Status Check: Only allow settlement if the month has completed + if (Carbon::now()->lessThan($nextSettlementMonth->copy()->addMonth()->startOfMonth())) { + return response()->json([ + 'can_settle' => false, + 'message' => "Salary for {$nextSettlementMonth->format('F Y')} is still in progress.", + ]); } // Calculate Cycle and Pro-rata @@ -657,7 +664,30 @@ public function getAdvanceHistory($id) } $history = SalaryAdvanceDeduction::where('staff_id', $id) ->orderBy('created_at', 'desc') - ->get(); + ->get() + ->map(function($h) { + $schedule = []; + $months = $h->total_months ?: 1; + $paid = $h->paid_amount ?: 0; + $installment = $h->monthly_deduction ?: ($h->advance_amount / $months); + + // Assuming repayment starts from the month following the advance + $startDate = Carbon::parse($h->created_at)->startOfMonth(); + + for ($i = 0; $i < $months; $i++) { + $monthDate = $startDate->copy()->addMonths($i + 1); + $isPaid = $paid >= (($i + 1) * $installment - 0.01); + + $schedule[] = [ + 'month' => $monthDate->format('F Y'), + 'amount' => round($installment, 2), + 'status' => $isPaid ? 'Paid' : 'Pending' + ]; + } + + $h->installment_schedule = $schedule; + return $h; + }); return response()->json($history); } @@ -687,16 +717,16 @@ public function getAllPendingSalaries(Request $request) $staffMonths = []; - // Loop while generation date for the month has passed - while (true) { + $currentMonth = Carbon::now()->startOfMonth(); + + // Loop while the month has completed + while ($tempMonth->lessThan($currentMonth)) { $monthKey = $tempMonth->format('Y-m'); - // Calculate generation date for this month - $genDay = $branch->salary_generation_day ?? 2; - $generationDate = $tempMonth->copy()->addMonth()->day(min($genDay, $tempMonth->copy()->addMonth()->daysInMonth)); - - if (Carbon::now()->lessThan($generationDate)) { - break; + // Safety break for extremely old joining dates (limit to 10 years or similar) + if ($tempMonth->diffInYears(Carbon::now()) > 10) { + $tempMonth->addMonth(); + continue; } if (!isset($settlements[$monthKey])) { diff --git a/config/cors.php b/config/cors.php new file mode 100644 index 0000000..4bebf43 --- /dev/null +++ b/config/cors.php @@ -0,0 +1,34 @@ + ['api/*', 'sanctum/csrf-cookie', 'login', 'logout', 'receptionist/login'], + + 'allowed_methods' => ['*'], + + 'allowed_origins' => ['*'], + + 'allowed_origins_patterns' => [], + + 'allowed_headers' => ['*'], + + 'exposed_headers' => [], + + 'max_age' => 0, + + 'supports_credentials' => false, + +]; diff --git a/mobile_api_documentation.txt b/mobile_api_documentation.txt index a06a418..4d1bf69 100644 --- a/mobile_api_documentation.txt +++ b/mobile_api_documentation.txt @@ -1,133 +1,134 @@ -# Flutter Mobile App API Documentation (Owner Role) +# Flutter Mobile App API Documentation -Base URL: http://127.0.0.1:8000/api -Note: For testing on a real physical mobile device, replace 127.0.0.1 with your computer's local IP address (e.g., 192.168.1.5). +## Connection Details +- **Base URL**: `http://127.0.0.1:8002/api` +- **Mobile Testing**: For testing on a physical device, replace `127.0.0.1` with your computer's local IP (e.g., `http://192.168.1.5:8002/api`). +- **Headers**: + - `Accept: application/json` + - `Content-Type: application/json` + - `X-Requested-With: XMLHttpRequest` -## Authentication -- POST /login - - Params: email, password - - Returns: CSRF cookie and session (for web-based auth) or Auth token (if configured). +--- -- POST /logout - - Action: End session +## 1. Authentication Module +Manage user sessions and profiles. -- GET /profile - - Returns: Current logged-in user details and role. +- **POST /login** + - Params: `email`, `password` + - Response: `{ "user": { ... }, "redirect": "/owner/dashboard" }` +- **POST /receptionist/login** + - Params: `email`, `password` + - Response: `{ "user": { ... }, "redirect": "/receptionist/dashboard" }` +- **GET /api/profile** + - Query: `context` (owner/receptionist) + - Returns: Current user details and role. +- **POST /logout** + - Action: Terminates session. -## Branch Management -- GET /branches - - List all branches. -- POST /branches - - Create new branch (Multipart/form-data for documents). -- GET /branches/{id} - - View specific branch details. -- PUT /branches/{id} - - Update branch details. -- DELETE /branches/{id} - - Delete a branch. +--- -- GET /branches/{branch}/receptionist - - View receptionist for a branch. -- POST /branches/{branch}/receptionist - - Create/Update receptionist credentials. -- DELETE /branches/{branch}/receptionist - - Remove receptionist. +## 2. Branch Management +Manage business locations and their documents. -## Staff Management -- GET /staff - - List all staff members. -- POST /staff - - Add new staff (Multipart/form-data for documents). -- GET /staff/{id} - - View staff profile. -- PUT /staff/{id} - - Update staff profile. -- DELETE /staff/{id} - - Delete staff member. +- **GET /api/branches** + - Query: `status` (Active/Inactive) + - Returns: List of all branches with document counts and revenue. +- **GET /api/branches/{id}** + - Returns: Detailed branch info and documents. +- **POST /api/branches** + - Type: `multipart/form-data` + - Params: `name`, `location`, `manager_name`, `operational_start_date`, `payroll_from_day`, `payroll_to_day`, `salary_generation_day` + - Files: `docs[0][file]`, `docs[0][name]`, `docs[0][expiry_date]`... +- **PUT /api/branches/{id}** + - Params: Same as POST, plus `status`. +- **DELETE /api/branches/{id}** + - Note: Only deletable if not used in staff/inventory/accounts. +- **GET /api/branches/{id}/active-staff** + - Returns: List of active staff in that branch. -- GET /staff/pending-salaries - - List all pending salaries across branches. -- POST /staff/bulk-settle - - Params: staff_ids[] - - Settle multiple salaries at once. -- GET /staff/{id}/payments - - Salary payment history. -- GET /staff/{id}/payroll-status - - Current month's payroll calculation. -- POST /staff/{id}/settle - - Settle individual salary for a month. -- GET /staff/{id}/advance-history - - List of advance payments and deductions. +--- -## Investor & ROI Management -- GET /investors - - List all investors. -- POST /investors - - Add new investor (Multipart/form-data for documents). -- GET /investors/{id} - - View investor details. -- PUT /investors/{id} - - Update investor. -- DELETE /investors/{id} - - Delete investor. +## 3. Staff & Payroll Module +Employee management and salary settlements. -- GET /investors/pending-roi - - List all pending ROI settlements. -- GET /investors/{id}/roi-status - - Monthly ROI status breakdown (Base ROI, Carry Over, Paid, Net Due). -- POST /investors/{id}/settle-roi - - Params: payout_month, amount, payout_date, payment_method, remarks. - - Settle a month's ROI. +- **GET /api/staff** + - Query: `branch_id` + - Returns: Complete staff list with documents. +- **POST /api/staff** + - Type: `multipart/form-data` + - Params: `full_name`, `email`, `phone`, `role`, `branch_id`, `joining_date`, `status`, `salary_type`, `salary_amount`. + - Optional: `advance_enabled`, `advance_amount`, `commission_enabled`, `documents[]`. +- **GET /api/staff/{id}/payroll-status** + - Returns: Month-by-month payment history and unpaid months. +- **GET /api/staff/{id}/settlement** + - Returns: Pro-rated salary calculation, commissions, and advance deductions due. +- **POST /api/staff/{id}/settle** + - Params: `month` (Y-m), `remarks` + - Action: Processes salary payment and records expense. +- **GET /api/staff/pending-salaries** + - Returns: List of all staff with pending settlements across branches. +- **POST /api/staff/bulk-settle** + - Params: `settlements` (array of staff_id/month_key), `remarks`. -## Financials & Expenses -- GET /accounts - - General ledger of all credit/debit transactions. -- GET /expenses - - List of all business expenses. -- POST /expenses - - Params: date, branch_id, expense_category_id, expense_type (Account/Petty Cash), amount, remarks. - - Record a new expense. -- GET /expense-categories - - List of master expense categories. +--- -## Inventory Management -- GET /inventory/products - - List all products and stock levels. -- POST /inventory/products - - Add new product with image. -- POST /inventory/products/{id}/adjust - - Adjuts stock (Add/Remove) with remarks. -- GET /inventory/products/{id}/history - - Stock movement history for a specific product. -- GET /inventory/sales - - List of all POS sales. -- POST /inventory/sales - - Record a new product sale. -- GET /inventory/movements - - Global stock movement log. +## 4. Investor & ROI Module +Manage investments and monthly payouts. -## Collections -- GET /collections - - List all daily collections. -- POST /collections - - Record new collection. -- GET /collections/{id} - - View collection details. +- **GET /api/investors** + - Returns: List of all investors and their linked branches. +- **POST /api/investors** + - Params: `name`, `investment_date`, `investment_amount`, `roi_type` (Percentage/Fixed Amount), `roi_value`, `roi_period` (Monthly/Quarterly/Yearly). +- **GET /api/investors/{id}/roi-status** + - Returns: Breakdown of ROI due, paid, and carry-over for each period. +- **POST /api/investors/{id}/settle-roi** + - Params: `payout_month`, `amount`, `payout_date`, `payment_method`, `remarks`. -## Reports -- GET /reports/profit - - Profit & Loss report (Income vs Expenses). -- GET /reports/expiry-reminders - - Documents (Trade license, staff IDs, etc.) expiring soon. -- GET /reports/investments - - Summary of total investments and ROI distributed. +--- -## Master Settings -- GET /masters/{type} - - Types: expense_categories, product_categories, payment_methods, etc. -- POST /masters/{type} - - Add master entry. -- PUT /masters/{type}/{id} - - Edit master entry. -- DELETE /masters/{type}/{id} - - Delete master entry. +## 5. Inventory & POS Module +Product management and sales tracking. + +- **GET /api/inventory/products** + - Query: `branch_id`, `status` (In Stock/Low Stock/Out of Stock). +- **POST /api/inventory/products** + - Params: `name`, `sku`, `product_category_id`, `branch_id`, `cost_price`, `selling_price`, `current_stock`, `reorder_level`. +- **POST /api/inventory/products/{id}/adjust** + - Params: `adjustment_qty` (+/-), `reason`, `adjustment_date`. +- **POST /api/inventory/sales** + - Params: `branch_id`, `payment_method`, `items` (array of product_id/quantity/unit_price). + - Action: Deducts stock and records revenue. + +--- + +## 6. Collections & Expenses +Financial tracking. + +- **GET /api/collections** + - Query: `start_date`, `end_date`, `branch_id`. +- **POST /api/collections** + - Params: `date`, `branch_id`, `collection_type_id`, `amount`, `payment_method`, `items[]`. +- **POST /api/expenses** + - Params: `date`, `branch_id`, `expense_category_id`, `expense_type` (Account/Petty Cash), `amount`, `remarks`. + +--- + +## 7. Reports +Data analysis and reminders. + +- **GET /api/reports/profit** + - Returns: Total income, total expense, net profit, and 6-month trend. +- **GET /api/reports/expiry-reminders** + - Returns: Document expiry alerts for both Staff and Branches. +- **GET /api/reports/investments** + - Returns: Summary of total investments and total ROI returned. + +--- + +## 8. Master Settings +Manage dropdown options. + +- **GET /api/masters/{type}** + - Types: `collection`, `expense`, `product`, `payment_method`, `staff_role`. +- **POST /api/masters/{type}** + - Params: `name`, `status`. diff --git a/resources/js/Pages/Owner/Branches/Components/EditBranchModal.jsx b/resources/js/Pages/Owner/Branches/Components/EditBranchModal.jsx index 96837ca..12ec938 100644 --- a/resources/js/Pages/Owner/Branches/Components/EditBranchModal.jsx +++ b/resources/js/Pages/Owner/Branches/Components/EditBranchModal.jsx @@ -16,9 +16,6 @@ export default function EditBranchModal({ isOpen, onClose, onRefresh, branch }) const [newDocs, setNewDocs] = useState([]); const [activeStaff, setActiveStaff] = useState([]); - const [branches, setBranches] = useState([]); // All branches for "Move to" option - const [staffAction, setStaffAction] = useState('move'); // 'move' or 'inactivate' - const [moveToBranchId, setMoveToBranchId] = useState(''); const [loadingStaff, setLoadingStaff] = useState(false); useEffect(() => { @@ -35,8 +32,6 @@ export default function EditBranchModal({ isOpen, onClose, onRefresh, branch }) }); setNewDocs([]); setActiveStaff([]); - setStaffAction('move'); - setMoveToBranchId(''); } }, [branch]); @@ -49,11 +44,6 @@ export default function EditBranchModal({ isOpen, onClose, onRefresh, branch }) const res = await fetch(`/api/branches/${branch.id}/active-staff`); const data = await res.json(); setActiveStaff(data); - - // Fetch other branches for movement - const bRes = await fetch('/api/branches?status=Active'); - const bData = await bRes.json(); - setBranches(bData.filter(b => b.id !== branch.id)); } catch (error) { console.error('Error fetching active staff:', error); } finally { @@ -95,13 +85,6 @@ export default function EditBranchModal({ isOpen, onClose, onRefresh, branch }) data.append('payroll_to_day', formData.payroll_to_day); data.append('salary_generation_day', formData.salary_generation_day); - if (formData.status === 'Inactive' && activeStaff.length > 0) { - data.append('staff_action', staffAction); - if (staffAction === 'move') { - data.append('move_to_branch_id', moveToBranchId); - } - } - newDocs.forEach((doc, index) => { if (doc.file) { data.append(`new_docs[${index}][file]`, doc.file); @@ -257,44 +240,11 @@ export default function EditBranchModal({ isOpen, onClose, onRefresh, branch })

Active Staff Detected ({activeStaff.length})

-

- This branch has active staff members. Please choose what to do with them before inactivating the branch. +

+ Warning: Inactivating this branch will automatically inactivate all {activeStaff.length} active staff members.

-
-
- - -
- - {staffAction === 'move' && ( - - )} -
- -
+
{activeStaff.map(s => (
{s.full_name} diff --git a/resources/js/Pages/Owner/Branches/View.jsx b/resources/js/Pages/Owner/Branches/View.jsx index 3d47483..3890224 100644 --- a/resources/js/Pages/Owner/Branches/View.jsx +++ b/resources/js/Pages/Owner/Branches/View.jsx @@ -50,6 +50,19 @@ function ReceptionistForm({ branchId }) { setError(''); setSuccess(''); + if (editingId === null || formData.password !== '') { + if (formData.password.length < 6) { + setError('Password must be at least 6 characters.'); + setSaving(false); + return; + } + if (formData.password !== formData.password_confirmation) { + setError('Passwords do not match. Please make it proper.'); + setSaving(false); + return; + } + } + try { const csrfToken = document.querySelector('meta[name="csrf-token"]').content; const res = await fetch(`/api/branches/${branchId}/receptionist`, { @@ -66,7 +79,12 @@ function ReceptionistForm({ branchId }) { setSuccess(editingId ? 'Account updated successfully!' : 'Account created successfully!'); fetchReceptionists(); } else { - setError(data.message || 'Failed to save receptionist.'); + if (data.errors) { + const firstError = Object.values(data.errors)[0][0]; + setError(firstError); + } else { + setError(data.message || 'Failed to save receptionist.'); + } } } catch (err) { setError('An error occurred. Please try again.'); @@ -189,6 +207,49 @@ function ReceptionistForm({ branchId }) { required={formData.password !== ''} />
+ + {/* Password Criteria Feedback */} +
+
+
+
= 6 ? 'bg-emerald-500' : (formData.password.length > 0 ? 'bg-red-500' : 'bg-gray-300')}`}>
+ = 6 || (editingId && formData.password === '')) + ? 'text-emerald-600' + : (formData.password.length > 0 ? 'text-red-500' : 'text-gray-400')}`}> + Min 6 characters + +
+
+
+ + Passwords match + +
+
+ + {/* Explicit Error Messages */} +
+ {formData.password.length > 0 && formData.password.length < 6 && ( +

+ + Password is too short (min 6 characters) +

+ )} + {formData.password_confirmation !== '' && formData.password !== formData.password_confirmation && ( +

+ + Please make it proper (Passwords mismatch) +

+ )} +
+
diff --git a/resources/js/Pages/Owner/Expenses/List.jsx b/resources/js/Pages/Owner/Expenses/List.jsx index 40d69ca..52f4472 100644 --- a/resources/js/Pages/Owner/Expenses/List.jsx +++ b/resources/js/Pages/Owner/Expenses/List.jsx @@ -672,68 +672,80 @@ export default function ExpenseList() {
-
- {pendingSalaries.map(staff => ( -
-
-
-

{staff.staff_name}

- -
-

- { (staff.pending_months.filter(m => bulkData.selectedMonths[staff.staff_id]?.includes(m.month_key)).reduce((sum, m) => sum + m.net_payable, 0) || 0).toLocaleString() } AED -

-
-
- {staff.pending_months.map(m => { - const isSelected = bulkData.selectedMonths[staff.staff_id]?.includes(m.month_key); - return ( + {pendingSalaries.length > 0 ? ( +
+ {pendingSalaries.map(staff => ( +
+
+
+

{staff.staff_name}

- ); - })} +
+

+ { (staff.pending_months.filter(m => bulkData.selectedMonths[staff.staff_id]?.includes(m.month_key)).reduce((sum, m) => sum + m.net_payable, 0) || 0).toLocaleString() } AED +

+
+
+ {staff.pending_months.map(m => { + const isSelected = bulkData.selectedMonths[staff.staff_id]?.includes(m.month_key); + return ( + + ); + })} +
+ ))} +
+ ) : ( +
+
+
- ))} -
+
+

No Pending Salaries

+

All staff members in this branch are fully paid for previous months.

+
+
+ )}
diff --git a/resources/js/Pages/Owner/Inventory/NewSaleModal.jsx b/resources/js/Pages/Owner/Inventory/NewSaleModal.jsx index 9dae945..ca8ac03 100644 --- a/resources/js/Pages/Owner/Inventory/NewSaleModal.jsx +++ b/resources/js/Pages/Owner/Inventory/NewSaleModal.jsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { X, Search, ShoppingCart, Plus, Minus, CreditCard, DollarSign, Globe, Trash2 } from 'lucide-react'; +import { X, Search, ShoppingCart, Plus, Minus, CreditCard, DollarSign, Globe, Trash2, Banknote } from 'lucide-react'; export default function NewSaleModal({ isOpen, onClose, onSave, branches, products }) { const [searchTerm, setSearchTerm] = useState(''); @@ -323,9 +323,7 @@ export default function NewSaleModal({ isOpen, onClose, onSave, branches, produc diff --git a/resources/js/Pages/Owner/Staff/View.jsx b/resources/js/Pages/Owner/Staff/View.jsx index 3bbd296..0f80dad 100644 --- a/resources/js/Pages/Owner/Staff/View.jsx +++ b/resources/js/Pages/Owner/Staff/View.jsx @@ -48,6 +48,7 @@ export default function StaffView({ id }) { const [loadingPayroll, setLoadingPayroll] = useState(false); const [advanceHistory, setAdvanceHistory] = useState([]); const [loadingAdvanceHistory, setLoadingAdvanceHistory] = useState(false); + const [isAdvanceHistoryModalOpen, setIsAdvanceHistoryModalOpen] = useState(false); useEffect(() => { fetchStaff(); @@ -426,21 +427,32 @@ export default function StaffView({ id }) {
)} - {advanceHistory.length > 0 && ( + {advanceHistory.length > 0 && (
-

Advance History Summary

+
+

Monthly Repayment Schedule

+ {advanceHistory.flatMap(h => h.installment_schedule || []).length > 8 && ( + + )} +
- {advanceHistory.map((h, i) => ( -
+ {advanceHistory.flatMap(h => h.installment_schedule || []) + .slice(0, 8) + .map((item, idx) => ( +
-

{(h.advance_amount || 0).toLocaleString()} AED

-

Taken {new Date(h.created_at).toLocaleDateString()}

+

{item.month}

+

AED {item.amount.toLocaleString()}

- - {h.status} + + {item.status} -

{parseFloat(h.paid_amount || 0).toLocaleString()} Paid

))} @@ -993,6 +1005,63 @@ export default function StaffView({ id }) {
)} + + {/* Full Advance History Modal */} + {isAdvanceHistoryModalOpen && ( +
+
+
+
+

Full Advance History

+

Repayment Schedule & Status

+
+ +
+ +
+
+ {advanceHistory.map((h, hIdx) => ( +
+
+

+ Advance taken on {new Date(h.created_at).toLocaleDateString()} — {parseFloat(h.advance_amount).toLocaleString()} AED +

+ + {h.status} + +
+
+ {(h.installment_schedule || []).map((item, iIdx) => ( +
+
+

{item.month}

+

AED {item.amount.toLocaleString()}

+
+
+ {item.status} +
+
+ ))} +
+ {hIdx < advanceHistory.length - 1 &&
} +
+ ))} +
+
+ +
+ +
+
+
+ )} ); diff --git a/resources/js/Pages/Receptionist/Dashboard.jsx b/resources/js/Pages/Receptionist/Dashboard.jsx index 5c1cd0d..85ac8d1 100644 --- a/resources/js/Pages/Receptionist/Dashboard.jsx +++ b/resources/js/Pages/Receptionist/Dashboard.jsx @@ -23,7 +23,9 @@ export default function ReceptionistDashboard() { const [transactions, setTransactions] = useState([]); const [loading, setLoading] = useState(true); const [filterBranch, setFilterBranch] = useState(window.__APP_DATA__?.branch?.id || ''); - const [startDate, setStartDate] = useState(new Date().toISOString().split('T')[0]); + + // Default to current month for a better overview + const [startDate, setStartDate] = useState(new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString().split('T')[0]); const [endDate, setEndDate] = useState(new Date().toISOString().split('T')[0]); const [branches, setBranches] = useState([]); @@ -48,8 +50,8 @@ export default function ReceptionistDashboard() { const data = await response.json(); setStats({ - total_income: data.total_income || 0, - total_expenses: data.total_expense || 0, + total_income: data.today_income || 0, // Using today's actual income + total_expenses: data.monthly_expense || 0, // Using monthly actual expenses net_profit: data.net_profit || 0, low_stock_count: data.low_stock_count || 0 });