webmoney/backend/app/Http/Controllers/Api/AssetAccountController.php
marcoitaloesp-ai 9c9d6443e7
v1.57.0: Redesign category modals + i18n updates + demo transactions fix
- Redesigned category create/edit modal with elegant wizard-style UI
- Redesigned batch categorization modal with visual cards and better preview
- Added missing i18n translations (common.continue, creating, remove)
- Added budgets.general and wizard translations for ES, PT-BR, EN
- Fixed 3 demo user transactions that were missing categories
2025-12-18 19:06:07 +00:00

421 lines
14 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\AssetAccount;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class AssetAccountController extends Controller
{
/**
* Listar todos os ativos do usuário
*/
public function index(Request $request): JsonResponse
{
$query = AssetAccount::where('user_id', Auth::id());
// Filtros
if ($request->has('asset_type') && $request->asset_type) {
$query->where('asset_type', $request->asset_type);
}
if ($request->has('status') && $request->status) {
$query->where('status', $request->status);
}
if ($request->has('search') && $request->search) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('description', 'like', "%{$search}%")
->orWhere('document_number', 'like', "%{$search}%");
});
}
// Ordenação
$sortBy = $request->get('sort_by', 'created_at');
$sortDir = $request->get('sort_dir', 'desc');
$query->orderBy($sortBy, $sortDir);
// Paginação ou todos
if ($request->has('per_page')) {
$assets = $query->paginate($request->per_page);
} else {
$assets = $query->get();
}
return response()->json([
'success' => true,
'data' => $assets,
]);
}
/**
* Criar novo ativo
*/
public function store(Request $request): JsonResponse
{
$validator = Validator::make($request->all(), [
'asset_type' => ['required', Rule::in(array_keys(AssetAccount::ASSET_TYPES))],
'name' => 'required|string|max:255',
'description' => 'nullable|string',
'currency' => 'required|string|size:3',
'color' => 'nullable|string|max:7',
'acquisition_value' => 'required|numeric|min:0',
'current_value' => 'required|numeric|min:0',
'acquisition_date' => 'nullable|date',
// Campos opcionais conforme tipo
'property_type' => 'nullable|string',
'investment_type' => 'nullable|string',
'depreciation_method' => 'nullable|string',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'errors' => $validator->errors(),
], 422);
}
$data = $request->all();
$data['user_id'] = Auth::id();
$data['business_id'] = $request->business_id ?? Auth::user()->businesses()->first()?->id;
$asset = AssetAccount::create($data);
return response()->json([
'success' => true,
'message' => 'Activo creado con éxito',
'data' => $asset,
], 201);
}
/**
* Ver um ativo específico
*/
public function show(AssetAccount $assetAccount): JsonResponse
{
// Verificar se pertence ao usuário
if ($assetAccount->user_id !== Auth::id()) {
return response()->json([
'success' => false,
'message' => 'No autorizado',
], 403);
}
return response()->json([
'success' => true,
'data' => $assetAccount->load('linkedLiability'),
]);
}
/**
* Atualizar ativo
*/
public function update(Request $request, AssetAccount $assetAccount): JsonResponse
{
if ($assetAccount->user_id !== Auth::id()) {
return response()->json([
'success' => false,
'message' => 'No autorizado',
], 403);
}
$validator = Validator::make($request->all(), [
'asset_type' => ['nullable', Rule::in(array_keys(AssetAccount::ASSET_TYPES))],
'name' => 'nullable|string|max:255',
'current_value' => 'nullable|numeric|min:0',
'status' => ['nullable', Rule::in(array_keys(AssetAccount::STATUSES))],
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'errors' => $validator->errors(),
], 422);
}
$assetAccount->update($request->all());
return response()->json([
'success' => true,
'message' => 'Activo actualizado con éxito',
'data' => $assetAccount->fresh(),
]);
}
/**
* Deletar ativo
*/
public function destroy(AssetAccount $assetAccount): JsonResponse
{
if ($assetAccount->user_id !== Auth::id()) {
return response()->json([
'success' => false,
'message' => 'No autorizado',
], 403);
}
$assetAccount->delete();
return response()->json([
'success' => true,
'message' => 'Activo eliminado con éxito',
]);
}
/**
* Retornar tipos de ativos e opções para o wizard
*/
public function assetTypes(): JsonResponse
{
return response()->json([
'success' => true,
'data' => AssetAccount::ASSET_TYPES,
'property_types' => AssetAccount::PROPERTY_TYPES,
'investment_types' => AssetAccount::INVESTMENT_TYPES,
'depreciation_methods' => AssetAccount::DEPRECIATION_METHODS,
'index_types' => AssetAccount::INDEX_TYPES,
'statuses' => AssetAccount::STATUSES,
]);
}
/**
* Criar ativo via wizard
*/
public function storeWithWizard(Request $request): JsonResponse
{
$rules = [
// Step 1 - Tipo
'asset_type' => ['required', Rule::in(array_keys(AssetAccount::ASSET_TYPES))],
// Step 2 - Dados básicos
'name' => 'required|string|max:255',
'description' => 'nullable|string|max:1000',
'currency' => 'required|string|size:3',
'color' => 'nullable|string|max:7',
// Step 3 - Valores
'acquisition_value' => 'required|numeric|min:0',
'current_value' => 'required|numeric|min:0',
'acquisition_date' => 'nullable|date',
// Depreciação
'is_depreciable' => 'nullable|boolean',
'depreciation_method' => ['nullable', Rule::in(array_keys(AssetAccount::DEPRECIATION_METHODS))],
'useful_life_years' => 'nullable|numeric|min:0.5|max:100',
'residual_value' => 'nullable|numeric|min:0',
// Imóveis
'property_type' => ['nullable', Rule::in(array_keys(AssetAccount::PROPERTY_TYPES))],
'address' => 'nullable|string|max:500',
'city' => 'nullable|string|max:100',
'state' => 'nullable|string|max:100',
'postal_code' => 'nullable|string|max:20',
'country' => 'nullable|string|size:2',
'property_area_m2' => 'nullable|numeric|min:0',
'registry_number' => 'nullable|string|max:100',
// Veículos
'vehicle_brand' => 'nullable|string|max:100',
'vehicle_model' => 'nullable|string|max:100',
'vehicle_year' => 'nullable|integer|min:1900|max:2100',
'vehicle_plate' => 'nullable|string|max:20',
'vehicle_vin' => 'nullable|string|max:50',
'vehicle_mileage' => 'nullable|integer|min:0',
// Investimentos
'investment_type' => ['nullable', Rule::in(array_keys(AssetAccount::INVESTMENT_TYPES))],
'institution' => 'nullable|string|max:100',
'account_number' => 'nullable|string|max:100',
'quantity' => 'nullable|integer|min:0',
'unit_price' => 'nullable|numeric|min:0',
'ticker' => 'nullable|string|max:20',
'maturity_date' => 'nullable|date',
'interest_rate' => 'nullable|numeric|min:0|max:100',
'index_type' => ['nullable', Rule::in(array_keys(AssetAccount::INDEX_TYPES))],
// Equipamentos
'equipment_brand' => 'nullable|string|max:100',
'equipment_model' => 'nullable|string|max:100',
'serial_number' => 'nullable|string|max:100',
'warranty_expiry' => 'nullable|date',
// Recebíveis
'debtor_name' => 'nullable|string|max:200',
'debtor_document' => 'nullable|string|max:50',
'receivable_due_date' => 'nullable|date',
'receivable_amount' => 'nullable|numeric|min:0',
// Garantias
'is_collateral' => 'nullable|boolean',
'collateral_for' => 'nullable|string|max:200',
'linked_liability_id' => 'nullable|integer|exists:liability_accounts,id',
// Seguros
'has_insurance' => 'nullable|boolean',
'insurance_company' => 'nullable|string|max:100',
'insurance_policy' => 'nullable|string|max:100',
'insurance_value' => 'nullable|numeric|min:0',
'insurance_expiry' => 'nullable|date',
// Gestão
'alert_days_before' => 'nullable|integer|min:0|max:365',
'internal_responsible' => 'nullable|string|max:200',
'internal_notes' => 'nullable|string|max:2000',
'document_number' => 'nullable|string|max:100',
];
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
return response()->json([
'success' => false,
'errors' => $validator->errors(),
], 422);
}
// Criar o ativo
$data = $validator->validated();
$data['user_id'] = Auth::id();
$data['business_id'] = $request->business_id ?? Auth::user()->businesses()->first()?->id;
$data['status'] = 'active';
$asset = AssetAccount::create($data);
return response()->json([
'success' => true,
'message' => 'Activo creado con éxito',
'data' => $asset,
], 201);
}
/**
* Resumo dos ativos do usuário
*/
public function summary(): JsonResponse
{
$userId = Auth::id();
$summary = [
'total_assets' => AssetAccount::where('user_id', $userId)->active()->count(),
'total_value' => AssetAccount::where('user_id', $userId)->active()->sum('current_value'),
'total_acquisition' => AssetAccount::where('user_id', $userId)->active()->sum('acquisition_value'),
'by_type' => [],
];
// Agrupar por tipo
$byType = AssetAccount::where('user_id', $userId)
->active()
->selectRaw('asset_type, COUNT(*) as count, SUM(current_value) as total_value')
->groupBy('asset_type')
->get();
foreach ($byType as $item) {
$summary['by_type'][$item->asset_type] = [
'name' => AssetAccount::ASSET_TYPES[$item->asset_type]['name'] ?? $item->asset_type,
'count' => $item->count,
'total_value' => $item->total_value,
];
}
// Ganho/perda total
$summary['total_gain_loss'] = $summary['total_value'] - $summary['total_acquisition'];
$summary['total_gain_loss_percent'] = $summary['total_acquisition'] > 0
? (($summary['total_value'] - $summary['total_acquisition']) / $summary['total_acquisition']) * 100
: 0;
return response()->json([
'success' => true,
'data' => $summary,
]);
}
/**
* Atualizar valor de mercado de um ativo
*/
public function updateValue(Request $request, AssetAccount $assetAccount): JsonResponse
{
if ($assetAccount->user_id !== Auth::id()) {
return response()->json([
'success' => false,
'message' => 'No autorizado',
], 403);
}
$validator = Validator::make($request->all(), [
'current_value' => 'required|numeric|min:0',
'note' => 'nullable|string|max:500',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'errors' => $validator->errors(),
], 422);
}
$assetAccount->update([
'current_value' => $request->current_value,
]);
// Adicionar nota se fornecida
if ($request->note) {
$notes = $assetAccount->internal_notes ?? '';
$notes .= "\n[" . now()->format('d/m/Y') . "] Valor actualizado: {$request->note}";
$assetAccount->update(['internal_notes' => trim($notes)]);
}
return response()->json([
'success' => true,
'message' => 'Valor actualizado con éxito',
'data' => $assetAccount->fresh(),
]);
}
/**
* Registrar venda/baixa de ativo
*/
public function dispose(Request $request, AssetAccount $assetAccount): JsonResponse
{
if ($assetAccount->user_id !== Auth::id()) {
return response()->json([
'success' => false,
'message' => 'No autorizado',
], 403);
}
$validator = Validator::make($request->all(), [
'disposal_date' => 'required|date',
'disposal_value' => 'required|numeric|min:0',
'disposal_reason' => 'nullable|string|max:200',
'status' => ['required', Rule::in(['sold', 'written_off', 'depreciated'])],
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'errors' => $validator->errors(),
], 422);
}
$assetAccount->update([
'status' => $request->status,
'disposal_date' => $request->disposal_date,
'disposal_value' => $request->disposal_value,
'disposal_reason' => $request->disposal_reason,
]);
return response()->json([
'success' => true,
'message' => 'Baja registrada con éxito',
'data' => $assetAccount->fresh(),
]);
}
}