Compare commits

..

255 Commits

Author SHA1 Message Date
jekkos
86e150ad96 Update INSTALL.md with opensourcepos.org short URL
- Preferred install URL: https://opensourcepos.org/install
- Falls back to direct GitHub URL if redirect unavailable
- More professional and easier to remember
2026-03-06 10:21:22 +00:00
jekkos
8f4055c711 Add one-line Ubuntu installation script
- Creates scripts/install-ubuntu.sh for automated fresh Ubuntu server setup
- Installs Apache, MariaDB, PHP 8.2 with required extensions
- Downloads and configures OSPOS from GitHub
- Sets up Apache virtual host with proper permissions
- Generates secure random database password
- Supports environment variables for customization
- Updates INSTALL.md with curl pipe to bash instructions

This provides an alternative to cloud-specific instructions and
allows users to quickly set up OSPOS on any fresh Ubuntu server.
2026-03-05 15:11:06 +00:00
jekkos
3c25fd77e2 Add validation for invalid stock locations in CSV import
- Add validateCSVStockLocations() method to check CSV columns against allowed locations
- Log error when invalid stock location columns are detected
- Tests for valid, invalid, and mixed stock location columns
- Tests for location name case sensitivity
- Tests for CSV parsing and detecting location columns
- Add error message language string for invalid locations
2026-03-05 15:06:28 +00:00
jekkos
3f7ea18f18 Add unit tests for CSV import functionality
- Add comprehensive test suite for CSV import in ItemsCsvImportTest.php
- Test CSV header generation (locations, attributes, BOM handling)
- Test CSV file parsing (multiple rows, BOM detection)
- Test item import (basic fields, quantities, inventory records)
- Test item updates, taxes, and attributes
- Test edge cases (zero quantities, negative values, precision)
- Add GitHub Actions workflow for unit tests
- Tests verify data ends up correctly in items/item_quantities tables
2026-03-05 12:57:37 +00:00
jekkos
36bf130bdd Add comprehensive unit tests for PR #4384
This commit adds unit tests for the case-sensitive attribute updates
and CSV import attribute deletion capability features introduced in PR #4384.

Test Coverage:
- Attribute Model Tests (tests/Models/AttributeTest.php):
  - testCaseSensitiveAttributeValueUpdate: Verifies case-insensitive check then case-sensitive update
  - testDeleteAttributeLinks: Tests deletion of attribute links
  - testCategoryDropdownCanBeEnabled: Verifies dropdown enablement bug fix
  - testDropdownAttributeValueSave: Tests DROPDOWN type attributes
  - testDateAttributeValueSave/Update: Tests DATE type attributes
  - testDecimalAttributeValueSave: Tests DECIMAL type attributes
  - testCheckboxAttributeValueSave: Tests CHECKBOX type attributes
  - testCategoryDropdownWithConstant: Tests CATEGORY_DEFINITION_ID usage
  - testDeleteAttributeLinksPreservesSalesAndReceivings: Ensures sales/receivings links protected
  - testDeleteOrphanedValues: Tests orphan value cleanup
  - testUnicodeCaseComparison: Tests Unicode handling in case comparisons
  - testGetAttributeValueByAttributeId: Tests new utility method
  - testAttributeLinkWithNullAttributeId: Tests null attribute_id handling
  - testCategoryDropdownUpdatesItemCategory: Tests category dropdown behavior

- Attribute Helper Tests (tests/Helpers/AttributeHelperTest.php):
  - Test getAttributeDataType for all attribute types (TEXT, DECIMAL, DATE, DROPDOWN, CHECKBOX)
  - Test getAttributeDataType returns 'attribute_value' as fallback for invalid types
  - Test validateAttributeValueType throws InvalidArgumentException for invalid types
  - Test validateAttributeValueType accepts valid data types

- Import File Helper Tests (tests/Helpers/ImportFileHelperTest.php):
  - Tests _DELETE_ magic word case-insensitive comparison using strcasecmp
  - Tests that _DELETE_ does not match similar-looking strings (security)
  - Tests empty string does not match _DELETE_
  - Tests null safety considerations for strcasecmp usage

Test Configuration:
- Updated phpunit.xml to include Models and Controllers test suites
- Uses DatabaseTestTrait for database migration between tests
- Tests cover positive and negative cases
- Tests include edge cases: Unicode, null values, empty strings, similar strings

Files Added:
- tests/Models/AttributeTest.php (26,541 bytes, 16 test methods)
- tests/Helpers/AttributeHelperTest.php (3,331 bytes, 8 test methods)
- tests/Helpers/ImportFileHelperTest.php (2,906 bytes, 4 test methods)

Total: 28 test methods covering all new features and edge cases

Note: Tests currently designed; will run once PHP environment is configured.
2026-03-04 20:48:10 +00:00
objec
088ad47c99 CSV Barcode Update Bug
- Refactored variable names for PSR compliance
- Removed bug preventing updates in CSV import files from updating the barcode number.
- Corrected duplicate saveAttributeLink() calls with attribute type was not DROPDOWN.

Signed-off-by: objec <objecttothis@gmail.com>
2026-02-27 18:00:30 +04:00
objec
808840b2e9 Implement Magic word deletion in CSV import
- Corrected spacing
- Added business logic to delete an attribute_link if the import contains `_DELETE_` in that space.
- Removed unneeded PHPdoc comments
- Improved PHPdoc to clarify behavior of function
- Refactor variable names for PSR compliance
- Add logic in validation code for magic word

Signed-off-by: objec <objecttothis@gmail.com>
2026-02-27 16:53:03 +04:00
objec
2ed74c5c0e Resolve review comments
- Replaced -1 for CATEGORY_DEFINITION_ID constant for readability

Signed-off-by: objec <objecttothis@gmail.com>
2026-02-27 00:31:51 +04:00
objec
c935fc7a2a Resolve review comments
- Move validation function to attribute_helper.php
- Removed extra line in security_helper.php
- Corrected some calls to helper() that included `_helper`

Signed-off-by: objec <objecttothis@gmail.com>
2026-02-26 23:16:39 +04:00
objec
89012054b4 Resolve review comments
- Fixed call to deleteOrphanedValues that refactor missed.
- Removed unused import.
- Fixed issue preventing DROPDOWN values from being added.
- Updated logic to fix potential TypeError being thrown by strcasecmp()

Signed-off-by: objec <objecttothis@gmail.com>
2026-02-26 15:16:51 +04:00
objec
89572aa289 Resolve review comments
- Replaced unneeded case-sensitive database search with case-insensitive variant.
- Added input validation.
- Added logic to properly check for case changes in CSV import.
- Moved deleteOrphanedValues() to outside a foreach loop to prevent it running redundantly.

Signed-off-by: objec <objecttothis@gmail.com>
2026-02-26 13:52:14 +04:00
objec
2b56d56072 Resolve business logic bugs
- Fixed logic causing attribute_value to be updated to a value that already exists for a different attribute_id.
- Added logic for edge case where an attribute_value was updated due to capitalization that had a row in attribute_links for category_dropdown definitions (definition_id = -1). This will also update the items.category values to correct the capitalization of those.

Signed-off-by: objec <objecttothis@gmail.com>
2026-02-26 12:28:48 +04:00
objec
2fc9fc09a4 Comment Resolutions
- Removed redundant variable declaration.
- Refactored local variables for PSR compliance.
- Add back in Date Formatting and corrected business logic
- Corrected spacing in comments.
- Corrected business logic of function call in Attribute model and refactored redundant code to a private function.

Signed-off-by: objec <objecttothis@gmail.com>
2026-02-26 00:19:57 +04:00
objec
932b612c9e Case-sensitive attribute update in Item view
- Refactored local variables for PSR compliance
- Added business logic to Attribute->saveAttributeValue so that the attribute value gets overwritten if the only difference is capitalization.
- Added PHPdocs
- Fixed bug in Attribute->saveDefinition preventing category as dropdown from working.
- Modified Attribute->saveAttributeLink() to account for dropdown attributes.

Signed-off-by: objec <objecttothis@gmail.com>
2026-02-25 19:22:02 +04:00
objec
ab6e8ee083 Case-sensitive attributes in CSV imports
- Added attribute_helper.php and getAttributeDataType function for quick translation in the code.
- Refactored code for PSR compliance
- Added getAttributeValueByAttributeId() to the attribute model.
- Added PHPdocs where it was missing
- Updated business logic to check for capitalization differences on CSV import of an item.

Signed-off-by: objec <objecttothis@gmail.com>
2026-02-25 16:30:21 +04:00
jekkos
79427481b3 Fix XSS vulnerabilities in invoices + receipts (#3965) (#4363) 2026-02-23 20:14:55 +01:00
dependabot[bot]
b23351a45c Bump jspdf and jspdf-autotable (#4373)
Bumps [jspdf](https://github.com/parallax/jsPDF) and [jspdf-autotable](https://github.com/simonbengtsson/jsPDF-AutoTable). These dependencies needed to be updated together.

Updates `jspdf` from 3.0.2 to 4.1.0
- [Release notes](https://github.com/parallax/jsPDF/releases)
- [Changelog](https://github.com/parallax/jsPDF/blob/master/RELEASE.md)
- [Commits](https://github.com/parallax/jsPDF/compare/v3.0.2...v4.1.0)

Updates `jspdf-autotable` from 5.0.2 to 5.0.7
- [Release notes](https://github.com/simonbengtsson/jsPDF-AutoTable/releases)
- [Commits](https://github.com/simonbengtsson/jsPDF-AutoTable/compare/v5.0.2...v5.0.7)

---
updated-dependencies:
- dependency-name: jspdf
  dependency-version: 4.1.0
  dependency-type: direct:production
- dependency-name: jspdf-autotable
  dependency-version: 5.0.7
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-07 11:46:11 +00:00
dependabot[bot]
bee0c8e364 Bump lodash from 4.17.21 to 4.17.23 (#4369)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-22 20:51:03 +01:00
jekkos
849439c71e Fix multiple XSS vulnerabilities (#3965) (#4356) 2025-12-22 17:21:49 +01:00
Chathura Dilushanka
25680f05db Add equals as permitted URI character (#4329)
This should resolve the 400 error when deleting payments with base64 encoded IDs containing `=`.
2025-12-21 22:41:36 +01:00
jekkos
a11fb099e2 Fix travis build after merge (#4130) 2025-12-21 19:51:21 +01:00
BhojKamal
aee5f31cf5 Add show/hide cost price & profit feature - in reports #4130 (#4350)
* Add show/hide cost price & profit feature

* .env should be ignored.

* js code formatted. .vscode folder ignore for vscode user settings.json

* style is replaced with bootstrap class, formatted and .env.example

* toggle button on table to like in other

* comment corrected.

* class re-factored

* minor refactor

* formatted with 4 space

---------

Co-authored-by: Lotussoft Youngtech <lotussoftyoungtech@gmail.com>
2025-12-21 15:23:39 +05:45
jekkos
643b0ac499 Fix for detailed suppliers report (#4351) 2025-12-17 22:46:59 +01:00
jekkos
3e844f2f89 Escape return_policy in receipt + invoice (#4349)
* Escape return_policy in receipt + invoice

* Enable CSRF using session token (#3632)
2025-12-17 20:39:58 +01:00
jekkos
2acdec431f Fix wrong migration script location (#4285) 2025-12-08 23:06:48 +01:00
jekkos
f245f585da Fix creation of date attribute value (#4310) (#4344)
Fix type hints in case search string is empty in sales
2025-12-02 07:19:14 +01:00
jekkos
e48ab45094 Fix toast notifications in config (#4341) (#4343) 2025-11-28 09:01:07 +01:00
jekkos
46e31b1c16 Allow anonymous giftcard creation (#4278)
* Allow giftcard without person (#4276)

* Update giftcard form validation (#4276)
2025-11-24 22:54:52 +01:00
jekkos
bea69c7aa1 Add DOMPurify to JS includes (#4341) 2025-11-23 22:20:40 +01:00
jekkos
30da69a382 Fix attachment cid (#4314)
* Add attachment cid when sending emails (#4308)

Also check if an encryption key is set before decrypting the SMTP
password.

* Upgrade to CI 4.6.3 (#4308)

* Fix for changing invoice id in email (#4308)
2025-11-23 21:37:32 +01:00
jekkos
6dd5a9162f Add DOMpurify + fix XSS (#4341) 2025-11-23 21:35:47 +01:00
jekkos
26a398f7d2 Add recent releases to issue template (#4317) 2025-11-21 23:55:24 +01:00
jekkos
ce73d9bb31 Add env variable to disallow pwd change (#4325) 2025-11-21 23:46:48 +01:00
jekkos
83af580d40 Add server side validation for password (#4335) 2025-11-21 23:45:47 +01:00
jekkos
ca7adf76c1 Update SECURITY.md contact (#4335) 2025-11-21 23:22:39 +01:00
jekkos
832db664e5 Fix tax configuration pages (#4331) 2025-11-21 22:13:35 +01:00
jekkos
36e73a84af Clean up docker compose setup (#4308) 2025-10-27 21:57:12 +01:00
Joe Williams
bcddf482fe [Feature] Add logging to migrations (#4327)
* `execute_script()` now returns a boolean for error handling.

* Added transaction to `Migration_MissingConfigKeys.up()`.

* Added logging to various migrations.

* Added transaction to `Migration_MissingConfigKeys.up()`.

* Added logging to various migrations.

* Formatting and function call fixes

Fixed a minor formatting issue in the migration helper.
Replaced a few remaining error_log() calls.
Updated executeScriptWithTransaction() to use log_message()

* Function call fix

Replaced the last error_log() calls with log_message().

---------

Co-authored-by: Joe Williams <hey-there-joe@outlook.com>
2025-10-19 22:10:28 -07:00
Joe Williams
759356288b Add transactions to missing config keys migration. (#4318)
* `execute_script()` now returns a boolean for error handling.

* Added transaction to `Migration_MissingConfigKeys.up()`.

* Added `executeScriptWithTransaction()` to migration helpers.

* Many changes for testing; also minor formatting fixes.

* Removed test code and pointed the `NullableTaxCategoryId` migration at the right SQL file.

* Fixed header.php

* Code cleanup from code review:
- Added IGNORE to SQL scripts.
- Added try-catch to executeScriptWithTransaction().
- Various comment changes.

* Fixed naming issue

Nullable tax category ID migration now runs the correct script.

* Updated SQL

Replaced INSERT WHERE NOT EXISTS in missing config keys sql script to use a single INSERT IGNORE.

* Updated migration helper

Updated executeScriptWithTransaction to use transRollback

---------

Co-authored-by: Joe Williams <hey-there-joe@outlook.com>
2025-10-15 22:53:14 -07:00
j2272850861-pixel
d1e5575ac1 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (8 of 8 strings)

Translation: opensourcepos/bootstrap_tables
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/bootstrap_tables/pt_BR/
2025-10-10 12:58:48 +02:00
j2272850861-pixel
b3f67a5e0f Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (85 of 85 strings)

Translation: opensourcepos/common
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/common/pt_BR/
2025-10-10 12:58:48 +02:00
j2272850861-pixel
41b349134a Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (12 of 12 strings)

Translation: opensourcepos/login
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/login/pt_BR/
2025-10-10 12:58:48 +02:00
jekkos
b1f6ae6d35 Fix mount path for uploads (#4308)
Remove duplicated compose sections in nginx version.  We will include
parts of the main file instead of duplicating it here.
2025-08-29 09:12:02 +02:00
dependabot[bot]
4153c69ccd Bump jspdf from 3.0.1 to 3.0.2 (#4309)
Bumps [jspdf](https://github.com/parallax/jsPDF) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/parallax/jsPDF/releases)
- [Changelog](https://github.com/parallax/jsPDF/blob/master/RELEASE.md)
- [Commits](https://github.com/parallax/jsPDF/compare/v3.0.1...v3.0.2)

---
updated-dependencies:
- dependency-name: jspdf
  dependency-version: 3.0.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 07:32:54 +02:00
jekkos
87fbd72478 Add generic try/catch in import (#4302) 2025-08-28 00:05:58 +02:00
jekkos
a4ac42b4ad Fix reference to uploads folder (#4270) (#4286) 2025-08-18 21:19:36 +02:00
jekkos
2eff79a8b6 Fix for suspended sales (#4283) (#4303) 2025-08-15 23:12:35 +02:00
Aril Apria Susanto
880fb8faef Translated using Weblate (Indonesian)
Currently translated at 100.0% (327 of 327 strings)

Translation: opensourcepos/config
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/config/id/
2025-08-11 10:27:22 +02:00
Aril Apria Susanto
4d2347173b Translated using Weblate (Indonesian)
Currently translated at 100.0% (85 of 85 strings)

Translation: opensourcepos/common
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/common/id/
2025-08-11 10:27:22 +02:00
Aril Apria Susanto
82d36d01fb Translated using Weblate (Indonesian)
Currently translated at 100.0% (45 of 45 strings)

Translation: opensourcepos/module
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/module/id/
2025-08-11 10:27:22 +02:00
Aril Apria Susanto
13314b7da1 Translated using Weblate (Indonesian)
Currently translated at 100.0% (53 of 53 strings)

Translation: opensourcepos/customers
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/customers/id/
2025-08-11 10:27:22 +02:00
jekkos
43808c5970 Revert toast message sanitization (#4302) 2025-08-07 23:49:54 +02:00
jekkos
1615ef3832 Set release version to 3.4.2 2025-08-07 21:06:11 +02:00
jekkos
e089dc5e2c Fix item kits update (#4294) 2025-08-06 23:40:00 +02:00
jekkos
4cf70a95e6 Fix security incident email address (#4298) 2025-07-30 08:05:58 +02:00
jekkos
e08367aaae Allow empty tax category id (#4285) (#4288) 2025-07-29 23:59:23 +02:00
jekkos
9cd2f685ff Fix barcode generation in items (#4270) 2025-07-29 23:56:50 +02:00
jekkos
6800f338e7 Upgrade to ci 4.6.2 (#4296) (#4298) 2025-07-29 23:20:24 +02:00
jekkos
d4ab56b742 Fix migration 20250522000000 (#4284)
* Fix migration errors

Add dropColumnIfExists to migration_helper

* Add config key/values if missing (#4282)
2025-07-16 23:28:24 +02:00
jekkos
1eb75d6e05 Fix typo in writeable (#4270) 2025-07-11 23:23:13 +02:00
jekkos
8833420917 Upgrade github workflow (#3708) (#4280)
Co-authored-by: El_Coloso <diegoramosp@gmail.com>
2025-07-11 23:13:44 +02:00
jekkos
0d1f4efe3c Extended payment delete fix (#4274)
* Create a  Base64 URL-Safe encoding and decoding helper

* Rename web_helper to url_helper

---------

Co-authored-by: El_Coloso <diegoramosp@gmail.com>
2025-07-07 13:57:03 +02:00
jekkos
b9e17daac7 Fix writable folder permission check (#4270) (#4273) 2025-07-06 22:04:17 +02:00
jekkos
5f395d987b Set release version to 3.4.1 2025-06-05 21:28:32 +02:00
objecttothis
6f587498e6 Migration fix for MariaDB databases
- This fix properly creates Primary Keys on both MariaDB and MySQL

Signed-off-by: objecttothis <objecttothis@gmail.com>
2025-06-01 10:15:57 +02:00
jekkos
29c3c55fcc Fix item number lookup in sales/receivings (#4212) (#4250)
* Fix item number lookup in sales/receivings (#4212)

* Remove item_number check in exists()
2025-05-30 22:29:35 +02:00
objecttothis
e1fedab9b7 Bugfix: constraint migration fixes (#4230)
- Refactored function names for PSR-12 compliance
- Programmatically cascade delete attribute_link rows when a drop-down attribute is deleted but leave attribute_link rows associated with transactions.
- Added `WHERE item_id IS NOT NULL` to migration to prevent failure on MySQL databases during migration
- Retroactive correction of migration to prevent MySQL databases from failing.
- Refactored generic functions to helper
- Reverted attribute_links foreign key to ON DELETE RESTRICT which is required for a unique constraint on this table. Cascading deletes are now handled programmatically.
- Migration Session table to match Code Igniter 4.6
- Add index to attribute_links to prevent query timeout in items view on large databases
- Added overridePrefix() function to the migration_helper. Any time QueryBuilder is adding a prefix to the query when we don't want it to, this query can be used to override the prefix then set it back after you're done.
- Added dropAllForeignKeyConstraints() helper function.
- Added deleteIndex() helper function.
- Added indexExists() helper function.
- Added primaryKeyExists() helper function.
- Added recreateForeignKeyConstraints() helper function.
- Added CRUD section headings to the Attribute model.
- Replaced `==` with `===` to prevent type juggling.
- Removed unused delete_value function.
- Reworked deleteDefinition() and deleteDefinitionList() functions to delete rows from the attribute_links table which are associated.
- Added deleteAttributeLinksByDefinitionId() function

Implement Cascading Delete
- Function to delete attribute links with one or more attribute definitions.
- Call function to implement an effective cascading delete.
- Refactor function naming to meet PSR-12 conventions

Fix Migration
- Add drop of Generated Column to prevent failure of migration on MySQL databases.

Fix Migration
- Removed blank lines
- Refactored function naming for PSR compliance
- Reformatted code for PSR compliance
- Added logic to drop dependent foreign key constraints before deleting an index then recreating them.

Migrate ospos_sessions table
- DROP and CREATE session table to prevent migration problems on populated databases

Fixed Bug in Migration
- In the event that item_id = null (e.g., it's a dropdown) it should not be included in the results.

Fixed bug in Dropdown deletes
- Removed delete_value function in Attributes Controller as it is unused.
- Renamed postDelete_attribute_value function for PSR-12 compliance.
- Renamed delete_value Attribute model function for PSR-12 compliance.
- Refactored out function to getAttributeIdByValue
- Replaced == with === to prevent type juggling
- Reorganized parts of model to make it easier to find CRUD functions.

Refactoring
- PSR-12 Compliance formatting changes
- Refactored several generic functions into the migration_helper.php
- First check if primary key exists before attempting to create it.
- Grouped functions together in migration_helper.php
- phpdoc commenting functions

Optimizing Indices
- There are two queries run while opening the Items view which time out on large databases with weak hardware. These indices cut the query execution in half or better.

Add Unique constraint back into attribute_links
- This migration reverts ospos_attribute_links_ibfk_1 and 2 to ON DELETE RESTRICT. Cascade delete is done programmatically. This is needed to have a unique column on the attribute_links table which prevents duplicate attributes from begin created with the same item_id-attribute_id-definition_id combination

Correct spacing after if for PSR-12

Minor code cleanup.
- Removed Comments separating sections of code in Attribute model
- Removed extra log line to prevent cluttering of the log
2025-05-29 15:24:08 +04:00
Maxime
3c846e6324 Fixed broken escape string for success & warning messages (#4253)
* Fixed broken escape string for success & warning messages

* Fixed issue in sales register

---------

Co-authored-by: Franchovy <franchovy@pm.me>
2025-05-27 23:27:27 +02:00
diego-ramos
85120fa4be Fix encoding issue for payment types with special characters (#4232) 2025-05-22 22:34:39 +02:00
Mohamed-Qadir
7ba60ba58b Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (38 of 38 strings)

Translation: opensourcepos/item_kits
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/item_kits/ckb/
2025-05-10 02:04:32 +02:00
Mohamed-Qadir
64f34933c4 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (145 of 145 strings)

Translation: opensourcepos/reports
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/reports/ckb/
2025-05-10 02:04:32 +02:00
Mohamed-Qadir
1c0442c4f6 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (68 of 68 strings)

Translation: opensourcepos/giftcards
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/giftcards/ckb/
2025-05-10 02:04:32 +02:00
Mohamed-Qadir
8bc4ee3792 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (45 of 45 strings)

Translation: opensourcepos/module
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/module/ckb/
2025-05-10 02:04:31 +02:00
Mohamed-Qadir
c200561eb5 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (117 of 117 strings)

Translation: opensourcepos/items
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/items/ckb/
2025-05-10 02:04:19 +02:00
Mohamed-Qadir
a55d5b415e Translated using Weblate (Kurdish (Central))
Currently translated at 73.3% (33 of 45 strings)

Translation: opensourcepos/module
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/module/ckb/
2025-05-09 19:33:42 +02:00
Mohamed-Qadir
f31d004fb7 Translated using Weblate (Kurdish (Central))
Currently translated at 55.2% (21 of 38 strings)

Translation: opensourcepos/item_kits
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/item_kits/ckb/
2025-05-09 19:33:41 +02:00
Mohamed-Qadir
40e4ad3d38 Translated using Weblate (Kurdish (Central))
Currently translated at 35.8% (42 of 117 strings)

Translation: opensourcepos/items
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/items/ckb/
2025-05-09 19:33:41 +02:00
Mohamed-Qadir
7658ca8dd2 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (68 of 68 strings)

Translation: opensourcepos/giftcards
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/giftcards/ckb/
2025-05-09 19:33:41 +02:00
Mohamed-Qadir
f38272cb59 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (12 of 12 strings)

Translation: opensourcepos/login
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/login/ckb/
2025-05-09 19:33:40 +02:00
Mohamed-Qadir
dca3cdeaf5 Translated using Weblate (Kurdish (Central))
Currently translated at 31.1% (14 of 45 strings)

Translation: opensourcepos/module
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/module/ckb/
2025-05-09 19:11:03 +02:00
Mohamed-Qadir
41eb07caec Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (145 of 145 strings)

Translation: opensourcepos/reports
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/reports/ckb/
2025-05-09 19:11:03 +02:00
Mohamed-Qadir
766c9bb0f2 Translated using Weblate (Kurdish (Central))
Currently translated at 33.3% (39 of 117 strings)

Translation: opensourcepos/items
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/items/ckb/
2025-05-09 19:11:02 +02:00
Mohamed-Qadir
7113e1167c Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (55 of 55 strings)

Translation: opensourcepos/receivings
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/receivings/ckb/
2025-05-09 19:11:02 +02:00
Mohamed-Qadir
eaeb9cb426 Translated using Weblate (Kurdish (Central))
Currently translated at 89.7% (61 of 68 strings)

Translation: opensourcepos/giftcards
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/giftcards/ckb/
2025-05-09 19:11:01 +02:00
Mohamed-Qadir
1971519629 Translated using Weblate (Kurdish (Central))
Currently translated at 31.5% (12 of 38 strings)

Translation: opensourcepos/item_kits
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/item_kits/ckb/
2025-05-09 19:11:00 +02:00
Mohamed-Qadir
b4e010dab8 Translated using Weblate (Kurdish (Central))
Currently translated at 33.8% (23 of 68 strings)

Translation: opensourcepos/giftcards
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/giftcards/ckb/
2025-05-09 17:52:39 +02:00
Mohamed-Qadir
75e709d0b5 Translated using Weblate (Kurdish (Central))
Currently translated at 51.7% (75 of 145 strings)

Translation: opensourcepos/reports
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/reports/ckb/
2025-05-09 17:52:39 +02:00
Mohamed-Qadir
605f550666 Translated using Weblate (Kurdish (Central))
Currently translated at 20.0% (9 of 45 strings)

Translation: opensourcepos/module
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/module/ckb/
2025-05-09 17:52:38 +02:00
Mohamed-Qadir
bc55908af2 Translated using Weblate (Kurdish (Central))
Currently translated at 20.5% (24 of 117 strings)

Translation: opensourcepos/items
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/items/ckb/
2025-05-09 17:52:38 +02:00
Mohamed-Qadir
707339f3b5 Translated using Weblate (Kurdish (Central))
Currently translated at 27.9% (19 of 68 strings)

Translation: opensourcepos/giftcards
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/giftcards/ckb/
2025-05-09 16:40:06 +02:00
Mohamed-Qadir
d0bb7998a9 Translated using Weblate (Kurdish (Central))
Currently translated at 18.8% (22 of 117 strings)

Translation: opensourcepos/items
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/items/ckb/
2025-05-09 16:40:06 +02:00
Mohamed-Qadir
c47ea659bc Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (12 of 12 strings)

Translation: opensourcepos/messages
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/messages/ckb/
2025-05-09 16:40:06 +02:00
Mohamed-Qadir
9b8d6acb79 Translated using Weblate (Kurdish (Central))
Currently translated at 16.2% (19 of 117 strings)

Translation: opensourcepos/items
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/items/ckb/
2025-05-07 22:48:28 +02:00
Mohamed-Qadir
640bdfd0f9 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (21 of 21 strings)

Translation: opensourcepos/suppliers
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/suppliers/ckb/
2025-05-07 22:48:27 +02:00
Mohamed-Qadir
0ea4fcd474 Translated using Weblate (Kurdish (Central))
Currently translated at 42.0% (61 of 145 strings)

Translation: opensourcepos/reports
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/reports/ckb/
2025-05-07 22:48:27 +02:00
Mohamed-Qadir
056add7979 Translated using Weblate (Kurdish (Central))
Currently translated at 11.7% (8 of 68 strings)

Translation: opensourcepos/giftcards
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/giftcards/ckb/
2025-05-07 22:48:27 +02:00
Mohamed-Qadir
4577525566 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (19 of 19 strings)

Translation: opensourcepos/expenses_categories
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/expenses_categories/ckb/
2025-05-07 22:48:27 +02:00
Mohamed-Qadir
75d4d894a4 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (41 of 41 strings)

Translation: opensourcepos/employees
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/employees/ckb/
2025-05-07 22:48:27 +02:00
Mohamed-Qadir
e4b07125d6 Translated using Weblate (Kurdish (Central))
Currently translated at 89.4% (17 of 19 strings)

Translation: opensourcepos/expenses_categories
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/expenses_categories/ckb/
2025-05-07 22:48:27 +02:00
Mohamed-Qadir
2d35346d16 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (47 of 47 strings)

Translation: opensourcepos/expenses
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/expenses/ckb/
2025-05-04 15:27:04 +02:00
Mohamed-Qadir
e0969a8c2b Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (20 of 20 strings)

Translation: opensourcepos/datepicker
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/datepicker/ckb/
2025-05-03 23:45:43 +02:00
Mohamed-Qadir
965f3706da Translated using Weblate (Kurdish (Central))
Currently translated at 36.3% (20 of 55 strings)

Translation: opensourcepos/receivings
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/receivings/ckb/
2025-05-03 23:45:00 +02:00
BudsieBuds
e83c23cf0c Improve code style and PSR-12 compliance (#4204)
* Improve code style and PSR-12 compliance
- refactored code formatting to adhere to PSR-12 guidelines
- standardized coding conventions across the codebase
- added missing framework files and reverted markup changes
- reformatted arrays for enhanced readability
- updated language files for consistent styling and clarity
- minor miscellaneous improvements
2025-05-02 19:37:06 +02:00
Mohamed-Qadir
1456feae58 Translated using Weblate (Kurdish (Central))
Currently translated at 75.0% (15 of 20 strings)

Translation: opensourcepos/datepicker
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/datepicker/ckb/
2025-05-02 12:54:14 +02:00
Mohamed-Qadir
32c0b74e0a Translated using Weblate (Kurdish (Central))
Currently translated at 35.0% (7 of 20 strings)

Translation: opensourcepos/datepicker
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/datepicker/ckb/
2025-05-02 12:05:16 +02:00
Mohamed-Qadir
9ecbe5770c Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (327 of 327 strings)

Translation: opensourcepos/config
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/config/ckb/
2025-05-02 11:31:10 +02:00
Mohamed-Qadir
cedcbf459e Translated using Weblate (Kurdish (Central))
Currently translated at 41.6% (5 of 12 strings)

Translation: opensourcepos/messages
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/messages/ckb/
2025-05-02 11:31:09 +02:00
Mohamed-Qadir
73df6db4f8 Translated using Weblate (Kurdish (Central))
Currently translated at 94.4% (309 of 327 strings)

Translation: opensourcepos/config
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/config/ckb/
2025-05-02 10:55:23 +02:00
Mohamed-Qadir
b0e0b5b429 Translated using Weblate (Kurdish (Central))
Currently translated at 94.1% (308 of 327 strings)

Translation: opensourcepos/config
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/config/ckb/
2025-05-02 10:50:44 +02:00
Mohamed-Qadir
36f41db6aa Translated using Weblate (Kurdish (Central))
Currently translated at 15.5% (7 of 45 strings)

Translation: opensourcepos/module
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/module/ckb/
2025-05-02 10:50:44 +02:00
Mohamed-Qadir
a6c9011954 Translated using Weblate (Kurdish (Central))
Currently translated at 16.2% (19 of 117 strings)

Translation: opensourcepos/items
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/items/ckb/
2025-05-02 10:50:43 +02:00
Mohamed-Qadir
9f19a15845 Translated using Weblate (Kurdish (Central))
Currently translated at 11.7% (8 of 68 strings)

Translation: opensourcepos/giftcards
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/giftcards/ckb/
2025-05-02 10:50:34 +02:00
Mohamed-Qadir
c33bd9a868 Translated using Weblate (Kurdish (Central))
Currently translated at 26.3% (10 of 38 strings)

Translation: opensourcepos/item_kits
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/item_kits/ckb/
2025-05-02 10:50:33 +02:00
Mohamed-Qadir
d4e775d252 Translated using Weblate (Kurdish (Central))
Currently translated at 42.0% (61 of 145 strings)

Translation: opensourcepos/reports
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/reports/ckb/
2025-05-02 10:50:33 +02:00
Mohamed-Qadir
aeda461743 Translated using Weblate (Kurdish (Central))
Currently translated at 90.5% (296 of 327 strings)

Translation: opensourcepos/config
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/config/ckb/
2025-05-02 10:39:47 +02:00
Mohamed-Qadir
c1c74279f1 Translated using Weblate (Kurdish (Central))
Currently translated at 32.4% (47 of 145 strings)

Translation: opensourcepos/reports
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/reports/ckb/
2025-05-02 10:39:46 +02:00
Mohamed-Qadir
aecb4deac0 Translated using Weblate (Kurdish (Central))
Currently translated at 90.2% (295 of 327 strings)

Translation: opensourcepos/config
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/config/ckb/
2025-05-02 10:38:12 +02:00
Mohamed-Qadir
fb2d61fc49 Translated using Weblate (Kurdish (Central))
Currently translated at 23.6% (9 of 38 strings)

Translation: opensourcepos/item_kits
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/item_kits/ckb/
2025-05-02 10:38:11 +02:00
Mohamed-Qadir
ea21abf7a7 Translated using Weblate (Kurdish (Central))
Currently translated at 28.9% (42 of 145 strings)

Translation: opensourcepos/reports
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/reports/ckb/
2025-05-02 10:38:11 +02:00
Mohamed-Qadir
0acd52cfdd Translated using Weblate (Kurdish (Central))
Currently translated at 86.2% (282 of 327 strings)

Translation: opensourcepos/config
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/config/ckb/
2025-05-02 00:56:40 +02:00
Mohamed-Qadir
e2cfcc07a4 Translated using Weblate (Kurdish (Central))
Currently translated at 10.2% (7 of 68 strings)

Translation: opensourcepos/giftcards
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/giftcards/ckb/
2025-05-01 23:35:24 +02:00
Mohamed-Qadir
fc676091c3 Translated using Weblate (Kurdish (Central))
Currently translated at 24.1% (35 of 145 strings)

Translation: opensourcepos/reports
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/reports/ckb/
2025-05-01 23:35:24 +02:00
Mohamed-Qadir
bcf17ae4c3 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (222 of 222 strings)

Translation: opensourcepos/sales
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/sales/ckb/
2025-05-01 23:35:23 +02:00
Mohamed-Qadir
2c598c6e3c Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (46 of 46 strings)

Translation: opensourcepos/cashups
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/cashups/ckb/
2025-05-01 23:35:23 +02:00
Mohamed-Qadir
6139659c94 Translated using Weblate (Kurdish (Central))
Currently translated at 75.2% (246 of 327 strings)

Translation: opensourcepos/config
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/config/ckb/
2025-05-01 23:35:22 +02:00
Mohamed-Qadir
17c14c8a41 Translated using Weblate (Kurdish (Central))
Currently translated at 8.3% (1 of 12 strings)

Translation: opensourcepos/messages
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/messages/ckb/
2025-05-01 23:35:21 +02:00
Mohamed-Qadir
c7223e4b75 Translated using Weblate (Kurdish (Central))
Currently translated at 38.2% (18 of 47 strings)

Translation: opensourcepos/expenses
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/expenses/ckb/
2025-05-01 23:35:21 +02:00
Mohamed-Qadir
7e1895d06c Translated using Weblate (Kurdish (Central))
Currently translated at 34.5% (19 of 55 strings)

Translation: opensourcepos/receivings
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/receivings/ckb/
2025-05-01 23:35:20 +02:00
Mohamed-Qadir
3b959bb1e8 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (79 of 79 strings)

Translation: opensourcepos/taxes
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/taxes/ckb/
2025-05-01 23:35:20 +02:00
Mohamed-Qadir
33b8fc1607 Translated using Weblate (Kurdish (Central))
Currently translated at 14.5% (17 of 117 strings)

Translation: opensourcepos/items
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/items/ckb/
2025-05-01 23:35:20 +02:00
Mohamed-Qadir
0f5718e53e Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (46 of 46 strings)

Translation: opensourcepos/cashups
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/cashups/ckb/
2025-04-30 13:17:01 +02:00
Mohamed-Qadir
42feed19a0 Translated using Weblate (Kurdish (Central))
Currently translated at 12.1% (27 of 222 strings)

Translation: opensourcepos/sales
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/sales/ckb/
2025-04-30 13:17:01 +02:00
Mohamed-Qadir
bbab34e6ba Translated using Weblate (Kurdish (Central))
Currently translated at 12.7% (7 of 55 strings)

Translation: opensourcepos/receivings
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/receivings/ckb/
2025-04-30 13:17:01 +02:00
Mohamed-Qadir
d6bf2d11a0 Translated using Weblate (Kurdish (Central))
Currently translated at 18.6% (27 of 145 strings)

Translation: opensourcepos/reports
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/reports/ckb/
2025-04-30 13:17:00 +02:00
Mohamed-Qadir
f38661bd76 Translated using Weblate (Kurdish (Central))
Currently translated at 89.1% (41 of 46 strings)

Translation: opensourcepos/cashups
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/cashups/ckb/
2025-04-30 09:05:35 +02:00
Mohamed-Qadir
1fe6cf67f6 Translated using Weblate (Kurdish (Central))
Currently translated at 17.0% (8 of 47 strings)

Translation: opensourcepos/expenses
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/expenses/ckb/
2025-04-30 09:05:35 +02:00
Mohamed-Qadir
45b39cf8c5 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (8 of 8 strings)

Translation: opensourcepos/bootstrap_tables
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/bootstrap_tables/ckb/
2025-04-30 09:05:35 +02:00
Mohamed-Qadir
6056ebf9d4 Translated using Weblate (Kurdish (Central))
Currently translated at 17.9% (26 of 145 strings)

Translation: opensourcepos/reports
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/reports/ckb/
2025-04-30 09:05:35 +02:00
Mohamed-Qadir
9726b46b15 Translated using Weblate (Kurdish (Central))
Currently translated at 12.8% (15 of 117 strings)

Translation: opensourcepos/items
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/items/ckb/
2025-04-30 09:05:35 +02:00
Mohamed-Qadir
16307105a4 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (2 of 2 strings)

Translation: opensourcepos/error
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/error/ckb/
2025-04-30 09:05:35 +02:00
Mohamed-Qadir
86325263bc Translated using Weblate (Kurdish (Central))
Currently translated at 95.1% (39 of 41 strings)

Translation: opensourcepos/employees
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/employees/ckb/
2025-04-30 09:05:35 +02:00
Mohamed-Qadir
0fd1bd9b50 Translated using Weblate (Kurdish (Central))
Currently translated at 22.3% (73 of 327 strings)

Translation: opensourcepos/config
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/config/ckb/
2025-04-30 09:05:35 +02:00
Mohamed-Qadir
0339ed8292 Translated using Weblate (Kurdish (Central))
Currently translated at 3.7% (3 of 79 strings)

Translation: opensourcepos/taxes
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/taxes/ckb/
2025-04-30 09:05:35 +02:00
Mohamed-Qadir
2b6d5eae77 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (53 of 53 strings)

Translation: opensourcepos/customers
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/customers/ckb/
2025-04-30 09:05:35 +02:00
Mohamed-Qadir
b0c71621a9 Translated using Weblate (Kurdish (Central))
Currently translated at 16.6% (2 of 12 strings)

Translation: opensourcepos/login
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/login/ckb/
2025-04-30 09:05:35 +02:00
Mohamed-Qadir
b9c97324fa Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (29 of 29 strings)

Translation: opensourcepos/attributes
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/attributes/ckb/
2025-04-30 09:05:35 +02:00
Mohamed-Qadir
546b90e5f7 Translated using Weblate (Kurdish (Central))
Currently translated at 21.0% (8 of 38 strings)

Translation: opensourcepos/item_kits
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/item_kits/ckb/
2025-04-30 09:05:35 +02:00
Mohamed-Qadir
205346ff90 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (21 of 21 strings)

Translation: opensourcepos/suppliers
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/suppliers/ckb/
2025-04-30 09:05:35 +02:00
Mohamed-Qadir
edb0bcf206 Translated using Weblate (Kurdish (Central))
Currently translated at 7.3% (5 of 68 strings)

Translation: opensourcepos/giftcards
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/giftcards/ckb/
2025-04-30 09:05:35 +02:00
Mohamed-Qadir
6987e14147 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (7 of 7 strings)

Translation: opensourcepos/enum
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/enum/ckb/
2025-04-30 09:05:35 +02:00
odiea
d5910f2e75 Fix ajax cashup total (#4238) 2025-04-27 09:31:46 +02:00
odiea
7fb75dbea9 Fix reports to show table details (#4231) 2025-04-22 17:51:31 +02:00
diego-ramos
febe5109f0 Fix error when sending a receipt of a sale without invoice (#4229) 2025-04-21 18:21:30 +02:00
jekkos
a32519fe4a Fix password change submission (#1479) 2025-04-20 18:53:32 +02:00
jekkos
e0cb950083 Fix datetime rendering (#4226) (#4227) 2025-04-20 18:42:12 +02:00
BudsieBuds
9c963814dd Some bug fixes (#4225)
- use unminified login css since gulp doesn't minify it
- adjust container max width to bootstrap 5's container-xxl
- add rtl css to bootstrap theme, to match bootswatch standards
2025-04-20 18:27:36 +02:00
BudsieBuds
2fec49e7df Enhance license handling (#4223)
- automate license updates
- license text rendered in monospace font
- removed old bower license generation code
2025-04-19 20:20:50 +02:00
BudsieBuds
1bdc19f14f Convert menu icons to SVG (#4220)
* Convert menu icons to SVG
- replaced png images with svg
- 20% decrease in file size, improving load times
- removed 384 unused files from repo

* Transferred package to organisation
2025-04-18 19:48:19 +02:00
BudsieBuds
02d63fe067 Update install docs (#4217)
- updated to show support for php 8.4
2025-04-16 07:17:28 +02:00
BudsieBuds
3e996b7818 Update language names (#4218) 2025-04-16 07:16:28 +02:00
BudsieBuds
fc37848fa7 Add default bootstrap to themes (#4219)
- also update bootstrap
2025-04-16 07:15:27 +02:00
Omer Qadir
477942beea Translated using Weblate (Kurdish (Central))
Currently translated at 5.2% (1 of 19 strings)

Translation: opensourcepos/expenses_categories
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/expenses_categories/ckb/
2025-04-15 22:11:51 +02:00
Omer Qadir
f7e12d6ba1 Translated using Weblate (Kurdish (Central))
Currently translated at 21.0% (8 of 38 strings)

Translation: opensourcepos/item_kits
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/item_kits/ckb/
2025-04-15 22:11:51 +02:00
Omer Qadir
a0f49d70b1 Translated using Weblate (Kurdish (Central))
Currently translated at 5.8% (4 of 68 strings)

Translation: opensourcepos/giftcards
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/giftcards/ckb/
2025-04-15 22:11:51 +02:00
Omer Qadir
66502af0ad Translated using Weblate (Kurdish (Central))
Currently translated at 11.7% (26 of 222 strings)

Translation: opensourcepos/sales
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/sales/ckb/
2025-04-15 22:11:51 +02:00
Omer Qadir
b099161dd1 Translated using Weblate (Kurdish (Central))
Currently translated at 3.7% (3 of 79 strings)

Translation: opensourcepos/taxes
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/taxes/ckb/
2025-04-15 22:11:51 +02:00
Omer Qadir
2e2bbf35b9 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (53 of 53 strings)

Translation: opensourcepos/customers
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/customers/ckb/
2025-04-15 22:11:51 +02:00
Omer Qadir
bc8c42ee0d Translated using Weblate (Kurdish (Central))
Currently translated at 11.1% (13 of 117 strings)

Translation: opensourcepos/items
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/items/ckb/
2025-04-15 22:11:51 +02:00
Omer Qadir
2b361aaaed Translated using Weblate (Kurdish (Central))
Currently translated at 17.2% (25 of 145 strings)

Translation: opensourcepos/reports
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/reports/ckb/
2025-04-15 22:11:51 +02:00
BudsieBuds
82f0e75bf0 Fix PHP 8.4 errors (#4200) 2025-04-15 20:38:52 +02:00
Omer Qadir
4d8403eb2b Translated using Weblate (Kurdish (Central))
Currently translated at 50.9% (27 of 53 strings)

Translation: opensourcepos/customers
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/customers/ckb/
2025-04-15 16:55:14 +02:00
Omer Qadir
d89cf3c9ad Translated using Weblate (Kurdish (Central))
Currently translated at 19.5% (8 of 41 strings)

Translation: opensourcepos/employees
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/employees/ckb/
2025-04-15 16:55:14 +02:00
Omer Qadir
adfd708613 Translated using Weblate (Kurdish (Central))
Currently translated at 11.2% (25 of 222 strings)

Translation: opensourcepos/sales
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/sales/ckb/
2025-04-15 16:55:14 +02:00
Omer Qadir
4166ee96d5 Translated using Weblate (Kurdish (Central))
Currently translated at 10.9% (6 of 55 strings)

Translation: opensourcepos/receivings
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/receivings/ckb/
2025-04-15 16:55:13 +02:00
Omer Qadir
123606e842 Translated using Weblate (Kurdish (Central))
Currently translated at 8.5% (4 of 47 strings)

Translation: opensourcepos/expenses
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/expenses/ckb/
2025-04-15 16:55:13 +02:00
Omer Qadir
9d02e288e7 Translated using Weblate (Kurdish (Central))
Currently translated at 10.2% (12 of 117 strings)

Translation: opensourcepos/items
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/items/ckb/
2025-04-15 16:55:12 +02:00
Omer Qadir
c7f379f8a4 Translated using Weblate (Kurdish (Central))
Currently translated at 18.4% (7 of 38 strings)

Translation: opensourcepos/item_kits
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/item_kits/ckb/
2025-04-15 16:55:12 +02:00
Omer Qadir
229685f8e0 Translated using Weblate (Kurdish (Central))
Currently translated at 16.5% (24 of 145 strings)

Translation: opensourcepos/reports
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/reports/ckb/
2025-04-15 16:55:11 +02:00
Omer Qadir
d10b38a03b Translated using Weblate (Kurdish (Central))
Currently translated at 12.4% (18 of 145 strings)

Translation: opensourcepos/reports
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/reports/ckb/
2025-04-15 13:32:59 +02:00
Omer Qadir
264a449496 Translated using Weblate (Kurdish (Central))
Currently translated at 14.6% (6 of 41 strings)

Translation: opensourcepos/employees
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/employees/ckb/
2025-04-15 13:32:59 +02:00
Omer Qadir
12a57d5701 Translated using Weblate (Kurdish (Central))
Currently translated at 10.5% (4 of 38 strings)

Translation: opensourcepos/item_kits
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/item_kits/ckb/
2025-04-15 13:32:59 +02:00
Omer Qadir
27f769e3f4 Translated using Weblate (Kurdish (Central))
Currently translated at 9.4% (11 of 117 strings)

Translation: opensourcepos/items
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/items/ckb/
2025-04-15 13:32:58 +02:00
Omer Qadir
fc60a09f28 Translated using Weblate (Kurdish (Central))
Currently translated at 5.6% (3 of 53 strings)

Translation: opensourcepos/customers
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/customers/ckb/
2025-04-15 13:32:58 +02:00
Omer Qadir
59798cae28 Translated using Weblate (Kurdish (Central))
Currently translated at 4.4% (3 of 68 strings)

Translation: opensourcepos/giftcards
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/giftcards/ckb/
2025-04-15 13:32:58 +02:00
Omer Qadir
7a170b7f7f Translated using Weblate (Kurdish (Central))
Currently translated at 9.9% (22 of 222 strings)

Translation: opensourcepos/sales
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/sales/ckb/
2025-04-15 13:32:57 +02:00
Omer Qadir
9c6023e7f0 Translated using Weblate (Kurdish (Central))
Currently translated at 2.5% (2 of 79 strings)

Translation: opensourcepos/taxes
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/taxes/ckb/
2025-04-15 13:32:57 +02:00
Omer Qadir
70352ba954 Translated using Weblate (Kurdish (Central))
Currently translated at 8.2% (27 of 327 strings)

Translation: opensourcepos/config
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/config/ckb/
2025-04-15 13:32:57 +02:00
Omer Qadir
01d0555586 Translated using Weblate (Kurdish (Central))
Currently translated at 100.0% (21 of 21 strings)

Translation: opensourcepos/suppliers
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/suppliers/ckb/
2025-04-15 13:32:56 +02:00
Omer Qadir
22203a83d7 Translated using Weblate (Kurdish (Central))
Currently translated at 7.2% (4 of 55 strings)

Translation: opensourcepos/receivings
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/receivings/ckb/
2025-04-15 13:32:55 +02:00
Omer Qadir
2d99655400 Translated using Weblate (Kurdish)
Currently translated at 7.6% (9 of 117 strings)

Translation: opensourcepos/items
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/items/ku/
2025-04-15 11:55:07 +02:00
Omer Qadir
b8be47d4ef Translated using Weblate (Kurdish)
Currently translated at 11.7% (17 of 145 strings)

Translation: opensourcepos/reports
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/reports/ku/
2025-04-15 11:55:07 +02:00
Omer Qadir
fd86e08e7e Translated using Weblate (Kurdish)
Currently translated at 1.4% (1 of 68 strings)

Translation: opensourcepos/giftcards
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/giftcards/ku/
2025-04-15 11:55:07 +02:00
Omer Qadir
a1d2d19a5b Translated using Weblate (Kurdish)
Currently translated at 33.3% (7 of 21 strings)

Translation: opensourcepos/suppliers
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/suppliers/ku/
2025-04-15 11:55:07 +02:00
BudsieBuds
766b3b967e Convert language ku to ckb (#4211)
- convert ku (Kurdish) to ckb (Central Kurdish)
- replaced tabs with spaces
- replace single quotation marks with double
2025-04-15 08:31:40 +02:00
BudsieBuds
a62bef53b4 Add Kurdish language option to UI (#4210) 2025-04-14 18:33:05 +02:00
jekkos
eb643cc74c Added translation using Weblate (Kurdish (Central, Iraq)) 2025-04-13 00:47:11 +02:00
jekkos
a0fb5f317c Added translation using Weblate (Kurdish (Central, Iraq)) 2025-04-13 00:46:35 +02:00
jekkos
1f7da93189 Added translation using Weblate (Kurdish (Central, Iraq)) 2025-04-13 00:45:28 +02:00
jekkos
ed00395243 Added translation using Weblate (Kurdish (Central, Iraq)) 2025-04-13 00:44:04 +02:00
jekkos
f47f474335 Added translation using Weblate (Kurdish (Central, Iraq)) 2025-04-13 00:43:47 +02:00
jekkos
e0cebb86bd Added translation using Weblate (Kurdish (Central, Iraq)) 2025-04-13 00:43:29 +02:00
jekkos
78d0193121 Added translation using Weblate (Kurdish (Central, Iraq)) 2025-04-13 00:28:07 +02:00
jekkos
3d5d2ebb89 Added translation using Weblate (Kurdish (Central, Iraq)) 2025-04-13 00:27:06 +02:00
jekkos
075d261758 Added translation using Weblate (Kurdish (Central, Iraq)) 2025-04-13 00:26:32 +02:00
jekkos
8e9c3d7df5 Added translation using Weblate (Kurdish (Central, Iraq)) 2025-04-13 00:25:56 +02:00
jekkos
1428ad2789 Added translation using Weblate (Kurdish (Central, Iraq)) 2025-04-13 00:25:38 +02:00
jekkos
89919c88a2 Added translation using Weblate (Kurdish (Central, Iraq)) 2025-04-13 00:25:03 +02:00
jekkos
31edc87348 Added translation using Weblate (Kurdish (Central, Iraq)) 2025-04-13 00:24:13 +02:00
jekkos
8565e73f0c Added translation using Weblate (Kurdish (Central, Iraq)) 2025-04-13 00:22:53 +02:00
jekkos
942ea19fe4 Added translation using Weblate (Kurdish (Central, Iraq)) 2025-04-13 00:22:25 +02:00
Omer Qadir
c4fbdb1231 Translated using Weblate (Kurdish (Central, Iraq))
Currently translated at 100.0% (85 of 85 strings)

Translation: opensourcepos/common
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/common/ckb_IQ/
2025-04-12 15:15:31 +02:00
Omer Qadir
fd441d57a1 Translated using Weblate (Kurdish (Central, Iraq))
Currently translated at 88.2% (75 of 85 strings)

Translation: opensourcepos/common
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/common/ckb_IQ/
2025-04-12 14:21:16 +02:00
Omer Qadir
2080f5b187 Translated using Weblate (Kurdish (Central, Iraq))
Currently translated at 74.1% (63 of 85 strings)

Translation: opensourcepos/common
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/common/ckb_IQ/
2025-04-12 14:16:15 +02:00
Omer Qadir
ad2902cb19 Translated using Weblate (Kurdish (Central, Iraq))
Currently translated at 72.9% (62 of 85 strings)

Translation: opensourcepos/common
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/common/ckb_IQ/
2025-04-12 04:10:00 +02:00
Omer Qadir
606b9461d2 Translated using Weblate (Kurdish (Central, Iraq))
Currently translated at 40.0% (34 of 85 strings)

Translation: opensourcepos/common
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/common/ckb_IQ/
2025-04-12 01:13:11 +02:00
jekkos
d37016a9f5 Added translation using Weblate (Kurdish (Central, Iraq)) 2025-04-12 00:29:25 +02:00
objecttothis
09530c1609 Feature bump ci to 4.6.0 (#4197)
* Replace tabs with spaces

Signed-off-by: objecttothis <objecttothis@gmail.com>

* Composer package bumps

- Bump codeigniter4/framework to 4.6.0
- Bump codeIgniter/coding-standard to ^1.8
- Bump codeigniter4/devkit to ^1.3
- Updated framework files required by CI4.6.0
- Removed Deprecated variables
- Added new file in the repo from framework

Signed-off-by: objecttothis <objecttothis@gmail.com>

* Reflect PHP 8.4 support
Updates for PHP 8.4 support introduced with the upgrade to CodeIgniter 4.6.x

* Update INSTALL.md

- Revert PHP 8.4 support for now.
- Removed extra space before comma

---------

Signed-off-by: objecttothis <objecttothis@gmail.com>
Co-authored-by: BudsieBuds <bas_hubers@hotmail.com>
2025-04-03 14:16:06 +04:00
dependabot[bot]
2c9ae36247 Bump jspdf and jspdf-autotable (#4190)
Bumps [jspdf](https://github.com/MrRio/jsPDF) and [jspdf-autotable](https://github.com/simonbengtsson/jsPDF-AutoTable). These dependencies needed to be updated together.

Updates `jspdf` from 2.5.1 to 3.0.1
- [Release notes](https://github.com/MrRio/jsPDF/releases)
- [Changelog](https://github.com/parallax/jsPDF/blob/master/RELEASE.md)
- [Commits](https://github.com/MrRio/jsPDF/compare/v2.5.1...v3.0.1)

Updates `jspdf-autotable` from 3.8.2 to 5.0.2
- [Release notes](https://github.com/simonbengtsson/jsPDF-AutoTable/releases)
- [Commits](https://github.com/simonbengtsson/jsPDF-AutoTable/compare/v3.8.2...v5.0.2)

---
updated-dependencies:
- dependency-name: jspdf
  dependency-type: direct:production
- dependency-name: jspdf-autotable
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-31 13:03:50 +04:00
dependabot[bot]
69a507f879 Bump canvg from 3.0.10 to 3.0.11 (#4189)
Bumps [canvg](https://github.com/canvg/canvg) from 3.0.10 to 3.0.11.
- [Release notes](https://github.com/canvg/canvg/releases)
- [Changelog](https://github.com/canvg/canvg/blob/v3.0.11/CHANGELOG.md)
- [Commits](https://github.com/canvg/canvg/commits/v3.0.11)

---
updated-dependencies:
- dependency-name: canvg
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-31 11:40:49 +04:00
jekkos
e1e3a30fc0 Add CI4 coding standards linter (#3708) (#4198) 2025-03-31 11:39:44 +04:00
Almubaraq Ratomi
c1906727ec Translated using Weblate (Indonesian)
Currently translated at 100.0% (12 of 12 strings)

Translation: opensourcepos/login
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/login/id/
2025-03-28 22:21:40 +01:00
Almubaraq Ratomi
8dde4c3425 Translated using Weblate (Indonesian)
Currently translated at 100.0% (145 of 145 strings)

Translation: opensourcepos/reports
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/reports/id/
2025-03-28 22:21:40 +01:00
jekkos
f399714dc3 Add .env to dist zip (#4194) 2025-03-28 22:19:26 +01:00
objecttothis
e90b5b87da Replace tabs with spaces (#4196)
Signed-off-by: objecttothis <objecttothis@gmail.com>
2025-03-28 21:24:21 +04:00
jekkos
69bcd84699 Update INSTALL instructions (#4194) 2025-03-26 19:43:34 +01:00
jekkos
f3fae110d6 Update install instructions + remove build on tag 2025-03-23 22:49:27 +01:00
jekkos
e9e82e4e50 Set release version to 3.4 2025-03-11 21:08:11 +01:00
Chathura Dilushanka
2bd38737e1 Update locale_config.php 2025-03-04 21:36:39 +01:00
JoseLuisKukMagana
2a789bb583 Translated using Weblate (Spanish (Mexico))
Currently translated at 100.0% (145 of 145 strings)

Translation: opensourcepos/reports
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/reports/es_MX/
2025-03-03 00:22:29 +01:00
JoseLuisKukMagana
e8a79910fe Translated using Weblate (Spanish (Mexico))
Currently translated at 100.0% (53 of 53 strings)

Translation: opensourcepos/customers
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/customers/es_MX/
2025-03-03 00:22:29 +01:00
JoseLuisKukMagana
9bfe6c7c4e Translated using Weblate (Spanish (Mexico))
Currently translated at 98.8% (84 of 85 strings)

Translation: opensourcepos/common
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/common/es_MX/
2025-03-03 00:22:29 +01:00
JoseLuisKukMagana
bc0e2c6833 Translated using Weblate (Spanish (Mexico))
Currently translated at 100.0% (21 of 21 strings)

Translation: opensourcepos/suppliers
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/suppliers/es_MX/
2025-03-03 00:22:29 +01:00
JoseLuisKukMagana
196375d594 Translated using Weblate (Spanish (Mexico))
Currently translated at 100.0% (327 of 327 strings)

Translation: opensourcepos/config
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/config/es_MX/
2025-03-03 00:22:29 +01:00
JoseLuisKukMagana
fafba87894 Translated using Weblate (Spanish (Mexico))
Currently translated at 100.0% (12 of 12 strings)

Translation: opensourcepos/login
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/login/es_MX/
2025-03-03 00:22:29 +01:00
JoseLuisKukMagana
66a097d9f2 Translated using Weblate (Spanish (Mexico))
Currently translated at 100.0% (38 of 38 strings)

Translation: opensourcepos/item_kits
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/item_kits/es_MX/
2025-03-03 00:22:29 +01:00
JoseLuisKukMagana
f3931577be Translated using Weblate (Spanish (Mexico))
Currently translated at 69.1% (47 of 68 strings)

Translation: opensourcepos/giftcards
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/giftcards/es_MX/
2025-03-03 00:22:29 +01:00
JoseLuisKukMagana
f125960fe2 Translated using Weblate (Spanish (Mexico))
Currently translated at 99.5% (221 of 222 strings)

Translation: opensourcepos/sales
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/sales/es_MX/
2025-03-03 00:22:29 +01:00
JoseLuisKukMagana
787977ed3e Translated using Weblate (Spanish (Mexico))
Currently translated at 100.0% (79 of 79 strings)

Translation: opensourcepos/taxes
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/taxes/es_MX/
2025-03-03 00:22:29 +01:00
JoseLuisKukMagana
502b5fd6b9 Translated using Weblate (Spanish (Mexico))
Currently translated at 100.0% (41 of 41 strings)

Translation: opensourcepos/employees
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/employees/es_MX/
2025-03-03 00:22:29 +01:00
JoseLuisKukMagana
ec2b941f3f Translated using Weblate (Spanish (Mexico))
Currently translated at 100.0% (55 of 55 strings)

Translation: opensourcepos/receivings
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/receivings/es_MX/
2025-03-03 00:22:29 +01:00
JoseLuisKukMagana
8723274418 Translated using Weblate (Spanish (Mexico))
Currently translated at 55.5% (65 of 117 strings)

Translation: opensourcepos/items
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/items/es_MX/
2025-03-03 00:22:29 +01:00
jekkos
cf73ffa825 Fix attribute dropdown delete (#4176) 2025-03-01 00:37:23 +01:00
jekkos
eeaa693ede Fix for giftcard numbering (#4182) 2025-02-15 01:12:35 +01:00
jekkos
1378794e7e Revert "Use app language for current_lang (#4175)"
This reverts commit 19974bc8e0.
2025-02-15 01:10:16 +01:00
jekkos
d1d8aa0401 Fix greyed out submit after validation (#4174) 2025-02-15 01:09:53 +01:00
jekkos
882f3b4522 Fix table header translations (#4175) 2025-02-15 01:08:19 +01:00
jekkos
19974bc8e0 Use app language for current_lang (#4175) 2025-02-10 08:53:11 +01:00
SONKO ABDOU
d0b2b3e80b Translated using Weblate (French)
Currently translated at 100.0% (12 of 12 strings)

Translation: opensourcepos/login
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/login/fr/
2025-02-09 20:35:46 +01:00
BudsieBuds
57c36e7ba7 Fixes for CHANGELOG 2025-02-08 00:00:56 +01:00
jekkos
8516ffe216 Add php-json to dependency list (#4168) 2025-02-07 23:59:59 +01:00
jekkos
534f7361d6 Update CHANGELOG 2025-02-06 23:25:39 +01:00
jekkos
5609859fdf Fix attribute dropdown creation (#4171) 2025-02-05 22:24:33 +01:00
jekkos
c6c5fcac26 Fix sales tax summary with time filter (#4166) 2025-02-05 22:01:59 +01:00
BudsieBuds
4d9cd80f8b Random fixes #2
- change old directories to new (ci4)
- updated documentation for clarity
2025-02-05 21:58:28 +01:00
jekkos
2924a889c7 Remove localhost in port mapping (#4168) 2025-02-04 12:11:54 +01:00
1874 changed files with 118855 additions and 117540 deletions

View File

@@ -21,4 +21,3 @@ node_modules/
*.log
app/writable/session/*
!app/writable/session/index.html

View File

@@ -1,22 +1,15 @@
# editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = tab
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 120
tab_width = 4
[{*.cjs,*.js}]
indent_style = tab
[{*.ctp,*.hphp,*.inc,*.module,*.php,*.php4,*.php5,*.phtml}]
indent_style = tab
[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,bowerrc,composer.lock,jest.config}]
indent_style = tab
[{*.htm,*.html,*.ng,*.sht,*.shtm,*.shtml}]
indent_style = tab
[*.md]
trim_trailing_whitespace = false

86
.env
View File

@@ -1,86 +0,0 @@
#--------------------------------------------------------------------
# ENVIRONMENT
#--------------------------------------------------------------------
CI_ENVIRONMENT = production
CI_DEBUG = false
#--------------------------------------------------------------------
# APP
#--------------------------------------------------------------------
app.appTimezone = 'UTC'
#--------------------------------------------------------------------
# DATABASE
#--------------------------------------------------------------------
database.default.hostname = 'localhost'
database.default.database = 'ospos'
database.default.username = 'admin'
database.default.password = 'pointofsale'
database.default.DBDriver = 'MySQLi'
database.default.DBPrefix = 'ospos_'
database.default.port = 3306
database.development.hostname = 'localhost'
database.development.database = 'ospos'
database.development.username = 'admin'
database.development.password = 'pointofsale'
database.development.DBDriver = 'MySQLi'
database.development.DBPrefix = 'ospos_'
database.development.port = 3306
database.tests.hostname = 'localhost'
database.tests.database = 'ospos'
database.tests.username = 'admin'
database.tests.password = 'pointofsale'
database.tests.DBDriver = 'MySQLi'
database.tests.DBPrefix = 'ospos_'
database.tests.charset = utf8mb4
database.tests.DBCollat = utf8mb4_general_ci
database.tests.port = 3306
#--------------------------------------------------------------------
# EMAIL
#--------------------------------------------------------------------
email.SMTPHost = ''
email.SMTPUser = ''
email.SMTPPass = ''
email.SMTPPort =
email.SMTPTimeout = 5
email.SMTPCrypto = 'tls'
#--------------------------------------------------------------------
# ENCRYPTION
#--------------------------------------------------------------------
encryption.key = ''
#--------------------------------------------------------------------
# HONEYPOT
#--------------------------------------------------------------------
honeypot.hidden = true
honeypot.label = 'Fill This Field'
honeypot.name = 'honeypot'
honeypot.template = '<label>{label}</label><input type="text" name="{name}" value=""/>'
honeypot.container = '<div style="display:none">{template}</div>'
#--------------------------------------------------------------------
# LOGGER
# - 0 = Disables logging, Error logging TURNED OFF
# - 1 = Emergency Messages - System is unusable
# - 2 = Alert Messages - Action Must Be Taken Immediately
# - 3 = Critical Messages - Application component unavailable, unexpected exception.
# - 4 = Runtime Errors - Don't need immediate action, but should be monitored.
# - 5 = Warnings - Exceptional occurrences that are not errors.
# - 6 = Notices - Normal but significant events.
# - 7 = Info - Interesting events, like user logging in, etc.
# - 8 = Debug - Detailed debug information.
# - 9 = All Messages
#--------------------------------------------------------------------
logger.threshold = 0
app.db_log_enabled = false
app.db_log_only_long = false

View File

@@ -37,16 +37,16 @@ encryption.key = ''
#--------------------------------------------------------------------
# LOGGER
# - 0 = Disables logging, Error logging TURNED OFF
# - 1 = Emergency Messages - System is unusable
# - 2 = Alert Messages - Action Must Be Taken Immediately
# - 3 = Critical Messages - Application component unavailable, unexpected exception.
# - 4 = Runtime Errors - Don't need immediate action, but should be monitored.
# - 5 = Warnings - Exceptional occurrences that are not errors.
# - 6 = Notices - Normal but significant events.
# - 7 = Info - Interesting events, like user logging in, etc.
# - 8 = Debug - Detailed debug information.
# - 9 = All Messages
# - 0 = Disables logging, Error logging TURNED OFF
# - 1 = Emergency Messages - System is unusable
# - 2 = Alert Messages - Action Must Be Taken Immediately
# - 3 = Critical Messages - Application component unavailable, unexpected exception.
# - 4 = Runtime Errors - Don't need immediate action, but should be monitored.
# - 5 = Warnings - Exceptional occurrences that are not errors.
# - 6 = Notices - Normal but significant events.
# - 7 = Info - Interesting events, like user logging in, etc.
# - 8 = Debug - Detailed debug information.
# - 9 = All Messages
#--------------------------------------------------------------------
logger.threshold = 0
@@ -59,5 +59,5 @@ app.db_log_enabled = false
honeypot.hidden = true
honeypot.label = 'Fill This Field'
honeypot.name = 'honeypot'
honeypot.template = '<label>{label}</label><input type="text" name="{name}" value=""/>'
honeypot.template = '<label>{label}</label><input type="text" name="{name}" value="">'
honeypot.container = '<div style="display:none">{template}</div>'

View File

@@ -42,10 +42,12 @@ body:
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
- development (unreleased)
default: 0
validations:
required: true

63
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
name: Coding Standards
on:
push:
paths:
- '**.php'
- 'spark'
- '.github/workflows/test-coding-standards.yml'
pull_request:
paths:
- '**.php'
- 'spark'
- '.github/workflows/test-coding-standards.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
jobs:
lint:
name: PHP ${{ matrix.php-version }} Lint with PHP CS Fixer
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
php-version:
- '8.1'
- '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: tokenizer
coverage: none
- 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: Run lint on `app/`, `public/`
run: vendor/bin/php-cs-fixer fix --verbose --ansi --dry-run --config=.php-cs-fixer.no-header.php --using-cache=no --diff

View File

@@ -28,3 +28,7 @@ jobs:
uses: dbfx/github-phplint/8.3@master
with:
folder-to-exclude: "! -path \"./vendor/*\" ! -path \"./folder/excluded/*\""
- name: PHP Lint 8.4
uses: dbfx/github-phplint/8.4@master
with:
folder-to-exclude: "! -path \"./vendor/*\" ! -path \"./folder/excluded/*\""

116
.github/workflows/unit-tests.yml vendored Normal file
View File

@@ -0,0 +1,116 @@
name: Unit Tests
on:
push:
paths:
- 'app/**/*.php'
- 'tests/**/*.php'
- '.github/workflows/unit-tests.yml'
pull_request:
paths:
- 'app/**/*.php'
- 'tests/**/*.php'
- '.github/workflows/unit-tests.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
jobs:
test:
name: PHP ${{ matrix.php-version }} Unit Tests
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
php-version:
- '8.1'
- '8.2'
- '8.3'
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: ospos_test
MYSQL_USER: ospos
MYSQL_PASSWORD: ospos
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping --silent"
--health-interval=10s
--health-timeout=5s
--health-retries=5
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: intl, mysqli, pdo_mysql, mbstring, json, dom, xml
coverage: xdebug
- 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 install --no-progress --ansi --no-interaction
- name: Wait for MySQL
run: |
while ! mysqladmin ping -h"127.0.0.1" --silent; do
echo "Waiting for MySQL..."
sleep 1
done
- name: Setup test database
run: |
mysql -h 127.0.0.1 -u root -proot -e "CREATE DATABASE IF NOT EXISTS ospos_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql -h 127.0.0.1 -u root -proot -e "GRANT ALL PRIVILEGES ON ospos_test.* TO 'ospos'@'%' IDENTIFIED BY 'ospos';"
mysql -h 127.0.0.1 -u root -proot -e "FLUSH PRIVILEGES;"
- name: Copy test environment config
run: |
if [ -f ".env.testing" ]; then
cp .env.testing .env
else
cp .env.example .env
fi
- name: Run migrations
run: php spark migrate --all || true
- name: Run unit tests
run: vendor/bin/phpunit --configuration tests/phpunit.xml --testsuite Helpers,Models,Controllers --colors=always --verbose
- name: Generate test report
if: always()
run: |
vendor/bin/phpunit --configuration tests/phpunit.xml --testsuite Helpers,Models,Controllers --log-junit build/logs/junit.xml --coverage-clover build/logs/clover.xml || true
echo "Test run completed"
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-php-${{ matrix.php-version }}
path: build/logs/
retention-days: 30

5
.gitignore vendored
View File

@@ -2,8 +2,13 @@
node_modules
vendor
public/resources
public/images/menubar/*
!public/images/menubar/.gitkeep
public/license/*
!public/license/.gitkeep
app/Config/email.php
npm-debug.log*
.vscode
# Docker
!docker/.env

View File

@@ -40,7 +40,7 @@ IndexIgnore *
</Files>
# prevent access to csv, txt and md files
<FilesMatch "\.(csv|txt|md|yml|json|lock)$">
<FilesMatch "\.(csv|txt|md|yml|json|lock|env)$">
Require all denied
</FilesMatch>
</IfModule>

View File

@@ -29,7 +29,7 @@ $finder = Finder::create()
]);
$overrides = [
// for updating to coding-standard
// For updating to coding-standard
'modernize_strpos' => true,
];

View File

@@ -15,18 +15,21 @@ 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"
- 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
- 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`
- 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"
@@ -49,22 +52,3 @@ deploy:
on:
branch: master
- provider: releases
edge: true
file: dist/opensourcepos.$version.$rev.zip
name: "OpensourcePos $version"
release_notes_file: CHANGELOG.md
prerelease: true
user: jekkos
overwrite: true
api_key:
secure: "KOukL8IFf/uL/BjMyCSKjf2vylydjcWqgEx0eMqFCg3nZ4ybMaOwPORRthIfyT72/FvGX/aoxxEn0uR/AEtb+hYQXHmNS+kZdX72JCe8LpGuZ7FJ5X+Eo9mhJcsmS+smd1sC95DySSc/GolKPo+0WtJYONY/xGCLxm+9Ay4HREg="
branches:
except:
- unstable
- /unstable.*/
on:
tags: true
branch: master

View File

@@ -23,9 +23,9 @@ The build process uses the build tools "npm" and "gulp" to piece everything toge
2. Unzip it and copy the contents into the working folder.
3. Start a terminal session from the root of your working folder. For example, I normally open up the working folder in PHPStorm and run the commands from the Terminal provided by the IDE.
4. Enter the following three commands in sequence:
- `composer install`
- `npm install`
- `npm run build`
- `composer install`
- `npm install`
- `npm run build`
That's all there is to it.

View File

@@ -1,4 +1,7 @@
[unreleased]: https://github.com/opensourcepos/opensourcepos/compare/3.3.9...HEAD
[unreleased]: https://github.com/opensourcepos/opensourcepos/compare/3.4.0...HEAD
[3.4.2]: https://github.com/opensourcepos/opensourcepos/compare/3.4.1...3.4.2
[3.4.1]: https://github.com/opensourcepos/opensourcepos/compare/3.4.0...3.4.1
[3.4.0]: https://github.com/opensourcepos/opensourcepos/compare/3.3.9...3.4.0
[3.3.9]: https://github.com/opensourcepos/opensourcepos/compare/3.3.8...3.3.9
[3.3.8]: https://github.com/opensourcepos/opensourcepos/compare/3.3.7...3.3.8
[3.3.7]: https://github.com/opensourcepos/opensourcepos/compare/3.3.6...3.3.7
@@ -31,6 +34,16 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
## [3.4.0] - 2025-02-06
- Translation updates (Spanish, Indonesian, Swedish, Urdu, Chinese, Thai, French, Dutch)
- PHP 8.x support
- Security fixes (XSS, SQLi)
- Migration to Gulp as buildsystem
- Decimal validation fix
- Sticky header fix
- Receipt sent as attachment
- Barcode generation library upgrade
- Bump framework to CodeIgniter `4.x.x`
- Improve security performance against bots

View File

@@ -2,7 +2,7 @@ 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 a2enmod rewrite
RUN docker-php-ext-install mysqli bcmath intl gd
RUN echo "date.timezone = \"\${PHP_TIMEZONE}\"" > /usr/local/etc/php/conf.d/timezone.ini
@@ -18,11 +18,11 @@ 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 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"]
CMD ["/app/vendor/phpunit/phpunit/phpunit", "/app/test/helpers"]
FROM ospos AS ospos_dev

View File

@@ -1,25 +1,25 @@
## Server Requirements
- PHP version `7.4` is supported, PHP version `≤7.3` is NOT supported. Please note that PHP needs to have the extensions `php-gd`, `php-bcmath`, `php-intl`, `php-openssl`, `php-mbstring` , `php-curl` and `php-xml` installed and enabled. PHP version `8.x` is only supported by the code that is still under development in master branch. An unstable build can be downloaded in the releases section.
- MySQL `5.6` and `5.7` are supported, also MariaDB replacement `10.x` is supported and might offer better performance.
- 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.
- 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.
## Local install
First of all, if you're seeing the message `system folder missing` after launching your browser, or cannot find `database.php`, 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, 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.
1. Download the a [pre-release for a specific branch](https://github.com/opensourcepos/opensourcepos/packages/1047637) 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.
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 `database/database.sql` to create the tables needed.
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 `app/Config/database.php` and modify credentials to connect to your database if needed.
6. Open `app/Config/config.php` and swap the encryption key with your own.
5. Open `.env` file and modify credentials to connect to your database if needed. (First copy .env.example to .env and update)
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.
@@ -63,3 +63,39 @@ Do **not** use below command on live deployments unless you want to tear everyth
If you choose DigitalOcean:
[Through this link](https://m.do.co/c/ac38c262507b), you will get a [**free $100, 60-day credit**](https://m.do.co/c/ac38c262507b). [Check the wiki](https://github.com/opensourcepos/opensourcepos/wiki/Getting-Started-installations) for further instructions on how to install the necessary components.
## One-line Ubuntu Installation
For a fresh Ubuntu server (20.04 LTS or newer), you can install OSPOS directly with:
```bash
curl -sSL https://opensourcepos.org/install | sudo bash
```
> **Note:** This URL redirects to the latest installation script from the official repository. If the redirect is unavailable, use the direct GitHub URL:
> ```bash
> curl -sSL https://raw.githubusercontent.com/opensourcepos/opensourcepos/master/scripts/install-ubuntu.sh | sudo bash
> ```
This script will:
- Install Apache, MariaDB, PHP 8.2 and required extensions
- Create a MySQL database and user with a secure random password
- Download and configure OSPOS
- Set up Apache virtual host with proper permissions
- Display login credentials after completion
**Environment Variables (optional):**
- `DB_NAME` - Database name (default: ospos)
- `DB_USER` - Database user (default: ospos)
- `DB_PASS` - Database password (default: auto-generated)
- `OSPOS_DIR` - Installation directory (default: /var/www/ospos)
- `OSPOS_BRANCH` - Git branch to install (default: master)
- `PHP_VERSION` - PHP version (default: 8.2)
- `APACHE_SERVER_NAME` - Server hostname (default: localhost)
Example with custom settings:
```bash
curl -sSL https://opensourcepos.org/install | DB_PASS=mypassword APACHE_SERVER_NAME=pos.example.com sudo -E bash
```
**Note:** This script is designed for fresh servers. For production use, ensure you configure SSL/TLS certificates after installation.

View File

@@ -2,10 +2,10 @@ MIT License
Copyright (c) 2013-2025 jekkos
Copyright (c) 2017-2025 objecttothis
Copyright (c) 2017-2024 Steve Ireland
Copyright (c) 2017-2025 odiea
Copyright (c) 2018-2024 WebShells
Copyright (c) 2021-2025 BudsieBuds
Copyright (c) 2017-2024 Steve Ireland
Copyright (c) 2018-2024 WebShells
Copyright (c) 2015-2023 FrancescoUK (aka daN4cat)
Copyright (c) 2015-2022 Aamir Shahzad (aka asakpke), RoshanTech, eSite.pk
Copyright (c) 2019-2020 Andriux1990

View File

@@ -94,11 +94,11 @@ NOTE: If you're running non-release code, please make sure you always run the la
- Apache server configurations are SysAdmin issues and not strictly related to OSPOS. Please make sure you can show a "Hello world" HTML page before pointing to OSPOS public directory. Make sure `.htaccess` is correctly configured.
- If the avatar pictures are not shown in items or at item save you get an error, please make sure your `public` and subdirs are assigned to the correct owner and the access permission is set to `750`.
- If the avatar pictures are not shown in items or at item save you get an error, please make sure your `writable` and subdirs are assigned to the correct owner and the access permission is set to `750`.
- If you install OSPOS in Docker behind a proxy that performs `ssloffloading`, you can enable the URL generated to be HTTPS instead of HTTP, by activating the environment variable `FORCE_HTTPS = 1`.
- If you install OSPOS behind a proxy and OSPOS constantly drops your session, consider whitelisting the proxy IP address by setting `$config['proxy_ips'] = '<proxy ip>';` in the [main PHP config file](https://github.com/opensourcepos/opensourcepos/blob/master/application/config/config.php). In extreme instances, changing `$config['sess_match_ip'] = true;` to `FALSE` may also help.
- If you install OSPOS behind a proxy and OSPOS constantly drops your session, consider whitelisting the proxy IP address by setting `public array $proxyIPs = [];` in the [main PHP config file](https://github.com/opensourcepos/opensourcepos/blob/master/app/Config/App.php).
- 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).

View File

@@ -18,7 +18,8 @@ We release patches for security vulnerabilities. Which versions are eligible to
| --------- | -------------------------------------------------- |
| 7.3 | 3.3.5 |
| 9.8 | 3.3.6 |
| 6.8 | 3.4.2 |
## Reporting a Vulnerability
Please report (suspected) security vulnerabilities to **[jekkos@opensourcepos.org](mailto:jekkos@opensourcepos.org)**. 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.

View File

@@ -1,5 +1,8 @@
## How to Upgrade
> [!WARNING]
> Not updated for upcoming CodeIgniter4 release (3.4.0 and subsequent versions).
1. Back up all your current database and OSPOS code.
2. Make sure you have a copy of `application/config/config.php` and `application/config/database.php`.
3. Remove all directories.

View File

@@ -7,52 +7,52 @@ use CodeIgniter\Session\Handlers\DatabaseHandler;
class App extends BaseConfig
{
/**
* This is the code version of the Open Source Point of Sale you're running.
*
* @var string
*/
public string $application_version = '3.4.0-dev';
/**
* This is the code version of the Open Source Point of Sale you're running.
*
* @var string
*/
public string $application_version = '3.4.2';
/**
* This is the commit hash for the version you are currently using.
*
* @var string
*/
public string $commit_sha1 = 'dev';
/**
* This is the commit hash for the version you are currently using.
*
* @var string
*/
public string $commit_sha1 = 'dev';
/**
* Logs are stored in writable/logs
*
* @var bool
*/
public bool $db_log_enabled = false;
/**
* Logs are stored in writable/logs
*
* @var bool
*/
public bool $db_log_enabled = false;
/**
* DB Query Log only long-running queries
*
* @var bool
*/
public bool $db_log_only_long = false;
/**
* DB Query Log only long-running queries
*
* @var bool
*/
public bool $db_log_only_long = false;
/**
* Defines whether to require/reroute to HTTPS
*
* @var bool
*/
public bool $https_on; //Set in the constructor
/**
* Defines whether to require/reroute to HTTPS
*
* @var bool
*/
public bool $https_on; // Set in the constructor
/**
* --------------------------------------------------------------------------
* Base Site URL
* --------------------------------------------------------------------------
*
* URL to your CodeIgniter root. Typically, this will be your base URL,
* WITH a trailing slash:
*
* E.g., http://example.com/
*/
public string $baseURL; //Defined in the constructor
/**
* --------------------------------------------------------------------------
* Base Site URL
* --------------------------------------------------------------------------
*
* URL to your CodeIgniter root. Typically, this will be your base URL,
* WITH a trailing slash:
*
* E.g., http://example.com/
*/
public string $baseURL; // Defined in the constructor
/**
* Allowed Hostnames in the Site URL other than the hostname in the baseURL.
@@ -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~%.:_\-=';
/**
* --------------------------------------------------------------------------
@@ -143,63 +143,64 @@ class App extends BaseConfig
*/
public bool $negotiateLocale = true;
/**
* --------------------------------------------------------------------------
* Supported Locales
* --------------------------------------------------------------------------
*
* If $negotiateLocale is true, this array lists the locales supported
* by the application in descending order of priority. If no match is
* found, the first locale will be used.
*
* IncomingRequest::setLocale() also uses this list.
*
* @var list<string>
*/
public array $supportedLocales = [
'ar-EG',
'ar-LB',
'az',
'bg',
'bs',
'cs',
'da',
'de-CH',
'de-DE',
'el',
/**
* --------------------------------------------------------------------------
* Supported Locales
* --------------------------------------------------------------------------
*
* If $negotiateLocale is true, this array lists the locales supported
* by the application in descending order of priority. If no match is
* found, the first locale will be used.
*
* IncomingRequest::setLocale() also uses this list.
*
* @var list<string>
*/
public array $supportedLocales = [
'ar-EG',
'ar-LB',
'az',
'bg',
'bs',
'ckb',
'cs',
'da',
'de-CH',
'de-DE',
'el',
'en',
'en-GB',
'es-ES',
'es-MX',
'fa',
'fr',
'he',
'hr-HR',
'hu',
'hy',
'id',
'it',
'km',
'lo',
'ml',
'nb',
'nl-BE',
'en-GB',
'es-ES',
'es-MX',
'fa',
'fr',
'he',
'hr-HR',
'hu',
'hy',
'id',
'it',
'km',
'lo',
'ml',
'nb',
'nl-BE',
'nl-NL',
'pl',
'pt-BR',
'ro',
'ru',
'sv',
'ta',
'th',
'tl',
'tr',
'uk',
'ur',
'vi',
'zh-Hans',
'zh-Hant',
];
'pl',
'pt-BR',
'ro',
'ru',
'sv',
'ta',
'th',
'tl',
'tr',
'uk',
'ur',
'vi',
'zh-Hans',
'zh-Hant',
];
/**
* --------------------------------------------------------------------------
@@ -261,30 +262,30 @@ class App extends BaseConfig
*/
public array $proxyIPs = [];
/**
* --------------------------------------------------------------------------
* Content Security Policy
* --------------------------------------------------------------------------
*
* Enables the Response's Content Secure Policy to restrict the sources that
* can be used for images, scripts, CSS files, audio, video, etc. If enabled,
* the Response object will populate default values for the policy from the
* `ContentSecurityPolicy.php` file. Controllers can always add to those
* restrictions at run time.
*
* For a better understanding of CSP, see these documents:
*
* @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
/**
* --------------------------------------------------------------------------
* Content Security Policy
* --------------------------------------------------------------------------
*
* Enables the Response's Content Secure Policy to restrict the sources that
* can be used for images, scripts, CSS files, audio, video, etc. If enabled,
* the Response object will populate default values for the policy from the
* `ContentSecurityPolicy.php` file. Controllers can always add to those
* restrictions at run time.
*
* For a better understanding of CSP, see these documents:
*
* @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 function __construct()
{
parent::__construct();
$this->https_on = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_ENV['FORCE_HTTPS']) && $_ENV['FORCE_HTTPS'] == 'true');
$this->baseURL = $this->https_on ? 'https' : 'http';
$this->baseURL .= '://' . ((isset($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : 'localhost') . '/';
$this->baseURL .= str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']);
}
public function __construct()
{
parent::__construct();
$this->https_on = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_ENV['FORCE_HTTPS']) && $_ENV['FORCE_HTTPS'] == 'true');
$this->baseURL = $this->https_on ? 'https' : 'http';
$this->baseURL .= '://' . ((isset($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : 'localhost') . '/';
$this->baseURL .= str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']);
}
}

View File

@@ -42,7 +42,7 @@ class Autoload extends AutoloadConfig
public $psr4 = [
APP_NAMESPACE => APPPATH,
'Config' => APPPATH . 'Config',
'dompdf' => APPPATH . 'ThirdParty/dompdf/src'
'dompdf' => APPPATH . 'ThirdParty/dompdf/src'
];
/**
@@ -62,115 +62,115 @@ class Autoload extends AutoloadConfig
*
* @var array<string, string>
*/
public $classmap = [
//Controllers
'Attributes' => '/App/Controllers/Attributes.php',
'Cashups' => '/App/Controllers/Cashups.php',
'Config' => '/App/Controllers/Config.php',
'Customers' => '/App/Controllers/Customers.php',
'Employees' => '/App/Controllers/Employees.php',
'Expenses' => '/App/Controllers/Expenses.php',
'Expenses_categories' => '/App/Controllers/Expenses_categories.php',
'Giftcards' => '/App/Controllers/Giftcards.php',
'Home' => '/App/Controllers/Home.php',
'Item_kits' => '/App/Controllers/Item_kits.php',
'Items' => '/App/Controllers/Items.php',
'Login' => '/App/Controllers/Login.php',
'Messages' => '/App/Controllers/Messages.php',
'No_access' => '/App/Controllers/No_access.php',
'Office' => '/App/Controllers/Office.php',
'Persons' => '/App/Controllers/Persons.php',
'Receivings' => '/App/Controllers/Receivings.php',
'Reports' => '/App/Controllers/Reports.php',
'Sales' => '/App/Controllers/Sales.php',
'Secure_Controller' => '/App/Controllers/Secure_Controller.php',
'Suppliers' => '/App/Controllers/Suppliers.php',
'Tax_categories' => '/App/Controllers/Tax_categories.php',
'Tax_codes' => '/App/Controllers/Tax_codes.php',
'Tax_jurisdictions' => '/App/Controllers/Tax_jurisdictions.php',
'Taxes' => '/App/Controllers/Taxes.php',
public $classmap = [
// Controllers
'Attributes' => '/App/Controllers/Attributes.php',
'Cashups' => '/App/Controllers/Cashups.php',
'Config' => '/App/Controllers/Config.php',
'Customers' => '/App/Controllers/Customers.php',
'Employees' => '/App/Controllers/Employees.php',
'Expenses' => '/App/Controllers/Expenses.php',
'Expenses_categories' => '/App/Controllers/Expenses_categories.php',
'Giftcards' => '/App/Controllers/Giftcards.php',
'Home' => '/App/Controllers/Home.php',
'Item_kits' => '/App/Controllers/Item_kits.php',
'Items' => '/App/Controllers/Items.php',
'Login' => '/App/Controllers/Login.php',
'Messages' => '/App/Controllers/Messages.php',
'No_access' => '/App/Controllers/No_access.php',
'Office' => '/App/Controllers/Office.php',
'Persons' => '/App/Controllers/Persons.php',
'Receivings' => '/App/Controllers/Receivings.php',
'Reports' => '/App/Controllers/Reports.php',
'Sales' => '/App/Controllers/Sales.php',
'Secure_Controller' => '/App/Controllers/Secure_Controller.php',
'Suppliers' => '/App/Controllers/Suppliers.php',
'Tax_categories' => '/App/Controllers/Tax_categories.php',
'Tax_codes' => '/App/Controllers/Tax_codes.php',
'Tax_jurisdictions' => '/App/Controllers/Tax_jurisdictions.php',
'Taxes' => '/App/Controllers/Taxes.php',
//Models
'Appconfig' => '/App/Models/Appconfig.php',
'Attribute' => '/App/Models/Attribute.php',
'Cashup' => '/App/Models/Cashup.php',
'Customer' => '/App/Models/Customer.php',
'Customer_rewards' => '/App/Models/Customer_rewards.php',
'Dinner_table' => '/App/Models/Dinner_table.php',
'Employee' => '/App/Models/Employee.php',
'Expense' => '/App/Models/Expense.php',
'Expense_category' => '/App/Models/Expense_category.php',
'Giftcard' => '/App/Models/Giftcard.php',
'Inventory' => '/App/Models/Inventory.php',
'Item_kit' => '/App/Models/Item_kit.php',
'Item_kit_items' => '/App/Models/Item_kit_items.php',
'Item_quantity' => '/App/Models/Item_quantity.php',
'Item_taxes' => '/App/Models/Item_taxes.php',
'Module' => '/App/Models/Module.php',
'Person' => '/App/Models/Person.php',
'Receiving' => '/App/Models/Receiving.php',
'Rewards' => '/App/Models/Rewards.php',
'Sale' => '/App/Models/Sale.php',
'Stock_location' => '/App/Models/Stock_location.php',
'Supplier' => '/App/Models/Supplier.php',
'Tax' => '/App/Models/Tax.php',
'Tax_category' => '/App/Models/Tax_category.php',
'Tax_code' => '/App/Models/Tax_code.php',
'Tax_jurisdiction' => '/App/Models/Tax_jurisdiction.php',
// Models
'Appconfig' => '/App/Models/Appconfig.php',
'Attribute' => '/App/Models/Attribute.php',
'Cashup' => '/App/Models/Cashup.php',
'Customer' => '/App/Models/Customer.php',
'Customer_rewards' => '/App/Models/Customer_rewards.php',
'Dinner_table' => '/App/Models/Dinner_table.php',
'Employee' => '/App/Models/Employee.php',
'Expense' => '/App/Models/Expense.php',
'Expense_category' => '/App/Models/Expense_category.php',
'Giftcard' => '/App/Models/Giftcard.php',
'Inventory' => '/App/Models/Inventory.php',
'Item_kit' => '/App/Models/Item_kit.php',
'Item_kit_items' => '/App/Models/Item_kit_items.php',
'Item_quantity' => '/App/Models/Item_quantity.php',
'Item_taxes' => '/App/Models/Item_taxes.php',
'Module' => '/App/Models/Module.php',
'Person' => '/App/Models/Person.php',
'Receiving' => '/App/Models/Receiving.php',
'Rewards' => '/App/Models/Rewards.php',
'Sale' => '/App/Models/Sale.php',
'Stock_location' => '/App/Models/Stock_location.php',
'Supplier' => '/App/Models/Supplier.php',
'Tax' => '/App/Models/Tax.php',
'Tax_category' => '/App/Models/Tax_category.php',
'Tax_code' => '/App/Models/Tax_code.php',
'Tax_jurisdiction' => '/App/Models/Tax_jurisdiction.php',
//Reports
'Report' => '/App/Models/Reports/Report.php',
'Detailed_receiving' => '/App/Models/Reports/Detailed_receiving.php',
'Detailed_sales' => '/App/Models/Reports/Detailed_sales.php',
'Inventory_low' => '/App/Models/Reports/Inventory_low.php',
'Inventory_summary' => '/App/Models/Reports/Inventory_summary.php',
'Specific_customer' => '/App/Models/Reports/Specific_customer.php',
'Specific_discount' => '/App/Models/Reports/Specific_discount.php',
'Specific_employee' => '/App/Models/Reports/Specific_employee.php',
'Specific_supplier' => '/App/Models/Reports/Specific_supplier.php',
'Summary_categories' => '/App/Models/Reports/Summary_categories.php',
'Summary_customers' => '/App/Models/Reports/Summary_customers.php',
'Summary_discounts' => '/App/Models/Reports/Summary_discounts.php',
'Summary_employees' => '/App/Models/Reports/Summary_employees.php',
'Summary_expenses_categories' => '/App/Models/Reports/Summary_expenses_categories.php',
'Summary_items' => '/App/Models/Reports/Summary_items.php',
'Summary_payments' => '/App/Models/Reports/Summary_payments.php',
'Summary_report' => '/App/Models/Reports/Summary_report.php',
'Summary_sales' => '/App/Models/Reports/Summary_sales.php',
'Summary_sales_taxes' => '/App/Models/Reports/Summary_sales_taxes.php',
'Summary_suppliers' => '/App/Models/Reports/Summary_suppliers.php',
'Summary_taxes' => '/App/Models/Reports/Summary_taxes.php',
// Reports
'Report' => '/App/Models/Reports/Report.php',
'Detailed_receiving' => '/App/Models/Reports/Detailed_receiving.php',
'Detailed_sales' => '/App/Models/Reports/Detailed_sales.php',
'Inventory_low' => '/App/Models/Reports/Inventory_low.php',
'Inventory_summary' => '/App/Models/Reports/Inventory_summary.php',
'Specific_customer' => '/App/Models/Reports/Specific_customer.php',
'Specific_discount' => '/App/Models/Reports/Specific_discount.php',
'Specific_employee' => '/App/Models/Reports/Specific_employee.php',
'Specific_supplier' => '/App/Models/Reports/Specific_supplier.php',
'Summary_categories' => '/App/Models/Reports/Summary_categories.php',
'Summary_customers' => '/App/Models/Reports/Summary_customers.php',
'Summary_discounts' => '/App/Models/Reports/Summary_discounts.php',
'Summary_employees' => '/App/Models/Reports/Summary_employees.php',
'Summary_expenses_categories' => '/App/Models/Reports/Summary_expenses_categories.php',
'Summary_items' => '/App/Models/Reports/Summary_items.php',
'Summary_payments' => '/App/Models/Reports/Summary_payments.php',
'Summary_report' => '/App/Models/Reports/Summary_report.php',
'Summary_sales' => '/App/Models/Reports/Summary_sales.php',
'Summary_sales_taxes' => '/App/Models/Reports/Summary_sales_taxes.php',
'Summary_suppliers' => '/App/Models/Reports/Summary_suppliers.php',
'Summary_taxes' => '/App/Models/Reports/Summary_taxes.php',
//Tokens
'Token' => '/App/Models/Tokens/Token.php',
'Token_barcode_ean' => '/App/Models/Tokens/Token_barcode_ean.php',
'Token_barcode_price' => '/App/Models/Tokens/Token_barcode_price.php',
'Token_barcode_weight' => '/App/Models/Tokens/Token_barcode_weight.php',
'Token_customer' => '/App/Models/Tokens/Token_customer.php',
'Token_invoice_count' => '/App/Models/Tokens/Token_invoice_count.php',
'Token_invoice_sequence' => '/App/Models/Tokens/Token_invoice_sequence.php',
'Token_quote_sequence' => '/App/Models/Tokens/Token_quote_sequence.php',
'Token_suspended_invoice_count' => '/App/Models/Tokens/Token_suspended_invoice_count.php',
'Token_work_order_sequence' => '/App/Models/Tokens/Token_work_order_sequence.php',
'Token_year_invoice_count' => '/App/Models/Tokens/Token_year_invoice_count.php',
'Token_year_quote_count' => '/App/Models/Tokens/Token_year_quote_count.php',
// Tokens
'Token' => '/App/Models/Tokens/Token.php',
'Token_barcode_ean' => '/App/Models/Tokens/Token_barcode_ean.php',
'Token_barcode_price' => '/App/Models/Tokens/Token_barcode_price.php',
'Token_barcode_weight' => '/App/Models/Tokens/Token_barcode_weight.php',
'Token_customer' => '/App/Models/Tokens/Token_customer.php',
'Token_invoice_count' => '/App/Models/Tokens/Token_invoice_count.php',
'Token_invoice_sequence' => '/App/Models/Tokens/Token_invoice_sequence.php',
'Token_quote_sequence' => '/App/Models/Tokens/Token_quote_sequence.php',
'Token_suspended_invoice_count' => '/App/Models/Tokens/Token_suspended_invoice_count.php',
'Token_work_order_sequence' => '/App/Models/Tokens/Token_work_order_sequence.php',
'Token_year_invoice_count' => '/App/Models/Tokens/Token_year_invoice_count.php',
'Token_year_quote_count' => '/App/Models/Tokens/Token_year_quote_count.php',
//Libraries
'Barcode_lib' => '/App/Libraries/Barcode_lib.php',
'Email_lib' => '/App/Libraries/Email_lib.php',
'Item_lib' => '/App/Libraries/Item_lib.php',
'Mailchimp_lib' => '/App/Libraries/Mailchimp_lib.php',
'MY_Email' => '/App/Libraries/MY_Email.php',
'MY_Migration' => '/App/Libraries/MY_Migration.php',
'Receving_lib' => '/App/Libraries/Receiving_lib.php',
'Sale_lib' => '/App/Libraries/Sale_lib.php',
'Sms_lib' => '/App/Libraries/Sms_lib.php',
'Tax_lib' => '/App/Libraries/Tax_lib.php',
'Token_lib' => '/App/Libraries/Token_lib.php',
// Libraries
'Barcode_lib' => '/App/Libraries/Barcode_lib.php',
'Email_lib' => '/App/Libraries/Email_lib.php',
'Item_lib' => '/App/Libraries/Item_lib.php',
'Mailchimp_lib' => '/App/Libraries/Mailchimp_lib.php',
'MY_Email' => '/App/Libraries/MY_Email.php',
'MY_Migration' => '/App/Libraries/MY_Migration.php',
'Receving_lib' => '/App/Libraries/Receiving_lib.php',
'Sale_lib' => '/App/Libraries/Sale_lib.php',
'Sms_lib' => '/App/Libraries/Sms_lib.php',
'Tax_lib' => '/App/Libraries/Tax_lib.php',
'Token_lib' => '/App/Libraries/Token_lib.php',
//Miscellaneous
'Rounding_mode' => '/App/Models/Enums/Rounding_mode.php'
];
// Miscellaneous
'Rounding_mode' => '/App/Models/Enums/Rounding_mode.php'
];
/**
* -------------------------------------------------------------------
@@ -201,10 +201,10 @@ class Autoload extends AutoloadConfig
* @var list<string>
*/
public $helpers = [
'form',
'cookie',
'tabular',
'locale',
'security'
];
'form',
'cookie',
'tabular',
'locale',
'security'
];
}

View File

@@ -34,18 +34,6 @@ class Cache extends BaseConfig
*/
public string $backupHandler = 'dummy';
/**
* --------------------------------------------------------------------------
* Cache Directory Path
* --------------------------------------------------------------------------
*
* The path to where cache files should be stored, if using a file-based
* system.
*
* @deprecated Use the driver-specific variant under $file
*/
public string $storePath = WRITEPATH . 'cache/';
/**
* --------------------------------------------------------------------------
* Key Prefix
@@ -86,6 +74,7 @@ class Cache extends BaseConfig
* --------------------------------------------------------------------------
* File settings
* --------------------------------------------------------------------------
*
* Your file storage preferences can be specified below, if you are using
* the File driver.
*
@@ -100,6 +89,7 @@ class Cache extends BaseConfig
* -------------------------------------------------------------------------
* Memcached settings
* -------------------------------------------------------------------------
*
* Your Memcached servers can be specified below, if you are using
* the Memcached drivers.
*

View File

@@ -41,10 +41,9 @@ defined('WEEK') || define('WEEK', 604800);
defined('MONTH') || define('MONTH', 2_592_000);
defined('YEAR') || define('YEAR', 31_536_000);
defined('DECADE') || define('DECADE', 315_360_000);
defined('DEFAULT_DATE') || define('DEFAULT_DATE', mktime(0, 0, 0, 1, 1, 2010));
defined('DEFAULT_DATE') || define('DEFAULT_DATE', mktime(0, 0, 0, 1, 1, 2010));
defined('DEFAULT_DATETIME') || define('DEFAULT_DATETIME', mktime(0, 0, 0, 1, 1, 2010));
defined('NOW') || define('NOW', time());
defined('NOW') || define('NOW', time());
/*
| --------------------------------------------------------------------------
@@ -82,21 +81,6 @@ defined('EXIT_DATABASE') || define('EXIT_DATABASE', 8); // database
defined('EXIT__AUTO_MIN') || define('EXIT__AUTO_MIN', 9); // lowest automatically-assigned error code
defined('EXIT__AUTO_MAX') || define('EXIT__AUTO_MAX', 125); // highest automatically-assigned error code
/**
* @deprecated Use \CodeIgniter\Events\Events::PRIORITY_LOW instead.
*/
define('EVENT_PRIORITY_LOW', 200);
/**
* @deprecated Use \CodeIgniter\Events\Events::PRIORITY_NORMAL instead.
*/
define('EVENT_PRIORITY_NORMAL', 100);
/**
* @deprecated Use \CodeIgniter\Events\Events::PRIORITY_HIGH instead.
*/
define('EVENT_PRIORITY_HIGH', 10);
/**
* Global Constants.
*/
@@ -116,6 +100,7 @@ const CHECKBOX = 'CHECKBOX';
const NO_DEFINITION_ID = 0;
const CATEGORY_DEFINITION_ID = -1;
const DEFINITION_TYPES = [GROUP, DROPDOWN, DECIMAL, TEXT, DATE, CHECKBOX];
const ATTRIBUTE_VALUE_TYPES = ['attribute_value', 'attribute_decimal', 'attribute_date'];
/**
* Item Related Constants.

View File

@@ -48,9 +48,9 @@ class ContentSecurityPolicy extends BaseConfig
* @var list<string>|string|null
*/
public $defaultSrc = [
'self',
'www.google.com',
];
'self',
'www.google.com',
];
/**
* Lists allowed scripts' URLs.
@@ -58,23 +58,23 @@ class ContentSecurityPolicy extends BaseConfig
* @var list<string>|string
*/
public $scriptSrc = [
'self',
'unsafe-inline',
'unsafe-eval',
'www.google.com www.gstatic.com'
];
'self',
'unsafe-inline',
'unsafe-eval',
'www.google.com www.gstatic.com'
];
/**
* Lists allowed stylesheets' URLs.
*
* @var list<string>|string
*/
public $styleSrc = [
'self',
'unsafe-inline',
'nonce-{csp-style-nonce}',
'https://fonts.googleapis.com',
];
public $styleSrc = [
'self',
'unsafe-inline',
'nonce-{csp-style-nonce}',
'https://fonts.googleapis.com',
];
/**
* Defines the origins from which images can be loaded.
@@ -82,10 +82,10 @@ class ContentSecurityPolicy extends BaseConfig
* @var list<string>|string
*/
public $imageSrc = [
'self',
'data:',
'blob:',
];
'self',
'data:',
'blob:',
];
/**
* Restricts the URLs that can appear in a page's `<base>` element.
@@ -110,9 +110,9 @@ class ContentSecurityPolicy extends BaseConfig
* @var list<string>|string
*/
public $connectSrc = [
'self',
'nominatim.openstreetmap.org',
];
'self',
'nominatim.openstreetmap.org',
];
/**
* Specifies the origins that can serve web fonts.
@@ -120,10 +120,10 @@ class ContentSecurityPolicy extends BaseConfig
* @var list<string>|string
*/
public $fontSrc = [
'self',
'fonts.googleapis.com',
'fonts.gstatic.com',
];
'self',
'fonts.googleapis.com',
'fonts.gstatic.com',
];
/**
* Lists valid endpoints for submission from `<form>` tags.

View File

@@ -19,104 +19,99 @@ class Database extends Config
*/
public string $defaultGroup = 'default';
/**
* The default database connection.
*
* @var array<string, mixed>
*/
public array $default = [
'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,
'numberNative' => false,
'dateFormat' => [
'date' => 'Y-m-d',
'datetime' => 'Y-m-d H:i:s',
'time' => 'H:i:s',
],
];
/**
* The default database connection.
*
* @var array<string, mixed>
*/
public array $default = [
'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,
'dateFormat' => [
'date' => 'Y-m-d',
'datetime' => 'Y-m-d H:i:s',
'time' => 'H:i:s',
],
];
/**
* This database connection is used when
* running PHPUnit database tests.
*
* @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,
'numberNative' => false,
'dateFormat' => [
'date' => 'Y-m-d',
'datetime' => 'Y-m-d H:i:s',
'time' => 'H:i:s',
],
];
/**
* This database connection is used when running PHPUnit database tests.
*
* @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' => [
'date' => 'Y-m-d',
'datetime' => 'Y-m-d H:i:s',
'time' => 'H:i:s',
],
];
/**
* This database connection is used when
* developing against non-production data.
*
* @var array
*/
public $development = [
'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,
'numberNative' => false,
'dateFormat' => [
'date' => 'Y-m-d',
'datetime' => 'Y-m-d H:i:s',
'time' => 'H:i:s',
],
];
/**
* This database connection is used when developing against non-production data.
*
* @var array
*/
public $development = [
'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' => [
'date' => 'Y-m-d',
'datetime' => 'Y-m-d H:i:s',
'time' => 'H:i:s',
],
];
public function __construct()
{
@@ -125,22 +120,20 @@ class Database extends Config
// Ensure that we always set the database group to 'tests' if
// we are currently running an automated test suite, so that
// we don't overwrite live data on accident.
switch(ENVIRONMENT)
{
case 'testing':
$this->defaultGroup = 'tests';
break;
case 'development';
$this->defaultGroup = 'development';
break;
}
switch (ENVIRONMENT) {
case 'testing':
$this->defaultGroup = 'tests';
break;
case 'development';
$this->defaultGroup = 'development';
break;
}
foreach ([&$this->development, &$this->tests, &$this->default] as &$config)
{
$config['hostname'] = !getenv('MYSQL_HOST_NAME') ? $config['hostname'] : getenv('MYSQL_HOST_NAME');
$config['username'] = !getenv('MYSQL_USERNAME') ? $config['username'] : getenv('MYSQL_USERNAME');
$config['password'] = !getenv('MYSQL_PASSWORD') ? $config['password'] : getenv('MYSQL_PASSWORD');
$config['database'] = !getenv('MYSQL_DB_NAME') ? $config['database'] : getenv('MYSQL_DB_NAME');
}
foreach ([&$this->development, &$this->tests, &$this->default] as &$config) {
$config['hostname'] = !getenv('MYSQL_HOST_NAME') ? $config['hostname'] : getenv('MYSQL_HOST_NAME');
$config['username'] = !getenv('MYSQL_USERNAME') ? $config['username'] : getenv('MYSQL_USERNAME');
$config['password'] = !getenv('MYSQL_PASSWORD') ? $config['password'] : getenv('MYSQL_PASSWORD');
$config['database'] = !getenv('MYSQL_DB_NAME') ? $config['database'] : getenv('MYSQL_DB_NAME');
}
}
}

View File

@@ -2,12 +2,12 @@
namespace Config;
use App\Events\Db_log;
use App\Events\Load_config;
use App\Events\Method;
use CodeIgniter\Events\Events;
use CodeIgniter\Exceptions\FrameworkException;
use CodeIgniter\HotReloader\HotReloader;
use App\Events\Db_log;
use App\Events\Load_config;
use App\Events\Method;
/*
* --------------------------------------------------------------------
@@ -47,10 +47,10 @@ Events::on('pre_system', static function (): void {
*/
if (CI_DEBUG && ! is_cli()) {
Events::on('DBQuery', 'CodeIgniter\Debug\Toolbar\Collectors\Database::collect');
Services::toolbar()->respond();
service('toolbar')->respond();
// Hot Reload route - for framework use on the hot reloader.
if (ENVIRONMENT === 'development') {
Services::routes()->get('__hot-reload', static function (): void {
service('routes')->get('__hot-reload', static function (): void {
(new HotReloader())->run();
});
}

View File

@@ -80,7 +80,7 @@ class Exceptions extends BaseConfig
*/
public string $deprecationLogLevel = LogLevel::WARNING;
/**
/*
* DEFINE THE HANDLERS USED
* --------------------------------------------------------------------------
* Given the HTTP status code, returns exception handler that

View File

@@ -10,7 +10,7 @@ use CodeIgniter\Config\BaseConfig;
class Feature extends BaseConfig
{
/**
* Use improved new auto routing instead of the default legacy version.
* Use improved new auto routing instead of the legacy version.
*/
public bool $autoRoutesImproved = true;
@@ -26,4 +26,12 @@ class Feature extends BaseConfig
* If false, `limit(0)` returns no records. (the behavior of 3.1.9 or later in version 3.x.)
*/
public bool $limitZeroAsAll = true;
/**
* Use strict location negotiation.
*
* By default, the locale is selected based on a loose comparison of the language code (ISO 639-1)
* Enabling strict comparison will also consider the region code (ISO 3166-1 alpha-2).
*/
public bool $strictLocaleNegotiation = false;
}

View File

@@ -70,7 +70,7 @@ class Filters extends BaseFilters
public array $globals = [
'before' => [
'honeypot',
//'csrf' => ['except' => 'login'], //TODO: Temporarily disable CSRF until we get everything sorted.
'csrf' => ['except' => 'login'],
'invalidchars',
],
'after' => [

View File

@@ -3,7 +3,6 @@
namespace Config;
use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Format\FormatterInterface;
use CodeIgniter\Format\JSONFormatter;
use CodeIgniter\Format\XMLFormatter;
@@ -62,16 +61,4 @@ class Format extends BaseConfig
'application/xml' => 0,
'text/xml' => 0,
];
/**
* A Factory method to return the appropriate formatter for the given mime type.
*
* @return FormatterInterface
*
* @deprecated This is an alias of `\CodeIgniter\Format\Format::getFormatter`. Use that instead.
*/
public function getFormatter(string $mime)
{
return Services::format()->getFormatter($mime);
}
}

View File

@@ -3,7 +3,6 @@
namespace Config;
use Kint\Parser\ConstructablePluginInterface;
use Kint\Renderer\AbstractRenderer;
use Kint\Renderer\Rich\TabPluginInterface;
use Kint\Renderer\Rich\ValuePluginInterface;
@@ -41,7 +40,6 @@ class Kint
*/
public string $richTheme = 'aante-light.css';
public bool $richFolder = false;
public int $richSort = AbstractRenderer::SORT_FULL;
/**
* @var array<string, class-string<ValuePluginInterface>>|null

View File

@@ -491,11 +491,10 @@ class Mimes
* @return string|null The mime type found, or none if unable to determine.
*/
public static function guessTypeFromExtension(string $extension): array|string|null
{
{
$extension = trim(strtolower($extension), '. ');
if (!array_key_exists($extension, static::$mimes))
{
if (! array_key_exists($extension, static::$mimes)) {
return null;
}
@@ -510,7 +509,7 @@ class Mimes
* @return string|null The extension determined, or null if unable to match.
*/
public static function guessExtensionFromType(string $type, ?string $proposedExtension = null): ?string
{
{
$type = trim(strtolower($type), '. ');
$proposedExtension = trim(strtolower($proposedExtension ?? ''));

View File

@@ -13,45 +13,41 @@ use CodeIgniter\Config\BaseConfig;
*/
class OSPOS extends BaseConfig
{
public array $settings;
public string $commit_sha1 = 'dev'; //TODO: Travis scripts need to be updated to replace this with the commit hash on build
private CacheInterface $cache;
public array $settings;
public string $commit_sha1 = 'dev'; // TODO: Travis scripts need to be updated to replace this with the commit hash on build
private CacheInterface $cache;
public function __construct()
{
parent::__construct();
$this->cache = Services::cache();
$this->set_settings();
}
public function __construct()
{
parent::__construct();
$this->cache = Services::cache();
$this->set_settings();
}
/**
* @return void
*/
public function set_settings(): void
{
$cache = $this->cache->get('settings');
/**
* @return void
*/
public function set_settings(): void
{
$cache = $this->cache->get('settings');
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;
}
$this->cache->save('settings', encode_array($this->settings));
}
}
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;
}
$this->cache->save('settings', encode_array($this->settings));
}
}
/**
* @return void
*/
public function update_settings(): void
{
$this->cache->delete('settings');
$this->set_settings();
}
/**
* @return void
*/
public function update_settings(): void
{
$this->cache->delete('settings');
$this->set_settings();
}
}

View File

@@ -35,27 +35,27 @@ class Pager extends BaseConfig
*/
public int $perPage = 20;
/**
* --------------------------------------------------------------------------
* Bootstrap 3 pagination links styling
* --------------------------------------------------------------------------
*
* Source code from http://stackoverflow.com/questions/20088779/bootstrap-3-pagination-with-codeigniter
*/
public $config = [
'full_tag_open' => "<ul class='pagination pagination-sm'>",
'full_tag_close' => '</ul>',
'num_tag_open' => '<li>',
'num_tag_close' => '</li>',
'cur_tag_open' => "<li class='disabled'><li class='active'><a href='#'>",
'cur_tag_close' => "<span class='sr-only'></span></a></li>",
'next_tag_open' => "<li>",
'next_tagl_close' => "</li>",
'prev_tag_open' => "<li>",
'prev_tagl_close' => "</li>",
'first_tag_open' => "<li>",
'first_tagl_close' => "</li>",
'last_tag_open' => "<li>",
'last_tagl_close' => "</li>"
];
/**
* --------------------------------------------------------------------------
* Bootstrap 3 pagination links styling
* --------------------------------------------------------------------------
*
* Source code from http://stackoverflow.com/questions/20088779/bootstrap-3-pagination-with-codeigniter
*/
public $config = [
'full_tag_open' => '<ul class="pagination pagination-sm">',
'full_tag_close' => '</ul>',
'num_tag_open' => '<li>',
'num_tag_close' => '</li>',
'cur_tag_open' => '<li class="disabled"><li class="active"><a href="#">',
'cur_tag_close' => '<span class="sr-only"></span></a></li>',
'next_tag_open' => '<li>',
'next_tagl_close' => '</li>',
'prev_tag_open' => '<li>',
'prev_tagl_close' => '</li>',
'first_tag_open' => '<li>',
'first_tagl_close' => '</li>',
'last_tag_open' => '<li>',
'last_tagl_close' => '</li>'
];
}

View File

@@ -15,7 +15,7 @@ class Security extends BaseConfig
*
* @var string 'cookie' or 'session'
*/
public string $csrfProtection = 'cookie';
public string $csrfProtection = 'session';
/**
* --------------------------------------------------------------------------
@@ -71,7 +71,7 @@ class Security extends BaseConfig
*
* Regenerate CSRF Token on every submission.
*/
public bool $regenerate = true;
public bool $regenerate = false;
/**
* --------------------------------------------------------------------------
@@ -83,21 +83,4 @@ class Security extends BaseConfig
* @see https://codeigniter4.github.io/userguide/libraries/security.html#redirection-on-failure
*/
public bool $redirect = (ENVIRONMENT === 'production');
/**
* --------------------------------------------------------------------------
* CSRF SameSite
* --------------------------------------------------------------------------
*
* Setting for CSRF SameSite cookie token.
*
* Allowed values are: None - Lax - Strict - ''.
*
* Defaults to `Lax` as recommended in this link:
*
* @see https://portswigger.net/web-security/csrf/samesite-cookies
*
* @deprecated `Config\Cookie` $samesite property is used.
*/
public string $samesite = 'Lax';
}

View File

@@ -2,11 +2,12 @@
namespace Config;
use CodeIgniter\Config\BaseService;
use CodeIgniter\HTTP\IncomingRequest;
use Config\Services as AppServices;
use Locale;
use HTMLPurifier;
use HTMLPurifier_Config;
use CodeIgniter\Config\BaseService;
use Config\Services as AppServices;
use CodeIgniter\HTTP\IncomingRequest;
/**
* Services Configuration file.
@@ -34,44 +35,42 @@ class Services extends BaseService
* }
*/
/**
* Responsible for loading the language string translations.
*
* @return MY_Language
*/
public static function language(?string $locale = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('language', $locale)->setLocale($locale);
}
/**
* Responsible for loading the language string translations.
*
* @return MY_Language
*/
public static function language(?string $locale = null, bool $getShared = true)
{
if ($getShared) {
return static::getSharedInstance('language', $locale)->setLocale($locale);
}
if (AppServices::get('request') instanceof IncomingRequest) {
$requestLocale = AppServices::get('request')->getLocale();
} else {
$requestLocale = Locale::getDefault();
}
if (AppServices::get('request') instanceof IncomingRequest) {
$requestLocale = AppServices::get('request')->getLocale();
} else {
$requestLocale = Locale::getDefault();
}
// Use '?:' for empty string check
$locale = $locale ?: $requestLocale;
// Use '?:' for empty string check
$locale = $locale ?: $requestLocale;
return new \App\Libraries\MY_Language($locale);
}
return new \App\Libraries\MY_Language($locale);
}
private static $htmlPurifier;
private static $htmlPurifier;
public static function htmlPurifier($getShared = true)
{
if ($getShared)
{
return static::getSharedInstance('htmlPurifier');
}
public static function htmlPurifier($getShared = true)
{
if ($getShared) {
return static::getSharedInstance('htmlPurifier');
}
if (!isset(static::$htmlPurifier))
{
$config = HTMLPurifier_Config::createDefault();
static::$htmlPurifier = new HTMLPurifier($config);
}
if (!isset(static::$htmlPurifier)) {
$config = HTMLPurifier_Config::createDefault();
static::$htmlPurifier = new HTMLPurifier($config);
}
return static::$htmlPurifier;
}
return static::$htmlPurifier;
}
}

View File

@@ -2,12 +2,12 @@
namespace Config;
use App\Config\Validation\OSPOSRules;
use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Validation\StrictRules\CreditCardRules;
use CodeIgniter\Validation\StrictRules\FileRules;
use CodeIgniter\Validation\StrictRules\FormatRules;
use CodeIgniter\Validation\StrictRules\Rules;
use App\Config\Validation\OSPOSRules;
class Validation extends BaseConfig
{
@@ -26,8 +26,8 @@ class Validation extends BaseConfig
FormatRules::class,
FileRules::class,
CreditCardRules::class,
OSPOSRules::class
];
OSPOSRules::class,
];
/**
* Specifies the views that are used to display the

View File

@@ -1,4 +1,5 @@
<?php
namespace App\Config\Validation;
use App\Models\Employee;
@@ -12,134 +13,126 @@ use Config\Services;
*/
class OSPOSRules
{
private IncomingRequest $request;
private array $config;
private IncomingRequest $request;
private array $config;
/**
* Validates the username and password sent to the login view. User is logged in on successful validation.
*
* @param string $username Username to check against.
* @param string $fields Comma separated string of the fields for validation.
* @param array $data Data sent to the view.
* @param string|null $error The error sent back to the validation handler on failure.
* @return bool True if validation passes or false if there are errors.
* @noinspection PhpUnused
*/
public function login_check(string $username, string $fields , array $data, ?string &$error = null): bool
{
$employee = model(Employee::class);
$this->request = Services::request();
$this->config = config(OSPOS::class)->settings;
/**
* Validates the username and password sent to the login view. User is logged in on successful validation.
*
* @param string $username Username to check against.
* @param string $fields Comma separated string of the fields for validation.
* @param array $data Data sent to the view.
* @param string|null $error The error sent back to the validation handler on failure.
* @return bool True if validation passes or false if there are errors.
* @noinspection PhpUnused
*/
public function login_check(string $username, string $fields, array $data, ?string &$error = null): bool
{
$employee = model(Employee::class);
$this->request = Services::request();
$this->config = config(OSPOS::class)->settings;
//Installation Check
if(!$this->installation_check())
{
$error = lang('Login.invalid_installation');
// Installation Check
if (!$this->installation_check()) {
$error = lang('Login.invalid_installation');
return false;
}
return false;
}
$password = $data['password'];
if(!$employee->login($username, $password))
{
$error = lang('Login.invalid_username_and_password');
$password = $data['password'];
if (!$employee->login($username, $password)) {
$error = lang('Login.invalid_username_and_password');
return false;
}
return false;
}
$gcaptcha_enabled = array_key_exists('gcaptcha_enable', $this->config) && $this->config['gcaptcha_enable'];
if($gcaptcha_enabled)
{
$g_recaptcha_response = $this->request->getPost('g-recaptcha-response');
$gcaptcha_enabled = array_key_exists('gcaptcha_enable', $this->config) && $this->config['gcaptcha_enable'];
if ($gcaptcha_enabled) {
$g_recaptcha_response = $this->request->getPost('g-recaptcha-response');
if(!$this->gcaptcha_check($g_recaptcha_response))
{
$error = lang('Login.invalid_gcaptcha');
if (!$this->gcaptcha_check($g_recaptcha_response)) {
$error = lang('Login.invalid_gcaptcha');
return false;
}
}
return false;
}
}
return true;
}
return true;
}
/**
* Checks to see if GCaptcha verification was successful.
*
* @param $response
* @return bool true on successful GCaptcha verification or false if GCaptcha failed.
*/
private function gcaptcha_check($response): bool
{
if(!empty($response))
{
$check = [
'secret' => $this->config['gcaptcha_secret_key'],
'response' => $response,
'remoteip' => $this->request->getIPAddress()
];
/**
* Checks to see if GCaptcha verification was successful.
*
* @param $response
* @return bool true on successful GCaptcha verification or false if GCaptcha failed.
*/
private function gcaptcha_check($response): bool
{
if (!empty($response)) {
$check = [
'secret' => $this->config['gcaptcha_secret_key'],
'response' => $response,
'remoteip' => $this->request->getIPAddress()
];
$ch = curl_init();
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://www.google.com/recaptcha/api/siteverify");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($check));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, "https://www.google.com/recaptcha/api/siteverify");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($check));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
$result = curl_exec($ch);
curl_close($ch);
curl_close($ch);
$status = json_decode($result, true);
$status = json_decode($result, true);
if(!empty($status['success']))
{
return true;
}
}
if (!empty($status['success'])) {
return true;
}
}
return false;
}
return false;
}
/**
* Checks to make sure dependency PHP extensions are installed
*
* @return bool
*/
private function installation_check(): bool
{
$installed_extensions = implode(', ', get_loaded_extensions());
$required_extensions = ['bcmath', 'intl', 'gd', 'openssl', 'mbstring', 'curl', 'xml', 'json'];
$pattern = '/';
/**
* Checks to make sure dependency PHP extensions are installed
*
* @return bool
*/
private function installation_check(): bool
{
$installed_extensions = implode(', ', get_loaded_extensions());
$required_extensions = ['bcmath', 'intl', 'gd', 'openssl', 'mbstring', 'curl', 'xml', 'json'];
$pattern = '/';
foreach($required_extensions as $extension)
{
$pattern .= '(?=.*\b' . preg_quote($extension, '/') . '\b)';
}
foreach ($required_extensions as $extension) {
$pattern .= '(?=.*\b' . preg_quote($extension, '/') . '\b)';
}
$pattern .= '/i';
$is_installed = preg_match($pattern, $installed_extensions);
$pattern .= '/i';
$is_installed = preg_match($pattern, $installed_extensions);
if(!$is_installed)
{
log_message('error', '[ERROR] Check your php.ini.');
log_message('error',"PHP installed extensions: $installed_extensions");
log_message('error','PHP required extensions: ' . implode(', ', $required_extensions));
}
if (!$is_installed) {
log_message('error', '[ERROR] Check your php.ini.');
log_message('error', "PHP installed extensions: $installed_extensions");
log_message('error', 'PHP required extensions: ' . implode(', ', $required_extensions));
}
return $is_installed;
}
return $is_installed;
}
/**
* Validates the candidate as a decimal number. Takes the locale into account. Used in validation rule calls.
*
* @param string $candidate
* @param string|null $error
* @return bool
* @noinspection PhpUnused
*/
public function decimal_locale(string $candidate, ?string &$error = null): bool
{
return parse_decimals($candidate) !== false;
}
/**
* Validates the candidate as a decimal number. Takes the locale into account. Used in validation rule calls.
*
* @param string $candidate
* @param string|null $error
* @return bool
* @noinspection PhpUnused
*/
public function decimal_locale(string $candidate, ?string &$error = null): bool
{
return parse_decimals($candidate) !== false;
}
}

View File

@@ -12,251 +12,222 @@ require_once('Secure_Controller.php');
**/
class Attributes extends Secure_Controller
{
private Attribute $attribute;
private Attribute $attribute;
public function __construct()
{
parent::__construct('attributes');
public function __construct()
{
parent::__construct('attributes');
$this->attribute = model(Attribute::class);
}
$this->attribute = model(Attribute::class);
}
/**
* Gets and sends the main view for Attributes to the browser.
*
* @return void
**/
public function getIndex(): void
{
$data['table_headers'] = get_attribute_definition_manage_table_headers();
/**
* Gets and sends the main view for Attributes to the browser.
*
* @return void
**/
public function getIndex(): void
{
$data['table_headers'] = get_attribute_definition_manage_table_headers();
echo view('attributes/manage', $data);
}
echo view('attributes/manage', $data);
}
/**
* Returns attribute table data rows. This will be called with AJAX.
*/
public function getSearch(): void
{
$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->sanitizeSortColumn(ATTRIBUTE_DEFINITION_HEADERS, $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'definition_id');
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* Returns attribute table data rows. This will be called with AJAX.
*/
public function getSearch(): void
{
$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->sanitizeSortColumn(attribute_definition_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'definition_id');
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$attributes = $this->attribute->search($search, $limit, $offset, $sort, $order);
$total_rows = $this->attribute->get_found_rows($search);
$attributes = $this->attribute->search($search, $limit, $offset, $sort, $order);
$total_rows = $this->attribute->get_found_rows($search);
$data_rows = [];
foreach($attributes->getResult() as $attribute_row)
{
$attribute_row->definition_flags = $this->get_attributes($attribute_row->definition_flags);
$data_rows[] = get_attribute_definition_data_row($attribute_row);
}
$data_rows = [];
foreach ($attributes->getResult() as $attribute_row) {
$attribute_row->definition_flags = $this->get_attributes($attribute_row->definition_flags);
$data_rows[] = get_attribute_definition_data_row($attribute_row);
}
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
}
echo json_encode(['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
* @noinspection PhpUnused
*/
public function postSaveAttributeValue(): void
{
$success = $this->attribute->saveAttributeValue(
html_entity_decode($this->request->getPost('attribute_value')),
$this->request->getPost('definition_id', FILTER_SANITIZE_NUMBER_INT),
$this->request->getPost('item_id', FILTER_SANITIZE_NUMBER_INT),
$this->request->getPost('attribute_id', FILTER_SANITIZE_NUMBER_INT)
);
/**
* AJAX called function which saves the attribute value sent via POST by using the model save function.
* @return void
* @noinspection PhpUnused
*/
public function postSaveAttributeValue(): void
{
$success = $this->attribute->saveAttributeValue(
html_entity_decode($this->request->getPost('attribute_value')),
$this->request->getPost('definition_id', FILTER_SANITIZE_NUMBER_INT),
$this->request->getPost('item_id', FILTER_SANITIZE_NUMBER_INT) ?? false,
$this->request->getPost('attribute_id', FILTER_SANITIZE_NUMBER_INT) ?? false
);
echo json_encode(['success' => $success != 0]);
}
echo json_encode(['success' => $success != 0]);
}
/**
* AJAX called function deleting an attribute value using the model delete function.
* @return void
* @noinspection PhpUnused
*/
public function postDelete_attribute_value(): void
{
$success = $this->attribute->delete_value(
html_entity_decode($this->request->getPost('attribute_value')),
$this->request->getPost('definition_id', FILTER_SANITIZE_NUMBER_INT)
);
/**
* AJAX called function deleting an attribute value using the model delete function.
* @return void
* @noinspection PhpUnused
*/
public function postDeleteDropdownAttributeValue(): void
{
$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]);
}
echo json_encode(['success' => $success]);
}
/**
* AJAX called function which saves the attribute definition.
*
* @param int $definition_id
* @return void
* @noinspection PhpUnused
*/
public function postSaveDefinition(int $definition_id = NO_DEFINITION_ID): void
{
$definition_flags = 0;
/**
* AJAX called function which saves the attribute definition.
*
* @param int $definition_id
* @return void
* @noinspection PhpUnused
*/
public function postSaveDefinition(int $definition_id = NO_DEFINITION_ID): void
{
$definition_flags = 0;
$flags = (empty($this->request->getPost('definition_flags'))) ? [] : $this->request->getPost('definition_flags', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$flags = (empty($this->request->getPost('definition_flags'))) ? [] : $this->request->getPost('definition_flags', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
foreach($flags as $flag)
{
$definition_flags |= $flag;
}
foreach ($flags as $flag) {
$definition_flags |= $flag;
}
//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
];
// 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
];
if ($this->request->getPost('definition_type') != null)
{
$definition_data['definition_type'] = DEFINITION_TYPES[$this->request->getPost('definition_type')];
}
if ($this->request->getPost('definition_type') != null) {
$definition_data['definition_type'] = DEFINITION_TYPES[$this->request->getPost('definition_type')];
}
$definition_name = $definition_data['definition_name'];
$definition_name = $definition_data['definition_name'];
if($this->attribute->save_definition($definition_data, $definition_id))
{
//New definition
if($definition_id == NO_DEFINITION_ID)
{
$definition_values = json_decode(html_entity_decode($this->request->getPost('definition_values')));
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')));
foreach($definition_values as $definition_value)
{
$this->attribute->saveAttributeValue($definition_value, $definition_data['definition_id']);
}
foreach ($definition_values as $definition_value) {
$this->attribute->saveAttributeValue($definition_value, $definition_data['definition_id']);
}
echo json_encode([
'success' => true,
'message' => lang('Attributes.definition_successful_adding') . ' ' . $definition_name,
'id' => $definition_data['definition_id']
]);
}
//Existing definition
else
{
echo json_encode([
'success' => true,
'message' => lang('Attributes.definition_successful_updating') . ' ' . $definition_name,
'id' => $definition_id
]);
}
}
//Failure
else
{
echo json_encode([
'success' => false,
'message' => lang('Attributes.definition_error_adding_updating', [$definition_name]),
'id' => NEW_ENTRY
]);
}
}
echo json_encode([
'success' => true,
'message' => lang('Attributes.definition_successful_adding') . ' ' . $definition_name,
'id' => $definition_data['definition_id']
]);
} else { // Existing definition
echo json_encode([
'success' => true,
'message' => lang('Attributes.definition_successful_updating') . ' ' . $definition_name,
'id' => $definition_id
]);
}
} else { // Failure
echo json_encode([
'success' => false,
'message' => lang('Attributes.definition_error_adding_updating', [$definition_name]),
'id' => NEW_ENTRY
]);
}
}
/**
*
* @param int $definition_id
* @return void
* @noinspection PhpUnused
*/
public function getSuggestAttribute(int $definition_id): void
{
$suggestions = $this->attribute->get_suggestions($definition_id, html_entity_decode($this->request->getGet('term')));
/**
*
* @param int $definition_id
* @return void
* @noinspection PhpUnused
*/
public function getSuggestAttribute(int $definition_id): void
{
$suggestions = $this->attribute->get_suggestions($definition_id, html_entity_decode($this->request->getGet('term')));
echo json_encode($suggestions);
}
echo json_encode($suggestions);
}
/**
* @param int $row_id
* @return void
*/
public function getRow(int $row_id): void
{
$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);
/**
* @param int $row_id
* @return void
*/
public function getRow(int $row_id): void
{
$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);
}
echo json_encode($data_row);
}
/**
* @param int $definition_flags
* @return array
*/
private function get_attributes(int $definition_flags = 0): array
{
$definition_flag_names = [];
foreach (Attribute::get_definition_flags() as $id => $term)
{
if ($id & $definition_flags)
{
$definition_flag_names[$id] = lang('Attributes.' . strtolower($term) . '_visibility');
}
}
return $definition_flag_names;
}
/**
* @param int $definition_flags
* @return array
*/
private function get_attributes(int $definition_flags = 0): array
{
$definition_flag_names = [];
foreach (Attribute::get_definition_flags() as $id => $term) {
if ($id & $definition_flags) {
$definition_flag_names[$id] = lang('Attributes.' . strtolower($term) . '_visibility');
}
}
return $definition_flag_names;
}
/**
* @param int $definition_id
* @return void
*/
public function getView(int $definition_id = NO_DEFINITION_ID): void
{
$info = $this->attribute->getAttributeInfo($definition_id);
foreach(get_object_vars($info) as $property => $value)
{
$info->$property = $value;
}
/**
* @param int $definition_id
* @return void
*/
public function getView(int $definition_id = NO_DEFINITION_ID): void
{
$info = $this->attribute->getAttributeInfo($definition_id);
foreach (get_object_vars($info) as $property => $value) {
$info->$property = $value;
}
$data['definition_id'] = $definition_id;
$data['definition_values'] = $this->attribute->get_definition_values($definition_id);
$data['definition_group'] = $this->attribute->get_definitions_by_type(GROUP, $definition_id);
$data['definition_group'][''] = lang('Common.none_selected_text');
$data['definition_info'] = $info;
$data['definition_id'] = $definition_id;
$data['definition_values'] = $this->attribute->get_definition_values($definition_id);
$data['definition_group'] = $this->attribute->get_definitions_by_type(GROUP, $definition_id);
$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;
$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);
$show_all = Attribute::SHOW_IN_ITEMS | Attribute::SHOW_IN_RECEIVINGS | Attribute::SHOW_IN_SALES;
$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);
}
echo view('attributes/form', $data);
}
/**
* AJAX called function to delete an attribute value. This is never called in the code. Perhaps it was boiler plate code that just isn't needed?
* @param int $attribute_id
* @return bool
* @noinspection PhpUnused
*/
public function delete_value(int $attribute_id): bool //TODO: This function appears to never be used in the codebase. Is it needed?
{
return $this->attribute->delete_value($attribute_id, NO_DEFINITION_ID);
}
/**
* Deletes an attribute definition
* @return void
*/
public function postDelete(): void
{
$attributes_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* Deletes an attribute definition
* @return void
*/
public function postDelete(): void
{
$attributes_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
if($this->attribute->delete_definition_list($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]);
}
else
{
echo json_encode(['success' => false, 'message' => lang('Attributes.definition_cannot_be_deleted')]);
}
}
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]);
} else {
echo json_encode(['success' => false, 'message' => lang('Attributes.definition_cannot_be_deleted')]);
}
}
}

View File

@@ -53,6 +53,6 @@ abstract class BaseController extends Controller
// Preload any models, libraries, etc, here.
// E.g.: $this->session = \Config\Services::session();
// E.g.: $this->session = service('session');
}
}

View File

@@ -10,301 +10,271 @@ use Config\Services;
class Cashups extends Secure_Controller
{
private Cashup $cashup;
private Expense $expense;
private Summary_payments $summary_payments;
private array $config;
private Cashup $cashup;
private Expense $expense;
private Summary_payments $summary_payments;
private array $config;
public function __construct()
{
parent::__construct('cashups');
public function __construct()
{
parent::__construct('cashups');
$this->cashup = model(Cashup::class);
$this->expense = model(Expense::class);
$this->summary_payments = model(Summary_payments::class);
$this->config = config(OSPOS::class)->settings;
}
$this->cashup = model(Cashup::class);
$this->expense = model(Expense::class);
$this->summary_payments = model(Summary_payments::class);
$this->config = config(OSPOS::class)->settings;
}
/**
* @return void
*/
public function getIndex(): void
{
$data['table_headers'] = get_cashups_manage_table_headers();
/**
* @return void
*/
public function getIndex(): void
{
$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')];
// filters that will be loaded in the multiselect dropdown
$data['filters'] = ['is_deleted' => lang('Cashups.is_deleted')];
echo view('cashups/manage', $data);
}
echo view('cashups/manage', $data);
}
/**
* @return void
*/
public function getSearch(): void
{
$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->sanitizeSortColumn(CASHUPS_HEADERS, $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'cashup_id');
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$filters = [
'start_date' => $this->request->getGet('start_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS), //TODO: Is this the best way to filter dates
'end_date' => $this->request->getGet('end_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'is_deleted' => false
];
/**
* @return void
*/
public function getSearch(): void
{
$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->sanitizeSortColumn(cashup_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'cashup_id');
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$filters = [
'start_date' => $this->request->getGet('start_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS), // TODO: Is this the best way to filter dates
'end_date' => $this->request->getGet('end_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'is_deleted' => false
];
// check if any filter is set in the multiselect dropdown
$request_filters = array_fill_keys($this->request->getGet('filters', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? [], true);
$filters = array_merge($filters, $request_filters);
$cash_ups = $this->cashup->search($search, $filters, $limit, $offset, $sort, $order);
$total_rows = $this->cashup->get_found_rows($search, $filters);
$data_rows = [];
foreach($cash_ups->getResult() as $cash_up)
{
$data_rows[] = get_cash_up_data_row($cash_up);
}
// Check if any filter is set in the multiselect dropdown
$request_filters = array_fill_keys($this->request->getGet('filters', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? [], true);
$filters = array_merge($filters, $request_filters);
$cash_ups = $this->cashup->search($search, $filters, $limit, $offset, $sort, $order);
$total_rows = $this->cashup->get_found_rows($search, $filters);
$data_rows = [];
foreach ($cash_ups->getResult() as $cash_up) {
$data_rows[] = get_cash_up_data_row($cash_up);
}
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
}
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
}
/**
* @param int $cashup_id
* @return void
*/
public function getView(int $cashup_id = NEW_ENTRY): void
{
$data = [];
/**
* @param int $cashup_id
* @return void
*/
public function getView(int $cashup_id = NEW_ENTRY): void
{
$data = [];
$data['employees'] = [];
foreach($this->employee->get_all()->getResult() as $employee)
{
foreach(get_object_vars($employee) as $property => $value)
{
$employee->$property = $value;
}
$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['employees'][$employee->person_id] = $employee->first_name . ' ' . $employee->last_name;
}
$cash_ups_info = $this->cashup->get_info($cashup_id);
$cash_ups_info = $this->cashup->get_info($cashup_id);
foreach(get_object_vars($cash_ups_info) as $property => $value)
{
$cash_ups_info->$property = $value;
}
foreach (get_object_vars($cash_ups_info) as $property => $value) {
$cash_ups_info->$property = $value;
}
// open cashup
if($cash_ups_info->cashup_id == NEW_ENTRY)
{
$cash_ups_info->open_date = date('Y-m-d H:i:s');
$cash_ups_info->close_date = $cash_ups_info->open_date;
$cash_ups_info->open_employee_id = $this->employee->get_logged_in_employee_info()->person_id;
$cash_ups_info->close_employee_id = $this->employee->get_logged_in_employee_info()->person_id;
}
// if all the amounts are null or 0 that means it's a close cashup
elseif(floatval($cash_ups_info->closed_amount_cash) == 0
&& floatval($cash_ups_info->closed_amount_due) == 0
&& floatval($cash_ups_info->closed_amount_card) == 0
&& floatval($cash_ups_info->closed_amount_check) == 0)
{
// set the close date and time to the actual as this is a close session
$cash_ups_info->close_date = date('Y-m-d H:i:s');
// Open cashup
if ($cash_ups_info->cashup_id == NEW_ENTRY) {
$cash_ups_info->open_date = date('Y-m-d H:i:s');
$cash_ups_info->close_date = $cash_ups_info->open_date;
$cash_ups_info->open_employee_id = $this->employee->get_logged_in_employee_info()->person_id;
$cash_ups_info->close_employee_id = $this->employee->get_logged_in_employee_info()->person_id;
}
// If all the amounts are null or 0 that means it's a close cashup
elseif (
floatval($cash_ups_info->closed_amount_cash) == 0
&& floatval($cash_ups_info->closed_amount_due) == 0
&& floatval($cash_ups_info->closed_amount_card) == 0
&& floatval($cash_ups_info->closed_amount_check) == 0
) {
// Set the close date and time to the actual as this is a close session
$cash_ups_info->close_date = date('Y-m-d H:i:s');
// the closed amount starts with the open amount -/+ any trasferred amount
$cash_ups_info->closed_amount_cash = $cash_ups_info->open_amount_cash + $cash_ups_info->transfer_amount_cash;
// The closed amount starts with the open amount -/+ any trasferred amount
$cash_ups_info->closed_amount_cash = $cash_ups_info->open_amount_cash + $cash_ups_info->transfer_amount_cash;
// if it's date mode only and not date & time truncate the open and end date to date only
if(empty($this->config['date_or_time_format']))
{
if($cash_ups_info->open_date != null)
{
$start_date = substr($cash_ups_info->open_date, 0, 10);
}
else
{
$start_date = null;
}
if($cash_ups_info->close_date != null)
{
$end_date = substr($cash_ups_info->close_date, 0, 10);
}
else
{
$end_date = null;
}
// search for all the payments given the time range
$inputs = [
'start_date' => $start_date,
'end_date' => $end_date,
'sale_type' => 'complete',
'location_id' => 'all'
];
}
else
{
// search for all the payments given the time range
$inputs = [
'start_date' => $cash_ups_info->open_date,
'end_date' => $cash_ups_info->close_date,
'sale_type' => 'complete',
'location_id' => 'all'
];
}
// If it's date mode only and not date & time truncate the open and end date to date only
if (empty($this->config['date_or_time_format'])) {
if ($cash_ups_info->open_date != null) {
$start_date = substr($cash_ups_info->open_date, 0, 10);
} else {
$start_date = null;
}
if ($cash_ups_info->close_date != null) {
$end_date = substr($cash_ups_info->close_date, 0, 10);
} else {
$end_date = null;
}
// Search for all the payments given the time range
$inputs = [
'start_date' => $start_date,
'end_date' => $end_date,
'sale_type' => 'complete',
'location_id' => 'all'
];
} else {
// Search for all the payments given the time range
$inputs = [
'start_date' => $cash_ups_info->open_date,
'end_date' => $cash_ups_info->close_date,
'sale_type' => 'complete',
'location_id' => 'all'
];
}
// get all the transactions payment summaries
$reports_data = $this->summary_payments->getData($inputs);
// Get all the transactions payment summaries
$reports_data = $this->summary_payments->getData($inputs);
foreach($reports_data as $row)
{
if($row['trans_group'] == lang('Reports.trans_payments'))
{
if($row['trans_type'] == lang('Sales.cash'))
{
$cash_ups_info->closed_amount_cash += $row['trans_amount'];
}
elseif($row['trans_type'] == lang('Sales.due'))
{
$cash_ups_info->closed_amount_due += $row['trans_amount'];
}
elseif($row['trans_type'] == lang('Sales.debit') ||
$row['trans_type'] == lang('Sales.credit'))
{
$cash_ups_info->closed_amount_card += $row['trans_amount'];
}
elseif($row['trans_type'] == lang('Sales.check'))
{
$cash_ups_info->closed_amount_check += $row['trans_amount'];
}
}
}
foreach ($reports_data as $row) {
if ($row['trans_group'] == lang('Reports.trans_payments')) {
if ($row['trans_type'] == lang('Sales.cash')) {
$cash_ups_info->closed_amount_cash += $row['trans_amount'];
} elseif ($row['trans_type'] == lang('Sales.due')) {
$cash_ups_info->closed_amount_due += $row['trans_amount'];
} elseif (
$row['trans_type'] == lang('Sales.debit') ||
$row['trans_type'] == lang('Sales.credit')
) {
$cash_ups_info->closed_amount_card += $row['trans_amount'];
} elseif ($row['trans_type'] == lang('Sales.check')) {
$cash_ups_info->closed_amount_check += $row['trans_amount'];
}
}
}
// lookup expenses paid in cash
$filters = [
'only_cash' => true,
'only_due' => false,
'only_check' => false,
'only_credit' => false,
'only_debit' => false,
'is_deleted' => false
];
// Lookup expenses paid in cash
$filters = [
'only_cash' => true,
'only_due' => false,
'only_check' => false,
'only_credit' => false,
'only_debit' => false,
'is_deleted' => false
];
$payments = $this->expense->get_payments_summary('', array_merge($inputs, $filters));
$payments = $this->expense->get_payments_summary('', array_merge($inputs, $filters));
foreach($payments as $row)
{
$cash_ups_info->closed_amount_cash -= $row['amount'];
}
foreach ($payments as $row) {
$cash_ups_info->closed_amount_cash -= $row['amount'];
}
$cash_ups_info->closed_amount_total = $this->_calculate_total($cash_ups_info->open_amount_cash, $cash_ups_info->transfer_amount_cash, $cash_ups_info->closed_amount_cash, $cash_ups_info->closed_amount_due, $cash_ups_info->closed_amount_card, $cash_ups_info->closed_amount_check);
}
$cash_ups_info->closed_amount_total = $this->_calculate_total($cash_ups_info->open_amount_cash, $cash_ups_info->transfer_amount_cash, $cash_ups_info->closed_amount_cash, $cash_ups_info->closed_amount_due, $cash_ups_info->closed_amount_card, $cash_ups_info->closed_amount_check);
}
$data['cash_ups_info'] = $cash_ups_info;
$data['cash_ups_info'] = $cash_ups_info;
echo view("cashups/form", $data);
}
echo view("cashups/form", $data);
}
/**
* @param int $row_id
* @return void
*/
public function getRow(int $row_id): void
{
$cash_ups_info = $this->cashup->get_info($row_id);
$data_row = get_cash_up_data_row($cash_ups_info);
/**
* @param int $row_id
* @return void
*/
public function getRow(int $row_id): void
{
$cash_ups_info = $this->cashup->get_info($row_id);
$data_row = get_cash_up_data_row($cash_ups_info);
echo json_encode($data_row);
}
echo json_encode($data_row);
}
/**
* @param int $cashup_id
* @return void
*/
public function postSave(int $cashup_id = NEW_ENTRY): void
{
$open_date = $this->request->getPost('open_date');
$open_date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $open_date);
/**
* @param int $cashup_id
* @return void
*/
public function postSave(int $cashup_id = NEW_ENTRY): void
{
$open_date = $this->request->getPost('open_date');
$open_date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $open_date);
$close_date = $this->request->getPost('close_date');
$close_date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $close_date);
$close_date = $this->request->getPost('close_date');
$close_date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $close_date);
$cash_up_data = [
'open_date' => $open_date_formatter->format('Y-m-d H:i:s'),
'close_date' => $close_date_formatter->format('Y-m-d H:i:s'),
'open_amount_cash' => parse_decimals($this->request->getPost('open_amount_cash')),
'transfer_amount_cash' => parse_decimals($this->request->getPost('transfer_amount_cash')),
'closed_amount_cash' => parse_decimals($this->request->getPost('closed_amount_cash')),
'closed_amount_due' => parse_decimals($this->request->getPost('closed_amount_due')),
'closed_amount_card' => parse_decimals($this->request->getPost('closed_amount_card')),
'closed_amount_check' => parse_decimals($this->request->getPost('closed_amount_check')),
'closed_amount_total' => parse_decimals($this->request->getPost('closed_amount_total')),
'note' => $this->request->getPost('note') != null,
'description' => $this->request->getPost('description', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'open_employee_id' => $this->request->getPost('open_employee_id', FILTER_SANITIZE_NUMBER_INT),
'close_employee_id' => $this->request->getPost('close_employee_id', FILTER_SANITIZE_NUMBER_INT),
'deleted' => $this->request->getPost('deleted') != null
];
$cash_up_data = [
'open_date' => $open_date_formatter->format('Y-m-d H:i:s'),
'close_date' => $close_date_formatter->format('Y-m-d H:i:s'),
'open_amount_cash' => parse_decimals($this->request->getPost('open_amount_cash')),
'transfer_amount_cash' => parse_decimals($this->request->getPost('transfer_amount_cash')),
'closed_amount_cash' => parse_decimals($this->request->getPost('closed_amount_cash')),
'closed_amount_due' => parse_decimals($this->request->getPost('closed_amount_due')),
'closed_amount_card' => parse_decimals($this->request->getPost('closed_amount_card')),
'closed_amount_check' => parse_decimals($this->request->getPost('closed_amount_check')),
'closed_amount_total' => parse_decimals($this->request->getPost('closed_amount_total')),
'note' => $this->request->getPost('note') != null,
'description' => $this->request->getPost('description', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'open_employee_id' => $this->request->getPost('open_employee_id', FILTER_SANITIZE_NUMBER_INT),
'close_employee_id' => $this->request->getPost('close_employee_id', FILTER_SANITIZE_NUMBER_INT),
'deleted' => $this->request->getPost('deleted') != null
];
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']]);
}
else // Existing Cashup
{
echo json_encode(['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]);
}
}
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']]);
} else { // Existing Cashup
echo json_encode(['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 void
*/
public function postDelete(): void
{
$cash_ups_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* @return void
*/
public function postDelete(): void
{
$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]);
}
else
{
echo json_encode(['success' => false, 'message' => lang('Cashups.cannot_be_deleted'), 'ids' => $cash_ups_to_delete]);
}
}
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]);
} else {
echo json_encode(['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
* @noinspection PhpUnused
*/
public function ajax_cashup_total(): void
{
$open_amount_cash = parse_decimals($this->request->getPost('open_amount_cash'));
$transfer_amount_cash = parse_decimals($this->request->getPost('transfer_amount_cash'));
$closed_amount_cash = parse_decimals($this->request->getPost('closed_amount_cash'));
$closed_amount_due = parse_decimals($this->request->getPost('closed_amount_due'));
$closed_amount_card = parse_decimals($this->request->getPost('closed_amount_card'));
$closed_amount_check = parse_decimals($this->request->getPost('closed_amount_check'));
/**
* Calculate the total for cashups. Used in app\Views\cashups\form.php
*
* @return void
* @noinspection PhpUnused
*/
public function postAjax_cashup_total(): void
{
$open_amount_cash = parse_decimals($this->request->getPost('open_amount_cash'));
$transfer_amount_cash = parse_decimals($this->request->getPost('transfer_amount_cash'));
$closed_amount_cash = parse_decimals($this->request->getPost('closed_amount_cash'));
$closed_amount_due = parse_decimals($this->request->getPost('closed_amount_due'));
$closed_amount_card = parse_decimals($this->request->getPost('closed_amount_card'));
$closed_amount_check = parse_decimals($this->request->getPost('closed_amount_check'));
$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
$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)]);
}
echo json_encode(['total' => to_currency_no_money($total)]);
}
/**
* Calculate total
*/
private function _calculate_total(float $open_amount_cash, float $transfer_amount_cash, float $closed_amount_due, float $closed_amount_cash, float $closed_amount_card, $closed_amount_check): float //TODO: need to get rid of hungarian notation here. Also, the signature is pretty long. Perhaps they need to go into an object or array?
{
return ($closed_amount_cash - $open_amount_cash - $transfer_amount_cash + $closed_amount_due + $closed_amount_card + $closed_amount_check);
}
/**
* Calculate total
*/
private function _calculate_total(float $open_amount_cash, float $transfer_amount_cash, float $closed_amount_due, float $closed_amount_cash, float $closed_amount_card, $closed_amount_check): float // TODO: need to get rid of hungarian notation here. Also, the signature is pretty long. Perhaps they need to go into an object or array?
{
return ($closed_amount_cash - $open_amount_cash - $transfer_amount_cash + $closed_amount_due + $closed_amount_card + $closed_amount_check);
}
}

View File

@@ -72,11 +72,12 @@ class Config extends Secure_Controller
/**
* This function loads all the licenses starting with the first one being OSPOS one
*/
private function _licenses(): array //TODO: remove hungarian notation. Super long function. Perhaps we need to refactor out functions?
private function _licenses(): array // TODO: remove hungarian notation. Super long function. Perhaps we need to refactor out functions?
{
$i = 0;
$bower = false;
$composer = false;
$npmProd = false;
$npmDev = false;
$license = [];
$license[$i]['title'] = 'Open Source Point Of Sale ' . config('App')->application_version;
@@ -87,10 +88,10 @@ class Config extends Secure_Controller
$license[$i]['text'] = 'LICENSE file must be in OSPOS license directory. You are not allowed to use OSPOS application until the distribution copy of LICENSE file is present.';
}
$dir = new DirectoryIterator('license'); // read all the files in the dir license
$dir = new DirectoryIterator('license'); // Read all the files in the dir license
foreach ($dir as $fileinfo) { //TODO: $fileinfo doesn't match our variable naming convention
// license files must be in couples: .version (name & version) & .license (license text)
foreach ($dir as $fileinfo) { // TODO: $fileinfo doesn't match our variable naming convention
// License files must be in couples: .version (name & version) & .license (license text)
if ($fileinfo->isFile()) {
if ($fileinfo->getExtension() == 'version') {
++$i;
@@ -106,17 +107,20 @@ class Config extends Secure_Controller
} else {
$license[$i]['text'] = $license_text_file . ' file is missing';
}
} elseif ($fileinfo->getBasename() == 'bower.LICENSES') {
// set a flag to indicate that the JS Plugin bower.LICENSES file is available and needs to be attached at the end
$bower = true;
} elseif ($fileinfo->getBasename() == 'composer.LICENSES') {
// set a flag to indicate that the composer.LICENSES file is available and needs to be attached at the end
// Set a flag to indicate that the composer.LICENSES file is available and needs to be attached at the end
$composer = true;
} elseif ($fileinfo->getBasename() == 'npm-prod.LICENSES') {
// Set a flag to indicate that the npm-prod.LICENSES file is available and needs to be attached at the end
$npmProd = true;
} elseif ($fileinfo->getBasename() == 'npm-dev.LICENSES') {
// Set a flag to indicate that the npm-dev.LICENSES file is available and needs to be attached at the end
$npmDev = true;
}
}
}
// attach the licenses from the LICENSES file generated by bower
// Attach the licenses from the LICENSES file generated by Composer
if ($composer) {
++$i;
$license[$i]['title'] = 'Composer Libraries';
@@ -125,65 +129,63 @@ class Config extends Secure_Controller
$file = file_get_contents('license/composer.LICENSES');
$array = json_decode($file, true);
foreach ($array as $key => $val) {
if (is_array($val) && $key == 'dependencies') {
foreach ($val as $key1 => $val1) {
if (is_array($val1)) {
$license[$i]['text'] .= "component: $key1\n"; //TODO: Duplicated Code
if (isset($array['dependencies'])) {
foreach ($array['dependencies'] as $dependency => $details) {
$license[$i]['text'] .= "library: $dependency\n";
foreach ($val1 as $key2 => $val2) {
if (is_array($val2)) {
$license[$i]['text'] .= "$key2: ";
foreach ($val2 as $key3 => $val3) {
$license[$i]['text'] .= "$val3 ";
}
$license[$i]['text'] .= "\n";
} else {
$license[$i]['text'] .= "$key2: $val2\n";
}
}
$license[$i]['text'] .= "\n";
foreach ($details as $key => $value) {
if (is_array($value)) {
$license[$i]['text'] .= "$key: " . implode(' ', $value) . "\n";
} else {
$license[$i]['text'] .= "$key1: $val1\n";
$license[$i]['text'] .= "$key: $value\n";
}
}
$license[$i]['text'] .= "\n";
}
$license[$i]['text'] = rtrim($license[$i]['text'], "\n");
}
}
// attach the licenses from the LICENSES file generated by bower
if ($bower) {
// Attach the licenses from the LICENSES file generated by license-report
if ($npmProd) {
++$i;
$license[$i]['title'] = 'JS Plugins';
$license[$i]['title'] = 'NPM Production Libraries';
$license[$i]['text'] = '';
$file = file_get_contents('license/bower.LICENSES');
$file = file_get_contents('license/npm-prod.LICENSES');
$array = json_decode($file, true);
foreach ($array as $key => $val) {
if (is_array($val)) {
$license[$i]['text'] .= "component: $key\n"; //TODO: Duplicated Code.
foreach ($array as $dependency) {
$license[$i]['text'] .= "library: {$dependency['name']}\n";
$license[$i]['text'] .= "authors: {$dependency['author']}\n";
$license[$i]['text'] .= "website: {$dependency['homepage']}\n";
$license[$i]['text'] .= "version: {$dependency['installedVersion']}\n";
$license[$i]['text'] .= "license: {$dependency['licenseType']}\n";
foreach ($val as $key1 => $val1) {
if (is_array($val1)) {
$license[$i]['text'] .= "$key1: ";
foreach ($val1 as $key2 => $val2) {
$license[$i]['text'] .= "$val2 ";
}
$license[$i]['text'] .= '\n';
} else {
$license[$i]['text'] .= "$key1: $val1\n";
}
}
$license[$i]['text'] .= '\n';
}
$license[$i]['text'] .= "\n";
}
$license[$i]['text'] = rtrim($license[$i]['text'], "\n");
}
if ($npmDev) {
++$i;
$license[$i]['title'] = 'NPM Development Libraries';
$license[$i]['text'] = '';
$file = file_get_contents('license/npm-dev.LICENSES');
$array = json_decode($file, true);
foreach ($array as $dependency) {
$license[$i]['text'] .= "library: {$dependency['name']}\n";
$license[$i]['text'] .= "authors: {$dependency['author']}\n";
$license[$i]['text'] .= "website: {$dependency['homepage']}\n";
$license[$i]['text'] .= "version: {$dependency['installedVersion']}\n";
$license[$i]['text'] .= "license: {$dependency['licenseType']}\n";
$license[$i]['text'] .= "\n";
}
$license[$i]['text'] = rtrim($license[$i]['text'], "\n");
}
return $license;
@@ -193,14 +195,14 @@ class Config extends Secure_Controller
* This function loads all the available themes in the dist/bootswatch directory
* @return array
*/
private function _themes(): array //TODO: Hungarian notation
private function _themes(): array // TODO: Hungarian notation
{
$themes = [];
// read all themes in the dist folder
// Read all themes in the dist folder
$dir = new DirectoryIterator('resources/bootswatch');
foreach ($dir as $dirinfo) { //TODO: $dirinfo doesn't follow naming convention
foreach ($dir as $dirinfo) { // TODO: $dirinfo doesn't follow naming convention
if ($dirinfo->isDir() && !$dirinfo->isDot() && $dirinfo->getFileName() != 'fonts') {
$file = $dirinfo->getFileName();
$themes[$file] = ucfirst($file);
@@ -233,21 +235,21 @@ class Config extends Secure_Controller
$data['currency_code'] = $this->config['currency_code'] ?? '';
$data['dbVersion'] = mysqli_get_server_info($this->db->getConnection());
//Load all the license statements, they are already XSS cleaned in the private function
// Load all the license statements, they are already XSS cleaned in the private function
$data['licenses'] = $this->_licenses();
//Load all the themes, already XSS cleaned in the private function
// Load all the themes, already XSS cleaned in the private function
$data['themes'] = $this->_themes();
//General related fields
$image_allowed_types = ['jpg','jpeg','gif','svg','webp','bmp','png','tif','tiff'];
// General related fields
$image_allowed_types = ['jpg', 'jpeg', 'gif', 'svg', 'webp', 'bmp', 'png', 'tif', 'tiff'];
$data['image_allowed_types'] = array_combine($image_allowed_types, $image_allowed_types);
$data['selected_image_allowed_types'] = explode(',', $this->config['image_allowed_types']);
//Integrations Related fields
$data['mailchimp'] = [];
// Integrations Related fields
$data['mailchimp'] = [];
if (check_encryption()) { //TODO: Hungarian notation
if (check_encryption()) { // TODO: Hungarian notation
if (!isset($this->encrypter)) {
helper('security');
$this->encrypter = Services::encrypter();
@@ -261,7 +263,7 @@ class Config extends Secure_Controller
? $this->encrypter->decrypt($this->config['mailchimp_list_id'])
: '';
//Remove any backup of .env created by check_encryption()
// Remove any backup of .env created by check_encryption()
remove_backup();
} else {
$data['mailchimp']['api_key'] = '';
@@ -286,12 +288,12 @@ class Config extends Secure_Controller
$upload_success = empty($upload_data['error']);
$batch_save_data = [
'company' => $this->request->getPost('company'),
'address' => $this->request->getPost('address'),
'phone' => $this->request->getPost('phone'),
'email' => strtolower($this->request->getPost('email', FILTER_SANITIZE_EMAIL)),
'fax' => $this->request->getPost('fax'),
'website' => $this->request->getPost('website', FILTER_SANITIZE_URL),
'company' => $this->request->getPost('company'),
'address' => $this->request->getPost('address'),
'phone' => $this->request->getPost('phone'),
'email' => strtolower($this->request->getPost('email', FILTER_SANITIZE_EMAIL)),
'fax' => $this->request->getPost('fax'),
'website' => $this->request->getPost('website', FILTER_SANITIZE_URL),
'return_policy' => $this->request->getPost('return_policy')
];
@@ -343,8 +345,8 @@ class Config extends Secure_Controller
$file_info = [
'orig_name' => $filename,
'raw_name' => $info['filename'],
'file_ext' => $file->guessExtension()
'raw_name' => $info['filename'],
'file_ext' => $file->guessExtension()
];
$file->move(FCPATH . 'uploads/', $file_info['raw_name'] . '.' . $file_info['file_ext'], true);
@@ -360,50 +362,50 @@ class Config extends Secure_Controller
*/
public function postSaveGeneral(): void
{
$batch_save_data = [
'theme' => $this->request->getPost('theme'),
'login_form' => $this->request->getPost('login_form'),
'default_sales_discount_type' => $this->request->getPost('default_sales_discount_type') != null,
'default_sales_discount' => parse_decimals($this->request->getPost('default_sales_discount')),
'default_receivings_discount_type' => $this->request->getPost('default_receivings_discount_type') != null,
'default_receivings_discount' => parse_decimals($this->request->getPost('default_receivings_discount')),
'enforce_privacy' => $this->request->getPost('enforce_privacy') != null,
$batchSaveData = [
'theme' => $this->request->getPost('theme'),
'login_form' => $this->request->getPost('login_form'),
'default_sales_discount_type' => $this->request->getPost('default_sales_discount_type') != null,
'default_sales_discount' => parse_decimals($this->request->getPost('default_sales_discount')),
'default_receivings_discount_type' => $this->request->getPost('default_receivings_discount_type') != null,
'default_receivings_discount' => parse_decimals($this->request->getPost('default_receivings_discount')),
'enforce_privacy' => $this->request->getPost('enforce_privacy') != null,
'receiving_calculate_average_price' => $this->request->getPost('receiving_calculate_average_price') != null,
'lines_per_page' => $this->request->getPost('lines_per_page', FILTER_SANITIZE_NUMBER_INT),
'notify_horizontal_position' => $this->request->getPost('notify_horizontal_position'),
'notify_vertical_position' => $this->request->getPost('notify_vertical_position'),
'image_max_width' => $this->request->getPost('image_max_width', FILTER_SANITIZE_NUMBER_INT),
'image_max_height' => $this->request->getPost('image_max_height', FILTER_SANITIZE_NUMBER_INT),
'image_max_size' => $this->request->getPost('image_max_size', FILTER_SANITIZE_NUMBER_INT),
'image_allowed_types' => implode(',', $this->request->getPost('image_allowed_types')),
'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'),
'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,
'include_hsn' => $this->request->getPost('include_hsn') != null,
'category_dropdown' => $this->request->getPost('category_dropdown') != null
'lines_per_page' => $this->request->getPost('lines_per_page', FILTER_SANITIZE_NUMBER_INT),
'notify_horizontal_position' => $this->request->getPost('notify_horizontal_position'),
'notify_vertical_position' => $this->request->getPost('notify_vertical_position'),
'image_max_width' => $this->request->getPost('image_max_width', FILTER_SANITIZE_NUMBER_INT),
'image_max_height' => $this->request->getPost('image_max_height', FILTER_SANITIZE_NUMBER_INT),
'image_max_size' => $this->request->getPost('image_max_size', FILTER_SANITIZE_NUMBER_INT),
'image_allowed_types' => implode(',', $this->request->getPost('image_allowed_types')),
'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'),
'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,
'include_hsn' => $this->request->getPost('include_hsn') != null,
'category_dropdown' => $this->request->getPost('category_dropdown') != null
];
$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;
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;
$this->attribute->save_definition($definition_data, CATEGORY_DEFINITION_ID);
} elseif ($batch_save_data['category_dropdown'] == NO_DEFINITION_ID) {
$this->attribute->delete_definition(CATEGORY_DEFINITION_ID);
$this->attribute->saveDefinition($definitionData, CATEGORY_DEFINITION_ID);
} elseif ($batchSaveData['category_dropdown'] == NO_DEFINITION_ID) {
$this->attribute->deleteDefinition(CATEGORY_DEFINITION_ID);
}
$success = $this->appconfig->batch_save($batch_save_data);
$success = $this->appconfig->batch_save($batchSaveData);
echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
}
@@ -437,11 +439,11 @@ class Config extends Secure_Controller
$number_local_example = $fmt->format(1234567890.12300);
echo json_encode([
'success' => $number_local_example != false,
'save_number_locale' => $save_number_locale,
'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,
'currency_symbol' => $currency_symbol,
'currency_code' => $currency_code,
]);
}
@@ -456,24 +458,24 @@ class Config extends Secure_Controller
{
$exploded = explode(":", $this->request->getPost('language'));
$batch_save_data = [
'currency_symbol' => $this->request->getPost('currency_symbol'),
'currency_code' => $this->request->getPost('currency_code'),
'language_code' => $exploded[0],
'language' => $exploded[1],
'timezone' => $this->request->getPost('timezone'),
'dateformat' => $this->request->getPost('dateformat'),
'timeformat' => $this->request->getPost('timeformat'),
'thousands_separator' => $this->request->getPost('thousands_separator') != null,
'number_locale' => $this->request->getPost('number_locale'),
'currency_decimals' => $this->request->getPost('currency_decimals', FILTER_SANITIZE_NUMBER_INT),
'tax_decimals' => $this->request->getPost('tax_decimals', FILTER_SANITIZE_NUMBER_INT),
'quantity_decimals' => $this->request->getPost('quantity_decimals', FILTER_SANITIZE_NUMBER_INT),
'country_codes' => htmlspecialchars($this->request->getPost('country_codes')),
'currency_symbol' => $this->request->getPost('currency_symbol'),
'currency_code' => $this->request->getPost('currency_code'),
'language_code' => $exploded[0],
'language' => $exploded[1],
'timezone' => $this->request->getPost('timezone'),
'dateformat' => $this->request->getPost('dateformat'),
'timeformat' => $this->request->getPost('timeformat'),
'thousands_separator' => $this->request->getPost('thousands_separator') != null,
'number_locale' => $this->request->getPost('number_locale'),
'currency_decimals' => $this->request->getPost('currency_decimals', FILTER_SANITIZE_NUMBER_INT),
'tax_decimals' => $this->request->getPost('tax_decimals', FILTER_SANITIZE_NUMBER_INT),
'quantity_decimals' => $this->request->getPost('quantity_decimals', FILTER_SANITIZE_NUMBER_INT),
'country_codes' => htmlspecialchars($this->request->getPost('country_codes')),
'payment_options_order' => $this->request->getPost('payment_options_order'),
'date_or_time_format' => $this->request->getPost('date_or_time_format') != null,
'cash_decimals' => $this->request->getPost('cash_decimals', FILTER_SANITIZE_NUMBER_INT),
'cash_rounding_code' => $this->request->getPost('cash_rounding_code'),
'financial_year' => $this->request->getPost('financial_year', FILTER_SANITIZE_NUMBER_INT)
'date_or_time_format' => $this->request->getPost('date_or_time_format') != null,
'cash_decimals' => $this->request->getPost('cash_decimals', FILTER_SANITIZE_NUMBER_INT),
'cash_rounding_code' => $this->request->getPost('cash_rounding_code'),
'financial_year' => $this->request->getPost('financial_year', FILTER_SANITIZE_NUMBER_INT)
];
$success = $this->appconfig->batch_save($batch_save_data);
@@ -497,14 +499,14 @@ class Config extends Secure_Controller
}
$batch_save_data = [
'protocol' => $this->request->getPost('protocol'),
'mailpath' => $this->request->getPost('mailpath'),
'smtp_host' => $this->request->getPost('smtp_host'),
'smtp_user' => $this->request->getPost('smtp_user'),
'smtp_pass' => $password,
'smtp_port' => $this->request->getPost('smtp_port', FILTER_SANITIZE_NUMBER_INT),
'protocol' => $this->request->getPost('protocol'),
'mailpath' => $this->request->getPost('mailpath'),
'smtp_host' => $this->request->getPost('smtp_host'),
'smtp_user' => $this->request->getPost('smtp_user'),
'smtp_pass' => $password,
'smtp_port' => $this->request->getPost('smtp_port', FILTER_SANITIZE_NUMBER_INT),
'smtp_timeout' => $this->request->getPost('smtp_timeout', FILTER_SANITIZE_NUMBER_INT),
'smtp_crypto' => $this->request->getPost('smtp_crypto')
'smtp_crypto' => $this->request->getPost('smtp_crypto')
];
$success = $this->appconfig->batch_save($batch_save_data);
@@ -542,7 +544,7 @@ class Config extends Secure_Controller
/**
* This function fetches all the available lists from Mailchimp for the given API key
*/
private function _mailchimp(string $api_key = ''): array //TODO: Hungarian notation
private function _mailchimp(string $api_key = ''): array // TODO: Hungarian notation
{
$mailchimp_lib = new Mailchimp_lib(['api_key' => $api_key]);
@@ -572,8 +574,8 @@ class Config extends Secure_Controller
$success = count($lists) > 0;
echo json_encode([
'success' => $success,
'message' => lang('Config.mailchimp_key_' . ($success ? '' : 'un') . 'successfully'),
'success' => $success,
'message' => lang('Config.mailchimp_key_' . ($success ? '' : 'un') . 'successfully'),
'mailchimp_lists' => $lists
]);
}
@@ -638,7 +640,7 @@ class Config extends Secure_Controller
*
* @return void
*/
public function ajax_tax_categories(): void //TODO: Is this function called anywhere in the code?
public function ajax_tax_categories(): void // TODO: Is this function called anywhere in the code?
{
$tax_categories = $this->tax->get_all_tax_categories()->getResultArray();
@@ -661,7 +663,7 @@ class Config extends Secure_Controller
/**
* @return void
*/
private function _clear_session_state(): void //TODO: Hungarian notation
private function _clear_session_state(): void // TODO: Hungarian notation
{
$this->sale_lib->clear_sale_location();
$this->sale_lib->clear_table();
@@ -685,7 +687,7 @@ class Config extends Secure_Controller
$not_to_delete = [];
foreach ($this->request->getPost() as $key => $value) {
if (str_contains($key, 'stock_location')) {
// save or update
// Save or update
foreach ($value as $location_id => $location_name) {
$location_data = ['location_name' => $location_name];
if ($this->stock_location->save_value($location_data, $location_id)) {
@@ -697,7 +699,7 @@ class Config extends Secure_Controller
}
}
// all locations not available in post will be deleted now
// All locations not available in post will be deleted now
$deleted_locations = $this->stock_location->get_all()->getResultArray();
foreach ($deleted_locations as $location => $location_data) {
@@ -730,20 +732,20 @@ class Config extends Secure_Controller
if ($dinner_table_enable) {
$not_to_delete = [];
foreach ($this->request->getPost() as $key => $value) { //TODO: Not sure if this is the best way to filter the array
foreach ($this->request->getPost() as $key => $value) { // TODO: Not sure if this is the best way to filter the array
if (strstr($key, 'dinner_table') && $key != 'dinner_table_enable') {
$dinner_table_id = preg_replace("/.*?_(\d+)$/", "$1", $key);
$not_to_delete[] = $dinner_table_id;
// save or update
// Save or update
$table_data = ['name' => $value];
if ($this->dinner_table->save_value($table_data, $dinner_table_id)) {
$this->_clear_session_state(); //TODO: Remove hungarian notation.
$this->_clear_session_state(); // TODO: Remove hungarian notation.
}
}
}
// all tables not available in post will be deleted now
// All tables not available in post will be deleted now
$deleted_tables = $this->dinner_table->get_all()->getResultArray();
foreach ($deleted_tables as $dinner_tables => $table) {
@@ -757,7 +759,7 @@ class Config extends Secure_Controller
$success = $this->db->transStatus();
echo json_encode(['success' => $success,'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
echo json_encode(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]);
}
/**
@@ -773,16 +775,16 @@ class Config extends Secure_Controller
$default_tax_2_rate = $this->request->getPost('default_tax_2_rate');
$batch_save_data = [
'default_tax_1_rate' => parse_tax(filter_var($default_tax_1_rate, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION)),
'default_tax_1_name' => $this->request->getPost('default_tax_1_name'),
'default_tax_2_rate' => parse_tax(filter_var($default_tax_2_rate, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION)),
'default_tax_2_name' => $this->request->getPost('default_tax_2_name'),
'tax_included' => $this->request->getPost('tax_included') != null,
'default_tax_1_rate' => parse_tax(filter_var($default_tax_1_rate, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION)),
'default_tax_1_name' => $this->request->getPost('default_tax_1_name'),
'default_tax_2_rate' => parse_tax(filter_var($default_tax_2_rate, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION)),
'default_tax_2_name' => $this->request->getPost('default_tax_2_name'),
'tax_included' => $this->request->getPost('tax_included') != null,
'use_destination_based_tax' => $this->request->getPost('use_destination_based_tax') != null,
'default_tax_code' => $this->request->getPost('default_tax_code'),
'default_tax_category' => $this->request->getPost('default_tax_category'),
'default_tax_jurisdiction' => $this->request->getPost('default_tax_jurisdiction'),
'tax_id' => $this->request->getPost('tax_id', FILTER_SANITIZE_NUMBER_INT)
'default_tax_code' => $this->request->getPost('default_tax_code'),
'default_tax_category' => $this->request->getPost('default_tax_category'),
'default_tax_jurisdiction' => $this->request->getPost('default_tax_jurisdiction'),
'tax_id' => $this->request->getPost('tax_id', FILTER_SANITIZE_NUMBER_INT)
];
$success = $this->appconfig->batch_save($batch_save_data);
@@ -823,13 +825,13 @@ class Config extends Secure_Controller
if (!empty($array_save)) {
foreach ($array_save as $key => $value) {
// save or update
// Save or update
$package_data = ['package_name' => $value['package_name'], 'points_percent' => $value['points_percent']];
$this->customer_rewards->save_value($package_data, $key); //TODO: reflection exception
$this->customer_rewards->save_value($package_data, $key); // TODO: reflection exception
}
}
// all packages not available in post will be deleted now
// All packages not available in post will be deleted now
$deleted_packages = $this->customer_rewards->get_all()->getResultArray();
foreach ($deleted_packages as $customer_rewards => $reward_category) {
@@ -856,21 +858,21 @@ class Config extends Secure_Controller
public function postSaveBarcode(): void
{
$batch_save_data = [
'barcode_type' => $this->request->getPost('barcode_type'),
'barcode_width' => $this->request->getPost('barcode_width', FILTER_SANITIZE_NUMBER_INT),
'barcode_height' => $this->request->getPost('barcode_height', FILTER_SANITIZE_NUMBER_INT),
'barcode_font' => $this->request->getPost('barcode_font'),
'barcode_font_size' => $this->request->getPost('barcode_font_size', FILTER_SANITIZE_NUMBER_INT),
'barcode_first_row' => $this->request->getPost('barcode_first_row'),
'barcode_second_row' => $this->request->getPost('barcode_second_row'),
'barcode_third_row' => $this->request->getPost('barcode_third_row'),
'barcode_num_in_row' => $this->request->getPost('barcode_num_in_row', FILTER_SANITIZE_NUMBER_INT),
'barcode_page_width' => $this->request->getPost('barcode_page_width', FILTER_SANITIZE_NUMBER_INT),
'barcode_page_cellspacing' => $this->request->getPost('barcode_page_cellspacing', FILTER_SANITIZE_NUMBER_INT),
'barcode_type' => $this->request->getPost('barcode_type'),
'barcode_width' => $this->request->getPost('barcode_width', FILTER_SANITIZE_NUMBER_INT),
'barcode_height' => $this->request->getPost('barcode_height', FILTER_SANITIZE_NUMBER_INT),
'barcode_font' => $this->request->getPost('barcode_font'),
'barcode_font_size' => $this->request->getPost('barcode_font_size', FILTER_SANITIZE_NUMBER_INT),
'barcode_first_row' => $this->request->getPost('barcode_first_row'),
'barcode_second_row' => $this->request->getPost('barcode_second_row'),
'barcode_third_row' => $this->request->getPost('barcode_third_row'),
'barcode_num_in_row' => $this->request->getPost('barcode_num_in_row', FILTER_SANITIZE_NUMBER_INT),
'barcode_page_width' => $this->request->getPost('barcode_page_width', FILTER_SANITIZE_NUMBER_INT),
'barcode_page_cellspacing' => $this->request->getPost('barcode_page_cellspacing', FILTER_SANITIZE_NUMBER_INT),
'barcode_generate_if_empty' => $this->request->getPost('barcode_generate_if_empty') != null,
'allow_duplicate_barcodes' => $this->request->getPost('allow_duplicate_barcodes') != null,
'barcode_content' => $this->request->getPost('barcode_content'),
'barcode_formats' => json_encode($this->request->getPost('barcode_formats'))
'allow_duplicate_barcodes' => $this->request->getPost('allow_duplicate_barcodes') != null,
'barcode_content' => $this->request->getPost('barcode_content'),
'barcode_formats' => json_encode($this->request->getPost('barcode_formats'))
];
$success = $this->appconfig->batch_save($batch_save_data);
@@ -888,24 +890,24 @@ class Config extends Secure_Controller
public function postSaveReceipt(): void
{
$batch_save_data = [
'receipt_template' => $this->request->getPost('receipt_template'),
'receipt_font_size' => $this->request->getPost('receipt_font_size', FILTER_SANITIZE_NUMBER_INT),
'print_delay_autoreturn' => $this->request->getPost('print_delay_autoreturn', FILTER_SANITIZE_NUMBER_INT),
'receipt_template' => $this->request->getPost('receipt_template'),
'receipt_font_size' => $this->request->getPost('receipt_font_size', FILTER_SANITIZE_NUMBER_INT),
'print_delay_autoreturn' => $this->request->getPost('print_delay_autoreturn', FILTER_SANITIZE_NUMBER_INT),
'email_receipt_check_behaviour' => $this->request->getPost('email_receipt_check_behaviour'),
'print_receipt_check_behaviour' => $this->request->getPost('print_receipt_check_behaviour'),
'receipt_show_company_name' => $this->request->getPost('receipt_show_company_name') != null,
'receipt_show_taxes' => $this->request->getPost('receipt_show_taxes') != null,
'receipt_show_tax_ind' => $this->request->getPost('receipt_show_tax_ind') != null,
'receipt_show_total_discount' => $this->request->getPost('receipt_show_total_discount') != null,
'receipt_show_description' => $this->request->getPost('receipt_show_description') != null,
'receipt_show_serialnumber' => $this->request->getPost('receipt_show_serialnumber') != null,
'print_silently' => $this->request->getPost('print_silently') != null,
'print_header' => $this->request->getPost('print_header') != null,
'print_footer' => $this->request->getPost('print_footer') != null,
'print_top_margin' => $this->request->getPost('print_top_margin', FILTER_SANITIZE_NUMBER_INT),
'print_left_margin' => $this->request->getPost('print_left_margin', FILTER_SANITIZE_NUMBER_INT),
'print_bottom_margin' => $this->request->getPost('print_bottom_margin', FILTER_SANITIZE_NUMBER_INT),
'print_right_margin' => $this->request->getPost('print_right_margin', FILTER_SANITIZE_NUMBER_INT)
'receipt_show_company_name' => $this->request->getPost('receipt_show_company_name') != null,
'receipt_show_taxes' => $this->request->getPost('receipt_show_taxes') != null,
'receipt_show_tax_ind' => $this->request->getPost('receipt_show_tax_ind') != null,
'receipt_show_total_discount' => $this->request->getPost('receipt_show_total_discount') != null,
'receipt_show_description' => $this->request->getPost('receipt_show_description') != null,
'receipt_show_serialnumber' => $this->request->getPost('receipt_show_serialnumber') != null,
'print_silently' => $this->request->getPost('print_silently') != null,
'print_header' => $this->request->getPost('print_header') != null,
'print_footer' => $this->request->getPost('print_footer') != null,
'print_top_margin' => $this->request->getPost('print_top_margin', FILTER_SANITIZE_NUMBER_INT),
'print_left_margin' => $this->request->getPost('print_left_margin', FILTER_SANITIZE_NUMBER_INT),
'print_bottom_margin' => $this->request->getPost('print_bottom_margin', FILTER_SANITIZE_NUMBER_INT),
'print_right_margin' => $this->request->getPost('print_right_margin', FILTER_SANITIZE_NUMBER_INT)
];
$success = $this->appconfig->batch_save($batch_save_data);
@@ -923,20 +925,20 @@ class Config extends Secure_Controller
public function postSaveInvoice(): void
{
$batch_save_data = [
'invoice_enable' => $this->request->getPost('invoice_enable') != null,
'sales_invoice_format' => $this->request->getPost('sales_invoice_format'),
'sales_quote_format' => $this->request->getPost('sales_quote_format'),
'recv_invoice_format' => $this->request->getPost('recv_invoice_format'),
'invoice_default_comments' => $this->request->getPost('invoice_default_comments'),
'invoice_email_message' => $this->request->getPost('invoice_email_message'),
'line_sequence' => $this->request->getPost('line_sequence'),
'last_used_invoice_number' => $this->request->getPost('last_used_invoice_number', FILTER_SANITIZE_NUMBER_INT),
'last_used_quote_number' => $this->request->getPost('last_used_quote_number', FILTER_SANITIZE_NUMBER_INT),
'quote_default_comments' => $this->request->getPost('quote_default_comments'),
'work_order_enable' => $this->request->getPost('work_order_enable') != null,
'work_order_format' => $this->request->getPost('work_order_format'),
'invoice_enable' => $this->request->getPost('invoice_enable') != null,
'sales_invoice_format' => $this->request->getPost('sales_invoice_format'),
'sales_quote_format' => $this->request->getPost('sales_quote_format'),
'recv_invoice_format' => $this->request->getPost('recv_invoice_format'),
'invoice_default_comments' => $this->request->getPost('invoice_default_comments'),
'invoice_email_message' => $this->request->getPost('invoice_email_message'),
'line_sequence' => $this->request->getPost('line_sequence'),
'last_used_invoice_number' => $this->request->getPost('last_used_invoice_number', FILTER_SANITIZE_NUMBER_INT),
'last_used_quote_number' => $this->request->getPost('last_used_quote_number', FILTER_SANITIZE_NUMBER_INT),
'quote_default_comments' => $this->request->getPost('quote_default_comments'),
'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' => $this->request->getPost('invoice_type')
];
$success = $this->appconfig->batch_save($batch_save_data);

View File

@@ -14,521 +14,466 @@ use stdClass;
class Customers extends Persons
{
private string $_list_id;
private Mailchimp_lib $mailchimp_lib;
private Customer_rewards $customer_rewards;
private Customer $customer;
private Tax_code $tax_code;
private array $config;
public function __construct()
{
parent::__construct('customers');
$this->mailchimp_lib = new Mailchimp_lib();
$this->customer_rewards = model(Customer_rewards::class);
$this->customer = model(Customer::class);
$this->tax_code = model(Tax_code::class);
$this->config = config(OSPOS::class)->settings;
$encrypter = Services::encrypter();
if(!empty($this->config['mailchimp_list_id']))
{
$this->_list_id = $encrypter->decrypt($this->config['mailchimp_list_id']);
}
else
{
$this->_list_id = '';
}
}
/**
* @return void
*/
public function getIndex(): void
{
$data['table_headers'] = get_customer_manage_table_headers();
echo 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
{
$person = $this->customer->get_info($row_id);
// retrieve the total amount the customer spent so far together with min, max and average values
$stats = $this->customer->get_stats($person->person_id); //TODO: This and the next 11 lines are duplicated in search(). Extract a method.
if(empty($stats))
{
//create object with empty properties.
$stats = new stdClass();
$stats->total = 0;
$stats->min = 0;
$stats->max = 0;
$stats->average = 0;
$stats->avg_discount = 0;
$stats->quantity = 0;
}
$data_row = get_customer_data_row($person, $stats);
echo json_encode($data_row);
}
/**
* Returns customer table data rows. This will be called with AJAX.
*
* @return void
*/
public function getSearch(): void
{
$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->sanitizeSortColumn(CUSTOMER_HEADERS, $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'people.person_id');
$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);
$data_rows = [];
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
if(empty($stats))
{
//create object with empty properties.
$stats = new stdClass();
$stats->total = 0;
$stats->min = 0;
$stats->max = 0;
$stats->average = 0;
$stats->avg_discount = 0;
$stats->quantity = 0;
}
$data_rows[] = get_customer_data_row($person, $stats);
}
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
}
/**
* Gives search suggestions based on what is being searched for
*/
public function getSuggest(): void
{
$search = $this->request->getGet('term');
$suggestions = $this->customer->get_search_suggestions($search);
echo json_encode($suggestions);
}
/**
* @return void
*/
public function suggest_search(): void
{
$search = $this->request->getGet('term');
$suggestions = $this->customer->get_search_suggestions($search, 25, false);
echo json_encode($suggestions);
}
/**
* Loads the customer edit form
*/
public function getView(int $customer_id = NEW_ENTRY): void
{
// Set default values
if($customer_id == null) $customer_id = NEW_ENTRY;
$info = $this->customer->get_info($customer_id);
foreach(get_object_vars($info) as $property => $value)
{
$info->$property = $value;
}
$data['person_info'] = $info;
if(empty($info->person_id) || empty($info->date) || empty($info->employee_id))
{
$data['person_info']->date = date('Y-m-d H:i:s');
$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;
$tax_code_info = $this->tax_code->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;
}
else
{
$data['sales_tax_code_label'] = '';
}
$packages = ['' => lang('Items.none')];
foreach($this->customer_rewards->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'];
// retrieve the total amount the customer spent so far together with min, max and average values
$stats = $this->customer->get_stats($customer_id);
if(!empty($stats))
{
foreach(get_object_vars($stats) as $property => $value)
{
$info->$property = $value;
}
$data['stats'] = $stats;
}
// retrieve the info from Mailchimp only if there is an email address assigned
if(!empty($info->email))
{
// collect mailchimp customer info
if(($mailchimp_info = $this->mailchimp_lib->getMemberInfo($this->_list_id, $info->email)) !== false)
{
$data['mailchimp_info'] = $mailchimp_info;
// collect customer mailchimp emails activities (stats)
if(($activities = $this->mailchimp_lib->getMemberActivity($this->_list_id, $info->email)) !== false)
{
if(array_key_exists('activity', $activities))
{
$open = 0;
$unopen = 0;
$click = 0;
$total = 0;
$lastopen = '';
foreach($activities['activity'] as $activity)
{
if($activity['action'] == 'sent')
{
++$unopen;
}
elseif($activity['action'] == 'open')
{
if(empty($lastopen))
{
$lastopen = substr($activity['timestamp'], 0, 10);
}
++$open;
}
elseif($activity['action'] == 'click')
{
if(empty($lastopen))
{
$lastopen = substr($activity['timestamp'], 0, 10);
}
++$click;
}
++$total;
}
$data['mailchimp_activity']['total'] = $total;
$data['mailchimp_activity']['open'] = $open;
$data['mailchimp_activity']['unopen'] = $unopen;
$data['mailchimp_activity']['click'] = $click;
$data['mailchimp_activity']['lastopen'] = $lastopen;
}
}
}
}
echo view("customers/form", $data);
}
/**
* Inserts/updates a customer
*/
public function postSave(int $customer_id = NEW_ENTRY): void
{
$first_name = $this->request->getPost('first_name');
$last_name = $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);
$person_data = [
'first_name' => $first_name,
'last_name' => $last_name,
'gender' => $this->request->getPost('gender', FILTER_SANITIZE_NUMBER_INT),
'email' => $email,
'phone_number' => $this->request->getPost('phone_number'),
'address_1' => $this->request->getPost('address_1'),
'address_2' => $this->request->getPost('address_2'),
'city' => $this->request->getPost('city'),
'state' => $this->request->getPost('state'),
'zip' => $this->request->getPost('zip'),
'country' => $this->request->getPost('country'),
'comments' => $this->request->getPost('comments')
];
$date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $this->request->getPost('date'));
$customer_data = [
'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'),
'company_name' => $this->request->getPost('company_name') == '' ? null : $this->request->getPost('company_name'),
'discount' => $this->request->getPost('discount') == '' ? 0.00 : parse_decimals($this->request->getPost('discount')),
'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'),
'employee_id' => $this->request->getPost('employee_id', FILTER_SANITIZE_NUMBER_INT),
'sales_tax_code_id' => $this->request->getPost('sales_tax_code_id') == '' ? null : $this->request->getPost('sales_tax_code_id', FILTER_SANITIZE_NUMBER_INT)
];
if($this->customer->save_customer($person_data, $customer_data, $customer_id))
{
// save customer to Mailchimp selected list //TODO: addOrUpdateMember should be refactored... potentially pass an array or object instead of 6 parameters.
$mailchimp_status = $this->request->getPost('mailchimp_status');
$this->mailchimp_lib->addOrUpdateMember(
$this->_list_id,
$email,
$first_name,
$last_name,
$mailchimp_status == null ? "" : $mailchimp_status,
['vip' => $this->request->getPost('mailchimp_vip') != null]
);
// New customer
if($customer_id == NEW_ENTRY)
{
echo json_encode ([
'success' => true,
'message' => lang('Customers.successful_adding') . ' ' . $first_name . ' ' . $last_name,
'id' => $customer_data['person_id']
]);
}
else // Existing customer
{
echo json_encode ([
'success' => true,
'message' => lang('Customers.successful_updating') . ' ' . $first_name . ' ' . $last_name,
'id' => $customer_id
]);
}
}
else // Failure
{
echo json_encode ([
'success' => false,
'message' => lang('Customers.error_adding_updating') . ' ' . $first_name . ' ' . $last_name,
'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
{
$email = strtolower($this->request->getPost('email', FILTER_SANITIZE_EMAIL));
$person_id = $this->request->getPost('person_id', FILTER_SANITIZE_NUMBER_INT);
$exists = $this->customer->check_email_exists($email, $person_id);
echo !$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
{
$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';
}
/**
* This deletes customers from the customers table
*/
public function postDelete(): void
{
$customers_to_delete = $this->request->getPost('ids');
$customers_info = $this->customer->get_multiple_info($customers_to_delete);
$count = 0;
foreach($customers_info->getResult() as $info)
{
if($this->customer->delete($info->person_id))
{
// remove customer from Mailchimp selected list
$this->mailchimp_lib->removeMember($this->_list_id, $info->email);
$count++;
}
}
if($count == count($customers_to_delete))
{
echo json_encode (['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')]);
}
}
/**
* 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';
$data = file_get_contents(WRITEPATH . "uploads/$name");
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
{
echo 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
{
if($_FILES['file_path']['error'] != UPLOAD_ERR_OK)
{
echo json_encode (['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;
$failCodes = [];
while(($data = fgetcsv($handle)) !== false)
{
$consent = $data[3] == '' ? 0 : 1;
if(sizeof($data) >= 16 && $consent)
{
$email = strtolower($data[4]);
$person_data = [
'first_name' => $data[0],
'last_name' => $data[1],
'gender' => $data[2],
'email' => $email,
'phone_number' => $data[5],
'address_1' => $data[6],
'address_2' => $data[7],
'city' => $data[8],
'state' => $data[9],
'zip' => $data[10],
'country' => $data[11],
'comments' => $data[12]
];
$customer_data = [
'consent' => $consent,
'company_name' => $data[13],
'discount' => $data[15],
'discount_type' => $data[16],
'taxable' => $data[17] == '' ? 0 : 1,
'date' => date('Y-m-d H:i:s'),
'employee_id' => $this->employee->get_logged_in_employee_info()->person_id
];
$account_number = $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);
}
}
else
{
$invalidated = true;
}
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']);
}
else
{
$failCodes[] = $i;
}
++$i;
}
if(count($failCodes) > 0)
{
$message = lang('Customers.csv_import_partially_failed', [count($failCodes), implode(', ', $failCodes)]);
echo json_encode (['success' => false, 'message' => $message]);
}
else
{
echo json_encode (['success' => true, 'message' => lang('Customers.csv_import_success')]);
}
}
else
{
echo json_encode (['success' => false, 'message' => lang('Customers.csv_import_nodata_wrongformat')]);
}
}
}
private string $_list_id;
private Mailchimp_lib $mailchimp_lib;
private Customer_rewards $customer_rewards;
private Customer $customer;
private Tax_code $tax_code;
private array $config;
public function __construct()
{
parent::__construct('customers');
$this->mailchimp_lib = new Mailchimp_lib();
$this->customer_rewards = model(Customer_rewards::class);
$this->customer = model(Customer::class);
$this->tax_code = model(Tax_code::class);
$this->config = config(OSPOS::class)->settings;
$encrypter = Services::encrypter();
if (!empty($this->config['mailchimp_list_id'])) {
$this->_list_id = $encrypter->decrypt($this->config['mailchimp_list_id']);
} else {
$this->_list_id = '';
}
}
/**
* @return void
*/
public function getIndex(): void
{
$data['table_headers'] = get_customer_manage_table_headers();
echo 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
{
$person = $this->customer->get_info($row_id);
// Retrieve the total amount the customer spent so far together with min, max and average values
$stats = $this->customer->get_stats($person->person_id); // TODO: This and the next 11 lines are duplicated in search(). Extract a method.
if (empty($stats)) {
// Create object with empty properties.
$stats = new stdClass();
$stats->total = 0;
$stats->min = 0;
$stats->max = 0;
$stats->average = 0;
$stats->avg_discount = 0;
$stats->quantity = 0;
}
$data_row = get_customer_data_row($person, $stats);
echo json_encode($data_row);
}
/**
* Returns customer table data rows. This will be called with AJAX.
*
* @return void
*/
public function getSearch(): void
{
$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->sanitizeSortColumn(customer_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'people.person_id');
$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);
$data_rows = [];
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
if (empty($stats)) {
// Create object with empty properties.
$stats = new stdClass();
$stats->total = 0;
$stats->min = 0;
$stats->max = 0;
$stats->average = 0;
$stats->avg_discount = 0;
$stats->quantity = 0;
}
$data_rows[] = get_customer_data_row($person, $stats);
}
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
}
/**
* Gives search suggestions based on what is being searched for
*/
public function getSuggest(): void
{
$search = $this->request->getGet('term');
$suggestions = $this->customer->get_search_suggestions($search);
echo json_encode($suggestions);
}
/**
* @return void
*/
public function suggest_search(): void
{
$search = $this->request->getGet('term');
$suggestions = $this->customer->get_search_suggestions($search, 25, false);
echo json_encode($suggestions);
}
/**
* Loads the customer edit form
*/
public function getView(int $customer_id = NEW_ENTRY): void
{
// Set default values
if ($customer_id == null) $customer_id = NEW_ENTRY;
$info = $this->customer->get_info($customer_id);
foreach (get_object_vars($info) as $property => $value) {
$info->$property = $value;
}
$data['person_info'] = $info;
if (empty($info->person_id) || empty($info->date) || empty($info->employee_id)) {
$data['person_info']->date = date('Y-m-d H:i:s');
$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;
$tax_code_info = $this->tax_code->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;
} else {
$data['sales_tax_code_label'] = '';
}
$packages = ['' => lang('Items.none')];
foreach ($this->customer_rewards->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'];
// Retrieve the total amount the customer spent so far together with min, max and average values
$stats = $this->customer->get_stats($customer_id);
if (!empty($stats)) {
foreach (get_object_vars($stats) as $property => $value) {
$info->$property = $value;
}
$data['stats'] = $stats;
}
// Retrieve the info from Mailchimp only if there is an email address assigned
if (!empty($info->email)) {
// Collect Mailchimp customer info
if (($mailchimp_info = $this->mailchimp_lib->getMemberInfo($this->_list_id, $info->email)) !== false) {
$data['mailchimp_info'] = $mailchimp_info;
// Collect customer Mailchimp emails activities (stats)
if (($activities = $this->mailchimp_lib->getMemberActivity($this->_list_id, $info->email)) !== false) {
if (array_key_exists('activity', $activities)) {
$open = 0;
$unopen = 0;
$click = 0;
$total = 0;
$lastopen = '';
foreach ($activities['activity'] as $activity) {
if ($activity['action'] == 'sent') {
++$unopen;
} elseif ($activity['action'] == 'open') {
if (empty($lastopen)) {
$lastopen = substr($activity['timestamp'], 0, 10);
}
++$open;
} elseif ($activity['action'] == 'click') {
if (empty($lastopen)) {
$lastopen = substr($activity['timestamp'], 0, 10);
}
++$click;
}
++$total;
}
$data['mailchimp_activity']['total'] = $total;
$data['mailchimp_activity']['open'] = $open;
$data['mailchimp_activity']['unopen'] = $unopen;
$data['mailchimp_activity']['click'] = $click;
$data['mailchimp_activity']['lastopen'] = $lastopen;
}
}
}
}
echo view("customers/form", $data);
}
/**
* Inserts/updates a customer
*/
public function postSave(int $customer_id = NEW_ENTRY): void
{
$first_name = $this->request->getPost('first_name');
$last_name = $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);
$person_data = [
'first_name' => $first_name,
'last_name' => $last_name,
'gender' => $this->request->getPost('gender', FILTER_SANITIZE_NUMBER_INT),
'email' => $email,
'phone_number' => $this->request->getPost('phone_number'),
'address_1' => $this->request->getPost('address_1'),
'address_2' => $this->request->getPost('address_2'),
'city' => $this->request->getPost('city'),
'state' => $this->request->getPost('state'),
'zip' => $this->request->getPost('zip'),
'country' => $this->request->getPost('country'),
'comments' => $this->request->getPost('comments')
];
$date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $this->request->getPost('date'));
$customer_data = [
'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'),
'company_name' => $this->request->getPost('company_name') == '' ? null : $this->request->getPost('company_name'),
'discount' => $this->request->getPost('discount') == '' ? 0.00 : parse_decimals($this->request->getPost('discount')),
'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'),
'employee_id' => $this->request->getPost('employee_id', FILTER_SANITIZE_NUMBER_INT),
'sales_tax_code_id' => $this->request->getPost('sales_tax_code_id') == '' ? null : $this->request->getPost('sales_tax_code_id', FILTER_SANITIZE_NUMBER_INT)
];
if ($this->customer->save_customer($person_data, $customer_data, $customer_id)) {
// Save customer to Mailchimp selected list // TODO: addOrUpdateMember should be refactored. Potentially pass an array or object instead of 6 parameters.
$mailchimp_status = $this->request->getPost('mailchimp_status');
$this->mailchimp_lib->addOrUpdateMember(
$this->_list_id,
$email,
$first_name,
$last_name,
$mailchimp_status == null ? "" : $mailchimp_status,
['vip' => $this->request->getPost('mailchimp_vip') != null]
);
// New customer
if ($customer_id == NEW_ENTRY) {
echo json_encode([
'success' => true,
'message' => lang('Customers.successful_adding') . ' ' . $first_name . ' ' . $last_name,
'id' => $customer_data['person_id']
]);
} else { // Existing customer
echo json_encode([
'success' => true,
'message' => lang('Customers.successful_updating') . ' ' . $first_name . ' ' . $last_name,
'id' => $customer_id
]);
}
} else { // Failure
echo json_encode([
'success' => false,
'message' => lang('Customers.error_adding_updating') . ' ' . $first_name . ' ' . $last_name,
'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
{
$email = strtolower($this->request->getPost('email', FILTER_SANITIZE_EMAIL));
$person_id = $this->request->getPost('person_id', FILTER_SANITIZE_NUMBER_INT);
$exists = $this->customer->check_email_exists($email, $person_id);
echo !$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
{
$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';
}
/**
* This deletes customers from the customers table
*/
public function postDelete(): void
{
$customers_to_delete = $this->request->getPost('ids');
$customers_info = $this->customer->get_multiple_info($customers_to_delete);
$count = 0;
foreach ($customers_info->getResult() as $info) {
if ($this->customer->delete($info->person_id)) {
// remove customer from Mailchimp selected list
$this->mailchimp_lib->removeMember($this->_list_id, $info->email);
$count++;
}
}
if ($count == count($customers_to_delete)) {
echo json_encode([
'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')]);
}
}
/**
* 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';
$data = file_get_contents(WRITEPATH . "uploads/$name");
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
{
echo 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
{
if ($_FILES['file_path']['error'] != UPLOAD_ERR_OK) {
echo json_encode(['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;
$failCodes = [];
while (($data = fgetcsv($handle)) !== false) {
$consent = $data[3] == '' ? 0 : 1;
if (sizeof($data) >= 16 && $consent) {
$email = strtolower($data[4]);
$person_data = [
'first_name' => $data[0],
'last_name' => $data[1],
'gender' => $data[2],
'email' => $email,
'phone_number' => $data[5],
'address_1' => $data[6],
'address_2' => $data[7],
'city' => $data[8],
'state' => $data[9],
'zip' => $data[10],
'country' => $data[11],
'comments' => $data[12]
];
$customer_data = [
'consent' => $consent,
'company_name' => $data[13],
'discount' => $data[15],
'discount_type' => $data[16],
'taxable' => $data[17] == '' ? 0 : 1,
'date' => date('Y-m-d H:i:s'),
'employee_id' => $this->employee->get_logged_in_employee_info()->person_id
];
$account_number = $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);
}
} else {
$invalidated = true;
}
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']);
} else {
$failCodes[] = $i;
}
++$i;
}
if (count($failCodes) > 0) {
$message = lang('Customers.csv_import_partially_failed', [count($failCodes), implode(', ', $failCodes)]);
echo json_encode(['success' => false, 'message' => $message]);
} else {
echo json_encode(['success' => true, 'message' => lang('Customers.csv_import_success')]);
}
} else {
echo json_encode(['success' => false, 'message' => lang('Customers.csv_import_nodata_wrongformat')]);
}
}
}
}

View File

@@ -13,222 +13,204 @@ use Config\Services;
*/
class Employees extends Persons
{
public function __construct()
{
parent::__construct('employees');
public function __construct()
{
parent::__construct('employees');
$this->module = model('Module');
}
$this->module = model('Module');
}
/**
* Returns employee table data rows. This will be called with AJAX.
*
* @return void
*/
public function getSearch(): void
{
$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->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);
/**
* Returns employee table data rows. This will be called with AJAX.
*
* @return void
*/
public function getSearch(): void
{
$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->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);
$employees = $this->employee->search($search, $limit, $offset, $sort, $order);
$total_rows = $this->employee->get_found_rows($search);
$data_rows = [];
foreach($employees->getResult() as $person)
{
$data_rows[] = get_person_data_row($person);
}
$data_rows = [];
foreach ($employees->getResult() as $person) {
$data_rows[] = get_person_data_row($person);
}
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
}
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
}
/**
* AJAX called function gives search suggestions based on what is being searched for.
*
* @return void
*/
public function getSuggest(): void
{
$search = $this->request->getGet('term');
$suggestions = $this->employee->get_search_suggestions($search, 25, true);
/**
* AJAX called function gives search suggestions based on what is being searched for.
*
* @return void
*/
public function getSuggest(): void
{
$search = $this->request->getGet('term');
$suggestions = $this->employee->get_search_suggestions($search, 25, true);
echo json_encode($suggestions);
}
echo json_encode($suggestions);
}
/**
* @return void
*/
public function suggest_search(): void
{
$search = $this->request->getPost('term');
$suggestions = $this->employee->get_search_suggestions($search);
/**
* @return void
*/
public function suggest_search(): void
{
$search = $this->request->getPost('term');
$suggestions = $this->employee->get_search_suggestions($search);
echo json_encode($suggestions);
}
echo json_encode($suggestions);
}
/**
* Loads the employee edit form
*/
public function getView(int $employee_id = NEW_ENTRY): void
{
$person_info = $this->employee->get_info($employee_id);
foreach(get_object_vars($person_info) as $property => $value)
{
$person_info->$property = $value;
}
$data['person_info'] = $person_info;
$data['employee_id'] = $employee_id;
/**
* Loads the employee edit form
*/
public function getView(int $employee_id = NEW_ENTRY): void
{
$person_info = $this->employee->get_info($employee_id);
foreach (get_object_vars($person_info) as $property => $value) {
$person_info->$property = $value;
}
$data['person_info'] = $person_info;
$data['employee_id'] = $employee_id;
$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);
$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);
$modules[] = $module;
}
$data['all_modules'] = $modules;
$modules[] = $module;
}
$data['all_modules'] = $modules;
$permissions = [];
foreach($this->module->get_all_subpermissions()->getResult() as $permission) //TODO: subpermissions does not follow naming standards.
{
$permission->permission_id = str_replace(' ', '_', $permission->permission_id);
$permission->grant = $this->employee->has_grant($permission->permission_id, $person_info->person_id);
$permissions = [];
foreach ($this->module->get_all_subpermissions()->getResult() as $permission) { // TODO: subpermissions does not follow naming standards.
$permission->permission_id = str_replace(' ', '_', $permission->permission_id);
$permission->grant = $this->employee->has_grant($permission->permission_id, $person_info->person_id);
$permissions[] = $permission;
}
$data['all_subpermissions'] = $permissions;
$permissions[] = $permission;
}
$data['all_subpermissions'] = $permissions;
echo view('employees/form', $data);
}
echo view('employees/form', $data);
}
/**
* Inserts/updates an employee
*/
public function postSave(int $employee_id = NEW_ENTRY): void
{
$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);
$email = strtolower($this->request->getPost('email', FILTER_SANITIZE_EMAIL));
/**
* Inserts/updates an employee
*/
public function postSave(int $employee_id = NEW_ENTRY): void
{
$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);
$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);
// format first and last name properly
$first_name = $this->nameize($first_name);
$last_name = $this->nameize($last_name);
$person_data = [
'first_name' => $first_name,
'last_name' => $last_name,
'gender' => $this->request->getPost('gender', FILTER_SANITIZE_NUMBER_INT),
'email' => $email,
'phone_number' => $this->request->getPost('phone_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'address_1' => $this->request->getPost('address_1', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'address_2' => $this->request->getPost('address_2', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'city' => $this->request->getPost('city', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'state' => $this->request->getPost('state', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'zip' => $this->request->getPost('zip', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'country' => $this->request->getPost('country', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'comments' => $this->request->getPost('comments', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
];
$person_data = [
'first_name' => $first_name,
'last_name' => $last_name,
'gender' => $this->request->getPost('gender', FILTER_SANITIZE_NUMBER_INT),
'email' => $email,
'phone_number' => $this->request->getPost('phone_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'address_1' => $this->request->getPost('address_1', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'address_2' => $this->request->getPost('address_2', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'city' => $this->request->getPost('city', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'state' => $this->request->getPost('state', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'zip' => $this->request->getPost('zip', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'country' => $this->request->getPost('country', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'comments' => $this->request->getPost('comments', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
];
$grants_array = [];
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) : '';
$grants_array = [];
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)
{
$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;
}
}
if ($grant == $permission->permission_id) {
$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;
}
}
//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 = [
'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
{
$exploded = explode(":", $this->request->getPost('language', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
$employee_data = [
'username' => $this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'language_code' => $exploded[0],
'language' => $exploded[1]
];
}
// 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 = [
'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
$exploded = explode(":", $this->request->getPost('language', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
$employee_data = [
'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 ([
'success' => true,
'message' => lang('Employees.successful_adding') . ' ' . $first_name . ' ' . $last_name,
'id' => $employee_data['person_id']
]);
}
else // Existing employee
{
echo json_encode ([
'success' => true,
'message' => lang('Employees.successful_updating') . ' ' . $first_name . ' ' . $last_name,
'id' => $employee_id
]);
}
}
else // Failure
{
echo json_encode ([
'success' => false,
'message' => lang('Employees.error_adding_updating') . ' ' . $first_name . ' ' . $last_name,
'id' => NEW_ENTRY
]);
}
}
if ($this->employee->save_employee($person_data, $employee_data, $grants_array, $employee_id)) {
// New employee
if ($employee_id == NEW_ENTRY) {
echo json_encode([
'success' => true,
'message' => lang('Employees.successful_adding') . ' ' . $first_name . ' ' . $last_name,
'id' => $employee_data['person_id']
]);
} else { // Existing employee
echo json_encode([
'success' => true,
'message' => lang('Employees.successful_updating') . ' ' . $first_name . ' ' . $last_name,
'id' => $employee_id
]);
}
} else { // Failure
echo json_encode([
'success' => false,
'message' => lang('Employees.error_adding_updating') . ' ' . $first_name . ' ' . $last_name,
'id' => NEW_ENTRY
]);
}
}
/**
* This deletes employees from the employees table
*/
public function postDelete(): void
{
$employees_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* This deletes employees from the employees table
*/
public function postDelete(): void
{
$employees_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
if($this->employee->delete_list($employees_to_delete)) //TODO: this is passing a string, but delete_list expects an array
{
echo json_encode ([
'success' => true,
'message' => lang('Employees.successful_deleted') . ' ' . count($employees_to_delete) . ' ' . lang('Employees.one_or_multiple')
]);
}
else
{
echo json_encode (['success' => false, 'message' => lang('Employees.cannot_be_deleted')]);
}
}
if ($this->employee->delete_list($employees_to_delete)) { // TODO: this is passing a string, but delete_list expects an array
echo json_encode([
'success' => true,
'message' => lang('Employees.successful_deleted') . ' ' . count($employees_to_delete) . ' ' . lang('Employees.one_or_multiple')
]);
} else {
echo json_encode(['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
{
$exists = $this->employee->username_exists($employee_id, $this->request->getGet('username'));
echo !$exists ? 'true' : 'false';
}
/**
* 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
{
$exists = $this->employee->username_exists($employee_id, $this->request->getGet('username'));
echo !$exists ? 'true' : 'false';
}
}

View File

@@ -9,201 +9,184 @@ use Config\Services;
class Expenses extends Secure_Controller
{
private Expense $expense;
private Expense_category $expense_category;
private Expense $expense;
private Expense_category $expense_category;
public function __construct()
{
parent::__construct('expenses');
public function __construct()
{
parent::__construct('expenses');
$this->expense = model(Expense::class);
$this->expense_category = model(Expense_category::class);
}
$this->expense = model(Expense::class);
$this->expense_category = model(Expense_category::class);
}
/**
* @return void
*/
public function getIndex(): void
{
$data['table_headers'] = get_expenses_manage_table_headers();
/**
* @return void
*/
public function getIndex(): void
{
$data['table_headers'] = get_expenses_manage_table_headers();
// filters that will be loaded in the multiselect dropdown
$data['filters'] = [
'only_cash' => lang('Expenses.cash_filter'),
'only_due' => lang('Expenses.due_filter'),
'only_check' => lang('Expenses.check_filter'),
'only_credit' => lang('Expenses.credit_filter'),
'only_debit' => lang('Expenses.debit_filter'),
'is_deleted' => lang('Expenses.is_deleted')
];
// filters that will be loaded in the multiselect dropdown
$data['filters'] = [
'only_cash' => lang('Expenses.cash_filter'),
'only_due' => lang('Expenses.due_filter'),
'only_check' => lang('Expenses.check_filter'),
'only_credit' => lang('Expenses.credit_filter'),
'only_debit' => lang('Expenses.debit_filter'),
'is_deleted' => lang('Expenses.is_deleted')
];
echo view('expenses/manage', $data);
}
echo view('expenses/manage', $data);
}
/**
* @return void
*/
public function getSearch(): void
{
$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->sanitizeSortColumn(EXPENSE_HEADERS, $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'expense_id');
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$filters = [
'start_date' => $this->request->getGet('start_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'end_date' => $this->request->getGet('end_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'only_cash' => false,
'only_due' => false,
'only_check' => false,
'only_credit' => false,
'only_debit' => false,
'is_deleted' => false
];
/**
* @return void
*/
public function getSearch(): void
{
$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->sanitizeSortColumn(expense_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'expense_id');
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$filters = [
'start_date' => $this->request->getGet('start_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'end_date' => $this->request->getGet('end_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'only_cash' => false,
'only_due' => false,
'only_check' => false,
'only_credit' => false,
'only_debit' => false,
'is_deleted' => false
];
// check if any filter is set in the multiselect dropdown
$request_filters = array_fill_keys($this->request->getGet('filters', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? [], true);
$filters = array_merge($filters, $request_filters);
$expenses = $this->expense->search($search, $filters, $limit, $offset, $sort, $order);
$total_rows = $this->expense->get_found_rows($search, $filters);
$payments = $this->expense->get_payments_summary($search, $filters);
$payment_summary = get_expenses_manage_payments_summary($payments, $expenses);
$data_rows = [];
// Check if any filter is set in the multiselect dropdown
$request_filters = array_fill_keys($this->request->getGet('filters', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? [], true);
$filters = array_merge($filters, $request_filters);
$expenses = $this->expense->search($search, $filters, $limit, $offset, $sort, $order);
$total_rows = $this->expense->get_found_rows($search, $filters);
$payments = $this->expense->get_payments_summary($search, $filters);
$payment_summary = get_expenses_manage_payments_summary($payments, $expenses);
$data_rows = [];
foreach($expenses->getResult() as $expense)
{
$data_rows[] = get_expenses_data_row($expense);
}
foreach ($expenses->getResult() as $expense) {
$data_rows[] = get_expenses_data_row($expense);
}
if($total_rows > 0)
{
$data_rows[] = get_expenses_data_last_row($expenses);
}
if ($total_rows > 0) {
$data_rows[] = get_expenses_data_last_row($expenses);
}
echo json_encode (['total' => $total_rows, 'rows' => $data_rows, 'payment_summary' => $payment_summary]);
}
echo json_encode(['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
{
$data = []; //TODO: Duplicated code
/**
* @param int $expense_id
* @return void
*/
public function getView(int $expense_id = NEW_ENTRY): void
{
$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'] = [];
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['employees'][$employee->person_id] = $employee->first_name . ' ' . $employee->last_name;
}
$data['expenses_info'] = $this->expense->get_info($expense_id);
$data['expenses_info'] = $this->expense->get_info($expense_id);
$expense_categories = [];
foreach($this->expense_category->get_all(0, 0, true)->getResultArray() as $row)
{
$expense_categories[$row['expense_category_id']] = $row['category_name'];
}
$data['expense_categories'] = $expense_categories;
$expense_categories = [];
foreach ($this->expense_category->get_all(0, 0, true)->getResultArray() as $row) {
$expense_categories[$row['expense_category_id']] = $row['category_name'];
}
$data['expense_categories'] = $expense_categories;
$expense_id = $data['expenses_info']->expense_id;
$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;
}
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['payments'] = [];
foreach($this->expense->get_expense_payment($expense_id)->getResult() as $payment)
{
foreach(get_object_vars($payment) as $property => $value)
{
$payment->$property = $value;
}
$data['payments'] = [];
foreach ($this->expense->get_expense_payment($expense_id)->getResult() as $payment) {
foreach (get_object_vars($payment) as $property => $value) {
$payment->$property = $value;
}
$data['payments'][] = $payment;
}
$data['payments'][] = $payment;
}
// 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();
// 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);
}
echo view("expenses/form", $data);
}
/**
* @param int $row_id
* @return void
*/
public function getRow(int $row_id): void
{
$expense_info = $this->expense->get_info($row_id);
$data_row = get_expenses_data_row($expense_info);
/**
* @param int $row_id
* @return void
*/
public function getRow(int $row_id): void
{
$expense_info = $this->expense->get_info($row_id);
$data_row = get_expenses_data_row($expense_info);
echo json_encode($data_row);
}
echo json_encode($data_row);
}
/**
* @param int $expense_id
* @return void
*/
public function postSave(int $expense_id = NEW_ENTRY): void
{
$config = config(OSPOS::class)->settings;
$newdate = $this->request->getPost('date', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* @param int $expense_id
* @return void
*/
public function postSave(int $expense_id = NEW_ENTRY): void
{
$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);
$date_formatter = date_create_from_format($config['dateformat'] . ' ' . $config['timeformat'], $newdate);
$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),
'supplier_tax_code' => $this->request->getPost('supplier_tax_code', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'amount' => parse_decimals($this->request->getPost('amount')),
'tax_amount' => parse_decimals($this->request->getPost('tax_amount')),
'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),
'deleted' => $this->request->getPost('deleted') != null
];
$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),
'supplier_tax_code' => $this->request->getPost('supplier_tax_code', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'amount' => parse_decimals($this->request->getPost('amount')),
'tax_amount' => parse_decimals($this->request->getPost('tax_amount')),
'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),
'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']]);
}
else // Existing Expense
{
echo json_encode (['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]);
}
}
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']]);
} else { // Existing Expense
echo json_encode(['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 void
*/
public function postDelete(): void
{
$expenses_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* @return void
*/
public function postDelete(): void
{
$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]);
}
else
{
echo json_encode (['success' => false, 'message' => lang('Expenses.cannot_be_deleted'), 'ids' => $expenses_to_delete]);
}
}
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]);
} else {
echo json_encode(['success' => false, 'message' => lang('Expenses.cannot_be_deleted'), 'ids' => $expenses_to_delete]);
}
}
}

View File

@@ -5,130 +5,120 @@ namespace App\Controllers;
use App\Models\Expense_category;
use Config\Services;
class Expenses_categories extends Secure_Controller //TODO: Is this class ever used?
class Expenses_categories extends Secure_Controller // TODO: Is this class ever used?
{
private Expense_category $expense_category;
private Expense_category $expense_category;
public function __construct()
{
parent::__construct('expenses_categories');
public function __construct()
{
parent::__construct('expenses_categories');
$this->expense_category = model(Expense_category::class);
}
$this->expense_category = model(Expense_category::class);
}
/**
* @return void
*/
public function getIndex(): void
{
$data['table_headers'] = get_expense_category_manage_table_headers();
/**
* @return void
*/
public function getIndex(): void
{
$data['table_headers'] = get_expense_category_manage_table_headers();
echo view('expenses_categories/manage', $data);
}
echo view('expenses_categories/manage', $data);
}
/**
* Returns expense_category_manage table data rows. This will be called with AJAX.
**/
public function getSearch(): void
{
$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->sanitizeSortColumn(EXPENSE_CATEGORY_HEADERS, $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'expense_category_id');
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* Returns expense_category_manage table data rows. This will be called with AJAX.
**/
public function getSearch(): void
{
$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->sanitizeSortColumn(expense_category_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'expense_category_id');
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$expense_categories = $this->expense_category->search($search, $limit, $offset, $sort, $order);
$total_rows = $this->expense_category->get_found_rows($search);
$expense_categories = $this->expense_category->search($search, $limit, $offset, $sort, $order);
$total_rows = $this->expense_category->get_found_rows($search);
$data_rows = [];
foreach($expense_categories->getResult() as $expense_category)
{
$data_rows[] = get_expense_category_data_row($expense_category);
}
$data_rows = [];
foreach ($expense_categories->getResult() as $expense_category) {
$data_rows[] = get_expense_category_data_row($expense_category);
}
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
}
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
}
/**
* @param int $row_id
* @return void
*/
public function getRow(int $row_id): void
{
$data_row = get_expense_category_data_row($this->expense_category->get_info($row_id));
/**
* @param int $row_id
* @return void
*/
public function getRow(int $row_id): void
{
$data_row = get_expense_category_data_row($this->expense_category->get_info($row_id));
echo json_encode($data_row);
}
echo json_encode($data_row);
}
/**
* @param int $expense_category_id
* @return void
*/
public function getView(int $expense_category_id = NEW_ENTRY): void
{
$data['category_info'] = $this->expense_category->get_info($expense_category_id);
/**
* @param int $expense_category_id
* @return void
*/
public function getView(int $expense_category_id = NEW_ENTRY): void
{
$data['category_info'] = $this->expense_category->get_info($expense_category_id);
echo view("expenses_categories/form", $data);
}
echo view("expenses_categories/form", $data);
}
/**
* @param int $expense_category_id
* @return void
*/
public function postSave(int $expense_category_id = NEW_ENTRY): void
{
$expense_category_data = [
'category_name' => $this->request->getPost('category_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'category_description' => $this->request->getPost('category_description', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
];
/**
* @param int $expense_category_id
* @return void
*/
public function postSave(int $expense_category_id = NEW_ENTRY): void
{
$expense_category_data = [
'category_name' => $this->request->getPost('category_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'category_description' => $this->request->getPost('category_description', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
];
if($this->expense_category->save_value($expense_category_data, $expense_category_id))
{
// New expense_category
if($expense_category_id == NEW_ENTRY)
{
echo json_encode ([
'success' => true,
'message' => lang('Expenses_categories.successful_adding'),
'id' => $expense_category_data['expense_category_id']
]);
}
else // Existing Expense Category
{
echo json_encode ([
'success' => true,
'message' => lang('Expenses_categories.successful_updating'),
'id' => $expense_category_id
]);
}
}
else//failure
{
echo json_encode ([
'success' => true,
'message' => lang('Expenses_categories.error_adding_updating') . ' ' . $expense_category_data['category_name'],
'id' => NEW_ENTRY
]);
}
}
if ($this->expense_category->save_value($expense_category_data, $expense_category_id)) {
// New expense_category
if ($expense_category_id == NEW_ENTRY) {
echo json_encode([
'success' => true,
'message' => lang('Expenses_categories.successful_adding'),
'id' => $expense_category_data['expense_category_id']
]);
} else { // Existing Expense Category
echo json_encode([
'success' => true,
'message' => lang('Expenses_categories.successful_updating'),
'id' => $expense_category_id
]);
}
} else { // Failure
echo json_encode([
'success' => true,
'message' => lang('Expenses_categories.error_adding_updating') . ' ' . $expense_category_data['category_name'],
'id' => NEW_ENTRY
]);
}
}
/**
* @return void
*/
public function postDelete(): void
{
$expense_category_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* @return void
*/
public function postDelete(): void
{
$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([
'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')]);
}
}
if ($this->expense_category->delete_list($expense_category_to_delete)) { // TODO: Convert to ternary notation.
echo json_encode([
'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')]);
}
}
}

View File

@@ -8,191 +8,180 @@ use Config\Services;
class Giftcards extends Secure_Controller
{
private Giftcard $giftcard;
private Giftcard $giftcard;
public function __construct()
{
parent::__construct('giftcards');
public function __construct()
{
parent::__construct('giftcards');
$this->giftcard = model(Giftcard::class);
}
$this->giftcard = model(Giftcard::class);
}
/**
* @return void
*/
public function getIndex(): void
{
$data['table_headers'] = get_giftcards_manage_table_headers();
/**
* @return void
*/
public function getIndex(): void
{
$data['table_headers'] = get_giftcards_manage_table_headers();
echo view('giftcards/manage', $data);
}
echo view('giftcards/manage', $data);
}
/**
* Returns Giftcards table data rows. This will be called with AJAX.
*/
public function getSearch(): void
{
$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->sanitizeSortColumn(GIFTCARD_HEADERS, $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'giftcard_id');
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* Returns Giftcards table data rows. This will be called with AJAX.
*/
public function getSearch(): void
{
$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->sanitizeSortColumn(giftcard_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'giftcard_id');
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$giftcards = $this->giftcard->search($search, $limit, $offset, $sort, $order);
$total_rows = $this->giftcard->get_found_rows($search);
$giftcards = $this->giftcard->search($search, $limit, $offset, $sort, $order);
$total_rows = $this->giftcard->get_found_rows($search);
$data_rows = [];
foreach($giftcards->getResult() as $giftcard)
{
$data_rows[] = get_giftcard_data_row($giftcard);
}
$data_rows = [];
foreach ($giftcards->getResult() as $giftcard) {
$data_rows[] = get_giftcard_data_row($giftcard);
}
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
}
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
}
/**
* Gets search suggestions for giftcards. Used in app\Views\sales\register.php
*
* @return void
* @noinspection PhpUnused
*/
public function getSuggest(): void
{
$search = $this->request->getGet('term');
$suggestions = $this->giftcard->get_search_suggestions($search, true);
/**
* Gets search suggestions for giftcards. Used in app\Views\sales\register.php
*
* @return void
* @noinspection PhpUnused
*/
public function getSuggest(): void
{
$search = $this->request->getGet('term');
$suggestions = $this->giftcard->get_search_suggestions($search, true);
echo json_encode($suggestions);
}
echo json_encode($suggestions);
}
/**
* @return void
*/
public function suggest_search(): void
{
$search = $this->request->getPost('term');
$suggestions = $this->giftcard->get_search_suggestions($search);
/**
* @return void
*/
public function suggest_search(): void
{
$search = $this->request->getPost('term');
$suggestions = $this->giftcard->get_search_suggestions($search);
echo json_encode($suggestions);
}
echo json_encode($suggestions);
}
/**
* @param int $row_id
* @return void
*/
public function getRow(int $row_id): void
{
$data_row = get_giftcard_data_row($this->giftcard->get_info($row_id));
/**
* @param int $row_id
* @return void
*/
public function getRow(int $row_id): void
{
$data_row = get_giftcard_data_row($this->giftcard->get_info($row_id));
echo json_encode($data_row);
}
echo json_encode($data_row);
}
/**
* @param int $giftcard_id
* @return void
*/
public function getView(int $giftcard_id = NEW_ENTRY): void
{
$config = config(OSPOS::class)->settings;
$giftcard_info = $this->giftcard->get_info($giftcard_id);
/**
* @param int $giftcard_id
* @return void
*/
public function getView(int $giftcard_id = NEW_ENTRY): void
{
$config = config(OSPOS::class)->settings;
$giftcard_info = $this->giftcard->get_info($giftcard_id);
$data['selected_person_name'] = ($giftcard_id > 0 && isset($giftcard_info->person_id)) ? $giftcard_info->first_name . ' ' . $giftcard_info->last_name : '';
$data['selected_person_id'] = $giftcard_info->person_id;
if($config['giftcard_number'] == 'random')
{
$data['giftcard_number'] = $giftcard_id > 0 ? $giftcard_info->giftcard_number : '';
}
else
{
$max_number_obj = $this->giftcard->get_max_number();
$max_giftnumber = isset($max_number_obj) ? $this->giftcard->get_max_number()->giftcard_number : 0; //TODO: variable does not follow naming standard.
$data['giftcard_number'] = $giftcard_id > 0 ? $giftcard_info->giftcard_number : $max_giftnumber + 1;
}
$data['giftcard_id'] = $giftcard_id;
$data['giftcard_value'] = $giftcard_info->value;
$data['selected_person_name'] = ($giftcard_id > 0 && isset($giftcard_info->person_id)) ? $giftcard_info->first_name . ' ' . $giftcard_info->last_name : '';
$data['selected_person_id'] = $giftcard_info->person_id;
if ($config['giftcard_number'] == 'random') {
$data['giftcard_number'] = $giftcard_id > 0 ? $giftcard_info->giftcard_number : '';
} else {
$max_number_obj = $this->giftcard->get_max_number();
$max_giftnumber = isset($max_number_obj) ? $this->giftcard->get_max_number()->giftcard_number : 0; // TODO: variable does not follow naming standard.
$data['giftcard_number'] = $giftcard_id > 0 ? $giftcard_info->giftcard_number : $max_giftnumber + 1;
}
$data['giftcard_id'] = $giftcard_id;
$data['giftcard_value'] = $giftcard_info->value;
echo view("giftcards/form", $data);
}
echo view("giftcards/form", $data);
}
/**
* @param int $giftcard_id
* @return void
*/
public function postSave(int $giftcard_id = NEW_ENTRY): void
{
$giftcard_number = $this->request->getPost('giftcard_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* @param int $giftcard_id
* @return void
*/
public function postSave(int $giftcard_id = NEW_ENTRY): void
{
$giftcard_number = $this->request->getPost('giftcard_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
if($giftcard_id == NEW_ENTRY && trim($giftcard_number) == '')
{
$giftcard_number = $this->giftcard->generate_unique_giftcard_name($giftcard_number);
}
if ($giftcard_id == NEW_ENTRY && trim($giftcard_number) == '') {
$giftcard_number = $this->giftcard->generate_unique_giftcard_name($giftcard_number);
}
$giftcard_data = [
'record_time' => date('Y-m-d H:i:s'),
'giftcard_number' => $giftcard_number,
'value' => parse_decimals($this->request->getPost('giftcard_amount')),
'person_id' => $this->request->getPost('person_id') == '' ? null : $this->request->getPost('person_id', FILTER_SANITIZE_NUMBER_INT)
];
$giftcard_data = [
'record_time' => date('Y-m-d H:i:s'),
'giftcard_number' => $giftcard_number,
'value' => parse_decimals($this->request->getPost('giftcard_amount')),
'person_id' => empty($this->request->getPost('person_id')) ? null : $this->request->getPost('person_id', FILTER_SANITIZE_NUMBER_INT)
];
if($this->giftcard->save_value($giftcard_data, $giftcard_id))
{
//New giftcard
if($giftcard_id == NEW_ENTRY) //TODO: Constant needed
{
echo json_encode ([
'success' => true,
'message' => lang('Giftcards.successful_adding') . ' ' . $giftcard_data['giftcard_number'],
'id' => $giftcard_data['giftcard_id']
]);
}
else //Existing giftcard
{
echo json_encode ([
'success' => true,
'message' => lang('Giftcards.successful_updating') . ' ' . $giftcard_data['giftcard_number'],
'id' => $giftcard_id
]);
}
}
else //failure
{
echo json_encode ([
'success' => false,
'message' => lang('Giftcards.error_adding_updating') . ' ' . $giftcard_data['giftcard_number'],
'id' => NEW_ENTRY
]);
}
}
if ($this->giftcard->save_value($giftcard_data, $giftcard_id)) {
// New giftcard
if ($giftcard_id == NEW_ENTRY) { // TODO: Constant needed
echo json_encode([
'success' => true,
'message' => lang('Giftcards.successful_adding') . ' ' . $giftcard_data['giftcard_number'],
'id' => $giftcard_data['giftcard_id']
]);
} else { // Existing giftcard
echo json_encode([
'success' => true,
'message' => lang('Giftcards.successful_updating') . ' ' . $giftcard_data['giftcard_number'],
'id' => $giftcard_id
]);
}
} else { // Failure
echo json_encode([
'success' => false,
'message' => lang('Giftcards.error_adding_updating') . ' ' . $giftcard_data['giftcard_number'],
'id' => NEW_ENTRY
]);
}
}
/**
* Checks the giftcard number validity. Used in app\Views\giftcards\form.php
*
* @return void
* @noinspection PhpUnused
*/
public function postCheckNumberGiftcard(): void
{
$giftcard_amount = parse_decimals($this->request->getPost('giftcard_amount'));
$parsed_value = filter_var($giftcard_amount, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
echo json_encode (['success' => $parsed_value !== false && $parsed_value > 0 && $giftcard_amount !== false, 'giftcard_amount' => to_currency_no_money($parsed_value)]);
}
/**
* Checks the giftcard number validity. Used in app\Views\giftcards\form.php
*
* @return void
* @noinspection PhpUnused
*/
public function postCheckNumberGiftcard(): void
{
$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 );
/**
* @return void
*/
public function postDelete(): void
{
$giftcards_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
echo $success ? 'true' : 'false';
}
if($this->giftcard->delete_list($giftcards_to_delete))
{
echo json_encode ([
'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 void
*/
public function postDelete(): void
{
$giftcards_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
if ($this->giftcard->delete_list($giftcards_to_delete)) {
echo json_encode([
'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')]);
}
}
}

View File

@@ -6,97 +6,87 @@ use CodeIgniter\HTTP\RedirectResponse;
class Home extends Secure_Controller
{
public function __construct()
{
parent::__construct('home', null, 'home');
}
public function __construct()
{
parent::__construct('home', null, 'home');
}
/**
* @return void
*/
public function getIndex(): void
{
$logged_in = $this->employee->is_logged_in();
echo view('home/home');
}
/**
* @return void
*/
public function getIndex(): void
{
$logged_in = $this->employee->is_logged_in();
echo view('home/home');
}
/**
* Logs the currently logged in employee out of the system. Used in app/Views/partial/header.php
*
* @return RedirectResponse
* @noinspection PhpUnused
*/
public function getLogout(): RedirectResponse
{
$this->employee->logout();
return redirect()->to('login');
}
/**
* Logs the currently logged in employee out of the system. Used in app/Views/partial/header.php
*
* @return RedirectResponse
* @noinspection PhpUnused
*/
public function getLogout(): RedirectResponse
{
$this->employee->logout();
return redirect()->to('login');
}
/**
* Load "change employee password" form
*
* @noinspection PhpUnused
*/
public function getChangePassword(int $employee_id = -1): void //TODO: Replace -1 with a constant
{
$person_info = $this->employee->get_info($employee_id);
foreach(get_object_vars($person_info) as $property => $value)
{
$person_info->$property = $value;
}
$data['person_info'] = $person_info;
/**
* Load "change employee password" form
*
* @noinspection PhpUnused
*/
public function getChangePassword(int $employee_id = -1): void // TODO: Replace -1 with a constant
{
$person_info = $this->employee->get_info($employee_id);
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);
}
echo view('home/form_change_password', $data);
}
/**
* Change employee password
*/
public function save(int $employee_id = -1): void //TODO: Replace -1 with a constant
{
if(!empty($this->request->getPost('current_password')) && $employee_id != -1)
{
if($this->employee->check_password($this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS), $this->request->getPost('current_password')))
{
$employee_data = [
'username' => $this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'password' => password_hash($this->request->getPost('password'), PASSWORD_DEFAULT),
'hash_version' => 2
];
/**
* Change employee password
*/
public function postSave(int $employee_id = -1): void // TODO: Replace -1 with a constant
{
if (!empty($this->request->getPost('current_password')) && $employee_id != -1) {
if ($this->employee->check_password($this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS), $this->request->getPost('current_password'))) {
$employee_data = [
'username' => $this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'password' => password_hash($this->request->getPost('password'), PASSWORD_DEFAULT),
'hash_version' => 2
];
if($this->employee->change_password($employee_data, $employee_id))
{
echo json_encode ([
'success' => true,
'message' => lang('Employees.successful_change_password'),
'id' => $employee_id
]);
}
else//failure
{//TODO: Replace -1 with constant
echo json_encode ([
'success' => false,
'message' => lang('Employees.unsuccessful_change_password'),
'id' => -1
]);
}
}
else
{//TODO: Replace -1 with constant
echo json_encode ([
'success' => false,
'message' => lang('Employees.current_password_invalid'),
'id' => -1
]);
}
}
else
{//TODO: Replace -1 with constant
echo json_encode ([
'success' => false,
'message' => lang('Employees.current_password_invalid'),
'id' => -1
]);
}
}
if ($this->employee->change_password($employee_data, $employee_id) && strlen($employee_data['password']) >= 8) {
echo json_encode([
'success' => true,
'message' => lang('Employees.successful_change_password'),
'id' => $employee_id
]);
} else { // Failure // TODO: Replace -1 with constant
echo json_encode([
'success' => false,
'message' => lang('Employees.unsuccessful_change_password'),
'id' => -1
]);
}
} else { // TODO: Replace -1 with constant
echo json_encode([
'success' => false,
'message' => lang('Employees.current_password_invalid'),
'id' => -1
]);
}
} else { // TODO: Replace -1 with constant
echo json_encode([
'success' => false,
'message' => lang('Employees.current_password_invalid'),
'id' => -1
]);
}
}
}

View File

@@ -11,309 +11,284 @@ use Config\Services;
class Item_kits extends Secure_Controller
{
private Item $item;
private Item_kit $item_kit;
private Item_kit_items $item_kit_items;
private Item $item;
private Item_kit $item_kit;
private Item_kit_items $item_kit_items;
public function __construct()
{
parent::__construct('item_kits');
public function __construct()
{
parent::__construct('item_kits');
$this->item = model(Item::class);
$this->item_kit = model(Item_kit::class);
$this->item_kit_items = model(Item_kit_items::class);
}
$this->item = model(Item::class);
$this->item_kit = model(Item_kit::class);
$this->item_kit_items = model(Item_kit_items::class);
}
/**
* Add the total cost and retail price to a passed item_kit retrieving the data from each singular item part of the kit
*/
private function _add_totals_to_item_kit(object $item_kit): object //TODO: Hungarian notation
{
$kit_item_info = $this->item->get_info($item_kit->kit_item_id ?? $item_kit->item_id);
/**
* Add the total cost and retail price to a passed item_kit retrieving the data from each singular item part of the kit
*/
private function _add_totals_to_item_kit(object $item_kit): object // TODO: Hungarian notation
{
$kit_item_info = $this->item->get_info($item_kit->kit_item_id ?? $item_kit->item_id);
$item_kit->total_cost_price = 0;
$item_kit->total_unit_price = $kit_item_info->unit_price;
$total_quantity = 0;
$item_kit->total_cost_price = 0;
$item_kit->total_unit_price = $kit_item_info->unit_price;
$total_quantity = 0;
foreach($this->item_kit_items->get_info($item_kit->item_kit_id) as $item_kit_item)
{
$item_info = $this->item->get_info($item_kit_item['item_id']);
foreach(get_object_vars($item_info) as $property => $value)
{
$item_info->$property = $value;
}
foreach ($this->item_kit_items->get_info($item_kit->item_kit_id) as $item_kit_item) {
$item_info = $this->item->get_info($item_kit_item['item_id']);
foreach (get_object_vars($item_info) as $property => $value) {
$item_info->$property = $value;
}
$item_kit->total_cost_price += $item_info->cost_price * $item_kit_item['quantity'];
$item_kit->total_cost_price += $item_info->cost_price * $item_kit_item['quantity'];
if($item_kit->price_option == PRICE_OPTION_ALL || ($item_kit->price_option == PRICE_OPTION_KIT_STOCK && $item_info->stock_type == HAS_STOCK ))
{
$item_kit->total_unit_price += $item_info->unit_price * $item_kit_item['quantity'];
$total_quantity += $item_kit_item['quantity'];
}
}
if ($item_kit->price_option == PRICE_OPTION_ALL || ($item_kit->price_option == PRICE_OPTION_KIT_STOCK && $item_info->stock_type == HAS_STOCK)) {
$item_kit->total_unit_price += $item_info->unit_price * $item_kit_item['quantity'];
$total_quantity += $item_kit_item['quantity'];
}
}
$discount_fraction = bcdiv($item_kit->kit_discount, '100');
$discount_fraction = bcdiv($item_kit->kit_discount, '100');
$item_kit->total_unit_price = $item_kit->total_unit_price - round(($item_kit->kit_discount_type == PERCENT)
? bcmul($item_kit->total_unit_price, $discount_fraction)
: $item_kit->kit_discount, totals_decimals(), PHP_ROUND_HALF_UP);
$item_kit->total_unit_price = $item_kit->total_unit_price - round(($item_kit->kit_discount_type == PERCENT)
? bcmul($item_kit->total_unit_price, $discount_fraction)
: $item_kit->kit_discount, totals_decimals(), PHP_ROUND_HALF_UP);
return $item_kit;
}
return $item_kit;
}
/**
* @return void
*/
public function getIndex(): void
{
$data['table_headers'] = get_item_kits_manage_table_headers();
/**
* @return void
*/
public function getIndex(): void
{
$data['table_headers'] = get_item_kits_manage_table_headers();
echo view('item_kits/manage', $data);
}
echo view('item_kits/manage', $data);
}
/**
* Returns Item_kit table data rows. This will be called with AJAX.
*/
public function getSearch(): void
{
$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->sanitizeSortColumn(ITEM_KIT_HEADERS, $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'item_kit_id');
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* Returns Item_kit table data rows. This will be called with AJAX.
*/
public function getSearch(): void
{
$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->sanitizeSortColumn(item_kit_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'item_kit_id');
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$item_kits = $this->item_kit->search($search, $limit, $offset, $sort, $order);
$total_rows = $this->item_kit->get_found_rows($search);
$item_kits = $this->item_kit->search($search, $limit, $offset, $sort, $order);
$total_rows = $this->item_kit->get_found_rows($search);
$data_rows = [];
foreach($item_kits->getResult() as $item_kit)
{
// calculate the total cost and retail price of the Kit, so it can be printed out in the manage table
$item_kit = $this->_add_totals_to_item_kit($item_kit);
$data_rows[] = get_item_kit_data_row($item_kit);
}
$data_rows = [];
foreach ($item_kits->getResult() as $item_kit) {
// Calculate the total cost and retail price of the Kit, so it can be printed out in the manage table
$item_kit = $this->_add_totals_to_item_kit($item_kit);
$data_rows[] = get_item_kit_data_row($item_kit);
}
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
}
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
}
/**
* @return void
*/
public function suggest_search(): void
{
$search = $this->request->getPost('term');
$suggestions = $this->item_kit->get_search_suggestions($search);
/**
* @return void
*/
public function suggest_search(): void
{
$search = $this->request->getPost('term');
$suggestions = $this->item_kit->get_search_suggestions($search);
echo json_encode($suggestions);
}
echo json_encode($suggestions);
}
/**
* @param int $row_id
* @return void
*/
public function getRow(int $row_id): void
{
// 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));
/**
* @param int $row_id
* @return void
*/
public function getRow(int $row_id): void
{
// 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));
}
echo json_encode(get_item_kit_data_row($item_kit));
}
/**
* @param int $item_kit_id
* @return void
*/
public function getView(int $item_kit_id = NEW_ENTRY): void
{
$info = $this->item_kit->get_info($item_kit_id);
/**
* @param int $item_kit_id
* @return void
*/
public function getView(int $item_kit_id = NEW_ENTRY): void
{
$info = $this->item_kit->get_info($item_kit_id);
if($item_kit_id == NEW_ENTRY)
{
$info->price_option = '0';
$info->print_option = PRINT_ALL;
$info->kit_item_id = 0;
$info->item_number = '';
$info->kit_discount = 0;
}
if ($item_kit_id == NEW_ENTRY) {
$info->price_option = '0';
$info->print_option = PRINT_ALL;
$info->kit_item_id = 0;
$info->item_number = '';
$info->kit_discount = 0;
}
foreach(get_object_vars($info) as $property => $value)
{
$info->$property = $value;
}
foreach (get_object_vars($info) as $property => $value) {
$info->$property = $value;
}
$data['item_kit_info'] = $info;
$data['item_kit_info'] = $info;
$items = [];
$items = [];
foreach($this->item_kit_items->get_info($item_kit_id) as $item_kit_item)
{
$item['kit_sequence'] = $item_kit_item['kit_sequence'];
$item['name'] = $this->item->get_info($item_kit_item['item_id'])->name;
$item['item_id'] = $item_kit_item['item_id'];
$item['quantity'] = $item_kit_item['quantity'];
foreach ($this->item_kit_items->get_info($item_kit_id) as $item_kit_item) {
$item['kit_sequence'] = $item_kit_item['kit_sequence'];
$item['name'] = $this->item->get_info($item_kit_item['item_id'])->name;
$item['item_id'] = $item_kit_item['item_id'];
$item['quantity'] = $item_kit_item['quantity'];
$items[] = $item;
}
$items[] = $item;
}
$data['item_kit_items'] = $items;
$data['item_kit_items'] = $items;
$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 : '';
$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);
}
echo view("item_kits/form", $data);
}
/**
* @param int $item_kit_id
* @return void
*/
public function postSave(int $item_kit_id = NEW_ENTRY): void
{
$item_kit_data = [
'name' => $this->request->getPost('name'),
'item_kit_number' => $this->request->getPost('item_kit_number'),
'item_id' => $this->request->getPost('kit_item_id') ? null : intval($this->request->getPost('kit_item_id')),
'kit_discount' => parse_decimals($this->request->getPost('kit_discount')),
'kit_discount_type' => $this->request->getPost('kit_discount_type') === null ? PERCENT : intval($this->request->getPost('kit_discount_type')),
'price_option' => $this->request->getPost('price_option') === null ? PRICE_ALL : intval($this->request->getPost('price_option')),
'print_option' => $this->request->getPost('print_option') === null ? PRINT_ALL : intval($this->request->getPost('print_option')),
'description' => $this->request->getPost('description')
];
/**
* @param int $item_kit_id
* @return void
*/
public function postSave(int $item_kit_id = NEW_ENTRY): void
{
$item_kit_data = [
'name' => $this->request->getPost('name'),
'item_kit_number' => $this->request->getPost('item_kit_number'),
'item_id' => $this->request->getPost('kit_item_id'),
'kit_discount' => parse_decimals($this->request->getPost('kit_discount')),
'kit_discount_type' => $this->request->getPost('kit_discount_type') === null ? PERCENT : intval($this->request->getPost('kit_discount_type')),
'price_option' => $this->request->getPost('price_option') === null ? PRICE_ALL : intval($this->request->getPost('price_option')),
'print_option' => $this->request->getPost('print_option') === null ? PRINT_ALL : intval($this->request->getPost('print_option')),
'description' => $this->request->getPost('description')
];
if($this->item_kit->save_value($item_kit_data, $item_kit_id))
{
$new_item = false;
//New item kit
if($item_kit_id == NEW_ENTRY)
{
$item_kit_id = $item_kit_data['item_kit_id'];
$new_item = true;
}
if ($this->item_kit->save_value($item_kit_data, $item_kit_id)) {
$new_item = false;
// New item kit
if ($item_kit_id == NEW_ENTRY) {
$item_kit_id = $item_kit_data['item_kit_id'];
$new_item = true;
}
$item_kit_items_array = $this->request->getPost('item_kit_qty') === null ? null : $this->request->getPost('item_kit_qty');
$item_kit_items_array = $this->request->getPost('item_kit_qty') === null ? null : $this->request->getPost('item_kit_qty');
if($item_kit_items_array != null)
{
$item_kit_items = [];
foreach($item_kit_items_array as $item_id => $item_kit_qty)
{
$item_kit_items[] = [
'item_id' => $item_id,
'quantity' => $item_kit_qty === null ? 0 : parse_quantity($item_kit_qty),
'kit_sequence' => $this->request->getPost("item_kit_seq[$item_id]") === null ? 0 : intval($this->request->getPost("item_kit_seq[$item_id]"))
];
}
}
if ($item_kit_items_array != null) {
$item_kit_items = [];
foreach ($item_kit_items_array as $item_id => $item_kit_qty) {
$item_kit_items[] = [
'item_id' => $item_id,
'quantity' => $item_kit_qty === null ? 0 : parse_quantity($item_kit_qty),
'kit_sequence' => $this->request->getPost("item_kit_seq[$item_id]") === null ? 0 : intval($this->request->getPost("item_kit_seq[$item_id]"))
];
}
}
if (!empty($item_kit_items))
{
$success = $this->item_kit_items->save_value($item_kit_items, $item_kit_id);
}
else
{
$success = true;
}
if (!empty($item_kit_items)) {
$success = $this->item_kit_items->save_value($item_kit_items, $item_kit_id);
} else {
$success = true;
}
if($new_item)
{
echo json_encode ([
'success' => $success,
'message' => lang('Item_kits.successful_adding').' '.$item_kit_data['name'],
'id' => $item_kit_id
]);
if ($new_item) {
echo json_encode([
'success' => $success,
'message' => lang('Item_kits.successful_adding') . ' ' . $item_kit_data['name'],
'id' => $item_kit_id
]);
} else {
echo json_encode([
'success' => $success,
'message' => lang('Item_kits.successful_updating') . ' ' . $item_kit_data['name'],
'id' => $item_kit_id
]);
}
} else { // Failure
echo json_encode([
'success' => false,
'message' => lang('Item_kits.error_adding_updating') . ' ' . $item_kit_data['name'],
'id' => NEW_ENTRY
]);
}
}
}
else
{
echo json_encode ([
'success' => $success,
'message' => lang('Item_kits.successful_updating').' '.$item_kit_data['name'],
'id' => $item_kit_id
]);
}
}
else//failure
{
echo json_encode ([
'success' => false,
'message' => lang('Item_kits.error_adding_updating') . ' ' . $item_kit_data['name'],
'id' => NEW_ENTRY
]);
}
}
/**
* @return void
*/
public function postDelete(): void
{
$item_kits_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* @return void
*/
public function postDelete(): void
{
$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([
'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')]);
}
}
if($this->item_kit->delete_list($item_kits_to_delete))
{
echo json_encode ([
'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')]);
}
}
/**
* Checks the validity of the item kit number. Used in app/Views/item_kits/form.php
*
* @return void
* @noinspection PhpUnused
*/
public function postCheckItemNumber(): void
{
$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';
}
/**
* Checks the validity of the item kit number. Used in app/Views/item_kits/form.php
*
* @return void
* @noinspection PhpUnused
*/
public function postCheckItemNumber(): void
{
$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';
}
/**
* 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
* @noinspection PhpUnused
*/
public function getGenerateBarcodes(string $item_kit_ids): void
{
$barcode_lib = new Barcode_lib();
$result = [];
/**
* 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
* @noinspection PhpUnused
*/
public function getGenerateBarcodes(string $item_kit_ids): void
{
$barcode_lib = new Barcode_lib();
$result = [];
$item_kit_ids = explode(':', $item_kit_ids);
foreach ($item_kit_ids as $item_kid_id) {
// Calculate the total cost and retail price of the Kit, so it can be added to the barcode text at the bottom
$item_kit = $this->_add_totals_to_item_kit($this->item_kit->get_info($item_kid_id));
$item_kit_ids = explode(':', $item_kit_ids);
foreach($item_kit_ids as $item_kid_id)
{
// calculate the total cost and retail price of the Kit, so it can be added to the barcode text at the bottom
$item_kit = $this->_add_totals_to_item_kit($this->item_kit->get_info($item_kid_id));
$item_kid_id = 'KIT ' . urldecode($item_kid_id);
$item_kid_id = 'KIT '. urldecode($item_kid_id);
$result[] = [
'name' => $item_kit->name,
'item_id' => $item_kid_id,
'item_number' => $item_kid_id,
'cost_price' => $item_kit->total_cost_price,
'unit_price' => $item_kit->total_unit_price
];
}
$result[] = [
'name' => $item_kit->name,
'item_id' => $item_kid_id,
'item_number' => $item_kid_id,
'cost_price' => $item_kit->total_cost_price,
'unit_price' => $item_kit->total_unit_price
];
}
$data['items'] = $result;
$barcode_config = $barcode_lib->get_barcode_config();
// In case the selected barcode type is not Code39 or Code128 we set by default Code128
// The rationale for this is that EAN codes cannot have strings as seed, so 'KIT ' is not allowed
if ($barcode_config['barcode_type'] != 'C39' && $barcode_config['barcode_type'] != 'C128') {
$barcode_config['barcode_type'] = 'C128';
}
$data['barcode_config'] = $barcode_config;
$data['items'] = $result;
$barcode_config = $barcode_lib->get_barcode_config();
// in case the selected barcode type is not Code39 or Code128 we set by default Code128
// the rationale for this is that EAN codes cannot have strings as seed, so 'KIT ' is not allowed
if($barcode_config['barcode_type'] != 'C39' && $barcode_config['barcode_type'] != 'C128')
{
$barcode_config['barcode_type'] = 'C128';
}
$data['barcode_config'] = $barcode_config;
// display barcodes
echo view("barcodes/barcode_sheet", $data);
}
// Display barcodes
echo view("barcodes/barcode_sheet", $data);
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -14,65 +14,61 @@ use Config\Services;
*/
class Login extends BaseController
{
public Model $employee;
public Model $employee;
/**
* @return RedirectResponse|string
*/
public function index(): string|RedirectResponse
{
$this->employee = model(Employee::class);
if(!$this->employee->is_logged_in())
{
$migration = new MY_Migration(config('Migrations'));
$config = config(OSPOS::class)->settings;
/**
* @return RedirectResponse|string
*/
public function index(): string|RedirectResponse
{
$this->employee = model(Employee::class);
if (!$this->employee->is_logged_in()) {
$migration = new MY_Migration(config('Migrations'));
$config = config(OSPOS::class)->settings;
$gcaptcha_enabled = array_key_exists('gcaptcha_enable', $config)
? $config['gcaptcha_enable']
: false;
$gcaptcha_enabled = array_key_exists('gcaptcha_enable', $config)
? $config['gcaptcha_enable']
: false;
$migration->migrate_to_ci4();
$migration->migrate_to_ci4();
$validation = Services::validation();
$validation = Services::validation();
$data = [
'has_errors' => false,
'is_latest' => $migration->is_latest(),
'latest_version' => $migration->get_latest_migration(),
'gcaptcha_enabled' => $gcaptcha_enabled,
'config' => $config,
'validation' => $validation
];
$data = [
'has_errors' => false,
'is_latest' => $migration->is_latest(),
'latest_version' => $migration->get_latest_migration(),
'gcaptcha_enabled' => $gcaptcha_enabled,
'config' => $config,
'validation' => $validation
];
if($this->request->getMethod() !== 'POST')
{
return view('login', $data);
}
if ($this->request->getMethod() !== 'POST') {
return view('login', $data);
}
$rules = ['username' => 'required|login_check[data]'];
$messages = [
'username' => [
'required' => lang('Login.required_username'),
'login_check' => lang('Login.invalid_username_and_password'),
]
];
$rules = ['username' => 'required|login_check[data]'];
$messages = [
'username' => [
'required' => lang('Login.required_username'),
'login_check' => lang('Login.invalid_username_and_password'),
]
];
if(!$this->validate($rules, $messages))
{
$data['has_errors'] = !empty($validation->getErrors());
if (!$this->validate($rules, $messages)) {
$data['has_errors'] = !empty($validation->getErrors());
return view('login', $data);
}
return view('login', $data);
}
if(!$data['is_latest'])
{
set_time_limit(3600);
if (!$data['is_latest']) {
set_time_limit(3600);
$migration->setNamespace('App')->latest();
return redirect()->to('login');
}
}
$migration->setNamespace('App')->latest();
return redirect()->to('login');
}
}
return redirect()->to('home');
}
return redirect()->to('home');
}
}

View File

@@ -8,90 +8,83 @@ use App\Models\Person;
class Messages extends Secure_Controller
{
private Sms_lib $sms_lib;
private Sms_lib $sms_lib;
public function __construct()
{
parent::__construct('messages');
public function __construct()
{
parent::__construct('messages');
$this->sms_lib = new Sms_lib();
}
$this->sms_lib = new Sms_lib();
}
/**
* @return void
*/
public function getIndex(): void
{
echo view('messages/sms');
}
/**
* @return void
*/
public function getIndex(): void
{
echo view('messages/sms');
}
/**
* @param int $person_id
* @return void
*/
public function getView(int $person_id = NEW_ENTRY): void
{
$person = model(Person::class);
$info = $person->get_info($person_id);
/**
* @param int $person_id
* @return void
*/
public function getView(int $person_id = NEW_ENTRY): void
{
$person = model(Person::class);
$info = $person->get_info($person_id);
foreach(get_object_vars($info) as $property => $value)
{
$info->$property = $value;
}
$data['person_info'] = $info;
foreach (get_object_vars($info) as $property => $value) {
$info->$property = $value;
}
$data['person_info'] = $info;
echo view('messages/form_sms', $data);
}
echo view('messages/form_sms', $data);
}
/**
* @return void
*/
public function send(): void
{
$phone = $this->request->getPost('phone', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$message = $this->request->getPost('message', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* @return void
*/
public function send(): void
{
$phone = $this->request->getPost('phone', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$message = $this->request->getPost('message', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$response = $this->sms_lib->sendSMS($phone, $message);
$response = $this->sms_lib->sendSMS($phone, $message);
if($response)
{
echo json_encode (['success' => true, 'message' => lang('Messages.successfully_sent') . ' ' . esc($phone)]);
}
else
{
echo json_encode (['success' => false, 'message' => lang('Messages.unsuccessfully_sent') . ' ' . esc($phone)]);
}
}
if ($response) {
echo json_encode(['success' => true, 'message' => lang('Messages.successfully_sent') . ' ' . esc($phone)]);
} else {
echo json_encode(['success' => false, 'message' => lang('Messages.unsuccessfully_sent') . ' ' . esc($phone)]);
}
}
/**
* Sends an SMS message to a user. Used in app/Views/messages/form_sms.php.
*
* @param int $person_id
* @return void
* @noinspection PhpUnused
*/
public function send_form(int $person_id = NEW_ENTRY): void
{
$phone = $this->request->getPost('phone', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$message = $this->request->getPost('message', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* Sends an SMS message to a user. Used in app/Views/messages/form_sms.php.
*
* @param int $person_id
* @return void
* @noinspection PhpUnused
*/
public function send_form(int $person_id = NEW_ENTRY): void
{
$phone = $this->request->getPost('phone', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$message = $this->request->getPost('message', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$response = $this->sms_lib->sendSMS($phone, $message);
$response = $this->sms_lib->sendSMS($phone, $message);
if($response)
{
echo json_encode ([
'success' => true,
'message' => lang('Messages.successfully_sent') . ' ' . esc($phone),
'person_id' => $person_id
]);
}
else
{
echo json_encode ([
'success' => false,
'message' => lang('Messages.unsuccessfully_sent') . ' ' . esc($phone),
'person_id' => NEW_ENTRY
]);
}
}
if ($response) {
echo json_encode([
'success' => true,
'message' => lang('Messages.successfully_sent') . ' ' . esc($phone),
'person_id' => $person_id
]);
} else {
echo json_encode([
'success' => false,
'message' => lang('Messages.unsuccessfully_sent') . ' ' . esc($phone),
'person_id' => NEW_ENTRY
]);
}
}
}

View File

@@ -12,23 +12,23 @@ use App\Models\Module;
*/
class No_access extends BaseController
{
private Module $module;
private Module $module;
public function __construct()
{
$this->module = model(Module::class);
}
public function __construct()
{
$this->module = model(Module::class);
}
/**
* @param string $module_id
* @param string $permission_id
* @return void
*/
public function getIndex(string $module_id = '', string $permission_id = ''): void
{
$data['module_name'] = $this->module->get_module_name($module_id);
$data['permission_id'] = $permission_id;
/**
* @param string $module_id
* @param string $permission_id
* @return void
*/
public function getIndex(string $module_id = '', string $permission_id = ''): void
{
$data['module_name'] = $this->module->get_module_name($module_id);
$data['permission_id'] = $permission_id;
echo view('no_access', $data);
}
echo view('no_access', $data);
}
}

View File

@@ -9,28 +9,28 @@ use App\Models\Employee;
*/
class Office extends Secure_Controller
{
protected Employee $employee;
protected Employee $employee;
public function __construct()
{
parent::__construct('office', null, 'office');
}
public function __construct()
{
parent::__construct('office', null, 'office');
}
/**
* @return void
*/
public function getIndex(): void
{
echo view('home/office');
}
/**
* @return void
*/
public function getIndex(): void
{
echo view('home/office');
}
/**
* @return void
*/
public function logout(): void
{
$this->employee = model(Employee::class);
/**
* @return void
*/
public function logout(): void
{
$this->employee = model(Employee::class);
$this->employee->logout();
}
$this->employee->logout();
}
}

View File

@@ -8,64 +8,66 @@ use function Tamtamchik\NameCase\str_name_case;
abstract class Persons extends Secure_Controller
{
protected Person $person;
protected Person $person;
/**
* @param string|null $module_id
*/
public function __construct(string $module_id = null)
{
parent::__construct($module_id);
/**
* @param string|null $module_id
*/
public function __construct(?string $module_id = null)
{
parent::__construct($module_id);
$this->person = model(Person::class);
}
$this->person = model(Person::class);
}
/**
* @return void
*/
public function getIndex(): void
{
$data['table_headers'] = get_people_manage_table_headers();
/**
* @return void
*/
public function getIndex(): void
{
$data['table_headers'] = get_people_manage_table_headers();
echo view('people/manage', $data);
}
echo view('people/manage', $data);
}
/**
* Gives search suggestions based on what is being searched for
*/
public function getSuggest(): void
{
$search = $this->request->getGet('term');
$suggestions = $this->person->get_search_suggestions($search);
/**
* Gives search suggestions based on what is being searched for
*/
public function getSuggest(): void
{
$search = $this->request->getGet('term');
$suggestions = $this->person->get_search_suggestions($search);
echo json_encode($suggestions);
}
echo json_encode($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
{
$data_row = get_person_data_row($this->person->get_info($row_id));
/**
* Gets one row for a person manage table. This is called using AJAX to update one row.
*/
public function getRow(int $row_id): void
{
$data_row = get_person_data_row($this->person->get_info($row_id));
echo json_encode($data_row);
}
echo json_encode($data_row);
}
/**
* 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);
/**
* 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);
//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);
}
// 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);
}
}

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

@@ -22,130 +22,141 @@ use Config\Services;
*/
class Secure_Controller extends BaseController
{
public array $global_view_data;
protected Employee $employee;
protected Module $module;
protected Session $session;
public array $global_view_data;
protected Employee $employee;
protected Module $module;
protected Session $session;
/**
* @param string $module_id
* @param string|null $submodule_id
* @param string|null $menu_group
*/
public function __construct(string $module_id = '', string $submodule_id = null, string $menu_group = null)
{
$this->employee = model(Employee::class);
$this->module = model(Module::class);
$config = config(OSPOS::class)->settings;
$validation = Services::validation();
/**
* @param string $module_id
* @param string|null $submodule_id
* @param string|null $menu_group
*/
public function __construct(string $module_id = '', ?string $submodule_id = null, ?string $menu_group = null)
{
$this->employee = model(Employee::class);
$this->module = model(Module::class);
$config = config(OSPOS::class)->settings;
$validation = Services::validation();
if(!$this->employee->is_logged_in())
{
header("Location:".base_url('login'));
exit();
}
if (!$this->employee->is_logged_in()) {
header("Location:" . base_url('login'));
exit();
}
$logged_in_employee_info = $this->employee->get_logged_in_employee_info();
if(!$this->employee->has_module_grant($module_id, $logged_in_employee_info->person_id)
|| (isset($submodule_id) && !$this->employee->has_module_grant($submodule_id, $logged_in_employee_info->person_id)))
{
header("Location:".base_url("no_access/$module_id/$submodule_id"));
exit();
}
$logged_in_employee_info = $this->employee->get_logged_in_employee_info();
if (
!$this->employee->has_module_grant($module_id, $logged_in_employee_info->person_id)
|| (isset($submodule_id) && !$this->employee->has_module_grant($submodule_id, $logged_in_employee_info->person_id))
) {
header("Location:" . base_url("no_access/$module_id/$submodule_id"));
exit();
}
// load up global global_view_data visible to all the loaded views
$this->session = session();
if($menu_group == null)
{
$menu_group = $this->session->get('menu_group');
}
else
{
$this->session->set('menu_group', $menu_group);
}
// Load up global global_view_data visible to all the loaded views
$this->session = session();
if ($menu_group == null) {
$menu_group = $this->session->get('menu_group');
} else {
$this->session->set('menu_group', $menu_group);
}
$allowed_modules = $menu_group == 'home'
? $this->module->get_allowed_home_modules($logged_in_employee_info->person_id)
: $this->module->get_allowed_office_modules($logged_in_employee_info->person_id);
$allowed_modules = $menu_group == 'home'
? $this->module->get_allowed_home_modules($logged_in_employee_info->person_id)
: $this->module->get_allowed_office_modules($logged_in_employee_info->person_id);
$this->global_view_data = [];
foreach($allowed_modules->getResult() as $module)
{
$this->global_view_data['allowed_modules'][] = $module;
}
$this->global_view_data = [];
foreach ($allowed_modules->getResult() as $module) {
$this->global_view_data['allowed_modules'][] = $module;
}
$this->global_view_data += [
'user_info' => $logged_in_employee_info,
'controller_name' => $module_id,
'config' => $config
];
view('viewData', $this->global_view_data);
}
$this->global_view_data += [
'user_info' => $logged_in_employee_info,
'controller_name' => $module_id,
'config' => $config
];
view('viewData', $this->global_view_data);
}
public function sanitizeSortColumn($headers, $field, $default): string
{
return $field != null && in_array($field, array_keys(array_merge(...$headers))) ? $field : $default;
}
public function sanitizeSortColumn($headers, $field, $default): string
{
return $field != null && in_array($field, array_keys(array_merge(...$headers))) ? $field : $default;
}
/**
* AJAX function used to confirm whether values sent in the request are numeric
* @return void
* @noinspection PhpUnused
*/
public function getCheckNumeric(): void
{
foreach($this->request->getGet() as $value)
{
if (parse_decimals($value) === false)
{
echo 'false';
return;
}
}
echo 'true';
}
/**
* AJAX function used to confirm whether values sent in the request are numeric
* @return void
* @noinspection PhpUnused
*/
public function getCheckNumeric(): void
{
foreach ($this->request->getGet() as $value) {
if (parse_decimals($value) === false) {
echo 'false';
return;
}
}
echo 'true';
}
/**
* @param $key
* @return mixed|void
*/
public function getConfig($key)
{
if (isset($config[$key]))
{
return $config[$key];
}
}
/**
* @param $key
* @return mixed|void
*/
public function getConfig($key)
{
if (isset($config[$key])) {
return $config[$key];
}
}
/**
* @return false
*/
public function getIndex() { return false; }
/**
* @return false
*/
public function getIndex()
{
return false;
}
/**
* @return false
*/
public function getSearch() { return false; }
/**
* @return false
*/
public function getSearch()
{
return false;
}
/**
* @return false
*/
public function suggest_search() { return false; }
/**
* @return false
*/
public function suggest_search()
{
return false;
}
/**
* @param int $data_item_id
* @return false
*/
public function getView(int $data_item_id = -1) { return false; }
/**
* @param int $data_item_id
* @return false
*/
public function getView(int $data_item_id = -1)
{
return false;
}
/**
* @param int $data_item_id
* @return false
*/
public function postSave(int $data_item_id = -1) { return false; }
/**
* @param int $data_item_id
* @return false
*/
public function postSave(int $data_item_id = -1)
{
return false;
}
/**
* @return false
*/
public function postDelete() { return false; }
/**
* @return false
*/
public function postDelete()
{
return false;
}
}

View File

@@ -7,193 +7,184 @@ use Config\Services;
class Suppliers extends Persons
{
private Supplier $supplier;
private Supplier $supplier;
public function __construct()
{
parent::__construct('suppliers');
public function __construct()
{
parent::__construct('suppliers');
$this->supplier = model(Supplier::class);
}
$this->supplier = model(Supplier::class);
}
/**
* @return void
*/
public function getIndex(): void
{
$data['table_headers'] = get_suppliers_manage_table_headers();
/**
* @return void
*/
public function getIndex(): void
{
$data['table_headers'] = get_suppliers_manage_table_headers();
echo view('people/manage', $data);
}
echo 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
{
$data_row = get_supplier_data_row($this->supplier->get_info($row_id));
$data_row['category'] = $this->supplier->get_category_name($data_row['category']);
/**
* 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
{
$data_row = get_supplier_data_row($this->supplier->get_info($row_id));
$data_row['category'] = $this->supplier->get_category_name($data_row['category']);
echo json_encode($data_row);
}
echo json_encode($data_row);
}
/**
* Returns Supplier table data rows. This will be called with AJAX.
* @return void
**/
public function getSearch(): void
{
$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->sanitizeSortColumn(SUPPLIER_HEADERS, $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'people.person_id');
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* Returns Supplier table data rows. This will be called with AJAX.
* @return void
**/
public function getSearch(): void
{
$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->sanitizeSortColumn(supplier_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'people.person_id');
$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);
$suppliers = $this->supplier->search($search, $limit, $offset, $sort, $order);
$total_rows = $this->supplier->get_found_rows($search);
$data_rows = [];
$data_rows = [];
foreach($suppliers->getResult() as $supplier)
{
$row = get_supplier_data_row($supplier);
$row['category'] = $this->supplier->get_category_name($row['category']);
$data_rows[] = $row;
}
foreach ($suppliers->getResult() as $supplier) {
$row = get_supplier_data_row($supplier);
$row['category'] = $this->supplier->get_category_name($row['category']);
$data_rows[] = $row;
}
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
}
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
}
/**
* Gives search suggestions based on what is being searched for
**/
public function getSuggest(): void
{
$search = $this->request->getGet('term');
$suggestions = $this->supplier->get_search_suggestions($search, true);
/**
* Gives search suggestions based on what is being searched for
**/
public function getSuggest(): void
{
$search = $this->request->getGet('term');
$suggestions = $this->supplier->get_search_suggestions($search, true);
echo json_encode($suggestions);
}
echo json_encode($suggestions);
}
/**
* @return void
*/
public function suggest_search(): void
{
$search = $this->request->getPost('term');
$suggestions = $this->supplier->get_search_suggestions($search, false);
/**
* @return void
*/
public function suggest_search(): void
{
$search = $this->request->getPost('term');
$suggestions = $this->supplier->get_search_suggestions($search, false);
echo json_encode($suggestions);
}
echo json_encode($suggestions);
}
/**
* Loads the supplier edit form
*
* @param int $supplier_id
* @return void
*/
public function getView(int $supplier_id = NEW_ENTRY): void
{
$info = $this->supplier->get_info($supplier_id);
foreach(get_object_vars($info) as $property => $value)
{
$info->$property = $value;
}
$data['person_info'] = $info;
$data['categories'] = $this->supplier->get_categories();
/**
* Loads the supplier edit form
*
* @param int $supplier_id
* @return void
*/
public function getView(int $supplier_id = NEW_ENTRY): void
{
$info = $this->supplier->get_info($supplier_id);
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);
}
echo view("suppliers/form", $data);
}
/**
* Inserts/updates a supplier
*
* @param int $supplier_id
* @return void
*/
public function postSave(int $supplier_id = NEW_ENTRY): void
{
$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);
$email = strtolower($this->request->getPost('email', FILTER_SANITIZE_EMAIL));
/**
* Inserts/updates a supplier
*
* @param int $supplier_id
* @return void
*/
public function postSave(int $supplier_id = NEW_ENTRY): void
{
$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);
$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);
// Format first and last name properly
$first_name = $this->nameize($first_name);
$last_name = $this->nameize($last_name);
$person_data = [
'first_name' => $first_name,
'last_name' => $last_name,
'gender' => $this->request->getPost('gender'),
'email' => $email,
'phone_number' => $this->request->getPost('phone_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'address_1' => $this->request->getPost('address_1', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'address_2' => $this->request->getPost('address_2', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'city' => $this->request->getPost('city', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'state' => $this->request->getPost('state', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'zip' => $this->request->getPost('zip', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'country' => $this->request->getPost('country', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'comments' => $this->request->getPost('comments', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
];
$person_data = [
'first_name' => $first_name,
'last_name' => $last_name,
'gender' => $this->request->getPost('gender'),
'email' => $email,
'phone_number' => $this->request->getPost('phone_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'address_1' => $this->request->getPost('address_1', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'address_2' => $this->request->getPost('address_2', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'city' => $this->request->getPost('city', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'state' => $this->request->getPost('state', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'zip' => $this->request->getPost('zip', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'country' => $this->request->getPost('country', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'comments' => $this->request->getPost('comments', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
];
$supplier_data = [
'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),
'account_number' => $this->request->getPost('account_number') == '' ? null : $this->request->getPost('account_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'tax_id' => $this->request->getPost('tax_id', FILTER_SANITIZE_NUMBER_INT)
];
$supplier_data = [
'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),
'account_number' => $this->request->getPost('account_number') == '' ? null : $this->request->getPost('account_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'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
{
echo json_encode ([
'success' => true,
'message' => lang('Suppliers.successful_updating') . ' ' . $supplier_data['company_name'],
'id' => $supplier_id]);
}
}
else//failure
{
echo json_encode ([
'success' => false,
'message' => lang('Suppliers.error_adding_updating') . ' ' . $supplier_data['company_name'],
'id' => NEW_ENTRY
]);
}
}
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
/**
* This deletes suppliers from the suppliers table
*
* @return void
*/
public function postDelete(): void
{
$suppliers_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
echo json_encode([
'success' => true,
'message' => lang('Suppliers.successful_updating') . ' ' . $supplier_data['company_name'],
'id' => $supplier_id
]);
}
} else { // Failure
echo json_encode([
'success' => false,
'message' => lang('Suppliers.error_adding_updating') . ' ' . $supplier_data['company_name'],
'id' => NEW_ENTRY
]);
}
}
if($this->supplier->delete_list($suppliers_to_delete))
{
echo json_encode ([
'success' => true,
'message' => lang('Suppliers.successful_deleted') . ' ' . count($suppliers_to_delete) . ' ' . lang('Suppliers.one_or_multiple')
]);
}
else
{
echo json_encode (['success' => false, 'message' => lang('Suppliers.cannot_be_deleted')]);
}
}
/**
* This deletes suppliers from the suppliers table
*
* @return void
*/
public function postDelete(): void
{
$suppliers_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
if ($this->supplier->delete_list($suppliers_to_delete)) {
echo json_encode([
'success' => true,
'message' => lang('Suppliers.successful_deleted') . ' ' . count($suppliers_to_delete) . ' ' . lang('Suppliers.one_or_multiple')
]);
} else {
echo json_encode(['success' => false, 'message' => lang('Suppliers.cannot_be_deleted')]);
}
}
}

View File

@@ -10,132 +10,122 @@ use Config\Services;
*/
class Tax_categories extends Secure_Controller
{
private Tax_category $tax_category;
private Tax_category $tax_category;
public function __construct()
{
parent::__construct('tax_categories');
public function __construct()
{
parent::__construct('tax_categories');
$this->tax_category = model(Tax_category::class);
}
$this->tax_category = model(Tax_category::class);
}
/**
* @return void
*/
public function getIndex(): void
{
$data['tax_categories_table_headers'] = get_tax_categories_table_headers();
/**
* @return void
*/
public function getIndex(): void
{
$data['tax_categories_table_headers'] = get_tax_categories_table_headers();
echo view('taxes/tax_categories', $data);
}
echo view('taxes/tax_categories', $data);
}
/**
* Returns tax_category table data rows. This will be called with AJAX.
*
* @return void
*/
public function getSearch(): void
{
$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);
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* Returns tax_category table data rows. This will be called with AJAX.
*
* @return void
*/
public function getSearch(): void
{
$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);
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$tax_categories = $this->tax_category->search($search, $limit, $offset, $sort, $order);
$total_rows = $this->tax_category->get_found_rows($search);
$tax_categories = $this->tax_category->search($search, $limit, $offset, $sort, $order);
$total_rows = $this->tax_category->get_found_rows($search);
$data_rows = [];
foreach($tax_categories->getResult() as $tax_category)
{
$data_rows[] = get_tax_categories_data_row($tax_category);
}
$data_rows = [];
foreach ($tax_categories->getResult() as $tax_category) {
$data_rows[] = get_tax_categories_data_row($tax_category);
}
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
}
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
}
/**
* @param $row_id
* @return void
*/
public function getRow($row_id): void
{
$data_row = get_tax_categories_data_row($this->tax_category->get_info($row_id));
/**
* @param $row_id
* @return void
*/
public function getRow($row_id): void
{
$data_row = get_tax_categories_data_row($this->tax_category->get_info($row_id));
echo json_encode($data_row);
}
echo json_encode($data_row);
}
/**
* @param int $tax_category_id
* @return void
*/
public function getView(int $tax_category_id = NEW_ENTRY): void
{
$data['tax_category_info'] = $this->tax_category->get_info($tax_category_id);
/**
* @param int $tax_category_id
* @return void
*/
public function getView(int $tax_category_id = NEW_ENTRY): void
{
$data['tax_category_info'] = $this->tax_category->get_info($tax_category_id);
echo view("taxes/tax_category_form", $data);
}
echo view("taxes/tax_category_form", $data);
}
/**
* @param int $tax_category_id
* @return void
*/
public function postSave(int $tax_category_id = NEW_ENTRY): void
{
$tax_category_data = [
'tax_category' => $this->request->getPost('tax_category', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'tax_category_code' => $this->request->getPost('tax_category_code', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'tax_group_sequence' => $this->request->getPost('tax_group_sequence', FILTER_SANITIZE_NUMBER_INT)
];
/**
* @param int $tax_category_id
* @return void
*/
public function postSave(int $tax_category_id = NEW_ENTRY): void
{
$tax_category_data = [
'tax_category' => $this->request->getPost('tax_category', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'tax_category_code' => $this->request->getPost('tax_category_code', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'tax_group_sequence' => $this->request->getPost('tax_group_sequence', FILTER_SANITIZE_NUMBER_INT)
];
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 ([
'success' => true,
'message' => lang('Tax_categories.successful_adding'),
'id' => $tax_category_data['tax_category_id']
]);
}
else
{
echo json_encode ([
'success' => true,
'message' => lang('Tax_categories.successful_updating'),
'id' => $tax_category_id
]);
}
}
else
{
echo json_encode ([
'success' => false,
'message' => lang('Tax_categories.error_adding_updating') . ' ' . $tax_category_data['tax_category'],
'id' => NEW_ENTRY
]);
}
}
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([
'success' => true,
'message' => lang('Tax_categories.successful_adding'),
'id' => $tax_category_data['tax_category_id']
]);
} else {
echo json_encode([
'success' => true,
'message' => lang('Tax_categories.successful_updating'),
'id' => $tax_category_id
]);
}
} else {
echo json_encode([
'success' => false,
'message' => lang('Tax_categories.error_adding_updating') . ' ' . $tax_category_data['tax_category'],
'id' => NEW_ENTRY
]);
}
}
/**
* @return void
*/
public function postDelete(): void
{
$tax_categories_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
/**
* @return void
*/
public function postDelete(): void
{
$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 ([
'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')]);
}
}
if ($this->tax_category->delete_list($tax_categories_to_delete)) {
echo json_encode([
'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')]);
}
}
}

View File

@@ -10,143 +10,133 @@ use Config\Services;
*/
class Tax_codes extends Secure_Controller
{
private Tax_code $tax_code;
private Tax_code $tax_code;
public function __construct()
{
parent::__construct('tax_codes');
public function __construct()
{
parent::__construct('tax_codes');
$this->tax_code = model(Tax_code::class);
helper('tax_helper');
}
$this->tax_code = model(Tax_code::class);
helper('tax_helper');
}
/**
* @return void
*/
public function getIndex(): void
{
echo view('taxes/tax_codes', $this->get_data());
}
/**
* @return void
*/
public function getIndex(): void
{
echo view('taxes/tax_codes', $this->get_data());
}
/**
* @return array
*/
public function get_data(): array
{
/**
* @return array
*/
public function get_data(): array
{
$data['table_headers'] = get_tax_code_table_headers();
return $data;
}
$data['table_headers'] = get_tax_code_table_headers();
return $data;
}
/**
* Returns tax_category table data rows. This will be called with AJAX.
*
* @return void
*/
public function getSearch(): void
{
$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);
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* Returns tax_category table data rows. This will be called with AJAX.
*
* @return void
*/
public function getSearch(): void
{
$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);
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$tax_codes = $this->tax_code->search($search, $limit, $offset, $sort, $order);
$total_rows = $this->tax_code->get_found_rows($search);
$tax_codes = $this->tax_code->search($search, $limit, $offset, $sort, $order);
$total_rows = $this->tax_code->get_found_rows($search);
$data_rows = [];
$data_rows = [];
foreach($tax_codes->getResult() as $tax_code)
{
$data_rows[] = get_tax_code_data_row($tax_code);
}
foreach ($tax_codes->getResult() as $tax_code) {
$data_rows[] = get_tax_code_data_row($tax_code);
}
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
}
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
}
/**
* @param int $row_id
* @return void
*/
public function getRow(int $row_id): void
{
$data_row = get_tax_code_data_row($this->tax_code->get_info($row_id));
/**
* @param int $row_id
* @return void
*/
public function getRow(int $row_id): void
{
$data_row = get_tax_code_data_row($this->tax_code->get_info($row_id));
echo json_encode($data_row);
}
echo json_encode($data_row);
}
/**
* @param int $tax_code_id
* @return void
*/
public function getView(int $tax_code_id = NEW_ENTRY): void
{
$data['tax_code_info'] = $this->tax_code->get_info($tax_code_id);
/**
* @param int $tax_code_id
* @return void
*/
public function getView(int $tax_code_id = NEW_ENTRY): void
{
$data['tax_code_info'] = $this->tax_code->get_info($tax_code_id);
echo view("taxes/tax_code_form", $data);
}
echo view("taxes/tax_code_form", $data);
}
/**
* @param int $tax_code_id
* @return void
*/
public function postSave(int $tax_code_id = NEW_ENTRY): void
{
$tax_code_data = [
'tax_code' => $this->request->getPost('tax_code', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'tax_code_name' => $this->request->getPost('tax_code_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'city' => $this->request->getPost('city', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'state' => $this->request->getPost('state', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
];
/**
* @param int $tax_code_id
* @return void
*/
public function postSave(int $tax_code_id = NEW_ENTRY): void
{
$tax_code_data = [
'tax_code' => $this->request->getPost('tax_code', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'tax_code_name' => $this->request->getPost('tax_code_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'city' => $this->request->getPost('city', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'state' => $this->request->getPost('state', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
];
if($this->tax_code->save($tax_code_data))
{
if($tax_code_id == NEW_ENTRY)
{
echo json_encode ([
'success' => true,
'message' => lang('Tax_codes.successful_adding'),
'id' => $tax_code_data['tax_code_id']
]);
}
else
{
echo json_encode ([
'success' => true,
'message' => lang('Tax_codes.successful_updating'),
'id' => $tax_code_id
]);
}
}
else
{
echo json_encode ([
'success' => false,
'message' => lang('Tax_codes.error_adding_updating') . ' ' . $tax_code_data['tax_code_id'],
'id' => NEW_ENTRY
]);
}
}
if ($this->tax_code->save($tax_code_data)) {
if ($tax_code_id == NEW_ENTRY) {
echo json_encode([
'success' => true,
'message' => lang('Tax_codes.successful_adding'),
'id' => $tax_code_data['tax_code_id']
]);
} else {
echo json_encode([
'success' => true,
'message' => lang('Tax_codes.successful_updating'),
'id' => $tax_code_id
]);
}
} else {
echo json_encode([
'success' => false,
'message' => lang('Tax_codes.error_adding_updating') . ' ' . $tax_code_data['tax_code_id'],
'id' => NEW_ENTRY
]);
}
}
/**
* @return void
*/
public function postDelete(): void
{
$tax_codes_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
/**
* @return void
*/
public function postDelete(): void
{
$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 ([
'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')]);
}
}
if ($this->tax_code->delete_list($tax_codes_to_delete)) {
echo json_encode([
'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')]);
}
}
}

View File

@@ -10,133 +10,123 @@ use Config\Services;
*/
class Tax_jurisdictions extends Secure_Controller
{
private Tax_jurisdiction $tax_jurisdiction;
private Tax_jurisdiction $tax_jurisdiction;
public function __construct()
{
parent::__construct('tax_jurisdictions');
public function __construct()
{
parent::__construct('tax_jurisdictions');
$this->tax_jurisdiction = model(Tax_jurisdiction::class);
$this->tax_jurisdiction = model(Tax_jurisdiction::class);
helper('tax_helper');
}
helper('tax_helper');
}
/**
* @return void
*/
public function getIndex(): void
{
$data['table_headers'] = get_tax_jurisdictions_table_headers();
/**
* @return void
*/
public function getIndex(): void
{
$data['table_headers'] = get_tax_jurisdictions_table_headers();
echo view('taxes/tax_jurisdictions', $data);
}
echo view('taxes/tax_jurisdictions', $data);
}
/**
* Returns tax_category table data rows. This will be called with AJAX.
*
* @return void
*/
public function getSearch(): void
{
$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);
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* Returns tax_category table data rows. This will be called with AJAX.
*
* @return void
*/
public function getSearch(): void
{
$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);
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$tax_jurisdictions = $this->tax_jurisdiction->search($search, $limit, $offset, $sort, $order);
$total_rows = $this->tax_jurisdiction->get_found_rows($search);
$tax_jurisdictions = $this->tax_jurisdiction->search($search, $limit, $offset, $sort, $order);
$total_rows = $this->tax_jurisdiction->get_found_rows($search);
$data_rows = [];
foreach($tax_jurisdictions->getResult() as $tax_jurisdiction)
{
$data_rows[] = get_tax_jurisdictions_data_row($tax_jurisdiction);
}
$data_rows = [];
foreach ($tax_jurisdictions->getResult() as $tax_jurisdiction) {
$data_rows[] = get_tax_jurisdictions_data_row($tax_jurisdiction);
}
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
}
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
}
/**
* @param int $row_id
* @return void
*/
public function getRow(int $row_id): void
{
$data_row = get_tax_jurisdictions_data_row($this->tax_jurisdiction->get_info($row_id));
/**
* @param int $row_id
* @return void
*/
public function getRow(int $row_id): void
{
$data_row = get_tax_jurisdictions_data_row($this->tax_jurisdiction->get_info($row_id));
echo json_encode($data_row);
}
echo json_encode($data_row);
}
/**
* @param int $tax_jurisdiction_id
* @return void
*/
public function getView(int $tax_jurisdiction_id = NEW_ENTRY): void
{
$data['tax_jurisdiction_info'] = $this->tax_jurisdiction->get_info($tax_jurisdiction_id);
/**
* @param int $tax_jurisdiction_id
* @return void
*/
public function getView(int $tax_jurisdiction_id = NEW_ENTRY): void
{
$data['tax_jurisdiction_info'] = $this->tax_jurisdiction->get_info($tax_jurisdiction_id);
echo view("taxes/tax_jurisdiction_form", $data);
}
echo view("taxes/tax_jurisdiction_form", $data);
}
/**
* @param int $jurisdiction_id
* @return void
*/
public function postSave(int $jurisdiction_id = NEW_ENTRY): void
{
$tax_jurisdiction_data = [
'jurisdiction_name' => $this->request->getPost('jurisdiction_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'reporting_authority' => $this->request->getPost('reporting_authority', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
];
/**
* @param int $jurisdiction_id
* @return void
*/
public function postSave(int $jurisdiction_id = NEW_ENTRY): void
{
$tax_jurisdiction_data = [
'jurisdiction_name' => $this->request->getPost('jurisdiction_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'reporting_authority' => $this->request->getPost('reporting_authority', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
];
if($this->tax_jurisdiction->save_value($tax_jurisdiction_data))
{
if($jurisdiction_id == NEW_ENTRY)
{
echo json_encode ([
'success' => true,
'message' => lang('Tax_jurisdictions.successful_adding'),
'id' => $tax_jurisdiction_data['jurisdiction_id']
]);
}
else
{
echo json_encode ([
'success' => true,
'message' => lang('Tax_jurisdictions.successful_updating'),
'id' => $jurisdiction_id
]);
}
}
else
{
echo json_encode ([
'success' => false,
'message' => lang('Tax_jurisdictions.error_adding_updating') . ' ' . $tax_jurisdiction_data['jurisdiction_name'],
'id' => NEW_ENTRY
]);
}
}
if ($this->tax_jurisdiction->save_value($tax_jurisdiction_data)) {
if ($jurisdiction_id == NEW_ENTRY) {
echo json_encode([
'success' => true,
'message' => lang('Tax_jurisdictions.successful_adding'),
'id' => $tax_jurisdiction_data['jurisdiction_id']
]);
} else {
echo json_encode([
'success' => true,
'message' => lang('Tax_jurisdictions.successful_updating'),
'id' => $jurisdiction_id
]);
}
} else {
echo json_encode([
'success' => false,
'message' => lang('Tax_jurisdictions.error_adding_updating') . ' ' . $tax_jurisdiction_data['jurisdiction_name'],
'id' => NEW_ENTRY
]);
}
}
/**
* @return void
*/
public function postDelete(): void
{
$tax_jurisdictions_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
/**
* @return void
*/
public function postDelete(): void
{
$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 ([
'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')]);
}
}
if ($this->tax_jurisdiction->delete_list($tax_jurisdictions_to_delete)) {
echo json_encode([
'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')]);
}
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>403 Forbidden</title>
<title>403 Forbidden</title>
</head>
<body>

View File

@@ -6,25 +6,22 @@ use CodeIgniter\Database\Migration;
class Migration_Upgrade_To_3_1_1 extends Migration
{
public function __construct()
{
parent::__construct();
}
public function __construct()
{
parent::__construct();
}
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.0.2_to_3.1.1.sql');
}
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.0.2_to_3.1.1.sql');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -13,425 +13,391 @@ use CodeIgniter\Database\ResultInterface;
*/
class Migration_Sales_Tax_Data extends Migration
{
public const ROUND_UP = 5; //TODO: These need to be moved to constants.php
public const ROUND_DOWN = 6;
public const HALF_FIVE = 7;
public const YES = '1';
public const VAT_TAX = '0';
public const SALES_TAX = '1';
private Appconfig $appconfig;
public const ROUND_UP = 5; // TODO: These need to be moved to constants.php
public const ROUND_DOWN = 6;
public const HALF_FIVE = 7;
public const YES = '1';
public const VAT_TAX = '0';
public const SALES_TAX = '1';
private Appconfig $appconfig;
public function __construct()
{
parent::__construct();
$this->appconfig = model(Appconfig::class);
}
//TODO: we need to figure out why we get a server error when uncommented portions of this migration run
public function __construct()
{
parent::__construct();
$this->appconfig = model(Appconfig::class);
}
// TODO: we need to figure out why we get a server error when uncommented portions of this migration run
/**
* Perform a migration step.
*/
public function up(): void
{
$number_of_unmigrated = $this->get_count_of_unmigrated();
error_log("Migrating sales tax history. The number of sales that will be migrated is $number_of_unmigrated");
/**
* Perform a migration step.
*/
public function up(): void
{
$number_of_unmigrated = $this->get_count_of_unmigrated();
log_message('info', "Migrating sales tax history. The number of sales that will be migrated is $number_of_unmigrated");
if($number_of_unmigrated > 0)
{
$unmigrated_invoices = $this->get_unmigrated($number_of_unmigrated)->getResultArray();
if ($number_of_unmigrated > 0) {
$unmigrated_invoices = $this->get_unmigrated($number_of_unmigrated)->getResultArray();
foreach($unmigrated_invoices as $key => $unmigrated_invoice)
{
$this->upgrade_tax_history_for_sale($unmigrated_invoice['sale_id']);
}
}
foreach ($unmigrated_invoices as $key => $unmigrated_invoice) {
$this->upgrade_tax_history_for_sale($unmigrated_invoice['sale_id']);
}
}
error_log('Migrating sales tax history. The number of sales that will be migrated is finished.');
}
log_message('info', 'Migrating sales tax history. The number of sales that will be migrated is finished.');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
/**
* @param int $sale_id
* @return void
*/
private function upgrade_tax_history_for_sale(int $sale_id): void
{
$tax_decimals = $this->appconfig->get_value('tax_decimals', 2);
$tax_included = $this->appconfig->get_value('tax_included', Migration_Sales_Tax_Data::YES) == Migration_Sales_Tax_Data::YES;
$customer_sales_tax_support = false;
/**
* @param int $sale_id
* @return void
*/
private function upgrade_tax_history_for_sale(int $sale_id): void
{
$tax_decimals = $this->appconfig->get_value('tax_decimals', 2);
$tax_included = $this->appconfig->get_value('tax_included', Migration_Sales_Tax_Data::YES) == Migration_Sales_Tax_Data::YES;
$customer_sales_tax_support = false;
if($tax_included) //TODO: Convert to ternary notation.
{
$tax_type = Migration_Sales_Tax_Data::VAT_TAX;
}
else
{
$tax_type = Migration_Sales_Tax_Data::SALES_TAX;
}
if ($tax_included) { // TODO: Convert to ternary notation.
$tax_type = Migration_Sales_Tax_Data::VAT_TAX;
} else {
$tax_type = Migration_Sales_Tax_Data::SALES_TAX;
}
$sales_taxes = [];
$tax_group_sequence = 0;
$items = $this->get_sale_items_for_migration($sale_id)->getResultArray();
$sales_taxes = [];
$tax_group_sequence = 0;
$items = $this->get_sale_items_for_migration($sale_id)->getResultArray();
foreach($items as $item)
{
// This computes tax for each line item and adds it to the tax type total
$tax_group = (float)$item['percent'] . '% ' . $item['name'];
$tax_basis = $this->get_item_total($item['quantity_purchased'], $item['item_unit_price'], $item['discount_percent'], true);
foreach ($items as $item) {
// This computes tax for each line item and adds it to the tax type total
$tax_group = (float)$item['percent'] . '% ' . $item['name'];
$tax_basis = $this->get_item_total($item['quantity_purchased'], $item['item_unit_price'], $item['discount_percent'], true);
$item_tax_amount = $tax_included
? $this->get_item_tax($item['quantity_purchased'], $item['item_unit_price'], $item['discount_percent'], $item['percent'])
: $this->get_sales_tax_for_amount($tax_basis, $item['percent'], PHP_ROUND_HALF_UP, $tax_decimals);
$item_tax_amount = $tax_included
? $this->get_item_tax($item['quantity_purchased'], $item['item_unit_price'], $item['discount_percent'], $item['percent'])
: $this->get_sales_tax_for_amount($tax_basis, $item['percent'], PHP_ROUND_HALF_UP, $tax_decimals);
$this->update_sales_items_taxes_amount($sale_id, $item['line'], $item['name'], $item['percent'], $tax_type, $item_tax_amount);
$this->update_sales_taxes($sales_taxes, $tax_type, $tax_group, $item['percent'], $tax_basis, $item_tax_amount, $tax_group_sequence, PHP_ROUND_HALF_UP, $sale_id, $item['name']);
$tax_group_sequence++;
}
//Not sure when this would ever kick in, but this is technically the correct logic.
if($customer_sales_tax_support) //TODO: This will always evaluate to false
{
$this->apply_invoice_taxing($sales_taxes);
}
$this->update_sales_items_taxes_amount($sale_id, $item['line'], $item['name'], $item['percent'], $tax_type, $item_tax_amount);
$this->update_sales_taxes($sales_taxes, $tax_type, $tax_group, $item['percent'], $tax_basis, $item_tax_amount, $tax_group_sequence, PHP_ROUND_HALF_UP, $sale_id, $item['name']);
$tax_group_sequence++;
}
// Not sure when this would ever kick in, but this is technically the correct logic
if ($customer_sales_tax_support) { // TODO: This will always evaluate to false
$this->apply_invoice_taxing($sales_taxes);
}
$this->round_sales_taxes($sales_taxes);
$this->save_sales_tax($sales_taxes);
}
$this->round_sales_taxes($sales_taxes);
$this->save_sales_tax($sales_taxes);
}
/**
* @param int $block_count
* @return ResultInterface
*/
private function get_unmigrated(int $block_count): ResultInterface
{
$builder = $this->db->table('sales_items_taxes as SIT');
$builder->select('SIT.sale_id');
$builder->select('ST.sale_id as sales_taxes_sale_id');
$builder->join('sales_taxes as ST','SIT.sale_id = ST.sale_id', 'left');
$builder->where('ST.sale_id', null);
$builder->groupBy('SIT.sale_id');
$builder->groupBy('ST.sale_id');
$builder->orderBy('SIT.sale_id');
$builder->limit($block_count);
/**
* @param int $block_count
* @return ResultInterface
*/
private function get_unmigrated(int $block_count): ResultInterface
{
$builder = $this->db->table('sales_items_taxes as SIT');
$builder->select('SIT.sale_id');
$builder->select('ST.sale_id as sales_taxes_sale_id');
$builder->join('sales_taxes as ST', 'SIT.sale_id = ST.sale_id', 'left');
$builder->where('ST.sale_id', null);
$builder->groupBy('SIT.sale_id');
$builder->groupBy('ST.sale_id');
$builder->orderBy('SIT.sale_id');
$builder->limit($block_count);
return $builder->get();
}
return $builder->get();
}
/**
* @param int $sale_id
* @return ResultInterface
*/
private function get_sale_items_for_migration(int $sale_id): ResultInterface
{
$builder = $this->db->table('sales_items as sales_items');
$builder->select('sales_items.sale_id as sale_id');
$builder->select('sales_items.line as line');
$builder->select('item_unit_price');
$builder->select('discount_percent');
$builder->select('quantity_purchased');
$builder->select('percent');
$builder->select('name');
$builder->join('sales_items_taxes as sales_items_taxes', 'sales_items.sale_id = sales_items_taxes.sale_id and sales_items.line = sales_items_taxes.line');
$builder->where('sales_items.sale_id', $sale_id);
/**
* @param int $sale_id
* @return ResultInterface
*/
private function get_sale_items_for_migration(int $sale_id): ResultInterface
{
$builder = $this->db->table('sales_items as sales_items');
$builder->select('sales_items.sale_id as sale_id');
$builder->select('sales_items.line as line');
$builder->select('item_unit_price');
$builder->select('discount_percent');
$builder->select('quantity_purchased');
$builder->select('percent');
$builder->select('name');
$builder->join('sales_items_taxes as sales_items_taxes', 'sales_items.sale_id = sales_items_taxes.sale_id and sales_items.line = sales_items_taxes.line');
$builder->where('sales_items.sale_id', $sale_id);
return $builder->get();
}
return $builder->get();
}
/**
* @return int
*/
private function get_count_of_unmigrated(): int
{
$result = $this->db->query('SELECT COUNT(*) FROM(SELECT SIT.sale_id, ST.sale_id as sales_taxes_sale_id FROM '
. $this->db->prefixTable('sales_items_taxes')
. ' as SIT LEFT JOIN '
. $this->db->prefixTable('sales_taxes')
. ' as ST ON SIT.sale_id = ST.sale_id WHERE ST.sale_id is null GROUP BY SIT.sale_id, ST.sale_id'
. ' ORDER BY SIT.sale_id) as US')->getResultArray();
/**
* @return int
*/
private function get_count_of_unmigrated(): int
{
$result = $this->db->query('SELECT COUNT(*) FROM(SELECT SIT.sale_id, ST.sale_id as sales_taxes_sale_id FROM '
. $this->db->prefixTable('sales_items_taxes')
. ' as SIT LEFT JOIN '
. $this->db->prefixTable('sales_taxes')
. ' as ST ON SIT.sale_id = ST.sale_id WHERE ST.sale_id is null GROUP BY SIT.sale_id, ST.sale_id'
. ' ORDER BY SIT.sale_id) as US')->getResultArray();
if(!$result)
{
error_log('Database error in 20170502221506_sales_tax_data.php related to sales_taxes or sales_items_taxes.');
return 0;
}
if (!$result) {
log_message('info', 'Database error in 20170502221506_sales_tax_data.php related to sales_taxes or sales_items_taxes.');
return 0;
}
return $result[0]['COUNT(*)'] ?: 0;
}
return $result[0]['COUNT(*)'] ?: 0;
}
/**
* @param int $sale_id
* @param int $line
* @param string $name
* @param float $percent
* @param int $tax_type
* @param float $item_tax_amount
* @return void
*/
private function update_sales_items_taxes_amount(int $sale_id, int $line, string $name, float $percent, int $tax_type, float $item_tax_amount): void
{
$builder = $this->db->table('sales_items_taxes');
$builder->where('sale_id', $sale_id);
$builder->where('line', $line);
$builder->where('name', $name);
$builder->where('percent', $percent);
$builder->update(['tax_type' => $tax_type, 'item_tax_amount' => $item_tax_amount]);
}
/**
* @param int $sale_id
* @param int $line
* @param string $name
* @param float $percent
* @param int $tax_type
* @param float $item_tax_amount
* @return void
*/
private function update_sales_items_taxes_amount(int $sale_id, int $line, string $name, float $percent, int $tax_type, float $item_tax_amount): void
{
$builder = $this->db->table('sales_items_taxes');
$builder->where('sale_id', $sale_id);
$builder->where('line', $line);
$builder->where('name', $name);
$builder->where('percent', $percent);
$builder->update(['tax_type' => $tax_type, 'item_tax_amount' => $item_tax_amount]);
}
/**
* @param array $sales_taxes
* @return void
*/
private function save_sales_tax(array &$sales_taxes): void
{
$builder = $this->db->Table('sales_taxes');
/**
* @param array $sales_taxes
* @return void
*/
private function save_sales_tax(array &$sales_taxes): void
{
$builder = $this->db->Table('sales_taxes');
foreach($sales_taxes as $line => $sales_tax)
{
$builder->insert($sales_tax);
}
}
foreach ($sales_taxes as $line => $sales_tax) {
$builder->insert($sales_tax);
}
}
/**
* @param string $quantity
* @param string $price
* @param string $discount_percentage
* @param bool $include_discount
* @return string
*/
public function get_item_total(string $quantity, string $price, string $discount_percentage, bool $include_discount = false): string
{
$total = bcmul($quantity, $price);
/**
* @param string $quantity
* @param string $price
* @param string $discount_percentage
* @param bool $include_discount
* @return string
*/
public function get_item_total(string $quantity, string $price, string $discount_percentage, bool $include_discount = false): string
{
$total = bcmul($quantity, $price);
if($include_discount)
{
$discount_amount = $this->get_item_discount($quantity, $price, $discount_percentage);
return bcsub($total, $discount_amount);
}
if ($include_discount) {
$discount_amount = $this->get_item_discount($quantity, $price, $discount_percentage);
return bcsub($total, $discount_amount);
}
return $total;
}
return $total;
}
/**
* @param string $quantity
* @param string $price
* @param string $discount
* @return float
*/
public function get_item_discount(string $quantity, string $price, string $discount): float
{
$total = bcmul($quantity, $price);
$discount_fraction = bcdiv($discount, 100);
$discount = bcmul($total, $discount_fraction);
/**
* @param string $quantity
* @param string $price
* @param string $discount
* @return float
*/
public function get_item_discount(string $quantity, string $price, string $discount): float
{
$total = bcmul($quantity, $price);
$discount_fraction = bcdiv($discount, 100);
$discount = bcmul($total, $discount_fraction);
return round($discount, totals_decimals(), PHP_ROUND_HALF_UP); //TODO: I don't think this is currency safe. Round will cast it's first parameter to a float. It also returns a float.
}
return round($discount, totals_decimals(), PHP_ROUND_HALF_UP); // TODO: I don't think this is currency safe. Round will cast it's first parameter to a float. It also returns a float.
}
/**
* @param string $quantity
* @param string $price
* @param string $discount_percentage
* @param string $tax_percentage
* @return string
*/
public function get_item_tax(string $quantity, string $price, string $discount_percentage, string $tax_percentage): string
{
$tax_included = $this->appconfig->get_value('tax_included', Migration_Sales_Tax_Data::YES) == Migration_Sales_Tax_Data::YES;
/**
* @param string $quantity
* @param string $price
* @param string $discount_percentage
* @param string $tax_percentage
* @return string
*/
public function get_item_tax(string $quantity, string $price, string $discount_percentage, string $tax_percentage): string
{
$tax_included = $this->appconfig->get_value('tax_included', Migration_Sales_Tax_Data::YES) == Migration_Sales_Tax_Data::YES;
$price = $this->get_item_total($quantity, $price, $discount_percentage, true);
$price = $this->get_item_total($quantity, $price, $discount_percentage, true);
if($tax_included)
{
$tax_fraction = bcadd('100', $tax_percentage);
$tax_fraction = bcdiv($tax_fraction, '100');
$price_tax_excl = bcdiv($price, $tax_fraction);
if ($tax_included) {
$tax_fraction = bcadd('100', $tax_percentage);
$tax_fraction = bcdiv($tax_fraction, '100');
$price_tax_excl = bcdiv($price, $tax_fraction);
return bcsub($price, $price_tax_excl);
}
$tax_fraction = bcdiv($tax_percentage, '100');
return bcsub($price, $price_tax_excl);
}
$tax_fraction = bcdiv($tax_percentage, '100');
return bcmul($price, $tax_fraction);
}
return bcmul($price, $tax_fraction);
}
/**
* @param string $tax_basis
* @param string $tax_percentage
* @param int $rounding_mode
* @param int $decimals
* @return float
*/
public function get_sales_tax_for_amount(string $tax_basis, string $tax_percentage, int $rounding_mode, int $decimals): float
{
$tax_fraction = bcdiv($tax_percentage, '100');
$tax_amount = bcmul($tax_basis, $tax_fraction);
/**
* @param string $tax_basis
* @param string $tax_percentage
* @param int $rounding_mode
* @param int $decimals
* @return float
*/
public function get_sales_tax_for_amount(string $tax_basis, string $tax_percentage, int $rounding_mode, int $decimals): float
{
$tax_fraction = bcdiv($tax_percentage, '100');
$tax_amount = bcmul($tax_basis, $tax_fraction);
return $this->round_number($rounding_mode, $tax_amount, $decimals);
}
return $this->round_number($rounding_mode, $tax_amount, $decimals);
}
/**
* @param int $rounding_mode
* @param string $amount
* @param int $decimals
* @return float
*/
public function round_number(int $rounding_mode, string $amount, int $decimals): float
{
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;
}
elseif($rounding_mode == Migration_Sales_Tax_Data::ROUND_DOWN)
{
$fig = pow(10,$decimals);
$rounded_total = (floor($fig * $amount) + floor($fig * $amount - floor($fig * $amount)))/$fig;
}
elseif($rounding_mode == Migration_Sales_Tax_Data::HALF_FIVE)
{
$rounded_total = round($amount / 5) * 5;
}
else
{
$rounded_total = round($amount, $decimals, $rounding_mode);
}
/**
* @param int $rounding_mode
* @param string $amount
* @param int $decimals
* @return float
*/
public function round_number(int $rounding_mode, string $amount, int $decimals): float
{
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;
} elseif ($rounding_mode == Migration_Sales_Tax_Data::ROUND_DOWN) {
$fig = pow(10, $decimals);
$rounded_total = (floor($fig * $amount) + floor($fig * $amount - floor($fig * $amount))) / $fig;
} elseif ($rounding_mode == Migration_Sales_Tax_Data::HALF_FIVE) {
$rounded_total = round($amount / 5) * 5;
} else {
$rounded_total = round($amount, $decimals, $rounding_mode);
}
return $rounded_total; //TODO: I don't think this is currency safe. I think we need to be using bcmath() functions like we are in the rest of the code.
}
return $rounded_total; // TODO: I don't think this is currency safe. I think we need to be using bcmath() functions like we are in the rest of the code.
}
/**
* @param array $sales_taxes
* @param string $tax_type
* @param string $tax_group
* @param float $tax_rate
* @param string $tax_basis
* @param string $item_tax_amount
* @param int $tax_group_sequence
* @param int $rounding_code
* @param int $sale_id
* @param string $name
* @param string $tax_code
* @return void
*/
public function update_sales_taxes(array &$sales_taxes, string $tax_type, string $tax_group, float $tax_rate, string $tax_basis, string $item_tax_amount, int $tax_group_sequence, int $rounding_code, int $sale_id, string $name = '', string $tax_code = ''): void
{
$tax_group_index = $this->clean('X'.$tax_group);
if(!array_key_exists($tax_group_index, $sales_taxes))
{
$insertkey = $tax_group_index; //TODO: $insertkey does not follow naming conventions.
$sales_tax = [
$insertkey => [
'sale_id' => $sale_id,
'tax_type' => $tax_type,
'tax_group' => $tax_group,
'sale_tax_basis' => $tax_basis,
'sale_tax_amount' => $item_tax_amount,
'print_sequence' => $tax_group_sequence,
'name' => $name,
'tax_rate' => $tax_rate,
'sales_tax_code' => $tax_code,
'rounding_code' => $rounding_code
]
];
//add to existing array
$sales_taxes += $sales_tax;
}
else
{
// Important ... the sales amounts are accumulated for the group at the maximum configurable scale value of 4
// but the scale will in reality be the scale specified by the tax_decimal configuration value used for sales_items_taxes
$sales_taxes[$tax_group_index]['sale_tax_basis'] = bcadd($sales_taxes[$tax_group_index]['sale_tax_basis'], $tax_basis, 4);
$sales_taxes[$tax_group_index]['sale_tax_amount'] = bcadd($sales_taxes[$tax_group_index]['sale_tax_amount'], $item_tax_amount, 4);
}
}
/**
* @param array $sales_taxes
* @param string $tax_type
* @param string $tax_group
* @param float $tax_rate
* @param string $tax_basis
* @param string $item_tax_amount
* @param int $tax_group_sequence
* @param int $rounding_code
* @param int $sale_id
* @param string $name
* @param string $tax_code
* @return void
*/
public function update_sales_taxes(array &$sales_taxes, string $tax_type, string $tax_group, float $tax_rate, string $tax_basis, string $item_tax_amount, int $tax_group_sequence, int $rounding_code, int $sale_id, string $name = '', string $tax_code = ''): void
{
$tax_group_index = $this->clean('X' . $tax_group);
if (!array_key_exists($tax_group_index, $sales_taxes)) {
$insertkey = $tax_group_index; // TODO: $insertkey does not follow naming conventions.
$sales_tax = [
$insertkey => [
'sale_id' => $sale_id,
'tax_type' => $tax_type,
'tax_group' => $tax_group,
'sale_tax_basis' => $tax_basis,
'sale_tax_amount' => $item_tax_amount,
'print_sequence' => $tax_group_sequence,
'name' => $name,
'tax_rate' => $tax_rate,
'sales_tax_code' => $tax_code,
'rounding_code' => $rounding_code
]
];
// Add to existing array
$sales_taxes += $sales_tax;
} else {
// Important: the sales amounts are accumulated for the group at the maximum configurable scale value of 4
// but the scale will in reality be the scale specified by the tax_decimal configuration value used for sales_items_taxes
$sales_taxes[$tax_group_index]['sale_tax_basis'] = bcadd($sales_taxes[$tax_group_index]['sale_tax_basis'], $tax_basis, 4);
$sales_taxes[$tax_group_index]['sale_tax_amount'] = bcadd($sales_taxes[$tax_group_index]['sale_tax_amount'], $item_tax_amount, 4);
}
}
/**
* @param string $string
* @return string
*/
public function clean(string $string): string //TODO: $string is not a good name for this variable
{
$string = str_replace(' ', '-', $string); // Replaces all spaces with hyphens.
/**
* @param string $string
* @return string
*/
public function clean(string $string): string // TODO: $string is not a good name for this variable
{
$string = str_replace(' ', '-', $string); // Replaces all spaces with hyphens.
return preg_replace('/[^A-Za-z0-9\-]/', '', $string); // Removes special chars.
}
return preg_replace('/[^A-Za-z0-9\-]/', '', $string); // Removes special chars.
}
/**
* @param array $sales_taxes
* @return void
*/
public function apply_invoice_taxing(array &$sales_taxes): void
{
if(!empty($sales_taxes)) //TODO: Duplicated code
{
$sort = [];
/**
* @param array $sales_taxes
* @return void
*/
public function apply_invoice_taxing(array &$sales_taxes): void
{
if (!empty($sales_taxes)) { // TODO: Duplicated code
$sort = [];
foreach($sales_taxes as $key => $value)
{
$sort['print_sequence'][$key] = $value['print_sequence'];
}
foreach ($sales_taxes as $key => $value) {
$sort['print_sequence'][$key] = $value['print_sequence'];
}
array_multisort($sort['print_sequence'], SORT_ASC, $sales_taxes);
}
array_multisort($sort['print_sequence'], SORT_ASC, $sales_taxes);
}
$decimals = totals_decimals();
$decimals = totals_decimals();
foreach($sales_taxes as $row_number => $sales_tax)
{
$sales_taxes[$row_number]['sale_tax_amount'] = $this->get_sales_tax_for_amount($sales_tax['sale_tax_basis'], $sales_tax['tax_rate'], $sales_tax['rounding_code'], $decimals);
}
}
foreach ($sales_taxes as $row_number => $sales_tax) {
$sales_taxes[$row_number]['sale_tax_amount'] = $this->get_sales_tax_for_amount($sales_tax['sale_tax_basis'], $sales_tax['tax_rate'], $sales_tax['rounding_code'], $decimals);
}
}
/**
* @param array $sales_taxes
* @return void
*/
public function round_sales_taxes(array &$sales_taxes): void
{
if(!empty($sales_taxes))
{
$sort = [];
foreach($sales_taxes as $k=>$v)
{
$sort['print_sequence'][$k] = $v['print_sequence'];
}
array_multisort($sort['print_sequence'], SORT_ASC, $sales_taxes);
}
/**
* @param array $sales_taxes
* @return void
*/
public function round_sales_taxes(array &$sales_taxes): void
{
if (!empty($sales_taxes)) {
$sort = [];
foreach ($sales_taxes as $k => $v) {
$sort['print_sequence'][$k] = $v['print_sequence'];
}
array_multisort($sort['print_sequence'], SORT_ASC, $sales_taxes);
}
$decimals = totals_decimals();
$decimals = totals_decimals();
foreach($sales_taxes as $row_number => $sales_tax)
{
$sale_tax_amount = $sales_tax['sale_tax_amount'];
$rounding_code = $sales_tax['rounding_code'];
$rounded_sale_tax_amount = $sale_tax_amount;
foreach ($sales_taxes as $row_number => $sales_tax) {
$sale_tax_amount = $sales_tax['sale_tax_amount'];
$rounding_code = $sales_tax['rounding_code'];
$rounded_sale_tax_amount = $sale_tax_amount;
if ($rounding_code == PHP_ROUND_HALF_UP
|| $rounding_code == PHP_ROUND_HALF_DOWN
|| $rounding_code == PHP_ROUND_HALF_EVEN
|| $rounding_code == PHP_ROUND_HALF_ODD)
{
$rounded_sale_tax_amount = round($sale_tax_amount, $decimals, $rounding_code);
}
elseif($rounding_code == Migration_Sales_Tax_Data::ROUND_UP)
{
$fig = (int) str_pad('1', $decimals, '0');
$rounded_sale_tax_amount = (ceil($sale_tax_amount * $fig) / $fig);
}
elseif($rounding_code == Migration_Sales_Tax_Data::ROUND_DOWN)
{
$fig = (int) str_pad('1', $decimals, '0');
$rounded_sale_tax_amount = (floor($sale_tax_amount * $fig) / $fig);
}
elseif($rounding_code == Migration_Sales_Tax_Data::HALF_FIVE)
{
$rounded_sale_tax_amount = round($sale_tax_amount / 5) * 5;
}
if (
$rounding_code == PHP_ROUND_HALF_UP
|| $rounding_code == PHP_ROUND_HALF_DOWN
|| $rounding_code == PHP_ROUND_HALF_EVEN
|| $rounding_code == PHP_ROUND_HALF_ODD
) {
$rounded_sale_tax_amount = round($sale_tax_amount, $decimals, $rounding_code);
} elseif ($rounding_code == Migration_Sales_Tax_Data::ROUND_UP) {
$fig = (int) str_pad('1', $decimals, '0');
$rounded_sale_tax_amount = (ceil($sale_tax_amount * $fig) / $fig);
} elseif ($rounding_code == Migration_Sales_Tax_Data::ROUND_DOWN) {
$fig = (int) str_pad('1', $decimals, '0');
$rounded_sale_tax_amount = (floor($sale_tax_amount * $fig) / $fig);
} elseif ($rounding_code == Migration_Sales_Tax_Data::HALF_FIVE) {
$rounded_sale_tax_amount = round($sale_tax_amount / 5) * 5;
}
$sales_taxes[$row_number]['sale_tax_amount'] = $rounded_sale_tax_amount;
}
}
$sales_taxes[$row_number]['sale_tax_amount'] = $rounded_sale_tax_amount;
}
}
}

View File

@@ -6,20 +6,17 @@ use CodeIgniter\Database\Migration;
class Migration_Upgrade_To_3_2_0 extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.1.1_to_3.2.0.sql');
}
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.1.1_to_3.2.0.sql');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -6,20 +6,17 @@ use CodeIgniter\Database\Migration;
class Migration_Upgrade_To_3_2_1 extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.2.0_to_3.2.1.sql');
}
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.2.0_to_3.2.1.sql');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -6,20 +6,17 @@ use CodeIgniter\Database\Migration;
class Migration_Attributes extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_attributes.sql');
}
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_attributes.sql');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -6,20 +6,17 @@ use CodeIgniter\Database\Migration;
class Migration_Upgrade_To_3_3_0 extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.2.1_to_3.3.0.sql');
}
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.2.1_to_3.3.0.sql');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -6,169 +6,159 @@ use CodeIgniter\Database\Migration;
class Migration_IndiaGST extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
if(!$this->db->fieldExists('sales_tax_code', 'customers'))
{
return;
}
/**
* Perform a migration step.
*/
public function up(): void
{
if (!$this->db->fieldExists('sales_tax_code', 'customers')) {
return;
}
// If number of entries is greater than zero then the tax data needs to be migrated
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_indiagst.sql');
// If number of entries is greater than zero then the tax data needs to be migrated
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_indiagst.sql');
error_log('Migrating tax configuration');
$count_of_tax_codes = $this->get_count_of_tax_code_entries();
$count_of_tax_codes = $this->get_count_of_tax_code_entries();
if ($count_of_tax_codes > 0) {
$this->migrate_tax_code_data();
}
if($count_of_tax_codes > 0)
{
$this->migrate_tax_code_data();
}
$this->migrate_customer_tax_codes();
$this->migrate_customer_tax_codes();
$count_of_rate_entries = $this->get_count_of_rate_entries();
$count_of_rate_entries = $this->get_count_of_rate_entries();
if ($count_of_rate_entries > 0) {
$this->migrate_tax_rates();
}
if($count_of_rate_entries > 0)
{
$this->migrate_tax_rates();
}
$count_of_sales_taxes_entries = $this->get_count_of_sales_taxes_entries();
$count_of_sales_taxes_entries = $this->get_count_of_sales_taxes_entries();
if ($count_of_sales_taxes_entries > 0) {
$this->migrate_sales_taxes_data();
}
if($count_of_sales_taxes_entries > 0)
{
$this->migrate_sales_taxes_data();
}
$this->drop_backups();
}
$this->drop_backups();
/**
* Revert a migration step.
*/
public function down(): void {}
error_log('Migrating tax configuration completed');
}
/**
* @return int
*/
private function get_count_of_tax_code_entries(): int
{
$builder = $this->db->table('tax_codes_backup');
$builder->select('COUNT(*) as count');
/**
* Revert a migration step.
*/
public function down(): void
{
}
return $builder->get()->getRow()->count;
}
/**
* @return int
*/
private function get_count_of_tax_code_entries(): int
{
$builder = $this->db->table('tax_codes_backup');
$builder->select('COUNT(*) as count');
/**
* @return int
*/
private function get_count_of_sales_taxes_entries(): int
{
$builder = $this->db->table('sales_taxes_backup');
$builder->select('COUNT(*) as count');
return $builder->get()->getRow()->count;
}
return $builder->get()->getRow()->count;
}
/**
* @return int
*/
private function get_count_of_sales_taxes_entries(): int
{
$builder = $this->db->table('sales_taxes_backup');
$builder->select('COUNT(*) as count');
/**
* @return int
*/
private function get_count_of_rate_entries(): int
{
$builder = $this->db->table('tax_code_rates_backup');
$builder->select('COUNT(*) as count');
return $builder->get()->getRow()->count;
}
return $builder->get()->getRow()->count;
}
/**
* @return int
*/
private function get_count_of_rate_entries(): int
{
$builder = $this->db->table('tax_code_rates_backup');
$builder->select('COUNT(*) as count');
/**
* This copies the old tax code configuration into the new tax code configuration
* assigning a tax_code_id id to the entry This only needs to be done if there are
* tax codes in the table.
*
* @return void
*/
private function migrate_tax_code_data(): void
{
$this->db->query('INSERT INTO ' . $this->db->prefixTable('tax_codes') . ' (tax_code, tax_code_name, city, state)
SELECT tax_code, tax_code_name, city, state FROM ' . $this->db->prefixTable('tax_codes_backup'));
}
return $builder->get()->getRow()->count;
}
/**
* The previous upgrade script added the new column to the customers table.
* This will assign a tax code id using the tax code field that was left in place on the customer table.
* After it is complete then it will drop the old customer tax code.
* This MUST run so that the old tax code is dropped
*
* @return void
*/
private function migrate_customer_tax_codes(): void
{
$this->db->query('UPDATE ' . $this->db->prefixTable('customers') . ' AS fa SET fa.sales_tax_code_id = (
SELECT tax_code_id FROM ' . $this->db->prefixTable('tax_codes') . ' AS fb where fa.sales_tax_code = fb.tax_code)');
/**
* This copies the old tax code configuration into the new tax code configuration
* assigning a tax_code_id id to the entry This only needs to be done if there are
* tax codes in the table.
*
* @return void
*/
private function migrate_tax_code_data(): void
{
$this->db->query('INSERT INTO ' . $this->db->prefixTable('tax_codes') . ' (tax_code, tax_code_name, city, state)
SELECT tax_code, tax_code_name, city, state FROM ' . $this->db->prefixTable('tax_codes_backup'));
}
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('customers') . ' DROP COLUMN sales_tax_code');
}
/**
* The previous upgrade script added the new column to the customers table.
* This will assign a tax code id using the tax code field that was left in place on the customer table.
* After it is complete then it will drop the old customer tax code.
* This MUST run so that the old tax code is dropped
*
* @return void
*/
private function migrate_customer_tax_codes(): void
{
$this->db->query('UPDATE ' . $this->db->prefixTable('customers') . ' AS fa SET fa.sales_tax_code_id = (
SELECT tax_code_id FROM ' . $this->db->prefixTable('tax_codes') . ' AS fb where fa.sales_tax_code = fb.tax_code)');
/**
* The sales taxes table is undergoing a significant primary key change
* The new table assumes that sales taxes are associated with a jurisdiction
* For base taxes and the older tax system the tax jurisdiction code table will be
* initialized with an entry that is used to represent a dummy or consolidated jurisdiction.
* If there is only one tax jurisdiction then it can be renamed and life moves on.
* If the user wants to start reporting taxes by jurisdiction then the new jurisdictions need
* to be created and defined manually AFTER the upgrade.
* CONVERTING OLD TAX DATA TO BE SPLIT OUT BY JURISDICTION IS BEYOND THE SCOPE OF THIS EFFORT
*/
private function migrate_sales_taxes_data(): void
{
$this->db->query('INSERT INTO ' . $this->db->prefixTable('sales_taxes')
. ' (sale_id, jurisdiction_id, tax_category_id, tax_type, tax_group, sale_tax_basis, sale_tax_amount, print_sequence, '
. '`name`, tax_rate, sales_tax_code_id, rounding_code) '
. 'select sale_id, rate_jurisdiction_id, rate_tax_category_id, tax_type, tax_group, sale_tax_basis, sale_tax_amount, '
. 'print_sequence, `name`, A.tax_rate, tax_code_id, rounding_code '
. 'from ' . $this->db->prefixTable('sales_taxes_backup') . ' AS A '
. 'left outer join ' . $this->db->prefixTable('tax_codes') . ' AS B on sales_tax_code = tax_code '
. 'left outer join ' . $this->db->prefixTable('tax_rates') . ' AS C on tax_code_id = rate_tax_code_id and A.tax_rate = C.tax_rate '
. 'order by sale_id');
}
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('customers') . ' DROP COLUMN sales_tax_code');
}
/**
* @return void
*/
private function migrate_tax_rates(): void
{
// Create a dummy jurisdiction record and retrieve the jurisdiction rate id
/**
* The sales taxes table is undergoing a significant primary key change
* The new table assumes that sales taxes are associated with a jurisdiction
* For base taxes and the older tax system the tax jurisdiction code table will be
* initialized with an entry that is used to represent a dummy or consolidated jurisdiction.
* If there is only one tax jurisdiction then it can be renamed and life moves on.
* If the user wants to start reporting taxes by jurisdiction then the new jurisdictions need
* to be created and defined manually AFTER the upgrade.
* CONVERTING OLD TAX DATA TO BE SPLIT OUT BY JURISDICTION IS BEYOND THE SCOPE OF THIS EFFORT
*/
private function migrate_sales_taxes_data(): void
{
$this->db->query('INSERT INTO ' . $this->db->prefixTable('sales_taxes')
. ' (sale_id, jurisdiction_id, tax_category_id, tax_type, tax_group, sale_tax_basis, sale_tax_amount, print_sequence, '
. '`name`, tax_rate, sales_tax_code_id, rounding_code) '
. 'select sale_id, rate_jurisdiction_id, rate_tax_category_id, tax_type, tax_group, sale_tax_basis, sale_tax_amount, '
. 'print_sequence, `name`, A.tax_rate, tax_code_id, rounding_code '
. 'from ' . $this->db->prefixTable('sales_taxes_backup') . ' AS A '
. 'left outer join ' . $this->db->prefixTable('tax_codes') . ' AS B on sales_tax_code = tax_code '
. 'left outer join ' . $this->db->prefixTable('tax_rates') . ' AS C on tax_code_id = rate_tax_code_id and A.tax_rate = C.tax_rate '
. 'order by sale_id');
}
$this->db->query('INSERT INTO ' . $this->db->prefixTable('tax_jurisdictions') . ' (jurisdiction_name, tax_group, tax_type, reporting_authority, '
. "tax_group_sequence, cascade_sequence, deleted) VALUES ('Jurisdiction1', 'TaxGroup1', '1', 'Authority1', 1, 0, '0')");
/**
* @return void
*/
private function migrate_tax_rates(): void
{
// create a dummy jurisdiction record and retrieve the jurisdiction rate id
$jurisdiction_id = $this->db->query('SELECT jurisdiction_id FROM ' . $this->db->prefixTable('tax_jurisdictions') . " WHERE jurisdiction_name = 'Jurisdiction1'")->getRow()->jurisdiction_id;
$this->db->query('INSERT INTO ' . $this->db->prefixTable('tax_jurisdictions') . ' (jurisdiction_name, tax_group, tax_type, reporting_authority, '
. "tax_group_sequence, cascade_sequence, deleted) VALUES ('Jurisdiction1', 'TaxGroup1', '1', 'Authority1', 1, 0, '0')");
// Insert old tax_code rates data into the new tax rates table
$this->db->query('INSERT INTO ' . $this->db->prefixTable('tax_rates')
. ' (rate_tax_category_id, rate_jurisdiction_id, rate_tax_code_id, tax_rate, tax_rounding_code) '
. 'SELECT rate_tax_category_id, ' . $jurisdiction_id . ', tax_code_id, tax_rate, rounding_code FROM '
. $this->db->prefixTable('tax_code_rates_backup') . ' JOIN ' . $this->db->prefixTable('tax_codes')
. ' ON tax_code = rate_tax_code');
}
$jurisdiction_id = $this->db->query('SELECT jurisdiction_id FROM ' . $this->db->prefixTable('tax_jurisdictions') . " WHERE jurisdiction_name = 'Jurisdiction1'")->getRow()->jurisdiction_id;
// Insert old tax_code rates data into the new tax rates table
$this->db->query('INSERT INTO ' . $this->db->prefixTable('tax_rates')
. ' (rate_tax_category_id, rate_jurisdiction_id, rate_tax_code_id, tax_rate, tax_rounding_code) '
. 'SELECT rate_tax_category_id, ' . $jurisdiction_id . ', tax_code_id, tax_rate, rounding_code FROM '
. $this->db->prefixTable('tax_code_rates_backup') . ' JOIN ' . $this->db->prefixTable('tax_codes')
. ' ON tax_code = rate_tax_code');
}
/**
* @return void
*/
private function drop_backups(): void
{
$this->db->query('DROP TABLE IF EXISTS ' . $this->db->prefixTable('tax_codes_backup'));
$this->db->query('DROP TABLE IF EXISTS ' . $this->db->prefixTable('sales_taxes_backup'));
$this->db->query('DROP TABLE IF EXISTS ' . $this->db->prefixTable('tax_code_rates_backup'));
}
/**
* @return void
*/
private function drop_backups(): void
{
$this->db->query('DROP TABLE IF EXISTS ' . $this->db->prefixTable('tax_codes_backup'));
$this->db->query('DROP TABLE IF EXISTS ' . $this->db->prefixTable('sales_taxes_backup'));
$this->db->query('DROP TABLE IF EXISTS ' . $this->db->prefixTable('tax_code_rates_backup'));
}
}

View File

@@ -6,24 +6,17 @@ use CodeIgniter\Database\Migration;
class Migration_IndiaGST1 extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_indiagst1.sql');
error_log('Fix definition of Supplier.Tax Id');
error_log('Definition of Supplier.Tax Id corrected');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_indiagst1.sql');
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -6,20 +6,17 @@ use CodeIgniter\Database\Migration;
class Migration_IndiaGST2 extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_indiagst2.sql');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_indiagst2.sql');
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -6,20 +6,17 @@ use CodeIgniter\Database\Migration;
class Migration_decimal_attribute_type extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_decimal_attribute_type.sql');
}
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_decimal_attribute_type.sql');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -6,20 +6,17 @@ use CodeIgniter\Database\Migration;
class Migration_add_iso_4217 extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_add_iso_4217.sql');
}
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_add_iso_4217.sql');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -6,20 +6,17 @@ use CodeIgniter\Database\Migration;
class Migration_PaymentTracking extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_paymenttracking.sql');
}
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_paymenttracking.sql');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -6,108 +6,111 @@ use CodeIgniter\Database\Migration;
class Migration_RefundTracking extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
helper(['migration', 'locale']);
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_refundtracking.sql');
/**
* Perform a migration step.
*/
public function up(): void
{
helper(['migration', 'locale']);
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_refundtracking.sql');
// Add missing cash_refund amounts to payments table
$decimals = totals_decimals();
// Add missing cash_refund amounts to payments table
$decimals = totals_decimals();
$trans_amount = 'ROUND(SUM(CASE WHEN sales_items.discount_type = ' . PERCENT
. ' THEN sales_items.item_unit_price * sales_items.quantity_purchased * (1 - sales_items.discount / 100) '
. 'ELSE sales_items.item_unit_price * sales_items.quantity_purchased - sales_items.discount END), ' . $decimals . ') AS trans_amount';
$trans_amount = 'ROUND(SUM(CASE WHEN sales_items.discount_type = ' . PERCENT
. ' THEN sales_items.item_unit_price * sales_items.quantity_purchased * (1 - sales_items.discount / 100) '
. 'ELSE sales_items.item_unit_price * sales_items.quantity_purchased - sales_items.discount END), ' . $decimals . ') AS trans_amount';
$cash_payment = lang('Sales.cash');
$cash_payment = lang('Sales.cash');
$this->db->query('CREATE TEMPORARY TABLE IF NOT EXISTS ' . $this->db->prefixTable('migrate_taxes') .
' (INDEX(sale_id)) ENGINE=MEMORY
(
SELECT sales.sale_id, SUM(sales_taxes.sale_tax_amount) AS total_taxes
FROM ' . $this->db->prefixTable('sales') . ' AS sales
LEFT OUTER JOIN ' . $this->db->prefixTable('sales_taxes') . ' AS sales_taxes
ON sales.sale_id = sales_taxes.sale_id
WHERE sales.sale_status = \'' . COMPLETED . '\' AND sales_taxes.tax_type = \'1\'
GROUP BY sale_id
)'
);
$this->db->query(
'CREATE TEMPORARY TABLE IF NOT EXISTS ' . $this->db->prefixTable('migrate_taxes') .
' (INDEX(sale_id)) ENGINE=MEMORY
(
SELECT sales.sale_id, SUM(sales_taxes.sale_tax_amount) AS total_taxes
FROM ' . $this->db->prefixTable('sales') . ' AS sales
LEFT OUTER JOIN ' . $this->db->prefixTable('sales_taxes') . ' AS sales_taxes
ON sales.sale_id = sales_taxes.sale_id
WHERE sales.sale_status = \'' . COMPLETED . '\' AND sales_taxes.tax_type = \'1\'
GROUP BY sale_id
)'
);
$this->db->query('CREATE TEMPORARY TABLE IF NOT EXISTS ' . $this->db->prefixTable('migrate_sales') .
' (INDEX(sale_id)) ENGINE=MEMORY
(
SELECT sales.sale_id, '. $trans_amount . ', sales.employee_id, sales.sale_time'
. ' FROM ' . $this->db->prefixTable('sales') . ' AS sales '
. 'LEFT OUTER JOIN ' . $this->db->prefixTable('sales_items') . ' AS sales_items '
. 'ON sales.sale_id = sales_items.sale_id '
. 'LEFT OUTER JOIN ' . $this->db->prefixTable('migrate_taxes') . ' AS sumpay_taxes '
. 'ON sales.sale_id = sumpay_taxes.sale_id '
. 'WHERE sales.sale_status = \'' . COMPLETED . '\' GROUP BY sale_id
)'
);
$this->db->query(
'CREATE TEMPORARY TABLE IF NOT EXISTS ' . $this->db->prefixTable('migrate_sales') .
' (INDEX(sale_id)) ENGINE=MEMORY
(
SELECT sales.sale_id, ' . $trans_amount . ', sales.employee_id, sales.sale_time'
. ' FROM ' . $this->db->prefixTable('sales') . ' AS sales '
. 'LEFT OUTER JOIN ' . $this->db->prefixTable('sales_items') . ' AS sales_items '
. 'ON sales.sale_id = sales_items.sale_id '
. 'LEFT OUTER JOIN ' . $this->db->prefixTable('migrate_taxes') . ' AS sumpay_taxes '
. 'ON sales.sale_id = sumpay_taxes.sale_id '
. 'WHERE sales.sale_status = \'' . COMPLETED . '\' GROUP BY sale_id
)'
);
$this->db->query('UPDATE ' . $this->db->prefixTable('migrate_sales') . ' AS sumpay_items '
. 'SET trans_amount = trans_amount + IFNULL((SELECT total_taxes FROM ' . $this->db->prefixTable('migrate_taxes')
. ' AS sumpay_taxes WHERE sumpay_items.sale_id = sumpay_taxes.sale_id),0)');
$this->db->query('UPDATE ' . $this->db->prefixTable('migrate_sales') . ' AS sumpay_items '
. 'SET trans_amount = trans_amount + IFNULL((SELECT total_taxes FROM ' . $this->db->prefixTable('migrate_taxes')
. ' AS sumpay_taxes WHERE sumpay_items.sale_id = sumpay_taxes.sale_id),0)');
$this->db->query('CREATE TEMPORARY TABLE IF NOT EXISTS ' . $this->db->prefixTable('migrate_payments') .
' (INDEX(sale_id)) ENGINE=MEMORY
(
SELECT sales.sale_id, COUNT(sales.sale_id) AS number_payments,
SUM(sales_payments.payment_amount - sales_payments.cash_refund) AS total_payments
FROM ' . $this->db->prefixTable('sales') . ' AS sales
LEFT OUTER JOIN ' . $this->db->prefixTable('sales_payments') . ' AS sales_payments
ON sales.sale_id = sales_payments.sale_id
WHERE sales.sale_status = \'' . COMPLETED . '\' GROUP BY sale_id
)'
);
$this->db->query(
'CREATE TEMPORARY TABLE IF NOT EXISTS ' . $this->db->prefixTable('migrate_payments') .
' (INDEX(sale_id)) ENGINE=MEMORY
(
SELECT sales.sale_id, COUNT(sales.sale_id) AS number_payments,
SUM(sales_payments.payment_amount - sales_payments.cash_refund) AS total_payments
FROM ' . $this->db->prefixTable('sales') . ' AS sales
LEFT OUTER JOIN ' . $this->db->prefixTable('sales_payments') . ' AS sales_payments
ON sales.sale_id = sales_payments.sale_id
WHERE sales.sale_status = \'' . COMPLETED . '\' GROUP BY sale_id
)'
);
// You may be asking yourself why the following is not creating a temporary table.
// It should be, it originallly was, but there is a bug in MySQL where temporary tables where some SQL statements fail.
// The update statement that follows this CREATE TABLE is one of those statements.
$this->db->query('CREATE TABLE IF NOT EXISTS ' . $this->db->prefixTable('migrate_refund') .
' (INDEX(sale_id)) ENGINE=MEMORY
(
SELECT a.sale_id, total_payments - trans_amount AS refund_amount
FROM ' . $this->db->prefixTable('migrate_sales') . ' AS a
JOIN ' . $this->db->prefixTable('migrate_payments') . ' AS b ON a.sale_id = b.sale_id
WHERE total_payments > trans_amount AND number_payments = 1
)'
);
// You may be asking yourself why the following is not creating a temporary table.
// It should be, it originallly was, but there is a bug in MySQL where temporary tables where some SQL statements fail.
// The update statement that follows this CREATE TABLE is one of those statements.
$this->db->query(
'CREATE TABLE IF NOT EXISTS ' . $this->db->prefixTable('migrate_refund') .
' (INDEX(sale_id)) ENGINE=MEMORY
(
SELECT a.sale_id, total_payments - trans_amount AS refund_amount
FROM ' . $this->db->prefixTable('migrate_sales') . ' AS a
JOIN ' . $this->db->prefixTable('migrate_payments') . ' AS b ON a.sale_id = b.sale_id
WHERE total_payments > trans_amount AND number_payments = 1
)'
);
// Update existing cash transactions with refund amount
$this->db->query('UPDATE ' . $this->db->prefixTable('sales_payments') . ' AS a
SET a.cash_refund =
(SELECT b.refund_amount
FROM ' . $this->db->prefixTable('migrate_refund') . ' AS b
WHERE a.sale_id = b.sale_id AND a.payment_type = \'' . $cash_payment . '\')
WHERE EXISTS
(SELECT b.refund_amount
FROM ' . $this->db->prefixTable('migrate_refund') . ' AS b
WHERE a.sale_id = b.sale_id AND a.payment_type = \'' . $cash_payment . ' \')'
);
// Update existing cash transactions with refund amount
$this->db->query(
'UPDATE ' . $this->db->prefixTable('sales_payments') . ' AS a
SET a.cash_refund =
(SELECT b.refund_amount
FROM ' . $this->db->prefixTable('migrate_refund') . ' AS b
WHERE a.sale_id = b.sale_id AND a.payment_type = \'' . $cash_payment . '\')
WHERE EXISTS
(SELECT b.refund_amount
FROM ' . $this->db->prefixTable('migrate_refund') . ' AS b
WHERE a.sale_id = b.sale_id AND a.payment_type = \'' . $cash_payment . ' \')'
);
// Insert new cash refund transactions for non-cash payments
$this->db->query('INSERT INTO ' . $this->db->prefixTable('sales_payments') .
' (sale_id, payment_type, employee_id, payment_time, payment_amount, cash_refund)
SELECT r.sale_id, \'' . $cash_payment . '\', s.employee_id, sale_time, 0, r.refund_amount
FROM ' . $this->db->prefixTable('migrate_refund') . ' AS r
JOIN ' . $this->db->prefixTable('sales_payments') . ' AS p ON r.sale_id = p.sale_id
JOIN ' . $this->db->prefixTable('migrate_sales') . ' AS s ON r.sale_id = s.sale_id
WHERE p.payment_type != \'' . $cash_payment . '\''
);
// Insert new cash refund transactions for non-cash payments
$this->db->query(
'INSERT INTO ' . $this->db->prefixTable('sales_payments') .
' (sale_id, payment_type, employee_id, payment_time, payment_amount, cash_refund)
SELECT r.sale_id, \'' . $cash_payment . '\', s.employee_id, sale_time, 0, r.refund_amount
FROM ' . $this->db->prefixTable('migrate_refund') . ' AS r
JOIN ' . $this->db->prefixTable('sales_payments') . ' AS p ON r.sale_id = p.sale_id
JOIN ' . $this->db->prefixTable('migrate_sales') . ' AS s ON r.sale_id = s.sale_id
WHERE p.payment_type != \'' . $cash_payment . '\''
);
// Post migration cleanup
$this->db->query('DROP TABLE IF EXISTS ' . $this->db->prefixTable('migrate_refund'));
}
// Post migration cleanup
$this->db->query('DROP TABLE IF EXISTS ' . $this->db->prefixTable('migrate_refund'));
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -6,20 +6,17 @@ use CodeIgniter\Database\Migration;
class Migration_DBFix extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_dbfix.sql');
}
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_dbfix.sql');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -6,20 +6,17 @@ use CodeIgniter\Database\Migration;
class Migration_fix_attribute_datetime extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_fix_attribute_datetime.sql');
}
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.0_fix_attribute_datetime.sql');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -6,30 +6,31 @@ use CodeIgniter\Database\Migration;
class Migration_fix_empty_reports extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
$builder = $this->db->table('stock_locations');
$builder->select('location_name');
$builder->where('location_id', 1);
$builder->limit(1);
$location_name = $builder->get()->getResultArray()[0]['location_name'];
/**
* Perform a migration step.
*/
public function up(): void
{
log_message('info', 'Starting migration: Fix empty reports.');
$location_name = str_replace(' ', '_', $location_name);
$builder = $this->db->table('permissions');
$builder->set('location_id', 1);
$builder->where('permission_id','receivings_' . $location_name);
$builder->orWhere('permission_id', 'sales_' . $location_name);
$builder->update();
}
$builder = $this->db->table('stock_locations');
$builder->select('location_name');
$builder->where('location_id', 1);
$builder->limit(1);
$location_name = $builder->get()->getResultArray()[0]['location_name'];
/**
* Revert a migration step.
*/
public function down(): void
{
$location_name = str_replace(' ', '_', $location_name);
$builder = $this->db->table('permissions');
$builder->set('location_id', 1);
$builder->where('permission_id', 'receivings_' . $location_name);
$builder->orWhere('permission_id', 'sales_' . $location_name);
$builder->update();
}
log_message('info', 'Finished migration: Fix empty reports.');
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -6,20 +6,21 @@ use CodeIgniter\Database\Migration;
class Migration_receipttaxindicator extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
$this->db->query('INSERT INTO ' . $this->db->prefixTable('app_config') . ' (`key`, `value`)
VALUES (\'receipt_show_tax_ind\', \'0\')');
}
/**
* Perform a migration step.
*/
public function up(): void
{
log_message('info', 'Migrating receipt tax indicator.');
$this->db->query('INSERT INTO ' . $this->db->prefixTable('app_config') . ' (`key`, `value`)
VALUES (\'receipt_show_tax_ind\', \'0\')');
}
/**
* Revert a migration step.
*/
public function down(): void
{
$this->db->query('DELETE FROM ' . $this->db->prefixTable('app_config') . ' WHERE key = \'receipt_show_tax_ind\'');
}
/**
* Revert a migration step.
*/
public function down(): void
{
$this->db->query('DELETE FROM ' . $this->db->prefixTable('app_config') . ' WHERE key = \'receipt_show_tax_ind\'');
}
}

View File

@@ -6,20 +6,17 @@ use CodeIgniter\Database\Migration;
class Migration_PaymentDateFix extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.2_paymentdatefix.sql');
}
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.2_paymentdatefix.sql');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -6,20 +6,17 @@ use CodeIgniter\Database\Migration;
class Migration_SalesChangePrice extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.2_saleschangeprice.sql');
}
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.2_saleschangeprice.sql');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -15,399 +15,367 @@ use CodeIgniter\Database\ResultInterface;
*/
class Migration_TaxAmount extends Migration
{
public const ROUND_UP = 5;
public const ROUND_DOWN = 6;
public const HALF_FIVE = 7;
public const YES = '1';
public const VAT_TAX = '0';
public const SALES_TAX = '1'; //TODO: It appears that this constant is never used
private Appconfig $appconfig;
public const ROUND_UP = 5;
public const ROUND_DOWN = 6;
public const HALF_FIVE = 7;
public const YES = '1';
public const VAT_TAX = '0';
public const SALES_TAX = '1'; // TODO: It appears that this constant is never used
private Appconfig $appconfig;
public function __construct()
{
parent::__construct();
public function __construct()
{
parent::__construct();
$this->appconfig = model(Appconfig::class);
}
$this->appconfig = model(Appconfig::class);
}
/**
* Perform a migration step.
*/
public function up(): void
{
$tax_included = ($this->appconfig->get_value('tax_included', Migration_TaxAmount::YES) == Migration_TaxAmount::YES);
/**
* Perform a migration step.
*/
public function up(): void
{
$tax_included = ($this->appconfig->get_value('tax_included', Migration_TaxAmount::YES) == Migration_TaxAmount::YES);
if($tax_included)
{
$tax_decimals = $this->appconfig->get_value('tax_decimals', 2);
$number_of_unmigrated = $this->get_count_of_unmigrated();
if ($tax_included) {
$tax_decimals = $this->appconfig->get_value('tax_decimals', 2);
$number_of_unmigrated = $this->get_count_of_unmigrated();
error_log('Migrating sales tax fixing. The number of sales that will be migrated is ' . $number_of_unmigrated);
log_message('info', 'Migrating sales tax fixing. The number of sales that will be migrated is ' . $number_of_unmigrated);
if($number_of_unmigrated > 0)
{
$unmigrated_invoices = $this->get_unmigrated($number_of_unmigrated)->getResultArray();
$this->db->query('RENAME TABLE ' . $this->db->prefixTable('sales_taxes') . ' TO ' . $this->db->prefixTable('sales_taxes_backup'));
$this->db->query('CREATE TABLE ' . $this->db->prefixTable('sales_taxes') . ' LIKE ' . $this->db->prefixTable('sales_taxes_backup'));
if ($number_of_unmigrated > 0) {
$unmigrated_invoices = $this->get_unmigrated($number_of_unmigrated)->getResultArray();
$this->db->query('RENAME TABLE ' . $this->db->prefixTable('sales_taxes') . ' TO ' . $this->db->prefixTable('sales_taxes_backup'));
$this->db->query('CREATE TABLE ' . $this->db->prefixTable('sales_taxes') . ' LIKE ' . $this->db->prefixTable('sales_taxes_backup'));
foreach($unmigrated_invoices as $key => $unmigrated_invoice)
{
$this->upgrade_tax_history_for_sale($unmigrated_invoice['sale_id'], $tax_decimals, true);
}
$this->db->query('DROP TABLE ' . $this->db->prefixTable('sales_taxes_backup'));
}
foreach ($unmigrated_invoices as $key => $unmigrated_invoice) {
$this->upgrade_tax_history_for_sale($unmigrated_invoice['sale_id'], $tax_decimals, true);
}
$this->db->query('DROP TABLE ' . $this->db->prefixTable('sales_taxes_backup'));
}
error_log('Migrating sales tax fixing. The number of sales that will be migrated is finished.');
}
}
log_message('info', 'Migrating sales tax fixing. The number of sales that will be migrated is finished.');
}
}
/**
* Revert a migration step.
*/
public function down(): void
{
/**
* Revert a migration step.
*/
public function down(): void {}
}
/**
* @param int $sale_id
* @param string $tax_decimals
* @param bool $tax_included
* @return void
*/
private function upgrade_tax_history_for_sale(int $sale_id, string $tax_decimals, bool $tax_included): void // TODO: $tax_included is passed as a parameter but never used in the function body.
{
$customer_sales_tax_support = false;
$tax_type = Migration_TaxAmount::VAT_TAX;
$sales_taxes = [];
$tax_group_sequence = 0;
$items = $this->get_sale_items_for_migration($sale_id)->getResultArray();
/**
* @param int $sale_id
* @param string $tax_decimals
* @param bool $tax_included
* @return void
*/
private function upgrade_tax_history_for_sale(int $sale_id, string $tax_decimals, bool $tax_included): void //TODO: $tax_included is passed as a parameter but never used in the function body.
{
$customer_sales_tax_support = false;
$tax_type = Migration_TaxAmount::VAT_TAX;
$sales_taxes = [];
$tax_group_sequence = 0;
$items = $this->get_sale_items_for_migration($sale_id)->getResultArray();
foreach ($items as $item) {
// This computes tax for each line item and adds it to the tax type total
$tax_group = (float)$item['percent'] . '% ' . $item['name'];
$tax_basis = $this->get_item_total($item['quantity_purchased'], $item['item_unit_price'], $item['discount'], true);
$item_tax_amount = $this->get_item_tax($tax_basis, $item['percent'], PHP_ROUND_HALF_UP, $tax_decimals);
$this->update_sales_items_taxes_amount($sale_id, $item['line'], $item['name'], $item['percent'], $tax_type, $item_tax_amount);
$this->update_sales_taxes($sales_taxes, $tax_type, $tax_group, $item['percent'], $tax_basis, $item_tax_amount, $tax_group_sequence, PHP_ROUND_HALF_UP, $sale_id, $item['name']);
$tax_group_sequence += 1;
}
foreach($items as $item)
{
// This computes tax for each line item and adds it to the tax type total
$tax_group = (float)$item['percent'] . '% ' . $item['name'];
$tax_basis = $this->get_item_total($item['quantity_purchased'], $item['item_unit_price'], $item['discount'], true);
$item_tax_amount = $this->get_item_tax($tax_basis, $item['percent'], PHP_ROUND_HALF_UP, $tax_decimals);
$this->update_sales_items_taxes_amount($sale_id, $item['line'], $item['name'], $item['percent'], $tax_type, $item_tax_amount);
$this->update_sales_taxes($sales_taxes, $tax_type, $tax_group, $item['percent'], $tax_basis, $item_tax_amount, $tax_group_sequence, PHP_ROUND_HALF_UP, $sale_id, $item['name']);
$tax_group_sequence += 1;
}
if ($customer_sales_tax_support) { // TODO: This will always evaluate to false.
$this->apply_invoice_taxing($sales_taxes);
}
if($customer_sales_tax_support) //TODO: This will always evaluate to false.
{
$this->apply_invoice_taxing($sales_taxes);
}
$this->round_sales_taxes($sales_taxes);
$this->save_sales_tax($sales_taxes);
}
$this->round_sales_taxes($sales_taxes);
$this->save_sales_tax($sales_taxes);
}
/**
* @param int $block_count
* @return ResultInterface
*/
private function get_unmigrated(int $block_count): ResultInterface
{
$builder = $this->db->table('sales_items_taxes as SIT');
$builder->select('SIT.sale_id');
$builder->select('ST.sale_id as sales_taxes_sale_id');
$builder->join('sales_taxes as ST', 'SIT.sale_id = ST.sale_id', 'left');
$builder->groupBy('SIT.sale_id');
$builder->groupBy('ST.sale_id');
$builder->orderBy('SIT.sale_id');
$builder->limit($block_count);
/**
* @param int $block_count
* @return ResultInterface
*/
private function get_unmigrated(int $block_count): ResultInterface
{
$builder = $this->db->table('sales_items_taxes as SIT');
$builder->select('SIT.sale_id');
$builder->select('ST.sale_id as sales_taxes_sale_id');
$builder->join('sales_taxes as ST', 'SIT.sale_id = ST.sale_id', 'left');
$builder->groupBy('SIT.sale_id');
$builder->groupBy('ST.sale_id');
$builder->orderBy('SIT.sale_id');
$builder->limit($block_count);
return $builder->get();
}
return $builder->get();
}
/**
* @return int
*/
private function get_count_of_unmigrated(): int
{
$result = $this->db->query('SELECT COUNT(*) FROM(SELECT SIT.sale_id, ST.sale_id as sales_taxes_sale_id FROM '
. $this->db->prefixTable('sales_items_taxes')
. ' as SIT LEFT JOIN '
. $this->db->prefixTable('sales_taxes')
. ' as ST ON SIT.sale_id = ST.sale_id GROUP BY SIT.sale_id, ST.sale_id'
. ' ORDER BY SIT.sale_id) as US')->getResultArray();
/**
* @return int
*/
private function get_count_of_unmigrated(): int
{
$result = $this->db->query('SELECT COUNT(*) FROM(SELECT SIT.sale_id, ST.sale_id as sales_taxes_sale_id FROM '
. $this->db->prefixTable('sales_items_taxes')
. ' as SIT LEFT JOIN '
. $this->db->prefixTable('sales_taxes')
. ' as ST ON SIT.sale_id = ST.sale_id GROUP BY SIT.sale_id, ST.sale_id'
. ' ORDER BY SIT.sale_id) as US')->getResultArray();
if(!$result)
{
error_log('Database error in 20200202000000_taxamount.php related to sales_taxes or sales_items_taxes.');
return 0;
}
if (!$result) {
log_message('info', 'Database error in 20200202000000_taxamount.php related to sales_taxes or sales_items_taxes.');
return 0;
}
return $result[0]['COUNT(*)'] ?: 0;
}
return $result[0]['COUNT(*)'] ?: 0;
}
/**
* @param int $sale_id
* @return ResultInterface
*/
private function get_sale_items_for_migration(int $sale_id): ResultInterface
{
$builder = $this->db->table('sales_items as sales_items');
$builder->select('sales_items.sale_id as sale_id');
$builder->select('sales_items.line as line');
$builder->select('item_unit_price');
$builder->select('discount');
$builder->select('quantity_purchased');
$builder->select('percent');
$builder->select('name');
$builder->join('sales_items_taxes as sales_items_taxes', 'sales_items.sale_id = sales_items_taxes.sale_id and sales_items.line = sales_items_taxes.line');
$builder->where('sales_items.sale_id', $sale_id);
/**
* @param int $sale_id
* @return ResultInterface
*/
private function get_sale_items_for_migration(int $sale_id): ResultInterface
{
$builder = $this->db->table('sales_items as sales_items');
$builder->select('sales_items.sale_id as sale_id');
$builder->select('sales_items.line as line');
$builder->select('item_unit_price');
$builder->select('discount');
$builder->select('quantity_purchased');
$builder->select('percent');
$builder->select('name');
$builder->join('sales_items_taxes as sales_items_taxes', 'sales_items.sale_id = sales_items_taxes.sale_id and sales_items.line = sales_items_taxes.line');
$builder->where('sales_items.sale_id', $sale_id);
return $builder->get();
}
return $builder->get();
}
/**
* @param int $sale_id
* @param int $line
* @param string $name
* @param float $percent
* @param int $tax_type
* @param float $item_tax_amount
* @return void
*/
private function update_sales_items_taxes_amount(int $sale_id, int $line, string $name, float $percent, int $tax_type, float $item_tax_amount): void
{
$builder = $this->db->table('sales_items_taxes');
$builder->where('sale_id', $sale_id);
$builder->where('line', $line);
$builder->where('name', $name);
$builder->where('percent', $percent);
$builder->update(['tax_type' => $tax_type, 'item_tax_amount' => $item_tax_amount]);
}
/**
* @param int $sale_id
* @param int $line
* @param string $name
* @param float $percent
* @param int $tax_type
* @param float $item_tax_amount
* @return void
*/
private function update_sales_items_taxes_amount(int $sale_id, int $line, string $name, float $percent, int $tax_type, float $item_tax_amount): void
{
$builder = $this->db->table('sales_items_taxes');
$builder->where('sale_id', $sale_id);
$builder->where('line', $line);
$builder->where('name', $name);
$builder->where('percent', $percent);
$builder->update(['tax_type' => $tax_type, 'item_tax_amount' => $item_tax_amount]);
}
/**
* @param array $sales_taxes
* @return void
*/
private function save_sales_tax(array &$sales_taxes): void
{
$builder = $this->db->table('sales_taxes');
/**
* @param array $sales_taxes
* @return void
*/
private function save_sales_tax(array &$sales_taxes): void
{
$builder = $this->db->table('sales_taxes');
foreach($sales_taxes as $line => $sales_tax)
{
$builder->insert($sales_tax);
}
}
foreach ($sales_taxes as $line => $sales_tax) {
$builder->insert($sales_tax);
}
}
/**
* @param string $quantity
* @param string $price
* @param string $discount
* @param bool $include_discount
* @return string
*/
public function get_item_total(string $quantity, string $price, string $discount, bool $include_discount = false): string
{
$total = bcmul($quantity, $price);
/**
* @param string $quantity
* @param string $price
* @param string $discount
* @param bool $include_discount
* @return string
*/
public function get_item_total(string $quantity, string $price, string $discount, bool $include_discount = false): string
{
$total = bcmul($quantity, $price);
if($include_discount)
{
$total = bcsub($total, bcmul(bcmul($quantity, $price), bcdiv($discount, 100)));
}
if ($include_discount) {
$total = bcsub($total, bcmul(bcmul($quantity, $price), bcdiv($discount, 100)));
}
return $total;
}
return $total;
}
/**
* @param string $tax_basis
* @param string $tax_percentage
* @param int $rounding_mode
* @param int $decimals
* @return float
*/
public function get_item_tax(string $tax_basis, string $tax_percentage, int $rounding_mode, int $decimals): float //TODO: is this currency safe?
{
$tax_fraction = bcdiv(bcadd('100', $tax_percentage), '100');
$price_tax_excl = bcdiv($tax_basis, $tax_fraction);
$tax_amount = bcsub($tax_basis, $price_tax_excl);
/**
* @param string $tax_basis
* @param string $tax_percentage
* @param int $rounding_mode
* @param int $decimals
* @return float
*/
public function get_item_tax(string $tax_basis, string $tax_percentage, int $rounding_mode, int $decimals): float // TODO: is this currency safe?
{
$tax_fraction = bcdiv(bcadd('100', $tax_percentage), '100');
$price_tax_excl = bcdiv($tax_basis, $tax_fraction);
$tax_amount = bcsub($tax_basis, $price_tax_excl);
return $this->round_number($rounding_mode, $tax_amount, $decimals);
}
return $this->round_number($rounding_mode, $tax_amount, $decimals);
}
/**
* @param string $tax_basis
* @param string $tax_percentage
* @param int $rounding_mode
* @param int $decimals
* @return float
*/
public function get_sales_tax_for_amount(string $tax_basis, string $tax_percentage, int $rounding_mode, int $decimals): float //TODO: is this currency safe?
{
$tax_fraction = bcdiv($tax_percentage, '100');
$tax_amount = bcmul($tax_basis, $tax_fraction);
/**
* @param string $tax_basis
* @param string $tax_percentage
* @param int $rounding_mode
* @param int $decimals
* @return float
*/
public function get_sales_tax_for_amount(string $tax_basis, string $tax_percentage, int $rounding_mode, int $decimals): float // TODO: is this currency safe?
{
$tax_fraction = bcdiv($tax_percentage, '100');
$tax_amount = bcmul($tax_basis, $tax_fraction);
return $this->round_number($rounding_mode, $tax_amount, $decimals);
}
return $this->round_number($rounding_mode, $tax_amount, $decimals);
}
/**
* @param int $rounding_mode
* @param string $amount
* @param int $decimals
* @return float
*/
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
if($rounding_mode == Migration_TaxAmount::ROUND_UP) //TODO: === ?
{
$fig = pow(10, $decimals);
$rounded_total = (ceil($fig * $amount) + ceil($fig*$amount - ceil($fig * $amount)))/$fig;
}
elseif($rounding_mode == Migration_TaxAmount::ROUND_DOWN) //TODO: === ?
{
$fig = pow(10, $decimals);
$rounded_total = (floor($fig * $amount) + floor($fig * $amount - floor($fig * $amount)))/$fig;
}
elseif($rounding_mode == Migration_TaxAmount::HALF_FIVE) //TODO: === ?
{
$rounded_total = round($amount / 5) * 5;
}
else
{
$rounded_total = round($amount, $decimals, $rounding_mode);
}
/**
* @param int $rounding_mode
* @param string $amount
* @param int $decimals
* @return float
*/
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
if ($rounding_mode == Migration_TaxAmount::ROUND_UP) { // TODO: === ?
$fig = pow(10, $decimals);
$rounded_total = (ceil($fig * $amount) + ceil($fig * $amount - ceil($fig * $amount))) / $fig;
} elseif ($rounding_mode == Migration_TaxAmount::ROUND_DOWN) { // TODO: === ?
$fig = pow(10, $decimals);
$rounded_total = (floor($fig * $amount) + floor($fig * $amount - floor($fig * $amount))) / $fig;
} elseif ($rounding_mode == Migration_TaxAmount::HALF_FIVE) { // TODO: === ?
$rounded_total = round($amount / 5) * 5;
} else {
$rounded_total = round($amount, $decimals, $rounding_mode);
}
return $rounded_total;
}
return $rounded_total;
}
/**
* @param array $sales_taxes
* @param int $tax_type
* @param string $tax_group
* @param float $tax_rate
* @param string $tax_basis
* @param string $item_tax_amount
* @param int $tax_group_sequence
* @param int $rounding_code
* @param int $sale_id
* @param string $name
* @param string $tax_code
* @return void
*/
public function update_sales_taxes(array &$sales_taxes, int $tax_type, string $tax_group, float $tax_rate, string $tax_basis, string $item_tax_amount, int $tax_group_sequence, int $rounding_code, int $sale_id, string $name = '', string $tax_code = ''): void
{
$tax_group_index = $this->clean('X' . $tax_group);
/**
* @param array $sales_taxes
* @param int $tax_type
* @param string $tax_group
* @param float $tax_rate
* @param string $tax_basis
* @param string $item_tax_amount
* @param int $tax_group_sequence
* @param int $rounding_code
* @param int $sale_id
* @param string $name
* @param string $tax_code
* @return void
*/
public function update_sales_taxes(array &$sales_taxes, int $tax_type, string $tax_group, float $tax_rate, string $tax_basis, string $item_tax_amount, int $tax_group_sequence, int $rounding_code, int $sale_id, string $name = '', string $tax_code = ''): void
{
$tax_group_index = $this->clean('X' . $tax_group);
if(!array_key_exists($tax_group_index, $sales_taxes))
{
$insertkey = $tax_group_index;
$sales_tax = [
$insertkey => [
'sale_id' => $sale_id,
'tax_type' => $tax_type,
'tax_group' => $tax_group,
'sale_tax_basis' => $tax_basis,
'sale_tax_amount' => $item_tax_amount,
'print_sequence' => $tax_group_sequence,
'name' => $name,
'tax_rate' => $tax_rate,
'sales_tax_code_id' => $tax_code,
'rounding_code' => $rounding_code
]
];
if (!array_key_exists($tax_group_index, $sales_taxes)) {
$insertkey = $tax_group_index;
$sales_tax = [
$insertkey => [
'sale_id' => $sale_id,
'tax_type' => $tax_type,
'tax_group' => $tax_group,
'sale_tax_basis' => $tax_basis,
'sale_tax_amount' => $item_tax_amount,
'print_sequence' => $tax_group_sequence,
'name' => $name,
'tax_rate' => $tax_rate,
'sales_tax_code_id' => $tax_code,
'rounding_code' => $rounding_code
]
];
//add to existing array
$sales_taxes += $sales_tax;
}
else
{
// Important ... the sales amounts are accumulated for the group at the maximum configurable scale value of 4
// but the scale will in reality be the scale specified by the tax_decimal configuration value used for sales_items_taxes
$sales_taxes[$tax_group_index]['sale_tax_basis'] = bcadd($sales_taxes[$tax_group_index]['sale_tax_basis'], $tax_basis, 4);
$sales_taxes[$tax_group_index]['sale_tax_amount'] = bcadd($sales_taxes[$tax_group_index]['sale_tax_amount'], $item_tax_amount, 4);
}
}
// Add to existing array
$sales_taxes += $sales_tax;
} else {
// Important: the sales amounts are accumulated for the group at the maximum configurable scale value of 4
// but the scale will in reality be the scale specified by the tax_decimal configuration value used for sales_items_taxes
$sales_taxes[$tax_group_index]['sale_tax_basis'] = bcadd($sales_taxes[$tax_group_index]['sale_tax_basis'], $tax_basis, 4);
$sales_taxes[$tax_group_index]['sale_tax_amount'] = bcadd($sales_taxes[$tax_group_index]['sale_tax_amount'], $item_tax_amount, 4);
}
}
/**
* @param string $string
* @return string
*/
public function clean(string $string): string //TODO: This can probably go into the migration helper as it's used it more than one migration. Also, $string needs to be refactored to a different name.
{
$string = str_replace(' ', '-', $string); // Replaces all spaces with hyphens.
/**
* @param string $string
* @return string
*/
public function clean(string $string): string // TODO: This can probably go into the migration helper as it's used it more than one migration. Also, $string needs to be refactored to a different name.
{
$string = str_replace(' ', '-', $string); // Replaces all spaces with hyphens.
return preg_replace('/[^A-Za-z0-9\-]/', '', $string); // Removes special chars.
}
return preg_replace('/[^A-Za-z0-9\-]/', '', $string); // Removes special chars.
}
/**
* @param array $sales_taxes
* @return void
*/
public function apply_invoice_taxing(array &$sales_taxes): void
{
if(!empty($sales_taxes)) //TODO: Duplicated code
{
$sort = [];
foreach($sales_taxes as $k => $v)
{
$sort['print_sequence'][$k] = $v['print_sequence'];
}
array_multisort($sort['print_sequence'], SORT_ASC, $sales_taxes);
}
/**
* @param array $sales_taxes
* @return void
*/
public function apply_invoice_taxing(array &$sales_taxes): void
{
if (!empty($sales_taxes)) { // TODO: Duplicated code
$sort = [];
foreach ($sales_taxes as $k => $v) {
$sort['print_sequence'][$k] = $v['print_sequence'];
}
array_multisort($sort['print_sequence'], SORT_ASC, $sales_taxes);
}
$decimals = totals_decimals();
$decimals = totals_decimals();
foreach($sales_taxes as $row_number => $sales_tax)
{
$sales_taxes[$row_number]['sale_tax_amount'] = $this->get_sales_tax_for_amount($sales_tax['sale_tax_basis'], $sales_tax['tax_rate'], $sales_tax['rounding_code'], $decimals);
}
}
foreach ($sales_taxes as $row_number => $sales_tax) {
$sales_taxes[$row_number]['sale_tax_amount'] = $this->get_sales_tax_for_amount($sales_tax['sale_tax_basis'], $sales_tax['tax_rate'], $sales_tax['rounding_code'], $decimals);
}
}
/**
* @param array $sales_taxes
* @return void
*/
public function round_sales_taxes(array &$sales_taxes): void
{
if(!empty($sales_taxes))
{
$sort = [];
/**
* @param array $sales_taxes
* @return void
*/
public function round_sales_taxes(array &$sales_taxes): void
{
if (!empty($sales_taxes)) {
$sort = [];
foreach($sales_taxes as $k=>$v)
{
$sort['print_sequence'][$k] = $v['print_sequence'];
}
foreach ($sales_taxes as $k => $v) {
$sort['print_sequence'][$k] = $v['print_sequence'];
}
array_multisort($sort['print_sequence'], SORT_ASC, $sales_taxes);
}
array_multisort($sort['print_sequence'], SORT_ASC, $sales_taxes);
}
$decimals = totals_decimals();
$decimals = totals_decimals();
foreach($sales_taxes as $row_number => $sales_tax)
{
$sale_tax_amount = $sales_tax['sale_tax_amount'];
$rounding_code = $sales_tax['rounding_code'];
$rounded_sale_tax_amount = $sale_tax_amount;
foreach ($sales_taxes as $row_number => $sales_tax) {
$sale_tax_amount = $sales_tax['sale_tax_amount'];
$rounding_code = $sales_tax['rounding_code'];
$rounded_sale_tax_amount = $sale_tax_amount;
if ($rounding_code == PHP_ROUND_HALF_UP //TODO: This block of if/elseif statements can be converted to a switch.
|| $rounding_code == PHP_ROUND_HALF_DOWN
|| $rounding_code == PHP_ROUND_HALF_EVEN
|| $rounding_code == PHP_ROUND_HALF_ODD)
{
$rounded_sale_tax_amount = round($sale_tax_amount, $decimals, $rounding_code);
}
elseif($rounding_code == Migration_TaxAmount::ROUND_UP)
{
$fig = (int) str_pad('1', $decimals, '0');
$rounded_sale_tax_amount = (ceil($sale_tax_amount * $fig) / $fig);
}
elseif($rounding_code == Migration_TaxAmount::ROUND_DOWN)
{
$fig = (int) str_pad('1', $decimals, '0');
$rounded_sale_tax_amount = (floor($sale_tax_amount * $fig) / $fig);
}
elseif($rounding_code == Migration_TaxAmount::HALF_FIVE)
{
$rounded_sale_tax_amount = round($sale_tax_amount / 5) * 5;
}
if (
$rounding_code == PHP_ROUND_HALF_UP // TODO: This block of if/elseif statements can be converted to a switch.
|| $rounding_code == PHP_ROUND_HALF_DOWN
|| $rounding_code == PHP_ROUND_HALF_EVEN
|| $rounding_code == PHP_ROUND_HALF_ODD
) {
$rounded_sale_tax_amount = round($sale_tax_amount, $decimals, $rounding_code);
} elseif ($rounding_code == Migration_TaxAmount::ROUND_UP) {
$fig = (int) str_pad('1', $decimals, '0');
$rounded_sale_tax_amount = (ceil($sale_tax_amount * $fig) / $fig);
} elseif ($rounding_code == Migration_TaxAmount::ROUND_DOWN) {
$fig = (int) str_pad('1', $decimals, '0');
$rounded_sale_tax_amount = (floor($sale_tax_amount * $fig) / $fig);
} elseif ($rounding_code == Migration_TaxAmount::HALF_FIVE) {
$rounded_sale_tax_amount = round($sale_tax_amount / 5) * 5;
}
$sales_taxes[$row_number]['sale_tax_amount'] = $rounded_sale_tax_amount;
}
}
$sales_taxes[$row_number]['sale_tax_amount'] = $rounded_sale_tax_amount;
}
}
}

View File

@@ -6,19 +6,20 @@ use CodeIgniter\Database\Migration;
class Migration_taxgroupconstraint extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('tax_jurisdictions') . ' ADD CONSTRAINT tax_jurisdictions_uq1 UNIQUE (tax_group)');
}
/**
* Perform a migration step.
*/
public function up(): void
{
log_message('info', 'Migrating tax group constraints.');
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('tax_jurisdictions') . ' ADD CONSTRAINT tax_jurisdictions_uq1 UNIQUE (tax_group)');
}
/**
* Revert a migration step.
*/
public function down(): void
{
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('tax_jurisdictions') . ' DROP INDEX tax_jurisdictions_uq1');
}
/**
* Revert a migration step.
*/
public function down(): void
{
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('tax_jurisdictions') . ' DROP INDEX tax_jurisdictions_uq1');
}
}

View File

@@ -6,29 +6,30 @@ use CodeIgniter\Database\Migration;
class Migration_image_upload_defaults extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
$image_values = [
['key' => 'image_allowed_types', 'value' => 'gif|jpg|png'],
['key' => 'image_max_height', 'value' => '480'],
['key' => 'image_max_size', 'value' => '128'],
['key' => 'image_max_width', 'value' => '640']
];
/**
* Perform a migration step.
*/
public function up(): void
{
log_message('info', 'Migrating image upload defaults.');
$image_values = [
['key' => 'image_allowed_types', 'value' => 'gif|jpg|png'],
['key' => 'image_max_height', 'value' => '480'],
['key' => 'image_max_size', 'value' => '128'],
['key' => 'image_max_width', 'value' => '640']
];
$builder = $this->db->table('app_config');
$builder->insertBatch($image_values);
}
$builder = $this->db->table('app_config');
$builder->insertBatch($image_values);
}
/**
* Revert a migration step.
*/
public function down(): void
{
$builder = $this->db->table('app_config');
$builder->whereIn('key', ['image_allowed_types','image_max_height','image_max_size','image_max_width']);
$builder->delete();
}
/**
* Revert a migration step.
*/
public function down(): void
{
$builder = $this->db->table('app_config');
$builder->whereIn('key', ['image_allowed_types', 'image_max_height', 'image_max_size', 'image_max_width']);
$builder->delete();
}
}

View File

@@ -6,23 +6,17 @@ use CodeIgniter\Database\Migration;
class Migration_modify_attr_links_constraint extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
error_log('Migrating modify_attr_links_constraint');
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.2_modify_attr_links_constraint.sql');
}
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.2_modify_attr_links_constraint.sql');
error_log('Migrating modify_attr_links_constraint');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -6,19 +6,20 @@ use CodeIgniter\Database\Migration;
class Migration_cashrounding extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('sales_payments') . ' ADD COLUMN `cash_adjustment` tinyint NOT NULL DEFAULT 0 AFTER `cash_refund`');
}
/**
* Perform a migration step.
*/
public function up(): void
{
log_message('info', 'Migrating cash rounding.');
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('sales_payments') . ' ADD COLUMN `cash_adjustment` tinyint NOT NULL DEFAULT 0 AFTER `cash_refund`');
}
/**
* Revert a migration step.
*/
public function down(): void
{
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('sales_payments') . ' DROP COLUMN `cash_adjustment`');
}
/**
* Revert a migration step.
*/
public function down(): void
{
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('sales_payments') . ' DROP COLUMN `cash_adjustment`');
}
}

View File

@@ -6,23 +6,17 @@ use CodeIgniter\Database\Migration;
class Migration_add_item_kit_number extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
error_log('Migrating add_item_kit_number');
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.3_add_kits_item_number.sql');
}
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.3_add_kits_item_number.sql');
error_log('Migrating add_item_kit_number');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -6,23 +6,17 @@ use CodeIgniter\Database\Migration;
class Migration_modify_session_datatype extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
error_log('Migrating modify_session_datatype');
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.4_modify_session_datatype.sql');
}
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.3.4_modify_session_datatype.sql');
error_log('Migrating modify_session_datatype');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -11,150 +11,141 @@ use DateTime;
class Migration_database_optimizations extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
error_log('Migrating database_optimizations');
/**
* Perform a migration step.
*/
public function up(): void
{
log_message('info', 'Migrating database optimizations.');
$attribute = model(Attribute::class);
$attribute = model(Attribute::class);
$attribute->delete_orphaned_values();
$attribute->deleteOrphanedValues();
$this->migrate_duplicate_attribute_values(DECIMAL);
$this->migrate_duplicate_attribute_values(DATE);
$this->migrate_duplicate_attribute_values(DECIMAL);
$this->migrate_duplicate_attribute_values(DATE);
//Select all attributes that have data in more than one column
$builder = $this->db->table('attribute_values');
$builder->select('attribute_id, attribute_value, attribute_decimal, attribute_date');
$builder->groupStart();
$builder->where('attribute_value IS NOT NULL');
$builder->where('attribute_date IS NOT NULL');
$builder->groupEnd();
$builder->orGroupStart();
$builder->where('attribute_value IS NOT NULL');
$builder->where('attribute_decimal IS NOT NULL');
$builder->groupEnd();
$attribute_values = $builder->get();
// Select all attributes that have data in more than one column
$builder = $this->db->table('attribute_values');
$builder->select('attribute_id, attribute_value, attribute_decimal, attribute_date');
$builder->groupStart();
$builder->where('attribute_value IS NOT NULL');
$builder->where('attribute_date IS NOT NULL');
$builder->groupEnd();
$builder->orGroupStart();
$builder->where('attribute_value IS NOT NULL');
$builder->where('attribute_decimal IS NOT NULL');
$builder->groupEnd();
$attribute_values = $builder->get();
$this->db->transStart();
$this->db->transStart();
//Clean up Attribute values table where there is an attribute value and an attribute_date/attribute_decimal
foreach($attribute_values->getResultArray() as $attribute_value)
{
$builder = $this->db->table('attribute_values');
$builder->delete(['attribute_id' => $attribute_value['attribute_id']]);
// Clean up Attribute values table where there is an attribute value and an attribute_date/attribute_decimal
foreach ($attribute_values->getResultArray() as $attribute_value) {
$builder = $this->db->table('attribute_values');
$builder->delete(['attribute_id' => $attribute_value['attribute_id']]);
$builder = $this->db->table('attribute_links');
$builder->select('links.definition_id, links.item_id, links.attribute_id, defs.definition_type');
$builder->join('attribute_definitions defs', 'defs.definition_id = links.definition_id');
$builder->where('attribute_id', $attribute_value['attribute_id']);
$attribute_links = $builder->get();
$builder = $this->db->table('attribute_links');
$builder->select('links.definition_id, links.item_id, links.attribute_id, defs.definition_type');
$builder->join('attribute_definitions defs', 'defs.definition_id = links.definition_id');
$builder->where('attribute_id', $attribute_value['attribute_id']);
$attribute_links = $builder->get();
if($attribute_links)
{
$builder = $this->db->table('attribute_links');
$attribute_links = $attribute_links->getResultArray() ?: [];
if ($attribute_links) {
$builder = $this->db->table('attribute_links');
$attribute_links = $attribute_links->getResultArray() ?: [];
foreach($attribute_links->getResultArray() as $attribute_link)
{
$builder->where('attribute_id', $attribute_link['attribute_id']);
$builder->where('item_id', $attribute_link['item_id']);
$builder->delete();
foreach ($attribute_links->getResultArray() as $attribute_link) {
$builder->where('attribute_id', $attribute_link['attribute_id']);
$builder->where('item_id', $attribute_link['item_id']);
$builder->delete();
switch($attribute_link['definition_type'])
{
case DECIMAL:
$value = $attribute_value['attribute_decimal'];
break;
case DATE:
$config = config(OSPOS::class)->settings;
$attribute_date = DateTime::createFromFormat('Y-m-d', $attribute_value['attribute_date']);
$value = $attribute_date->format($config['dateformat']);
break;
default:
$value = $attribute_value['attribute_value'];
break;
}
switch ($attribute_link['definition_type']) {
case DECIMAL:
$value = $attribute_value['attribute_decimal'];
break;
case DATE:
$config = config(OSPOS::class)->settings;
$attribute_date = DateTime::createFromFormat('Y-m-d', $attribute_value['attribute_date']);
$value = $attribute_date->format($config['dateformat']);
break;
default:
$value = $attribute_value['attribute_value'];
break;
}
$attribute->saveAttributeValue($value, $attribute_link['definition_id'], $attribute_link['item_id'], false, $attribute_link['definition_type']);
}
}
}
$this->db->transComplete();
$attribute->saveAttributeValue($value, $attribute_link['definition_id'], $attribute_link['item_id'], false, $attribute_link['definition_type']);
}
}
}
$this->db->transComplete();
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.4.0_database_optimizations.sql');
error_log('Migrating database_optimizations completed');
}
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.4.0_database_optimizations.sql');
log_message('info', 'Finished migrating database optimizations.');
}
/**
* Given the type of attribute, deletes any duplicates it finds in the attribute_values table and reassigns those
*/
private function migrate_duplicate_attribute_values($attribute_type): void
{
//Remove duplicate attribute values needed to make attribute_decimals and attribute_dates unique
$this->db->transStart();
/**
* Given the type of attribute, deletes any duplicates it finds in the attribute_values table and reassigns those
*/
private function migrate_duplicate_attribute_values($attribute_type): void
{
// Remove duplicate attribute values needed to make attribute_decimals and attribute_dates unique
$this->db->transStart();
$column = 'attribute_' . strtolower($attribute_type);
$column = 'attribute_' . strtolower($attribute_type);
$builder = $this->db->table('attribute_values');
$builder->select("$column");
$builder->groupBy($column);
$builder->having("COUNT($column) > 1");
$duplicated_values = $builder->get();
$builder = $this->db->table('attribute_values');
$builder->select("$column");
$builder->groupBy($column);
$builder->having("COUNT($column) > 1");
$duplicated_values = $builder->get();
foreach($duplicated_values->getResultArray() as $duplicated_value)
{
$subquery_builder = $this->db->table('attribute_values');
$subquery_builder->select('attribute_id');
$subquery_builder->where($column, $duplicated_value[$column]);
$subquery = $subquery_builder->getCompiledSelect();
foreach ($duplicated_values->getResultArray() as $duplicated_value) {
$subquery_builder = $this->db->table('attribute_values');
$subquery_builder->select('attribute_id');
$subquery_builder->where($column, $duplicated_value[$column]);
$subquery = $subquery_builder->getCompiledSelect();
$builder = $this->db->table('attribute_values');
$builder->select('attribute_id');
$builder->where($column, $duplicated_value[$column]);
$builder->where("attribute_id IN ($subquery)", null, false);
$attribute_ids_to_fix = $builder->get();
$builder = $this->db->table('attribute_values');
$builder->select('attribute_id');
$builder->where($column, $duplicated_value[$column]);
$builder->where("attribute_id IN ($subquery)", null, false);
$attribute_ids_to_fix = $builder->get();
$this->reassign_duplicate_attribute_values($attribute_ids_to_fix, $duplicated_value);
}
$this->reassign_duplicate_attribute_values($attribute_ids_to_fix, $duplicated_value);
}
$this->db->transComplete();
}
$this->db->transComplete();
}
/**
* Updates the attribute_id in all attribute_link rows with duplicated attribute_ids then deletes unneeded rows from attribute_values
*
* @param ResultInterface $attribute_ids_to_fix All attribute_ids that need to parsed
* @param array $attribute_value The attribute value in question.
*/
private function reassign_duplicate_attribute_values(ResultInterface $attribute_ids_to_fix, array $attribute_value): void
{
$attribute_ids = $attribute_ids_to_fix->getResultArray();
$retain_attribute_id = $attribute_ids[0]['attribute_id'];
/**
* Updates the attribute_id in all attribute_link rows with duplicated attribute_ids then deletes unneeded rows from attribute_values
*
* @param ResultInterface $attribute_ids_to_fix All attribute_ids that need to parsed
* @param array $attribute_value The attribute value in question.
*/
private function reassign_duplicate_attribute_values(ResultInterface $attribute_ids_to_fix, array $attribute_value): void
{
$attribute_ids = $attribute_ids_to_fix->getResultArray();
$retain_attribute_id = $attribute_ids[0]['attribute_id'];
foreach($attribute_ids as $attribute_id)
{
//Update attribute_link with the attribute_id we are keeping
$builder = $this->db->table('attribute_links');
$builder->where('attribute_id', $attribute_id['attribute_id']);
$builder->update(['attribute_id' => $retain_attribute_id]);
foreach ($attribute_ids as $attribute_id) {
// Update attribute_link with the attribute_id we are keeping
$builder = $this->db->table('attribute_links');
$builder->where('attribute_id', $attribute_id['attribute_id']);
$builder->update(['attribute_id' => $retain_attribute_id]);
//Delete the row from attribute_values if it isn't our keeper
if($attribute_id['attribute_id'] !== $retain_attribute_id)
{
$builder = $this->db->table('attribute_values');
$builder->delete(['attribute_id' => $attribute_id['attribute_id']]);
}
}
}
// Delete the row from attribute_values if it isn't our keeper
if ($attribute_id['attribute_id'] !== $retain_attribute_id) {
$builder = $this->db->table('attribute_values');
$builder->delete(['attribute_id' => $attribute_id['attribute_id']]);
}
}
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -7,60 +7,58 @@ use App\Models\Attribute;
class Migration_remove_duplicate_links extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
error_log('Migrating remove_duplicate_links');
/**
* Perform a migration step.
*/
public function up(): void
{
log_message('info', 'Removing duplicate links.');
$this->migrate_duplicate_attribute_links();
$this->migrate_duplicate_attribute_links();
error_log('Migrating remove_duplicate_links completed');
}
log_message('info', 'Duplicate links removed.');
}
/**
* Given the type of attribute, deletes any duplicates it finds in the attribute_values table and reassigns those
*
* @property attribute $attribute
*/
private function migrate_duplicate_attribute_links(): void
{
$attribute = model(Attribute::class);
/**
* Given the type of attribute, deletes any duplicates it finds in the attribute_values table and reassigns those
*
* @property attribute $attribute
*/
private function migrate_duplicate_attribute_links(): void
{
$attribute = model(Attribute::class);
//Remove duplicate attribute links
$this->db->transStart();
// Remove duplicate attribute links
$this->db->transStart();
$builder = $this->db->table('attribute_links');
$builder->select('item_id, definition_id, attribute_id, COUNT(*) as count');
$builder->where('sale_id', null);
$builder->where('receiving_id', null);
$builder->groupBy('item_id');
$builder->groupBy('definition_id');
$builder->groupBy('attribute_id');
$builder->having('count > 1');
$duplicated_links = $builder->get();
$builder = $this->db->table('attribute_links');
$builder->select('item_id, definition_id, attribute_id, COUNT(*) as count');
$builder->where('sale_id', null);
$builder->where('receiving_id', null);
$builder->where('item_id IS NOT NULL');
$builder->groupBy('item_id');
$builder->groupBy('definition_id');
$builder->groupBy('attribute_id');
$builder->having('count > 1');
$duplicated_links = $builder->get();
$builder = $this->db->table('attribute_links');
$builder = $this->db->table('attribute_links');
foreach($duplicated_links->getResultArray() as $duplicated_link)
{
$builder->where('sale_id', null);
$builder->where('receiving_id', null);
$builder->where('item_id', $duplicated_link['item_id']);
$builder->where('definition_id', $duplicated_link['definition_id']);
$builder->delete();
foreach ($duplicated_links->getResultArray() as $duplicated_link) {
$builder->where('sale_id', null);
$builder->where('receiving_id', null);
$builder->where('item_id', $duplicated_link['item_id']);
$builder->where('definition_id', $duplicated_link['definition_id']);
$builder->delete();
$attribute->saveAttributeLink($duplicated_link['item_id'], $duplicated_link['definition_id'], $duplicated_link['attribute_id']);
}
$attribute->saveAttributeLink($duplicated_link['item_id'], $duplicated_link['definition_id'], $duplicated_link['attribute_id']);
}
$this->db->transComplete();
}
$this->db->transComplete();
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -6,23 +6,20 @@ use CodeIgniter\Database\Migration;
class Migration_move_expenses_categories extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
error_log('Migrating expense categories module');
/**
* Perform a migration step.
*/
public function up(): void
{
log_message('info', 'Migrating expense categories module');
$this->db->simpleQuery("UPDATE ospos_grants SET menu_group = 'office' WHERE permission_id = 'expenses_categories'");
$this->db->simpleQuery("UPDATE ospos_grants SET menu_group = 'office' WHERE permission_id = 'expenses_categories'");
error_log('Migrating expense categories module completed');
}
log_message('info', 'Migrating expense categories module completed');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void {}
}

View File

@@ -13,153 +13,136 @@ use ReflectionException;
class Convert_to_ci4 extends Migration
{
/**
* Constructor.
*/
public function __construct(?Forge $forge = null)
{
parent::__construct($forge);
helper('security');
}
/**
* Constructor.
*/
public function __construct(?Forge $forge = null)
{
parent::__construct($forge);
helper('security');
}
/**
* Perform a migration step.
*/
public function up(): void
{
error_log('Migrating database to CodeIgniter4 formats');
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.4.0_ci4_conversion.sql');
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.4.0_ci4_conversion.sql');
if (!empty(config('Encryption')->key)) {
$this->convert_ci3_encrypted_data();
} else {
check_encryption();
}
if(!empty(config('Encryption')->key))
{
$this->convert_ci3_encrypted_data();
}
else
{
check_encryption();
}
remove_backup();
}
remove_backup();
/**
* Revert a migration step.
*/
public function down(): void {}
error_log('Migrating to CodeIgniter4 formats completed');
}
/**
* @return RedirectResponse|void
* @throws ReflectionException
*/
private function convert_ci3_encrypted_data()
{
$appconfig = model(Appconfig::class);
/**
* Revert a migration step.
*/
public function down(): void
{
$ci3_encrypted_data = [
'clcdesq_api_key' => '',
'clcdesq_api_url' => '',
'mailchimp_api_key' => '',
'mailchimp_list_id' => '',
'smtp_pass' => ''
];
}
foreach ($ci3_encrypted_data as $key => $value) {
$ci3_encrypted_data[$key] = $appconfig->get_value($key);
}
/**
* @return RedirectResponse|void
* @throws ReflectionException
*/
private function convert_ci3_encrypted_data()
{
$appconfig = model(Appconfig::class);
$decrypted_data = $this->decrypt_ci3_data($ci3_encrypted_data);
$ci3_encrypted_data = [
'clcdesq_api_key' => '',
'clcdesq_api_url' => '',
'mailchimp_api_key' => '',
'mailchimp_list_id' => '',
'smtp_pass' => ''
];
check_encryption();
foreach($ci3_encrypted_data as $key => $value)
{
$ci3_encrypted_data[$key] = $appconfig->get_value($key);
}
try {
$ci4_encrypted_data = $this->encrypt_data($decrypted_data);
$decrypted_data = $this->decrypt_ci3_data($ci3_encrypted_data);
$success = empty(array_diff_assoc($decrypted_data, $this->decrypt_data($ci4_encrypted_data)));
if (!$success) {
abort_encryption_conversion();
remove_backup();
throw new RedirectException('login');
}
check_encryption();
$appconfig->batch_save($ci4_encrypted_data);
} catch (RedirectException $e) {
return redirect()->to('login'); // TODO: Need to figure out how to pass the error to the Login controller so that it gets displayed.
}
}
try
{
$ci4_encrypted_data = $this->encrypt_data($decrypted_data);
/**
* Decrypts CI3 encrypted data and returns the plaintext values.
*
* @param array $encrypted_data Data encrypted using CI3 methodology.
* @return array Plaintext, unencrypted data.
*/
private function decrypt_ci3_data(array $encrypted_data): array
{
$config = new Encryption();
$config->driver = 'OpenSSL';
$config->key = config('Encryption')->key;
$config->cipher = 'AES-128-CBC';
$config->rawData = false;
$config->encryptKeyInfo = 'encryption';
$config->authKeyInfo = 'authentication';
$success = empty(array_diff_assoc($decrypted_data, $this->decrypt_data($ci4_encrypted_data)));
if(!$success)
{
abort_encryption_conversion();
remove_backup();
throw new RedirectException('login');
}
$encrypter = Services::encrypter($config);
$appconfig->batch_save($ci4_encrypted_data);
} catch(RedirectException $e)
{
return redirect()->to('login'); //TODO: Need to figure out how to pass the error to the Login controller so that it gets displayed.
}
}
$decrypted_data = [];
foreach ($encrypted_data as $key => $value) {
$decrypted_data[$key] = !empty($value) ? $encrypter->decrypt($value) : '';
}
/**
* Decrypts CI3 encrypted data and returns the plaintext values.
*
* @param array $encrypted_data Data encrypted using CI3 methodology.
* @return array Plaintext, unencrypted data.
*/
private function decrypt_ci3_data(array $encrypted_data): array
{
$config = new Encryption();
$config->driver = 'OpenSSL';
$config->key = config('Encryption')->key;
$config->cipher = 'AES-128-CBC';
$config->rawData = false;
$config->encryptKeyInfo = 'encryption';
$config->authKeyInfo = 'authentication';
return $decrypted_data;
}
$encrypter = Services::encrypter($config);
/**
* Encrypts data using CI4 algorithms.
*
* @param array $plain_data Data to be encrypted.
* @return array Encrypted data.
*/
private function encrypt_data(array $plain_data): array
{
$encrypter = Services::encrypter();
$decrypted_data = [];
foreach($encrypted_data as $key => $value)
{
$decrypted_data[$key] = !empty($value) ? $encrypter->decrypt($value): '';
}
$encrypted_data = [];
foreach ($plain_data as $key => $value) {
$encrypted_data[$key] = !empty($value) ? $encrypter->encrypt($value) : '';
}
return $decrypted_data;
}
return $encrypted_data;
}
/**
* Encrypts data using CI4 algorithms.
*
* @param array $plain_data Data to be encrypted.
* @return array Encrypted data.
*/
private function encrypt_data(array $plain_data): array
{
$encrypter = Services::encrypter();
/**
* Decrypts data using CI4 algorithms.
*
* @param array $encrypted_data Data to be decrypted.
* @return array Decrypted data.
*/
private function decrypt_data(array $encrypted_data): array
{
$encrypter = Services::encrypter();
$encrypted_data = [];
foreach($plain_data as $key => $value)
{
$encrypted_data[$key] = !empty($value) ? $encrypter->encrypt($value) : '';
}
$decrypted_data = [];
foreach ($encrypted_data as $key => $value) {
$decrypted_data[$key] = !empty($value) ? $encrypter->decrypt($value) : '';
}
return $encrypted_data;
}
/**
* Decrypts data using CI4 algorithms.
*
* @param array $encrypted_data Data to be decrypted.
* @return array Decrypted data.
*/
private function decrypt_data(array $encrypted_data): array
{
$encrypter = Services::encrypter();
$decrypted_data = [];
foreach($encrypted_data as $key => $value)
{
$decrypted_data[$key] = !empty($value) ? $encrypter->decrypt($value) : '';
}
return $decrypted_data;
}
return $decrypted_data;
}
}

View File

@@ -1,27 +1,28 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class IntToTinyint extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('customers') . ' MODIFY `consent` tinyint NOT NULL DEFAULT 0');
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('cash_up') . ' MODIFY `note` tinyint NOT NULL DEFAULT 0');
}
/**
* Revert a migration step.
*/
public function down(): void
{
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('customers') . ' MODIFY `consent` int NOT NULL DEFAULT 0');
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('cash_up') . ' MODIFY `note` int NOT NULL DEFAULT 0');
}
}
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class IntToTinyint extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
log_message('info', 'Converting ints to tinyints.');
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('customers') . ' MODIFY `consent` tinyint NOT NULL DEFAULT 0');
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('cash_up') . ' MODIFY `note` tinyint NOT NULL DEFAULT 0');
}
/**
* Revert a migration step.
*/
public function down(): void
{
log_message('info', 'Converting tinyints to ints.');
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('customers') . ' MODIFY `consent` int NOT NULL DEFAULT 0');
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('cash_up') . ' MODIFY `note` int NOT NULL DEFAULT 0');
}
}

View File

@@ -6,30 +6,31 @@ use CodeIgniter\Database\Migration;
class Migration_add_missing_config extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
$image_values = [
['key' => 'account_number', 'value' => ''], // This has no current maintenance, but it's used in Sales
['key' => 'category_dropdown', 'value' => ''],
['key' => 'smtp_host', 'value' => ''],
['key' => 'smtp_user', 'value' => ''],
['key' => 'smtp_pass', 'value' => ''],
['key' => 'login_form', 'value' => ''],
['key' => 'receiving_calculate_average_price', 'value' => ''],
['key' => 'payment_message', 'value' => '']
];
/**
* Perform a migration step.
*/
public function up(): void
{
log_message('info', 'Adding missing configs.');
$image_values = [
['key' => 'account_number', 'value' => ''], // This has no current maintenance, but it's used in Sales
['key' => 'category_dropdown', 'value' => ''],
['key' => 'smtp_host', 'value' => ''],
['key' => 'smtp_user', 'value' => ''],
['key' => 'smtp_pass', 'value' => ''],
['key' => 'login_form', 'value' => ''],
['key' => 'receiving_calculate_average_price', 'value' => ''],
['key' => 'payment_message', 'value' => '']
];
$this->db->table('app_config')->ignore(true)->insertBatch($image_values);
}
$this->db->table('app_config')->ignore(true)->insertBatch($image_values);
}
/**
* Revert a migration step.
*/
public function down(): void
{
// no need to remove necessary config values.
}
/**
* Revert a migration step.
*/
public function down(): void
{
// No need to remove necessary config values.
}
}

View File

@@ -6,21 +6,22 @@ use CodeIgniter\Database\Migration;
class Migration_drop_account_number_index extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('customers') . ' DROP INDEX account_number');
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('customers') . ' ADD INDEX account_number (account_number)');
}
/**
* Perform a migration step.
*/
public function up(): void
{
log_message('info', 'Dropping account number index.');
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('customers') . ' DROP INDEX account_number');
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('customers') . ' ADD INDEX account_number (account_number)');
}
/**
* Revert a migration step.
*/
public function down(): void
{
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('customers') . ' DROP INDEX account_number');
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('customers') . ' ADD UNIQUE account_number (account_number)');
}
/**
* Revert a migration step.
*/
public function down(): void
{
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('customers') . ' DROP INDEX account_number');
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('customers') . ' ADD UNIQUE account_number (account_number)');
}
}

View File

@@ -9,69 +9,68 @@ use Config\OSPOS;
class Migration_Convert_Barcode_Types extends Migration
{
private Appconfig $appconfig;
private array $config;
private Appconfig $appconfig;
private array $config;
public function __construct(?Forge $forge = null)
{
$this->appconfig = model(Appconfig::class);
$this->config = config(OSPOS::class)->settings;
public function __construct(?Forge $forge = null)
{
$this->appconfig = model(Appconfig::class);
$this->config = config(OSPOS::class)->settings;
parent::__construct($forge);
}
parent::__construct($forge);
}
/**
* Perform a migration step.
*/
public function up(): void
{
/**
* Perform a migration step.
*/
public function up(): void
{
log_message('info', 'Converting barcode types.');
$old_barcode_type = $this->config['barcode_type'];
$old_barcode_type = $this->config['barcode_type'];
switch ($old_barcode_type) {
case 'Code39':
$new_barcode_type = 'C39';
break;
case 'Ean13':
$new_barcode_type = 'EAN13';
break;
case 'Ean8':
$new_barcode_type = 'EAN8';
break;
default:
case 'Code128':
$new_barcode_type = 'C128';
break;
}
switch($old_barcode_type)
{
case 'Code39':
$new_barcode_type = 'C39';
break;
case 'Ean13':
$new_barcode_type = 'EAN13';
break;
case 'Ean8':
$new_barcode_type = 'EAN8';
break;
default:
case 'Code128':
$new_barcode_type = 'C128';
break;
}
$this->appconfig->save(['barcode_type' => $new_barcode_type]);
}
$this->appconfig->save(['barcode_type' => $new_barcode_type]);
}
/**
* Revert a migration step.
*/
public function down(): void
{
log_message('info', 'Converting barcode types.');
$new_barcode_type = $this->config['barcode_type'];
/**
* Revert a migration step.
*/
public function down(): void
{
$new_barcode_type = $this->config['barcode_type'];
switch ($new_barcode_type) {
case 'C39':
$old_barcode_type = 'Code39';
break;
case 'EAN13':
$old_barcode_type = 'Ean13';
break;
case 'EAN8':
$old_barcode_type = 'Ean8';
break;
default:
case 'C128':
$old_barcode_type = 'Code128';
break;
}
switch($new_barcode_type)
{
case 'C39':
$old_barcode_type = 'Code39';
break;
case 'EAN13':
$old_barcode_type = 'Ean13';
break;
case 'EAN8':
$old_barcode_type = 'Ean8';
break;
default:
case 'C128':
$old_barcode_type = 'Code128';
break;
}
$this->appconfig->save(['barcode_type' => $old_barcode_type]);
}
$this->appconfig->save(['barcode_type' => $old_barcode_type]);
}
}

Some files were not shown because too many files have changed in this diff Show More