mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-05-25 00:44:03 -04:00
Compare commits
118 Commits
bootstrap-
...
plugin-sys
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93713f8e4b | ||
|
|
b7384296c1 | ||
|
|
ad901f9c2d | ||
|
|
388c8ad631 | ||
|
|
705c61b48c | ||
|
|
d39067e2e1 | ||
|
|
50eead4da4 | ||
|
|
4c7ac7b5d0 | ||
|
|
bed8a1c34d | ||
|
|
10588867c4 | ||
|
|
139f754a07 | ||
|
|
c08872f83e | ||
|
|
01172fc522 | ||
|
|
f8fd12c5de | ||
|
|
a15a6516a6 | ||
|
|
84a10ec218 | ||
|
|
f650f17181 | ||
|
|
d699d82388 | ||
|
|
b0dddc22a3 | ||
|
|
7afaeef6a3 | ||
|
|
8d6b166673 | ||
|
|
df24ef5193 | ||
|
|
1c2112a78b | ||
|
|
093ec7fb13 | ||
|
|
ad097adccd | ||
|
|
796657118a | ||
|
|
445a506ea8 | ||
|
|
5d608ec873 | ||
|
|
9c89a2e2cb | ||
|
|
2f51c4ef52 | ||
|
|
def0c27a0e | ||
|
|
90c981b6b7 | ||
|
|
6ff28d8a4d | ||
|
|
e5fdea85f3 | ||
|
|
ca07aac9a0 | ||
|
|
cd91ac3ff3 | ||
|
|
478934321d | ||
|
|
4d266c9b5e | ||
|
|
43bee7bfe4 | ||
|
|
f71af765f8 | ||
|
|
4246a915c4 | ||
|
|
939012dc1b | ||
|
|
d2d0c8bf37 | ||
|
|
6c55526479 | ||
|
|
fe331c34dd | ||
|
|
6630fb56f6 | ||
|
|
2f48e0499f | ||
|
|
cbabe1d56c | ||
|
|
8c1c9d85dc | ||
|
|
97adee0c28 | ||
|
|
32997d48c0 | ||
|
|
1a9e84bd37 | ||
|
|
9d0b14a8ce | ||
|
|
c796b52c22 | ||
|
|
f863be68f9 | ||
|
|
54c476a498 | ||
|
|
ec139c477a | ||
|
|
bae361c637 | ||
|
|
dcfdc212da | ||
|
|
c217fd770c | ||
|
|
6ef6f49693 | ||
|
|
eae6417f97 | ||
|
|
4cfff5388c | ||
|
|
a77b95f0cc | ||
|
|
506cded6e9 | ||
|
|
2639a8b212 | ||
|
|
f6106e7ead | ||
|
|
202c016dd8 | ||
|
|
d47ead8747 | ||
|
|
0e5ba88f6c | ||
|
|
d88cf54f99 | ||
|
|
2f200b47c6 | ||
|
|
f819bc92f8 | ||
|
|
db180d134e | ||
|
|
edd97a3c78 | ||
|
|
d73bfd39f6 | ||
|
|
d9d93e0d9d | ||
|
|
bfb4ad4617 | ||
|
|
00cd13f735 | ||
|
|
43972b8f0e | ||
|
|
ff3c7d1b14 | ||
|
|
9fc918b53d | ||
|
|
56f68f7577 | ||
|
|
65fb6339d7 | ||
|
|
57a19bb35f | ||
|
|
c81cf4a5cc | ||
|
|
1918f3e6e2 | ||
|
|
196e87fa49 | ||
|
|
ebd1c8fa0e | ||
|
|
f842be50b3 | ||
|
|
e94e5e634c | ||
|
|
bcff389b34 | ||
|
|
deb122246c | ||
|
|
e1b76d2e0d | ||
|
|
84ea65b1bd | ||
|
|
a19fe03ecf | ||
|
|
15227523d9 | ||
|
|
1587d4276d | ||
|
|
d6967704e6 | ||
|
|
80dc62948d | ||
|
|
acb3b18584 | ||
|
|
19cdb76bb3 | ||
|
|
07f1a35e9d | ||
|
|
3b0476f2b3 | ||
|
|
2fa324ba4e | ||
|
|
8b14ed81e0 | ||
|
|
0ea3ced674 | ||
|
|
896ed87797 | ||
|
|
eb264ad76d | ||
|
|
10a64e7af9 | ||
|
|
6e99f05d63 | ||
|
|
c430c7afb5 | ||
|
|
519347f4f5 | ||
|
|
62d84411b2 | ||
|
|
6bd4bb545d | ||
|
|
66f7d70749 | ||
|
|
bd8b4fa6c1 | ||
|
|
a9669ddf19 |
219
.github/workflows/deploy-core.yml
vendored
Normal file
219
.github/workflows/deploy-core.yml
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
name: Deploy Core
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
image_tag:
|
||||
description: 'Docker image tag to deploy'
|
||||
type: string
|
||||
required: true
|
||||
sha:
|
||||
description: 'Git commit SHA to deploy'
|
||||
type: string
|
||||
required: true
|
||||
description:
|
||||
description: 'Deployment description'
|
||||
type: string
|
||||
required: true
|
||||
pr_number:
|
||||
description: 'Pull request number (optional)'
|
||||
type: string
|
||||
required: false
|
||||
outputs:
|
||||
deployment_id:
|
||||
description: 'GitHub deployment ID'
|
||||
value: ${{ jobs.deploy.outputs.deployment_id }}
|
||||
status:
|
||||
description: 'Deployment status (success/failure)'
|
||||
value: ${{ jobs.deploy.outputs.status }}
|
||||
|
||||
concurrency:
|
||||
group: deploy-staging
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy to staging
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
environment:
|
||||
name: staging
|
||||
url: ${{ vars.DEPLOY_URL || 'https://dev.opensourcepos.org' }}
|
||||
deployment: false
|
||||
|
||||
outputs:
|
||||
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
|
||||
status: ${{ steps.webhook.outputs.status }}
|
||||
|
||||
steps:
|
||||
- name: Create GitHub Deployment
|
||||
id: deployment
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
IMAGE_TAG: ${{ inputs.image_tag }}
|
||||
REF_SHA: ${{ inputs.sha }}
|
||||
DESCRIPTION: ${{ inputs.description }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
DEPLOYMENT_ID=$(gh api "repos/${GITHUB_REPOSITORY}/deployments" \
|
||||
-X POST \
|
||||
-f ref="${REF_SHA}" \
|
||||
-f environment="staging" \
|
||||
-f description="${DESCRIPTION}" \
|
||||
-F auto_merge=false \
|
||||
-F required_contexts[] \
|
||||
--jq '.id')
|
||||
|
||||
if [ -z "$DEPLOYMENT_ID" ]; then
|
||||
echo "::error::Failed to create deployment"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "deployment_id=$DEPLOYMENT_ID" >> "$GITHUB_OUTPUT"
|
||||
echo "Created deployment: $DEPLOYMENT_ID"
|
||||
|
||||
- name: Set deployment status to in_progress
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
gh api "repos/${GITHUB_REPOSITORY}/deployments/${{ steps.deployment.outputs.deployment_id }}/statuses" \
|
||||
-X POST \
|
||||
-f state="in_progress" \
|
||||
-f description="Deployment in progress..." \
|
||||
-f log_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
|
||||
|
||||
- name: Trigger deployment webhook
|
||||
id: webhook
|
||||
env:
|
||||
DEPLOY_WEBHOOK_URL: ${{ secrets.DEPLOY_WEBHOOK_URL }}
|
||||
DEPLOY_WEBHOOK_SECRET: ${{ secrets.DEPLOY_WEBHOOK_SECRET }}
|
||||
DOCKER_REPO_NAME: ${{ secrets.DOCKER_REPO_NAME }}
|
||||
IMAGE_TAG: ${{ inputs.image_tag }}
|
||||
REF_SHA: ${{ inputs.sha }}
|
||||
DEPLOYMENT_ID: ${{ steps.deployment.outputs.deployment_id }}
|
||||
PR_NUMBER: ${{ inputs.pr_number }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ -z "$DEPLOY_WEBHOOK_URL" ]; then
|
||||
echo "::error::DEPLOY_WEBHOOK_URL secret is not configured"
|
||||
echo "Please add the DEPLOY_WEBHOOK_URL secret in your repository settings"
|
||||
echo "status=failure" >> "$GITHUB_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REPO_NAME="${DOCKER_REPO_NAME:-opensourcepos/opensourcepos}"
|
||||
REPO_NAMESPACE="${REPO_NAME%%/*}"
|
||||
REPO_SHORT_NAME="${REPO_NAME#*/}"
|
||||
PUSHED_AT=$(date +%s)
|
||||
|
||||
if [ -n "$PR_NUMBER" ]; then
|
||||
PAYLOAD=$(jq -n \
|
||||
--arg callback_url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
|
||||
--argjson pushed_at "$PUSHED_AT" \
|
||||
--arg pusher "$GITHUB_ACTOR" \
|
||||
--arg tag "$IMAGE_TAG" \
|
||||
--arg repo_name "$REPO_NAME" \
|
||||
--arg name "$REPO_SHORT_NAME" \
|
||||
--arg namespace "$REPO_NAMESPACE" \
|
||||
--arg repo_url "https://hub.docker.com/r/${REPO_NAME}/" \
|
||||
--arg deployment_id "$DEPLOYMENT_ID" \
|
||||
--arg repository "$GITHUB_REPOSITORY" \
|
||||
--arg sha "$REF_SHA" \
|
||||
--arg run_id "$GITHUB_RUN_ID" \
|
||||
--arg actor "$GITHUB_ACTOR" \
|
||||
--argjson pr_number "$PR_NUMBER" \
|
||||
'{
|
||||
callback_url: $callback_url,
|
||||
push_data: {pushed_at: $pushed_at, pusher: $pusher, tag: $tag},
|
||||
repository: {repo_name: $repo_name, name: $name, namespace: $namespace, repo_url: $repo_url, status: "Active"},
|
||||
github_deployment: {id: $deployment_id, environment: "staging", repository: $repository, sha: $sha, run_id: $run_id, actor: $actor, pull_request: $pr_number}
|
||||
}')
|
||||
else
|
||||
PAYLOAD=$(jq -n \
|
||||
--arg callback_url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
|
||||
--argjson pushed_at "$PUSHED_AT" \
|
||||
--arg pusher "$GITHUB_ACTOR" \
|
||||
--arg tag "$IMAGE_TAG" \
|
||||
--arg repo_name "$REPO_NAME" \
|
||||
--arg name "$REPO_SHORT_NAME" \
|
||||
--arg namespace "$REPO_NAMESPACE" \
|
||||
--arg repo_url "https://hub.docker.com/r/${REPO_NAME}/" \
|
||||
--arg deployment_id "$DEPLOYMENT_ID" \
|
||||
--arg repository "$GITHUB_REPOSITORY" \
|
||||
--arg sha "$REF_SHA" \
|
||||
--arg run_id "$GITHUB_RUN_ID" \
|
||||
--arg actor "$GITHUB_ACTOR" \
|
||||
'{
|
||||
callback_url: $callback_url,
|
||||
push_data: {pushed_at: $pushed_at, pusher: $pusher, tag: $tag},
|
||||
repository: {repo_name: $repo_name, name: $name, namespace: $namespace, repo_url: $repo_url, status: "Active"},
|
||||
github_deployment: {id: $deployment_id, environment: "staging", repository: $repository, sha: $sha, run_id: $run_id, actor: $actor}
|
||||
}')
|
||||
fi
|
||||
|
||||
echo "Sending webhook..."
|
||||
echo "Image: ${IMAGE_TAG}"
|
||||
echo "Environment: staging"
|
||||
|
||||
HEADERS=(-H "Content-Type: application/json")
|
||||
|
||||
if [ -n "$DEPLOY_WEBHOOK_SECRET" ]; then
|
||||
SIGNATURE=$(printf '%s' "$PAYLOAD" | openssl dgst -sha256 -hmac "$DEPLOY_WEBHOOK_SECRET" | sed 's/.*= //')
|
||||
HEADERS+=(-H "X-Hub-Signature-256: sha256=$SIGNATURE")
|
||||
echo "Using HMAC-SHA256 signature verification"
|
||||
else
|
||||
echo "::warning::DEPLOY_WEBHOOK_SECRET not set - webhook calls will not be signed"
|
||||
echo "For security, configure DEPLOY_WEBHOOK_SECRET in your repository settings"
|
||||
fi
|
||||
|
||||
HTTP_CODE=$(curl -sS --connect-timeout 10 --max-time 120 \
|
||||
-o response.txt -w "%{http_code}" \
|
||||
-X POST \
|
||||
"${HEADERS[@]}" \
|
||||
-d "$PAYLOAD" \
|
||||
"$DEPLOY_WEBHOOK_URL") || HTTP_CODE="000"
|
||||
|
||||
echo "Response code: $HTTP_CODE"
|
||||
if [ -s response.txt ]; then
|
||||
cat response.txt
|
||||
fi
|
||||
|
||||
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||
echo "status=success" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "status=failure" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Set deployment status
|
||||
if: always()
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
IMAGE_TAG: ${{ inputs.image_tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
STATE="${{ steps.webhook.outputs.status }}"
|
||||
|
||||
if [ "$STATE" = "success" ]; then
|
||||
DESCRIPTION=$(jq -nr --arg tag "$IMAGE_TAG" \
|
||||
'"Deployed image \($tag) to staging"')
|
||||
|
||||
gh api "repos/${GITHUB_REPOSITORY}/deployments/${{ steps.deployment.outputs.deployment_id }}/statuses" \
|
||||
-X POST \
|
||||
-f state="success" \
|
||||
-f description="$DESCRIPTION"
|
||||
else
|
||||
gh api "repos/${GITHUB_REPOSITORY}/deployments/${{ steps.deployment.outputs.deployment_id }}/statuses" \
|
||||
-X POST \
|
||||
-f state="failure" \
|
||||
-f description="Deployment failed"
|
||||
exit 1
|
||||
fi
|
||||
79
.github/workflows/deploy-pr.yml
vendored
Normal file
79
.github/workflows/deploy-pr.yml
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
name: PR Deploy
|
||||
|
||||
on:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
concurrency:
|
||||
group: staging-deploy
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
name: Prepare deployment
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
github.event.review.state == 'approved' &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
outputs:
|
||||
image_tag: ${{ steps.image.outputs.tag }}
|
||||
sha: ${{ github.event.pull_request.head.sha }}
|
||||
pr_number: ${{ github.event.pull_request.number }}
|
||||
|
||||
steps:
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Get image tag
|
||||
id: image
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
PR_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: |
|
||||
IMAGE_TAG="pr-${PR_NUMBER}-${PR_SHA:0:7}"
|
||||
echo "tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
|
||||
|
||||
deploy:
|
||||
name: Deploy to staging
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/deploy-core.yml
|
||||
with:
|
||||
image_tag: ${{ needs.prepare.outputs.image_tag }}
|
||||
sha: ${{ needs.prepare.outputs.sha }}
|
||||
description: Deploy PR #${{ needs.prepare.outputs.pr_number }} to staging
|
||||
pr_number: ${{ needs.prepare.outputs.pr_number }}
|
||||
secrets: inherit
|
||||
|
||||
comment:
|
||||
name: Comment deployment status
|
||||
needs: [prepare, deploy]
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }}
|
||||
PR_NUMBER: ${{ needs.prepare.outputs.pr_number }}
|
||||
REF_SHA: ${{ needs.prepare.outputs.sha }}
|
||||
STATUS: ${{ needs.deploy.outputs.status }}
|
||||
|
||||
steps:
|
||||
- name: Comment on PR
|
||||
run: |
|
||||
if [ "$STATUS" = "success" ]; then
|
||||
BODY=$(jq -nr --arg tag "$IMAGE_TAG" --arg sha "$REF_SHA" --arg url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
|
||||
'"✅ **Staging deployment completed**\n\n🔗 **URL**: https://dev.opensourcepos.org\n📦 **Image Tag**: `\($tag)`\n🔨 **Commit**: \($sha)\n\nView logs: \($url)"')
|
||||
else
|
||||
BODY=$(jq -nr --arg url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
|
||||
'"❌ **Staging deployment failed**\n\nCheck the [workflow logs](\($url)) for details."')
|
||||
fi
|
||||
|
||||
gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \
|
||||
-X POST \
|
||||
-f body="$BODY"
|
||||
23
.github/workflows/deploy.yml
vendored
Normal file
23
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
image_tag:
|
||||
description: 'Docker image tag to deploy (e.g., v3.4.0, latest)'
|
||||
required: true
|
||||
default: 'latest'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy to staging
|
||||
uses: ./.github/workflows/deploy-core.yml
|
||||
with:
|
||||
image_tag: ${{ inputs.image_tag }}
|
||||
sha: ${{ github.sha }}
|
||||
description: Deploy image ${{ inputs.image_tag }}
|
||||
secrets: inherit
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -87,3 +87,5 @@ auth.json
|
||||
/app/Database/database.sql
|
||||
/writable/cache/settings
|
||||
/.env.bak
|
||||
/.php-cs-fixer.cache
|
||||
/build
|
||||
|
||||
127
AGENTS.md
127
AGENTS.md
@@ -1,40 +1,125 @@
|
||||
# Agent Instructions
|
||||
|
||||
This document provides guidance for AI agents working on the Open Source Point of Sale (OSPOS) codebase.
|
||||
This document is the single source of truth for all AI agents working on the Open Source Point of Sale (OSPOS) codebase. Read it fully before making any changes.
|
||||
|
||||
## Project Overview
|
||||
|
||||
OpenSourcePOS is a web-based Point of Sale system built on **CodeIgniter 4** (PHP 8.2+) with MySQL/MariaDB. Frontend uses Bootstrap 3 (Bootstrap 5 migration in progress) and jQuery, with assets built via Gulp.
|
||||
|
||||
## Common Commands
|
||||
|
||||
```bash
|
||||
# PHP dependencies
|
||||
composer install
|
||||
|
||||
# Frontend dependencies and asset build
|
||||
npm install
|
||||
npm run build # Runs Gulp: compiles and copies all CSS/JS to public/resources/
|
||||
|
||||
# Run full test suite
|
||||
composer test
|
||||
|
||||
# Run a single test file
|
||||
vendor/bin/phpunit tests/unit/AppTest.php
|
||||
|
||||
# Lint / code style check
|
||||
vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.no-header.php --dry-run
|
||||
|
||||
# Apply code style fixes
|
||||
vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.no-header.php
|
||||
```
|
||||
|
||||
Tests require a MariaDB/MySQL database (see CI config in `.github/workflows/phpunit.yml`).
|
||||
|
||||
## Architecture
|
||||
|
||||
### Framework & Entry Point
|
||||
|
||||
- **Framework**: CodeIgniter 4 — MVC with QueryBuilder ORM, no Eloquent
|
||||
- **Web root**: `public/` — `public/index.php` is the only entry point
|
||||
- **Routes**: `app/Config/Routes.php`
|
||||
- **App config**: `app/Config/App.php` (version, session, security settings)
|
||||
- **Environment**: `.env` file (copy from `.env.example`); `CI_ENVIRONMENT` controls dev/prod/test mode
|
||||
|
||||
### Directory Layout
|
||||
|
||||
```text
|
||||
app/
|
||||
├── Config/ # CI4 config classes
|
||||
├── Controllers/ # ~27 controllers (Sales, Items, Reports, Customers, etc.)
|
||||
├── Models/ # ~28 models (Sale, Item, Customer, Supplier, etc.)
|
||||
├── Views/ # PHP view templates
|
||||
├── Libraries/ # Business logic (Sale_lib, Tax_lib, Receiving_lib, etc.)
|
||||
├── Plugins/ # Plugin system — each plugin is a subdirectory here
|
||||
├── Database/ # Migrations (ospos_ prefix) and seeds
|
||||
├── Language/ # i18n files (IETF BCP 47 locale names)
|
||||
├── Filters/ # Request/response filters (auth, HTTPS, etc.)
|
||||
└── Events/ # CI4 event subscribers
|
||||
public/
|
||||
└── resources/ # Built CSS/JS (do not edit directly — generated by npm run build)
|
||||
tests/ # PHPUnit test suite
|
||||
```
|
||||
|
||||
### Key Libraries
|
||||
|
||||
`app/Libraries/` holds core business logic:
|
||||
- `Sale_lib.php` — sale cart state, pricing, discounts, tax calculation
|
||||
- `Tax_lib.php` — multi-tier tax engine
|
||||
- `Receiving_lib.php` — purchase orders / receivings
|
||||
- `Barcode_lib.php` — barcode generation
|
||||
- `Email_lib.php` — email delivery
|
||||
- `Token_lib.php` — CSRF/session token management
|
||||
|
||||
### Database
|
||||
|
||||
- Table prefix: `ospos_` (defined in `app/Config/Database.php`)
|
||||
- Migrations live in `app/Database/Migrations/` and run automatically on first access
|
||||
- CodeIgniter QueryBuilder throughout — no raw SQL unless necessary
|
||||
|
||||
### Plugin System
|
||||
|
||||
Plugins live in `app/Plugins/<PluginName>/` and are auto-discovered by `PluginManager`. Each plugin:
|
||||
- Extends `BasePlugin` or implements `PluginInterface`
|
||||
- Registers event hooks (e.g., `item_sale`, `customer_saved`, view hooks like `customer_tabs`)
|
||||
- Can include its own `Views/`, `Models/`, `Controllers/`, and `Language/` subdirectories
|
||||
- Configuration stored in `ospos_plugin_config` table
|
||||
- See `app/Plugins/README.md` for plugin structure, event hooks, and LICENSE requirements
|
||||
|
||||
### Frontend Build
|
||||
|
||||
`gulpfile.js` (Gulp 5) copies vendor CSS/JS from `node_modules/` into `public/resources/`. Run `npm run build` after installing npm packages or changing gulp tasks. Do not manually edit files under `public/resources/`.
|
||||
|
||||
## Code Style
|
||||
|
||||
- Follow PHP CodeIgniter 4 coding standards
|
||||
- Run PHP-CS-Fixer before committing: `vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.no-header.php`
|
||||
- Write PHP 8.1+ compatible code with proper type declarations
|
||||
- Use PSR-12 naming conventions: `camelCase` for variables and functions, `PascalCase` for classes, `UPPER_CASE` for constants
|
||||
- **PSR-12** enforced via PHP-CS-Fixer (config: `.php-cs-fixer.no-header.php`)
|
||||
- `camelCase` for variables and methods; `PascalCase` for classes; `UPPER_CASE` for constants
|
||||
- PHP 8.2+ features acceptable (named arguments, enums, readonly properties)
|
||||
- Views in `app/Views/errors/html/` are excluded from the fixer
|
||||
- Run fixer before committing: `vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.no-header.php`
|
||||
|
||||
## Development
|
||||
## Development Workflow
|
||||
|
||||
- Create a new git worktree for each issue, based on the latest state of `origin/master`
|
||||
- Commit fixes to the worktree and push to the remote
|
||||
|
||||
## Testing
|
||||
|
||||
- Run PHPUnit tests: `composer test`
|
||||
- Tests must pass before submitting changes
|
||||
|
||||
## Build
|
||||
|
||||
- Install dependencies: `composer install && npm install`
|
||||
- Build assets: `npm run build` or `gulp`
|
||||
- Tests must pass before submitting changes (`composer test`)
|
||||
- Minimum PHPUnit version: 10.5.16+. Default config: `phpunit.xml.dist`
|
||||
|
||||
## Conventions
|
||||
|
||||
- Controllers go in `app/Controllers/`
|
||||
- Models go in `app/Models/`
|
||||
- Views go in `app/Views/`
|
||||
- Database migrations in `app/Database/Migrations/`
|
||||
- Controllers → `app/Controllers/`
|
||||
- Models → `app/Models/`
|
||||
- Views → `app/Views/`
|
||||
- Migrations → `app/Database/Migrations/`
|
||||
- Plugins → `app/Plugins/` (see `app/Plugins/README.md` for plugin structure, event hooks, and LICENSE requirements)
|
||||
- Use CodeIgniter 4 framework patterns and helpers
|
||||
- Sanitize user input; escape output using `esc()` helper
|
||||
|
||||
## Security
|
||||
|
||||
- `app.allowedHostnames` **must** be set in production (host header injection protection)
|
||||
- HTMLPurifier for HTML sanitization; Laminas Escaper for output escaping
|
||||
- CSRF tokens managed via `Token_lib` — do not bypass CI4's CSRF filter
|
||||
- Session storage is database-backed (`ospos_sessions` table) for multi-instance support
|
||||
- Never commit secrets, credentials, or `.env` files
|
||||
- Use parameterized queries to prevent SQL injection
|
||||
- Validate and sanitize all user input
|
||||
- Validate and sanitize all user input
|
||||
|
||||
3
CLAUDE.md
Normal file
3
CLAUDE.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# CLAUDE.md
|
||||
|
||||
> **MANDATORY INSTRUCTION**: You MUST read `AGENTS.md` in this directory before doing anything else. `AGENTS.md` is the single source of truth for this project — architecture, commands, conventions, security rules, and workflow are all defined there. Do not proceed with any task until you have read and internalized its contents.
|
||||
@@ -1,10 +1,6 @@
|
||||
<p align="center"><img src="https://raw.githubusercontent.com/opensourcepos/opensourcepos/master/branding/emblem.svg" alt="Open Source Point of Sale Logo" width="auto" height="200"></p>
|
||||
<h3 align="center">Open Source Point of Sale</h3>
|
||||
|
||||
## ☢️ Bootstrap 5 conversion WIP
|
||||
|
||||
This is a heavily under-construction build converting OSPOS from Bootstrap 3 to Bootstrap 5. Please do not use!
|
||||
|
||||
<p align="center">
|
||||
<a href="#-introduction">Introduction</a> · <a href="#-live-demo">Demo</a> · <a href="#-installation">Installation</a> ·
|
||||
<a href="#-contributing">Contributing</a> · <a href="#-reporting-bugs">Bugs</a> · <a href="#-faq">FAQ</a> ·
|
||||
|
||||
131
SECURITY.md
131
SECURITY.md
@@ -5,8 +5,9 @@
|
||||
- [Supported Versions](#supported-versions)
|
||||
- [Security Advisories](#security-advisories)
|
||||
- [Reporting a Vulnerability](#reporting-a-vulnerability)
|
||||
- [Disclosure Process](#disclosure-process)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- END doctoc generated TOC please keep comment here to allow update -->
|
||||
|
||||
# Security Policy
|
||||
|
||||
@@ -21,26 +22,116 @@ We release patches for security vulnerabilities.
|
||||
|
||||
## Security Advisories
|
||||
|
||||
The following security vulnerabilities have been published:
|
||||
|
||||
### High Severity
|
||||
|
||||
| CVE | Vulnerability | CVSS | Published | Fixed In | Credit |
|
||||
|-----|--------------|------|-----------|----------|--------|
|
||||
| [CVE-2025-68434](https://github.com/opensourcepos/opensourcepos/security/advisories/GHSA-wjm4-hfwg-5w5r) | CSRF leading to Admin Creation | 8.8 | 2025-12-17 | 3.4.2 | @Nixon-H, @jekkos |
|
||||
| [CVE-2025-68147](https://github.com/opensourcepos/opensourcepos/security/advisories/GHSA-xgr7-7pvw-fpmh) | Stored XSS in Return Policy | 8.1 | 2025-12-17 | 3.4.2 | @Nixon-H, @jekkos |
|
||||
| [CVE-2025-66924](https://github.com/opensourcepos/opensourcepos/security/advisories/GHSA-gv8j-f6gq-g59m) | Stored XSS in Item Kits | 7.2 | 2026-03-04 | 3.4.2 | @hungnqdz, @omkaryepre |
|
||||
|
||||
### Medium Severity
|
||||
|
||||
| CVE | Vulnerability | CVSS | Published | Fixed In | Credit |
|
||||
|-----|--------------|------|-----------|----------|--------|
|
||||
| [CVE-2025-68658](https://github.com/opensourcepos/opensourcepos/security/advisories/GHSA-32r8-8r9r-9chw) | Stored XSS in Company Name | 4.3 | 2026-01-13 | 3.4.2 | @hungnqdz |
|
||||
|
||||
For a complete list including draft advisories, see our [GitHub Security Advisories page](https://github.com/opensourcepos/opensourcepos/security/advisories).
|
||||
For a complete list of published and draft security advisories with CVE details, see our [GitHub Security Advisories page](https://github.com/opensourcepos/opensourcepos/security/advisories).
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report (suspected) security vulnerabilities to **[jeroen@steganos.dev](mailto:jeroen@steganos.dev)**.
|
||||
**Option 1: GitHub Security Advisory (Preferred)**
|
||||
|
||||
You will receive a response from us within 48 hours. If the issue is confirmed, we will release a patch as soon as possible depending on complexity but historically within a few days.
|
||||
1. Create a draft security advisory directly on GitHub:
|
||||
- Go to https://github.com/opensourcepos/opensourcepos/security/advisories
|
||||
- Click "New draft security advisory"
|
||||
- Fill in the vulnerability details using our [template below](#vulnerability-template)
|
||||
- Submit as **draft** (not published)
|
||||
|
||||
2. Notify us for triage:
|
||||
- Send an email to **[jeroen@steganos.dev](mailto:jeroen@steganos.dev)** with:
|
||||
- Subject: `[GHSA] Brief description of vulnerability`
|
||||
- Link to the draft advisory
|
||||
- Brief summary
|
||||
|
||||
**Option 2: Email Report**
|
||||
|
||||
Send vulnerability details to **[jeroen@steganos.dev](mailto:jeroen@steganos.dev)**.
|
||||
|
||||
You will receive a response within 48 hours. Confirmed vulnerabilities will be patched within a few days depending on complexity.
|
||||
|
||||
## Disclosure Process
|
||||
|
||||
### Timeline
|
||||
|
||||
| Step | Timeline | Action |
|
||||
|------|----------|--------|
|
||||
| 1. Report received | Day 0 | We acknowledge within 48 hours |
|
||||
| 2. Triage & confirmation | Day 1-3 | We validate the vulnerability |
|
||||
| 3. Fix development | Day 3-7 | We develop and test the fix |
|
||||
| 4. Patch release | Day 7-10 | We release a security patch |
|
||||
| 5. CVE request | Day 7-14 | We request CVE from GitHub (if applicable) |
|
||||
| 6. Advisory published | Day 14 | We publish the advisory with credit |
|
||||
| 7. Public disclosure | Day 14+ | Full disclosure after patch release |
|
||||
|
||||
### CVE Process
|
||||
|
||||
**We request CVE identifiers through GitHub's security advisory system.** This is the preferred and easiest method:
|
||||
|
||||
1. After we confirm and fix the vulnerability, we'll request a CVE through GitHub
|
||||
2. GitHub coordinates with MITRE on our behalf
|
||||
3. The CVE is automatically linked to the advisory
|
||||
4. You'll be credited as the reporter in the published advisory
|
||||
|
||||
**Already have a CVE?** If you've already obtained a CVE from another source (e.g., VulDB, CVE.MITRE.ORG), please include it in your report or advisory. We'll update our advisory to reference the existing CVE.
|
||||
|
||||
### No Bug Bounty Program
|
||||
|
||||
**Important:** Open Source Point of Sale does not offer a bug bounty program.
|
||||
|
||||
- All security research and vulnerability triage is done on a **voluntary basis** in our free time
|
||||
- We do not offer monetary rewards for vulnerability reports
|
||||
- We do credit reporters in published advisories (unless anonymity is requested)
|
||||
- We greatly appreciate the security research community's efforts to help improve project security
|
||||
|
||||
### Security Best Practices for Researchers
|
||||
|
||||
- **Do not** access, modify, or delete data that doesn't belong to you
|
||||
- **Do not** perform denial of service attacks
|
||||
- **Do not** publicly disclose vulnerabilities before we've had time to fix them
|
||||
- **Do** provide sufficient information to reproduce the vulnerability
|
||||
- **Do** allow us reasonable time to fix before public disclosure
|
||||
- **Do** report through official channels (GitHub advisories or email)
|
||||
|
||||
### Vulnerability Template
|
||||
|
||||
When creating a draft advisory, please include:
|
||||
|
||||
```
|
||||
## Summary
|
||||
[Brief description of the vulnerability]
|
||||
|
||||
## Impact
|
||||
- **Confidentiality:** [High/Medium/Low - what data can be exposed]
|
||||
- **Integrity:** [High/Medium/Low - what can be modified]
|
||||
- **Availability:** [High/Medium/Low - service disruption potential]
|
||||
- **Privilege Required:** [None/Low/High - authentication level needed]
|
||||
- **CVSS v3.1:** [Score] ([Vector string])
|
||||
|
||||
## Details
|
||||
[Technical details about the vulnerability]
|
||||
|
||||
**Affected Code:**
|
||||
```php
|
||||
// Path to affected file and vulnerable code
|
||||
```
|
||||
|
||||
**Attack Vector:**
|
||||
[How an attacker can exploit this]
|
||||
|
||||
## Proof of Concept
|
||||
```bash
|
||||
# Steps to reproduce
|
||||
```
|
||||
|
||||
## Patch
|
||||
[Suggested fix or approach]
|
||||
|
||||
## Affected Versions
|
||||
- OpenSourcePOS X.Y.Z and earlier
|
||||
|
||||
## Credit
|
||||
[Your GitHub username or preferred name]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Thank you to all security researchers who have contributed to making Open Source Point of Sale more secure.** Your voluntary efforts help protect thousands of users worldwide and contribute to a safer, more trustworthy free and open-source software ecosystem. We deeply appreciate your responsible disclosure and the time you invest in improving our project.
|
||||
|
||||
If you've reported a vulnerability and would like to discuss CVE coordination or have questions about the process, please reach out to us at [jeroen@steganos.dev](mailto:jeroen@steganos.dev).
|
||||
@@ -78,6 +78,7 @@ class Autoload extends AutoloadConfig
|
||||
'No_access' => '/App/Controllers/No_access.php',
|
||||
'Office' => '/App/Controllers/Office.php',
|
||||
'Persons' => '/App/Controllers/Persons.php',
|
||||
'Plugins' => '/App/Controllers/Plugins.php',
|
||||
'Receivings' => '/App/Controllers/Receivings.php',
|
||||
'Reports' => '/App/Controllers/Reports.php',
|
||||
'Sales' => '/App/Controllers/Sales.php',
|
||||
@@ -157,9 +158,9 @@ class Autoload extends AutoloadConfig
|
||||
'Barcode_lib' => '/App/Libraries/Barcode_lib.php',
|
||||
'Email_lib' => '/App/Libraries/Email_lib.php',
|
||||
'Item_lib' => '/App/Libraries/Item_lib.php',
|
||||
'Mailchimp_lib' => '/App/Libraries/Mailchimp_lib.php',
|
||||
'MY_Email' => '/App/Libraries/MY_Email.php',
|
||||
'MY_Migration' => '/App/Libraries/MY_Migration.php',
|
||||
'PluginManager' => '/App/Libraries/Plugins/PluginManager.php',
|
||||
'Receving_lib' => '/App/Libraries/Receiving_lib.php',
|
||||
'Sale_lib' => '/App/Libraries/Sale_lib.php',
|
||||
'Sms_lib' => '/App/Libraries/Sms_lib.php',
|
||||
@@ -203,6 +204,7 @@ class Autoload extends AutoloadConfig
|
||||
'cookie',
|
||||
'tabular',
|
||||
'locale',
|
||||
'security'
|
||||
'security',
|
||||
'plugin'
|
||||
];
|
||||
}
|
||||
|
||||
@@ -173,4 +173,4 @@ const DEFAULT_LANGUAGE_CODE = 'en';
|
||||
/**
|
||||
* Admin modules - list of modules required for admin privileges
|
||||
*/
|
||||
const ADMIN_MODULES = ['customers', 'employees', 'giftcards', 'items', 'item_kits', 'messages', 'receivings', 'reports', 'sales', 'config', 'suppliers'];
|
||||
const ADMIN_MODULES = ['customers', 'employees', 'giftcards', 'items', 'item_kits', 'messages', 'plugins', 'receivings', 'reports', 'sales', 'config', 'suppliers'];
|
||||
|
||||
@@ -8,6 +8,7 @@ use CodeIgniter\HotReloader\HotReloader;
|
||||
use App\Events\Db_log;
|
||||
use App\Events\Load_config;
|
||||
use App\Events\Method;
|
||||
use App\Libraries\Plugins\PluginManager;
|
||||
|
||||
/*
|
||||
* --------------------------------------------------------------------
|
||||
@@ -25,6 +26,9 @@ use App\Events\Method;
|
||||
* Example:
|
||||
* Events::on('create', [$myInstance, 'myMethod']);
|
||||
*/
|
||||
Events::on('pre_system', static function (): void {
|
||||
PluginManager::registerAllNamespaces();
|
||||
});
|
||||
|
||||
Events::on('pre_system', static function (): void {
|
||||
if (ENVIRONMENT !== 'testing') {
|
||||
@@ -48,7 +52,6 @@ Events::on('pre_system', static function (): void {
|
||||
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();
|
||||
@@ -57,8 +60,12 @@ Events::on('pre_system', static function (): void {
|
||||
}
|
||||
});
|
||||
|
||||
Events::on('post_controller_constructor', static function (): void {
|
||||
service('pluginManager');
|
||||
}, 10);
|
||||
|
||||
$config = new Load_config();
|
||||
Events::on('post_controller_constructor', [$config, 'load_config']);
|
||||
Events::on('post_controller_constructor', [$config, 'load_config'], 1);
|
||||
|
||||
$db_log = new Db_log();
|
||||
Events::on('DBQuery', [$db_log, 'db_log_queries']);
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Config;
|
||||
use App\Models\Appconfig;
|
||||
use CodeIgniter\Cache\CacheInterface;
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use Config\Database;
|
||||
|
||||
/**
|
||||
* This class holds the configuration options stored from the database so that on launch those settings can be cached
|
||||
@@ -13,7 +14,7 @@ use CodeIgniter\Config\BaseConfig;
|
||||
*/
|
||||
class OSPOS extends BaseConfig
|
||||
{
|
||||
public array $settings;
|
||||
public array $settings = [];
|
||||
public string $commit_sha1 = 'dev'; // TODO: Travis scripts need to be updated to replace this with the commit hash on build
|
||||
private CacheInterface $cache;
|
||||
|
||||
@@ -33,26 +34,35 @@ class OSPOS extends BaseConfig
|
||||
|
||||
if ($cache) {
|
||||
$this->settings = decode_array($cache);
|
||||
} else {
|
||||
try {
|
||||
$appconfig = model(Appconfig::class);
|
||||
foreach ($appconfig->get_all()->getResult() as $app_config) {
|
||||
$this->settings[$app_config->key] = $app_config->value;
|
||||
}
|
||||
$this->cache->save('settings', encode_array($this->settings));
|
||||
} catch (\Exception $e) {
|
||||
// Database table doesn't exist yet (migrations haven't run)
|
||||
// or database connection failed. Return empty settings to
|
||||
// allow migration page to display. Catches mysqli_sql_exception
|
||||
// which is not a subclass of DatabaseException.
|
||||
$this->settings = [
|
||||
'language' => 'english',
|
||||
'language_code' => 'en',
|
||||
'company' => 'Home',
|
||||
'barcode_type' => 'Code39'
|
||||
];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$db = Database::connect();
|
||||
|
||||
if (!$db->tableExists('app_config')) {
|
||||
$this->settings = $this->getDefaultSettings();
|
||||
return;
|
||||
}
|
||||
|
||||
$appconfig = model(Appconfig::class);
|
||||
foreach ($appconfig->get_all()->getResult() as $app_config) {
|
||||
$this->settings[$app_config->key] = $app_config->value;
|
||||
}
|
||||
$this->cache->save('settings', encode_array($this->settings));
|
||||
} catch (\Exception $e) {
|
||||
$this->settings = $this->getDefaultSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private function getDefaultSettings(): array
|
||||
{
|
||||
return [
|
||||
'language' => 'english',
|
||||
'language_code' => 'en',
|
||||
'company' => 'Home',
|
||||
'barcode_type' => 'Code39'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,4 +73,4 @@ class OSPOS extends BaseConfig
|
||||
$this->cache->delete('settings');
|
||||
$this->set_settings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Config;
|
||||
|
||||
use App\Libraries\MY_Language;
|
||||
use App\Libraries\Plugins\PluginManager;
|
||||
use Locale;
|
||||
use HTMLPurifier;
|
||||
use HTMLPurifier_Config;
|
||||
@@ -61,6 +62,24 @@ class Services extends BaseService
|
||||
return new MY_Language($locale);
|
||||
}
|
||||
|
||||
public static function pluginManager(bool $getShared = true): PluginManager
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('pluginManager');
|
||||
}
|
||||
|
||||
$manager = new PluginManager();
|
||||
|
||||
if ($manager->canLoadPlugins()) {
|
||||
$manager->discoverPlugins();
|
||||
$manager->registerPluginEvents();
|
||||
} else {
|
||||
log_message('debug', 'PluginManager: skipping init, plugin_config table not found.');
|
||||
}
|
||||
|
||||
return $manager;
|
||||
}
|
||||
|
||||
private static HTMLPurifier $htmlPurifier;
|
||||
|
||||
public static function htmlPurifier($getShared = true): object
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Libraries\Barcode_lib;
|
||||
use App\Libraries\Mailchimp_lib;
|
||||
use App\Libraries\Receiving_lib;
|
||||
use App\Libraries\Sale_lib;
|
||||
use App\Libraries\Tax_lib;
|
||||
@@ -253,32 +252,6 @@ class Config extends Secure_Controller
|
||||
$data['image_allowed_types'] = array_combine($image_allowed_types, $image_allowed_types);
|
||||
$data['selected_image_allowed_types'] = explode(',', $this->config['image_allowed_types']);
|
||||
|
||||
// Integrations Related fields
|
||||
$data['mailchimp'] = [];
|
||||
|
||||
if (check_encryption()) { // TODO: Hungarian notation
|
||||
if (!isset($this->encrypter)) {
|
||||
helper('security');
|
||||
$this->encrypter = Services::encrypter();
|
||||
}
|
||||
|
||||
$data['mailchimp']['api_key'] = (isset($this->config['mailchimp_api_key']) && !empty($this->config['mailchimp_api_key']))
|
||||
? $this->encrypter->decrypt($this->config['mailchimp_api_key'])
|
||||
: '';
|
||||
|
||||
$data['mailchimp']['list_id'] = (isset($this->config['mailchimp_list_id']) && !empty($this->config['mailchimp_list_id']))
|
||||
? $this->encrypter->decrypt($this->config['mailchimp_list_id'])
|
||||
: '';
|
||||
|
||||
// Remove any backup of .env created by check_encryption()
|
||||
remove_backup();
|
||||
} else {
|
||||
$data['mailchimp']['api_key'] = '';
|
||||
$data['mailchimp']['list_id'] = '';
|
||||
}
|
||||
|
||||
$data['mailchimp']['lists'] = $this->_mailchimp();
|
||||
|
||||
return view('configs/manage', $data);
|
||||
}
|
||||
|
||||
@@ -316,7 +289,6 @@ class Config extends Secure_Controller
|
||||
return $this->response->setJSON(['success' => $success, 'message' => $message]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
@@ -371,6 +343,8 @@ class Config extends Secure_Controller
|
||||
public function postSaveGeneral(): ResponseInterface
|
||||
{
|
||||
$batchSaveData = [
|
||||
'theme' => $this->request->getPost('theme'),
|
||||
'login_form' => $this->request->getPost('login_form'),
|
||||
'default_sales_discount_type' => $this->request->getPost('default_sales_discount_type') != null,
|
||||
'default_sales_discount' => parse_decimals($this->request->getPost('default_sales_discount')),
|
||||
'default_receivings_discount_type' => $this->request->getPost('default_receivings_discount_type') != null,
|
||||
@@ -378,6 +352,8 @@ class Config extends Secure_Controller
|
||||
'enforce_privacy' => $this->request->getPost('enforce_privacy') != null,
|
||||
'receiving_calculate_average_price' => $this->request->getPost('receiving_calculate_average_price') != null,
|
||||
'lines_per_page' => $this->request->getPost('lines_per_page', FILTER_SANITIZE_NUMBER_INT),
|
||||
'notify_horizontal_position' => $this->request->getPost('notify_horizontal_position'),
|
||||
'notify_vertical_position' => $this->request->getPost('notify_vertical_position'),
|
||||
'image_max_width' => $this->request->getPost('image_max_width', FILTER_SANITIZE_NUMBER_INT),
|
||||
'image_max_height' => $this->request->getPost('image_max_height', FILTER_SANITIZE_NUMBER_INT),
|
||||
'image_max_size' => $this->request->getPost('image_max_size', FILTER_SANITIZE_NUMBER_INT),
|
||||
@@ -421,26 +397,6 @@ class Config extends Secure_Controller
|
||||
return $this->response->setJSON(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves Appearance configuration. Used in app/Views/configs/appearance_config.php
|
||||
*/
|
||||
public function postSaveAppearance(): ResponseInterface
|
||||
{
|
||||
$batch_save_data = [
|
||||
'theme' => $this->request->getPost('theme'),
|
||||
'login_form' => $this->request->getPost('login_form'),
|
||||
'notify_horizontal_position' => $this->request->getPost('notify_horizontal_position'),
|
||||
'notify_vertical_position' => $this->request->getPost('notify_vertical_position'),
|
||||
'color_mode' => $this->request->getPost('color_mode'),
|
||||
'config_menu_position' => $this->request->getPost('config_menu_position'),
|
||||
'responsive_design' => $this->request->getPost('responsive_design') != null
|
||||
];
|
||||
|
||||
$success = $this->appconfig->batch_save($batch_save_data);
|
||||
|
||||
return $this->response->setJSON(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a number against the currently selected locale. Used in app/Views/configs/locale_config.php
|
||||
*
|
||||
@@ -497,7 +453,6 @@ class Config extends Secure_Controller
|
||||
'currency_code' => $this->request->getPost('currency_code'),
|
||||
'language_code' => $exploded[0],
|
||||
'language' => $exploded[1],
|
||||
'rtl_language' => $this->request->getPost('rtl_language') != null,
|
||||
'timezone' => $this->request->getPost('timezone'),
|
||||
'dateformat' => $this->request->getPost('dateformat'),
|
||||
'timeformat' => $this->request->getPost('timeformat'),
|
||||
@@ -592,76 +547,6 @@ class Config extends Secure_Controller
|
||||
return $this->response->setJSON(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function fetches all the available lists from Mailchimp for the given API key
|
||||
*/
|
||||
private function _mailchimp(string $api_key = ''): array // TODO: Hungarian notation
|
||||
{
|
||||
$mailchimp_lib = new Mailchimp_lib(['api_key' => $api_key]);
|
||||
|
||||
$result = [];
|
||||
|
||||
$lists = $mailchimp_lib->getLists();
|
||||
if ($lists !== false) {
|
||||
if (is_array($lists) && !empty($lists['lists']) && is_array($lists['lists'])) {
|
||||
foreach ($lists['lists'] as $list) {
|
||||
$result[$list['id']] = $list['name'] . ' [' . $list['stats']['member_count'] . ']';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Mailchimp lists when a valid API key is inserted. Used in app/Views/configs/integrations_config.php
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postCheckMailchimpApiKey(): ResponseInterface
|
||||
{
|
||||
$lists = $this->_mailchimp($this->request->getPost('mailchimp_api_key'));
|
||||
$success = count($lists) > 0;
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => $success,
|
||||
'message' => lang('Config.mailchimp_key_' . ($success ? '' : 'un') . 'successfully'),
|
||||
'mailchimp_lists' => $lists
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves Mailchimp configuration. Used in app/Views/configs/integrations_config.php
|
||||
*
|
||||
* @throws ReflectionException
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSaveMailchimp(): ResponseInterface
|
||||
{
|
||||
$api_key = '';
|
||||
$list_id = '';
|
||||
|
||||
if (check_encryption()) {
|
||||
$api_key_unencrypted = $this->request->getPost('mailchimp_api_key');
|
||||
if (!empty($api_key_unencrypted)) {
|
||||
$api_key = $this->encrypter->encrypt($api_key_unencrypted);
|
||||
}
|
||||
|
||||
$list_id_unencrypted = $this->request->getPost('mailchimp_list_id');
|
||||
if (!empty($list_id_unencrypted)) {
|
||||
$list_id = $this->encrypter->encrypt($list_id_unencrypted);
|
||||
}
|
||||
}
|
||||
|
||||
$batch_save_data = ['mailchimp_api_key' => $api_key, 'mailchimp_list_id' => $list_id];
|
||||
|
||||
$success = $this->appconfig->batch_save($batch_save_data);
|
||||
|
||||
return $this->response->setJSON(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all stock locations. Used in app/Views/configs/stock_config.php
|
||||
*
|
||||
@@ -941,7 +826,9 @@ class Config extends Secure_Controller
|
||||
public function postSaveReceipt(): ResponseInterface
|
||||
{
|
||||
$batch_save_data = [
|
||||
'receipt_template' => $this->request->getPost('receipt_template'),
|
||||
'receipt_template' => Sale_lib::isValidReceiptTemplate($this->request->getPost('receipt_template'))
|
||||
? $this->request->getPost('receipt_template')
|
||||
: 'receipt_default',
|
||||
'receipt_font_size' => $this->request->getPost('receipt_font_size', FILTER_SANITIZE_NUMBER_INT),
|
||||
'print_delay_autoreturn' => $this->request->getPost('print_delay_autoreturn', FILTER_SANITIZE_NUMBER_INT),
|
||||
'email_receipt_check_behaviour' => $this->request->getPost('email_receipt_check_behaviour'),
|
||||
@@ -1027,8 +914,8 @@ class Config extends Secure_Controller
|
||||
'work_order_enable' => $this->request->getPost('work_order_enable') != null,
|
||||
'work_order_format' => $this->request->getPost('work_order_format'),
|
||||
'last_used_work_order_number' => $this->request->getPost('last_used_work_order_number', FILTER_SANITIZE_NUMBER_INT),
|
||||
'invoice_type' => Sale_lib::isValidInvoiceType($this->request->getPost('invoice_type'))
|
||||
? $this->request->getPost('invoice_type')
|
||||
'invoice_type' => Sale_lib::isValidInvoiceType($this->request->getPost('invoice_type'))
|
||||
? $this->request->getPost('invoice_type')
|
||||
: 'invoice'
|
||||
];
|
||||
|
||||
@@ -1074,8 +961,8 @@ class Config extends Secure_Controller
|
||||
return $fieldType === 'first' ? 'name' : '';
|
||||
}
|
||||
|
||||
$allowed = $fieldType === 'first'
|
||||
? Item::ALLOWED_SUGGESTIONS_COLUMNS
|
||||
$allowed = $fieldType === 'first'
|
||||
? Item::ALLOWED_SUGGESTIONS_COLUMNS
|
||||
: Item::ALLOWED_SUGGESTIONS_COLUMNS_WITH_EMPTY;
|
||||
|
||||
$fallback = $fieldType === 'first' ? 'name' : '';
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Libraries\Mailchimp_lib;
|
||||
|
||||
use App\Models\Customer;
|
||||
use App\Models\Customer_rewards;
|
||||
use App\Models\Tax_code;
|
||||
use CodeIgniter\Events\Events;
|
||||
use CodeIgniter\HTTP\DownloadResponse;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\OSPOS;
|
||||
@@ -15,8 +14,6 @@ use stdClass;
|
||||
|
||||
class Customers extends Persons
|
||||
{
|
||||
private string $_list_id;
|
||||
private Mailchimp_lib $mailchimp_lib;
|
||||
private Customer_rewards $customer_rewards;
|
||||
private Customer $customer;
|
||||
private Tax_code $tax_code;
|
||||
@@ -25,19 +22,11 @@ class Customers extends Persons
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('customers');
|
||||
$this->mailchimp_lib = new Mailchimp_lib();
|
||||
|
||||
$this->customer_rewards = model(Customer_rewards::class);
|
||||
$this->customer = model(Customer::class);
|
||||
$this->tax_code = model(Tax_code::class);
|
||||
$this->config = config(OSPOS::class)->settings;
|
||||
|
||||
$encrypter = Services::encrypter();
|
||||
|
||||
if (!empty($this->config['mailchimp_list_id'])) {
|
||||
$this->_list_id = $encrypter->decrypt($this->config['mailchimp_list_id']);
|
||||
} else {
|
||||
$this->_list_id = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,11 +41,12 @@ class Customers extends Persons
|
||||
|
||||
/**
|
||||
* Gets one row for a customer manage table. This is called using AJAX to update one row.
|
||||
* @param int $row_id
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getRow(int $row_id): ResponseInterface
|
||||
{
|
||||
$person = $this->customer->get_info($row_id);
|
||||
$person = $this->customer->getInfo($row_id);
|
||||
|
||||
// Retrieve the total amount the customer spent so far together with min, max and average values
|
||||
$stats = $this->customer->get_stats($person->person_id); // TODO: This and the next 11 lines are duplicated in search(). Extract a method.
|
||||
@@ -141,14 +131,16 @@ class Customers extends Persons
|
||||
|
||||
/**
|
||||
* Loads the customer edit form
|
||||
* @param int $customerId
|
||||
* @return string
|
||||
*/
|
||||
public function getView(int $customer_id = NEW_ENTRY): string
|
||||
public function getView(int $customerId = NEW_ENTRY): string
|
||||
{
|
||||
// Set default values
|
||||
if ($customer_id == null) $customer_id = NEW_ENTRY;
|
||||
if ($customerId == null) {
|
||||
$customerId = NEW_ENTRY;
|
||||
}
|
||||
|
||||
$info = $this->customer->get_info($customer_id);
|
||||
$info = $this->customer->getInfo($customerId);
|
||||
foreach (get_object_vars($info) as $property => $value) {
|
||||
$info->$property = $value;
|
||||
}
|
||||
@@ -159,7 +151,7 @@ class Customers extends Persons
|
||||
$data['person_info']->employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
||||
}
|
||||
|
||||
$employee_info = $this->employee->get_info($info->employee_id);
|
||||
$employee_info = $this->employee->getInfo($info->employee_id);
|
||||
$data['employee'] = $employee_info->first_name . ' ' . $employee_info->last_name;
|
||||
|
||||
$tax_code_info = $this->tax_code->get_info($info->sales_tax_code_id);
|
||||
@@ -180,7 +172,7 @@ class Customers extends Persons
|
||||
$data['use_destination_based_tax'] = $this->config['use_destination_based_tax'];
|
||||
|
||||
// Retrieve the total amount the customer spent so far together with min, max and average values
|
||||
$stats = $this->customer->get_stats($customer_id);
|
||||
$stats = $this->customer->get_stats($customerId);
|
||||
if (!empty($stats)) {
|
||||
foreach (get_object_vars($stats) as $property => $value) {
|
||||
$info->$property = $value;
|
||||
@@ -188,69 +180,29 @@ class Customers extends Persons
|
||||
$data['stats'] = $stats;
|
||||
}
|
||||
|
||||
// Retrieve the info from Mailchimp only if there is an email address assigned
|
||||
if (!empty($info->email)) {
|
||||
// Collect Mailchimp customer info
|
||||
if (($mailchimp_info = $this->mailchimp_lib->getMemberInfo($this->_list_id, $info->email)) !== false) {
|
||||
$data['mailchimp_info'] = $mailchimp_info;
|
||||
|
||||
// Collect customer Mailchimp emails activities (stats)
|
||||
if (($activities = $this->mailchimp_lib->getMemberActivity($this->_list_id, $info->email)) !== false) {
|
||||
if (array_key_exists('activity', $activities)) {
|
||||
$open = 0;
|
||||
$unopen = 0;
|
||||
$click = 0;
|
||||
$total = 0;
|
||||
$lastopen = '';
|
||||
|
||||
foreach ($activities['activity'] as $activity) {
|
||||
if ($activity['action'] == 'sent') {
|
||||
++$unopen;
|
||||
} elseif ($activity['action'] == 'open') {
|
||||
if (empty($lastopen)) {
|
||||
$lastopen = substr($activity['timestamp'], 0, 10);
|
||||
}
|
||||
++$open;
|
||||
} elseif ($activity['action'] == 'click') {
|
||||
if (empty($lastopen)) {
|
||||
$lastopen = substr($activity['timestamp'], 0, 10);
|
||||
}
|
||||
++$click;
|
||||
}
|
||||
|
||||
++$total;
|
||||
}
|
||||
|
||||
$data['mailchimp_activity']['total'] = $total;
|
||||
$data['mailchimp_activity']['open'] = $open;
|
||||
$data['mailchimp_activity']['unopen'] = $unopen;
|
||||
$data['mailchimp_activity']['click'] = $click;
|
||||
$data['mailchimp_activity']['lastopen'] = $lastopen;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Events::trigger('customer_loaded', $customerId);
|
||||
|
||||
return view("customers/form", $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts/updates a customer
|
||||
* @param int $customerId
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postSave(int $customer_id = NEW_ENTRY): ResponseInterface
|
||||
public function postSave(int $customerId = NEW_ENTRY): ResponseInterface
|
||||
{
|
||||
$first_name = $this->request->getPost('first_name');
|
||||
$last_name = $this->request->getPost('last_name');
|
||||
$firstName = $this->request->getPost('first_name');
|
||||
$lastName = $this->request->getPost('last_name');
|
||||
$email = strtolower($this->request->getPost('email', FILTER_SANITIZE_EMAIL));
|
||||
|
||||
// Format first and last name properly
|
||||
$first_name = $this->nameize($first_name);
|
||||
$last_name = $this->nameize($last_name);
|
||||
$firstName = $this->nameize($firstName);
|
||||
$lastName = $this->nameize($lastName);
|
||||
|
||||
$person_data = [
|
||||
'first_name' => $first_name,
|
||||
'last_name' => $last_name,
|
||||
$personData = [
|
||||
'first_name' => $firstName,
|
||||
'last_name' => $lastName,
|
||||
'gender' => $this->request->getPost('gender', FILTER_SANITIZE_NUMBER_INT),
|
||||
'email' => $email,
|
||||
'phone_number' => $this->request->getPost('phone_number'),
|
||||
@@ -263,9 +215,9 @@ class Customers extends Persons
|
||||
'comments' => $this->request->getPost('comments')
|
||||
];
|
||||
|
||||
$date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $this->request->getPost('date'));
|
||||
$dateFormatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $this->request->getPost('date'));
|
||||
|
||||
$customer_data = [
|
||||
$customerData = [
|
||||
'consent' => $this->request->getPost('consent') != null,
|
||||
'account_number' => $this->request->getPost('account_number') == '' ? null : $this->request->getPost('account_number'),
|
||||
'tax_id' => $this->request->getPost('tax_id'),
|
||||
@@ -274,41 +226,32 @@ class Customers extends Persons
|
||||
'discount_type' => $this->request->getPost('discount_type') == null ? PERCENT : $this->request->getPost('discount_type', FILTER_SANITIZE_NUMBER_INT),
|
||||
'package_id' => $this->request->getPost('package_id') == '' ? null : $this->request->getPost('package_id'),
|
||||
'taxable' => $this->request->getPost('taxable') != null,
|
||||
'date' => $date_formatter->format('Y-m-d H:i:s'),
|
||||
'date' => $dateFormatter->format('Y-m-d H:i:s'),
|
||||
'employee_id' => $this->request->getPost('employee_id', FILTER_SANITIZE_NUMBER_INT),
|
||||
'sales_tax_code_id' => $this->request->getPost('sales_tax_code_id') == '' ? null : $this->request->getPost('sales_tax_code_id', FILTER_SANITIZE_NUMBER_INT)
|
||||
];
|
||||
|
||||
if ($this->customer->save_customer($person_data, $customer_data, $customer_id)) {
|
||||
// Save customer to Mailchimp selected list // TODO: addOrUpdateMember should be refactored. Potentially pass an array or object instead of 6 parameters.
|
||||
$mailchimp_status = $this->request->getPost('mailchimp_status');
|
||||
$this->mailchimp_lib->addOrUpdateMember(
|
||||
$this->_list_id,
|
||||
$email,
|
||||
$first_name,
|
||||
$last_name,
|
||||
$mailchimp_status == null ? "" : $mailchimp_status,
|
||||
['vip' => $this->request->getPost('mailchimp_vip') != null]
|
||||
);
|
||||
if ($this->customer->saveCustomer($personData, $customerData, $customerId)) {
|
||||
Events::trigger('customer_saved', [$customerData['person_id']]);
|
||||
|
||||
// New customer
|
||||
if ($customer_id == NEW_ENTRY) {
|
||||
if ($customerId == NEW_ENTRY) {
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Customers.successful_adding') . ' ' . $first_name . ' ' . $last_name,
|
||||
'id' => $customer_data['person_id']
|
||||
'message' => lang('Customers.successful_adding') . " $firstName $lastName",
|
||||
'id' => $customerData['person_id']
|
||||
]);
|
||||
} else { // Existing customer
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Customers.successful_updating') . ' ' . $first_name . ' ' . $last_name,
|
||||
'id' => $customer_id
|
||||
'message' => lang('Customers.successful_updating') . " $firstName $lastName",
|
||||
'id' => $customerId
|
||||
]);
|
||||
}
|
||||
} else { // Failure
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Customers.error_adding_updating') . ' ' . $first_name . ' ' . $last_name,
|
||||
'message' => lang('Customers.error_adding_updating') . " $firstName $lastName",
|
||||
'id' => NEW_ENTRY
|
||||
]);
|
||||
}
|
||||
@@ -344,26 +287,23 @@ class Customers extends Persons
|
||||
}
|
||||
|
||||
/**
|
||||
* This deletes customers from the customers table
|
||||
* This deletes customers from the customer's table
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postDelete(): ResponseInterface
|
||||
{
|
||||
$customers_to_delete = $this->request->getPost('ids');
|
||||
$customers_info = $this->customer->get_multiple_info($customers_to_delete);
|
||||
$customersToDelete = $this->request->getPost('ids');
|
||||
$customers = $this->customer->get_multiple_info($customersToDelete);
|
||||
|
||||
$count = 0;
|
||||
|
||||
foreach ($customers_info->getResult() as $info) {
|
||||
if ($this->customer->delete($info->person_id)) {
|
||||
// remove customer from Mailchimp selected list
|
||||
$this->mailchimp_lib->removeMember($this->_list_id, $info->email);
|
||||
|
||||
foreach ($customers->getResult() as $customer) {
|
||||
if ($this->customer->delete($customer->person_id)) {
|
||||
Events::trigger('customer_deleted', (int)$customer->person_id, (string)$customer->email);
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($count == count($customers_to_delete)) {
|
||||
if ($count === count($customersToDelete)) {
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Customers.successful_deleted') . ' ' . $count . ' ' . lang('Customers.one_or_multiple')
|
||||
@@ -411,16 +351,17 @@ class Customers extends Persons
|
||||
if (($handle = fopen($_FILES['file_path']['tmp_name'], 'r')) !== false) {
|
||||
// Skip the first row as it's the table description
|
||||
fgetcsv($handle);
|
||||
$i = 1;
|
||||
$rowNumber = 1;
|
||||
|
||||
$failCodes = [];
|
||||
$customerIds = [];
|
||||
|
||||
while (($data = fgetcsv($handle)) !== false) {
|
||||
$consent = $data[3] == '' ? 0 : 1;
|
||||
|
||||
if (sizeof($data) >= 16 && $consent) {
|
||||
$email = strtolower($data[4]);
|
||||
$person_data = [
|
||||
$personData = [
|
||||
'first_name' => $data[0],
|
||||
'last_name' => $data[1],
|
||||
'gender' => $data[2],
|
||||
@@ -435,7 +376,7 @@ class Customers extends Persons
|
||||
'comments' => $data[12]
|
||||
];
|
||||
|
||||
$customer_data = [
|
||||
$customerData = [
|
||||
'consent' => $consent,
|
||||
'company_name' => $data[13],
|
||||
'discount' => $data[15],
|
||||
@@ -450,7 +391,7 @@ class Customers extends Persons
|
||||
$invalidated = $this->customer->check_email_exists($email);
|
||||
|
||||
if ($account_number != '') {
|
||||
$customer_data['account_number'] = $account_number;
|
||||
$customerData['account_number'] = $account_number;
|
||||
$invalidated &= $this->customer->check_account_number_exists($account_number);
|
||||
}
|
||||
} else {
|
||||
@@ -458,16 +399,15 @@ class Customers extends Persons
|
||||
}
|
||||
|
||||
if ($invalidated) {
|
||||
$failCodes[] = $i;
|
||||
log_message('error', "Row $i was not imported: Either email or account number already exist or data was invalid.");
|
||||
} elseif ($this->customer->save_customer($person_data, $customer_data)) {
|
||||
// Save customer to Mailchimp selected list
|
||||
$this->mailchimp_lib->addOrUpdateMember($this->_list_id, $person_data['email'], $person_data['first_name'], '', $person_data['last_name']);
|
||||
$failCodes[] = $rowNumber;
|
||||
log_message('error', "Row $rowNumber was not imported: Either email or account number already exist or data was invalid.");
|
||||
} elseif ($this->customer->saveCustomer($personData, $customerData)) {
|
||||
$customerIds[] = $customerData['person_id'];
|
||||
} else {
|
||||
$failCodes[] = $i;
|
||||
$failCodes[] = $rowNumber;
|
||||
}
|
||||
|
||||
++$i;
|
||||
++$rowNumber;
|
||||
}
|
||||
|
||||
if (count($failCodes) > 0) {
|
||||
@@ -475,6 +415,8 @@ class Customers extends Persons
|
||||
|
||||
return $this->response->setJSON(['success' => false, 'message' => $message]);
|
||||
} else {
|
||||
Events::trigger('customer_saved', $customerIds);
|
||||
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Customers.csv_import_success')]);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -75,7 +75,7 @@ class Employees extends Persons
|
||||
*/
|
||||
public function getView(int $employee_id = NEW_ENTRY): string
|
||||
{
|
||||
$person_info = $this->employee->get_info($employee_id);
|
||||
$person_info = $this->employee->getInfo($employee_id);
|
||||
$current_user = $this->employee->get_logged_in_employee_info();
|
||||
|
||||
if ($employee_id != NEW_ENTRY && !$this->employee->canModifyEmployee($person_info->person_id, $current_user->person_id)) {
|
||||
@@ -119,7 +119,7 @@ class Employees extends Persons
|
||||
$current_user = $this->employee->get_logged_in_employee_info();
|
||||
|
||||
if ($employee_id != NEW_ENTRY) {
|
||||
$target_employee = $this->employee->get_info($employee_id);
|
||||
$target_employee = $this->employee->getInfo($employee_id);
|
||||
if (!$this->employee->canModifyEmployee($target_employee->person_id, $current_user->person_id)) {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
|
||||
@@ -106,7 +106,7 @@ class Expenses extends Secure_Controller
|
||||
}
|
||||
} else {
|
||||
$stored_employee_id = $expense_id == NEW_ENTRY ? $current_employee_id : $data['expenses_info']->employee_id;
|
||||
$stored_employee = $this->employee->get_info($stored_employee_id);
|
||||
$stored_employee = $this->employee->getInfo($stored_employee_id);
|
||||
$data['employees'][$stored_employee_id] = $stored_employee->first_name . ' ' . $stored_employee->last_name;
|
||||
}
|
||||
$data['can_assign_employee'] = $can_assign_employee;
|
||||
|
||||
@@ -51,7 +51,7 @@ class Home extends Secure_Controller
|
||||
return $this->response->setStatusCode(403)->setBody(lang('Employees.unauthorized_modify'));
|
||||
}
|
||||
|
||||
$person_info = $this->employee->get_info($employeeId);
|
||||
$person_info = $this->employee->getInfo($employeeId);
|
||||
foreach (get_object_vars($person_info) as $property => $value) {
|
||||
$person_info->$property = $value;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Models\Item_taxes;
|
||||
use App\Models\Stock_location;
|
||||
use App\Models\Supplier;
|
||||
use App\Models\Tax_category;
|
||||
use CodeIgniter\Events\Events;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\Images\Handlers\BaseHandler;
|
||||
use CodeIgniter\HTTP\DownloadResponse;
|
||||
@@ -154,8 +155,23 @@ class Items extends Secure_Controller
|
||||
{
|
||||
helper('file');
|
||||
|
||||
$pic_filename = rawurldecode($pic_filename);
|
||||
$file_extension = pathinfo($pic_filename, PATHINFO_EXTENSION);
|
||||
// Security: Sanitize filename to prevent path traversal
|
||||
// Use basename() to strip directory components and prevent '../' attacks
|
||||
$pic_filename = basename(rawurldecode($pic_filename));
|
||||
$file_extension = strtolower(pathinfo($pic_filename, PATHINFO_EXTENSION));
|
||||
|
||||
// Validate file extension against system-configured allowed image types
|
||||
// Handle both legacy pipe-separated and current comma-separated formats
|
||||
// Fallback to types that GD library can process for thumbnail generation
|
||||
$allowed_types = $this->config['image_allowed_types'] ?? 'jpg,jpeg,gif,png,webp,bmp,tif,tiff';
|
||||
$allowed_extensions = strpos($allowed_types, '|') !== false
|
||||
? explode('|', $allowed_types)
|
||||
: explode(',', $allowed_types);
|
||||
|
||||
if (!in_array($file_extension, $allowed_extensions, true)) {
|
||||
return $this->response->setStatusCode(400)->setBody('Invalid file type');
|
||||
}
|
||||
|
||||
$images = glob("./uploads/item_pics/$pic_filename");
|
||||
$base_path = './uploads/item_pics/' . pathinfo($pic_filename, PATHINFO_FILENAME);
|
||||
|
||||
@@ -260,7 +276,7 @@ class Items extends Secure_Controller
|
||||
*/
|
||||
public function getRow(string $item_ids): ResponseInterface // TODO: An array would be better for parameter.
|
||||
{
|
||||
$item_infos = $this->item->get_multiple_info(explode(':', $item_ids), $this->item_lib->get_item_location());
|
||||
$item_infos = $this->item->getMultipleInfo(explode(':', $item_ids), $this->item_lib->get_item_location());
|
||||
|
||||
$result = [];
|
||||
|
||||
@@ -476,7 +492,7 @@ class Items extends Secure_Controller
|
||||
public function getGenerateBarcodes(string $item_ids): string // TODO: Passing these through as a string instead of an array limits the contents of the item_ids. Perhaps a better approach would to serialize as JSON in an array and pass through post variables?
|
||||
{
|
||||
$item_ids = explode(':', $item_ids);
|
||||
$result = $this->item->get_multiple_info($item_ids, $this->item_lib->get_item_location())->getResultArray();
|
||||
$result = $this->item->getMultipleInfo($item_ids, $this->item_lib->get_item_location())->getResultArray();
|
||||
$data['barcode_config'] = $this->barcode_lib->get_barcode_config();
|
||||
|
||||
foreach ($result as &$item) {
|
||||
@@ -596,148 +612,149 @@ class Items extends Secure_Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $item_id
|
||||
* @param int $itemId
|
||||
* @return ResponseInterface
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function postSave(int $item_id = NEW_ENTRY): ResponseInterface
|
||||
public function postSave(int $itemId = NEW_ENTRY): ResponseInterface
|
||||
{
|
||||
$upload_data = $this->upload_image();
|
||||
$upload_success = empty($upload_data['error']);
|
||||
$uploadData = $this->upload_image();
|
||||
$uploadSuccess = empty($uploadData['error']);
|
||||
|
||||
$raw_receiving_quantity = $this->request->getPost('receiving_quantity');
|
||||
$rawReceivingQuantity = $this->request->getPost('receiving_quantity');
|
||||
|
||||
$receiving_quantity = parse_quantity($raw_receiving_quantity);
|
||||
$item_type = $this->request->getPost('item_type') === null ? ITEM : intval($this->request->getPost('item_type'));
|
||||
$receivingQuantity = parse_quantity($rawReceivingQuantity);
|
||||
$itemType = $this->request->getPost('item_type') === null ? ITEM : intval($this->request->getPost('item_type'));
|
||||
|
||||
if ($receiving_quantity === 0.0 && $item_type !== ITEM_TEMP) {
|
||||
$receiving_quantity = 1;
|
||||
if ($receivingQuantity === 0.0 && $itemType !== ITEM_TEMP) {
|
||||
$receivingQuantity = 1;
|
||||
}
|
||||
|
||||
$default_pack_name = lang('Items.default_pack_name');
|
||||
$defaultPackName = lang('Items.default_pack_name');
|
||||
|
||||
$cost_price = parse_decimals($this->request->getPost('cost_price'));
|
||||
$unit_price = parse_decimals($this->request->getPost('unit_price'));
|
||||
$reorder_level = parse_quantity($this->request->getPost('reorder_level'));
|
||||
$qty_per_pack = parse_quantity($this->request->getPost('qty_per_pack') ?? '');
|
||||
$costPrice = parse_decimals($this->request->getPost('cost_price'));
|
||||
$unitPrice = parse_decimals($this->request->getPost('unit_price'));
|
||||
$reorderLevel = parse_quantity($this->request->getPost('reorder_level'));
|
||||
$quantityPerPack = parse_quantity($this->request->getPost('qty_per_pack') ?? '');
|
||||
|
||||
// Save item data
|
||||
$item_data = [
|
||||
$itemData = [
|
||||
'name' => $this->request->getPost('name'),
|
||||
'description' => $this->request->getPost('description', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||
'category' => $this->request->getPost('category'),
|
||||
'item_type' => $item_type,
|
||||
'item_type' => $itemType,
|
||||
'stock_type' => $this->request->getPost('stock_type') === null ? HAS_STOCK : intval($this->request->getPost('stock_type')),
|
||||
'supplier_id' => empty($this->request->getPost('supplier_id')) ? null : intval($this->request->getPost('supplier_id')),
|
||||
'item_number' => empty($this->request->getPost('item_number')) ? null : $this->request->getPost('item_number'),
|
||||
'cost_price' => $cost_price,
|
||||
'unit_price' => $unit_price,
|
||||
'reorder_level' => $reorder_level,
|
||||
'receiving_quantity' => $receiving_quantity,
|
||||
'cost_price' => $costPrice,
|
||||
'unit_price' => $unitPrice,
|
||||
'reorder_level' => $reorderLevel,
|
||||
'receiving_quantity' => $receivingQuantity,
|
||||
'allow_alt_description' => $this->request->getPost('allow_alt_description') != null,
|
||||
'is_serialized' => $this->request->getPost('is_serialized') != null,
|
||||
'qty_per_pack' => $this->request->getPost('qty_per_pack') == null ? 1 : parse_quantity($qty_per_pack),
|
||||
'pack_name' => $this->request->getPost('pack_name') == null ? $default_pack_name : $this->request->getPost('pack_name'),
|
||||
'low_sell_item_id' => $this->request->getPost('low_sell_item_id') === null ? $item_id : intval($this->request->getPost('low_sell_item_id')),
|
||||
'qty_per_pack' => $this->request->getPost('qty_per_pack') == null ? 1 : parse_quantity($quantityPerPack),
|
||||
'pack_name' => $this->request->getPost('pack_name') == null ? $defaultPackName : $this->request->getPost('pack_name'),
|
||||
'low_sell_item_id' => $this->request->getPost('low_sell_item_id') === null ? $itemId : intval($this->request->getPost('low_sell_item_id')),
|
||||
'deleted' => $this->request->getPost('is_deleted') != null,
|
||||
'hsn_code' => $this->request->getPost('hsn_code') === null ? '' : $this->request->getPost('hsn_code')
|
||||
];
|
||||
|
||||
if ($item_data['item_type'] == ITEM_TEMP) {
|
||||
$item_data['stock_type'] = HAS_NO_STOCK;
|
||||
$item_data['receiving_quantity'] = 0;
|
||||
$item_data['reorder_level'] = 0;
|
||||
if ($itemData['item_type'] == ITEM_TEMP) {
|
||||
$itemData['stock_type'] = HAS_NO_STOCK;
|
||||
$itemData['receiving_quantity'] = 0;
|
||||
$itemData['reorder_level'] = 0;
|
||||
}
|
||||
|
||||
$tax_category_id = $this->request->getPost('tax_category_id');
|
||||
$taxCategoryId = $this->request->getPost('tax_category_id');
|
||||
|
||||
if (!isset($tax_category_id)) {
|
||||
$item_data['tax_category_id'] = null;
|
||||
if (!isset($taxCategoryId)) {
|
||||
$itemData['tax_category_id'] = null;
|
||||
} else {
|
||||
$item_data['tax_category_id'] = empty($this->request->getPost('tax_category_id')) ? null : intval($this->request->getPost('tax_category_id'));
|
||||
$itemData['tax_category_id'] = empty($this->request->getPost('tax_category_id')) ? null : intval($this->request->getPost('tax_category_id'));
|
||||
}
|
||||
|
||||
if (!empty($upload_data['orig_name']) && $upload_data['raw_name']) {
|
||||
$item_data['pic_filename'] = $upload_data['raw_name'] . '.' . $upload_data['file_ext'];
|
||||
if (!empty($uploadData['orig_name']) && $uploadData['raw_name']) {
|
||||
$itemData['pic_filename'] = $uploadData['raw_name'] . '.' . $uploadData['file_ext'];
|
||||
}
|
||||
|
||||
$employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
||||
$employeeId = $this->employee->get_logged_in_employee_info()->person_id;
|
||||
|
||||
if ($this->item->save_value($item_data, $item_id)) {
|
||||
if ($this->item->save_value($itemData, $itemId)) {
|
||||
$success = true;
|
||||
$new_item = false;
|
||||
$newItem = false;
|
||||
|
||||
if ($item_id === NEW_ENTRY) {
|
||||
$item_id = $item_data['item_id'];
|
||||
$new_item = true;
|
||||
if ($itemId === NEW_ENTRY) {
|
||||
$itemId = $itemData['item_id'];
|
||||
$newItem = true;
|
||||
}
|
||||
|
||||
$use_destination_based_tax = (bool)$this->config['use_destination_based_tax'];
|
||||
$useDestinationBasedTax = (bool)$this->config['use_destination_based_tax'];
|
||||
|
||||
if (!$use_destination_based_tax) {
|
||||
$items_taxes_data = [];
|
||||
$tax_names = $this->request->getPost('tax_names');
|
||||
$tax_percents = $this->request->getPost('tax_percents');
|
||||
if (!$useDestinationBasedTax) {
|
||||
$itemsTaxesData = [];
|
||||
$taxNames = $this->request->getPost('tax_names');
|
||||
$taxPercents = $this->request->getPost('tax_percents');
|
||||
|
||||
$tax_name_index = 0;
|
||||
$taxNameIndex = 0;
|
||||
|
||||
foreach ($tax_percents as $tax_percent) {
|
||||
$tax_percentage = parse_tax($tax_percent);
|
||||
foreach ($taxPercents as $taxPercent) {
|
||||
$taxpercentage = parse_tax($taxPercent);
|
||||
|
||||
if (is_numeric($tax_percentage)) {
|
||||
$items_taxes_data[] = ['name' => $tax_names[$tax_name_index], 'percent' => $tax_percentage];
|
||||
if (is_numeric($taxpercentage)) {
|
||||
$itemsTaxesData[] = ['name' => $taxNames[$taxNameIndex], 'percent' => $taxpercentage];
|
||||
}
|
||||
|
||||
$tax_name_index++;
|
||||
$taxNameIndex++;
|
||||
}
|
||||
$success &= $this->item_taxes->save_value($items_taxes_data, $item_id);
|
||||
$success &= $this->item_taxes->save_value($itemsTaxesData, $itemId);
|
||||
}
|
||||
|
||||
// Save item quantity
|
||||
$stock_locations = $this->stock_location->get_undeleted_all()->getResultArray();
|
||||
foreach ($stock_locations as $location) {
|
||||
$updated_quantity = parse_quantity($this->request->getPost('quantity_' . $location['location_id']));
|
||||
$stockLocations = $this->stock_location->get_undeleted_all()->getResultArray();
|
||||
foreach ($stockLocations as $location) {
|
||||
$updatedQuantity = parse_quantity($this->request->getPost('quantity_' . $location['location_id']));
|
||||
|
||||
if ($item_data['item_type'] == ITEM_TEMP) {
|
||||
$updated_quantity = 0;
|
||||
if ($itemData['item_type'] == ITEM_TEMP) {
|
||||
$updatedQuantity = 0;
|
||||
}
|
||||
|
||||
$location_detail = [
|
||||
'item_id' => $item_id,
|
||||
$locationDetail = [
|
||||
'item_id' => $itemId,
|
||||
'location_id' => $location['location_id'],
|
||||
'quantity' => $updated_quantity
|
||||
'quantity' => $updatedQuantity
|
||||
];
|
||||
|
||||
$item_quantity = $this->item_quantity->get_item_quantity($item_id, $location['location_id']);
|
||||
$itemQuantity = $this->item_quantity->get_item_quantity($itemId, $location['location_id']);
|
||||
|
||||
if ($item_quantity->quantity != $updated_quantity || $new_item) {
|
||||
$success = $success && $this->item_quantity->save_value($location_detail, $item_id, $location['location_id']);
|
||||
if ($itemQuantity->quantity != $updatedQuantity || $newItem) {
|
||||
$success = $success && $this->item_quantity->save_value($locationDetail, $itemId, $location['location_id']);
|
||||
|
||||
$inv_data = [
|
||||
'trans_date' => date('Y-m-d H:i:s'),
|
||||
'trans_items' => $item_id,
|
||||
'trans_user' => $employee_id,
|
||||
'trans_items' => $itemId,
|
||||
'trans_user' => $employeeId,
|
||||
'trans_location' => $location['location_id'],
|
||||
'trans_comment' => lang('Items.manually_editing_of_quantity'),
|
||||
'trans_inventory' => $updated_quantity - $item_quantity->quantity
|
||||
'trans_inventory' => $updatedQuantity - $itemQuantity->quantity
|
||||
];
|
||||
|
||||
$success = $success && $this->inventory->insert($inv_data, false);
|
||||
}
|
||||
}
|
||||
$success = $success && $this->saveItemAttributes($item_id);
|
||||
$success = $success && $this->saveItemAttributes($itemId);
|
||||
|
||||
if ($success && $upload_success) {
|
||||
$message = lang('Items.successful_' . ($new_item ? 'adding' : 'updating')) . ' ' . $item_data['name'];
|
||||
if ($success && $uploadSuccess) {
|
||||
Events::trigger('item_saved', [$itemId]);
|
||||
|
||||
return $this->response->setJSON(['success' => true, 'message' => $message, 'id' => $item_id]);
|
||||
$message = lang('Items.successful_' . ($newItem ? 'adding' : 'updating')) . ' ' . $itemData['name'];
|
||||
return $this->response->setJSON(['success' => true, 'message' => $message, 'id' => $itemId]);
|
||||
} else {
|
||||
$message = $upload_success ? lang('Items.error_adding_updating') . ' ' . $item_data['name'] : strip_tags($upload_data['error']);
|
||||
$message = $uploadSuccess ? lang('Items.error_adding_updating') . ' ' . $itemData['name'] : strip_tags($uploadData['error']);
|
||||
|
||||
return $this->response->setJSON(['success' => false, 'message' => $message, 'id' => $item_id]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => $message, 'id' => $itemId]);
|
||||
}
|
||||
} else {
|
||||
$message = lang('Items.error_adding_updating') . ' ' . $item_data['name'];
|
||||
$message = lang('Items.error_adding_updating') . ' ' . $itemData['name'];
|
||||
|
||||
return $this->response->setJSON(['success' => false, 'message' => $message, 'id' => NEW_ENTRY]);
|
||||
}
|
||||
@@ -957,7 +974,7 @@ class Items extends Secure_Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports items from a CSV formatted file.
|
||||
* Imports items from a CSV-formatted file.
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
@@ -982,7 +999,7 @@ class Items extends Secure_Controller
|
||||
$attributeData = [];
|
||||
|
||||
foreach ($attributeDefinitionNames as $definitionName) {
|
||||
$attributeData[$definitionName] = $this->attribute->get_definition_by_name($definitionName)[0];
|
||||
$attributeData[$definitionName] = $this->attribute->getDefinitionByName($definitionName)[0];
|
||||
|
||||
if ($attributeData[$definitionName]['definition_type'] === DROPDOWN) {
|
||||
$attributeData[$definitionName]['dropdown_values'] = $this->attribute->get_definition_values($attributeData[$definitionName]['definition_id']);
|
||||
@@ -991,6 +1008,7 @@ class Items extends Secure_Controller
|
||||
$db = db_connect();
|
||||
$db->transBegin(); // TODO: This section needs to be reworked so that the data array is being created then passed to the Item model because $db doesn't exist in the controller without being instantiated, but database operations should be restricted to the model
|
||||
|
||||
$itemIds = [];
|
||||
foreach ($csvRows as $key => $row) {
|
||||
$isFailedRow = false;
|
||||
$itemId = (int)$row['Id'];
|
||||
@@ -1040,20 +1058,28 @@ class Items extends Secure_Controller
|
||||
});
|
||||
|
||||
if (!$isFailedRow && $this->item->save_value($itemData, $itemId)) {
|
||||
$this->save_tax_data($row, $itemData);
|
||||
$this->save_inventory_quantities($row, $itemData, $allowedStockLocations, $employeeId);
|
||||
if (!$this->save_tax_data($row, $itemData)) {
|
||||
$isFailedRow = true;
|
||||
}
|
||||
if (!$this->save_inventory_quantities($row, $itemData, $allowedStockLocations, $employeeId)) {
|
||||
$isFailedRow = true;
|
||||
}
|
||||
$csvAttributeValues = $this->extractAttributeData($row);
|
||||
$isFailedRow = !$this->attribute->saveCSVRowAttributeData($csvAttributeValues, $itemData, $attributeData);
|
||||
if (!$this->attribute->saveCSVRowAttributeData($csvAttributeValues, $itemData, $attributeData)) {
|
||||
$isFailedRow = true;
|
||||
}
|
||||
if ($isFailedRow) {
|
||||
$failedRow = $key + 2;
|
||||
$failCodes[] = $failedRow;
|
||||
log_message('error', "CSV Item import failed on line $failedRow while saving attributes.");
|
||||
log_message('error', "CSV Item import failed on line $failedRow while saving item.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($isUpdate) {
|
||||
$itemData = array_merge($itemData, get_object_vars($this->item->get_info_by_id_or_number($itemId)));
|
||||
}
|
||||
|
||||
$itemIds[] = $itemData['item_id'];
|
||||
} else {
|
||||
$failedRow = $key + 2;
|
||||
$failCodes[] = $failedRow;
|
||||
@@ -1073,6 +1099,8 @@ class Items extends Secure_Controller
|
||||
$db->transCommit();
|
||||
$this->attribute->deleteOrphanedValues();
|
||||
|
||||
Events::trigger('item_saved', [$itemIds]);
|
||||
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Items.csv_import_success')]);
|
||||
}
|
||||
} else {
|
||||
@@ -1237,13 +1265,15 @@ class Items extends Secure_Controller
|
||||
* @param array $item_data
|
||||
* @param array $allowed_locations
|
||||
* @param int $employee_id
|
||||
* @return bool Returns true on success, false on failure
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
private function save_inventory_quantities(array $row, array $item_data, array $allowed_locations, int $employee_id): void
|
||||
private function save_inventory_quantities(array $row, array $item_data, array $allowed_locations, int $employee_id): bool
|
||||
{
|
||||
// Quantities & Inventory Section
|
||||
$comment = lang('Items.inventory_CSV_import_quantity');
|
||||
$is_update = (bool)$row['Id'];
|
||||
$success = true;
|
||||
|
||||
foreach ($allowed_locations as $location_id => $location_name) {
|
||||
$item_quantity_data = ['item_id' => $item_data['item_id'], 'location_id' => $location_id];
|
||||
@@ -1257,20 +1287,22 @@ class Items extends Secure_Controller
|
||||
|
||||
if (!empty($row["location_$location_name"]) || $row["location_$location_name"] === '0') {
|
||||
$item_quantity_data['quantity'] = $row["location_$location_name"];
|
||||
$this->item_quantity->save_value($item_quantity_data, $item_data['item_id'], $location_id);
|
||||
$success &= $this->item_quantity->save_value($item_quantity_data, $item_data['item_id'], $location_id);
|
||||
|
||||
$csv_data['trans_inventory'] = $row["location_$location_name"];
|
||||
$this->inventory->insert($csv_data, false);
|
||||
$success &= (bool)$this->inventory->insert($csv_data, false);
|
||||
} elseif ($is_update) {
|
||||
return;
|
||||
continue;
|
||||
} else {
|
||||
$item_quantity_data['quantity'] = 0;
|
||||
$this->item_quantity->save_value($item_quantity_data, $item_data['item_id'], $location_id);
|
||||
$success &= $this->item_quantity->save_value($item_quantity_data, $item_data['item_id'], $location_id);
|
||||
|
||||
$csv_data['trans_inventory'] = 0;
|
||||
$this->inventory->insert($csv_data, false);
|
||||
$success &= (bool)$this->inventory->insert($csv_data, false);
|
||||
}
|
||||
}
|
||||
|
||||
return (bool)$success;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1278,8 +1310,9 @@ class Items extends Secure_Controller
|
||||
*
|
||||
* @param array $row
|
||||
* @param array $item_data
|
||||
* @return bool Returns true on success, false on failure
|
||||
*/
|
||||
private function save_tax_data(array $row, array $item_data): void
|
||||
private function save_tax_data(array $row, array $item_data): bool
|
||||
{
|
||||
$items_taxes_data = [];
|
||||
|
||||
@@ -1291,9 +1324,11 @@ class Items extends Secure_Controller
|
||||
$items_taxes_data[] = ['name' => $row['Tax 2 Name'], 'percent' => $row['Tax 2 Percent']];
|
||||
}
|
||||
|
||||
if (isset($items_taxes_data)) {
|
||||
$this->item_taxes->save_value($items_taxes_data, $item_data['item_id']);
|
||||
if (!empty($items_taxes_data)) {
|
||||
return $this->item_taxes->save_value($items_taxes_data, $item_data['item_id']);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,6 +49,13 @@ class Login extends BaseController
|
||||
return view('login', $data);
|
||||
}
|
||||
|
||||
if (!$data['is_latest'] || $data['is_new_install']) {
|
||||
set_time_limit(3600);
|
||||
|
||||
$migration->setNamespace('App')->latest();
|
||||
return redirect()->to('login');
|
||||
}
|
||||
|
||||
$rules = ['username' => 'required|login_check[data]'];
|
||||
$messages = [
|
||||
'username' => [
|
||||
@@ -62,13 +69,6 @@ class Login extends BaseController
|
||||
|
||||
return view('login', $data);
|
||||
}
|
||||
|
||||
if (!$data['is_latest']) {
|
||||
set_time_limit(3600);
|
||||
|
||||
$migration->setNamespace('App')->latest();
|
||||
return redirect()->to('login');
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->to('home');
|
||||
@@ -79,18 +79,18 @@ class Login extends BaseController
|
||||
try {
|
||||
$migration = new MY_Migration(config('Migrations'));
|
||||
$migration->migrate_to_ci4();
|
||||
|
||||
|
||||
set_time_limit(3600);
|
||||
$migration->setNamespace('App')->latest();
|
||||
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => 'Migration completed successfully'
|
||||
]);
|
||||
|
||||
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Migration failed: ' . $e->getMessage());
|
||||
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => 'Migration failed: ' . $e->getMessage()
|
||||
|
||||
@@ -33,7 +33,7 @@ class Messages extends Secure_Controller
|
||||
public function getView(int $person_id = NEW_ENTRY): string
|
||||
{
|
||||
$person = model(Person::class);
|
||||
$info = $person->get_info($person_id);
|
||||
$info = $person->getInfo($person_id);
|
||||
|
||||
foreach (get_object_vars($info) as $property => $value) {
|
||||
$info->$property = $value;
|
||||
|
||||
@@ -49,7 +49,7 @@ abstract class Persons extends Secure_Controller
|
||||
*/
|
||||
public function getRow(int $row_id): ResponseInterface
|
||||
{
|
||||
$data_row = get_person_data_row($this->person->get_info($row_id));
|
||||
$data_row = get_person_data_row($this->person->getInfo($row_id));
|
||||
|
||||
return $this->response->setJSON($data_row);
|
||||
}
|
||||
|
||||
169
app/Controllers/Plugins.php
Normal file
169
app/Controllers/Plugins.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Libraries\Plugins\PluginManager;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
class Plugins extends Secure_Controller
|
||||
{
|
||||
private PluginManager $pluginManager;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('plugins');
|
||||
$this->pluginManager = service('pluginManager');
|
||||
}
|
||||
|
||||
public function getIndex(): string
|
||||
{
|
||||
$data['table_headers'] = get_plugin_manage_table_headers();
|
||||
return view('plugins/manage', $data);
|
||||
}
|
||||
|
||||
public function getSearch(): ResponseInterface
|
||||
{
|
||||
$search = strtolower($this->request->getGet('search') ?? '');
|
||||
$limit = (int)($this->request->getGet('limit') ?? 0);
|
||||
$offset = (int)($this->request->getGet('offset') ?? 0);
|
||||
$sort = $this->sanitizeSortColumn(plugin_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'name');
|
||||
$order = strtolower($this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? 'asc');
|
||||
|
||||
$pluginData = $this->buildPluginDataArray();
|
||||
|
||||
if ($search !== '') {
|
||||
$pluginData = array_values(array_filter($pluginData, static function (array $p) use ($search): bool {
|
||||
return str_contains(strtolower($p['name']), $search)
|
||||
|| str_contains(strtolower($p['description']), $search)
|
||||
|| str_contains(strtolower($p['id']), $search);
|
||||
}));
|
||||
}
|
||||
|
||||
$total = count($pluginData);
|
||||
|
||||
usort($pluginData, static function (array $a, array $b) use ($sort, $order): int {
|
||||
$valA = strtolower($a[$sort] ?? $a['name']);
|
||||
$valB = strtolower($b[$sort] ?? $b['name']);
|
||||
return $order === 'asc' ? strcmp($valA, $valB) : strcmp($valB, $valA);
|
||||
});
|
||||
|
||||
$pluginData = $limit > 0 ? array_slice($pluginData, $offset, $limit) : array_slice($pluginData, $offset);
|
||||
|
||||
return $this->response->setJSON(['total' => $total, 'rows' => array_map('get_plugin_data_row', $pluginData)]);
|
||||
}
|
||||
|
||||
public function getRow(string $pluginId): ResponseInterface
|
||||
{
|
||||
$plugin = $this->pluginManager->getPlugin($pluginId);
|
||||
if (!$plugin) {
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Plugins.not_found')]);
|
||||
}
|
||||
|
||||
$enabled = $this->pluginManager->getEnabledPlugins();
|
||||
$pluginData = [
|
||||
'id' => $plugin->getPluginId(),
|
||||
'name' => $plugin->getPluginName(),
|
||||
'description' => $plugin->getPluginDescription(),
|
||||
'version' => $plugin->getVersion(),
|
||||
'enabled' => isset($enabled[$pluginId]),
|
||||
'has_config' => $plugin->getConfigView() !== null,
|
||||
];
|
||||
|
||||
return $this->response->setJSON(get_plugin_data_row($pluginData));
|
||||
}
|
||||
|
||||
private function buildPluginDataArray(): array
|
||||
{
|
||||
$plugins = $this->pluginManager->getAllPlugins();
|
||||
$enabled = $this->pluginManager->getEnabledPlugins();
|
||||
$result = [];
|
||||
|
||||
foreach ($plugins as $pluginId => $plugin) {
|
||||
$result[] = [
|
||||
'id' => $plugin->getPluginId(),
|
||||
'name' => $plugin->getPluginName(),
|
||||
'description' => $plugin->getPluginDescription(),
|
||||
'version' => $plugin->getVersion(),
|
||||
'enabled' => isset($enabled[$pluginId]),
|
||||
'has_config' => $plugin->getConfigView() !== null,
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function postEnable(string $pluginId): ResponseInterface
|
||||
{
|
||||
if ($this->pluginManager->enablePlugin($pluginId)) {
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Plugins.enabled')]);
|
||||
}
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Plugins.enable_failed')]);
|
||||
}
|
||||
|
||||
public function postDisable(string $pluginId): ResponseInterface
|
||||
{
|
||||
if ($this->pluginManager->disablePlugin($pluginId)) {
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Plugins.disabled')]);
|
||||
}
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Plugins.disable_failed')]);
|
||||
}
|
||||
|
||||
public function postUninstall(string $pluginId): ResponseInterface
|
||||
{
|
||||
if ($this->pluginManager->uninstallPlugin($pluginId)) {
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Plugins.uninstalled')]);
|
||||
}
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Plugins.uninstall_failed')]);
|
||||
}
|
||||
|
||||
public function getConfig(string $pluginId): ResponseInterface
|
||||
{
|
||||
$plugin = $this->pluginManager->getPlugin($pluginId);
|
||||
|
||||
if (!$plugin) {
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Plugins.not_found')]);
|
||||
}
|
||||
|
||||
$configView = $plugin->getConfigView();
|
||||
if (!$configView) {
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Plugins.no_config')]);
|
||||
}
|
||||
|
||||
$settings = $plugin->getSettings();
|
||||
$data = array_merge(['settings' => $settings, 'plugin' => $plugin], $plugin->getConfigViewData());
|
||||
|
||||
// Plugin views may live outside app/Views/ (absolute path from plugin's __DIR__)
|
||||
if (is_file($configView . '.php')) {
|
||||
$renderer = \Config\Services::renderer(dirname($configView) . DIRECTORY_SEPARATOR, null, false);
|
||||
echo $renderer->setData($data)->render(basename($configView));
|
||||
} else {
|
||||
echo view($configView, $data);
|
||||
}
|
||||
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save plugin settings by calling the plugin's saveSettings method.
|
||||
*
|
||||
* @param string $pluginId The plugin ID for the current plugin
|
||||
* @return ResponseInterface The JSON response
|
||||
* @noinspection PhpUnused Called via AJAX
|
||||
*/
|
||||
public function postSaveConfig(string $pluginId): ResponseInterface
|
||||
{
|
||||
$plugin = $this->pluginManager->getPlugin($pluginId);
|
||||
|
||||
if (!$plugin) {
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Plugins.not_found')]);
|
||||
}
|
||||
|
||||
$settings = $this->request->getPost();
|
||||
unset($settings['_method'], $settings[csrf_token()]);
|
||||
|
||||
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')]);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use App\Models\Item_kit;
|
||||
use App\Models\Receiving;
|
||||
use App\Models\Stock_location;
|
||||
use App\Models\Supplier;
|
||||
use CodeIgniter\Events\Events;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\OSPOS;
|
||||
use Config\Services;
|
||||
@@ -253,7 +254,7 @@ class Receivings extends Secure_Controller
|
||||
}
|
||||
} else {
|
||||
$stored_employee_id = $receiving_info['employee_id'];
|
||||
$stored_employee = $this->employee->get_info($stored_employee_id);
|
||||
$stored_employee = $this->employee->getInfo($stored_employee_id);
|
||||
$data['employees'][$stored_employee_id] = $stored_employee->first_name . ' ' . $stored_employee->last_name;
|
||||
}
|
||||
|
||||
@@ -342,12 +343,12 @@ class Receivings extends Secure_Controller
|
||||
}
|
||||
|
||||
$employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
||||
$employee_info = $this->employee->get_info($employee_id);
|
||||
$employee_info = $this->employee->getInfo($employee_id);
|
||||
$data['employee'] = $employee_info->first_name . ' ' . $employee_info->last_name;
|
||||
|
||||
$supplier_id = $this->receiving_lib->get_supplier();
|
||||
if ($supplier_id != -1) {
|
||||
$supplier_info = $this->supplier->get_info($supplier_id);
|
||||
$supplier_info = $this->supplier->getInfo($supplier_id);
|
||||
$data['supplier'] = $supplier_info->company_name; // TODO: duplicated code
|
||||
$data['first_name'] = $supplier_info->first_name;
|
||||
$data['last_name'] = $supplier_info->last_name;
|
||||
@@ -367,6 +368,7 @@ class Receivings extends Secure_Controller
|
||||
$data['error_message'] = lang('Receivings.transaction_failed');
|
||||
} else {
|
||||
$data['barcode'] = $this->barcode_lib->generate_receipt_barcode($data['receiving_id']);
|
||||
Events::trigger('receiving_complete', (int) substr($data['receiving_id'], 5), $data['mode']);
|
||||
}
|
||||
|
||||
$data['print_after_sale'] = $this->receiving_lib->is_print_after_sale();
|
||||
@@ -422,12 +424,12 @@ class Receivings extends Secure_Controller
|
||||
$data['reference'] = $this->receiving_lib->get_reference();
|
||||
$data['receiving_id'] = 'RECV ' . $receiving_id;
|
||||
$data['barcode'] = $this->barcode_lib->generate_receipt_barcode($data['receiving_id']);
|
||||
$employee_info = $this->employee->get_info($receiving_info['employee_id']);
|
||||
$employee_info = $this->employee->getInfo($receiving_info['employee_id']);
|
||||
$data['employee'] = $employee_info->first_name . ' ' . $employee_info->last_name;
|
||||
|
||||
$supplier_id = $this->receiving_lib->get_supplier(); // TODO: Duplicated code
|
||||
if ($supplier_id != -1) {
|
||||
$supplier_info = $this->supplier->get_info($supplier_id);
|
||||
$supplier_info = $this->supplier->getInfo($supplier_id);
|
||||
$data['supplier'] = $supplier_info->company_name;
|
||||
$data['first_name'] = $supplier_info->first_name;
|
||||
$data['last_name'] = $supplier_info->last_name;
|
||||
@@ -475,7 +477,7 @@ class Receivings extends Secure_Controller
|
||||
$supplier_id = $this->receiving_lib->get_supplier();
|
||||
|
||||
if ($supplier_id != -1) { // TODO: Duplicated Code... replace -1 with a constant
|
||||
$supplier_info = $this->supplier->get_info($supplier_id);
|
||||
$supplier_info = $this->supplier->getInfo($supplier_id);
|
||||
$data['supplier'] = $supplier_info->company_name;
|
||||
$data['first_name'] = $supplier_info->first_name;
|
||||
$data['last_name'] = $supplier_info->last_name;
|
||||
|
||||
@@ -1246,13 +1246,15 @@ class Reports extends Secure_Controller
|
||||
public function get_payment_type(): array
|
||||
{
|
||||
return [
|
||||
'all' => lang('Common.none_selected_text'),
|
||||
'cash' => lang('Sales.cash'),
|
||||
'due' => lang('Sales.due'),
|
||||
'check' => lang('Sales.check'),
|
||||
'credit' => lang('Sales.credit'),
|
||||
'debit' => lang('Sales.debit'),
|
||||
'invoices' => lang('Sales.invoice')
|
||||
'all' => lang('Common.none_selected_text'),
|
||||
'cash' => lang('Sales.cash'),
|
||||
'due' => lang('Sales.due'),
|
||||
'check' => lang('Sales.check'),
|
||||
'credit' => lang('Sales.credit'),
|
||||
'debit' => lang('Sales.debit'),
|
||||
'bank_transfer' => lang('Sales.bank_transfer'),
|
||||
'wallet' => lang('Sales.wallet'),
|
||||
'invoices' => lang('Sales.invoice')
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1308,9 +1310,9 @@ class Reports extends Secure_Controller
|
||||
'comment' => $row['comment'],
|
||||
'edit' => anchor(
|
||||
'sales/edit/' . $row['sale_id'],
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch print_hide',
|
||||
'class' => 'modal-dlg print_hide',
|
||||
$button_key => $button_label,
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang('Sales.update')
|
||||
@@ -1341,7 +1343,7 @@ class Reports extends Secure_Controller
|
||||
}
|
||||
}
|
||||
|
||||
$customer_info = $this->customer->get_info($customer_id);
|
||||
$customer_info = $this->customer->getInfo($customer_id);
|
||||
$customer_name = !empty($customer_info->company_name) // TODO: This variable is not used anywhere in the code. Should it be or can it be deleted?
|
||||
? "[ $customer_info->company_name ]"
|
||||
: $customer_info->company_name;
|
||||
@@ -1435,9 +1437,9 @@ class Reports extends Secure_Controller
|
||||
'comment' => $row['comment'],
|
||||
'edit' => anchor(
|
||||
'sales/edit/' . $row['sale_id'],
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch print_hide',
|
||||
'class' => 'modal-dlg print_hide',
|
||||
$button_key => $button_label,
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang('Sales.update')
|
||||
@@ -1468,7 +1470,7 @@ class Reports extends Secure_Controller
|
||||
}
|
||||
}
|
||||
|
||||
$employee_info = $this->employee->get_info($employee_id);
|
||||
$employee_info = $this->employee->getInfo($employee_id);
|
||||
// TODO: Duplicated Code
|
||||
$data = [
|
||||
'title' => $employee_info->first_name . ' ' . $employee_info->last_name . ' ' . lang('Reports.report'),
|
||||
@@ -1567,9 +1569,9 @@ class Reports extends Secure_Controller
|
||||
'comment' => $row['comment'],
|
||||
'edit' => anchor(
|
||||
'sales/edit/' . $row['sale_id'],
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch print_hide',
|
||||
'class' => 'modal-dlg print_hide',
|
||||
$button_key => $button_label,
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang('Sales.update')
|
||||
@@ -1655,9 +1657,9 @@ class Reports extends Secure_Controller
|
||||
'comment' => $report_data['comment'],
|
||||
'edit' => anchor(
|
||||
'sales/edit/' . $report_data['sale_id'],
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch print_hide',
|
||||
'class' => 'modal-dlg print_hide',
|
||||
$button_key => $button_label,
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang('Sales.update')
|
||||
@@ -1734,7 +1736,7 @@ class Reports extends Secure_Controller
|
||||
];
|
||||
}
|
||||
|
||||
$supplier_info = $this->supplier->get_info((int) $supplier_id);
|
||||
$supplier_info = $this->supplier->getInfo((int) $supplier_id);
|
||||
$data = [
|
||||
'title' => $supplier_info->company_name . ' (' . $supplier_info->first_name . ' ' . $supplier_info->last_name . ') ' . lang('Reports.report'),
|
||||
'subtitle' => $this->_get_subtitle_report(['start_date' => $start_date, 'end_date' => $end_date]),
|
||||
@@ -1831,9 +1833,9 @@ class Reports extends Secure_Controller
|
||||
'comment' => $row['comment'],
|
||||
'edit' => anchor(
|
||||
'sales/edit/' . $row['sale_id'],
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch print_hide',
|
||||
'class' => 'modal-dlg print_hide',
|
||||
$button_key => $button_label,
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang('Sales.update')
|
||||
@@ -1911,9 +1913,9 @@ class Reports extends Secure_Controller
|
||||
'comment' => $report_data['comment'],
|
||||
'edit' => anchor(
|
||||
'receivings/edit/' . $report_data['receiving_id'],
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch print_hide',
|
||||
'class' => 'modal-dlg print_hide',
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'data-btn-delete' => lang('Common.delete'),
|
||||
'title' => lang('Receivings.update')
|
||||
@@ -1971,9 +1973,9 @@ class Reports extends Secure_Controller
|
||||
'comment' => $row['comment'],
|
||||
'edit' => anchor(
|
||||
'receivings/edit/' . $row['receiving_id'],
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch print_hide',
|
||||
'class' => 'modal-dlg print_hide',
|
||||
'data-btn-delete' => lang('Common.delete'),
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang('Receivings.update')
|
||||
|
||||
@@ -20,6 +20,7 @@ use App\Models\Stock_location;
|
||||
use App\Models\Tokens\Token_invoice_count;
|
||||
use App\Models\Tokens\Token_customer;
|
||||
use App\Models\Tokens\Token_invoice_sequence;
|
||||
use CodeIgniter\Events\Events;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\Services;
|
||||
use Config\OSPOS;
|
||||
@@ -93,6 +94,8 @@ class Sales extends Secure_Controller
|
||||
'only_check' => lang('Sales.check_filter'),
|
||||
'only_creditcard' => lang('Sales.credit_filter'),
|
||||
'only_debit' => lang('Sales.debit'),
|
||||
'only_bank_transfer'=> lang('Sales.bank_transfer'),
|
||||
'only_wallet' => lang('Sales.wallet'),
|
||||
'only_invoices' => lang('Sales.invoice_filter'),
|
||||
'selected_customer' => lang('Sales.selected_customer')
|
||||
];
|
||||
@@ -156,8 +159,10 @@ class Sales extends Secure_Controller
|
||||
'selected_customer' => false,
|
||||
'only_creditcard' => false,
|
||||
'only_debit' => false,
|
||||
'only_bank_transfer'=> false,
|
||||
'only_wallet' => false,
|
||||
'only_invoices' => $this->config['invoice_enable'] && $this->request->getGet('only_invoices', FILTER_SANITIZE_NUMBER_INT),
|
||||
'is_valid_receipt' => $this->sale->is_valid_receipt($search)
|
||||
'is_valid_receipt' => $this->sale->isValidReceipt($search)
|
||||
];
|
||||
|
||||
// Check if any filter is set in the multiselect dropdown
|
||||
@@ -194,7 +199,7 @@ class Sales extends Secure_Controller
|
||||
? $this->request->getGet('term')
|
||||
: null;
|
||||
|
||||
if ($this->sale_lib->get_mode() == 'return' && $this->sale->is_valid_receipt($receipt)) {
|
||||
if ($this->sale_lib->get_mode() == 'return' && $this->sale->isValidReceipt($receipt)) {
|
||||
// If a valid receipt or invoice was found the search term will be replaced with a receipt number (POS #)
|
||||
$suggestions[] = $receipt;
|
||||
}
|
||||
@@ -229,8 +234,8 @@ class Sales extends Secure_Controller
|
||||
$customer_id = (int)$this->request->getPost('customer', FILTER_SANITIZE_NUMBER_INT);
|
||||
if ($this->customer->exists($customer_id)) {
|
||||
$this->sale_lib->set_customer($customer_id);
|
||||
$discount = $this->customer->get_info($customer_id)->discount;
|
||||
$discount_type = $this->customer->get_info($customer_id)->discount_type;
|
||||
$discount = $this->customer->getInfo($customer_id)->discount;
|
||||
$discount_type = $this->customer->getInfo($customer_id)->discount_type;
|
||||
|
||||
// Apply customer default discount to items that have 0 discount
|
||||
if ($discount != '') {
|
||||
@@ -433,9 +438,9 @@ class Sales extends Secure_Controller
|
||||
}
|
||||
} elseif ($payment_type === lang('Sales.rewards')) {
|
||||
$customer_id = $this->sale_lib->get_customer();
|
||||
$package_id = $this->customer->get_info($customer_id)->package_id;
|
||||
$package_id = $this->customer->getInfo($customer_id)->package_id;
|
||||
if (!empty($package_id)) {
|
||||
$points = $this->customer->get_info($customer_id)->points;
|
||||
$points = $this->customer->getInfo($customer_id)->points;
|
||||
$points = ($points == null ? 0 : $points);
|
||||
|
||||
$payments = $this->sale_lib->get_payments();
|
||||
@@ -507,8 +512,8 @@ class Sales extends Secure_Controller
|
||||
$customer_id = $this->sale_lib->get_customer();
|
||||
if ($customer_id != NEW_ENTRY) {
|
||||
// Load the customer discount if any
|
||||
$customer_discount = $this->customer->get_info($customer_id)->discount;
|
||||
$customer_discount_type = $this->customer->get_info($customer_id)->discount_type;
|
||||
$customer_discount = $this->customer->getInfo($customer_id)->discount;
|
||||
$customer_discount_type = $this->customer->getInfo($customer_id)->discount_type;
|
||||
if ($customer_discount != '') {
|
||||
$discount = $customer_discount;
|
||||
$discount_type = $customer_discount_type;
|
||||
@@ -521,7 +526,7 @@ class Sales extends Secure_Controller
|
||||
$quantity = ($mode == 'return') ? -$quantity : $quantity;
|
||||
$item_location = $this->sale_lib->get_sale_location();
|
||||
|
||||
if ($mode == 'return' && $this->sale->is_valid_receipt($item_id_or_number_or_item_kit_or_receipt)) {
|
||||
if ($mode == 'return' && $this->sale->isValidReceipt($item_id_or_number_or_item_kit_or_receipt)) {
|
||||
$this->sale_lib->return_entire_sale($item_id_or_number_or_item_kit_or_receipt);
|
||||
} elseif ($this->item_kit->is_valid_item_kit($item_id_or_number_or_item_kit_or_receipt)) {
|
||||
// Add kit item to order if one is assigned
|
||||
@@ -699,7 +704,7 @@ class Sales extends Secure_Controller
|
||||
$data['show_stock_locations'] = $this->stock_location->show_locations('sales');
|
||||
$data['comments'] = $this->sale_lib->get_comment();
|
||||
$employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
||||
$employee_info = $this->employee->get_info($employee_id);
|
||||
$employee_info = $this->employee->getInfo($employee_id);
|
||||
$data['employee'] = $employee_info->first_name . ' ' . mb_substr($employee_info->last_name, 0, 1);
|
||||
|
||||
$data['company_info'] = implode("\n", [$this->config['address'], $this->config['phone']]);
|
||||
@@ -904,6 +909,16 @@ class Sales extends Secure_Controller
|
||||
return $this->_reload($data);
|
||||
} else {
|
||||
$data['barcode'] = $this->barcode_lib->generate_receipt_barcode($data['sale_id']);
|
||||
|
||||
// Validate receipt template to prevent path traversal
|
||||
$receipt_template = $this->config['receipt_template'] ?? '';
|
||||
if (!Sale_lib::isValidReceiptTemplate($receipt_template)) {
|
||||
$receipt_template = 'receipt_default';
|
||||
}
|
||||
$data['receipt_template_view'] = $receipt_template;
|
||||
|
||||
Events::trigger('sale_complete', $data['sale_id_num'], $sale_type);
|
||||
|
||||
$this->sale_lib->clear_all();
|
||||
return view('sales/receipt', $data);
|
||||
}
|
||||
@@ -1006,7 +1021,7 @@ class Sales extends Secure_Controller
|
||||
$customer_info = '';
|
||||
|
||||
if ($customer_id != NEW_ENTRY) {
|
||||
$customer_info = $this->customer->get_info($customer_id);
|
||||
$customer_info = $this->customer->getInfo($customer_id);
|
||||
$data['customer_id'] = $customer_id;
|
||||
|
||||
if (!empty($customer_info->company_name)) {
|
||||
@@ -1029,11 +1044,11 @@ class Sales extends Secure_Controller
|
||||
$data['customer_account_number'] = $customer_info->account_number;
|
||||
$data['customer_discount'] = $customer_info->discount;
|
||||
$data['customer_discount_type'] = $customer_info->discount_type;
|
||||
$package_id = $this->customer->get_info($customer_id)->package_id;
|
||||
$package_id = $this->customer->getInfo($customer_id)->package_id;
|
||||
|
||||
if ($package_id != null) {
|
||||
$package_name = $this->customer_rewards->get_name($package_id);
|
||||
$points = $this->customer->get_info($customer_id)->points;
|
||||
$points = $this->customer->getInfo($customer_id)->points;
|
||||
$data['customer_rewards']['package_id'] = $package_id;
|
||||
$data['customer_rewards']['points'] = empty($points) ? 0 : $points;
|
||||
$data['customer_rewards']['package_name'] = $package_name;
|
||||
@@ -1112,7 +1127,7 @@ class Sales extends Secure_Controller
|
||||
|
||||
$data['amount_change'] = $data['amount_due'] * -1;
|
||||
|
||||
$employee_info = $this->employee->get_info($this->sale_lib->get_employee());
|
||||
$employee_info = $this->employee->getInfo($this->sale_lib->get_employee());
|
||||
$data['employee'] = $employee_info->first_name . ' ' . mb_substr($employee_info->last_name, 0, 1);
|
||||
$this->_load_customer_data($this->sale_lib->get_customer(), $data);
|
||||
|
||||
@@ -1159,6 +1174,13 @@ class Sales extends Secure_Controller
|
||||
}
|
||||
$data['invoice_view'] = $invoice_type;
|
||||
|
||||
// Validate receipt template to prevent path traversal
|
||||
$receipt_template = $this->config['receipt_template'] ?? '';
|
||||
if (!Sale_lib::isValidReceiptTemplate($receipt_template)) {
|
||||
$receipt_template = 'receipt_default';
|
||||
}
|
||||
$data['receipt_template_view'] = $receipt_template;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -1320,7 +1342,7 @@ class Sales extends Secure_Controller
|
||||
$sale_info = $this->sale->get_info($sale_id)->getRowArray();
|
||||
$data['selected_customer_id'] = $sale_info['customer_id'];
|
||||
$data['selected_customer_name'] = $sale_info['customer_name'];
|
||||
$employee_info = $this->employee->get_info($sale_info['employee_id']);
|
||||
$employee_info = $this->employee->getInfo($sale_info['employee_id']);
|
||||
$data['selected_employee_id'] = $sale_info['employee_id'];
|
||||
$data['selected_employee_name'] = $employee_info->first_name . ' ' . $employee_info->last_name;
|
||||
$data['sale_info'] = $sale_info;
|
||||
|
||||
@@ -99,10 +99,10 @@ class Secure_Controller extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
* @param string $key
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function getConfig($key)
|
||||
public function getConfig(string $key)
|
||||
{
|
||||
if (isset($config[$key])) {
|
||||
return $config[$key];
|
||||
|
||||
@@ -34,7 +34,7 @@ class Suppliers extends Persons
|
||||
*/
|
||||
public function getRow($row_id): ResponseInterface
|
||||
{
|
||||
$data_row = get_supplier_data_row($this->supplier->get_info($row_id));
|
||||
$data_row = get_supplier_data_row($this->supplier->getInfo($row_id));
|
||||
$data_row['category'] = $this->supplier->get_category_name($data_row['category']);
|
||||
|
||||
return $this->response->setJSON($data_row);
|
||||
@@ -97,7 +97,7 @@ class Suppliers extends Persons
|
||||
*/
|
||||
public function getView(int $supplier_id = NEW_ENTRY): string
|
||||
{
|
||||
$info = $this->supplier->get_info($supplier_id);
|
||||
$info = $this->supplier->getInfo($supplier_id);
|
||||
foreach (get_object_vars($info) as $property => $value) {
|
||||
$info->$property = $value;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class Migration_Upgrade_To_3_1_1 extends Migration
|
||||
@@ -17,7 +18,37 @@ class Migration_Upgrade_To_3_1_1 extends Migration
|
||||
public function up(): void
|
||||
{
|
||||
helper('migration');
|
||||
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.0.2_to_3.1.1.sql');
|
||||
|
||||
// MariaDB blocks CONVERT TO CHARACTER SET on tables with FK constraints.
|
||||
// Drop all FKs across affected tables before running the SQL script, recreate after.
|
||||
$fkColumns = [
|
||||
['modules', 'module_id'],
|
||||
['stock_locations', 'location_id'],
|
||||
['permissions', 'permission_id'],
|
||||
['people', 'person_id'],
|
||||
['suppliers', 'supplier_id'],
|
||||
['items', 'item_id'],
|
||||
['item_kits', 'item_kit_id'],
|
||||
['sales', 'sale_id'],
|
||||
['receivings', 'receiving_id'],
|
||||
['employees', 'employee_id'],
|
||||
['customers', 'person_id'],
|
||||
];
|
||||
|
||||
$constraints = [];
|
||||
foreach ($fkColumns as [$table, $column]) {
|
||||
foreach (dropAllForeignKeyConstraints($table, $column) as $c) {
|
||||
$constraints[$c['constraintName']] = $c;
|
||||
}
|
||||
}
|
||||
|
||||
if (!execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.0.2_to_3.1.1.sql')) {
|
||||
throw new DatabaseException('Migration script 3.0.2_to_3.1.1.sql failed. Check logs for details.');
|
||||
}
|
||||
|
||||
$droppedTables = ['sales_suspended', 'sales_suspended_items', 'sales_suspended_items_taxes', 'sales_suspended_payments'];
|
||||
$toRecreate = array_filter($constraints, fn($c) => !in_array($c['tableName'], $droppedTables, true));
|
||||
recreateForeignKeyConstraints(array_values($toRecreate));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.5.0_PluginConfigTableCreate.sql');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('plugin_config', true);
|
||||
}
|
||||
}
|
||||
@@ -327,19 +327,6 @@ INSERT INTO `ospos_sales_items` (sale_id, item_id, description, serialnumber, li
|
||||
INSERT INTO `ospos_sales_payments` (sale_id, payment_type, payment_amount) SELECT sale_id, payment_type, payment_amount FROM `ospos_sales_suspended_payments`;
|
||||
INSERT INTO `ospos_sales_items_taxes` (sale_id, item_id, line, name, percent) SELECT sale_id, item_id, line, name, percent FROM `ospos_sales_suspended_items_taxes`;
|
||||
|
||||
ALTER TABLE `ospos_sales_suspended_payments` DROP FOREIGN KEY `ospos_sales_suspended_payments_ibfk_1`;
|
||||
|
||||
ALTER TABLE `ospos_sales_suspended_items_taxes` DROP FOREIGN KEY `ospos_sales_suspended_items_taxes_ibfk_1`;
|
||||
ALTER TABLE `ospos_sales_suspended_items_taxes` DROP FOREIGN KEY `ospos_sales_suspended_items_taxes_ibfk_2`;
|
||||
|
||||
ALTER TABLE `ospos_sales_suspended_items` DROP FOREIGN KEY `ospos_sales_suspended_items_ibfk_1`;
|
||||
ALTER TABLE `ospos_sales_suspended_items` DROP FOREIGN KEY `ospos_sales_suspended_items_ibfk_2`;
|
||||
ALTER TABLE `ospos_sales_suspended_items` DROP FOREIGN KEY `ospos_sales_suspended_items_ibfk_3`;
|
||||
|
||||
ALTER TABLE `ospos_sales_suspended` DROP FOREIGN KEY `ospos_sales_suspended_ibfk_1`;
|
||||
ALTER TABLE `ospos_sales_suspended` DROP FOREIGN KEY `ospos_sales_suspended_ibfk_2`;
|
||||
ALTER TABLE `ospos_sales_suspended` DROP FOREIGN KEY `ospos_sales_suspended_ibfk_3`;
|
||||
|
||||
DROP TABLE `ospos_sales_suspended_payments`, `ospos_sales_suspended_items_taxes`, `ospos_sales_suspended_items`, `ospos_sales_suspended`;
|
||||
|
||||
--
|
||||
|
||||
@@ -140,7 +140,7 @@ CREATE TABLE IF NOT EXISTS `ospos_expense_categories` (
|
||||
`category_name` varchar(255) DEFAULT NULL,
|
||||
`category_description` varchar(255) NOT NULL,
|
||||
`deleted` int(1) NOT NULL DEFAULT '0'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
|
||||
-- Table structure for table `ospos_expenses`
|
||||
@@ -154,7 +154,7 @@ CREATE TABLE IF NOT EXISTS `ospos_expenses` (
|
||||
`description` varchar(255) NOT NULL,
|
||||
`employee_id` int(10) NOT NULL,
|
||||
`deleted` int(1) NOT NULL DEFAULT '0'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
|
||||
-- Indexes for table `ospos_expense_categories`
|
||||
|
||||
@@ -75,7 +75,7 @@ CREATE TABLE `ospos_cash_up` (
|
||||
`open_employee_id` int(10) NOT NULL,
|
||||
`close_employee_id` int(10) NOT NULL,
|
||||
`deleted` int(1) NOT NULL DEFAULT '0'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
-- Indexes for table `ospos_cash_up`
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ CREATE TABLE IF NOT EXISTS `ospos_tax_codes` (
|
||||
`state` varchar(255) NOT NULL DEFAULT '',
|
||||
`deleted` int(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`tax_code_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
ALTER TABLE `ospos_customers`
|
||||
ADD COLUMN `tax_id` varchar(32) NOT NULL DEFAULT '' AFTER `taxable`,
|
||||
@@ -59,7 +59,7 @@ CREATE TABLE `ospos_sales_taxes` (
|
||||
`rounding_code` tinyint(2) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`sales_taxes_id`),
|
||||
KEY `print_sequence` (`sale_id`,`print_sequence`,`tax_group`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `ospos_tax_jurisdictions` (
|
||||
`jurisdiction_id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
@@ -71,7 +71,7 @@ CREATE TABLE IF NOT EXISTS `ospos_tax_jurisdictions` (
|
||||
`cascade_sequence` tinyint(2) NOT NULL DEFAULT 0,
|
||||
`deleted` int(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`jurisdiction_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci AUTO_INCREMENT=1;
|
||||
|
||||
ALTER TABLE `ospos_suppliers`
|
||||
ADD COLUMN `tax_id` varchar(32) DEFAULT NULL AFTER `account_number`;
|
||||
@@ -89,7 +89,7 @@ CREATE TABLE IF NOT EXISTS `ospos_tax_rates` (
|
||||
`tax_rate` decimal(15,4) NOT NULL DEFAULT 0.0000,
|
||||
`tax_rounding_code` tinyint(2) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`tax_rate_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
-- Add support for sales tax report
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ CREATE TABLE `ospos_sales_payments` (
|
||||
`reference_code` varchar(40) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`payment_id`),
|
||||
KEY `payment_sale` (`sale_id`, `payment_type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
INSERT INTO ospos_sales_payments (sale_id, payment_type, payment_amount, payment_user)
|
||||
SELECT payments.sale_id, payments.payment_type, payments.payment_amount, sales.employee_id
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
CREATE TABLE IF NOT EXISTS `ospos_plugin_config` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`plugin_id` varchar(100) NOT NULL,
|
||||
`key` varchar(100) NOT NULL,
|
||||
`value` text NOT NULL,
|
||||
`is_control` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uq_plugin_key` (`plugin_id`, `key`),
|
||||
KEY `idx_plugin_id` (`plugin_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
INSERT IGNORE INTO `ospos_modules` (`name_lang_key`, `desc_lang_key`, `sort`, `module_id`) VALUES
|
||||
('module_plugins', 'module_plugins_desc', 111, 'plugins');
|
||||
|
||||
INSERT IGNORE INTO `ospos_permissions` (`permission_id`, `module_id`) VALUES
|
||||
('plugins', 'plugins');
|
||||
|
||||
INSERT IGNORE INTO `ospos_grants` (`permission_id`, `person_id`, `menu_group`)
|
||||
SELECT 'plugins', `person_id`, 'office' FROM `ospos_grants` WHERE `permission_id` = 'config';
|
||||
@@ -79,14 +79,10 @@ INSERT INTO `ospos_app_config` (`key`, `value`) VALUES
|
||||
('smtp_timeout', '5'),
|
||||
('smtp_crypto', 'ssl'),
|
||||
('receipt_template', 'receipt_default'),
|
||||
('theme', 'bootstrap'),
|
||||
('theme', 'flatly'),
|
||||
('statistics', '1'),
|
||||
('language', 'english'),
|
||||
('language_code', 'en'),
|
||||
('rtl_language', '0'),
|
||||
('color_mode', 'light'),
|
||||
('config_menu_position', 'start'),
|
||||
('responsive_design', '1');
|
||||
('language_code', 'en');
|
||||
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
@@ -272,6 +272,9 @@ function get_payment_options(): array
|
||||
$payments[lang('Sales.upi')] = lang('Sales.upi');
|
||||
}
|
||||
|
||||
$payments[lang('Sales.bank_transfer')] = lang('Sales.bank_transfer');
|
||||
$payments[lang('Sales.wallet')] = lang('Sales.wallet');
|
||||
|
||||
return $payments;
|
||||
}
|
||||
|
||||
|
||||
@@ -172,6 +172,7 @@ function dropAllForeignKeyConstraints(string $table, string $column): array {
|
||||
WHERE kcu.TABLE_SCHEMA = DATABASE()
|
||||
AND ((kcu.REFERENCED_TABLE_NAME = '" . $db->getPrefix() . "$table' AND kcu.REFERENCED_COLUMN_NAME = '$column')
|
||||
OR (kcu.TABLE_NAME = '" . $db->getPrefix() . "$table' AND kcu.COLUMN_NAME = '$column'))
|
||||
AND rc.CONSTRAINT_NAME IS NOT NULL
|
||||
");
|
||||
|
||||
$deletedConstraints = [];
|
||||
|
||||
19
app/Helpers/plugin_helper.php
Normal file
19
app/Helpers/plugin_helper.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
use CodeIgniter\Events\Events;
|
||||
|
||||
if (!function_exists('pluginContent')) {
|
||||
function pluginContent(string $section, array $data = []): string
|
||||
{
|
||||
ob_start();
|
||||
Events::trigger("view:{$section}", $data);
|
||||
return ob_get_clean() ?: '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('pluginContentExists')) {
|
||||
function pluginContentExists(string $section): bool
|
||||
{
|
||||
return !empty(Events::listeners("view:{$section}"));
|
||||
}
|
||||
}
|
||||
@@ -118,21 +118,21 @@ function get_sale_data_row(object $sale): array
|
||||
? '-'
|
||||
: anchor(
|
||||
"$controller/invoice/$sale->sale_id",
|
||||
'<i class="bi bi-file-text"></i>',
|
||||
'<span class="glyphicon glyphicon-list-alt"></span>',
|
||||
['title' => lang('Sales.show_invoice')]
|
||||
);
|
||||
}
|
||||
|
||||
$row['receipt'] = anchor(
|
||||
"$controller/receipt/$sale->sale_id",
|
||||
'<i class="bi bi-receipt"></i>',
|
||||
'<span class="glyphicon glyphicon-usd"></span>',
|
||||
['title' => lang('Sales.show_receipt')]
|
||||
);
|
||||
$row['edit'] = anchor(
|
||||
"$controller/edit/$sale->sale_id",
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch print_hide',
|
||||
'class' => 'modal-dlg print_hide',
|
||||
'data-btn-delete' => lang('Common.delete'),
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang(ucfirst($controller) . ".update")
|
||||
@@ -232,18 +232,18 @@ function get_person_data_row(object $person): array
|
||||
? ''
|
||||
: anchor(
|
||||
"Messages/view/$person->person_id",
|
||||
'<i class="bi bi-telephone"></i>',
|
||||
'<span class="glyphicon glyphicon-phone"></span>',
|
||||
[
|
||||
'class' => 'modal-launch',
|
||||
'class' => 'modal-dlg',
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang('Messages.sms_send')
|
||||
]
|
||||
),
|
||||
'edit' => anchor(
|
||||
"$controller/view/$person->person_id",
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch',
|
||||
'class' => 'modal-dlg',
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang(ucfirst($controller) . '.update') // TODO: String interpolation
|
||||
]
|
||||
@@ -299,18 +299,18 @@ function get_customer_data_row(object $person, object $stats): array
|
||||
? ''
|
||||
: anchor(
|
||||
"Messages/view/$person->person_id",
|
||||
'<i class="bi bi-telephone"></i>',
|
||||
'<span class="glyphicon glyphicon-phone"></span>',
|
||||
[
|
||||
'class' => 'modal-launch',
|
||||
'class' => 'modal-dlg',
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang('Messages.sms_send')
|
||||
]
|
||||
),
|
||||
'edit' => anchor(
|
||||
"$controller/view/$person->person_id",
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch',
|
||||
'class' => 'modal-dlg',
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang(ucfirst($controller) . ".update")
|
||||
]
|
||||
@@ -369,18 +369,18 @@ function get_supplier_data_row(object $supplier): array
|
||||
? ''
|
||||
: anchor(
|
||||
"Messages/view/$supplier->person_id",
|
||||
'<i class="bi bi-telephone"></i>',
|
||||
'<span class="glyphicon glyphicon-phone"></span>',
|
||||
[
|
||||
'class' => "modal-launch",
|
||||
'class' => "modal-dlg",
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang('Messages.sms_send')
|
||||
]
|
||||
),
|
||||
'edit' => anchor(
|
||||
"$controller/view/$supplier->person_id",
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => "modal-launch",
|
||||
'class' => "modal-dlg",
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang(ucfirst($controller) . ".update")
|
||||
]
|
||||
@@ -498,26 +498,26 @@ function get_item_data_row(object $item): array
|
||||
$icons = [
|
||||
'inventory' => anchor(
|
||||
"$controller/inventory/$item->item_id",
|
||||
'<i class="bi bi-box"></i>',
|
||||
'<span class="glyphicon glyphicon-pushpin"></span>',
|
||||
[
|
||||
'class' => 'modal-launch',
|
||||
'class' => 'modal-dlg',
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang(ucfirst($controller) . ".count")
|
||||
]
|
||||
),
|
||||
'stock' => anchor(
|
||||
"$controller/countDetails/$item->item_id",
|
||||
'<i class="bi bi-info-square"></i>',
|
||||
'<span class="glyphicon glyphicon-list-alt"></span>',
|
||||
[
|
||||
'class' => 'modal-launch',
|
||||
'class' => 'modal-dlg',
|
||||
'title' => lang(ucfirst($controller) . ".details_count")
|
||||
]
|
||||
),
|
||||
'edit' => anchor(
|
||||
"$controller/view/$item->item_id",
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch',
|
||||
'class' => 'modal-dlg',
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang(ucfirst($controller) . ".update")
|
||||
]
|
||||
@@ -561,9 +561,9 @@ function get_giftcard_data_row(object $giftcard): array
|
||||
'value' => to_currency($giftcard->value),
|
||||
'edit' => anchor(
|
||||
"$controller/view/$giftcard->giftcard_id",
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch',
|
||||
'class' => 'modal-dlg',
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang(ucfirst($controller) . ".update")
|
||||
]
|
||||
@@ -607,9 +607,9 @@ function get_item_kit_data_row(object $item_kit): array
|
||||
'total_unit_price' => to_currency($item_kit->total_unit_price),
|
||||
'edit' => anchor(
|
||||
"$controller/view/$item_kit->item_kit_id",
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch',
|
||||
'class' => 'modal-dlg',
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang(ucfirst($controller) . ".update")
|
||||
]
|
||||
@@ -715,9 +715,9 @@ function get_attribute_definition_data_row(object $attribute_row): array
|
||||
'definition_flags' => $definition_flags,
|
||||
'edit' => anchor(
|
||||
"$controller/view/$attribute_row->definition_id",
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch',
|
||||
'class' => 'modal-dlg',
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang(ucfirst($controller) . ".update")
|
||||
]
|
||||
@@ -755,9 +755,9 @@ function get_expense_category_data_row(object $expense_category): array
|
||||
'category_description' => $expense_category->category_description,
|
||||
'edit' => anchor(
|
||||
"$controller/view/$expense_category->expense_category_id",
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch',
|
||||
'class' => 'modal-dlg',
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang(ucfirst($controller) . ".update")
|
||||
]
|
||||
@@ -809,9 +809,9 @@ function get_expenses_data_row(object $expense): array
|
||||
'created_by' => $expense->first_name . ' ' . $expense->last_name,
|
||||
'edit' => anchor(
|
||||
"$controller/view/$expense->expense_id",
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch',
|
||||
'class' => 'modal-dlg',
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang(ucfirst($controller) . ".update")
|
||||
]
|
||||
@@ -904,16 +904,16 @@ function get_cash_up_data_row(object $cash_up): array
|
||||
'close_date' => to_datetime(strtotime($cash_up->close_date)),
|
||||
'close_employee_id' => $cash_up->close_first_name . ' ' . $cash_up->close_last_name,
|
||||
'closed_amount_cash' => to_currency($cash_up->closed_amount_cash),
|
||||
'note' => $cash_up->note ? '<i class="bi bi-check-lg"></i>' : '<i class="bi bi-x-lg"></i>',
|
||||
'note' => $cash_up->note ? '<span class="glyphicon glyphicon-ok"></span>' : '<span class="glyphicon glyphicon-remove"></span>',
|
||||
'closed_amount_due' => to_currency($cash_up->closed_amount_due),
|
||||
'closed_amount_card' => to_currency($cash_up->closed_amount_card),
|
||||
'closed_amount_check' => to_currency($cash_up->closed_amount_check),
|
||||
'closed_amount_total' => to_currency($cash_up->closed_amount_total),
|
||||
'edit' => anchor(
|
||||
"$controller/view/$cash_up->cashup_id",
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch',
|
||||
'class' => 'modal-dlg',
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang(ucfirst($controller) . ".update")
|
||||
]
|
||||
@@ -933,6 +933,50 @@ function get_controller(): string
|
||||
return end($controller_name_parts);
|
||||
}
|
||||
|
||||
function plugin_headers(): array
|
||||
{
|
||||
return [
|
||||
['name' => lang('Plugins.name'), 'escape' => false],
|
||||
['description' => lang('Plugins.description')],
|
||||
['version' => lang('Plugins.version'), 'escape' => false],
|
||||
['status' => lang('Plugins.status'), 'escape' => false],
|
||||
];
|
||||
}
|
||||
|
||||
function get_plugin_manage_table_headers(): string
|
||||
{
|
||||
return transform_headers(plugin_headers(), false, true);
|
||||
}
|
||||
|
||||
function get_plugin_data_row(array $plugin): array
|
||||
{
|
||||
$pluginId = $plugin['id'];
|
||||
|
||||
$statusHtml = $plugin['enabled']
|
||||
? '<span class="label label-success">' . lang('Plugins.active') . '</span>'
|
||||
: '<span class="label label-default">' . lang('Plugins.inactive') . '</span>';
|
||||
|
||||
$editHtml = $plugin['enabled']
|
||||
? '<button class="btn btn-warning btn-xs plugin-action" data-action="disable" data-plugin-id="' . esc($pluginId) . '">'
|
||||
. '<span class="glyphicon glyphicon-pause"></span> ' . lang('Plugins.disable') . '</button>'
|
||||
: '<button class="btn btn-success btn-xs plugin-action" data-action="enable" data-plugin-id="' . esc($pluginId) . '">'
|
||||
. '<span class="glyphicon glyphicon-play"></span> ' . lang('Plugins.enable') . '</button>';
|
||||
|
||||
if ($plugin['has_config'] && $plugin['enabled']) {
|
||||
$editHtml .= ' <button class="btn btn-primary btn-xs plugin-config" data-plugin-id="' . esc($pluginId) . '">'
|
||||
. '<span class="glyphicon glyphicon-cog"></span> ' . lang('Plugins.configure') . '</button>';
|
||||
}
|
||||
|
||||
return [
|
||||
'plugin_id' => $pluginId,
|
||||
'name' => '<strong>' . esc($plugin['name']) . '</strong><br><small class="text-muted">' . esc($pluginId) . '</small>',
|
||||
'description' => esc($plugin['description']),
|
||||
'version' => '<span class="label label-default">' . esc($plugin['version']) . '</span>',
|
||||
'status' => $statusHtml,
|
||||
'edit' => $editHtml,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores filter values from the URL query string.
|
||||
*
|
||||
|
||||
@@ -36,9 +36,9 @@ function get_tax_code_data_row($tax_code_row): array
|
||||
'state' => $tax_code_row->state,
|
||||
'edit' => anchor(
|
||||
"$controller_name/view_tax_codes/$tax_code_row->tax_code",
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch',
|
||||
'class' => 'modal-dlg',
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang(ucfirst($controller_name) . ".update_tax_codes")
|
||||
]
|
||||
@@ -74,9 +74,9 @@ function get_tax_categories_data_row($tax_categories_row): array
|
||||
'tax_group_sequence' => $tax_categories_row->tax_group_sequence,
|
||||
'edit' => anchor(
|
||||
"$controller_name/view/$tax_categories_row->tax_category_id",
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch',
|
||||
'class' => 'modal-dlg',
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang(ucfirst($controller_name) . ".update")
|
||||
]
|
||||
@@ -111,9 +111,9 @@ function get_tax_jurisdictions_data_row($tax_jurisdiction_row): array
|
||||
'reporting_authority' => $tax_jurisdiction_row->reporting_authority,
|
||||
'edit' => anchor(
|
||||
"$controller_name/view/$tax_jurisdiction_row->jurisdiction_id",
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch',
|
||||
'class' => 'modal-dlg',
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang(ucfirst($controller_name) . ".update")
|
||||
]
|
||||
@@ -156,9 +156,9 @@ function get_tax_rates_data_row($tax_rates_row): array
|
||||
'rounding_code_name' => Rounding_mode::get_rounding_code_name($tax_rates_row->tax_rounding_code),
|
||||
'edit' => anchor(
|
||||
"$controller_name/view/$tax_rates_row->tax_rate_id",
|
||||
'<i class="bi bi-pencil-square"></i>',
|
||||
'<span class="glyphicon glyphicon-edit"></span>',
|
||||
[
|
||||
'class' => 'modal-launch',
|
||||
'class' => 'modal-dlg',
|
||||
'data-btn-submit' => lang('Common.submit'),
|
||||
'title' => lang(ucfirst($controller_name) . ".update")
|
||||
]
|
||||
|
||||
@@ -166,8 +166,6 @@ return [
|
||||
"info" => "معلومات",
|
||||
"info_configuration" => "معلومات الشركة",
|
||||
"input_groups" => "مجموعات الإدخال",
|
||||
"integrations" => "التكامل",
|
||||
"integrations_configuration" => "تكامل",
|
||||
"invoice" => "الفاتورة",
|
||||
"invoice_configuration" => "إعدادات طباعة الفاتورة",
|
||||
"invoice_default_comments" => "التعليق الافتراضي على الفاتورة",
|
||||
@@ -198,13 +196,6 @@ return [
|
||||
"location_info" => "معلومات تهيئة الأماكن",
|
||||
"login_form" => "نمط نموذج تسجيل الدخول",
|
||||
"logout" => "هل تريد عمل نسخة إحتياطية قبل الخروج؟ اضغط [نعم] لعمل النسخة أو [الغاء] للخروج.",
|
||||
"mailchimp" => "ميل تشامب",
|
||||
"mailchimp_api_key" => "مفتاح ميل شيمب",
|
||||
"mailchimp_configuration" => "إعدادات ميل شيمب",
|
||||
"mailchimp_key_successfully" => "نجاح.",
|
||||
"mailchimp_key_unsuccessfully" => "فشل.",
|
||||
"mailchimp_lists" => "إعدادات ميل شيمب",
|
||||
"mailchimp_tooltip" => "انقر على رمز مفتاح API.",
|
||||
"message" => "الرسائل",
|
||||
"message_configuration" => "إعدادات الرسائل",
|
||||
"msg_msg" => "الرسائل النصية المحفوظة",
|
||||
|
||||
@@ -28,16 +28,6 @@ return [
|
||||
"employee" => "الموظف",
|
||||
"error_adding_updating" => "خطاء فى إضافة أو تحديث العميل.",
|
||||
"import_items_csv" => "استيراد العملا ء من ورقة عمل اكسل",
|
||||
"mailchimp_activity_click" => "النقر على البريد الإلكتروني",
|
||||
"mailchimp_activity_lastopen" => "آخر رسالة إلكترونية مفتوحة",
|
||||
"mailchimp_activity_open" => "رسالة إلكترونية مفتوحة",
|
||||
"mailchimp_activity_total" => "تم ارسال الرسالة الإلكترونية بنجاح",
|
||||
"mailchimp_activity_unopen" => "رسالة إلكترونية غير مفتوحة",
|
||||
"mailchimp_email_client" => "بريد الكتروني",
|
||||
"mailchimp_info" => "ميل تشيمب",
|
||||
"mailchimp_member_rating" => "التقييم",
|
||||
"mailchimp_status" => "الحالة",
|
||||
"mailchimp_vip" => "مهم",
|
||||
"max" => "الحد الأقصى",
|
||||
"min" => "الحد الأدنى",
|
||||
"new" => "عميل جديد",
|
||||
|
||||
@@ -32,6 +32,7 @@ return [
|
||||
"migrate_desc" => "تحديث قاعدة البيانات.",
|
||||
"office" => "المكتب",
|
||||
"office_desc" => "اظهار الائحة المكتبية.",
|
||||
'plugins' => 'الإضافات',
|
||||
"receivings" => "استلام الأصناف",
|
||||
"receivings_desc" => "معالجة أوامر الشراء و استلام الأصناف.",
|
||||
"reports" => "التقارير",
|
||||
|
||||
27
app/Language/ar-EG/Plugins.php
Normal file
27
app/Language/ar-EG/Plugins.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
return [
|
||||
'actions' => 'إجراءات',
|
||||
'active' => 'نشط',
|
||||
'configure' => 'تكوين',
|
||||
'description' => 'الوصف',
|
||||
'disable' => 'تعطيل',
|
||||
'disable_failed' => 'فشل تعطيل الإضافة',
|
||||
'disabled' => 'تم تعطيل الإضافة بنجاح',
|
||||
'enable' => 'تفعيل',
|
||||
'enable_failed' => 'فشل تفعيل الإضافة',
|
||||
'enabled' => 'تم تفعيل الإضافة بنجاح',
|
||||
'inactive' => 'غير نشط',
|
||||
'management' => 'إدارة الإضافات',
|
||||
'name' => 'اسم الإضافة',
|
||||
'no_config' => 'هذه الإضافة لا تحتوي على خيارات تكوين',
|
||||
'no_plugins_to_display' => 'لا توجد إضافات للعرض',
|
||||
'not_found' => 'الإضافة غير موجودة',
|
||||
'plugins' => 'الإضافات',
|
||||
'settings_save_failed' => 'فشل حفظ إعدادات الإضافة',
|
||||
'settings_saved' => 'تم حفظ إعدادات الإضافة بنجاح',
|
||||
'status' => 'الحالة',
|
||||
'uninstall' => 'إلغاء التثبيت',
|
||||
'uninstall_failed' => 'فشل إلغاء تثبيت الإضافة',
|
||||
'uninstalled' => 'تم إلغاء تثبيت الإضافة بنجاح',
|
||||
'version' => 'الإصدار',
|
||||
];
|
||||
@@ -41,7 +41,6 @@ return [
|
||||
"customer_discount" => "الخصم",
|
||||
"customer_email" => "البريد الإلكترونى",
|
||||
"customer_location" => "المكان",
|
||||
"customer_mailchimp_status" => "حالة بريد ميل تشيمب",
|
||||
"customer_optional" => "(مطلوب للدفعات المستحقة)",
|
||||
"customer_required" => "(اجباري)",
|
||||
"customer_total" => "المجموع",
|
||||
|
||||
@@ -166,8 +166,6 @@ return [
|
||||
"info" => "معلومات",
|
||||
"info_configuration" => "معلومات الشركة",
|
||||
"input_groups" => "مجموعات الإدخال",
|
||||
"integrations" => "التكامل",
|
||||
"integrations_configuration" => "تكامل",
|
||||
"invoice" => "الفاتورة",
|
||||
"invoice_configuration" => "إعدادات طباعة الفاتورة",
|
||||
"invoice_default_comments" => "التعليق الافتراضي على الفاتورة",
|
||||
@@ -198,13 +196,6 @@ return [
|
||||
"location_info" => "معلومات تهيئة الأماكن",
|
||||
"login_form" => "نمط نموذج تسجيل الدخول",
|
||||
"logout" => "هل تريد عمل نسخة إحتياطية قبل الخروج؟ اضغط [نعم] لعمل النسخة أو [الغاء] للخروج.",
|
||||
"mailchimp" => "ميل تشامب",
|
||||
"mailchimp_api_key" => "مفتاح ميل شيمب",
|
||||
"mailchimp_configuration" => "إعدادات ميل شيمب",
|
||||
"mailchimp_key_successfully" => "نجاح.",
|
||||
"mailchimp_key_unsuccessfully" => "فشل.",
|
||||
"mailchimp_lists" => "قوائم ميل شيمب",
|
||||
"mailchimp_tooltip" => "انقر على رمز مفتاح API.",
|
||||
"message" => "الرسائل",
|
||||
"message_configuration" => "إعدادات الرسائل",
|
||||
"msg_msg" => "الرسائل النصية المحفوظة",
|
||||
|
||||
@@ -28,16 +28,6 @@ return [
|
||||
"employee" => "الموظف",
|
||||
"error_adding_updating" => "خطاء فى إضافة أو تحديث العميل.",
|
||||
"import_items_csv" => "استيراد العملا ء من ورقة عمل اكسل",
|
||||
"mailchimp_activity_click" => "النقر على البريد الإلكتروني",
|
||||
"mailchimp_activity_lastopen" => "آخر رسالة إلكترونية مفتوحة",
|
||||
"mailchimp_activity_open" => "رسالة إلكترونية مفتوحة",
|
||||
"mailchimp_activity_total" => "تم ارسال الرسالة الإلكترونية بنجاح",
|
||||
"mailchimp_activity_unopen" => "رسالة إلكترونية غير مفتوحة",
|
||||
"mailchimp_email_client" => "بريد الكتروني",
|
||||
"mailchimp_info" => "ميل تشيمب",
|
||||
"mailchimp_member_rating" => "التقييم",
|
||||
"mailchimp_status" => "الحالة",
|
||||
"mailchimp_vip" => "مهم",
|
||||
"max" => "الحد الأقصى",
|
||||
"min" => "الحد الأدنى",
|
||||
"new" => "عميل جديد",
|
||||
|
||||
@@ -32,6 +32,7 @@ return [
|
||||
"migrate_desc" => "تحديث قاعدة البيانات.",
|
||||
"office" => "المكتب",
|
||||
"office_desc" => "اظهار الائحة المكتبية.",
|
||||
'plugins' => 'الإضافات',
|
||||
"receivings" => "استلام الأصناف",
|
||||
"receivings_desc" => "معالجة أوامر الشراء و استلام الأصناف.",
|
||||
"reports" => "التقارير",
|
||||
|
||||
27
app/Language/ar-LB/Plugins.php
Normal file
27
app/Language/ar-LB/Plugins.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
return [
|
||||
'actions' => 'إجراءات',
|
||||
'active' => 'نشط',
|
||||
'configure' => 'تكوين',
|
||||
'description' => 'الوصف',
|
||||
'disable' => 'تعطيل',
|
||||
'disable_failed' => 'فشل تعطيل الإضافة',
|
||||
'disabled' => 'تم تعطيل الإضافة بنجاح',
|
||||
'enable' => 'تفعيل',
|
||||
'enable_failed' => 'فشل تفعيل الإضافة',
|
||||
'enabled' => 'تم تفعيل الإضافة بنجاح',
|
||||
'inactive' => 'غير نشط',
|
||||
'management' => 'إدارة الإضافات',
|
||||
'name' => 'اسم الإضافة',
|
||||
'no_config' => 'هذه الإضافة لا تحتوي على خيارات تكوين',
|
||||
'no_plugins_to_display' => 'لا توجد إضافات للعرض',
|
||||
'not_found' => 'الإضافة غير موجودة',
|
||||
'plugins' => 'الإضافات',
|
||||
'settings_save_failed' => 'فشل حفظ إعدادات الإضافة',
|
||||
'settings_saved' => 'تم حفظ إعدادات الإضافة بنجاح',
|
||||
'status' => 'الحالة',
|
||||
'uninstall' => 'إلغاء التثبيت',
|
||||
'uninstall_failed' => 'فشل إلغاء تثبيت الإضافة',
|
||||
'uninstalled' => 'تم إلغاء تثبيت الإضافة بنجاح',
|
||||
'version' => 'الإصدار',
|
||||
];
|
||||
@@ -41,7 +41,6 @@ return [
|
||||
"customer_discount" => "الخصم",
|
||||
"customer_email" => "البريد الإلكترونى",
|
||||
"customer_location" => "المكان",
|
||||
"customer_mailchimp_status" => "حالة بريد ميل تشيمب",
|
||||
"customer_optional" => "(مطلوب للدفعات المستحقة)",
|
||||
"customer_required" => "(اجباري)",
|
||||
"customer_total" => "المجموع",
|
||||
|
||||
@@ -166,8 +166,6 @@ return [
|
||||
"info" => "Məlumat",
|
||||
"info_configuration" => "Dükan İnformasiyası",
|
||||
"input_groups" => "",
|
||||
"integrations" => "İnteqrasiya",
|
||||
"integrations_configuration" => "Üçüncü tərəf inteqrasiya",
|
||||
"invoice" => "Faktura",
|
||||
"invoice_configuration" => "Faktura Çap Parametrləri",
|
||||
"invoice_default_comments" => "Standart Faktura Şərhləri",
|
||||
@@ -198,13 +196,6 @@ return [
|
||||
"location_info" => "Yer Konfiqurasiya Məlumatı",
|
||||
"login_form" => "",
|
||||
"logout" => "Çıxışdan əvvəl məlumatlari ehtiyat bazasına köçürmək istəyirsinizmi? Çıxış üçün Bekap və ya [Ləğv] üçün [OK]' düyməsinə basın.",
|
||||
"mailchimp" => "Mailçimp",
|
||||
"mailchimp_api_key" => "Mailchimp API Açarı",
|
||||
"mailchimp_configuration" => "Mailchimp Konfiqurasiyası",
|
||||
"mailchimp_key_successfully" => "API Açarı etibarlıdır.",
|
||||
"mailchimp_key_unsuccessfully" => "API Açarı etibarsızdır.",
|
||||
"mailchimp_lists" => "Mailchimp siyahısı (lar)",
|
||||
"mailchimp_tooltip" => "API Açarının İşarəsinə basın.",
|
||||
"message" => "Mesaj",
|
||||
"message_configuration" => "Mesaj Konfiqurasiyası",
|
||||
"msg_msg" => "Saxlanılan Mətn Mesajı",
|
||||
|
||||
@@ -28,16 +28,6 @@ return [
|
||||
"employee" => "Əməkdaş",
|
||||
"error_adding_updating" => "Müştəri əlavəsində ya da yenilənməsində XƏTA.",
|
||||
"import_items_csv" => "CSVdən müştəri əlavə et",
|
||||
"mailchimp_activity_click" => "Elektron poçt düyməsi",
|
||||
"mailchimp_activity_lastopen" => "Son açılan məktub",
|
||||
"mailchimp_activity_open" => "Açıq məktub",
|
||||
"mailchimp_activity_total" => "Məktub göndərildi",
|
||||
"mailchimp_activity_unopen" => "Açılmamış məktub",
|
||||
"mailchimp_email_client" => "Müştəriyə Məktub Göndər",
|
||||
"mailchimp_info" => "Mailchimp-də",
|
||||
"mailchimp_member_rating" => "Reytinq",
|
||||
"mailchimp_status" => "Status",
|
||||
"mailchimp_vip" => "siz silmək üçün heç bir müştəri seçməmisiniz",
|
||||
"max" => "Ən çox xərclənən",
|
||||
"min" => "Ən az xərclənən",
|
||||
"new" => "Yeni Müştəri",
|
||||
|
||||
@@ -32,6 +32,7 @@ return [
|
||||
"migrate_desc" => "ALSAN Məlumat Bazasıni Yenilə.",
|
||||
"office" => "Ofis",
|
||||
"office_desc" => "Ofis menyusu siyahısı modulları.",
|
||||
'plugins' => 'Plaginlər',
|
||||
"receivings" => "Qəbul Edilənlər",
|
||||
"receivings_desc" => "Satınalma sifarişləri işləyin.",
|
||||
"reports" => "Hesabatlar",
|
||||
|
||||
27
app/Language/az/Plugins.php
Normal file
27
app/Language/az/Plugins.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
return [
|
||||
'actions' => 'Əməliyyatlar',
|
||||
'active' => 'Aktiv',
|
||||
'configure' => 'Konfiqurasiya',
|
||||
'description' => 'Təsvir',
|
||||
'disable' => 'Deaktiv et',
|
||||
'disable_failed' => 'Plagini deaktiv etmək alınmadı',
|
||||
'disabled' => 'Plagin uğurla deaktiv edildi',
|
||||
'enable' => 'Aktiv et',
|
||||
'enable_failed' => 'Plagini aktiv etmək alınmadı',
|
||||
'enabled' => 'Plagin uğurla aktiv edildi',
|
||||
'inactive' => 'Deaktiv',
|
||||
'management' => 'Plagin idarəetməsi',
|
||||
'name' => 'Plagin adı',
|
||||
'no_config' => 'Bu plaginin konfiqurasiya seçimləri yoxdur',
|
||||
'no_plugins_to_display' => 'Göstəriləcək plagin yoxdur',
|
||||
'not_found' => 'Plagin tapılmadı',
|
||||
'plugins' => 'Plaginlər',
|
||||
'settings_save_failed' => 'Plagin parametrlərini saxlamaq alınmadı',
|
||||
'settings_saved' => 'Plagin parametrləri uğurla saxlandı',
|
||||
'status' => 'Status',
|
||||
'uninstall' => 'Sil',
|
||||
'uninstall_failed' => 'Plagini silmək alınmadı',
|
||||
'uninstalled' => 'Plagin uğurla silindi',
|
||||
'version' => 'Versiya',
|
||||
];
|
||||
@@ -41,7 +41,6 @@ return [
|
||||
"customer_discount" => "Endirim",
|
||||
"customer_email" => "E-poçt",
|
||||
"customer_location" => "Yer",
|
||||
"customer_mailchimp_status" => "Mailchimp Statusu",
|
||||
"customer_optional" => "(Ödənişlərdə tələb olunur)",
|
||||
"customer_required" => "(Vacib)",
|
||||
"customer_total" => "Cəmi",
|
||||
|
||||
@@ -166,8 +166,6 @@ return [
|
||||
"info" => "Information",
|
||||
"info_configuration" => "Store Information",
|
||||
"input_groups" => "",
|
||||
"integrations" => "",
|
||||
"integrations_configuration" => "",
|
||||
"invoice" => "Invoice",
|
||||
"invoice_configuration" => "Invoice Print Settings",
|
||||
"invoice_default_comments" => "Default Invoice Comments",
|
||||
@@ -198,13 +196,6 @@ return [
|
||||
"location_info" => "Location Configuration Information",
|
||||
"login_form" => "",
|
||||
"logout" => "Do you want to make a backup before logging out? Click [OK] to backup or [Cancel] to logout.",
|
||||
"mailchimp" => "Mailchimp",
|
||||
"mailchimp_api_key" => "Mailchimp API Key",
|
||||
"mailchimp_configuration" => "Mailchimp Configuration",
|
||||
"mailchimp_key_successfully" => "API Key is valid.",
|
||||
"mailchimp_key_unsuccessfully" => "API Key is invalid.",
|
||||
"mailchimp_lists" => "Mailchimp List(s)",
|
||||
"mailchimp_tooltip" => "Click the icon for an API Key.",
|
||||
"message" => "Message",
|
||||
"message_configuration" => "Message Configuration",
|
||||
"msg_msg" => "Saved Text Message",
|
||||
|
||||
@@ -28,16 +28,6 @@ return [
|
||||
"employee" => "Служител",
|
||||
"error_adding_updating" => "Добавянето или актуализирането на клиента е неуспешно.",
|
||||
"import_items_csv" => "Импортиране на клиент от CSV",
|
||||
"mailchimp_activity_click" => "Email click",
|
||||
"mailchimp_activity_lastopen" => "Последно отворен Имейл",
|
||||
"mailchimp_activity_open" => "Имейлът е отворен",
|
||||
"mailchimp_activity_total" => "Имейлът е изпратен",
|
||||
"mailchimp_activity_unopen" => "Имейлът е неотворен",
|
||||
"mailchimp_email_client" => "Имейл клиент",
|
||||
"mailchimp_info" => "Mailchimp (Услуга)",
|
||||
"mailchimp_member_rating" => "Оценка",
|
||||
"mailchimp_status" => "Статус",
|
||||
"mailchimp_vip" => "VIP",
|
||||
"max" => "Максимално похарчени",
|
||||
"min" => "Минимално похарчено",
|
||||
"new" => "Нов клиент",
|
||||
|
||||
@@ -32,6 +32,7 @@ return [
|
||||
"migrate_desc" => "Update the OSPOS Database.",
|
||||
"office" => "Office",
|
||||
"office_desc" => "List office menu modules.",
|
||||
'plugins' => 'Плъгини',
|
||||
"receivings" => "Receivings",
|
||||
"receivings_desc" => "Process Purchase Orders.",
|
||||
"reports" => "Reports",
|
||||
|
||||
27
app/Language/bg/Plugins.php
Normal file
27
app/Language/bg/Plugins.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
return [
|
||||
'actions' => 'Действия',
|
||||
'active' => 'Активен',
|
||||
'configure' => 'Конфигуриране',
|
||||
'description' => 'Описание',
|
||||
'disable' => 'Деактивиране',
|
||||
'disable_failed' => 'Неуспешно деактивиране на приставката',
|
||||
'disabled' => 'Приставката е деактивирана успешно',
|
||||
'enable' => 'Активиране',
|
||||
'enable_failed' => 'Неуспешно активиране на приставката',
|
||||
'enabled' => 'Приставката е активирана успешно',
|
||||
'inactive' => 'Неактивен',
|
||||
'management' => 'Управление на приставки',
|
||||
'name' => 'Име на приставката',
|
||||
'no_config' => 'Тази приставка няма опции за конфигурация',
|
||||
'no_plugins_to_display' => 'Няма приставки за показване',
|
||||
'not_found' => 'Приставката не е намерена',
|
||||
'plugins' => 'Приставки',
|
||||
'settings_save_failed' => 'Неуспешно запазване на настройките на приставката',
|
||||
'settings_saved' => 'Настройките на приставката са запазени успешно',
|
||||
'status' => 'Статус',
|
||||
'uninstall' => 'Деинсталиране',
|
||||
'uninstall_failed' => 'Неуспешно деинсталиране на приставката',
|
||||
'uninstalled' => 'Приставката е деинсталирана успешно',
|
||||
'version' => 'Версия',
|
||||
];
|
||||
@@ -41,7 +41,7 @@ return [
|
||||
"customer_discount" => "Намаление",
|
||||
"customer_email" => "Електронна поща",
|
||||
"customer_location" => "Местоположение",
|
||||
"customer_mailchimp_status" => "Състояние на Mailchimp",
|
||||
"mailchimp_customer_status" => "Състояние на Mailchimp",
|
||||
"customer_optional" => "(Незадължително)",
|
||||
"customer_required" => "(Задължително)",
|
||||
"customer_total" => "Обща сума",
|
||||
|
||||
@@ -166,8 +166,6 @@ return [
|
||||
"info" => "Informacije",
|
||||
"info_configuration" => "Info o web trgovini",
|
||||
"input_groups" => "Grupe unosa",
|
||||
"integrations" => "Integracije",
|
||||
"integrations_configuration" => "Integracije trećih strana",
|
||||
"invoice" => "Faktura",
|
||||
"invoice_configuration" => "Podešavanja štamapnja",
|
||||
"invoice_default_comments" => "Komentar na fakturi",
|
||||
@@ -198,13 +196,6 @@ return [
|
||||
"location_info" => "Informacije o konfiguraciji lokacije",
|
||||
"login_form" => "Stil formulara za prijavu",
|
||||
"logout" => "Zar ne želite da napravite rezervnu kopiju prije odjave? Kliknite [OK] za sigurnosnu kopiju, [Cancel] da biste se odjavili.",
|
||||
"mailchimp" => "MeilChimp",
|
||||
"mailchimp_api_key" => "MailChimp API ključ",
|
||||
"mailchimp_configuration" => "MailChimp konfiguracija",
|
||||
"mailchimp_key_successfully" => "API ključ je važeći.",
|
||||
"mailchimp_key_unsuccessfully" => "API ključ je nevažeći.",
|
||||
"mailchimp_lists" => "MailChimp lista(e)",
|
||||
"mailchimp_tooltip" => "Kliknite na ikonu za API ključ.",
|
||||
"message" => "Poruke",
|
||||
"message_configuration" => "Konfigurisanje poruke",
|
||||
"msg_msg" => "Snimljena tekst poruka",
|
||||
|
||||
@@ -28,16 +28,6 @@ return [
|
||||
"employee" => "Zaposlenik",
|
||||
"error_adding_updating" => "Dodavanje ili ažuriranje kupca nije uspjelo.",
|
||||
"import_items_csv" => "Uvezi kupce iz CSV datoteke",
|
||||
"mailchimp_activity_click" => "Klik na e-mail",
|
||||
"mailchimp_activity_lastopen" => "Zadnji otvoreni e-mail",
|
||||
"mailchimp_activity_open" => "E-mail otvoren",
|
||||
"mailchimp_activity_total" => "E-mail poslat",
|
||||
"mailchimp_activity_unopen" => "E-mail nije otvoren",
|
||||
"mailchimp_email_client" => "E-mail klijenta",
|
||||
"mailchimp_info" => "MeilChimp",
|
||||
"mailchimp_member_rating" => "Ocjena",
|
||||
"mailchimp_status" => "Status",
|
||||
"mailchimp_vip" => "VIP",
|
||||
"max" => "Maks. potrošeno",
|
||||
"min" => "Min. potrošeno",
|
||||
"new" => "Novi kupac",
|
||||
|
||||
@@ -32,6 +32,7 @@ return [
|
||||
"migrate_desc" => "Ažurirajte OSPOS bazu podataka.",
|
||||
"office" => "Administracija",
|
||||
"office_desc" => "Lista modula kancelarijskog menija.",
|
||||
'plugins' => 'Dodaci',
|
||||
"receivings" => "Ulazi",
|
||||
"receivings_desc" => "Obrada narudžbenica.",
|
||||
"reports" => "Izvještaji",
|
||||
|
||||
27
app/Language/bs/Plugins.php
Normal file
27
app/Language/bs/Plugins.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
return [
|
||||
'actions' => 'Akcije',
|
||||
'active' => 'Aktivno',
|
||||
'configure' => 'Konfiguracija',
|
||||
'description' => 'Opis',
|
||||
'disable' => 'Onemogući',
|
||||
'disable_failed' => 'Nije uspjelo onemogućavanje dodatka',
|
||||
'disabled' => 'Dodatak je uspješno onemogućen',
|
||||
'enable' => 'Omogući',
|
||||
'enable_failed' => 'Nije uspjelo omogućavanje dodatka',
|
||||
'enabled' => 'Dodatak je uspješno omogućen',
|
||||
'inactive' => 'Neaktivno',
|
||||
'management' => 'Upravljanje dodacima',
|
||||
'name' => 'Naziv dodatka',
|
||||
'no_config' => 'Ovaj dodatak nema opcija konfiguracije',
|
||||
'no_plugins_to_display' => 'Nema dodataka za prikaz',
|
||||
'not_found' => 'Dodatak nije pronađen',
|
||||
'plugins' => 'Dodaci',
|
||||
'settings_save_failed' => 'Nije uspjelo spremanje postavki dodatka',
|
||||
'settings_saved' => 'Postavke dodatka su uspješno sačuvane',
|
||||
'status' => 'Status',
|
||||
'uninstall' => 'Deinstaliraj',
|
||||
'uninstall_failed' => 'Nije uspjelo deinstaliranje dodatka',
|
||||
'uninstalled' => 'Dodatak je uspješno deinstaliran',
|
||||
'version' => 'Verzija',
|
||||
];
|
||||
@@ -41,7 +41,6 @@ return [
|
||||
"customer_discount" => "Popust",
|
||||
"customer_email" => "E-mail kupca",
|
||||
"customer_location" => "Mjesto kupca",
|
||||
"customer_mailchimp_status" => "Status MailChimp-a",
|
||||
"customer_optional" => "(Potrebno za odloženo plaćanje)",
|
||||
"customer_required" => "Obavezno",
|
||||
"customer_total" => "Ukupno",
|
||||
|
||||
@@ -166,8 +166,8 @@ return [
|
||||
'info' => "زانیاری",
|
||||
'info_configuration' => "زانیاری فڕۆشتگا",
|
||||
'input_groups' => "گروپەکانی زانیارییە پێدراوەکان",
|
||||
'integrations' => "یەکگرتنەکان",
|
||||
'integrations_configuration' => "یەکگرتنەکانی لایەنی سێیەم",
|
||||
'plugins' => "یەکگرتنەکان",
|
||||
'plugins_configuration' => "یەکگرتنەکانی لایەنی سێیەم",
|
||||
'invoice' => "فاکتۆرە",
|
||||
'invoice_configuration' => "ڕێکخستنەکانی چاپی فاکتورە",
|
||||
'invoice_default_comments' => "سەرنجەکانی فاکتۆرەی بنەڕەتیی",
|
||||
@@ -198,13 +198,6 @@ return [
|
||||
'location_info' => "زانیاری ڕێکخستنی شوێن",
|
||||
'login_form' => "ستایلی فۆڕمی چوونەژوورەوە",
|
||||
'logout' => "دەتەوێت پاڵپشت دروست بکەیت پێش چوونە دەرەوە؟ کرتە بکە لەسەر [باشە] بۆ پاڵپشت دروستکردن یان [هەڵوەشاندنەوە] بۆ چوونە دەرەوە.",
|
||||
'mailchimp' => "مەیڵچیمپ",
|
||||
'mailchimp_api_key' => "کلیلی (ئەی پی ئای)ی مەیڵچیمپ",
|
||||
'mailchimp_configuration' => "ڕێکخستنی مەیڵچیمپ",
|
||||
'mailchimp_key_successfully' => "کلیلی (ئەی پی ئای) دروستە.",
|
||||
'mailchimp_key_unsuccessfully' => "کلیلی (ئەی پی ئای) نادروستە.",
|
||||
'mailchimp_lists' => "لیست(ەکان)ی مەیڵچیمپ",
|
||||
'mailchimp_tooltip' => "کرتە لەسەر ئایکۆنی کلیلی (ئەی پی ئای) بکە.",
|
||||
'message' => "نامە",
|
||||
'message_configuration' => "ڕێکخستنی نامە",
|
||||
'msg_msg' => "دەقی نامەی پاشەکەوتکراو",
|
||||
|
||||
@@ -28,16 +28,6 @@ return [
|
||||
'employee' => "فەرمانبەر",
|
||||
'error_adding_updating' => "زیادکردن یان نوێکردنەوەی کڕیار سەرکەوتوو نەبوو.",
|
||||
'import_items_csv' => "هاوردەکردنی کڕیار لەڕێگایCSV",
|
||||
'mailchimp_activity_click' => "کرتەی ئیمەیل",
|
||||
'mailchimp_activity_lastopen' => "دوایین ئیمەیڵی کراوە",
|
||||
'mailchimp_activity_open' => "ئیمەیڵ کرایەوە",
|
||||
'mailchimp_activity_total' => "ئیمەیڵ نێردرا",
|
||||
'mailchimp_activity_unopen' => "ئیمەیڵ نەکراوە",
|
||||
'mailchimp_email_client' => "کڕیاری ئیمەیل",
|
||||
'mailchimp_info' => "مەیڵچیمپ",
|
||||
'mailchimp_member_rating' => "پلەپێدان",
|
||||
'mailchimp_status' => "دۆخ",
|
||||
'mailchimp_vip' => "ڤی ئای پی",
|
||||
'max' => "زۆرترین. خەرجکراو",
|
||||
'min' => "کەمترین. خەرجکراو",
|
||||
'new' => "کڕیاری نوێ",
|
||||
|
||||
@@ -32,6 +32,7 @@ return [
|
||||
'migrate_desc' => "داتابەیسی OSPOS نوێ بکەرەوە.",
|
||||
'office' => "ئۆفیس",
|
||||
'office_desc' => "لیستی مۆدۆلی پێڕستی ئۆفیس نیشان بدە.",
|
||||
'plugins' => 'پڵەگینەکان',
|
||||
'receivings' => "وەرگرتنەکان",
|
||||
'receivings_desc' => "پرۆسەی داواکاری کڕین.",
|
||||
'reports' => "ڕاپۆرتەکان",
|
||||
|
||||
27
app/Language/ckb/Plugins.php
Normal file
27
app/Language/ckb/Plugins.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
return [
|
||||
'actions' => 'کردارەکان',
|
||||
'active' => 'چالاک',
|
||||
'configure' => 'ڕێکخستن',
|
||||
'description' => 'وەسف',
|
||||
'disable' => 'ناچالاک کردن',
|
||||
'disable_failed' => 'ناکامی لە ناچالاک کردنی پڵەگین',
|
||||
'disabled' => 'پڵەگین بە سەرکەوتوویی ناچالاک کرا',
|
||||
'enable' => 'چالاک کردن',
|
||||
'enable_failed' => 'ناکامی لە چالاک کردنی پڵەگین',
|
||||
'enabled' => 'پڵەگین بە سەرکەوتوویی چالاک کرا',
|
||||
'inactive' => 'ناچالاک',
|
||||
'management' => 'بەڕێوەبردنی پڵەگین',
|
||||
'name' => 'ناوی پڵەگین',
|
||||
'no_config' => 'ئەم پڵەگینە هیچ بژاردەیەکی ڕێکخستن نییە',
|
||||
'no_plugins_to_display' => 'هیچ پڵەگینێک بۆ نیشاندان نییە',
|
||||
'not_found' => 'پڵەگین نەدۆزرایەوە',
|
||||
'plugins' => 'پڵەگینەکان',
|
||||
'settings_save_failed' => 'ناکامی لە پاشەکەوت کردنی ڕێکخستنەکانی پڵەگین',
|
||||
'settings_saved' => 'ڕێکخستنەکانی پڵەگین بە سەرکەوتوویی پاشەکەوت کران',
|
||||
'status' => 'باری',
|
||||
'uninstall' => 'لادانی دامەزراندن',
|
||||
'uninstall_failed' => 'ناکامی لە لادانی دامەزراندنی پڵەگین',
|
||||
'uninstalled' => 'پڵەگین بە سەرکەوتوویی لادرا',
|
||||
'version' => 'وەشان',
|
||||
];
|
||||
@@ -41,7 +41,6 @@ return [
|
||||
'customer_discount' => "داشکاندن",
|
||||
'customer_email' => "ئیمەیڵ",
|
||||
'customer_location' => "ناونیشان",
|
||||
'customer_mailchimp_status' => "دۆخی بەکارهێنان مایلچیمپ",
|
||||
'customer_optional' => "(پێویستە بۆئەو پارانەی دەبێت بدرێت)",
|
||||
'customer_required' => "(پێویستە)",
|
||||
'customer_total' => "کۆی گشتی",
|
||||
|
||||
@@ -166,8 +166,6 @@ return [
|
||||
"info" => "",
|
||||
"info_configuration" => "",
|
||||
"input_groups" => "",
|
||||
"integrations" => "",
|
||||
"integrations_configuration" => "",
|
||||
"invoice" => "",
|
||||
"invoice_configuration" => "",
|
||||
"invoice_default_comments" => "",
|
||||
@@ -198,13 +196,6 @@ return [
|
||||
"location_info" => "",
|
||||
"login_form" => "",
|
||||
"logout" => "",
|
||||
"mailchimp" => "",
|
||||
"mailchimp_api_key" => "",
|
||||
"mailchimp_configuration" => "",
|
||||
"mailchimp_key_successfully" => "",
|
||||
"mailchimp_key_unsuccessfully" => "",
|
||||
"mailchimp_lists" => "",
|
||||
"mailchimp_tooltip" => "",
|
||||
"message" => "",
|
||||
"message_configuration" => "",
|
||||
"msg_msg" => "",
|
||||
|
||||
@@ -28,16 +28,6 @@ return [
|
||||
"employee" => "Zaměstnanec",
|
||||
"error_adding_updating" => "Chyba při vytváření nebo aktualizaci zákazníka.",
|
||||
"import_items_csv" => "Import zákazníků z CSV",
|
||||
"mailchimp_activity_click" => "",
|
||||
"mailchimp_activity_lastopen" => "Poslední otevřený email",
|
||||
"mailchimp_activity_open" => "",
|
||||
"mailchimp_activity_total" => "",
|
||||
"mailchimp_activity_unopen" => "",
|
||||
"mailchimp_email_client" => "",
|
||||
"mailchimp_info" => "",
|
||||
"mailchimp_member_rating" => "Hodnocení",
|
||||
"mailchimp_status" => "",
|
||||
"mailchimp_vip" => "VIP",
|
||||
"max" => "",
|
||||
"min" => "",
|
||||
"new" => "",
|
||||
|
||||
@@ -32,6 +32,7 @@ return [
|
||||
"migrate_desc" => "Aktualizovat databázi OSPOS.",
|
||||
"office" => "Správa",
|
||||
"office_desc" => "Seznam modulů pro správu.",
|
||||
'plugins' => 'Doplňky',
|
||||
"receivings" => "Příjem zboží",
|
||||
"receivings_desc" => "",
|
||||
"reports" => "Sestavy",
|
||||
|
||||
27
app/Language/cs/Plugins.php
Normal file
27
app/Language/cs/Plugins.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
return [
|
||||
'actions' => 'Akce',
|
||||
'active' => 'Aktivní',
|
||||
'configure' => 'Konfigurovat',
|
||||
'description' => 'Popis',
|
||||
'disable' => 'Deaktivovat',
|
||||
'disable_failed' => 'Deaktivace pluginu se nezdařila',
|
||||
'disabled' => 'Plugin byl úspěšně deaktivován',
|
||||
'enable' => 'Aktivovat',
|
||||
'enable_failed' => 'Aktivace pluginu se nezdařila',
|
||||
'enabled' => 'Plugin byl úspěšně aktivován',
|
||||
'inactive' => 'Neaktivní',
|
||||
'management' => 'Správa pluginů',
|
||||
'name' => 'Název pluginu',
|
||||
'no_config' => 'Tento plugin nemá žádné možnosti konfigurace',
|
||||
'no_plugins_to_display' => 'Žádné pluginy k zobrazení',
|
||||
'not_found' => 'Plugin nebyl nalezen',
|
||||
'plugins' => 'Pluginy',
|
||||
'settings_save_failed' => 'Uložení nastavení pluginu se nezdařilo',
|
||||
'settings_saved' => 'Nastavení pluginu bylo úspěšně uloženo',
|
||||
'status' => 'Stav',
|
||||
'uninstall' => 'Odinstalovat',
|
||||
'uninstall_failed' => 'Odinstalace pluginu se nezdařila',
|
||||
'uninstalled' => 'Plugin byl úspěšně odinstalován',
|
||||
'version' => 'Verze',
|
||||
];
|
||||
@@ -41,7 +41,6 @@ return [
|
||||
"customer_discount" => "Sleva",
|
||||
"customer_email" => "Email",
|
||||
"customer_location" => "Místo",
|
||||
"customer_mailchimp_status" => "Stav mailchimp",
|
||||
"customer_optional" => "(Volitelné)",
|
||||
"customer_required" => "(Vyžadováno)",
|
||||
"customer_total" => "Celkem",
|
||||
|
||||
@@ -166,8 +166,6 @@ return [
|
||||
"info" => "Information",
|
||||
"info_configuration" => "Store Information",
|
||||
"input_groups" => "",
|
||||
"integrations" => "Integrations",
|
||||
"integrations_configuration" => "Third Party Integrations",
|
||||
"invoice" => "Invoice",
|
||||
"invoice_configuration" => "Invoice Print Settings",
|
||||
"invoice_default_comments" => "Default Invoice Comments",
|
||||
@@ -198,13 +196,6 @@ return [
|
||||
"location_info" => "Location Configuration Information",
|
||||
"login_form" => "",
|
||||
"logout" => "Do you want to make a backup before logging out? Click [OK] to backup or [Cancel] to logout.",
|
||||
"mailchimp" => "Mailchimp",
|
||||
"mailchimp_api_key" => "Mailchimp API Key",
|
||||
"mailchimp_configuration" => "Mailchimp Configuration",
|
||||
"mailchimp_key_successfully" => "API Key is valid.",
|
||||
"mailchimp_key_unsuccessfully" => "API Key is invalid.",
|
||||
"mailchimp_lists" => "Mailchimp List(s)",
|
||||
"mailchimp_tooltip" => "Click the icon for an API Key.",
|
||||
"message" => "Message",
|
||||
"message_configuration" => "Message Configuration",
|
||||
"msg_msg" => "Saved Text Message",
|
||||
|
||||
@@ -28,16 +28,6 @@ return [
|
||||
"employee" => "Employee",
|
||||
"error_adding_updating" => "Customer add or update failed.",
|
||||
"import_items_csv" => "Customer Import from CSV",
|
||||
"mailchimp_activity_click" => "Email click",
|
||||
"mailchimp_activity_lastopen" => "Last open email",
|
||||
"mailchimp_activity_open" => "Email open",
|
||||
"mailchimp_activity_total" => "Email sent",
|
||||
"mailchimp_activity_unopen" => "Email unopen",
|
||||
"mailchimp_email_client" => "Email client",
|
||||
"mailchimp_info" => "Mailchimp",
|
||||
"mailchimp_member_rating" => "Rating",
|
||||
"mailchimp_status" => "Status",
|
||||
"mailchimp_vip" => "VIP",
|
||||
"max" => "Max. spent",
|
||||
"min" => "Min. spent",
|
||||
"new" => "New Customer",
|
||||
|
||||
@@ -32,6 +32,7 @@ return [
|
||||
"migrate_desc" => "",
|
||||
"office" => "",
|
||||
"office_desc" => "",
|
||||
'plugins' => 'Plugins',
|
||||
"receivings" => "",
|
||||
"receivings_desc" => "",
|
||||
"reports" => "",
|
||||
|
||||
27
app/Language/da/Plugins.php
Normal file
27
app/Language/da/Plugins.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
return [
|
||||
'actions' => 'Handlinger',
|
||||
'active' => 'Aktiv',
|
||||
'configure' => 'Konfigurer',
|
||||
'description' => 'Beskrivelse',
|
||||
'disable' => 'Deaktiver',
|
||||
'disable_failed' => 'Deaktivering af plugin mislykkedes',
|
||||
'disabled' => 'Plugin blev deaktiveret',
|
||||
'enable' => 'Aktiver',
|
||||
'enable_failed' => 'Aktivering af plugin mislykkedes',
|
||||
'enabled' => 'Plugin blev aktiveret',
|
||||
'inactive' => 'Inaktiv',
|
||||
'management' => 'Plugin-administration',
|
||||
'name' => 'Plugin-navn',
|
||||
'no_config' => 'Dette plugin har ingen konfigurationsmuligheder',
|
||||
'no_plugins_to_display' => 'Ingen plugins at vise',
|
||||
'not_found' => 'Plugin ikke fundet',
|
||||
'plugins' => 'Plugins',
|
||||
'settings_save_failed' => 'Gemning af plugin-indstillinger mislykkedes',
|
||||
'settings_saved' => 'Plugin-indstillinger blev gemt',
|
||||
'status' => 'Status',
|
||||
'uninstall' => 'Afinstaller',
|
||||
'uninstall_failed' => 'Afinstallation af plugin mislykkedes',
|
||||
'uninstalled' => 'Plugin blev afinstalleret',
|
||||
'version' => 'Version',
|
||||
];
|
||||
@@ -41,7 +41,6 @@ return [
|
||||
"customer_discount" => "Rabat",
|
||||
"customer_email" => "",
|
||||
"customer_location" => "",
|
||||
"customer_mailchimp_status" => "",
|
||||
"customer_optional" => "",
|
||||
"customer_required" => "",
|
||||
"customer_total" => "",
|
||||
|
||||
@@ -166,8 +166,6 @@ return [
|
||||
"info" => "Instellungen",
|
||||
"info_configuration" => "Instellungen",
|
||||
"input_groups" => "",
|
||||
"integrations" => "",
|
||||
"integrations_configuration" => "",
|
||||
"invoice" => "Rechnungs",
|
||||
"invoice_configuration" => "Druckereinstellungen",
|
||||
"invoice_default_comments" => "Rechnungskommentar",
|
||||
@@ -198,13 +196,6 @@ return [
|
||||
"location_info" => "Lagerort-Information",
|
||||
"login_form" => "",
|
||||
"logout" => "Wollen Sie eine Sicherung machen vor dem Beenden? Klicke [OK] für Sicherung",
|
||||
"mailchimp" => "",
|
||||
"mailchimp_api_key" => "",
|
||||
"mailchimp_configuration" => "",
|
||||
"mailchimp_key_successfully" => "",
|
||||
"mailchimp_key_unsuccessfully" => "",
|
||||
"mailchimp_lists" => "",
|
||||
"mailchimp_tooltip" => "",
|
||||
"message" => "Message",
|
||||
"message_configuration" => "Message Configuration",
|
||||
"msg_msg" => "Saved Text Message",
|
||||
|
||||
@@ -28,16 +28,6 @@ return [
|
||||
"employee" => "",
|
||||
"error_adding_updating" => "Fehler beim Hinzufügen/Ändern",
|
||||
"import_items_csv" => "Importiere Kunden via CSV",
|
||||
"mailchimp_activity_click" => "",
|
||||
"mailchimp_activity_lastopen" => "",
|
||||
"mailchimp_activity_open" => "",
|
||||
"mailchimp_activity_total" => "",
|
||||
"mailchimp_activity_unopen" => "",
|
||||
"mailchimp_email_client" => "",
|
||||
"mailchimp_info" => "",
|
||||
"mailchimp_member_rating" => "",
|
||||
"mailchimp_status" => "",
|
||||
"mailchimp_vip" => "",
|
||||
"max" => "",
|
||||
"min" => "",
|
||||
"new" => "Neuer Kunde",
|
||||
|
||||
@@ -32,6 +32,7 @@ return [
|
||||
"migrate_desc" => "",
|
||||
"office" => "",
|
||||
"office_desc" => "",
|
||||
'plugins' => 'Plugins',
|
||||
"receivings" => "Eingänge",
|
||||
"receivings_desc" => "Hinzufügen, Ändern, Löschen und Suchen",
|
||||
"reports" => "Berichte",
|
||||
|
||||
27
app/Language/de-CH/Plugins.php
Normal file
27
app/Language/de-CH/Plugins.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
return [
|
||||
'actions' => 'Aktionen',
|
||||
'active' => 'Aktiv',
|
||||
'configure' => 'Konfigurieren',
|
||||
'description' => 'Beschreibung',
|
||||
'disable' => 'Deaktivieren',
|
||||
'disable_failed' => 'Plugin konnte nicht deaktiviert werden',
|
||||
'disabled' => 'Plugin erfolgreich deaktiviert',
|
||||
'enable' => 'Aktivieren',
|
||||
'enable_failed' => 'Plugin konnte nicht aktiviert werden',
|
||||
'enabled' => 'Plugin erfolgreich aktiviert',
|
||||
'inactive' => 'Inaktiv',
|
||||
'management' => 'Plugin-Verwaltung',
|
||||
'name' => 'Plugin-Name',
|
||||
'no_config' => 'Dieses Plugin hat keine Konfigurationsoptionen',
|
||||
'no_plugins_to_display' => 'Keine Plugins anzuzeigen',
|
||||
'not_found' => 'Plugin nicht gefunden',
|
||||
'plugins' => 'Plugins',
|
||||
'settings_save_failed' => 'Plugin-Einstellungen konnten nicht gespeichert werden',
|
||||
'settings_saved' => 'Plugin-Einstellungen erfolgreich gespeichert',
|
||||
'status' => 'Status',
|
||||
'uninstall' => 'Deinstallieren',
|
||||
'uninstall_failed' => 'Plugin konnte nicht deinstalliert werden',
|
||||
'uninstalled' => 'Plugin erfolgreich deinstalliert',
|
||||
'version' => 'Version',
|
||||
];
|
||||
@@ -41,7 +41,6 @@ return [
|
||||
"customer_discount" => "Discount",
|
||||
"customer_email" => "Customer Email",
|
||||
"customer_location" => "Customer Location",
|
||||
"customer_mailchimp_status" => "",
|
||||
"customer_optional" => "",
|
||||
"customer_required" => "",
|
||||
"customer_total" => "Total",
|
||||
|
||||
@@ -166,8 +166,6 @@ return [
|
||||
"info" => "Informationen",
|
||||
"info_configuration" => "Generelle Einstellungen",
|
||||
"input_groups" => "",
|
||||
"integrations" => "Integrationen",
|
||||
"integrations_configuration" => "Drittanbieter Integrationen",
|
||||
"invoice" => "Rechnungs",
|
||||
"invoice_configuration" => "Druckereinstellungen",
|
||||
"invoice_default_comments" => "Rechnungskommentar",
|
||||
@@ -198,14 +196,7 @@ return [
|
||||
"location_info" => "Lagerort-Information",
|
||||
"login_form" => "",
|
||||
"logout" => "Wollen Sie vor dem Beenden eine Sicherung erstellen? Klicke [OK] für Sicherung.",
|
||||
"mailchimp" => "Mailchimp",
|
||||
"mailchimp_api_key" => "Mailchimp API Schlüssel",
|
||||
"mailchimp_configuration" => "Mailchimp Konfiguration",
|
||||
"mailchimp_key_successfully" => "API Key ist gültig.",
|
||||
"mailchimp_key_unsuccessfully" => "API Key ist ungültig.",
|
||||
"mailchimp_lists" => "Mailchimp Liste(n)",
|
||||
"mailchimp_tooltip" => "Icon anklicken um API Key zu erhalten.",
|
||||
"message" => "Nachricht",
|
||||
"message" => "Nachricht",
|
||||
"message_configuration" => "Nachrichtenkonfiguration",
|
||||
"msg_msg" => "Gespeicherte Nachricht",
|
||||
"msg_msg_placeholder" => "Wenn Sie eine SMS Vorlage benutzen wollen, geben Sie diese hier ein, ansonsten lassen Sie dieses Feld frei.",
|
||||
|
||||
@@ -28,16 +28,6 @@ return [
|
||||
"employee" => "Mitarbeiter",
|
||||
"error_adding_updating" => "Fehler beim Hinzufügen/Ändern.",
|
||||
"import_items_csv" => "Importiere Kunden via CSV",
|
||||
"mailchimp_activity_click" => "E-Mail klick",
|
||||
"mailchimp_activity_lastopen" => "Letzte geöffnet E-Mail",
|
||||
"mailchimp_activity_open" => "E-Mail geöffnet",
|
||||
"mailchimp_activity_total" => "E-Mail gesendet",
|
||||
"mailchimp_activity_unopen" => "E-Mail ungeöffnet",
|
||||
"mailchimp_email_client" => "E-Mail Client",
|
||||
"mailchimp_info" => "Mailchimp",
|
||||
"mailchimp_member_rating" => "Bewertung",
|
||||
"mailchimp_status" => "Status",
|
||||
"mailchimp_vip" => "VIP",
|
||||
"max" => "Maximal Ausgegeben",
|
||||
"min" => "Minimal Ausgegeben",
|
||||
"new" => "Neuer Kunde",
|
||||
|
||||
@@ -32,6 +32,7 @@ return [
|
||||
"migrate_desc" => "Aktualisiere die OSPOS-Datenbank.",
|
||||
"office" => "Verwaltung",
|
||||
"office_desc" => "Auflistung der Module für das Verwaltungs-Menü.",
|
||||
'plugins' => 'Plugins',
|
||||
"receivings" => "Eingänge",
|
||||
"receivings_desc" => "Hinzufügen, Ändern, Löschen und Suchen von Bestellungen.",
|
||||
"reports" => "Berichte",
|
||||
|
||||
27
app/Language/de-DE/Plugins.php
Normal file
27
app/Language/de-DE/Plugins.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
return [
|
||||
'actions' => 'Aktionen',
|
||||
'active' => 'Aktiv',
|
||||
'configure' => 'Konfigurieren',
|
||||
'description' => 'Beschreibung',
|
||||
'disable' => 'Deaktivieren',
|
||||
'disable_failed' => 'Plugin konnte nicht deaktiviert werden',
|
||||
'disabled' => 'Plugin erfolgreich deaktiviert',
|
||||
'enable' => 'Aktivieren',
|
||||
'enable_failed' => 'Plugin konnte nicht aktiviert werden',
|
||||
'enabled' => 'Plugin erfolgreich aktiviert',
|
||||
'inactive' => 'Inaktiv',
|
||||
'management' => 'Plugin-Verwaltung',
|
||||
'name' => 'Plugin-Name',
|
||||
'no_config' => 'Dieses Plugin hat keine Konfigurationsoptionen',
|
||||
'no_plugins_to_display' => 'Keine Plugins anzuzeigen',
|
||||
'not_found' => 'Plugin nicht gefunden',
|
||||
'plugins' => 'Plugins',
|
||||
'settings_save_failed' => 'Plugin-Einstellungen konnten nicht gespeichert werden',
|
||||
'settings_saved' => 'Plugin-Einstellungen erfolgreich gespeichert',
|
||||
'status' => 'Status',
|
||||
'uninstall' => 'Deinstallieren',
|
||||
'uninstall_failed' => 'Plugin konnte nicht deinstalliert werden',
|
||||
'uninstalled' => 'Plugin erfolgreich deinstalliert',
|
||||
'version' => 'Version',
|
||||
];
|
||||
@@ -41,7 +41,6 @@ return [
|
||||
"customer_discount" => "Rabatt",
|
||||
"customer_email" => "Kunden eMail",
|
||||
"customer_location" => "Kunden Stadt",
|
||||
"customer_mailchimp_status" => "Mailchim Status",
|
||||
"customer_optional" => "(Benötigt für fällige Zahlungen)",
|
||||
"customer_required" => "(Benötigt)",
|
||||
"customer_total" => "Gesamtbetrag",
|
||||
|
||||
@@ -166,8 +166,6 @@ return [
|
||||
"info" => "",
|
||||
"info_configuration" => "",
|
||||
"input_groups" => "",
|
||||
"integrations" => "",
|
||||
"integrations_configuration" => "",
|
||||
"invoice" => "",
|
||||
"invoice_configuration" => "",
|
||||
"invoice_default_comments" => "",
|
||||
@@ -198,13 +196,6 @@ return [
|
||||
"location_info" => "",
|
||||
"login_form" => "",
|
||||
"logout" => "",
|
||||
"mailchimp" => "",
|
||||
"mailchimp_api_key" => "",
|
||||
"mailchimp_configuration" => "",
|
||||
"mailchimp_key_successfully" => "",
|
||||
"mailchimp_key_unsuccessfully" => "",
|
||||
"mailchimp_lists" => "",
|
||||
"mailchimp_tooltip" => "",
|
||||
"message" => "",
|
||||
"message_configuration" => "",
|
||||
"msg_msg" => "",
|
||||
|
||||
@@ -28,16 +28,6 @@ return [
|
||||
"employee" => "",
|
||||
"error_adding_updating" => "",
|
||||
"import_items_csv" => "",
|
||||
"mailchimp_activity_click" => "",
|
||||
"mailchimp_activity_lastopen" => "",
|
||||
"mailchimp_activity_open" => "",
|
||||
"mailchimp_activity_total" => "",
|
||||
"mailchimp_activity_unopen" => "",
|
||||
"mailchimp_email_client" => "",
|
||||
"mailchimp_info" => "",
|
||||
"mailchimp_member_rating" => "",
|
||||
"mailchimp_status" => "",
|
||||
"mailchimp_vip" => "",
|
||||
"max" => "",
|
||||
"min" => "",
|
||||
"new" => "",
|
||||
|
||||
@@ -32,6 +32,7 @@ return [
|
||||
"migrate_desc" => "",
|
||||
"office" => "",
|
||||
"office_desc" => "",
|
||||
'plugins' => 'Πρόσθετα',
|
||||
"receivings" => "",
|
||||
"receivings_desc" => "",
|
||||
"reports" => "",
|
||||
|
||||
27
app/Language/el/Plugins.php
Normal file
27
app/Language/el/Plugins.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
return [
|
||||
'actions' => 'Ενέργειες',
|
||||
'active' => 'Ενεργό',
|
||||
'configure' => 'Διαμόρφωση',
|
||||
'description' => 'Περιγραφή',
|
||||
'disable' => 'Απενεργοποίηση',
|
||||
'disable_failed' => 'Η απενεργοποίηση της προσθήκης απέτυχε',
|
||||
'disabled' => 'Η προσθήκη απενεργοποιήθηκε επιτυχώς',
|
||||
'enable' => 'Ενεργοποίηση',
|
||||
'enable_failed' => 'Η ενεργοποίηση της προσθήκης απέτυχε',
|
||||
'enabled' => 'Η προσθήκη ενεργοποιήθηκε επιτυχώς',
|
||||
'inactive' => 'Ανενεργό',
|
||||
'management' => 'Διαχείριση Προσθηκών',
|
||||
'name' => 'Όνομα Προσθήκης',
|
||||
'no_config' => 'Αυτή η προσθήκη δεν έχει επιλογές διαμόρφωσης',
|
||||
'no_plugins_to_display' => 'Δεν υπάρχουν προσθήκες για εμφάνιση',
|
||||
'not_found' => 'Η προσθήκη δεν βρέθηκε',
|
||||
'plugins' => 'Προσθήκες',
|
||||
'settings_save_failed' => 'Η αποθήκευση των ρυθμίσεων της προσθήκης απέτυχε',
|
||||
'settings_saved' => 'Οι ρυθμίσεις της προσθήκης αποθηκεύτηκαν επιτυχώς',
|
||||
'status' => 'Κατάσταση',
|
||||
'uninstall' => 'Απεγκατάσταση',
|
||||
'uninstall_failed' => 'Η απεγκατάσταση της προσθήκης απέτυχε',
|
||||
'uninstalled' => 'Η προσθήκη απεγκαταστάθηκε επιτυχώς',
|
||||
'version' => 'Έκδοση',
|
||||
];
|
||||
@@ -41,7 +41,6 @@ return [
|
||||
"customer_discount" => "Έκπτωση",
|
||||
"customer_email" => "Διεύθυνση ηλεκτρονικού ταχυδρομείου",
|
||||
"customer_location" => "Τοποθεσία",
|
||||
"customer_mailchimp_status" => "Κατάσταση Mailchimp",
|
||||
"customer_optional" => "(Απαραίτητο για πληρωμές επί Πιστώσει)",
|
||||
"customer_required" => "(Απαραίτητο)",
|
||||
"customer_total" => "Σύνολο",
|
||||
|
||||
@@ -166,8 +166,6 @@ return [
|
||||
"info" => "Information",
|
||||
"info_configuration" => "Shop Information",
|
||||
"input_groups" => "Input Groups",
|
||||
"integrations" => "Integrations",
|
||||
"integrations_configuration" => "Third Party Integrations",
|
||||
"invoice" => "Invoice",
|
||||
"invoice_configuration" => "Invoice Print Settings",
|
||||
"invoice_default_comments" => "Default Invoice Comments",
|
||||
@@ -198,13 +196,6 @@ return [
|
||||
"location_info" => "Location Configuration Information",
|
||||
"login_form" => "Login Form Style",
|
||||
"logout" => "Don't you want to make a backup before logging out? Click [OK] to backup, [Cancel] to logout.",
|
||||
"mailchimp" => "MailChimp",
|
||||
"mailchimp_api_key" => "MailChimp API Key",
|
||||
"mailchimp_configuration" => "MailChimp Configuration",
|
||||
"mailchimp_key_successfully" => "Valid API Key.",
|
||||
"mailchimp_key_unsuccessfully" => "Invalid API Key.",
|
||||
"mailchimp_lists" => "MailChimp List(s)",
|
||||
"mailchimp_tooltip" => "Click the icon for an API key.",
|
||||
"message" => "Message",
|
||||
"message_configuration" => "Message Configuration",
|
||||
"msg_msg" => "Saved Text Message",
|
||||
|
||||
@@ -28,16 +28,6 @@ return [
|
||||
"employee" => "Employee",
|
||||
"error_adding_updating" => "Error adding/updating Customer.",
|
||||
"import_items_csv" => "Customer Import from CSV",
|
||||
"mailchimp_activity_click" => "Email click",
|
||||
"mailchimp_activity_lastopen" => "Last open email",
|
||||
"mailchimp_activity_open" => "Email open",
|
||||
"mailchimp_activity_total" => "Email sent",
|
||||
"mailchimp_activity_unopen" => "Email unopen",
|
||||
"mailchimp_email_client" => "Email client",
|
||||
"mailchimp_info" => "MailChimp",
|
||||
"mailchimp_member_rating" => "Rating",
|
||||
"mailchimp_status" => "Status",
|
||||
"mailchimp_vip" => "VIP",
|
||||
"max" => "Max spent",
|
||||
"min" => "Min spent",
|
||||
"new" => "New Customer",
|
||||
|
||||
@@ -32,6 +32,7 @@ return [
|
||||
"migrate_desc" => "Update the OSPOS Database.",
|
||||
"office" => "Office",
|
||||
"office_desc" => "List office menu modules.",
|
||||
'plugins' => 'Plugins',
|
||||
"receivings" => "Receivings",
|
||||
"receivings_desc" => "Process Purchase Orders.",
|
||||
"reports" => "Reports",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user