731 lines
26 KiB
PHP
731 lines
26 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Category;
|
|
use App\Models\CategoryKeyword;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Validation\Rule;
|
|
|
|
class CategoryController extends Controller
|
|
{
|
|
/**
|
|
* Listar todas as categorias do usuário (hierárquico)
|
|
*/
|
|
public function index(Request $request): JsonResponse
|
|
{
|
|
$query = Category::where('user_id', Auth::id())
|
|
->with(['keywords', 'children.keywords']);
|
|
|
|
if ($request->has('type')) {
|
|
$query->ofType($request->type);
|
|
}
|
|
|
|
if ($request->has('is_active')) {
|
|
$query->where('is_active', $request->boolean('is_active'));
|
|
}
|
|
|
|
// Se flat=true, retorna todas as categorias
|
|
if ($request->boolean('flat')) {
|
|
$categories = $query->orderBy('name')->get();
|
|
} else {
|
|
// Retorna apenas categorias raiz com filhas aninhadas
|
|
$categories = $query->root()->orderBy('order')->orderBy('name')->get();
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $categories,
|
|
'types' => Category::TYPES,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Criar nova categoria
|
|
*/
|
|
public function store(Request $request): JsonResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'name' => 'required|string|max:100',
|
|
'parent_id' => 'nullable|exists:categories,id',
|
|
'type' => ['nullable', Rule::in(array_keys(Category::TYPES))],
|
|
'description' => 'nullable|string',
|
|
'color' => 'nullable|string|max:7',
|
|
'icon' => 'nullable|string|max:50',
|
|
'order' => 'nullable|integer',
|
|
'is_active' => 'nullable|boolean',
|
|
'keywords' => 'nullable|array',
|
|
'keywords.*' => 'string|max:100',
|
|
]);
|
|
|
|
// Verificar se parent_id pertence ao usuário
|
|
if (!empty($validated['parent_id'])) {
|
|
$parent = Category::where('user_id', Auth::id())
|
|
->findOrFail($validated['parent_id']);
|
|
// Herdar tipo do pai se não especificado
|
|
if (empty($validated['type'])) {
|
|
$validated['type'] = $parent->type;
|
|
}
|
|
}
|
|
|
|
$keywords = $validated['keywords'] ?? [];
|
|
unset($validated['keywords']);
|
|
|
|
$validated['user_id'] = Auth::id();
|
|
$validated['type'] = $validated['type'] ?? Category::TYPE_EXPENSE;
|
|
|
|
DB::beginTransaction();
|
|
try {
|
|
$category = Category::create($validated);
|
|
|
|
// Adicionar palavras-chave
|
|
foreach ($keywords as $keyword) {
|
|
$category->keywords()->create([
|
|
'keyword' => trim($keyword),
|
|
'is_case_sensitive' => false,
|
|
'is_active' => true,
|
|
]);
|
|
}
|
|
|
|
DB::commit();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Categoria criada com sucesso',
|
|
'data' => $category->load(['keywords', 'parent', 'children']),
|
|
], 201);
|
|
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Erro ao criar categoria: ' . $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Exibir uma categoria específica
|
|
*/
|
|
public function show(int $id): JsonResponse
|
|
{
|
|
$category = Category::where('user_id', Auth::id())
|
|
->with(['keywords', 'parent', 'children.keywords'])
|
|
->findOrFail($id);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $category,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Atualizar uma categoria
|
|
*/
|
|
public function update(Request $request, int $id): JsonResponse
|
|
{
|
|
$category = Category::where('user_id', Auth::id())->findOrFail($id);
|
|
|
|
$validated = $request->validate([
|
|
'name' => 'sometimes|required|string|max:100',
|
|
'parent_id' => 'nullable|exists:categories,id',
|
|
'type' => ['nullable', Rule::in(array_keys(Category::TYPES))],
|
|
'description' => 'nullable|string',
|
|
'color' => 'nullable|string|max:7',
|
|
'icon' => 'nullable|string|max:50',
|
|
'order' => 'nullable|integer',
|
|
'is_active' => 'nullable|boolean',
|
|
'keywords' => 'nullable|array',
|
|
'keywords.*' => 'string|max:100',
|
|
]);
|
|
|
|
// Verificar se parent_id pertence ao usuário e não é a própria categoria
|
|
if (!empty($validated['parent_id'])) {
|
|
if ($validated['parent_id'] == $id) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Uma categoria não pode ser pai de si mesma',
|
|
], 422);
|
|
}
|
|
Category::where('user_id', Auth::id())->findOrFail($validated['parent_id']);
|
|
}
|
|
|
|
$keywords = $validated['keywords'] ?? null;
|
|
unset($validated['keywords']);
|
|
|
|
DB::beginTransaction();
|
|
try {
|
|
$category->update($validated);
|
|
|
|
// Se keywords foram fornecidas, sincronizar
|
|
if ($keywords !== null) {
|
|
$category->keywords()->delete();
|
|
foreach ($keywords as $keyword) {
|
|
$category->keywords()->create([
|
|
'keyword' => trim($keyword),
|
|
'is_case_sensitive' => false,
|
|
'is_active' => true,
|
|
]);
|
|
}
|
|
}
|
|
|
|
DB::commit();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Categoria atualizada com sucesso',
|
|
'data' => $category->fresh()->load(['keywords', 'parent', 'children']),
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Erro ao atualizar categoria: ' . $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deletar uma categoria (soft delete)
|
|
*/
|
|
public function destroy(int $id): JsonResponse
|
|
{
|
|
$category = Category::where('user_id', Auth::id())->findOrFail($id);
|
|
|
|
// Se tem filhas, mover para nível raiz
|
|
if ($category->children()->exists()) {
|
|
$category->children()->update(['parent_id' => null]);
|
|
}
|
|
|
|
$category->keywords()->delete();
|
|
$category->delete();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Categoria excluída com sucesso',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Adicionar palavra-chave a uma categoria
|
|
*/
|
|
public function addKeyword(Request $request, int $id): JsonResponse
|
|
{
|
|
$category = Category::where('user_id', Auth::id())->findOrFail($id);
|
|
|
|
$validated = $request->validate([
|
|
'keyword' => 'required|string|max:100',
|
|
'is_case_sensitive' => 'nullable|boolean',
|
|
]);
|
|
|
|
$keyword = $category->keywords()->create([
|
|
'keyword' => trim($validated['keyword']),
|
|
'is_case_sensitive' => $validated['is_case_sensitive'] ?? false,
|
|
'is_active' => true,
|
|
]);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Palavra-chave adicionada com sucesso',
|
|
'data' => $keyword,
|
|
], 201);
|
|
}
|
|
|
|
/**
|
|
* Remover palavra-chave de uma categoria
|
|
*/
|
|
public function removeKeyword(int $id, int $keywordId): JsonResponse
|
|
{
|
|
$category = Category::where('user_id', Auth::id())->findOrFail($id);
|
|
$keyword = $category->keywords()->findOrFail($keywordId);
|
|
$keyword->delete();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Palavra-chave removida com sucesso',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Encontrar categoria por texto (usando palavras-chave)
|
|
*/
|
|
public function matchByText(Request $request): JsonResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'text' => 'required|string',
|
|
'type' => ['nullable', Rule::in(array_keys(Category::TYPES))],
|
|
]);
|
|
|
|
$text = $validated['text'];
|
|
$textLower = strtolower($text);
|
|
|
|
$query = CategoryKeyword::whereHas('category', function ($query) use ($validated) {
|
|
$query->where('user_id', Auth::id())->where('is_active', true);
|
|
if (!empty($validated['type'])) {
|
|
$query->ofType($validated['type']);
|
|
}
|
|
})
|
|
->where('is_active', true)
|
|
->with('category');
|
|
|
|
$keywords = $query->get();
|
|
|
|
$matches = [];
|
|
foreach ($keywords as $keyword) {
|
|
$searchText = $keyword->is_case_sensitive ? $text : $textLower;
|
|
$searchKeyword = $keyword->is_case_sensitive ? $keyword->keyword : strtolower($keyword->keyword);
|
|
|
|
if (str_contains($searchText, $searchKeyword)) {
|
|
$matches[] = [
|
|
'category' => $keyword->category,
|
|
'matched_keyword' => $keyword->keyword,
|
|
];
|
|
}
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $matches,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Reordenar categorias
|
|
*/
|
|
public function reorder(Request $request): JsonResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'orders' => 'required|array',
|
|
'orders.*.id' => 'required|exists:categories,id',
|
|
'orders.*.order' => 'required|integer',
|
|
]);
|
|
|
|
DB::beginTransaction();
|
|
try {
|
|
foreach ($validated['orders'] as $orderData) {
|
|
Category::where('user_id', Auth::id())
|
|
->where('id', $orderData['id'])
|
|
->update(['order' => $orderData['order']]);
|
|
}
|
|
DB::commit();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Ordem atualizada com sucesso',
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Erro ao reordenar categorias: ' . $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Categorizar transações em lote baseado em palavras-chave
|
|
*/
|
|
public function categorizeBatch(Request $request): JsonResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'only_uncategorized' => 'nullable|boolean',
|
|
'transaction_ids' => 'nullable|array',
|
|
'transaction_ids.*' => 'integer|exists:transactions,id',
|
|
]);
|
|
|
|
$onlyUncategorized = $validated['only_uncategorized'] ?? true;
|
|
$transactionIds = $validated['transaction_ids'] ?? null;
|
|
|
|
// Buscar todas as categorias com keywords do usuário
|
|
$categories = Category::where('user_id', Auth::id())
|
|
->whereNotNull('parent_id') // Apenas subcategorias
|
|
->with('keywords')
|
|
->where('is_active', true)
|
|
->get();
|
|
|
|
// Construir mapa de keywords -> categoria
|
|
$keywordMap = [];
|
|
foreach ($categories as $category) {
|
|
foreach ($category->keywords as $keyword) {
|
|
if ($keyword->is_active) {
|
|
$keywordMap[strtoupper($keyword->keyword)] = $category->id;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (empty($keywordMap)) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Nenhuma palavra-chave configurada nas categorias',
|
|
'data' => [
|
|
'categorized' => 0,
|
|
'skipped' => 0,
|
|
]
|
|
]);
|
|
}
|
|
|
|
// Buscar transações para categorizar
|
|
$query = \App\Models\Transaction::where('user_id', Auth::id());
|
|
|
|
if ($onlyUncategorized) {
|
|
$query->whereNull('category_id');
|
|
}
|
|
|
|
if ($transactionIds) {
|
|
$query->whereIn('id', $transactionIds);
|
|
}
|
|
|
|
$transactions = $query->get();
|
|
|
|
$categorized = 0;
|
|
$skipped = 0;
|
|
|
|
foreach ($transactions as $transaction) {
|
|
// Usar description ou original_description
|
|
$text = strtoupper($transaction->original_description ?? $transaction->description ?? '');
|
|
|
|
$matched = false;
|
|
foreach ($keywordMap as $keyword => $categoryId) {
|
|
if (str_contains($text, $keyword)) {
|
|
$transaction->category_id = $categoryId;
|
|
$transaction->save();
|
|
$categorized++;
|
|
$matched = true;
|
|
break; // Usar primeira keyword que bater
|
|
}
|
|
}
|
|
|
|
if (!$matched) {
|
|
$skipped++;
|
|
}
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => "Categorização em lote concluída",
|
|
'data' => [
|
|
'categorized' => $categorized,
|
|
'skipped' => $skipped,
|
|
'total_keywords' => count($keywordMap),
|
|
]
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Preview da categorização em lote (sem salvar)
|
|
*/
|
|
public function categorizeBatchPreview(Request $request): JsonResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'only_uncategorized' => 'nullable|boolean',
|
|
'preview_limit' => 'nullable|integer|min:1|max:100',
|
|
'filters' => 'nullable|array',
|
|
'filters.account_id' => 'nullable|integer',
|
|
'filters.category_id' => 'nullable|integer',
|
|
'filters.cost_center_id' => 'nullable|integer',
|
|
'filters.type' => 'nullable|string|in:credit,debit',
|
|
'filters.status' => 'nullable|string|in:pending,completed,cancelled',
|
|
'filters.start_date' => 'nullable|date',
|
|
'filters.end_date' => 'nullable|date',
|
|
'filters.search' => 'nullable|string|max:255',
|
|
]);
|
|
|
|
$onlyUncategorized = $validated['only_uncategorized'] ?? true;
|
|
$previewLimit = $validated['preview_limit'] ?? 50;
|
|
$filters = $validated['filters'] ?? [];
|
|
|
|
// Buscar todas as categorias com keywords do usuário
|
|
$categories = Category::where('user_id', Auth::id())
|
|
->whereNotNull('parent_id')
|
|
->with(['keywords', 'parent'])
|
|
->where('is_active', true)
|
|
->get();
|
|
|
|
// Construir mapa de keywords -> categoria
|
|
$keywordMap = [];
|
|
$categoryNames = [];
|
|
foreach ($categories as $category) {
|
|
$categoryNames[$category->id] = ($category->parent ? $category->parent->name . ' > ' : '') . $category->name;
|
|
foreach ($category->keywords as $keyword) {
|
|
if ($keyword->is_active) {
|
|
$keywordMap[strtoupper($keyword->keyword)] = [
|
|
'category_id' => $category->id,
|
|
'category_name' => $categoryNames[$category->id],
|
|
'keyword' => $keyword->keyword,
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Buscar transações com filtros aplicados
|
|
$query = \App\Models\Transaction::where('user_id', Auth::id());
|
|
|
|
if ($onlyUncategorized) {
|
|
$query->whereNull('category_id');
|
|
}
|
|
|
|
// Aplicar filtros
|
|
if (!empty($filters['account_id'])) {
|
|
$query->where('account_id', $filters['account_id']);
|
|
}
|
|
if (!empty($filters['category_id'])) {
|
|
$query->where('category_id', $filters['category_id']);
|
|
}
|
|
if (!empty($filters['cost_center_id'])) {
|
|
$query->where('cost_center_id', $filters['cost_center_id']);
|
|
}
|
|
if (!empty($filters['type'])) {
|
|
$query->where('type', $filters['type']);
|
|
}
|
|
if (!empty($filters['status'])) {
|
|
$query->where('status', $filters['status']);
|
|
}
|
|
if (!empty($filters['start_date'])) {
|
|
$query->where(function ($q) use ($filters) {
|
|
$q->where('effective_date', '>=', $filters['start_date'])
|
|
->orWhere(function ($q2) use ($filters) {
|
|
$q2->whereNull('effective_date')
|
|
->where('planned_date', '>=', $filters['start_date']);
|
|
});
|
|
});
|
|
}
|
|
if (!empty($filters['end_date'])) {
|
|
$query->where(function ($q) use ($filters) {
|
|
$q->where('effective_date', '<=', $filters['end_date'])
|
|
->orWhere(function ($q2) use ($filters) {
|
|
$q2->whereNull('effective_date')
|
|
->where('planned_date', '<=', $filters['end_date']);
|
|
});
|
|
});
|
|
}
|
|
if (!empty($filters['search'])) {
|
|
$search = $filters['search'];
|
|
$query->where(function ($q) use ($search) {
|
|
$q->where('description', 'like', "%{$search}%")
|
|
->orWhere('original_description', 'like', "%{$search}%")
|
|
->orWhere('reference', 'like', "%{$search}%")
|
|
->orWhere('notes', 'like', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
$allTransactions = $query->get();
|
|
|
|
$preview = [];
|
|
$wouldCategorize = 0;
|
|
$wouldSkip = 0;
|
|
$transactionIds = [];
|
|
|
|
foreach ($allTransactions as $transaction) {
|
|
$text = strtoupper($transaction->original_description ?? $transaction->description ?? '');
|
|
|
|
$matched = null;
|
|
foreach ($keywordMap as $keyword => $info) {
|
|
if (str_contains($text, $keyword)) {
|
|
$matched = $info;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($matched) {
|
|
$wouldCategorize++;
|
|
$transactionIds[] = $transaction->id;
|
|
// Só adiciona preview até o limite
|
|
if (count($preview) < $previewLimit) {
|
|
$preview[] = [
|
|
'transaction_id' => $transaction->id,
|
|
'description' => $transaction->description,
|
|
'amount' => $transaction->amount ?? $transaction->planned_amount,
|
|
'matched_keyword' => $matched['keyword'],
|
|
'category_id' => $matched['category_id'],
|
|
'category_name' => $matched['category_name'],
|
|
];
|
|
}
|
|
} else {
|
|
$wouldSkip++;
|
|
}
|
|
}
|
|
|
|
// Contar total sem categoria (com filtros)
|
|
$totalUncategorized = $allTransactions->whereNull('category_id')->count();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'preview' => $preview,
|
|
'would_categorize' => $wouldCategorize,
|
|
'would_skip' => $wouldSkip,
|
|
'total_uncategorized' => $totalUncategorized,
|
|
'total_keywords' => count($keywordMap),
|
|
'total_filtered' => $allTransactions->count(),
|
|
'transaction_ids' => $transactionIds,
|
|
]
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Categorização em lote manual - aplicar categoria/centro de custo selecionados
|
|
*/
|
|
public function categorizeBatchManual(Request $request): JsonResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'category_id' => 'nullable|integer|exists:categories,id',
|
|
'cost_center_id' => 'nullable|integer|exists:cost_centers,id',
|
|
'filters' => 'nullable|array',
|
|
'filters.account_id' => 'nullable|integer',
|
|
'filters.type' => 'nullable|string|in:credit,debit',
|
|
'filters.status' => 'nullable|string|in:pending,completed,cancelled',
|
|
'filters.start_date' => 'nullable|date',
|
|
'filters.end_date' => 'nullable|date',
|
|
'filters.search' => 'nullable|string|max:255',
|
|
'add_keyword' => 'nullable|boolean',
|
|
]);
|
|
|
|
$categoryId = $validated['category_id'] ?? null;
|
|
$costCenterId = $validated['cost_center_id'] ?? null;
|
|
$filters = $validated['filters'] ?? [];
|
|
$addKeyword = $validated['add_keyword'] ?? false;
|
|
|
|
// Verificar se pelo menos uma opção foi selecionada
|
|
if (!$categoryId && !$costCenterId) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Selecione pelo menos uma categoria ou centro de custo',
|
|
], 400);
|
|
}
|
|
|
|
// Verificar se a categoria pertence ao usuário
|
|
if ($categoryId) {
|
|
$category = Category::where('id', $categoryId)
|
|
->where('user_id', Auth::id())
|
|
->first();
|
|
if (!$category) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Categoria não encontrada',
|
|
], 404);
|
|
}
|
|
}
|
|
|
|
// Verificar se o centro de custo pertence ao usuário
|
|
if ($costCenterId) {
|
|
$costCenter = \App\Models\CostCenter::where('id', $costCenterId)
|
|
->where('user_id', Auth::id())
|
|
->first();
|
|
if (!$costCenter) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Centro de custo não encontrado',
|
|
], 404);
|
|
}
|
|
}
|
|
|
|
// Construir query com filtros
|
|
$query = \App\Models\Transaction::where('user_id', Auth::id());
|
|
|
|
if (!empty($filters['account_id'])) {
|
|
$query->where('account_id', $filters['account_id']);
|
|
}
|
|
if (!empty($filters['type'])) {
|
|
$query->where('type', $filters['type']);
|
|
}
|
|
if (!empty($filters['status'])) {
|
|
$query->where('status', $filters['status']);
|
|
}
|
|
if (!empty($filters['start_date'])) {
|
|
$query->where(function ($q) use ($filters) {
|
|
$q->where('effective_date', '>=', $filters['start_date'])
|
|
->orWhere(function ($q2) use ($filters) {
|
|
$q2->whereNull('effective_date')
|
|
->where('planned_date', '>=', $filters['start_date']);
|
|
});
|
|
});
|
|
}
|
|
if (!empty($filters['end_date'])) {
|
|
$query->where(function ($q) use ($filters) {
|
|
$q->where('effective_date', '<=', $filters['end_date'])
|
|
->orWhere(function ($q2) use ($filters) {
|
|
$q2->whereNull('effective_date')
|
|
->where('planned_date', '<=', $filters['end_date']);
|
|
});
|
|
});
|
|
}
|
|
if (!empty($filters['search'])) {
|
|
$search = $filters['search'];
|
|
$query->where(function ($q) use ($search) {
|
|
$q->where('description', 'like', "%{$search}%")
|
|
->orWhere('original_description', 'like', "%{$search}%")
|
|
->orWhere('reference', 'like', "%{$search}%")
|
|
->orWhere('notes', 'like', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
// Atualizar transações
|
|
$updateData = [];
|
|
if ($categoryId) {
|
|
$updateData['category_id'] = $categoryId;
|
|
}
|
|
if ($costCenterId) {
|
|
$updateData['cost_center_id'] = $costCenterId;
|
|
}
|
|
|
|
$updated = $query->update($updateData);
|
|
|
|
// Adicionar keyword se solicitado e houver termo de busca
|
|
$keywordAdded = false;
|
|
$keywordText = null;
|
|
if ($addKeyword && !empty($filters['search']) && strlen(trim($filters['search'])) >= 2) {
|
|
$keywordText = trim($filters['search']);
|
|
|
|
// Adicionar keyword à categoria (se selecionada)
|
|
if ($categoryId) {
|
|
$existingCategoryKeyword = \App\Models\CategoryKeyword::where('category_id', $categoryId)
|
|
->whereRaw('UPPER(keyword) = ?', [strtoupper($keywordText)])
|
|
->first();
|
|
|
|
if (!$existingCategoryKeyword) {
|
|
\App\Models\CategoryKeyword::create([
|
|
'category_id' => $categoryId,
|
|
'keyword' => $keywordText,
|
|
'is_case_sensitive' => false,
|
|
'is_active' => true,
|
|
]);
|
|
$keywordAdded = true;
|
|
}
|
|
}
|
|
|
|
// Adicionar keyword ao centro de custo (se selecionado)
|
|
if ($costCenterId) {
|
|
$existingCostCenterKeyword = \App\Models\CostCenterKeyword::where('cost_center_id', $costCenterId)
|
|
->whereRaw('UPPER(keyword) = ?', [strtoupper($keywordText)])
|
|
->first();
|
|
|
|
if (!$existingCostCenterKeyword) {
|
|
\App\Models\CostCenterKeyword::create([
|
|
'cost_center_id' => $costCenterId,
|
|
'keyword' => $keywordText,
|
|
'is_case_sensitive' => false,
|
|
'is_active' => true,
|
|
]);
|
|
$keywordAdded = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Categorização em lote concluída',
|
|
'data' => [
|
|
'updated' => $updated,
|
|
'keyword_added' => $keywordAdded,
|
|
'keyword_text' => $keywordText,
|
|
]
|
|
]);
|
|
}
|
|
}
|