First Commit
This commit is contained in:
Executable
+9
@@ -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.
|
||||
Executable
+21
@@ -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
@@ -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
@@ -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/');
|
||||
}
|
||||
}
|
||||
Executable
+316
@@ -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');
|
||||
}
|
||||
}
|
||||
+42
@@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -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');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+68
@@ -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"
|
||||
}
|
||||
Executable
+68
@@ -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"
|
||||
}
|
||||
Executable
+68
@@ -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"
|
||||
}
|
||||
Executable
+68
@@ -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"
|
||||
}
|
||||
Executable
+80
@@ -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."
|
||||
}
|
||||
Executable
+40
@@ -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"
|
||||
]
|
||||
}
|
||||
Executable
+15
@@ -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']);
|
||||
};
|
||||
Executable
+128
@@ -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'));
|
||||
}
|
||||
}
|
||||
Executable
+295
@@ -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 %}
|
||||
Executable
+84
@@ -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 %}
|
||||
Executable
+176
@@ -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 %}
|
||||
Reference in New Issue
Block a user