First Commit

This commit is contained in:
marco
2026-09-01 10:46:29 -03:00
commit bf96b77e9c
10000 changed files with 3508600 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
SysCMS Commercial License
Copyright (c) 2026 Marco Costa. All rights reserved.
This software is licensed, not sold. Permission is granted to use this software subject to the terms of the SysCMS Commercial License.
Redistribution, sublicensing, resale, or disclosure of this software, in whole or in part, is strictly prohibited without prior written permission from the author.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
# Company Module for SysCMS
Module for managing company information and settings.
## Features
- Company profile management
- Multi-company support
- Company-specific configurations
- Module enable/disable per company
## Installation
1. Upload the `company.zip` file through the module manager or extract it to the `modules/` directory.
2. Run the migrations to register the module and create the database tables.
3. Configure company settings in the admin panel.
## Routes
- Web routes: `routes/web.php`
## License
Copyright (c) 2026 Marco Costa. All rights reserved.
Licensed under the SysCMS Commercial License.
+692
View File
@@ -0,0 +1,692 @@
<?php
namespace Modules\Company\Controllers;
use App\Controllers\BaseAdminController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Modules\Company\Models\Company;
use App\Models\Module;
use App\Helpers\Log;
use App\Helpers\Crypto;
use Intervention\Image\ImageManagerStatic as Image;
use Kernel\License;
class CompanyController extends BaseAdminController
{
public function index(Request $request): Response
{
$user = $this->getCurrentUser();
// Allow access to SuperAdmin, Admin, or Member with company.view permission
$hasAccess = $user && (
$user->isSuperAdmin() ||
$user->isAdmin() ||
($user->isMember() && $user->hasPermission('company.view'))
);
if (!$hasAccess) {
return new RedirectResponse(url('admin/dashboard?error=unauthorized'));
}
if ($user->isSuperAdmin()) {
$companies = Company::all();
} else {
$companies = Company::where('bus_id', $user->users_bus_id)->get();
}
return $this->renderAdminView('company/views/admin/index.twig', [
'companies' => $companies,
'title' => 'Companies Management',
'can_create_company' => $user->isSuperAdmin() && License::canCreateCompany()
]);
}
public function settings(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user) {
return new RedirectResponse(url('login'));
}
if ($user->isSuperAdmin()) {
return new RedirectResponse(url('admin/companies'));
}
return $this->edit($request, $user->users_bus_id);
}
public function create(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new RedirectResponse(url('admin/dashboard?error=unauthorized'));
}
if (!License::canCreateCompany()) {
return new RedirectResponse(url('admin/companies?error=license_limit'));
}
$error = $request->query->get('error');
$errorMessage = $error ? $this->getErrorMessage($error) : null;
$modules = Module::active()->ordered()->get();
return $this->renderAdminView('company/views/admin/form.twig', [
'title' => 'Create Company',
'error' => $errorMessage,
'target_company' => null,
'modules' => $modules
]);
}
public function store(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new RedirectResponse(url('admin/dashboard?error=unauthorized'));
}
$currentUserId = $_SESSION['user_id'] ?? null;
$currentUserRole = $_SESSION['user_role'] ?? null;
$currentUserBus = $_SESSION['user_idbus'] ?? null;
$data = $request->request->all();
// Basic validation
if (empty($data['name']) || empty($data['email'])) {
return new RedirectResponse(url('admin/companies/create?error=missing_fields'));
}
// Email format validation
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
return new RedirectResponse(url('admin/companies/create?error=invalid_email'));
}
// Check if email exists
if (Company::where('bus_email', $data['email'])->exists()) {
return new RedirectResponse(url('admin/companies/create?error=email_exists'));
}
// Generate slug from name
$slug = strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $data['name'])));
// Check if slug exists
if (Company::where('bus_slug', $slug)->exists()) {
$slug .= '-' . time();
}
$company = new Company();
$company->bus_name = $data['name'];
$company->bus_slug = $slug;
$company->bus_description = $data['description'] ?? '';
$company->bus_email = $data['email'];
$company->bus_phone = $data['phone'] ?? '';
$company->bus_address = $data['address'] ?? '';
$company->bus_city = $data['city'] ?? '';
$company->bus_state = $data['state'] ?? '';
$company->bus_country = $data['country'] ?? '';
$company->bus_postal_code = $data['postal_code'] ?? '';
$company->bus_website = $data['website'] ?? '';
$company->bus_tax_id = $data['tax_id'] ?? '';
$company->bus_type = $data['bus_type'] ?? 'business';
$company->bus_status = $data['status'] ?? 'active';
$company->platform_name = $data['platform_name'] ?? '';
// Encrypt OpenRouter API Key if present
$newApiKey = $data['openrouter_api_key'] ?? '';
if (!empty($newApiKey) && strpos($newApiKey, '***') === false) {
$company->openrouter_api_key = Crypto::encrypt($newApiKey);
}
$company->ai_provider = $data['ai_provider'] ?? 'openrouter';
$company->ai_model = $data['ai_model'] ?? 'google/gemini-2.0-flash-001';
$company->ai_endpoint = $data['ai_endpoint'] ?? '';
// Settings are now handled via modules table
// $company->bus_settings = $data['bus_settings'] ?? [];
$company->save();
// Handle modules - ONLY Super Admin can change which modules a company has access to
if ($user->isSuperAdmin()) {
$modulesData = $request->request->all()['modules'] ?? [];
$syncData = [];
foreach ($modulesData as $moduleId => $settings) {
$syncData[$moduleId] = [
'bus_modules_is_enabled' => isset($settings['enabled']),
'bus_modules_enabled_by' => $currentUserId,
'bus_modules_enabled_at' => \Carbon\Carbon::now(),
'bus_modules_show_public_nav' => isset($settings['public']),
'bus_modules_show_dashboard_nav' => isset($settings['dashboard'])
];
}
if (!empty($syncData)) {
$company->modules()->sync($syncData);
} else {
$company->modules()->detach();
}
}
if ($request->files->has('logo')) {
$file = $request->files->get('logo');
if ($file && $file->isValid()) {
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (in_array($file->getMimeType(), $allowedMimes)) {
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/company/logos';
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$formatSetting = strtolower((string) get_env('IMG_FORMAT', 'webp'));
$format = $formatSetting === 'auto' ? 'webp' : $formatSetting;
$quality = env_int('IMG_QUALITY', 80);
$resolutionPercent = env_int('IMG_RESOLUTION', 100);
$image = Image::make($file->getPathname());
$width = $image->width();
$height = $image->height();
if ($resolutionPercent > 0 && $resolutionPercent < 100) {
$newW = (int) round($width * $resolutionPercent / 100);
$newH = (int) round($height * $resolutionPercent / 100);
$image->resize($newW, $newH, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
}
$ext = $format === 'jpeg' || $format === 'jpg' ? 'jpg' : ($format === 'png' ? 'png' : ($format === 'gif' ? 'gif' : 'webp'));
$filename = 'logo_' . $company->bus_id . '_' . time() . '.' . $ext;
if ($format === 'jpeg' || $format === 'jpg') {
$image->interlace(true);
$image->encode('jpeg', max(1, min(100, $quality)));
} elseif ($format === 'png') {
$pngCompression = max(0, min(9, (int) round((100 - max(1, min(100, $quality))) / 10)));
$image->encode('png', $pngCompression);
} elseif ($format === 'gif') {
$image->encode('gif');
} else {
$image->encode('webp', max(1, min(100, $quality)));
}
$image->save($uploadDir . '/' . $filename);
// Delete old logo if exists (check both old and new directories)
if ($company->bus_logo) {
$oldDirs = [
dirname(__DIR__, 4) . '/storage/uploads/logos',
dirname(__DIR__, 4) . '/storage/uploads/company/logos'
];
foreach ($oldDirs as $dir) {
$oldFile = $dir . '/' . $company->bus_logo;
if (file_exists($oldFile)) {
unlink($oldFile);
break; // Delete only once
}
}
}
$company->bus_logo = $filename;
}
}
}
// Handle business card upload
if ($request->files->has('business_card')) {
$file = $request->files->get('business_card');
if ($file && $file->isValid()) {
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (in_array($file->getMimeType(), $allowedMimes)) {
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/company/business-cards';
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$formatSetting = strtolower((string) get_env('IMG_FORMAT', 'webp'));
$format = $formatSetting === 'auto' ? 'webp' : $formatSetting;
$quality = env_int('IMG_QUALITY', 80);
$resolutionPercent = env_int('IMG_RESOLUTION', 100);
$image = Image::make($file->getPathname());
$width = $image->width();
$height = $image->height();
if ($resolutionPercent > 0 && $resolutionPercent < 100) {
$newW = (int) round($width * $resolutionPercent / 100);
$newH = (int) round($height * $resolutionPercent / 100);
$image->resize($newW, $newH, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
}
$ext = $format === 'jpeg' || $format === 'jpg' ? 'jpg' : ($format === 'png' ? 'png' : ($format === 'gif' ? 'gif' : 'webp'));
$filename = 'business_card_' . $company->bus_id . '_' . time() . '.' . $ext;
if ($format === 'jpeg' || $format === 'jpg') {
$image->interlace(true);
$image->encode('jpeg', max(1, min(100, $quality)));
} elseif ($format === 'png') {
$pngCompression = max(0, min(9, (int) round((100 - max(1, min(100, $quality))) / 10)));
$image->encode('png', $pngCompression);
} elseif ($format === 'gif') {
$image->encode('gif');
} else {
$image->encode('webp', max(1, min(100, $quality)));
}
$image->save($uploadDir . '/' . $filename);
// Delete old business card if exists (check both old and new directories)
if ($company->bus_business_card) {
$oldDirs = [
dirname(__DIR__, 4) . '/storage/uploads/business-cards',
dirname(__DIR__, 4) . '/storage/uploads/company/business-cards'
];
foreach ($oldDirs as $dir) {
$oldFile = $dir . '/' . $company->bus_business_card;
if (file_exists($oldFile)) {
unlink($oldFile);
break; // Delete only once
}
}
}
$company->bus_business_card = $filename;
}
}
}
// Handle document upload
if ($request->files->has('document')) {
$file = $request->files->get('document');
if ($file && $file->isValid()) {
$allowedMimes = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation'
];
if (in_array($file->getMimeType(), $allowedMimes)) {
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/company/documents';
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$originalName = $file->getClientOriginalName();
$filename = 'document_' . $company->bus_id . '_' . time() . '_' . $originalName;
$file->move($uploadDir, $filename);
// Delete old document if exists (check both old and new directories)
if ($company->bus_document) {
$oldDirs = [
dirname(__DIR__, 4) . '/storage/uploads/documents',
dirname(__DIR__, 4) . '/storage/uploads/company/documents'
];
foreach ($oldDirs as $dir) {
$oldFile = $dir . '/' . $company->bus_document;
if (file_exists($oldFile)) {
unlink($oldFile);
break; // Delete only once
}
}
}
$company->bus_document = $filename;
}
}
}
$company->save();
// Restore session to ensure super_admin remains logged
$_SESSION['user_id'] = $currentUserId;
$_SESSION['user_role'] = $currentUserRole;
$_SESSION['user_idbus'] = $currentUserBus;
return new RedirectResponse(url('admin/companies'));
}
public function show(Request $request, string $id): Response
{
$user = $this->getCurrentUser();
if (!$user || (!$user->isSuperAdmin() && $user->users_bus_id != $id)) {
return new RedirectResponse(url('admin/dashboard?error=unauthorized'));
}
$company = Company::find($id);
if (!$company) {
return new RedirectResponse(url('admin/companies'));
}
return $this->renderAdminView('company/views/admin/show.twig', [
'company' => $company,
'title' => 'Company Details'
]);
}
public function edit(Request $request, string $id): Response
{
$user = $this->getCurrentUser();
if (!$user || (!$user->isSuperAdmin() && $user->users_bus_id != $id)) {
return new RedirectResponse(url('admin/dashboard?error=unauthorized'));
}
$company = Company::find($id);
if (!$company) {
return new RedirectResponse(url('admin/companies'));
}
// Mask the API Key for the view
if ($company->openrouter_api_key) {
$company->openrouter_api_key = substr(Crypto::decrypt($company->openrouter_api_key), 0, 8) . '****************';
}
$error = $request->query->get('error');
$errorMessage = $error ? $this->getErrorMessage($error) : null;
$modules = Module::active()->ordered()->get();
return $this->renderAdminView('company/views/admin/form.twig', [
'target_company' => $company,
'title' => 'Edit Company',
'error' => $errorMessage,
'modules' => $modules
]);
}
public function update(Request $request, string $id): Response
{
$user = $this->getCurrentUser();
if (!$user || (!$user->isSuperAdmin() && $user->users_bus_id != $id)) {
return new RedirectResponse(url('admin/dashboard?error=unauthorized'));
}
$currentUserId = $_SESSION['user_id'] ?? null;
$currentUserRole = $_SESSION['user_role'] ?? null;
$currentUserBus = $_SESSION['user_idbus'] ?? null;
$company = Company::find($id);
if (!$company) {
return new RedirectResponse(url('admin/companies'));
}
$data = $request->request->all();
// Basic validation
if (empty($data['name']) || empty($data['email'])) {
return new RedirectResponse(url('admin/companies/' . $id . '/edit?error=missing_fields'));
}
// Email format validation
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
return new RedirectResponse(url('admin/companies/' . $id . '/edit?error=invalid_email'));
}
// Check if email exists (exclude current company)
if ($data['email'] !== $company->bus_email && Company::where('bus_email', $data['email'])->exists()) {
return new RedirectResponse(url('admin/companies/' . $id . '/edit?error=email_exists'));
}
$company->bus_name = $data['name'];
$company->bus_description = $data['description'] ?? '';
$company->bus_email = $data['email'];
$company->bus_phone = $data['phone'] ?? '';
$company->bus_address = $data['address'] ?? '';
$company->bus_city = $data['city'] ?? '';
$company->bus_state = $data['state'] ?? '';
$company->bus_country = $data['country'] ?? '';
$company->bus_postal_code = $data['postal_code'] ?? '';
$company->bus_website = $data['website'] ?? '';
$company->bus_tax_id = $data['tax_id'] ?? '';
$company->bus_type = $data['bus_type'] ?? 'business';
$company->bus_status = $data['status'] ?? 'active';
$company->platform_name = $data['platform_name'] ?? '';
// Encrypt OpenRouter API Key if present
$newApiKey = $data['openrouter_api_key'] ?? '';
if (!empty($newApiKey) && strpos($newApiKey, '***') === false) {
$company->openrouter_api_key = Crypto::encrypt($newApiKey);
}
$company->ai_provider = $data['ai_provider'] ?? 'openrouter';
$company->ai_model = $data['ai_model'] ?? 'google/gemini-2.0-flash-001';
$company->ai_endpoint = $data['ai_endpoint'] ?? '';
// Settings are now handled via modules table
// $company->bus_settings = $data['bus_settings'] ?? [];
// Update slug if name changed
if ($data['name'] !== $company->getOriginal('name')) {
$slug = strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $data['name'])));
// Check if slug exists (exclude current company)
if ($slug !== $company->bus_slug && Company::where('bus_slug', $slug)->exists()) {
$slug .= '-' . time();
}
$company->bus_slug = $slug;
}
$company->save();
// Handle modules - ONLY Super Admin can change which modules a company has access to
if ($user->isSuperAdmin()) {
$modulesData = $request->request->all()['modules'] ?? [];
$syncData = [];
foreach ($modulesData as $moduleId => $settings) {
$syncData[$moduleId] = [
'bus_modules_is_enabled' => isset($settings['enabled']),
'bus_modules_enabled_by' => $currentUserId,
'bus_modules_enabled_at' => \Carbon\Carbon::now(),
'bus_modules_show_public_nav' => isset($settings['public']),
'bus_modules_show_dashboard_nav' => isset($settings['dashboard'])
];
}
if (!empty($syncData)) {
$company->modules()->sync($syncData);
} else {
$company->modules()->detach();
}
}
if ($request->files->has('logo')) {
$file = $request->files->get('logo');
if ($file && $file->isValid()) {
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (in_array($file->getMimeType(), $allowedMimes)) {
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/company/logos';
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$formatSetting = strtolower((string) get_env('IMG_FORMAT', 'webp'));
$format = $formatSetting === 'auto' ? 'webp' : $formatSetting;
$quality = env_int('IMG_QUALITY', 80);
$resolutionPercent = env_int('IMG_RESOLUTION', 100);
$image = Image::make($file->getPathname());
$width = $image->width();
$height = $image->height();
if ($resolutionPercent > 0 && $resolutionPercent < 100) {
$newW = (int) round($width * $resolutionPercent / 100);
$newH = (int) round($height * $resolutionPercent / 100);
$image->resize($newW, $newH, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
}
$ext = $format === 'jpeg' || $format === 'jpg' ? 'jpg' : ($format === 'png' ? 'png' : ($format === 'gif' ? 'gif' : 'webp'));
$filename = 'logo_' . $company->bus_id . '_' . time() . '.' . $ext;
if ($format === 'jpeg' || $format === 'jpg') {
$image->interlace(true);
$image->encode('jpeg', max(1, min(100, $quality)));
} elseif ($format === 'png') {
$pngCompression = max(0, min(9, (int) round((100 - max(1, min(100, $quality))) / 10)));
$image->encode('png', $pngCompression);
} elseif ($format === 'gif') {
$image->encode('gif');
} else {
$image->encode('webp', max(1, min(100, $quality)));
}
$image->save($uploadDir . '/' . $filename);
if ($company->bus_logo) {
$oldDirs = [
dirname(__DIR__, 4) . '/storage/uploads/logos',
dirname(__DIR__, 4) . '/storage/uploads/company/logos'
];
foreach ($oldDirs as $dir) {
$oldFile = $dir . '/' . $company->bus_logo;
if (file_exists($oldFile)) {
unlink($oldFile);
break;
}
}
}
$company->bus_logo = $filename;
}
}
}
// Handle business card upload
if ($request->files->has('business_card')) {
$file = $request->files->get('business_card');
if ($file && $file->isValid()) {
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (in_array($file->getMimeType(), $allowedMimes)) {
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/company/business-cards';
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$formatSetting = strtolower((string) get_env('IMG_FORMAT', 'webp'));
$format = $formatSetting === 'auto' ? 'webp' : $formatSetting;
$quality = env_int('IMG_QUALITY', 80);
$resolutionPercent = env_int('IMG_RESOLUTION', 100);
$image = Image::make($file->getPathname());
$width = $image->width();
$height = $image->height();
if ($resolutionPercent > 0 && $resolutionPercent < 100) {
$newW = (int) round($width * $resolutionPercent / 100);
$newH = (int) round($height * $resolutionPercent / 100);
$image->resize($newW, $newH, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
}
$ext = $format === 'jpeg' || $format === 'jpg' ? 'jpg' : ($format === 'png' ? 'png' : ($format === 'gif' ? 'gif' : 'webp'));
$filename = 'business_card_' . $company->bus_id . '_' . time() . '.' . $ext;
if ($format === 'jpeg' || $format === 'jpg') {
$image->interlace(true);
$image->encode('jpeg', max(1, min(100, $quality)));
} elseif ($format === 'png') {
$pngCompression = max(0, min(9, (int) round((100 - max(1, min(100, $quality))) / 10)));
$image->encode('png', $pngCompression);
} elseif ($format === 'gif') {
$image->encode('gif');
} else {
$image->encode('webp', max(1, min(100, $quality)));
}
$image->save($uploadDir . '/' . $filename);
if ($company->bus_business_card) {
$oldDirs = [
dirname(__DIR__, 4) . '/storage/uploads/business-cards',
dirname(__DIR__, 4) . '/storage/uploads/company/business-cards'
];
foreach ($oldDirs as $dir) {
$oldFile = $dir . '/' . $company->bus_business_card;
if (file_exists($oldFile)) {
unlink($oldFile);
break;
}
}
}
$company->bus_business_card = $filename;
}
}
}
// Handle document upload
if ($request->files->has('document')) {
$file = $request->files->get('document');
if ($file && $file->isValid()) {
$allowedMimes = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation'
];
if (in_array($file->getMimeType(), $allowedMimes)) {
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/company/documents';
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$originalName = $file->getClientOriginalName();
$filename = 'document_' . $company->bus_id . '_' . time() . '_' . $originalName;
$file->move($uploadDir, $filename);
if ($company->bus_document) {
$oldDirs = [
dirname(__DIR__, 4) . '/storage/uploads/documents',
dirname(__DIR__, 4) . '/storage/uploads/company/documents'
];
foreach ($oldDirs as $dir) {
$oldFile = $dir . '/' . $company->bus_document;
if (file_exists($oldFile)) {
unlink($oldFile);
break;
}
}
}
$company->bus_document = $filename;
}
}
}
$company->save();
// Restore session to ensure super_admin remains logged
$_SESSION['user_id'] = $currentUserId;
$_SESSION['user_role'] = $currentUserRole;
$_SESSION['user_idbus'] = $currentUserBus;
return new RedirectResponse(url('admin/companies'));
}
private function getErrorMessage(string $error): string
{
$messages = [
'missing_fields' => 'Por favor, preencha todos os campos obrigatórios.',
'email_exists' => 'Este endereço de e-mail já está registrado.',
'invalid_email' => 'Por favor, insira um endereço de e-mail válido.',
];
return $messages[$error] ?? 'Ocorreu um erro.';
}
public function destroy(Request $request, string $id): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new RedirectResponse(url('admin/dashboard?error=unauthorized'));
}
$company = Company::find($id);
if ($company) {
// Delete logo file if exists
if ($company->bus_logo) {
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/company/logos';
if (file_exists($uploadDir . '/' . $company->bus_logo)) {
unlink($uploadDir . '/' . $company->bus_logo);
}
}
// Delete business card file if exists
if ($company->bus_business_card) {
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/company/business-cards';
if (file_exists($uploadDir . '/' . $company->bus_business_card)) {
unlink($uploadDir . '/' . $company->bus_business_card);
}
}
// Delete document file if exists
if ($company->bus_document) {
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/company/documents';
if (file_exists($uploadDir . '/' . $company->bus_document)) {
unlink($uploadDir . '/' . $company->bus_document);
}
}
$company->delete();
}
return new RedirectResponse(url('admin/companies'));
}
}
+147
View File
@@ -0,0 +1,147 @@
<?php
namespace Modules\Company\Middleware;
use Closure;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
class CompanyMiddleware
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
$user = $this->getCurrentUser();
if (!$user) {
return $this->unauthorizedResponse($request);
}
// Super Admin can access any company
if ($user->isSuperAdmin()) {
return $next($request);
}
// Routes that do not require a company context
$bypassRoutes = ['/profile', '/logout', '/dashboard'];
if (in_array($request->getPathInfo(), $bypassRoutes)) {
return $next($request);
}
// Get company ID from route or user session
$companyId = $this->getCompanyId($request, $user);
// Check if user can access the company
if (!$this->canAccessCompany($user, $companyId)) {
return $this->forbiddenResponse($request);
}
// Set company context in session for easy access
$_SESSION['current_company_id'] = $companyId;
return $next($request);
}
/**
* Get current authenticated user.
*
* @return \Modules\Auth\Models\User|null
*/
protected function getCurrentUser(): ?\Modules\Auth\Models\User
{
if (!isset($_SESSION['user_id'])) {
return null;
}
try {
if (class_exists('\Modules\Auth\Models\User')) {
return \Modules\Auth\Models\User::find($_SESSION['user_id']);
}
} catch (\Exception $e) {
error_log("Error loading user: " . $e->getMessage());
}
return null;
}
/**
* Get company ID from request or user session.
*
* @param Request $request
* @param \Modules\Auth\Models\User $user
* @return int|null
*/
protected function getCompanyId(Request $request, $user): ?int
{
// 1. Check URL parameter (if present)
// Note: FastRoute parameters are passed to controller, not easily accessible in middleware via standard request
// We can parse the path if necessary, but standard app logic usually relies on session or explicit selection.
// 2. Check Session
if (isset($_SESSION['current_company_id'])) {
return (int) $_SESSION['current_company_id'];
}
// 3. Default to user's primary business ID
return $user->users_bus_id;
}
/**
* Check if user can access the company.
*
* @param \Modules\Auth\Models\User $user
* @param int|null $companyId
* @return bool
*/
protected function canAccessCompany($user, $companyId): bool
{
if (!$companyId) {
return false;
}
// If user belongs to this company directly
if ($user->users_bus_id == $companyId) {
return true;
}
// If user has permission/association to this company (Multi-tenant logic)
// TODO: Implement multi-company association logic if needed
return false;
}
protected function unauthorizedResponse(Request $request)
{
if ($this->expectsJson($request)) {
return new JsonResponse(['message' => 'Unauthorized'], 401);
}
return new Response('', 302, ['Location' => url('/login')]);
}
protected function forbiddenResponse(Request $request)
{
if ($this->expectsJson($request)) {
return new JsonResponse(['message' => 'Forbidden - Access denied to this company'], 403);
}
// Show 403 error page
try {
$view = new \Kernel\View();
return new Response($view->render('errors/403.twig'), 403);
} catch (\Throwable $e) {
return new Response('Access denied to this company', 403);
}
}
protected function expectsJson(Request $request): bool
{
return $request->headers->get('Accept') === 'application/json' ||
$request->headers->get('Content-Type') === 'application/json' ||
str_contains($request->getPathInfo(), '/api/');
}
}
+316
View File
@@ -0,0 +1,316 @@
<?php
namespace Modules\Company\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Company extends Model
{
/**
* The table associated with model.
*
* @var string
*/
protected $table = 'bus';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'bus_id';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'bus_name',
'bus_slug',
'bus_description',
'bus_logo',
'bus_business_card',
'bus_document',
'bus_email',
'bus_phone',
'bus_address',
'bus_city',
'bus_state',
'bus_country',
'bus_postal_code',
'bus_website',
'bus_tax_id',
'bus_type',
'bus_status',
'bus_settings',
'openrouter_api_key',
'ai_provider',
'ai_model',
'ai_endpoint',
'platform_name'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'bus_settings' => 'array',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime'
];
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = [
'created_at',
'updated_at',
'deleted_at'
];
/**
* Get users for the company.
*/
public function users(): HasMany
{
return $this->hasMany(\Modules\Auth\Models\User::class, 'users_bus_id');
}
/**
* Get modules for the company.
*/
public function modules(): BelongsToMany
{
return $this->belongsToMany(\App\Models\Module::class, 'bus_modules', 'bus_modules_bus_id', 'bus_modules_module_id')
->withPivot(['bus_modules_is_enabled', 'bus_modules_settings', 'bus_modules_enabled_by', 'bus_modules_enabled_at', 'bus_modules_show_public_nav', 'bus_modules_show_dashboard_nav'])
->withTimestamps();
}
/**
* Get activity logs for the company.
*/
public function activityLogs(): HasMany
{
return $this->hasMany(\App\Models\ActivityLog::class, 'activity_logs_bus_id');
}
/**
* Get enabled modules only.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeEnabledModules($query)
{
return $query->where('bus_modules.bus_modules_is_enabled', true);
}
/**
* Get active companies only.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeActive($query)
{
return $query->where('bus_status', 'active');
}
/**
* Get companies by type.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $type
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeType($query, string $type)
{
return $query->where('bus_type', $type);
}
/**
* Check if company has specific module enabled.
*
* @param string $moduleName
* @return bool
*/
public function hasModuleEnabled(string $moduleName): bool
{
return $this->modules()
->where('modules.modules_name', $moduleName)
->where('bus_modules.bus_modules_is_enabled', true)
->exists();
}
/**
* Enable module for company.
*
* @param int $moduleId
* @param int $enabledBy
* @param array $settings
* @return bool
*/
public function enableModule(int $moduleId, int $enabledBy, array $settings = []): bool
{
$this->modules()->attach($moduleId, [
'bus_modules_is_enabled' => true,
'bus_modules_settings' => json_encode($settings),
'bus_modules_enabled_by' => $enabledBy,
'bus_modules_enabled_at' => \Carbon\Carbon::now()
]);
return true;
}
/**
* Disable module for company.
*
* @param int $moduleId
* @return bool
*/
public function disableModule(int $moduleId): bool
{
return $this->modules()->updateExistingPivot($moduleId, [
'bus_modules_is_enabled' => false
]);
}
/**
* Get logo URL.
*
* @return string
*/
public function getLogoUrlAttribute(): string
{
if ($this->bus_logo) {
return url('storage/uploads/company/logos/' . $this->bus_logo);
}
return asset_url('img/default-logo.png');
}
/**
* Get business card URL.
*
* @return string
*/
public function getBusinessCardUrlAttribute(): string
{
if ($this->bus_business_card) {
return url('storage/uploads/company/business-cards/' . $this->bus_business_card);
}
return asset_url('img/default-business-card.png');
}
/**
* Get document URL.
*
* @return string
*/
public function getDocumentUrlAttribute(): string
{
if ($this->bus_document) {
return url('storage/uploads/company/documents/' . $this->bus_document);
}
return '#';
}
/**
* Get display name.
*
* @return string
*/
public function getDisplayNameAttribute(): string
{
return $this->bus_name ?: $this->bus_slug;
}
/**
* Get full address.
*
* @return string
*/
public function getFullAddressAttribute(): string
{
$address = [];
if ($this->bus_address) {
$address[] = $this->bus_address;
}
if ($this->bus_city) {
$address[] = $this->bus_city;
}
if ($this->bus_state) {
$address[] = $this->bus_state;
}
if ($this->bus_country) {
$address[] = $this->bus_country;
}
if ($this->bus_postal_code) {
$address[] = $this->bus_postal_code;
}
return implode(', ', $address);
}
/**
* Get setting value.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public function getSetting(string $key, $default = null)
{
$settings = $this->bus_settings ?? [];
return $settings[$key] ?? $default;
}
/**
* Set setting value.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function setSetting(string $key, $value): void
{
$settings = $this->bus_settings ?? [];
$settings[$key] = $value;
$this->bus_settings = $settings;
}
/**
* Get user count.
*
* @return int
*/
public function getUserCountAttribute(): int
{
return $this->users()->count();
}
/**
* Get active user count.
*
* @return int
*/
public function getActiveUserCountAttribute(): int
{
return $this->users()->active()->count();
}
}
@@ -0,0 +1,33 @@
<?php
namespace Modules\Company\Providers;
use Kernel\Application;
use Kernel\View;
use Modules\Company\Models\Company;
use Modules\Company\Middleware\CompanyMiddleware;
class CompanyServiceProvider
{
public function register()
{
}
public function boot()
{
$app = Application::getInstance();
$app->addProtectedMiddleware(new CompanyMiddleware());
try {
if (class_exists(Company::class)) {
$mainCompany = Company::find(1);
if ($mainCompany) {
$view = new View();
$view->getEngine()->addGlobal('main_company', $mainCompany);
}
}
} catch (\Exception $e) {
// Silently fail
}
}
}
@@ -0,0 +1,68 @@
<?php
use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Database\Schema\Blueprint;
class CreateCompanyModuleTables
{
public function up()
{
// Companies Table
if (!Capsule::schema()->hasTable('bus')) {
Capsule::schema()->create('bus', function (Blueprint $table) {
$table->increments('bus_id');
$table->string('bus_name');
$table->string('bus_slug')->unique();
$table->text('bus_description')->nullable();
$table->string('bus_logo')->nullable();
$table->string('bus_business_card')->nullable();
$table->string('bus_document')->nullable();
$table->string('bus_email')->nullable();
$table->string('bus_phone')->nullable();
$table->string('bus_address')->nullable();
$table->string('bus_city')->nullable();
$table->string('bus_state')->nullable();
$table->string('bus_country')->nullable();
$table->string('bus_postal_code')->nullable();
$table->string('bus_website')->nullable();
$table->string('bus_tax_id')->nullable();
$table->string('bus_type')->nullable(); // e.g., 'personal', 'business'
$table->enum('bus_status', ['active', 'inactive', 'suspended'])->default('active');
$table->json('bus_settings')->nullable();
$table->timestamps();
$table->softDeletes();
});
}
// Company Modules Pivot Table
if (!Capsule::schema()->hasTable('bus_modules')) {
Capsule::schema()->create('bus_modules', function (Blueprint $table) {
$table->increments('bus_modules_id');
$table->integer('bus_modules_bus_id')->unsigned();
$table->integer('bus_modules_module_id')->unsigned();
$table->boolean('bus_modules_is_enabled')->default(true);
$table->json('bus_modules_settings')->nullable();
$table->integer('bus_modules_enabled_by')->unsigned()->nullable();
$table->timestamp('bus_modules_enabled_at')->useCurrent();
// Navigation visibility
$table->boolean('bus_modules_show_public_nav')->default(true);
$table->boolean('bus_modules_show_dashboard_nav')->default(true);
$table->timestamps();
$table->unique(['bus_modules_bus_id', 'bus_modules_module_id']);
$table->foreign('bus_modules_bus_id')->references('bus_id')->on('bus')->onDelete('cascade');
// Note: modules table is created in SystemConfig or Core, so we might need to be careful with foreign keys if order varies.
// Assuming modules table exists or will exist. If strict, we can add FK.
// $table->foreign('bus_modules_module_id')->references('modules_id')->on('modules')->onDelete('cascade');
});
}
}
public function down()
{
Capsule::schema()->dropIfExists('bus_modules');
Capsule::schema()->dropIfExists('bus');
}
}
@@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Database\Schema\Blueprint;
/**
* Migration: Add Date Format to Companies Table
*/
class AddDateFormatToCompaniesTable
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
if (Capsule::schema()->hasTable('bus')) {
Capsule::schema()->table('bus', function (Blueprint $table) {
if (!Capsule::schema()->hasColumn('bus', 'bus_date_format')) {
$table->string('bus_date_format', 20)->default('d/m/Y')->after('bus_status');
}
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
if (Capsule::schema()->hasTable('bus')) {
Capsule::schema()->table('bus', function (Blueprint $table) {
if (Capsule::schema()->hasColumn('bus', 'bus_date_format')) {
$table->dropColumn('bus_date_format');
}
});
}
}
}
@@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Database\Schema\Blueprint;
class AddNavVisibilityToBusModules
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
if (Capsule::schema()->hasTable('bus_modules')) {
Capsule::schema()->table('bus_modules', function (Blueprint $table) {
if (!Capsule::schema()->hasColumn('bus_modules', 'bus_modules_show_public_nav')) {
$table->boolean('bus_modules_show_public_nav')->default(true)->after('bus_modules_is_enabled');
}
if (!Capsule::schema()->hasColumn('bus_modules', 'bus_modules_show_dashboard_nav')) {
$table->boolean('bus_modules_show_dashboard_nav')->default(true)->after('bus_modules_show_public_nav');
}
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
if (Capsule::schema()->hasTable('bus_modules')) {
Capsule::schema()->table('bus_modules', function (Blueprint $table) {
$table->dropColumn(['bus_modules_show_public_nav', 'bus_modules_show_dashboard_nav']);
});
}
}
}
@@ -0,0 +1,87 @@
<?php
use Illuminate\Database\Capsule\Manager as Capsule;
use App\Models\Module;
use Modules\Company\Models\Company;
class InsertCompanyModule
{
public function up()
{
$moduleData = [
'modules_name' => 'company',
'modules_display_name' => 'Company',
'modules_description' => 'Company management module',
'modules_version' => '1.0.0',
'modules_author' => 'SysCMS Team',
'modules_ative' => true,
'modules_public' => false,
'modules_blocked' => false,
'modules_dependencies' => ["auth"],
'modules_permissions' => [
"company.view",
"company.create",
"company.edit",
"company.delete"
],
'modules_settings' => [
'menu' => [
'dashboard' => [
'icon' => 'bi bi-building',
'label' => 'company.menu.dashboard',
'route' => 'admin/companies',
'order' => 30
]
]
],
'modules_path' => 'modules/company',
'modules_namespace' => 'Modules\\Company',
'modules_icon' => 'bi bi-building',
'menu_side_bar' => true,
'menu_nav_bar' => false,
'modules_order' => 30,
'modules_install_date' => date('Y-m-d H:i:s'),
'modules_update_date' => date('Y-m-d H:i:s')
];
$existingModule = Module::where('modules_name', 'company')->first();
if (!$existingModule) {
$module = Module::create($moduleData);
if (Capsule::schema()->hasTable('bus')) {
$companies = Company::all();
foreach ($companies as $company) {
$exists = Capsule::table('bus_modules')
->where('bus_modules_bus_id', $company->bus_id)
->where('bus_modules_module_id', $module->modules_id)
->exists();
if (!$exists) {
Capsule::table('bus_modules')->insert([
'bus_modules_bus_id' => $company->bus_id,
'bus_modules_module_id' => $module->modules_id,
'bus_modules_is_enabled' => true,
'bus_modules_settings' => json_encode([]),
'bus_modules_enabled_by' => 1,
'bus_modules_enabled_at' => date('Y-m-d H:i:s'),
'bus_modules_show_public_nav' => true,
'bus_modules_show_dashboard_nav' => true,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
]);
}
}
}
}
}
public function down()
{
$module = Module::where('modules_name', 'company')->first();
if ($module) {
Capsule::table('bus_modules')->where('bus_modules_module_id', $module->modules_id)->delete();
$module->delete();
}
}
}
@@ -0,0 +1,26 @@
<?php
use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Database\Schema\Blueprint;
class AddPlatformNameToCompanies
{
public function up()
{
// Adiciona campo platform_name à tabela bus (companies)
if (Capsule::schema()->hasTable('bus') && !Capsule::schema()->hasColumn('bus', 'platform_name')) {
Capsule::schema()->table('bus', function (Blueprint $table) {
$table->string('platform_name')->nullable()->after('bus_name');
});
}
}
public function down()
{
if (Capsule::schema()->hasTable('bus') && Capsule::schema()->hasColumn('bus', 'platform_name')) {
Capsule::schema()->table('bus', function (Blueprint $table) {
$table->dropColumn('platform_name');
});
}
}
}
+68
View File
@@ -0,0 +1,68 @@
{
"menu": {
"dashboard": "Firmen"
},
"title": "Unternehmen",
"new_company": "Neues Unternehmen",
"list_title": "Unternehmensliste",
"name": "Name",
"email": "E-Mail",
"type": "Typ",
"status": "Status",
"created_at": "Erstellt am",
"actions": "Aktionen",
"no_companies": "Keine Unternehmen gefunden.",
"confirm_delete": "Sind Sie sicher, dass Sie dieses Unternehmen löschen möchten?",
"view": "Ansehen",
"edit": "Bearbeiten",
"delete": "Löschen",
"form": {
"description_label": "Beschreibung",
"description_placeholder": "Geben Sie hier die Unternehmensbeschreibung ein..."
},
"edit_title": "Bearbeiten",
"create_title": "Erstellen",
"details_title": "Unternehmensdetails",
"company_name": "Unternehmensname",
"description": "Beschreibung",
"phone": "Telefon",
"website": "Webseite",
"tax_id": "Steuer-ID",
"business_type": "Geschäftstyp",
"individual": "Einzelperson",
"business": "Geschäft",
"nonprofit": "Gemeinnützig",
"active": "Aktiv",
"inactive": "Inaktiv",
"address": "Adresse",
"city": "Stadt",
"state": "Bundesland",
"country": "Land",
"postal_code": "Postleitzahl",
"module_visibility_title": "Modul-Sichtbarkeitseinstellungen",
"module_header": "Modul",
"enabled_header": "Aktiviert",
"public_navbar_header": "Öffentliche Navigationsleiste",
"dashboard_navbar_header": "Dashboard-Navigationsleiste",
"no_active_modules": "Keine aktiven Module gefunden.",
"logo": "Logo",
"preview": "Vorschau:",
"current_logo": "Aktuelles Logo:",
"business_card": "Visitenkarte",
"current_business_card": "Aktuelle Visitenkarte:",
"document": "Dokument",
"current_document": "Aktuelles Dokument:",
"view_document": "Dokument anzeigen",
"cancel": "Abbrechen",
"update_button": "Unternehmen aktualisieren",
"create_button": "Unternehmen erstellen",
"selected_file": "Ausgewählte Datei:",
"slug": "Slug",
"updated_at": "Aktualisiert am",
"back_to_list": "Zurück zur Liste",
"edit_company": "Unternehmen bearbeiten",
"na": "N/A",
"alt_logo": "Unternehmenslogo",
"alt_business_card": "Visitenkarte",
"alt_preview": "Vorschau"
}
+68
View File
@@ -0,0 +1,68 @@
{
"menu": {
"dashboard": "Companies"
},
"title": "Companies",
"new_company": "New Company",
"list_title": "Companies List",
"name": "Name",
"email": "Email",
"type": "Type",
"status": "Status",
"created_at": "Created At",
"actions": "Actions",
"no_companies": "No companies found.",
"confirm_delete": "Are you sure you want to delete this company?",
"view": "View",
"edit": "Edit",
"delete": "Delete",
"form": {
"description_label": "Description",
"description_placeholder": "Type the company description here..."
},
"edit_title": "Edit",
"create_title": "Create",
"details_title": "Company Details",
"company_name": "Company Name",
"description": "Description",
"phone": "Phone",
"website": "Website",
"tax_id": "Tax ID",
"business_type": "Business Type",
"individual": "Individual",
"business": "Business",
"nonprofit": "Non-profit",
"active": "Active",
"inactive": "Inactive",
"address": "Address",
"city": "City",
"state": "State",
"country": "Country",
"postal_code": "Postal Code",
"module_visibility_title": "Module Visibility Settings",
"module_header": "Module",
"enabled_header": "Enabled",
"public_navbar_header": "Public Navbar",
"dashboard_navbar_header": "Dashboard Navbar",
"no_active_modules": "No active modules found.",
"logo": "Logo",
"preview": "Preview:",
"current_logo": "Current logo:",
"business_card": "Business Card",
"current_business_card": "Current business card:",
"document": "Document",
"current_document": "Current document:",
"view_document": "View Document",
"cancel": "Cancel",
"update_button": "Update Company",
"create_button": "Create Company",
"selected_file": "Selected file:",
"slug": "Slug",
"updated_at": "Updated At",
"back_to_list": "Back to List",
"edit_company": "Edit Company",
"na": "N/A",
"alt_logo": "Company Logo",
"alt_business_card": "Business Card",
"alt_preview": "Preview"
}
+68
View File
@@ -0,0 +1,68 @@
{
"menu": {
"dashboard": "Empresas"
},
"title": "Empresas",
"new_company": "Nueva Empresa",
"list_title": "Lista de Empresas",
"name": "Nombre",
"email": "Correo Electrónico",
"type": "Tipo",
"status": "Estado",
"created_at": "Creado el",
"actions": "Acciones",
"no_companies": "No se encontraron empresas.",
"confirm_delete": "¿Está seguro de que desea eliminar esta empresa?",
"view": "Ver",
"edit": "Editar",
"delete": "Eliminar",
"form": {
"description_label": "Descripción",
"description_placeholder": "Escriba la descripción de la empresa aquí..."
},
"edit_title": "Editar",
"create_title": "Crear",
"details_title": "Detalles de la Empresa",
"company_name": "Nombre de la Empresa",
"description": "Descripción",
"phone": "Teléfono",
"website": "Sitio Web",
"tax_id": "ID Fiscal",
"business_type": "Tipo de Negocio",
"individual": "Individuo",
"business": "Empresa",
"nonprofit": "Sin Fines de Lucro",
"active": "Activo",
"inactive": "Inactivo",
"address": "Dirección",
"city": "Ciudad",
"state": "Estado/Provincia",
"country": "País",
"postal_code": "Código Postal",
"module_visibility_title": "Configuración de Visibilidad del Módulo",
"module_header": "Módulo",
"enabled_header": "Habilitado",
"public_navbar_header": "Barra de Navegación Pública",
"dashboard_navbar_header": "Barra de Navegación del Panel",
"no_active_modules": "No se encontraron módulos activos.",
"logo": "Logotipo",
"preview": "Vista Previa:",
"current_logo": "Logotipo Actual:",
"business_card": "Tarjeta de Visita",
"current_business_card": "Tarjeta actual:",
"document": "Documento",
"current_document": "Documento actual:",
"view_document": "Ver Documento",
"cancel": "Cancelar",
"update_button": "Actualizar Empresa",
"create_button": "Crear Empresa",
"selected_file": "Archivo seleccionado:",
"slug": "Slug",
"updated_at": "Actualizado el",
"back_to_list": "Volver a la Lista",
"edit_company": "Editar Empresa",
"na": "N/A",
"alt_logo": "Logotipo de la Empresa",
"alt_business_card": "Tarjeta de Visita",
"alt_preview": "Vista Previa"
}
+68
View File
@@ -0,0 +1,68 @@
{
"menu": {
"dashboard": "Entreprises"
},
"title": "Entreprises",
"new_company": "Nouvelle Entreprise",
"list_title": "Liste des Entreprises",
"name": "Nom",
"email": "Email",
"type": "Type",
"status": "Statut",
"created_at": "Créé le",
"actions": "Actions",
"no_companies": "Aucune entreprise trouvée.",
"confirm_delete": "Êtes-vous sûr de vouloir supprimer cette entreprise ?",
"view": "Voir",
"edit": "Modifier",
"delete": "Supprimer",
"form": {
"description_label": "Description",
"description_placeholder": "Entrez la description de l'entreprise ici..."
},
"edit_title": "Modifier",
"create_title": "Créer",
"details_title": "Détails de l'Entreprise",
"company_name": "Nom de l'Entreprise",
"description": "Description",
"phone": "Téléphone",
"website": "Site Web",
"tax_id": "Numéro Fiscal",
"business_type": "Type d'Entreprise",
"individual": "Individu",
"business": "Entreprise",
"nonprofit": "À But Non Lucratif",
"active": "Actif",
"inactive": "Inactif",
"address": "Adresse",
"city": "Ville",
"state": "État/Région",
"country": "Pays",
"postal_code": "Code Postal",
"module_visibility_title": "Paramètres de Visibilité du Module",
"module_header": "Module",
"enabled_header": "Activé",
"public_navbar_header": "Barre de Navigation Publique",
"dashboard_navbar_header": "Barre de Navigation du Tableau de Bord",
"no_active_modules": "Aucun module actif trouvé.",
"logo": "Logo",
"preview": "Aperçu :",
"current_logo": "Logo actuel :",
"business_card": "Carte de Visite",
"current_business_card": "Carte actuelle :",
"document": "Document",
"current_document": "Document actuel :",
"view_document": "Voir le Document",
"cancel": "Annuler",
"update_button": "Mettre à Jour l'Entreprise",
"create_button": "Créer l'Entreprise",
"selected_file": "Fichier sélectionné :",
"slug": "Slug",
"updated_at": "Mis à jour le",
"back_to_list": "Retour à la Liste",
"edit_company": "Modifier l'Entreprise",
"na": "N/A",
"alt_logo": "Logo de l'Entreprise",
"alt_business_card": "Carte de Visite",
"alt_preview": "Aperçu"
}
+80
View File
@@ -0,0 +1,80 @@
{
"menu": {
"dashboard": "Dados da Empresa"
},
"title": "Empresas",
"new_company": "Nova Empresa",
"list_title": "Lista de Empresas",
"name": "Nome",
"email": "Email",
"type": "Tipo",
"status": "Status",
"created_at": "Criado em",
"actions": "Ações",
"no_companies": "Nenhuma empresa encontrada.",
"confirm_delete": "Tem certeza que deseja excluir esta empresa?",
"view": "Visualizar",
"edit": "Editar",
"delete": "Excluir",
"form": {
"description_label": "Descrição",
"description_placeholder": "Digite a descrição da empresa aqui..."
},
"edit_title": "Editar",
"create_title": "Criar",
"details_title": "Detalhes da Empresa",
"company_name": "Nome da Empresa",
"description": "Descrição",
"phone": "Telefone",
"website": "Website",
"tax_id": "CNPJ/ID Fiscal",
"business_type": "Tipo de Negócio",
"individual": "Pessoa Física",
"business": "Empresa",
"nonprofit": "Sem Fins Lucrativos",
"active": "Ativo",
"inactive": "Inativo",
"address": "Endereço",
"city": "Cidade",
"state": "Estado",
"country": "País",
"postal_code": "CEP",
"module_visibility_title": "Configurações de Visibilidade do Módulo",
"module_header": "Módulo",
"enabled_header": "Habilitado",
"public_navbar_header": "Navbar Público",
"dashboard_navbar_header": "Navbar do Painel",
"no_active_modules": "Nenhum módulo ativo encontrado.",
"logo": "Logotipo",
"preview": "Pré-visualização:",
"current_logo": "Logotipo atual:",
"business_card": "Cartão de Visitas",
"current_business_card": "Cartão atual:",
"document": "Documento",
"current_document": "Documento atual:",
"view_document": "Visualizar Documento",
"cancel": "Cancelar",
"update_button": "Atualizar Empresa",
"create_button": "Criar Empresa",
"selected_file": "Arquivo selecionado:",
"slug": "Slug",
"updated_at": "Atualizado em",
"back_to_list": "Voltar para a Lista",
"edit_company": "Editar Empresa",
"na": "N/A",
"alt_logo": "Logotipo da Empresa",
"alt_business_card": "Cartão de Visitas",
"alt_preview": "Pré-visualização",
"platform_config_title": "Configuração da Plataforma",
"platform_name": "Nome da Plataforma",
"platform_name_placeholder": "Ex: MeuSistema, PlataformaCloud, etc.",
"platform_name_help": "Este nome será exibido no título das páginas e na interface do sistema. Apenas Super Admin ou a empresa ID 1 podem alterar.",
"ai_agent_config_title": "Configurações do Agente de IA",
"ai_provider": "Operadora de IA",
"recommended": "Recomendado",
"ai_model": "Modelo de IA",
"ai_endpoint": "Endpoint",
"optional": "Opcional",
"api_key_secret": "API Key (Secreta)",
"api_key_help": "Chave de API exclusiva para esta empresa."
}
+40
View File
@@ -0,0 +1,40 @@
{
"name": "company",
"display_name": "Company",
"description": "Company management module",
"version": "1.0.0",
"author": "SysCMS Team",
"dependencies": [
"auth"
],
"permissions": [
"company.view",
"company.create",
"company.edit",
"company.delete"
],
"settings": [],
"providers": [
"Modules\\Company\\Providers\\CompanyServiceProvider"
],
"routes": {
"web": "routes/web.php"
},
"menu": {
"dashboard": {
"icon": "bi bi-building",
"label": "company.menu.dashboard",
"route": "admin/company/settings",
"order": 30
}
},
"views": "views",
"locales": "locales",
"module_ative": true,
"module_public": false,
"module_blocked": false,
"ready": true,
"tables": [
"bus"
]
}
+15
View File
@@ -0,0 +1,15 @@
<?php
use Kernel\Application;
return function (Application $app) {
// Admin routes
$app->addRoute('GET', '/admin/companies', [\Modules\Company\Controllers\CompanyController::class, 'index']);
$app->addRoute('GET', '/admin/company/settings', [\Modules\Company\Controllers\CompanyController::class, 'settings']);
$app->addRoute('GET', '/admin/companies/create', [\Modules\Company\Controllers\CompanyController::class, 'create']);
$app->addRoute('POST', '/admin/companies', [\Modules\Company\Controllers\CompanyController::class, 'store']);
$app->addRoute('GET', '/admin/companies/{id:\d+}', [\Modules\Company\Controllers\CompanyController::class, 'show']);
$app->addRoute('GET', '/admin/companies/{id:\d+}/edit', [\Modules\Company\Controllers\CompanyController::class, 'edit']);
$app->addRoute('PUT', '/admin/companies/{id:\d+}', [\Modules\Company\Controllers\CompanyController::class, 'update']);
$app->addRoute('DELETE', '/admin/companies/{id:\d+}', [\Modules\Company\Controllers\CompanyController::class, 'destroy']);
};
+128
View File
@@ -0,0 +1,128 @@
<?php
namespace Modules\Company\Tests;
use Tests\ModuleTestCase;
use Modules\Company\Models\Company;
use Modules\Company\Controllers\CompanyController;
/**
* Company Module Test
*
* Tests for the Companies module.
* Ensures translations are complete and routes are properly registered.
* Validates upload directory structure and compliance with SysCMS rules.
*/
class CompanyTest extends ModuleTestCase
{
protected string $moduleName = 'company';
public function testTranslationsAreComplete(): void
{
$this->assertTranslationsComplete($this->moduleName);
}
public function testRoutesExist(): void
{
$this->assertRouteExists('GET', '/admin/companies');
$this->assertRouteExists('GET', '/admin/companies/create');
$this->assertRouteExists('POST', '/admin/companies/store');
$this->assertRouteExists('GET', '/admin/companies/edit/{id}');
$this->assertRouteExists('POST', '/admin/companies/update/{id}');
$this->assertRouteExists('GET', '/admin/companies/delete/{id}');
$this->assertRouteExists('GET', '/admin/companies/{id}');
}
public function testStorageRouteExists(): void
{
$this->assertRouteExists('GET', '/storage/uploads/{module}/{filename}');
}
public function testCompanyModelTable(): void
{
$company = new Company();
$this->assertEquals('bus', $company->getTable());
}
public function testCompanyModelHasUploadFields(): void
{
$company = new Company();
$fillable = $company->getFillable();
$this->assertContains('bus_logo', $fillable);
$this->assertContains('bus_business_card', $fillable);
$this->assertContains('bus_document', $fillable);
}
public function testCompanyModelHasUrlAccessors(): void
{
$company = new Company();
// Use reflection to check if methods exist
$reflection = new \ReflectionClass($company);
$this->assertTrue($reflection->hasMethod('getLogoUrlAttribute'));
$this->assertTrue($reflection->hasMethod('getBusinessCardUrlAttribute'));
$this->assertTrue($reflection->hasMethod('getDocumentUrlAttribute'));
}
public function testUploadDirectoriesExist(): void
{
$baseDir = dirname(__DIR__, 4) . '/storage/uploads/company/';
$this->assertTrue(is_dir($baseDir . 'logos'));
$this->assertTrue(is_dir($baseDir . 'business-cards'));
$this->assertTrue(is_dir($baseDir . 'documents'));
}
public function testUploadDirectoriesAreWritable(): void
{
$baseDir = dirname(__DIR__, 4) . '/storage/uploads/company/';
$this->assertTrue(is_writable($baseDir . 'logos'));
$this->assertTrue(is_writable($baseDir . 'business-cards'));
$this->assertTrue(is_writable($baseDir . 'documents'));
}
public function testLogoUrlAccessorReturnsCorrectPath(): void
{
$company = new Company();
$company->bus_logo = 'test_logo.jpg';
$url = $company->logo_url;
$this->assertStringContainsString('storage/uploads/company/logos/test_logo.jpg', $url);
$this->assertStringStartsWith(url('/'), $url);
}
public function testBusinessCardUrlAccessorReturnsCorrectPath(): void
{
$company = new Company();
$company->bus_business_card = 'test_card.jpg';
$url = $company->business_card_url;
$this->assertStringContainsString('storage/uploads/company/business-cards/test_card.jpg', $url);
$this->assertStringStartsWith(url('/'), $url);
}
public function testDocumentUrlAccessorReturnsCorrectPath(): void
{
$company = new Company();
$company->bus_document = 'test_doc.pdf';
$url = $company->document_url;
$this->assertStringContainsString('storage/uploads/company/documents/test_doc.pdf', $url);
$this->assertStringStartsWith(url('/'), $url);
}
public function testCompanyControllerMethodsExist(): void
{
$view = new \Kernel\View();
$controller = new CompanyController($view);
$reflection = new \ReflectionClass($controller);
$this->assertTrue($reflection->hasMethod('index'));
$this->assertTrue($reflection->hasMethod('create'));
$this->assertTrue($reflection->hasMethod('store'));
$this->assertTrue($reflection->hasMethod('show'));
$this->assertTrue($reflection->hasMethod('edit'));
$this->assertTrue($reflection->hasMethod('update'));
$this->assertTrue($reflection->hasMethod('destroy'));
}
}
+295
View File
@@ -0,0 +1,295 @@
{% extends "layouts/dashboard.twig" %}
{% block title %}{{ title }} - {{ platformName }}{% endblock %}
{% block content %}
<div class="container-fluid px-4">
<h1 class="mt-4">{{ title }}</h1>
<ol class="breadcrumb mb-4">
<li class="breadcrumb-item"><a href="{{ url('admin/dashboard') }}">{{ __('dashboard.dashboard') }}</a></li>
<li class="breadcrumb-item"><a href="{{ url('admin/companies') }}">{{ __('company.title') }}</a></li>
<li class="breadcrumb-item active">{{ target_company ? __('company.edit_title') : __('company.create_title') }}</li>
</ol>
<div class="card mb-4">
<div class="card-header">
<i class="bi bi-building me-1"></i>
{{ __('company.details_title') }}
</div>
<div class="card-body">
{% if error %}
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endif %}
<form action="{{ target_company ? url('admin/companies/' ~ target_company.bus_id) : url('admin/companies') }}" method="POST" enctype="multipart/form-data">
{% if target_company %}
<input type="hidden" name="_method" value="PUT">
{% endif %}
{{ csrf_field()|raw }}
<div class="mb-3">
<label for="name" class="form-label">{{ __('company.company_name') }}</label>
<input type="text" class="form-control" id="name" name="name" value="{{ target_company ? target_company.bus_name : '' }}" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">{{ __('company.email') }}</label>
<input type="email" class="form-control" id="email" name="email" value="{{ target_company ? target_company.bus_email : '' }}" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">{{ __('company.description') }}</label>
<div id="quill-editor" style="height: 300px;" data-target="description">{{ target_company ? target_company.bus_description|raw : '' }}</div>
<textarea class="form-control" id="description" name="description" style="display:none">{{ target_company ? target_company.bus_description : '' }}</textarea>
</div>
<div class="mb-3">
<label for="phone" class="form-label">{{ __('company.phone') }}</label>
<input type="text" class="form-control" id="phone" name="phone" value="{{ target_company ? target_company.bus_phone : '' }}">
</div>
<div class="mb-3">
<label for="website" class="form-label">{{ __('company.website') }}</label>
<input type="url" class="form-control" id="website" name="website" value="{{ target_company ? target_company.bus_website : '' }}">
</div>
<div class="mb-3">
<label for="tax_id" class="form-label">{{ __('company.tax_id') }}</label>
<input type="text" class="form-control" id="tax_id" name="tax_id" value="{{ target_company ? target_company.bus_tax_id : '' }}">
</div>
<div class="mb-3">
<label for="bus_type" class="form-label">{{ __('company.business_type') }}</label>
<select class="form-select" id="bus_type" name="bus_type">
<option value="individual" {{ target_company and target_company.bus_type == 'individual' ? 'selected' : '' }}>{{ __('company.individual') }}</option>
<option value="business" {{ target_company and target_company.bus_type == 'business' ? 'selected' : '' }}>{{ __('company.business') }}</option>
<option value="nonprofit" {{ target_company and target_company.bus_type == 'nonprofit' ? 'selected' : '' }}>{{ __('company.nonprofit') }}</option>
</select>
</div>
<div class="mb-3">
<label for="status" class="form-label">{{ __('company.status') }}</label>
<select class="form-select" id="status" name="status">
<option value="active" {{ target_company and target_company.bus_status == 'active' ? 'selected' : '' }}>{{ __('company.active') }}</option>
<option value="inactive" {{ target_company and target_company.bus_status == 'inactive' ? 'selected' : '' }}>{{ __('company.inactive') }}</option>
</select>
</div>
{% if user.isSuperAdmin() or target_company and target_company.bus_id == 1 %}
<div class="card mb-3 border-warning">
<div class="card-header bg-warning text-dark">
<i class="bi bi-globe me-1"></i> {{ __('company.platform_config_title') }}
</div>
<div class="card-body">
<div class="mb-3">
<label for="platform_name" class="form-label">{{ __('company.platform_name') }}</label>
<input type="text" class="form-control" id="platform_name" name="platform_name" value="{{ target_company ? target_company.platform_name : '' }}" placeholder="{{ __('company.platform_name_placeholder') }}">
<div class="form-text">{{ __('company.platform_name_help') }}</div>
</div>
</div>
</div>
{% endif %}
<div class="card mb-4 border-primary">
<div class="card-header bg-primary text-white">
<i class="bi bi-robot me-1"></i> {{ __('company.ai_agent_config_title') }}
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<div class="mb-3">
<label for="ai_provider" class="form-label">{{ __('company.ai_provider') }}</label>
<select class="form-select" id="ai_provider" name="ai_provider">
<option value="openrouter" {{ target_company and target_company.ai_provider == 'openrouter' ? 'selected' : '' }}>OpenRouter ({{ __('company.recommended') }})</option>
<option value="openai" {{ target_company and target_company.ai_provider == 'openai' ? 'selected' : '' }}>OpenAI (Direct)</option>
<option value="gemini" {{ target_company and target_company.ai_provider == 'gemini' ? 'selected' : '' }}>Google Gemini (Direct)</option>
<option value="zai" {{ target_company and target_company.ai_provider == 'zai' ? 'selected' : '' }}>Z.ai (Direct)</option>
<option value="ollama" {{ target_company and target_company.ai_provider == 'ollama' ? 'selected' : '' }}>Ollama (Local)</option>
</select>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="ai_model" class="form-label">{{ __('company.ai_model') }}</label>
<input type="text" class="form-control" id="ai_model" name="ai_model" value="{{ target_company ? target_company.ai_model : 'google/gemini-2.0-flash-001' }}" placeholder="ex: stepfun/step-3.5-flash:free">
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="ai_endpoint" class="form-label">{{ __('company.ai_endpoint') }} ({{ __('company.optional') }})</label>
<input type="text" class="form-control" id="ai_endpoint" name="ai_endpoint" value="{{ target_company ? target_company.ai_endpoint : '' }}" placeholder="http://localhost:11434/v1">
</div>
</div>
</div>
<div class="mb-0">
<label for="openrouter_api_key" class="form-label">{{ __('company.api_key_secret') }}</label>
<input type="password" class="form-control" id="openrouter_api_key" name="openrouter_api_key" value="{{ target_company ? target_company.openrouter_api_key : '' }}" placeholder="sk-...">
<div class="form-text">{{ __('company.api_key_help') }}</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="address" class="form-label">{{ __('company.address') }}</label>
<input type="text" class="form-control" id="address" name="address" value="{{ target_company ? target_company.bus_address : '' }}">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="city" class="form-label">{{ __('company.city') }}</label>
<input type="text" class="form-control" id="city" name="city" value="{{ target_company ? target_company.bus_city : '' }}">
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="mb-3">
<label for="state" class="form-label">{{ __('company.state') }}</label>
<input type="text" class="form-control" id="state" name="state" value="{{ target_company ? target_company.bus_state : '' }}">
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="country" class="form-label">{{ __('company.country') }}</label>
<input type="text" class="form-control" id="country" name="country" value="{{ target_company ? target_company.bus_country : '' }}">
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="postal_code" class="form-label">{{ __('company.postal_code') }}</label>
<input type="text" class="form-control" id="postal_code" name="postal_code" value="{{ target_company ? target_company.bus_postal_code : '' }}">
</div>
</div>
</div>
{% if user.isSuperAdmin() %}
<div class="card mb-3">
<div class="card-header">
{{ __('company.module_visibility_title') }}
</div>
<div class="card-body">
{% if modules %}
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>{{ __('company.module_header') }}</th>
<th class="text-center">{{ __('company.enabled_header') }}</th>
<th class="text-center">{{ __('company.public_navbar_header') }}</th>
<th class="text-center">{{ __('company.dashboard_navbar_header') }}</th>
</tr>
</thead>
<tbody>
{% for module in modules %}
{% set isEnabled = false %}
{% set showPublic = true %}
{% set showDashboard = true %}
{% if target_company %}
{% for cm in target_company.modules %}
{% if cm.modules_id == module.modules_id %}
{% set isEnabled = cm.pivot.bus_modules_is_enabled %}
{% set showPublic = cm.pivot.bus_modules_show_public_nav %}
{% set showDashboard = cm.pivot.bus_modules_show_dashboard_nav %}
{% endif %}
{% endfor %}
{% endif %}
<tr>
<td>
<input type="hidden" name="modules[{{ module.modules_id }}][present]" value="1">
<strong>{{ module.modules_display_name }}</strong>
<small class="d-block text-muted">{{ module.modules_description }}</small>
</td>
<td class="text-center">
<div class="form-check d-inline-block">
<input class="form-check-input" type="checkbox" name="modules[{{ module.modules_id }}][enabled]" id="module_enabled_{{ module.modules_id }}" {{ isEnabled ? 'checked' : '' }}>
</div>
</td>
<td class="text-center">
<div class="form-check d-inline-block">
<input class="form-check-input" type="checkbox" name="modules[{{ module.modules_id }}][public]" id="module_public_{{ module.modules_id }}" {{ showPublic ? 'checked' : '' }}>
</div>
</td>
<td class="text-center">
<div class="form-check d-inline-block">
<input class="form-check-input" type="checkbox" name="modules[{{ module.modules_id }}][dashboard]" id="module_dashboard_{{ module.modules_id }}" {{ showDashboard ? 'checked' : '' }}>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted mb-0">{{ __('company.no_active_modules') }}</p>
{% endif %}
</div>
</div>
{% endif %}
<div class="mb-3">
<label for="logo" class="form-label">{{ __('company.logo') }}</label>
<input type="file" class="form-control" id="logo" name="logo" accept="image/*">
<div id="logoPreview" class="mt-2"></div>
{% if target_company and target_company.bus_logo %}
<div class="mt-2">
<small class="form-text text-muted">{{ __('company.current_logo') }}</small>
<img src="{{ target_company.logo_url }}" alt="{{ __('company.alt_logo') }}" class="img-thumbnail" style="max-height: 100px;">
</div>
{% endif %}
</div>
<div class="mb-3">
<label for="business_card" class="form-label">{{ __('company.business_card') }}</label>
<input type="file" class="form-control" id="business_card" name="business_card" accept="image/*">
<div id="businessCardPreview" class="mt-2"></div>
{% if target_company and target_company.bus_business_card %}
<div class="mt-2">
<small class="form-text text-muted">{{ __('company.current_business_card') }}</small>
<img src="{{ target_company.business_card_url }}" alt="{{ __('company.alt_business_card') }}" class="img-thumbnail" style="max-height: 100px;">
</div>
{% endif %}
</div>
<div class="mt-4 border-top pt-3">
<button type="submit" class="btn btn-primary"><i class="bi bi-save me-1"></i> {{ target_company ? __('company.update_button') : __('company.create_button') }}</button>
<a href="{{ url('admin/companies') }}" class="btn btn-secondary"><i class="bi bi-x-lg me-1"></i> {{ __('company.cancel') }}</a>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
// Logo Preview
document.getElementById('logo').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('logoPreview').innerHTML = `<img src="${e.target.result}" class="img-thumbnail" style="max-height: 100px;">`;
}
reader.readAsDataURL(file);
}
});
// Business Card Preview
document.getElementById('business_card').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('businessCardPreview').innerHTML = `<img src="${e.target.result}" class="img-thumbnail" style="max-height: 100px;">`;
}
reader.readAsDataURL(file);
}
});
</script>
{% endblock %}
+84
View File
@@ -0,0 +1,84 @@
{% extends "layouts/dashboard.twig" %}
{% block title %}{{ title }} - {{ platformName }}{% endblock %}
{% block content %}
<div class="container-fluid px-4">
<h1 class="mt-4">{{ title }}</h1>
<ol class="breadcrumb mb-4">
<li class="breadcrumb-item"><a href="{{ url('admin/dashboard') }}">{{ __('dashboard.dashboard') }}</a></li>
<li class="breadcrumb-item"><a href="{{ url('admin/erp') }}">ERP</a></li>
<li class="breadcrumb-item active">{{ __('company.title') }}</li>
</ol>
<div class="mb-4 d-flex justify-content-between">
<a href="/admin/erp" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> {{ __('erp.admin.back_to_erp') }}
</a>
{% if can_create_company %}
<a href="{{ url('admin/companies/create') }}" class="btn btn-primary">
<i class="bi bi-plus me-1"></i> {{ __('company.new_company') }}
</a>
{% endif %}
</div>
<div class="card mb-4">
<div class="card-header">
<i class="bi bi-building me-1"></i>
{{ __('company.list_title') }}
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered" id="dataTable" width="100%" cellspacing="0">
<thead>
<tr>
<th>{{ __('company.name') }}</th>
<th>{{ __('company.email') }}</th>
<th>{{ __('company.type') }}</th>
<th>{{ __('company.status') }}</th>
<th>{{ __('company.created_at') }}</th>
<th>{{ __('company.actions') }}</th>
</tr>
</thead>
<tbody>
{% for company in companies %}
<tr>
<td>{{ company.bus_name }}</td>
<td>{{ company.bus_email }}</td>
<td>{{ __('company.' ~ company.bus_type) }}</td>
<td>
<span class="badge bg-{{ company.bus_status == 'active' ? 'success' : 'secondary' }}">
{{ __('company.' ~ company.bus_status) }}
</span>
</td>
<td>{{ company.created_at|date("d/m/Y H:i") }}</td>
<td>
<a href="{{ url('admin/companies/' ~ company.bus_id) }}" class="btn btn-info btn-sm" title="{{ __('company.view') }}">
<i class="bi bi-eye"></i>
</a>
<a href="{{ url('admin/companies/' ~ company.bus_id ~ '/edit') }}" class="btn btn-warning btn-sm" title="{{ __('company.edit') }}">
<i class="bi bi-pencil"></i>
</a>
{% if user.isSuperAdmin() %}
<form action="{{ url('admin/companies/' ~ company.bus_id) }}" method="POST" class="d-inline" onsubmit="return confirm('{{ __('company.confirm_delete') }}');">
<input type="hidden" name="_method" value="DELETE">
{{ csrf_field()|raw }}
<button type="submit" class="btn btn-danger btn-sm" title="{{ __('company.delete') }}">
<i class="bi bi-trash"></i>
</button>
</form>
{% endif %}
</td>
</tr>
{% else %}
<tr>
<td colspan="6" class="text-center">{{ __('company.no_companies') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
+176
View File
@@ -0,0 +1,176 @@
{% extends "layouts/dashboard.twig" %}
{% block title %}{{ title }} - {{ platformName }}{% endblock %}
{% block content %}
<div class="container-fluid px-4">
<h1 class="mt-4">{{ title }}</h1>
<ol class="breadcrumb mb-4">
<li class="breadcrumb-item"><a href="{{ url('admin/dashboard') }}">{{ __('dashboard.dashboard') }}</a></li>
<li class="breadcrumb-item"><a href="{{ url('admin/companies') }}">{{ __('company.title') }}</a></li>
<li class="breadcrumb-item active">{{ company.bus_name }}</li>
</ol>
<div class="card mb-4">
<div class="card-header">
<i class="bi bi-building me-1"></i>
{{ __('company.details_title') }}
</div>
<div class="card-body">
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('company.company_name') }}</label>
<div class="col-sm-10">
<p class="form-control-plaintext">{{ company.bus_name }}</p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('company.slug') }}</label>
<div class="col-sm-10">
<p class="form-control-plaintext">{{ company.bus_slug }}</p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('company.email') }}</label>
<div class="col-sm-10">
<p class="form-control-plaintext">{{ company.bus_email }}</p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('company.description') }}</label>
<div class="col-sm-10">
<p class="form-control-plaintext">{{ company.bus_description ?: __('company.na') }}</p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('company.phone') }}</label>
<div class="col-sm-10">
<p class="form-control-plaintext">{{ company.bus_phone ?: __('company.na') }}</p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('company.website') }}</label>
<div class="col-sm-10">
{% if company.website %}
<p class="form-control-plaintext">
<a href="{{ company.bus_website }}" target="_blank">{{ company.bus_website }}</a>
</p>
{% else %}
<p class="form-control-plaintext">{{ __('company.na') }}</p>
{% endif %}
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('company.tax_id') }}</label>
<div class="col-sm-10">
<p class="form-control-plaintext">{{ company.bus_tax_id ?: __('company.na') }}</p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('company.business_type') }}</label>
<div class="col-sm-10">
<p class="form-control-plaintext">
{% if company.bus_type == 'individual' %}
{{ __('company.individual') }}
{% elseif company.bus_type == 'business' %}
{{ __('company.business') }}
{% elseif company.bus_type == 'nonprofit' %}
{{ __('company.nonprofit') }}
{% else %}
{{ company.bus_type|capitalize }}
{% endif %}
</p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('company.status') }}</label>
<div class="col-sm-10">
{% if company.bus_status == 'active' %}
<span class="badge bg-success">{{ __('company.active') }}</span>
{% else %}
<span class="badge bg-secondary">{{ __('company.inactive') }}</span>
{% endif %}
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('company.address') }}</label>
<div class="col-sm-10">
<p class="form-control-plaintext">
{% if company.bus_address or company.bus_city or company.bus_state or company.bus_country or company.bus_postal_code %}
{{ company.bus_address }}<br>
{% if company.bus_city or company.bus_state or company.bus_postal_code %}
{{ company.bus_city }}{{ company.bus_city and company.bus_state ? ', ' : '' }}{{ company.bus_state }} {{ company.bus_postal_code }}<br>
{% endif %}
{% if company.bus_country %}
{{ company.bus_country }}
{% endif %}
{% else %}
{{ __('company.na') }}
{% endif %}
</p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('company.logo') }}</label>
<div class="col-sm-10">
<img src="{{ company.logo_url }}" alt="{{ __('company.alt_logo') }}" class="img-thumbnail" style="max-height: 150px;">
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('company.business_card') }}</label>
<div class="col-sm-10">
{% if company.bus_business_card %}
<img src="{{ company.business_card_url }}" alt="{{ __('company.alt_business_card') }}" class="img-thumbnail" style="max-height: 150px;">
{% else %}
<p class="form-control-plaintext">{{ __('company.na') }}</p>
{% endif %}
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('company.document') }}</label>
<div class="col-sm-10">
{% if company.bus_document %}
<a href="{{ company.document_url }}" target="_blank" class="btn btn-sm btn-outline-primary">
<i class="bi bi-file-earmark-text me-1"></i> {{ __('company.view_document') }}
</a>
{% else %}
<p class="form-control-plaintext">{{ __('company.na') }}</p>
{% endif %}
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('company.created_at') }}</label>
<div class="col-sm-10">
<p class="form-control-plaintext">{{ company.created_at|date("Y-m-d H:i:s") }}</p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('company.updated_at') }}</label>
<div class="col-sm-10">
<p class="form-control-plaintext">{{ company.updated_at|date("Y-m-d H:i:s") }}</p>
</div>
</div>
<div class="d-flex justify-content-between mt-4">
<a href="{{ url('admin/companies') }}" class="btn btn-secondary">{{ __('company.back_to_list') }}</a>
<div>
<a href="{{ url('admin/companies/' ~ company.bus_id ~ '/edit') }}" class="btn btn-primary">{{ __('company.edit_company') }}</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}