Initial commit: Sistema de comisiones y gastos personales
This commit is contained in:
101
app/Http/Controllers/AuthController.php
Executable file
101
app/Http/Controllers/AuthController.php
Executable file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
/**
|
||||
* Mostrar formulario de login
|
||||
*/
|
||||
public function showLoginForm()
|
||||
{
|
||||
return view('auth.login');
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesar login
|
||||
*/
|
||||
public function login(Request $request)
|
||||
{
|
||||
$credentials = $request->validate([
|
||||
'username' => ['required', 'string'],
|
||||
'password' => ['required'],
|
||||
]);
|
||||
|
||||
$user = User::where('username', $credentials['username'])->first();
|
||||
|
||||
if (!$user || !Hash::check($credentials['password'], $user->password)) {
|
||||
throw ValidationException::withMessages([
|
||||
'username' => ['Las credenciales no coinciden con nuestros registros.'],
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$user->is_active) {
|
||||
throw ValidationException::withMessages([
|
||||
'username' => ['Tu cuenta está inactiva. Contacta al administrador.'],
|
||||
]);
|
||||
}
|
||||
|
||||
Auth::login($user, $request->boolean('remember'));
|
||||
|
||||
$request->session()->regenerate();
|
||||
|
||||
return redirect()->intended('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostrar formulario de registro
|
||||
*/
|
||||
public function showRegisterForm()
|
||||
{
|
||||
return view('auth.register');
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesar registro
|
||||
*/
|
||||
public function register(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'username' => ['required', 'string', 'max:50', 'unique:users,username'],
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'email', 'unique:users,email'],
|
||||
'password' => ['required', 'confirmed', 'min:8'],
|
||||
'commission_percentage' => ['required', 'numeric', 'min:0', 'max:100'],
|
||||
'monthly_salary' => ['required', 'numeric', 'min:0'],
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
'username' => $validated['username'],
|
||||
'name' => $validated['name'],
|
||||
'email' => $validated['email'],
|
||||
'password' => $validated['password'],
|
||||
'commission_percentage' => $validated['commission_percentage'],
|
||||
'monthly_salary' => $validated['monthly_salary'],
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Cerrar sesión
|
||||
*/
|
||||
public function logout(Request $request)
|
||||
{
|
||||
Auth::logout();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect('/login');
|
||||
}
|
||||
}
|
||||
154
app/Http/Controllers/CalendarController.php
Executable file
154
app/Http/Controllers/CalendarController.php
Executable file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\DailySale;
|
||||
use App\Models\Month;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class CalendarController extends Controller
|
||||
{
|
||||
/**
|
||||
* Mostrar calendario interactivo
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$year = $request->get('year', now()->year);
|
||||
$monthName = $request->get('month');
|
||||
|
||||
// Obtener meses del usuario
|
||||
$months = $user->months()
|
||||
->where('year', $year)
|
||||
->get()
|
||||
->keyBy('name');
|
||||
|
||||
// Si se especifica mes, mostrar ese mes
|
||||
if ($monthName) {
|
||||
$currentMonth = $months->get($monthName);
|
||||
} else {
|
||||
// Si no, buscar mes abierto o el último del año
|
||||
$currentMonth = $user->months()
|
||||
->where('year', $year)
|
||||
->orderByRaw("FIELD(name, 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre')")
|
||||
->first();
|
||||
}
|
||||
|
||||
// Obtener ventas diarias del mes actual
|
||||
$dailySales = [];
|
||||
if ($currentMonth) {
|
||||
$sales = $currentMonth->dailySales()->get()->keyBy('date');
|
||||
foreach ($sales as $sale) {
|
||||
$dailySales[$sale->date->format('Y-m-d')] = $sale;
|
||||
}
|
||||
}
|
||||
|
||||
// Obtener gastos del mes
|
||||
$expenses = [];
|
||||
if ($currentMonth) {
|
||||
$expenseList = $currentMonth->expenses()->get();
|
||||
foreach ($expenseList as $expense) {
|
||||
$expenses[$expense->date->format('Y-m-d')] = $expense;
|
||||
}
|
||||
}
|
||||
|
||||
return view('calendar.index', compact('year', 'months', 'currentMonth', 'dailySales', 'expenses'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener datos de un día específico (para AJAX)
|
||||
*/
|
||||
public function day(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
$date = $request->get('date');
|
||||
|
||||
if (!$date) {
|
||||
return response()->json(['error' => 'Fecha requerida'], 400);
|
||||
}
|
||||
|
||||
// Buscar venta del día
|
||||
$sale = DailySale::whereHas('month', function ($query) use ($user) {
|
||||
$query->where('user_id', $user->id);
|
||||
})
|
||||
->where('date', $date)
|
||||
->first();
|
||||
|
||||
// Buscar gastos del día
|
||||
$expenses = $user->expenses()
|
||||
->where('date', $date)
|
||||
->get();
|
||||
|
||||
return response()->json([
|
||||
'date' => $date,
|
||||
'sale' => $sale,
|
||||
'expenses' => $expenses,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Guardar venta y gasto del día (para AJAX)
|
||||
*/
|
||||
public function storeDay(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$data = $request->all();
|
||||
|
||||
$monthId = $request->input('month_id');
|
||||
$date = $request->input('date');
|
||||
$userSales = floatval($request->input('user_sales', 0));
|
||||
$systemSales = floatval($request->input('system_sales', 0));
|
||||
$expenseAmount = floatval($request->input('expense_amount', 0));
|
||||
|
||||
if (!$monthId || !$date) {
|
||||
return response()->json(['success' => false, 'message' => 'Faltan datos requeridos']);
|
||||
}
|
||||
|
||||
$month = Month::where('id', $monthId)
|
||||
->where('user_id', $user->id)
|
||||
->first();
|
||||
|
||||
if (!$month) {
|
||||
return response()->json(['success' => false, 'message' => 'Mes no encontrado']);
|
||||
}
|
||||
|
||||
// Guardar o actualizar venta solo si hay datos
|
||||
if ($userSales > 0) {
|
||||
DailySale::updateOrCreate(
|
||||
[
|
||||
'month_id' => $month->id,
|
||||
'date' => $date,
|
||||
],
|
||||
[
|
||||
'user_id' => $user->id,
|
||||
'user_sales' => $userSales,
|
||||
'system_sales' => $systemSales,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Guardar o actualizar gasto si hay monto
|
||||
$expenseType = $request->input('expense_type', 'q1');
|
||||
|
||||
if ($expenseAmount > 0) {
|
||||
\App\Models\Expense::updateOrCreate(
|
||||
[
|
||||
'month_id' => $month->id,
|
||||
'date' => $date,
|
||||
],
|
||||
[
|
||||
'user_id' => $user->id,
|
||||
'description' => $request->input('expense_description', 'Gasto del día'),
|
||||
'amount' => $expenseAmount,
|
||||
'expense_type' => $expenseType,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Datos guardados correctamente']);
|
||||
}
|
||||
}
|
||||
8
app/Http/Controllers/Controller.php
Executable file
8
app/Http/Controllers/Controller.php
Executable file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
44
app/Http/Controllers/DashboardController.php
Executable file
44
app/Http/Controllers/DashboardController.php
Executable file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\DailySale;
|
||||
use App\Models\Month;
|
||||
use App\Services\CommissionCalculator;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
/**
|
||||
* Mostrar dashboard del usuario
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
// Obtener mes actual o último mes
|
||||
$currentMonth = $user->getCurrentMonth();
|
||||
|
||||
// Si no hay mes abierto, buscar el último mes
|
||||
if (!$currentMonth) {
|
||||
$currentMonth = $user->months()
|
||||
->orderBy('year', 'desc')
|
||||
->orderByRaw("FIELD(name, 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre') DESC")
|
||||
->first();
|
||||
}
|
||||
|
||||
$data = null;
|
||||
if ($currentMonth) {
|
||||
$data = CommissionCalculator::calculateForMonth($user, $currentMonth);
|
||||
}
|
||||
|
||||
// Últimos meses del usuario
|
||||
$recentMonths = $user->months()
|
||||
->orderBy('year', 'desc')
|
||||
->orderByRaw("FIELD(name, 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre') DESC")
|
||||
->limit(6)
|
||||
->get();
|
||||
|
||||
return view('dashboard.index', compact('currentMonth', 'data', 'recentMonths'));
|
||||
}
|
||||
}
|
||||
152
app/Http/Controllers/ExpenseController.php
Executable file
152
app/Http/Controllers/ExpenseController.php
Executable file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Expense;
|
||||
use App\Models\Month;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class ExpenseController extends Controller
|
||||
{
|
||||
/**
|
||||
* Listar todos los gastos
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$monthId = $request->get('month_id');
|
||||
|
||||
if ($monthId) {
|
||||
$month = $user->months()->findOrFail($monthId);
|
||||
$expenses = $month->expenses()
|
||||
->orderBy('date', 'desc')
|
||||
->paginate(30);
|
||||
} else {
|
||||
$month = null;
|
||||
$expenses = $user->expenses()
|
||||
->orderBy('date', 'desc')
|
||||
->paginate(30);
|
||||
}
|
||||
|
||||
$months = $user->months()
|
||||
->orderBy('year', 'desc')
|
||||
->orderByRaw("FIELD(name, 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre') DESC")
|
||||
->get();
|
||||
|
||||
return view('expenses.index', compact('expenses', 'month', 'months'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostrar formulario para crear gasto
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$monthId = $request->get('month_id');
|
||||
|
||||
if ($monthId) {
|
||||
$month = $user->months()->findOrFail($monthId);
|
||||
} else {
|
||||
$month = $user->getCurrentMonth();
|
||||
|
||||
if (!$month) {
|
||||
$month = $user->months()->latest()->first();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$month) {
|
||||
return redirect()->route('months.index')->with('info', 'Necesitas un mes de trabajo primero.');
|
||||
}
|
||||
|
||||
return view('expenses.create', compact('month'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Guardar nuevo gasto
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$validated = $request->validate([
|
||||
'month_id' => ['required', 'exists:months,id'],
|
||||
'description' => ['required', 'string', 'max:255'],
|
||||
'amount' => ['required', 'numeric', 'min:0.01'],
|
||||
'date' => ['required', 'date'],
|
||||
'expense_type' => ['required', 'in:q1,q2,mensual'],
|
||||
]);
|
||||
|
||||
// Verificar que el mes pertenece al usuario
|
||||
$month = $user->months()->findOrFail($validated['month_id']);
|
||||
|
||||
$month->expenses()->create([
|
||||
'user_id' => $user->id,
|
||||
'description' => $validated['description'],
|
||||
'amount' => $validated['amount'],
|
||||
'date' => $validated['date'],
|
||||
'expense_type' => $validated['expense_type'],
|
||||
]);
|
||||
|
||||
return redirect()->route('expenses.index', ['month_id' => $month->id])
|
||||
->with('success', 'Gasto registrado correctamente.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostrar formulario de edición
|
||||
*/
|
||||
public function edit(Expense $expense)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($expense->user_id !== $user->id) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
return view('expenses.edit', compact('expense'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualizar gasto
|
||||
*/
|
||||
public function update(Request $request, Expense $expense)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($expense->user_id !== $user->id) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'description' => ['required', 'string', 'max:255'],
|
||||
'amount' => ['required', 'numeric', 'min:0.01'],
|
||||
'date' => ['required', 'date'],
|
||||
'expense_type' => ['required', 'in:q1,q2,mensual'],
|
||||
]);
|
||||
|
||||
$expense->update($validated);
|
||||
|
||||
return redirect()->route('expenses.index', ['month_id' => $expense->month_id])
|
||||
->with('success', 'Gasto actualizado correctamente.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Eliminar gasto
|
||||
*/
|
||||
public function destroy(Expense $expense)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($expense->user_id !== $user->id) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$monthId = $expense->month_id;
|
||||
$expense->delete();
|
||||
|
||||
return redirect()->route('expenses.index', ['month_id' => $monthId])
|
||||
->with('success', 'Gasto eliminado correctamente.');
|
||||
}
|
||||
}
|
||||
163
app/Http/Controllers/MonthController.php
Executable file
163
app/Http/Controllers/MonthController.php
Executable file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Month;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class MonthController extends Controller
|
||||
{
|
||||
/**
|
||||
* Listar todos los meses
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$months = $user->months()
|
||||
->orderBy('year', 'desc')
|
||||
->orderByRaw("FIELD(name, 'Diciembre', 'Noviembre', 'Octubre', 'Septiembre', 'Agosto', 'Julio', 'Junio', 'Mayo', 'Abril', 'Marzo', 'Febrero', 'Enero')")
|
||||
->paginate(12);
|
||||
|
||||
return view('months.index', compact('months'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostrar formulario para crear mes
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
return view('months.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Guardar nuevo mes
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => ['required', 'string', 'max:50'],
|
||||
'year' => ['required', 'integer', 'min:2020', 'max:2100'],
|
||||
]);
|
||||
|
||||
// Verificar que no exista ya el mes para el usuario
|
||||
$exists = $user->months()
|
||||
->where('name', $validated['name'])
|
||||
->where('year', $validated['year'])
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return back()->withErrors([
|
||||
'name' => 'Ya existe un mes con ese nombre y año.',
|
||||
])->withInput();
|
||||
}
|
||||
|
||||
$user->months()->create([
|
||||
'name' => $validated['name'],
|
||||
'year' => $validated['year'],
|
||||
'status' => 'open',
|
||||
]);
|
||||
|
||||
return redirect()->route('months.index')
|
||||
->with('success', 'Mes creado correctamente.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostrar detalles del mes
|
||||
*/
|
||||
public function show(Month $month)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($month->user_id !== $user->id) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$month->load(['dailySales', 'expenses']);
|
||||
|
||||
return view('months.show', compact('month'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostrar formulario de edición
|
||||
*/
|
||||
public function edit(Month $month)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($month->user_id !== $user->id) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
return view('months.edit', compact('month'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualizar mes
|
||||
*/
|
||||
public function update(Request $request, Month $month)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($month->user_id !== $user->id) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => ['required', 'string', 'max:50'],
|
||||
'year' => ['required', 'integer', 'min:2020', 'max:2100'],
|
||||
'status' => ['required', 'in:open,closed,paid'],
|
||||
]);
|
||||
|
||||
$month->update($validated);
|
||||
|
||||
return redirect()->route('months.show', $month->id)
|
||||
->with('success', 'Mes actualizado correctamente.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Cerrar mes
|
||||
*/
|
||||
public function close(Month $month)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($month->user_id !== $user->id) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
if ($month->status !== 'open') {
|
||||
return back()->with('error', 'Solo se pueden cerrar meses abiertos.');
|
||||
}
|
||||
|
||||
$month->update(['status' => 'closed']);
|
||||
|
||||
return redirect()->route('months.show', $month->id)
|
||||
->with('success', 'Mes cerrado correctamente.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Eliminar mes
|
||||
*/
|
||||
public function destroy(Month $month)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($month->user_id !== $user->id) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
// Verificar que no tenga ventas o gastos asociados
|
||||
if ($month->dailySales()->count() > 0 || $month->expenses()->count() > 0) {
|
||||
return back()->with('error', 'No puedes eliminar un mes que tiene ventas o gastos asociados.');
|
||||
}
|
||||
|
||||
$month->delete();
|
||||
|
||||
return redirect()->route('months.index')
|
||||
->with('success', 'Mes eliminado correctamente.');
|
||||
}
|
||||
}
|
||||
147
app/Http/Controllers/ReportController.php
Executable file
147
app/Http/Controllers/ReportController.php
Executable file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Month;
|
||||
use App\Services\CommissionCalculator;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class ReportController extends Controller
|
||||
{
|
||||
/**
|
||||
* Mostrar reporte mensual
|
||||
*/
|
||||
public function monthly(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$monthId = $request->get('month_id');
|
||||
|
||||
if ($monthId) {
|
||||
$month = $user->months()->findOrFail($monthId);
|
||||
} else {
|
||||
$month = $user->getCurrentMonth();
|
||||
|
||||
if (!$month) {
|
||||
$month = $user->months()->latest()->first();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$month) {
|
||||
return redirect()->route('dashboard')->with('info', 'No hay meses disponibles.');
|
||||
}
|
||||
|
||||
$report = CommissionCalculator::calculateForMonth($user, $month);
|
||||
|
||||
// Cargar ventas y gastos detalles
|
||||
$dailySales = $month->dailySales()
|
||||
->orderBy('date', 'asc')
|
||||
->get();
|
||||
|
||||
$expenses = $month->expenses()
|
||||
->orderBy('date', 'desc')
|
||||
->get();
|
||||
|
||||
$months = $user->months()
|
||||
->orderBy('year', 'desc')
|
||||
->orderByRaw("FIELD(name, 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre') DESC")
|
||||
->get();
|
||||
|
||||
return view('reports.monthly', compact('report', 'month', 'dailySales', 'expenses', 'months'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostrar reporte quincenal
|
||||
*/
|
||||
public function biweekly(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$monthId = $request->get('month_id');
|
||||
$biweekly = $request->get('biweekly', 1);
|
||||
|
||||
if ($monthId) {
|
||||
$month = $user->months()->findOrFail($monthId);
|
||||
} else {
|
||||
$month = $user->getCurrentMonth();
|
||||
|
||||
if (!$month) {
|
||||
$month = $user->months()->latest()->first();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$month) {
|
||||
return redirect()->route('dashboard')->with('info', 'No hay meses disponibles.');
|
||||
}
|
||||
|
||||
$report = CommissionCalculator::calculateBiweekly($user, $month, $biweekly);
|
||||
|
||||
// Obtener ventas del mes
|
||||
$dailySales = $month->dailySales()
|
||||
->orderBy('date', 'asc')
|
||||
->get();
|
||||
|
||||
// Obtener gastos según la quincena seleccionada
|
||||
$expenses = $month->expenses()
|
||||
->where(function($query) use ($biweekly) {
|
||||
if ($biweekly === 1) {
|
||||
$query->where('expense_type', 'q1')
|
||||
->orWhere('expense_type', 'mensual');
|
||||
} else {
|
||||
$query->where('expense_type', 'q2')
|
||||
->orWhere('expense_type', 'mensual');
|
||||
}
|
||||
})
|
||||
->orderBy('date', 'desc')
|
||||
->get();
|
||||
|
||||
$months = $user->months()
|
||||
->orderBy('year', 'desc')
|
||||
->orderByRaw("FIELD(name, 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre') DESC")
|
||||
->get();
|
||||
|
||||
return view('reports.biweekly', compact('report', 'month', 'dailySales', 'expenses', 'months', 'biweekly'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumen anual
|
||||
*/
|
||||
public function yearly(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$year = $request->get('year', now()->year);
|
||||
|
||||
$report = CommissionCalculator::calculateYearly($user, $year);
|
||||
|
||||
// Obtener meses del año
|
||||
$months = $user->months()
|
||||
->where('year', $year)
|
||||
->orderByRaw("FIELD(name, 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre')")
|
||||
->get();
|
||||
|
||||
// Años disponibles
|
||||
$years = $user->months()
|
||||
->select('year')
|
||||
->distinct()
|
||||
->orderBy('year', 'desc')
|
||||
->pluck('year');
|
||||
|
||||
return view('reports.yearly', compact('report', 'year', 'months', 'years'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener número del mes por nombre
|
||||
*/
|
||||
private static function getMonthNumber(string $monthName): int
|
||||
{
|
||||
$months = [
|
||||
'Enero' => 1, 'Febrero' => 2, 'Marzo' => 3, 'Abril' => 4,
|
||||
'Mayo' => 5, 'Junio' => 6, 'Julio' => 7, 'Agosto' => 8,
|
||||
'Septiembre' => 9, 'Octubre' => 10, 'Noviembre' => 11, 'Diciembre' => 12
|
||||
];
|
||||
|
||||
return $months[$monthName] ?? 1;
|
||||
}
|
||||
}
|
||||
172
app/Http/Controllers/SaleController.php
Executable file
172
app/Http/Controllers/SaleController.php
Executable file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\DailySale;
|
||||
use App\Models\Month;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class SaleController extends Controller
|
||||
{
|
||||
/**
|
||||
* Listar todas las ventas del mes actual
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
// Obtener mes seleccionado
|
||||
$monthId = $request->get('month_id');
|
||||
|
||||
if ($monthId) {
|
||||
$month = $user->months()->findOrFail($monthId);
|
||||
} else {
|
||||
$month = $user->getCurrentMonth();
|
||||
|
||||
if (!$month) {
|
||||
$month = $user->months()->latest()->first();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$month) {
|
||||
return redirect()->route('months.create')->with('info', 'Primero debes crear un mes de trabajo.');
|
||||
}
|
||||
|
||||
$sales = $month->dailySales()
|
||||
->orderBy('date', 'desc')
|
||||
->paginate(31);
|
||||
|
||||
$months = $user->months()
|
||||
->orderBy('year', 'desc')
|
||||
->orderByRaw("FIELD(name, 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre') DESC")
|
||||
->get();
|
||||
|
||||
return view('sales.index', compact('sales', 'month', 'months'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostrar formulario para crear venta
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$monthId = $request->get('month_id');
|
||||
|
||||
if ($monthId) {
|
||||
$month = $user->months()->findOrFail($monthId);
|
||||
} else {
|
||||
$month = $user->getCurrentMonth();
|
||||
|
||||
if (!$month) {
|
||||
$month = $user->months()->latest()->first();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$month) {
|
||||
return redirect()->route('months.index')->with('info', 'Necesitas un mes de trabajo primero.');
|
||||
}
|
||||
|
||||
return view('sales.create', compact('month'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Guardar nueva venta
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$validated = $request->validate([
|
||||
'month_id' => ['required', 'exists:months,id'],
|
||||
'date' => ['required', 'date'],
|
||||
'user_sales' => ['required', 'numeric', 'min:0'],
|
||||
'system_sales' => ['nullable', 'numeric', 'min:0'],
|
||||
]);
|
||||
|
||||
// Verificar que el mes pertenece al usuario
|
||||
$month = $user->months()->findOrFail($validated['month_id']);
|
||||
|
||||
// Verificar si ya existe una venta para esa fecha
|
||||
$existingSale = $month->dailySales()->where('date', $validated['date'])->first();
|
||||
|
||||
if ($existingSale) {
|
||||
// Actualizar venta existente
|
||||
$existingSale->update([
|
||||
'user_sales' => $validated['user_sales'],
|
||||
'system_sales' => $validated['system_sales'] ?? $existingSale->system_sales,
|
||||
]);
|
||||
|
||||
return redirect()->route('sales.index', ['month_id' => $month->id])
|
||||
->with('success', 'Venta actualizada correctamente.');
|
||||
}
|
||||
|
||||
// Crear nueva venta
|
||||
$month->dailySales()->create([
|
||||
'user_id' => $user->id,
|
||||
'date' => $validated['date'],
|
||||
'user_sales' => $validated['user_sales'],
|
||||
'system_sales' => $validated['system_sales'] ?? 0,
|
||||
]);
|
||||
|
||||
return redirect()->route('sales.index', ['month_id' => $month->id])
|
||||
->with('success', 'Venta registrada correctamente.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostrar formulario de edición
|
||||
*/
|
||||
public function edit(DailySale $sale)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
// Verificar que la venta pertenece al usuario
|
||||
if ($sale->user_id !== $user->id) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
return view('sales.edit', compact('sale'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualizar venta
|
||||
*/
|
||||
public function update(Request $request, DailySale $sale)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($sale->user_id !== $user->id) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'date' => ['required', 'date'],
|
||||
'user_sales' => ['required', 'numeric', 'min:0'],
|
||||
'system_sales' => ['nullable', 'numeric', 'min:0'],
|
||||
]);
|
||||
|
||||
$sale->update($validated);
|
||||
|
||||
return redirect()->route('sales.index', ['month_id' => $sale->month_id])
|
||||
->with('success', 'Venta actualizada correctamente.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Eliminar venta
|
||||
*/
|
||||
public function destroy(DailySale $sale)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($sale->user_id !== $user->id) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$monthId = $sale->month_id;
|
||||
$sale->delete();
|
||||
|
||||
return redirect()->route('sales.index', ['month_id' => $monthId])
|
||||
->with('success', 'Venta eliminada correctamente.');
|
||||
}
|
||||
}
|
||||
76
app/Http/Controllers/SettingsController.php
Executable file
76
app/Http/Controllers/SettingsController.php
Executable file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class SettingsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Mostrar configuración del usuario
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$user = Auth::user();
|
||||
return view('settings.index', compact('user'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualizar configuración del usuario
|
||||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
foreach (['fecha_ingreso', 'razon_social', 'sueldo_integro_diario'] as $field) {
|
||||
if ($request->has($field) && $request->input($field) === '') {
|
||||
$request->merge([$field => null]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->has('commission_percentage')) {
|
||||
$request->validate([
|
||||
'commission_percentage' => ['required', 'numeric', 'min:0', 'max:100'],
|
||||
]);
|
||||
$user->commission_percentage = floatval($request->input('commission_percentage', 0));
|
||||
}
|
||||
|
||||
if ($request->has('monthly_salary')) {
|
||||
$request->validate([
|
||||
'monthly_salary' => ['required', 'numeric', 'min:0'],
|
||||
]);
|
||||
$user->monthly_salary = floatval($request->input('monthly_salary', 0));
|
||||
}
|
||||
|
||||
if ($request->has('fecha_ingreso')) {
|
||||
if ($request->filled('fecha_ingreso')) {
|
||||
$user->fecha_ingreso = $request->input('fecha_ingreso');
|
||||
} else {
|
||||
$user->fecha_ingreso = null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->has('razon_social')) {
|
||||
if ($request->filled('razon_social')) {
|
||||
$user->razon_social = $request->input('razon_social');
|
||||
} else {
|
||||
$user->razon_social = null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->has('sueldo_integro_diario')) {
|
||||
if ($request->filled('sueldo_integro_diario')) {
|
||||
$user->sueldo_integro_diario = floatval($request->input('sueldo_integro_diario'));
|
||||
} else {
|
||||
$user->sueldo_integro_diario = null;
|
||||
}
|
||||
}
|
||||
|
||||
$user->save();
|
||||
|
||||
return redirect()->route('settings.index')->with('success', 'Configuración actualizada correctamente.');
|
||||
}
|
||||
}
|
||||
140
app/Http/Controllers/TelegramController.php
Executable file
140
app/Http/Controllers/TelegramController.php
Executable file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\TelegramAccount;
|
||||
use App\Models\User;
|
||||
use App\Services\TelegramBotService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class TelegramController extends Controller
|
||||
{
|
||||
/**
|
||||
* Mostrar página de vinculación de Telegram
|
||||
*/
|
||||
public function showVerifyPage()
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
// Obtener o crear cuenta de Telegram
|
||||
$telegramAccount = $user->telegramAccount()->first();
|
||||
|
||||
if (!$telegramAccount) {
|
||||
// Generar nuevo código de verificación
|
||||
$verificationCode = TelegramBotService::generateVerificationCode();
|
||||
|
||||
$telegramAccount = TelegramAccount::create([
|
||||
'user_id' => $user->id,
|
||||
'chat_id' => null,
|
||||
'verification_code' => $verificationCode,
|
||||
'is_verified' => false,
|
||||
]);
|
||||
} elseif (!$telegramAccount->is_verified && !$telegramAccount->verification_code) {
|
||||
// Regenerar código si no existe
|
||||
$verificationCode = TelegramBotService::generateVerificationCode();
|
||||
$telegramAccount->update(['verification_code' => $verificationCode]);
|
||||
}
|
||||
|
||||
return view('telegram.verify', compact('telegramAccount'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerar código de verificación
|
||||
*/
|
||||
public function regenerateCode(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$telegramAccount = $user->telegramAccount;
|
||||
|
||||
if (!$telegramAccount) {
|
||||
return back()->with('error', 'No tienes una cuenta de Telegram vinculada.');
|
||||
}
|
||||
|
||||
if ($telegramAccount->is_verified) {
|
||||
return back()->with('error', 'Tu cuenta ya está verificada.');
|
||||
}
|
||||
|
||||
$verificationCode = TelegramBotService::generateVerificationCode();
|
||||
$telegramAccount->update(['verification_code' => $verificationCode]);
|
||||
|
||||
return back()->with('success', 'Nuevo código de verificación generado.');
|
||||
}
|
||||
|
||||
/**
|
||||
* desvincular cuenta de Telegram
|
||||
*/
|
||||
public function unlink(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$telegramAccount = $user->telegramAccount;
|
||||
|
||||
if ($telegramAccount) {
|
||||
$telegramAccount->delete();
|
||||
|
||||
// Crear nueva cuenta sin verificar
|
||||
TelegramAccount::create([
|
||||
'user_id' => $user->id,
|
||||
'chat_id' => null,
|
||||
'verification_code' => TelegramBotService::generateVerificationCode(),
|
||||
'is_verified' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
return back()->with('success', 'Cuenta de Telegram desvinculada.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Webhook para recibir mensajes de Telegram
|
||||
*/
|
||||
public function webhook(Request $request)
|
||||
{
|
||||
try {
|
||||
$update = $request->all();
|
||||
|
||||
Log::info('Telegram webhook received', $update);
|
||||
|
||||
if (empty($update)) {
|
||||
return response()->json(['ok' => true, 'message' => 'No update
|
||||
received']);
|
||||
}
|
||||
|
||||
$botService = new TelegramBotService();
|
||||
$result = $botService->handleUpdate($update);
|
||||
|
||||
return response()->json($result);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Telegram webhook error', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return response()->json(['ok' => false, 'error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configurar webhook (ruta temporal para configurar desde el navegador)
|
||||
*/
|
||||
public function setupWebhook(Request $request)
|
||||
{
|
||||
$token = $request->get('token');
|
||||
|
||||
// Verificar token de seguridad simple
|
||||
if ($token !== config('app.key')) {
|
||||
abort(403, 'Token inválido');
|
||||
}
|
||||
|
||||
$botService = new TelegramBotService();
|
||||
$result = $botService->setWebhook();
|
||||
|
||||
if ($result) {
|
||||
return response()->json(['success' => true, 'message' => 'Webhook configurado correctamente']);
|
||||
}
|
||||
|
||||
return response()->json(['success' => false, 'error' => 'Error al configurar webhook'], 500);
|
||||
}
|
||||
}
|
||||
67
app/Models/DailySale.php
Executable file
67
app/Models/DailySale.php
Executable file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
#[Fillable(['user_id', 'month_id', 'date', 'user_sales', 'system_sales'])]
|
||||
class DailySale extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'daily_sales';
|
||||
|
||||
/**
|
||||
* Los atributos que son asignables en masa.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'month_id',
|
||||
'date',
|
||||
'user_sales',
|
||||
'system_sales',
|
||||
];
|
||||
|
||||
/**
|
||||
* Los atributos que deben ser convertidos.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'date',
|
||||
'user_sales' => 'decimal:2',
|
||||
'system_sales' => 'decimal:2',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relación con usuario
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relación con mes
|
||||
*/
|
||||
public function month(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Month::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener la diferencia entre ventas usuario y sistema
|
||||
*/
|
||||
public function getDifferenceAttribute(): float
|
||||
{
|
||||
return $this->user_sales - $this->system_sales;
|
||||
}
|
||||
}
|
||||
59
app/Models/Expense.php
Executable file
59
app/Models/Expense.php
Executable file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
#[Fillable(['user_id', 'month_id', 'description', 'amount', 'date', 'expense_type'])]
|
||||
class Expense extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'expenses';
|
||||
|
||||
/**
|
||||
* Los atributos que son asignables en masa.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'month_id',
|
||||
'description',
|
||||
'amount',
|
||||
'date',
|
||||
'expense_type',
|
||||
];
|
||||
|
||||
/**
|
||||
* Los atributos que deben ser convertidos.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'date',
|
||||
'amount' => 'decimal:2',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relación con usuario
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relación con mes
|
||||
*/
|
||||
public function month(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Month::class);
|
||||
}
|
||||
}
|
||||
97
app/Models/Month.php
Executable file
97
app/Models/Month.php
Executable file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
#[Fillable(['user_id', 'name', 'year', 'status'])]
|
||||
class Month extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'months';
|
||||
|
||||
/**
|
||||
* Los atributos que son asignables en masa.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'name',
|
||||
'year',
|
||||
'status',
|
||||
];
|
||||
|
||||
/**
|
||||
* Los atributos que deben ser convertidos.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'year' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relación con usuario
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relación con ventas diarias
|
||||
*/
|
||||
public function dailySales(): HasMany
|
||||
{
|
||||
return $this->hasMany(DailySale::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relación con gastos
|
||||
*/
|
||||
public function expenses(): HasMany
|
||||
{
|
||||
return $this->hasMany(Expense::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener el nombre del mes con formato
|
||||
*/
|
||||
public function getDisplayNameAttribute(): string
|
||||
{
|
||||
return $this->name . ' ' . $this->year;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcular ventas totales del mes
|
||||
*/
|
||||
public function getTotalUserSalesAttribute(): float
|
||||
{
|
||||
return $this->dailySales()->sum('user_sales');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcular ventas del sistema
|
||||
*/
|
||||
public function getTotalSystemSalesAttribute(): float
|
||||
{
|
||||
return $this->dailySales()->sum('system_sales');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcular gastos totales del mes
|
||||
*/
|
||||
public function getTotalExpensesAttribute(): float
|
||||
{
|
||||
return $this->expenses()->sum('amount');
|
||||
}
|
||||
}
|
||||
48
app/Models/TelegramAccount.php
Executable file
48
app/Models/TelegramAccount.php
Executable file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
#[Fillable(['user_id', 'chat_id', 'verification_code', 'is_verified'])]
|
||||
class TelegramAccount extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'telegram_accounts';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'chat_id',
|
||||
'verification_code',
|
||||
'is_verified',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'is_verified' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relación con usuario
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
84
app/Models/User.php
Executable file
84
app/Models/User.php
Executable file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
#[Fillable(['username', 'name', 'email', 'password', 'commission_percentage', 'monthly_salary', 'is_active', 'fecha_ingreso', 'razon_social', 'sueldo_integro_diario'])]
|
||||
#[Hidden(['password', 'remember_token'])]
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<UserFactory> */
|
||||
use HasFactory, Notifiable;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'commission_percentage' => 'decimal:2',
|
||||
'monthly_salary' => 'decimal:2',
|
||||
'sueldo_integro_diario' => 'decimal:2',
|
||||
'fecha_ingreso' => 'date',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relación con cuenta de Telegram
|
||||
*/
|
||||
public function telegramAccount(): HasOne
|
||||
{
|
||||
return $this->hasOne(TelegramAccount::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relación con meses de trabajo
|
||||
*/
|
||||
public function months(): HasMany
|
||||
{
|
||||
return $this->hasMany(Month::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relación con ventas diarias
|
||||
*/
|
||||
public function dailySales(): HasMany
|
||||
{
|
||||
return $this->hasMany(DailySale::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relación con gastos
|
||||
*/
|
||||
public function expenses(): HasMany
|
||||
{
|
||||
return $this->hasMany(Expense::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener el mes activo (si existe)
|
||||
*/
|
||||
public function getCurrentMonth(): ?Month
|
||||
{
|
||||
return $this->months()
|
||||
->where('status', 'open')
|
||||
->orderBy('year', 'desc')
|
||||
->orderByRaw("FIELD(name, 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre')")
|
||||
->first();
|
||||
}
|
||||
}
|
||||
24
app/Providers/AppServiceProvider.php
Executable file
24
app/Providers/AppServiceProvider.php
Executable file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
211
app/Services/CommissionCalculator.php
Executable file
211
app/Services/CommissionCalculator.php
Executable file
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\DailySale;
|
||||
use App\Models\Expense;
|
||||
use App\Models\Month;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class CommissionCalculator
|
||||
{
|
||||
/**
|
||||
* Calcular comisión para un usuario en un mes específico
|
||||
*/
|
||||
public static function calculateForMonth(User $user, Month $month): array
|
||||
{
|
||||
// Verificar que el mes pertenece al usuario
|
||||
if ($month->user_id !== $user->id) {
|
||||
throw new \InvalidArgumentException('El mes no pertenece al usuario');
|
||||
}
|
||||
|
||||
// Total de ventas del usuario
|
||||
$totalUserSales = $month->dailySales()->sum('user_sales');
|
||||
|
||||
// Total de ventas del sistema
|
||||
$totalSystemSales = $month->dailySales()->sum('system_sales');
|
||||
|
||||
// Total de gastos
|
||||
$totalExpenses = $month->expenses()->sum('amount');
|
||||
|
||||
// Salario base
|
||||
$monthlySalary = $user->monthly_salary;
|
||||
|
||||
// Porcentaje de comisión
|
||||
$commissionPercentage = $user->commission_percentage;
|
||||
|
||||
// Calcular comisión basada en ventas del sistema (ventas consolidadas)
|
||||
$commission = ($totalSystemSales * $commissionPercentage) / 100;
|
||||
|
||||
// Calcular percepción total (salario + comisión - gastos)
|
||||
$totalEarning = $monthlySalary + $commission - $totalExpenses;
|
||||
|
||||
return [
|
||||
'user_id' => $user->id,
|
||||
'month_id' => $month->id,
|
||||
'month_name' => $month->name . ' ' . $month->year,
|
||||
'total_user_sales' => round($totalUserSales, 2),
|
||||
'total_system_sales' => round($totalSystemSales, 2),
|
||||
'total_expenses' => round($totalExpenses, 2),
|
||||
'monthly_salary' => round($monthlySalary, 2),
|
||||
'commission_percentage' => round($commissionPercentage, 2),
|
||||
'commission_amount' => round($commission, 2),
|
||||
'total_earning' => round($totalEarning, 2),
|
||||
'has_difference' => ($totalUserSales !== $totalSystemSales),
|
||||
'sales_difference' => round($totalUserSales - $totalSystemSales, 2),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcular quincena (primera o segunda)
|
||||
*
|
||||
* QUINCENA 1 (ANTICIPO): mitad salary + comisiones del MES completo
|
||||
* QUINCENA 2 (LIQUIDACIÓN): mitad salary - gastos de la segunda quincena
|
||||
*/
|
||||
public static function calculateBiweekly(User $user, Month $month, int $biweekly): array
|
||||
{
|
||||
if ($month->user_id !== $user->id) {
|
||||
throw new \InvalidArgumentException('El mes no pertenece al usuario');
|
||||
}
|
||||
|
||||
$monthlySalary = $user->monthly_salary;
|
||||
$biweeklySalary = $monthlySalary / 2; // Mitad del sueldo
|
||||
$commissionPercentage = $user->commission_percentage;
|
||||
|
||||
// Get month number
|
||||
$monthNumber = self::getMonthNumber($month->name);
|
||||
$year = $month->year;
|
||||
$lastDay = self::getLastDayOfMonth($month->name, $year);
|
||||
|
||||
if ($biweekly === 1) {
|
||||
// =====================
|
||||
// QUINCENA 1 - ANTICIPO
|
||||
// =====================
|
||||
// Anticipo = mitad del sueldo + comisiones del MES completo
|
||||
$totalSystemSales = $month->dailySales()->sum('system_sales');
|
||||
$commission = ($totalSystemSales * $commissionPercentage) / 100;
|
||||
|
||||
// Gastos: q1 completo + mensual/2
|
||||
$expensesQ1Amount = $month->expenses()
|
||||
->where(function($q) {
|
||||
$q->where('expense_type', 'q1')
|
||||
->orWhere('expense_type', 'mensual');
|
||||
})
|
||||
->get()
|
||||
->sum(function($e) {
|
||||
return $e->expense_type === 'mensual' ? $e->amount / 2 : $e->amount;
|
||||
});
|
||||
|
||||
$totalEarning = $biweeklySalary + $commission - $expensesQ1Amount;
|
||||
|
||||
return [
|
||||
'user_id' => $user->id,
|
||||
'month_id' => $month->id,
|
||||
'month_name' => $month->name . ' ' . $year,
|
||||
'biweekly' => $biweekly,
|
||||
'period' => '1ra Quincena (1-15) - ANTICIPO',
|
||||
'description' => 'Mitad del sueldo + comisiones del mes completo',
|
||||
'biweekly_salary' => round($biweeklySalary, 2),
|
||||
'total_system_sales' => round($totalSystemSales, 2),
|
||||
'commission_percentage' => round($commissionPercentage, 2),
|
||||
'commission_amount' => round($commission, 2),
|
||||
'total_expenses_month' => 0,
|
||||
'expenses_q1' => round($expensesQ1Amount, 2),
|
||||
'expenses_q2' => 0,
|
||||
'total_earning' => round($totalEarning, 2),
|
||||
'type' => 'anticipo',
|
||||
];
|
||||
} else {
|
||||
// =====================
|
||||
// QUINCENA 2 - LIQUIDACIÓN
|
||||
// =====================
|
||||
// Liquidación = mitad del sueldo - gastos de Q2
|
||||
|
||||
// Gastos: q2 completo + mensual/2
|
||||
$expensesQ2Amount = $month->expenses()
|
||||
->where(function($q) {
|
||||
$q->where('expense_type', 'q2')
|
||||
->orWhere('expense_type', 'mensual');
|
||||
})
|
||||
->get()
|
||||
->sum(function($e) {
|
||||
return $e->expense_type === 'mensual' ? $e->amount / 2 : $e->amount;
|
||||
});
|
||||
|
||||
// Total a pagar en liquidacion
|
||||
$totalEarning = $biweeklySalary - $expensesQ2Amount;
|
||||
|
||||
return [
|
||||
'user_id' => $user->id,
|
||||
'month_id' => $month->id,
|
||||
'month_name' => $month->name . ' ' . $year,
|
||||
'biweekly' => $biweekly,
|
||||
'period' => "2da Quincena (16-$lastDay) - LIQUIDACIÓN",
|
||||
'description' => 'Mitad del sueldo - mitad de gastos del mes',
|
||||
'biweekly_salary' => round($biweeklySalary, 2),
|
||||
'total_system_sales' => 0,
|
||||
'expenses_q2' => round($expensesQ2Amount, 2),
|
||||
'total_earning' => round($totalEarning, 2),
|
||||
'type' => 'liquidacion',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumen anual del usuario
|
||||
*/
|
||||
public static function calculateYearly(User $user, int $year): array
|
||||
{
|
||||
$months = $user->months()->where('year', $year)->get();
|
||||
|
||||
$totalUserSales = 0;
|
||||
$totalSystemSales = 0;
|
||||
$totalExpenses = 0;
|
||||
$totalSalary = 0;
|
||||
$totalCommission = 0;
|
||||
|
||||
foreach ($months as $month) {
|
||||
$totalUserSales += $month->dailySales()->sum('user_sales');
|
||||
$totalSystemSales += $month->dailySales()->sum('system_sales');
|
||||
$totalExpenses += $month->expenses()->sum('amount');
|
||||
$totalSalary += $user->monthly_salary;
|
||||
$totalCommission += ($month->dailySales()->sum('system_sales') * $user->commission_percentage) / 100;
|
||||
}
|
||||
|
||||
return [
|
||||
'user_id' => $user->id,
|
||||
'year' => $year,
|
||||
'months_count' => $months->count(),
|
||||
'total_user_sales' => round($totalUserSales, 2),
|
||||
'total_system_sales' => round($totalSystemSales, 2),
|
||||
'total_expenses' => round($totalExpenses, 2),
|
||||
'total_salary' => round($totalSalary, 2),
|
||||
'total_commission' => round($totalCommission, 2),
|
||||
'total_earning' => round($totalSalary + $totalCommission - $totalExpenses, 2),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener número del mes por nombre
|
||||
*/
|
||||
private static function getMonthNumber(string $monthName): int
|
||||
{
|
||||
$months = [
|
||||
'Enero' => 1, 'Febrero' => 2, 'Marzo' => 3, 'Abril' => 4,
|
||||
'Mayo' => 5, 'Junio' => 6, 'Julio' => 7, 'Agosto' => 8,
|
||||
'Septiembre' => 9, 'Octubre' => 10, 'Noviembre' => 11, 'Diciembre' => 12
|
||||
];
|
||||
|
||||
return $months[$monthName] ?? 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener último día del mes
|
||||
*/
|
||||
private static function getLastDayOfMonth(string $monthName, int $year): int
|
||||
{
|
||||
$monthNumber = self::getMonthNumber($monthName);
|
||||
return (int) date('t', mktime(0, 0, 0, $monthNumber, 1, $year));
|
||||
}
|
||||
}
|
||||
300
app/Services/TelegramBotService.php
Executable file
300
app/Services/TelegramBotService.php
Executable file
@@ -0,0 +1,300 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\TelegramAccount;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class TelegramBotService
|
||||
{
|
||||
private ?string $botToken;
|
||||
private ?string $webhookUrl;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->botToken = config('services.telegram.bot_token', env('TELEGRAM_BOT_TOKEN'));
|
||||
|
||||
$appUrl = config('app.url', env('APP_URL', 'http://nomina-pegaso.casa'));
|
||||
$this->webhookUrl = rtrim($appUrl, '/') . '/telegram/webhook';
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesar actualización recibida del webhook
|
||||
*/
|
||||
public function handleUpdate(array $update): array
|
||||
{
|
||||
if (!isset($update['message'])) {
|
||||
return ['ok' => false, 'error' => 'No message found'];
|
||||
}
|
||||
|
||||
$message = $update['message'];
|
||||
$chatId = $message['chat']['id'];
|
||||
$text = $message['text'] ?? '';
|
||||
$from = $message['from'] ?? [];
|
||||
|
||||
Log::info('Telegram update received', [
|
||||
'chat_id' => $chatId,
|
||||
'text' => $text,
|
||||
'from' => $from
|
||||
]);
|
||||
|
||||
// Verificar si el usuario está verificado
|
||||
$telegramAccount = TelegramAccount::where('chat_id', $chatId)->first();
|
||||
|
||||
if (!$telegramAccount || !$telegramAccount->is_verified) {
|
||||
return $this->handleUnverifiedUser($chatId, $text);
|
||||
}
|
||||
|
||||
// Procesar comandos del usuario verificado
|
||||
return $this->handleCommand($telegramAccount->user, $text, $chatId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manejar usuario no verificado
|
||||
*/
|
||||
private function handleUnverifiedUser(string $chatId, string $text): array
|
||||
{
|
||||
// Si es un código de verificación
|
||||
if (strlen($text) === 6 && is_numeric($text)) {
|
||||
$telegramAccount = TelegramAccount::where('chat_id', $chatId)
|
||||
->where('verification_code', $text)
|
||||
->first();
|
||||
|
||||
if ($telegramAccount) {
|
||||
$telegramAccount->update([
|
||||
'is_verified' => true,
|
||||
'verification_code' => null
|
||||
]);
|
||||
|
||||
$user = $telegramAccount->user;
|
||||
$this->sendMessage($chatId, "¡Verificación exitosa! Tu cuenta de Telegram está vinculada a {$user->name}. Ahora recibirás notificaciones de tus comisiones.");
|
||||
|
||||
return ['ok' => true, 'verified' => true];
|
||||
} else {
|
||||
$this->sendMessage($chatId, "Código de verificación inválido. Por favor intenta con el código correcto.");
|
||||
return ['ok' => true, 'verified' => false];
|
||||
}
|
||||
}
|
||||
|
||||
// Mensaje de bienvenida para usuarios no verificados
|
||||
$this->sendMessage($chatId, "¡Hola! Para usar este bot necesitas verificar tu cuenta.\n\nPor favor ingresa el código de verificación de 6 dígitos que encontrarás en la sección de Telegram de tu panel de usuario.");
|
||||
|
||||
return ['ok' => true, 'verified' => false];
|
||||
}
|
||||
|
||||
/**
|
||||
* Manejar comandos de usuario verificado
|
||||
*/
|
||||
private function handleCommand(User $user, string $text, string $chatId): array
|
||||
{
|
||||
$command = strtolower(trim($text));
|
||||
$commandParts = explode(' ', $command);
|
||||
$mainCommand = $commandParts[0] ?? '';
|
||||
|
||||
switch ($mainCommand) {
|
||||
case '/start':
|
||||
$this->sendMessage($chatId, "¡Hola {$user->name}! Usa /help para ver los comandos disponibles.");
|
||||
break;
|
||||
|
||||
case '/help':
|
||||
$this->sendHelp($chatId);
|
||||
break;
|
||||
|
||||
case '/mes':
|
||||
$this->showCurrentMonth($user, $chatId);
|
||||
break;
|
||||
|
||||
case '/ventas':
|
||||
$this->showSales($user, $chatId);
|
||||
break;
|
||||
|
||||
case '/gastos':
|
||||
$this->showExpenses($user, $chatId);
|
||||
break;
|
||||
|
||||
case '/resumen':
|
||||
$this->showSummary($user, $chatId);
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->sendMessage($chatId, "Comando no reconocido. Usa /help para ver los comandos disponibles.");
|
||||
}
|
||||
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enviar mensaje
|
||||
*/
|
||||
public function sendMessage(string $chatId, string $text): array
|
||||
{
|
||||
if (!$this->botToken) {
|
||||
Log::warning('Telegram bot token not configured');
|
||||
return ['ok' => false, 'error' => 'Bot token not configured'];
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::post("https://api.telegram.org/bot{$this->botToken}/sendMessage", [
|
||||
'chat_id' => $chatId,
|
||||
'text' => $text,
|
||||
'parse_mode' => 'Markdown'
|
||||
]);
|
||||
|
||||
return $response->json();
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Telegram send message error', ['error' => $e->getMessage()]);
|
||||
return ['ok' => false, 'error' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enviar mensaje de ayuda
|
||||
*/
|
||||
private function sendHelp(string $chatId): void
|
||||
{
|
||||
$text = "📋 *Comandos disponibles:*\n\n" .
|
||||
"• /start - Iniciar bot\n" .
|
||||
"• /help - Mostrar ayuda\n" .
|
||||
"• /mes - Ver mes actual\n" .
|
||||
"• /ventas - Ver ventas del mes\n" .
|
||||
"• /gastos - Ver gastos del mes\n" .
|
||||
"• /resumen - Resumen de comisiones\n";
|
||||
|
||||
$this->sendMessage($chatId, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostrar mes actual
|
||||
*/
|
||||
private function showCurrentMonth(User $user, string $chatId): void
|
||||
{
|
||||
$month = $user->getCurrentMonth();
|
||||
|
||||
if (!$month) {
|
||||
$this->sendMessage($chatId, "No tienes ningún mes abierto actualmente.");
|
||||
return;
|
||||
}
|
||||
|
||||
$statusText = match($month->status) {
|
||||
'open' => '🟢 Abierto',
|
||||
'closed' => '🟡 Cerrado',
|
||||
'paid' => '✅ Pagado',
|
||||
default => 'Desconocido'
|
||||
};
|
||||
|
||||
$text = "📅 *Mes Actual*\n\n" .
|
||||
"• *Nombre:* {$month->name} {$month->year}\n" .
|
||||
"• *Estado:* {$statusText}";
|
||||
|
||||
$this->sendMessage($chatId, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostrar ventas del mes
|
||||
*/
|
||||
private function showSales(User $user, string $chatId): void
|
||||
{
|
||||
$month = $user->getCurrentMonth();
|
||||
|
||||
if (!$month) {
|
||||
$this->sendMessage($chatId, "No tienes ningún mes abierto actualmente.");
|
||||
return;
|
||||
}
|
||||
|
||||
$totalUserSales = $month->dailySales()->sum('user_sales');
|
||||
$totalSystemSales = $month->dailySales()->sum('system_sales');
|
||||
$diff = $totalUserSales - $totalSystemSales;
|
||||
|
||||
$text = "💰 *Ventas del Mes*\n\n" .
|
||||
"• *Usuario:* $" . number_format($totalUserSales, 2) . "\n" .
|
||||
"• *Sistema:* $" . number_format($totalSystemSales, 2) . "\n" .
|
||||
"• *Diferencia:* $" . number_format($diff, 2);
|
||||
|
||||
$this->sendMessage($chatId, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostrar gastos del mes
|
||||
*/
|
||||
private function showExpenses(User $user, string $chatId): void
|
||||
{
|
||||
$month = $user->getCurrentMonth();
|
||||
|
||||
if (!$month) {
|
||||
$this->sendMessage($chatId, "No tienes ningún mes abierto actualmente.");
|
||||
return;
|
||||
}
|
||||
|
||||
$totalExpenses = $month->expenses()->sum('amount');
|
||||
$expensesList = $month->expenses()->latest()->limit(5)->get();
|
||||
|
||||
$text = "📝 *Gastos del Mes*\n\n" .
|
||||
"• *Total:* $" . number_format($totalExpenses, 2) . "\n\n";
|
||||
|
||||
if ($expensesList->count() > 0) {
|
||||
$text .= "Últimos gastos:\n";
|
||||
foreach ($expensesList as $expense) {
|
||||
$text .= "• {$expense->description}: \$" . number_format($expense->amount, 2) . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$this->sendMessage($chatId, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostrar resumen de comisiones
|
||||
*/
|
||||
private function showSummary(User $user, string $chatId): void
|
||||
{
|
||||
$month = $user->getCurrentMonth();
|
||||
|
||||
if (!$month) {
|
||||
$this->sendMessage($chatId, "No tienes ningún mes abierto actualmente.");
|
||||
return;
|
||||
}
|
||||
|
||||
$data = CommissionCalculator::calculateForMonth($user, $month);
|
||||
|
||||
$text = "💵 *Resumen de Comisiones*\n\n" .
|
||||
"• *Mes:* {$data['month_name']}\n" .
|
||||
"• *Ventas Sistema:* \$" . number_format($data['total_system_sales'], 2) . "\n" .
|
||||
"• *Comisión ({$data['commission_percentage']}%):* \$" . number_format($data['commission_amount'], 2) . "\n" .
|
||||
"• *Salario:* \$" . number_format($data['monthly_salary'], 2) . "\n" .
|
||||
"• *Gastos:* \$" . number_format($data['total_expenses'], 2) . "\n" .
|
||||
"• *Total a Recibir:* \$" . number_format($data['total_earning'], 2);
|
||||
|
||||
$this->sendMessage($chatId, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generar código de verificación
|
||||
*/
|
||||
public static function generateVerificationCode(): string
|
||||
{
|
||||
return str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configurar webhook
|
||||
*/
|
||||
public function setWebhook(): bool
|
||||
{
|
||||
if (!$this->botToken || !$this->webhookUrl) {
|
||||
Log::warning('Cannot set webhook: missing configuration');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::post("https://api.telegram.org/bot{$this->botToken}/setWebhook", [
|
||||
'url' => $this->webhookUrl
|
||||
]);
|
||||
|
||||
return $response->json('ok', false);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Telegram set webhook error', ['error' => $e->getMessage()]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user