Compare commits

...

252 Commits

Author SHA1 Message Date
jekkos
f754b7b8ab Apply CI4 coding standards (#3708) 2025-07-11 23:57:46 +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
BudsieBuds
beb18ff96b Random fixes (#4144)
Random fixes in time for the 3.4.0 release.
- corrects typo in the items controller
- small update to login view
- removes deprecated code from header view
- ospos license updated to end 2024
- moved gulp packages to dev dependencies
- updated gulp-zip and npm-check-updates to latest version
- updated readme for consistency
- makes ospos license in config fully readable
- fixes composer libraries license view in config
- gulp now updates composer libraries license and ospos license
- updated other license views in config
2025-01-28 23:48:45 +01:00
El_Coloso
7ad1bfa0fb Fix requisitions (#4147)
* Fix data types on null values
* Fix receiving receipt image tag
* Fix error on Receiving Model
2025-01-28 23:32:05 +01:00
El_Coloso
9cc24f0c70 Send receipt by email as PDF (#2682) 2025-01-26 22:13:27 +01:00
jekkos
b86e5ca6ef Use parse_decimal in decimal validation (#4152) 2025-01-24 00:17:57 +01:00
jekkos
4879fe2cf3 Show error when hitting enter in sales (#4155) 2025-01-24 00:17:57 +01:00
El_Coloso
a5b2b5f771 Fixes for receipt + invoice (#2682)
* Email invoice bar code
* Send invoice by email
* Remove default comment on invoice if comment was set
2025-01-24 00:17:25 +01:00
jekkos
ac90c07c90 Remove support for PHP7.4 for now 2025-01-13 01:13:28 +01:00
jekkos
c81c546286 Remove prepare_decimal and filter_var 2025-01-13 01:13:28 +01:00
Derek Christman
a87b6eebb2 Removed PSR12 reformatting 2025-01-13 01:13:28 +01:00
Derek Christman
487e7dc0bd Revert "Fixed cast to int and inadvertant cast of false to double when parsing locale values to float"
This reverts commit 3e4c987894e3790f671e49398c9db7820bc3378d.
2025-01-13 01:13:28 +01:00
Derek Christman
467144f884 Fixed cast to int and inadvertant cast of false to double when parsing locale values to float 2025-01-13 01:13:28 +01:00
jekkos
2f365dce91 Parse prices directly using numberformatter (#4107) 2025-01-13 01:13:28 +01:00
jekkos
5bee124965 Add php linter (#3708) 2025-01-10 19:15:38 +01:00
jekkos
6195368dfc Fix person suggestion (#4142) 2025-01-06 23:47:48 +01:00
jekkos
deb9d1e65d Fix item kits addition (#4142) 2025-01-06 23:37:07 +01:00
jekkos
b541d473cf Fix requisitions (#4142) 2025-01-06 22:33:32 +01:00
jekkos
ff6ec1bd4e Fix image inclusion in gulp compress (#3916) 2025-01-05 17:41:43 +01:00
khao_lek
6b48078b44 Translated using Weblate (Thai)
Currently translated at 100.0% (55 of 55 strings)

Translation: opensourcepos/receivings
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/receivings/th/
2024-12-31 22:05:15 +01:00
jekkos
3e63b99aef Add reference to unstable in INSTALL.md (#4136) 2024-12-27 00:34:21 +01:00
jekkos
0f3175bc19 Add delete unstable release after push (#4136) 2024-12-27 00:23:32 +01:00
jekkos
ebc923801b Fix gulp compress dir layout (#3916) 2024-12-26 15:58:12 +01:00
jekkos
6128924723 Use github releases for unstable (#2814) 2024-12-22 21:42:08 +01:00
jekkos
3faa48330a Fix category as dropdown save (#4134) 2024-12-22 17:12:47 +01:00
Aril Apria Susanto
86763e460c Translated using Weblate (Indonesian)
Currently translated at 100.0% (38 of 38 strings)

Translation: opensourcepos/item_kits
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/item_kits/id/
2024-12-16 12:46:53 +01:00
Aril Apria Susanto
1463151f64 Translated using Weblate (Indonesian)
Currently translated at 100.0% (222 of 222 strings)

Translation: opensourcepos/sales
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/sales/id/
2024-12-16 12:46:53 +01:00
Aril Apria Susanto
ae83b47b5b 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/
2024-12-16 12:46:52 +01:00
Aril Apria Susanto
a925cb3f22 Translated using Weblate (Indonesian)
Currently translated at 100.0% (41 of 41 strings)

Translation: opensourcepos/employees
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/employees/id/
2024-12-16 12:46:52 +01:00
Aril Apria Susanto
564df8aff0 Translated using Weblate (Indonesian)
Currently translated at 100.0% (55 of 55 strings)

Translation: opensourcepos/receivings
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/receivings/id/
2024-12-16 12:46:52 +01:00
Aril Apria Susanto
8aee7350ae Translated using Weblate (Indonesian)
Currently translated at 100.0% (79 of 79 strings)

Translation: opensourcepos/taxes
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/taxes/id/
2024-12-16 12:46:51 +01:00
Aril Apria Susanto
91f1863617 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/
2024-12-16 12:46:51 +01:00
Aril Apria Susanto
ae18737c6b Translated using Weblate (Indonesian)
Currently translated at 100.0% (68 of 68 strings)

Translation: opensourcepos/giftcards
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/giftcards/id/
2024-12-16 12:46:51 +01:00
Aril Apria Susanto
bf6ef090e7 Translated using Weblate (Indonesian)
Currently translated at 100.0% (117 of 117 strings)

Translation: opensourcepos/items
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/items/id/
2024-12-16 12:46:51 +01:00
Aril Apria Susanto
b8883954a4 Translated using Weblate (Indonesian)
Currently translated at 100.0% (21 of 21 strings)

Translation: opensourcepos/suppliers
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/suppliers/id/
2024-12-16 12:46:50 +01:00
Aril Apria Susanto
618c942529 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/
2024-12-16 12:46:50 +01:00
Munibullah Shah
16d3a8bab1 Translated using Weblate (Urdu)
Currently translated at 5.0% (1 of 20 strings)

Translation: opensourcepos/datepicker
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/datepicker/ur/
2024-12-13 20:37:41 +01:00
Munibullah Shah
8b2d0b5208 Translated using Weblate (Urdu)
Currently translated at 15.7% (35 of 222 strings)

Translation: opensourcepos/sales
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/sales/ur/
2024-12-13 20:37:41 +01:00
Munibullah Shah
e4d5ba70eb Translated using Weblate (Urdu)
Currently translated at 10.6% (5 of 47 strings)

Translation: opensourcepos/expenses
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/expenses/ur/
2024-12-13 20:37:40 +01:00
Munibullah Shah
507b2b3cf3 Translated using Weblate (Urdu)
Currently translated at 17.6% (15 of 85 strings)

Translation: opensourcepos/common
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/common/ur/
2024-12-13 20:37:40 +01:00
Munibullah Shah
a848fbe432 Translated using Weblate (Urdu)
Currently translated at 3.7% (2 of 53 strings)

Translation: opensourcepos/customers
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/customers/ur/
2024-12-13 20:37:40 +01:00
Munibullah Shah
05ec5f2e7a Translated using Weblate (Urdu)
Currently translated at 23.9% (11 of 46 strings)

Translation: opensourcepos/cashups
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/cashups/ur/
2024-12-13 20:37:40 +01:00
Munibullah Shah
4fd1c64c61 Translated using Weblate (Urdu)
Currently translated at 12.5% (1 of 8 strings)

Translation: opensourcepos/bootstrap_tables
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/bootstrap_tables/ur/
2024-12-13 20:37:39 +01:00
Munibullah Shah
55cba0c30d Translated using Weblate (Urdu)
Currently translated at 36.8% (7 of 19 strings)

Translation: opensourcepos/expenses_categories
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/expenses_categories/ur/
2024-12-13 20:37:39 +01:00
Munibullah Shah
ffd957ba2f Translated using Weblate (Urdu)
Currently translated at 100.0% (2 of 2 strings)

Translation: opensourcepos/error
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/error/ur/
2024-12-13 20:37:39 +01:00
objecttothis
aeee79c494 Translated using Weblate (Azerbaijani)
Currently translated at 99.3% (144 of 145 strings)

Translation: opensourcepos/reports
Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/reports/az/
2024-12-09 22:53:57 +01:00
odiea
4d65bd6c92 Fix sticky header issue in reports (#3854) 2024-12-05 21:08:59 +01:00
jekkos
248299521b Revert IntlFormatter refactor (#4126) 2024-12-03 00:15:06 +01:00
jekkos
cea8717378 Fix disappearing avatar (#4128) 2024-12-02 00:50:39 +01:00
jekkos
6eade2eed6 Add DigitalOcean credits (#4122) 2024-12-02 00:11:46 +01:00
jekkos
3cac58965a Remove html space in headers (#4125) 2024-11-29 00:25:33 +01:00
jekkos
255968f5ea Remove sticky headers offset (#3854) 2024-11-29 00:19:12 +01:00
jekkos
150210cee3 Add code of conduct 2024-11-15 22:53:10 +01:00
jekkos
6d106d69d2 Use npmv2 deploy (#2834) 2024-11-15 00:13:18 +01:00
jekkos
555a00d385 Apply decimal rule to receivings (#4117) 2024-11-13 23:46:12 +01:00
objecttothis
71d6502929 Use custom rule to account for all locales (#4117)
Signed-off-by: objecttothis <objecttothis@gmail.com>
2024-11-13 23:22:33 +01:00
Ludwittge
46e14a3642 Fixed translation error (#4119) 2024-11-13 23:00:29 +01:00
1863 changed files with 116367 additions and 119877 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

27
.env
View File

@@ -5,10 +5,10 @@
CI_ENVIRONMENT = production
CI_DEBUG = false
#--------------------------------------------------------------------
# APP
#--------------------------------------------------------------------
app.appTimezone = 'UTC'
#--------------------------------------------------------------------
@@ -44,10 +44,11 @@ database.tests.port = 3306
#--------------------------------------------------------------------
# EMAIL
#--------------------------------------------------------------------
email.SMTPHost = ''
email.SMTPUser = ''
email.SMTPPass = ''
email.SMTPPort =
email.SMTPPort =
email.SMTPTimeout = 5
email.SMTPCrypto = 'tls'
@@ -64,21 +65,21 @@ encryption.key = ''
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>'
#--------------------------------------------------------------------
# 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

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

@@ -0,0 +1,22 @@
name: "Delete Unstable Release"
on:
push:
branches:
- master
jobs:
delete_unstable_release:
runs-on: ubuntu-latest
steps:
- name: "Delete last unstable release"
uses: sgpublic/delete-release-action@v1.2
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
with:
release-drop: false
release-drop-tag: false
pre-release-drop: true
pre-release-keep-count: -1
pre-release-drop-tag: true

34
.github/workflows/php-linter.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: PHP Linting
on: push
jobs:
phplint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: PHP Lint 8.0
uses: dbfx/github-phplint/8.0@master
with:
folder-to-exclude: "! -path \"./vendor/*\" ! -path \"./folder/excluded/*\""
- name: PHP Lint 8.1
uses: dbfx/github-phplint/8.1@master
with:
folder-to-exclude: "! -path \"./vendor/*\" ! -path \"./folder/excluded/*\""
- name: PHP Lint 8.2
uses: dbfx/github-phplint/8.2@master
with:
folder-to-exclude: "! -path \"./vendor/*\" ! -path \"./folder/excluded/*\""
- name: PHP Lint 8.3
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/*\""

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

4
.gitignore vendored
View File

@@ -2,6 +2,10 @@
node_modules
vendor
public/resources
public/images/menubar/*
!public/images/menubar/.gitkeep
public/license/*
!public/license/.gitkeep
app/Config/email.php
npm-debug.log*

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

@@ -2,6 +2,7 @@ sudo: required
branches:
except:
- unstable
- weblate
services:
- docker
@@ -14,17 +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")
- 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
- npm ci && npm install -g gulp && npm run build
- sed -i 's/opensourcepos.tar.gz/opensourcepos.$version.tgz/g' package.json
- npm ci && npm install -g gulp && npm run build
- docker build . --target ospos -t ospos
- docker build . --target ospos_test -t ospos_test
- docker run --rm ospos_test /app/vendor/bin/phpunit --testdox
- docker build app/Database/ -t "jekkos/opensourcepos:sql-$TAG"
env:
global:
- BRANCH=$(echo ${TRAVIS_BRANCH} | sed s/feature\\///)
- TAG=${TRAVIS_TAG:-$BRANCH}
- date=`date +%Y%m%d%H%M%S` && branch=${TRAVIS_BRANCH} && rev=`git rev-parse --short=6 HEAD`
- 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"
@@ -32,28 +37,18 @@ after_success:
- mv dist/opensourcepos.tar.gz "dist/opensourcepos.$version.$rev.tgz"
- mv dist/opensourcepos.zip "dist/opensourcepos.$version.$rev.zip"
deploy:
- provider: npm
edge: true
src: dist/opensourcepos.$version.$rev.tgz
registry: https://npm.pkg.github.com
email: jeroen@steganos.dev
api_key:
secure: "DNPJOrT51wdO0BAbkX2hKowdXYh7x8d43xvAw7eVfOslyBPiv6Bb/1QdC2Bpnlqe0WiJVS5hvBTMrJ+vSDK5i/l8jA+ZoI6ms1+P1DQ6sBBMBQI2fuvRCrJj+Fp3WnaduZb/N7R+FqdKQwD/ZORyhzJ4whtHkrO8uC7cY/wlacU="
on:
all_branches: true
- provider: releases
edge: true
file: dist/opensourcepos.$version.$rev.zip
name: "OpensourcePos $version"
release_notes_file: CHANGELOG.md
prerelease: true
user: jekkos
name: "Unstable OpensourcePos"
overwrite: true
release_notes: "This is a build of the latest master which might contain bugs. Use at your own risk. Check releases section for the latest official release"
prerelease: true
tag_name: unstable
user: jekkos
api_key:
secure: "DNPJOrT51wdO0BAbkX2hKowdXYh7x8d43xvAw7eVfOslyBPiv6Bb/1QdC2Bpnlqe0WiJVS5hvBTMrJ+vSDK5i/l8jA+ZoI6ms1+P1DQ6sBBMBQI2fuvRCrJj+Fp3WnaduZb/N7R+FqdKQwD/ZORyhzJ4whtHkrO8uC7cY/wlacU="
secure: "KOukL8IFf/uL/BjMyCSKjf2vylydjcWqgEx0eMqFCg3nZ4ybMaOwPORRthIfyT72/FvGX/aoxxEn0uR/AEtb+hYQXHmNS+kZdX72JCe8LpGuZ7FJ5X+Eo9mhJcsmS+smd1sC95DySSc/GolKPo+0WtJYONY/xGCLxm+9Ay4HREg="
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,6 @@
[unreleased]: https://github.com/opensourcepos/opensourcepos/compare/3.3.9...HEAD
[unreleased]: https://github.com/opensourcepos/opensourcepos/compare/3.4.0...HEAD
[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 +33,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

98
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,98 @@
Contributor Covenant Code of Conduct
Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official email address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[INSERT CONTACT METHOD].
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
1. Correction
Community Impact: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
Consequence: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
2. Warning
Community Impact: A violation through a single incident or series of
actions.
Consequence: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
3. Temporary Ban
Community Impact: A serious violation of community standards, including
sustained inappropriate behavior.
Consequence: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
4. Permanent Ban
Community Impact: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
Consequence: A permanent ban from any sort of public interaction within the
community.
Attribution
This Code of Conduct is adapted from the Contributor Covenant,
version 2.1, available at
https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
Community Impact Guidelines were inspired by
Mozillas code of conduct enforcement ladder.
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

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.
- 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.
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.

26
LICENSE
View File

@@ -1,30 +1,30 @@
MIT License
Copyright (c) 2013-2023 jekkos
Copyright (c) 2013-2025 jekkos
Copyright (c) 2017-2025 objecttothis
Copyright (c) 2017-2025 odiea
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) 2017-2023 Steve Ireland
Copyright (c) 2017-2023 objecttothis
Copyright (c) 2017-2023 odiea
Copyright (c) 2017-2023 WebShells
Copyright (c) 2020-2021 Andriux1990
Copyright (c) 2021 BudsieBuds
Copyright (c) 2015-2022 Aamir Shahzad (aka asakpke), RoshanTech, eSite.pk
Copyright (c) 2019-2020 Andriux1990
Copyright (c) 2018-2019 Erasto Marroquin (aka Erastus)
Copyright (c) 2019 Loyd Jayme (aka loydjayme25)
Copyright (c) 2018 Erasto Marroquin (aka Erastus)
Copyright (c) 2018 Nathan Sas (aka nathanzky)
Copyright (c) 2018 Emilio Silva (aka emi-silva)
Copyright (c) 2017 Jesus Guerrero Botella (aka i92guboj)
Copyright (c) 2016-2017 Ramkrishna Mondal (aka RamkrishnaMondal)
Copyright (c) 2016-2017 Jorge Colmenarez (aka jlctmaster), frontuari.com
Copyright (c) 2016-2017 Jesus Guerrero Botella (aka i92guboj)
Copyright (c) 2017 Deep Shah (aka deepshah)
Copyright (c) 2017 Joshua Fernandez (aka joshua1234511)
Copyright (c) 2017 asadjaved63
Copyright (c) 2016-2017 Ramkrishna Mondal (aka RamkrishnaMondal)
Copyright (c) 2016-2017 Jorge Colmenarez (aka jlctmaster), frontuari.com
Copyright (c) 2016 Rinaldy@dbarber (aka rnld26)
Copyright (c) 2015-2022 Aamir Shahzad (aka asakpke), RoshanTech, eSite.pk
Copyright (c) 2015 Toni Haryanto (aka yllumi)
Copyright (c) 2012-2014 pappastech
Copyright (c) 2013 Rob Garrison
Copyright (c) 2013 Parq
Copyright (c) 2013 Ramel
Copyright (c) 2012-2014 pappastech
Copyright (c) 2012 Alain
Permission is hereby granted, free of charge, to any person obtaining a copy

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).
@@ -137,7 +137,7 @@ Any person or company found breaching the license agreement might find a bunch o
## 🙏 Credits
| <div align="center">JetBrains</div> | <div align="center">Travis CI</div> |
|--- | --- |
| <div align="center"><a href="https://www.jetbrains.com/idea/" target="_blank"><img src="https://github.com/opensourcepos/opensourcepos/assets/12870258/187f9bbe-4484-475c-9b58-5e5d5f931f09" alt="IntelliJ IDEA Logo" height="50"></a></div> | <div align="center"><a href="https://www.travis-ci.com/" target="_blank"><img src="https://github.com/opensourcepos/opensourcepos/assets/12870258/71cc2b44-83af-4510-a543-6358285f43c6" alt="Travis CI Logo" height="50"></a></div> |
| Many thanks to [JetBrains](https://www.jetbrains.com/) for providing a free license of [IntelliJ IDEA](https://www.jetbrains.com/idea/) to kindly support the development of OSPOS. | Many thanks to [Travis CI](https://www.travis-ci.com/) for providing a free continuous integration service for open source projects. |
| <div align="center">DigitalOcean</div> | <div align="center">JetBrains</div> | <div align="center">Travis CI</div> |
| --- | --- | --- |
| <div align="center"><a href="https://www.digitalocean.com?utm_medium=opensource&utm_source=opensourcepos" target="_blank"><img src="https://github.com/user-attachments/assets/fbbf7433-ed35-407d-8946-fd03d236d350" alt="DigitalOcean Logo" height="50"></a></div> | <div align="center"><a href="https://www.jetbrains.com/idea/" target="_blank"><img src="https://github.com/opensourcepos/opensourcepos/assets/12870258/187f9bbe-4484-475c-9b58-5e5d5f931f09" alt="IntelliJ IDEA Logo" height="50"></a></div> | <div align="center"><a href="https://www.travis-ci.com/" target="_blank"><img src="https://github.com/opensourcepos/opensourcepos/assets/12870258/71cc2b44-83af-4510-a543-6358285f43c6" alt="Travis CI Logo" height="50"></a></div> |
| Many thanks to [DigitalOcean](https://www.digitalocean.com) for providing the project with hosting credits. | Many thanks to [JetBrains](https://www.jetbrains.com/) for providing a free license of [IntelliJ IDEA](https://www.jetbrains.com/idea/) to kindly support the development of OSPOS. | Many thanks to [Travis CI](https://www.travis-ci.com/) for providing a free continuous integration service for open source projects. |

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

@@ -3,56 +3,45 @@
namespace Config;
use CodeIgniter\Config\BaseConfig;
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.
*/
public string $application_version = '3.4.1';
/**
* 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.
*/
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
*/
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
*/
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
*/
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.
@@ -143,63 +132,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 +251,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

@@ -33,18 +33,17 @@ defined('COMPOSER_PATH') || define('COMPOSER_PATH', ROOTPATH . 'vendor/autoload.
| Provide simple ways to work with the myriad of PHP functions that
| require information to be in seconds.
*/
defined('SECOND') || define('SECOND', 1);
defined('MINUTE') || define('MINUTE', 60);
defined('HOUR') || define('HOUR', 3600);
defined('DAY') || define('DAY', 86400);
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('SECOND') || define('SECOND', 1);
defined('MINUTE') || define('MINUTE', 60);
defined('HOUR') || define('HOUR', 3600);
defined('DAY') || define('DAY', 86400);
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_DATETIME') || define('DEFAULT_DATETIME', mktime(0, 0, 0, 1, 1, 2010));
defined('NOW') || define('NOW', time());
defined('NOW') || define('NOW', time());
/*
| --------------------------------------------------------------------------
@@ -82,66 +81,51 @@ 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.
*/
const NEW_ENTRY = -1;
const ACTIVE = 0;
const DELETED = 1;
const ACTIVE = 0;
const DELETED = 1;
/**
* Attribute Related Constants.
*/
const GROUP = 'GROUP';
const DROPDOWN = 'DROPDOWN';
const DECIMAL = 'DECIMAL';
const DATE = 'DATE';
const TEXT = 'TEXT';
const CHECKBOX = 'CHECKBOX';
const NO_DEFINITION_ID = 0;
const GROUP = 'GROUP';
const DROPDOWN = 'DROPDOWN';
const DECIMAL = 'DECIMAL';
const DATE = 'DATE';
const TEXT = 'TEXT';
const CHECKBOX = 'CHECKBOX';
const NO_DEFINITION_ID = 0;
const CATEGORY_DEFINITION_ID = -1;
const DEFINITION_TYPES = [GROUP, DROPDOWN, DECIMAL, TEXT, DATE, CHECKBOX];
const DEFINITION_TYPES = [GROUP, DROPDOWN, DECIMAL, TEXT, DATE, CHECKBOX];
/**
* Item Related Constants.
*/
const HAS_STOCK = 0;
const HAS_STOCK = 0;
const HAS_NO_STOCK = 1;
const ITEM = 0;
const ITEM_KIT = 1;
const ITEM = 0;
const ITEM_KIT = 1;
const ITEM_AMOUNT_ENTRY = 2;
const ITEM_TEMP = 3;
const NEW_ITEM = -1;
const ITEM_TEMP = 3;
const NEW_ITEM = -1;
const PRINT_ALL = 0;
const PRINT_ALL = 0;
const PRINT_PRICED = 1;
const PRINT_KIT = 2;
const PRINT_KIT = 2;
const PRINT_YES = 0;
const PRINT_NO = 1;
const PRINT_NO = 1;
const PRICE_ALL = 0;
const PRICE_KIT = 1;
const PRICE_ALL = 0;
const PRICE_KIT = 1;
const PRICE_KIT_ITEMS = 2;
const PRICE_OPTION_ALL = 0;
const PRICE_OPTION_KIT = 1;
const PRICE_OPTION_ALL = 0;
const PRICE_OPTION_KIT = 1;
const PRICE_OPTION_KIT_STOCK = 2;
const NAME_SEPARATOR = ' | ';
@@ -151,37 +135,37 @@ const NAME_SEPARATOR = ' | ';
*/
const COMPLETED = 0;
const SUSPENDED = 1;
const CANCELED = 2;
const CANCELED = 2;
const SALE_TYPE_POS = 0;
const SALE_TYPE_INVOICE = 1;
const SALE_TYPE_POS = 0;
const SALE_TYPE_INVOICE = 1;
const SALE_TYPE_WORK_ORDER = 2;
const SALE_TYPE_QUOTE = 3;
const SALE_TYPE_RETURN = 4;
const SALE_TYPE_QUOTE = 3;
const SALE_TYPE_RETURN = 4;
const PERCENT = 0;
const FIXED = 1;
const FIXED = 1;
const PRICE_MODE_STANDARD = 0;
const PRICE_MODE_KIT = 1;
const PRICE_MODE_KIT = 1;
const PAYMENT_TYPE_UNASSIGNED = '--';
const CASH_ADJUSTMENT_TRUE = 1;
const CASH_ADJUSTMENT_TRUE = 1;
const CASH_ADJUSTMENT_FALSE = 0;
const CASH_MODE_TRUE = 1;
const CASH_MODE_FALSE = 0;
const CASH_MODE_TRUE = 1;
const CASH_MODE_FALSE = 0;
/**
* Supplier Related Constants
*/
const GOODS_SUPPLIER = 0;
const COST_SUPPLIER = 1;
const COST_SUPPLIER = 1;
/**
* Locale Related Constants
*/
const MAX_PRECISION = 1e14;
const DEFAULT_PRECISION = 2;
const DEFAULT_LANGUAGE = 'english';
const MAX_PRECISION = 1e14;
const DEFAULT_PRECISION = 2;
const DEFAULT_LANGUAGE = 'english';
const DEFAULT_LANGUAGE_CODE = 'en';

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,21 @@ 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;
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');
}
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');
}
}
}

View File

@@ -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'], // TODO: Temporarily disable CSRF until we get everything sorted
'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,36 @@ 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');
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);
/**
* @return void
*/
public function update_settings(): void
{
$this->cache->delete('settings');
$this->set_settings();
}
foreach ($appconfig->get_all()->getResult() as $app_config) {
$this->settings[$app_config->key] = $app_config->value;
}
$this->cache->save('settings', encode_array($this->settings));
}
}
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

@@ -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

@@ -23,6 +23,7 @@ use HTMLPurifier_Config;
*/
class Services extends BaseService
{
private static $htmlPurifier;
/*
* public static function example($getShared = true)
* {
@@ -34,44 +35,40 @@ 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;
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

@@ -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,151 +13,124 @@ 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 mixed $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'];
$pattern = '/';
/**
* Checks to make sure dependency PHP extensions are installed
*/
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
{
$candidate = prepare_decimal($candidate);
$validation = Services::validation();
$validation->setRules([
'candidate' => 'decimal'
]);
$data = [
'candidate' => $candidate
];
if (!$validation->run($data))
{
$error = $validation->getErrors();
return false;
}
return true;
}
/**
* Validates the candidate as a decimal number. Takes the locale into account. Used in validation rule calls.
*
* @noinspection PhpUnused
*/
public function decimal_locale(string $candidate, ?string &$error = null): bool
{
return parse_decimals($candidate) !== false;
}
}

View File

@@ -3,260 +3,214 @@
namespace App\Controllers;
use App\Models\Attribute;
use Config\Services;
require_once('Secure_Controller.php');
require_once 'Secure_Controller.php';
/**
* Attributes controls the custom attributes assigned to items
**/
*/
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.
*/
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 = [];
echo json_encode(['total' => $total_rows, 'rows' => $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);
}
/**
* 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)
);
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
}
echo json_encode(['success' => $success != 0]);
}
/**
* AJAX called function which saves the attribute value sent via POST by using the model save function.
*
* @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,
);
/**
* 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)
);
echo json_encode(['success' => $success !== 0]);
}
echo json_encode(['success' => $success]);
}
/**
* AJAX called function deleting an attribute value using the model delete function.
*
* @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),
);
/**
* 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;
echo json_encode(['success' => $success]);
}
$flags = (empty($this->request->getPost('definition_flags'))) ? [] : $this->request->getPost('definition_flags', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* AJAX called function which saves the attribute definition.
*
* @noinspection PhpUnused
*/
public function postSaveDefinition(int $definition_id = NO_DEFINITION_ID): void
{
$definition_flags = 0;
foreach($flags as $flag)
{
$definition_flags |= $flag;
}
$flags = (empty($this->request->getPost('definition_flags'))) ? [] : $this->request->getPost('definition_flags', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
//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
];
foreach ($flags as $flag) {
$definition_flags |= $flag;
}
if ($this->request->getPost('definition_type') != null)
{
$definition_data['definition_type'] = DEFINITION_TYPES[$this->request->getPost('definition_type')];
}
// Save definition data
$definition_data = [
'definition_name' => $this->request->getPost('definition_name'),
'definition_unit' => $this->request->getPost('definition_unit') !== '' ? $this->request->getPost('definition_unit') : null,
'definition_flags' => $definition_flags,
'definition_fk' => $this->request->getPost('definition_group') !== '' ? $this->request->getPost('definition_group') : null,
];
$definition_name = $definition_data['definition_name'];
if ($this->request->getPost('definition_type') !== null) {
$definition_data['definition_type'] = DEFINITION_TYPES[$this->request->getPost('definition_type')];
}
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')));
$definition_name = $definition_data['definition_name'];
foreach($definition_values as $definition_value)
{
$this->attribute->saveAttributeValue($definition_value, $definition_data['definition_id']);
}
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')));
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
]);
}
}
foreach ($definition_values as $definition_value) {
$this->attribute->saveAttributeValue($definition_value, $definition_data['definition_id']);
}
/**
*
* @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([
'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,
]);
}
}
echo json_encode($suggestions);
}
/**
* @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 $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($suggestions);
}
echo json_encode($data_row);
}
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 $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;
}
echo json_encode($data_row);
}
/**
* @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;
}
private function get_attributes(int $definition_flags = 0): array
{
$definition_flag_names = [];
$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;
foreach (Attribute::get_definition_flags() as $id => $term) {
if ($id & $definition_flags) {
$definition_flag_names[$id] = lang('Attributes.' . strtolower($term) . '_visibility');
}
}
$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);
return $definition_flag_names;
}
echo view('attributes/form', $data);
}
public function getView(int $definition_id = NO_DEFINITION_ID): void
{
$info = $this->attribute->getAttributeInfo($definition_id);
/**
* 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);
}
foreach (get_object_vars($info) as $property => $value) {
$info->{$property} = $value;
}
/**
* Deletes an attribute definition
* @return void
*/
public function postDelete(): void
{
$attributes_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$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;
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')]);
}
}
$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);
}
/**
* Deletes an attribute definition
*/
public function postDelete(): void
{
$attributes_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
if ($this->attribute->deleteDefinitionList($attributes_to_delete)) {
$message = lang('Attributes.definition_successful_deleted') . ' ' . count($attributes_to_delete) . ' ' . lang('Attributes.definition_one_or_multiple');
echo json_encode(['success' => true, 'message' => $message]);
} 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

@@ -6,320 +6,256 @@ use App\Models\Cashup;
use App\Models\Expense;
use App\Models\Reports\Summary_payments;
use Config\OSPOS;
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();
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
];
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 = [];
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
}
foreach ($cash_ups->getResult() as $cash_up) {
$data_rows[] = get_cash_up_data_row($cash_up);
}
/**
* @param int $cashup_id
* @return void
*/
public function getView(int $cashup_id = NEW_ENTRY): void
{
$data = [];
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
}
$data['employees'] = [];
foreach($this->employee->get_all()->getResult() as $employee)
{
foreach(get_object_vars($employee) as $property => $value)
{
$employee->$property = $value;
}
public function getView(int $cashup_id = NEW_ENTRY): void
{
$data = [];
$data['employees'][$employee->person_id] = $employee->first_name . ' ' . $employee->last_name;
}
$data['employees'] = [];
$cash_ups_info = $this->cashup->get_info($cashup_id);
foreach ($this->employee->get_all()->getResult() as $employee) {
foreach (get_object_vars($employee) as $property => $value) {
$employee->{$property} = $value;
}
foreach(get_object_vars($cash_ups_info) as $property => $value)
{
$cash_ups_info->$property = $value;
}
$data['employees'][$employee->person_id] = $employee->first_name . ' ' . $employee->last_name;
}
// 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');
$cash_ups_info = $this->cashup->get_info($cashup_id);
// 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;
foreach (get_object_vars($cash_ups_info) as $property => $value) {
$cash_ups_info->{$property} = $value;
}
// 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'
];
}
// 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 (
(float) ($cash_ups_info->closed_amount_cash) === 0
&& (float) ($cash_ups_info->closed_amount_due) === 0
&& (float) ($cash_ups_info->closed_amount_card) === 0
&& (float) ($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');
// get all the transactions payment summaries
$reports_data = $this->summary_payments->getData($inputs);
// 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;
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'];
}
}
}
// 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',
];
}
// lookup expenses paid in cash
$filters = [
'only_cash' => true,
'only_due' => false,
'only_check' => false,
'only_credit' => false,
'only_debit' => false,
'is_deleted' => false
];
// Get all the transactions payment summaries
$reports_data = $this->summary_payments->getData($inputs);
$payments = $this->expense->get_payments_summary('', array_merge($inputs, $filters));
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($payments as $row)
{
$cash_ups_info->closed_amount_cash -= $row['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,
];
$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);
}
$payments = $this->expense->get_payments_summary('', array_merge($inputs, $filters));
$data['cash_ups_info'] = $cash_ups_info;
foreach ($payments as $row) {
$cash_ups_info->closed_amount_cash -= $row['amount'];
}
echo view("cashups/form", $data);
}
$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);
}
/**
* @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);
$data['cash_ups_info'] = $cash_ups_info;
echo json_encode($data_row);
}
echo view('cashups/form', $data);
}
/**
* @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);
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);
$close_date = $this->request->getPost('close_date');
$close_date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $close_date);
echo json_encode($data_row);
}
$open_amount_cash = prepare_decimal($this->request->getPost('open_amount_cash'));
$transfer_amount_cash = prepare_decimal($this->request->getPost('transfer_amount_cash'));
$closed_amount_cash = prepare_decimal($this->request->getPost('closed_amount_cash'));
$closed_amount_due = prepare_decimal($this->request->getPost('closed_amount_due'));
$closed_amount_card = prepare_decimal($this->request->getPost('closed_amount_card'));
$closed_amount_check = prepare_decimal($this->request->getPost('closed_amount_check'));
$closed_amount_total = prepare_decimal($this->request->getPost('closed_amount_total'));
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);
$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(filter_var($open_amount_cash, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION)),
'transfer_amount_cash' => parse_decimals(filter_var($transfer_amount_cash, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION)),
'closed_amount_cash' => parse_decimals(filter_var($closed_amount_cash, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION)),
'closed_amount_due' => parse_decimals(filter_var($closed_amount_due, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION)),
'closed_amount_card' => parse_decimals(filter_var($closed_amount_card, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION)),
'closed_amount_check' => parse_decimals(filter_var($closed_amount_check, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION)),
'closed_amount_total' => parse_decimals(filter_var($closed_amount_total, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION)),
'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
];
$close_date = $this->request->getPost('close_date');
$close_date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $close_date);
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]);
}
}
$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,
];
/**
* @return void
*/
public function postDelete(): void
{
$cash_ups_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
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->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]);
}
}
public function postDelete(): void
{
$cash_ups_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* Calculate the total for cashups. Used in app\Views\cashups\form.php
*
* @return void
* @noinspection PhpUnused
*/
public function ajax_cashup_total(): void
{
$raw_open_amount_cash = $this->request->getPost('open_amount_cash');
$raw_transfer_amount_cash = $this->request->getPost('transfer_amount_cash');
$raw_closed_amount_cash = $this->request->getPost('closed_amount_cash');
$raw_closed_amount_due = $this->request->getPost('closed_amount_due');
$raw_closed_amount_card = $this->request->getPost('closed_amount_card');
$raw_closed_amount_check = $this->request->getPost('closed_amount_check');
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]);
}
}
$open_amount_cash = parse_decimals(filter_var(prepare_decimal($raw_open_amount_cash), FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));
$transfer_amount_cash = parse_decimals(filter_var(prepare_decimal($raw_transfer_amount_cash), FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));
$closed_amount_cash = parse_decimals(filter_var(prepare_decimal($raw_closed_amount_cash), FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));
$closed_amount_due = parse_decimals(filter_var(prepare_decimal($raw_closed_amount_due), FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));
$closed_amount_card = parse_decimals(filter_var(prepare_decimal($raw_closed_amount_card), FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));
$closed_amount_check = parse_decimals(filter_var(prepare_decimal($raw_closed_amount_check), FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));
/**
* Calculate the total for cashups. Used in app\Views\cashups\form.php
*
* @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
*
* @param mixed $closed_amount_check
*/
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

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,6 @@
namespace App\Controllers;
use App\Libraries\Mailchimp_lib;
use App\Models\Customer;
use App\Models\Customer_rewards;
use App\Models\Tax_code;
@@ -14,523 +13,459 @@ 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'));
$discount = prepare_decimal($this->request->getPost('discount'));
$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 : filter_var($discount, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION),
'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 = '';
}
}
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.
*/
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);
}
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
*
* @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
*
* @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
*
* @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
*
* @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'], 'rb')) !== 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 (count($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

@@ -3,232 +3,208 @@
namespace App\Controllers;
use App\Models\Module;
use Config\Services;
/**
*
*
* @property module module
*
* @property Module module
*/
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.
*/
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 = [];
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
}
foreach ($employees->getResult() as $person) {
$data_rows[] = get_person_data_row($person);
}
/**
* 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(['total' => $total_rows, 'rows' => $data_rows]);
}
echo json_encode($suggestions);
}
/**
* AJAX called function gives search suggestions based on what is being searched for.
*/
public function getSuggest(): void
{
$search = $this->request->getGet('term');
$suggestions = $this->employee->get_search_suggestions($search, 25, true);
/**
* @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);
}
public function suggest_search(): void
{
$search = $this->request->getPost('term');
$suggestions = $this->employee->get_search_suggestions($search);
/**
* 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;
echo json_encode($suggestions);
}
$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);
/**
* Loads the employee edit form
*/
public function getView(int $employee_id = NEW_ENTRY): void
{
$person_info = $this->employee->get_info($employee_id);
$modules[] = $module;
}
$data['all_modules'] = $modules;
foreach (get_object_vars($person_info) as $property => $value) {
$person_info->{$property} = $value;
}
$data['person_info'] = $person_info;
$data['employee_id'] = $employee_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);
$modules = [];
$permissions[] = $permission;
}
$data['all_subpermissions'] = $permissions;
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);
echo view('employees/form', $data);
}
$modules[] = $module;
}
$data['all_modules'] = $modules;
/**
* 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));
$permissions = [];
// format first and last name properly
$first_name = $this->nameize($first_name);
$last_name = $this->nameize($last_name);
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);
$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)
];
$permissions[] = $permission;
}
$data['all_subpermissions'] = $permissions;
$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) : '';
echo view('employees/form', $data);
}
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;
}
}
/**
* 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));
//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]
];
}
// format first and last name properly
$first_name = $this->nameize($first_name);
$last_name = $this->nameize($last_name);
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
]);
}
}
$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),
];
/**
* This deletes employees from the employees table
*/
public function postDelete(): void
{
$employees_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$grants_array = [];
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')]);
}
}
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) : '';
/**
* 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';
}
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],
];
}
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);
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
*
* @noinspection PhpUnused
*
* @param mixed $employee_id
*/
public function getCheckUsername($employee_id): void
{
$exists = $this->employee->username_exists($employee_id, $this->request->getGet('username'));
echo ! $exists ? 'true' : 'false';
}
}

View File

@@ -5,207 +5,169 @@ namespace App\Controllers;
use App\Models\Expense;
use App\Models\Expense_category;
use Config\OSPOS;
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();
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
];
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
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'] = [];
$data['employees'][$employee->person_id] = $employee->first_name . ' ' . $employee->last_name;
}
foreach ($this->employee->get_all()->getResult() as $employee) {
foreach (get_object_vars($employee) as $property => $value) {
$employee->{$property} = $value;
}
$data['expenses_info'] = $this->expense->get_info($expense_id);
$data['employees'][$employee->person_id] = $employee->first_name . ' ' . $employee->last_name;
}
$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;
$data['expenses_info'] = $this->expense->get_info($expense_id);
$expense_id = $data['expenses_info']->expense_id;
$expense_categories = [];
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;
}
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;
$data['payments'] = [];
foreach($this->expense->get_expense_payment($expense_id)->getResult() as $payment)
{
foreach(get_object_vars($payment) as $property => $value)
{
$payment->$property = $value;
}
$expense_id = $data['expenses_info']->expense_id;
$data['payments'][] = $payment;
}
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;
}
// 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();
$data['payments'] = [];
echo view("expenses/form", $data);
}
foreach ($this->expense->get_expense_payment($expense_id)->getResult() as $payment) {
foreach (get_object_vars($payment) as $property => $value) {
$payment->{$property} = $value;
}
/**
* @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);
$data['payments'][] = $payment;
}
echo json_encode($data_row);
}
// 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();
/**
* @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);
echo view('expenses/form', $data);
}
$date_formatter = date_create_from_format($config['dateformat'] . ' ' . $config['timeformat'], $newdate);
$amount = prepare_decimal($this->request->getPost('amount'));
$tax_amount = prepare_decimal($this->request->getPost('tax_amount'));
public function getRow(int $row_id): void
{
$expense_info = $this->expense->get_info($row_id);
$data_row = get_expenses_data_row($expense_info);
$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' => filter_var($amount, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION),
'tax_amount' => filter_var($tax_amount, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION),
'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
];
echo json_encode($data_row);
}
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]);
}
}
public function postSave(int $expense_id = NEW_ENTRY): void
{
$config = config(OSPOS::class)->settings;
$newdate = $this->request->getPost('date', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* @return void
*/
public function postDelete(): void
{
$expenses_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$date_formatter = date_create_from_format($config['dateformat'] . ' ' . $config['timeformat'], $newdate);
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]);
}
}
$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]);
}
}
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]);
}
}
}

View File

@@ -3,132 +3,104 @@
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();
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 = [];
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
}
foreach ($expense_categories->getResult() as $expense_category) {
$data_rows[] = get_expense_category_data_row($expense_category);
}
/**
* @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(['total' => $total_rows, 'rows' => $data_rows]);
}
echo json_encode($data_row);
}
public function getRow(int $row_id): void
{
$data_row = get_expense_category_data_row($this->expense_category->get_info($row_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 json_encode($data_row);
}
echo view("expenses_categories/form", $data);
}
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 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)
];
echo view('expenses_categories/form', $data);
}
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
]);
}
}
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),
];
/**
* @return void
*/
public function postDelete(): void
{
$expense_category_to_delete = $this->request->getPost('ids', 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->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')]);
}
}
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')]);
}
}
}

View File

@@ -4,196 +4,158 @@ namespace App\Controllers;
use App\Models\Giftcard;
use Config\OSPOS;
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();
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 = [];
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
}
foreach ($giftcards->getResult() as $giftcard) {
$data_rows[] = get_giftcard_data_row($giftcard);
}
/**
* 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(['total' => $total_rows, 'rows' => $data_rows]);
}
echo json_encode($suggestions);
}
/**
* Gets search suggestions for giftcards. Used in app\Views\sales\register.php
*
* @noinspection PhpUnused
*/
public function getSuggest(): void
{
$search = $this->request->getGet('term');
$suggestions = $this->giftcard->get_search_suggestions($search, true);
/**
* @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);
}
public function suggest_search(): void
{
$search = $this->request->getPost('term');
$suggestions = $this->giftcard->get_search_suggestions($search);
/**
* @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($suggestions);
}
echo json_encode($data_row);
}
public function getRow(int $row_id): void
{
$data_row = get_giftcard_data_row($this->giftcard->get_info($row_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);
echo json_encode($data_row);
}
$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;
public function getView(int $giftcard_id = NEW_ENTRY): void
{
$config = config(OSPOS::class)->settings;
$giftcard_info = $this->giftcard->get_info($giftcard_id);
echo view("giftcards/form", $data);
}
$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;
/**
* @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);
$giftcard_amount = prepare_decimal($this->request->getPost('giftcard_amount'));
echo view('giftcards/form', $data);
}
if($giftcard_id == NEW_ENTRY && trim($giftcard_number) == '')
{
$giftcard_number = $this->giftcard->generate_unique_giftcard_name(filter_var($giftcard_amount, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));
}
public function postSave(int $giftcard_id = NEW_ENTRY): void
{
$giftcard_number = $this->request->getPost('giftcard_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$giftcard_data = [
'record_time' => date('Y-m-d H:i:s'),
'giftcard_number' => $giftcard_number,
'value' => filter_var($giftcard_amount, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION),
'person_id' => $this->request->getPost('person_id') == '' ? null : $this->request->getPost('person_id', FILTER_SANITIZE_NUMBER_INT)
];
if ($giftcard_id === NEW_ENTRY && trim($giftcard_number) === '') {
$giftcard_number = $this->giftcard->generate_unique_giftcard_name($giftcard_number);
}
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
]);
}
}
$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),
];
/**
* 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)]);
}
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,
]);
}
}
/**
* @return void
*/
public function postDelete(): void
{
$giftcards_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* Checks the giftcard number validity. Used in app\Views\giftcards\form.php
*
* @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)]);
}
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')]);
}
}
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,85 @@ 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');
}
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
*
* @noinspection PhpUnused
*/
public function getLogout(): RedirectResponse
{
$this->employee->logout();
/**
* 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;
return redirect()->to('login');
}
echo view('home/form_change_password', $data);
}
/**
* 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);
/**
* 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
];
foreach (get_object_vars($person_info) as $property => $value) {
$person_info->{$property} = $value;
}
$data['person_info'] = $person_info;
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
]);
}
}
echo view('home/form_change_password', $data);
}
/**
* 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,
]);
}
}
}

View File

@@ -3,319 +3,272 @@
namespace App\Controllers;
use App\Libraries\Barcode_lib;
use App\Models\Item;
use App\Models\Item_kit;
use App\Models\Item_kit_items;
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']);
$item_kit->total_cost_price += $item_info->cost_price * $item_kit_item['quantity'];
foreach (get_object_vars($item_info) as $property => $value) {
$item_info->{$property} = $value;
}
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'];
}
}
$item_kit->total_cost_price += $item_info->cost_price * $item_kit_item['quantity'];
$discount_fraction = bcdiv($item_kit->kit_discount, '100');
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'];
}
}
$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);
$discount_fraction = bcdiv($item_kit->kit_discount, '100');
return $item_kit;
}
$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 void
*/
public function getIndex(): void
{
$data['table_headers'] = get_item_kits_manage_table_headers();
return $item_kit;
}
echo view('item_kits/manage', $data);
}
public function getIndex(): void
{
$data['table_headers'] = get_item_kits_manage_table_headers();
/**
* 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);
echo view('item_kits/manage', $data);
}
$item_kits = $this->item_kit->search($search, $limit, $offset, $sort, $order);
$total_rows = $this->item_kit->get_found_rows($search);
/**
* 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);
$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);
}
$item_kits = $this->item_kit->search($search, $limit, $offset, $sort, $order);
$total_rows = $this->item_kit->get_found_rows($search);
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
}
$data_rows = [];
/**
* @return void
*/
public function suggest_search(): void
{
$search = $this->request->getPost('term');
$suggestions = $this->item_kit->get_search_suggestions($search);
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($suggestions);
}
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
}
/**
* @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));
public function suggest_search(): void
{
$search = $this->request->getPost('term');
$suggestions = $this->item_kit->get_search_suggestions($search);
echo json_encode(get_item_kit_data_row($item_kit));
}
echo json_encode($suggestions);
}
/**
* @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);
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));
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;
}
echo json_encode(get_item_kit_data_row($item_kit));
}
foreach(get_object_vars($info) as $property => $value)
{
$info->$property = $value;
}
public function getView(int $item_kit_id = NEW_ENTRY): void
{
$info = $this->item_kit->get_info($item_kit_id);
$data['item_kit_info'] = $info;
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;
}
$items = [];
foreach (get_object_vars($info) as $property => $value) {
$info->{$property} = $value;
}
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'];
$data['item_kit_info'] = $info;
$items[] = $item;
}
$items = [];
$data['item_kit_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'];
$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 : '';
$items[] = $item;
}
echo view("item_kits/form", $data);
}
$data['item_kit_items'] = $items;
/**
* @param int $item_kit_id
* @return void
*/
public function postSave(int $item_kit_id = NEW_ENTRY): void
{
$kit_discount = prepare_decimal($this->request->getPost('kit_discount'));
$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 : '';
$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' => filter_var($kit_discount,FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION),
'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')
];
echo view('item_kits/form', $data);
}
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;
}
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 : (int) ($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 : (int) ($this->request->getPost('kit_discount_type')),
'price_option' => $this->request->getPost('price_option') === null ? PRICE_ALL : (int) ($this->request->getPost('price_option')),
'print_option' => $this->request->getPost('print_option') === null ? PRINT_ALL : (int) ($this->request->getPost('print_option')),
'description' => $this->request->getPost('description'),
];
$item_kit_items_array = $this->request->getPost('item_kit_qty') === null ? null : $this->request->getPost('item_kit_qty');
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($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]"))
];
}
}
$item_kit_items_array = $this->request->getPost('item_kit_qty') === null ? null : $this->request->getPost('item_kit_qty');
if (!empty($item_kit_items))
{
$success = $this->item_kit_items->save_value($item_kit_items, $item_kit_id);
}
else
{
$success = true;
}
if ($item_kit_items_array !== null) {
$item_kit_items = [];
if($new_item)
{
echo json_encode ([
'success' => $success,
'message' => lang('Item_kits.successful_adding').' '.$item_kit_data['name'],
'id' => $item_kit_id
]);
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 : (int) ($this->request->getPost("item_kit_seq[{$item_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
]);
}
}
if (! empty($item_kit_items)) {
$success = $this->item_kit_items->save_value($item_kit_items, $item_kit_id);
} else {
$success = true;
}
/**
* @return void
*/
public function postDelete(): void
{
$item_kits_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
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,
]);
}
}
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')]);
}
}
public function postDelete(): void
{
$item_kits_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
/**
* 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';
}
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')]);
}
}
/**
* 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 = [];
/**
* Checks the validity of the item kit number. Used in app/Views/item_kits/form.php
*
* @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';
}
$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));
/**
* 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.
*
* @noinspection PhpUnused
*/
public function getGenerateBarcodes(string $item_kit_ids): void
{
$barcode_lib = new Barcode_lib();
$result = [];
$item_kid_id = 'KIT '. urldecode($item_kid_id);
$item_kit_ids = explode(':', $item_kit_ids);
$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
];
}
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));
$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;
$item_kid_id = 'KIT ' . urldecode($item_kid_id);
// display barcodes
echo view("barcodes/barcode_sheet", $data);
}
$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;
// Display barcodes
echo view('barcodes/barcode_sheet', $data);
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -10,69 +10,63 @@ use Config\OSPOS;
use Config\Services;
/**
* @property employee employee
* @property Employee employee
*/
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;
public function index(): RedirectResponse|string
{
$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('home');
}
return redirect()->to('login');
}
}
return redirect()->to('home');
}
}

View File

@@ -3,95 +3,75 @@
namespace App\Controllers;
use App\Libraries\Sms_lib;
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');
}
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);
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);
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.
*
* @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

@@ -8,27 +8,22 @@ use App\Models\Module;
* Part of the grants mechanism to restrict access to modules that the user doesn't have permission for.
* Instantiated in the views.
*
* @property module module
* @property Module 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;
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,22 @@ 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');
}
public function getIndex(): void
{
echo view('home/office');
}
/**
* @return void
*/
public function logout(): void
{
$this->employee = model(Employee::class);
public function logout(): void
{
$this->employee = model(Employee::class);
$this->employee->logout();
}
$this->employee->logout();
}
}

View File

@@ -3,69 +3,63 @@
namespace App\Controllers;
use App\Models\Person;
use Config\Services;
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);
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();
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->getPost('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#]+;/', static fn ($matches) => 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

@@ -4,8 +4,6 @@ namespace App\Controllers;
use App\Models\Employee;
use App\Models\Module;
use CodeIgniter\Model;
use CodeIgniter\Session\Session;
use Config\OSPOS;
use Config\Services;
@@ -14,138 +12,146 @@ use Config\Services;
* Controllers that are considered secure extend Secure_Controller, optionally a $module_id can
* be set to also check if a user can access a particular module in the system.
*
* @property employee employee
* @property module module
* @property array global_view_data
* @property session session
*
* @property Employee employee
* @property Module module
* @property Session session
*/
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();
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'));
$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();
}
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);
}
$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}"));
$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);
exit();
}
$this->global_view_data = [];
foreach($allowed_modules->getResult() as $module)
{
$this->global_view_data['allowed_modules'][] = $module;
}
// 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);
}
$this->global_view_data += [
'user_info' => $logged_in_employee_info,
'controller_name' => $module_id,
'config' => $config
];
view('viewData', $this->global_view_data);
}
$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);
public function sanitizeSortColumn($headers, $field, $default): string
{
return $field != null && in_array($field, array_keys(array_merge(...$headers))) ? $field : $default;
}
$this->global_view_data = [];
/**
* 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(null, FILTER_SANITIZE_FULL_SPECIAL_CHARS) as $value)
{
if (parse_decimals($value) === false)
{
echo 'false';
return;
}
}
echo 'true';
}
foreach ($allowed_modules->getResult() as $module) {
$this->global_view_data['allowed_modules'][] = $module;
}
/**
* @param $key
* @return mixed|void
*/
public function getConfig($key)
{
if (isset($config[$key]))
{
return $config[$key];
}
}
$this->global_view_data += [
'user_info' => $logged_in_employee_info,
'controller_name' => $module_id,
'config' => $config,
];
view('viewData', $this->global_view_data);
}
/**
* @return false
*/
public function getIndex() { return false; }
public function sanitizeSortColumn($headers, $field, $default): string
{
return $field !== null && in_array($field, array_keys(array_merge(...$headers)), true) ? $field : $default;
}
/**
* @return false
*/
public function getSearch() { return false; }
/**
* AJAX function used to confirm whether values sent in the request are numeric
*
* @noinspection PhpUnused
*/
public function getCheckNumeric(): void
{
foreach ($this->request->getGet() as $value) {
if (parse_decimals($value) === false) {
echo 'false';
/**
* @return false
*/
public function suggest_search() { return false; }
return;
}
}
echo 'true';
}
/**
* @param int $data_item_id
* @return false
*/
public function getView(int $data_item_id = -1) { return false; }
/**
* @param mixed $key
*
* @return mixed|void
*/
public function getConfig($key)
{
if (isset($config[$key])) {
return $config[$key];
}
}
/**
* @param int $data_item_id
* @return false
*/
public function postSave(int $data_item_id = -1) { return false; }
/**
* @return false
*/
public function getIndex()
{
return false;
}
/**
* @return false
*/
public function postDelete() { return false; }
/**
* @return false
*/
public function getSearch()
{
return false;
}
/**
* @return false
*/
public function suggest_search()
{
return false;
}
/**
* @return false
*/
public function getView(int $data_item_id = -1)
{
return false;
}
/**
* @return false
*/
public function postSave(int $data_item_id = -1)
{
return false;
}
/**
* @return false
*/
public function postDelete()
{
return false;
}
}

View File

@@ -3,197 +3,172 @@
namespace App\Controllers;
use App\Models\Supplier;
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();
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 mixed $row_id
*/
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.
*/
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);
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
*/
public function getView(int $supplier_id = NEW_ENTRY): void
{
$info = $this->supplier->get_info($supplier_id);
echo view("suppliers/form", $data);
}
foreach (get_object_vars($info) as $property => $value) {
$info->{$property} = $value;
}
$data['person_info'] = $info;
$data['categories'] = $this->supplier->get_categories();
/**
* 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));
echo view('suppliers/form', $data);
}
// format first and last name properly
$first_name = $this->nameize($first_name);
$last_name = $this->nameize($last_name);
/**
* Inserts/updates a supplier
*/
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));
$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)
];
// Format first and last name properly
$first_name = $this->nameize($first_name);
$last_name = $this->nameize($last_name);
$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)
];
$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),
];
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
]);
}
}
$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),
];
/**
* 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->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->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
*/
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

@@ -3,139 +3,108 @@
namespace App\Controllers;
use App\Models\Tax_category;
use Config\Services;
/**
* @property tax_category tax_category
* @property Tax_category tax_category
*/
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();
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.
*/
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 = [];
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
}
foreach ($tax_categories->getResult() as $tax_category) {
$data_rows[] = get_tax_categories_data_row($tax_category);
}
/**
* @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(['total' => $total_rows, 'rows' => $data_rows]);
}
echo json_encode($data_row);
}
public function getRow($row_id): void
{
$data_row = get_tax_categories_data_row($this->tax_category->get_info($row_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 json_encode($data_row);
}
echo view("taxes/tax_category_form", $data);
}
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);
}
/**
* @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)
];
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);
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

@@ -3,150 +3,114 @@
namespace App\Controllers;
use App\Models\Tax_code;
use Config\Services;
/**
* @property tax_code tax_code
* @property Tax_code tax_code
*/
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');
}
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());
}
public function get_data(): array
{
$data['table_headers'] = get_tax_code_table_headers();
/**
* @return array
*/
public function get_data(): array
{
return $data;
}
$data['table_headers'] = get_tax_code_table_headers();
return $data;
}
/**
* Returns tax_category 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->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]);
}
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);
}
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);
}
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,
]);
}
}
/**
* @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)
];
public function postDelete(): void
{
$tax_codes_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
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);
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

@@ -3,140 +3,108 @@
namespace App\Controllers;
use App\Models\Tax_jurisdiction;
use Config\Services;
/**
* @property tax_jurisdiction tax_jurisdiction
* @property Tax_jurisdiction tax_jurisdiction
*/
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');
}
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.
*/
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 = [];
$data_rows = [];
foreach($tax_jurisdictions->getResult() as $tax_jurisdiction)
{
$data_rows[] = get_tax_jurisdictions_data_row($tax_jurisdiction);
}
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));
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);
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);
}
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
]);
}
}
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,24 @@ 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

@@ -8,430 +8,315 @@ use CodeIgniter\Database\Migration;
use CodeIgniter\Database\ResultInterface;
/**
* @property tax_lib tax_lib
* @property appconfig appconfig
* @property Appconfig appconfig
* @property Tax_lib tax_lib
*/
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';
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
private Appconfig $appconfig;
/**
* 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");
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
if($number_of_unmigrated > 0)
{
$unmigrated_invoices = $this->get_unmigrated($number_of_unmigrated)->getResultArray();
/**
* 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}");
foreach($unmigrated_invoices as $key => $unmigrated_invoice)
{
$this->upgrade_tax_history_for_sale($unmigrated_invoice['sale_id']);
}
}
if ($number_of_unmigrated > 0) {
$unmigrated_invoices = $this->get_unmigrated($number_of_unmigrated)->getResultArray();
error_log('Migrating sales tax history. The number of sales that will be migrated is finished.');
}
foreach ($unmigrated_invoices as $key => $unmigrated_invoice) {
$this->upgrade_tax_history_for_sale($unmigrated_invoice['sale_id']);
}
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
error_log('Migrating sales tax history. The number of sales that will be migrated is finished.');
}
/**
* @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;
/**
* Revert a migration step.
*/
public function down(): void
{
}
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;
}
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;
$sales_taxes = [];
$tax_group_sequence = 0;
$items = $this->get_sale_items_for_migration($sale_id)->getResultArray();
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;
}
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);
$sales_taxes = [];
$tax_group_sequence = 0;
$items = $this->get_sale_items_for_migration($sale_id)->getResultArray();
$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);
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);
$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);
}
$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->round_sales_taxes($sales_taxes);
$this->save_sales_tax($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);
}
/**
* @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);
$this->round_sales_taxes($sales_taxes);
$this->save_sales_tax($sales_taxes);
}
return $builder->get();
}
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 $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();
}
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 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 $builder->get();
}
if(!$result)
{
error_log('Database error in 20170502221506_sales_tax_data.php related to sales_taxes or sales_items_taxes.');
return 0;
}
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 $result[0]['COUNT(*)'] ?: 0;
}
if (! $result) {
error_log('Database error in 20170502221506_sales_tax_data.php related to sales_taxes or sales_items_taxes.');
/**
* @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]);
}
return 0;
}
/**
* @param array $sales_taxes
* @return void
*/
private function save_sales_tax(array &$sales_taxes): void
{
$builder = $this->db->Table('sales_taxes');
return $result[0]['COUNT(*)'] ?: 0;
}
foreach($sales_taxes as $line => $sales_tax)
{
$builder->insert($sales_tax);
}
}
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 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);
private function save_sales_tax(array &$sales_taxes): void
{
$builder = $this->db->Table('sales_taxes');
if($include_discount)
{
$discount_amount = $this->get_item_discount($quantity, $price, $discount_percentage);
return bcsub($total, $discount_amount);
}
foreach ($sales_taxes as $line => $sales_tax) {
$builder->insert($sales_tax);
}
}
return $total;
}
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
* @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);
if ($include_discount) {
$discount_amount = $this->get_item_discount($quantity, $price, $discount_percentage);
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 bcsub($total, $discount_amount);
}
/**
* @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;
return $total;
}
$price = $this->get_item_total($quantity, $price, $discount_percentage, true);
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);
if($tax_included)
{
$tax_fraction = bcadd('100', $tax_percentage);
$tax_fraction = bcdiv($tax_fraction, '100');
$price_tax_excl = bcdiv($price, $tax_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 bcsub($price, $price_tax_excl);
}
$tax_fraction = bcdiv($tax_percentage, '100');
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;
return bcmul($price, $tax_fraction);
}
$price = $this->get_item_total($quantity, $price, $discount_percentage, true);
/**
* @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);
if ($tax_included) {
$tax_fraction = bcadd('100', $tax_percentage);
$tax_fraction = bcdiv($tax_fraction, '100');
$price_tax_excl = bcdiv($price, $tax_fraction);
return $this->round_number($rounding_mode, $tax_amount, $decimals);
}
return bcsub($price, $price_tax_excl);
}
$tax_fraction = bcdiv($tax_percentage, '100');
/**
* @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 bcmul($price, $tax_fraction);
}
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.
}
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 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);
}
}
return $this->round_number($rounding_mode, $tax_amount, $decimals);
}
/**
* @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.
public function round_number(int $rounding_mode, string $amount, int $decimals): float
{
if ($rounding_mode === Migration_Sales_Tax_Data::ROUND_UP) {
$fig = 10 ** $decimals;
$rounded_total = (ceil($fig * $amount) + ceil($fig * $amount - ceil($fig * $amount))) / $fig;
} elseif ($rounding_mode === Migration_Sales_Tax_Data::ROUND_DOWN) {
$fig = 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 preg_replace('/[^A-Za-z0-9\-]/', '', $string); // Removes special chars.
}
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
* @return void
*/
public function apply_invoice_taxing(array &$sales_taxes): void
{
if(!empty($sales_taxes)) //TODO: Duplicated code
{
$sort = [];
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);
}
}
foreach($sales_taxes as $key => $value)
{
$sort['print_sequence'][$key] = $value['print_sequence'];
}
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.
array_multisort($sort['print_sequence'], SORT_ASC, $sales_taxes);
}
return preg_replace('/[^A-Za-z0-9\-]/', '', $string); // Removes special chars.
}
$decimals = totals_decimals();
public function apply_invoice_taxing(array &$sales_taxes): void
{
if (! empty($sales_taxes)) { // TODO: Duplicated code
$sort = [];
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 $key => $value) {
$sort['print_sequence'][$key] = $value['print_sequence'];
}
/**
* @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);
}
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) {
$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);
}
}
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;
}
public function round_sales_taxes(array &$sales_taxes): void
{
if (! empty($sales_taxes)) {
$sort = [];
$sales_taxes[$row_number]['sale_tax_amount'] = $rounded_sale_tax_amount;
}
}
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();
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;
}
$sales_taxes[$row_number]['sale_tax_amount'] = $rounded_sale_tax_amount;
}
}
}

View File

@@ -6,20 +6,19 @@ 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,19 @@ 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,19 @@ 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,19 @@ 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,146 @@ 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');
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();
error_log('Migrating tax configuration completed');
}
error_log('Migrating tax configuration completed');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* @return int
*/
private function get_count_of_tax_code_entries(): int
{
$builder = $this->db->table('tax_codes_backup');
$builder->select('COUNT(*) as count');
private function get_count_of_tax_code_entries(): int
{
$builder = $this->db->table('tax_codes_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');
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_rate_entries(): int
{
$builder = $this->db->table('tax_code_rates_backup');
$builder->select('COUNT(*) as count');
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;
}
/**
* 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 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.
*/
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'));
}
/**
* 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 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
*/
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->db->query('ALTER TABLE ' . $this->db->prefixTable('customers') . ' DROP COLUMN sales_tax_code');
}
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('customers') . ' DROP COLUMN sales_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');
}
/**
* 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');
}
/**
* @return void
*/
private function migrate_tax_rates(): void
{
// create a dummy jurisdiction record and retrieve the jurisdiction rate id
private function migrate_tax_rates(): void
{
// Create a dummy jurisdiction record and retrieve the jurisdiction rate 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')");
$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')");
$jurisdiction_id = $this->db->query('SELECT jurisdiction_id FROM ' . $this->db->prefixTable('tax_jurisdictions') . " WHERE jurisdiction_name = 'Jurisdiction1'")->getRow()->jurisdiction_id;
$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');
}
// 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'));
}
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,23 @@ 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');
/**
* 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('Fix definition of Supplier.Tax Id');
error_log('Definition of Supplier.Tax Id corrected');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
error_log('Definition of Supplier.Tax Id corrected');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
}

View File

@@ -6,20 +6,19 @@ 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,19 @@ 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,19 @@ 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,19 @@ 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,113 @@ 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,19 @@ 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,19 @@ 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,29 @@ 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
{
$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'];
$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();
}
$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();
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
}

View File

@@ -6,20 +6,20 @@ 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
{
$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,19 @@ 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,19 @@ 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

@@ -2,412 +2,299 @@
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
use App\Libraries\Tax_lib;
use App\Models\Appconfig;
use CodeIgniter\Database\Migration;
use CodeIgniter\Database\ResultInterface;
/**
*
*
* @property appconfig appconfig
* @property tax_lib tax_lib
* @property Appconfig appconfig
* @property Tax_lib tax_lib
*/
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
public function __construct()
{
parent::__construct();
private Appconfig $appconfig;
$this->appconfig = model(Appconfig::class);
}
public function __construct()
{
parent::__construct();
/**
* Perform a migration step.
*/
public function up(): void
{
$tax_included = ($this->appconfig->get_value('tax_included', Migration_TaxAmount::YES) == Migration_TaxAmount::YES);
$this->appconfig = model(Appconfig::class);
}
if($tax_included)
{
$tax_decimals = $this->appconfig->get_value('tax_decimals', 2);
$number_of_unmigrated = $this->get_count_of_unmigrated();
/**
* Perform a migration step.
*/
public function up(): void
{
$tax_included = ($this->appconfig->get_value('tax_included', Migration_TaxAmount::YES) === Migration_TaxAmount::YES);
error_log('Migrating sales tax fixing. The number of sales that will be migrated is ' . $number_of_unmigrated);
if ($tax_included) {
$tax_decimals = $this->appconfig->get_value('tax_decimals', 2);
$number_of_unmigrated = $this->get_count_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'));
error_log('Migrating sales tax fixing. The number of sales that will be migrated is ' . $number_of_unmigrated);
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'));
}
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'));
error_log('Migrating sales tax fixing. The number of sales that will be migrated is finished.');
}
}
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'));
}
/**
* Revert a migration step.
*/
public function down(): void
{
error_log('Migrating sales tax fixing. The number of sales that will be migrated is finished.');
}
}
}
/**
* 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();
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++;
}
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);
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();
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) {
error_log('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);
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]);
}
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');
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);
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);
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);
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);
}
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 = 10 ** $decimals;
$rounded_total = (ceil($fig * $amount) + ceil($fig * $amount - ceil($fig * $amount))) / $fig;
} elseif ($rounding_mode === Migration_TaxAmount::ROUND_DOWN) { // TODO: === ?
$fig = 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);
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.
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);
}
public function apply_invoice_taxing(array &$sales_taxes): void
{
if (! empty($sales_taxes)) { // TODO: Duplicated code
$sort = [];
$decimals = totals_decimals();
foreach ($sales_taxes as $k => $v) {
$sort['print_sequence'][$k] = $v['print_sequence'];
}
array_multisort($sort['print_sequence'], SORT_ASC, $sales_taxes);
}
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);
}
}
$decimals = totals_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 $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 $k=>$v)
{
$sort['print_sequence'][$k] = $v['print_sequence'];
}
public function round_sales_taxes(array &$sales_taxes): void
{
if (! empty($sales_taxes)) {
$sort = [];
array_multisort($sort['print_sequence'], SORT_ASC, $sales_taxes);
}
foreach ($sales_taxes as $k => $v) {
$sort['print_sequence'][$k] = $v['print_sequence'];
}
$decimals = totals_decimals();
array_multisort($sort['print_sequence'], SORT_ASC, $sales_taxes);
}
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;
$decimals = totals_decimals();
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;
}
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;
$sales_taxes[$row_number]['sale_tax_amount'] = $rounded_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;
}
$sales_taxes[$row_number]['sale_tax_amount'] = $rounded_sale_tax_amount;
}
}
}

View File

@@ -6,19 +6,19 @@ 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
{
$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,29 @@ 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
{
$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,23 @@ 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
{
error_log('Migrating modify_attr_links_constraint');
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');
}
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,19 @@ 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
{
$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,23 @@ 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
{
error_log('Migrating add_item_kit_number');
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');
}
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,23 @@ 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
{
error_log('Migrating modify_session_datatype');
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');
}
error_log('Migrating modify_session_datatype');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
}

View File

@@ -2,159 +2,155 @@
namespace App\Database\Migrations;
use App\Models\Attribute;
use CodeIgniter\Database\Migration;
use CodeIgniter\Database\ResultInterface;
use App\Models\Attribute;
use Config\Database;
use Config\OSPOS;
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
{
error_log('Migrating database_optimizations');
$attribute = model(Attribute::class);
$attribute = model(Attribute::class);
$attribute->delete_orphaned_values();
$attribute->delete_orphaned_values();
$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;
$attribute->saveAttributeValue($value, $attribute_link['definition_id'], $attribute_link['item_id'], false, $attribute_link['definition_type']);
}
}
}
$this->db->transComplete();
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;
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.4.0_database_optimizations.sql');
error_log('Migrating database_optimizations completed');
}
default:
$value = $attribute_value['attribute_value'];
break;
}
/**
* 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();
$attribute->saveAttributeValue($value, $attribute_link['definition_id'], $attribute_link['item_id'], false, $attribute_link['definition_type']);
}
}
}
$this->db->transComplete();
$column = 'attribute_' . strtolower($attribute_type);
helper('migration');
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.4.0_database_optimizations.sql');
error_log('Migrating database_optimizations completed');
}
$builder = $this->db->table('attribute_values');
$builder->select("$column");
$builder->groupBy($column);
$builder->having("COUNT($column) > 1");
$duplicated_values = $builder->get();
/**
* Given the type of attribute, deletes any duplicates it finds in the attribute_values table and reassigns those
*
* @param mixed $attribute_type
*/
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();
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();
$column = 'attribute_' . strtolower($attribute_type);
$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("{$column}");
$builder->groupBy($column);
$builder->having("COUNT({$column}) > 1");
$duplicated_values = $builder->get();
$this->reassign_duplicate_attribute_values($attribute_ids_to_fix, $duplicated_value);
}
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();
$this->db->transComplete();
}
$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();
/**
* 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'];
$this->reassign_duplicate_attribute_values($attribute_ids_to_fix, $duplicated_value);
}
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]);
$this->db->transComplete();
}
//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']]);
}
}
}
/**
* 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'];
/**
* Revert a migration step.
*/
public function down(): void
{
}
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']]);
}
}
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
}

View File

@@ -2,65 +2,65 @@
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
use App\Models\Attribute;
use CodeIgniter\Database\Migration;
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
{
error_log('Migrating remove_duplicate_links');
$this->migrate_duplicate_attribute_links();
$this->migrate_duplicate_attribute_links();
error_log('Migrating remove_duplicate_links completed');
}
error_log('Migrating remove_duplicate_links completed');
}
/**
* 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,22 @@ 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
{
error_log('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');
}
error_log('Migrating expense categories module completed');
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
/**
* Revert a migration step.
*/
public function down(): void
{
}
}

View File

@@ -5,161 +5,158 @@ namespace App\Database\Migrations;
use App\Models\Appconfig;
use CodeIgniter\Database\Forge;
use CodeIgniter\Database\Migration;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\Exceptions\RedirectException;
use CodeIgniter\HTTP\RedirectResponse;
use Config\Encryption;
use Config\Services;
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
{
error_log('Migrating database to CodeIgniter4 formats');
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();
error_log('Migrating to CodeIgniter4 formats completed');
}
error_log('Migrating to CodeIgniter4 formats completed');
}
/**
* Revert a migration step.
*/
public function down(): void
{
/**
* Revert a migration step.
*/
public function down(): void
{
}
}
/**
* @return RedirectResponse|void
*
* @throws ReflectionException
*/
private function convert_ci3_encrypted_data()
{
$appconfig = model(Appconfig::class);
/**
* @return RedirectResponse|void
* @throws ReflectionException
*/
private function convert_ci3_encrypted_data()
{
$appconfig = model(Appconfig::class);
$ci3_encrypted_data = [
'clcdesq_api_key' => '',
'clcdesq_api_url' => '',
'mailchimp_api_key' => '',
'mailchimp_list_id' => '',
'smtp_pass' => '',
];
$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);
}
foreach($ci3_encrypted_data as $key => $value)
{
$ci3_encrypted_data[$key] = $appconfig->get_value($key);
}
$decrypted_data = $this->decrypt_ci3_data($ci3_encrypted_data);
$decrypted_data = $this->decrypt_ci3_data($ci3_encrypted_data);
check_encryption();
check_encryption();
try {
$ci4_encrypted_data = $this->encrypt_data($decrypted_data);
try
{
$ci4_encrypted_data = $this->encrypt_data($decrypted_data);
$success = empty(array_diff_assoc($decrypted_data, $this->decrypt_data($ci4_encrypted_data)));
if (! $success) {
abort_encryption_conversion();
remove_backup();
$success = empty(array_diff_assoc($decrypted_data, $this->decrypt_data($ci4_encrypted_data)));
if(!$success)
{
abort_encryption_conversion();
remove_backup();
throw new RedirectException('login');
}
throw new RedirectException('login');
}
$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.
}
}
$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.
}
}
/**
* 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';
/**
* 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';
$encrypter = Services::encrypter($config);
$encrypter = Services::encrypter($config);
$decrypted_data = [];
foreach($encrypted_data as $key => $value)
{
$decrypted_data[$key] = !empty($value) ? $encrypter->decrypt($value): '';
}
$decrypted_data = [];
return $decrypted_data;
}
foreach ($encrypted_data as $key => $value) {
$decrypted_data[$key] = ! empty($value) ? $encrypter->decrypt($value) : '';
}
/**
* 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();
return $decrypted_data;
}
$encrypted_data = [];
foreach($plain_data as $key => $value)
{
$encrypted_data[$key] = !empty($value) ? $encrypter->encrypt($value) : '';
}
/**
* 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();
return $encrypted_data;
}
$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();
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;
}
return $decrypted_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;
}
}

View File

@@ -1,27 +1,26 @@
<?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
{
$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');
}
}

View File

@@ -6,30 +6,30 @@ 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
{
$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,21 @@ 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
{
$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,72 @@ 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
{
$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;
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;
}
case 'Ean13':
$new_barcode_type = 'EAN13';
break;
$this->appconfig->save(['barcode_type' => $new_barcode_type]);
}
case 'Ean8':
$new_barcode_type = 'EAN8';
break;
/**
* Revert a migration step.
*/
public function down(): void
{
$new_barcode_type = $this->config['barcode_type'];
default:
case 'Code128':
$new_barcode_type = 'C128';
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' => $new_barcode_type]);
}
$this->appconfig->save(['barcode_type' => $old_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;
}
$this->appconfig->save(['barcode_type' => $old_barcode_type]);
}
}

View File

@@ -5,82 +5,61 @@ namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
use Config\Database;
class Migration_fix_keys_for_db_upgrade extends Migration {
/**
* Perform a migration step.
*/
public function up(): void
{
$this->db->query("ALTER TABLE `ospos_tax_codes` MODIFY `deleted` tinyint(1) DEFAULT 0 NOT NULL;");
class Migration_fix_keys_for_db_upgrade extends Migration
{
/**
* Perform a migration step.
*/
public function up(): void
{
helper('migration');
if (!$this->index_exists('ospos_customers', 'company_name'))
{
$this->db->query("ALTER TABLE `ospos_customers` ADD INDEX(`company_name`)");
}
$forge = Database::forge();
$fields = [
'deleted' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
'null' => false,
],
];
$forge->modifyColumn('tax_codes', $fields);
$checkSql = "SELECT CONSTRAINT_NAME FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = DATABASE() AND TABLE_NAME = '" . $this->db->prefixTable('sales_items_taxes') . "' AND CONSTRAINT_NAME = 'ospos_sales_items_taxes_ibfk_1'";
$foreignKeyExists = $this->db->query($checkSql)->getRow();
if (! indexExists('customers', 'company_name')) {
$forge->addKey('company_name', false, false, 'company_name');
$forge->processIndexes('customers');
}
if ($foreignKeyExists)
{
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('sales_items_taxes') . ' DROP FOREIGN KEY ospos_sales_items_taxes_ibfk_1');
}
$checkSql = "SELECT CONSTRAINT_NAME FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = DATABASE() AND TABLE_NAME = '" . $this->db->prefixTable('sales_items_taxes') . "' AND CONSTRAINT_NAME = 'ospos_sales_items_taxes_ibfk_1'";
$foreignKeyExists = $this->db->query($checkSql)->getRow();
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('sales_items_taxes')
. ' ADD CONSTRAINT ospos_sales_items_taxes_ibfk_1 FOREIGN KEY (sale_id, item_id, line) '
. ' REFERENCES ' . $this->db->prefixTable('sales_items') . ' (sale_id, item_id, line)');
if ($foreignKeyExists) {
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('sales_items_taxes') . ' DROP FOREIGN KEY ospos_sales_items_taxes_ibfk_1');
}
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('sales_items_taxes')
. ' ADD CONSTRAINT ospos_sales_items_taxes_ibfk_1 FOREIGN KEY (sale_id, item_id, line) '
. ' REFERENCES ' . $this->db->prefixTable('sales_items') . ' (sale_id, item_id, line)');
$this->create_primary_key('customers', 'person_id');
$this->create_primary_key('employees', 'person_id');
$this->create_primary_key('suppliers', 'person_id');
}
createPrimaryKey('customers', 'person_id');
createPrimaryKey('employees', 'person_id');
createPrimaryKey('suppliers', 'person_id');
}
/**
* Revert a migration step.
*/
public function down(): void
{
$checkSql = "SELECT CONSTRAINT_NAME FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = DATABASE() AND TABLE_NAME = '" . $this->db->prefixTable('sales_items_taxes') . "' AND CONSTRAINT_NAME = 'ospos_sales_items_taxes_ibfk_1'";
$foreignKeyExists = $this->db->query($checkSql)->getRow();
/**
* Revert a migration step.
*/
public function down(): void
{
$checkSql = "SELECT CONSTRAINT_NAME FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = DATABASE() AND TABLE_NAME = '" . $this->db->prefixTable('sales_items_taxes') . "' AND CONSTRAINT_NAME = 'ospos_sales_items_taxes_ibfk_1'";
$foreignKeyExists = $this->db->query($checkSql)->getRow();
if ($foreignKeyExists)
{
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('sales_items_taxes') . ' DROP CONSTRAINT ospos_sales_items_taxes_ibfk_1');
}
if ($foreignKeyExists) {
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('sales_items_taxes') . ' DROP CONSTRAINT ospos_sales_items_taxes_ibfk_1');
}
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('sales_items_taxes')
. ' ADD CONSTRAINT ospos_sales_items_taxes_ibfk_1 FOREIGN KEY (sale_id) '
. ' REFERENCES ' . $this->db->prefixTable('sales_items') . ' (sale_id)');
}
private function create_primary_key(string $table, string $index): void
{
$result = $this->db->query('SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name= \'' . $this->db->getPrefix() . "$table' AND column_key = '$index'");
if ( ! $result->getRowArray())
{
$this->delete_index($table, $index);
$forge = Database::forge();
$forge->addPrimaryKey($table, '');
}
}
private function index_exists(string $table, string $index): bool
{
$result = $this->db->query('SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = \'' . $this->db->getPrefix() . "$table' AND index_name = '$index'");
$row_array = $result->getRowArray();
return $row_array && $row_array['COUNT(*)'] > 0;
}
private function delete_index(string $table, string $index): void
{
if ($this->index_exists($table, $index))
{
$forge = Database::forge();
$forge->dropKey($table, $index, FALSE);
}
}
$this->db->query('ALTER TABLE ' . $this->db->prefixTable('sales_items_taxes')
. ' ADD CONSTRAINT ospos_sales_items_taxes_ibfk_1 FOREIGN KEY (sale_id) '
. ' REFERENCES ' . $this->db->prefixTable('sales_items') . ' (sale_id)');
}
}

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