mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-05-25 08:44:42 -04:00
Compare commits
17 Commits
feature/in
...
feature/pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ea3ced674 | ||
|
|
896ed87797 | ||
|
|
eb264ad76d | ||
|
|
10a64e7af9 | ||
|
|
6e99f05d63 | ||
|
|
c430c7afb5 | ||
|
|
519347f4f5 | ||
|
|
62d84411b2 | ||
|
|
6bd4bb545d | ||
|
|
66f7d70749 | ||
|
|
bd8b4fa6c1 | ||
|
|
a9669ddf19 | ||
|
|
9a2b308647 | ||
|
|
1f55d96580 | ||
|
|
b2fadea44a | ||
|
|
0fdb3ba37b | ||
|
|
d7b2264ac1 |
144
.github/workflows/integration-tests.yml
vendored
144
.github/workflows/integration-tests.yml
vendored
@@ -1,144 +0,0 @@
|
||||
name: Integration Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, main ]
|
||||
paths:
|
||||
- 'app/**'
|
||||
- 'public/**'
|
||||
- 'docker/**'
|
||||
- 'docker-compose*.yml'
|
||||
- 'tests/**'
|
||||
- 'integration-tests/**'
|
||||
- '.github/workflows/integration-tests.yml'
|
||||
pull_request:
|
||||
branches: [ master, main ]
|
||||
paths:
|
||||
- 'app/**'
|
||||
- 'public/**'
|
||||
- 'docker/**'
|
||||
- 'docker-compose*.yml'
|
||||
- 'tests/**'
|
||||
- 'integration-tests/**'
|
||||
- '.github/workflows/integration-tests.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
integration:
|
||||
name: Docker Integration Tests
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run Basic Integration Tests
|
||||
run: chmod +x integration-tests/run-integration-tests.sh && cd integration-tests && ./run-integration-tests.sh
|
||||
|
||||
- name: View Logs on Failure
|
||||
if: failure()
|
||||
run: |
|
||||
echo "=== Application Logs ==="
|
||||
docker logs opensourcepos-integration-tests-ospos-1
|
||||
echo ""
|
||||
echo "=== Database Logs ==="
|
||||
docker logs mysql || docker logs opensourcepos-mysql || echo "No database logs found"
|
||||
|
||||
- name: Stop Docker Stack
|
||||
if: always()
|
||||
run: docker compose down -v || true
|
||||
|
||||
playwright:
|
||||
name: Playwright E2E Tests
|
||||
runs-on: ubuntu-22.04
|
||||
needs: integration
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps chromium firefox
|
||||
|
||||
- name: Start Docker Stack
|
||||
run: docker compose up -d
|
||||
|
||||
- name: Wait for Application
|
||||
run: |
|
||||
echo "Waiting for application to be ready..."
|
||||
timeout 90 bash -c 'until curl -s -f http://localhost/ > /dev/null; do sleep 2; done'
|
||||
echo "Application is ready!"
|
||||
|
||||
- name: Run Playwright Tests
|
||||
run: npm run test
|
||||
env:
|
||||
BASE_URL: http://localhost
|
||||
|
||||
- name: Upload Playwright Report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report
|
||||
path: integration-tests/playwright-report/
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload Test Results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-results
|
||||
path: integration-tests/test-results/
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload Screenshots on Failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-screenshots
|
||||
path: integration-tests/test-results/**/*.png
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload Trace Files on Failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-traces
|
||||
path: integration-tests/test-results/**/*.zip
|
||||
retention-days: 7
|
||||
|
||||
- name: View Logs on Failure
|
||||
if: failure()
|
||||
run: |
|
||||
echo "=== Application Logs ==="
|
||||
docker logs opensourcepos-integration-tests-ospos-1
|
||||
echo ""
|
||||
echo "=== Database Logs ==="
|
||||
docker logs mysql || docker logs opensourcepos-mysql || echo "No database logs found"
|
||||
|
||||
- name: Stop Docker Stack
|
||||
if: always()
|
||||
run: docker compose down -v || true
|
||||
@@ -205,6 +205,7 @@ class Autoload extends AutoloadConfig
|
||||
'cookie',
|
||||
'tabular',
|
||||
'locale',
|
||||
'security'
|
||||
'security',
|
||||
'plugin'
|
||||
];
|
||||
}
|
||||
|
||||
@@ -8,23 +8,7 @@ use CodeIgniter\HotReloader\HotReloader;
|
||||
use App\Events\Db_log;
|
||||
use App\Events\Load_config;
|
||||
use App\Events\Method;
|
||||
|
||||
/*
|
||||
* --------------------------------------------------------------------
|
||||
* Application Events
|
||||
* --------------------------------------------------------------------
|
||||
* Events allow you to tap into the execution of the program without
|
||||
* modifying or extending core files. This file provides a central
|
||||
* location to define your events, though they can always be added
|
||||
* at run-time, also, if needed.
|
||||
*
|
||||
* You create code that can execute by subscribing to events with
|
||||
* the 'on()' method. This accepts any form of callable, including
|
||||
* Closures, that will be executed when the event is triggered.
|
||||
*
|
||||
* Example:
|
||||
* Events::on('create', [$myInstance, 'myMethod']);
|
||||
*/
|
||||
use App\Libraries\Plugins\PluginManager;
|
||||
|
||||
Events::on('pre_system', static function (): void {
|
||||
if (ENVIRONMENT !== 'testing') {
|
||||
@@ -39,22 +23,19 @@ Events::on('pre_system', static function (): void {
|
||||
ob_start(static fn ($buffer) => $buffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* --------------------------------------------------------------------
|
||||
* Debug Toolbar Listeners.
|
||||
* --------------------------------------------------------------------
|
||||
* If you delete, they will no longer be collected.
|
||||
*/
|
||||
if (CI_DEBUG && ! is_cli()) {
|
||||
Events::on('DBQuery', 'CodeIgniter\Debug\Toolbar\Collectors\Database::collect');
|
||||
service('toolbar')->respond();
|
||||
// Hot Reload route - for framework use on the hot reloader.
|
||||
if (ENVIRONMENT === 'development') {
|
||||
service('routes')->get('__hot-reload', static function (): void {
|
||||
(new HotReloader())->run();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$pluginManager = new PluginManager();
|
||||
$pluginManager->discoverPlugins();
|
||||
$pluginManager->registerPluginEvents();
|
||||
});
|
||||
|
||||
$config = new Load_config();
|
||||
@@ -64,4 +45,4 @@ $db_log = new Db_log();
|
||||
Events::on('DBQuery', [$db_log, 'db_log_queries']);
|
||||
|
||||
$method = new Method();
|
||||
Events::on('pre_controller', [$method, 'validate_method']);
|
||||
Events::on('pre_controller', [$method, 'validate_method']);
|
||||
@@ -876,12 +876,12 @@ class Items extends Secure_Controller
|
||||
$items_to_update = $this->request->getPost('item_ids');
|
||||
$item_data = [];
|
||||
|
||||
foreach ($_POST as $key => $value) {
|
||||
// This field is nullable, so treat it differently
|
||||
if ($key === 'supplier_id' && $value !== '') {
|
||||
$item_data[$key] = $value;
|
||||
} elseif ($value !== '' && !(in_array($key, ['item_ids', 'tax_names', 'tax_percents']))) {
|
||||
$item_data[$key] = $value;
|
||||
foreach (Item::ALLOWED_BULK_EDIT_FIELDS as $field) {
|
||||
$value = $this->request->getPost($field);
|
||||
if ($field === 'supplier_id' && $value !== '') {
|
||||
$item_data[$field] = $value;
|
||||
} elseif ($value !== null && $value !== '') {
|
||||
$item_data[$field] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
99
app/Controllers/Plugins/Manage.php
Normal file
99
app/Controllers/Plugins/Manage.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Plugins;
|
||||
|
||||
use App\Controllers\Secure_Controller;
|
||||
use App\Libraries\Plugins\PluginManager;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
class Manage extends Secure_Controller
|
||||
{
|
||||
private PluginManager $pluginManager;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('plugins');
|
||||
$this->pluginManager = new PluginManager();
|
||||
$this->pluginManager->discoverPlugins();
|
||||
}
|
||||
|
||||
public function getIndex(): string
|
||||
{
|
||||
$plugins = $this->pluginManager->getAllPlugins();
|
||||
$enabledPlugins = $this->pluginManager->getEnabledPlugins();
|
||||
|
||||
$pluginData = [];
|
||||
foreach ($plugins as $pluginId => $plugin) {
|
||||
$pluginData[$pluginId] = [
|
||||
'id' => $plugin->getPluginId(),
|
||||
'name' => $plugin->getPluginName(),
|
||||
'description' => $plugin->getPluginDescription(),
|
||||
'version' => $plugin->getVersion(),
|
||||
'enabled' => isset($enabledPlugins[$pluginId]),
|
||||
'has_config' => $plugin->getConfigView() !== null,
|
||||
];
|
||||
}
|
||||
|
||||
echo view('plugins/manage', ['plugins' => $pluginData]);
|
||||
return '';
|
||||
}
|
||||
|
||||
public function postEnable(string $pluginId): ResponseInterface
|
||||
{
|
||||
if ($this->pluginManager->enablePlugin($pluginId)) {
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Plugins.plugin_enabled')]);
|
||||
}
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Plugins.plugin_enable_failed')]);
|
||||
}
|
||||
|
||||
public function postDisable(string $pluginId): ResponseInterface
|
||||
{
|
||||
if ($this->pluginManager->disablePlugin($pluginId)) {
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Plugins.plugin_disabled')]);
|
||||
}
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Plugins.plugin_disable_failed')]);
|
||||
}
|
||||
|
||||
public function postUninstall(string $pluginId): ResponseInterface
|
||||
{
|
||||
if ($this->pluginManager->uninstallPlugin($pluginId)) {
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Plugins.plugin_uninstalled')]);
|
||||
}
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Plugins.plugin_uninstall_failed')]);
|
||||
}
|
||||
|
||||
public function getConfig(string $pluginId): ResponseInterface
|
||||
{
|
||||
$plugin = $this->pluginManager->getPlugin($pluginId);
|
||||
|
||||
if (!$plugin) {
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Plugins.plugin_not_found')]);
|
||||
}
|
||||
|
||||
$configView = $plugin->getConfigView();
|
||||
if (!$configView) {
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Plugins.plugin_no_config')]);
|
||||
}
|
||||
|
||||
$settings = $plugin->getSettings();
|
||||
echo view($configView, ['settings' => $settings, 'plugin' => $plugin]);
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
public function postSaveConfig(string $pluginId): ResponseInterface
|
||||
{
|
||||
$plugin = $this->pluginManager->getPlugin($pluginId);
|
||||
|
||||
if (!$plugin) {
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Plugins.plugin_not_found')]);
|
||||
}
|
||||
|
||||
$settings = $this->request->getPost();
|
||||
unset($settings['_method'], $settings['csrf_token_name']);
|
||||
|
||||
if ($plugin->saveSettings($settings)) {
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Plugins.settings_saved')]);
|
||||
}
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Plugins.settings_save_failed')]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class PluginConfigTableCreate extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
log_message('info', 'Migrating plugin_config table started');
|
||||
|
||||
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.4.1_PluginConfigTableCreate.sql');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('plugin_config', true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
CREATE TABLE IF NOT EXISTS `ospos_plugin_config` (
|
||||
`key` varchar(100) NOT NULL,
|
||||
`value` text NOT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
PRIMARY KEY (`key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
24
app/Helpers/plugin_helper.php
Normal file
24
app/Helpers/plugin_helper.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use CodeIgniter\Events\Events;
|
||||
|
||||
if (!function_exists('plugin_content')) {
|
||||
function plugin_content(string $section, array $data = []): string
|
||||
{
|
||||
$results = Events::trigger("view:{$section}", $data);
|
||||
|
||||
if (is_array($results)) {
|
||||
return implode('', array_filter($results, fn($r) => is_string($r)));
|
||||
}
|
||||
|
||||
return is_string($results) ? $results : '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('plugin_content_exists')) {
|
||||
function plugin_content_exists(string $section): bool
|
||||
{
|
||||
$observers = Events::listRegistered("view:{$section}");
|
||||
return !empty($observers);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "كلمة المرور الحالية غير صحيحة.",
|
||||
"employee" => "موظف",
|
||||
"error_adding_updating" => "خطاء فى إضافة/تعديل موظف.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "لايمكن حذف المستخدم admin الخاص بنسخة العرض.",
|
||||
"error_updating_demo_admin" => "لايمكن تغيير بيانات المستخدم admin الخاص بنسخة العرض.",
|
||||
"language" => "اللغة",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "سعر التكلفة مطلوب.",
|
||||
"count" => "تحديث المخزون",
|
||||
"csv_import_failed" => "فشل الإستيراد من اكسل",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "الملف الذى رفعته إما فارغ أو أنه مختلف البنية.",
|
||||
"csv_import_partially_failed" => "يوجد خطأ بنسبة {0} في استيراد الاصناف في السطر: {1}. لم يتم استيرادهم.",
|
||||
"csv_import_success" => "تم استيراد الأصناف بنجاح.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "كلمة المرور الحالية غير صحيحة.",
|
||||
"employee" => "موظف",
|
||||
"error_adding_updating" => "خطاء فى إضافة/تعديل موظف.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "لايمكن حذف المستخدم admin الخاص بنسخة العرض.",
|
||||
"error_updating_demo_admin" => "لايمكن تغيير بيانات المستخدم admin الخاص بنسخة العرض.",
|
||||
"language" => "اللغة",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "سعر التكلفة مطلوب.",
|
||||
"count" => "تحديث المخزون",
|
||||
"csv_import_failed" => "فشل الإستيراد من اكسل",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "الملف الذى رفعته إما فارغ أو أنه مختلف البنية.",
|
||||
"csv_import_partially_failed" => "يوجد خطأ بنسبة {0} في استيراد الاصناف في السطر: {1}. لم يتم استيرادهم.",
|
||||
"csv_import_success" => "تم استيراد الأصناف بنجاح.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Hazirki Şifrə düzgün deyil.",
|
||||
"employee" => "Əməkdaş",
|
||||
"error_adding_updating" => "Əməkdaş əlavə etməsk və ya yeniləməsi baş vermədi.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Demo administrator istifadəçisini silə bilməzsiniz.",
|
||||
"error_updating_demo_admin" => "Demo administrator istifadəçisini dəyişə bilməzsiniz.",
|
||||
"language" => "Dil",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Topdan satiış - doldurulması vacib sahə.",
|
||||
"count" => "inventorun yenilənməsi",
|
||||
"csv_import_failed" => "səhv csv import",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Yüklənmiş faylda məlumat yoxdur və ya düzgün formatlanmır.",
|
||||
"csv_import_partially_failed" => "Xətlərdə {0} element idxalı uğursuzluq (lar) var: {1}. Heç bir sıra idxal edilmədi.",
|
||||
"csv_import_success" => "Malların İdxalı Uğurla Həyata Keçdi.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Текущата парола е невалидна.",
|
||||
"employee" => "Служител",
|
||||
"error_adding_updating" => "Добавянето или актуализирането на служителите е неуспешно.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Не може да изтриете Пробният Администратор.",
|
||||
"error_updating_demo_admin" => "Не може да промените Пробният Администратор.",
|
||||
"language" => "Език",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Wholesale Price is a required field.",
|
||||
"count" => "Update Inventory",
|
||||
"csv_import_failed" => "CSV import failed",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "The uploaded file has no data or is formatted incorrectly.",
|
||||
"csv_import_partially_failed" => "Item import successful with some failures:",
|
||||
"csv_import_success" => "Item import successful.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Trenutna lozinka je nevažeća.",
|
||||
"employee" => "Zaposlenik",
|
||||
"error_adding_updating" => "Dodavanje ili ažuriranje zaposlenika nije uspjelo.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Ne možete izbrisati demo korisnika administratora.",
|
||||
"error_updating_demo_admin" => "Ne možete promijeniti korisnika demo administratora.",
|
||||
"language" => "Jezik",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Fakturna cijena je obavezno polje.",
|
||||
"count" => "Ažuriraj zalihu",
|
||||
"csv_import_failed" => "Uvoz CSV-a nije uspio",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Učitana CSV datoteka nema podatke ili je pogrešno formatirana.",
|
||||
"csv_import_partially_failed" => "Bilo je {0} grešaka pri uvozu stavke na liniji: {1}. Nijedan red nije uvezen.",
|
||||
"csv_import_success" => "Uvoz CSV stavke je uspješan.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
'current_password_invalid' => "وشەی نهێنی ئێستا نادروستە.",
|
||||
'employee' => "فەرمانبەر",
|
||||
'error_adding_updating' => "زیادکردن یان نوێکردنەوەی کارمەند سەرکەوتوو نەبوو.",
|
||||
'error_deleting_admin' => "",
|
||||
'error_updating_admin' => "",
|
||||
'error_deleting_demo_admin' => "ناتوانیت بەکارهێنەری ئەدمینی تاقیکردنەوەیی بسڕیتەوە.",
|
||||
'error_updating_demo_admin' => "ناتوانیت بەکارهێنەری ئەدمین تاقیکردنەوەیی بگۆڕیت.",
|
||||
'language' => "زمان",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
'cost_price_required' => "نرخی جوملە خانەیەکی پێویستە.",
|
||||
'count' => "جەرد نوێ بکەوە",
|
||||
'csv_import_failed' => "هاوردەکردنی CSV سەرکەوتوو نەبوو",
|
||||
'csv_import_invalid_location' => "",
|
||||
'csv_import_nodata_wrongformat' => "پەڕگەی CSV بارکراو هیچ داتایەکی نییە یان بە هەڵە فۆرمات کراوە.",
|
||||
'csv_import_partially_failed' => "{0} شکستی هاوردەکردنی بابەتی لەسەر هێڵەکان هەبوو: {1}. هیچ ڕیزێک هاوردە نەکرا.",
|
||||
'csv_import_success' => "بابەتی هاوردەکردنی CSV سەرکەوتوو بوو.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "",
|
||||
"employee" => "",
|
||||
"error_adding_updating" => "",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "",
|
||||
"error_updating_demo_admin" => "",
|
||||
"language" => "",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Musíte zadat nákupní cenu.",
|
||||
"count" => "Upravit množství",
|
||||
"csv_import_failed" => "Import z CSVu se nepovedl",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Nahraný soubor neobsahuje žádná data nebo má špatný formát.",
|
||||
"csv_import_partially_failed" => "Při importu položek došlo k několika chybám:",
|
||||
"csv_import_success" => "Import položek proběhl bez chyby.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Current Password is invalid.",
|
||||
"employee" => "Employee",
|
||||
"error_adding_updating" => "Employee add or update failed.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "You can not delete the demo admin user.",
|
||||
"error_updating_demo_admin" => "You can not change the demo admin user.",
|
||||
"language" => "Language",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "",
|
||||
"count" => "",
|
||||
"csv_import_failed" => "",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "",
|
||||
"csv_import_partially_failed" => "",
|
||||
"csv_import_success" => "",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "",
|
||||
"employee" => "Mitarbeiter",
|
||||
"error_adding_updating" => "Fehler beim Hinzufügen/Ändern",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Sie können den Admin nicht löschen",
|
||||
"error_updating_demo_admin" => "Sie können den Admin nicht ändern",
|
||||
"language" => "",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Einstandspreis ist erforderlich",
|
||||
"count" => "Ändere Bestand",
|
||||
"csv_import_failed" => "CSV Import fehlerhaft",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Your uploaded file has no data or wrong format",
|
||||
"csv_import_partially_failed" => "Most Items imported. But some were not, here is the list",
|
||||
"csv_import_success" => "Import of Items successful",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Aktuelles Passwort ist ungültig.",
|
||||
"employee" => "Mitarbeiter",
|
||||
"error_adding_updating" => "Fehler beim Hinzufügen/Ändern.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Sie können den Demo-Administrator nicht löschen.",
|
||||
"error_updating_demo_admin" => "Sie können den Demo-Administrator nicht verändern.",
|
||||
"language" => "Sprache",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Der Großhandelspreis ist ein Pflichtfeld.",
|
||||
"count" => "Ändere Bestand",
|
||||
"csv_import_failed" => "CSV Import fehlgeschlagen",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Die hochgeladene Datei enthält keine Daten oder ist falsch formatiert.",
|
||||
"csv_import_partially_failed" => "{0} Artikel-Import Fehler in Zeile: {1}. Keine Reihen wurden importiert.",
|
||||
"csv_import_success" => "Artikelimport erfolgreich.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "",
|
||||
"employee" => "",
|
||||
"error_adding_updating" => "",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "",
|
||||
"error_updating_demo_admin" => "",
|
||||
"language" => "",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "",
|
||||
"count" => "",
|
||||
"csv_import_failed" => "",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "",
|
||||
"csv_import_partially_failed" => "",
|
||||
"csv_import_success" => "",
|
||||
|
||||
27
app/Language/en/Plugins.php
Normal file
27
app/Language/en/Plugins.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
return [
|
||||
// Plugin Management
|
||||
"plugins" => "Plugins",
|
||||
"plugin_management" => "Plugin Management",
|
||||
"plugin_name" => "Plugin Name",
|
||||
"plugin_description" => "Description",
|
||||
"plugin_version" => "Version",
|
||||
"plugin_status" => "Status",
|
||||
"plugin_enabled" => "Plugin enabled successfully",
|
||||
"plugin_enable_failed" => "Failed to enable plugin",
|
||||
"plugin_disabled" => "Plugin disabled successfully",
|
||||
"plugin_disable_failed" => "Failed to disable plugin",
|
||||
"plugin_uninstalled" => "Plugin uninstalled successfully",
|
||||
"plugin_uninstall_failed" => "Failed to uninstall plugin",
|
||||
"plugin_not_found" => "Plugin not found",
|
||||
"plugin_no_config" => "This plugin has no configuration options",
|
||||
"settings_saved" => "Plugin settings saved successfully",
|
||||
"settings_save_failed" => "Failed to save plugin settings",
|
||||
"enable" => "Enable",
|
||||
"disable" => "Disable",
|
||||
"configure" => "Configure",
|
||||
"uninstall" => "Uninstall",
|
||||
"no_plugins_found" => "No plugins found",
|
||||
"active" => "Active",
|
||||
"inactive" => "Inactive",
|
||||
];
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Contraseña Actual Inválida.",
|
||||
"employee" => "Empleado",
|
||||
"error_adding_updating" => "Error al agregar/actualizar empleado.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "No puedes borrar el usuario admin del demo.",
|
||||
"error_updating_demo_admin" => "No puedes cambiar el usuario admin del demo.",
|
||||
"language" => "Idioma",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Precio al Por Mayor es un campo requerido.",
|
||||
"count" => "Actualizar Inventario",
|
||||
"csv_import_failed" => "Falló la importación de Hoja de Cálculo",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "El archivo subido no tiene datos o el formato es incorrecto.",
|
||||
"csv_import_partially_failed" => "Hubo {0} falla(s) en la importación de producto(s) en la(s) línea(s): {1}. Ninguna fila ha sido importada.",
|
||||
"csv_import_success" => "Se importaron los articulos exitosamente.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "La contraseña actual es inválida.",
|
||||
"employee" => "Empleado",
|
||||
"error_adding_updating" => "Agregar ó Actualizar empleado ha fallado.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "No puede borrar el usuario demo de administrador.",
|
||||
"error_updating_demo_admin" => "No puede cambiar el usuario demo de administrador.",
|
||||
"language" => "Idioma",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "El precio de mayoreo es requerido.",
|
||||
"count" => "Actualizar inventario",
|
||||
"csv_import_failed" => "",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "",
|
||||
"csv_import_partially_failed" => "",
|
||||
"csv_import_success" => "",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "گذرواژه فعلی نامعتبر است.",
|
||||
"employee" => "کارمند",
|
||||
"error_adding_updating" => "افزودن یا به روزرسانی کارکنان انجام نشد.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "شما نمی توانید کاربر مدیر نسخه ی نمایشی را حذف کنید.",
|
||||
"error_updating_demo_admin" => "شما نمی توانید کاربر مدیر نسخه ی نمایشی را تغییر دهید.",
|
||||
"language" => "زبان",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "قیمت عمده فروشی یک زمینه ضروری است.",
|
||||
"count" => "به روزرسانی موجودی",
|
||||
"csv_import_failed" => "واردات سیاسوی انجام نشد",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "پرونده سیاسوی آپلود شده داده ای ندارد یا به طور نادرست قالب بندی شده است.",
|
||||
"csv_import_partially_failed" => "در خط (ها){0} شکست واردات کالا وجود دارد:{1}. هیچ سطر وارد نشده است.",
|
||||
"csv_import_success" => "وارد کردن سیاسوی مورد موفقیت آمیز است.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Le mot de passe actuel est invalide.",
|
||||
"employee" => "Employé",
|
||||
"error_adding_updating" => "Erreur d'ajout/édition d'employé.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Vous ne pouvez pas supprimer l'utilisateur de démonstration admin.",
|
||||
"error_updating_demo_admin" => "Vous ne pouvez pas modifier l'utilisateur de démonstration admin.",
|
||||
"language" => "Langue",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Le prix de gros est requis.",
|
||||
"count" => "Mise à jour de l'inventaire",
|
||||
"csv_import_failed" => "Échec d'import CSV",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Le CSV envoyé ne contient aucune donnée, ou elles sont dans un format erroné.",
|
||||
"csv_import_partially_failed" => "Il y a eu {0} importation(s) d'articles échoué(s) au(x) ligne(s) : {1}. Aucune ligne n'a été importée.",
|
||||
"csv_import_success" => "Importation des articles réussie.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "הסיסמה הנוכחית אינה חוקית.",
|
||||
"employee" => "עובד",
|
||||
"error_adding_updating" => "הוספה או עדכון של עובד נכשלה.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "לא ניתן למחוק את משתמש המנהל ההדגמה.",
|
||||
"error_updating_demo_admin" => "לא ניתן לשנות את משתמש המנהל ההדגמה.",
|
||||
"language" => "שפה",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "מחיר סיטונאי הינו שדה חובה.",
|
||||
"count" => "עדכן מלאי",
|
||||
"csv_import_failed" => "ייבוא אקסל נכשל",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "בקובץ שהועלה אין נתונים או פורמט שגוי.",
|
||||
"csv_import_partially_failed" => "ייבוא פריט הצליח עם מספר שגיאות:",
|
||||
"csv_import_success" => "ייבוא הפריט הצליח.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "",
|
||||
"employee" => "Radnik",
|
||||
"error_adding_updating" => "Greška kod dodavanja/ažuriranja radnika",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Ne možete obrisati demo admin korisnika",
|
||||
"error_updating_demo_admin" => "Ne možete promijeniti demo admin korisnika",
|
||||
"language" => "",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Nabavna cijena je potrebna",
|
||||
"count" => "Ažuriraj inveturu",
|
||||
"csv_import_failed" => "Greška kod uvoza iz CSV-a",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Your uploaded file has no data or wrong format",
|
||||
"csv_import_partially_failed" => "Most Items imported. But some were not, here is the list",
|
||||
"csv_import_success" => "Import of Items successful",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "",
|
||||
"employee" => "Munkavállaló",
|
||||
"error_adding_updating" => "Hiba a munkavállaló módosításánál/hozzáadásánál",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Nem tudja törölni a demo admin felhasználót",
|
||||
"error_updating_demo_admin" => "Nem tudja módosítani a demo admin felhasználót",
|
||||
"language" => "",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Bekerülési ár kötelező mező",
|
||||
"count" => "Raktárkészlet módosítása",
|
||||
"csv_import_failed" => "CSV import sikertelen",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "A feltöltött fájlban nincs adat, vagy rossz formátum.",
|
||||
"csv_import_partially_failed" => "Most Items imported. But some were not, here is the list",
|
||||
"csv_import_success" => "Import of Items successful",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "",
|
||||
"employee" => "",
|
||||
"error_adding_updating" => "",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "",
|
||||
"error_updating_demo_admin" => "",
|
||||
"language" => "",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "",
|
||||
"count" => "",
|
||||
"csv_import_failed" => "",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "",
|
||||
"csv_import_partially_failed" => "",
|
||||
"csv_import_success" => "",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Kata kunci sekarang salah.",
|
||||
"employee" => "Karyawan",
|
||||
"error_adding_updating" => "Kesalahan menambah / memperbarui karyawan.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Anda tidak dapat menghapus Demo admin user.",
|
||||
"error_updating_demo_admin" => "Anda tidak dapat mengubah Demo admin user.",
|
||||
"language" => "Bahasa",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Harga beli harus diisi.",
|
||||
"count" => "Mutasi Inventori",
|
||||
"csv_import_failed" => "Gagal impor CSV",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Berkas CSV terunggah tidak berisi data atau formatnya salah.",
|
||||
"csv_import_partially_failed" => "Terdapat {0} item gagal impor pada baris: {1}. Tidak ada baris yang diimpor.",
|
||||
"csv_import_success" => "Impor item CSV berhasil.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Password corrente non valida.",
|
||||
"employee" => "Impiegato",
|
||||
"error_adding_updating" => "Aggiunta o aggiornamento di impiegati fallito.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Non puoi eliminare l'utente admin demo.",
|
||||
"error_updating_demo_admin" => "Non puoi cambiare l'utente admin demo.",
|
||||
"language" => "Lingua",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Prezzo all'ingrosso è un campo obbligatorio.",
|
||||
"count" => "Aggiorna Inventario",
|
||||
"csv_import_failed" => "Importazione CSV fallita",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "L'upload del file non ha dati o non è formattato correttamente.",
|
||||
"csv_import_partially_failed" => "Si sono verificati {0} errori di importazione degli elementi nelle righe: {1}. Nessuna riga è stata importata.",
|
||||
"csv_import_success" => "Importazione CSV dell'articolo riuscita.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "ពាក្យសម្ងាត់បច្ចុប្បន្ន មិនត្រឹមត្រូវ។",
|
||||
"employee" => "បុគ្គលិក",
|
||||
"error_adding_updating" => "បន្ថែម ឬកែប្រែបុគ្គលិកមិនបានសំរេច។",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "អ្នកមិនអាចលុប គណនីសាកល្បង បានទេ។",
|
||||
"error_updating_demo_admin" => "អ្នកមិនអាចកែប្រែ គណនីសាកល្បងបានទេ។",
|
||||
"language" => "ភាសា",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "ត្រូវការតម្លៃលក់ដុំជាចាំបាច់។",
|
||||
"count" => "កែប្រែ ទំនិញក្នុងស្តុក",
|
||||
"csv_import_failed" => "CSV បញ្ចូលមិនបានសំរេច",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "ដាក់បញ្ជុល CSV មិនមានទិន្នន័យ ឬទំរង់មិនត្រឹមត្រូវ។",
|
||||
"csv_import_partially_failed" => "មានទំននិញ {0} បញ្ជូលមិនបានសំរេច នៅជួរ: {1} ។ គ្មានជួរណាមួយត្រូវបានបញ្ជូលនោះទេ។",
|
||||
"csv_import_success" => "ទំនិញក្នុង CSV បញ្ចូលបានសំរេច។",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Password ປັດຈຸບັນບໍ່ຖືກຕ້ອງ.",
|
||||
"employee" => "ພະນັກງານ",
|
||||
"error_adding_updating" => "ເພີ່ມ ຫຼື ແກ້ໄຂ ພະນັກງານ ບໍ່ສຳເລັດ.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "ທ່ານບໍ່ສາມາດລຶບບັນຊີທົດລອງຜູ້ດູແລລະບົບໄດ້.",
|
||||
"error_updating_demo_admin" => "ທ່ານບໍ່ສາມາດປ່ຽນແປງບັນຊີທົດລອງຜູ້ດູແລລະບົບໄດ້.",
|
||||
"language" => "ພາສາ",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "ກະລຸນາກຳນົດລາຄາຕົ້ນທຶນ.",
|
||||
"count" => "ອັບເດດປະລິມານສິນຄ້າໃນສາງ",
|
||||
"csv_import_failed" => "CSV import failed",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "The uploaded file has no data or is formatted incorrectly.",
|
||||
"csv_import_partially_failed" => "Item import successful with some failures:",
|
||||
"csv_import_success" => "Item import successful.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "",
|
||||
"employee" => "",
|
||||
"error_adding_updating" => "",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "",
|
||||
"error_updating_demo_admin" => "",
|
||||
"language" => "",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "",
|
||||
"count" => "",
|
||||
"csv_import_failed" => "",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "",
|
||||
"csv_import_partially_failed" => "",
|
||||
"csv_import_success" => "",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "",
|
||||
"employee" => "",
|
||||
"error_adding_updating" => "",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "",
|
||||
"error_updating_demo_admin" => "",
|
||||
"language" => "",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "",
|
||||
"count" => "",
|
||||
"csv_import_failed" => "",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "",
|
||||
"csv_import_partially_failed" => "",
|
||||
"csv_import_success" => "",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Huidig paswoord is ongeldig.",
|
||||
"employee" => "Werknemer",
|
||||
"error_adding_updating" => "Fout bij het toevoegen/aanpassen medewerker.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Je kan de demo gebruilker niet verwijderen.",
|
||||
"error_updating_demo_admin" => "Jij kan de demo gebruiker niet veranderen.",
|
||||
"language" => "Taal",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Groothandelsprijs is een verplicht veld.",
|
||||
"count" => "Update Stock",
|
||||
"csv_import_failed" => "CSV import mislukt",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Het geüploade CSV-bestand bevat geen gegevens of is onjuist geformatteerd.",
|
||||
"csv_import_partially_failed" => "Er waren {0} artikel import fout(en) op regel(s): {1}. Er werden geen rijen geïmporteerd.",
|
||||
"csv_import_success" => "Artikel CSV import geslaagd.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Huidige wachtwoord is ongeldig.",
|
||||
"employee" => "Werknemer",
|
||||
"error_adding_updating" => "Werknemer toevoegen of bijwerken mislukt.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Kan de demo admin gebruiker niet verwijderen.",
|
||||
"error_updating_demo_admin" => "Kan de demo admin gebruiker niet wijzigen.",
|
||||
"language" => "Taal",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Inkoopprijs is een vereist veld.",
|
||||
"count" => "Voorraad bijwerken",
|
||||
"csv_import_failed" => "CSV importeren mislukt",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Het geüploade CSV-bestand bevat geen gegevens or heeft de verkeerde indeling.",
|
||||
"csv_import_partially_failed" => "Er zijn {0} artikel import fout(en) in lijn(en): {1}. Geen rijen geïmporteerd.",
|
||||
"csv_import_success" => "Artikel CSV geïmporteerd.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "",
|
||||
"employee" => "",
|
||||
"error_adding_updating" => "",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "",
|
||||
"error_updating_demo_admin" => "",
|
||||
"language" => "",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "",
|
||||
"count" => "",
|
||||
"csv_import_failed" => "",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "",
|
||||
"csv_import_partially_failed" => "",
|
||||
"csv_import_success" => "",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Senha atual inválida.",
|
||||
"employee" => "Funcionário",
|
||||
"error_adding_updating" => "Erro ao adicionar/atualizar funcionário.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Você não pode excluir o usuário administrador de demonstração.",
|
||||
"error_updating_demo_admin" => "Você não pode alterar o usuário de demonstração de administração.",
|
||||
"language" => "Linguagem",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Preço de custo é um campo obrigatório.",
|
||||
"count" => "Acrescentar ao Inventário",
|
||||
"csv_import_failed" => "Importação do CSV falhou",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Seu arquivo enviado não contém dados ou formato errado.",
|
||||
"csv_import_partially_failed" => "Houve {0} falha na importação de itens na(s) linha(s): {1}. Nenhuma linha foi importada.",
|
||||
"csv_import_success" => "Importação de Itens com sucesso.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "",
|
||||
"employee" => "",
|
||||
"error_adding_updating" => "",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "",
|
||||
"error_updating_demo_admin" => "",
|
||||
"language" => "",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "",
|
||||
"count" => "",
|
||||
"csv_import_failed" => "",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "",
|
||||
"csv_import_partially_failed" => "",
|
||||
"csv_import_success" => "",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Текущий пароль введен неверно.",
|
||||
"employee" => "Сотрудник",
|
||||
"error_adding_updating" => "Ошибка при добавлении/обновлении сотрудника.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Вы не можете удалить демо-администратора.",
|
||||
"error_updating_demo_admin" => "Вы не можете изменить демо-администратора.",
|
||||
"language" => "Язык",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Оптовая цена - обязательное поле.",
|
||||
"count" => "Обновление запасов",
|
||||
"csv_import_failed" => "Ошибка импорта CSV",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Загруженный файл CSV не содержит данных или имеет неправильный формат.",
|
||||
"csv_import_partially_failed" => "В строке (строках) произошло {0} ошибок импорта: {1}. Ничего не было импортировано.",
|
||||
"csv_import_success" => "Товар успешно импортирован из CSV.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Nuvarande lösenord är fel.",
|
||||
"employee" => "Anställd",
|
||||
"error_adding_updating" => "Anställd lägg till eller uppdatering misslyckades.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Du kan inte radera demo admin-användaren.",
|
||||
"error_updating_demo_admin" => "Du kan inte ändra demo admin-användaren.",
|
||||
"language" => "Språk",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Grossistpris är ett obligatoriskt fält.",
|
||||
"count" => "Uppdatera Inventory",
|
||||
"csv_import_failed" => "CSV-import misslyckades",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Den uppladdade filen har ingen data eller är formaterad felaktigt.",
|
||||
"csv_import_partially_failed" => "Det fanns{0} importfel (er) på rad (er):{1}. Inga rader importerades.",
|
||||
"csv_import_success" => "Artikelimporten lyckades.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Nenosiri la sasa si sahihi.",
|
||||
"employee" => "Mfanyakazi",
|
||||
"error_adding_updating" => "Kuongeza au kusasisha mfanyakazi kumeshindikana.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Huwezi kufuta mtumiaji wa admin wa majaribio.",
|
||||
"error_updating_demo_admin" => "Huwezi kubadilisha mtumiaji wa admin wa majaribio.",
|
||||
"language" => "Lugha",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Bei ya Jumla ni kiashiria kinachohitajika.",
|
||||
"count" => "Sasisha Hisa",
|
||||
"csv_import_failed" => "Uingizaji wa CSV umeshindikana",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Faili ya CSV iliyopakiwa haina data au imepangwa vibaya.",
|
||||
"csv_import_partially_failed" => "Kumekuwa na makosa {0} ya uingizaji wa bidhaa kwenye mstari: {1}. Hakuna safu zilizoingizwa.",
|
||||
"csv_import_success" => "Uingizaji wa Bidhaa kutoka CSV umefanikiwa.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Nenosiri la sasa si sahihi.",
|
||||
"employee" => "Mfanyakazi",
|
||||
"error_adding_updating" => "Kuongeza au kusasisha mfanyakazi kumeshindikana.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Huwezi kufuta mtumiaji wa admin wa majaribio.",
|
||||
"error_updating_demo_admin" => "Huwezi kubadilisha mtumiaji wa admin wa majaribio.",
|
||||
"language" => "Lugha",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Bei ya Jumla ni kiashiria kinachohitajika.",
|
||||
"count" => "Sasisha Hisa",
|
||||
"csv_import_failed" => "Uingizaji wa CSV umeshindikana",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Faili ya CSV iliyopakiwa haina data au imepangwa vibaya.",
|
||||
"csv_import_partially_failed" => "Kumekuwa na makosa {0} ya uingizaji wa bidhaa kwenye mstari: {1}. Hakuna safu zilizoingizwa.",
|
||||
"csv_import_success" => "Uingizaji wa Bidhaa kutoka CSV umefanikiwa.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Current Password is invalid.",
|
||||
"employee" => "Employee",
|
||||
"error_adding_updating" => "Employee add or update failed.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "You can not delete the demo admin user.",
|
||||
"error_updating_demo_admin" => "You can not change the demo admin user.",
|
||||
"language" => "Language",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Wholesale Price is a required field.",
|
||||
"count" => "Update Inventory",
|
||||
"csv_import_failed" => "CSV import failed",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "The uploaded CSV file has no data or is formatted incorrectly.",
|
||||
"csv_import_partially_failed" => "There were {0} item import failure(s) on line(s): {1}. No rows were imported.",
|
||||
"csv_import_success" => "Item CSV import successful.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "รหัสผ่านปัจจุบันไม่ถูกต้อง",
|
||||
"employee" => "พนักงาน",
|
||||
"error_adding_updating" => "การเพิ่มหรือปรับปรุงข้อมูลพนักงานผิดพลาด",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "คุณไม่สามารถลบผู้ใช้งานสำหรับการเดโม้ได้",
|
||||
"error_updating_demo_admin" => "คุณไม่สามารถทำการเปลี่ยนข้อมูลผู้ใช้งานเดโม้ได้",
|
||||
"language" => "ภาษา",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "ต้องกรอกราคาขายส่ง",
|
||||
"count" => "แก้ไขจำนวนสินค้าคงคลัง",
|
||||
"csv_import_failed" => "นำเข้าข้อมูล CSV ล้มเหลว",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Your uploaded file has no data or wrong format",
|
||||
"csv_import_partially_failed" => "มีรายการ {0} รายการที่นำเข้าล้มเหลว : {1} รายการที่ยังไม่ได้นำเข้า",
|
||||
"csv_import_success" => "Import of Items successful",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Current Password is invalid.",
|
||||
"employee" => "Employee",
|
||||
"error_adding_updating" => "Employee add or update failed.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "You can not change the demo admin user.",
|
||||
"error_updating_demo_admin" => "You can not delete the demo admin user.",
|
||||
"language" => "Language",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Purchase Price is a required field.",
|
||||
"count" => "Update Inventory",
|
||||
"csv_import_failed" => "CSV import failed",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "The uploaded file has no data or is formatted incorrectly.",
|
||||
"csv_import_partially_failed" => "Customer import successful with some failures:",
|
||||
"csv_import_success" => "Item import successful.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Var Olan Parola geçersiz.",
|
||||
"employee" => "Personel",
|
||||
"error_adding_updating" => "Personel ekleme/güncelleme hatası.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Admin güncellenemez.",
|
||||
"error_updating_demo_admin" => "Admin silinemez.",
|
||||
"language" => "Dil",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Toptan Fiyatı zorunlu alandır.",
|
||||
"count" => "Stoğu Güncelle",
|
||||
"csv_import_failed" => "CSV aktarım hatası",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Yüklenen dosya herhangi bir veri içermiyor veya hatalı formatta.",
|
||||
"csv_import_partially_failed" => "Bazı ürünler aktarılamadı. Aktarılamayanlar listesi.",
|
||||
"csv_import_success" => "Ürün aktarımı başarılı.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Поточний пароль невірний.",
|
||||
"employee" => "Працівник",
|
||||
"error_adding_updating" => "Помилка при додаванні/оновлені працівника.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Ви не можете видалити аккаунт користувача.",
|
||||
"error_updating_demo_admin" => "Ви не можете змінити аккаунт користувача.",
|
||||
"language" => "Мова",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Оптова ціна - обов'язкове поле.",
|
||||
"count" => "Оновити інвентар",
|
||||
"csv_import_failed" => "Помилка імпорту CSV",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Завандажений файл порожній або відформатований неправильно.",
|
||||
"csv_import_partially_failed" => "У рядках виявлено {0} помилки імпортування елементів: {1}. Не було імпортовано жодних рядків.",
|
||||
"csv_import_success" => "Імпорт товару CSV успішний.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "",
|
||||
"employee" => "",
|
||||
"error_adding_updating" => "",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "",
|
||||
"error_updating_demo_admin" => "",
|
||||
"language" => "",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "",
|
||||
"count" => "",
|
||||
"csv_import_failed" => "",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "",
|
||||
"csv_import_partially_failed" => "",
|
||||
"csv_import_success" => "",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "Mật khẩu hiện tại không hợp lệ.",
|
||||
"employee" => "Nhân viên",
|
||||
"error_adding_updating" => "Gặp lỗi khi cập nhật hay thêm nhân viên.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "Bạn không thể xóa người dùng demo admin.",
|
||||
"error_updating_demo_admin" => "Bạn không thể thay đổi người dùng demo admin.",
|
||||
"language" => "Ngôn ngữ",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "Trường Giá bán buôn là bắt buộc.",
|
||||
"count" => "Cập hàng tồn kho",
|
||||
"csv_import_failed" => "Gặp lỗi khi nhập từ CSV",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Tập tin tải lên không có dữ liệu hoặc là nó có định dạng không đúng.",
|
||||
"csv_import_partially_failed" => "Có {0} mục tin gặp lỗi khi nhập trên dòng: {1}. Không có hàng nào được nhập vào.",
|
||||
"csv_import_success" => "Nhập hàng hóa thành công.",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "",
|
||||
"employee" => "員工",
|
||||
"error_adding_updating" => "添加/更新員工錯誤",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "您不能刪除admin用戶",
|
||||
"error_updating_demo_admin" => "您不能更改admin用戶",
|
||||
"language" => "",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "成本價為必填攔位",
|
||||
"count" => "更新庫存",
|
||||
"csv_import_failed" => "CSV匯入失敗",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "Your uploaded file has no data or wrong format",
|
||||
"csv_import_partially_failed" => "Most Items imported. But some were not, here is the list",
|
||||
"csv_import_success" => "Import of Items successful",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "當前密碼無效。",
|
||||
"employee" => "員工",
|
||||
"error_adding_updating" => "添加/更新員工錯誤.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "您不能刪除admin用戶.",
|
||||
"error_updating_demo_admin" => "您不能更改admin用戶.",
|
||||
"language" => "語言",
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
"cost_price_required" => "成本價為必填攔位.",
|
||||
"count" => "更新庫存",
|
||||
"csv_import_failed" => "CSV匯入失敗",
|
||||
"csv_import_invalid_location" => "",
|
||||
"csv_import_nodata_wrongformat" => "上傳的 CSV 文件沒有數據或格式不正確。",
|
||||
"csv_import_partially_failed" => "線上有 {0} 個項目導入失敗:{1}。未導入任何行。",
|
||||
"csv_import_success" => "項目 CSV 導入成功。",
|
||||
|
||||
70
app/Libraries/Plugins/BasePlugin.php
Normal file
70
app/Libraries/Plugins/BasePlugin.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Libraries\Plugins;
|
||||
|
||||
use App\Models\PluginConfig;
|
||||
|
||||
abstract class BasePlugin implements PluginInterface
|
||||
{
|
||||
protected PluginConfig $configModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->configModel = new PluginConfig();
|
||||
}
|
||||
|
||||
public function install(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function uninstall(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
$enabled = $this->configModel->getValue("{$this->getPluginId()}_enabled");
|
||||
return $enabled === '1' || $enabled === 'true';
|
||||
}
|
||||
|
||||
protected function getSetting(string $key, mixed $default = null): mixed
|
||||
{
|
||||
$value = $this->configModel->getValue("{$this->getPluginId()}_{$key}");
|
||||
return $value ?? $default;
|
||||
}
|
||||
|
||||
protected function setSetting(string $key, mixed $value): bool
|
||||
{
|
||||
$stringValue = is_array($value) || is_object($value)
|
||||
? json_encode($value)
|
||||
: (string)$value;
|
||||
|
||||
return $this->configModel->setValue("{$this->getPluginId()}_{$key}", $stringValue);
|
||||
}
|
||||
|
||||
public function getSettings(): array
|
||||
{
|
||||
return $this->configModel->getPluginSettings($this->getPluginId());
|
||||
}
|
||||
|
||||
public function saveSettings(array $settings): bool
|
||||
{
|
||||
$prefixedSettings = [];
|
||||
foreach ($settings as $key => $value) {
|
||||
if (is_array($value) || is_object($value)) {
|
||||
$prefixedSettings["{$this->getPluginId()}_{$key}"] = json_encode($value);
|
||||
} else {
|
||||
$prefixedSettings["{$this->getPluginId()}_{$key}"] = (string)$value;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->configModel->batchSave($prefixedSettings);
|
||||
}
|
||||
|
||||
protected function log(string $level, string $message): void
|
||||
{
|
||||
log_message($level, "[Plugin:{$this->getPluginName()}] {$message}");
|
||||
}
|
||||
}
|
||||
56
app/Libraries/Plugins/PluginInterface.php
Normal file
56
app/Libraries/Plugins/PluginInterface.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Libraries\Plugins;
|
||||
|
||||
interface PluginInterface
|
||||
{
|
||||
public function getPluginId(): string;
|
||||
|
||||
public function getPluginName(): string;
|
||||
|
||||
public function getPluginDescription(): string;
|
||||
|
||||
public function getVersion(): string;
|
||||
|
||||
/**
|
||||
* Register event listeners for this plugin.
|
||||
*
|
||||
* Use Events::on() to register callbacks for OSPOS events.
|
||||
* This method is called when the plugin is loaded and enabled.
|
||||
*
|
||||
* Example:
|
||||
* Events::on('item_sale', [$this, 'onItemSale']);
|
||||
* Events::on('item_change', [$this, 'onItemChange']);
|
||||
*/
|
||||
public function registerEvents(): void;
|
||||
|
||||
/**
|
||||
* Install the plugin.
|
||||
*
|
||||
* Called when the plugin is first enabled. Use this to create database tables,
|
||||
* set default configuration values, and run any setup required.
|
||||
*/
|
||||
public function install(): bool;
|
||||
|
||||
/**
|
||||
* Uninstall the plugin.
|
||||
*
|
||||
* Called when the plugin is being removed. Use this to remove database tables,
|
||||
* clean up configuration, etc.
|
||||
*/
|
||||
public function uninstall(): bool;
|
||||
|
||||
public function isEnabled(): bool;
|
||||
|
||||
/**
|
||||
* Get the path to the plugin's configuration view file.
|
||||
* Returns null if the plugin has no configuration UI.
|
||||
*
|
||||
* Example: 'Plugins/mailchimp/config'
|
||||
*/
|
||||
public function getConfigView(): ?string;
|
||||
|
||||
public function getSettings(): array;
|
||||
|
||||
public function saveSettings(array $settings): bool;
|
||||
}
|
||||
174
app/Libraries/Plugins/PluginManager.php
Normal file
174
app/Libraries/Plugins/PluginManager.php
Normal file
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
namespace App\Libraries\Plugins;
|
||||
|
||||
use App\Models\PluginConfig;
|
||||
use CodeIgniter\Events\Events;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
|
||||
class PluginManager
|
||||
{
|
||||
private array $plugins = [];
|
||||
private array $enabledPlugins = [];
|
||||
private PluginConfig $configModel;
|
||||
private string $pluginsPath;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->configModel = new PluginConfig();
|
||||
$this->pluginsPath = APPPATH . 'Plugins';
|
||||
}
|
||||
|
||||
public function discoverPlugins(): void
|
||||
{
|
||||
if (!is_dir($this->pluginsPath)) {
|
||||
log_message('debug', 'Plugins directory does not exist: ' . $this->pluginsPath);
|
||||
return;
|
||||
}
|
||||
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($this->pluginsPath, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::SELF_FIRST
|
||||
);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isDir() || $file->getExtension() !== 'php') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$className = $this->getClassNameFromFile($file->getPathname());
|
||||
|
||||
if (!$className) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!class_exists($className)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_subclass_of($className, PluginInterface::class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$plugin = new $className();
|
||||
|
||||
$this->plugins[$plugin->getPluginId()] = $plugin;
|
||||
log_message('debug', "Discovered plugin: {$plugin->getPluginName()}");
|
||||
}
|
||||
}
|
||||
|
||||
private function getClassNameFromFile(string $pathname): ?string
|
||||
{
|
||||
$relativePath = str_replace($this->pluginsPath . DIRECTORY_SEPARATOR, '', $pathname);
|
||||
$relativePath = str_replace(DIRECTORY_SEPARATOR, '\\', $relativePath);
|
||||
$className = 'App\\Plugins\\' . str_replace('.php', '', $relativePath);
|
||||
|
||||
return $className;
|
||||
}
|
||||
|
||||
public function registerPluginEvents(): void
|
||||
{
|
||||
foreach ($this->plugins as $pluginId => $plugin) {
|
||||
if ($this->isPluginEnabled($pluginId)) {
|
||||
$this->enabledPlugins[$pluginId] = $plugin;
|
||||
$plugin->registerEvents();
|
||||
log_message('debug', "Registered events for plugin: {$plugin->getPluginName()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getAllPlugins(): array
|
||||
{
|
||||
return $this->plugins;
|
||||
}
|
||||
|
||||
public function getEnabledPlugins(): array
|
||||
{
|
||||
return $this->enabledPlugins;
|
||||
}
|
||||
|
||||
public function getPlugin(string $pluginId): ?PluginInterface
|
||||
{
|
||||
return $this->plugins[$pluginId] ?? null;
|
||||
}
|
||||
|
||||
public function isPluginEnabled(string $pluginId): bool
|
||||
{
|
||||
$enabled = $this->configModel->getValue($this->getEnabledKey($pluginId));
|
||||
return $enabled === '1' || $enabled === 'true';
|
||||
}
|
||||
|
||||
public function enablePlugin(string $pluginId): bool
|
||||
{
|
||||
$plugin = $this->getPlugin($pluginId);
|
||||
if (!$plugin) {
|
||||
log_message('error', "Plugin not found: {$pluginId}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->configModel->exists($this->getInstalledKey($pluginId))) {
|
||||
if (!$plugin->install()) {
|
||||
log_message('error', "Failed to install plugin: {$pluginId}");
|
||||
return false;
|
||||
}
|
||||
$this->configModel->setValue($this->getInstalledKey($pluginId), '1');
|
||||
}
|
||||
|
||||
$this->configModel->setValue($this->getEnabledKey($pluginId), '1');
|
||||
log_message('info', "Plugin enabled: {$pluginId}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function disablePlugin(string $pluginId): bool
|
||||
{
|
||||
if (!$this->getPlugin($pluginId)) {
|
||||
log_message('error', "Plugin not found: {$pluginId}");
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->configModel->setValue($this->getEnabledKey($pluginId), '0');
|
||||
log_message('info', "Plugin disabled: {$pluginId}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function uninstallPlugin(string $pluginId): bool
|
||||
{
|
||||
$plugin = $this->getPlugin($pluginId);
|
||||
if (!$plugin) {
|
||||
log_message('error', "Plugin not found: {$pluginId}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$plugin->uninstall()) {
|
||||
log_message('error', "Failed to uninstall plugin: {$pluginId}");
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->configModel->deleteAllStartingWith($pluginId . '_');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getSetting(string $pluginId, string $key, mixed $default = null): mixed
|
||||
{
|
||||
return $this->configModel->getValue("{$pluginId}_{$key}") ?? $default;
|
||||
}
|
||||
|
||||
public function setSetting(string $pluginId, string $key, mixed $value): bool
|
||||
{
|
||||
return $this->configModel->setValue("{$pluginId}_{$key}", $value);
|
||||
}
|
||||
|
||||
private function getEnabledKey(string $pluginId): string
|
||||
{
|
||||
return "{$pluginId}_enabled";
|
||||
}
|
||||
|
||||
private function getInstalledKey(string $pluginId): string
|
||||
{
|
||||
return "{$pluginId}_installed";
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,18 @@ use stdClass;
|
||||
*/
|
||||
class Item extends Model
|
||||
{
|
||||
public const ALLOWED_BULK_EDIT_FIELDS = [
|
||||
'name',
|
||||
'category',
|
||||
'supplier_id',
|
||||
'cost_price',
|
||||
'unit_price',
|
||||
'reorder_level',
|
||||
'description',
|
||||
'allow_alt_description',
|
||||
'is_serialized'
|
||||
];
|
||||
|
||||
protected $table = 'items';
|
||||
protected $primaryKey = 'item_id';
|
||||
protected $useAutoIncrement = true;
|
||||
@@ -199,9 +211,9 @@ class Item extends Model
|
||||
|
||||
if (!empty($search)) {
|
||||
if ($attributes_enabled && $filters['search_custom']) {
|
||||
$builder->having("attribute_values LIKE :search:", ['search' => "%$search%"]);
|
||||
$builder->orHaving("attribute_dtvalues LIKE :search_dt:", ['search_dt' => "%$search%"]);
|
||||
$builder->orHaving("attribute_dvalues LIKE :search_d:", ['search_d' => "%$search%"]);
|
||||
$builder->havingLike('attribute_values', $search);
|
||||
$builder->orHavingLike('attribute_dtvalues', $search);
|
||||
$builder->orHavingLike('attribute_dvalues', $search);
|
||||
} else {
|
||||
$builder->groupStart();
|
||||
$builder->like('name', $search);
|
||||
|
||||
107
app/Models/PluginConfig.php
Normal file
107
app/Models/PluginConfig.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class PluginConfig extends Model
|
||||
{
|
||||
protected $table = 'plugin_config';
|
||||
protected $primaryKey = 'key';
|
||||
protected $useAutoIncrement = false;
|
||||
protected $useSoftDeletes = false;
|
||||
protected $allowedFields = [
|
||||
'key',
|
||||
'value'
|
||||
];
|
||||
|
||||
public function exists(string $key): bool
|
||||
{
|
||||
$builder = $this->db->table('plugin_config');
|
||||
$builder->where('key', $key);
|
||||
|
||||
return ($builder->get()->getNumRows() === 1);
|
||||
}
|
||||
|
||||
public function getValue(string $key): ?string
|
||||
{
|
||||
$builder = $this->db->table('plugin_config');
|
||||
$query = $builder->getWhere(['key' => $key], 1);
|
||||
|
||||
if ($query->getNumRows() === 1) {
|
||||
return $query->getRow()->value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setValue(string $key, string $value): bool
|
||||
{
|
||||
$builder = $this->db->table('plugin_config');
|
||||
|
||||
if ($this->exists($key)) {
|
||||
return $builder->update(['value' => $value], ['key' => $key]);
|
||||
}
|
||||
|
||||
return $builder->insert(['key' => $key, 'value' => $value]);
|
||||
}
|
||||
|
||||
public function getPluginSettings(string $pluginId): array
|
||||
{
|
||||
$builder = $this->db->table('plugin_config');
|
||||
$builder->like('key', $pluginId . '_', 'after');
|
||||
$query = $builder->get();
|
||||
|
||||
$settings = [];
|
||||
$prefix = $pluginId . '_';
|
||||
foreach ($query->getResult() as $row) {
|
||||
$key = str_starts_with($row->key, $prefix)
|
||||
? substr($row->key, strlen($prefix))
|
||||
: $row->key;
|
||||
$settings[$key] = $row->value;
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
public function deleteKey(string $key): bool
|
||||
{
|
||||
$builder = $this->db->table('plugin_config');
|
||||
return $builder->delete(['key' => $key]);
|
||||
}
|
||||
|
||||
public function deleteAllStartingWith(string $prefix): bool
|
||||
{
|
||||
$builder = $this->db->table('plugin_config');
|
||||
$builder->like('key', $prefix, 'after');
|
||||
return $builder->delete();
|
||||
}
|
||||
|
||||
public function batchSave(array $data): bool
|
||||
{
|
||||
$success = true;
|
||||
|
||||
$this->db->transStart();
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
$success &= $this->setValue($key, $value);
|
||||
}
|
||||
|
||||
$this->db->transComplete();
|
||||
|
||||
return $success && $this->db->transStatus();
|
||||
}
|
||||
|
||||
public function getAll(): array
|
||||
{
|
||||
$builder = $this->db->table('plugin_config');
|
||||
$query = $builder->get();
|
||||
|
||||
$configs = [];
|
||||
foreach ($query->getResult() as $row) {
|
||||
$configs[$row->key] = $row->value;
|
||||
}
|
||||
|
||||
return $configs;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user