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 })
- 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.
-+ + Password is too short (min 6 characters) +
+ )} + {formData.password_confirmation !== '' && formData.password !== formData.password_confirmation && ( ++ + Please make it proper (Passwords mismatch) +
+ )} +{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.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 +
+No Pending Salaries
+All staff members in this branch are fully paid for previous months.
+Advance History Summary
+Monthly Repayment Schedule
+ {advanceHistory.flatMap(h => h.installment_schedule || []).length > 8 && ( + + )} +{(h.advance_amount || 0).toLocaleString()} AED
-Taken {new Date(h.created_at).toLocaleDateString()}
+{item.month}
+AED {item.amount.toLocaleString()}
{parseFloat(h.paid_amount || 0).toLocaleString()} Paid
Repayment Schedule & Status
++ Advance taken on {new Date(h.created_at).toLocaleDateString()} — {parseFloat(h.advance_amount).toLocaleString()} AED +
+ + {h.status} + +{item.month}
+AED {item.amount.toLocaleString()}
+