mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-05-25 08:44:42 -04:00
Compare commits
163 Commits
feature/ub
...
person_att
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fefd34864 | ||
|
|
e602eddb47 | ||
|
|
0a313aa09d | ||
|
|
12e3c7e31f | ||
|
|
de62e9f3bd | ||
|
|
97ca738b2d | ||
|
|
c714dd6f68 | ||
|
|
b6f28da058 | ||
|
|
165c3351eb | ||
|
|
905b58ca6e | ||
|
|
609b206375 | ||
|
|
6fec2464f8 | ||
|
|
332d8c8c69 | ||
|
|
577cf55b6a | ||
|
|
e70395bb85 | ||
|
|
7f9321eca0 | ||
|
|
71056d9b03 | ||
|
|
e17944d883 | ||
|
|
0ac427b2b1 | ||
|
|
3038f83a4a | ||
|
|
75f6ce3140 | ||
|
|
ce7a3ce341 | ||
|
|
d99d2855ec | ||
|
|
96b4b24d9b | ||
|
|
871231e406 | ||
|
|
e62477ed4e | ||
|
|
2a0997f267 | ||
|
|
1ca8effe08 | ||
|
|
ed2c975ad5 | ||
|
|
403feed3e5 | ||
|
|
7f6f36210c | ||
|
|
1121ced532 | ||
|
|
632a18212d | ||
|
|
3208f15244 | ||
|
|
079b809622 | ||
|
|
d685e09c29 | ||
|
|
149c27d60f | ||
|
|
57b7705cd4 | ||
|
|
e8951422c0 | ||
|
|
8afc57fcf4 | ||
|
|
7af64a9a21 | ||
|
|
46d5781498 | ||
|
|
66b61c0554 | ||
|
|
6b97131c48 | ||
|
|
a4c19a3c2c | ||
|
|
7ca8c9561a | ||
|
|
4fac5d9198 | ||
|
|
221995b6db | ||
|
|
91dbe5b869 | ||
|
|
afd908327b | ||
|
|
cfde66481d | ||
|
|
80f00c8552 | ||
|
|
dbdf4db4fb | ||
|
|
64004db271 | ||
|
|
7f20a5dd4c | ||
|
|
d7a276b488 | ||
|
|
57dbe43313 | ||
|
|
6f1c39d99e | ||
|
|
45902caa67 | ||
|
|
1fe865a100 | ||
|
|
90da63cb13 | ||
|
|
8da4aff262 | ||
|
|
0e9f4a998d | ||
|
|
85c7ce2da4 | ||
|
|
e723e2ddf4 | ||
|
|
71eb8de7fe | ||
|
|
9d5019e12e | ||
|
|
56670271d6 | ||
|
|
cef103445e | ||
|
|
68e9a56632 | ||
|
|
ba05536317 | ||
|
|
f74f286a51 | ||
|
|
7180ec33e8 | ||
|
|
496c8a8262 | ||
|
|
493d9cc9c1 | ||
|
|
f761e1464f | ||
|
|
a5bbb2bcc5 | ||
|
|
92ec321d08 | ||
|
|
e046e74c79 | ||
|
|
e0cd0f6129 | ||
|
|
3b102adf3f | ||
|
|
260358d611 | ||
|
|
e615200466 | ||
|
|
56cead478a | ||
|
|
7030f6bac3 | ||
|
|
299f62669a | ||
|
|
072865620a | ||
|
|
3bbd4c4c95 | ||
|
|
0253bf85b8 | ||
|
|
92c1be8bb1 | ||
|
|
23829eab35 | ||
|
|
c81c6506cb | ||
|
|
840d9ccc81 | ||
|
|
e763ee2acc | ||
|
|
8ef109efbc | ||
|
|
9a544096c2 | ||
|
|
3e4ac0b24d | ||
|
|
3c9c592ca3 | ||
|
|
a4d8bedbf3 | ||
|
|
c4304fd0a9 | ||
|
|
44fe2c087a | ||
|
|
985c1c55ce | ||
|
|
8029e5538f | ||
|
|
1a7683a8ac | ||
|
|
e4b92b58c3 | ||
|
|
dc1e448bc3 | ||
|
|
24b2825b31 | ||
|
|
38d672592b | ||
|
|
6f7e06e986 | ||
|
|
fda40d9340 | ||
|
|
b49186ec7c | ||
|
|
8b56f61b8a | ||
|
|
9820beb0e1 | ||
|
|
e01dad728f | ||
|
|
234f930079 | ||
|
|
3001dc0e17 | ||
|
|
3ba207e8b9 | ||
|
|
d684c49ebd | ||
|
|
071e641f95 | ||
|
|
48af67bd00 | ||
|
|
7cb1d95da7 | ||
|
|
bafe3ddf1b | ||
|
|
c482e75304 | ||
|
|
afc2f82dc6 | ||
|
|
ce411707b4 | ||
|
|
37c6e22fc4 | ||
|
|
3c7ece5c33 | ||
|
|
02fccaf43f | ||
|
|
ee4d44ed39 | ||
|
|
fa3f257e7b | ||
|
|
431a9951e9 | ||
|
|
f7e8d6e427 | ||
|
|
85889b6e65 | ||
|
|
6818f02ef9 | ||
|
|
436696b11b | ||
|
|
9a2b308647 | ||
|
|
1f55d96580 | ||
|
|
b2fadea44a | ||
|
|
0fdb3ba37b | ||
|
|
d7b2264ac1 | ||
|
|
a229bf6031 | ||
|
|
977fa5647b | ||
|
|
52b0a83190 | ||
|
|
f25a0f5b09 | ||
|
|
f0f288797a | ||
|
|
63083a0946 | ||
|
|
3a33098776 | ||
|
|
ca6a1b35af | ||
|
|
418580a52d | ||
|
|
31d25e06dc | ||
|
|
b1819b3b36 | ||
|
|
6705420373 | ||
|
|
d6b767c80a | ||
|
|
19eb43270a | ||
|
|
df4549bb0b | ||
|
|
bdc965be23 | ||
|
|
5c8905aa1b | ||
|
|
690f43578d | ||
|
|
0858a1c23c | ||
|
|
3c217bbddd | ||
|
|
87a0606141 | ||
|
|
b6a90f7880 | ||
|
|
b93359bcaf |
@@ -1,23 +1,56 @@
|
||||
node_modules
|
||||
tmp
|
||||
# Version control
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Sensitive config (user may mount their own)
|
||||
app/Config/Email.php
|
||||
|
||||
# Build artifacts
|
||||
node_modules/
|
||||
dist/
|
||||
tmp/
|
||||
*.patch
|
||||
patches/
|
||||
|
||||
# IDE and editor files
|
||||
.idea/
|
||||
git-svn-diff.py
|
||||
*.bash
|
||||
.vscode/
|
||||
.swp
|
||||
*.swp
|
||||
.buildpath
|
||||
.project
|
||||
.settings/*
|
||||
.git
|
||||
dist/
|
||||
node_modules/
|
||||
*.swp
|
||||
.settings/
|
||||
|
||||
# Development tools and configs
|
||||
tests/
|
||||
phpunit.xml
|
||||
.php-cs-fixer.*
|
||||
phpstan.neon
|
||||
*.bash
|
||||
git-svn-diff.py
|
||||
|
||||
# Documentation
|
||||
*.md
|
||||
!LICENSE
|
||||
branding/
|
||||
|
||||
# Build configs (not needed at runtime)
|
||||
composer.json
|
||||
composer.lock
|
||||
package.json
|
||||
package-lock.json
|
||||
gulpfile.js
|
||||
.env.example
|
||||
.dockerignore
|
||||
|
||||
# Temporary and backup files
|
||||
*.rej
|
||||
*.orig
|
||||
*~
|
||||
*.~
|
||||
*.log
|
||||
app/writable/session/*
|
||||
!app/writable/session/index.html
|
||||
|
||||
# CI
|
||||
.github/
|
||||
.github/workflows/
|
||||
build/
|
||||
|
||||
18
.env.example
18
.env.example
@@ -4,6 +4,24 @@
|
||||
|
||||
CI_ENVIRONMENT = production
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# SECURITY: ALLOWED HOSTNAMES
|
||||
#--------------------------------------------------------------------
|
||||
# CRITICAL: Whitelist of allowed hostnames to prevent Host Header
|
||||
# Injection attacks (GHSA-jchf-7hr6-h4f3).
|
||||
#
|
||||
# REQUIRED IN PRODUCTION: Application will fail to start if not configured.
|
||||
# In development, falls back to 'localhost' with an error log.
|
||||
#
|
||||
# Configure with comma-separated list of domains/subdomains:
|
||||
# app.allowedHostnames = 'yourdomain.com,www.yourdomain.com'
|
||||
#
|
||||
# For local development:
|
||||
# app.allowedHostnames = 'localhost'
|
||||
#
|
||||
# Note: Do not include protocol (http/https) or port numbers.
|
||||
app.allowedHostnames = ''
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# DATABASE
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
308
.github/ISSUE_TEMPLATE/bug report.yml
vendored
308
.github/ISSUE_TEMPLATE/bug report.yml
vendored
@@ -1,121 +1,187 @@
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
title: "[Bug]: "
|
||||
labels: ["bug", "triage"]
|
||||
projects: ["ospos/3", "ospos/4"]
|
||||
assignees:
|
||||
- none
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Bug reports indicate that something is not working as intended.
|
||||
Please include as much detail as possible and submit a separate bug report for each problem.
|
||||
Do not include personal identifying information such as email addresses or encryption keys.
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Bug Description?
|
||||
description: Describe the problem that you are seeing
|
||||
placeholder: "Describe the problem that you are seeing"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps-reproduce
|
||||
attributes:
|
||||
label: Steps to Reproduce?
|
||||
description: List the steps to reproduce this issue
|
||||
placeholder: "Steps to Reproduce"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected Behavior?
|
||||
description: Tell us what did you expect to happen?
|
||||
placeholder: "Expected Behavior"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: ospos-version
|
||||
attributes:
|
||||
label: OpensourcePOS Version
|
||||
description: What version of our software are you running?
|
||||
options:
|
||||
- development (unreleased)
|
||||
- opensourcepos 3.4.1
|
||||
- opensourcepos 3.4.0
|
||||
- opensourcepos 3.3.9
|
||||
- opensourcepos 3.3.8
|
||||
- opensourcepos 3.3.7
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: php-version
|
||||
attributes:
|
||||
label: Php version
|
||||
description: What version of Php?
|
||||
options:
|
||||
- Php 7.2
|
||||
- Php 7.3
|
||||
- Php 7.4
|
||||
- Php 8.1
|
||||
- Php 8.2
|
||||
- Php 8.3
|
||||
- Php 8.4
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: browsers
|
||||
attributes:
|
||||
label: What browsers are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- Firefox
|
||||
- Chrome
|
||||
- Safari
|
||||
- Microsoft Edge
|
||||
- Other
|
||||
- type: input
|
||||
id: server
|
||||
attributes:
|
||||
label: Server Operating System and version
|
||||
description: "Server Operating System "
|
||||
placeholder: "Server Operating System "
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: database
|
||||
attributes:
|
||||
label: Database Management System and version
|
||||
description: "Database Management System"
|
||||
placeholder: "Database Management"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: webserver
|
||||
attributes:
|
||||
label: Web Server and version
|
||||
description: "Web Server and version "
|
||||
placeholder: "Web Server and version "
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: servers
|
||||
attributes:
|
||||
label: System Information Report (optional)
|
||||
description: Copy and paste from OSPOS > Configuration > Setup & Conf > Setup & Conf?
|
||||
placeholder: System Information Report
|
||||
value: "System Information Report"
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Unmodified copy of OpensourcePOS
|
||||
description: By submitting this issue you agree this copy has not been modified
|
||||
options:
|
||||
- label: I agree this copy has not been modified
|
||||
required: true
|
||||
name: 🐛 Bug Report
|
||||
description: File a bug report to help us improve
|
||||
title: "[Bug]: "
|
||||
labels: ["bug", "triage"]
|
||||
projects: ["ospos/3", "ospos/4"]
|
||||
assignees: []
|
||||
body:
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# INTRODUCTION
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Thanks for taking the time to fill out this bug report! 🐜
|
||||
|
||||
Bug reports help us identify and fix issues. Please provide as much detail as possible.
|
||||
|
||||
> ⚠️ **Important:** Submit a separate bug report for each problem you encounter.
|
||||
>
|
||||
> 🚫 Do not include personal identifying information such as email addresses or encryption keys.
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# PROBLEM DESCRIPTION
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: 🐛 Bug Description
|
||||
description: A clear and concise description of what the bug is.
|
||||
placeholder: |
|
||||
Example: When I try to print a receipt, the application crashes
|
||||
with an error message saying "Unable to connect to printer".
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps-reproduce
|
||||
attributes:
|
||||
label: 📋 Steps to Reproduce
|
||||
description: Detailed steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: ✅ Expected Behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
placeholder: |
|
||||
Example: The receipt should print successfully without any errors.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# ENVIRONMENT DETAILS
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
- type: dropdown
|
||||
id: ospos-version
|
||||
attributes:
|
||||
label: 📦 OpenSourcePOS Version
|
||||
description: What version of our software are you running?
|
||||
options:
|
||||
- development (unreleased)
|
||||
- OpenSourcePOS 3.4.2
|
||||
- OpenSourcePOS 3.4.1
|
||||
- OpenSourcePOS 3.4.0
|
||||
- OpenSourcePOS 3.3.9
|
||||
- OpenSourcePOS 3.3.8
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: php-version
|
||||
attributes:
|
||||
label: 🔧 PHP Version
|
||||
description: What version of PHP are you running?
|
||||
options:
|
||||
- PHP 8.4
|
||||
- PHP 8.3
|
||||
- PHP 8.2
|
||||
- PHP 8.1
|
||||
- PHP 7.4
|
||||
- Other
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: browsers
|
||||
attributes:
|
||||
label: 🌐 Browser(s)
|
||||
description: What browser(s) are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- Firefox
|
||||
- Chrome
|
||||
- Safari
|
||||
- Microsoft Edge
|
||||
- Other
|
||||
|
||||
- type: input
|
||||
id: server
|
||||
attributes:
|
||||
label: 🖥️ Server Operating System
|
||||
description: What server OS and version are you running?
|
||||
placeholder: "e.g., Ubuntu 22.04, CentOS 7, Windows Server 2022"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: database
|
||||
attributes:
|
||||
label: 🗄️ Database
|
||||
description: What database management system and version are you using?
|
||||
placeholder: "e.g., MySQL 8.0, MariaDB 10.11, Percona 8.0"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: webserver
|
||||
attributes:
|
||||
label: 🌍 Web Server
|
||||
description: What web server and version are you using?
|
||||
placeholder: "e.g., Apache 2.4, Nginx 1.24, Caddy 2.7"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# ADDITIONAL INFORMATION
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
- type: textarea
|
||||
id: system-info
|
||||
attributes:
|
||||
label: 📊 System Information Report
|
||||
description: |
|
||||
Copy and paste the system information from OSPOS:
|
||||
|
||||
**Navigation:** Configuration → Setup & Conf → System Info
|
||||
placeholder: |
|
||||
Paste the System Information Report here...
|
||||
render: text
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: 📜 Relevant Log Output
|
||||
description: |
|
||||
Please copy and paste any relevant log output.
|
||||
|
||||
**Log locations:**
|
||||
- OSPOS logs: `writable/logs/`
|
||||
- Web server logs: `/var/log/apache2/` or `/var/log/nginx/`
|
||||
- PHP logs: Check your `php.ini` for `error_log` location
|
||||
placeholder: |
|
||||
Paste log output here...
|
||||
render: shell
|
||||
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: 📸 Screenshots
|
||||
description: If applicable, add screenshots to help explain your problem.
|
||||
placeholder: Drag and drop images here...
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# CONFIRMATION
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: ✓ Confirmation
|
||||
description: Please confirm the following before submitting
|
||||
options:
|
||||
- label: I certify that this is an unmodified copy of OpenSourcePOS
|
||||
required: true
|
||||
- label: I have searched existing issues to ensure this bug has not already been reported
|
||||
required: true
|
||||
- label: I have provided all the information requested above
|
||||
required: true
|
||||
|
||||
199
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
199
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,63 +1,136 @@
|
||||
name: ✨ Feature Request
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature]: "
|
||||
labels: ["enhancement"]
|
||||
assignees: ["none"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this feature request! 🤗
|
||||
Please make sure this feature request hasn't been already submitted by someone by looking through other open/closed issues. 😃
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
multiple: false
|
||||
label: Type of Feature
|
||||
description: Select the type of feature request.
|
||||
options:
|
||||
- "✨ New Feature"
|
||||
- "📝 Documentation"
|
||||
- "🎨 Style and UI"
|
||||
- "🔨 Code Refactor"
|
||||
- "⚡ Performance Improvements"
|
||||
- "✅ New Test"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: ospos-version
|
||||
attributes:
|
||||
label: OpensourcePOS Version
|
||||
description: What version of our software are you running?
|
||||
options:
|
||||
- opensourcepos 3.3.9
|
||||
- opensourcepos 3.3.8
|
||||
- opensourcepos 3.3.7
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Give us a brief description of the feature or enhancement you would like
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional-information
|
||||
attributes:
|
||||
label: Additional Information
|
||||
description: Give us some additional information on the feature request like proposed solutions, links, screenshots, etc.
|
||||
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Verify you searched open requests in OpensourcePOS
|
||||
description: By submitting this request you agree that you have searched Open Requests in the Tracker
|
||||
options:
|
||||
- label: I agree I have searched Open Requests
|
||||
required: true
|
||||
|
||||
name: ✨ Feature Request
|
||||
description: Suggest an idea or enhancement for this project
|
||||
title: "[Feature]: "
|
||||
labels: ["enhancement"]
|
||||
assignees: []
|
||||
body:
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# INTRODUCTION
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Thanks for suggesting a new feature! 💡
|
||||
|
||||
We appreciate you taking the time to help improve OpenSourcePOS.
|
||||
|
||||
> 📋 **Before submitting:** Please search [existing feature requests](https://github.com/opensourcepos/opensourcepos/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement) to ensure your idea hasn't already been suggested.
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# FEATURE DETAILS
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
- type: dropdown
|
||||
id: feature-type
|
||||
attributes:
|
||||
label: 🏷️ Feature Type
|
||||
description: What type of feature are you requesting?
|
||||
options:
|
||||
- "✨ New Feature"
|
||||
- "📝 Documentation Improvement"
|
||||
- "🎨 UI/UX Enhancement"
|
||||
- "🔨 Code Refactoring"
|
||||
- "⚡ Performance Improvement"
|
||||
- "✅ New Test Coverage"
|
||||
- "🔌 Plugin/Integration"
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: ospos-version
|
||||
attributes:
|
||||
label: 📦 OpenSourcePOS Version
|
||||
description: What version are you currently running?
|
||||
options:
|
||||
- development (unreleased)
|
||||
- OpenSourcePOS 3.4.2
|
||||
- OpenSourcePOS 3.4.1
|
||||
- OpenSourcePOS 3.4.0
|
||||
- OpenSourcePOS 3.3.9
|
||||
- OpenSourcePOS 3.3.8
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: problem-statement
|
||||
attributes:
|
||||
label: 🎯 Problem Statement
|
||||
description: |
|
||||
Is your feature request related to a problem? Please describe.
|
||||
|
||||
A clear description of what the problem is. Ex: I'm always frustrated when [...]
|
||||
placeholder: |
|
||||
Example: I always have to manually calculate taxes for different regions,
|
||||
which is time-consuming and error-prone.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: proposed-solution
|
||||
attributes:
|
||||
label: 💡 Proposed Solution
|
||||
description: A clear and concise description of what you want to happen.
|
||||
placeholder: |
|
||||
Example: Add an automatic tax calculation feature that:
|
||||
- Detects the customer's region
|
||||
- Applies the correct tax rate
|
||||
- Generates a tax report automatically
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: 🔄 Alternatives Considered
|
||||
description: A clear description of any alternative solutions or features you've considered.
|
||||
placeholder: |
|
||||
Example: I considered using an external tax service, but it would be
|
||||
better to have this integrated directly into OpenSourcePOS.
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# ADDITIONAL INFORMATION
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: 📎 Additional Context
|
||||
description: |
|
||||
Add any other context, screenshots, mockups, or references about the feature request here.
|
||||
|
||||
**Helpful additions:**
|
||||
- Links to similar features in other software
|
||||
- Mockups or diagrams
|
||||
- Code examples
|
||||
- Documentation references
|
||||
placeholder: |
|
||||
Any other relevant information, links, or screenshots...
|
||||
|
||||
- type: textarea
|
||||
id: acceptance-criteria
|
||||
attributes:
|
||||
label: ✅ Acceptance Criteria
|
||||
description: |
|
||||
(Optional) Define what "done" looks like for this feature.
|
||||
|
||||
Format: **Given** [context], **When** [action], **Then** [outcome]
|
||||
placeholder: |
|
||||
Given a customer is selected from region X
|
||||
When the sale is completed
|
||||
Then the tax rate for region X is automatically applied
|
||||
And the tax amount is correctly calculated
|
||||
And a tax entry is logged in the report
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# CONFIRMATION
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: ✓ Confirmation
|
||||
description: Please confirm before submitting
|
||||
options:
|
||||
- label: I have searched existing feature requests to ensure this is not a duplicate
|
||||
required: true
|
||||
- label: I have provided a clear problem statement and proposed solution
|
||||
required: true
|
||||
61
.github/workflows/README.md
vendored
Normal file
61
.github/workflows/README.md
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
# GitHub Actions
|
||||
|
||||
This document describes the CI/CD workflows for OSPOS.
|
||||
|
||||
## Build and Release Workflow (`.github/workflows/build-release.yml`)
|
||||
|
||||
### Build Process
|
||||
- Setup PHP 8.2 with required extensions
|
||||
- Setup Node.js 20
|
||||
- Install composer dependencies
|
||||
- Install npm dependencies
|
||||
- Build frontend assets with Gulp
|
||||
|
||||
### Docker Images
|
||||
- Build and push `opensourcepos` Docker image for multiple architectures (linux/amd64, linux/arm64)
|
||||
- On master: tagged with version and `latest`
|
||||
- On other branches: tagged with version only
|
||||
- Pushed to Docker Hub
|
||||
|
||||
### Releases
|
||||
- Create distribution archives (tar.gz, zip)
|
||||
- Create/update GitHub "unstable" release on master branch only
|
||||
|
||||
## Required Secrets
|
||||
|
||||
To use this workflow, you need to add the following secrets to your repository:
|
||||
|
||||
1. **DOCKER_USERNAME** - Docker Hub username for pushing images
|
||||
2. **DOCKER_PASSWORD** - Docker Hub password/token for pushing images
|
||||
|
||||
### How to add secrets
|
||||
|
||||
1. Go to your repository on GitHub
|
||||
2. Click **Settings** → **Secrets and variables** → **Actions**
|
||||
3. Click **New repository secret**
|
||||
4. Add `DOCKER_USERNAME` and `DOCKER_PASSWORD`
|
||||
|
||||
The `GITHUB_TOKEN` is automatically provided by GitHub Actions.
|
||||
|
||||
## Workflow Triggers
|
||||
|
||||
- **Push to master** - Runs build, Docker push (with `latest` tag), and release
|
||||
- **Push to other branches** - Runs build and Docker push (version tag only)
|
||||
- **Push tags** - Runs build and Docker push (version tag only)
|
||||
- **Pull requests** - Runs build only (PHPUnit tests run in parallel via phpunit.yml)
|
||||
|
||||
## Existing Workflows
|
||||
|
||||
This repository also has these workflows:
|
||||
- `.github/workflows/main.yml` - PHP linting with PHP-CS-Fixer
|
||||
- `.github/workflows/phpunit.yml` - PHPUnit tests (runs on all PHP versions 8.1-8.4)
|
||||
- `.github/workflows/php-linter.yml` - PHP linting
|
||||
|
||||
## Testing
|
||||
|
||||
PHPUnit tests are run separately via `.github/workflows/phpunit.yml` on every push and pull request, testing against PHP 8.1, 8.2, 8.3, and 8.4.
|
||||
|
||||
To test the build workflow:
|
||||
1. Add the required secrets
|
||||
2. Push to master or create a PR
|
||||
3. Monitor the Actions tab in GitHub
|
||||
214
.github/workflows/build-release.yml
vendored
Normal file
214
.github/workflows/build-release.yml
vendored
Normal file
@@ -0,0 +1,214 @@
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
version-tag: ${{ steps.version.outputs.version-tag }}
|
||||
short-sha: ${{ steps.version.outputs.short-sha }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.2'
|
||||
extensions: intl, mbstring, mysqli, gd, bcmath, zip
|
||||
coverage: none
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Get composer cache directory
|
||||
run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache composer dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.COMPOSER_CACHE_FILES_DIR }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-composer-
|
||||
|
||||
- name: Get npm cache directory
|
||||
run: echo "NPM_CACHE_DIR=$(npm config get cache)" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache npm dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.NPM_CACHE_DIR }}
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Install composer dependencies
|
||||
run: composer install --no-dev --optimize-autoloader
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install gulp globally
|
||||
run: npm install -g gulp-cli
|
||||
|
||||
- name: Get version info
|
||||
id: version
|
||||
run: |
|
||||
VERSION=$(grep "application_version" app/Config/App.php | sed "s/.*= '\(.*\)';/\1/g")
|
||||
BRANCH=$(echo "${GITHUB_REF#refs/heads/}" | sed 's/feature\///' | tr '/' '_')
|
||||
TAG=$(echo "${GITHUB_TAG:-$BRANCH}" | tr '/' '_')
|
||||
SHORT_SHA=$(git rev-parse --short=6 HEAD)
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "version-tag=$VERSION-$BRANCH-$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||
echo "short-sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
GITHUB_TAG: ${{ github.ref_name }}
|
||||
|
||||
- name: Create .env file
|
||||
run: |
|
||||
cp .env.example .env
|
||||
sed -i 's/production/development/g' .env
|
||||
|
||||
- name: Update commit hash
|
||||
run: |
|
||||
SHORT_SHA="${{ steps.version.outputs.short-sha }}"
|
||||
sed -i "s/commit_sha1 = 'dev'/commit_sha1 = '$SHORT_SHA'/g" app/Config/OSPOS.php
|
||||
|
||||
- name: Build frontend assets
|
||||
run: npm run build
|
||||
|
||||
- name: Create distribution archives
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gulp compress
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
SHORT_SHA="${{ steps.version.outputs.short-sha }}"
|
||||
mv dist/opensourcepos.tar "dist/opensourcepos.$VERSION.$SHORT_SHA.tar"
|
||||
mv dist/opensourcepos.zip "dist/opensourcepos.$VERSION.$SHORT_SHA.zip"
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist-${{ steps.version.outputs.short-sha }}
|
||||
path: dist/
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload build context for Docker
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-context-${{ steps.version.outputs.short-sha }}
|
||||
path: |
|
||||
.
|
||||
!.git
|
||||
!node_modules
|
||||
retention-days: 1
|
||||
|
||||
docker:
|
||||
name: Build Docker Image
|
||||
runs-on: ubuntu-22.04
|
||||
needs: build
|
||||
if: github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- name: Download build context
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-context-${{ needs.build.outputs.short-sha }}
|
||||
path: .
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Determine Docker tags
|
||||
id: tags
|
||||
run: |
|
||||
BRANCH=$(echo "${GITHUB_REF#refs/heads/}" | tr '/' '_')
|
||||
if [ "$BRANCH" = "master" ]; then
|
||||
echo "tags=${{ secrets.DOCKER_USERNAME }}/opensourcepos:${{ needs.build.outputs.version-tag }},${{ secrets.DOCKER_USERNAME }}/opensourcepos:master" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "tags=${{ secrets.DOCKER_USERNAME }}/opensourcepos:${{ needs.build.outputs.version-tag }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
|
||||
- name: Build and push Docker images
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
target: ospos
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.tags.outputs.tags }}
|
||||
|
||||
release:
|
||||
name: Create Release
|
||||
needs: build
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: dist-${{ needs.build.outputs.short-sha }}
|
||||
path: dist/
|
||||
|
||||
- name: Get version info
|
||||
id: version
|
||||
run: |
|
||||
VERSION="${{ needs.build.outputs.version }}"
|
||||
SHORT_SHA=$(git rev-parse --short=6 HEAD)
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "short-sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create/Update unstable release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: unstable
|
||||
name: Unstable OpenSourcePOS
|
||||
body: |
|
||||
This is a build of the latest master which might contain bugs. Use at your own risk.
|
||||
|
||||
Check the releases section for the latest official release.
|
||||
files: |
|
||||
dist/opensourcepos.${{ steps.version.outputs.version }}.${{ steps.version.outputs.short-sha }}.zip
|
||||
prerelease: true
|
||||
draft: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
71
.github/workflows/codeql-analysis.yml
vendored
71
.github/workflows/codeql-analysis.yml
vendored
@@ -1,71 +0,0 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '21 12 * * 3'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
1
.github/workflows/main.yml
vendored
1
.github/workflows/main.yml
vendored
@@ -28,7 +28,6 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-version:
|
||||
- '8.1'
|
||||
- '8.2'
|
||||
- '8.3'
|
||||
- '8.4'
|
||||
|
||||
8
.github/workflows/php-linter.yml
vendored
8
.github/workflows/php-linter.yml
vendored
@@ -12,14 +12,6 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: PHP Lint 8.0
|
||||
uses: dbfx/github-phplint/8.0@master
|
||||
with:
|
||||
folder-to-exclude: "! -path \"./vendor/*\" ! -path \"./folder/excluded/*\""
|
||||
- name: PHP Lint 8.1
|
||||
uses: dbfx/github-phplint/8.1@master
|
||||
with:
|
||||
folder-to-exclude: "! -path \"./vendor/*\" ! -path \"./folder/excluded/*\""
|
||||
- name: PHP Lint 8.2
|
||||
uses: dbfx/github-phplint/8.2@master
|
||||
with:
|
||||
|
||||
121
.github/workflows/phpunit.yml
vendored
Normal file
121
.github/workflows/phpunit.yml
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
name: PHPUnit Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '**.php'
|
||||
- 'spark'
|
||||
- 'tests/**'
|
||||
- '.github/workflows/phpunit.yml'
|
||||
- 'gulpfile.js'
|
||||
- 'app/Database/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.php'
|
||||
- 'spark'
|
||||
- 'tests/**'
|
||||
- '.github/workflows/phpunit.yml'
|
||||
- 'gulpfile.js'
|
||||
- 'app/Database/**'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: PHP ${{ matrix.php-version }} Tests
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-version:
|
||||
- '8.2'
|
||||
- '8.3'
|
||||
- '8.4'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
extensions: intl, mbstring, mysqli
|
||||
coverage: none
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Get npm cache directory
|
||||
run: echo "NPM_CACHE_DIR=$(npm config get cache)" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache npm dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ env.NPM_CACHE_DIR }}
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Start MariaDB
|
||||
run: |
|
||||
docker run -d --name mysql \
|
||||
-e MYSQL_ROOT_PASSWORD=root \
|
||||
-e MYSQL_DATABASE=ospos \
|
||||
-e MYSQL_USER=admin \
|
||||
-e MYSQL_PASSWORD=pointofsale \
|
||||
-p 3306:3306 \
|
||||
mariadb:10.5
|
||||
# Wait for MariaDB to be ready
|
||||
until docker exec mysql mysqladmin ping -h 127.0.0.1 -u root -proot --silent; do
|
||||
echo "Waiting for MariaDB..."
|
||||
sleep 2
|
||||
done
|
||||
echo "MariaDB is ready!"
|
||||
|
||||
- name: Get composer cache directory
|
||||
run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ env.COMPOSER_CACHE_FILES_DIR }}
|
||||
key: ${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.php-version }}-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer update --ansi --no-interaction
|
||||
|
||||
- name: Create .env file
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Run PHPUnit tests
|
||||
env:
|
||||
CI_ENVIRONMENT: testing
|
||||
MYSQL_HOST_NAME: 127.0.0.1
|
||||
run: composer test -- --log-junit test-results/junit.xml
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: test-results-php-${{ matrix.php-version }}
|
||||
path: test-results/
|
||||
retention-days: 30
|
||||
|
||||
- name: Stop MariaDB
|
||||
if: always()
|
||||
run: docker stop mysql && docker rm mysql
|
||||
172
.github/workflows/release.yml
vendored
Normal file
172
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
name: Release Version Bump
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version_type:
|
||||
description: 'Version bump type'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- minor
|
||||
- major
|
||||
- patch
|
||||
default: 'minor'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
prepare-release:
|
||||
name: Prepare Release
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get current version
|
||||
id: current_version
|
||||
run: |
|
||||
CURRENT_VERSION=$(grep "application_version" app/Config/App.php | sed "s/.*= '\(.*\)';/\1/g")
|
||||
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Current version: $CURRENT_VERSION"
|
||||
|
||||
- name: Calculate new version
|
||||
id: version
|
||||
run: |
|
||||
CURRENT_VERSION="${{ steps.current_version.outputs.current_version }}"
|
||||
VERSION_TYPE="${{ github.event.inputs.version_type }}"
|
||||
|
||||
# Parse current version
|
||||
MAJOR=$(echo $CURRENT_VERSION | cut -d. -f1)
|
||||
MINOR=$(echo $CURRENT_VERSION | cut -d. -f2)
|
||||
PATCH=$(echo $CURRENT_VERSION | cut -d. -f3)
|
||||
|
||||
# Bump version based on type
|
||||
case $VERSION_TYPE in
|
||||
major)
|
||||
MAJOR=$((MAJOR + 1))
|
||||
MINOR=0
|
||||
PATCH=0
|
||||
;;
|
||||
minor)
|
||||
MINOR=$((MINOR + 1))
|
||||
PATCH=0
|
||||
;;
|
||||
patch)
|
||||
PATCH=$((PATCH + 1))
|
||||
;;
|
||||
esac
|
||||
|
||||
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
|
||||
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "previous_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "New version: $NEW_VERSION (was: $CURRENT_VERSION, type: $VERSION_TYPE)"
|
||||
|
||||
- name: Update version in App.php
|
||||
run: |
|
||||
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||
sed -i "s/public string \\\$application_version = '[^']*';/public string \\\$application_version = '$NEW_VERSION';/" app/Config/App.php
|
||||
echo "Updated app/Config/App.php"
|
||||
|
||||
- name: Update version in package.json
|
||||
run: |
|
||||
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||
sed -i "s/\"version\": \"[^\"]*\",/\"version\": \"$NEW_VERSION\",/" package.json
|
||||
echo "Updated package.json"
|
||||
|
||||
- name: Update version in docker-compose.nginx.yml
|
||||
run: |
|
||||
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||
sed -i "s/jekkos\/opensourcepos:[^ ]*/jekkos\/opensourcepos:$NEW_VERSION/" docker-compose.nginx.yml
|
||||
echo "Updated docker-compose.nginx.yml"
|
||||
|
||||
- name: Update version in README.md
|
||||
run: |
|
||||
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||
# Extract major.minor for the "latest X.Y version" text
|
||||
MAJOR_MINOR=$(echo "$NEW_VERSION" | cut -d. -f1,2)
|
||||
sed -i "s/The latest \`[0-9]*\.[0-9]*\` version/The latest \`${MAJOR_MINOR}\` version/" README.md
|
||||
echo "Updated README.md with version ${MAJOR_MINOR}"
|
||||
|
||||
- name: Generate changelog
|
||||
id: changelog
|
||||
run: |
|
||||
PREVIOUS_VERSION="${{ steps.version.outputs.previous_version }}"
|
||||
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||
|
||||
# Get commits since last version
|
||||
if git rev-parse "$PREVIOUS_VERSION" >/dev/null 2>&1; then
|
||||
COMMITS=$(git log "$PREVIOUS_VERSION"..HEAD --pretty=format:"- %s" --no-merges)
|
||||
else
|
||||
COMMITS=$(git log --pretty=format:"- %s" --no-merges -50)
|
||||
fi
|
||||
|
||||
# Create changelog entry
|
||||
CHANGELOG_FILE="CHANGELOG.md"
|
||||
|
||||
# Create the new version comparison link
|
||||
NEW_LINK="[${NEW_VERSION}]: https://github.com/opensourcepos/opensourcepos/compare/${PREVIOUS_VERSION}...${NEW_VERSION}"
|
||||
|
||||
# Insert new link after [unreleased] line
|
||||
sed -i "/^\[unreleased\]/a $NEW_LINK" "$CHANGELOG_FILE"
|
||||
|
||||
# Update [unreleased] link to start from new version
|
||||
sed -i "s|^\[unreleased\]: .*|\[unreleased\]: https://github.com/opensourcepos/opensourcepos/compare/${NEW_VERSION}...HEAD|" "$CHANGELOG_FILE"
|
||||
|
||||
# Create version header and content using temp file to avoid sed issues with special characters
|
||||
VERSION_DATE=$(date +%Y-%m-%d)
|
||||
VERSION_HEADER="## [$NEW_VERSION] - $VERSION_DATE"
|
||||
|
||||
# Create temp file with changelog entry
|
||||
TMP_FILE=$(mktemp)
|
||||
{
|
||||
echo ""
|
||||
echo "$VERSION_HEADER"
|
||||
echo ""
|
||||
echo "$COMMITS"
|
||||
} > "$TMP_FILE"
|
||||
|
||||
# Insert after Unreleased header
|
||||
sed -i "/^## \[Unreleased\]/r $TMP_FILE" "$CHANGELOG_FILE"
|
||||
rm "$TMP_FILE"
|
||||
|
||||
echo "Updated CHANGELOG.md"
|
||||
echo "Changelog entries:"
|
||||
echo "$COMMITS"
|
||||
|
||||
- name: Update version in issue templates
|
||||
run: |
|
||||
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||
|
||||
# Calculate version to remove (keep 5 versions)
|
||||
PREVIOUS_VERSION="${{ steps.version.outputs.previous_version }}"
|
||||
|
||||
# Bug report template - insert new version after development (unreleased)
|
||||
BUG_TEMPLATE=".github/ISSUE_TEMPLATE/bug report.yml"
|
||||
sed -i "/- development (unreleased)/a\\ - OpenSourcePOS ${NEW_VERSION}" "$BUG_TEMPLATE"
|
||||
# Remove the oldest version (5th version from the end)
|
||||
sed -i "/OpenSourcePOS 3\\.3\\.7/d" "$BUG_TEMPLATE"
|
||||
echo "Updated $BUG_TEMPLATE"
|
||||
|
||||
# Feature request template - insert new version after development (unreleased)
|
||||
FEATURE_TEMPLATE=".github/ISSUE_TEMPLATE/feature_request.yml"
|
||||
sed -i "/- development (unreleased)/a\\ - OpenSourcePOS ${NEW_VERSION}" "$FEATURE_TEMPLATE"
|
||||
# Remove the oldest version (5th version from the end)
|
||||
sed -i "/OpenSourcePOS 3\\.3\\.7/d" "$FEATURE_TEMPLATE"
|
||||
echo "Updated $FEATURE_TEMPLATE"
|
||||
|
||||
- name: Commit version bump
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||
|
||||
git add app/Config/App.php package.json docker-compose.nginx.yml CHANGELOG.md README.md .github/ISSUE_TEMPLATE/
|
||||
git commit -m "chore: release version $NEW_VERSION"
|
||||
git push origin HEAD
|
||||
54
.travis.yml
54
.travis.yml
@@ -1,54 +0,0 @@
|
||||
sudo: required
|
||||
|
||||
branches:
|
||||
except:
|
||||
- unstable
|
||||
- weblate
|
||||
services:
|
||||
- docker
|
||||
|
||||
dist: jammy
|
||||
language: node_js
|
||||
node_js:
|
||||
- 20
|
||||
script:
|
||||
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||
- docker run --rm -u $(id -u) -v $(pwd):/app opensourcepos/composer:ci4 composer install
|
||||
- version=$(grep application_version app/Config/App.php | sed "s/.*=\s'\(.*\)';/\1/g")
|
||||
- cp .env.example .env && sed -i 's/production/development/g' .env
|
||||
- sed -i "s/commit_sha1 = 'dev'/commit_sha1 = '$rev'/g" app/Config/OSPOS.php
|
||||
- echo "$version-$branch-$rev"
|
||||
- npm version "$version-$branch-$rev" --force || true
|
||||
- sed -i 's/opensourcepos.tar.gz/opensourcepos.$version.tgz/g' package.json
|
||||
- npm ci && npm install -g gulp && npm run build
|
||||
- docker build . --target ospos -t ospos
|
||||
- docker build . --target ospos_test -t ospos_test
|
||||
- docker run --rm ospos_test /app/vendor/bin/phpunit --testdox
|
||||
- docker build app/Database/ -t "jekkos/opensourcepos:sql-$TAG"
|
||||
env:
|
||||
global:
|
||||
- BRANCH=$(echo ${TRAVIS_BRANCH} | sed s/feature\\///)
|
||||
- TAG=${TRAVIS_TAG:-$BRANCH}
|
||||
- date=`date +%Y%m%d%H%M%S` && branch=${TRAVIS_BRANCH} && rev=`git rev-parse --short=6 HEAD`
|
||||
after_success:
|
||||
- docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" && docker tag "ospos:latest"
|
||||
"jekkos/opensourcepos:$TAG" && docker push "jekkos/opensourcepos:$TAG" && docker push "jekkos/opensourcepos:sql-$TAG"
|
||||
- gulp compress
|
||||
- mv dist/opensourcepos.tar.gz "dist/opensourcepos.$version.$rev.tgz"
|
||||
- mv dist/opensourcepos.zip "dist/opensourcepos.$version.$rev.zip"
|
||||
deploy:
|
||||
- provider: releases
|
||||
edge: true
|
||||
file: dist/opensourcepos.$version.$rev.zip
|
||||
name: "Unstable OpensourcePos"
|
||||
overwrite: true
|
||||
release_notes: "This is a build of the latest master which might contain bugs. Use at your own risk. Check releases section for the latest official release"
|
||||
prerelease: true
|
||||
tag_name: unstable
|
||||
user: jekkos
|
||||
|
||||
api_key:
|
||||
secure: "KOukL8IFf/uL/BjMyCSKjf2vylydjcWqgEx0eMqFCg3nZ4ybMaOwPORRthIfyT72/FvGX/aoxxEn0uR/AEtb+hYQXHmNS+kZdX72JCe8LpGuZ7FJ5X+Eo9mhJcsmS+smd1sC95DySSc/GolKPo+0WtJYONY/xGCLxm+9Ay4HREg="
|
||||
|
||||
on:
|
||||
branch: master
|
||||
40
AGENTS.md
Normal file
40
AGENTS.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Agent Instructions
|
||||
|
||||
This document provides guidance for AI agents working on the Open Source Point of Sale (OSPOS) codebase.
|
||||
|
||||
## 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
|
||||
|
||||
## Development
|
||||
|
||||
- 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`
|
||||
|
||||
## Conventions
|
||||
|
||||
- Controllers go in `app/Controllers/`
|
||||
- Models go in `app/Models/`
|
||||
- Views go in `app/Views/`
|
||||
- Database migrations in `app/Database/Migrations/`
|
||||
- Use CodeIgniter 4 framework patterns and helpers
|
||||
- Sanitize user input; escape output using `esc()` helper
|
||||
|
||||
## Security
|
||||
|
||||
- Never commit secrets, credentials, or `.env` files
|
||||
- Use parameterized queries to prevent SQL injection
|
||||
- Validate and sanitize all user input
|
||||
32
Dockerfile
32
Dockerfile
@@ -1,28 +1,22 @@
|
||||
FROM php:8.2-apache AS ospos
|
||||
LABEL maintainer="jekkos"
|
||||
|
||||
RUN apt update && apt-get install -y libicu-dev libgd-dev
|
||||
RUN a2enmod rewrite
|
||||
RUN docker-php-ext-install mysqli bcmath intl gd
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libicu-dev \
|
||||
libgd-dev \
|
||||
&& docker-php-ext-install mysqli bcmath intl gd \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& a2enmod rewrite
|
||||
|
||||
RUN echo "date.timezone = \"\${PHP_TIMEZONE}\"" > /usr/local/etc/php/conf.d/timezone.ini
|
||||
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
RUN ln -s /app/*[^public] /var/www && rm -rf /var/www/html && ln -nsf /app/public /var/www/html
|
||||
RUN chmod -R 770 /app/writable/uploads /app/writable/logs /app/writable/cache && chown -R www-data:www-data /app
|
||||
|
||||
FROM ospos AS ospos_test
|
||||
|
||||
COPY --from=composer /usr/bin/composer /usr/bin/composer
|
||||
|
||||
RUN apt-get install -y libzip-dev wget git
|
||||
RUN wget https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh -O /bin/wait-for-it.sh && chmod +x /bin/wait-for-it.sh
|
||||
RUN docker-php-ext-install zip
|
||||
RUN composer install -d/app
|
||||
#RUN sed -i 's/backupGlobals="true"/backupGlobals="false"/g' /app/tests/phpunit.xml
|
||||
WORKDIR /app/tests
|
||||
|
||||
CMD ["/app/vendor/phpunit/phpunit/phpunit", "/app/test/helpers"]
|
||||
COPY --chown=www-data:www-data . /app
|
||||
RUN chmod 770 /app/writable/uploads /app/writable/logs /app/writable/cache \
|
||||
&& ln -s /app/*[^public] /var/www \
|
||||
&& rm -rf /var/www/html \
|
||||
&& ln -nsf /app/public /var/www/html
|
||||
|
||||
FROM ospos AS ospos_dev
|
||||
|
||||
|
||||
55
INSTALL.md
55
INSTALL.md
@@ -1,27 +1,68 @@
|
||||
## Server Requirements
|
||||
|
||||
- PHP version `8.1` to `8.4` are supported, PHP version `≤7.4` is NOT supported. Please note that PHP needs to have the extensions `php-json`, `php-gd`, `php-bcmath`, `php-intl`, `php-openssl`, `php-mbstring`, `php-curl` and `php-xml` installed and enabled. An unstable master build can be downloaded in the releases section.
|
||||
- PHP version `8.2` to `8.4` are supported, PHP version `≤ 8.1` is NOT supported. Please note that PHP needs to have the extensions `php-json`, `php-gd`, `php-bcmath`, `php-intl`, `php-openssl`, `php-mbstring`, `php-curl` and `php-xml` installed and enabled. An unstable master build can be downloaded in the releases section.
|
||||
- MySQL `5.7` is supported, also MariaDB replacement `10.x` is supported and might offer better performance.
|
||||
- Apache `2.4` is supported. Nginx should work fine too, see [wiki page here](https://github.com/opensourcepos/opensourcepos/wiki/Local-Deployment-using-LEMP).
|
||||
- Raspberry PI based installations proved to work, see [wiki page here](<https://github.com/opensourcepos/opensourcepos/wiki/Installing-on-Raspberry-PI---Orange-PI-(Headless-OSPOS)>).
|
||||
- For Windows based installations please read [the wiki](https://github.com/opensourcepos/opensourcepos/wiki). There are closed issues about this subject, as this topic has been covered a lot.
|
||||
|
||||
## Security Configuration
|
||||
|
||||
### Allowed Hostnames (REQUIRED for Production)
|
||||
|
||||
⚠️ **CRITICAL**: OpenSourcePOS validates the Host header to prevent Host Header Injection attacks (GHSA-jchf-7hr6-h4f3). **You MUST configure `app.allowedHostnames` for production deployments. If not configured, the application will fail to start.**
|
||||
|
||||
**Add to your `.env` file:**
|
||||
|
||||
```bash
|
||||
# Comma-separated list of allowed hostnames (no protocols or ports)
|
||||
app.allowedHostnames = 'yourdomain.com,www.yourdomain.com'
|
||||
```
|
||||
|
||||
**For local development:**
|
||||
|
||||
```bash
|
||||
app.allowedHostnames = 'localhost'
|
||||
```
|
||||
|
||||
**If you see this error at startup:**
|
||||
|
||||
```text
|
||||
RuntimeException: Security: allowedHostnames is not configured.
|
||||
```
|
||||
|
||||
**Solution**: Add `app.allowedHostnames` to your `.env` file with your domain(s).
|
||||
|
||||
**Why this matters:**
|
||||
- Prevents Host Header Injection attacks (GHSA-jchf-7hr6-h4f3)
|
||||
- Ensures URLs are generated with the correct domain
|
||||
- Security advisory: https://github.com/opensourcepos/opensourcepos/security/advisories/GHSA-jchf-7hr6-h4f3
|
||||
- Fixes issue #4480: .env configuration now works via comma-separated values
|
||||
|
||||
### HTTPS Behind Proxy
|
||||
|
||||
If your installation is behind a proxy with SSL offloading, set:
|
||||
```
|
||||
FORCE_HTTPS = true
|
||||
```
|
||||
|
||||
## Local install
|
||||
|
||||
First of all, if you're seeing the message `system folder missing` after launching your browser, or cannot find `database.sql`, that most likely means you have cloned the repository and have not built the project. To build the project from a source commit point instead of from an official release check out [Building OSPOS](BUILD.md). Otherwise, continue with the following steps.
|
||||
First of all, if you're seeing the message `system folder missing` after launching your browser, that most likely means you have cloned the repository and have not built the project. To build the project from a source commit point instead of from an official release check out [Building OSPOS](BUILD.md). Otherwise, continue with the following steps.
|
||||
|
||||
1. Download the a [pre-release for a specific branch](https://github.com/opensourcepos/opensourcepos/releases) or the latest stable [from GitHub here](https://github.com/opensourcepos/opensourcepos/releases). A repository clone will not work unless know how to build the project.
|
||||
2. Create/locate a new MySQL database to install Open Source Point of Sale into.
|
||||
3. Execute the file `app/Database/database.sql` to create the tables needed.
|
||||
4. Unzip and upload Open Source Point of Sale files to the web-server.
|
||||
5. Open `.env` file and modify credentials to connect to your database if needed. (First copy .env.example to .env and update)
|
||||
3. Unzip and upload Open Source Point of Sale files to the web-server.
|
||||
4. If `.env` does not exist, copy `.env.example` to `.env`.
|
||||
5. Open `.env` and modify credentials to connect to your database if needed.
|
||||
6. The database schema will be automatically created when you first access the application. Migrations run automatically on fresh installs.
|
||||
7. Go to your install `public` dir via the browser.
|
||||
8. Log in using
|
||||
- Username: admin
|
||||
- Password: pointofsale
|
||||
9. If everything works, then set the `CI_ENVIRONMENT` variable to `production` in the .env file
|
||||
9. Enjoy!
|
||||
10. Oops, an issue? Please make sure you read the FAQ, wiki page, and you checked open and closed issues on GitHub. PHP `display_errors` is disabled by default. Create an` app/Config/.env` file from the `.env.example` to enable it in a development environment.
|
||||
10. Enjoy!
|
||||
11. Oops, an issue? Please make sure you read the FAQ, wiki page, and you checked open and closed issues on GitHub. PHP `display_errors` is disabled by default. Create an` app/Config/.env` file from the `.env.example` to enable it in a development environment.
|
||||
|
||||
## Local install using Docker
|
||||
|
||||
|
||||
12
README.md
12
README.md
@@ -8,7 +8,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://app.travis-ci.com/opensourcepos/opensourcepos" target="_blank"><img src="https://api.travis-ci.com/opensourcepos/opensourcepos.svg?branch=master" alt="Build Status"></a>
|
||||
<a href="https://github.com/opensourcepos/opensourcepos/actions/workflows/build-release.yml" target="_blank"><img src="https://github.com/opensourcepos/opensourcepos/actions/workflows/build-release.yml/badge.svg" alt="Build Status"></a>
|
||||
<a href="https://app.gitter.im/#/room/#opensourcepos_Lobby:gitter.im?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" target="_blank"><img src="https://badges.gitter.im/jekkos/opensourcepos.svg" alt="Join the chat at https://app.gitter.im"></a>
|
||||
<a href="https://badge.fury.io/gh/opensourcepos%2Fopensourcepos" target="_blank"><img src="https://badge.fury.io/gh/opensourcepos%2Fopensourcepos.svg" alt="Project Version"></a>
|
||||
<a href="https://translate.opensourcepos.org/engage/opensourcepos/?utm_source=widget" target="_blank"><img src="https://translate.opensourcepos.org/widgets/opensourcepos/-/svg-badge.svg" alt="Translation Status"></a>
|
||||
@@ -102,11 +102,11 @@ NOTE: If you're running non-release code, please make sure you always run the la
|
||||
|
||||
- If you have suhosin installed and face an issue with CSRF, please make sure you read [issue #1492](https://github.com/opensourcepos/opensourcepos/issues/1492).
|
||||
|
||||
- PHP `≥ 8.1` is required to run this app.
|
||||
- PHP `≥ 8.2` is required to run this app.
|
||||
|
||||
## 🏃 Keep the Machine Running
|
||||
|
||||
If you like our project, please consider buying us a coffee through the button below so we can keep adding features.
|
||||
If you like our project, please consider buying us a coffee through the button below so we can keep adding features. Please star the project if you like it!
|
||||
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=MUN6AEG7NY6H8)\
|
||||
Or refer to the [FUNDING.yml](.github/FUNDING.yml) file.
|
||||
@@ -137,7 +137,7 @@ Any person or company found breaching the license agreement might find a bunch o
|
||||
|
||||
## 🙏 Credits
|
||||
|
||||
| <div align="center">DigitalOcean</div> | <div align="center">JetBrains</div> | <div align="center">Travis CI</div> |
|
||||
| <div align="center">DigitalOcean</div> | <div align="center">JetBrains</div> | <div align="center">GitHub</div> |
|
||||
| --- | --- | --- |
|
||||
| <div align="center"><a href="https://www.digitalocean.com?utm_medium=opensource&utm_source=opensourcepos" target="_blank"><img src="https://github.com/user-attachments/assets/fbbf7433-ed35-407d-8946-fd03d236d350" alt="DigitalOcean Logo" height="50"></a></div> | <div align="center"><a href="https://www.jetbrains.com/idea/" target="_blank"><img src="https://github.com/opensourcepos/opensourcepos/assets/12870258/187f9bbe-4484-475c-9b58-5e5d5f931f09" alt="IntelliJ IDEA Logo" height="50"></a></div> | <div align="center"><a href="https://www.travis-ci.com/" target="_blank"><img src="https://github.com/opensourcepos/opensourcepos/assets/12870258/71cc2b44-83af-4510-a543-6358285f43c6" alt="Travis CI Logo" height="50"></a></div> |
|
||||
| Many thanks to [DigitalOcean](https://www.digitalocean.com) for providing the project with hosting credits. | Many thanks to [JetBrains](https://www.jetbrains.com/) for providing a free license of [IntelliJ IDEA](https://www.jetbrains.com/idea/) to kindly support the development of OSPOS. | Many thanks to [Travis CI](https://www.travis-ci.com/) for providing a free continuous integration service for open source projects. |
|
||||
| <div align="center"><a href="https://www.digitalocean.com?utm_medium=opensource&utm_source=opensourcepos" target="_blank"><img src="https://github.com/user-attachments/assets/fbbf7433-ed35-407d-8946-fd03d236d350" alt="DigitalOcean Logo" height="50"></a></div> | <div align="center"><a href="https://www.jetbrains.com/idea/" target="_blank"><img src="https://github.com/opensourcepos/opensourcepos/assets/12870258/187f9bbe-4484-475c-9b58-5e5d5f931f09" alt="IntelliJ IDEA Logo" height="50"></a></div> | <div align="center"><a href="https://github.com/features/actions" target="_blank"><img src="https://github.githubassets.com/images/modules/site/icons/eyebrow-panel/actions-icon.svg" alt="GitHub Actions Logo" height="50"></a></div> |
|
||||
| Many thanks to [DigitalOcean](https://www.digitalocean.com) for providing the project with hosting credits. | Many thanks to [JetBrains](https://www.jetbrains.com/) for providing a free license of [IntelliJ IDEA](https://www.jetbrains.com/idea/) to kindly support the development of OSPOS. | Many thanks to [GitHub](https://github.com) for providing free continuous integration via GitHub Actions for open-source projects. |
|
||||
|
||||
37
SECURITY.md
37
SECURITY.md
@@ -1,9 +1,9 @@
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
|
||||
|
||||
- [Security Policy](#security-policy)
|
||||
- [Supported Versions](#supported-versions)
|
||||
- [Security Advisories](#security-advisories)
|
||||
- [Reporting a Vulnerability](#reporting-a-vulnerability)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
@@ -12,14 +12,35 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We release patches for security vulnerabilities. Which versions are eligible to receive such patches depend on the CVSS v3.0 Rating:
|
||||
We release patches for security vulnerabilities.
|
||||
|
||||
| CVSS v3.0 | Supported Versions |
|
||||
| --------- | -------------------------------------------------- |
|
||||
| 7.3 | 3.3.5 |
|
||||
| 9.8 | 3.3.6 |
|
||||
| 6.8 | 3.4.2 |
|
||||
| Version | Supported |
|
||||
| --------- | ------------------ |
|
||||
| >= 3.4.2 | :white_check_mark: |
|
||||
| < 3.4.2 | :x: |
|
||||
|
||||
## 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).
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report (suspected) security vulnerabilities to **[jeroen@steganos.dev](mailto:jeroen@steganos.dev)**. 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.
|
||||
Please report (suspected) security vulnerabilities to **[jeroen@steganos.dev](mailto:jeroen@steganos.dev)**.
|
||||
|
||||
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.
|
||||
@@ -117,7 +117,7 @@ class App extends BaseConfig
|
||||
| DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!!
|
||||
|
|
||||
*/
|
||||
public string $permittedURIChars = 'a-z 0-9~%.:_\-=';
|
||||
public string $permittedURIChars = 'a-z 0-9~%.:_\-';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
@@ -278,14 +278,79 @@ class App extends BaseConfig
|
||||
* @see http://www.html5rocks.com/en/tutorials/security/content-security-policy/
|
||||
* @see http://www.w3.org/TR/CSP/
|
||||
*/
|
||||
public bool $CSPEnabled = false; // TODO: Currently CSP3 tags are not supported so enabling this causes problems with script-src-elem, style-src-attr and style-src-elem
|
||||
public bool $CSPEnabled = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// Solution for CodeIgniter 4 limitation: arrays cannot be set from .env
|
||||
// See: https://github.com/codeigniter4/CodeIgniter4/issues/7311
|
||||
$envAllowedHostnames = getenv('app.allowedHostnames');
|
||||
if ($envAllowedHostnames !== false && trim($envAllowedHostnames) !== '') {
|
||||
$this->allowedHostnames = array_values(array_filter(
|
||||
array_map('trim', explode(',', $envAllowedHostnames)),
|
||||
static fn (string $hostname): bool => $hostname !== ''
|
||||
));
|
||||
}
|
||||
|
||||
$this->https_on = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_ENV['FORCE_HTTPS']) && $_ENV['FORCE_HTTPS'] == 'true');
|
||||
|
||||
$host = $this->getValidHost();
|
||||
$this->baseURL = $this->https_on ? 'https' : 'http';
|
||||
$this->baseURL .= '://' . ((isset($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : 'localhost') . '/';
|
||||
$this->baseURL .= '://' . $host . '/';
|
||||
$this->baseURL .= str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and returns a trusted hostname.
|
||||
*
|
||||
* Security: Prevents Host Header Injection attacks (GHSA-jchf-7hr6-h4f3)
|
||||
* by validating the HTTP_HOST against a whitelist of allowed hostnames.
|
||||
*
|
||||
* In production: Fails fast if allowedHostnames is not configured.
|
||||
* In development: Allows localhost fallback with an error log.
|
||||
*
|
||||
* @return string A validated hostname
|
||||
* @throws \RuntimeException If allowedHostnames is not configured in production
|
||||
*/
|
||||
private function getValidHost(): string
|
||||
{
|
||||
$httpHost = $_SERVER['HTTP_HOST'] ?? 'localhost';
|
||||
|
||||
// Determine environment
|
||||
// CodeIgniter's test bootstrap sets $_SERVER['CI_ENVIRONMENT'] = 'testing'
|
||||
// Check $_SERVER first, then $_ENV, then fall back to 'production'
|
||||
$environment = $_SERVER['CI_ENVIRONMENT'] ?? $_ENV['CI_ENVIRONMENT'] ?? getenv('CI_ENVIRONMENT') ?: 'production';
|
||||
|
||||
if (empty($this->allowedHostnames)) {
|
||||
$errorMessage =
|
||||
'Security: allowedHostnames is not configured. ' .
|
||||
'Host header injection protection is disabled. ' .
|
||||
'Set app.allowedHostnames in your .env file. ' .
|
||||
'Example: app.allowedHostnames = "example.com,www.example.com" ' .
|
||||
'Received Host: ' . $httpHost;
|
||||
|
||||
// Production: Fail explicitly to prevent silent security vulnerabilities
|
||||
// Testing and development: Allow localhost fallback
|
||||
if ($environment === 'production') {
|
||||
throw new \RuntimeException($errorMessage);
|
||||
}
|
||||
|
||||
log_message('error', $errorMessage . ' Using localhost fallback (development only).');
|
||||
return 'localhost';
|
||||
}
|
||||
|
||||
if (in_array($httpHost, $this->allowedHostnames, true)) {
|
||||
return $httpHost;
|
||||
}
|
||||
|
||||
// Host not in whitelist - use first configured hostname as fallback
|
||||
log_message('warning',
|
||||
'Security: Rejected HTTP_HOST "' . $httpHost . '" - not in allowedHostnames whitelist. ' .
|
||||
'Using fallback: ' . $this->allowedHostnames[0]
|
||||
);
|
||||
|
||||
return $this->allowedHostnames[0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ use CodeIgniter\Config\AutoloadConfig;
|
||||
*
|
||||
* NOTE: This class is required prior to Autoloader instantiation,
|
||||
* and does not extend BaseConfig.
|
||||
*
|
||||
* @immutable
|
||||
*/
|
||||
class Autoload extends AutoloadConfig
|
||||
{
|
||||
|
||||
@@ -6,6 +6,22 @@ use CodeIgniter\Config\BaseConfig;
|
||||
|
||||
class CURLRequest extends BaseConfig
|
||||
{
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* CURLRequest Share Connection Options
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Share connection options between requests.
|
||||
*
|
||||
* @var list<int>
|
||||
*
|
||||
* @see https://www.php.net/manual/en/curl.constants.php#constant.curl-lock-data-connect
|
||||
*/
|
||||
public array $shareConnectionOptions = [
|
||||
CURL_LOCK_DATA_CONNECT,
|
||||
CURL_LOCK_DATA_DNS,
|
||||
];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* CURLRequest Share Options
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Config;
|
||||
|
||||
use CodeIgniter\Cache\CacheInterface;
|
||||
use CodeIgniter\Cache\Handlers\ApcuHandler;
|
||||
use CodeIgniter\Cache\Handlers\DummyHandler;
|
||||
use CodeIgniter\Cache\Handlers\FileHandler;
|
||||
use CodeIgniter\Cache\Handlers\MemcachedHandler;
|
||||
@@ -78,7 +79,7 @@ class Cache extends BaseConfig
|
||||
* Your file storage preferences can be specified below, if you are using
|
||||
* the File driver.
|
||||
*
|
||||
* @var array<string, int|string|null>
|
||||
* @var array{storePath?: string, mode?: int}
|
||||
*/
|
||||
public array $file = [
|
||||
'storePath' => WRITEPATH . 'cache/',
|
||||
@@ -95,7 +96,7 @@ class Cache extends BaseConfig
|
||||
*
|
||||
* @see https://codeigniter.com/user_guide/libraries/caching.html#memcached
|
||||
*
|
||||
* @var array<string, bool|int|string>
|
||||
* @var array{host?: string, port?: int, weight?: int, raw?: bool}
|
||||
*/
|
||||
public array $memcached = [
|
||||
'host' => '127.0.0.1',
|
||||
@@ -108,17 +109,28 @@ class Cache extends BaseConfig
|
||||
* -------------------------------------------------------------------------
|
||||
* Redis settings
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
* Your Redis server can be specified below, if you are using
|
||||
* the Redis or Predis drivers.
|
||||
*
|
||||
* @var array<string, int|string|null>
|
||||
* @var array{
|
||||
* host?: string,
|
||||
* password?: string|null,
|
||||
* port?: int,
|
||||
* timeout?: int,
|
||||
* async?: bool,
|
||||
* persistent?: bool,
|
||||
* database?: int
|
||||
* }
|
||||
*/
|
||||
public array $redis = [
|
||||
'host' => '127.0.0.1',
|
||||
'password' => null,
|
||||
'port' => 6379,
|
||||
'timeout' => 0,
|
||||
'database' => 0,
|
||||
'host' => '127.0.0.1',
|
||||
'password' => null,
|
||||
'port' => 6379,
|
||||
'timeout' => 0,
|
||||
'async' => false, // specific to Predis and ignored by the native Redis extension
|
||||
'persistent' => false,
|
||||
'database' => 0,
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -132,6 +144,7 @@ class Cache extends BaseConfig
|
||||
* @var array<string, class-string<CacheInterface>>
|
||||
*/
|
||||
public array $validHandlers = [
|
||||
'apcu' => ApcuHandler::class,
|
||||
'dummy' => DummyHandler::class,
|
||||
'file' => FileHandler::class,
|
||||
'memcached' => MemcachedHandler::class,
|
||||
@@ -158,4 +171,28 @@ class Cache extends BaseConfig
|
||||
* @var bool|list<string>
|
||||
*/
|
||||
public $cacheQueryString = false;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Web Page Caching: Cache Status Codes
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* HTTP status codes that are allowed to be cached. Only responses with
|
||||
* these status codes will be cached by the PageCache filter.
|
||||
*
|
||||
* Default: [] - Cache all status codes (backward compatible)
|
||||
*
|
||||
* Recommended: [200] - Only cache successful responses
|
||||
*
|
||||
* You can also use status codes like:
|
||||
* [200, 404, 410] - Cache successful responses and specific error codes
|
||||
* [200, 201, 202, 203, 204] - All 2xx successful responses
|
||||
*
|
||||
* WARNING: Using [] may cache temporary error pages (404, 500, etc).
|
||||
* Consider restricting to [200] for production applications to avoid
|
||||
* caching errors that should be temporary.
|
||||
*
|
||||
* @var list<int>
|
||||
*/
|
||||
public array $cacheStatusCodes = [];
|
||||
}
|
||||
|
||||
@@ -169,3 +169,8 @@ const MAX_PRECISION = 1e14;
|
||||
const DEFAULT_PRECISION = 2;
|
||||
const DEFAULT_LANGUAGE = 'english';
|
||||
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'];
|
||||
|
||||
@@ -30,6 +30,11 @@ class ContentSecurityPolicy extends BaseConfig
|
||||
*/
|
||||
public ?string $reportURI = null;
|
||||
|
||||
/**
|
||||
* Specifies a reporting endpoint to which violation reports ought to be sent.
|
||||
*/
|
||||
public ?string $reportTo = null;
|
||||
|
||||
/**
|
||||
* Instructs user agents to rewrite URL schemes, changing
|
||||
* HTTP to HTTPS. This directive is for websites with
|
||||
@@ -38,12 +43,12 @@ class ContentSecurityPolicy extends BaseConfig
|
||||
public bool $upgradeInsecureRequests = false;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Sources allowed
|
||||
// CSP DIRECTIVES SETTINGS
|
||||
// NOTE: once you set a policy to 'none', it cannot be further restricted
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Will default to self if not overridden
|
||||
* Will default to `'self'` if not overridden
|
||||
*
|
||||
* @var list<string>|string|null
|
||||
*/
|
||||
@@ -64,6 +69,21 @@ class ContentSecurityPolicy extends BaseConfig
|
||||
'www.google.com www.gstatic.com'
|
||||
];
|
||||
|
||||
/**
|
||||
* Specifies valid sources for JavaScript <script> elements.
|
||||
*
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public array|string $scriptSrcElem = 'self';
|
||||
|
||||
/**
|
||||
* Specifies valid sources for JavaScript inline event
|
||||
* handlers and JavaScript URLs.
|
||||
*
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public array|string $scriptSrcAttr = 'self';
|
||||
|
||||
/**
|
||||
* Lists allowed stylesheets' URLs.
|
||||
*
|
||||
@@ -76,6 +96,21 @@ class ContentSecurityPolicy extends BaseConfig
|
||||
'https://fonts.googleapis.com',
|
||||
];
|
||||
|
||||
/**
|
||||
* Specifies valid sources for stylesheets <link> elements.
|
||||
*
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public array|string $styleSrcElem = 'self';
|
||||
|
||||
/**
|
||||
* Specifies valid sources for stylesheets inline
|
||||
* style attributes and `<style>` elements.
|
||||
*
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public array|string $styleSrcAttr = 'self';
|
||||
|
||||
/**
|
||||
* Defines the origins from which images can be loaded.
|
||||
*
|
||||
@@ -169,6 +204,11 @@ class ContentSecurityPolicy extends BaseConfig
|
||||
*/
|
||||
public $manifestSrc;
|
||||
|
||||
/**
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public array|string $workerSrc = [];
|
||||
|
||||
/**
|
||||
* Limits the kinds of plugins a page may invoke.
|
||||
*
|
||||
@@ -184,17 +224,17 @@ class ContentSecurityPolicy extends BaseConfig
|
||||
public $sandbox;
|
||||
|
||||
/**
|
||||
* Nonce tag for style
|
||||
* Nonce placeholder for style tags.
|
||||
*/
|
||||
public string $styleNonceTag = '{csp-style-nonce}';
|
||||
|
||||
/**
|
||||
* Nonce tag for script
|
||||
* Nonce placeholder for script tags.
|
||||
*/
|
||||
public string $scriptNonceTag = '{csp-script-nonce}';
|
||||
|
||||
/**
|
||||
* Replace nonce tag automatically
|
||||
* Replace nonce tag automatically?
|
||||
*/
|
||||
public bool $autoNonce = true;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ class Cookie extends BaseConfig
|
||||
* (empty string) means default SameSite attribute set by browsers (`Lax`)
|
||||
* will be set on cookies. If set to `None`, `$secure` must also be set.
|
||||
*
|
||||
* @phpstan-var 'None'|'Lax'|'Strict'|''
|
||||
* @var ''|'Lax'|'None'|'Strict'
|
||||
*/
|
||||
public string $samesite = 'Lax';
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@ class Database extends Config
|
||||
'strictOn' => false,
|
||||
'failover' => [],
|
||||
'port' => 3306,
|
||||
'numberNative' => false,
|
||||
'foundRows' => false,
|
||||
'dateFormat' => [
|
||||
'date' => 'Y-m-d',
|
||||
'datetime' => 'Y-m-d H:i:s',
|
||||
@@ -55,26 +57,27 @@ class Database extends Config
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
public array $tests = [
|
||||
'DSN' => '',
|
||||
'hostname' => 'localhost',
|
||||
'username' => 'admin',
|
||||
'password' => 'pointofsale',
|
||||
'database' => 'ospos',
|
||||
'DBDriver' => 'MySQLi',
|
||||
'DBPrefix' => 'ospos_',
|
||||
'pConnect' => false,
|
||||
'DBDebug' => (ENVIRONMENT !== 'production'),
|
||||
'charset' => 'utf8mb4',
|
||||
'DBCollat' => 'utf8mb4_general_ci',
|
||||
'swapPre' => '',
|
||||
'encrypt' => false,
|
||||
'compress' => false,
|
||||
'strictOn' => false,
|
||||
'failover' => [],
|
||||
'port' => 3306,
|
||||
'foreignKeys' => true,
|
||||
'busyTimeout' => 1000,
|
||||
'dateFormat' => [
|
||||
'DSN' => '',
|
||||
'hostname' => 'localhost',
|
||||
'username' => 'admin',
|
||||
'password' => 'pointofsale',
|
||||
'database' => 'ospos',
|
||||
'DBDriver' => 'MySQLi',
|
||||
'DBPrefix' => 'ospos_',
|
||||
'pConnect' => false,
|
||||
'DBDebug' => (ENVIRONMENT !== 'production'),
|
||||
'charset' => 'utf8mb4',
|
||||
'DBCollat' => 'utf8mb4_general_ci',
|
||||
'swapPre' => '',
|
||||
'encrypt' => false,
|
||||
'compress' => false,
|
||||
'strictOn' => false,
|
||||
'failover' => [],
|
||||
'port' => 3306,
|
||||
'foreignKeys' => true,
|
||||
'busyTimeout' => 1000,
|
||||
'synchronous' => null,
|
||||
'dateFormat' => [
|
||||
'date' => 'Y-m-d',
|
||||
'datetime' => 'Y-m-d H:i:s',
|
||||
'time' => 'H:i:s',
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
namespace Config;
|
||||
|
||||
/**
|
||||
* @immutable
|
||||
*/
|
||||
class DocTypes
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,11 @@ class Email extends BaseConfig
|
||||
*/
|
||||
public string $SMTPHost = 'mail.mxserver.com';
|
||||
|
||||
/**
|
||||
* Which SMTP authentication method to use: login, plain
|
||||
*/
|
||||
public string $SMTPAuthMethod = 'login';
|
||||
|
||||
/**
|
||||
* SMTP Username
|
||||
*/
|
||||
|
||||
@@ -23,6 +23,23 @@ class Encryption extends BaseConfig
|
||||
*/
|
||||
public string $key = '';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Previous Encryption Keys
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* When rotating encryption keys, add old keys here to maintain ability
|
||||
* to decrypt data encrypted with previous keys. Encryption always uses
|
||||
* the current $key. Decryption tries current key first, then falls back
|
||||
* to previous keys if decryption fails.
|
||||
*
|
||||
* In .env file, use comma-separated string:
|
||||
* encryption.previousKeys = hex2bin:9be8c64fcea509867...,hex2bin:3f5a1d8e9c2b7a4f6...
|
||||
*
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public array|string $previousKeys = '';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Encryption Driver to Use
|
||||
|
||||
@@ -65,12 +65,15 @@ class Filters extends BaseFilters
|
||||
* List of filter aliases that are always
|
||||
* applied before and after every request.
|
||||
*
|
||||
* @var array<string, array<string, array<string, string>>>|array<string, list<string>>
|
||||
* @var array{
|
||||
* before: array<string, array{except: list<string>|string}>|list<string>,
|
||||
* after: array<string, array{except: list<string>|string}>|list<string>
|
||||
* }
|
||||
*/
|
||||
public array $globals = [
|
||||
'before' => [
|
||||
'honeypot',
|
||||
'csrf' => ['except' => 'login'],
|
||||
'csrf' => ['except' => 'login|migrate'],
|
||||
'invalidchars',
|
||||
],
|
||||
'after' => [
|
||||
@@ -105,4 +108,20 @@ class Filters extends BaseFilters
|
||||
* @var array<string, array<string, list<string>>>
|
||||
*/
|
||||
public array $filters = [];
|
||||
|
||||
/**
|
||||
* Constructor to conditionally disable CSRF filter in testing environment
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Check for testing environment via env variable or constant
|
||||
$isTesting = ($_ENV['CI_ENVIRONMENT'] ?? $_SERVER['CI_ENVIRONMENT'] ?? getenv('CI_ENVIRONMENT')) === 'testing'
|
||||
|| (defined('ENVIRONMENT') && ENVIRONMENT === 'testing');
|
||||
|
||||
// Remove CSRF filter from globals in testing environment
|
||||
if ($isTesting) {
|
||||
// Remove the 'csrf' key from $globals['before'] while preserving array structure
|
||||
$this->globals['before'] = array_filter($this->globals['before'], static fn($key) => $key !== 'csrf', ARRAY_FILTER_USE_KEY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,4 +61,13 @@ class Format extends BaseConfig
|
||||
'application/xml' => 0,
|
||||
'text/xml' => 0,
|
||||
];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Maximum depth for JSON encoding.
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* This value determines how deep the JSON encoder will traverse nested structures.
|
||||
*/
|
||||
public int $jsonEncodeDepth = 512;
|
||||
}
|
||||
|
||||
40
app/Config/Hostnames.php
Normal file
40
app/Config/Hostnames.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Config;
|
||||
|
||||
class Hostnames
|
||||
{
|
||||
// List of known two-part TLDs for subdomain extraction
|
||||
public const TWO_PART_TLDS = [
|
||||
'co.uk', 'org.uk', 'gov.uk', 'ac.uk', 'sch.uk', 'ltd.uk', 'plc.uk',
|
||||
'com.au', 'net.au', 'org.au', 'edu.au', 'gov.au', 'asn.au', 'id.au',
|
||||
'co.jp', 'ac.jp', 'go.jp', 'or.jp', 'ne.jp', 'gr.jp',
|
||||
'co.nz', 'org.nz', 'govt.nz', 'ac.nz', 'net.nz', 'geek.nz', 'maori.nz', 'school.nz',
|
||||
'co.in', 'net.in', 'org.in', 'ind.in', 'ac.in', 'gov.in', 'res.in',
|
||||
'com.cn', 'net.cn', 'org.cn', 'gov.cn', 'edu.cn',
|
||||
'com.sg', 'net.sg', 'org.sg', 'gov.sg', 'edu.sg', 'per.sg',
|
||||
'co.za', 'org.za', 'gov.za', 'ac.za', 'net.za',
|
||||
'co.kr', 'or.kr', 'go.kr', 'ac.kr', 'ne.kr', 'pe.kr',
|
||||
'co.th', 'or.th', 'go.th', 'ac.th', 'net.th', 'in.th',
|
||||
'com.my', 'net.my', 'org.my', 'edu.my', 'gov.my', 'mil.my', 'name.my',
|
||||
'com.mx', 'org.mx', 'net.mx', 'edu.mx', 'gob.mx',
|
||||
'com.br', 'net.br', 'org.br', 'gov.br', 'edu.br', 'art.br', 'eng.br',
|
||||
'co.il', 'org.il', 'ac.il', 'gov.il', 'net.il', 'muni.il',
|
||||
'co.id', 'or.id', 'ac.id', 'go.id', 'net.id', 'web.id', 'my.id',
|
||||
'com.hk', 'edu.hk', 'gov.hk', 'idv.hk', 'net.hk', 'org.hk',
|
||||
'com.tw', 'net.tw', 'org.tw', 'edu.tw', 'gov.tw', 'idv.tw',
|
||||
'com.sa', 'net.sa', 'org.sa', 'gov.sa', 'edu.sa', 'sch.sa', 'med.sa',
|
||||
'co.ae', 'net.ae', 'org.ae', 'gov.ae', 'ac.ae', 'sch.ae',
|
||||
'com.tr', 'net.tr', 'org.tr', 'gov.tr', 'edu.tr', 'av.tr', 'gen.tr',
|
||||
'co.ke', 'or.ke', 'go.ke', 'ac.ke', 'sc.ke', 'me.ke', 'mobi.ke', 'info.ke',
|
||||
'com.ng', 'org.ng', 'gov.ng', 'edu.ng', 'net.ng', 'sch.ng', 'name.ng',
|
||||
'com.pk', 'net.pk', 'org.pk', 'gov.pk', 'edu.pk', 'fam.pk',
|
||||
'com.eg', 'edu.eg', 'gov.eg', 'org.eg', 'net.eg',
|
||||
'com.cy', 'net.cy', 'org.cy', 'gov.cy', 'ac.cy',
|
||||
'com.lk', 'org.lk', 'edu.lk', 'gov.lk', 'net.lk', 'int.lk',
|
||||
'com.bd', 'net.bd', 'org.bd', 'ac.bd', 'gov.bd', 'mil.bd',
|
||||
'com.ar', 'net.ar', 'org.ar', 'gov.ar', 'edu.ar', 'mil.ar',
|
||||
'gob.cl', 'com.pl', 'net.pl', 'org.pl', 'gov.pl', 'edu.pl',
|
||||
'co.ir', 'ac.ir', 'org.ir', 'id.ir', 'gov.ir', 'sch.ir', 'net.ir',
|
||||
];
|
||||
}
|
||||
@@ -16,6 +16,8 @@ class Images extends BaseConfig
|
||||
/**
|
||||
* The path to the image library.
|
||||
* Required for ImageMagick, GraphicsMagick, or NetPBM.
|
||||
*
|
||||
* @deprecated 4.7.0 No longer used.
|
||||
*/
|
||||
public string $libraryPath = '/usr/local/bin/convert';
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Config;
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use CodeIgniter\Log\Handlers\FileHandler;
|
||||
use CodeIgniter\Log\Handlers\HandlerInterface;
|
||||
|
||||
class Logger extends BaseConfig
|
||||
{
|
||||
@@ -73,7 +74,7 @@ class Logger extends BaseConfig
|
||||
* Handlers are executed in the order defined in this array, starting with
|
||||
* the handler on top and continuing down.
|
||||
*
|
||||
* @var array<class-string, array<string, int|list<string>|string>>
|
||||
* @var array<class-string<HandlerInterface>, array<string, int|list<string>|string>>
|
||||
*/
|
||||
public array $handlers = [
|
||||
/*
|
||||
|
||||
@@ -47,4 +47,19 @@ class Migrations extends BaseConfig
|
||||
* - Y_m_d_His_
|
||||
*/
|
||||
public string $timestampFormat = 'YmdHis_';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Enable/Disable Migration Lock
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Locking is disabled by default.
|
||||
*
|
||||
* When enabled, it will prevent multiple migration processes
|
||||
* from running at the same time by using a lock mechanism.
|
||||
*
|
||||
* This is useful in production environments to avoid conflicts
|
||||
* or race conditions during concurrent deployments.
|
||||
*/
|
||||
public bool $lock = false;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
namespace Config;
|
||||
|
||||
/**
|
||||
* Mimes
|
||||
*
|
||||
* This file contains an array of mime types. It is used by the
|
||||
* Upload class to help identify allowed file types.
|
||||
*
|
||||
@@ -15,8 +13,6 @@ namespace Config;
|
||||
*
|
||||
* When working with mime types, please make sure you have the ´fileinfo´
|
||||
* extension enabled to reliably detect the media types.
|
||||
*
|
||||
* @immutable
|
||||
*/
|
||||
class Mimes
|
||||
{
|
||||
@@ -482,13 +478,16 @@ class Mimes
|
||||
'application/sla',
|
||||
'application/vnd.ms-pki.stl',
|
||||
'application/x-navistyle',
|
||||
'model/stl',
|
||||
'application/octet-stream',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Attempts to determine the best mime type for the given file extension.
|
||||
*
|
||||
* @return string|null The mime type found, or none if unable to determine.
|
||||
* @param string $extension
|
||||
* @return array|string|null The mime type found, or none if unable to determine.
|
||||
*/
|
||||
public static function guessTypeFromExtension(string $extension): array|string|null
|
||||
{
|
||||
@@ -524,7 +523,7 @@ class Mimes
|
||||
}
|
||||
|
||||
// Reverse check the mime type list if no extension was proposed.
|
||||
// This search is order sensitive!
|
||||
// This search is order-sensitive!
|
||||
foreach (static::$mimes as $ext => $types) {
|
||||
if (in_array($type, (array) $types, true)) {
|
||||
return $ext;
|
||||
|
||||
@@ -9,8 +9,6 @@ use CodeIgniter\Modules\Modules as BaseModules;
|
||||
*
|
||||
* NOTE: This class is required prior to Autoloader instantiation,
|
||||
* and does not extend BaseConfig.
|
||||
*
|
||||
* @immutable
|
||||
*/
|
||||
class Modules extends BaseModules
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Config;
|
||||
use App\Models\Appconfig;
|
||||
use CodeIgniter\Cache\CacheInterface;
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
|
||||
/**
|
||||
* This class holds the configuration options stored from the database so that on launch those settings can be cached
|
||||
@@ -34,11 +35,21 @@ class OSPOS extends BaseConfig
|
||||
if ($cache) {
|
||||
$this->settings = decode_array($cache);
|
||||
} else {
|
||||
$appconfig = model(Appconfig::class);
|
||||
foreach ($appconfig->get_all()->getResult() as $app_config) {
|
||||
$this->settings[$app_config->key] = $app_config->value;
|
||||
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 (DatabaseException $e) {
|
||||
// Database table doesn't exist yet (migrations haven't run)
|
||||
// Return empty settings to allow migration page to display
|
||||
$this->settings = [
|
||||
'language' => 'english',
|
||||
'language_code' => 'en',
|
||||
'company' => 'Home'
|
||||
];
|
||||
}
|
||||
$this->cache->save('settings', encode_array($this->settings));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,4 +61,4 @@ class OSPOS extends BaseConfig
|
||||
$this->cache->delete('settings');
|
||||
$this->set_settings();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace Config;
|
||||
* NOTE: This class does not extend BaseConfig for performance reasons.
|
||||
* So you cannot replace the property values with Environment Variables.
|
||||
*
|
||||
* @immutable
|
||||
* WARNING: Do not use these options when running the app in the Worker Mode.
|
||||
*/
|
||||
class Optimize
|
||||
{
|
||||
|
||||
@@ -15,8 +15,6 @@ namespace Config;
|
||||
*
|
||||
* NOTE: This class is required prior to Autoloader instantiation,
|
||||
* and does not extend BaseConfig.
|
||||
*
|
||||
* @immutable
|
||||
*/
|
||||
class Paths
|
||||
{
|
||||
@@ -77,4 +75,16 @@ class Paths
|
||||
* is used when no value is provided to `Services::renderer()`.
|
||||
*/
|
||||
public string $viewDirectory = __DIR__ . '/../Views';
|
||||
|
||||
/**
|
||||
* ---------------------------------------------------------------
|
||||
* ENVIRONMENT DIRECTORY NAME
|
||||
* ---------------------------------------------------------------
|
||||
*
|
||||
* This variable must contain the name of the directory where
|
||||
* the .env file is located.
|
||||
* Please consider security implications when changing this
|
||||
* value - the directory should not be publicly accessible.
|
||||
*/
|
||||
public string $envDirectory = __DIR__ . '/../../';
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ $routes->setDefaultController('Login');
|
||||
$routes->get('/', 'Login::index');
|
||||
$routes->get('login', 'Login::index');
|
||||
$routes->post('login', 'Login::index');
|
||||
$routes->post('migrate', 'Login::migrate');
|
||||
|
||||
$routes->add('no_access/index/(:segment)', 'No_access::index/$1');
|
||||
$routes->add('no_access/index/(:segment)/(:segment)', 'No_access::index/$1/$2');
|
||||
|
||||
@@ -96,6 +96,15 @@ class Routing extends BaseRouting
|
||||
*/
|
||||
public bool $autoRoute = true;
|
||||
|
||||
/**
|
||||
* If TRUE, the system will look for attributes on controller
|
||||
* class and methods that can run before and after the
|
||||
* controller/method.
|
||||
*
|
||||
* If FALSE, will ignore any attributes.
|
||||
*/
|
||||
public bool $useControllerAttributes = true;
|
||||
|
||||
/**
|
||||
* For Defined Routes.
|
||||
* If TRUE, will enable the use of the 'prioritize' option
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Config;
|
||||
|
||||
use App\Libraries\MY_Language;
|
||||
use Locale;
|
||||
use HTMLPurifier;
|
||||
use HTMLPurifier_Config;
|
||||
@@ -38,9 +39,11 @@ class Services extends BaseService
|
||||
/**
|
||||
* Responsible for loading the language string translations.
|
||||
*
|
||||
* @param string|null $locale
|
||||
* @param bool $getShared
|
||||
* @return MY_Language
|
||||
*/
|
||||
public static function language(?string $locale = null, bool $getShared = true)
|
||||
public static function language(?string $locale = null, bool $getShared = true): MY_Language
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('language', $locale)->setLocale($locale);
|
||||
@@ -55,12 +58,12 @@ class Services extends BaseService
|
||||
// Use '?:' for empty string check
|
||||
$locale = $locale ?: $requestLocale;
|
||||
|
||||
return new \App\Libraries\MY_Language($locale);
|
||||
return new MY_Language($locale);
|
||||
}
|
||||
|
||||
private static $htmlPurifier;
|
||||
private static HTMLPurifier $htmlPurifier;
|
||||
|
||||
public static function htmlPurifier($getShared = true)
|
||||
public static function htmlPurifier($getShared = true): object
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('htmlPurifier');
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
namespace Config;
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
use CodeIgniter\Session\Handlers\BaseHandler;
|
||||
use CodeIgniter\Session\Handlers\DatabaseHandler;
|
||||
use CodeIgniter\Session\Handlers\FileHandler;
|
||||
|
||||
class Session extends BaseConfig
|
||||
{
|
||||
@@ -124,4 +126,23 @@ class Session extends BaseConfig
|
||||
* seconds.
|
||||
*/
|
||||
public int $lockMaxRetries = 300;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
if ($this->driver === DatabaseHandler::class) {
|
||||
try {
|
||||
$db = Database::connect();
|
||||
|
||||
if (!$db->tableExists($this->savePath)) {
|
||||
$this->driver = FileHandler::class;
|
||||
$this->savePath = WRITEPATH . 'session';
|
||||
}
|
||||
} catch (DatabaseException $e) {
|
||||
$this->driver = FileHandler::class;
|
||||
$this->savePath = WRITEPATH . 'session';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,4 +119,29 @@ class Toolbar extends BaseConfig
|
||||
public array $watchedExtensions = [
|
||||
'php', 'css', 'js', 'html', 'svg', 'json', 'env',
|
||||
];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Ignored HTTP Headers
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* CodeIgniter Debug Toolbar normally injects HTML and JavaScript into every
|
||||
* HTML response. This is correct for full page loads, but it breaks requests
|
||||
* that expect only a clean HTML fragment.
|
||||
*
|
||||
* Libraries like HTMX, Unpoly, and Hotwire (Turbo) update parts of the page or
|
||||
* manage navigation on the client side. Injecting the Debug Toolbar into their
|
||||
* responses can cause invalid HTML, duplicated scripts, or JavaScript errors
|
||||
* (such as infinite loops or "Maximum call stack size exceeded").
|
||||
*
|
||||
* Any request containing one of the following headers is treated as a
|
||||
* client-managed or partial request, and the Debug Toolbar injection is skipped.
|
||||
*
|
||||
* @var array<string, string|null>
|
||||
*/
|
||||
public array $disableOnHeaders = [
|
||||
'X-Requested-With' => 'xmlhttprequest', // AJAX requests
|
||||
'HX-Request' => 'true', // HTMX requests
|
||||
'X-Up-Version' => null, // Unpoly partial requests
|
||||
];
|
||||
}
|
||||
|
||||
@@ -230,9 +230,13 @@ class UserAgents extends BaseConfig
|
||||
*/
|
||||
public array $robots = [
|
||||
'googlebot' => 'Googlebot',
|
||||
'google-pagerenderer' => 'Google Page Renderer',
|
||||
'google-read-aloud' => 'Google Read Aloud',
|
||||
'google-safety' => 'Google Safety Bot',
|
||||
'msnbot' => 'MSNBot',
|
||||
'baiduspider' => 'Baiduspider',
|
||||
'bingbot' => 'Bing',
|
||||
'bingpreview' => 'BingPreview',
|
||||
'slurp' => 'Inktomi Slurp',
|
||||
'yahoo' => 'Yahoo',
|
||||
'ask jeeves' => 'Ask Jeeves',
|
||||
@@ -248,5 +252,11 @@ class UserAgents extends BaseConfig
|
||||
'ia_archiver' => 'Alexa Crawler',
|
||||
'MJ12bot' => 'Majestic-12',
|
||||
'Uptimebot' => 'Uptimebot',
|
||||
'duckduckbot' => 'DuckDuckBot',
|
||||
'sogou' => 'Sogou Spider',
|
||||
'exabot' => 'Exabot',
|
||||
'bot' => 'Generic Bot',
|
||||
'crawler' => 'Generic Crawler',
|
||||
'spider' => 'Generic Spider',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -135,4 +135,19 @@ class OSPOSRules
|
||||
{
|
||||
return parse_decimals($candidate) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a locale-aware decimal value is non-negative (>= 0).
|
||||
*
|
||||
* @param string $candidate
|
||||
* @param string|null $error
|
||||
* @return bool
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function nonNegativeDecimal(string $candidate, ?string &$error = null): bool
|
||||
{
|
||||
$value = parse_decimals($candidate);
|
||||
|
||||
return $value !== false && $value >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,4 +59,21 @@ class View extends BaseView
|
||||
* @var list<class-string<ViewDecoratorInterface>>
|
||||
*/
|
||||
public array $decorators = [];
|
||||
|
||||
/**
|
||||
* Subdirectory within app/Views for namespaced view overrides.
|
||||
*
|
||||
* Namespaced views will be searched in:
|
||||
*
|
||||
* app/Views/{$appOverridesFolder}/{Namespace}/{view_path}.{php|html...}
|
||||
*
|
||||
* This allows application-level overrides for package or module views
|
||||
* without modifying vendor source files.
|
||||
*
|
||||
* Examples:
|
||||
* 'overrides' -> app/Views/overrides/Example/Blog/post/card.php
|
||||
* 'vendor' -> app/Views/vendor/Example/Blog/post/card.php
|
||||
* '' -> app/Views/Example/Blog/post/card.php (direct mapping)
|
||||
*/
|
||||
public string $appOverridesFolder = 'overrides';
|
||||
}
|
||||
|
||||
62
app/Config/WorkerMode.php
Normal file
62
app/Config/WorkerMode.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Config;
|
||||
|
||||
/**
|
||||
* This configuration controls how CodeIgniter behaves when running
|
||||
* in worker mode (with FrankenPHP).
|
||||
*/
|
||||
class WorkerMode
|
||||
{
|
||||
/**
|
||||
* Persistent Services
|
||||
*
|
||||
* List of service names that should persist across requests.
|
||||
* These services will NOT be reset between requests.
|
||||
*
|
||||
* Services not in this list will be reset for each request to prevent
|
||||
* state leakage.
|
||||
*
|
||||
* Recommended persistent services:
|
||||
* - `autoloader`: PSR-4 autoloading configuration
|
||||
* - `locator`: File locator
|
||||
* - `exceptions`: Exception handler
|
||||
* - `commands`: CLI commands registry
|
||||
* - `codeigniter`: Main application instance
|
||||
* - `superglobals`: Superglobals wrapper
|
||||
* - `routes`: Router configuration
|
||||
* - `cache`: Cache instance
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
public array $persistentServices = [
|
||||
'autoloader',
|
||||
'locator',
|
||||
'exceptions',
|
||||
'commands',
|
||||
'codeigniter',
|
||||
'superglobals',
|
||||
'routes',
|
||||
'cache',
|
||||
];
|
||||
|
||||
/**
|
||||
* Reset Event Listeners
|
||||
*
|
||||
* List of event names whose listeners should be removed between requests.
|
||||
* Use this if you register event listeners inside other event callbacks
|
||||
* (rather than at the top level of Config/Events.php), which would cause
|
||||
* them to accumulate across requests in worker mode.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
public array $resetEventListeners = [];
|
||||
|
||||
/**
|
||||
* Force Garbage Collection
|
||||
*
|
||||
* Whether to force garbage collection after each request.
|
||||
* Helps prevent memory leaks at a small performance cost.
|
||||
*/
|
||||
public bool $forceGarbageCollection = true;
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Attribute;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\Services;
|
||||
|
||||
require_once('Secure_Controller.php');
|
||||
@@ -24,19 +25,19 @@ class Attributes extends Secure_Controller
|
||||
/**
|
||||
* Gets and sends the main view for Attributes to the browser.
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
**/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
$data['table_headers'] = get_attribute_definition_manage_table_headers();
|
||||
|
||||
echo view('attributes/manage', $data);
|
||||
return view('attributes/manage', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns attribute table data rows. This will be called with AJAX.
|
||||
*/
|
||||
public function getSearch(): void
|
||||
public function getSearch(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('search');
|
||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||
@@ -53,15 +54,15 @@ class Attributes extends Secure_Controller
|
||||
$data_rows[] = get_attribute_definition_data_row($attribute_row);
|
||||
}
|
||||
|
||||
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX called function which saves the attribute value sent via POST by using the model save function.
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSaveAttributeValue(): void
|
||||
public function postSaveAttributeValue(): ResponseInterface
|
||||
{
|
||||
$success = $this->attribute->saveAttributeValue(
|
||||
html_entity_decode($this->request->getPost('attribute_value')),
|
||||
@@ -70,32 +71,32 @@ class Attributes extends Secure_Controller
|
||||
$this->request->getPost('attribute_id', FILTER_SANITIZE_NUMBER_INT) ?? false
|
||||
);
|
||||
|
||||
echo json_encode(['success' => $success != 0]);
|
||||
return $this->response->setJSON(['success' => $success != 0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX called function deleting an attribute value using the model delete function.
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postDeleteDropdownAttributeValue(): void
|
||||
public function postDeleteDropdownAttributeValue(): ResponseInterface
|
||||
{
|
||||
$success = $this->attribute->deleteDropdownAttributeValue(
|
||||
html_entity_decode($this->request->getPost('attribute_value')),
|
||||
$this->request->getPost('definition_id', FILTER_SANITIZE_NUMBER_INT)
|
||||
);
|
||||
|
||||
echo json_encode(['success' => $success]);
|
||||
return $this->response->setJSON(['success' => $success]);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX called function which saves the attribute definition.
|
||||
*
|
||||
* @param int $definition_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSaveDefinition(int $definition_id = NO_DEFINITION_ID): void
|
||||
public function postSaveDefinition(int $definition_id = NO_DEFINITION_ID): ResponseInterface
|
||||
{
|
||||
$definition_flags = 0;
|
||||
|
||||
@@ -105,12 +106,24 @@ class Attributes extends Secure_Controller
|
||||
$definition_flags |= $flag;
|
||||
}
|
||||
|
||||
// Validate definition_group (definition_fk) foreign key
|
||||
$definition_group_input = $this->request->getPost('definition_group');
|
||||
$definition_fk = $this->validateDefinitionGroup($definition_group_input);
|
||||
|
||||
if ($definition_fk === false) {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Attributes.definition_invalid_group'),
|
||||
'id' => NEW_ENTRY
|
||||
]);
|
||||
}
|
||||
|
||||
// Save definition data
|
||||
$definition_data = [
|
||||
'definition_name' => $this->request->getPost('definition_name'),
|
||||
'definition_unit' => $this->request->getPost('definition_unit') != '' ? $this->request->getPost('definition_unit') : null,
|
||||
'definition_flags' => $definition_flags,
|
||||
'definition_fk' => $this->request->getPost('definition_group') != '' ? $this->request->getPost('definition_group') : null
|
||||
'definition_fk' => $definition_fk
|
||||
];
|
||||
|
||||
if ($this->request->getPost('definition_type') != null) {
|
||||
@@ -119,7 +132,7 @@ class Attributes extends Secure_Controller
|
||||
|
||||
$definition_name = $definition_data['definition_name'];
|
||||
|
||||
if ($this->attribute->save_definition($definition_data, $definition_id)) {
|
||||
if ($this->attribute->saveDefinition($definition_data, $definition_id)) {
|
||||
// New definition
|
||||
if ($definition_id == NO_DEFINITION_ID) {
|
||||
$definition_values = json_decode(html_entity_decode($this->request->getPost('definition_values')));
|
||||
@@ -128,20 +141,20 @@ class Attributes extends Secure_Controller
|
||||
$this->attribute->saveAttributeValue($definition_value, $definition_data['definition_id']);
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Attributes.definition_successful_adding') . ' ' . $definition_name,
|
||||
'id' => $definition_data['definition_id']
|
||||
]);
|
||||
} else { // Existing definition
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Attributes.definition_successful_updating') . ' ' . $definition_name,
|
||||
'id' => $definition_id
|
||||
]);
|
||||
}
|
||||
} else { // Failure
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Attributes.definition_error_adding_updating', [$definition_name]),
|
||||
'id' => NEW_ENTRY
|
||||
@@ -149,30 +162,56 @@ class Attributes extends Secure_Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a definition_group foreign key.
|
||||
* Returns the validated integer ID, null if empty, or false if invalid.
|
||||
*
|
||||
* @param mixed $definition_group_input
|
||||
* @return int|null|false
|
||||
*/
|
||||
private function validateDefinitionGroup(mixed $definition_group_input): int|null|false
|
||||
{
|
||||
if ($definition_group_input === '' || $definition_group_input === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$definition_group_id = (int) $definition_group_input;
|
||||
|
||||
// Must be a positive integer, exist in attribute_definitions, and be of type GROUP
|
||||
if ($definition_group_id <= 0
|
||||
|| !$this->attribute->exists($definition_group_id)
|
||||
|| $this->attribute->getAttributeInfo($definition_group_id)->definition_type !== GROUP
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $definition_group_id;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param int $definition_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getSuggestAttribute(int $definition_id): void
|
||||
public function getSuggestAttribute(int $definition_id): ResponseInterface
|
||||
{
|
||||
$suggestions = $this->attribute->get_suggestions($definition_id, html_entity_decode($this->request->getGet('term')));
|
||||
|
||||
echo json_encode($suggestions);
|
||||
return $this->response->setJSON($suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $row_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getRow(int $row_id): void
|
||||
public function getRow(int $row_id): ResponseInterface
|
||||
{
|
||||
$attribute_definition_info = $this->attribute->getAttributeInfo($row_id);
|
||||
$attribute_definition_info->definition_flags = $this->get_attributes($attribute_definition_info->definition_flags);
|
||||
$data_row = get_attribute_definition_data_row($attribute_definition_info);
|
||||
|
||||
echo json_encode($data_row);
|
||||
return $this->response->setJSON($data_row);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,9 +231,9 @@ class Attributes extends Secure_Controller
|
||||
|
||||
/**
|
||||
* @param int $definition_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getView(int $definition_id = NO_DEFINITION_ID): void
|
||||
public function getView(int $definition_id = NO_DEFINITION_ID): string
|
||||
{
|
||||
$info = $this->attribute->getAttributeInfo($definition_id);
|
||||
foreach (get_object_vars($info) as $property => $value) {
|
||||
@@ -207,27 +246,27 @@ class Attributes extends Secure_Controller
|
||||
$data['definition_group'][''] = lang('Common.none_selected_text');
|
||||
$data['definition_info'] = $info;
|
||||
|
||||
$show_all = Attribute::SHOW_IN_ITEMS | Attribute::SHOW_IN_RECEIVINGS | Attribute::SHOW_IN_SALES;
|
||||
$show_all = Attribute::SHOW_IN_ITEMS | Attribute::SHOW_IN_RECEIVINGS | Attribute::SHOW_IN_SALES | Attribute::SHOW_IN_SEARCH | Attribute::SHOW_IN_CUSTOMERS | Attribute::SHOW_IN_EMPLOYEES | Attribute::SHOW_IN_SUPPLIERS;
|
||||
$data['definition_flags'] = $this->get_attributes($show_all);
|
||||
$selected_flags = $info->definition_flags === '' ? $show_all : $info->definition_flags;
|
||||
$data['selected_definition_flags'] = $this->get_attributes($selected_flags);
|
||||
|
||||
echo view('attributes/form', $data);
|
||||
return view('attributes/form', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an attribute definition
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postDelete(): void
|
||||
public function postDelete(): ResponseInterface
|
||||
{
|
||||
$attributes_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
if($this->attribute->deleteDefinitionList($attributes_to_delete)) {
|
||||
$message = lang('Attributes.definition_successful_deleted') . ' ' . count($attributes_to_delete) . ' ' . lang('Attributes.definition_one_or_multiple');
|
||||
echo json_encode(['success' => true, 'message' => $message]);
|
||||
return $this->response->setJSON(['success' => true, 'message' => $message]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => lang('Attributes.definition_cannot_be_deleted')]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Attributes.definition_cannot_be_deleted')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,56 +3,46 @@
|
||||
namespace App\Controllers;
|
||||
|
||||
use CodeIgniter\Controller;
|
||||
use CodeIgniter\HTTP\CLIRequest;
|
||||
use CodeIgniter\HTTP\IncomingRequest;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Class BaseController
|
||||
*
|
||||
* BaseController provides a convenient place for loading components
|
||||
* and performing functions that are needed by all your controllers.
|
||||
* Extend this class in any new controllers:
|
||||
* class Home extends BaseController
|
||||
*
|
||||
* For security be sure to declare any new methods as protected or private.
|
||||
* Extend this class in any new controllers:
|
||||
* ```
|
||||
* class Home extends BaseController
|
||||
* ```
|
||||
*
|
||||
* For security, be sure to declare any new methods as protected or private.
|
||||
*/
|
||||
abstract class BaseController extends Controller
|
||||
{
|
||||
/**
|
||||
* Instance of the main Request object.
|
||||
*
|
||||
* @var CLIRequest|IncomingRequest
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* An array of helpers to be loaded automatically upon
|
||||
* class instantiation. These helpers will be available
|
||||
* to all other controllers that extend BaseController.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $helpers = [];
|
||||
|
||||
/**
|
||||
* Be sure to declare properties for any property fetch you initialized.
|
||||
* The creation of dynamic property is deprecated in PHP 8.2.
|
||||
*/
|
||||
|
||||
// protected $session;
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @param LoggerInterface $logger
|
||||
* @return void
|
||||
*/
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger): void
|
||||
{
|
||||
// Do Not Edit This Line
|
||||
// Load here all helpers you want to be available in your controllers that extend BaseController.
|
||||
// Caution: Do not put the this below the parent::initController() call below.
|
||||
// $this->helpers = ['form', 'url'];
|
||||
|
||||
// Caution: Do not edit this line.
|
||||
parent::initController($request, $response, $logger);
|
||||
|
||||
// Preload any models, libraries, etc, here.
|
||||
|
||||
// E.g.: $this->session = service('session');
|
||||
// $this->session = service('session');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Controllers;
|
||||
use App\Models\Cashup;
|
||||
use App\Models\Expense;
|
||||
use App\Models\Reports\Summary_payments;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\OSPOS;
|
||||
use Config\Services;
|
||||
|
||||
@@ -26,22 +27,25 @@ class Cashups extends Secure_Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
$data['table_headers'] = get_cashups_manage_table_headers();
|
||||
|
||||
// filters that will be loaded in the multiselect dropdown
|
||||
$data['filters'] = ['is_deleted' => lang('Cashups.is_deleted')];
|
||||
|
||||
echo view('cashups/manage', $data);
|
||||
// Restore filters from URL
|
||||
$data = array_merge($data, restoreTableFilters($this->request));
|
||||
|
||||
return view('cashups/manage', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function getSearch(): void
|
||||
public function getSearch(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('search');
|
||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||
@@ -64,14 +68,14 @@ class Cashups extends Secure_Controller
|
||||
$data_rows[] = get_cash_up_data_row($cash_up);
|
||||
}
|
||||
|
||||
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $cashup_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getView(int $cashup_id = NEW_ENTRY): void
|
||||
public function getView(int $cashup_id = NEW_ENTRY): string
|
||||
{
|
||||
$data = [];
|
||||
|
||||
@@ -180,26 +184,26 @@ class Cashups extends Secure_Controller
|
||||
|
||||
$data['cash_ups_info'] = $cash_ups_info;
|
||||
|
||||
echo view("cashups/form", $data);
|
||||
return view("cashups/form", $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $row_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getRow(int $row_id): void
|
||||
public function getRow(int $row_id): ResponseInterface
|
||||
{
|
||||
$cash_ups_info = $this->cashup->get_info($row_id);
|
||||
$data_row = get_cash_up_data_row($cash_ups_info);
|
||||
|
||||
echo json_encode($data_row);
|
||||
return $this->response->setJSON($data_row);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $cashup_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postSave(int $cashup_id = NEW_ENTRY): void
|
||||
public function postSave(int $cashup_id = NEW_ENTRY): ResponseInterface
|
||||
{
|
||||
$open_date = $this->request->getPost('open_date');
|
||||
$open_date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $open_date);
|
||||
@@ -227,36 +231,36 @@ class Cashups extends Secure_Controller
|
||||
if ($this->cashup->save_value($cash_up_data, $cashup_id)) {
|
||||
// New cashup_id
|
||||
if ($cashup_id == NEW_ENTRY) {
|
||||
echo json_encode(['success' => true, 'message' => lang('Cashups.successful_adding'), 'id' => $cash_up_data['cashup_id']]);
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Cashups.successful_adding'), 'id' => $cash_up_data['cashup_id']]);
|
||||
} else { // Existing Cashup
|
||||
echo json_encode(['success' => true, 'message' => lang('Cashups.successful_updating'), 'id' => $cashup_id]);
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Cashups.successful_updating'), 'id' => $cashup_id]);
|
||||
}
|
||||
} else { // Failure
|
||||
echo json_encode(['success' => false, 'message' => lang('Cashups.error_adding_updating'), 'id' => NEW_ENTRY]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Cashups.error_adding_updating'), 'id' => NEW_ENTRY]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postDelete(): void
|
||||
public function postDelete(): ResponseInterface
|
||||
{
|
||||
$cash_ups_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
if ($this->cashup->delete_list($cash_ups_to_delete)) {
|
||||
echo json_encode(['success' => true, 'message' => lang('Cashups.successful_deleted') . ' ' . count($cash_ups_to_delete) . ' ' . lang('Cashups.one_or_multiple'), 'ids' => $cash_ups_to_delete]);
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Cashups.successful_deleted') . ' ' . count($cash_ups_to_delete) . ' ' . lang('Cashups.one_or_multiple'), 'ids' => $cash_ups_to_delete]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => lang('Cashups.cannot_be_deleted'), 'ids' => $cash_ups_to_delete]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Cashups.cannot_be_deleted'), 'ids' => $cash_ups_to_delete]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the total for cashups. Used in app\Views\cashups\form.php
|
||||
*
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postAjax_cashup_total(): void
|
||||
public function postAjax_cashup_total(): ResponseInterface
|
||||
{
|
||||
$open_amount_cash = parse_decimals($this->request->getPost('open_amount_cash'));
|
||||
$transfer_amount_cash = parse_decimals($this->request->getPost('transfer_amount_cash'));
|
||||
@@ -267,7 +271,7 @@ class Cashups extends Secure_Controller
|
||||
|
||||
$total = $this->_calculate_total($open_amount_cash, $transfer_amount_cash, $closed_amount_due, $closed_amount_cash, $closed_amount_card, $closed_amount_check); // TODO: hungarian notation
|
||||
|
||||
echo json_encode(['total' => to_currency_no_money($total)]);
|
||||
return $this->response->setJSON(['total' => to_currency_no_money($total)]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,12 +11,14 @@ use App\Models\Appconfig;
|
||||
use App\Models\Attribute;
|
||||
use App\Models\Customer_rewards;
|
||||
use App\Models\Dinner_table;
|
||||
use App\Models\Item;
|
||||
use App\Models\Module;
|
||||
use App\Models\Enums\Rounding_mode;
|
||||
use App\Models\Stock_location;
|
||||
use App\Models\Tax;
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use CodeIgniter\Encryption\EncrypterInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\Database;
|
||||
use Config\OSPOS;
|
||||
use Config\Services;
|
||||
@@ -215,8 +217,9 @@ class Config extends Secure_Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
$data['stock_locations'] = $this->stock_location->get_all()->getResultArray();
|
||||
$data['dinner_tables'] = $this->dinner_table->get_all()->getResultArray();
|
||||
@@ -224,6 +227,7 @@ class Config extends Secure_Controller
|
||||
$data['support_barcode'] = $this->barcode_lib->get_list_barcodes();
|
||||
$data['barcode_fonts'] = $this->barcode_lib->listfonts('fonts');
|
||||
$data['logo_exists'] = $this->config['company_logo'] != '';
|
||||
$data['logo_src'] = !empty($this->config['company_logo']) ? base_url('uploads/' . $this->config['company_logo']) : '';
|
||||
$data['line_sequence_options'] = $this->sale_lib->get_line_sequence_options();
|
||||
$data['register_mode_options'] = $this->sale_lib->get_register_mode_options();
|
||||
$data['invoice_type_options'] = $this->sale_lib->get_invoice_type_options();
|
||||
@@ -272,17 +276,17 @@ class Config extends Secure_Controller
|
||||
|
||||
$data['mailchimp']['lists'] = $this->_mailchimp();
|
||||
|
||||
echo view('configs/manage', $data);
|
||||
return view('configs/manage', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves company information. Used in app/Views/configs/info_config.php
|
||||
*
|
||||
* @throws ReflectionException
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSaveInfo(): void
|
||||
public function postSaveInfo(): ResponseInterface
|
||||
{
|
||||
$upload_data = $this->upload_logo();
|
||||
$upload_success = empty($upload_data['error']);
|
||||
@@ -306,7 +310,7 @@ class Config extends Secure_Controller
|
||||
$message = lang('Config.saved_' . ($success ? '' : 'un') . 'successfully');
|
||||
$message = $upload_success ? $message : strip_tags($upload_data['error']);
|
||||
|
||||
echo json_encode(['success' => $success, 'message' => $message]);
|
||||
return $this->response->setJSON(['success' => $success, 'message' => $message]);
|
||||
}
|
||||
|
||||
|
||||
@@ -358,11 +362,12 @@ class Config extends Secure_Controller
|
||||
* Saves general configuration. Used in app/Views/configs/general_config.php
|
||||
*
|
||||
* @throws ReflectionException
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSaveGeneral(): void
|
||||
public function postSaveGeneral(): ResponseInterface
|
||||
{
|
||||
$batch_save_data = [
|
||||
$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,
|
||||
@@ -381,9 +386,9 @@ class Config extends Secure_Controller
|
||||
'gcaptcha_enable' => $this->request->getPost('gcaptcha_enable') != null,
|
||||
'gcaptcha_secret_key' => $this->request->getPost('gcaptcha_secret_key'),
|
||||
'gcaptcha_site_key' => $this->request->getPost('gcaptcha_site_key'),
|
||||
'suggestions_first_column' => $this->request->getPost('suggestions_first_column'),
|
||||
'suggestions_second_column' => $this->request->getPost('suggestions_second_column'),
|
||||
'suggestions_third_column' => $this->request->getPost('suggestions_third_column'),
|
||||
'suggestions_first_column' => $this->validateSuggestionsColumn($this->request->getPost('suggestions_first_column'), 'first'),
|
||||
'suggestions_second_column' => $this->validateSuggestionsColumn($this->request->getPost('suggestions_second_column'), 'other'),
|
||||
'suggestions_third_column' => $this->validateSuggestionsColumn($this->request->getPost('suggestions_third_column'), 'other'),
|
||||
'giftcard_number' => $this->request->getPost('giftcard_number'),
|
||||
'derive_sale_quantity' => $this->request->getPost('derive_sale_quantity') != null,
|
||||
'multi_pack_enabled' => $this->request->getPost('multi_pack_enabled') != null,
|
||||
@@ -393,57 +398,67 @@ class Config extends Secure_Controller
|
||||
|
||||
$this->module->set_show_office_group($this->request->getPost('show_office_group') != null);
|
||||
|
||||
if ($batch_save_data['category_dropdown'] == 1) {
|
||||
$definition_data['definition_name'] = 'ospos_category';
|
||||
$definition_data['definition_flags'] = 0;
|
||||
$definition_data['definition_type'] = 'DROPDOWN';
|
||||
$definition_data['definition_id'] = CATEGORY_DEFINITION_ID;
|
||||
$definition_data['deleted'] = 0;
|
||||
$this->db->transStart();
|
||||
|
||||
$this->attribute->save_definition($definition_data, CATEGORY_DEFINITION_ID);
|
||||
} elseif ($batch_save_data['category_dropdown'] == NO_DEFINITION_ID) {
|
||||
$this->attribute->deleteDefinition(CATEGORY_DEFINITION_ID);
|
||||
$attributeSuccess = true;
|
||||
if ($batchSaveData['category_dropdown']) {
|
||||
$definitionData['definition_name'] = 'ospos_category';
|
||||
$definitionData['definition_flags'] = 0;
|
||||
$definitionData['definition_type'] = 'DROPDOWN';
|
||||
$definitionData['definition_id'] = CATEGORY_DEFINITION_ID;
|
||||
$definitionData['deleted'] = 0;
|
||||
|
||||
$attributeSuccess = $this->attribute->saveDefinition($definitionData, CATEGORY_DEFINITION_ID);
|
||||
} elseif ($batchSaveData['category_dropdown'] == NO_DEFINITION_ID) {
|
||||
$attributeSuccess = $this->attribute->deleteDefinition(CATEGORY_DEFINITION_ID);
|
||||
}
|
||||
|
||||
$success = $this->appconfig->batch_save($batch_save_data);
|
||||
$success = $attributeSuccess && $this->appconfig->batch_save($batchSaveData);
|
||||
|
||||
echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
$this->db->transComplete();
|
||||
|
||||
$success = $success && $this->db->transStatus();
|
||||
|
||||
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
|
||||
*
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postCheckNumberLocale(): void
|
||||
public function postCheckNumberLocale(): ResponseInterface
|
||||
{
|
||||
$number_locale = $this->request->getPost('number_locale');
|
||||
$save_number_locale = $this->request->getPost('save_number_locale');
|
||||
$numberLocale = $this->request->getPost('number_locale');
|
||||
$saveNumberLocale = $this->request->getPost('save_number_locale');
|
||||
$postedCurrencySymbol = $this->request->getPost('currency_symbol');
|
||||
$postedCurrencyCode = $this->request->getPost('currency_code');
|
||||
|
||||
$fmt = new NumberFormatter($number_locale, NumberFormatter::CURRENCY);
|
||||
if ($number_locale != $save_number_locale) {
|
||||
$currency_symbol = $fmt->getSymbol(NumberFormatter::CURRENCY_SYMBOL);
|
||||
$currency_code = $fmt->getTextAttribute(NumberFormatter::CURRENCY_CODE);
|
||||
$save_number_locale = $number_locale;
|
||||
} else {
|
||||
$currency_symbol = empty($this->request->getPost('currency_symbol')) ? $fmt->getSymbol(NumberFormatter::CURRENCY_SYMBOL) : $this->request->getPost('currency_symbol');
|
||||
$currency_code = empty($this->request->getPost('currency_code')) ? $fmt->getTextAttribute(NumberFormatter::CURRENCY_CODE) : $this->request->getPost('currency_code');
|
||||
$fmt = new NumberFormatter($numberLocale, NumberFormatter::CURRENCY);
|
||||
|
||||
// Use posted values if provided, otherwise fall back to locale defaults
|
||||
$currencySymbol = $postedCurrencySymbol !== '' ? $postedCurrencySymbol : $fmt->getSymbol(NumberFormatter::CURRENCY_SYMBOL);
|
||||
$currencyCode = $postedCurrencyCode !== '' ? $postedCurrencyCode : $fmt->getTextAttribute(NumberFormatter::CURRENCY_CODE);
|
||||
|
||||
// Update saved locale if it changed
|
||||
if ($numberLocale !== $saveNumberLocale) {
|
||||
$saveNumberLocale = $numberLocale;
|
||||
}
|
||||
|
||||
if ($this->request->getPost('thousands_separator') == 'false') {
|
||||
$fmt->setTextAttribute(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, '');
|
||||
}
|
||||
|
||||
$fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL, $currency_symbol);
|
||||
$number_local_example = $fmt->format(1234567890.12300);
|
||||
$fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL, $currencySymbol);
|
||||
$numberLocaleExample = $fmt->format(1234567890.12300);
|
||||
|
||||
echo json_encode([
|
||||
'success' => $number_local_example != false,
|
||||
'save_number_locale' => $save_number_locale,
|
||||
'number_locale_example' => $number_local_example,
|
||||
'currency_symbol' => $currency_symbol,
|
||||
'currency_code' => $currency_code,
|
||||
return $this->response->setJSON([
|
||||
'success' => $numberLocaleExample != false,
|
||||
'save_number_locale' => $saveNumberLocale,
|
||||
'number_locale_example' => $numberLocaleExample,
|
||||
'currency_symbol' => $currencySymbol,
|
||||
'currency_code' => $currencyCode,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -451,14 +466,15 @@ class Config extends Secure_Controller
|
||||
* Saves locale configuration. Used in app/Views/configs/locale_config.php
|
||||
*
|
||||
* @throws ReflectionException
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSaveLocale(): void
|
||||
public function postSaveLocale(): ResponseInterface
|
||||
{
|
||||
$exploded = explode(":", $this->request->getPost('language'));
|
||||
$currency_symbol = $this->request->getPost('currency_symbol');
|
||||
$batch_save_data = [
|
||||
'currency_symbol' => $this->request->getPost('currency_symbol'),
|
||||
'currency_symbol' => htmlspecialchars($currency_symbol ?? ''),
|
||||
'currency_code' => $this->request->getPost('currency_code'),
|
||||
'language_code' => $exploded[0],
|
||||
'language' => $exploded[1],
|
||||
@@ -480,17 +496,17 @@ class Config extends Secure_Controller
|
||||
|
||||
$success = $this->appconfig->batch_save($batch_save_data);
|
||||
|
||||
echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
return $this->response->setJSON(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves email configuration. Used in app/Views/configs/email_config.php
|
||||
*
|
||||
* @throws ReflectionException
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSaveEmail(): void
|
||||
public function postSaveEmail(): ResponseInterface
|
||||
{
|
||||
$password = '';
|
||||
|
||||
@@ -498,9 +514,24 @@ class Config extends Secure_Controller
|
||||
$password = $this->encrypter->encrypt($this->request->getPost('smtp_pass'));
|
||||
}
|
||||
|
||||
$protocol = $this->request->getPost('protocol');
|
||||
$mailpath = $this->request->getPost('mailpath');
|
||||
|
||||
// Validate mailpath: required for sendmail, optional for others but must be safe if provided
|
||||
$isMailpathRequired = ($protocol === 'sendmail');
|
||||
$isMailpathProvided = !empty($mailpath);
|
||||
$isMailpathValid = $isMailpathProvided && preg_match('/^[a-zA-Z0-9_\-\/.]+$/', $mailpath);
|
||||
|
||||
if (($isMailpathRequired && !$isMailpathProvided) || ($isMailpathProvided && !$isMailpathValid)) {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Config.mailpath_invalid')
|
||||
]);
|
||||
}
|
||||
|
||||
$batch_save_data = [
|
||||
'protocol' => $this->request->getPost('protocol'),
|
||||
'mailpath' => $this->request->getPost('mailpath'),
|
||||
'protocol' => $protocol,
|
||||
'mailpath' => $mailpath,
|
||||
'smtp_host' => $this->request->getPost('smtp_host'),
|
||||
'smtp_user' => $this->request->getPost('smtp_user'),
|
||||
'smtp_pass' => $password,
|
||||
@@ -511,17 +542,17 @@ class Config extends Secure_Controller
|
||||
|
||||
$success = $this->appconfig->batch_save($batch_save_data);
|
||||
|
||||
echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
return $this->response->setJSON(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves SMS message configuration. Used in app/Views/configs/message_config.php.
|
||||
*
|
||||
* @throws ReflectionException
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSaveMessage(): void
|
||||
public function postSaveMessage(): ResponseInterface
|
||||
{
|
||||
$password = '';
|
||||
|
||||
@@ -538,7 +569,7 @@ class Config extends Secure_Controller
|
||||
|
||||
$success = $this->appconfig->batch_save($batch_save_data);
|
||||
|
||||
echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
return $this->response->setJSON(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -565,15 +596,15 @@ class Config extends Secure_Controller
|
||||
/**
|
||||
* Gets Mailchimp lists when a valid API key is inserted. Used in app/Views/configs/integrations_config.php
|
||||
*
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postCheckMailchimpApiKey(): void
|
||||
public function postCheckMailchimpApiKey(): ResponseInterface
|
||||
{
|
||||
$lists = $this->_mailchimp($this->request->getPost('mailchimp_api_key'));
|
||||
$success = count($lists) > 0;
|
||||
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => $success,
|
||||
'message' => lang('Config.mailchimp_key_' . ($success ? '' : 'un') . 'successfully'),
|
||||
'mailchimp_lists' => $lists
|
||||
@@ -584,10 +615,10 @@ class Config extends Secure_Controller
|
||||
* Saves Mailchimp configuration. Used in app/Views/configs/integrations_config.php
|
||||
*
|
||||
* @throws ReflectionException
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSaveMailchimp(): void
|
||||
public function postSaveMailchimp(): ResponseInterface
|
||||
{
|
||||
$api_key = '';
|
||||
$list_id = '';
|
||||
@@ -608,56 +639,56 @@ class Config extends Secure_Controller
|
||||
|
||||
$success = $this->appconfig->batch_save($batch_save_data);
|
||||
|
||||
echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
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
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getStockLocations(): void
|
||||
public function getStockLocations(): string
|
||||
{
|
||||
$stock_locations = $this->stock_location->get_all()->getResultArray();
|
||||
|
||||
echo view('partial/stock_locations', ['stock_locations' => $stock_locations]);
|
||||
return view('partial/stock_locations', ['stock_locations' => $stock_locations]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getDinnerTables(): void
|
||||
public function getDinnerTables(): string
|
||||
{
|
||||
$dinner_tables = $this->dinner_table->get_all()->getResultArray();
|
||||
|
||||
echo view('partial/dinner_tables', ['dinner_tables' => $dinner_tables]);
|
||||
return view('partial/dinner_tables', ['dinner_tables' => $dinner_tables]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets all tax categories.
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function ajax_tax_categories(): void // TODO: Is this function called anywhere in the code?
|
||||
public function ajax_tax_categories(): string // TODO: Is this function called anywhere in the code?
|
||||
{
|
||||
$tax_categories = $this->tax->get_all_tax_categories()->getResultArray();
|
||||
|
||||
echo view('partial/tax_categories', ['tax_categories' => $tax_categories]);
|
||||
return view('partial/tax_categories', ['tax_categories' => $tax_categories]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all customer rewards. Used in app/Views/configs/reward_config.php
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getCustomerRewards(): void
|
||||
public function getCustomerRewards(): string
|
||||
{
|
||||
$customer_rewards = $this->customer_rewards->get_all()->getResultArray();
|
||||
|
||||
echo view('partial/customer_rewards', ['customer_rewards' => $customer_rewards]);
|
||||
return view('partial/customer_rewards', ['customer_rewards' => $customer_rewards]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -677,10 +708,10 @@ class Config extends Secure_Controller
|
||||
/**
|
||||
* Saves stock locations. Used in app/Views/configs/stock_config.php
|
||||
*
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSaveLocations(): void
|
||||
public function postSaveLocations(): ResponseInterface
|
||||
{
|
||||
$this->db->transStart();
|
||||
|
||||
@@ -712,17 +743,17 @@ class Config extends Secure_Controller
|
||||
|
||||
$success = $this->db->transStatus();
|
||||
|
||||
echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
return $this->response->setJSON(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all dinner tables. Used in app/Views/configs/table_config.php
|
||||
*
|
||||
* @throws ReflectionException
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSaveTables(): void
|
||||
public function postSaveTables(): ResponseInterface
|
||||
{
|
||||
$this->db->transStart();
|
||||
|
||||
@@ -759,17 +790,17 @@ class Config extends Secure_Controller
|
||||
|
||||
$success = $this->db->transStatus();
|
||||
|
||||
echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
return $this->response->setJSON(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves tax configuration. Used in app/Views/configs/tax_config.php
|
||||
*
|
||||
* @throws ReflectionException
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSaveTax(): void
|
||||
public function postSaveTax(): ResponseInterface
|
||||
{
|
||||
$default_tax_1_rate = $this->request->getPost('default_tax_1_rate');
|
||||
$default_tax_2_rate = $this->request->getPost('default_tax_2_rate');
|
||||
@@ -791,17 +822,17 @@ class Config extends Secure_Controller
|
||||
|
||||
$message = lang('Config.saved_' . ($success ? '' : 'un') . 'successfully');
|
||||
|
||||
echo json_encode(['success' => $success, 'message' => $message]);
|
||||
return $this->response->setJSON(['success' => $success, 'message' => $message]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves customer rewards configuration. Used in app/Views/configs/reward_config.php
|
||||
*
|
||||
* @throws ReflectionException
|
||||
* @return void
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSaveRewards(): void
|
||||
* @throws ReflectionException
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSaveRewards(): ResponseInterface
|
||||
{
|
||||
$this->db->transStart();
|
||||
|
||||
@@ -845,17 +876,17 @@ class Config extends Secure_Controller
|
||||
|
||||
$success = $this->db->transStatus();
|
||||
|
||||
echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
return $this->response->setJSON(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves barcode configuration. Used in app/Views/configs/barcode_config.php
|
||||
*
|
||||
* @throws ReflectionException
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSaveBarcode(): void
|
||||
public function postSaveBarcode(): ResponseInterface
|
||||
{
|
||||
$batch_save_data = [
|
||||
'barcode_type' => $this->request->getPost('barcode_type'),
|
||||
@@ -877,17 +908,17 @@ class Config extends Secure_Controller
|
||||
|
||||
$success = $this->appconfig->batch_save($batch_save_data);
|
||||
|
||||
echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
return $this->response->setJSON(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves receipt configuration. Used in app/Views/configs/receipt_config.php.
|
||||
*
|
||||
* @throws ReflectionException
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSaveReceipt(): void
|
||||
public function postSaveReceipt(): ResponseInterface
|
||||
{
|
||||
$batch_save_data = [
|
||||
'receipt_template' => $this->request->getPost('receipt_template'),
|
||||
@@ -912,17 +943,17 @@ class Config extends Secure_Controller
|
||||
|
||||
$success = $this->appconfig->batch_save($batch_save_data);
|
||||
|
||||
echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
return $this->response->setJSON(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves invoice configuration. Used in app/Views/configs/invoice_config.php.
|
||||
*
|
||||
* @throws ReflectionException
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSaveInvoice(): void
|
||||
public function postSaveInvoice(): ResponseInterface
|
||||
{
|
||||
$batch_save_data = [
|
||||
'invoice_enable' => $this->request->getPost('invoice_enable') != null,
|
||||
@@ -938,7 +969,9 @@ 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' => $this->request->getPost('invoice_type')
|
||||
'invoice_type' => Sale_lib::isValidInvoiceType($this->request->getPost('invoice_type'))
|
||||
? $this->request->getPost('invoice_type')
|
||||
: 'invoice'
|
||||
];
|
||||
|
||||
$success = $this->appconfig->batch_save($batch_save_data);
|
||||
@@ -953,20 +986,42 @@ class Config extends Secure_Controller
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
return $this->response->setJSON(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the company logo from the database. Used in app/Views/configs/info_config.php.
|
||||
*
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @throws ReflectionException
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postRemoveLogo(): void
|
||||
public function postRemoveLogo(): ResponseInterface
|
||||
{
|
||||
$success = $this->appconfig->save(['company_logo' => '']);
|
||||
|
||||
echo json_encode(['success' => $success]);
|
||||
return $this->response->setJSON(['success' => $success]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates suggestions column configuration to prevent SQL injection.
|
||||
*
|
||||
* @param mixed $column The column value from POST
|
||||
* @param string $fieldType Either 'first' or 'other' to determine default fallback
|
||||
* @return string Validated column name
|
||||
*/
|
||||
private function validateSuggestionsColumn(mixed $column, string $fieldType): string
|
||||
{
|
||||
if (!is_string($column)) {
|
||||
return $fieldType === 'first' ? 'name' : '';
|
||||
}
|
||||
|
||||
$allowed = $fieldType === 'first'
|
||||
? Item::ALLOWED_SUGGESTIONS_COLUMNS
|
||||
: Item::ALLOWED_SUGGESTIONS_COLUMNS_WITH_EMPTY;
|
||||
|
||||
$fallback = $fieldType === 'first' ? 'name' : '';
|
||||
|
||||
return in_array($column, $allowed, true) ? $column : $fallback;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,64 +3,57 @@
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Libraries\Mailchimp_lib;
|
||||
|
||||
use App\Models\Attribute;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Customer_rewards;
|
||||
use App\Models\Tax_code;
|
||||
use CodeIgniter\HTTP\DownloadResponse;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\OSPOS;
|
||||
use Config\Services;
|
||||
use stdClass;
|
||||
|
||||
class Customers extends Persons
|
||||
{
|
||||
private string $_list_id;
|
||||
private Mailchimp_lib $mailchimp_lib;
|
||||
private Customer_rewards $customer_rewards;
|
||||
private string $listId;
|
||||
private Mailchimp_lib $mailchimpLib;
|
||||
private Customer_rewards $customerRewards;
|
||||
private Customer $customer;
|
||||
private Tax_code $tax_code;
|
||||
private array $config;
|
||||
private Tax_code $taxCode;
|
||||
private array $appConfig;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('customers');
|
||||
$this->mailchimp_lib = new Mailchimp_lib();
|
||||
$this->customer_rewards = model(Customer_rewards::class);
|
||||
$this->mailchimpLib = new Mailchimp_lib();
|
||||
$this->customerRewards = model(Customer_rewards::class);
|
||||
$this->customer = model(Customer::class);
|
||||
$this->tax_code = model(Tax_code::class);
|
||||
$this->config = config(OSPOS::class)->settings;
|
||||
$this->taxCode = model(Tax_code::class);
|
||||
$this->appConfig = config(OSPOS::class)->settings;
|
||||
|
||||
$encrypter = Services::encrypter();
|
||||
|
||||
if (!empty($this->config['mailchimp_list_id'])) {
|
||||
$this->_list_id = $encrypter->decrypt($this->config['mailchimp_list_id']);
|
||||
if (!empty($this->appConfig['mailchimp_list_id'])) {
|
||||
$this->listId = $encrypter->decrypt($this->appConfig['mailchimp_list_id']);
|
||||
} else {
|
||||
$this->_list_id = '';
|
||||
$this->listId = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
$data['table_headers'] = get_customer_manage_table_headers();
|
||||
|
||||
echo view('people/manage', $data);
|
||||
return view('people/manage', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets one row for a customer manage table. This is called using AJAX to update one row.
|
||||
*/
|
||||
public function getRow(int $row_id): void
|
||||
public function getRow(int $rowId): ResponseInterface
|
||||
{
|
||||
$person = $this->customer->get_info($row_id);
|
||||
$person = $this->customer->get_info($rowId);
|
||||
|
||||
// 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.
|
||||
$stats = $this->customer->get_stats($person->person_id);
|
||||
|
||||
if (empty($stats)) {
|
||||
// Create object with empty properties.
|
||||
$stats = new stdClass();
|
||||
$stats->total = 0;
|
||||
$stats->min = 0;
|
||||
@@ -70,18 +63,12 @@ class Customers extends Persons
|
||||
$stats->quantity = 0;
|
||||
}
|
||||
|
||||
$data_row = get_customer_data_row($person, $stats);
|
||||
$dataRow = get_customer_data_row($person, $stats);
|
||||
|
||||
echo json_encode($data_row);
|
||||
return $this->response->setJSON($dataRow);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns customer table data rows. This will be called with AJAX.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getSearch(): void
|
||||
public function getSearch(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('search');
|
||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||
@@ -90,15 +77,13 @@ class Customers extends Persons
|
||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
$customers = $this->customer->search($search, $limit, $offset, $sort, $order);
|
||||
$total_rows = $this->customer->get_found_rows($search);
|
||||
$totalRows = $this->customer->get_found_rows($search);
|
||||
|
||||
$data_rows = [];
|
||||
$dataRows = [];
|
||||
|
||||
foreach ($customers->getResult() as $person) {
|
||||
// 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: duplicated... see above
|
||||
$stats = $this->customer->get_stats($person->person_id);
|
||||
if (empty($stats)) {
|
||||
// Create object with empty properties.
|
||||
$stats = new stdClass();
|
||||
$stats->total = 0;
|
||||
$stats->min = 0;
|
||||
@@ -108,43 +93,33 @@ class Customers extends Persons
|
||||
$stats->quantity = 0;
|
||||
}
|
||||
|
||||
$data_rows[] = get_customer_data_row($person, $stats);
|
||||
$dataRows[] = get_customer_data_row($person, $stats);
|
||||
}
|
||||
|
||||
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
return $this->response->setJSON(['total' => $totalRows, 'rows' => $dataRows]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives search suggestions based on what is being searched for
|
||||
*/
|
||||
public function getSuggest(): void
|
||||
public function getSuggest(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('term');
|
||||
$suggestions = $this->customer->get_search_suggestions($search);
|
||||
|
||||
echo json_encode($suggestions);
|
||||
return $this->response->setJSON($suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function suggest_search(): void
|
||||
public function suggestSearch(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('term');
|
||||
$suggestions = $this->customer->get_search_suggestions($search, 25, false);
|
||||
|
||||
echo json_encode($suggestions);
|
||||
return $this->response->setJSON($suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the customer edit form
|
||||
*/
|
||||
public function getView(int $customer_id = NEW_ENTRY): void
|
||||
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->get_info($customerId);
|
||||
foreach (get_object_vars($info) as $property => $value) {
|
||||
$info->$property = $value;
|
||||
}
|
||||
@@ -155,28 +130,27 @@ 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);
|
||||
$data['employee'] = $employee_info->first_name . ' ' . $employee_info->last_name;
|
||||
$employeeInfo = $this->employee->get_info($info->employee_id);
|
||||
$data['employee'] = $employeeInfo->first_name . ' ' . $employeeInfo->last_name;
|
||||
|
||||
$tax_code_info = $this->tax_code->get_info($info->sales_tax_code_id);
|
||||
$taxCodeInfo = $this->taxCode->get_info($info->sales_tax_code_id);
|
||||
|
||||
if ($tax_code_info->tax_code != null) {
|
||||
$data['sales_tax_code_label'] = $tax_code_info->tax_code . ' ' . $tax_code_info->tax_code_name;
|
||||
if ($taxCodeInfo->tax_code != null) {
|
||||
$data['sales_tax_code_label'] = $taxCodeInfo->tax_code . ' ' . $taxCodeInfo->tax_code_name;
|
||||
} else {
|
||||
$data['sales_tax_code_label'] = '';
|
||||
}
|
||||
|
||||
$packages = ['' => lang('Items.none')];
|
||||
foreach ($this->customer_rewards->get_all()->getResultArray() as $row) {
|
||||
foreach ($this->customerRewards->get_all()->getResultArray() as $row) {
|
||||
$packages[$row['package_id']] = $row['package_name'];
|
||||
}
|
||||
$data['packages'] = $packages;
|
||||
$data['selected_package'] = $info->package_id;
|
||||
|
||||
$data['use_destination_based_tax'] = $this->config['use_destination_based_tax'];
|
||||
$data['use_destination_based_tax'] = $this->appConfig['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;
|
||||
@@ -184,14 +158,11 @@ 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;
|
||||
if (($mailchimpInfo = $this->mailchimpLib->getMemberInfo($this->listId, $info->email)) !== false) {
|
||||
$data['mailchimp_info'] = $mailchimpInfo;
|
||||
|
||||
// Collect customer Mailchimp emails activities (stats)
|
||||
if (($activities = $this->mailchimp_lib->getMemberActivity($this->_list_id, $info->email)) !== false) {
|
||||
if (($activities = $this->mailchimpLib->getMemberActivity($this->listId, $info->email)) !== false) {
|
||||
if (array_key_exists('activity', $activities)) {
|
||||
$open = 0;
|
||||
$unopen = 0;
|
||||
@@ -227,25 +198,29 @@ class Customers extends Persons
|
||||
}
|
||||
}
|
||||
|
||||
echo view("customers/form", $data);
|
||||
return view("customers/form", $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts/updates a customer
|
||||
* Gets person attributes for a customer (AJAX)
|
||||
*/
|
||||
public function postSave(int $customer_id = NEW_ENTRY): void
|
||||
public function getAttributes(int $customerId = NEW_ENTRY): string
|
||||
{
|
||||
$first_name = $this->request->getPost('first_name');
|
||||
$last_name = $this->request->getPost('last_name');
|
||||
return $this->getPersonAttributes($customerId, Attribute::SHOW_IN_CUSTOMERS);
|
||||
}
|
||||
|
||||
public function postSave(int $customerId = NEW_ENTRY): ResponseInterface
|
||||
{
|
||||
$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'),
|
||||
@@ -258,9 +233,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->appConfig['dateformat'] . ' ' . $this->appConfig['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'),
|
||||
@@ -269,110 +244,89 @@ 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,
|
||||
if ($this->customer->save_customer($personData, $customerData, $customerId)) {
|
||||
$personId = $customerId == NEW_ENTRY ? $customerData['person_id'] : $customerId;
|
||||
$this->savePersonAttributes($personId, Attribute::SHOW_IN_CUSTOMERS);
|
||||
|
||||
$mailchimpStatus = $this->request->getPost('mailchimp_status');
|
||||
$this->mailchimpLib->addOrUpdateMember(
|
||||
$this->listId,
|
||||
$email,
|
||||
$first_name,
|
||||
$last_name,
|
||||
$mailchimp_status == null ? "" : $mailchimp_status,
|
||||
$firstName,
|
||||
$lastName,
|
||||
$mailchimpStatus == null ? "" : $mailchimpStatus,
|
||||
['vip' => $this->request->getPost('mailchimp_vip') != null]
|
||||
);
|
||||
|
||||
// New customer
|
||||
if ($customer_id == NEW_ENTRY) {
|
||||
echo json_encode([
|
||||
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
|
||||
echo json_encode([
|
||||
} else {
|
||||
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
|
||||
echo json_encode([
|
||||
} else {
|
||||
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
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if an email address already exists. Used in app/Views/customers/form.php
|
||||
*
|
||||
* @return void
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postCheckEmail(): void
|
||||
public function postCheckEmail(): ResponseInterface
|
||||
{
|
||||
$email = strtolower($this->request->getPost('email', FILTER_SANITIZE_EMAIL));
|
||||
$person_id = $this->request->getPost('person_id', FILTER_SANITIZE_NUMBER_INT);
|
||||
$personId = $this->request->getPost('person_id', FILTER_SANITIZE_NUMBER_INT);
|
||||
|
||||
$exists = $this->customer->check_email_exists($email, $person_id);
|
||||
$exists = $this->customer->check_email_exists($email, $personId);
|
||||
|
||||
echo !$exists ? 'true' : 'false';
|
||||
return $this->response->setJSON(!$exists ? 'true' : 'false');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if an account number already exists. Used in app/Views/customers/form.php
|
||||
*
|
||||
* @return void
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postCheckAccountNumber(): void
|
||||
public function postCheckAccountNumber(): ResponseInterface
|
||||
{
|
||||
$exists = $this->customer->check_account_number_exists($this->request->getPost('account_number'), $this->request->getPost('person_id', FILTER_SANITIZE_NUMBER_INT));
|
||||
|
||||
echo !$exists ? 'true' : 'false';
|
||||
return $this->response->setJSON(!$exists ? 'true' : 'false');
|
||||
}
|
||||
|
||||
/**
|
||||
* This deletes customers from the customers table
|
||||
*/
|
||||
public function postDelete(): void
|
||||
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');
|
||||
$customersInfo = $this->customer->get_multiple_info($customersToDelete);
|
||||
|
||||
$count = 0;
|
||||
|
||||
foreach ($customers_info->getResult() as $info) {
|
||||
foreach ($customersInfo->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);
|
||||
$this->mailchimpLib->removeMember($this->listId, $info->email);
|
||||
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($count == count($customers_to_delete)) {
|
||||
echo json_encode([
|
||||
if ($count == count($customersToDelete)) {
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Customers.successful_deleted') . ' ' . $count . ' ' . lang('Customers.one_or_multiple')
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => lang('Customers.cannot_be_deleted')]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Customers.cannot_be_deleted')]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Customers import from csv spreadsheet
|
||||
*
|
||||
* @return DownloadResponse The template for Customer CSV imports is returned and download forced.
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getCsv(): DownloadResponse
|
||||
{
|
||||
$name = 'importCustomers.csv';
|
||||
@@ -380,30 +334,17 @@ class Customers extends Persons
|
||||
return $this->response->download($name, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the customer CSV import modal. Used in app/Views/people/manage.php
|
||||
*
|
||||
* @return void
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getCsvImport(): void
|
||||
public function getCsvImport(): string
|
||||
{
|
||||
echo view('customers/form_csv_import');
|
||||
return view('customers/form_csv_import');
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a CSV file containing customers. Used in app/Views/customers/form_csv_import.php
|
||||
*
|
||||
* @return void
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postImportCsvFile(): void
|
||||
public function postImportCsvFile(): ResponseInterface
|
||||
{
|
||||
if ($_FILES['file_path']['error'] != UPLOAD_ERR_OK) {
|
||||
echo json_encode(['success' => false, 'message' => lang('Customers.csv_import_failed')]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Customers.csv_import_failed')]);
|
||||
} else {
|
||||
if (($handle = fopen($_FILES['file_path']['tmp_name'], 'r')) !== false) {
|
||||
// Skip the first row as it's the table description
|
||||
fgetcsv($handle);
|
||||
$i = 1;
|
||||
|
||||
@@ -414,7 +355,7 @@ class Customers extends Persons
|
||||
|
||||
if (sizeof($data) >= 16 && $consent) {
|
||||
$email = strtolower($data[4]);
|
||||
$person_data = [
|
||||
$personData = [
|
||||
'first_name' => $data[0],
|
||||
'last_name' => $data[1],
|
||||
'gender' => $data[2],
|
||||
@@ -429,7 +370,7 @@ class Customers extends Persons
|
||||
'comments' => $data[12]
|
||||
];
|
||||
|
||||
$customer_data = [
|
||||
$customerData = [
|
||||
'consent' => $consent,
|
||||
'company_name' => $data[13],
|
||||
'discount' => $data[15],
|
||||
@@ -438,14 +379,13 @@ class Customers extends Persons
|
||||
'date' => date('Y-m-d H:i:s'),
|
||||
'employee_id' => $this->employee->get_logged_in_employee_info()->person_id
|
||||
];
|
||||
$account_number = $data[14];
|
||||
$accountNumber = $data[14];
|
||||
|
||||
// Don't duplicate people with same email
|
||||
$invalidated = $this->customer->check_email_exists($email);
|
||||
|
||||
if ($account_number != '') {
|
||||
$customer_data['account_number'] = $account_number;
|
||||
$invalidated &= $this->customer->check_account_number_exists($account_number);
|
||||
if ($accountNumber != '') {
|
||||
$customerData['account_number'] = $accountNumber;
|
||||
$invalidated &= $this->customer->check_account_number_exists($accountNumber);
|
||||
}
|
||||
} else {
|
||||
$invalidated = true;
|
||||
@@ -454,9 +394,8 @@ 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']);
|
||||
} elseif ($this->customer->save_customer($personData, $customerData)) {
|
||||
$this->mailchimpLib->addOrUpdateMember($this->listId, $personData['email'], $personData['first_name'], '', $personData['last_name']);
|
||||
} else {
|
||||
$failCodes[] = $i;
|
||||
}
|
||||
@@ -467,13 +406,13 @@ class Customers extends Persons
|
||||
if (count($failCodes) > 0) {
|
||||
$message = lang('Customers.csv_import_partially_failed', [count($failCodes), implode(', ', $failCodes)]);
|
||||
|
||||
echo json_encode(['success' => false, 'message' => $message]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => $message]);
|
||||
} else {
|
||||
echo json_encode(['success' => true, 'message' => lang('Customers.csv_import_success')]);
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Customers.csv_import_success')]);
|
||||
}
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => lang('Customers.csv_import_nodata_wrongformat')]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Customers.csv_import_nodata_wrongformat')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,15 @@
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Attribute;
|
||||
use App\Models\Module;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\Services;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @property module module
|
||||
*
|
||||
*/
|
||||
class Employees extends Persons
|
||||
{
|
||||
protected Module $module;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('employees');
|
||||
@@ -20,103 +18,108 @@ class Employees extends Persons
|
||||
$this->module = model('Module');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns employee table data rows. This will be called with AJAX.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getSearch(): void
|
||||
public function getSearch(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('search');
|
||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||
$sort = $this->sanitizeSortColumn(person_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'people.person_id');
|
||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$sort = $this->sanitizeSortColumn(person_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'people.person_id');
|
||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
$employees = $this->employee->search($search, $limit, $offset, $sort, $order);
|
||||
$total_rows = $this->employee->get_found_rows($search);
|
||||
$totalRows = $this->employee->get_found_rows($search);
|
||||
|
||||
$data_rows = [];
|
||||
$dataRows = [];
|
||||
foreach ($employees->getResult() as $person) {
|
||||
$data_rows[] = get_person_data_row($person);
|
||||
$dataRows[] = get_person_data_row($person);
|
||||
}
|
||||
|
||||
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
return $this->response->setJSON(['total' => $totalRows, 'rows' => $dataRows]);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX called function gives search suggestions based on what is being searched for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getSuggest(): void
|
||||
public function getSuggest(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('term');
|
||||
$suggestions = $this->employee->get_search_suggestions($search, 25, true);
|
||||
|
||||
echo json_encode($suggestions);
|
||||
return $this->response->setJSON($suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function suggest_search(): void
|
||||
public function suggestSearch(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getPost('term');
|
||||
$suggestions = $this->employee->get_search_suggestions($search);
|
||||
|
||||
echo json_encode($suggestions);
|
||||
return $this->response->setJSON($suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the employee edit form
|
||||
*/
|
||||
public function getView(int $employee_id = NEW_ENTRY): void
|
||||
public function getView(int $employeeId = NEW_ENTRY): string
|
||||
{
|
||||
$person_info = $this->employee->get_info($employee_id);
|
||||
foreach (get_object_vars($person_info) as $property => $value) {
|
||||
$person_info->$property = $value;
|
||||
$personInfo = $this->employee->get_info($employeeId);
|
||||
$currentUser = $this->employee->get_logged_in_employee_info();
|
||||
|
||||
if ($employeeId != NEW_ENTRY && !$this->employee->canModifyEmployee($personInfo->person_id, $currentUser->person_id)) {
|
||||
header('Location: ' . base_url('no_access/employees/employees'));
|
||||
exit();
|
||||
}
|
||||
$data['person_info'] = $person_info;
|
||||
$data['employee_id'] = $employee_id;
|
||||
|
||||
foreach (get_object_vars($personInfo) as $property => $value) {
|
||||
$personInfo->$property = $value;
|
||||
}
|
||||
$data['person_info'] = $personInfo;
|
||||
$data['employee_id'] = $employeeId;
|
||||
|
||||
$modules = [];
|
||||
foreach ($this->module->get_all_modules()->getResult() as $module) {
|
||||
$module->grant = $this->employee->has_grant($module->module_id, $person_info->person_id);
|
||||
$module->menu_group = $this->employee->get_menu_group($module->module_id, $person_info->person_id);
|
||||
$module->grant = $this->employee->has_grant($module->module_id, $personInfo->person_id);
|
||||
$module->menu_group = $this->employee->get_menu_group($module->module_id, $personInfo->person_id);
|
||||
|
||||
$modules[] = $module;
|
||||
}
|
||||
$data['all_modules'] = $modules;
|
||||
|
||||
$permissions = [];
|
||||
foreach ($this->module->get_all_subpermissions()->getResult() as $permission) { // TODO: subpermissions does not follow naming standards.
|
||||
foreach ($this->module->get_all_subpermissions()->getResult() as $permission) {
|
||||
$permission->permission_id = str_replace(' ', '_', $permission->permission_id);
|
||||
$permission->grant = $this->employee->has_grant($permission->permission_id, $person_info->person_id);
|
||||
$permission->grant = $this->employee->has_grant($permission->permission_id, $personInfo->person_id);
|
||||
|
||||
$permissions[] = $permission;
|
||||
}
|
||||
$data['all_subpermissions'] = $permissions;
|
||||
|
||||
echo view('employees/form', $data);
|
||||
return view('employees/form', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts/updates an employee
|
||||
*/
|
||||
public function postSave(int $employee_id = NEW_ENTRY): void
|
||||
public function getAttributes(int $employeeId = NEW_ENTRY): string
|
||||
{
|
||||
$first_name = $this->request->getPost('first_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS); // TODO: duplicated code
|
||||
$last_name = $this->request->getPost('last_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
return $this->getPersonAttributes($employeeId, Attribute::SHOW_IN_EMPLOYEES);
|
||||
}
|
||||
|
||||
public function postSave(int $employeeId = NEW_ENTRY): ResponseInterface
|
||||
{
|
||||
$currentUser = $this->employee->get_logged_in_employee_info();
|
||||
|
||||
if ($employeeId != NEW_ENTRY) {
|
||||
$targetEmployee = $this->employee->get_info($employeeId);
|
||||
if (!$this->employee->canModifyEmployee($targetEmployee->person_id, $currentUser->person_id)) {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Employees.error_updating_admin'),
|
||||
'id' => NEW_ENTRY
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$firstName = $this->request->getPost('first_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$lastName = $this->request->getPost('last_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$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', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||
@@ -129,88 +132,98 @@ class Employees extends Persons
|
||||
'comments' => $this->request->getPost('comments', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
|
||||
];
|
||||
|
||||
$grants_array = [];
|
||||
$grantsArray = [];
|
||||
$isAdmin = $this->employee->isAdmin($currentUser->person_id);
|
||||
|
||||
foreach ($this->module->get_all_permissions()->getResult() as $permission) {
|
||||
$grants = [];
|
||||
$grant = $this->request->getPost('grant_' . $permission->permission_id) != null ? $this->request->getPost('grant_' . $permission->permission_id, FILTER_SANITIZE_FULL_SPECIAL_CHARS) : '';
|
||||
|
||||
if ($grant == $permission->permission_id) {
|
||||
if (!$isAdmin && !$this->employee->has_grant($permission->permission_id, $currentUser->person_id)) {
|
||||
continue;
|
||||
}
|
||||
$grants['permission_id'] = $permission->permission_id;
|
||||
$grants['menu_group'] = $this->request->getPost('menu_group_' . $permission->permission_id) != null ? $this->request->getPost('menu_group_' . $permission->permission_id, FILTER_SANITIZE_FULL_SPECIAL_CHARS) : '--';
|
||||
$grants_array[] = $grants;
|
||||
$grantsArray[] = $grants;
|
||||
}
|
||||
}
|
||||
|
||||
// Password has been changed OR first time password set
|
||||
if (!empty($this->request->getPost('password')) && ENVIRONMENT != 'testing') {
|
||||
$exploded = explode(":", $this->request->getPost('language', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
|
||||
$employee_data = [
|
||||
$employeeData = [
|
||||
'username' => $this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||
'password' => password_hash($this->request->getPost('password'), PASSWORD_DEFAULT),
|
||||
'hash_version' => 2,
|
||||
'language_code' => $exploded[0],
|
||||
'language' => $exploded[1]
|
||||
];
|
||||
} else { // Password not changed
|
||||
} else {
|
||||
$exploded = explode(":", $this->request->getPost('language', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
|
||||
$employee_data = [
|
||||
$employeeData = [
|
||||
'username' => $this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||
'language_code' => $exploded[0],
|
||||
'language' => $exploded[1]
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->employee->save_employee($person_data, $employee_data, $grants_array, $employee_id)) {
|
||||
// New employee
|
||||
if ($employee_id == NEW_ENTRY) {
|
||||
echo json_encode([
|
||||
if ($this->employee->save_employee($personData, $employeeData, $grantsArray, $employeeId)) {
|
||||
$personId = $employeeId == NEW_ENTRY ? $employeeData['person_id'] : $employeeId;
|
||||
$this->savePersonAttributes($personId, Attribute::SHOW_IN_EMPLOYEES);
|
||||
|
||||
if ($employeeId == NEW_ENTRY) {
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Employees.successful_adding') . ' ' . $first_name . ' ' . $last_name,
|
||||
'id' => $employee_data['person_id']
|
||||
'message' => lang('Employees.successful_adding') . ' ' . $firstName . ' ' . $lastName,
|
||||
'id' => $employeeData['person_id']
|
||||
]);
|
||||
} else { // Existing employee
|
||||
echo json_encode([
|
||||
} else {
|
||||
$loggedInEmployeeId = session()->get('person_id');
|
||||
if ($employeeId == $loggedInEmployeeId) {
|
||||
session()->set('language_code', $employeeData['language_code']);
|
||||
session()->set('language', $employeeData['language']);
|
||||
}
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Employees.successful_updating') . ' ' . $first_name . ' ' . $last_name,
|
||||
'id' => $employee_id
|
||||
'message' => lang('Employees.successful_updating') . ' ' . $firstName . ' ' . $lastName,
|
||||
'id' => $employeeId
|
||||
]);
|
||||
}
|
||||
} else { // Failure
|
||||
echo json_encode([
|
||||
} else {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Employees.error_adding_updating') . ' ' . $first_name . ' ' . $last_name,
|
||||
'message' => lang('Employees.error_adding_updating') . ' ' . $firstName . ' ' . $lastName,
|
||||
'id' => NEW_ENTRY
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This deletes employees from the employees table
|
||||
*/
|
||||
public function postDelete(): void
|
||||
public function postDelete(): ResponseInterface
|
||||
{
|
||||
$employees_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$employeesToDelete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$currentUser = $this->employee->get_logged_in_employee_info();
|
||||
|
||||
if ($this->employee->delete_list($employees_to_delete)) { // TODO: this is passing a string, but delete_list expects an array
|
||||
echo json_encode([
|
||||
if (!$this->employee->isAdmin($currentUser->person_id)) {
|
||||
foreach ($employeesToDelete as $empId) {
|
||||
if ($this->employee->isAdmin((int)$empId)) {
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Employees.error_deleting_admin')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->employee->delete_list($employeesToDelete)) {
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Employees.successful_deleted') . ' ' . count($employees_to_delete) . ' ' . lang('Employees.one_or_multiple')
|
||||
'message' => lang('Employees.successful_deleted') . ' ' . count($employeesToDelete) . ' ' . lang('Employees.one_or_multiple')
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => lang('Employees.cannot_be_deleted')]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Employees.cannot_be_deleted')]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks an employee username against the database. Used in app\Views\employees\form.php
|
||||
*
|
||||
* @param $employee_id
|
||||
* @return void
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getCheckUsername($employee_id): void
|
||||
public function getCheckUsername($employeeId): ResponseInterface
|
||||
{
|
||||
$exists = $this->employee->username_exists($employee_id, $this->request->getGet('username'));
|
||||
echo !$exists ? 'true' : 'false';
|
||||
$exists = $this->employee->username_exists($employeeId, $this->request->getGet('username'));
|
||||
return $this->response->setJSON(!$exists ? 'true' : 'false');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Controllers;
|
||||
|
||||
use App\Models\Expense;
|
||||
use App\Models\Expense_category;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\OSPOS;
|
||||
use Config\Services;
|
||||
|
||||
@@ -23,7 +24,7 @@ class Expenses extends Secure_Controller
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
$data['table_headers'] = get_expenses_manage_table_headers();
|
||||
|
||||
@@ -37,13 +38,16 @@ class Expenses extends Secure_Controller
|
||||
'is_deleted' => lang('Expenses.is_deleted')
|
||||
];
|
||||
|
||||
echo view('expenses/manage', $data);
|
||||
// Restore filters from URL
|
||||
$data = array_merge($data, restoreTableFilters($this->request));
|
||||
|
||||
return view('expenses/manage', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function getSearch(): void
|
||||
public function getSearch(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('search');
|
||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||
@@ -78,27 +82,34 @@ class Expenses extends Secure_Controller
|
||||
$data_rows[] = get_expenses_data_last_row($expenses);
|
||||
}
|
||||
|
||||
echo json_encode(['total' => $total_rows, 'rows' => $data_rows, 'payment_summary' => $payment_summary]);
|
||||
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows, 'payment_summary' => $payment_summary]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $expense_id
|
||||
* @return void
|
||||
*/
|
||||
public function getView(int $expense_id = NEW_ENTRY): void
|
||||
public function getView(int $expense_id = NEW_ENTRY): string
|
||||
{
|
||||
$data = []; // TODO: Duplicated code
|
||||
|
||||
$data['employees'] = [];
|
||||
foreach ($this->employee->get_all()->getResult() as $employee) {
|
||||
foreach (get_object_vars($employee) as $property => $value) {
|
||||
$employee->$property = $value;
|
||||
}
|
||||
|
||||
$data['employees'][$employee->person_id] = $employee->first_name . ' ' . $employee->last_name;
|
||||
}
|
||||
|
||||
$data['expenses_info'] = $this->expense->get_info($expense_id);
|
||||
$expense_id = $data['expenses_info']->expense_id;
|
||||
|
||||
$current_employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
||||
$can_assign_employee = $this->employee->has_grant('employees', $current_employee_id);
|
||||
|
||||
$data['employees'] = [];
|
||||
if ($can_assign_employee) {
|
||||
foreach ($this->employee->get_all()->getResult() as $employee) {
|
||||
$data['employees'][$employee->person_id] = $employee->first_name . ' ' . $employee->last_name;
|
||||
}
|
||||
} 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);
|
||||
$data['employees'][$stored_employee_id] = $stored_employee->first_name . ' ' . $stored_employee->last_name;
|
||||
}
|
||||
$data['can_assign_employee'] = $can_assign_employee;
|
||||
|
||||
$expense_categories = [];
|
||||
foreach ($this->expense_category->get_all(0, 0, true)->getResultArray() as $row) {
|
||||
@@ -106,11 +117,9 @@ class Expenses extends Secure_Controller
|
||||
}
|
||||
$data['expense_categories'] = $expense_categories;
|
||||
|
||||
$expense_id = $data['expenses_info']->expense_id;
|
||||
|
||||
if ($expense_id == NEW_ENTRY) {
|
||||
$data['expenses_info']->date = date('Y-m-d H:i:s');
|
||||
$data['expenses_info']->employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
||||
$data['expenses_info']->employee_id = $current_employee_id;
|
||||
}
|
||||
|
||||
$data['payments'] = [];
|
||||
@@ -125,32 +134,46 @@ class Expenses extends Secure_Controller
|
||||
// Don't allow gift card to be a payment option in a sale transaction edit because it's a complex change
|
||||
$data['payment_options'] = $this->expense->get_payment_options();
|
||||
|
||||
echo view("expenses/form", $data);
|
||||
return view("expenses/form", $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $row_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getRow(int $row_id): void
|
||||
public function getRow(int $row_id): ResponseInterface
|
||||
{
|
||||
$expense_info = $this->expense->get_info($row_id);
|
||||
$data_row = get_expenses_data_row($expense_info);
|
||||
|
||||
echo json_encode($data_row);
|
||||
return $this->response->setJSON($data_row);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $expense_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postSave(int $expense_id = NEW_ENTRY): void
|
||||
public function postSave(int $expense_id = NEW_ENTRY): ResponseInterface
|
||||
{
|
||||
$config = config(OSPOS::class)->settings;
|
||||
$newdate = $this->request->getPost('date', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
$date_formatter = date_create_from_format($config['dateformat'] . ' ' . $config['timeformat'], $newdate);
|
||||
|
||||
$current_employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
||||
$submitted_employee_id = $this->request->getPost('employee_id', FILTER_SANITIZE_NUMBER_INT);
|
||||
|
||||
if (!$this->employee->has_grant('employees', $current_employee_id)) {
|
||||
if ($expense_id == NEW_ENTRY) {
|
||||
$employee_id = $current_employee_id;
|
||||
} else {
|
||||
$existing_expense = $this->expense->get_info($expense_id);
|
||||
$employee_id = $existing_expense->employee_id;
|
||||
}
|
||||
} else {
|
||||
$employee_id = $submitted_employee_id;
|
||||
}
|
||||
|
||||
$expense_data = [
|
||||
'date' => $date_formatter->format('Y-m-d H:i:s'),
|
||||
'supplier_id' => $this->request->getPost('supplier_id') == '' ? null : $this->request->getPost('supplier_id', FILTER_SANITIZE_NUMBER_INT),
|
||||
@@ -160,33 +183,33 @@ class Expenses extends Secure_Controller
|
||||
'payment_type' => $this->request->getPost('payment_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||
'expense_category_id' => $this->request->getPost('expense_category_id', FILTER_SANITIZE_NUMBER_INT),
|
||||
'description' => $this->request->getPost('description', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||
'employee_id' => $this->request->getPost('employee_id', FILTER_SANITIZE_NUMBER_INT),
|
||||
'employee_id' => $employee_id,
|
||||
'deleted' => $this->request->getPost('deleted') != null
|
||||
];
|
||||
|
||||
if ($this->expense->save_value($expense_data, $expense_id)) {
|
||||
// New Expense
|
||||
if ($expense_id == NEW_ENTRY) {
|
||||
echo json_encode(['success' => true, 'message' => lang('Expenses.successful_adding'), 'id' => $expense_data['expense_id']]);
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Expenses.successful_adding'), 'id' => $expense_data['expense_id']]);
|
||||
} else { // Existing Expense
|
||||
echo json_encode(['success' => true, 'message' => lang('Expenses.successful_updating'), 'id' => $expense_id]);
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Expenses.successful_updating'), 'id' => $expense_id]);
|
||||
}
|
||||
} else { // Failure
|
||||
echo json_encode(['success' => false, 'message' => lang('Expenses.error_adding_updating'), 'id' => NEW_ENTRY]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Expenses.error_adding_updating'), 'id' => NEW_ENTRY]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postDelete(): void
|
||||
public function postDelete(): ResponseInterface
|
||||
{
|
||||
$expenses_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
if ($this->expense->delete_list($expenses_to_delete)) {
|
||||
echo json_encode(['success' => true, 'message' => lang('Expenses.successful_deleted') . ' ' . count($expenses_to_delete) . ' ' . lang('Expenses.one_or_multiple'), 'ids' => $expenses_to_delete]);
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Expenses.successful_deleted') . ' ' . count($expenses_to_delete) . ' ' . lang('Expenses.one_or_multiple'), 'ids' => $expenses_to_delete]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => lang('Expenses.cannot_be_deleted'), 'ids' => $expenses_to_delete]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Expenses.cannot_be_deleted'), 'ids' => $expenses_to_delete]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Expense_category;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\Services;
|
||||
|
||||
class Expenses_categories extends Secure_Controller // TODO: Is this class ever used?
|
||||
@@ -19,17 +20,17 @@ class Expenses_categories extends Secure_Controller // TODO: Is this class ev
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
$data['table_headers'] = get_expense_category_manage_table_headers();
|
||||
|
||||
echo view('expenses_categories/manage', $data);
|
||||
return view('expenses_categories/manage', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns expense_category_manage table data rows. This will be called with AJAX.
|
||||
**/
|
||||
public function getSearch(): void
|
||||
public function getSearch(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('search');
|
||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||
@@ -45,36 +46,36 @@ class Expenses_categories extends Secure_Controller // TODO: Is this class ev
|
||||
$data_rows[] = get_expense_category_data_row($expense_category);
|
||||
}
|
||||
|
||||
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $row_id
|
||||
* @return void
|
||||
*/
|
||||
public function getRow(int $row_id): void
|
||||
public function getRow(int $row_id): ResponseInterface
|
||||
{
|
||||
$data_row = get_expense_category_data_row($this->expense_category->get_info($row_id));
|
||||
|
||||
echo json_encode($data_row);
|
||||
return $this->response->setJSON($data_row);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $expense_category_id
|
||||
* @return void
|
||||
*/
|
||||
public function getView(int $expense_category_id = NEW_ENTRY): void
|
||||
public function getView(int $expense_category_id = NEW_ENTRY): string
|
||||
{
|
||||
$data['category_info'] = $this->expense_category->get_info($expense_category_id);
|
||||
|
||||
echo view("expenses_categories/form", $data);
|
||||
return view("expenses_categories/form", $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $expense_category_id
|
||||
* @return void
|
||||
*/
|
||||
public function postSave(int $expense_category_id = NEW_ENTRY): void
|
||||
public function postSave(int $expense_category_id = NEW_ENTRY): ResponseInterface
|
||||
{
|
||||
$expense_category_data = [
|
||||
'category_name' => $this->request->getPost('category_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||
@@ -84,20 +85,20 @@ class Expenses_categories extends Secure_Controller // TODO: Is this class ev
|
||||
if ($this->expense_category->save_value($expense_category_data, $expense_category_id)) {
|
||||
// New expense_category
|
||||
if ($expense_category_id == NEW_ENTRY) {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Expenses_categories.successful_adding'),
|
||||
'id' => $expense_category_data['expense_category_id']
|
||||
]);
|
||||
} else { // Existing Expense Category
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Expenses_categories.successful_updating'),
|
||||
'id' => $expense_category_id
|
||||
]);
|
||||
}
|
||||
} else { // Failure
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Expenses_categories.error_adding_updating') . ' ' . $expense_category_data['category_name'],
|
||||
'id' => NEW_ENTRY
|
||||
@@ -108,17 +109,17 @@ class Expenses_categories extends Secure_Controller // TODO: Is this class ev
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function postDelete(): void
|
||||
public function postDelete(): ResponseInterface
|
||||
{
|
||||
$expense_category_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
if ($this->expense_category->delete_list($expense_category_to_delete)) { // TODO: Convert to ternary notation.
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Expenses_categories.successful_deleted') . ' ' . count($expense_category_to_delete) . ' ' . lang('Expenses_categories.one_or_multiple')
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => lang('Expenses_categories.cannot_be_deleted')]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Expenses_categories.cannot_be_deleted')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Giftcard;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\OSPOS;
|
||||
use Config\Services;
|
||||
|
||||
@@ -18,19 +19,19 @@ class Giftcards extends Secure_Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
$data['table_headers'] = get_giftcards_manage_table_headers();
|
||||
|
||||
echo view('giftcards/manage', $data);
|
||||
return view('giftcards/manage', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Giftcards table data rows. This will be called with AJAX.
|
||||
*/
|
||||
public function getSearch(): void
|
||||
public function getSearch(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('search');
|
||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||
@@ -46,50 +47,50 @@ class Giftcards extends Secure_Controller
|
||||
$data_rows[] = get_giftcard_data_row($giftcard);
|
||||
}
|
||||
|
||||
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets search suggestions for giftcards. Used in app\Views\sales\register.php
|
||||
*
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getSuggest(): void
|
||||
public function getSuggest(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('term');
|
||||
$suggestions = $this->giftcard->get_search_suggestions($search, true);
|
||||
|
||||
echo json_encode($suggestions);
|
||||
return $this->response->setJSON($suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function suggest_search(): void
|
||||
public function suggest_search(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getPost('term');
|
||||
$suggestions = $this->giftcard->get_search_suggestions($search);
|
||||
|
||||
echo json_encode($suggestions);
|
||||
return $this->response->setJSON($suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $row_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getRow(int $row_id): void
|
||||
public function getRow(int $row_id): ResponseInterface
|
||||
{
|
||||
$data_row = get_giftcard_data_row($this->giftcard->get_info($row_id));
|
||||
|
||||
echo json_encode($data_row);
|
||||
return $this->response->setJSON($data_row);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $giftcard_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getView(int $giftcard_id = NEW_ENTRY): void
|
||||
public function getView(int $giftcard_id = NEW_ENTRY): string
|
||||
{
|
||||
$config = config(OSPOS::class)->settings;
|
||||
$giftcard_info = $this->giftcard->get_info($giftcard_id);
|
||||
@@ -106,14 +107,14 @@ class Giftcards extends Secure_Controller
|
||||
$data['giftcard_id'] = $giftcard_id;
|
||||
$data['giftcard_value'] = $giftcard_info->value;
|
||||
|
||||
echo view("giftcards/form", $data);
|
||||
return view("giftcards/form", $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $giftcard_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postSave(int $giftcard_id = NEW_ENTRY): void
|
||||
public function postSave(int $giftcard_id = NEW_ENTRY): ResponseInterface
|
||||
{
|
||||
$giftcard_number = $this->request->getPost('giftcard_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
@@ -131,20 +132,20 @@ class Giftcards extends Secure_Controller
|
||||
if ($this->giftcard->save_value($giftcard_data, $giftcard_id)) {
|
||||
// New giftcard
|
||||
if ($giftcard_id == NEW_ENTRY) { // TODO: Constant needed
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Giftcards.successful_adding') . ' ' . $giftcard_data['giftcard_number'],
|
||||
'id' => $giftcard_data['giftcard_id']
|
||||
]);
|
||||
} else { // Existing giftcard
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Giftcards.successful_updating') . ' ' . $giftcard_data['giftcard_number'],
|
||||
'id' => $giftcard_id
|
||||
]);
|
||||
}
|
||||
} else { // Failure
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Giftcards.error_adding_updating') . ' ' . $giftcard_data['giftcard_number'],
|
||||
'id' => NEW_ENTRY
|
||||
@@ -158,30 +159,30 @@ class Giftcards extends Secure_Controller
|
||||
* @return void
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postCheckNumberGiftcard(): void
|
||||
public function postCheckNumberGiftcard(): ResponseInterface
|
||||
{
|
||||
$existing_id = $this->request->getPost('giftcard_id', FILTER_SANITIZE_NUMBER_INT);
|
||||
$giftcard_number = $this->request->getPost('giftcard_number', FILTER_SANITIZE_NUMBER_INT);
|
||||
$giftcard_id = $this->giftcard->get_giftcard_id($giftcard_number);
|
||||
$success = ($giftcard_id == (int) $existing_id || !$giftcard_id );
|
||||
|
||||
echo $success ? 'true' : 'false';
|
||||
return $this->response->setJSON($success ? 'true' : 'false');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postDelete(): void
|
||||
public function postDelete(): ResponseInterface
|
||||
{
|
||||
$giftcards_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
if ($this->giftcard->delete_list($giftcards_to_delete)) {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Giftcards.successful_deleted') . ' ' . count($giftcards_to_delete) . ' ' . lang('Giftcards.one_or_multiple')
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => lang('Giftcards.cannot_be_deleted')]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Giftcards.cannot_be_deleted')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Libraries\MY_Migration;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
class Home extends Secure_Controller
|
||||
{
|
||||
@@ -12,12 +14,12 @@ class Home extends Secure_Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
$logged_in = $this->employee->is_logged_in();
|
||||
echo view('home/home');
|
||||
return view('home/home');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,59 +35,93 @@ class Home extends Secure_Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Load "change employee password" form
|
||||
* Load the "change employee password" form
|
||||
*
|
||||
* @noinspection PhpUnused
|
||||
* @param int $employeeId
|
||||
* @return ResponseInterface|string
|
||||
*/
|
||||
public function getChangePassword(int $employee_id = -1): void // TODO: Replace -1 with a constant
|
||||
public function getChangePassword(int $employeeId = NEW_ENTRY): ResponseInterface|string
|
||||
{
|
||||
$person_info = $this->employee->get_info($employee_id);
|
||||
$loggedInEmployee = $this->employee->get_logged_in_employee_info();
|
||||
$currentPersonId = $loggedInEmployee->person_id;
|
||||
|
||||
$employeeId = $employeeId === NEW_ENTRY ? $currentPersonId : $employeeId;
|
||||
|
||||
if (!$this->employee->isAdmin($currentPersonId) && $employeeId !== $currentPersonId) {
|
||||
return $this->response->setStatusCode(403)->setBody(lang('Employees.unauthorized_modify'));
|
||||
}
|
||||
|
||||
$person_info = $this->employee->get_info($employeeId);
|
||||
foreach (get_object_vars($person_info) as $property => $value) {
|
||||
$person_info->$property = $value;
|
||||
}
|
||||
$data['person_info'] = $person_info;
|
||||
|
||||
echo view('home/form_change_password', $data);
|
||||
return view('home/form_change_password', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change employee password
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postSave(int $employee_id = -1): void // TODO: Replace -1 with a constant
|
||||
public function postSave(int $employeeId = NEW_ENTRY): ResponseInterface
|
||||
{
|
||||
if (!empty($this->request->getPost('current_password')) && $employee_id != -1) {
|
||||
$currentUser = $this->employee->get_logged_in_employee_info();
|
||||
|
||||
$employeeId = $employeeId === NEW_ENTRY ? $currentUser->person_id : $employeeId;
|
||||
|
||||
if (!$this->employee->isAdmin($currentUser->person_id) && $employeeId !== $currentUser->person_id) {
|
||||
return $this->response->setStatusCode(403)->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Employees.unauthorized_modify')
|
||||
]);
|
||||
}
|
||||
|
||||
if (!empty($this->request->getPost('current_password')) && $employeeId != NEW_ENTRY) {
|
||||
if ($this->employee->check_password($this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS), $this->request->getPost('current_password'))) {
|
||||
// Validate password length BEFORE hashing
|
||||
$new_password = $this->request->getPost('password');
|
||||
|
||||
if (strlen($new_password) < 8) {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Employees.password_minlength'),
|
||||
'id' => NEW_ENTRY
|
||||
]);
|
||||
}
|
||||
|
||||
$employee_data = [
|
||||
'username' => $this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||
'password' => password_hash($this->request->getPost('password'), PASSWORD_DEFAULT),
|
||||
'password' => password_hash($new_password, PASSWORD_DEFAULT),
|
||||
'hash_version' => 2
|
||||
];
|
||||
|
||||
if ($this->employee->change_password($employee_data, $employee_id) && strlen($employee_data['password']) >= 8) {
|
||||
echo json_encode([
|
||||
if ($this->employee->change_password($employee_data, $employeeId)) {
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Employees.successful_change_password'),
|
||||
'id' => $employee_id
|
||||
'id' => $employeeId
|
||||
]);
|
||||
} else { // Failure // TODO: Replace -1 with constant
|
||||
echo json_encode([
|
||||
} else {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Employees.unsuccessful_change_password'),
|
||||
'id' => -1
|
||||
'id' => NEW_ENTRY
|
||||
]);
|
||||
}
|
||||
} else { // TODO: Replace -1 with constant
|
||||
echo json_encode([
|
||||
} else {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Employees.current_password_invalid'),
|
||||
'id' => -1
|
||||
'id' => NEW_ENTRY
|
||||
]);
|
||||
}
|
||||
} else { // TODO: Replace -1 with constant
|
||||
echo json_encode([
|
||||
} else {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Employees.current_password_invalid'),
|
||||
'id' => -1
|
||||
'id' => NEW_ENTRY
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Libraries\Barcode_lib;
|
||||
use App\Models\Item;
|
||||
use App\Models\Item_kit;
|
||||
use App\Models\Item_kit_items;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\Services;
|
||||
|
||||
class Item_kits extends Secure_Controller
|
||||
@@ -59,19 +60,19 @@ class Item_kits extends Secure_Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
$data['table_headers'] = get_item_kits_manage_table_headers();
|
||||
|
||||
echo view('item_kits/manage', $data);
|
||||
return view('item_kits/manage', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Item_kit table data rows. This will be called with AJAX.
|
||||
*/
|
||||
public function getSearch(): void
|
||||
public function getSearch(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('search') ?? '';
|
||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||
@@ -89,37 +90,37 @@ class Item_kits extends Secure_Controller
|
||||
$data_rows[] = get_item_kit_data_row($item_kit);
|
||||
}
|
||||
|
||||
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function suggest_search(): void
|
||||
public function suggest_search(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getPost('term');
|
||||
$suggestions = $this->item_kit->get_search_suggestions($search);
|
||||
|
||||
echo json_encode($suggestions);
|
||||
return $this->response->setJSON($suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $row_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getRow(int $row_id): void
|
||||
public function getRow(int $row_id): ResponseInterface
|
||||
{
|
||||
// Calculate the total cost and retail price of the Kit, so it can be added to the table refresh
|
||||
$item_kit = $this->_add_totals_to_item_kit($this->item_kit->get_info($row_id));
|
||||
|
||||
echo json_encode(get_item_kit_data_row($item_kit));
|
||||
return $this->response->setJSON(get_item_kit_data_row($item_kit));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $item_kit_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getView(int $item_kit_id = NEW_ENTRY): void
|
||||
public function getView(int $item_kit_id = NEW_ENTRY): string
|
||||
{
|
||||
$info = $this->item_kit->get_info($item_kit_id);
|
||||
|
||||
@@ -153,14 +154,14 @@ class Item_kits extends Secure_Controller
|
||||
$data['selected_kit_item_id'] = $info->kit_item_id;
|
||||
$data['selected_kit_item'] = ($item_kit_id > 0 && isset($info->kit_item_id)) ? $info->item_name : '';
|
||||
|
||||
echo view("item_kits/form", $data);
|
||||
return view("item_kits/form", $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $item_kit_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postSave(int $item_kit_id = NEW_ENTRY): void
|
||||
public function postSave(int $item_kit_id = NEW_ENTRY): ResponseInterface
|
||||
{
|
||||
$item_kit_data = [
|
||||
'name' => $this->request->getPost('name'),
|
||||
@@ -201,20 +202,20 @@ class Item_kits extends Secure_Controller
|
||||
}
|
||||
|
||||
if ($new_item) {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => $success,
|
||||
'message' => lang('Item_kits.successful_adding') . ' ' . $item_kit_data['name'],
|
||||
'id' => $item_kit_id
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => $success,
|
||||
'message' => lang('Item_kits.successful_updating') . ' ' . $item_kit_data['name'],
|
||||
'id' => $item_kit_id
|
||||
]);
|
||||
}
|
||||
} else { // Failure
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Item_kits.error_adding_updating') . ' ' . $item_kit_data['name'],
|
||||
'id' => NEW_ENTRY
|
||||
@@ -223,42 +224,42 @@ class Item_kits extends Secure_Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postDelete(): void
|
||||
public function postDelete(): ResponseInterface
|
||||
{
|
||||
$item_kits_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
if ($this->item_kit->delete_list($item_kits_to_delete)) {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Item_kits.successful_deleted') . ' ' . count($item_kits_to_delete) . ' ' . lang('Item_kits.one_or_multiple')
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => lang('Item_kits.cannot_be_deleted')]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Item_kits.cannot_be_deleted')]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the validity of the item kit number. Used in app/Views/item_kits/form.php
|
||||
*
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postCheckItemNumber(): void
|
||||
public function postCheckItemNumber(): ResponseInterface
|
||||
{
|
||||
$exists = $this->item_kit->item_number_exists($this->request->getPost('item_kit_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS), $this->request->getPost('item_kit_id', FILTER_SANITIZE_NUMBER_INT));
|
||||
echo !$exists ? 'true' : 'false';
|
||||
return $this->response->setJSON(!$exists ? 'true' : 'false');
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX called function that generates barcodes for selected item_kits.
|
||||
*
|
||||
* @param string $item_kit_ids Colon separated list of item_kit_id values to generate barcodes for.
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getGenerateBarcodes(string $item_kit_ids): void
|
||||
public function getGenerateBarcodes(string $item_kit_ids): string
|
||||
{
|
||||
$barcode_lib = new Barcode_lib();
|
||||
$result = [];
|
||||
@@ -289,6 +290,6 @@ class Item_kits extends Secure_Controller
|
||||
$data['barcode_config'] = $barcode_config;
|
||||
|
||||
// Display barcodes
|
||||
echo view("barcodes/barcode_sheet", $data);
|
||||
return view("barcodes/barcode_sheet", $data);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ namespace App\Controllers;
|
||||
use App\Libraries\MY_Migration;
|
||||
use App\Models\Employee;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\Model;
|
||||
use Config\OSPOS;
|
||||
use Config\Services;
|
||||
@@ -36,6 +37,7 @@ class Login extends BaseController
|
||||
|
||||
$data = [
|
||||
'has_errors' => false,
|
||||
'is_new_install' => !(MY_Migration::get_current_version()),
|
||||
'is_latest' => $migration->is_latest(),
|
||||
'latest_version' => $migration->get_latest_migration(),
|
||||
'gcaptcha_enabled' => $gcaptcha_enabled,
|
||||
@@ -71,4 +73,28 @@ class Login extends BaseController
|
||||
|
||||
return redirect()->to('home');
|
||||
}
|
||||
|
||||
public function migrate(): ResponseInterface
|
||||
{
|
||||
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()
|
||||
])->setStatusCode(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Controllers;
|
||||
use App\Libraries\Sms_lib;
|
||||
|
||||
use App\Models\Person;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
class Messages extends Secure_Controller
|
||||
{
|
||||
@@ -18,18 +19,18 @@ class Messages extends Secure_Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
echo view('messages/sms');
|
||||
return view('messages/sms');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $person_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getView(int $person_id = NEW_ENTRY): void
|
||||
public function getView(int $person_id = NEW_ENTRY): string
|
||||
{
|
||||
$person = model(Person::class);
|
||||
$info = $person->get_info($person_id);
|
||||
@@ -39,13 +40,13 @@ class Messages extends Secure_Controller
|
||||
}
|
||||
$data['person_info'] = $info;
|
||||
|
||||
echo view('messages/form_sms', $data);
|
||||
return view('messages/form_sms', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function send(): void
|
||||
public function send(): ResponseInterface
|
||||
{
|
||||
$phone = $this->request->getPost('phone', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$message = $this->request->getPost('message', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
@@ -53,9 +54,9 @@ class Messages extends Secure_Controller
|
||||
$response = $this->sms_lib->sendSMS($phone, $message);
|
||||
|
||||
if ($response) {
|
||||
echo json_encode(['success' => true, 'message' => lang('Messages.successfully_sent') . ' ' . esc($phone)]);
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Messages.successfully_sent') . ' ' . esc($phone)]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => lang('Messages.unsuccessfully_sent') . ' ' . esc($phone)]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Messages.unsuccessfully_sent') . ' ' . esc($phone)]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,10 +64,10 @@ class Messages extends Secure_Controller
|
||||
* Sends an SMS message to a user. Used in app/Views/messages/form_sms.php.
|
||||
*
|
||||
* @param int $person_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function send_form(int $person_id = NEW_ENTRY): void
|
||||
public function send_form(int $person_id = NEW_ENTRY): ResponseInterface
|
||||
{
|
||||
$phone = $this->request->getPost('phone', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$message = $this->request->getPost('message', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
@@ -74,13 +75,13 @@ class Messages extends Secure_Controller
|
||||
$response = $this->sms_lib->sendSMS($phone, $message);
|
||||
|
||||
if ($response) {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Messages.successfully_sent') . ' ' . esc($phone),
|
||||
'person_id' => $person_id
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Messages.unsuccessfully_sent') . ' ' . esc($phone),
|
||||
'person_id' => NEW_ENTRY
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Module;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Part of the grants mechanism to restrict access to modules that the user doesn't have permission for.
|
||||
@@ -22,13 +23,13 @@ class No_access extends BaseController
|
||||
/**
|
||||
* @param string $module_id
|
||||
* @param string $permission_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex(string $module_id = '', string $permission_id = ''): void
|
||||
public function getIndex(string $module_id = '', string $permission_id = ''): string
|
||||
{
|
||||
$data['module_name'] = $this->module->get_module_name($module_id);
|
||||
$data['permission_id'] = $permission_id;
|
||||
|
||||
echo view('no_access', $data);
|
||||
return view('no_access', $data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Employee;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @property Employee employee
|
||||
@@ -17,11 +18,11 @@ class Office extends Secure_Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
echo view('home/office');
|
||||
return view('home/office');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,72 +2,125 @@
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Attribute;
|
||||
use App\Models\Person;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\OSPOS;
|
||||
use Config\Services;
|
||||
use function Tamtamchik\NameCase\str_name_case;
|
||||
|
||||
abstract class Persons extends Secure_Controller
|
||||
{
|
||||
protected Person $person;
|
||||
protected Attribute $attribute;
|
||||
protected array $appConfig;
|
||||
|
||||
/**
|
||||
* @param string|null $module_id
|
||||
*/
|
||||
public function __construct(?string $module_id = null)
|
||||
public function __construct(?string $moduleId = null)
|
||||
{
|
||||
parent::__construct($module_id);
|
||||
parent::__construct($moduleId);
|
||||
|
||||
$this->person = model(Person::class);
|
||||
$this->attribute = model(Attribute::class);
|
||||
$this->appConfig = config(OSPOS::class)->settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
$data['table_headers'] = get_people_manage_table_headers();
|
||||
|
||||
echo view('people/manage', $data);
|
||||
return view('people/manage', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives search suggestions based on what is being searched for
|
||||
*/
|
||||
public function getSuggest(): void
|
||||
public function getSuggest(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('term');
|
||||
$suggestions = $this->person->get_search_suggestions($search);
|
||||
|
||||
echo json_encode($suggestions);
|
||||
return $this->response->setJSON($suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets one row for a person manage table. This is called using AJAX to update one row.
|
||||
*/
|
||||
public function getRow(int $row_id): void
|
||||
public function getRow(int $rowId): ResponseInterface
|
||||
{
|
||||
$data_row = get_person_data_row($this->person->get_info($row_id));
|
||||
$dataRow = get_person_data_row($this->person->get_info($rowId));
|
||||
|
||||
echo json_encode($data_row);
|
||||
return $this->response->setJSON($dataRow);
|
||||
}
|
||||
|
||||
protected function getPersonAttributes(int $personId, int $definitionFlags): string
|
||||
{
|
||||
$data['person_id'] = $personId;
|
||||
$data['config'] = $this->appConfig;
|
||||
$definitionIds = json_decode($this->request->getGet('definition_ids') ?? '', true);
|
||||
$data['definition_values'] = $this->attribute->getAttributesByPerson($personId) + $this->attribute->get_values_by_definitions($definitionIds);
|
||||
$data['definition_names'] = $this->attribute->getDefinitionsByType(true, $definitionFlags);
|
||||
|
||||
foreach ($data['definition_values'] as $definitionId => $definitionValue) {
|
||||
$attributeValue = $this->attribute->getPersonAttributeValue($personId, $definitionId);
|
||||
$attributeId = (empty($attributeValue) || empty($attributeValue->attribute_id)) ? null : $attributeValue->attribute_id;
|
||||
$values = &$data['definition_values'][$definitionId];
|
||||
$values['attribute_id'] = $attributeId;
|
||||
$values['attribute_value'] = $attributeValue;
|
||||
$values['selected_value'] = '';
|
||||
|
||||
if ($definitionValue['definition_type'] === DROPDOWN) {
|
||||
$values['values'] = $this->attribute->get_definition_values($definitionId);
|
||||
$linkValue = $this->getPersonLinkValue($personId, $definitionId);
|
||||
$values['selected_value'] = (empty($linkValue)) ? '' : $linkValue->attribute_id;
|
||||
}
|
||||
|
||||
if (!empty($definitionIds[$definitionId])) {
|
||||
$values['selected_value'] = $definitionIds[$definitionId];
|
||||
}
|
||||
|
||||
unset($data['definition_names'][$definitionId]);
|
||||
}
|
||||
|
||||
return view('attributes/person', $data);
|
||||
}
|
||||
|
||||
private function getPersonLinkValue(int $personId, int $definitionId): ?object
|
||||
{
|
||||
$builder = $this->db->table('attribute_links');
|
||||
$builder->where('person_id', $personId);
|
||||
$builder->where('item_id', null);
|
||||
$builder->where('sale_id', null);
|
||||
$builder->where('receiving_id', null);
|
||||
$builder->where('definition_id', $definitionId);
|
||||
|
||||
return $builder->get()->getRowObject();
|
||||
}
|
||||
|
||||
protected function savePersonAttributes(int $personId, int $definitionFlags): void
|
||||
{
|
||||
$attributeLinks = $this->request->getPost('attribute_links') ?? [];
|
||||
$attributeIds = $this->request->getPost('attribute_ids') ?? [];
|
||||
|
||||
$this->attribute->deletePersonAttributeLinks($personId);
|
||||
|
||||
foreach ($attributeLinks as $definitionId => $attributeId) {
|
||||
$definitionInfo = $this->attribute->getAttributeInfo((int)$definitionId);
|
||||
$definitionType = $definitionInfo->definition_type;
|
||||
|
||||
if ($definitionType !== DROPDOWN) {
|
||||
$attributeId = $this->attribute->savePersonAttributeValue(
|
||||
$attributeId,
|
||||
(int)$definitionId,
|
||||
$personId,
|
||||
$attributeIds[$definitionId] ?? false,
|
||||
$definitionType
|
||||
);
|
||||
}
|
||||
|
||||
$this->attribute->savePersonAttributeLink($personId, (int)$definitionId, (int)$attributeId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Capitalize segments of a name, and put the rest into lower case.
|
||||
* You can pass the characters you want to use as delimiters as exceptions.
|
||||
* The function supports UTF-8 strings
|
||||
*
|
||||
* Example:
|
||||
* i.e. <?php echo nameize("john o'grady-smith"); ?>
|
||||
*
|
||||
* returns John O'Grady-Smith
|
||||
*/
|
||||
protected function nameize(string $input): string
|
||||
{
|
||||
$adjusted_name = str_name_case($input);
|
||||
$adjustedName = str_name_case($input);
|
||||
|
||||
// TODO: Use preg_replace to match HTML entities and convert them to lowercase. This is a workaround for https://github.com/tamtamchik/namecase/issues/20
|
||||
return preg_replace_callback('/&[a-zA-Z0-9#]+;/', function ($matches) {
|
||||
return strtolower($matches[0]);
|
||||
}, $adjusted_name);
|
||||
}, $adjustedName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use App\Models\Item_kit;
|
||||
use App\Models\Receiving;
|
||||
use App\Models\Stock_location;
|
||||
use App\Models\Supplier;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\OSPOS;
|
||||
use Config\Services;
|
||||
use ReflectionException;
|
||||
@@ -46,66 +47,66 @@ class Receivings extends Secure_Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
$this->_reload();
|
||||
return $this->_reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns search suggestions for an item. Used in app/Views/sales/register.php
|
||||
*
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getItemSearch(): void
|
||||
public function getItemSearch(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('term');
|
||||
$suggestions = $this->item->get_search_suggestions($search, ['search_custom' => false, 'is_deleted' => false], true);
|
||||
$suggestions = array_merge($suggestions, $this->item_kit->get_search_suggestions($search));
|
||||
|
||||
echo json_encode($suggestions);
|
||||
return $this->response->setJSON($suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets search suggestions for a stock item. Used in app/Views/receivings/receiving.php
|
||||
*
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getStockItemSearch(): void
|
||||
public function getStockItemSearch(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('term');
|
||||
$suggestions = $this->item->get_stock_search_suggestions($search, ['search_custom' => false, 'is_deleted' => false], true);
|
||||
$suggestions = array_merge($suggestions, $this->item_kit->get_search_suggestions($search));
|
||||
|
||||
echo json_encode($suggestions);
|
||||
return $this->response->setJSON($suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set supplier if it exists in the database. Used in app/Views/receivings/receiving.php
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSelectSupplier(): void
|
||||
public function postSelectSupplier(): string
|
||||
{
|
||||
$supplier_id = $this->request->getPost('supplier', FILTER_SANITIZE_NUMBER_INT);
|
||||
if ($this->supplier->exists($supplier_id)) {
|
||||
$this->receiving_lib->set_supplier($supplier_id);
|
||||
}
|
||||
|
||||
$this->_reload(); // TODO: Hungarian notation
|
||||
return $this->_reload(); // TODO: Hungarian notation
|
||||
}
|
||||
|
||||
/**
|
||||
* Change receiving mode for current receiving. Used in app/Views/receivings/receiving.php
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postChangeMode(): void
|
||||
public function postChangeMode(): string
|
||||
{
|
||||
$stock_destination = $this->request->getPost('stock_destination', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$stock_source = $this->request->getPost('stock_source', FILTER_SANITIZE_NUMBER_INT);
|
||||
@@ -121,49 +122,49 @@ class Receivings extends Secure_Controller
|
||||
$this->receiving_lib->set_stock_destination($stock_destination);
|
||||
}
|
||||
|
||||
$this->_reload(); // TODO: Hungarian notation
|
||||
return $this->_reload(); // TODO: Hungarian notation
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets receiving comment. Used in app/Views/receivings/receiving.php
|
||||
*
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSetComment(): void
|
||||
public function postSetComment(): ResponseInterface
|
||||
{
|
||||
$this->receiving_lib->set_comment($this->request->getPost('comment', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
|
||||
return $this->response->setJSON(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the print after sale flag for the receiving. Used in app/Views/receivings/receiving.php
|
||||
*
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSetPrintAfterSale(): void
|
||||
public function postSetPrintAfterSale(): ResponseInterface
|
||||
{
|
||||
$this->receiving_lib->set_print_after_sale($this->request->getPost('recv_print_after_sale') != null);
|
||||
return $this->response->setJSON(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reference number for the receiving. Used in app/Views/receivings/receiving.php
|
||||
*
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSetReference(): void
|
||||
public function postSetReference(): ResponseInterface
|
||||
{
|
||||
$this->receiving_lib->set_reference($this->request->getPost('recv_reference', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
|
||||
return $this->response->setJSON(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item to the receiving. Used in app/Views/receivings/receiving.php
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postAdd(): void
|
||||
public function postAdd(): string
|
||||
{
|
||||
$data = [];
|
||||
|
||||
@@ -183,17 +184,17 @@ class Receivings extends Secure_Controller
|
||||
$data['error'] = lang('Receivings.unable_to_add_item');
|
||||
}
|
||||
|
||||
$this->_reload($data); // TODO: Hungarian notation
|
||||
return $this->_reload($data); // TODO: Hungarian notation
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit line item in current receiving. Used in app/Views/receivings/receiving.php
|
||||
*
|
||||
* @param $item_id
|
||||
* @return void
|
||||
* @param int|string|null $item_id
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postEditItem($item_id): void
|
||||
public function postEditItem(int|string|null $item_id): string
|
||||
{
|
||||
$data = [];
|
||||
|
||||
@@ -222,17 +223,16 @@ class Receivings extends Secure_Controller
|
||||
$data['error'] = lang('Receivings.error_editing_item');
|
||||
}
|
||||
|
||||
$this->_reload($data); // TODO: Hungarian notation
|
||||
return $this->_reload($data); // TODO: Hungarian notation
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a receiving. Used in app/Controllers/Receivings.php
|
||||
*
|
||||
* @param $receiving_id
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getEdit($receiving_id): void
|
||||
public function getEdit($receiving_id): string
|
||||
{
|
||||
$data = [];
|
||||
|
||||
@@ -241,73 +241,88 @@ class Receivings extends Secure_Controller
|
||||
$data['suppliers'][$supplier->person_id] = $supplier->first_name . ' ' . $supplier->last_name;
|
||||
}
|
||||
|
||||
$receiving_info = $this->receiving->get_info($receiving_id)->getRowArray();
|
||||
|
||||
$current_employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
||||
$can_assign_employee = $this->employee->has_grant('employees', $current_employee_id);
|
||||
|
||||
$data['employees'] = [];
|
||||
foreach ($this->employee->get_all()->getResult() as $employee) {
|
||||
$data['employees'][$employee->person_id] = $employee->first_name . ' ' . $employee->last_name;
|
||||
if ($can_assign_employee) {
|
||||
foreach ($this->employee->get_all()->getResult() as $employee) {
|
||||
$data['employees'][$employee->person_id] = $employee->first_name . ' ' . $employee->last_name;
|
||||
}
|
||||
} else {
|
||||
$stored_employee_id = $receiving_info['employee_id'];
|
||||
$stored_employee = $this->employee->get_info($stored_employee_id);
|
||||
$data['employees'][$stored_employee_id] = $stored_employee->first_name . ' ' . $stored_employee->last_name;
|
||||
}
|
||||
|
||||
$receiving_info = $this->receiving->get_info($receiving_id)->getRowArray();
|
||||
$data['selected_supplier_name'] = !empty($receiving_info['supplier_id']) ? $receiving_info['company_name'] : '';
|
||||
$data['selected_supplier_id'] = $receiving_info['supplier_id'];
|
||||
$data['receiving_info'] = $receiving_info;
|
||||
$data['can_assign_employee'] = $can_assign_employee;
|
||||
|
||||
echo view('receivings/form', $data);
|
||||
return view('receivings/form', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an item from the current receiving. Used in app/Views/receivings/receiving.php
|
||||
*
|
||||
* @param $item_number
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getDeleteItem($item_number): void
|
||||
public function getDeleteItem($item_number): string
|
||||
{
|
||||
$this->receiving_lib->delete_item($item_number);
|
||||
|
||||
$this->_reload(); // TODO: Hungarian notation
|
||||
return $this->_reload(); // TODO: Hungarian notation
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $receiving_id
|
||||
* @param bool $update_inventory
|
||||
* @return ResponseInterface
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function postDelete(int $receiving_id = -1, bool $update_inventory = true): void
|
||||
public function postDelete(int $receiving_id = -1, bool $update_inventory = true): ResponseInterface
|
||||
{
|
||||
$employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
||||
$receiving_ids = $receiving_id == -1 ? $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT) : [$receiving_id]; // TODO: Replace -1 with constant
|
||||
|
||||
if ($this->receiving->delete_list($receiving_ids, $employee_id, $update_inventory)) { // TODO: Likely need to surround this block of code in a try-catch to catch the ReflectionException
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Receivings.successfully_deleted') . ' ' . count($receiving_ids) . ' ' . lang('Receivings.one_or_multiple'),
|
||||
'ids' => $receiving_ids
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => lang('Receivings.cannot_be_deleted')]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Receivings.cannot_be_deleted')]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a supplier from a receiving. Used in app/Views/receivings/receiving.php
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getRemoveSupplier(): void
|
||||
public function getRemoveSupplier(): string
|
||||
{
|
||||
$this->receiving_lib->clear_reference();
|
||||
$this->receiving_lib->remove_supplier();
|
||||
|
||||
$this->_reload(); // TODO: Hungarian notation
|
||||
return $this->_reload(); // TODO: Hungarian notation
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete and finalize receiving. Used in app/Views/receivings/receiving.php
|
||||
*
|
||||
* @return string
|
||||
* @throws ReflectionException
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postComplete(): void
|
||||
public function postComplete(): string
|
||||
{
|
||||
|
||||
$data = [];
|
||||
@@ -356,18 +371,21 @@ class Receivings extends Secure_Controller
|
||||
|
||||
$data['print_after_sale'] = $this->receiving_lib->is_print_after_sale();
|
||||
|
||||
echo view("receivings/receipt", $data);
|
||||
$view = view("receivings/receipt", $data);
|
||||
|
||||
$this->receiving_lib->clear_all();
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a receiving requisition. Used in app/Views/receivings/receiving.php.
|
||||
*
|
||||
* @return string
|
||||
* @throws ReflectionException
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postRequisitionComplete(): void
|
||||
public function postRequisitionComplete(): string
|
||||
{
|
||||
if ($this->receiving_lib->get_stock_source() != $this->receiving_lib->get_stock_destination()) {
|
||||
foreach ($this->receiving_lib->get_cart() as $item) {
|
||||
@@ -376,11 +394,11 @@ class Receivings extends Secure_Controller
|
||||
$this->receiving_lib->add_item($item['item_id'], -$item['quantity'], $this->receiving_lib->get_stock_source(), $item['discount_type']);
|
||||
}
|
||||
|
||||
$this->postComplete();
|
||||
return $this->postComplete();
|
||||
} else {
|
||||
$data['error'] = lang('Receivings.error_requisition');
|
||||
|
||||
$this->_reload($data); // TODO: Hungarian notation
|
||||
return $this->_reload($data); // TODO: Hungarian notation
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,10 +406,10 @@ class Receivings extends Secure_Controller
|
||||
* Gets the receipt for a receiving. Used in app/Views/receivings/form.php
|
||||
*
|
||||
* @param $receiving_id
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getReceipt($receiving_id): void
|
||||
public function getReceipt($receiving_id): string
|
||||
{
|
||||
$receiving_info = $this->receiving->get_info($receiving_id)->getRowArray();
|
||||
$this->receiving_lib->copy_entire_receiving($receiving_id);
|
||||
@@ -424,16 +442,18 @@ class Receivings extends Secure_Controller
|
||||
|
||||
$data['print_after_sale'] = false;
|
||||
|
||||
echo view("receivings/receipt", $data);
|
||||
$view = view("receivings/receipt", $data);
|
||||
|
||||
$this->receiving_lib->clear_all();
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
private function _reload(array $data = []): void // TODO: Hungarian notation
|
||||
private function _reload(array $data = []): string // TODO: Hungarian notation
|
||||
{
|
||||
$data['cart'] = $this->receiving_lib->get_cart();
|
||||
$data['modes'] = ['receive' => lang('Receivings.receiving'), 'return' => lang('Receivings.return')];
|
||||
@@ -470,36 +490,47 @@ class Receivings extends Secure_Controller
|
||||
|
||||
$data['print_after_sale'] = $this->receiving_lib->is_print_after_sale();
|
||||
|
||||
echo view("receivings/receiving", $data);
|
||||
return view("receivings/receiving", $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function postSave(int $receiving_id = -1): void // TODO: Replace -1 with a constant
|
||||
public function postSave(int $receiving_id = -1): ResponseInterface // TODO: Replace -1 with a constant
|
||||
{
|
||||
$newdate = $this->request->getPost('date', FILTER_SANITIZE_FULL_SPECIAL_CHARS); // TODO: newdate does not follow naming conventions
|
||||
|
||||
$date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $newdate);
|
||||
$receiving_time = $date_formatter->format('Y-m-d H:i:s');
|
||||
|
||||
$current_employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
||||
$submitted_employee_id = $this->request->getPost('employee_id', FILTER_SANITIZE_NUMBER_INT);
|
||||
|
||||
if (!$this->employee->has_grant('employees', $current_employee_id)) {
|
||||
$existing_receiving = $this->receiving->get_info($receiving_id)->getRowArray();
|
||||
$employee_id = $existing_receiving['employee_id'];
|
||||
} else {
|
||||
$employee_id = $submitted_employee_id;
|
||||
}
|
||||
|
||||
$receiving_data = [
|
||||
'receiving_time' => $receiving_time,
|
||||
'supplier_id' => $this->request->getPost('supplier_id') ? $this->request->getPost('supplier_id', FILTER_SANITIZE_NUMBER_INT) : null,
|
||||
'employee_id' => $this->request->getPost('employee_id', FILTER_SANITIZE_NUMBER_INT),
|
||||
'employee_id' => $employee_id,
|
||||
'comment' => $this->request->getPost('comment', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||
'reference' => $this->request->getPost('reference') != '' ? $this->request->getPost('reference', FILTER_SANITIZE_FULL_SPECIAL_CHARS) : null
|
||||
];
|
||||
|
||||
$this->inventory->update('RECV ' . $receiving_id, ['trans_date' => $receiving_time]);
|
||||
if ($this->receiving->update($receiving_id, $receiving_data)) {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Receivings.successfully_updated'),
|
||||
'id' => $receiving_id
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Receivings.unsuccessfully_updated'),
|
||||
'id' => $receiving_id
|
||||
@@ -510,13 +541,13 @@ class Receivings extends Secure_Controller
|
||||
/**
|
||||
* Cancel an in-process receiving. Used in app/Views/receivings/receiving.php
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postCancelReceiving(): void
|
||||
public function postCancelReceiving(): string
|
||||
{
|
||||
$this->receiving_lib->clear_all();
|
||||
|
||||
$this->_reload(); // TODO: Hungarian Notation
|
||||
return $this->_reload(); // TODO: Hungarian Notation
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ use App\Models\Reports\Summary_sales;
|
||||
use App\Models\Reports\Summary_sales_taxes;
|
||||
use App\Models\Reports\Summary_suppliers;
|
||||
use App\Models\Reports\Summary_taxes;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\OSPOS;
|
||||
use Config\Services;
|
||||
|
||||
@@ -84,7 +85,8 @@ class Reports extends Secure_Controller
|
||||
|
||||
// Check access to report submodule
|
||||
if (!$this->employee->has_grant('reports_' . $submodule_id, $this->employee->get_logged_in_employee_info()->person_id)) {
|
||||
redirect('no_access/reports/reports_' . $submodule_id);
|
||||
header('Location: ' . base_url('no_access/reports/reports_' . $submodule_id));
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,8 +103,9 @@ class Reports extends Secure_Controller
|
||||
|
||||
/**
|
||||
* Initial Report listing screen
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
$person_id = $this->session->get('person_id');
|
||||
$grants = $this->employee->get_employee_grants($this->session->get('person_id'));
|
||||
@@ -114,7 +117,7 @@ class Reports extends Secure_Controller
|
||||
'permission_ids' => $permissions_ids,
|
||||
];
|
||||
|
||||
echo view('reports/listing', $data);
|
||||
return view('reports/listing', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,9 +126,9 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @param string $location_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function summary_sales(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): void // TODO: Perhaps these need to be passed as an array? Too many parameters in the signature.
|
||||
public function summary_sales(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): string // TODO: Perhaps these need to be passed as an array? Too many parameters in the signature.
|
||||
{ // TODO: Duplicated code
|
||||
$this->clearCache();
|
||||
|
||||
@@ -161,7 +164,7 @@ class Reports extends Secure_Controller
|
||||
'summary_data' => $summary
|
||||
];
|
||||
|
||||
echo view('reports/tabular', $data);
|
||||
return view('reports/tabular', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,9 +173,9 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @param string $location_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function summary_categories(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): void
|
||||
public function summary_categories(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): string
|
||||
{ // TODO: Duplicated code
|
||||
$this->clearCache();
|
||||
|
||||
@@ -207,7 +210,7 @@ class Reports extends Secure_Controller
|
||||
'summary_data' => $summary
|
||||
];
|
||||
|
||||
echo view('reports/tabular', $data);
|
||||
return view('reports/tabular', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,9 +218,9 @@ class Reports extends Secure_Controller
|
||||
* @param string $start_date
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function summary_expenses_categories(string $start_date, string $end_date, string $sale_type): void
|
||||
public function summary_expenses_categories(string $start_date, string $end_date, string $sale_type): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -244,7 +247,7 @@ class Reports extends Secure_Controller
|
||||
'summary_data' => $summary
|
||||
];
|
||||
|
||||
echo view('reports/tabular', $data);
|
||||
return view('reports/tabular', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,9 +256,9 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @param string $location_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function summary_customers(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): void
|
||||
public function summary_customers(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -292,7 +295,7 @@ class Reports extends Secure_Controller
|
||||
'summary_data' => $summary
|
||||
];
|
||||
|
||||
echo view('reports/tabular', $data);
|
||||
return view('reports/tabular', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,9 +304,9 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @param string $location_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function summary_suppliers(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): void
|
||||
public function summary_suppliers(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): string
|
||||
{ // TODO: Duplicated Code
|
||||
$this->clearCache();
|
||||
|
||||
@@ -338,7 +341,7 @@ class Reports extends Secure_Controller
|
||||
'summary_data' => $summary
|
||||
];
|
||||
|
||||
echo view('reports/tabular', $data);
|
||||
return view('reports/tabular', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -347,9 +350,9 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @param string $location_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function summary_items(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): void
|
||||
public function summary_items(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -388,7 +391,7 @@ class Reports extends Secure_Controller
|
||||
'summary_data' => $summary
|
||||
];
|
||||
|
||||
echo view('reports/tabular', $data);
|
||||
return view('reports/tabular', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -397,9 +400,9 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @param string $location_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function summary_employees(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): void
|
||||
public function summary_employees(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -436,7 +439,7 @@ class Reports extends Secure_Controller
|
||||
'summary_data' => $summary
|
||||
];
|
||||
|
||||
echo view('reports/tabular', $data);
|
||||
return view('reports/tabular', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -445,9 +448,9 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @param string $location_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function summary_taxes(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): void
|
||||
public function summary_taxes(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): string
|
||||
{ // TODO: Duplicate Code
|
||||
$this->clearCache();
|
||||
|
||||
@@ -482,13 +485,14 @@ class Reports extends Secure_Controller
|
||||
'summary_data' => $summary
|
||||
];
|
||||
|
||||
echo view('reports/tabular', $data);
|
||||
return view('reports/tabular', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary Sales Taxes report
|
||||
* @return string
|
||||
*/
|
||||
public function summary_sales_taxes(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): void
|
||||
public function summary_sales_taxes(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): string
|
||||
{ // TODO: Duplicated code
|
||||
$this->clearCache();
|
||||
|
||||
@@ -521,16 +525,16 @@ class Reports extends Secure_Controller
|
||||
'summary_data' => $summary
|
||||
];
|
||||
|
||||
echo view('reports/tabular', $data);
|
||||
return view('reports/tabular', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary Discounts report input. Used in app/Config/Routes.php
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function summary_discounts_input(): void
|
||||
public function summary_discounts_input(): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -541,13 +545,14 @@ class Reports extends Secure_Controller
|
||||
$data['discount_type_options'] = ['0' => lang('Reports.discount_percent'), '1' => lang('Reports.discount_fixed')];
|
||||
$data['sale_type_options'] = $this->get_sale_type_options();
|
||||
|
||||
echo view('reports/date_input', $data);
|
||||
return view('reports/date_input', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary Discounts report
|
||||
* @return string
|
||||
**/
|
||||
public function summary_discounts(string $start_date, string $end_date, string $sale_type, string $location_id = 'all', int $discount_type = 0): void
|
||||
public function summary_discounts(string $start_date, string $end_date, string $sale_type, string $location_id = 'all', int $discount_type = 0): string
|
||||
{ // TODO: Duplicated Code
|
||||
$this->clearCache();
|
||||
|
||||
@@ -579,13 +584,14 @@ class Reports extends Secure_Controller
|
||||
'summary_data' => $summary
|
||||
];
|
||||
|
||||
echo view('reports/tabular', $data);
|
||||
return view('reports/tabular', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary Payments report
|
||||
* @return string
|
||||
*/
|
||||
public function summary_payments(string $start_date, string $end_date): void
|
||||
public function summary_payments(string $start_date, string $end_date): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -637,16 +643,16 @@ class Reports extends Secure_Controller
|
||||
'summary_data' => $summary
|
||||
];
|
||||
|
||||
echo view('reports/tabular', $data);
|
||||
return view('reports/tabular', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Input for reports that require only a date range. Used in app/Config/Routes.php
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function date_input(): void
|
||||
public function date_input(): string
|
||||
{ // TODO: Duplicated Code
|
||||
$this->clearCache();
|
||||
|
||||
@@ -656,30 +662,30 @@ class Reports extends Secure_Controller
|
||||
$data['mode'] = 'sale';
|
||||
$data['sale_type_options'] = $this->get_sale_type_options();
|
||||
|
||||
echo view('reports/date_input', $data);
|
||||
return view('reports/date_input', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Input for reports that require only a date range. Used in app/Config/Routes.php
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function date_input_only(): void
|
||||
public function date_input_only(): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
$data = [];
|
||||
echo view('reports/date_input', $data);
|
||||
return view('reports/date_input', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Input for reports that require only a date range. Used in app/Config/Routes.php
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function date_input_sales(): void
|
||||
public function date_input_sales(): string
|
||||
{ // TODO: Duplicated Code
|
||||
$this->clearCache();
|
||||
|
||||
@@ -689,23 +695,23 @@ class Reports extends Secure_Controller
|
||||
$data['mode'] = 'sale';
|
||||
$data['sale_type_options'] = $this->get_sale_type_options();
|
||||
|
||||
echo view('reports/date_input', $data);
|
||||
return view('reports/date_input', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receivings date input. Used in app/Config/Routes.php
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function date_input_recv(): void
|
||||
public function date_input_recv(): string
|
||||
{
|
||||
$stock_locations = $data = $this->stock_location->get_allowed_locations('receivings');
|
||||
$stock_locations['all'] = lang('Reports.all');
|
||||
$data['stock_locations'] = array_reverse($stock_locations, true);
|
||||
$data['mode'] = 'receiving';
|
||||
|
||||
echo view('reports/date_input', $data);
|
||||
return view('reports/date_input', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -714,10 +720,10 @@ class Reports extends Secure_Controller
|
||||
* @param string $start_date
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function graphical_summary_expenses_categories(string $start_date, string $end_date, string $sale_type): void
|
||||
public function graphical_summary_expenses_categories(string $start_date, string $end_date, string $sale_type): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -750,7 +756,7 @@ class Reports extends Secure_Controller
|
||||
'show_currency' => true
|
||||
];
|
||||
|
||||
echo view('reports/graphical', $data);
|
||||
return view('reports/graphical', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -760,9 +766,9 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @param string $location_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function graphical_summary_sales(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): void
|
||||
public function graphical_summary_sales(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -796,7 +802,7 @@ class Reports extends Secure_Controller
|
||||
'show_currency' => true
|
||||
];
|
||||
|
||||
echo view('reports/graphical', $data);
|
||||
return view('reports/graphical', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -806,9 +812,9 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @param string $location_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function graphical_summary_items(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): void
|
||||
public function graphical_summary_items(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -843,7 +849,7 @@ class Reports extends Secure_Controller
|
||||
'show_currency' => true
|
||||
];
|
||||
|
||||
echo view('reports/graphical', $data);
|
||||
return view('reports/graphical', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -853,9 +859,9 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @param string $location_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function graphical_summary_categories(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): void
|
||||
public function graphical_summary_categories(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): string
|
||||
{ // TODO: Duplicated Code
|
||||
$this->clearCache();
|
||||
|
||||
@@ -886,7 +892,7 @@ class Reports extends Secure_Controller
|
||||
'show_currency' => true
|
||||
];
|
||||
|
||||
echo view('reports/graphical', $data);
|
||||
return view('reports/graphical', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -896,9 +902,9 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @param string $location_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function graphical_summary_suppliers(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): void
|
||||
public function graphical_summary_suppliers(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): string
|
||||
{ // TODO: Duplicated Code
|
||||
$this->clearCache();
|
||||
|
||||
@@ -931,7 +937,7 @@ class Reports extends Secure_Controller
|
||||
'show_currency' => true
|
||||
];
|
||||
|
||||
echo view('reports/graphical', $data);
|
||||
return view('reports/graphical', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -941,9 +947,9 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @param string $location_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function graphical_summary_employees(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): void
|
||||
public function graphical_summary_employees(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -975,7 +981,7 @@ class Reports extends Secure_Controller
|
||||
'show_currency' => true
|
||||
];
|
||||
|
||||
echo view('reports/graphical', $data);
|
||||
return view('reports/graphical', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -985,9 +991,9 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @param string $location_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function graphical_summary_taxes(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): void
|
||||
public function graphical_summary_taxes(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): string
|
||||
{ // TODO: Duplicated Code
|
||||
$this->clearCache();
|
||||
|
||||
@@ -1019,7 +1025,7 @@ class Reports extends Secure_Controller
|
||||
'show_currency' => true
|
||||
];
|
||||
|
||||
echo view('reports/graphical', $data);
|
||||
return view('reports/graphical', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1029,9 +1035,9 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @param string $location_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function graphical_summary_sales_taxes(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): void
|
||||
public function graphical_summary_sales_taxes(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): string
|
||||
{ // TODO: Duplicated Code
|
||||
$this->clearCache();
|
||||
|
||||
@@ -1063,7 +1069,7 @@ class Reports extends Secure_Controller
|
||||
'show_currency' => true
|
||||
];
|
||||
|
||||
echo view('reports/graphical', $data);
|
||||
return view('reports/graphical', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1073,9 +1079,9 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @param string $location_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function graphical_summary_customers(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): void
|
||||
public function graphical_summary_customers(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): string
|
||||
{ // TODO: Duplicated Code
|
||||
$this->clearCache();
|
||||
|
||||
@@ -1109,7 +1115,7 @@ class Reports extends Secure_Controller
|
||||
'show_currency' => true
|
||||
];
|
||||
|
||||
echo view('reports/graphical', $data);
|
||||
return view('reports/graphical', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1120,9 +1126,10 @@ class Reports extends Secure_Controller
|
||||
* @param string $sale_type
|
||||
* @param string $location_id ID of the location to be reported or 'all' if none is specified
|
||||
* @param int $discount_type
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function graphical_summary_discounts(string $start_date, string $end_date, string $sale_type, string $location_id = 'all', int $discount_type = 0): void
|
||||
public function graphical_summary_discounts(string $start_date, string $end_date, string $sale_type, string $location_id = 'all', int $discount_type = 0): string
|
||||
{ // TODO: Duplicated Code
|
||||
$this->clearCache();
|
||||
|
||||
@@ -1157,7 +1164,7 @@ class Reports extends Secure_Controller
|
||||
'show_currency' => false
|
||||
];
|
||||
|
||||
echo view('reports/graphical', $data);
|
||||
return view('reports/graphical', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1167,9 +1174,9 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @param string $location_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function graphical_summary_payments(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): void
|
||||
public function graphical_summary_payments(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -1203,16 +1210,16 @@ class Reports extends Secure_Controller
|
||||
'show_currency' => true
|
||||
];
|
||||
|
||||
echo view('reports/graphical', $data);
|
||||
return view('reports/graphical', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the specific customer input view. Used in app/Config/Routes.php
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function specific_customer_input(): void
|
||||
public function specific_customer_input(): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -1230,7 +1237,7 @@ class Reports extends Secure_Controller
|
||||
$data['sale_type_options'] = $this->get_sale_type_options();
|
||||
|
||||
$data['payment_type'] = $this->get_payment_type();
|
||||
echo view('reports/specific_customer_input', $data);
|
||||
return view('reports/specific_customer_input', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1257,10 +1264,10 @@ class Reports extends Secure_Controller
|
||||
* @param string $customer_id
|
||||
* @param string $sale_type
|
||||
* @param string $payment_type
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function specific_customers(string $start_date, string $end_date, string $customer_id, string $sale_type, string $payment_type): void
|
||||
public function specific_customers(string $start_date, string $end_date, string $customer_id, string $sale_type, string $payment_type): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -1351,16 +1358,16 @@ class Reports extends Secure_Controller
|
||||
'overall_summary_data' => $specific_customer->getSummaryData($inputs)
|
||||
];
|
||||
|
||||
echo view('reports/tabular_details', $data);
|
||||
return view('reports/tabular_details', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detailed employee report input form. Used in app/Config/Routes.php
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function specific_employee_input(): void
|
||||
public function specific_employee_input(): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -1374,7 +1381,7 @@ class Reports extends Secure_Controller
|
||||
$data['specific_input_data'] = $employees;
|
||||
$data['sale_type_options'] = $this->get_sale_type_options();
|
||||
|
||||
echo view('reports/specific_input', $data);
|
||||
return view('reports/specific_input', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1384,10 +1391,10 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $employee_id
|
||||
* @param string $sale_type
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function specific_employees(string $start_date, string $end_date, string $employee_id, string $sale_type): void
|
||||
public function specific_employees(string $start_date, string $end_date, string $employee_id, string $sale_type): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -1474,16 +1481,16 @@ class Reports extends Secure_Controller
|
||||
'overall_summary_data' => $specific_employee->getSummaryData($inputs)
|
||||
];
|
||||
|
||||
echo view('reports/tabular_details', $data);
|
||||
return view('reports/tabular_details', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detailed discount report. Used in app/Config/Routes.php
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function specific_discount_input(): void
|
||||
public function specific_discount_input(): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -1498,7 +1505,7 @@ class Reports extends Secure_Controller
|
||||
$data['discount_type_options'] = ['0' => lang('Reports.discount_percent'), '1' => lang('Reports.discount_fixed')];
|
||||
$data['sale_type_options'] = $this->get_sale_type_options();
|
||||
|
||||
echo view('reports/specific_input', $data);
|
||||
return view('reports/specific_input', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1509,10 +1516,10 @@ class Reports extends Secure_Controller
|
||||
* @param string $discount
|
||||
* @param string $sale_type
|
||||
* @param string $discount_type
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function specific_discounts(string $start_date, string $end_date, string $discount, string $sale_type, string $discount_type): void
|
||||
public function specific_discounts(string $start_date, string $end_date, string $discount, string $sale_type, string $discount_type): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -1605,17 +1612,17 @@ class Reports extends Secure_Controller
|
||||
'overall_summary_data' => $specific_discount->getSummaryData($inputs)
|
||||
];
|
||||
|
||||
echo view('reports/tabular_details', $data);
|
||||
return view('reports/tabular_details', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the detailed sales data row for given sale_id. Used in app/Views/reports/tabular_details.php
|
||||
*
|
||||
* @param string $sale_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getGet_detailed_sales_row(string $sale_id): void
|
||||
public function getGet_detailed_sales_row(string $sale_id): ResponseInterface
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -1658,16 +1665,16 @@ class Reports extends Secure_Controller
|
||||
)
|
||||
];
|
||||
|
||||
echo json_encode([$sale_id => $summary_data]);
|
||||
return $this->response->setJSON([$sale_id => $summary_data]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detailed Supplier report input form. Used in app/Config/Routes.php
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function specific_supplier_input(): void
|
||||
public function specific_supplier_input(): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -1681,7 +1688,7 @@ class Reports extends Secure_Controller
|
||||
$data['specific_input_data'] = $suppliers;
|
||||
$data['sale_type_options'] = $this->get_sale_type_options();
|
||||
|
||||
echo view('reports/specific_input', $data);
|
||||
return view('reports/specific_input', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1691,9 +1698,9 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $supplier_id
|
||||
* @param string $sale_type
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function specific_suppliers(string $start_date, string $end_date, string $supplier_id, string $sale_type): void
|
||||
public function specific_suppliers(string $start_date, string $end_date, string $supplier_id, string $sale_type): string
|
||||
{
|
||||
$inputs = [
|
||||
'start_date' => $start_date,
|
||||
@@ -1736,7 +1743,7 @@ class Reports extends Secure_Controller
|
||||
'summary_data' => $specific_supplier->getSummaryData($inputs)
|
||||
];
|
||||
|
||||
echo view('reports/tabular', $data);
|
||||
return view('reports/tabular', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1763,13 +1770,13 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $sale_type
|
||||
* @param string $location_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function detailed_sales(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): void
|
||||
public function detailed_sales(string $start_date, string $end_date, string $sale_type, string $location_id = 'all'): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
$definition_names = $this->attribute->get_definitions_by_flags(attribute::SHOW_IN_SALES);
|
||||
$definition_names = $this->attribute->get_definitions_by_flags(attribute::SHOW_IN_SALES, true);
|
||||
|
||||
$inputs = [
|
||||
'start_date' => $start_date,
|
||||
@@ -1782,7 +1789,12 @@ class Reports extends Secure_Controller
|
||||
$this->detailed_sales->create($inputs);
|
||||
|
||||
$columns = $this->detailed_sales->getDataColumns();
|
||||
$columns['details'] = array_merge($columns['details'], $definition_names);
|
||||
// Extract just names for column headers
|
||||
$definitionHeaders = [];
|
||||
foreach ($definition_names as $definition_id => $definitionInfo) {
|
||||
$definitionHeaders[$definition_id] = $definitionInfo['name'];
|
||||
}
|
||||
$columns['details'] = array_merge($columns['details'], $definitionHeaders);
|
||||
|
||||
$headers = $columns;
|
||||
|
||||
@@ -1869,17 +1881,17 @@ class Reports extends Secure_Controller
|
||||
'details_data_rewards' => $details_data_rewards,
|
||||
'overall_summary_data' => $this->detailed_sales->getSummaryData($inputs)
|
||||
];
|
||||
echo view('reports/tabular_details', $data);
|
||||
return view('reports/tabular_details', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns detailed receivings row for given receiving_id. Used in app/Views/reports/tabular_details.php
|
||||
*
|
||||
* @param string $receiving_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getGet_detailed_receivings_row(string $receiving_id): void
|
||||
public function getGet_detailed_receivings_row(string $receiving_id): ResponseInterface
|
||||
{
|
||||
$inputs = ['receiving_id' => $receiving_id];
|
||||
|
||||
@@ -1909,7 +1921,7 @@ class Reports extends Secure_Controller
|
||||
)
|
||||
];
|
||||
|
||||
echo json_encode([$receiving_id => $summary_data]);
|
||||
return $this->response->setJSON([$receiving_id => $summary_data]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1917,20 +1929,25 @@ class Reports extends Secure_Controller
|
||||
* @param string $end_date
|
||||
* @param string $receiving_type
|
||||
* @param string $location_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function detailed_receivings(string $start_date, string $end_date, string $receiving_type, string $location_id = 'all'): void
|
||||
public function detailed_receivings(string $start_date, string $end_date, string $receiving_type, string $location_id = 'all'): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
$definition_names = $this->attribute->get_definitions_by_flags(attribute::SHOW_IN_RECEIVINGS);
|
||||
$definition_names = $this->attribute->get_definitions_by_flags(attribute::SHOW_IN_RECEIVINGS, true);
|
||||
|
||||
$inputs = ['start_date' => $start_date, 'end_date' => $end_date, 'receiving_type' => $receiving_type, 'location_id' => $location_id, 'definition_ids' => array_keys($definition_names)];
|
||||
|
||||
$this->detailed_receivings->create($inputs);
|
||||
|
||||
$columns = $this->detailed_receivings->getDataColumns();
|
||||
$columns['details'] = array_merge($columns['details'], $definition_names);
|
||||
// Extract just names for column headers
|
||||
$definitionHeaders = [];
|
||||
foreach ($definition_names as $definition_id => $definitionInfo) {
|
||||
$definitionHeaders[$definition_id] = $definitionInfo['name'];
|
||||
}
|
||||
$columns['details'] = array_merge($columns['details'], $definitionHeaders);
|
||||
|
||||
$headers = $columns;
|
||||
$report_data = $this->detailed_receivings->getData($inputs);
|
||||
@@ -1993,13 +2010,13 @@ class Reports extends Secure_Controller
|
||||
'overall_summary_data' => $this->detailed_receivings->getSummaryData($inputs)
|
||||
];
|
||||
|
||||
echo view('reports/tabular_details', $data);
|
||||
return view('reports/tabular_details', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function inventory_low(): void
|
||||
public function inventory_low(): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -2028,16 +2045,16 @@ class Reports extends Secure_Controller
|
||||
'summary_data' => $inventory_low->getSummaryData($inputs)
|
||||
];
|
||||
|
||||
echo view('reports/tabular', $data);
|
||||
return view('reports/tabular', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the inventory summary input view. Used in app/Config/Routes.php
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function inventory_summary_input(): void
|
||||
public function inventory_summary_input(): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -2048,15 +2065,15 @@ class Reports extends Secure_Controller
|
||||
$stock_locations['all'] = lang('Reports.all');
|
||||
$data['stock_locations'] = array_reverse($stock_locations, true);
|
||||
|
||||
echo view('reports/inventory_summary_input', $data);
|
||||
return view('reports/inventory_summary_input', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $location_id
|
||||
* @param string $item_count
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function inventory_summary(string $location_id = 'all', string $item_count = 'all'): void
|
||||
public function inventory_summary(string $location_id = 'all', string $item_count = 'all'): string
|
||||
{
|
||||
$this->clearCache();
|
||||
|
||||
@@ -2088,7 +2105,7 @@ class Reports extends Secure_Controller
|
||||
'summary_data' => $this->inventory_summary->getSummaryData($report_data)
|
||||
];
|
||||
|
||||
echo view('reports/tabular', $data);
|
||||
return view('reports/tabular', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ namespace App\Controllers;
|
||||
|
||||
use App\Models\Employee;
|
||||
use App\Models\Module;
|
||||
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\Model;
|
||||
use CodeIgniter\Session\Session;
|
||||
use Config\OSPOS;
|
||||
@@ -85,18 +85,17 @@ class Secure_Controller extends BaseController
|
||||
|
||||
/**
|
||||
* AJAX function used to confirm whether values sent in the request are numeric
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getCheckNumeric(): void
|
||||
public function getCheckNumeric(): ResponseInterface
|
||||
{
|
||||
foreach ($this->request->getGet() as $value) {
|
||||
if (parse_decimals($value) === false) {
|
||||
echo 'false';
|
||||
return;
|
||||
return $this->response->setJSON('false');
|
||||
}
|
||||
}
|
||||
echo 'true';
|
||||
return $this->response->setJSON('true');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Attribute;
|
||||
use App\Models\Supplier;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\Services;
|
||||
|
||||
class Suppliers extends Persons
|
||||
@@ -16,34 +18,22 @@ class Suppliers extends Persons
|
||||
$this->supplier = model(Supplier::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
$data['table_headers'] = get_suppliers_manage_table_headers();
|
||||
|
||||
echo view('people/manage', $data);
|
||||
return view('people/manage', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets one row for a supplier manage table. This is called using AJAX to update one row.
|
||||
* @param $row_id
|
||||
* @return void
|
||||
*/
|
||||
public function getRow($row_id): void
|
||||
public function getRow($rowId): ResponseInterface
|
||||
{
|
||||
$data_row = get_supplier_data_row($this->supplier->get_info($row_id));
|
||||
$data_row['category'] = $this->supplier->get_category_name($data_row['category']);
|
||||
$dataRow = get_supplier_data_row($this->supplier->get_info($rowId));
|
||||
$dataRow['category'] = $this->supplier->get_category_name($dataRow['category']);
|
||||
|
||||
echo json_encode($data_row);
|
||||
return $this->response->setJSON($dataRow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Supplier table data rows. This will be called with AJAX.
|
||||
* @return void
|
||||
**/
|
||||
public function getSearch(): void
|
||||
public function getSearch(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('search');
|
||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||
@@ -52,78 +42,64 @@ class Suppliers extends Persons
|
||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
$suppliers = $this->supplier->search($search, $limit, $offset, $sort, $order);
|
||||
$total_rows = $this->supplier->get_found_rows($search);
|
||||
$totalRows = $this->supplier->get_found_rows($search);
|
||||
|
||||
$data_rows = [];
|
||||
$dataRows = [];
|
||||
|
||||
foreach ($suppliers->getResult() as $supplier) {
|
||||
$row = get_supplier_data_row($supplier);
|
||||
$row['category'] = $this->supplier->get_category_name($row['category']);
|
||||
$data_rows[] = $row;
|
||||
$dataRows[] = $row;
|
||||
}
|
||||
|
||||
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
return $this->response->setJSON(['total' => $totalRows, 'rows' => $dataRows]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives search suggestions based on what is being searched for
|
||||
**/
|
||||
public function getSuggest(): void
|
||||
public function getSuggest(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('term');
|
||||
$suggestions = $this->supplier->get_search_suggestions($search, true);
|
||||
|
||||
echo json_encode($suggestions);
|
||||
return $this->response->setJSON($suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function suggest_search(): void
|
||||
public function suggestSearch(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getPost('term');
|
||||
$suggestions = $this->supplier->get_search_suggestions($search, false);
|
||||
|
||||
echo json_encode($suggestions);
|
||||
return $this->response->setJSON($suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the supplier edit form
|
||||
*
|
||||
* @param int $supplier_id
|
||||
* @return void
|
||||
*/
|
||||
public function getView(int $supplier_id = NEW_ENTRY): void
|
||||
public function getView(int $supplierId = NEW_ENTRY): string
|
||||
{
|
||||
$info = $this->supplier->get_info($supplier_id);
|
||||
$info = $this->supplier->get_info($supplierId);
|
||||
foreach (get_object_vars($info) as $property => $value) {
|
||||
$info->$property = $value;
|
||||
}
|
||||
$data['person_info'] = $info;
|
||||
$data['categories'] = $this->supplier->get_categories();
|
||||
|
||||
echo view("suppliers/form", $data);
|
||||
return view("suppliers/form", $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts/updates a supplier
|
||||
*
|
||||
* @param int $supplier_id
|
||||
* @return void
|
||||
*/
|
||||
public function postSave(int $supplier_id = NEW_ENTRY): void
|
||||
public function getAttributes(int $supplierId = NEW_ENTRY): string
|
||||
{
|
||||
$first_name = $this->request->getPost('first_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS); // TODO: Duplicate code
|
||||
$last_name = $this->request->getPost('last_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
return $this->getPersonAttributes($supplierId, Attribute::SHOW_IN_SUPPLIERS);
|
||||
}
|
||||
|
||||
public function postSave(int $supplierId = NEW_ENTRY): ResponseInterface
|
||||
{
|
||||
$firstName = $this->request->getPost('first_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$lastName = $this->request->getPost('last_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$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'),
|
||||
'email' => $email,
|
||||
'phone_number' => $this->request->getPost('phone_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||
@@ -136,7 +112,7 @@ class Suppliers extends Persons
|
||||
'comments' => $this->request->getPost('comments', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
|
||||
];
|
||||
|
||||
$supplier_data = [
|
||||
$supplierData = [
|
||||
'company_name' => $this->request->getPost('company_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||
'agency_name' => $this->request->getPost('agency_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||
'category' => $this->request->getPost('category', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||
@@ -144,47 +120,43 @@ class Suppliers extends Persons
|
||||
'tax_id' => $this->request->getPost('tax_id', FILTER_SANITIZE_NUMBER_INT)
|
||||
];
|
||||
|
||||
if ($this->supplier->save_supplier($person_data, $supplier_data, $supplier_id)) {
|
||||
// New supplier
|
||||
if ($supplier_id == NEW_ENTRY) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => lang('Suppliers.successful_adding') . ' ' . $supplier_data['company_name'],
|
||||
'id' => $supplier_data['person_id']
|
||||
]);
|
||||
} else { // Existing supplier
|
||||
if ($this->supplier->save_supplier($personData, $supplierData, $supplierId)) {
|
||||
$personId = $supplierId == NEW_ENTRY ? $supplierData['person_id'] : $supplierId;
|
||||
$this->savePersonAttributes($personId, Attribute::SHOW_IN_SUPPLIERS);
|
||||
|
||||
echo json_encode([
|
||||
if ($supplierId == NEW_ENTRY) {
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Suppliers.successful_updating') . ' ' . $supplier_data['company_name'],
|
||||
'id' => $supplier_id
|
||||
'message' => lang('Suppliers.successful_adding') . ' ' . $supplierData['company_name'],
|
||||
'id' => $supplierData['person_id']
|
||||
]);
|
||||
} else {
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Suppliers.successful_updating') . ' ' . $supplierData['company_name'],
|
||||
'id' => $supplierId
|
||||
]);
|
||||
}
|
||||
} else { // Failure
|
||||
echo json_encode([
|
||||
} else {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Suppliers.error_adding_updating') . ' ' . $supplier_data['company_name'],
|
||||
'message' => lang('Suppliers.error_adding_updating') . ' ' . $supplierData['company_name'],
|
||||
'id' => NEW_ENTRY
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This deletes suppliers from the suppliers table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function postDelete(): void
|
||||
public function postDelete(): ResponseInterface
|
||||
{
|
||||
$suppliers_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
|
||||
$suppliersToDelete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
|
||||
|
||||
if ($this->supplier->delete_list($suppliers_to_delete)) {
|
||||
echo json_encode([
|
||||
if ($this->supplier->delete_list($suppliersToDelete)) {
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Suppliers.successful_deleted') . ' ' . count($suppliers_to_delete) . ' ' . lang('Suppliers.one_or_multiple')
|
||||
'message' => lang('Suppliers.successful_deleted') . ' ' . count($suppliersToDelete) . ' ' . lang('Suppliers.one_or_multiple')
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => lang('Suppliers.cannot_be_deleted')]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Suppliers.cannot_be_deleted')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Tax_category;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\Services;
|
||||
|
||||
/**
|
||||
@@ -20,13 +21,13 @@ class Tax_categories extends Secure_Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
$data['tax_categories_table_headers'] = get_tax_categories_table_headers();
|
||||
|
||||
echo view('taxes/tax_categories', $data);
|
||||
return view('taxes/tax_categories', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,12 +35,12 @@ class Tax_categories extends Secure_Controller
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getSearch(): void
|
||||
public function getSearch(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('search');
|
||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||
$sort = $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$sort = $this->sanitizeSortColumn(get_tax_categories_table_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'tax_category_id');
|
||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
$tax_categories = $this->tax_category->search($search, $limit, $offset, $sort, $order);
|
||||
@@ -50,37 +51,37 @@ class Tax_categories extends Secure_Controller
|
||||
$data_rows[] = get_tax_categories_data_row($tax_category);
|
||||
}
|
||||
|
||||
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $row_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getRow($row_id): void
|
||||
public function getRow($row_id): ResponseInterface
|
||||
{
|
||||
$data_row = get_tax_categories_data_row($this->tax_category->get_info($row_id));
|
||||
|
||||
echo json_encode($data_row);
|
||||
return $this->response->setJSON($data_row);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $tax_category_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getView(int $tax_category_id = NEW_ENTRY): void
|
||||
public function getView(int $tax_category_id = NEW_ENTRY): string
|
||||
{
|
||||
$data['tax_category_info'] = $this->tax_category->get_info($tax_category_id);
|
||||
|
||||
echo view("taxes/tax_category_form", $data);
|
||||
return view("taxes/tax_category_form", $data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param int $tax_category_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postSave(int $tax_category_id = NEW_ENTRY): void
|
||||
public function postSave(int $tax_category_id = NEW_ENTRY): ResponseInterface
|
||||
{
|
||||
$tax_category_data = [
|
||||
'tax_category' => $this->request->getPost('tax_category', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||
@@ -91,20 +92,20 @@ class Tax_categories extends Secure_Controller
|
||||
if ($this->tax_category->save_value($tax_category_data, $tax_category_id)) {
|
||||
// New tax_category_id
|
||||
if ($tax_category_id == NEW_ENTRY) {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Tax_categories.successful_adding'),
|
||||
'id' => $tax_category_data['tax_category_id']
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Tax_categories.successful_updating'),
|
||||
'id' => $tax_category_id
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Tax_categories.error_adding_updating') . ' ' . $tax_category_data['tax_category'],
|
||||
'id' => NEW_ENTRY
|
||||
@@ -113,19 +114,19 @@ class Tax_categories extends Secure_Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postDelete(): void
|
||||
public function postDelete(): ResponseInterface
|
||||
{
|
||||
$tax_categories_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
|
||||
|
||||
if ($this->tax_category->delete_list($tax_categories_to_delete)) {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Tax_categories.successful_deleted') . ' ' . count($tax_categories_to_delete) . ' ' . lang('Tax_categories.one_or_multiple')
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => lang('Tax_categories.cannot_be_deleted')]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Tax_categories.cannot_be_deleted')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Tax_code;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\Services;
|
||||
|
||||
/**
|
||||
@@ -22,11 +23,11 @@ class Tax_codes extends Secure_Controller
|
||||
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
echo view('taxes/tax_codes', $this->get_data());
|
||||
return view('taxes/tax_codes', $this->get_data());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,12 +45,12 @@ class Tax_codes extends Secure_Controller
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getSearch(): void
|
||||
public function getSearch(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('search');
|
||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||
$sort = $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$sort = $this->sanitizeSortColumn(get_tax_code_table_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'tax_code');
|
||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
$tax_codes = $this->tax_code->search($search, $limit, $offset, $sort, $order);
|
||||
@@ -61,37 +62,37 @@ class Tax_codes extends Secure_Controller
|
||||
$data_rows[] = get_tax_code_data_row($tax_code);
|
||||
}
|
||||
|
||||
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $row_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getRow(int $row_id): void
|
||||
public function getRow(int $row_id): ResponseInterface
|
||||
{
|
||||
$data_row = get_tax_code_data_row($this->tax_code->get_info($row_id));
|
||||
|
||||
echo json_encode($data_row);
|
||||
return $this->response->setJSON($data_row);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $tax_code_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getView(int $tax_code_id = NEW_ENTRY): void
|
||||
public function getView(int $tax_code_id = NEW_ENTRY): string
|
||||
{
|
||||
$data['tax_code_info'] = $this->tax_code->get_info($tax_code_id);
|
||||
|
||||
echo view("taxes/tax_code_form", $data);
|
||||
return view("taxes/tax_code_form", $data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param int $tax_code_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postSave(int $tax_code_id = NEW_ENTRY): void
|
||||
public function postSave(int $tax_code_id = NEW_ENTRY): ResponseInterface
|
||||
{
|
||||
$tax_code_data = [
|
||||
'tax_code' => $this->request->getPost('tax_code', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||
@@ -102,20 +103,20 @@ class Tax_codes extends Secure_Controller
|
||||
|
||||
if ($this->tax_code->save($tax_code_data)) {
|
||||
if ($tax_code_id == NEW_ENTRY) {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Tax_codes.successful_adding'),
|
||||
'id' => $tax_code_data['tax_code_id']
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Tax_codes.successful_updating'),
|
||||
'id' => $tax_code_id
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Tax_codes.error_adding_updating') . ' ' . $tax_code_data['tax_code_id'],
|
||||
'id' => NEW_ENTRY
|
||||
@@ -124,19 +125,19 @@ class Tax_codes extends Secure_Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postDelete(): void
|
||||
public function postDelete(): ResponseInterface
|
||||
{
|
||||
$tax_codes_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
|
||||
|
||||
if ($this->tax_code->delete_list($tax_codes_to_delete)) {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Tax_codes.successful_deleted') . ' ' . count($tax_codes_to_delete) . ' ' . lang('Tax_codes.one_or_multiple')
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => lang('Tax_codes.cannot_be_deleted')]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Tax_codes.cannot_be_deleted')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\Tax_jurisdiction;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\Services;
|
||||
|
||||
/**
|
||||
@@ -23,13 +24,13 @@ class Tax_jurisdictions extends Secure_Controller
|
||||
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
$data['table_headers'] = get_tax_jurisdictions_table_headers();
|
||||
|
||||
echo view('taxes/tax_jurisdictions', $data);
|
||||
return view('taxes/tax_jurisdictions', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,12 +38,12 @@ class Tax_jurisdictions extends Secure_Controller
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getSearch(): void
|
||||
public function getSearch(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('search');
|
||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||
$sort = $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$sort = $this->sanitizeSortColumn(get_tax_jurisdictions_table_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'jurisdiction_id');
|
||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
$tax_jurisdictions = $this->tax_jurisdiction->search($search, $limit, $offset, $sort, $order);
|
||||
@@ -53,37 +54,37 @@ class Tax_jurisdictions extends Secure_Controller
|
||||
$data_rows[] = get_tax_jurisdictions_data_row($tax_jurisdiction);
|
||||
}
|
||||
|
||||
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $row_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getRow(int $row_id): void
|
||||
public function getRow(int $row_id): ResponseInterface
|
||||
{
|
||||
$data_row = get_tax_jurisdictions_data_row($this->tax_jurisdiction->get_info($row_id));
|
||||
|
||||
echo json_encode($data_row);
|
||||
return $this->response->setJSON($data_row);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $tax_jurisdiction_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getView(int $tax_jurisdiction_id = NEW_ENTRY): void
|
||||
public function getView(int $tax_jurisdiction_id = NEW_ENTRY): string
|
||||
{
|
||||
$data['tax_jurisdiction_info'] = $this->tax_jurisdiction->get_info($tax_jurisdiction_id);
|
||||
|
||||
echo view("taxes/tax_jurisdiction_form", $data);
|
||||
return view("taxes/tax_jurisdiction_form", $data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param int $jurisdiction_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postSave(int $jurisdiction_id = NEW_ENTRY): void
|
||||
public function postSave(int $jurisdiction_id = NEW_ENTRY): ResponseInterface
|
||||
{
|
||||
$tax_jurisdiction_data = [
|
||||
'jurisdiction_name' => $this->request->getPost('jurisdiction_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||
@@ -92,20 +93,20 @@ class Tax_jurisdictions extends Secure_Controller
|
||||
|
||||
if ($this->tax_jurisdiction->save_value($tax_jurisdiction_data)) {
|
||||
if ($jurisdiction_id == NEW_ENTRY) {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Tax_jurisdictions.successful_adding'),
|
||||
'id' => $tax_jurisdiction_data['jurisdiction_id']
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Tax_jurisdictions.successful_updating'),
|
||||
'id' => $jurisdiction_id
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Tax_jurisdictions.error_adding_updating') . ' ' . $tax_jurisdiction_data['jurisdiction_name'],
|
||||
'id' => NEW_ENTRY
|
||||
@@ -114,19 +115,19 @@ class Tax_jurisdictions extends Secure_Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postDelete(): void
|
||||
public function postDelete(): ResponseInterface
|
||||
{
|
||||
$tax_jurisdictions_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
|
||||
|
||||
if ($this->tax_jurisdiction->delete_list($tax_jurisdictions_to_delete)) {
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'message' => lang('Tax_jurisdictions.successful_deleted') . ' ' . count($tax_jurisdictions_to_delete) . ' ' . lang('Tax_jurisdictions.one_or_multiple')
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => lang('Tax_jurisdictions.cannot_be_deleted')]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Tax_jurisdictions.cannot_be_deleted')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Models\Tax;
|
||||
use App\Models\Tax_category;
|
||||
use App\Models\Tax_code;
|
||||
use App\Models\Tax_jurisdiction;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\OSPOS;
|
||||
use Config\Services;
|
||||
|
||||
@@ -36,9 +37,9 @@ class Taxes extends Secure_Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex(): void
|
||||
public function getIndex(): string
|
||||
{
|
||||
$data['tax_codes'] = $this->tax_code->get_all()->getResultArray();
|
||||
if (count($data['tax_codes']) == 0) {
|
||||
@@ -67,7 +68,7 @@ class Taxes extends Secure_Controller
|
||||
|
||||
$data['tax_type_options'] = $this->tax_lib->get_tax_type_options($data['default_tax_type']);
|
||||
|
||||
echo view('taxes/manage', $data);
|
||||
return view('taxes/manage', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,12 +76,12 @@ class Taxes extends Secure_Controller
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getSearch(): void
|
||||
public function getSearch(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getGet('search');
|
||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||
$sort = $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$sort = $this->sanitizeSortColumn(get_tax_rates_manage_table_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'tax_rate_id');
|
||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
$tax_rates = $this->tax->search($search, $limit, $offset, $sort, $order);
|
||||
@@ -92,50 +93,50 @@ class Taxes extends Secure_Controller
|
||||
$data_rows[] = get_tax_rates_data_row($tax_rate_row);
|
||||
}
|
||||
|
||||
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives search suggestions based on what is being searched for
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function suggest_search(): void
|
||||
public function suggest_search(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getPost('term');
|
||||
$suggestions = $this->tax->get_search_suggestions($search); // TODO: There is no get_search_suggestions function in the tax model
|
||||
|
||||
echo json_encode($suggestions);
|
||||
return $this->response->setJSON($suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides list of tax categories to select from
|
||||
*
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function suggest_tax_categories(): void
|
||||
public function suggest_tax_categories(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getPost('term');
|
||||
$suggestions = $this->tax_category->get_tax_category_suggestions($search);
|
||||
|
||||
echo json_encode($suggestions);
|
||||
return $this->response->setJSON($suggestions);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param int $row_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getRow(int $row_id): void
|
||||
public function getRow(int $row_id): ResponseInterface
|
||||
{
|
||||
$data_row = get_tax_rates_data_row($this->tax->get_info($row_id));
|
||||
|
||||
echo json_encode($data_row);
|
||||
return $this->response->setJSON($data_row);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $tax_code
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getView_tax_codes(int $tax_code = NEW_ENTRY): void
|
||||
public function getView_tax_codes(int $tax_code = NEW_ENTRY): string
|
||||
{
|
||||
$tax_code_info = $this->tax->get_info($tax_code);
|
||||
|
||||
@@ -192,15 +193,15 @@ class Taxes extends Secure_Controller
|
||||
|
||||
$data['tax_rates'] = $tax_rates;
|
||||
|
||||
echo view('taxes/tax_code_form', $data);
|
||||
return view('taxes/tax_code_form', $data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param int $tax_rate_id
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getView(int $tax_rate_id = NEW_ENTRY): void
|
||||
public function getView(int $tax_rate_id = NEW_ENTRY): string
|
||||
{
|
||||
$tax_rate_info = $this->tax->get_info($tax_rate_id);
|
||||
|
||||
@@ -226,14 +227,14 @@ class Taxes extends Secure_Controller
|
||||
$data['tax_rate'] = $tax_rate_info->tax_rate;
|
||||
}
|
||||
|
||||
echo view('taxes/tax_rates_form', $data);
|
||||
return view('taxes/tax_rates_form', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $tax_code
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getView_tax_categories(int $tax_code = NEW_ENTRY): void // TODO: This appears to be called no where in the code.
|
||||
public function getView_tax_categories(int $tax_code = NEW_ENTRY): string // TODO: This appears to be called no where in the code.
|
||||
{
|
||||
$tax_code_info = $this->tax->get_info($tax_code); // TODO: Duplicated Code
|
||||
|
||||
@@ -290,14 +291,14 @@ class Taxes extends Secure_Controller
|
||||
|
||||
$data['tax_rates'] = $tax_rates;
|
||||
|
||||
echo view('taxes/tax_category_form', $data);
|
||||
return view('taxes/tax_category_form', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $tax_code
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getView_tax_jurisdictions(int $tax_code = NEW_ENTRY): void // TODO: This appears to be called no where in the code.
|
||||
public function getView_tax_jurisdictions(int $tax_code = NEW_ENTRY): string // TODO: This appears to be called no where in the code.
|
||||
{
|
||||
$tax_code_info = $this->tax->get_info($tax_code); // TODO: Duplicated code
|
||||
|
||||
@@ -354,7 +355,7 @@ class Taxes extends Secure_Controller
|
||||
|
||||
$data['tax_rates'] = $tax_rates;
|
||||
|
||||
echo view('taxes/tax_jurisdiction_form', $data);
|
||||
return view('taxes/tax_jurisdiction_form', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -367,9 +368,9 @@ class Taxes extends Secure_Controller
|
||||
|
||||
/**
|
||||
* @param int $tax_rate_id
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postSave(int $tax_rate_id = NEW_ENTRY): void
|
||||
public function postSave(int $tax_rate_id = NEW_ENTRY): ResponseInterface
|
||||
{
|
||||
$tax_category_id = $this->request->getPost('rate_tax_category_id', FILTER_SANITIZE_NUMBER_INT);
|
||||
$tax_rate = parse_tax($this->request->getPost('tax_rate'));
|
||||
@@ -388,50 +389,50 @@ class Taxes extends Secure_Controller
|
||||
|
||||
if ($this->tax->save_value($tax_rate_data, $tax_rate_id)) {
|
||||
if ($tax_rate_id == NEW_ENTRY) { // TODO: this needs to be replaced with ternary notation
|
||||
echo json_encode(['success' => true, 'message' => lang('Taxes.tax_rate_successfully_added')]);
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Taxes.tax_rate_successfully_added')]);
|
||||
} else { // Existing tax_code
|
||||
echo json_encode(['success' => true, 'message' => lang('Taxes.tax_rate_successful_updated')]);
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Taxes.tax_rate_successful_updated')]);
|
||||
}
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => lang('Taxes.tax_rate_error_adding_updating')]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Taxes.tax_rate_error_adding_updating')]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function postDelete(): void
|
||||
public function postDelete(): ResponseInterface
|
||||
{
|
||||
$tax_codes_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
|
||||
|
||||
if ($this->tax->delete_list($tax_codes_to_delete)) { // TODO: this needs to be replaced with ternary notation
|
||||
echo json_encode(['success' => true, 'message' => lang('Taxes.tax_code_successful_deleted')]);
|
||||
return $this->response->setJSON(['success' => true, 'message' => lang('Taxes.tax_code_successful_deleted')]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => lang('Taxes.tax_code_cannot_be_deleted')]);
|
||||
return $this->response->setJSON(['success' => false, 'message' => lang('Taxes.tax_code_cannot_be_deleted')]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get search suggestions for tax codes. Used in app/Views/customers/form.php
|
||||
*
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getSuggestTaxCodes(): void
|
||||
public function getSuggestTaxCodes(): ResponseInterface
|
||||
{
|
||||
$search = $this->request->getPostGet('term');
|
||||
$suggestions = $this->tax_code->get_tax_codes_search_suggestions($search);
|
||||
|
||||
echo json_encode($suggestions);
|
||||
return $this->response->setJSON($suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves Tax Codes. Used in app/Views/taxes/tax_codes.php
|
||||
*
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSave_tax_codes(): void
|
||||
public function postSave_tax_codes(): ResponseInterface
|
||||
{
|
||||
$tax_code_id = $this->request->getPost('tax_code_id', FILTER_SANITIZE_NUMBER_INT);
|
||||
$tax_code = $this->request->getPost('tax_code', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
@@ -452,7 +453,7 @@ class Taxes extends Secure_Controller
|
||||
|
||||
$success = $this->tax_code->save_tax_codes($array_save);
|
||||
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => $success,
|
||||
'message' => lang('Taxes.tax_codes_saved_' . ($success ? '' : 'un') . 'successfully')
|
||||
]);
|
||||
@@ -461,10 +462,10 @@ class Taxes extends Secure_Controller
|
||||
/**
|
||||
* Saves given tax jurisdiction. Used in app/Views/taxes/tax_jurisdictions.php.
|
||||
*
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSave_tax_jurisdictions(): void
|
||||
public function postSave_tax_jurisdictions(): ResponseInterface
|
||||
{
|
||||
$jurisdiction_id = $this->request->getPost('jurisdiction_id', FILTER_SANITIZE_NUMBER_INT);
|
||||
$jurisdiction_name = $this->request->getPost('jurisdiction_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
@@ -489,11 +490,10 @@ class Taxes extends Secure_Controller
|
||||
];
|
||||
|
||||
if (in_array($tax_group[$key], $unique_tax_groups)) { // TODO: This can be replaced with `in_array($tax_group[$key], $unique_tax_groups)`
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => lang('Taxes.tax_group_not_unique', [$tax_group[$key]])
|
||||
]);
|
||||
return;
|
||||
} else {
|
||||
$unique_tax_groups[] = $tax_group[$key];
|
||||
}
|
||||
@@ -501,7 +501,7 @@ class Taxes extends Secure_Controller
|
||||
|
||||
$success = $this->tax_jurisdiction->save_jurisdictions($array_save);
|
||||
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => $success,
|
||||
'message' => lang('Taxes.tax_jurisdictions_saved_' . ($success ? '' : 'un') . 'successfully')
|
||||
]);
|
||||
@@ -510,10 +510,10 @@ class Taxes extends Secure_Controller
|
||||
/**
|
||||
* Saves tax categories. Used in app/Views/taxes/tax_categories.php
|
||||
*
|
||||
* @return void
|
||||
* @return ResponseInterface
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function postSave_tax_categories(): void
|
||||
public function postSave_tax_categories(): ResponseInterface
|
||||
{
|
||||
$tax_category_id = $this->request->getPost('tax_category_id', FILTER_SANITIZE_NUMBER_INT);
|
||||
$tax_category = $this->request->getPost('tax_category', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
@@ -531,7 +531,7 @@ class Taxes extends Secure_Controller
|
||||
|
||||
$success = $this->tax_category->save_categories($array_save);
|
||||
|
||||
echo json_encode([
|
||||
return $this->response->setJSON([
|
||||
'success' => $success,
|
||||
'message' => lang('Taxes.tax_categories_saved_' . ($success ? '' : 'un') . 'successfully')
|
||||
]);
|
||||
@@ -540,36 +540,36 @@ class Taxes extends Secure_Controller
|
||||
/**
|
||||
* Gets tax codes partial view. Used in app/Views/taxes/tax_codes.php.
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getAjax_tax_codes(): void
|
||||
public function getAjax_tax_codes(): string
|
||||
{
|
||||
$tax_codes = $this->tax_code->get_all()->getResultArray();
|
||||
|
||||
echo view('partial/tax_codes', ['tax_codes' => $tax_codes]);
|
||||
return view('partial/tax_codes', ['tax_codes' => $tax_codes]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current tax categories. Used in app/Views/taxes/tax_categories.php
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getAjax_tax_categories(): void
|
||||
public function getAjax_tax_categories(): string
|
||||
{
|
||||
$tax_categories = $this->tax_category->get_all()->getResultArray();
|
||||
|
||||
echo view('partial/tax_categories', ['tax_categories' => $tax_categories]);
|
||||
return view('partial/tax_categories', ['tax_categories' => $tax_categories]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tax jurisdiction partial view. Used in app/Views/taxes/tax_jurisdictions.php.
|
||||
*
|
||||
* @return void
|
||||
* @return string
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getAjax_tax_jurisdictions(): void
|
||||
public function getAjax_tax_jurisdictions(): string
|
||||
{
|
||||
$tax_jurisdictions = $this->tax_jurisdiction->get_all()->getResultArray();
|
||||
|
||||
@@ -581,7 +581,7 @@ class Taxes extends Secure_Controller
|
||||
|
||||
$tax_types = $this->tax_lib->get_tax_types();
|
||||
|
||||
echo view('partial/tax_jurisdictions', [
|
||||
return view('partial/tax_jurisdictions', [
|
||||
'tax_jurisdictions' => $tax_jurisdictions,
|
||||
'tax_types' => $tax_types,
|
||||
'default_tax_type' => $default_tax_type
|
||||
|
||||
60
app/Database/Migrations/20170501000000_initial_schema.php
Normal file
60
app/Database/Migrations/20170501000000_initial_schema.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class Migration_Initial_Schema extends Migration
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a migration step.
|
||||
* Only runs on fresh installs - skips if database already has tables.
|
||||
*
|
||||
* For testing: CI4's DatabaseTestTrait with $refresh=true handles table
|
||||
* cleanup/creation automatically. This migration only loads initial schema
|
||||
* on fresh databases where no application tables exist.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Check if core application tables exist (existing install)
|
||||
// Note: migrations table may exist even on fresh DB due to migration tracking
|
||||
$tables = $this->db->listTables();
|
||||
|
||||
// Check for a core application table, not just migrations table
|
||||
foreach ($tables as $table) {
|
||||
// Strip prefix if present for comparison
|
||||
$tableName = str_replace($this->db->getPrefix(), '', $table);
|
||||
if (in_array($tableName, ['app_config', 'items', 'employees', 'people'])) {
|
||||
// Database already populated - skip initial schema
|
||||
// This is an existing installation upgrading from older version
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fresh install - load initial schema
|
||||
helper('migration');
|
||||
execute_script(APPPATH . 'Database/Migrations/sqlscripts/initial_schema.sql');
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert a migration step.
|
||||
* Cannot revert initial schema - would lose all data.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// Cannot safely revert initial schema
|
||||
// Would require dropping all tables which would lose all data
|
||||
$this->db->query('SET FOREIGN_KEY_CHECKS = 0');
|
||||
|
||||
foreach ($this->db->listTables() as $table) {
|
||||
$this->db->query('DROP TABLE IF EXISTS `' . $table . '`');
|
||||
}
|
||||
|
||||
$this->db->query('SET FOREIGN_KEY_CHECKS = 1');
|
||||
}
|
||||
}
|
||||
@@ -267,6 +267,8 @@ class Migration_Sales_Tax_Data extends Migration
|
||||
*/
|
||||
public function round_number(int $rounding_mode, string $amount, int $decimals): float
|
||||
{
|
||||
$amount = (float)$amount;
|
||||
|
||||
if ($rounding_mode == Migration_Sales_Tax_Data::ROUND_UP) {
|
||||
$fig = pow(10, $decimals);
|
||||
$rounded_total = (ceil($fig * $amount) + ceil($fig * $amount - ceil($fig * $amount))) / $fig;
|
||||
@@ -376,7 +378,7 @@ class Migration_Sales_Tax_Data extends Migration
|
||||
$decimals = totals_decimals();
|
||||
|
||||
foreach ($sales_taxes as $row_number => $sales_tax) {
|
||||
$sale_tax_amount = $sales_tax['sale_tax_amount'];
|
||||
$sale_tax_amount = (float)$sales_tax['sale_tax_amount'];
|
||||
$rounding_code = $sales_tax['rounding_code'];
|
||||
$rounded_sale_tax_amount = $sale_tax_amount;
|
||||
|
||||
|
||||
@@ -21,6 +21,6 @@ class Migration_receipttaxindicator extends Migration
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$this->db->query('DELETE FROM ' . $this->db->prefixTable('app_config') . ' WHERE key = \'receipt_show_tax_ind\'');
|
||||
$this->db->query('DELETE FROM ' . $this->db->prefixTable('app_config') . ' WHERE `key` = \'receipt_show_tax_ind\'');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,6 +243,8 @@ class Migration_TaxAmount extends Migration
|
||||
*/
|
||||
public function round_number(int $rounding_mode, string $amount, int $decimals): float // TODO: is this currency safe?
|
||||
{ // TODO: This needs to be converted to a switch
|
||||
$amount = (float)$amount;
|
||||
|
||||
if ($rounding_mode == Migration_TaxAmount::ROUND_UP) { // TODO: === ?
|
||||
$fig = pow(10, $decimals);
|
||||
$rounded_total = (ceil($fig * $amount) + ceil($fig * $amount - ceil($fig * $amount))) / $fig;
|
||||
@@ -354,7 +356,7 @@ class Migration_TaxAmount extends Migration
|
||||
$decimals = totals_decimals();
|
||||
|
||||
foreach ($sales_taxes as $row_number => $sales_tax) {
|
||||
$sale_tax_amount = $sales_tax['sale_tax_amount'];
|
||||
$sale_tax_amount = (float)$sales_tax['sale_tax_amount'];
|
||||
$rounding_code = $sales_tax['rounding_code'];
|
||||
$rounded_sale_tax_amount = $sale_tax_amount;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class Migration_database_optimizations extends Migration
|
||||
|
||||
$attribute = model(Attribute::class);
|
||||
|
||||
$attribute->delete_orphaned_values();
|
||||
$attribute->deleteOrphanedValues();
|
||||
|
||||
$this->migrate_duplicate_attribute_values(DECIMAL);
|
||||
$this->migrate_duplicate_attribute_values(DATE);
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
/**
|
||||
* Migration to sanitize existing image filenames by replacing spaces with underscores
|
||||
* This fixes issue #4372 where thumbnails failed to load for images with spaces in filenames
|
||||
*/
|
||||
class FixImageFilenameSpaces extends Migration
|
||||
{
|
||||
/**
|
||||
* Perform a migration.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$db = \Config\Database::connect();
|
||||
$builder = $db->table('ospos_items');
|
||||
|
||||
// Get all items with pic_filename containing spaces
|
||||
$query = $builder->like('pic_filename', ' ', 'both')->get();
|
||||
$items = $query->getResult();
|
||||
|
||||
foreach ($items as $item) {
|
||||
$old_filename = $item->pic_filename;
|
||||
$ext = pathinfo($old_filename, PATHINFO_EXTENSION);
|
||||
$base_name = pathinfo($old_filename, PATHINFO_FILENAME);
|
||||
|
||||
// Sanitize the filename by replacing spaces and special characters
|
||||
$sanitized_name = preg_replace('/[^a-zA-Z0-9_\-\.]/', '_', $base_name);
|
||||
$new_filename = $sanitized_name . '.' . $ext;
|
||||
|
||||
// Rename the file on the filesystem
|
||||
$old_path = FCPATH . 'uploads/item_pics/' . $old_filename;
|
||||
$new_path = FCPATH . 'uploads/item_pics/' . $new_filename;
|
||||
|
||||
if (file_exists($old_path)) {
|
||||
// Rename the original file
|
||||
if (rename($old_path, $new_path)) {
|
||||
// Check if thumbnail exists and rename it too
|
||||
$old_thumb = FCPATH . 'uploads/item_pics/' . $base_name . '_thumb.' . $ext;
|
||||
$new_thumb = FCPATH . 'uploads/item_pics/' . $sanitized_name . '_thumb.' . $ext;
|
||||
if (file_exists($old_thumb)) {
|
||||
rename($old_thumb, $new_thumb);
|
||||
}
|
||||
|
||||
// Update database record
|
||||
$builder->where('item_id', $item->item_id)
|
||||
->update(['pic_filename' => $new_filename]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert a migration.
|
||||
* Note: This migration does not support rollback as the original filenames are lost
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// This migration cannot be safely reversed as the original filenames are lost
|
||||
// after sanitization.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Config\Database;
|
||||
|
||||
class AddPersonToAttributeLinks extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
helper('migration');
|
||||
|
||||
// First, modify the generated unique column to include person_id
|
||||
// Drop the existing unique constraint
|
||||
$this->db->query('ALTER TABLE `ospos_attribute_links` DROP INDEX `attribute_links_uq3`');
|
||||
$this->db->query('ALTER TABLE `ospos_attribute_links` DROP COLUMN `generated_unique_column`');
|
||||
|
||||
// Add person_id column
|
||||
$this->db->query('ALTER TABLE `ospos_attribute_links` ADD COLUMN `person_id` INT(10) NULL AFTER `receiving_id`');
|
||||
|
||||
// Add index for person_id
|
||||
$this->db->query('ALTER TABLE `ospos_attribute_links` ADD KEY `person_id` (`person_id`)');
|
||||
|
||||
// Add foreign key constraint for person_id
|
||||
$this->db->query('ALTER TABLE `ospos_attribute_links` ADD CONSTRAINT `ospos_attribute_links_ibfk_6` FOREIGN KEY (`person_id`) REFERENCES `ospos_people` (`person_id`) ON DELETE CASCADE');
|
||||
|
||||
// Recreate the generated unique column with person_id support
|
||||
// This ensures uniqueness for both item attributes and person attributes
|
||||
$this->db->query("ALTER TABLE `ospos_attribute_links`
|
||||
ADD COLUMN `generated_unique_column` VARCHAR(255) GENERATED ALWAYS AS (
|
||||
CASE
|
||||
WHEN `sale_id` IS NULL AND `receiving_id` IS NULL AND `item_id` IS NOT NULL THEN CONCAT('item-', `definition_id`, '-', `item_id`)
|
||||
WHEN `sale_id` IS NULL AND `receiving_id` IS NULL AND `item_id` IS NULL AND `person_id` IS NOT NULL THEN CONCAT('person-', `definition_id`, '-', `person_id`)
|
||||
ELSE NULL
|
||||
END
|
||||
) STORED");
|
||||
|
||||
// Re-add unique constraint
|
||||
$this->db->query('ALTER TABLE `ospos_attribute_links` ADD UNIQUE INDEX `attribute_links_uq3` (`generated_unique_column`)');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// Drop person_id related constraints and column
|
||||
$this->db->query('ALTER TABLE `ospos_attribute_links` DROP INDEX `attribute_links_uq3`');
|
||||
$this->db->query('ALTER TABLE `ospos_attribute_links` DROP COLUMN `generated_unique_column`');
|
||||
$this->db->query('ALTER TABLE `ospos_attribute_links` DROP FOREIGN KEY `ospos_attribute_links_ibfk_6`');
|
||||
$this->db->query('ALTER TABLE `ospos_attribute_links` DROP COLUMN `person_id`');
|
||||
|
||||
// Restore original generated column
|
||||
$this->db->query("ALTER TABLE `ospos_attribute_links`
|
||||
ADD COLUMN `generated_unique_column` VARCHAR(255) GENERATED ALWAYS AS (
|
||||
CASE
|
||||
WHEN `sale_id` IS NULL AND `receiving_id` IS NULL AND `item_id` IS NOT NULL THEN CONCAT(`definition_id`, '-', `item_id`)
|
||||
ELSE NULL
|
||||
END
|
||||
) STORED");
|
||||
$this->db->query('ALTER TABLE `ospos_attribute_links` ADD UNIQUE INDEX `attribute_links_uq3` (`generated_unique_column`)');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddPersonAttributeFlag extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->db->query('ALTER TABLE `ospos_attribute_definitions` ADD COLUMN `person_attribute` TINYINT(1) DEFAULT 0 AFTER `definition_flags`');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->db->query('ALTER TABLE `ospos_attribute_definitions` DROP COLUMN `person_attribute`');
|
||||
}
|
||||
}
|
||||
@@ -730,3 +730,148 @@ CREATE TABLE `ospos_suppliers` (
|
||||
--
|
||||
-- Dumping data for table `ospos_suppliers`
|
||||
--
|
||||
--
|
||||
-- Constraints for dumped tables
|
||||
--
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_customers`
|
||||
--
|
||||
ALTER TABLE `ospos_customers`
|
||||
ADD CONSTRAINT `ospos_customers_ibfk_1` FOREIGN KEY (`person_id`) REFERENCES `ospos_people` (`person_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_employees`
|
||||
--
|
||||
ALTER TABLE `ospos_employees`
|
||||
ADD CONSTRAINT `ospos_employees_ibfk_1` FOREIGN KEY (`person_id`) REFERENCES `ospos_people` (`person_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_inventory`
|
||||
--
|
||||
ALTER TABLE `ospos_inventory`
|
||||
ADD CONSTRAINT `ospos_inventory_ibfk_1` FOREIGN KEY (`trans_items`) REFERENCES `ospos_items` (`item_id`),
|
||||
ADD CONSTRAINT `ospos_inventory_ibfk_2` FOREIGN KEY (`trans_user`) REFERENCES `ospos_employees` (`person_id`),
|
||||
ADD CONSTRAINT `ospos_inventory_ibfk_3` FOREIGN KEY (`trans_location`) REFERENCES `ospos_stock_locations` (`location_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_items`
|
||||
--
|
||||
ALTER TABLE `ospos_items`
|
||||
ADD CONSTRAINT `ospos_items_ibfk_1` FOREIGN KEY (`supplier_id`) REFERENCES `ospos_suppliers` (`person_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_items_taxes`
|
||||
--
|
||||
ALTER TABLE `ospos_items_taxes`
|
||||
ADD CONSTRAINT `ospos_items_taxes_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `ospos_items` (`item_id`) ON DELETE CASCADE;
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_item_kit_items`
|
||||
--
|
||||
ALTER TABLE `ospos_item_kit_items`
|
||||
ADD CONSTRAINT `ospos_item_kit_items_ibfk_1` FOREIGN KEY (`item_kit_id`) REFERENCES `ospos_item_kits` (`item_kit_id`) ON DELETE CASCADE,
|
||||
ADD CONSTRAINT `ospos_item_kit_items_ibfk_2` FOREIGN KEY (`item_id`) REFERENCES `ospos_items` (`item_id`) ON DELETE CASCADE;
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_permissions`
|
||||
--
|
||||
ALTER TABLE `ospos_permissions`
|
||||
ADD CONSTRAINT `ospos_permissions_ibfk_1` FOREIGN KEY (`module_id`) REFERENCES `ospos_modules` (`module_id`) ON DELETE CASCADE,
|
||||
ADD CONSTRAINT `ospos_permissions_ibfk_2` FOREIGN KEY (`location_id`) REFERENCES `ospos_stock_locations` (`location_id`) ON DELETE CASCADE;
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_grants`
|
||||
--
|
||||
ALTER TABLE `ospos_grants`
|
||||
ADD CONSTRAINT `ospos_grants_ibfk_1` foreign key (`permission_id`) references `ospos_permissions` (`permission_id`) ON DELETE CASCADE,
|
||||
ADD CONSTRAINT `ospos_grants_ibfk_2` foreign key (`person_id`) references `ospos_employees` (`person_id`) ON DELETE CASCADE;
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_receivings`
|
||||
--
|
||||
ALTER TABLE `ospos_receivings`
|
||||
ADD CONSTRAINT `ospos_receivings_ibfk_1` FOREIGN KEY (`employee_id`) REFERENCES `ospos_employees` (`person_id`),
|
||||
ADD CONSTRAINT `ospos_receivings_ibfk_2` FOREIGN KEY (`supplier_id`) REFERENCES `ospos_suppliers` (`person_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_receivings_items`
|
||||
--
|
||||
ALTER TABLE `ospos_receivings_items`
|
||||
ADD CONSTRAINT `ospos_receivings_items_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `ospos_items` (`item_id`),
|
||||
ADD CONSTRAINT `ospos_receivings_items_ibfk_2` FOREIGN KEY (`receiving_id`) REFERENCES `ospos_receivings` (`receiving_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_sales`
|
||||
--
|
||||
ALTER TABLE `ospos_sales`
|
||||
ADD CONSTRAINT `ospos_sales_ibfk_1` FOREIGN KEY (`employee_id`) REFERENCES `ospos_employees` (`person_id`),
|
||||
ADD CONSTRAINT `ospos_sales_ibfk_2` FOREIGN KEY (`customer_id`) REFERENCES `ospos_customers` (`person_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_sales_items`
|
||||
--
|
||||
ALTER TABLE `ospos_sales_items`
|
||||
ADD CONSTRAINT `ospos_sales_items_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `ospos_items` (`item_id`),
|
||||
ADD CONSTRAINT `ospos_sales_items_ibfk_2` FOREIGN KEY (`sale_id`) REFERENCES `ospos_sales` (`sale_id`),
|
||||
ADD CONSTRAINT `ospos_sales_items_ibfk_3` FOREIGN KEY (`item_location`) REFERENCES `ospos_stock_locations` (`location_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_sales_items_taxes`
|
||||
--
|
||||
ALTER TABLE `ospos_sales_items_taxes`
|
||||
ADD CONSTRAINT `ospos_sales_items_taxes_ibfk_1` FOREIGN KEY (`sale_id`,`item_id`,`line`) REFERENCES `ospos_sales_items` (`sale_id`,`item_id`,`line`),
|
||||
ADD CONSTRAINT `ospos_sales_items_taxes_ibfk_2` FOREIGN KEY (`item_id`) REFERENCES `ospos_items` (`item_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_sales_payments`
|
||||
--
|
||||
ALTER TABLE `ospos_sales_payments`
|
||||
ADD CONSTRAINT `ospos_sales_payments_ibfk_1` FOREIGN KEY (`sale_id`) REFERENCES `ospos_sales` (`sale_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_sales_suspended`
|
||||
--
|
||||
ALTER TABLE `ospos_sales_suspended`
|
||||
ADD CONSTRAINT `ospos_sales_suspended_ibfk_1` FOREIGN KEY (`employee_id`) REFERENCES `ospos_employees` (`person_id`),
|
||||
ADD CONSTRAINT `ospos_sales_suspended_ibfk_2` FOREIGN KEY (`customer_id`) REFERENCES `ospos_customers` (`person_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_sales_suspended_items`
|
||||
--
|
||||
ALTER TABLE `ospos_sales_suspended_items`
|
||||
ADD CONSTRAINT `ospos_sales_suspended_items_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `ospos_items` (`item_id`),
|
||||
ADD CONSTRAINT `ospos_sales_suspended_items_ibfk_2` FOREIGN KEY (`sale_id`) REFERENCES `ospos_sales_suspended` (`sale_id`),
|
||||
ADD CONSTRAINT `ospos_sales_suspended_items_ibfk_3` FOREIGN KEY (`item_location`) REFERENCES `ospos_stock_locations` (`location_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_sales_suspended_items_taxes`
|
||||
--
|
||||
ALTER TABLE `ospos_sales_suspended_items_taxes`
|
||||
ADD CONSTRAINT `ospos_sales_suspended_items_taxes_ibfk_1` FOREIGN KEY (`sale_id`,`item_id`,`line`) REFERENCES `ospos_sales_suspended_items` (`sale_id`,`item_id`,`line`),
|
||||
ADD CONSTRAINT `ospos_sales_suspended_items_taxes_ibfk_2` FOREIGN KEY (`item_id`) REFERENCES `ospos_items` (`item_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_sales_suspended_payments`
|
||||
--
|
||||
ALTER TABLE `ospos_sales_suspended_payments`
|
||||
ADD CONSTRAINT `ospos_sales_suspended_payments_ibfk_1` FOREIGN KEY (`sale_id`) REFERENCES `ospos_sales_suspended` (`sale_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_item_quantities`
|
||||
--
|
||||
ALTER TABLE `ospos_item_quantities`
|
||||
ADD CONSTRAINT `ospos_item_quantities_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `ospos_items` (`item_id`),
|
||||
ADD CONSTRAINT `ospos_item_quantities_ibfk_2` FOREIGN KEY (`location_id`) REFERENCES `ospos_stock_locations` (`location_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_suppliers`
|
||||
--
|
||||
ALTER TABLE `ospos_suppliers`
|
||||
ADD CONSTRAINT `ospos_suppliers_ibfk_1` FOREIGN KEY (`person_id`) REFERENCES `ospos_people` (`person_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_giftcards`
|
||||
--
|
||||
ALTER TABLE `ospos_giftcards`
|
||||
ADD CONSTRAINT `ospos_giftcards_ibfk_1` FOREIGN KEY (`person_id`) REFERENCES `ospos_people` (`person_id`);
|
||||
37
app/Database/Seeds/TestDatabaseBootstrapSeeder.php
Normal file
37
app/Database/Seeds/TestDatabaseBootstrapSeeder.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
use Config\Database;
|
||||
|
||||
class TestDatabaseBootstrapSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
if (ENVIRONMENT !== 'testing') {
|
||||
throw new \RuntimeException('TestDatabaseBootstrapSeeder can only run in the testing environment.');
|
||||
}
|
||||
|
||||
$config = config('Database');
|
||||
$group = $config->tests;
|
||||
$dbName = $group['database'];
|
||||
|
||||
if ($dbName === '' || !str_contains(strtolower($dbName), 'test')) {
|
||||
throw new \RuntimeException("Refusing to reset non-test database: {$dbName}");
|
||||
}
|
||||
|
||||
$serverConn = Database::connect([
|
||||
'hostname' => $group['hostname'],
|
||||
'username' => $group['username'],
|
||||
'password' => $group['password'],
|
||||
'DBDriver' => $group['DBDriver'],
|
||||
'database' => null,
|
||||
'charset' => $group['charset'] ?? 'utf8mb4',
|
||||
'DBCollat' => $group['DBCollat'] ?? 'utf8mb4_general_ci',
|
||||
], false);
|
||||
|
||||
$serverConn->query("DROP DATABASE IF EXISTS `{$dbName}`");
|
||||
$serverConn->query("CREATE DATABASE IF NOT EXISTS `{$dbName}`");
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
--
|
||||
-- Constraints for dumped tables
|
||||
--
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_customers`
|
||||
--
|
||||
ALTER TABLE `ospos_customers`
|
||||
ADD CONSTRAINT `ospos_customers_ibfk_1` FOREIGN KEY (`person_id`) REFERENCES `ospos_people` (`person_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_employees`
|
||||
--
|
||||
ALTER TABLE `ospos_employees`
|
||||
ADD CONSTRAINT `ospos_employees_ibfk_1` FOREIGN KEY (`person_id`) REFERENCES `ospos_people` (`person_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_inventory`
|
||||
--
|
||||
ALTER TABLE `ospos_inventory`
|
||||
ADD CONSTRAINT `ospos_inventory_ibfk_1` FOREIGN KEY (`trans_items`) REFERENCES `ospos_items` (`item_id`),
|
||||
ADD CONSTRAINT `ospos_inventory_ibfk_2` FOREIGN KEY (`trans_user`) REFERENCES `ospos_employees` (`person_id`),
|
||||
ADD CONSTRAINT `ospos_inventory_ibfk_3` FOREIGN KEY (`trans_location`) REFERENCES `ospos_stock_locations` (`location_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_items`
|
||||
--
|
||||
ALTER TABLE `ospos_items`
|
||||
ADD CONSTRAINT `ospos_items_ibfk_1` FOREIGN KEY (`supplier_id`) REFERENCES `ospos_suppliers` (`person_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_items_taxes`
|
||||
--
|
||||
ALTER TABLE `ospos_items_taxes`
|
||||
ADD CONSTRAINT `ospos_items_taxes_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `ospos_items` (`item_id`) ON DELETE CASCADE;
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_item_kit_items`
|
||||
--
|
||||
ALTER TABLE `ospos_item_kit_items`
|
||||
ADD CONSTRAINT `ospos_item_kit_items_ibfk_1` FOREIGN KEY (`item_kit_id`) REFERENCES `ospos_item_kits` (`item_kit_id`) ON DELETE CASCADE,
|
||||
ADD CONSTRAINT `ospos_item_kit_items_ibfk_2` FOREIGN KEY (`item_id`) REFERENCES `ospos_items` (`item_id`) ON DELETE CASCADE;
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_permissions`
|
||||
--
|
||||
ALTER TABLE `ospos_permissions`
|
||||
ADD CONSTRAINT `ospos_permissions_ibfk_1` FOREIGN KEY (`module_id`) REFERENCES `ospos_modules` (`module_id`) ON DELETE CASCADE,
|
||||
ADD CONSTRAINT `ospos_permissions_ibfk_2` FOREIGN KEY (`location_id`) REFERENCES `ospos_stock_locations` (`location_id`) ON DELETE CASCADE;
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_grants`
|
||||
--
|
||||
ALTER TABLE `ospos_grants`
|
||||
ADD CONSTRAINT `ospos_grants_ibfk_1` foreign key (`permission_id`) references `ospos_permissions` (`permission_id`) ON DELETE CASCADE,
|
||||
ADD CONSTRAINT `ospos_grants_ibfk_2` foreign key (`person_id`) references `ospos_employees` (`person_id`) ON DELETE CASCADE;
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_receivings`
|
||||
--
|
||||
ALTER TABLE `ospos_receivings`
|
||||
ADD CONSTRAINT `ospos_receivings_ibfk_1` FOREIGN KEY (`employee_id`) REFERENCES `ospos_employees` (`person_id`),
|
||||
ADD CONSTRAINT `ospos_receivings_ibfk_2` FOREIGN KEY (`supplier_id`) REFERENCES `ospos_suppliers` (`person_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_receivings_items`
|
||||
--
|
||||
ALTER TABLE `ospos_receivings_items`
|
||||
ADD CONSTRAINT `ospos_receivings_items_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `ospos_items` (`item_id`),
|
||||
ADD CONSTRAINT `ospos_receivings_items_ibfk_2` FOREIGN KEY (`receiving_id`) REFERENCES `ospos_receivings` (`receiving_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_sales`
|
||||
--
|
||||
ALTER TABLE `ospos_sales`
|
||||
ADD CONSTRAINT `ospos_sales_ibfk_1` FOREIGN KEY (`employee_id`) REFERENCES `ospos_employees` (`person_id`),
|
||||
ADD CONSTRAINT `ospos_sales_ibfk_2` FOREIGN KEY (`customer_id`) REFERENCES `ospos_customers` (`person_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_sales_items`
|
||||
--
|
||||
ALTER TABLE `ospos_sales_items`
|
||||
ADD CONSTRAINT `ospos_sales_items_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `ospos_items` (`item_id`),
|
||||
ADD CONSTRAINT `ospos_sales_items_ibfk_2` FOREIGN KEY (`sale_id`) REFERENCES `ospos_sales` (`sale_id`),
|
||||
ADD CONSTRAINT `ospos_sales_items_ibfk_3` FOREIGN KEY (`item_location`) REFERENCES `ospos_stock_locations` (`location_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_sales_items_taxes`
|
||||
--
|
||||
ALTER TABLE `ospos_sales_items_taxes`
|
||||
ADD CONSTRAINT `ospos_sales_items_taxes_ibfk_1` FOREIGN KEY (`sale_id`,`item_id`,`line`) REFERENCES `ospos_sales_items` (`sale_id`,`item_id`,`line`),
|
||||
ADD CONSTRAINT `ospos_sales_items_taxes_ibfk_2` FOREIGN KEY (`item_id`) REFERENCES `ospos_items` (`item_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_sales_payments`
|
||||
--
|
||||
ALTER TABLE `ospos_sales_payments`
|
||||
ADD CONSTRAINT `ospos_sales_payments_ibfk_1` FOREIGN KEY (`sale_id`) REFERENCES `ospos_sales` (`sale_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_sales_suspended`
|
||||
--
|
||||
ALTER TABLE `ospos_sales_suspended`
|
||||
ADD CONSTRAINT `ospos_sales_suspended_ibfk_1` FOREIGN KEY (`employee_id`) REFERENCES `ospos_employees` (`person_id`),
|
||||
ADD CONSTRAINT `ospos_sales_suspended_ibfk_2` FOREIGN KEY (`customer_id`) REFERENCES `ospos_customers` (`person_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_sales_suspended_items`
|
||||
--
|
||||
ALTER TABLE `ospos_sales_suspended_items`
|
||||
ADD CONSTRAINT `ospos_sales_suspended_items_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `ospos_items` (`item_id`),
|
||||
ADD CONSTRAINT `ospos_sales_suspended_items_ibfk_2` FOREIGN KEY (`sale_id`) REFERENCES `ospos_sales_suspended` (`sale_id`),
|
||||
ADD CONSTRAINT `ospos_sales_suspended_items_ibfk_3` FOREIGN KEY (`item_location`) REFERENCES `ospos_stock_locations` (`location_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_sales_suspended_items_taxes`
|
||||
--
|
||||
ALTER TABLE `ospos_sales_suspended_items_taxes`
|
||||
ADD CONSTRAINT `ospos_sales_suspended_items_taxes_ibfk_1` FOREIGN KEY (`sale_id`,`item_id`,`line`) REFERENCES `ospos_sales_suspended_items` (`sale_id`,`item_id`,`line`),
|
||||
ADD CONSTRAINT `ospos_sales_suspended_items_taxes_ibfk_2` FOREIGN KEY (`item_id`) REFERENCES `ospos_items` (`item_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_sales_suspended_payments`
|
||||
--
|
||||
ALTER TABLE `ospos_sales_suspended_payments`
|
||||
ADD CONSTRAINT `ospos_sales_suspended_payments_ibfk_1` FOREIGN KEY (`sale_id`) REFERENCES `ospos_sales_suspended` (`sale_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_item_quantities`
|
||||
--
|
||||
ALTER TABLE `ospos_item_quantities`
|
||||
ADD CONSTRAINT `ospos_item_quantities_ibfk_1` FOREIGN KEY (`item_id`) REFERENCES `ospos_items` (`item_id`),
|
||||
ADD CONSTRAINT `ospos_item_quantities_ibfk_2` FOREIGN KEY (`location_id`) REFERENCES `ospos_stock_locations` (`location_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_suppliers`
|
||||
--
|
||||
ALTER TABLE `ospos_suppliers`
|
||||
ADD CONSTRAINT `ospos_suppliers_ibfk_1` FOREIGN KEY (`person_id`) REFERENCES `ospos_people` (`person_id`);
|
||||
|
||||
--
|
||||
-- Constraints for table `ospos_giftcards`
|
||||
--
|
||||
ALTER TABLE `ospos_giftcards`
|
||||
ADD CONSTRAINT `ospos_giftcards_ibfk_1` FOREIGN KEY (`person_id`) REFERENCES `ospos_people` (`person_id`);
|
||||
@@ -1,5 +1,5 @@
|
||||
[mysqld]
|
||||
sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
|
||||
sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"
|
||||
|
||||
key_buffer = 16M
|
||||
max_allowed_packet = 1M
|
||||
|
||||
@@ -36,21 +36,26 @@ class Db_log
|
||||
private function generate_message(): string
|
||||
{
|
||||
$db = Database::connect();
|
||||
$last_query = $db->getLastQuery();
|
||||
$affected_rows = $db->affectedRows();
|
||||
$execution_time = $this->convert_time($last_query->getDuration());
|
||||
$lastQuery = $db->getLastQuery();
|
||||
|
||||
if ($lastQuery === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$affectedRows = $db->affectedRows();
|
||||
$executionTime = $this->convert_time($lastQuery->getDuration());
|
||||
|
||||
$message = '*** Query: ' . date('Y-m-d H:i:s T') . ' *******************'
|
||||
. "\n" . $last_query->getQuery()
|
||||
. "\n Affected rows: $affected_rows"
|
||||
. "\n Execution Time: " . $execution_time['time'] . ' ' . $execution_time['unit'];
|
||||
. "\n" . $lastQuery->getQuery()
|
||||
. "\n Affected rows: $affectedRows"
|
||||
. "\n Execution Time: " . $executionTime['time'] . ' ' . $executionTime['unit'];
|
||||
|
||||
$long_query = ($execution_time['unit'] === 's') && ($execution_time['time'] > 0.5);
|
||||
if ($long_query) {
|
||||
$longQuery = ($executionTime['unit'] === 's') && ($executionTime['time'] > 0.5);
|
||||
if ($longQuery) {
|
||||
$message .= ' [LONG RUNNING QUERY]';
|
||||
}
|
||||
|
||||
return $this->config->db_log_only_long && !$long_query ? '' : $message;
|
||||
return $this->config->db_log_only_long && !$longQuery ? '' : $message;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace App\Events;
|
||||
|
||||
use App\Libraries\MY_Migration;
|
||||
use App\Models\Appconfig;
|
||||
use CodeIgniter\Session\Handlers\DatabaseHandler;
|
||||
use CodeIgniter\Session\Handlers\FileHandler;
|
||||
use CodeIgniter\Session\Session;
|
||||
use Config\OSPOS;
|
||||
use Config\Services;
|
||||
@@ -19,38 +21,47 @@ class Load_config
|
||||
{
|
||||
public Session $session;
|
||||
|
||||
/**
|
||||
* Loads configuration from database into App CI config and then applies those settings
|
||||
*/
|
||||
public function load_config(): void
|
||||
{
|
||||
// Migrations
|
||||
$migration_config = config('Migrations');
|
||||
$migration = new MY_Migration($migration_config);
|
||||
|
||||
$this->session = session();
|
||||
|
||||
// Database Configuration
|
||||
$config = config(OSPOS::class);
|
||||
|
||||
if (!$migration->is_latest()) {
|
||||
$this->session->destroy();
|
||||
}
|
||||
|
||||
// Language
|
||||
$language_exists = file_exists('../app/Language/' . current_language_code());
|
||||
|
||||
if (current_language_code() == null || current_language() == null || !$language_exists) { // TODO: current_language() is undefined
|
||||
$config->settings['language'] = 'english';
|
||||
$config->settings['language_code'] = 'en';
|
||||
}
|
||||
$this->setDefaultLanguage($config);
|
||||
|
||||
$language = Services::language();
|
||||
$language->setLocale($config->settings['language_code']);
|
||||
$language->setLocale(current_language_code());
|
||||
|
||||
// Time Zone
|
||||
date_default_timezone_set($config->settings['timezone'] ?? ini_get('date.timezone'));
|
||||
|
||||
bcscale(max(2, totals_decimals() + tax_decimals()));
|
||||
}
|
||||
|
||||
private function setDefaultLanguage(OSPOS $config): void
|
||||
{
|
||||
$languageCode = $config->settings['language_code'] ?? null;
|
||||
|
||||
if (empty($config->settings) || $languageCode === null) {
|
||||
$config->settings['language'] = 'english';
|
||||
$config->settings['language_code'] = 'en';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->languageExists($languageCode)) {
|
||||
$config->settings['language'] = 'english';
|
||||
$config->settings['language_code'] = 'en';
|
||||
}
|
||||
}
|
||||
|
||||
private function languageExists(string $languageCode): bool
|
||||
{
|
||||
return file_exists(APPPATH . 'Language/' . $languageCode);
|
||||
}
|
||||
}
|
||||
|
||||
35
app/Helpers/attribute_helper.php
Normal file
35
app/Helpers/attribute_helper.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Translates the attribute type to the corresponding database column name.
|
||||
*
|
||||
* Maps attribute type constants to their corresponding attribute_values table columns.
|
||||
* Defaults to 'attribute_value' for TEXT, DROPDOWN and CHECKBOX attribute types.
|
||||
*
|
||||
* @param string $input The attribute type constant (DATE, DECIMAL, etc.)
|
||||
* @return string The database column name for storing this attribute type
|
||||
*/
|
||||
function getAttributeDataType(string $input): string
|
||||
{
|
||||
$columnMap = [
|
||||
DATE => 'attribute_date',
|
||||
DECIMAL => 'attribute_decimal',
|
||||
];
|
||||
|
||||
return $columnMap[$input] ?? 'attribute_value';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the provided data type is an allowed attribute value type.
|
||||
*
|
||||
* @param string $dataType
|
||||
* @return void
|
||||
*/
|
||||
function validateAttributeValueType(string $dataType): void
|
||||
{
|
||||
$attributeValueTypes = ['attribute_value', 'attribute_decimal', 'attribute_date'];
|
||||
|
||||
if (!in_array($dataType, $attributeValueTypes, true)) {
|
||||
throw new InvalidArgumentException('Invalid data type');
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @param array $stock_locations
|
||||
* @param array $attributes
|
||||
* @return string
|
||||
*/
|
||||
|
||||
function generate_import_items_csv(array $stock_locations, array $attributes): string
|
||||
{
|
||||
$csv_headers = pack('CCC', 0xef, 0xbb, 0xbf); // Encode the Byte-Order Mark (BOM) so that UTF-8 File headers display properly in Microsoft Excel
|
||||
|
||||
@@ -22,9 +22,7 @@ function current_language_code(bool $load_system_language = false): string
|
||||
}
|
||||
}
|
||||
|
||||
$language_code = $config['language_code'];
|
||||
|
||||
return empty($language_code) ? DEFAULT_LANGUAGE_CODE : $language_code;
|
||||
return $config['language_code'] ?? DEFAULT_LANGUAGE_CODE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,9 +43,7 @@ function current_language(bool $load_system_language = false): string
|
||||
}
|
||||
}
|
||||
|
||||
$language = $config['language'];
|
||||
|
||||
return empty($language) ? DEFAULT_LANGUAGE : $language;
|
||||
return $config['language'] ?? DEFAULT_LANGUAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,6 +85,8 @@ function get_languages(): array
|
||||
'pt-BR:portuguese' => 'Portuguese (Brazil)',
|
||||
'ro:romanian' => 'Romanian',
|
||||
'ru:russian' => 'Russian',
|
||||
'sw-KE:swahili' => 'Swahili (Kenya)',
|
||||
'sw-TZ:swahili' => 'Swahili (Tanzania)',
|
||||
'sv:swedish' => 'Swedish',
|
||||
'ta:tamil' => 'Tamil',
|
||||
'th:thai' => 'Thai',
|
||||
|
||||
@@ -11,56 +11,54 @@ function check_encryption(): bool
|
||||
$old_key = config('Encryption')->key;
|
||||
|
||||
if ((empty($old_key)) || (strlen($old_key) < 64)) {
|
||||
// Create Key
|
||||
$encryption = new Encryption();
|
||||
$key = bin2hex($encryption->createKey());
|
||||
config('Encryption')->key = $key;
|
||||
|
||||
// Write to .env
|
||||
$config_path = ROOTPATH . '.env';
|
||||
$new_config_path = WRITEPATH . '/backup/.env';
|
||||
$backup_path = WRITEPATH . '/backup/.env.bak';
|
||||
|
||||
$backup_folder = WRITEPATH . '/backup';
|
||||
|
||||
if (!file_exists($backup_folder) && !mkdir($backup_folder)) {
|
||||
log_message('error', 'Could not create backup folder');
|
||||
return false;
|
||||
if (!file_exists($backup_folder)) {
|
||||
@mkdir($backup_folder, 0750, true);
|
||||
}
|
||||
|
||||
if (!copy($config_path, $backup_path)) {
|
||||
log_message('error', "Unable to copy $config_path to $backup_path");
|
||||
if (!file_exists($config_path)) {
|
||||
$example_path = ROOTPATH . '.env.example';
|
||||
if (file_exists($example_path)) {
|
||||
@copy($example_path, $config_path);
|
||||
} else {
|
||||
@file_put_contents($config_path, "# OSPOS Configuration\n\n");
|
||||
}
|
||||
@chmod($config_path, 0640);
|
||||
}
|
||||
|
||||
// Copy to backup
|
||||
@chmod($config_path, 0660);
|
||||
@chmod($backup_path, 0660);
|
||||
if (file_exists($config_path)) {
|
||||
@copy($config_path, $backup_path);
|
||||
@chmod($backup_path, 0640);
|
||||
@chmod($config_path, 0640);
|
||||
|
||||
$config_file = file_get_contents($config_path);
|
||||
$config_file = preg_replace("/(encryption\.key.*=.*)('.*')/", "$1'$key'", $config_file);
|
||||
$config_file = file_get_contents($config_path);
|
||||
|
||||
if (!empty($old_key)) {
|
||||
$old_line = "# encryption.key = '$old_key' REMOVE IF UNNEEDED\r\n";
|
||||
$insertion_point = stripos($config_file, 'encryption.key');
|
||||
$config_file = substr_replace($config_file, $old_line, $insertion_point, 0);
|
||||
if (strpos($config_file, 'encryption.key') !== false) {
|
||||
$config_file = preg_replace("/(encryption\.key.*=.*)('.*')/", "$1'$key'", $config_file);
|
||||
} else {
|
||||
$config_file .= "\nencryption.key = '$key'\n";
|
||||
}
|
||||
|
||||
if (!empty($old_key)) {
|
||||
$old_line = "# encryption.key = '$old_key' REMOVE IF UNNEEDED\r\n";
|
||||
$insertion_point = stripos($config_file, 'encryption.key');
|
||||
if ($insertion_point !== false) {
|
||||
$config_file = substr_replace($config_file, $old_line, $insertion_point, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@file_put_contents($config_path, $config_file);
|
||||
@chmod($config_path, 0640);
|
||||
|
||||
log_message('info', "Updated encryption key in $config_path");
|
||||
}
|
||||
|
||||
$handle = @fopen($config_path, 'w+');
|
||||
|
||||
if (empty($handle)) {
|
||||
log_message('error', "Unable to open $config_path for updating");
|
||||
return false;
|
||||
}
|
||||
|
||||
@chmod($config_path, 0660);
|
||||
$write_failed = !fwrite($handle, $config_file);
|
||||
fclose($handle);
|
||||
|
||||
if ($write_failed) {
|
||||
log_message('error', "Unable to write to $config_path for updating.");
|
||||
return false;
|
||||
}
|
||||
log_message('info', "File $config_path has been updated.");
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -74,23 +72,14 @@ function abort_encryption_conversion(): void
|
||||
$config_path = ROOTPATH . '.env';
|
||||
$backup_path = WRITEPATH . '/backup/.env.bak';
|
||||
|
||||
$config_file = file_get_contents($backup_path);
|
||||
|
||||
$handle = @fopen($config_path, 'w+');
|
||||
|
||||
if (empty($handle)) {
|
||||
log_message('error', "Unable to open $config_path to undo encryption conversion");
|
||||
} else {
|
||||
@chmod($config_path, 0660);
|
||||
$write_failed = !fwrite($handle, $config_file);
|
||||
fclose($handle);
|
||||
|
||||
if ($write_failed) {
|
||||
log_message('error', "Unable to write to $config_path to undo encryption conversion.");
|
||||
return;
|
||||
}
|
||||
log_message('info', "File $config_path has been updated to undo encryption conversion");
|
||||
if (!file_exists($backup_path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@chmod($config_path, 0640);
|
||||
$config_file = file_get_contents($backup_path);
|
||||
@file_put_contents($config_path, $config_file);
|
||||
log_message('info', "Restored $config_path from backup");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,13 +88,9 @@ function abort_encryption_conversion(): void
|
||||
function remove_backup(): void
|
||||
{
|
||||
$backup_path = WRITEPATH . '/backup/.env.bak';
|
||||
if (! file_exists($backup_path)) {
|
||||
if (!file_exists($backup_path)) {
|
||||
return;
|
||||
}
|
||||
if (!unlink($backup_path)) {
|
||||
log_message('error', "Unable to remove $backup_path.");
|
||||
return;
|
||||
}
|
||||
log_message('info', "File $backup_path has been removed");
|
||||
@unlink($backup_path);
|
||||
log_message('info', "Removed $backup_path");
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use App\Models\Employee;
|
||||
use App\Models\Item_taxes;
|
||||
use App\Models\Tax_category;
|
||||
use CodeIgniter\Database\ResultInterface;
|
||||
use CodeIgniter\HTTP\IncomingRequest;
|
||||
use CodeIgniter\Session\Session;
|
||||
use Config\OSPOS;
|
||||
use Config\Services;
|
||||
@@ -48,7 +49,7 @@ function transform_headers(array $headers, bool $readonly = false, bool $editabl
|
||||
'field' => key($element),
|
||||
'title' => current($element),
|
||||
'switchable' => $element['switchable'] ?? !preg_match('(^$| )', current($element)),
|
||||
'escape' => !preg_match("/(edit|email|messages|item_pic|customer_name|note)/", key($element)) && !(isset($element['escape']) && !$element['escape']),
|
||||
'escape' => !preg_match("/(edit|email|messages|item_pic)/", key($element)) && !(isset($element['escape']) && !$element['escape']),
|
||||
'sortable' => $element['sortable'] ?? current($element) != '',
|
||||
'checkbox' => $element['checkbox'] ?? false,
|
||||
'class' => isset($element['checkbox']) || preg_match('(^$| )', current($element)) ? 'print_hide' : '',
|
||||
@@ -408,7 +409,7 @@ function get_items_manage_table_headers(): string
|
||||
{
|
||||
$attribute = model(Attribute::class);
|
||||
$config = config(OSPOS::class)->settings;
|
||||
$definition_names = $attribute->get_definitions_by_flags($attribute::SHOW_IN_ITEMS); // TODO: this should be made into a constant in constants.php
|
||||
$definitionsWithTypes = $attribute->get_definitions_by_flags($attribute::SHOW_IN_ITEMS, true);
|
||||
|
||||
$headers = item_headers();
|
||||
|
||||
@@ -420,8 +421,8 @@ function get_items_manage_table_headers(): string
|
||||
|
||||
$headers[] = ['item_pic' => lang('Items.image'), 'sortable' => false];
|
||||
|
||||
foreach ($definition_names as $definition_id => $definition_name) {
|
||||
$headers[] = [$definition_id => $definition_name, 'sortable' => false];
|
||||
foreach ($definitionsWithTypes as $definition_id => $definitionInfo) {
|
||||
$headers[] = [$definition_id => $definitionInfo['name'], 'sortable' => false];
|
||||
}
|
||||
|
||||
$headers[] = ['inventory' => '', 'escape' => false];
|
||||
@@ -470,7 +471,8 @@ function get_item_data_row(object $item): array
|
||||
: glob("./uploads/item_pics/$item->pic_filename");
|
||||
|
||||
if (sizeof($images) > 0) {
|
||||
$image .= '<a class="rollover" href="' . base_url($images[0]) . '"><img alt="Image thumbnail" src="' . site_url('items/PicThumb/' . pathinfo($images[0], PATHINFO_BASENAME)) . '"></a>';
|
||||
$image_path = ltrim($images[0], './');
|
||||
$image .= '<a class="rollover" href="' . base_url(implode('/', array_map('rawurlencode', explode('/', $image_path)))) . '"><img alt="Image thumbnail" src="' . site_url('items/PicThumb/' . rawurlencode(pathinfo($images[0], PATHINFO_BASENAME))) . '"></a>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,7 +480,7 @@ function get_item_data_row(object $item): array
|
||||
$item->name .= NAME_SEPARATOR . $item->pack_name;
|
||||
}
|
||||
|
||||
$definition_names = $attribute->get_definitions_by_flags($attribute::SHOW_IN_ITEMS);
|
||||
$definition_names = $attribute->get_definitions_by_flags($attribute::SHOW_IN_ITEMS, true);
|
||||
|
||||
$columns = [
|
||||
'items.item_id' => $item->item_id,
|
||||
@@ -576,8 +578,8 @@ function item_kit_headers(): array
|
||||
['item_kit_number' => lang('Item_kits.item_kit_number')],
|
||||
['name' => lang('Item_kits.name')],
|
||||
['description' => lang('Item_kits.description')],
|
||||
['total_cost_price' => lang('Items.cost_price'), 'sortable' => FALSE],
|
||||
['total_unit_price' => lang('Items.unit_price'), 'sortable' => FALSE]
|
||||
['total_cost_price' => lang('Items.cost_price'), 'sortable' => false],
|
||||
['total_unit_price' => lang('Items.unit_price'), 'sortable' => false]
|
||||
];
|
||||
}
|
||||
|
||||
@@ -633,7 +635,7 @@ function parse_attribute_values(array $columns, array $row): array
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $definition_names
|
||||
* @param array $definition_names Array of definition_id => ['name' => name, 'type' => type] or definition_id => name
|
||||
* @param array $row
|
||||
* @return array
|
||||
*/
|
||||
@@ -650,10 +652,16 @@ function expand_attribute_values(array $definition_names, array $row): array
|
||||
}
|
||||
|
||||
$attribute_values = [];
|
||||
foreach ($definition_names as $definition_id => $definition_name) {
|
||||
foreach ($definition_names as $definition_id => $definitionInfo) {
|
||||
if (isset($indexed_values[$definition_id])) {
|
||||
$attribute_value = $indexed_values[$definition_id];
|
||||
$attribute_values["$definition_id"] = $attribute_value;
|
||||
$raw_value = $indexed_values[$definition_id];
|
||||
|
||||
// Format DECIMAL attributes according to locale
|
||||
if (is_array($definitionInfo) && isset($definitionInfo['type']) && $definitionInfo['type'] === DECIMAL) {
|
||||
$attribute_values["$definition_id"] = to_decimals($raw_value);
|
||||
} else {
|
||||
$attribute_values["$definition_id"] = $raw_value;
|
||||
}
|
||||
} else {
|
||||
$attribute_values["$definition_id"] = "";
|
||||
}
|
||||
@@ -735,7 +743,7 @@ function get_expense_category_manage_table_headers(): string
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the html data row for the expenses category
|
||||
* Gets the html data row for the expense category
|
||||
*/
|
||||
function get_expense_category_data_row(object $expense_category): array
|
||||
{
|
||||
@@ -834,7 +842,7 @@ function get_expenses_data_last_row(object $expense): array
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expenses payments summary
|
||||
* Get the expense payments summary
|
||||
*/
|
||||
function get_expenses_manage_payments_summary(array $payments, ResultInterface $expenses): string // TODO: $expenses is passed but never used.
|
||||
{
|
||||
@@ -924,3 +932,24 @@ function get_controller(): string
|
||||
$controller_name_parts = explode('\\', $controller_name);
|
||||
return end($controller_name_parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores filter values from the URL query string.
|
||||
*
|
||||
* @param IncomingRequest $request The request object
|
||||
* @return array Array with 'start_date', 'end_date', and 'selected_filters' keys
|
||||
*/
|
||||
function restoreTableFilters(IncomingRequest $request): array
|
||||
{
|
||||
$startDate = $request->getGet('start_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$endDate = $request->getGet('end_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
$urlFilters = $request->getGet('filters', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||
|
||||
return array_filter([
|
||||
'start_date' => $startDate ?: null,
|
||||
'end_date' => $endDate ?: null,
|
||||
'selected_filters' => $urlFilters ?? []
|
||||
], function ($value) {
|
||||
return $value !== null && $value !== [];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -143,8 +143,7 @@ function get_tax_rates_manage_table_headers(): string
|
||||
*/
|
||||
function get_tax_rates_data_row($tax_rates_row): array
|
||||
{
|
||||
$router = service('router');
|
||||
$controller_name = strtolower($router->controllerName());
|
||||
$controller_name = 'taxes';
|
||||
|
||||
return [
|
||||
'tax_rate_id' => $tax_rates_row->tax_rate_id,
|
||||
|
||||
@@ -7,7 +7,7 @@ if (!function_exists('base64url_encode')) {
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
function base64url_encode($data)
|
||||
function base64url_encode(string $data): string
|
||||
{
|
||||
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
|
||||
}
|
||||
@@ -20,7 +20,7 @@ if (!function_exists('base64url_decode')) {
|
||||
* @param string $data
|
||||
* @return string|false
|
||||
*/
|
||||
function base64url_decode($data)
|
||||
function base64url_decode(string $data): false|string
|
||||
{
|
||||
$remainder = strlen($data) % 4;
|
||||
if ($remainder) {
|
||||
@@ -28,4 +28,4 @@ if (!function_exists('base64url_decode')) {
|
||||
}
|
||||
return base64_decode(strtr($data, '-_', '+/'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ return [
|
||||
"confirm_delete" => "هل أنت متأكد من أنك تريد حذف الميزات المحددة ؟",
|
||||
"confirm_restore" => "هل أنت متأكد من أنك تريد استعادة السمة (السمات) المحددة؟",
|
||||
"definition_cannot_be_deleted" => "لا يمكن حذف السمات المحددة",
|
||||
"definition_invalid_group" => "المجموعة المحددة غير موجودة أو غير صالحة.",
|
||||
"definition_error_adding_updating" => "لا يمكن إضافة السمة {0} أو تحديثها. يرجى التحقق من سجل الخطأ.",
|
||||
"definition_flags" => "رؤية الميزات",
|
||||
"definition_group" => "المجموعة",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"all" => "الجميع",
|
||||
"columns" => "أعمدة",
|
||||
"hide_show_pagination" => "عرض/إخفاء أرقام الصفحات",
|
||||
"loading" => "جارى التحميل، برجاء الإنتظار ...",
|
||||
"page_from_to" => "عرض {0} إلى {1} من {2} صفوف",
|
||||
"refresh" => "إعادة تحميل",
|
||||
"rows_per_page" => "{0} صف بالصفحة",
|
||||
"toggle" => "تغيير",
|
||||
'all' => "الجميع",
|
||||
'columns' => "أعمدة",
|
||||
'hide_show_pagination' => "عرض/إخفاء أرقام الصفحات",
|
||||
'loading' => "جارى التحميل، برجاء الإنتظار",
|
||||
'page_from_to' => "عرض {0} إلى {1} من {2} صفوف",
|
||||
'refresh' => "إعادة تحميل",
|
||||
'rows_per_page' => "{0} صف بالصفحة",
|
||||
'toggle' => "تغيير",
|
||||
];
|
||||
|
||||
49
app/Language/ar-EG/Calendar.php
Normal file
49
app/Language/ar-EG/Calendar.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"su" => "أحد",
|
||||
"mo" => "اثنين",
|
||||
"tu" => "ثلاثاء",
|
||||
"we" => "أربعاء",
|
||||
"th" => "خميس",
|
||||
"fr" => "جمعة",
|
||||
"sa" => "سبت",
|
||||
"sun" => "الأحد",
|
||||
"mon" => "الاثنين",
|
||||
"tue" => "الثلاثاء",
|
||||
"wed" => "الأربعاء",
|
||||
"thu" => "الخميس",
|
||||
"fri" => "الجمعة",
|
||||
"sat" => "السبت",
|
||||
"sunday" => "الأحد",
|
||||
"monday" => "الاثنين",
|
||||
"tuesday" => "الثلاثاء",
|
||||
"wednesday" => "الأربعاء",
|
||||
"thursday" => "الخميس",
|
||||
"friday" => "الجمعة",
|
||||
"saturday" => "السبت",
|
||||
"jan" => "يناير",
|
||||
"feb" => "فبراير",
|
||||
"mar" => "مارس",
|
||||
"apr" => "أبريل",
|
||||
"may" => "مايو",
|
||||
"jun" => "يونيو",
|
||||
"jul" => "يوليو",
|
||||
"aug" => "أغسطس",
|
||||
"sep" => "سبتمبر",
|
||||
"oct" => "أكتوبر",
|
||||
"nov" => "نوفمبر",
|
||||
"dec" => "ديسمبر",
|
||||
"january" => "يناير",
|
||||
"february" => "فبراير",
|
||||
"march" => "مارس",
|
||||
"april" => "أبريل",
|
||||
"mayl" => "مايو",
|
||||
"june" => "يونيو",
|
||||
"july" => "يوليو",
|
||||
"august" => "أغسطس",
|
||||
"september" => "سبتمبر",
|
||||
"october" => "أكتوبر",
|
||||
"november" => "نوفمبر",
|
||||
"december" => "ديسمبر",
|
||||
];
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
"right" => "يمين",
|
||||
"sales_invoice_format" => "شكل فاتورة البيع",
|
||||
"sales_quote_format" => "شكل فاتورة عرض الاسعار",
|
||||
"mailpath_invalid" => "",
|
||||
"saved_successfully" => "تم حفظ التهيئة بنجاح.",
|
||||
"saved_unsuccessfully" => "لم يتم حفظ التهيئة بنجاح.",
|
||||
"security_issue" => "تحذير من ثغرة أمنية",
|
||||
|
||||
@@ -14,6 +14,8 @@ return [
|
||||
"current_password_invalid" => "كلمة المرور الحالية غير صحيحة.",
|
||||
"employee" => "موظف",
|
||||
"error_adding_updating" => "خطاء فى إضافة/تعديل موظف.",
|
||||
"error_deleting_admin" => "",
|
||||
"error_updating_admin" => "",
|
||||
"error_deleting_demo_admin" => "لايمكن حذف المستخدم admin الخاص بنسخة العرض.",
|
||||
"error_updating_demo_admin" => "لايمكن تغيير بيانات المستخدم admin الخاص بنسخة العرض.",
|
||||
"language" => "اللغة",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user