diff --git a/.env.dist b/.env.dist index f170c3c9..30f9a925 100644 --- a/.env.dist +++ b/.env.dist @@ -15,7 +15,7 @@ APP_SECRET=b344cd6cd151ae1d61403ed55806c5ce # Configure your db driver and server_version in config/packages/doctrine.yaml DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name ###< doctrine/doctrine-bundle ### -GMAPS_API_KEY=insertgmapsapikeyhere +GMAPS_API_KEY=insert_gmapsapikey_here # rising tide sms gateway RT_USER=rt_user @@ -28,6 +28,8 @@ RT_SHORTCODE=1234 MQTT_IP_ADDRESS=localhost MQTT_PORT=8883 MQTT_CERT=/location/of/cert/file.crt +MQTT_WS_HOST=insert_ip_here +MQTT_WS_PORT=8083 # redis client REDIS_CLIENT_SCHEME=tcp @@ -36,17 +38,22 @@ REDIS_CLIENT_PORT=6379 REDIS_CLIENT_PASSWORD=foobared # privacy policy ids -POLICY_PROMO=insertpromopolicyidhere -POLICY_THIRD_PARTY=insertthirdpartypolicyidhere -POLICY_MOBILE=insertmobilepolicyidhere +POLICY_PROMO=insert_promopolicyid_here +POLICY_THIRD_PARTY=insert_thirdpartypolicyid_here +POLICY_MOBILE=insert_mobilepolicyid_here # OTP -OTP_MODE=settotestorrandom +OTP_MODE=set_to_test_or_random # geofence -GEOFENCE_ENABLE=settotrueorfalse +GEOFENCE_ENABLE=set_to_true_or_false # unknown manufacturer and vehicle ids -CVU_MFG_ID=insertmfgidforunknownvehicles -CVU_BRAND_ID=insertbrandidforunknownvehicles +CVU_MFG_ID=insert_mfgid_for_unknown_vehicles +CVU_BRAND_ID=insert_brandid_for_unknown_vehicles +# country code prefix +COUNTRY_CODE=+insert_country_code_here + +# dashboard +DASHBOARD_ENABLE=set_to_true_or_false diff --git a/.gitignore b/.gitignore index 90038562..bd8a680c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ /sql/ /pem/ /migration/ +/kml/ ###< symfony/framework-bundle ### *.swp diff --git a/composer.json b/composer.json index e118abfc..7219d070 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ "predis/predis": "^1.1", "sensio/framework-extra-bundle": "^5.1", "setasign/fpdf": "^1.8", + "symfony/asset": "^4.0", "symfony/console": "^4.0", "symfony/debug": "^4.0", "symfony/filesystem": "^4.0", diff --git a/composer.lock b/composer.lock index b3630199..08ee90e0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "4873ae3fd18db755bc9bf395bbbfb141", + "content-hash": "b101ecfbc1f6f2270f0e8ad326035b7e", "packages": [ { "name": "catalyst/auth-bundle", @@ -12,12 +12,12 @@ "source": { "type": "git", "url": "https://gitlab.com/jankstudio-catalyst/auth-bundle.git", - "reference": "ed143d15debfae3b909c46e40c080201e4aa0fda" + "reference": "803e38f58907513c8df30c31d51303b68c645a17" }, "dist": { "type": "zip", - "url": "https://gitlab.com/api/v4/projects/jankstudio-catalyst%2Fauth-bundle/repository/archive.zip?sha=ed143d15debfae3b909c46e40c080201e4aa0fda", - "reference": "ed143d15debfae3b909c46e40c080201e4aa0fda", + "url": "https://gitlab.com/api/v4/projects/jankstudio-catalyst%2Fauth-bundle/repository/archive.zip?sha=803e38f58907513c8df30c31d51303b68c645a17", + "reference": "803e38f58907513c8df30c31d51303b68c645a17", "shasum": "" }, "require": { @@ -41,7 +41,7 @@ "email": "kc@jankstudio.com" } ], - "time": "2019-06-14T17:44:18+00:00" + "time": "2019-07-01T09:45:41+00:00" }, { "name": "catalyst/menu-bundle", @@ -139,26 +139,26 @@ }, { "name": "creof/geo-parser", - "version": "2.1.0", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/creof/geo-parser.git", - "reference": "14fbd732207bc264cfeff836230ef6022bb751b6" + "reference": "b55553a54c7775576c9ee629847973d0b6d7cbed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/creof/geo-parser/zipball/14fbd732207bc264cfeff836230ef6022bb751b6", - "reference": "14fbd732207bc264cfeff836230ef6022bb751b6", + "url": "https://api.github.com/repos/creof/geo-parser/zipball/b55553a54c7775576c9ee629847973d0b6d7cbed", + "reference": "b55553a54c7775576c9ee629847973d0b6d7cbed", "shasum": "" }, "require": { "doctrine/lexer": ">=1.0", "ext-spl": "*", - "php": ">=5.3.3" + "php": ">=7.1" }, "require-dev": { "codeclimate/php-test-reporter": "dev-master", - "phpunit/phpunit": ">=4.8" + "phpunit/phpunit": "~5.0" }, "type": "library", "autoload": { @@ -188,7 +188,7 @@ "string", "text" ], - "time": "2016-05-03T18:19:41+00:00" + "time": "2019-08-07T17:45:01+00:00" }, { "name": "creof/wkb-parser", @@ -297,22 +297,22 @@ }, { "name": "data-dog/audit-bundle", - "version": "v0.1.13", + "version": "v0.1.18", "source": { "type": "git", "url": "https://github.com/DATA-DOG/DataDogAuditBundle.git", - "reference": "aacbc16e037650fa48e670fe1cc6c662a1820094" + "reference": "9012a272f1aebd590ab1e324d951655cb48b097c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DATA-DOG/DataDogAuditBundle/zipball/aacbc16e037650fa48e670fe1cc6c662a1820094", - "reference": "aacbc16e037650fa48e670fe1cc6c662a1820094", + "url": "https://api.github.com/repos/DATA-DOG/DataDogAuditBundle/zipball/9012a272f1aebd590ab1e324d951655cb48b097c", + "reference": "9012a272f1aebd590ab1e324d951655cb48b097c", "shasum": "" }, "require": { "doctrine/orm": "^2.4", "php": ">=5.4.0", - "symfony/framework-bundle": "^2.6|^3.0|^4.0" + "symfony/framework-bundle": "^2.6|^3.0|^4.0|^5.0" }, "require-dev": { "data-dog/pager-bundle": "^0.2", @@ -326,7 +326,7 @@ "php": ">=5.6.0", "phpunit/phpunit": "~4.7.0", "sensio/framework-extra-bundle": "^3.0.2", - "symfony/symfony": "^3.0|^4.0" + "symfony/symfony": "^3.0|^4.0|^5.0" }, "type": "symfony-bundle", "extra": { @@ -363,20 +363,20 @@ "log", "orm" ], - "time": "2019-02-01T09:13:05+00:00" + "time": "2020-01-08T10:45:29+00:00" }, { "name": "doctrine/annotations", - "version": "v1.6.1", + "version": "v1.8.0", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24" + "reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/53120e0eb10355388d6ccbe462f1fea34ddadb24", - "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/904dca4eb10715b92569fbcd79e201d5c349b6bc", + "reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc", "shasum": "" }, "require": { @@ -385,12 +385,12 @@ }, "require-dev": { "doctrine/cache": "1.*", - "phpunit/phpunit": "^6.4" + "phpunit/phpunit": "^7.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6.x-dev" + "dev-master": "1.7.x-dev" } }, "autoload": { @@ -403,6 +403,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -411,10 +415,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -431,20 +431,20 @@ "docblock", "parser" ], - "time": "2019-03-25T19:12:02+00:00" + "time": "2019-10-01T18:55:10+00:00" }, { "name": "doctrine/cache", - "version": "v1.8.0", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "d768d58baee9a4862ca783840eca1b9add7a7f57" + "reference": "382e7f4db9a12dc6c19431743a2b096041bcdd62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/d768d58baee9a4862ca783840eca1b9add7a7f57", - "reference": "d768d58baee9a4862ca783840eca1b9add7a7f57", + "url": "https://api.github.com/repos/doctrine/cache/zipball/382e7f4db9a12dc6c19431743a2b096041bcdd62", + "reference": "382e7f4db9a12dc6c19431743a2b096041bcdd62", "shasum": "" }, "require": { @@ -455,7 +455,7 @@ }, "require-dev": { "alcaeus/mongo-php-adapter": "^1.1", - "doctrine/coding-standard": "^4.0", + "doctrine/coding-standard": "^6.0", "mongodb/mongodb": "^1.1", "phpunit/phpunit": "^7.0", "predis/predis": "~1.0" @@ -466,7 +466,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-master": "1.9.x-dev" } }, "autoload": { @@ -479,6 +479,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -487,10 +491,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -500,26 +500,33 @@ "email": "schmittjoh@gmail.com" } ], - "description": "Caching library offering an object-oriented API for many cache backends", - "homepage": "https://www.doctrine-project.org", + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", "keywords": [ + "abstraction", + "apcu", "cache", - "caching" + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" ], - "time": "2018-08-21T18:01:43+00:00" + "time": "2019-11-29T15:36:20+00:00" }, { "name": "doctrine/collections", - "version": "v1.6.2", + "version": "1.6.4", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "c5e0bc17b1620e97c968ac409acbff28b8b850be" + "reference": "6b1e4b2b66f6d6e49983cebfe23a21b7ccc5b0d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/c5e0bc17b1620e97c968ac409acbff28b8b850be", - "reference": "c5e0bc17b1620e97c968ac409acbff28b8b850be", + "url": "https://api.github.com/repos/doctrine/collections/zipball/6b1e4b2b66f6d6e49983cebfe23a21b7ccc5b0d7", + "reference": "6b1e4b2b66f6d6e49983cebfe23a21b7ccc5b0d7", "shasum": "" }, "require": { @@ -547,6 +554,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -555,10 +566,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -576,20 +583,20 @@ "iterators", "php" ], - "time": "2019-06-09T13:48:14+00:00" + "time": "2019-11-13T13:07:11+00:00" }, { "name": "doctrine/common", - "version": "v2.10.0", + "version": "2.12.0", "source": { "type": "git", "url": "https://github.com/doctrine/common.git", - "reference": "30e33f60f64deec87df728c02b107f82cdafad9d" + "reference": "2053eafdf60c2172ee1373d1b9289ba1db7f1fc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/30e33f60f64deec87df728c02b107f82cdafad9d", - "reference": "30e33f60f64deec87df728c02b107f82cdafad9d", + "url": "https://api.github.com/repos/doctrine/common/zipball/2053eafdf60c2172ee1373d1b9289ba1db7f1fc6", + "reference": "2053eafdf60c2172ee1373d1b9289ba1db7f1fc6", "shasum": "" }, "require": { @@ -605,14 +612,16 @@ }, "require-dev": { "doctrine/coding-standard": "^1.0", - "phpunit/phpunit": "^6.3", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpunit/phpunit": "^7.0", "squizlabs/php_codesniffer": "^3.0", "symfony/phpunit-bridge": "^4.0.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.10.x-dev" + "dev-master": "2.11.x-dev" } }, "autoload": { @@ -625,6 +634,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -633,10 +646,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -657,35 +666,34 @@ "doctrine", "php" ], - "time": "2018-11-21T01:24:55+00:00" + "time": "2020-01-10T15:49:25+00:00" }, { "name": "doctrine/dbal", - "version": "v2.9.2", + "version": "v2.10.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9" + "reference": "c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9", - "reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8", + "reference": "c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8", "shasum": "" }, "require": { "doctrine/cache": "^1.0", "doctrine/event-manager": "^1.0", "ext-pdo": "*", - "php": "^7.1" + "php": "^7.2" }, "require-dev": { - "doctrine/coding-standard": "^5.0", - "jetbrains/phpstorm-stubs": "^2018.1.2", - "phpstan/phpstan": "^0.10.1", - "phpunit/phpunit": "^7.4", - "symfony/console": "^2.0.5|^3.0|^4.0", - "symfony/phpunit-bridge": "^3.4.5|^4.0.5" + "doctrine/coding-standard": "^6.0", + "jetbrains/phpstorm-stubs": "^2019.1", + "phpstan/phpstan": "^0.11.3", + "phpunit/phpunit": "^8.4.1", + "symfony/console": "^2.0.5|^3.0|^4.0|^5.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -696,7 +704,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.9.x-dev", + "dev-master": "2.10.x-dev", "dev-develop": "3.0.x-dev" } }, @@ -710,6 +718,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -718,10 +730,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -732,39 +740,52 @@ "keywords": [ "abstraction", "database", + "db2", "dbal", + "mariadb", + "mssql", "mysql", - "persistence", + "oci8", + "oracle", + "pdo", "pgsql", - "php", - "queryobject" + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlanywhere", + "sqlite", + "sqlserver", + "sqlsrv" ], - "time": "2018-12-31T03:27:51+00:00" + "time": "2020-01-04T12:56:21+00:00" }, { "name": "doctrine/doctrine-bundle", - "version": "1.11.2", + "version": "2.0.7", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "28101e20776d8fa20a00b54947fbae2db0d09103" + "reference": "6926771140ee87a823c3b2c72602de9dda4490d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/28101e20776d8fa20a00b54947fbae2db0d09103", - "reference": "28101e20776d8fa20a00b54947fbae2db0d09103", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/6926771140ee87a823c3b2c72602de9dda4490d3", + "reference": "6926771140ee87a823c3b2c72602de9dda4490d3", "shasum": "" }, "require": { - "doctrine/dbal": "^2.5.12", - "doctrine/doctrine-cache-bundle": "~1.2", + "doctrine/dbal": "^2.9.0", + "doctrine/persistence": "^1.3.3", "jdorn/sql-formatter": "^1.2.16", "php": "^7.1", - "symfony/config": "^3.4|^4.1", - "symfony/console": "^3.4|^4.1", - "symfony/dependency-injection": "^3.4|^4.1", - "symfony/doctrine-bridge": "^3.4|^4.1", - "symfony/framework-bundle": "^3.4|^4.1" + "symfony/cache": "^4.3.3|^5.0", + "symfony/config": "^4.3.3|^5.0", + "symfony/console": "^3.4.30|^4.3.3|^5.0", + "symfony/dependency-injection": "^4.3.3|^5.0", + "symfony/doctrine-bridge": "^4.3.7|^5.0", + "symfony/framework-bundle": "^3.4.30|^4.3.3|^5.0", + "symfony/service-contracts": "^1.1.1|^2.0" }, "conflict": { "doctrine/orm": "<2.6", @@ -773,15 +794,16 @@ "require-dev": { "doctrine/coding-standard": "^6.0", "doctrine/orm": "^2.6", - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "7.0", - "symfony/cache": "^3.4|^4.1", + "ocramius/proxy-manager": "^2.1", + "phpunit/phpunit": "^7.5", "symfony/phpunit-bridge": "^4.2", - "symfony/property-info": "^3.4|^4.1", - "symfony/validator": "^3.4|^4.1", - "symfony/web-profiler-bundle": "^3.4|^4.1", - "symfony/yaml": "^3.4|^4.1", - "twig/twig": "^1.34|^2.4" + "symfony/property-info": "^4.3.3|^5.0", + "symfony/proxy-manager-bridge": "^3.4|^4.3.3|^5.0", + "symfony/twig-bridge": "^3.4.30|^4.3.3|^5.0", + "symfony/validator": "^3.4.30|^4.3.3|^5.0", + "symfony/web-profiler-bundle": "^3.4.30|^4.3.3|^5.0", + "symfony/yaml": "^3.4.30|^4.3.3|^5.0", + "twig/twig": "^1.34|^2.12" }, "suggest": { "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", @@ -790,7 +812,7 @@ "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "1.11.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -804,20 +826,20 @@ ], "authors": [ { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, { - "name": "Doctrine Project", - "homepage": "http://www.doctrine-project.org/" + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" }, { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Doctrine Project", + "homepage": "http://www.doctrine-project.org/" } ], "description": "Symfony DoctrineBundle", @@ -828,43 +850,43 @@ "orm", "persistence" ], - "time": "2019-06-04T07:35:05+00:00" + "time": "2020-01-18T11:56:15+00:00" }, { "name": "doctrine/doctrine-cache-bundle", - "version": "1.3.5", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineCacheBundle.git", - "reference": "5514c90d9fb595e1095e6d66ebb98ce9ef049927" + "reference": "6bee2f9b339847e8a984427353670bad4e7bdccb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineCacheBundle/zipball/5514c90d9fb595e1095e6d66ebb98ce9ef049927", - "reference": "5514c90d9fb595e1095e6d66ebb98ce9ef049927", + "url": "https://api.github.com/repos/doctrine/DoctrineCacheBundle/zipball/6bee2f9b339847e8a984427353670bad4e7bdccb", + "reference": "6bee2f9b339847e8a984427353670bad4e7bdccb", "shasum": "" }, "require": { "doctrine/cache": "^1.4.2", - "doctrine/inflector": "~1.0", - "php": ">=5.3.2", - "symfony/doctrine-bridge": "~2.7|~3.3|~4.0" + "doctrine/inflector": "^1.0", + "php": "^7.1", + "symfony/doctrine-bridge": "^3.4|^4.0" }, "require-dev": { "instaclick/coding-standard": "~1.1", "instaclick/object-calisthenics-sniffs": "dev-master", "instaclick/symfony2-coding-standard": "dev-remaster", - "phpunit/phpunit": "~4.8.36|~5.6|~6.5|~7.0", + "phpunit/phpunit": "^7.0", "predis/predis": "~0.8", "satooshi/php-coveralls": "^1.0", "squizlabs/php_codesniffer": "~1.5", - "symfony/console": "~2.7|~3.3|~4.0", - "symfony/finder": "~2.7|~3.3|~4.0", - "symfony/framework-bundle": "~2.7|~3.3|~4.0", - "symfony/phpunit-bridge": "~2.7|~3.3|~4.0", - "symfony/security-acl": "~2.7|~3.3", - "symfony/validator": "~2.7|~3.3|~4.0", - "symfony/yaml": "~2.7|~3.3|~4.0" + "symfony/console": "^3.4|^4.0", + "symfony/finder": "^3.4|^4.0", + "symfony/framework-bundle": "^3.4|^4.0", + "symfony/phpunit-bridge": "^3.4|^4.0", + "symfony/security-acl": "^2.8", + "symfony/validator": "^3.4|^4.0", + "symfony/yaml": "^3.4|^4.0" }, "suggest": { "symfony/security-acl": "For using this bundle to cache ACLs" @@ -872,7 +894,7 @@ "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "1.4.x-dev" } }, "autoload": { @@ -889,8 +911,8 @@ ], "authors": [ { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Benjamin Eberlei", @@ -905,12 +927,12 @@ "email": "guilhermeblanco@hotmail.com" }, { - "name": "Doctrine Project", - "homepage": "http://www.doctrine-project.org/" + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" }, { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Doctrine Project", + "homepage": "http://www.doctrine-project.org/" } ], "description": "Symfony Bundle for Doctrine Cache", @@ -919,45 +941,48 @@ "cache", "caching" ], - "time": "2018-11-09T06:25:35+00:00" + "time": "2019-11-29T11:22:01+00:00" }, { "name": "doctrine/doctrine-migrations-bundle", - "version": "v2.0.0", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", - "reference": "4c9579e0e43df1fb3f0ca29b9c20871c824fac71" + "reference": "856437e8de96a70233e1f0cc2352fc8dd15a899d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/4c9579e0e43df1fb3f0ca29b9c20871c824fac71", - "reference": "4c9579e0e43df1fb3f0ca29b9c20871c824fac71", + "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/856437e8de96a70233e1f0cc2352fc8dd15a899d", + "reference": "856437e8de96a70233e1f0cc2352fc8dd15a899d", "shasum": "" }, "require": { - "doctrine/doctrine-bundle": "~1.0", - "doctrine/migrations": "^2.0", + "doctrine/doctrine-bundle": "~1.0|~2.0", + "doctrine/migrations": "^2.2", "php": "^7.1", - "symfony/framework-bundle": "~3.4|~4.0" + "symfony/framework-bundle": "~3.4|~4.0|~5.0" }, "require-dev": { "doctrine/coding-standard": "^5.0", "mikey179/vfsstream": "^1.6", "phpstan/phpstan": "^0.9.2", "phpstan/phpstan-strict-rules": "^0.9", - "phpunit/phpunit": "^5.7|^6.4|^7.0" + "phpunit/phpunit": "^6.4|^7.0" }, "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "2.1.x-dev" } }, "autoload": { "psr-4": { "Doctrine\\Bundle\\MigrationsBundle\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -965,16 +990,16 @@ ], "authors": [ { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Doctrine Project", "homepage": "http://www.doctrine-project.org" }, { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" } ], "description": "Symfony DoctrineMigrationsBundle", @@ -984,20 +1009,20 @@ "migrations", "schema" ], - "time": "2019-01-09T18:49:50+00:00" + "time": "2019-11-13T12:57:41+00:00" }, { "name": "doctrine/event-manager", - "version": "v1.0.0", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3" + "reference": "629572819973f13486371cb611386eb17851e85c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/a520bc093a0170feeb6b14e9d83f3a14452e64b3", - "reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/629572819973f13486371cb611386eb17851e85c", + "reference": "629572819973f13486371cb611386eb17851e85c", "shasum": "" }, "require": { @@ -1007,7 +1032,7 @@ "doctrine/common": "<2.9@dev" }, "require-dev": { - "doctrine/coding-standard": "^4.0", + "doctrine/coding-standard": "^6.0", "phpunit/phpunit": "^7.0" }, "type": "library", @@ -1026,6 +1051,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -1034,10 +1063,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -1051,27 +1076,29 @@ "email": "ocramius@gmail.com" } ], - "description": "Doctrine Event Manager component", + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", "homepage": "https://www.doctrine-project.org/projects/event-manager.html", "keywords": [ "event", - "eventdispatcher", - "eventmanager" + "event dispatcher", + "event manager", + "event system", + "events" ], - "time": "2018-06-11T11:59:03+00:00" + "time": "2019-11-10T09:48:07+00:00" }, { "name": "doctrine/inflector", - "version": "v1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "5527a48b7313d15261292c149e55e26eae771b0a" + "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/5527a48b7313d15261292c149e55e26eae771b0a", - "reference": "5527a48b7313d15261292c149e55e26eae771b0a", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/ec3a55242203ffa6a4b27c58176da97ff0a7aec1", + "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1", "shasum": "" }, "require": { @@ -1096,6 +1123,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -1104,10 +1135,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -1125,20 +1152,20 @@ "singularize", "string" ], - "time": "2018-01-09T20:05:19+00:00" + "time": "2019-10-30T19:59:35+00:00" }, { "name": "doctrine/instantiator", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "a2c590166b2133a4633738648b6b064edae0814a" + "reference": "ae466f726242e637cebdd526a7d991b9433bacf1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", - "reference": "a2c590166b2133a4633738648b6b064edae0814a", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1", + "reference": "ae466f726242e637cebdd526a7d991b9433bacf1", "shasum": "" }, "require": { @@ -1181,32 +1208,34 @@ "constructor", "instantiate" ], - "time": "2019-03-17T17:37:11+00:00" + "time": "2019-10-21T16:45:58+00:00" }, { "name": "doctrine/lexer", - "version": "1.0.2", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "1febd6c3ef84253d7c815bed85fc622ad207a9f8" + "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/1febd6c3ef84253d7c815bed85fc622ad207a9f8", - "reference": "1febd6c3ef84253d7c815bed85fc622ad207a9f8", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6", + "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": "^7.2" }, "require-dev": { - "phpunit/phpunit": "^4.5" + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -1219,14 +1248,14 @@ "MIT" ], "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, { "name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com" }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" @@ -1241,20 +1270,20 @@ "parser", "php" ], - "time": "2019-06-08T11:03:04+00:00" + "time": "2019-10-30T14:39:59+00:00" }, { "name": "doctrine/migrations", - "version": "v2.1.0", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/doctrine/migrations.git", - "reference": "ebe6f891a4c61574f77fc4a06d913d29236b8466" + "reference": "a3987131febeb0e9acb3c47ab0df0af004588934" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/migrations/zipball/ebe6f891a4c61574f77fc4a06d913d29236b8466", - "reference": "ebe6f891a4c61574f77fc4a06d913d29236b8466", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/a3987131febeb0e9acb3c47ab0df0af004588934", + "reference": "a3987131febeb0e9acb3c47ab0df0af004588934", "shasum": "" }, "require": { @@ -1262,8 +1291,8 @@ "ocramius/package-versions": "^1.3", "ocramius/proxy-manager": "^2.0.2", "php": "^7.1", - "symfony/console": "^3.4||^4.0", - "symfony/stopwatch": "^3.4||^4.0" + "symfony/console": "^3.4||^4.0||^5.0", + "symfony/stopwatch": "^3.4||^4.0||^5.0" }, "require-dev": { "doctrine/coding-standard": "^6.0", @@ -1276,8 +1305,8 @@ "phpstan/phpstan-phpunit": "^0.10", "phpstan/phpstan-strict-rules": "^0.10", "phpunit/phpunit": "^7.0", - "symfony/process": "^3.4||^4.0", - "symfony/yaml": "^3.4||^4.0" + "symfony/process": "^3.4||^4.0||^5.0", + "symfony/yaml": "^3.4||^4.0||^5.0" }, "suggest": { "jdorn/sql-formatter": "Allows to generate formatted SQL with the diff command.", @@ -1289,7 +1318,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1.x-dev" + "dev-master": "2.2.x-dev" } }, "autoload": { @@ -1323,38 +1352,39 @@ "migrations", "php" ], - "time": "2019-06-06T15:47:41+00:00" + "time": "2019-12-04T06:09:14+00:00" }, { "name": "doctrine/orm", - "version": "v2.6.3", + "version": "v2.7.0", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "434820973cadf2da2d66e7184be370084cc32ca8" + "reference": "4d763ca4c925f647b248b9fa01b5f47aa3685d62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/434820973cadf2da2d66e7184be370084cc32ca8", - "reference": "434820973cadf2da2d66e7184be370084cc32ca8", + "url": "https://api.github.com/repos/doctrine/orm/zipball/4d763ca4c925f647b248b9fa01b5f47aa3685d62", + "reference": "4d763ca4c925f647b248b9fa01b5f47aa3685d62", "shasum": "" }, "require": { - "doctrine/annotations": "~1.5", - "doctrine/cache": "~1.6", - "doctrine/collections": "^1.4", - "doctrine/common": "^2.7.1", - "doctrine/dbal": "^2.6", - "doctrine/instantiator": "~1.1", + "doctrine/annotations": "^1.8", + "doctrine/cache": "^1.9.1", + "doctrine/collections": "^1.5", + "doctrine/common": "^2.11", + "doctrine/dbal": "^2.9.3", + "doctrine/event-manager": "^1.1", + "doctrine/instantiator": "^1.3", + "doctrine/persistence": "^1.2", "ext-pdo": "*", "php": "^7.1", - "symfony/console": "~3.0|~4.0" + "symfony/console": "^3.0|^4.0|^5.0" }, "require-dev": { - "doctrine/coding-standard": "^1.0", - "phpunit/phpunit": "^6.5", - "squizlabs/php_codesniffer": "^3.2", - "symfony/yaml": "~3.4|~4.0" + "doctrine/coding-standard": "^5.0", + "phpunit/phpunit": "^7.5", + "symfony/yaml": "^3.4|^4.0|^5.0" }, "suggest": { "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" @@ -1365,7 +1395,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6.x-dev" + "dev-master": "2.7.x-dev" } }, "autoload": { @@ -1378,6 +1408,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -1386,10 +1420,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -1400,25 +1430,25 @@ } ], "description": "Object-Relational-Mapper for PHP", - "homepage": "http://www.doctrine-project.org", + "homepage": "https://www.doctrine-project.org/projects/orm.html", "keywords": [ "database", "orm" ], - "time": "2018-11-20T23:46:46+00:00" + "time": "2019-11-19T08:38:05+00:00" }, { "name": "doctrine/persistence", - "version": "1.1.1", + "version": "1.3.6", "source": { "type": "git", "url": "https://github.com/doctrine/persistence.git", - "reference": "3da7c9d125591ca83944f477e65ed3d7b4617c48" + "reference": "5dd3ac5eebef2d0b074daa4440bb18f93132dee4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/3da7c9d125591ca83944f477e65ed3d7b4617c48", - "reference": "3da7c9d125591ca83944f477e65ed3d7b4617c48", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/5dd3ac5eebef2d0b074daa4440bb18f93132dee4", + "reference": "5dd3ac5eebef2d0b074daa4440bb18f93132dee4", "shasum": "" }, "require": { @@ -1426,26 +1456,27 @@ "doctrine/cache": "^1.0", "doctrine/collections": "^1.0", "doctrine/event-manager": "^1.0", - "doctrine/reflection": "^1.0", + "doctrine/reflection": "^1.1", "php": "^7.1" }, "conflict": { "doctrine/common": "<2.10@dev" }, "require-dev": { - "doctrine/coding-standard": "^5.0", - "phpstan/phpstan": "^0.8", + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11", "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.3.x-dev" } }, "autoload": { "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" + "Doctrine\\Common\\": "lib/Doctrine/Common", + "Doctrine\\Persistence\\": "lib/Doctrine/Persistence" } }, "notification-url": "https://packagist.org/downloads/", @@ -1453,6 +1484,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -1461,10 +1496,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -1487,20 +1518,20 @@ "orm", "persistence" ], - "time": "2019-04-23T08:28:24+00:00" + "time": "2020-01-16T22:06:23+00:00" }, { "name": "doctrine/reflection", - "version": "v1.0.0", + "version": "v1.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/reflection.git", - "reference": "02538d3f95e88eb397a5f86274deb2c6175c2ab6" + "reference": "bc420ead87fdfe08c03ecc3549db603a45b06d4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/reflection/zipball/02538d3f95e88eb397a5f86274deb2c6175c2ab6", - "reference": "02538d3f95e88eb397a5f86274deb2c6175c2ab6", + "url": "https://api.github.com/repos/doctrine/reflection/zipball/bc420ead87fdfe08c03ecc3549db603a45b06d4c", + "reference": "bc420ead87fdfe08c03ecc3549db603a45b06d4c", "shasum": "" }, "require": { @@ -1508,13 +1539,15 @@ "ext-tokenizer": "*", "php": "^7.1" }, + "conflict": { + "doctrine/common": "<2.9" + }, "require-dev": { - "doctrine/coding-standard": "^4.0", - "doctrine/common": "^2.8", - "phpstan/phpstan": "^0.9.2", - "phpstan/phpstan-phpunit": "^0.9.4", - "phpunit/phpunit": "^7.0", - "squizlabs/php_codesniffer": "^3.0" + "doctrine/coding-standard": "^5.0", + "doctrine/common": "^2.10", + "phpstan/phpstan": "^0.11.0", + "phpstan/phpstan-phpunit": "^0.11.0", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { @@ -1532,6 +1565,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -1540,10 +1577,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -1557,12 +1590,13 @@ "email": "ocramius@gmail.com" } ], - "description": "Doctrine Reflection component", + "description": "The Doctrine Reflection project is a simple library used by the various Doctrine projects which adds some additional functionality on top of the reflection functionality that comes with PHP. It allows you to get the reflection information about classes, methods and properties statically.", "homepage": "https://www.doctrine-project.org/projects/reflection.html", "keywords": [ - "reflection" + "reflection", + "static" ], - "time": "2018-06-14T14:45:07+00:00" + "time": "2020-01-08T19:53:19+00:00" }, { "name": "edwinhoksberg/php-fcm", @@ -1615,44 +1649,46 @@ }, { "name": "guzzlehttp/guzzle", - "version": "6.3.3", + "version": "6.5.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" + "reference": "43ece0e75098b7ecd8d13918293029e555a50f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/43ece0e75098b7ecd8d13918293029e555a50f82", + "reference": "43ece0e75098b7ecd8d13918293029e555a50f82", "shasum": "" }, "require": { + "ext-json": "*", "guzzlehttp/promises": "^1.0", - "guzzlehttp/psr7": "^1.4", + "guzzlehttp/psr7": "^1.6.1", "php": ">=5.5" }, "require-dev": { "ext-curl": "*", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", - "psr/log": "^1.0" + "psr/log": "^1.1" }, "suggest": { + "ext-intl": "Required for Internationalized Domain Name (IDN) support", "psr/log": "Required for using the Log middleware" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "6.3-dev" + "dev-master": "6.5-dev" } }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\": "src/" - } + }, + "files": [ + "src/functions_include.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1676,7 +1712,7 @@ "rest", "web service" ], - "time": "2018-04-22T15:46:56+00:00" + "time": "2019-12-23T11:57:10+00:00" }, { "name": "guzzlehttp/promises", @@ -1731,33 +1767,37 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.5.2", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "9f83dded91781a01c63574e387eaa769be769115" + "reference": "239400de7a173fe9901b9ac7c06497751f00727a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115", - "reference": "9f83dded91781a01c63574e387eaa769be769115", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a", "shasum": "" }, "require": { "php": ">=5.4.0", "psr/http-message": "~1.0", - "ralouphie/getallheaders": "^2.0.5" + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" }, "provide": { "psr/http-message-implementation": "1.0" }, "require-dev": { + "ext-zlib": "*", "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -1794,7 +1834,7 @@ "uri", "url" ], - "time": "2018-12-04T20:46:45+00:00" + "time": "2019-07-01T23:21:34+00:00" }, { "name": "jdorn/sql-formatter", @@ -1848,16 +1888,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.2.2", + "version": "v4.3.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "1bd73cc04c3843ad8d6b0bfc0956026a151fc420" + "reference": "9a9981c347c5c49d6dfe5cf826bb882b824080dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bd73cc04c3843ad8d6b0bfc0956026a151fc420", - "reference": "1bd73cc04c3843ad8d6b0bfc0956026a151fc420", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9a9981c347c5c49d6dfe5cf826bb882b824080dc", + "reference": "9a9981c347c5c49d6dfe5cf826bb882b824080dc", "shasum": "" }, "require": { @@ -1865,7 +1905,8 @@ "php": ">=7.0" }, "require-dev": { - "phpunit/phpunit": "^6.5 || ^7.0" + "ircmaxell/php-yacc": "0.0.5", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0" }, "bin": [ "bin/php-parse" @@ -1873,7 +1914,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -1895,20 +1936,20 @@ "parser", "php" ], - "time": "2019-05-25T20:07:01+00:00" + "time": "2019-11-08T13:50:10+00:00" }, { "name": "ocramius/package-versions", - "version": "1.4.0", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/Ocramius/PackageVersions.git", - "reference": "a4d4b60d0e60da2487bd21a2c6ac089f85570dbb" + "reference": "44af6f3a2e2e04f2af46bcb302ad9600cba41c7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/a4d4b60d0e60da2487bd21a2c6ac089f85570dbb", - "reference": "a4d4b60d0e60da2487bd21a2c6ac089f85570dbb", + "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/44af6f3a2e2e04f2af46bcb302ad9600cba41c7d", + "reference": "44af6f3a2e2e04f2af46bcb302ad9600cba41c7d", "shasum": "" }, "require": { @@ -1920,7 +1961,7 @@ "doctrine/coding-standard": "^5.0.1", "ext-zip": "*", "infection/infection": "^0.7.1", - "phpunit/phpunit": "^7.0.0" + "phpunit/phpunit": "^7.5.17" }, "type": "composer-plugin", "extra": { @@ -1945,20 +1986,20 @@ } ], "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", - "time": "2019-02-21T12:16:21+00:00" + "time": "2019-11-15T16:17:10+00:00" }, { "name": "ocramius/proxy-manager", - "version": "2.2.2", + "version": "2.2.3", "source": { "type": "git", "url": "https://github.com/Ocramius/ProxyManager.git", - "reference": "14b137b06b0f911944132df9d51e445a35920ab1" + "reference": "4d154742e31c35137d5374c998e8f86b54db2e2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Ocramius/ProxyManager/zipball/14b137b06b0f911944132df9d51e445a35920ab1", - "reference": "14b137b06b0f911944132df9d51e445a35920ab1", + "url": "https://api.github.com/repos/Ocramius/ProxyManager/zipball/4d154742e31c35137d5374c998e8f86b54db2e2f", + "reference": "4d154742e31c35137d5374c998e8f86b54db2e2f", "shasum": "" }, "require": { @@ -2015,7 +2056,7 @@ "proxy pattern", "service proxies" ], - "time": "2018-09-27T13:45:01+00:00" + "time": "2019-08-10T08:37:15+00:00" }, { "name": "predis/predis", @@ -2214,16 +2255,16 @@ }, { "name": "psr/log", - "version": "1.1.0", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", - "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", "shasum": "" }, "require": { @@ -2232,7 +2273,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { @@ -2257,28 +2298,28 @@ "psr", "psr-3" ], - "time": "2018-11-20T15:27:04+00:00" + "time": "2019-11-01T11:05:21+00:00" }, { "name": "ralouphie/getallheaders", - "version": "2.0.5", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa" + "reference": "120b605dfeb996808c31b6477290a714d356e822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa", - "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", "shasum": "" }, "require": { - "php": ">=5.3" + "php": ">=5.6" }, "require-dev": { - "phpunit/phpunit": "~3.7.0", - "satooshi/php-coveralls": ">=1.0" + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" }, "type": "library", "autoload": { @@ -2297,47 +2338,49 @@ } ], "description": "A polyfill for getallheaders.", - "time": "2016-02-11T07:05:27+00:00" + "time": "2019-03-08T08:55:37+00:00" }, { "name": "sensio/framework-extra-bundle", - "version": "v5.3.1", + "version": "v5.5.3", "source": { "type": "git", "url": "https://github.com/sensiolabs/SensioFrameworkExtraBundle.git", - "reference": "5f75c4658b03301cba17baa15a840b57b72b4262" + "reference": "98f0807137b13d0acfdf3c255a731516e97015de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/5f75c4658b03301cba17baa15a840b57b72b4262", - "reference": "5f75c4658b03301cba17baa15a840b57b72b4262", + "url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/98f0807137b13d0acfdf3c255a731516e97015de", + "reference": "98f0807137b13d0acfdf3c255a731516e97015de", "shasum": "" }, "require": { "doctrine/annotations": "^1.0", - "doctrine/persistence": "^1.0", "php": ">=7.1.3", - "symfony/config": "^3.4|^4.2", - "symfony/dependency-injection": "^3.4|^4.2", - "symfony/framework-bundle": "^3.4|^4.2", - "symfony/http-kernel": "^3.4|^4.2" + "symfony/config": "^4.3|^5.0", + "symfony/dependency-injection": "^4.3|^5.0", + "symfony/framework-bundle": "^4.3|^5.0", + "symfony/http-kernel": "^4.3|^5.0" + }, + "conflict": { + "doctrine/doctrine-cache-bundle": "<1.3.1" }, "require-dev": { - "doctrine/doctrine-bundle": "^1.6", + "doctrine/doctrine-bundle": "^1.11|^2.0", "doctrine/orm": "^2.5", "nyholm/psr7": "^1.1", - "symfony/browser-kit": "^3.4|^4.2", - "symfony/dom-crawler": "^3.4|^4.2", - "symfony/expression-language": "^3.4|^4.2", - "symfony/finder": "^3.4|^4.2", - "symfony/monolog-bridge": "^3.0|^4.0", + "symfony/browser-kit": "^4.3|^5.0", + "symfony/dom-crawler": "^4.3|^5.0", + "symfony/expression-language": "^4.3|^5.0", + "symfony/finder": "^4.3|^5.0", + "symfony/monolog-bridge": "^4.0|^5.0", "symfony/monolog-bundle": "^3.2", - "symfony/phpunit-bridge": "^3.4.19|^4.1.8", + "symfony/phpunit-bridge": "^4.3.5|^5.0", "symfony/psr-http-message-bridge": "^1.1", - "symfony/security-bundle": "^3.4|^4.2", - "symfony/twig-bundle": "^3.4|^4.2", - "symfony/yaml": "^3.4|^4.2", - "twig/twig": "~1.12|~2.0" + "symfony/security-bundle": "^4.3|^5.0", + "symfony/twig-bundle": "^4.3|^5.0", + "symfony/yaml": "^4.3|^5.0", + "twig/twig": "^1.34|^2.4|^3.0" }, "suggest": { "symfony/expression-language": "", @@ -2347,13 +2390,16 @@ "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "5.3.x-dev" + "dev-master": "5.5.x-dev" } }, "autoload": { "psr-4": { - "Sensio\\Bundle\\FrameworkExtraBundle\\": "" - } + "Sensio\\Bundle\\FrameworkExtraBundle\\": "src/" + }, + "exclude-from-classmap": [ + "/tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2370,20 +2416,20 @@ "annotations", "controllers" ], - "time": "2019-04-10T06:00:20+00:00" + "time": "2019-12-27T08:57:19+00:00" }, { "name": "setasign/fpdf", - "version": "1.8.1", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/Setasign/FPDF.git", - "reference": "2c68c9e6c034ac3187d25968790139a73184cdb1" + "reference": "d77904018090c17dc9f3ab6e944679a7a47e710a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Setasign/FPDF/zipball/2c68c9e6c034ac3187d25968790139a73184cdb1", - "reference": "2c68c9e6c034ac3187d25968790139a73184cdb1", + "url": "https://api.github.com/repos/Setasign/FPDF/zipball/d77904018090c17dc9f3ab6e944679a7a47e710a", + "reference": "d77904018090c17dc9f3ab6e944679a7a47e710a", "shasum": "" }, "type": "library", @@ -2394,7 +2440,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "no usage restriction" + "MIT" ], "authors": [ { @@ -2409,34 +2455,91 @@ "fpdf", "pdf" ], - "time": "2016-01-01T17:47:15+00:00" + "time": "2019-12-08T10:32:10+00:00" }, { - "name": "symfony/cache", - "version": "v4.3.1", + "name": "symfony/asset", + "version": "v4.4.4", "source": { "type": "git", - "url": "https://github.com/symfony/cache.git", - "reference": "2edc417da273bafee589a8758f0278416d04af38" + "url": "https://github.com/symfony/asset.git", + "reference": "2c67c89d064bfb689ea6bc41217c87100bb94c17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/2edc417da273bafee589a8758f0278416d04af38", - "reference": "2edc417da273bafee589a8758f0278416d04af38", + "url": "https://api.github.com/repos/symfony/asset/zipball/2c67c89d064bfb689ea6bc41217c87100bb94c17", + "reference": "2c67c89d064bfb689ea6bc41217c87100bb94c17", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": "^7.1.3" + }, + "require-dev": { + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/http-kernel": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/http-foundation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Asset\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Asset Component", + "homepage": "https://symfony.com", + "time": "2020-01-04T13:00:46+00:00" + }, + { + "name": "symfony/cache", + "version": "v5.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "4572116c640a6bc9fc0047180fe7f9362e5923fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/4572116c640a6bc9fc0047180fe7f9362e5923fc", + "reference": "4572116c640a6bc9fc0047180fe7f9362e5923fc", + "shasum": "" + }, + "require": { + "php": "^7.2.5", "psr/cache": "~1.0", "psr/log": "~1.0", - "symfony/cache-contracts": "^1.1", - "symfony/service-contracts": "^1.1", - "symfony/var-exporter": "^4.2" + "symfony/cache-contracts": "^1.1.7|^2", + "symfony/service-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" }, "conflict": { "doctrine/dbal": "<2.5", - "symfony/dependency-injection": "<3.4", - "symfony/var-dumper": "<3.4" + "symfony/dependency-injection": "<4.4", + "symfony/http-kernel": "<4.4", + "symfony/var-dumper": "<4.4" }, "provide": { "psr/cache-implementation": "1.0", @@ -2449,14 +2552,14 @@ "doctrine/dbal": "~2.5", "predis/predis": "~1.1", "psr/simple-cache": "^1.0", - "symfony/config": "~4.2", - "symfony/dependency-injection": "~3.4|~4.1", - "symfony/var-dumper": "^4.1.1" + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -2487,33 +2590,33 @@ "caching", "psr6" ], - "time": "2019-06-06T10:05:02+00:00" + "time": "2020-01-31T09:13:47+00:00" }, { "name": "symfony/cache-contracts", - "version": "v1.1.1", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "7ff3902cc747dd5e2c6f26dc42603ed07b530293" + "reference": "23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/7ff3902cc747dd5e2c6f26dc42603ed07b530293", - "reference": "7ff3902cc747dd5e2c6f26dc42603ed07b530293", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16", + "reference": "23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.2.5", + "psr/cache": "^1.0" }, "suggest": { - "psr/cache": "", "symfony/cache-implementation": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -2545,36 +2648,36 @@ "interoperability", "standards" ], - "time": "2019-05-22T12:23:29+00:00" + "time": "2019-11-18T17:27:11+00:00" }, { "name": "symfony/config", - "version": "v4.3.1", + "version": "v5.0.4", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "6379ee07398643e09e6ed1e87d9c62dfcad7f4eb" + "reference": "7640c6704f56bf64045066bc5d93fd9d664baa63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/6379ee07398643e09e6ed1e87d9c62dfcad7f4eb", - "reference": "6379ee07398643e09e6ed1e87d9c62dfcad7f4eb", + "url": "https://api.github.com/repos/symfony/config/zipball/7640c6704f56bf64045066bc5d93fd9d664baa63", + "reference": "7640c6704f56bf64045066bc5d93fd9d664baa63", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/filesystem": "~3.4|~4.0", + "php": "^7.2.5", + "symfony/filesystem": "^4.4|^5.0", "symfony/polyfill-ctype": "~1.8" }, "conflict": { - "symfony/finder": "<3.4" + "symfony/finder": "<4.4" }, "require-dev": { - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~3.4|~4.0", - "symfony/finder": "~3.4|~4.0", - "symfony/messenger": "~4.1", - "symfony/yaml": "~3.4|~4.0" + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/finder": "^4.4|^5.0", + "symfony/messenger": "^4.4|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/yaml": "^4.4|^5.0" }, "suggest": { "symfony/yaml": "To use the yaml reference dumper" @@ -2582,7 +2685,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -2609,31 +2712,32 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2019-05-30T16:10:05+00:00" + "time": "2020-01-04T14:08:26+00:00" }, { "name": "symfony/console", - "version": "v4.3.1", + "version": "v4.4.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "d50bbeeb0e17e6dd4124ea391eff235e932cbf64" + "reference": "f512001679f37e6a042b51897ed24a2f05eba656" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/d50bbeeb0e17e6dd4124ea391eff235e932cbf64", - "reference": "d50bbeeb0e17e6dd4124ea391eff235e932cbf64", + "url": "https://api.github.com/repos/symfony/console/zipball/f512001679f37e6a042b51897ed24a2f05eba656", + "reference": "f512001679f37e6a042b51897ed24a2f05eba656", "shasum": "" }, "require": { "php": "^7.1.3", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php73": "^1.8", - "symfony/service-contracts": "^1.1" + "symfony/service-contracts": "^1.1|^2" }, "conflict": { "symfony/dependency-injection": "<3.4", - "symfony/event-dispatcher": "<4.3", + "symfony/event-dispatcher": "<4.3|>=5", + "symfony/lock": "<4.4", "symfony/process": "<3.3" }, "provide": { @@ -2641,12 +2745,12 @@ }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", "symfony/event-dispatcher": "^4.3", - "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0", - "symfony/var-dumper": "^4.3" + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/var-dumper": "^4.3|^5.0" }, "suggest": { "psr/log": "For using the console logger", @@ -2657,7 +2761,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -2684,20 +2788,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-06-05T13:25:51+00:00" + "time": "2020-01-25T12:44:29+00:00" }, { "name": "symfony/debug", - "version": "v4.3.1", + "version": "v4.4.4", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "4e025104f1f9adb1f7a2d14fb102c9986d6e97c6" + "reference": "20236471058bbaa9907382500fc14005c84601f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/4e025104f1f9adb1f7a2d14fb102c9986d6e97c6", - "reference": "4e025104f1f9adb1f7a2d14fb102c9986d6e97c6", + "url": "https://api.github.com/repos/symfony/debug/zipball/20236471058bbaa9907382500fc14005c84601f0", + "reference": "20236471058bbaa9907382500fc14005c84601f0", "shasum": "" }, "require": { @@ -2708,12 +2812,12 @@ "symfony/http-kernel": "<3.4" }, "require-dev": { - "symfony/http-kernel": "~3.4|~4.0" + "symfony/http-kernel": "^3.4|^4.0|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -2740,41 +2844,41 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2019-05-30T16:10:05+00:00" + "time": "2020-01-25T12:44:29+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.3.1", + "version": "v5.0.4", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "fea7f73e278ee0337349a5a68b867fc656bb33f3" + "reference": "86338f459313525dd95f5a012f8a9ea118002f94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/fea7f73e278ee0337349a5a68b867fc656bb33f3", - "reference": "fea7f73e278ee0337349a5a68b867fc656bb33f3", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/86338f459313525dd95f5a012f8a9ea118002f94", + "reference": "86338f459313525dd95f5a012f8a9ea118002f94", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": "^7.2.5", "psr/container": "^1.0", - "symfony/service-contracts": "^1.1.2" + "symfony/service-contracts": "^1.1.6|^2" }, "conflict": { - "symfony/config": "<4.3", - "symfony/finder": "<3.4", - "symfony/proxy-manager-bridge": "<3.4", - "symfony/yaml": "<3.4" + "symfony/config": "<5.0", + "symfony/finder": "<4.4", + "symfony/proxy-manager-bridge": "<4.4", + "symfony/yaml": "<4.4" }, "provide": { "psr/container-implementation": "1.0", "symfony/service-implementation": "1.0" }, "require-dev": { - "symfony/config": "^4.3", - "symfony/expression-language": "~3.4|~4.0", - "symfony/yaml": "~3.4|~4.0" + "symfony/config": "^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/yaml": "^4.4|^5.0" }, "suggest": { "symfony/config": "", @@ -2786,7 +2890,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -2813,57 +2917,61 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2019-05-30T16:10:05+00:00" + "time": "2020-01-31T09:49:43+00:00" }, { "name": "symfony/doctrine-bridge", - "version": "v4.3.1", + "version": "v4.4.4", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "b0cda757096cef5b73415fd1b685cc916a76838b" + "reference": "b8d43116f0e5abef4b7abcbeec81c3b9328ca7b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/b0cda757096cef5b73415fd1b685cc916a76838b", - "reference": "b0cda757096cef5b73415fd1b685cc916a76838b", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/b8d43116f0e5abef4b7abcbeec81c3b9328ca7b7", + "reference": "b8d43116f0e5abef4b7abcbeec81c3b9328ca7b7", "shasum": "" }, "require": { "doctrine/event-manager": "~1.0", - "doctrine/persistence": "~1.0", + "doctrine/persistence": "^1.3", "php": "^7.1.3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^1.1" + "symfony/service-contracts": "^1.1|^2" }, "conflict": { "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", "symfony/dependency-injection": "<3.4", - "symfony/form": "<4.3", - "symfony/messenger": "<4.3" + "symfony/form": "<4.4", + "symfony/http-kernel": "<4.3.7", + "symfony/messenger": "<4.3", + "symfony/security-core": "<4.4", + "symfony/validator": "<4.4.2|<5.0.2,>=5.0" }, "require-dev": { - "doctrine/annotations": "~1.0", + "doctrine/annotations": "~1.7", "doctrine/cache": "~1.6", "doctrine/collections": "~1.0", "doctrine/data-fixtures": "1.0.*", "doctrine/dbal": "~2.4", - "doctrine/orm": "^2.4.5", + "doctrine/orm": "^2.6.3", "doctrine/reflection": "~1.0", - "symfony/config": "^4.2", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/form": "~4.3", - "symfony/http-kernel": "~3.4|~4.0", - "symfony/messenger": "~4.3", - "symfony/property-access": "~3.4|~4.0", - "symfony/property-info": "~3.4|~4.0", - "symfony/proxy-manager-bridge": "~3.4|~4.0", - "symfony/security-core": "~3.4|~4.0", - "symfony/stopwatch": "~3.4|~4.0", - "symfony/translation": "~3.4|~4.0", - "symfony/validator": "~3.4|~4.0" + "symfony/config": "^4.2|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/form": "^4.4|^5.0", + "symfony/http-kernel": "^4.3.7", + "symfony/messenger": "^4.4|^5.0", + "symfony/property-access": "^3.4|^4.0|^5.0", + "symfony/property-info": "^3.4|^4.0|^5.0", + "symfony/proxy-manager-bridge": "^3.4|^4.0|^5.0", + "symfony/security-core": "^4.4|^5.0", + "symfony/stopwatch": "^3.4|^4.0|^5.0", + "symfony/translation": "^3.4|^4.0|^5.0", + "symfony/validator": "^4.4.2|^5.0.2", + "symfony/var-dumper": "^3.4|^4.0|^5.0" }, "suggest": { "doctrine/data-fixtures": "", @@ -2876,7 +2984,7 @@ "type": "symfony-bridge", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -2903,20 +3011,76 @@ ], "description": "Symfony Doctrine Bridge", "homepage": "https://symfony.com", - "time": "2019-06-06T07:45:42+00:00" + "time": "2020-01-23T10:56:47+00:00" }, { - "name": "symfony/event-dispatcher", - "version": "v4.3.1", + "name": "symfony/error-handler", + "version": "v4.4.4", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "4e6c670af81c4fb0b6c08b035530a9915d0b691f" + "url": "https://github.com/symfony/error-handler.git", + "reference": "d2721499ffcaf246a743e01cdf6696d3d5dd74c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4e6c670af81c4fb0b6c08b035530a9915d0b691f", - "reference": "4e6c670af81c4fb0b6c08b035530a9915d0b691f", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/d2721499ffcaf246a743e01cdf6696d3d5dd74c1", + "reference": "d2721499ffcaf246a743e01cdf6696d3d5dd74c1", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/log": "~1.0", + "symfony/debug": "^4.4", + "symfony/var-dumper": "^4.4|^5.0" + }, + "require-dev": { + "symfony/http-kernel": "^4.4|^5.0", + "symfony/serializer": "^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony ErrorHandler Component", + "homepage": "https://symfony.com", + "time": "2020-01-27T09:48:47+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v4.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "9e3de195e5bc301704dd6915df55892f6dfc208b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9e3de195e5bc301704dd6915df55892f6dfc208b", + "reference": "9e3de195e5bc301704dd6915df55892f6dfc208b", "shasum": "" }, "require": { @@ -2932,12 +3096,12 @@ }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/http-foundation": "^3.4|^4.0", - "symfony/service-contracts": "^1.1", - "symfony/stopwatch": "~3.4|~4.0" + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" }, "suggest": { "symfony/dependency-injection": "", @@ -2946,7 +3110,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -2973,20 +3137,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2019-05-30T16:10:05+00:00" + "time": "2020-01-10T21:54:01+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v1.1.1", + "version": "v1.1.7", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "8fa2cf2177083dd59cf8e44ea4b6541764fbda69" + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8fa2cf2177083dd59cf8e44ea4b6541764fbda69", - "reference": "8fa2cf2177083dd59cf8e44ea4b6541764fbda69", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c43ab685673fb6c8d84220c77897b1d6cdbe1d18", + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18", "shasum": "" }, "require": { @@ -3031,20 +3195,20 @@ "interoperability", "standards" ], - "time": "2019-05-22T12:23:29+00:00" + "time": "2019-09-17T09:54:03+00:00" }, { "name": "symfony/filesystem", - "version": "v4.3.1", + "version": "v4.4.4", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "bf2af40d738dec5e433faea7b00daa4431d0a4cf" + "reference": "266c9540b475f26122b61ef8b23dd9198f5d1cfd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/bf2af40d738dec5e433faea7b00daa4431d0a4cf", - "reference": "bf2af40d738dec5e433faea7b00daa4431d0a4cf", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/266c9540b475f26122b61ef8b23dd9198f5d1cfd", + "reference": "266c9540b475f26122b61ef8b23dd9198f5d1cfd", "shasum": "" }, "require": { @@ -3054,7 +3218,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3081,29 +3245,29 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2019-06-03T20:27:40+00:00" + "time": "2020-01-21T08:20:44+00:00" }, { "name": "symfony/finder", - "version": "v4.3.1", + "version": "v5.0.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "b3d4f4c0e4eadfdd8b296af9ca637cfbf51d8176" + "reference": "4176e7cb846fe08f32518b7e0ed8462e2db8d9bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/b3d4f4c0e4eadfdd8b296af9ca637cfbf51d8176", - "reference": "b3d4f4c0e4eadfdd8b296af9ca637cfbf51d8176", + "url": "https://api.github.com/repos/symfony/finder/zipball/4176e7cb846fe08f32518b7e0ed8462e2db8d9bb", + "reference": "4176e7cb846fe08f32518b7e0ed8462e2db8d9bb", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.2.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -3130,20 +3294,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2019-05-26T20:47:49+00:00" + "time": "2020-01-04T14:08:26+00:00" }, { "name": "symfony/flex", - "version": "v1.2.7", + "version": "v1.6.2", "source": { "type": "git", "url": "https://github.com/symfony/flex.git", - "reference": "8618b243d44bac71e4006062f245d807d84f7a6c" + "reference": "e4f5a2653ca503782a31486198bd1dd1c9a47f83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/flex/zipball/8618b243d44bac71e4006062f245d807d84f7a6c", - "reference": "8618b243d44bac71e4006062f245d807d84f7a6c", + "url": "https://api.github.com/repos/symfony/flex/zipball/e4f5a2653ca503782a31486198bd1dd1c9a47f83", + "reference": "e4f5a2653ca503782a31486198bd1dd1c9a47f83", "shasum": "" }, "require": { @@ -3152,14 +3316,14 @@ }, "require-dev": { "composer/composer": "^1.0.2", - "symfony/dotenv": "^3.4|^4.0", - "symfony/phpunit-bridge": "^3.4.19|^4.1.8", - "symfony/process": "^2.7|^3.0|^4.0" + "symfony/dotenv": "^3.4|^4.0|^5.0", + "symfony/phpunit-bridge": "^3.4.19|^4.1.8|^5.0", + "symfony/process": "^2.7|^3.0|^4.0|^5.0" }, "type": "composer-plugin", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.5-dev" }, "class": "Symfony\\Flex\\Flex" }, @@ -3179,36 +3343,38 @@ } ], "description": "Composer plugin for Symfony", - "time": "2019-06-15T07:15:42+00:00" + "time": "2020-01-30T12:06:45+00:00" }, { "name": "symfony/framework-bundle", - "version": "v4.3.1", + "version": "v4.4.4", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "fe407e6840d2b8f34c3fb67111e05c6d65319ef6" + "reference": "afc96daad6049cbed34312b34005d33fc670d022" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/fe407e6840d2b8f34c3fb67111e05c6d65319ef6", - "reference": "fe407e6840d2b8f34c3fb67111e05c6d65319ef6", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/afc96daad6049cbed34312b34005d33fc670d022", + "reference": "afc96daad6049cbed34312b34005d33fc670d022", "shasum": "" }, "require": { "ext-xml": "*", "php": "^7.1.3", - "symfony/cache": "~4.3", - "symfony/config": "~4.2", - "symfony/dependency-injection": "^4.3", - "symfony/filesystem": "~3.4|~4.0", - "symfony/finder": "~3.4|~4.0", - "symfony/http-foundation": "^4.3", - "symfony/http-kernel": "^4.3", + "symfony/cache": "^4.4|^5.0", + "symfony/config": "^4.3.4|^5.0", + "symfony/dependency-injection": "^4.4.1|^5.0.1", + "symfony/error-handler": "^4.4.1|^5.0.1", + "symfony/filesystem": "^3.4|^4.0|^5.0", + "symfony/finder": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/http-kernel": "^4.4", "symfony/polyfill-mbstring": "~1.0", - "symfony/routing": "^4.3" + "symfony/routing": "^4.4|^5.0" }, "conflict": { + "doctrine/persistence": "<1.3", "phpdocumentor/reflection-docblock": "<3.0", "phpdocumentor/type-resolver": "<0.2.1", "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", @@ -3216,50 +3382,57 @@ "symfony/browser-kit": "<4.3", "symfony/console": "<4.3", "symfony/dom-crawler": "<4.3", - "symfony/dotenv": "<4.2", - "symfony/form": "<4.3", - "symfony/messenger": "<4.3", + "symfony/dotenv": "<4.3.6", + "symfony/form": "<4.3.5", + "symfony/http-client": "<4.4", + "symfony/lock": "<4.4", + "symfony/mailer": "<4.4", + "symfony/messenger": "<4.4", + "symfony/mime": "<4.4", "symfony/property-info": "<3.4", - "symfony/serializer": "<4.2", + "symfony/security-bundle": "<4.4", + "symfony/serializer": "<4.4", "symfony/stopwatch": "<3.4", - "symfony/translation": "<4.3", + "symfony/translation": "<4.4", "symfony/twig-bridge": "<4.1.1", - "symfony/validator": "<4.1", - "symfony/workflow": "<4.3" + "symfony/twig-bundle": "<4.4", + "symfony/validator": "<4.4", + "symfony/web-profiler-bundle": "<4.4", + "symfony/workflow": "<4.3.6" }, "require-dev": { - "doctrine/annotations": "~1.0", + "doctrine/annotations": "~1.7", "doctrine/cache": "~1.0", - "fig/link-util": "^1.0", + "paragonie/sodium_compat": "^1.8", "phpdocumentor/reflection-docblock": "^3.0|^4.0", - "symfony/asset": "~3.4|~4.0", - "symfony/browser-kit": "^4.3", - "symfony/console": "^4.3", - "symfony/css-selector": "~3.4|~4.0", - "symfony/dom-crawler": "^4.3", - "symfony/expression-language": "~3.4|~4.0", - "symfony/form": "^4.3", - "symfony/http-client": "^4.3", - "symfony/lock": "~3.4|~4.0", - "symfony/mailer": "^4.3", - "symfony/messenger": "^4.3", - "symfony/mime": "^4.3", + "symfony/asset": "^3.4|^4.0|^5.0", + "symfony/browser-kit": "^4.3|^5.0", + "symfony/console": "^4.3.4|^5.0", + "symfony/css-selector": "^3.4|^4.0|^5.0", + "symfony/dom-crawler": "^4.3|^5.0", + "symfony/dotenv": "^4.3.6|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/form": "^4.3.5|^5.0", + "symfony/http-client": "^4.4|^5.0", + "symfony/lock": "^4.4|^5.0", + "symfony/mailer": "^4.4|^5.0", + "symfony/messenger": "^4.4|^5.0", + "symfony/mime": "^4.4|^5.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/process": "~3.4|~4.0", - "symfony/property-info": "~3.4|~4.0", - "symfony/security-csrf": "~3.4|~4.0", - "symfony/security-http": "~3.4|~4.0", - "symfony/serializer": "^4.3", - "symfony/stopwatch": "~3.4|~4.0", - "symfony/templating": "~3.4|~4.0", - "symfony/translation": "~4.2", - "symfony/twig-bundle": "~2.8|~3.2|~4.0", - "symfony/validator": "^4.1", - "symfony/var-dumper": "^4.3", - "symfony/web-link": "~3.4|~4.0", - "symfony/workflow": "^4.3", - "symfony/yaml": "~3.4|~4.0", - "twig/twig": "~1.34|~2.4" + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/property-info": "^3.4|^4.0|^5.0", + "symfony/security-csrf": "^3.4|^4.0|^5.0", + "symfony/security-http": "^3.4|^4.0|^5.0", + "symfony/serializer": "^4.4|^5.0", + "symfony/stopwatch": "^3.4|^4.0|^5.0", + "symfony/templating": "^3.4|^4.0|^5.0", + "symfony/translation": "^4.4|^5.0", + "symfony/twig-bundle": "^4.4|^5.0", + "symfony/validator": "^4.4|^5.0", + "symfony/web-link": "^4.4|^5.0", + "symfony/workflow": "^4.3.6|^5.0", + "symfony/yaml": "^3.4|^4.0|^5.0", + "twig/twig": "^1.41|^2.10|^3.0" }, "suggest": { "ext-apcu": "For best performance of the system caches", @@ -3274,7 +3447,7 @@ "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3301,35 +3474,35 @@ ], "description": "Symfony FrameworkBundle", "homepage": "https://symfony.com", - "time": "2019-06-06T08:35:06+00:00" + "time": "2020-01-30T16:24:07+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.3.1", + "version": "v5.0.4", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "b7e4945dd9b277cd24e93566e4da0a87956392a9" + "reference": "2832d8cffc3a91df550ac42bcdce602f8c08be3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/b7e4945dd9b277cd24e93566e4da0a87956392a9", - "reference": "b7e4945dd9b277cd24e93566e4da0a87956392a9", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/2832d8cffc3a91df550ac42bcdce602f8c08be3e", + "reference": "2832d8cffc3a91df550ac42bcdce602f8c08be3e", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/mime": "^4.3", + "php": "^7.2.5", + "symfony/mime": "^4.4|^5.0", "symfony/polyfill-mbstring": "~1.1" }, "require-dev": { "predis/predis": "~1.0", - "symfony/expression-language": "~3.4|~4.0" + "symfony/expression-language": "^4.4|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -3356,37 +3529,37 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2019-06-06T10:05:02+00:00" + "time": "2020-01-31T09:13:47+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.3.1", + "version": "v4.4.4", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "738ad561cd6a8d1c44ee1da941b2e628e264c429" + "reference": "62116a9c8fb15faabb158ad9cb785c353c2572e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/738ad561cd6a8d1c44ee1da941b2e628e264c429", - "reference": "738ad561cd6a8d1c44ee1da941b2e628e264c429", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/62116a9c8fb15faabb158ad9cb785c353c2572e5", + "reference": "62116a9c8fb15faabb158ad9cb785c353c2572e5", "shasum": "" }, "require": { "php": "^7.1.3", "psr/log": "~1.0", - "symfony/debug": "~3.4|~4.0", - "symfony/event-dispatcher": "^4.3", - "symfony/http-foundation": "^4.1.1", - "symfony/polyfill-ctype": "~1.8", + "symfony/error-handler": "^4.4", + "symfony/event-dispatcher": "^4.4", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-php73": "^1.9" }, "conflict": { "symfony/browser-kit": "<4.3", "symfony/config": "<3.4", + "symfony/console": ">=5", "symfony/dependency-injection": "<4.3", "symfony/translation": "<4.2", - "symfony/var-dumper": "<4.1.1", "twig/twig": "<1.34|<2.4,>=2" }, "provide": { @@ -3394,34 +3567,32 @@ }, "require-dev": { "psr/cache": "~1.0", - "symfony/browser-kit": "^4.3", - "symfony/config": "~3.4|~4.0", - "symfony/console": "~3.4|~4.0", - "symfony/css-selector": "~3.4|~4.0", - "symfony/dependency-injection": "^4.3", - "symfony/dom-crawler": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/finder": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0", - "symfony/routing": "~3.4|~4.0", - "symfony/stopwatch": "~3.4|~4.0", - "symfony/templating": "~3.4|~4.0", - "symfony/translation": "~4.2", - "symfony/translation-contracts": "^1.1", - "symfony/var-dumper": "^4.1.1", - "twig/twig": "^1.34|^2.4" + "symfony/browser-kit": "^4.3|^5.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/console": "^3.4|^4.0", + "symfony/css-selector": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^4.3|^5.0", + "symfony/dom-crawler": "^3.4|^4.0|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/finder": "^3.4|^4.0|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/routing": "^3.4|^4.0|^5.0", + "symfony/stopwatch": "^3.4|^4.0|^5.0", + "symfony/templating": "^3.4|^4.0|^5.0", + "symfony/translation": "^4.2|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "twig/twig": "^1.34|^2.4|^3.0" }, "suggest": { "symfony/browser-kit": "", "symfony/config": "", "symfony/console": "", - "symfony/dependency-injection": "", - "symfony/var-dumper": "" + "symfony/dependency-injection": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -3448,30 +3619,30 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2019-06-06T13:23:34+00:00" + "time": "2020-01-31T12:45:06+00:00" }, { "name": "symfony/inflector", - "version": "v4.3.1", + "version": "v5.0.4", "source": { "type": "git", "url": "https://github.com/symfony/inflector.git", - "reference": "889dc28cb6350ddb302fe9b8c796e4e6eb836856" + "reference": "e375603b6bd12e8e3aec3fc1b640ac18a4ef4cb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/inflector/zipball/889dc28cb6350ddb302fe9b8c796e4e6eb836856", - "reference": "889dc28cb6350ddb302fe9b8c796e4e6eb836856", + "url": "https://api.github.com/repos/symfony/inflector/zipball/e375603b6bd12e8e3aec3fc1b640ac18a4ef4cb2", + "reference": "e375603b6bd12e8e3aec3fc1b640ac18a4ef4cb2", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": "^7.2.5", "symfony/polyfill-ctype": "~1.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -3506,42 +3677,44 @@ "symfony", "words" ], - "time": "2019-05-30T09:28:08+00:00" + "time": "2020-01-04T14:08:26+00:00" }, { "name": "symfony/maker-bundle", - "version": "v1.11.6", + "version": "v1.14.3", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "d262c2cace4d9bca99137a84f6fc6ba909a17e02" + "reference": "c864e7f9b8d1e1f5f60acc3beda11299f637aded" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/d262c2cace4d9bca99137a84f6fc6ba909a17e02", - "reference": "d262c2cace4d9bca99137a84f6fc6ba909a17e02", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/c864e7f9b8d1e1f5f60acc3beda11299f637aded", + "reference": "c864e7f9b8d1e1f5f60acc3beda11299f637aded", "shasum": "" }, "require": { "doctrine/inflector": "^1.2", "nikic/php-parser": "^4.0", "php": "^7.0.8", - "symfony/config": "^3.4|^4.0", - "symfony/console": "^3.4|^4.0", - "symfony/dependency-injection": "^3.4|^4.0", - "symfony/filesystem": "^3.4|^4.0", - "symfony/finder": "^3.4|^4.0", - "symfony/framework-bundle": "^3.4|^4.0", - "symfony/http-kernel": "^3.4|^4.0" + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/console": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/filesystem": "^3.4|^4.0|^5.0", + "symfony/finder": "^3.4|^4.0|^5.0", + "symfony/framework-bundle": "^3.4|^4.0|^5.0", + "symfony/http-kernel": "^3.4|^4.0|^5.0" }, "require-dev": { - "allocine/twigcs": "^3.0", - "doctrine/doctrine-bundle": "^1.8", + "doctrine/doctrine-bundle": "^1.8|^2.0", "doctrine/orm": "^2.3", "friendsofphp/php-cs-fixer": "^2.8", - "symfony/phpunit-bridge": "^3.4|^4.0", - "symfony/process": "^3.4|^4.0", - "symfony/yaml": "^3.4|^4.0" + "friendsoftwig/twigcs": "^3.1.2", + "symfony/http-client": "^4.3|^5.0", + "symfony/phpunit-bridge": "^4.3|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/security-core": "^3.4|^4.0|^5.0", + "symfony/yaml": "^3.4|^4.0|^5.0" }, "type": "symfony-bundle", "extra": { @@ -3572,35 +3745,38 @@ "scaffold", "scaffolding" ], - "time": "2019-04-19T17:26:45+00:00" + "time": "2019-11-07T00:56:03+00:00" }, { "name": "symfony/mime", - "version": "v4.3.1", + "version": "v5.0.4", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "ec2c5565de60e03f33d4296a655e3273f0ad1f8b" + "reference": "2a3c7fee1f1a0961fa9cf360d5da553d05095e59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/ec2c5565de60e03f33d4296a655e3273f0ad1f8b", - "reference": "ec2c5565de60e03f33d4296a655e3273f0ad1f8b", + "url": "https://api.github.com/repos/symfony/mime/zipball/2a3c7fee1f1a0961fa9cf360d5da553d05095e59", + "reference": "2a3c7fee1f1a0961fa9cf360d5da553d05095e59", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": "^7.2.5", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" }, + "conflict": { + "symfony/mailer": "<4.4" + }, "require-dev": { - "egulias/email-validator": "^2.0", - "symfony/dependency-injection": "~3.4|^4.1" + "egulias/email-validator": "^2.1.10", + "symfony/dependency-injection": "^4.4|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -3631,27 +3807,26 @@ "mime", "mime-type" ], - "time": "2019-06-04T09:22:54+00:00" + "time": "2020-01-04T14:08:26+00:00" }, { "name": "symfony/orm-pack", - "version": "v1.0.6", + "version": "v1.0.8", "source": { "type": "git", "url": "https://github.com/symfony/orm-pack.git", - "reference": "36c2a928482dc5f05c5c1c1b947242ae03ff1335" + "reference": "c9bcc08102061f406dc908192c0f33524a675666" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/orm-pack/zipball/36c2a928482dc5f05c5c1c1b947242ae03ff1335", - "reference": "36c2a928482dc5f05c5c1c1b947242ae03ff1335", + "url": "https://api.github.com/repos/symfony/orm-pack/zipball/c9bcc08102061f406dc908192c0f33524a675666", + "reference": "c9bcc08102061f406dc908192c0f33524a675666", "shasum": "" }, "require": { - "doctrine/doctrine-bundle": "^1.6.10", - "doctrine/doctrine-migrations-bundle": "^1.3|^2.0", - "doctrine/orm": "^2.5.11", - "php": "^7.0" + "doctrine/doctrine-bundle": "*", + "doctrine/doctrine-migrations-bundle": "*", + "doctrine/orm": "*" }, "type": "symfony-pack", "notification-url": "https://packagist.org/downloads/", @@ -3659,20 +3834,20 @@ "MIT" ], "description": "A pack for the Doctrine ORM", - "time": "2019-01-16T09:49:15+00:00" + "time": "2020-02-10T18:03:48+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.11.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "82ebae02209c21113908c229e9883c419720738a" + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", - "reference": "82ebae02209c21113908c229e9883c419720738a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", "shasum": "" }, "require": { @@ -3684,7 +3859,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -3700,13 +3875,13 @@ "MIT" ], "authors": [ - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - }, { "name": "Gert de Pagter", "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for ctype functions", @@ -3717,20 +3892,20 @@ "polyfill", "portable" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.11.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "c766e95bec706cdd89903b1eda8afab7d7a6b7af" + "reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c766e95bec706cdd89903b1eda8afab7d7a6b7af", - "reference": "c766e95bec706cdd89903b1eda8afab7d7a6b7af", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6f9c239e61e1b0c9229a28ff89a812dc449c3d46", + "reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46", "shasum": "" }, "require": { @@ -3744,7 +3919,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -3760,13 +3935,13 @@ "MIT" ], "authors": [ - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - }, { "name": "Laurent Bassin", "email": "laurent@bassin.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", @@ -3779,20 +3954,20 @@ "portable", "shim" ], - "time": "2019-03-04T13:44:35+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.11.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" + "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", - "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7b4aab9743c30be783b73de055d24a39cf4b954f", + "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f", "shasum": "" }, "require": { @@ -3804,7 +3979,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -3838,20 +4013,20 @@ "portable", "shim" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-11-27T14:18:11+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.11.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c" + "reference": "66fea50f6cb37a35eea048d75a7d99a45b586038" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/ab50dcf166d5f577978419edd37aa2bb8eabce0c", - "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/66fea50f6cb37a35eea048d75a7d99a45b586038", + "reference": "66fea50f6cb37a35eea048d75a7d99a45b586038", "shasum": "" }, "require": { @@ -3860,7 +4035,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -3893,20 +4068,20 @@ "portable", "shim" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.11.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "d1fb4abcc0c47be136208ad9d68bf59f1ee17abd" + "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/d1fb4abcc0c47be136208ad9d68bf59f1ee17abd", - "reference": "d1fb4abcc0c47be136208ad9d68bf59f1ee17abd", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/4b0e2222c55a25b4541305a053013d5647d3a25f", + "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f", "shasum": "" }, "require": { @@ -3915,7 +4090,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -3951,7 +4126,7 @@ "portable", "shim" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-11-27T16:25:15+00:00" }, { "name": "symfony/profiler-pack", @@ -3983,24 +4158,24 @@ }, { "name": "symfony/property-access", - "version": "v4.3.1", + "version": "v5.0.4", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "18ea48862a39e364927e71b9e4942af3c1a1cb8c" + "reference": "18617a8c26b97a262f816c78765eb3cd91630e19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/18ea48862a39e364927e71b9e4942af3c1a1cb8c", - "reference": "18ea48862a39e364927e71b9e4942af3c1a1cb8c", + "url": "https://api.github.com/repos/symfony/property-access/zipball/18617a8c26b97a262f816c78765eb3cd91630e19", + "reference": "18617a8c26b97a262f816c78765eb3cd91630e19", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/inflector": "~3.4|~4.0" + "php": "^7.2.5", + "symfony/inflector": "^4.4|^5.0" }, "require-dev": { - "symfony/cache": "~3.4|~4.0" + "symfony/cache": "^4.4|^5.0" }, "suggest": { "psr/cache-implementation": "To cache access methods." @@ -4008,7 +4183,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -4046,38 +4221,38 @@ "property path", "reflection" ], - "time": "2019-06-06T10:05:02+00:00" + "time": "2020-01-04T14:08:26+00:00" }, { "name": "symfony/routing", - "version": "v4.3.1", + "version": "v5.0.4", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "9b31cd24f6ad2cebde6845f6daa9c6d69efe2465" + "reference": "7da33371d8ecfed6c9d93d87c73749661606f803" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/9b31cd24f6ad2cebde6845f6daa9c6d69efe2465", - "reference": "9b31cd24f6ad2cebde6845f6daa9c6d69efe2465", + "url": "https://api.github.com/repos/symfony/routing/zipball/7da33371d8ecfed6c9d93d87c73749661606f803", + "reference": "7da33371d8ecfed6c9d93d87c73749661606f803", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.2.5" }, "conflict": { - "symfony/config": "<4.2", - "symfony/dependency-injection": "<3.4", - "symfony/yaml": "<3.4" + "symfony/config": "<5.0", + "symfony/dependency-injection": "<4.4", + "symfony/yaml": "<4.4" }, "require-dev": { "doctrine/annotations": "~1.2", "psr/log": "~1.0", - "symfony/config": "~4.2", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/http-foundation": "~3.4|~4.0", - "symfony/yaml": "~3.4|~4.0" + "symfony/config": "^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/yaml": "^4.4|^5.0" }, "suggest": { "doctrine/annotations": "For using the annotation loader", @@ -4089,7 +4264,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -4122,64 +4297,63 @@ "uri", "url" ], - "time": "2019-06-05T09:16:20+00:00" + "time": "2020-01-04T14:08:26+00:00" }, { "name": "symfony/security-bundle", - "version": "v4.3.1", + "version": "v4.4.4", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "ce3826058a4b1b892bb3b60e6f5019b44b079ddd" + "reference": "7829cc34b8231cb8d10621cdf27d04bfdc600334" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/ce3826058a4b1b892bb3b60e6f5019b44b079ddd", - "reference": "ce3826058a4b1b892bb3b60e6f5019b44b079ddd", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/7829cc34b8231cb8d10621cdf27d04bfdc600334", + "reference": "7829cc34b8231cb8d10621cdf27d04bfdc600334", "shasum": "" }, "require": { "ext-xml": "*", "php": "^7.1.3", - "symfony/config": "^4.2", - "symfony/dependency-injection": "^4.2", - "symfony/http-kernel": "^4.3", - "symfony/security-core": "~4.3", - "symfony/security-csrf": "~4.2", - "symfony/security-guard": "~4.2", - "symfony/security-http": "^4.3" + "symfony/config": "^4.2|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/http-kernel": "^4.4", + "symfony/security-core": "^4.4", + "symfony/security-csrf": "^4.2|^5.0", + "symfony/security-guard": "^4.2|^5.0", + "symfony/security-http": "^4.4.3" }, "conflict": { "symfony/browser-kit": "<4.2", "symfony/console": "<3.4", - "symfony/framework-bundle": "<4.2", - "symfony/twig-bundle": "<4.2", - "symfony/var-dumper": "<3.4" + "symfony/framework-bundle": "<4.4", + "symfony/ldap": "<4.4", + "symfony/twig-bundle": "<4.4" }, "require-dev": { - "doctrine/doctrine-bundle": "~1.5", - "symfony/asset": "~3.4|~4.0", - "symfony/browser-kit": "~4.2", - "symfony/console": "~3.4|~4.0", - "symfony/css-selector": "~3.4|~4.0", - "symfony/dom-crawler": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/form": "~3.4|~4.0", - "symfony/framework-bundle": "~4.2", - "symfony/http-foundation": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0", - "symfony/translation": "~3.4|~4.0", - "symfony/twig-bridge": "~3.4|~4.0", - "symfony/twig-bundle": "~4.2", - "symfony/validator": "~3.4|~4.0", - "symfony/var-dumper": "~3.4|~4.0", - "symfony/yaml": "~3.4|~4.0", - "twig/twig": "~1.34|~2.4" + "doctrine/doctrine-bundle": "^1.5|^2.0", + "symfony/asset": "^3.4|^4.0|^5.0", + "symfony/browser-kit": "^4.2|^5.0", + "symfony/console": "^3.4|^4.0|^5.0", + "symfony/css-selector": "^3.4|^4.0|^5.0", + "symfony/dom-crawler": "^3.4|^4.0|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/form": "^3.4|^4.0|^5.0", + "symfony/framework-bundle": "^4.4|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/serializer": "^4.4|^5.0", + "symfony/translation": "^3.4|^4.0|^5.0", + "symfony/twig-bridge": "^3.4|^4.0|^5.0", + "symfony/twig-bundle": "^4.4|^5.0", + "symfony/validator": "^3.4|^4.0|^5.0", + "symfony/yaml": "^3.4|^4.0|^5.0", + "twig/twig": "^1.41|^2.10|^3.0" }, "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -4206,39 +4380,40 @@ ], "description": "Symfony SecurityBundle", "homepage": "https://symfony.com", - "time": "2019-05-30T16:10:05+00:00" + "time": "2020-01-27T10:02:23+00:00" }, { "name": "symfony/security-core", - "version": "v4.3.1", + "version": "v4.4.4", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "7c1fc94098ce58452d28af4006b6870f8839d075" + "reference": "d2550b4ecd63f612763e0af2cbcb1a69a700fe99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/7c1fc94098ce58452d28af4006b6870f8839d075", - "reference": "7c1fc94098ce58452d28af4006b6870f8839d075", + "url": "https://api.github.com/repos/symfony/security-core/zipball/d2550b4ecd63f612763e0af2cbcb1a69a700fe99", + "reference": "d2550b4ecd63f612763e0af2cbcb1a69a700fe99", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/event-dispatcher-contracts": "^1.1", - "symfony/service-contracts": "^1.1" + "symfony/event-dispatcher-contracts": "^1.1|^2", + "symfony/service-contracts": "^1.1.6|^2" }, "conflict": { - "symfony/event-dispatcher": "<4.3", + "symfony/event-dispatcher": "<4.3|>=5", + "symfony/ldap": "<4.4", "symfony/security-guard": "<4.3" }, "require-dev": { "psr/container": "^1.0", "psr/log": "~1.0", "symfony/event-dispatcher": "^4.3", - "symfony/expression-language": "~3.4|~4.0", - "symfony/http-foundation": "~3.4|~4.0", - "symfony/ldap": "~3.4|~4.0", - "symfony/validator": "~3.4|~4.0" + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/ldap": "^4.4|^5.0", + "symfony/validator": "^3.4.31|^4.3.4|^5.0" }, "suggest": { "psr/container-implementation": "To instantiate the Security class", @@ -4251,7 +4426,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -4278,31 +4453,31 @@ ], "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", - "time": "2019-06-03T20:27:40+00:00" + "time": "2020-01-31T09:11:17+00:00" }, { "name": "symfony/security-csrf", - "version": "v4.3.1", + "version": "v5.0.4", "source": { "type": "git", "url": "https://github.com/symfony/security-csrf.git", - "reference": "e7e3509ef7de66ea4970c75f9a0a72bf132d452e" + "reference": "65066f7e0f6e38a8c5507c706e86e7a52fd7ff3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-csrf/zipball/e7e3509ef7de66ea4970c75f9a0a72bf132d452e", - "reference": "e7e3509ef7de66ea4970c75f9a0a72bf132d452e", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/65066f7e0f6e38a8c5507c706e86e7a52fd7ff3e", + "reference": "65066f7e0f6e38a8c5507c706e86e7a52fd7ff3e", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/security-core": "~3.4|~4.0" + "php": "^7.2.5", + "symfony/security-core": "^4.4|^5.0" }, "conflict": { - "symfony/http-foundation": "<3.4" + "symfony/http-foundation": "<4.4" }, "require-dev": { - "symfony/http-foundation": "~3.4|~4.0" + "symfony/http-foundation": "^4.4|^5.0" }, "suggest": { "symfony/http-foundation": "For using the class SessionTokenStorage." @@ -4310,7 +4485,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -4337,26 +4512,26 @@ ], "description": "Symfony Security Component - CSRF Library", "homepage": "https://symfony.com", - "time": "2019-05-30T16:10:05+00:00" + "time": "2020-01-04T14:08:26+00:00" }, { "name": "symfony/security-guard", - "version": "v4.3.1", + "version": "v4.4.4", "source": { "type": "git", "url": "https://github.com/symfony/security-guard.git", - "reference": "2177390e39f49e5ae0ac5765982fa32a4aeb536f" + "reference": "f457f2d6d7392259b1ede1d036a26b6c1fa20202" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-guard/zipball/2177390e39f49e5ae0ac5765982fa32a4aeb536f", - "reference": "2177390e39f49e5ae0ac5765982fa32a4aeb536f", + "url": "https://api.github.com/repos/symfony/security-guard/zipball/f457f2d6d7392259b1ede1d036a26b6c1fa20202", + "reference": "f457f2d6d7392259b1ede1d036a26b6c1fa20202", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/security-core": "~3.4.22|^4.2.3", - "symfony/security-http": "^4.3" + "symfony/security-core": "^3.4.22|^4.2.3|^5.0", + "symfony/security-http": "^4.4.1" }, "require-dev": { "psr/log": "~1.0" @@ -4364,7 +4539,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -4391,36 +4566,37 @@ ], "description": "Symfony Security Component - Guard", "homepage": "https://symfony.com", - "time": "2019-05-30T16:10:05+00:00" + "time": "2020-01-08T17:29:02+00:00" }, { "name": "symfony/security-http", - "version": "v4.3.1", + "version": "v4.4.4", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "8e8d92dc843be9855d6c1b1dbbe95d0477d1dfc6" + "reference": "736d09554f78f3444f5aeed3d18a928c7a8a53fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/8e8d92dc843be9855d6c1b1dbbe95d0477d1dfc6", - "reference": "8e8d92dc843be9855d6c1b1dbbe95d0477d1dfc6", + "url": "https://api.github.com/repos/symfony/security-http/zipball/736d09554f78f3444f5aeed3d18a928c7a8a53fb", + "reference": "736d09554f78f3444f5aeed3d18a928c7a8a53fb", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/http-foundation": "~3.4|~4.0", - "symfony/http-kernel": "^4.3", - "symfony/property-access": "~3.4|~4.0", - "symfony/security-core": "^4.3" + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/http-kernel": "^4.4", + "symfony/property-access": "^3.4|^4.0|^5.0", + "symfony/security-core": "^4.4" }, "conflict": { + "symfony/event-dispatcher": ">=5", "symfony/security-csrf": "<3.4.11|~4.0,<4.0.11" }, "require-dev": { "psr/log": "~1.0", - "symfony/routing": "~3.4|~4.0", - "symfony/security-csrf": "^3.4.11|^4.0.11" + "symfony/routing": "^3.4|^4.0|^5.0", + "symfony/security-csrf": "^3.4.11|^4.0.11|^5.0" }, "suggest": { "symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs", @@ -4429,7 +4605,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -4456,33 +4632,33 @@ ], "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", - "time": "2019-06-05T13:25:51+00:00" + "time": "2020-01-31T09:11:17+00:00" }, { "name": "symfony/service-contracts", - "version": "v1.1.2", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "191afdcb5804db960d26d8566b7e9a2843cab3a0" + "reference": "144c5e51266b281231e947b51223ba14acf1a749" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/191afdcb5804db960d26d8566b7e9a2843cab3a0", - "reference": "191afdcb5804db960d26d8566b7e9a2843cab3a0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749", + "reference": "144c5e51266b281231e947b51223ba14acf1a749", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.2.5", + "psr/container": "^1.0" }, "suggest": { - "psr/container": "", "symfony/service-implementation": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -4514,30 +4690,30 @@ "interoperability", "standards" ], - "time": "2019-05-28T07:50:59+00:00" + "time": "2019-11-18T17:27:11+00:00" }, { "name": "symfony/stopwatch", - "version": "v4.3.1", + "version": "v5.0.4", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "6b100e9309e8979cf1978ac1778eb155c1f7d93b" + "reference": "5d9add8034135b9a5f7b101d1e42c797e7f053e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/6b100e9309e8979cf1978ac1778eb155c1f7d93b", - "reference": "6b100e9309e8979cf1978ac1778eb155c1f7d93b", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5d9add8034135b9a5f7b101d1e42c797e7f053e4", + "reference": "5d9add8034135b9a5f7b101d1e42c797e7f053e4", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/service-contracts": "^1.0" + "php": "^7.2.5", + "symfony/service-contracts": "^1.0|^2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -4564,30 +4740,31 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2019-05-27T08:16:38+00:00" + "time": "2020-01-04T14:08:26+00:00" }, { "name": "symfony/translation", - "version": "v4.3.3", + "version": "v4.4.4", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "4e3e39cc485304f807622bdc64938e4633396406" + "reference": "f5d2ac46930238b30a9c2f1b17c905f3697d808c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/4e3e39cc485304f807622bdc64938e4633396406", - "reference": "4e3e39cc485304f807622bdc64938e4633396406", + "url": "https://api.github.com/repos/symfony/translation/zipball/f5d2ac46930238b30a9c2f1b17c905f3697d808c", + "reference": "f5d2ac46930238b30a9c2f1b17c905f3697d808c", "shasum": "" }, "require": { "php": "^7.1.3", "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^1.1.2" + "symfony/translation-contracts": "^1.1.6|^2" }, "conflict": { "symfony/config": "<3.4", "symfony/dependency-injection": "<3.4", + "symfony/http-kernel": "<4.4", "symfony/yaml": "<3.4" }, "provide": { @@ -4595,15 +4772,14 @@ }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/console": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/http-kernel": "~3.4|~4.0", - "symfony/intl": "~3.4|~4.0", - "symfony/service-contracts": "^1.1.2", - "symfony/var-dumper": "~3.4|~4.0", - "symfony/yaml": "~3.4|~4.0" + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/console": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/finder": "~2.8|~3.0|~4.0|^5.0", + "symfony/http-kernel": "^4.4", + "symfony/intl": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1.2|^2", + "symfony/yaml": "^3.4|^4.0|^5.0" }, "suggest": { "psr/log-implementation": "To use logging capability in translator", @@ -4613,7 +4789,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -4640,24 +4816,24 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2019-07-18T10:34:59+00:00" + "time": "2020-01-15T13:29:06+00:00" }, { "name": "symfony/translation-contracts", - "version": "v1.1.2", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "93597ce975d91c52ebfaca1253343cd9ccb7916d" + "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/93597ce975d91c52ebfaca1253343cd9ccb7916d", - "reference": "93597ce975d91c52ebfaca1253343cd9ccb7916d", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/8cc682ac458d75557203b2f2f14b0b92e1c744ed", + "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.2.5" }, "suggest": { "symfony/translation-implementation": "" @@ -4665,7 +4841,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -4697,56 +4873,61 @@ "interoperability", "standards" ], - "time": "2019-05-27T08:16:38+00:00" + "time": "2019-11-18T17:27:11+00:00" }, { "name": "symfony/twig-bridge", - "version": "v4.3.1", + "version": "v4.4.4", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "52aa76480b775be0f6465b90ca9e3c2dccc8f3cd" + "reference": "d5f3e83e2cc2fcdd60c351b5be1beb9533cf698c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/52aa76480b775be0f6465b90ca9e3c2dccc8f3cd", - "reference": "52aa76480b775be0f6465b90ca9e3c2dccc8f3cd", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/d5f3e83e2cc2fcdd60c351b5be1beb9533cf698c", + "reference": "d5f3e83e2cc2fcdd60c351b5be1beb9533cf698c", "shasum": "" }, "require": { "php": "^7.1.3", - "twig/twig": "^1.41|^2.10" + "symfony/translation-contracts": "^1.1|^2", + "twig/twig": "^1.41|^2.10|^3.0" }, "conflict": { "symfony/console": "<3.4", - "symfony/form": "<4.3", + "symfony/form": "<4.4", "symfony/http-foundation": "<4.3", "symfony/translation": "<4.2", "symfony/workflow": "<4.3" }, "require-dev": { - "egulias/email-validator": "^2.0", - "symfony/asset": "~3.4|~4.0", - "symfony/console": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/finder": "~3.4|~4.0", - "symfony/form": "^4.3", - "symfony/http-foundation": "~4.3", - "symfony/http-kernel": "~3.4|~4.0", - "symfony/mime": "~4.3", + "egulias/email-validator": "^2.1.10", + "symfony/asset": "^3.4|^4.0|^5.0", + "symfony/console": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/error-handler": "^4.4|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/finder": "^3.4|^4.0|^5.0", + "symfony/form": "^4.3.5", + "symfony/http-foundation": "^4.3|^5.0", + "symfony/http-kernel": "^4.4", + "symfony/mime": "^4.3|^5.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/routing": "~3.4|~4.0", - "symfony/security-acl": "~2.8|~3.0", - "symfony/security-csrf": "~3.4|~4.0", - "symfony/security-http": "~3.4|~4.0", - "symfony/stopwatch": "~3.4|~4.0", - "symfony/templating": "~3.4|~4.0", - "symfony/translation": "^4.2.1", - "symfony/var-dumper": "~3.4|~4.0", - "symfony/web-link": "~3.4|~4.0", - "symfony/workflow": "~4.3", - "symfony/yaml": "~3.4|~4.0" + "symfony/routing": "^3.4|^4.0|^5.0", + "symfony/security-acl": "^2.8|^3.0", + "symfony/security-core": "^3.0|^4.0|^5.0", + "symfony/security-csrf": "^3.4|^4.0|^5.0", + "symfony/security-http": "^3.4|^4.0|^5.0", + "symfony/stopwatch": "^3.4|^4.0|^5.0", + "symfony/templating": "^3.4|^4.0|^5.0", + "symfony/translation": "^4.2.1|^5.0", + "symfony/web-link": "^4.4|^5.0", + "symfony/workflow": "^4.3|^5.0", + "symfony/yaml": "^3.4|^4.0|^5.0", + "twig/cssinliner-extra": "^2.12", + "twig/inky-extra": "^2.12", + "twig/markdown-extra": "^2.12" }, "suggest": { "symfony/asset": "For using the AssetExtension", @@ -4768,7 +4949,7 @@ "type": "symfony-bridge", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -4795,56 +4976,55 @@ ], "description": "Symfony Twig Bridge", "homepage": "https://symfony.com", - "time": "2019-06-01T07:11:44+00:00" + "time": "2020-01-08T17:29:02+00:00" }, { "name": "symfony/twig-bundle", - "version": "v4.3.1", + "version": "v4.4.4", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "b8e1c193a474b97b608de74fe0a01214678bfd89" + "reference": "d3e3e46e9e683e946746219570299ba07506260a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/b8e1c193a474b97b608de74fe0a01214678bfd89", - "reference": "b8e1c193a474b97b608de74fe0a01214678bfd89", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/d3e3e46e9e683e946746219570299ba07506260a", + "reference": "d3e3e46e9e683e946746219570299ba07506260a", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/config": "~4.2", - "symfony/http-foundation": "~4.3", - "symfony/http-kernel": "~4.1", + "symfony/http-foundation": "^4.3|^5.0", + "symfony/http-kernel": "^4.4", "symfony/polyfill-ctype": "~1.8", - "symfony/twig-bridge": "^4.3", - "twig/twig": "~1.41|~2.10" + "symfony/twig-bridge": "^4.4|^5.0", + "twig/twig": "^1.41|^2.10|^3.0" }, "conflict": { "symfony/dependency-injection": "<4.1", - "symfony/framework-bundle": "<4.3", + "symfony/framework-bundle": "<4.4", "symfony/translation": "<4.2" }, "require-dev": { - "doctrine/annotations": "~1.0", + "doctrine/annotations": "~1.7", "doctrine/cache": "~1.0", - "symfony/asset": "~3.4|~4.0", - "symfony/dependency-injection": "^4.2.5", - "symfony/expression-language": "~3.4|~4.0", - "symfony/finder": "~3.4|~4.0", - "symfony/form": "~3.4|~4.0", - "symfony/framework-bundle": "~4.3", - "symfony/routing": "~3.4|~4.0", - "symfony/stopwatch": "~3.4|~4.0", - "symfony/templating": "~3.4|~4.0", - "symfony/translation": "^4.2", - "symfony/web-link": "~3.4|~4.0", - "symfony/yaml": "~3.4|~4.0" + "symfony/asset": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^4.2.5|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/finder": "^3.4|^4.0|^5.0", + "symfony/form": "^3.4|^4.0|^5.0", + "symfony/framework-bundle": "^4.4|^5.0", + "symfony/routing": "^3.4|^4.0|^5.0", + "symfony/stopwatch": "^3.4|^4.0|^5.0", + "symfony/templating": "^3.4|^4.0|^5.0", + "symfony/translation": "^4.2|^5.0", + "symfony/web-link": "^3.4|^4.0|^5.0", + "symfony/yaml": "^3.4|^4.0|^5.0" }, "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -4871,59 +5051,59 @@ ], "description": "Symfony TwigBundle", "homepage": "https://symfony.com", - "time": "2019-05-30T16:10:05+00:00" + "time": "2020-01-04T13:00:46+00:00" }, { "name": "symfony/validator", - "version": "v4.3.1", + "version": "v4.4.4", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "ea74d2843fd8a9f2d4800136c985d13da586a405" + "reference": "eb3e15de5c63873ca6e2a88b56a029f7be4c5953" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/ea74d2843fd8a9f2d4800136c985d13da586a405", - "reference": "ea74d2843fd8a9f2d4800136c985d13da586a405", + "url": "https://api.github.com/repos/symfony/validator/zipball/eb3e15de5c63873ca6e2a88b56a029f7be4c5953", + "reference": "eb3e15de5c63873ca6e2a88b56a029f7be4c5953", "shasum": "" }, "require": { "php": "^7.1.3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^1.1" + "symfony/translation-contracts": "^1.1|^2" }, "conflict": { + "doctrine/lexer": "<1.0.2", "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", "symfony/dependency-injection": "<3.4", - "symfony/http-kernel": "<3.4", + "symfony/http-kernel": "<4.4", "symfony/intl": "<4.3", - "symfony/translation": "<4.2", + "symfony/translation": ">=5.0", "symfony/yaml": "<3.4" }, "require-dev": { - "doctrine/annotations": "~1.0", + "doctrine/annotations": "~1.7", "doctrine/cache": "~1.0", - "egulias/email-validator": "^1.2.8|~2.0", - "symfony/cache": "~3.4|~4.0", - "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/http-client": "^4.3", - "symfony/http-foundation": "~4.1", - "symfony/http-kernel": "~3.4|~4.0", - "symfony/intl": "^4.3", - "symfony/property-access": "~3.4|~4.0", - "symfony/property-info": "~3.4|~4.0", - "symfony/translation": "~4.2", - "symfony/var-dumper": "~3.4|~4.0", - "symfony/yaml": "~3.4|~4.0" + "egulias/email-validator": "^2.1.10", + "symfony/cache": "^3.4|^4.0|^5.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-client": "^4.3|^5.0", + "symfony/http-foundation": "^4.1|^5.0", + "symfony/http-kernel": "^4.4", + "symfony/intl": "^4.3|^5.0", + "symfony/property-access": "^3.4|^4.0|^5.0", + "symfony/property-info": "^3.4|^4.0|^5.0", + "symfony/translation": "^4.2", + "symfony/yaml": "^3.4|^4.0|^5.0" }, "suggest": { "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.", - "doctrine/cache": "For using the default cached annotation reader and metadata cache.", + "doctrine/cache": "For using the default cached annotation reader.", "egulias/email-validator": "Strict (RFC compliant) email validation", - "psr/cache-implementation": "For using the metadata cache.", + "psr/cache-implementation": "For using the mapping cache.", "symfony/config": "", "symfony/expression-language": "For using the Expression validator", "symfony/http-foundation": "", @@ -4936,7 +5116,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -4963,36 +5143,35 @@ ], "description": "Symfony Validator Component", "homepage": "https://symfony.com", - "time": "2019-06-03T20:27:40+00:00" + "time": "2020-01-31T09:11:17+00:00" }, { "name": "symfony/var-dumper", - "version": "v4.3.1", + "version": "v5.0.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "f974f448154928d2b5fb7c412bd23b81d063f34b" + "reference": "923591cfb78a935f0c98968fedfad05bfda9d01f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/f974f448154928d2b5fb7c412bd23b81d063f34b", - "reference": "f974f448154928d2b5fb7c412bd23b81d063f34b", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/923591cfb78a935f0c98968fedfad05bfda9d01f", + "reference": "923591cfb78a935f0c98968fedfad05bfda9d01f", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php72": "~1.5" + "php": "^7.2.5", + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", - "symfony/console": "<3.4" + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<4.4" }, "require-dev": { "ext-iconv": "*", - "symfony/console": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0", - "twig/twig": "~1.34|~2.4" + "symfony/console": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "twig/twig": "^2.4|^3.0" }, "suggest": { "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", @@ -5005,7 +5184,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -5039,32 +5218,32 @@ "debug", "dump" ], - "time": "2019-06-05T02:08:12+00:00" + "time": "2020-01-25T15:56:29+00:00" }, { "name": "symfony/var-exporter", - "version": "v4.3.1", + "version": "v5.0.4", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "2b7c857d553423b2317ac0741fb2581d9bfd8fb7" + "reference": "960f9ac0fdbd642461ed29d7717aeb2a94d428b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/2b7c857d553423b2317ac0741fb2581d9bfd8fb7", - "reference": "2b7c857d553423b2317ac0741fb2581d9bfd8fb7", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/960f9ac0fdbd642461ed29d7717aeb2a94d428b9", + "reference": "960f9ac0fdbd642461ed29d7717aeb2a94d428b9", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.2.5" }, "require-dev": { - "symfony/var-dumper": "^4.1.1" + "symfony/var-dumper": "^4.4|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -5099,46 +5278,46 @@ "instantiate", "serialize" ], - "time": "2019-04-10T19:42:49+00:00" + "time": "2020-01-04T14:08:26+00:00" }, { "name": "symfony/web-profiler-bundle", - "version": "v4.3.1", + "version": "v5.0.4", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "ca3a3c8558bc641df7c8c2c546381ccd78d0777a" + "reference": "8f4831567fc39bbe42af415a14a6039621349787" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/ca3a3c8558bc641df7c8c2c546381ccd78d0777a", - "reference": "ca3a3c8558bc641df7c8c2c546381ccd78d0777a", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/8f4831567fc39bbe42af415a14a6039621349787", + "reference": "8f4831567fc39bbe42af415a14a6039621349787", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/config": "^4.2", - "symfony/http-kernel": "^4.3", - "symfony/routing": "~3.4|~4.0", - "symfony/twig-bundle": "~4.2", - "symfony/var-dumper": "~3.4|~4.0", - "twig/twig": "^1.41|^2.10" + "php": "^7.2.5", + "symfony/config": "^4.4|^5.0", + "symfony/framework-bundle": "^4.4|^5.0", + "symfony/http-kernel": "^4.4|^5.0", + "symfony/routing": "^4.4|^5.0", + "symfony/twig-bundle": "^4.4|^5.0", + "twig/twig": "^2.10|^3.0" }, "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/form": "<4.3", - "symfony/messenger": "<4.2", - "symfony/var-dumper": "<3.4" + "symfony/form": "<4.4", + "symfony/messenger": "<4.4" }, "require-dev": { - "symfony/console": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/stopwatch": "~3.4|~4.0" + "symfony/browser-kit": "^4.4|^5.0", + "symfony/console": "^4.4|^5.0", + "symfony/css-selector": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/stopwatch": "^4.4|^5.0" }, "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -5165,20 +5344,20 @@ ], "description": "Symfony WebProfilerBundle", "homepage": "https://symfony.com", - "time": "2019-05-30T16:10:05+00:00" + "time": "2020-01-23T11:07:12+00:00" }, { "name": "symfony/yaml", - "version": "v4.3.1", + "version": "v4.4.4", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "c60ecf5ba842324433b46f58dc7afc4487dbab99" + "reference": "cd014e425b3668220adb865f53bff64b3ad21767" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/c60ecf5ba842324433b46f58dc7afc4487dbab99", - "reference": "c60ecf5ba842324433b46f58dc7afc4487dbab99", + "url": "https://api.github.com/repos/symfony/yaml/zipball/cd014e425b3668220adb865f53bff64b3ad21767", + "reference": "cd014e425b3668220adb865f53bff64b3ad21767", "shasum": "" }, "require": { @@ -5189,7 +5368,7 @@ "symfony/console": "<3.4" }, "require-dev": { - "symfony/console": "~3.4|~4.0" + "symfony/console": "^3.4|^4.0|^5.0" }, "suggest": { "symfony/console": "For validating YAML files using the lint command" @@ -5197,7 +5376,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -5224,42 +5403,38 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2019-04-06T14:04:46+00:00" + "time": "2020-01-21T11:12:16+00:00" }, { "name": "twig/twig", - "version": "v2.11.3", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "699ed2342557c88789a15402de5eb834dedd6792" + "reference": "5e6df0763a83dab0b73c1e803342fc0315f63ff5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/699ed2342557c88789a15402de5eb834dedd6792", - "reference": "699ed2342557c88789a15402de5eb834dedd6792", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/5e6df0763a83dab0b73c1e803342fc0315f63ff5", + "reference": "5e6df0763a83dab0b73c1e803342fc0315f63ff5", "shasum": "" }, "require": { - "php": "^7.0", + "php": "^7.2.5", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { "psr/container": "^1.0", - "symfony/debug": "^2.7", - "symfony/phpunit-bridge": "^3.4.19|^4.1.8|^5.0" + "symfony/phpunit-bridge": "^4.4|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.11-dev" + "dev-master": "3.0-dev" } }, "autoload": { - "psr-0": { - "Twig_": "lib/" - }, "psr-4": { "Twig\\": "src/" } @@ -5275,15 +5450,14 @@ "homepage": "http://fabien.potencier.org", "role": "Lead Developer" }, + { + "name": "Twig Team", + "role": "Contributors" + }, { "name": "Armin Ronacher", "email": "armin.ronacher@active-4.com", "role": "Project Founder" - }, - { - "name": "Twig Team", - "homepage": "https://twig.symfony.com/contributors", - "role": "Contributors" } ], "description": "Twig, the flexible, fast, and secure template language for PHP", @@ -5291,31 +5465,34 @@ "keywords": [ "templating" ], - "time": "2019-06-18T15:37:11+00:00" + "time": "2020-02-11T06:03:57+00:00" }, { "name": "zendframework/zend-code", - "version": "3.3.1", + "version": "3.4.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-code.git", - "reference": "c21db169075c6ec4b342149f446e7b7b724f95eb" + "reference": "268040548f92c2bfcba164421c1add2ba43abaaa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-code/zipball/c21db169075c6ec4b342149f446e7b7b724f95eb", - "reference": "c21db169075c6ec4b342149f446e7b7b724f95eb", + "url": "https://api.github.com/repos/zendframework/zend-code/zipball/268040548f92c2bfcba164421c1add2ba43abaaa", + "reference": "268040548f92c2bfcba164421c1add2ba43abaaa", "shasum": "" }, "require": { "php": "^7.1", "zendframework/zend-eventmanager": "^2.6 || ^3.0" }, + "conflict": { + "phpspec/prophecy": "<1.9.0" + }, "require-dev": { - "doctrine/annotations": "~1.0", + "doctrine/annotations": "^1.7", "ext-phar": "*", - "phpunit/phpunit": "^6.2.3", - "zendframework/zend-coding-standard": "^1.0.0", + "phpunit/phpunit": "^7.5.16 || ^8.4", + "zendframework/zend-coding-standard": "^1.0", "zendframework/zend-stdlib": "^2.7 || ^3.0" }, "suggest": { @@ -5325,8 +5502,9 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3.x-dev", - "dev-develop": "3.4.x-dev" + "dev-master": "3.4.x-dev", + "dev-develop": "3.5.x-dev", + "dev-dev-4.0": "4.0.x-dev" } }, "autoload": { @@ -5338,13 +5516,14 @@ "license": [ "BSD-3-Clause" ], - "description": "provides facilities to generate arbitrary code using an object oriented interface", - "homepage": "https://github.com/zendframework/zend-code", + "description": "Extensions to the PHP Reflection API, static code scanning, and code generation", "keywords": [ + "ZendFramework", "code", - "zf2" + "zf" ], - "time": "2018-08-13T20:36:59+00:00" + "abandoned": "laminas/laminas-code", + "time": "2019-12-10T19:21:15+00:00" }, { "name": "zendframework/zend-eventmanager", @@ -5398,34 +5577,39 @@ "events", "zf2" ], + "abandoned": "laminas/laminas-eventmanager", "time": "2018-04-25T15:33:34+00:00" } ], "packages-dev": [ { "name": "doctrine/data-fixtures", - "version": "v1.3.1", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/doctrine/data-fixtures.git", - "reference": "3a1e2c3c600e615a2dffe56d4ca0875cc5233e0a" + "reference": "39e9777c9089351a468f780b01cffa3cb0a42907" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/3a1e2c3c600e615a2dffe56d4ca0875cc5233e0a", - "reference": "3a1e2c3c600e615a2dffe56d4ca0875cc5233e0a", + "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/39e9777c9089351a468f780b01cffa3cb0a42907", + "reference": "39e9777c9089351a468f780b01cffa3cb0a42907", "shasum": "" }, "require": { - "doctrine/common": "~2.2", - "php": "^7.1" + "doctrine/common": "^2.11", + "doctrine/persistence": "^1.3.3", + "php": "^7.2" }, "conflict": { "doctrine/phpcr-odm": "<1.3.0" }, "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "doctrine/coding-standard": "^6.0", "doctrine/dbal": "^2.5.4", - "doctrine/orm": "^2.5.4", + "doctrine/mongodb-odm": "^1.3.0", + "doctrine/orm": "^2.7.0", "phpunit/phpunit": "^7.0" }, "suggest": { @@ -5437,7 +5621,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "1.4.x-dev" } }, "autoload": { @@ -5460,39 +5644,42 @@ "keywords": [ "database" ], - "time": "2018-03-20T09:06:36+00:00" + "time": "2020-01-17T11:11:28+00:00" }, { "name": "doctrine/doctrine-fixtures-bundle", - "version": "3.2.2", + "version": "3.3.0", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineFixturesBundle.git", - "reference": "90e4a4f968b2dae40e290a6ee516957af043f16c" + "reference": "8f07fcfdac7f3591f3c4bf13a50cbae05f65ed70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/90e4a4f968b2dae40e290a6ee516957af043f16c", - "reference": "90e4a4f968b2dae40e290a6ee516957af043f16c", + "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/8f07fcfdac7f3591f3c4bf13a50cbae05f65ed70", + "reference": "8f07fcfdac7f3591f3c4bf13a50cbae05f65ed70", "shasum": "" }, "require": { "doctrine/data-fixtures": "^1.3", - "doctrine/doctrine-bundle": "^1.6", + "doctrine/doctrine-bundle": "^1.11|^2.0", "doctrine/orm": "^2.6.0", "php": "^7.1", - "symfony/doctrine-bridge": "~3.4|^4.1", - "symfony/framework-bundle": "^3.4|^4.1" + "symfony/config": "^3.4|^4.3|^5.0", + "symfony/console": "^3.4|^4.3|^5.0", + "symfony/dependency-injection": "^3.4|^4.3|^5.0", + "symfony/doctrine-bridge": "^3.4|^4.1|^5.0", + "symfony/http-kernel": "^3.4|^4.3|^5.0" }, "require-dev": { "doctrine/coding-standard": "^6.0", "phpunit/phpunit": "^7.4", - "symfony/phpunit-bridge": "^4.1" + "symfony/phpunit-bridge": "^4.1|^5.0" }, "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "3.2.x-dev" + "dev-master": "3.3.x-dev" } }, "autoload": { @@ -5506,16 +5693,16 @@ ], "authors": [ { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Doctrine Project", "homepage": "http://www.doctrine-project.org" }, { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" } ], "description": "Symfony DoctrineFixturesBundle", @@ -5524,32 +5711,32 @@ "Fixture", "persistence" ], - "time": "2019-06-12T12:03:37+00:00" + "time": "2019-11-13T15:46:58+00:00" }, { "name": "symfony/dotenv", - "version": "v4.3.1", + "version": "v4.4.4", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "efd677abff68ea6fcfd9c60dbdacb96d0d97b382" + "reference": "b74a1638f53e3c65e4bbfc2a03c23fdc400fd243" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/efd677abff68ea6fcfd9c60dbdacb96d0d97b382", - "reference": "efd677abff68ea6fcfd9c60dbdacb96d0d97b382", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/b74a1638f53e3c65e4bbfc2a03c23fdc400fd243", + "reference": "b74a1638f53e3c65e4bbfc2a03c23fdc400fd243", "shasum": "" }, "require": { "php": "^7.1.3" }, "require-dev": { - "symfony/process": "~3.4|~4.0" + "symfony/process": "^3.4.2|^4.0|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -5581,20 +5768,20 @@ "env", "environment" ], - "time": "2019-05-07T09:02:05+00:00" + "time": "2020-01-08T17:29:02+00:00" }, { "name": "symfony/thanks", - "version": "v1.1.0", + "version": "v1.2.5", "source": { "type": "git", "url": "https://github.com/symfony/thanks.git", - "reference": "9474a631b52737c623b6aeba22f00bbc003251da" + "reference": "a8a5fbe3907a52cb7e2bb704d3b0231782b4193c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/thanks/zipball/9474a631b52737c623b6aeba22f00bbc003251da", - "reference": "9474a631b52737c623b6aeba22f00bbc003251da", + "url": "https://api.github.com/repos/symfony/thanks/zipball/a8a5fbe3907a52cb7e2bb704d3b0231782b4193c", + "reference": "a8a5fbe3907a52cb7e2bb704d3b0231782b4193c", "shasum": "" }, "require": { @@ -5604,7 +5791,7 @@ "type": "composer-plugin", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.2-dev" }, "class": "Symfony\\Thanks\\Thanks" }, @@ -5623,8 +5810,8 @@ "email": "p@tchwork.com" } ], - "description": "Give thanks (in the form of a GitHub ⭐) to your fellow PHP package maintainers (not limited to Symfony components)!", - "time": "2018-08-24T14:08:13+00:00" + "description": "Encourages sending ⭐ and 💵 to fellow PHP package maintainers (not limited to Symfony components)!", + "time": "2020-01-15T17:39:29+00:00" } ], "aliases": [], diff --git a/config/acl.yaml b/config/acl.yaml index 57a1c427..90b2f06d 100644 --- a/config/acl.yaml +++ b/config/acl.yaml @@ -242,6 +242,10 @@ access_keys: label: Edit - id: joborder.cancel label: Cancel + - id: jo_onestep.form + label: One-step Process + - id: jo_onestep.edit + label: One-step Process Edit - id: support label: Customer Support Access diff --git a/config/cmb.services.yaml b/config/cmb.services.yaml new file mode 100644 index 00000000..d12b2acf --- /dev/null +++ b/config/cmb.services.yaml @@ -0,0 +1,229 @@ +# Put parameters here that don't need to change on each machine where the app is deployed +# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration +parameters: + map_default: + latitude: 14.6091 + longitude: 121.0223 + image_upload_directory: '%kernel.project_dir%/public/uploads' + job_order_refresh_interval: 300000 + api_acl_file: 'api_acl.yaml' + api_access_key: 'api_access_keys' + app_acl_file: 'acl.yaml' + app_access_key: 'access_keys' + cvu_brand_id: "%env(CVU_BRAND_ID)%" + country_code: "%env(COUNTRY_CODE)%" + +services: + # default configuration for services in *this* file + _defaults: + autowire: true # Automatically injects dependencies in your services. + autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. + public: false # Allows optimizing the container by removing unused services; this also means + # fetching services directly from the container via $container->get() won't work. + # The best practice is to be explicit about your dependencies anyway. + + # makes classes in src/ available to be used as services + # this creates a service per class whose id is the fully-qualified class name + App\: + resource: '../src/*' + exclude: '../src/{Entity,Migrations,Tests,Menu,Access}' + + # controllers are imported separately to make sure services can be injected + # as action arguments even if you don't extend any base controller class + App\Controller\: + resource: '../src/Controller' + tags: ['controller.service_arguments'] + + # add more service definitions when explicit configuration is needed + # please note that last definitions always *replace* previous ones + App\Menu\Generator: + arguments: + $router: "@router.default" + $cache_dir: "%kernel.cache_dir%" + $config_dir: "%kernel.root_dir%/../config" + + Catalyst\AuthBundle\Service\ACLGenerator: + arguments: + $router: "@router.default" + $cache_dir: "%kernel.cache_dir%" + $config_dir: "%kernel.root_dir%/../config" + $acl_file: "%app_acl_file%" + + Catalyst\AuthBundle\Service\ACLVoter: + arguments: + $user_class: "App\\Entity\\User" + tags: ['security.voter'] + + Catalyst\AuthBundle\Service\UserChecker: + + App\Service\FileUploader: + arguments: + $target_dir: '%image_upload_directory%' + + App\Service\MapTools: + arguments: + $em: "@doctrine.orm.entity_manager" + $gmaps_api_key: "%env(GMAPS_API_KEY)%" + + App\Service\RisingTideGateway: + arguments: + $em: "@doctrine.orm.entity_manager" + $user: "%env(RT_USER)%" + $pass: "%env(RT_PASS)%" + $usage_type: "%env(RT_USAGE_TYPE)%" + $shortcode: "%env(RT_SHORTCODE)%" + + App\Service\MQTTClient: + arguments: + $redis_client: "@App\\Service\\RedisClientProvider" + $key: "mqtt_events" + + App\Service\APNSClient: + arguments: + $redis_client: "@App\\Service\\RedisClientProvider" + + App\Service\RedisClientProvider: + arguments: + $scheme: "%env(REDIS_CLIENT_SCHEME)%" + $host: "%env(REDIS_CLIENT_HOST)%" + $port: "%env(REDIS_CLIENT_PORT)%" + $password: "%env(REDIS_CLIENT_PASSWORD)%" + + App\Service\GeofenceTracker: + arguments: + $geofence_flag: "%env(GEOFENCE_ENABLE)%" + + App\Service\WarrantyHandler: + arguments: + $em: "@doctrine.orm.entity_manager" + + App\Command\SetCustomerPrivacyPolicyCommand: + arguments: + $policy_promo: "%env(POLICY_PROMO)%" + $policy_third_party: "%env(POLICY_THIRD_PARTY)%" + $policy_mobile: "%env(POLICY_MOBILE)%" + + App\Command\CreateCustomerFromWarrantyCommand: + arguments: + $cvu_mfg_id: "%env(CVU_MFG_ID)%" + $cvu_brand_id: "%env(CVU_BRAND_ID)%" + + # rider tracker service + App\Service\RiderTracker: + arguments: + $redis_client: "@App\\Service\\RedisClientProvider" + + Catalyst\APIBundle\Security\APIKeyUserProvider: + arguments: + $em: "@doctrine.orm.entity_manager" + + Catalyst\APIBundle\Security\APIKeyAuthenticator: + arguments: + $em: "@doctrine.orm.entity_manager" + + Catalyst\APIBundle\Command\UserCreateCommand: + arguments: + $em: "@doctrine.orm.entity_manager" + tags: ['console.command'] + + Catalyst\APIBundle\Command\TestCommand: + tags: ['console.command'] + + Catalyst\APIBundle\Command\TestAPICommand: + tags: ['console.command'] + + Catalyst\APIBundle\Access\Voter: + arguments: + $acl_gen: "@Catalyst\\APIBundle\\Access\\Generator" + $user_class: "Catalyst\\APIBundle\\Entity\\User" + tags: ['security.voter'] + + Catalyst\APIBundle\Access\Generator: + arguments: + $router: "@router.default" + $cache_dir: "%kernel.cache_dir%" + $config_dir: "%kernel.root_dir%/../config" + $acl_file: "%api_acl_file%" + + Catalyst\MenuBundle\Menu\Generator: + arguments: + $router: "@router.default" + $cache_dir: "%kernel.cache_dir%" + $config_dir: "%kernel.root_dir%/../config" + + Catalyst\MenuBundle\Listener\MenuAnnotationListener: + arguments: + $menu_name: "main_menu" + tags: + - { name: kernel.event_listener, event: kernel.controller, method: onKernelController } + + # invoice generator + App\Service\InvoiceGenerator\CMBInvoiceGenerator: ~ + + # invoice generator interface + App\Service\InvoiceGeneratorInterface: "@App\\Service\\InvoiceGenerator\\CMBInvoiceGenerator" + + # job order generator + App\Service\JobOrderHandler\CMBJobOrderHandler: + arguments: + $country_code: "%env(COUNTRY_CODE)%" + + #job order generator interface + App\Service\JobOrderHandlerInterface: "@App\\Service\\JobOrderHandler\\CMBJobOrderHandler" + + # customer generator + App\Service\CustomerHandler\CMBCustomerHandler: + arguments: + $country_code: "%env(COUNTRY_CODE)%" + + # customer generator interface + App\Service\CustomerHandlerInterface: "@App\\Service\\CustomerHandler\\CMBCustomerHandler" + + # rider assignment + App\Service\RiderAssignmentHandler\CMBRiderAssignmentHandler: ~ + + # rider assignment interface + App\Service\RiderAssignmentHandlerInterface: "@App\\Service\\RiderAssignmentHandler\\CMBRiderAssignmentHandler" + + # rider API service + App\Service\RiderAPIHandler\CMBRiderAPIHandler: + arguments: + $country_code: "%env(COUNTRY_CODE)%" + + # rider API interface + App\Service\RiderAPIHandlerInterface: "@App\\Service\\RiderAPIHandler\\CMBRiderAPIHandler" + + # map manager + #App\Service\GISManager\Bing: ~ + App\Service\GISManager\OpenStreet: ~ + #App\Service\GISManager\Google: ~ + + #App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Bing" + App\Service\GISManagerInterface: "@App\\Service\\GISManager\\OpenStreet" + #App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Google" + + App\EventListener\JobOrderActiveCacheListener: + arguments: + $jo_cache: "@App\\Service\\JobOrderCache" + $mqtt: "@App\\Service\\MQTTClient" + tags: + - name: 'doctrine.orm.entity_listener' + event: 'postUpdate' + entity: 'App\Entity\JobOrder' + - name: 'doctrine.orm.entity_listener' + event: 'postRemove' + entity: 'App\Entity\JobOrder' + - name: 'doctrine.orm.entity_listener' + event: 'postPersist' + entity: 'App\Entity\JobOrder' + + App\Service\JobOrderCache: + arguments: + $redis_prov: "@App\\Service\\RedisClientProvider" + $active_jo_key: "%env(LOCATION_JO_ACTIVE_KEY)%" + + App\Service\RiderCache: + arguments: + $redis_prov: "@App\\Service\\RedisClientProvider" + $loc_key: "%env(LOCATION_RIDER_ACTIVE_KEY)%" + $status_key: "%env(STATUS_RIDER_KEY)%" diff --git a/config/menu.yaml b/config/menu.yaml index b0096cf5..a64dd8b4 100644 --- a/config/menu.yaml +++ b/config/menu.yaml @@ -98,6 +98,10 @@ main_menu: acl: joborder.menu label: Job Order icon: flaticon-calendar-3 + - id: jo_onestep_form + acl: jo_onestep.form + label: One-step Process + parent: joborder - id: jo_in acl: jo_in.list label: Incoming diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index b1b6d947..c154fbc2 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -19,6 +19,7 @@ doctrine: point: CrEOF\Spatial\DBAL\Types\Geometry\PointType polygon: CrEOF\Spatial\DBAL\Types\Geometry\PolygonType linestring: CrEOF\Spatial\DBAL\Types\Geometry\LineStringType + multipolygon: CrEOF\Spatial\DBAL\Types\Geometry\MultiPolygonType orm: auto_generate_proxy_classes: '%kernel.debug%' naming_strategy: doctrine.orm.naming_strategy.underscore diff --git a/config/packages/security.yaml b/config/packages/security.yaml index c8deb304..dc9aa78d 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -21,6 +21,11 @@ security: methods: [GET] security: false + tracker: + pattern: ^\/track\/ + methods: [GET] + security: false + api: pattern: ^\/api\/ security: false diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml index a11fab53..cb90797b 100644 --- a/config/packages/twig.yaml +++ b/config/packages/twig.yaml @@ -4,3 +4,6 @@ twig: strict_variables: '%kernel.debug%' globals: gmaps_api_key: "%env(GMAPS_API_KEY)%" + mqtt_host: "%env(MQTT_WS_HOST)%" + mqtt_port: "%env(MQTT_WS_PORT)%" + dashboard_enable: "%env(DASHBOARD_ENABLE)%" diff --git a/config/resq.services.yaml b/config/resq.services.yaml new file mode 100644 index 00000000..1756cf98 --- /dev/null +++ b/config/resq.services.yaml @@ -0,0 +1,228 @@ +# Put parameters here that don't need to change on each machine where the app is deployed +# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration +parameters: + map_default: + latitude: 14.6091 + longitude: 121.0223 + image_upload_directory: '%kernel.project_dir%/public/uploads' + job_order_refresh_interval: 300000 + api_acl_file: 'api_acl.yaml' + api_access_key: 'api_access_keys' + app_acl_file: 'acl.yaml' + app_access_key: 'access_keys' + cvu_brand_id: "%env(CVU_BRAND_ID)%" + country_code: "%env(COUNTRY_CODE)%" + +services: + # default configuration for services in *this* file + _defaults: + autowire: true # Automatically injects dependencies in your services. + autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. + public: false # Allows optimizing the container by removing unused services; this also means + # fetching services directly from the container via $container->get() won't work. + # The best practice is to be explicit about your dependencies anyway. + + # makes classes in src/ available to be used as services + # this creates a service per class whose id is the fully-qualified class name + App\: + resource: '../src/*' + exclude: '../src/{Entity,Migrations,Tests,Menu,Access}' + + # controllers are imported separately to make sure services can be injected + # as action arguments even if you don't extend any base controller class + App\Controller\: + resource: '../src/Controller' + tags: ['controller.service_arguments'] + + # add more service definitions when explicit configuration is needed + # please note that last definitions always *replace* previous ones + App\Menu\Generator: + arguments: + $router: "@router.default" + $cache_dir: "%kernel.cache_dir%" + $config_dir: "%kernel.root_dir%/../config" + + Catalyst\AuthBundle\Service\ACLGenerator: + arguments: + $router: "@router.default" + $cache_dir: "%kernel.cache_dir%" + $config_dir: "%kernel.root_dir%/../config" + $acl_file: "%app_acl_file%" + + Catalyst\AuthBundle\Service\ACLVoter: + arguments: + $user_class: "App\\Entity\\User" + tags: ['security.voter'] + + Catalyst\AuthBundle\Service\UserChecker: + + App\Service\FileUploader: + arguments: + $target_dir: '%image_upload_directory%' + + App\Service\MapTools: + arguments: + $em: "@doctrine.orm.entity_manager" + $gmaps_api_key: "%env(GMAPS_API_KEY)%" + + App\Service\RisingTideGateway: + arguments: + $em: "@doctrine.orm.entity_manager" + $user: "%env(RT_USER)%" + $pass: "%env(RT_PASS)%" + $usage_type: "%env(RT_USAGE_TYPE)%" + $shortcode: "%env(RT_SHORTCODE)%" + + App\Service\MQTTClient: + arguments: + $redis_client: "@App\\Service\\RedisClientProvider" + $key: "mqtt_events" + + App\Service\APNSClient: + arguments: + $redis_client: "@App\\Service\\RedisClientProvider" + + App\Service\RedisClientProvider: + arguments: + $scheme: "%env(REDIS_CLIENT_SCHEME)%" + $host: "%env(REDIS_CLIENT_HOST)%" + $port: "%env(REDIS_CLIENT_PORT)%" + $password: "%env(REDIS_CLIENT_PASSWORD)%" + + App\Service\GeofenceTracker: + arguments: + $geofence_flag: "%env(GEOFENCE_ENABLE)%" + + App\Service\WarrantyHandler: + arguments: + $em: "@doctrine.orm.entity_manager" + + App\Command\SetCustomerPrivacyPolicyCommand: + arguments: + $policy_promo: "%env(POLICY_PROMO)%" + $policy_third_party: "%env(POLICY_THIRD_PARTY)%" + $policy_mobile: "%env(POLICY_MOBILE)%" + + App\Command\CreateCustomerFromWarrantyCommand: + arguments: + $cvu_mfg_id: "%env(CVU_MFG_ID)%" + $cvu_brand_id: "%env(CVU_BRAND_ID)%" + + # rider tracker service + App\Service\RiderTracker: + arguments: + $redis_client: "@App\\Service\\RedisClientProvider" + + Catalyst\APIBundle\Security\APIKeyUserProvider: + arguments: + $em: "@doctrine.orm.entity_manager" + + Catalyst\APIBundle\Security\APIKeyAuthenticator: + arguments: + $em: "@doctrine.orm.entity_manager" + + Catalyst\APIBundle\Command\UserCreateCommand: + arguments: + $em: "@doctrine.orm.entity_manager" + tags: ['console.command'] + + Catalyst\APIBundle\Command\TestCommand: + tags: ['console.command'] + + Catalyst\APIBundle\Command\TestAPICommand: + tags: ['console.command'] + + Catalyst\APIBundle\Access\Voter: + arguments: + $acl_gen: "@Catalyst\\APIBundle\\Access\\Generator" + $user_class: "Catalyst\\APIBundle\\Entity\\User" + tags: ['security.voter'] + + Catalyst\APIBundle\Access\Generator: + arguments: + $router: "@router.default" + $cache_dir: "%kernel.cache_dir%" + $config_dir: "%kernel.root_dir%/../config" + $acl_file: "%api_acl_file%" + + Catalyst\MenuBundle\Menu\Generator: + arguments: + $router: "@router.default" + $cache_dir: "%kernel.cache_dir%" + $config_dir: "%kernel.root_dir%/../config" + + Catalyst\MenuBundle\Listener\MenuAnnotationListener: + arguments: + $menu_name: "main_menu" + tags: + - { name: kernel.event_listener, event: kernel.controller, method: onKernelController } + + # invoice generator + App\Service\InvoiceGenerator\ResqInvoiceGenerator: ~ + + # invoice generator interface + App\Service\InvoiceGeneratorInterface: "@App\\Service\\InvoiceGenerator\\ResqInvoiceGenerator" + + # job order generator + App\Service\JobOrderHandler\ResqJobOrderHandler: + arguments: + $country_code: "%env(COUNTRY_CODE)%" + + #job order generator interface + App\Service\JobOrderHandlerInterface: "@App\\Service\\JobOrderHandler\\ResqJobOrderHandler" + + # customer generator + App\Service\CustomerHandler\ResqCustomerHandler: + arguments: + $country_code: "%env(COUNTRY_CODE)%" + + # customer generator interface + App\Service\CustomerHandlerInterface: "@App\\Service\\CustomerHandler\\ResqCustomerHandler" + + # rider assignment + App\Service\RiderAssignmentHandler\ResqRiderAssignmentHandler: ~ + + # rider assignment interface + App\Service\RiderAssignmentHandlerInterface: "@App\\Service\\RiderAssignmentHandler\\ResqRiderAssignmentHandler" + + # rider API service + App\Service\RiderAPIHandler\ResqRiderAPIHandler: + arguments: + $country_code: "%env(COUNTRY_CODE)%" + + App\Service\RiderAPIHandlerInterface: "@App\\Service\\RiderAPIHandler\\ResqRiderAPIHandler" + + # map manager + #App\Service\GISManager\Bing: ~ + App\Service\GISManager\OpenStreet: ~ + #App\Service\GISManager\Google: ~ + + #App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Bing" + App\Service\GISManagerInterface: "@App\\Service\\GISManager\\OpenStreet" + #App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Google" + + App\EventListener\JobOrderActiveCacheListener: + arguments: + $jo_cache: "@App\\Service\\JobOrderCache" + $mqtt: "@App\\Service\\MQTTClient" + tags: + - name: 'doctrine.orm.entity_listener' + event: 'postUpdate' + entity: 'App\Entity\JobOrder' + - name: 'doctrine.orm.entity_listener' + event: 'postRemove' + entity: 'App\Entity\JobOrder' + - name: 'doctrine.orm.entity_listener' + event: 'postPersist' + entity: 'App\Entity\JobOrder' + + App\Service\JobOrderCache: + arguments: + $redis_prov: "@App\\Service\\RedisClientProvider" + $active_jo_key: "%env(LOCATION_JO_ACTIVE_KEY)%" + + App\Service\RiderCache: + arguments: + $redis_prov: "@App\\Service\\RedisClientProvider" + $loc_key: "%env(LOCATION_RIDER_ACTIVE_KEY)%" + $status_key: "%env(STATUS_RIDER_KEY)%" diff --git a/config/routes.yaml b/config/routes.yaml index fe5c04a3..e3cc0585 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -3,7 +3,3 @@ # controller: App\Controller\DefaultController::index # # -home: - path: / - controller: App\Controller\HomeController::index - diff --git a/config/routes/home.yaml b/config/routes/home.yaml new file mode 100644 index 00000000..da111860 --- /dev/null +++ b/config/routes/home.yaml @@ -0,0 +1,8 @@ +home: + path: / + controller: App\Controller\HomeController::index + +rider_locations: + path: /rider_locations + controller: App\Controller\HomeController::getRiderLocations + diff --git a/config/routes/hub.yaml b/config/routes/hub.yaml index 1634e7d7..d28498ec 100644 --- a/config/routes/hub.yaml +++ b/config/routes/hub.yaml @@ -34,3 +34,12 @@ hub_delete: controller: App\Controller\HubController::destroy methods: [DELETE] +hub_nearest: + path: /ajax/nearest_hubs + controller: App\Controller\HubController::nearest + methods: [GET] + +hub_riders: + path: /ajax/hubs/riders + controller: App\Controller\HubController::getHubRiders + methods: [GET] diff --git a/config/routes/job_order.yaml b/config/routes/job_order.yaml index 05cb08c8..bfd41df9 100644 --- a/config/routes/job_order.yaml +++ b/config/routes/job_order.yaml @@ -175,3 +175,34 @@ jo_reject_hub: path: /job-order/{id}/reject-hub controller: App\Controller\JobOrderController::rejectHubSubmit methods: [POST] + +jo_onestep_form: + path: /job-order/onestep + controller: App\Controller\JobOrderController::oneStepForm + methods: [GET] + +jo_onestep_submit: + path: /job-order/onestep + controller: App\Controller\JobOrderController::oneStepSubmit + methods: [POST] + +jo_onestep_edit_form: + path: /job-order/onestep/{id}/edit + controller: App\Controller\JobOrderController::oneStepEditForm + methods: [GET] + +jo_onestep_edit_submit: + path: /job-order/onestep/{id}/edit + controller: App\Controller\JobOrderController::oneStepEditSubmit + methods: [POST] + +jo_ajax_popup: + path: /job-order/{id}/popup + controller: App\Controller\JobOrderController::popupInfo + methods: [GET] + +jo_tracker: + path: /track/{id} + controller: App\Controller\JobOrderController::tracker + methods: [GET] + diff --git a/config/routes/rider.yaml b/config/routes/rider.yaml index 61a4522e..70ddd91d 100644 --- a/config/routes/rider.yaml +++ b/config/routes/rider.yaml @@ -36,3 +36,8 @@ rider_delete: path: /riders/{id} controller: App\Controller\RiderController::destroy methods: [DELETE] + +rider_ajax_popup: + path: /riders/{id}/popup + controller: App\Controller\RiderController::popupInfo + methods: [GET] diff --git a/config/services.yaml b/config/services.yaml index c8916c9b..8fe325e4 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -76,6 +76,7 @@ services: App\Service\MQTTClient: arguments: $redis_client: "@App\\Service\\RedisClientProvider" + $key: "mqtt_events" App\Service\APNSClient: arguments: @@ -87,7 +88,6 @@ services: $host: "%env(REDIS_CLIENT_HOST)%" $port: "%env(REDIS_CLIENT_PORT)%" $password: "%env(REDIS_CLIENT_PASSWORD)%" - $env_flag: "dev" App\Service\GeofenceTracker: arguments: @@ -108,6 +108,11 @@ services: $cvu_mfg_id: "%env(CVU_MFG_ID)%" $cvu_brand_id: "%env(CVU_BRAND_ID)%" + # rider tracker service + App\Service\RiderTracker: + arguments: + $redis_client: "@App\\Service\\RedisClientProvider" + Catalyst\APIBundle\Security\APIKeyUserProvider: arguments: $em: "@doctrine.orm.entity_manager" @@ -151,3 +156,94 @@ services: $menu_name: "main_menu" tags: - { name: kernel.event_listener, event: kernel.controller, method: onKernelController } + + # CMB invoice generator + App\Service\InvoiceGenerator\CMBInvoiceGenerator: ~ + + # Resq invoice generator + #App\Service\InvoiceGenerator\ResqInvoiceGenerator: ~ + + # invoice generator interface + App\Service\InvoiceGeneratorInterface: "@App\\Service\\InvoiceGenerator\\CMBInvoiceGenerator" + #App\Service\InvoiceGeneratorInterface: "@App\\Service\\InvoiceGenerator\\ResqInvoiceGenerator" + + # Resq job order generator + #App\Service\JobOrderHandler\ResqJobOrderHandler: + # arguments: + # $country_code: "%env(COUNTRY_CODE)%" + + # CMB job order generator + App\Service\JobOrderHandler\CMBJobOrderHandler: + arguments: + $country_code: "%env(COUNTRY_CODE)%" + + #job order generator interface + App\Service\JobOrderHandlerInterface: "@App\\Service\\JobOrderHandler\\CMBJobOrderHandler" + #App\Service\JobOrderHandlerInterface: "@App\\Service\\JobOrderHandler\\ResqJobOrderHandler" + + # CMB customer generator + App\Service\CustomerHandler\CMBCustomerHandler: + arguments: + $country_code: "%env(COUNTRY_CODE)%" + + # Resq customer generator + #App\Service\CustomerHandler\ResqCustomerHandler: + # arguments: + # $country_code: "%env(COUNTRY_CODE)%" + + # customer generator interface + App\Service\CustomerHandlerInterface: "@App\\Service\\CustomerHandler\\CMBCustomerHandler" + #App\Service\CustomerHandlerInterface: "@App\\Service\\CustomerHandler\\ResqCustomerHandler" + + # rider assignment + #App\Service\RiderAssignmentHandler\CMBRiderAssignmentHandler: ~ + + # rider assignment interface + App\Service\RiderAssignmentHandlerInterface: "@App\\Service\\RiderAssignmentHandler\\CMBRiderAssignmentHandler" + + # rider API service + App\Service\RiderAPIHandler\CMBRiderAPIHandler: + arguments: + $country_code: "%env(COUNTRY_CODE)%" + + #App\Service\RiderAPIHandler\ResqRiderAPIHandler: + # arguments: + # $country_code: "%env(COUNTRY_CODE)%" + + # rider API interface + App\Service\RiderAPIHandlerInterface: "@App\\Service\\RiderAPIHandler\\CMBRiderAPIHandler" + + # map manager + #App\Service\GISManager\Bing: ~ + App\Service\GISManager\OpenStreet: ~ + #App\Service\GISManager\Google: ~ + + #App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Bing" + App\Service\GISManagerInterface: "@App\\Service\\GISManager\\OpenStreet" + #App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Google" + + App\EventListener\JobOrderActiveCacheListener: + arguments: + $jo_cache: "@App\\Service\\JobOrderCache" + $mqtt: "@App\\Service\\MQTTClient" + tags: + - name: 'doctrine.orm.entity_listener' + event: 'postUpdate' + entity: 'App\Entity\JobOrder' + - name: 'doctrine.orm.entity_listener' + event: 'postRemove' + entity: 'App\Entity\JobOrder' + - name: 'doctrine.orm.entity_listener' + event: 'postPersist' + entity: 'App\Entity\JobOrder' + + App\Service\JobOrderCache: + arguments: + $redis_prov: "@App\\Service\\RedisClientProvider" + $active_jo_key: "%env(LOCATION_JO_ACTIVE_KEY)%" + + App\Service\RiderCache: + arguments: + $redis_prov: "@App\\Service\\RedisClientProvider" + $loc_key: "%env(LOCATION_RIDER_ACTIVE_KEY)%" + $status_key: "%env(STATUS_RIDER_KEY)%" diff --git a/migration/sql_delete_battery_vehicle_data.sql b/migration/sql_delete_battery_vehicle_data.sql new file mode 100644 index 00000000..7df32e17 --- /dev/null +++ b/migration/sql_delete_battery_vehicle_data.sql @@ -0,0 +1,8 @@ +DELETE FROM battery; +DELETE FROM battery_manufacturer; +DELETE FROM battery_manufacturer; +DELETE FROM battery_model; +DELETE FROM battery_size; +DELETE FROM vehicle; +DELETE FROM vehicle_manufacturer; +DELETE FROM battery_vehicle; diff --git a/public/assets/css/style.css b/public/assets/css/style.css index f8e6318d..140966da 100644 --- a/public/assets/css/style.css +++ b/public/assets/css/style.css @@ -295,3 +295,67 @@ span.has-danger, .btn-icon { margin-right: .5em; } + +.marker-pin { + width: 30px; + height: 30px; + border-radius: 50% 50% 50% 0; + background: #c30b82; + position: absolute; + transform: rotate(-45deg); + left: 50%; + top: 50%; + margin: -15px 0 0 -15px; +} + +.marker-pin::after { + content: ''; + width: 24px; + height: 24px; + margin: 3px 0 0 3px; + background: #fff; + position: absolute; + border-radius: 50%; + } + +.map-div-icon i { + position: absolute; + width: 22px; + font-size: 22px; + left: 0; + right: 0; + margin: 10px auto; + text-align: center; +} + +.map-div-icon i.awesome { + margin: 12px auto; + font-size: 17px; +} + +.map-info { + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + z-index: 9999; + padding: 1.5em; + width: 100%; +} + +.map-info > .m-portlet { + margin-bottom: 0; +} + +.map-info .m-portlet__body { + padding: 1.5rem; +} + +.map-info .rider-image { + width: 4.8rem; + border-radius: 50%; +} + +.map-info .m-badge { + border-radius: 0; +} \ No newline at end of file diff --git a/public/assets/images/battery-assist-bm-logo-16x16.png b/public/assets/images/battery-assist-bm-logo-16x16.png new file mode 100644 index 00000000..fe2a6769 Binary files /dev/null and b/public/assets/images/battery-assist-bm-logo-16x16.png differ diff --git a/public/assets/images/battery-assist-bm-logo-32x32.png b/public/assets/images/battery-assist-bm-logo-32x32.png new file mode 100644 index 00000000..fa0c4c85 Binary files /dev/null and b/public/assets/images/battery-assist-bm-logo-32x32.png differ diff --git a/public/assets/images/battery-assist-bm-logo-50x50.png b/public/assets/images/battery-assist-bm-logo-50x50.png new file mode 100644 index 00000000..4b086bd8 Binary files /dev/null and b/public/assets/images/battery-assist-bm-logo-50x50.png differ diff --git a/public/assets/images/battery-assist-bm-logo-edited.png b/public/assets/images/battery-assist-bm-logo-edited.png new file mode 100644 index 00000000..9f1b7bf3 Binary files /dev/null and b/public/assets/images/battery-assist-bm-logo-edited.png differ diff --git a/public/assets/images/battery-assist-bm-logo.png b/public/assets/images/battery-assist-bm-logo.png new file mode 100644 index 00000000..47311393 Binary files /dev/null and b/public/assets/images/battery-assist-bm-logo.png differ diff --git a/public/assets/images/black-text-logo-01-115x115.png b/public/assets/images/black-text-logo-01-115x115.png new file mode 100644 index 00000000..6cb04ea0 Binary files /dev/null and b/public/assets/images/black-text-logo-01-115x115.png differ diff --git a/public/assets/images/black-text-logo-01-125x125.png b/public/assets/images/black-text-logo-01-125x125.png new file mode 100644 index 00000000..f79218f7 Binary files /dev/null and b/public/assets/images/black-text-logo-01-125x125.png differ diff --git a/public/assets/images/black-text-logo-01-16x16.png b/public/assets/images/black-text-logo-01-16x16.png new file mode 100644 index 00000000..4ac80d27 Binary files /dev/null and b/public/assets/images/black-text-logo-01-16x16.png differ diff --git a/public/assets/images/black-text-logo-01-32x32.png b/public/assets/images/black-text-logo-01-32x32.png new file mode 100644 index 00000000..4ac80d27 Binary files /dev/null and b/public/assets/images/black-text-logo-01-32x32.png differ diff --git a/public/assets/images/black-text-logo-01.png b/public/assets/images/black-text-logo-01.png new file mode 100644 index 00000000..8286a75e Binary files /dev/null and b/public/assets/images/black-text-logo-01.png differ diff --git a/public/assets/images/century_logo.png b/public/assets/images/century_logo.png new file mode 100644 index 00000000..1457c03b Binary files /dev/null and b/public/assets/images/century_logo.png differ diff --git a/public/assets/js/dashboard_map.js b/public/assets/js/dashboard_map.js new file mode 100644 index 00000000..4691a11d --- /dev/null +++ b/public/assets/js/dashboard_map.js @@ -0,0 +1,171 @@ +class DashboardMap { + constructor(options, rider_markers, cust_markers) { + this.options = options; + this.rider_markers = rider_markers; + this.cust_markers = cust_markers; + + // layer groups + this.layer_groups = { + 'rider_available': L.layerGroup(), + 'rider_active_jo': L.layerGroup(), + 'customer': L.layerGroup() + }; + } + + initialize() { + // main map + this.map = L.map(this.options.div_id).setView( + [this.options.center_lat, this.options.center_lng], + this.options.zoom + ); + + // add tile layer + var streets = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', { + attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © Mapbox', + maxZoom: 18, + id: 'mapbox/streets-v11', + accessToken: this.options.access_token + }).addTo(this.map); + + // layer groups + this.layer_groups.rider_available.addTo(this.map); + this.layer_groups.rider_active_jo.addTo(this.map); + this.layer_groups.customer.addTo(this.map); + + // base layer + var baseMaps = { + 'Streets': streets + }; + + if (this.options.display_overlay) { + // overlay layer + var overlayMaps = { + 'Available Riders' : this.layer_groups.rider_available, + 'JO Riders' : this.layer_groups.rider_active_jo, + 'Customers' : this.layer_groups.customer + } + + L.control.layers(baseMaps, overlayMaps).addTo(this.map); + } + + return this.map; + } + + putMarker(id, lat, lng, markers, icon, layer_group, popup_url) { + var my = this; + // existing marker + if (markers.hasOwnProperty(id)) { + markers[id].setLatLng(L.latLng(lat, lng)); + return; + } + + // new marker + markers[id] = L.marker( + [lat, lng], + { icon: icon } + ).addTo(layer_group); + + if (my.options.enable_popup) { + markers[id].bindPopup('Loading...'); + + // bind ajax for popup + markers[id].on('click', function(e) { + var popup = e.target.getPopup(); + var url = popup_url.replace('[id]', id); + console.log(url); + $.get(url).done(function(data) { + popup.setContent(data); + popup.update(); + }); + }); + } + } + + putCustomerMarker(id, lat, lng) { + this.putMarker( + id, + lat, + lng, + this.cust_markers, + this.options.icons.customer, + this.layer_groups.customer, + this.options.cust_popup_url + ); + } + + removeCustomerMarker(id) { + console.log('removing customer marker for ' + id); + var layer_group = this.layer_groups.customer; + var markers = this.cust_markers; + + // no customer marker with that id + if (!markers.hasOwnProperty(id)) { + console.log('no such marker to remove'); + return; + } + + layer_group.removeLayer(markers[id]); + } + + putRiderAvailableMarker(id, lat, lng) { + this.putMarker( + id, + lat, + lng, + this.rider_markers, + this.options.icons.rider_available, + this.layer_groups.rider_available, + this.options.rider_popup_url + ); + } + + putRiderActiveJOMarker(id, lat, lng) { + this.putMarker( + id, + lat, + lng, + this.rider_markers, + this.options.icons.rider_active_jo, + this.layer_groups.rider_active_jo, + this.options.rider_popup_url + ); + } + + loadLocations(location_url) { + console.log(this.rider_markers); + var my = this; + $.ajax({ + url: location_url, + }).done(function(response) { + // clear all markers + my.layer_groups.rider_available.clearLayers(); + my.layer_groups.rider_active_jo.clearLayers(); + my.layer_groups.customer.clearLayers(); + + // get riders and job orders + var riders = response.riders; + var jos = response.jos; + + // job orders + $.each(jos, function(id, data) { + var lat = data.latitude; + var lng = data.longitude; + + my.putCustomerMarker(id, lat, lng); + }); + + // riders + $.each(riders, function(id, data) { + var lat = data.latitude; + var lng = data.longitude; + + if (data.has_jo) + my.putRiderActiveJOMarker(id, lat, lng); + else + my.putRiderAvailableMarker(id, lat, lng); + }); + + // console.log(rider_markers); + }); + } +} diff --git a/public/assets/js/map_mqtt.js b/public/assets/js/map_mqtt.js new file mode 100644 index 00000000..6eac1f65 --- /dev/null +++ b/public/assets/js/map_mqtt.js @@ -0,0 +1,114 @@ +class MapEventHandler { + constructor(options, dashmap) { + this.options = options; + this.dashmap = dashmap; + } + + connect(user_id, host, port) { + var d = new Date(); + var client_id = "dash-" + user_id + "-" + d.getMonth() + "-" + d.getDate() + "-" + d.getHours() + "-" + d.getMinutes() + "-" + d.getSeconds() + "-" + d.getMilliseconds(); + console.log(client_id); + + this.mqtt = new Paho.MQTT.Client(host, port, client_id); + var options = { + // useSSL: true, + timeout: 3, + invocationContext: this, + onSuccess: this.onConnect.bind(this), + }; + + this.mqtt.onMessageArrived = this.onMessage.bind(this); + + console.log('connecting to mqtt server...'); + this.mqtt.connect(options); + } + + onConnect(icontext) { + console.log('mqtt connected!'); + var my = icontext.invocationContext; + + // subscribe to rider locations + if (my.options.track_rider) { + console.log('subscribing to ' + my.options.channels.rider_location); + my.mqtt.subscribe(my.options.channels.rider_location); + } + + // subscribe to jo locations + if (my.options.track_jo) { + console.log('subscribing to ' + my.options.channels.jo_location); + my.mqtt.subscribe(my.options.channels.jo_location); + my.mqtt.subscribe(my.options.channels.jo_status); + } + } + + onMessage(msg) { + // console.log(msg); + console.log('received message'); + + var channel = msg.destinationName; + var chan_split = channel.split('/'); + var payload = msg.payloadString; + + // handle different channels + switch (chan_split[0]) { + case "rider": + this.handleRider(chan_split, payload); + break; + case "jo": + this.handleJobOrder(chan_split, payload); + break; + } + } + + handleRider(chan_split, payload) { + console.log("rider message"); + switch (chan_split[2]) { + case "location": + console.log("got location for rider " + chan_split[1] + " - " + payload); + var pl_split = payload.split(':'); + console.log(pl_split); + + // check for correct format + if (pl_split.length != 2) + break; + + var lat = parseFloat(pl_split[0]); + var lng = parseFloat(pl_split[1]); + + this.dashmap.putRiderAvailableMarker(chan_split[1], lat, lng); + break; + } + } + + handleJobOrder(chan_split, payload) { + console.log("jo message"); + var id = chan_split[1]; + switch (chan_split[2]) { + case "location": + // var my = this; + console.log("got location for jo " + id + " - " + payload); + var pl_split = payload.split(':'); + + // check for correct format + if (pl_split.length != 2) + break; + + var lat = parseFloat(pl_split[0]); + var lng = parseFloat(pl_split[1]); + + // move marker + console.log(lat + ' - ' + lng); + + this.dashmap.putCustomerMarker(id, lat, lng); + break; + case "status": + switch (payload) { + case 'cancel': + case 'fulfill': + case 'delete': + this.dashmap.removeCustomerMarker(id); + break; + } + } + } +} diff --git a/src/Command/AdjustLongLatCommand.php b/src/Command/AdjustLongLatCommand.php index 62405269..86e4fc0f 100644 --- a/src/Command/AdjustLongLatCommand.php +++ b/src/Command/AdjustLongLatCommand.php @@ -46,5 +46,7 @@ class AdjustLongLatCommand extends Command } $em->flush(); + + return 0; } } diff --git a/src/Command/ComputeWarrantyExpiryDateCommand.php b/src/Command/ComputeWarrantyExpiryDateCommand.php index cffc24ef..fa0fdc14 100644 --- a/src/Command/ComputeWarrantyExpiryDateCommand.php +++ b/src/Command/ComputeWarrantyExpiryDateCommand.php @@ -71,5 +71,7 @@ class ComputeWarrantyExpiryDateCommand extends Command $this->em->clear(); } + + return 0; } } diff --git a/src/Command/CreateCustomerFromWarrantyCommand.php b/src/Command/CreateCustomerFromWarrantyCommand.php index 42b2fe73..416ee514 100644 --- a/src/Command/CreateCustomerFromWarrantyCommand.php +++ b/src/Command/CreateCustomerFromWarrantyCommand.php @@ -290,6 +290,8 @@ class CreateCustomerFromWarrantyCommand extends Command //$output->writeln('Total warranties with no mobile number: ' . $total_inv_warr); $output->writeln('Total customers added: ' . $total_cust_added); $output->writeln('Total customer vehicles added: ' . $total_cv_added); + + return 0; } protected function getDefaultVehicle() diff --git a/src/Command/FulfillOldJobOrderCommand.php b/src/Command/FulfillOldJobOrderCommand.php index dc987e8b..dcf9d761 100644 --- a/src/Command/FulfillOldJobOrderCommand.php +++ b/src/Command/FulfillOldJobOrderCommand.php @@ -76,5 +76,7 @@ class FulfillOldJobOrderCommand extends Command } $em->flush(); + + return 0; } } diff --git a/src/Command/FulfillPendingJobOrderCommand.php b/src/Command/FulfillPendingJobOrderCommand.php index 0c1fe7b0..1ff47e8e 100644 --- a/src/Command/FulfillPendingJobOrderCommand.php +++ b/src/Command/FulfillPendingJobOrderCommand.php @@ -79,5 +79,7 @@ class FulfillPendingJobOrderCommand extends Command } $em->flush(); + + return 0; } } diff --git a/src/Command/GenerateBatteryCompatibilityCommand.php b/src/Command/GenerateBatteryCompatibilityCommand.php index ec0a1843..e4ca2d6c 100644 --- a/src/Command/GenerateBatteryCompatibilityCommand.php +++ b/src/Command/GenerateBatteryCompatibilityCommand.php @@ -100,5 +100,7 @@ class GenerateBatteryCompatibilityCommand extends Command fwrite($json_file, json_encode($this->vmfg_index, JSON_PRETTY_PRINT)); fclose($json_file); + return 0; + } } diff --git a/src/Command/GenerateWarrantyFromJobOrderCommand.php b/src/Command/GenerateWarrantyFromJobOrderCommand.php index 1d3f100b..23f436c4 100644 --- a/src/Command/GenerateWarrantyFromJobOrderCommand.php +++ b/src/Command/GenerateWarrantyFromJobOrderCommand.php @@ -211,5 +211,7 @@ class GenerateWarrantyFromJobOrderCommand extends Command $em->detach($row[0]); $em->clear(); } + + return 0; } } diff --git a/src/Command/ImportBatteryPriceCommand.php b/src/Command/ImportBatteryPriceCommand.php index 12fade3a..f7395a6e 100644 --- a/src/Command/ImportBatteryPriceCommand.php +++ b/src/Command/ImportBatteryPriceCommand.php @@ -174,5 +174,7 @@ class ImportBatteryPriceCommand extends Command } $em->flush(); + + return 0; } } diff --git a/src/Command/ImportCMBBatteryDataCommand.php b/src/Command/ImportCMBBatteryDataCommand.php new file mode 100644 index 00000000..79e84571 --- /dev/null +++ b/src/Command/ImportCMBBatteryDataCommand.php @@ -0,0 +1,317 @@ +em = $om; + + // load existing batteries and sizes + $this->loadBatteryManufacturers(); + $this->loadBatteryModels(); + $this->loadBatteries(); + $this->loadBatterySizes(); + + parent::__construct(); + } + + protected function configure() + { + $this->setName('cmbbatterydata:import') + ->setDescription('Import a CSV file with battery data.') + ->setHelp('Adds the battery data based on imported CSV.') + ->addArgument('file', InputArgument::REQUIRED, 'Path to the CSV file.'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $csv_file = $input->getArgument('file'); + + // attempt to open file + try + { + $fh = fopen($csv_file, "r"); + } + catch (Exception $e) + { + throw new Exception('The file "' . $csv_file . '" could be read.'); + } + + // get entity manager + $em = $this->em; + + // loop through the rows + $row_num = 0; + error_log('Processing battery csv file...'); + while (($fields = fgetcsv($fh)) !== false) + { + // data starts at row 2 + if ($row_num < 2) + { + $row_num++; + continue; + } + + // battery info + $code = trim($fields[self::F_BATT_CODE]); + $desc = trim($fields[self::F_BATT_DESC]); + $price = trim($fields[self::F_BATT_PRICE]); + + $clean_price = trim($price, '$'); + + $battery_info = explode(' ', $desc); + + // if battery_info has 3 elements, get the last two + // [0] = battery manufacturer + // [1] = battery model + // [2] = battery size + // if only 2, get both + // [0] = battery manufacturer and battery model + // [1] = battery size + // if 4, + // [0] = battery manufacturer + // concatenate [1] and [2] for the battery model + // [3] = battery size + $battery_manufacturer = ''; + $battery_model = ''; + $battery_size = ''; + if (count($battery_info) == 3) + { + // sample: Century Marathoner 120-7L + $battery_manufacturer = trim($battery_info[0]); + $battery_model = trim($battery_info[1]); + $battery_size = trim($battery_info[2]); + } + if (count($battery_info) == 2) + { + // sample: Marshall DIN55R + $battery_manufacturer = trim($battery_info[0]); + $battery_model = trim($battery_info[0]); + $battery_size = trim($battery_info[1]); + } + if (count($battery_info) == 4) + { + // sample: Motolite Classic Wetcharged DIN100L + $battery_manufacturer = trim($battery_info[0]); + $battery_model = trim($battery_info[1]) . ' ' . trim($battery_info[2]); + $battery_size = trim($battery_info[3]); + } + + // check if battery size has () + // if so, trim it to ignore the parenthesis and what's after (. + $pos = stripos($battery_size, '('); + if ($pos == true) + { + $sizes = explode('(', $battery_size); + $clean_size = trim($sizes[0]); + } + else + { + $clean_size = $battery_size; + } + + //error_log('battery manufacturer ' . $battery_manufacturer); + //error_log('battery model ' . $battery_model); + //error_log('battery size ' . $battery_size); + + // normalize the manufacturer, model and size for the hash + // when we add to db for manufacturer, model, and size, we do not use the normalized versions + $normalized_manu = $this->normalizeName($battery_manufacturer); + $normalized_model = $this->normalizeName($battery_model); + $normalized_size = $this->normalizeName($clean_size); + + // save battery manufacturer if not yet in system + if (!isset($this->bmanu_hash[$normalized_manu])) + { + $this->addBatteryManufacturer($battery_manufacturer); + } + + // save battery model if not yet in system + if (!isset($this->bmodel_hash[$normalized_model])) + { + $this->addBatteryModel($battery_model); + } + + // save battery size if not yet in system + if (!isset($this->bsize_hash[$normalized_size])) + { + $this->addBatterySize($clean_size); + } + + // save battery if not yet in system + if (!isset($this->batt_hash[$normalized_manu][$normalized_model][$normalized_size])) + { + $this->addBattery($normalized_manu, $normalized_model, $normalized_size, $code, $clean_price); + } + + } + + return 0; + } + + protected function addBatteryManufacturer($name) + { + $batt_manufacturer = new BatteryManufacturer(); + + $batt_manufacturer->setName($name); + + $this->em->persist($batt_manufacturer); + $this->em->flush(); + + // add new manufacturer to hash + $normalized_name = $this->normalizeName($name); + $this->bmanu_hash[$normalized_name] = $batt_manufacturer; + } + + protected function addBatteryModel($name) + { + $batt_model = new BatteryModel(); + + $batt_model->setName($name); + + $this->em->persist($batt_model); + $this->em->flush(); + + // add new model to hash + $normalized_name = $this->normalizeName($name); + $this->bmodel_hash[$normalized_name] = $batt_model; + } + + protected function addBatterySize($name) + { + if (!empty($name)) + { + // save to db + $batt_size = new BatterySize(); + + $batt_size->setName($name); + + $this->em->persist($batt_size); + $this->em->flush(); + + // add new size into hash + $normalized_name = $this->normalizeName($name); + $this->bsize_hash[$normalized_name] = $batt_size; + } + } + + protected function addBattery($manufacturer, $brand, $size, $code, $price) + { + // save to db + $bmanu = $this->bmanu_hash[$manufacturer]; + $bmodel = $this->bmodel_hash[$brand]; + $bsize = $this->bsize_hash[$size]; + + $battery = new Battery(); + $battery->setManufacturer($bmanu) + ->setModel($bmodel) + ->setSize($bsize) + ->setWarrantyPrivate(21) + ->setWarrantyCommercial(6) + ->setWarrantyTnv(12) + ->setProductCode($code) + ->setSAPCode($code) + ->setSellingPrice($price); + + $this->em->persist($battery); + $this->em->flush(); + + // insert into hash + $this->batt_hash[$brand][$brand][$size] = $battery; + + // add battery into battery manufacturer, battery model, and battery size + $bmanu->addBattery($battery); + $bmodel->addBattery($battery); + $bsize->addBattery($battery); + + $this->em->persist($bmanu); + $this->em->persist($bmodel); + $this->em->persist($bsize); + + $this->em->flush(); + } + + protected function loadBatteryManufacturers() + { + $this->bmanu_hash = []; + + $batt_manufacturers = $this->em->getRepository(BatteryManufacturer::class)->findAll(); + foreach ($batt_manufacturers as $batt_manu) + { + $name = $this->normalizeName($batt_manu->getName()); + $this->bmanu_hash[$name] = $batt_manu; + } + } + + protected function loadBatteryModels() + { + $this->bmodel_hash = []; + + $batt_models = $this->em->getRepository(BatteryModel::class)->findAll(); + foreach ($batt_models as $batt_model) + { + $name = $this->normalizeName($batt_model->getName()); + $this->bmodel_hash[$name] = $batt_model; + } + } + + protected function loadBatterySizes() + { + $this->bsize_hash = []; + + $batt_sizes = $this->em->getRepository(BatterySize::class)->findAll(); + foreach ($batt_sizes as $batt_size) + { + $name = $this->normalizeName($batt_size->getName()); + $this->bsize_hash[$name] = $batt_size; + } + } + + protected function loadBatteries() + { + $this->batt_hash = []; + + $batts = $this->em->getRepository(Battery::class)->findAll(); + foreach ($batts as $batt) + { + $brand = $this->normalizeName($batt->getManufacturer()->getName()); + $model = $this->normalizeName($batt->getModel()->getName()); + $size = $this->normalizeName($batt->getSize()->getName()); + + $this->batt_hash[$brand][$model][$size] = $batt; + } + } + + protected function normalizeName($name) + { + $normalized_key = trim(strtolower($name)); + + return $normalized_key; + } + +} diff --git a/src/Command/ImportCMBBatteryTradeInPriceCommand.php b/src/Command/ImportCMBBatteryTradeInPriceCommand.php new file mode 100644 index 00000000..b75f9ebb --- /dev/null +++ b/src/Command/ImportCMBBatteryTradeInPriceCommand.php @@ -0,0 +1,113 @@ +em = $om; + + // load existing sizes + $this->loadBatterySizes(); + + parent::__construct(); + } + + protected function configure() + { + $this->setName('cmbbatterydata:importtradeinprice') + ->setDescription('Import a CSV file with trade in prices.') + ->setHelp('Adds the battery tradein prices to existing batteries based on imported CSV.') + ->addArgument('file', InputArgument::REQUIRED, 'Path to the CSV file.'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $csv_file = $input->getArgument('file'); + + // attempt to open file + try + { + $fh = fopen($csv_file, "r"); + } + catch (Exception $e) + { + throw new Exception('The file "' . $csv_file . '" could be read.'); + } + + // get entity manager + $em = $this->em; + + // loop through the rows + $row_num = 0; + error_log('Processing battery tradein price csv file...'); + while (($fields = fgetcsv($fh)) !== false) + { + // data starts at row 2 + if ($row_num < 2) + { + $row_num++; + continue; + } + + // tradein price info + // battery price info + $desc = trim($fields[self::F_SIZE_DESC]); + $price = trim($fields[self::F_TRADEIN_PRICE]); + + $clean_price = trim($price, '$'); + + $size_info = explode(' ', $desc); + + $size = $size_info[1]; + + if (isset($this->bsize_hash[$size])) + { + $battery_size = $this->bsize_hash[$size]; + + // use TIPriceMotolite + $battery_size->setTIPriceMotolite($clean_price); + + $this->em->persist($battery_size); + $this->em->flush(); + } + else + { + error_log('Cannot find battery size ' . $size); + } + } + + return 0; + } + + protected function loadBatterySizes() + { + $this->bsize_hash = []; + + $batt_sizes = $this->em->getRepository(BatterySize::class)->findAll(); + foreach ($batt_sizes as $batt_size) + { + $name = $batt_size->getName(); + $this->bsize_hash[$name] = $batt_size; + } + } + + +} diff --git a/src/Command/ImportCMBVehicleCompatibilityCommand.php b/src/Command/ImportCMBVehicleCompatibilityCommand.php new file mode 100644 index 00000000..4034aec8 --- /dev/null +++ b/src/Command/ImportCMBVehicleCompatibilityCommand.php @@ -0,0 +1,449 @@ +em = $om; + + // load existing battery data + $this->loadBatteryManufacturers(); + $this->loadBatteryModels(); + $this->loadBatterySizes(); + $this->loadBatteries(); + + // load existing vehicle data + $this->loadVehicleManufacturers(); + $this->loadVehicleMakes(); + + parent::__construct(); + } + + protected function configure() + { + $this->setName('cmbvehiclecompatibility:import') + ->setDescription('Retrieve from a CSV file battery and vehicle information.') + ->setHelp('Creates battery manufacturers, models, sizes, vehicle makes, and models based on data from imported CSV.') + ->addArgument('file', InputArgument::REQUIRED, 'Path to the CSV file.'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $csv_file = $input->getArgument('file'); + + // attempt to open file + try + { + $fh = fopen($csv_file, "r"); + } + catch (Exception $e) + { + throw new Exception('The file "' . $csv_file . '" could be read.'); + } + + // get entity manager + $em = $this->em; + + $row_num = 0; + error_log('Processing vehicle compatibility csv file...'); + while (($fields = fgetcsv($fh)) !== false) + { + $comp_batteries = []; + if ($row_num < 2) + { + $row_num++; + continue; + } + + // initialize size battery array for cases where the battery size has '/' + $sdfc_sizes = []; + $ultramax_sizes = []; + $motolite_sizes = []; + $marathoner_sizes = []; + $excel_sizes = []; + + // battery info + $sdfc_size = trim($fields[self::F_BATT_SDFC]); + $ultramax_size = trim($fields[self::F_BATT_ULTRAMAX]); + $motolite_size = trim($fields[self::F_BATT_MOTOLITE]); + $marathoner_size = trim($fields[self::F_BATT_MARATHONER]); + $excel_size = trim($fields[self::F_BATT_EXCEL]); + + // check the sizes for '/' + $pos = stripos($sdfc_size, '/'); + if ($pos == false) + { + // no '/' in size + $sdfc_sizes[] = $this->normalizeName($sdfc_size); + } + else + { + // we have '/' in size so we have to explode + $sizes = explode('/', $sdfc_size); + foreach ($sizes as $size) + { + $sdfc_sizes[] = $this->normalizeName($size); + } + } + + $pos = stripos($motolite_size, '/'); + if ($pos == false) + { + // no '/' in size + $motolite_sizes[] = $this->normalizeName($motolite_size); + } + else + { + // we have '/' in size so we have to explode + $sizes = explode('/', $motolite_size); + foreach ($sizes as $size) + { + $motolite_sizes[] = $this->normalizeName($size); + } + } + + $pos = stripos($marathoner_size, '/'); + if ($pos == false) + { + // no '/' in size + $marathoner_sizes[] = $this->normalizeName($marathoner_size); + } + else + { + // we have '/' in size so we have to explode + $sizes = explode('/', $marathoner_size); + foreach ($sizes as $size) + { + $marathoner_sizes[] = $this->normalizeName($size); + } + } + + $pos = stripos($ultramax_size, '/'); + if ($pos == false) + { + // no '/' in size + $ultramax_sizes[] = $this->normalizeName($ultramax_size); + } + else + { + // we have '/' in size so we have to explode + $sizes = explode('/', $ultramax_size); + foreach ($sizes as $size) + { + $ultramax_sizes[] = $this->normalizeName($size); + } + } + + $pos = stripos($excel_size, '/'); + if ($pos == false) + { + // no '/' in size + $excel_sizes[] = $this->normalizeName($excel_size); + } + else + { + // we have '/' in size so we have to explode + $sizes = explode('/', $excel_size); + foreach ($sizes as $size) + { + $excel_sizes[] = $this->normalizeName($size); + } + } + + + // normalize the battery manufacturers and battery models + $norm_century = $this->normalizeName(self::STR_CENTURY); + $norm_sdfc = $this->normalizeName(self::STR_SDFC); + $norm_motolite = $this->normalizeName(self::STR_MOTOLITE); + $norm_wetcharged = $this->normalizeName(self::STR_WETCHARGED); + $norm_marathoner = $this->normalizeName(self::STR_MARATHONER); + $norm_ultramax = $this->normalizeName(self::STR_ULTRAMAX); + $norm_excel = $this->normalizeName(self::STR_EXCEL); + + //foreach($sdfc_sizes as $size) + //{ + // error_log('sdfc size ' . $size); + //} + //foreach($motolite_sizes as $size) + //{ + // error_log('motolite size ' . $size); + //} + //foreach($marathoner_sizes as $size) + //{ + // error_log('marathoner size ' . $size); + //} + + // vehicle info + $manufacturer = trim($fields[self::F_VEHICLE_MANUFACTURER]); + $make = trim($fields[self::F_VEHICLE_MAKE]); + $year = trim($fields[self::F_VEHICLE_YEAR]); + + // vehicle data + // check if vehicle manufacturer has been added + if (!isset($this->vmanu_hash[$manufacturer])) + $this->addVehicleManufacturer($manufacturer); + + // check if vehicle make has been added + if (!isset($this->vmake_hash[$manufacturer][$make])) + { + foreach($sdfc_sizes as $size) + { + if (!(empty($size))) + { + if (isset($this->batt_hash[$norm_century][$norm_sdfc][$size])) + $comp_batteries[] = $this->batt_hash[$norm_century][$norm_sdfc][$size]; + else + error_log('Not in the system: ' . $norm_century . ' ' . $norm_sdfc . ' ' . $size); + } + } + foreach($ultramax_sizes as $size) + { + if (!(empty($size))) + { + if (isset($this->batt_hash[$norm_ultramax][$norm_ultramax][$size])) + $comp_batteries[] = $this->batt_hash[$norm_ultramax][$norm_ultramax][$size]; + else + error_log('Not in the system: ' . $norm_ultramax . ' ' . $norm_ultramax . ' ' . $size); + } + } + foreach($motolite_sizes as $size) + { + if (!(empty($size))) + { + if (isset($this->batt_hash[$norm_motolite][$norm_wetcharged][$size])) + $comp_batteries[] = $this->batt_hash[$norm_motolite][$norm_wetcharged][$size]; + else + error_log('Not in the system: ' . $norm_motolite . ' ' . $norm_wetcharged . ' ' . $size); + } + } + foreach($marathoner_sizes as $size) + { + if (!(empty($size))) + { + if (isset($this->batt_hash[$norm_century][$norm_marathoner][$size])) + $comp_batteries[] = $this->batt_hash[$norm_century][$norm_marathoner][$size]; + else + error_log('Not in the system: ' . $norm_century . ' ' . $norm_marathoner . ' ' . $size); + } + } + foreach($excel_sizes as $size) + { + if (!(empty($size))) + { + if (isset($this->batt_hash[$norm_excel][$norm_excel][$size])) + $comp_batteries[] = $this->batt_hash[$norm_excel][$norm_excel][$size]; + else + error_log('Not in the system: ' . $norm_excel . ' ' . $norm_excel . ' ' . $size); + } + } + $this->addVehicleMake($manufacturer, $make, $year, $comp_batteries); + } + + $row_num++; + } + + return 0; + } + + protected function addVehicleManufacturer($name) + { + // save to db + $vehicle_manufacturer = new VehicleManufacturer(); + + $vehicle_manufacturer->setName($name); + + $this->em->persist($vehicle_manufacturer); + $this->em->flush(); + + // add to hash + $this->vmanu_hash[$name] = $vehicle_manufacturer; + } + + protected function addVehicleMake($manufacturer, $make, $year, $batteries) + { + // save to db + $vehicle = new Vehicle(); + + $vmanu = $this->vmanu_hash[$manufacturer]; + + // parse year from and year to + $year_from = ''; + $year_to = ''; + + if (!empty($year)) + { + $model_years = explode('-', $year); + $year_from = $model_years[0]; + if (!empty($year_to)) + $year_to = $model_years[1]; + + // check if $year_to is the string "Present" + // if so, set to 0, for now + if ($year_to == self::STR_PRESENT) + $year_to = 0; + } + + $vehicle->setManufacturer($vmanu) + ->setMake($make) + ->setModelYearFrom($year_from) + ->setModelYearTo($year_to); + + // add vehicle to battery + foreach ($batteries as $battery) + { + $battery->addVehicle($vehicle); + $this->em->persist($battery); + } + + // add vehicle to manufacturer + $vmanu->addVehicle($vehicle); + + $this->em->persist($vmanu); + $this->em->persist($vehicle); + $this->em->flush(); + + // add to hash + $this->vmake_hash[$manufacturer][$make] = $vehicle; + } + + protected function loadBatteryManufacturers() + { + $this->bmanu_hash = []; + + $batt_manufacturers = $this->em->getRepository(BatteryManufacturer::class)->findAll(); + foreach ($batt_manufacturers as $batt_manu) + { + $name = $this->normalizeName($batt_manu->getName()); + $this->bmanu_hash[$name] = $batt_manu; + } + } + + protected function loadBatteryModels() + { + $this->bmodel_hash = []; + + $batt_models = $this->em->getRepository(BatteryModel::class)->findAll(); + foreach ($batt_models as $batt_model) + { + $name = $this->normalizeName($batt_model->getName()); + $this->bmodel_hash[$name] = $batt_model; + } + } + + protected function loadBatterySizes() + { + $this->bsize_hash = []; + + $batt_sizes = $this->em->getRepository(BatterySize::class)->findAll(); + foreach ($batt_sizes as $batt_size) + { + $name = $this->normalizeName($batt_size->getName()); + $this->bsize_hash[$name] = $batt_size; + } + } + + protected function loadBatteries() + { + $this->batt_hash = []; + + $batts = $this->em->getRepository(Battery::class)->findAll(); + foreach ($batts as $batt) + { + $brand = $this->normalizeName($batt->getManufacturer()->getName()); + $model = $this->normalizeName($batt->getModel()->getName()); + $size = $this->normalizeName($batt->getSize()->getName()); + + $this->batt_hash[$brand][$model][$size] = $batt; + } + } + + protected function loadVehicleManufacturers() + { + $this->vmanu_hash = []; + + $vmanus = $this->em->getRepository(VehicleManufacturer::class)->findAll(); + foreach ($vmanus as $vmanu) + { + $name = $vmanu->getName(); + $this->vmanu_hash[$name] = $vmanu; + } + } + + protected function loadVehicleMakes() + { + $this->vmake_hash = []; + + $vmakes = $this->em->getRepository(Vehicle::class)->findAll(); + foreach ($vmakes as $vmake) + { + $manufacturer = $vmake->getManufacturer()->getName(); + $make = $vmake->getMake(); + + $this->vmake_hash[$manufacturer][$make] = $vmake; + } + } + + protected function normalizeName($name) + { + // check for M-42. Need to convert to M42 + if (strcasecmp($name, self::STR_M_42) == 0) + { + $normalized_key = strtolower(self::STR_M42); + } + else + { + $normalized_key = trim(strtolower($name)); + } + + return $normalized_key; + } + +} diff --git a/src/Command/ImportCustomerCommand.php b/src/Command/ImportCustomerCommand.php index 4c0e75e7..e2c55832 100644 --- a/src/Command/ImportCustomerCommand.php +++ b/src/Command/ImportCustomerCommand.php @@ -479,5 +479,7 @@ class ImportCustomerCommand extends Command } fclose($cust_file); fclose($cv_file); + + return 0; } } diff --git a/src/Command/ImportKMLFileCommand.php b/src/Command/ImportKMLFileCommand.php index 4d2e71a2..64bd2b7d 100644 --- a/src/Command/ImportKMLFileCommand.php +++ b/src/Command/ImportKMLFileCommand.php @@ -35,5 +35,7 @@ class ImportKMLFileCommand extends Command $kml_file = $input->getArgument('file'); $this->importer->getMapData($kml_file); + + return 0; } } diff --git a/src/Command/ImportLegacyJobOrderCommand.php b/src/Command/ImportLegacyJobOrderCommand.php index 76e30d10..bf5b3d52 100644 --- a/src/Command/ImportLegacyJobOrderCommand.php +++ b/src/Command/ImportLegacyJobOrderCommand.php @@ -544,6 +544,8 @@ class ImportLegacyJobOrderCommand extends Command fclose($warr_outfile); fclose($jo_outfile); fclose($jorow_outfile); + + return 0; } protected function initializeMaxFieldCounters() diff --git a/src/Command/ImportOutletsCommand.php b/src/Command/ImportOutletsCommand.php index 1558b74e..158929b8 100644 --- a/src/Command/ImportOutletsCommand.php +++ b/src/Command/ImportOutletsCommand.php @@ -168,7 +168,8 @@ class ImportOutletsCommand extends Command { $output->writeln("Errors occurred! Please check the CSV file is in the proper format."); } - + + return 0; } protected function setObject($obj, $fields) diff --git a/src/Command/ImportPartnersCommand.php b/src/Command/ImportPartnersCommand.php index 1a09df6d..facc74df 100644 --- a/src/Command/ImportPartnersCommand.php +++ b/src/Command/ImportPartnersCommand.php @@ -163,6 +163,7 @@ class ImportPartnersCommand extends Command $row_num++; } + return 0; } } diff --git a/src/Command/ImportSAPBatteryCommand.php b/src/Command/ImportSAPBatteryCommand.php index 9189a0e5..439cb230 100644 --- a/src/Command/ImportSAPBatteryCommand.php +++ b/src/Command/ImportSAPBatteryCommand.php @@ -145,6 +145,8 @@ class ImportSAPBatteryCommand extends Command { $this->writeDupeReport($report_file, $dupe_brands, $dupe_sizes, $dupe_batteries); } + + return 0; } protected function initBatteryHash() diff --git a/src/Command/ImportVehicleBatteryCommand.php b/src/Command/ImportVehicleBatteryCommand.php index c3511330..79ee07dc 100644 --- a/src/Command/ImportVehicleBatteryCommand.php +++ b/src/Command/ImportVehicleBatteryCommand.php @@ -221,6 +221,6 @@ class ImportVehicleBatteryCommand extends Command $em->flush(); - // print_r($batteries); + return 0; } } diff --git a/src/Command/ImportVehicleCompatibilityCommand.php b/src/Command/ImportVehicleCompatibilityCommand.php index 481579e1..14517885 100644 --- a/src/Command/ImportVehicleCompatibilityCommand.php +++ b/src/Command/ImportVehicleCompatibilityCommand.php @@ -317,6 +317,6 @@ class ImportVehicleCompatibilityCommand extends Command $em->flush(); - // print_r($batteries); + return 0; } } diff --git a/src/Command/MergeDuplicateVehiclesCommand.php b/src/Command/MergeDuplicateVehiclesCommand.php index 99bbbec4..79b7ce52 100644 --- a/src/Command/MergeDuplicateVehiclesCommand.php +++ b/src/Command/MergeDuplicateVehiclesCommand.php @@ -161,8 +161,7 @@ class MergeDuplicateVehiclesCommand extends Command $em->flush(); } } - - // print_r($dupes); + return 0; } } diff --git a/src/Command/RefreshJobOrderCacheCommand.php b/src/Command/RefreshJobOrderCacheCommand.php new file mode 100644 index 00000000..99fcb331 --- /dev/null +++ b/src/Command/RefreshJobOrderCacheCommand.php @@ -0,0 +1,70 @@ +em = $om; + $this->jo_cache = $jo_cache; + + parent::__construct(); + } + + protected function configure() + { + $this->setName('joborder:refresh_cache') + ->setDescription('Refresh active job order cache from database.') + ->setHelp('Refresh active job order cache from database.'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $date = new DateTime(); + $date->modify('-3 day'); + + $status_list = [ + JOStatus::PENDING, + JOStatus::RIDER_ASSIGN, + JOStatus::ASSIGNED, + JOStatus::IN_TRANSIT, + JOStatus::IN_PROGRESS, + ]; + + $qb = $this->em->getRepository(JobOrder::class) + ->createQueryBuilder('jo'); + $res = $qb->select('jo') + ->where('jo.status IN (:statuses)') + ->andWhere('jo.date_schedule >= :date') + ->setParameter('statuses', $status_list, Connection::PARAM_STR_ARRAY) + ->setParameter('date', $date) + ->getQuery() + ->execute(); + + // fulfill each + foreach ($res as $jo) + { + $this->jo_cache->addActiveJobOrder($jo); + } + + return 0; + } +} diff --git a/src/Command/ReportRiderTime.php b/src/Command/ReportRiderTime.php index e8825b5b..62a2a8e6 100644 --- a/src/Command/ReportRiderTime.php +++ b/src/Command/ReportRiderTime.php @@ -142,5 +142,7 @@ class ReportRiderTime extends Command } fclose($fh); + + return 0; } } diff --git a/src/Command/SeedRiderSessionsCommand.php b/src/Command/SeedRiderSessionsCommand.php new file mode 100644 index 00000000..e1c44954 --- /dev/null +++ b/src/Command/SeedRiderSessionsCommand.php @@ -0,0 +1,61 @@ +em = $om; + $this->redis = $redis->getRedisClient(); + + parent::__construct(); + } + + protected function configure() + { + $this->setName('rider:session:seed') + ->setDescription('Seed current rider sessions') + ->setHelp('Seed current rider sessions'); + } + + + protected function execute(InputInterface $input, OutputInterface $output) + { + // get all rider sessions + $r_sessions = $this->em->getRepository(RiderSession::class)->findAll(); + foreach ($r_sessions as $session) + { + // get session id + $session_id = $session->getID(); + + // get rider id + if ($session->getRider() != null) + { + $rider_id = $session->getRider()->getID(); + + // key for redis + $redis_key = 'rider.id.' . $session_id; + //$output->writeln('key: ' . $redis_key); + + // set to redis cache + $this->redis->set($redis_key, $rider_id); + } + } + + return 0; + } +} diff --git a/src/Command/SetCustomerPrivacyPolicyCommand.php b/src/Command/SetCustomerPrivacyPolicyCommand.php index e5d27617..6700b8e6 100644 --- a/src/Command/SetCustomerPrivacyPolicyCommand.php +++ b/src/Command/SetCustomerPrivacyPolicyCommand.php @@ -80,5 +80,7 @@ class SetCustomerPrivacyPolicyCommand extends Command } $this->em->flush(); + + return 0; } } diff --git a/src/Command/TestGeneralSearchCommand.php b/src/Command/TestGeneralSearchCommand.php index 0696fb86..c5d8d335 100644 --- a/src/Command/TestGeneralSearchCommand.php +++ b/src/Command/TestGeneralSearchCommand.php @@ -66,5 +66,7 @@ class TestGeneralSearchCommand extends Command echo "Last Name: " . $warranty->getLastName() . "\n"; echo "First Name: " . $warranty->getFirstName() . "\n"; } + + return 0; } } diff --git a/src/Command/TestGeofenceCommand.php b/src/Command/TestGeofenceCommand.php index 1397f08f..a4bdff69 100644 --- a/src/Command/TestGeofenceCommand.php +++ b/src/Command/TestGeofenceCommand.php @@ -40,5 +40,7 @@ class TestGeofenceCommand extends Command echo "In geofence\n"; else echo "NOT in geofence\n"; + + return 0; } } diff --git a/src/Command/TestHubCounterCommand.php b/src/Command/TestHubCounterCommand.php index e08c11dd..f9ba5eee 100644 --- a/src/Command/TestHubCounterCommand.php +++ b/src/Command/TestHubCounterCommand.php @@ -35,5 +35,7 @@ class TestHubCounterCommand extends Command $available_rc = $this->hc->getAvailableRiderCount($hub_id); echo "Available Riders in Hub: " . $available_rc . "\n"; + + return 0; } } diff --git a/src/Command/TestJobOrderManagerCommand.php b/src/Command/TestJobOrderManagerCommand.php index 4beeea46..7fefe37f 100644 --- a/src/Command/TestJobOrderManagerCommand.php +++ b/src/Command/TestJobOrderManagerCommand.php @@ -36,5 +36,7 @@ class TestJobOrderManagerCommand extends Command echo "Job Order successfully updated" . "\n"; else echo "Job Order not updated" . "\n"; + + return 0; } } diff --git a/src/Command/TestRegAPNSCommand.php b/src/Command/TestRegAPNSCommand.php index 52ec7d76..aeb7ad6a 100644 --- a/src/Command/TestRegAPNSCommand.php +++ b/src/Command/TestRegAPNSCommand.php @@ -34,5 +34,7 @@ class TestRegAPNSCommand extends Command { $push_id = $input->getArgument('push_id'); $this->apns->sendReg($push_id, 'Test Delay', 'Body'); + + return 0; } } diff --git a/src/Command/TestRiderTrackerCommand.php b/src/Command/TestRiderTrackerCommand.php index 7b26a9ed..c975c16e 100644 --- a/src/Command/TestRiderTrackerCommand.php +++ b/src/Command/TestRiderTrackerCommand.php @@ -42,5 +42,7 @@ class TestRiderTrackerCommand extends Command { echo "Invalid rider id." . "\n"; } + + return 0; } } diff --git a/src/Command/UpdateCustomerVehicleBatteryCommand.php b/src/Command/UpdateCustomerVehicleBatteryCommand.php index 57ad1246..3b858711 100644 --- a/src/Command/UpdateCustomerVehicleBatteryCommand.php +++ b/src/Command/UpdateCustomerVehicleBatteryCommand.php @@ -63,5 +63,7 @@ class UpdateCustomerVehicleBatteryCommand extends Command } } } + + return 0; } } diff --git a/src/Command/UpdateCustomerVehicleWarrantyCommand.php b/src/Command/UpdateCustomerVehicleWarrantyCommand.php index 98fec1cb..20fad9b1 100644 --- a/src/Command/UpdateCustomerVehicleWarrantyCommand.php +++ b/src/Command/UpdateCustomerVehicleWarrantyCommand.php @@ -94,5 +94,7 @@ class UpdateCustomerVehicleWarrantyCommand extends Command $this->em->detach($row[0]); } + + return 0; } } diff --git a/src/Command/UploadCleanupCommand.php b/src/Command/UploadCleanupCommand.php index ffa1828b..0bb98353 100644 --- a/src/Command/UploadCleanupCommand.php +++ b/src/Command/UploadCleanupCommand.php @@ -96,5 +96,7 @@ class UploadCleanupCommand extends Command $output->writeln('Deleted ' . $i . ' orphaned file(s). Done!'); else $output->writeln('No files found. Done!'); + + return 0; } } diff --git a/src/Command/UserCreateCommand.php b/src/Command/UserCreateCommand.php index 3b10d771..697f66fe 100644 --- a/src/Command/UserCreateCommand.php +++ b/src/Command/UserCreateCommand.php @@ -67,5 +67,7 @@ class UserCreateCommand extends Command $em->persist($user); $em->flush(); + + return 0; } } diff --git a/src/Controller/APIController.php b/src/Controller/APIController.php index 197021c5..9765ad8f 100644 --- a/src/Controller/APIController.php +++ b/src/Controller/APIController.php @@ -24,7 +24,7 @@ use App\Ramcar\TransactionOrigin; use App\Ramcar\TradeInType; use App\Ramcar\JOEventType; -use App\Service\InvoiceCreator; +use App\Service\InvoiceGeneratorInterface; use App\Service\RisingTideGateway; use App\Service\MQTTClient; use App\Service\GeofenceTracker; @@ -817,7 +817,7 @@ class APIController extends Controller return $res->getReturnResponse(); } - public function requestJobOrder(Request $req, InvoiceCreator $ic, GeofenceTracker $geo) + public function requestJobOrder(Request $req, InvoiceGeneratorInterface $ic, GeofenceTracker $geo) { // check required parameters and api key $required_params = [ @@ -979,7 +979,7 @@ class APIController extends Controller $icrit->addEntry($batt, $trade_in, 1); // send to invoice generator - $invoice = $ic->processCriteria($icrit); + $invoice = $ic->generateInvoice($icrit); $jo->setInvoice($invoice); $em->persist($jo); @@ -1026,7 +1026,7 @@ class APIController extends Controller return $res->getReturnResponse(); } - public function getEstimate(Request $req, InvoiceCreator $ic) + public function getEstimate(Request $req, InvoiceGeneratorInterface $ic) { // $this->debugRequest($req); @@ -1126,7 +1126,7 @@ class APIController extends Controller $icrit->addEntry($batt, $trade_in, 1); // send to invoice generator - $invoice = $ic->processCriteria($icrit); + $invoice = $ic->generateInvoice($icrit); // make invoice json data $data = [ diff --git a/src/Controller/CustomerController.php b/src/Controller/CustomerController.php index 1f3849ad..7421fec0 100644 --- a/src/Controller/CustomerController.php +++ b/src/Controller/CustomerController.php @@ -2,134 +2,54 @@ namespace App\Controller; -use App\Ramcar\CustomerClassification; -use App\Ramcar\FuelType; -use App\Ramcar\VehicleStatusCondition; use App\Ramcar\CrudException; +use App\Service\CustomerHandlerInterface; use App\Entity\Customer; -use App\Entity\CustomerVehicle; -use App\Entity\MobileNumber; -use App\Entity\Vehicle; -use App\Entity\VehicleManufacturer; -use App\Entity\Battery; -use App\Entity\BatteryManufacturer; -use Doctrine\ORM\Query; +use Doctrine\ORM\EntityManagerInterface; + use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Catalyst\MenuBundle\Annotation\Menu; -use DateTime; - class CustomerController extends Controller { + /** * @Menu(selected="customer_list") */ - public function index() + public function index(CustomerHandlerInterface $cust_handler) { $this->denyAccessUnlessGranted('customer.list', null, 'No access.'); - return $this->render('customer/list.html.twig'); + $params = $cust_handler->initializeCustomerIndexForm(); + + $template = $params['template']; + + return $this->render($template); } - public function rows(Request $req) + public function rows(Request $req, CustomerHandlerInterface $cust_handler) { $this->denyAccessUnlessGranted('customer.list', null, 'No access.'); - // build query - $tqb = $this->getDoctrine() - ->getRepository(Customer::class) - ->createQueryBuilder('q'); - - $qb = $this->getDoctrine() - ->getRepository(Customer::class) - ->createQueryBuilder('q'); - - // get datatable params - $datatable = $req->request->get('datatable'); - - // count total records - $tquery = $tqb->select('COUNT(q)'); - - // add filters to count query - $this->setQueryFilters($datatable, $tquery); - - $total = $tquery->getQuery() - ->getSingleScalarResult(); - - // get current page number - $page = $datatable['pagination']['page'] ?? 1; - - $perpage = $datatable['pagination']['perpage']; - $offset = ($page - 1) * $perpage; - - // add metadata - $meta = [ - 'page' => $page, - 'perpage' => $perpage, - 'pages' => ceil($total / $perpage), - 'total' => $total, - 'sort' => 'asc', - 'field' => 'id' - ]; - - // build query - $query = $qb->select('q'); - - // add filters to query - $this->setQueryFilters($datatable, $query); - - // check if sorting is present, otherwise use default - if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) { - $order = $datatable['sort']['sort'] ?? 'asc'; - $query->orderBy('q.' . $datatable['sort']['field'], $order); - } else { - $query->orderBy('q.first_name', 'asc'); - } - - // get rows for this page - $obj_rows = $query->setFirstResult($offset) - ->setMaxResults($perpage) - ->getQuery() - ->getResult(); + $params = $cust_handler->getCustomers($req); + $meta = $params['meta']; + $rows = $params['rows']; // process rows - $rows = []; - foreach ($obj_rows as $orow) { - // add row data - $row['id'] = $orow->getID(); - $row['title'] = $orow->getTitle(); - $row['first_name'] = $orow->getFirstName(); - $row['last_name'] = $orow->getLastName(); - $row['customer_classification'] = CustomerClassification::getName($orow->getCustomerClassification()); - $row['flag_mobile_app'] = $orow->hasMobileApp(); - $row['app_mobile_number'] = $orow->hasMobileApp() && !empty($orow->getMobileSessions()) ? $orow->getMobileSessions()[0]->getPhoneNumber() : ''; - $row['flag_active'] = $orow->isActive(); - $row['flag_csat'] = $orow->isCSAT(); - - // TODO: properly add mobile numbers and plate numbers as searchable/sortable fields, use doctrine events - $row['mobile_numbers'] = implode("
", $orow->getMobileNumberList()); - $row['plate_numbers'] = implode("
", $orow->getPlateNumberList()); - - // add row metadata - $row['meta'] = [ - 'update_url' => '', - 'delete_url' => '' - ]; - + foreach ($rows as $key => $data) { // add crud urls - if ($this->isGranted('customer.update')) - $row['meta']['update_url'] = $this->generateUrl('customer_update', ['id' => $row['id']]); - if ($this->isGranted('customer.delete')) - $row['meta']['delete_url'] = $this->generateUrl('customer_delete', ['id' => $row['id']]); + $cust_id = $rows[$key]['id']; - $rows[] = $row; + if ($this->isGranted('customer.update')) + $rows[$key]['meta']['update_url'] = $this->generateUrl('customer_update', ['id' => $cust_id]); + if ($this->isGranted('customer.delete')) + $rows[$key]['meta']['delete_url'] = $this->generateUrl('customer_delete', ['id' => $cust_id]); } // response @@ -139,347 +59,119 @@ class CustomerController extends Controller ]); } - protected function fillDropdownParameters(&$params) - { - $em = $this->getDoctrine()->getManager(); - - $params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); - $params['vmfgs'] = $em->getRepository(VehicleManufacturer::class)->findAll(); - - $params['classifications'] = CustomerClassification::getCollection(); - $params['fuel_types'] = FuelType::getCollection(); - $params['status_conditions'] = VehicleStatusCondition::getCollection(); - - $params['years'] = $this->generateYearOptions(); - $params['batteries'] = $em->getRepository(Battery::class)->findAll(); - } - /** * @Menu(selected="customer_list") */ - public function addForm() + public function addForm(CustomerHandlerInterface $cust_handler) { $this->denyAccessUnlessGranted('customer.add', null, 'No access.'); - $params['obj'] = new Customer(); - $params['mode'] = 'create'; + $params = $cust_handler->initializeAddCustomerForm(); - // get dropdown parameters - $this->fillDropdownParameters($params); + $template = $params['template']; // response - return $this->render('customer/form.html.twig', $params); + return $this->render($template, $params); } - protected function setObject($obj, $req) - { - // set and save values - $obj->setTitle($req->request->get('title')) - ->setFirstName($req->request->get('first_name')) - ->setLastName($req->request->get('last_name')) - ->setCustomerClassification($req->request->get('customer_classification')) - ->setCustomerNotes($req->request->get('customer_notes')) - ->setEmail($req->request->get('email')) - ->setIsCSAT($req->request->get('flag_csat') ? true : false) - ->setActive($req->request->get('flag_active') ? true : false); - - // phone numbers - $obj->setPhoneMobile($req->request->get('phone_mobile')) - ->setPhoneLandline($req->request->get('phone_landline')) - ->setPhoneOffice($req->request->get('phone_office')) - ->setPhoneFax($req->request->get('phone_fax')); - } - - - public function addSubmit(Request $req, ValidatorInterface $validator) + public function addSubmit(Request $req, CustomerHandlerInterface $cust_handler) { $this->denyAccessUnlessGranted('customer.add', null, 'No access.'); - // create new row - $em = $this->getDoctrine()->getManager(); - $row = new Customer(); + $result = $cust_handler->addCustomer($req); - $this->setObject($row, $req); - - // initialize error lists - $error_array = []; - $nerror_array = []; - $verror_array = []; - - // error_log(print_r($req->request->all(), true)); - - // custom validation for vehicles - $vehicles = json_decode($req->request->get('vehicles')); - - if (!empty($vehicles)) { - foreach ($vehicles as $vehicle) { - // check if vehicle exists - $vobj = $em->getRepository(Vehicle::class)->find($vehicle->vehicle); - - if (empty($vobj)) { - $verror_array[$vehicle->index]['vehicle'] = 'Invalid vehicle specified.'; - } else { - $cust_vehicle = new CustomerVehicle(); - $cust_vehicle->setName($vehicle->name) - ->setVehicle($vobj) - ->setPlateNumber($vehicle->plate_number) - ->setModelYear($vehicle->model_year) - ->setColor($vehicle->color) - ->setStatusCondition($vehicle->status_condition) - ->setFuelType($vehicle->fuel_type) - ->setActive($vehicle->flag_active) - ->setCustomer($row); - - // if specified, check if battery exists - if ($vehicle->battery) { - // check if battery exists - $bobj = $em->getRepository(Battery::class)->find($vehicle->battery); - - if (empty($bobj)) { - $verror_array[$vehicle->index]['battery'] = 'Invalid battery specified.'; - } else { - // check if warranty expiration was specified - $warr_ex = DateTime::createFromFormat("d M Y", $vehicle->warranty_expiration); - if (!$warr_ex) - $warr_ex = null; - - $cust_vehicle->setHasMotoliteBattery(true) - ->setCurrentBattery($bobj) - ->setWarrantyCode($vehicle->warranty_code) - ->setWarrantyExpiration($warr_ex); - } - } else { - $cust_vehicle->setHasMotoliteBattery(false); - } - - $verrors = $validator->validate($cust_vehicle); - - // add errors to list - foreach ($verrors as $error) { - if (!isset($verror_array[$vehicle->index])) - $verror_array[$vehicle->index] = []; - - $verror_array[$vehicle->index][$error->getPropertyPath()] = $error->getMessage(); - } - - // add to entity - if (!isset($verror_array[$vehicle->index])) { - $row->addVehicle($cust_vehicle); - } - } - } - } - - // validate - $errors = $validator->validate($row); - - // add errors to list - foreach ($errors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } - - // check if any errors were found - if (!empty($error_array) || !empty($nerror_array) || !empty($verror_array)) { - // return validation failure response - return $this->json([ - 'success' => false, - 'errors' => $error_array, - 'nerrors' => $nerror_array, - 'verrors' => $verror_array - ], 422); - } else { - // validated! save the entity - $em->persist($row); - $em->flush(); + if (isset($result['id'])) + { + $id = $result['id']; // return successful response return $this->json([ 'success' => 'Changes have been saved!', - 'id' => $row->getID() + 'id' => $id ]); - } - } - - /** - * @Menu(selected="customer_list") - */ - public function updateForm($id) - { - $this->denyAccessUnlessGranted('customer.update', null, 'No access.'); - - $params['mode'] = 'update'; - - // get row data - $em = $this->getDoctrine()->getManager(); - $row = $em->getRepository(Customer::class)->find($id); - - // make sure this row exists - if (empty($row)) - throw $this->createNotFoundException('The item does not exist'); - - // get dropdown parameters - $this->fillDropdownParameters($params); - - $params['obj'] = $row; - - // response - return $this->render('customer/form.html.twig', $params); - } - - protected function updateVehicles($em, Customer $cust, $vehicles) - { - $vehicle_ids = []; - - foreach ($vehicles as $vehicle) - { - // check if customer vehicle exists - if (!empty($vehicle->id)) - { - $cust_vehicle = $em->getRepository(CustomerVehicle::class)->find($vehicle->id); - if ($cust_vehicle == null) - throw new CrudException("Could not find customer vehicle."); - - } - // this is a new vehicle - else - { - $cust_vehicle = new CustomerVehicle(); - $cust_vehicle->setCustomer($cust); - $cust->addVehicle($cust_vehicle); - $em->persist($cust_vehicle); - } - - // vehicle, because they could have changed vehicle type - $vobj = $em->getRepository(Vehicle::class)->find($vehicle->vehicle); - if ($vobj == null) - throw new CrudException("Could not find vehicle."); - - // TODO: validate details - - $cust_vehicle->setName($vehicle->name) - ->setVehicle($vobj) - ->setPlateNumber($vehicle->plate_number) - ->setModelYear($vehicle->model_year) - ->setColor($vehicle->color) - ->setStatusCondition($vehicle->status_condition) - ->setFuelType($vehicle->fuel_type) - ->setActive($vehicle->flag_active); - - // if specified, check if battery exists - if ($vehicle->battery) - { - // check if battery exists - $bobj = $em->getRepository(Battery::class)->find($vehicle->battery); - if ($bobj == null) - throw new CrudException("Could not find battery."); - - // check if warranty expiration was specified - $warr_ex = DateTime::createFromFormat("d M Y", $vehicle->warranty_expiration); - if (!$warr_ex) - $warr_ex = null; - - $cust_vehicle->setHasMotoliteBattery(true) - ->setCurrentBattery($bobj) - ->setWarrantyCode($vehicle->warranty_code) - ->setWarrantyExpiration($warr_ex); - } - else - { - $cust_vehicle->setHasMotoliteBattery(false); - } - - - // add to list of vehicles to keep - $vehicle_ids[$cust_vehicle->getID()] = true; - } - - // cleanup - // delete all vehicles not in list - $cvs = $cust->getVehicles(); - foreach ($cvs as $cv) - { - if (!isset($vehicle_ids[$cv->getID()])) - { - $cust->removeVehicle($cv); - $em->remove($cv); - } - } - } - - public function updateSubmit(Request $req, ValidatorInterface $validator, $id) - { - $this->denyAccessUnlessGranted('customer.update', null, 'No access.'); - - // get row data - $em = $this->getDoctrine()->getManager(); - $cust = $em->getRepository(Customer::class)->find($id); - - // make sure this row exists - if (empty($cust)) - throw $this->createNotFoundException('The item does not exist'); - - $this->setObject($cust, $req); - - // initialize error lists - $error_array = []; - $nerror_array = []; - $verror_array = []; - - // TODO: validate mobile numbers - // TODO: validate vehicles - - // custom validation for vehicles - $vehicles = json_decode($req->request->get('vehicles')); - $this->updateVehicles($em, $cust, $vehicles); - - // validate - $errors = $validator->validate($cust); - - // add errors to list - foreach ($errors as $error) - { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } - - // check if any errors were found - if (!empty($error_array) || !empty($nerror_array) || !empty($verror_array)) - { - // return validation failure response - return $this->json([ - 'success' => false, - 'errors' => $error_array, - 'nerrors' => $nerror_array, - 'verrors' => $verror_array - ], 422); } else { - // validated! save the entity. do a persist anyway to save child entities - $em->persist($cust); - $em->flush(); + $error_array = $result['error_array']; + $nerror_array = $result['nerror_array']; + $verror_array = $result['verror_array']; + + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array, + 'nerrors' => $nerror_array, + 'verrors' => $verror_array + ], 422); + } + } + + /** + * @Menu(selected="customer_list") + */ + public function updateForm($id, CustomerHandlerInterface $cust_handler) + { + $this->denyAccessUnlessGranted('customer.update', null, 'No access.'); + + $params = $cust_handler->initializeUpdateCustomerForm($id); + + $template = $params['template']; + + // response + return $this->render($template, $params); + } + + public function updateSubmit(Request $req, CustomerHandlerInterface $cust_handler, $id) + { + $this->denyAccessUnlessGranted('customer.update', null, 'No access.'); + + try + { + $result = $cust_handler->updateCustomer($req, $id); + } + catch (CrudException $e) + { + throw new CrudException($e->getMessage()); + } + + if (isset($result['id'])) + { + $id = $result['id']; // return successful response return $this->json([ 'success' => 'Changes have been saved!', - 'id' => $cust->getID() + 'id' => $id ]); - } + } + else + { + $error_array = $result['error_array']; + $nerror_array = $result['nerror_array']; + $verror_array = $result['verror_array']; + + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array, + 'nerrors' => $nerror_array, + 'verrors' => $verror_array + ], 422); + } } - public function destroy($id) + public function destroy($id, CustomerHandlerInterface $cust_handler) { $this->denyAccessUnlessGranted('customer.delete', null, 'No access.'); - // get row data - $em = $this->getDoctrine()->getManager(); - $row = $em->getRepository(Customer::class)->find($id); - - if (empty($row)) - throw $this->createNotFoundException('The item does not exist'); - - // delete this row - $em->remove($row); - $em->flush(); + try + { + $cust_handler->deleteCustomer($id); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); + } // response $response = new Response(); @@ -487,113 +179,17 @@ class CustomerController extends Controller $response->send(); } - protected function generateYearOptions() - { - $start_year = 1950; - return range($start_year, date("Y") + 1); - } - - public function getCustomerVehicles(Request $req) + public function getCustomerVehicles(Request $req, CustomerHandlerInterface $cust_handler) { if (!$this->isGranted('jo_in.list')) { $exception = $this->createAccessDeniedException('No access.'); throw $exception; } - // get search term - $term = $req->query->get('search'); + $results = $cust_handler->getCustomerVehicles($req); - // get querybuilder - $qb = $this->getDoctrine() - ->getRepository(CustomerVehicle::class) - ->createQueryBuilder('q'); - - /* - // build expression now since we're reusing it - $vehicle_label = $qb->expr()->concat( - 'q.plate_number', - $qb->expr()->literal(' - '), - 'c.first_name', - $qb->expr()->literal(' '), - 'c.last_name', - $qb->expr()->literal(' (+63'), - 'c.phone_mobile', - $qb->expr()->literal(')') - ); - */ - - // count total records - $tquery = $qb->select('COUNT(q)'); - - // add filters to count query - if (!empty($term)) { - $tquery->where('q.plate_number like :search') - ->setParameter('search', $term . '%'); - /* - $tquery->where('match_against (q.plate_number, :search \'in boolean mode\') > 0.1') - ->setParameter('search', $term . '*'); - */ - /* - $tquery->where('q.plate_number LIKE :filter') - ->setParameter('filter', '%' . $term . '%'); - */ - } - - $total = $tquery->getQuery() - ->getSingleScalarResult(); - - // pagination vars - $page = $req->query->get('page') ?? 1; - $perpage = 20; - $offset = ($page - 1) * $perpage; - $pages = ceil($total / $perpage); - $has_more_pages = $page < $pages ? true : false; - - // build main query - $query = $qb->select('q'); - /* - ->addSelect($vehicle_label . ' as vehicle_label') - ->addSelect('c.first_name as cust_first_name') - ->addSelect('c.last_name as cust_last_name'); - */ - - // add filters if needed - if (!empty($term)) { - $query->where('q.plate_number like :search') - ->setParameter('search', $term . '%'); - /* - $query->where('match_against (q.plate_number, :search \'in boolean mode\') > 0.1') - ->setParameter('search', $term . '*'); - */ - /* - $query->where('q.plate_number LIKE :filter') - ->setParameter('filter', '%' . $term . '%'); - */ - } - - - // get rows - $query_obj = $query->orderBy('q.plate_number', 'asc') - ->setFirstResult($offset) - ->setMaxResults($perpage) - ->getQuery(); - // error_log($query_obj->getSql()); - - $obj_rows = $query_obj->getResult(); - - // build vehicles array - $vehicles = []; - - // get country code from services.yaml - $country_code = $this->getParameter('country_code'); - - foreach ($obj_rows as $cv) { - $cust = $cv->getCustomer(); - $vehicles[] = [ - 'id' => $cv->getID(), - 'text' => $cv->getPlateNumber() . ' ' . $cust->getFirstName() . ' ' . $cust->getLastName() . ' (' . $country_code . $cust->getPhoneMobile() . ')', - ]; - } + $vehicles = $results['vehicles']; + $has_more_pages = $results['has_more_pages']; // response return $this->json([ @@ -605,85 +201,26 @@ class CustomerController extends Controller ]); } - public function getCustomerVehicleInfo(Request $req) + public function getCustomerVehicleInfo(Request $req, CustomerHandlerInterface $cust_handler) { $this->denyAccessUnlessGranted('jo_in.list', null, 'No access.'); - // get id - $id = $req->query->get('id'); + $result = $cust_handler->getCustomerVehicleInfo($req); - // get row data - $em = $this->getDoctrine()->getManager(); - $obj = $em->getRepository(CustomerVehicle::class)->find($id); - - // make sure this row exists - if (empty($obj)) { + if ($result == null) + { return $this->json([ 'success' => false, 'error' => 'The item does not exist' ]); } - - $customer = $obj->getCustomer(); - $vehicle = $obj->getVehicle(); - $battery = $obj->getCurrentBattery(); - - // build response - $row = [ - 'customer' => [ - 'id' => $customer->getID(), - 'first_name' => $customer->getFirstName(), - 'last_name' => $customer->getLastName(), - 'customer_notes' => $customer->getCustomerNotes(), - 'phone_mobile' => $customer->getPhoneMobile(), - 'phone_landline' => $customer->getPhoneLandline(), - 'phone_office' => $customer->getPhoneOffice(), - 'phone_fax' => $customer->getPhoneFax(), - ], - 'vehicle' => [ - 'id' => $vehicle->getID(), - 'mfg_name' => $vehicle->getManufacturer()->getName(), - 'make' => $vehicle->getMake(), - 'model_year_from' => $vehicle->getModelYearFrom(), - 'model_year_to' => $vehicle->getModelYearTo(), - 'model_year' => $obj->getModelYear(), - 'color' => $obj->getColor(), - 'plate_number' => $obj->getPlateNumber(), - //'fuel_type' => $obj->getFuelType(), - //'status_condition' => $obj->getStatusCondition(), - ] - ]; - - if (!empty($battery)) { - $row['battery'] = [ - 'id' => $battery->getID(), - 'mfg_name' => $battery->getManufacturer()->getName(), - 'model_name' => $battery->getModel()->getName(), - 'size_name' => $battery->getSize()->getName(), - 'prod_code' => $battery->getProductCode(), - 'warranty_code' => $obj->getWarrantyCode(), - 'warranty_expiration' => $obj->getWarrantyExpiration() ? $obj->getWarrantyExpiration()->format("d M Y") : "", - 'has_motolite_battery' => $obj->hasMotoliteBattery(), - 'is_active' => $obj->isActive() - ]; - } - - // response - return $this->json([ - 'success' => true, - 'data' => $row - ]); - } - - // check if datatable filter is present and append to query - protected function setQueryFilters($datatable, &$query) { - if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) { - $query->join('q.vehicles', 'cv') - ->where('q.first_name LIKE :filter') - ->orWhere('q.last_name LIKE :filter') - ->orWhere('q.customer_classification LIKE :filter') - ->orWhere('cv.plate_number LIKE :filter') - ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); + else + { + // response + return $this->json([ + 'success' => true, + 'data' => $result + ]); } } } diff --git a/src/Controller/HomeController.php b/src/Controller/HomeController.php index 96380a23..5ba473af 100644 --- a/src/Controller/HomeController.php +++ b/src/Controller/HomeController.php @@ -5,13 +5,122 @@ namespace App\Controller; use Catalyst\MenuBundle\Annotation\Menu; use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Doctrine\ORM\EntityManagerInterface; + +use App\Service\RiderTracker; +use App\Service\GISManagerInterface; +use App\Service\JobOrderCache; +use App\Service\RiderCache; + +use App\Entity\Rider; + + class HomeController extends Controller { /** * @Menu(selected="home") */ - public function index() + public function index( + EntityManagerInterface $em, + RiderTracker $rider_tracker, + GISManagerInterface $gis_manager + ) { - return $this->render('home.html.twig'); + // get map + $params['map_js_file'] = $gis_manager->getJSInitFile(); + + return $this->render('home.html.twig', $params); + } + + public function getMapLocations(JobOrderCache $jo_cache) + { + $active_jos = $jo_cache->getAllActiveJobOrders(); + + // get active JOs from cache + } + + public function getRiderLocations(JobOrderCache $jo_cache, RiderCache $rider_cache, EntityManagerInterface $em, RiderTracker $rider_tracker) + { + // get active JOs from cache + $active_jos = $jo_cache->getAllActiveJobOrders(); + $riders = $rider_cache->getAllActiveRiders(); + + // TODO: optimize this + // get all riders and figure out if they have active jos + foreach ($riders as $rider_id => $rider_data) + { + $rider = $em->getRepository(Rider::class)->find($rider_id); + if ($rider == null) + { + unset($riders[$rider_id]); + continue; + } + + $jo = $rider->getActiveJobOrder(); + if ($jo == null) + $riders[$rider_id]['has_jo'] = false; + else + $riders[$rider_id]['has_jo'] = true; + } + + // get active riders from cache + // get all riders + /* + $riders = $em->getRepository(Rider::class)->findAll(); + + $locations = []; + foreach ($riders as $rider) + { + // get location for each rider + $rider_id = $rider->getID(); + $coordinates = $rider_tracker->getRiderLocation($rider_id); + + $lat = $coordinates->getLatitude(); + $long = $coordinates->getLongitude(); + + $jo = $rider->getActiveJobOrder(); + if ($jo == null) + { + $has_jo = false; + $clat = 0; + $clong = 0; + $jo_data = []; + } + else + { + $has_jo = true; + $cust_loc = $jo->getCoordinates(); + $clat = $cust_loc->getLatitude(); + $clong = $cust_loc->getLongitude(); + $jo_id = $jo->getID(); + $jo_data = [ + 'id' => $jo_id, + 'status' => $jo->getStatusText(), + 'stype' => $jo->getServiceTypeName(), + 'url' => $this->generateUrl('jo_all_form', ['id' => $jo_id]), + 'plate' => $jo->getCustomerVehicle()->getPlateNumber(), + 'cname' => $jo->getCustomer()->getNameDisplay(), + ]; + } + + + // use rider map label as key + $rider_map_label = $rider->getMapLabel(); + $locations[$rider_id] = [ + 'label' => $rider->getMapLabel(), + 'loc' => [$lat, $long], + 'has_jo' => $has_jo, + 'cust_loc' => [$clat, $clong], + 'jo' => $jo_data, + ]; + + } + */ + + return $this->json([ + 'jos' => $active_jos, + 'riders' => $riders, + ]); + } } diff --git a/src/Controller/HubController.php b/src/Controller/HubController.php index 115b7ef8..e1d41525 100644 --- a/src/Controller/HubController.php +++ b/src/Controller/HubController.php @@ -17,6 +17,9 @@ use DateTime; use Catalyst\MenuBundle\Annotation\Menu; +use App\Service\MapTools; +use App\Service\RiderTracker; + class HubController extends Controller { /** @@ -287,4 +290,87 @@ class HubController extends Controller $response->setStatusCode(Response::HTTP_OK); $response->send(); } + + public function nearest(MapTools $map_tools, Request $req) + { + // get lat / long + $lat = $req->query->get('lat'); + $long = $req->query->get('long'); + + // get nearest hubs according to position + $point = new Point($long, $lat); + $result = $map_tools->getClosestHubs($point, 10, date("H:i:s")); + + $hubs = []; + foreach ($result as $hub_res) + { + $hub = $hub_res['hub']; + $coords = $hub->getCoordinates(); + $hubs[] = [ + 'id' => $hub->getID(), + 'long' => $coords->getLongitude(), + 'lat' => $coords->getLatitude(), + 'label' => $hub->getFullName(), + 'name' => $hub->getName(), + 'branch' => $hub->getBranch(), + 'cnum' => $hub->getContactNumbers(), + 'distance' => $hub_res['distance'], + ]; + } + + return $this->json([ + 'hubs' => $hubs, + ]); + } + + public function getHubRiders(Request $req, RiderTracker $rider_tracker) + { + // get hub id + $hub_id = $req->query->get('id'); + + // get hub + $em = $this->getDoctrine()->getManager(); + $hub = $em->getRepository(Hub::class)->find($hub_id); + + // make sure this row exists + if (empty($hub)) + throw $this->createNotFoundException('The item does not exist'); + + //TODO: get available riders sort by proximity, show 10 + $available_riders = $hub->getAvailableRiders(); + + $riders = []; + + // TODO: remove this later when we don't get all available riders + $riders_limit = 5; + $num_riders = 0; + + foreach ($available_riders as $rider) + { + if ($num_riders > $riders_limit) + break; + + // get location for each rider + $rider_id = $rider->getID(); + $coordinates = $rider_tracker->getRiderLocation($rider_id); + + $lat = $coordinates->getLatitude(); + $long = $coordinates->getLongitude(); + + $riders[] = [ + 'id' => $rider->getID(), + 'first_name' => $rider->getFirstName(), + 'last_name' => $rider->getLastName(), + 'contact_num' => $rider->getContactNumber(), + 'plate_num' => $rider->getPlateNumber(), + 'location' => [$lat, $long], + ]; + + $num_riders++; + } + + return $this->json([ + 'riders' => $riders, + ]); + } } diff --git a/src/Controller/JobOrderController.php b/src/Controller/JobOrderController.php index 2036ffe0..f70d5484 100644 --- a/src/Controller/JobOrderController.php +++ b/src/Controller/JobOrderController.php @@ -2,128 +2,45 @@ namespace App\Controller; -use App\Ramcar\ServiceType; use App\Ramcar\JOStatus; -use App\Ramcar\WarrantyClass; -use App\Ramcar\DiscountApply; -use App\Ramcar\TradeInType; use App\Ramcar\InvoiceCriteria; -use App\Ramcar\InvoiceStatus; -use App\Ramcar\ModeOfPayment; -use App\Ramcar\TransactionOrigin; -use App\Ramcar\JOEventType; -use App\Ramcar\FacilitatedType; -use App\Ramcar\JORejectionReason; +use App\Ramcar\CMBServiceType; -use App\Entity\JobOrder; -use App\Entity\BatteryManufacturer; -use App\Entity\Customer; use App\Entity\CustomerVehicle; -//use App\Entity\Outlet; -use App\Entity\Hub; use App\Entity\Promo; -use App\Entity\Rider; use App\Entity\Battery; -use App\Entity\JOEvent; -use App\Entity\JORejection; +use App\Entity\JobOrder; +use App\Entity\VehicleManufacturer; +use App\Entity\Vehicle; -use App\Service\InvoiceCreator; +use App\Service\InvoiceGeneratorInterface; +use App\Service\JobOrderHandlerInterface; +use App\Service\GISManagerInterface; use App\Service\MapTools; -use App\Service\HubCounter; use App\Service\MQTTClient; use App\Service\APNSClient; -use App\Service\WarrantyHandler; - -use Doctrine\ORM\Query; -use Doctrine\DBAL\Connection; -use Doctrine\DBAL\LockMode; -use Doctrine\ORM\PessimisticLockException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Bundle\FrameworkBundle\Controller\Controller; -use Symfony\Contracts\Translation\TranslatorInterface; + +use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; + +use Doctrine\ORM\EntityManagerInterface; +use App\Service\RiderTracker; use Catalyst\MenuBundle\Annotation\Menu; -use CrEOF\Spatial\PHP\Types\Geometry\Point; - -use Mosquitto\Client as MosquittoClient; -use DateTime; -use DateInterval; - -use FPDF; - class JobOrderController extends Controller { - public function getJobOrders(Request $req) + public function getJobOrders(Request $req, JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_in.list', null, 'No access.'); - // get search term - $term = $req->query->get('search'); + $params = $jo_handler->getJobOrders($req); - // get querybuilder - $qb = $this->getDoctrine() - ->getRepository(JobOrder::class) - ->createQueryBuilder('q'); - - // build expression now since we're reusing it - $jo_label = $qb->expr()->concat($qb->expr()->literal('#'), 'q.id', $qb->expr()->literal(' - '), 'c.first_name', $qb->expr()->literal(' '), 'c.last_name', $qb->expr()->literal(' (Plate No: '), 'v.plate_number', $qb->expr()->literal(')')); - - // count total records - $tquery = $qb->select('COUNT(q)') - ->join('q.customer', 'c') - ->join('q.cus_vehicle', 'v'); - - // add filters to count query - if (!empty($term)) { - $tquery->where($jo_label . ' LIKE :filter') - ->setParameter('filter', '%' . $term . '%'); - } - - $total = $tquery->getQuery() - ->getSingleScalarResult(); - - // pagination vars - $page = $req->query->get('page') ?? 1; - $perpage = 20; - $offset = ($page - 1) * $perpage; - $pages = ceil($total / $perpage); - $has_more_pages = $page < $pages ? true : false; - - // build main query - $query = $qb->select('q') - ->addSelect($jo_label . ' as jo_label') - ->addSelect('c.first_name as cust_first_name') - ->addSelect('c.last_name as cust_last_name') - ->addSelect('v.plate_number as vehicle_plate_number'); - - // add filters if needed - if (!empty($term)) { - $query->where($jo_label . ' LIKE :filter') - ->setParameter('filter', '%' . $term . '%'); - } - - // get rows - $obj_rows = $query->orderBy('q.id', 'asc') - ->setFirstResult($offset) - ->setMaxResults($perpage) - ->getQuery() - ->getResult(); - - // build job order array - $job_orders = []; - - foreach ($obj_rows as $jo) { - $service_type = ServiceType::getName($jo[0]->getServiceType()); - - $job_orders[] = [ - 'id' => $jo[0]->getID(), - 'text' => $jo['jo_label'] . ' - ' . $service_type - ]; - } + $job_orders = $params['job_orders']; + $has_more_pages = $params['has_more_pages']; // response return $this->json([ @@ -135,233 +52,50 @@ class JobOrderController extends Controller ]); } - protected function fillDropdownParameters(&$params) - { - $em = $this->getDoctrine()->getManager(); - - // db loaded - $params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); - // $params['customers'] = $em->getRepository(Customer::class)->findAll(); - $params['promos'] = $em->getRepository(Promo::class)->findAll(); - - // list of hubs - $hubs = $em->getRepository(Hub::class)->findBy([], ['name' => 'ASC']); - $fac_hubs = []; - foreach ($hubs as $hub) - { - $fac_hubs[$hub->getID()] = $hub->getName() . ' - ' . $hub->getBranch(); - } - - // name values - $params['service_types'] = ServiceType::getCollection(); - $params['warranty_classes'] = WarrantyClass::getCollection(); - $params['modes_of_payment'] = ModeOfPayment::getCollection(); - $params['statuses'] = JOStatus::getCollection(); - $params['discount_apply'] = DiscountApply::getCollection(); - $params['trade_in_types'] = TradeInType::getCollection(); - $params['facilitated_types'] = FacilitatedType::getCollection(); - $params['facilitated_hubs'] = $fac_hubs; - $params['sources'] = TransactionOrigin::getCollection(); - } - - protected function initFormTags(&$params) - { - // default to editing, as we have more forms editing than creating - $params['ftags'] = [ - 'title' => 'Job Order Form', - 'vehicle_dropdown' => false, - 'invoice_edit' => false, - 'set_map_coordinate' => true, - 'preset_vehicle' => false, - 'ticket_table' => true, - 'cancel_button' => true, - ]; - } - - protected function fillFormTags(&$params) - { - $this->initFormTags($params); - - switch ($params['mode']) - { - case 'create': - $params['ftags']['vehicle_dropdown'] = true; - $params['ftags']['set_map_coordinate'] = false; - $params['ftags']['invoice_edit'] = true; - $params['ftags']['ticket_table'] = false; - $params['ftags']['cancel_button'] = false; - break; - case 'create_vehicle': - $params['ftags']['set_map_coordinate'] = false; - $params['ftags']['invoice_edit'] = true; - $params['ftags']['preset_vehicle'] = true; - $params['ftags']['ticket_table'] = false; - $params['ftags']['cancel_button'] = false; - break; - case 'open_edit': - $params['ftags']['invoice_edit'] = true; - $params['ftags']['preset_vehicle'] = true; - break; - } - } - /** * @Menu(selected="jo_in") */ - public function incomingForm() + public function incomingForm(JobOrderHandlerInterface $jo_handler, + GISManagerInterface $gis) { $this->denyAccessUnlessGranted('jo_in.list', null, 'No access.'); - $params['obj'] = new JobOrder(); - $params['mode'] = 'create'; + $params = $jo_handler->initializeIncomingForm(); + $params['submit_url'] = $this->generateUrl('jo_in_submit'); $params['return_url'] = $this->generateUrl('jo_in'); + $params['map_js_file'] = $gis->getJSJOFile(); - $em = $this->getDoctrine()->getManager(); - - $this->fillDropdownParameters($params); - $this->fillFormTags($params); + $template = $params['template']; // response - return $this->render('job-order/form.html.twig', $params); + return $this->render($template, $params); } /** * @Menu(selected="jo_in") */ - public function openEditForm($id) + public function openEditForm($id, JobOrderHandlerInterface $jo_handler, GISManagerInterface $gis) { $this->denyAccessUnlessGranted('jo_open.edit', null, 'No access.'); - $em = $this->getDoctrine()->getManager(); - $jo = $em->getRepository(JobOrder::class)->find($id); - - $params['obj'] = $jo; - $params['mode'] = 'open_edit'; + $params = $jo_handler->initializeOpenEditForm($id); $params['submit_url'] = $this->generateUrl('jo_open_edit_submit', ['id' => $id]); $params['return_url'] = $this->generateUrl('jo_open'); - $params['cvid'] = $jo->getCustomerVehicle()->getID(); - $params['vid'] = $jo->getCustomerVehicle()->getVehicle()->getID(); + $params['map_js_file'] = $gis->getJSJOFile(); - $em = $this->getDoctrine()->getManager(); - - $this->fillDropdownParameters($params); - $this->fillFormTags($params); + $template = $params['template']; // response - return $this->render('job-order/form.html.twig', $params); + return $this->render($template, $params); } - public function openEditSubmit(Request $req, ValidatorInterface $validator, InvoiceCreator $ic, $id) + public function openEditSubmit(Request $req, JobOrderHandlerInterface $jo_handler, $id) { $this->denyAccessUnlessGranted('jo_open.edit', null, 'No access.'); - // get object data - $em = $this->getDoctrine()->getManager(); - $obj = $em->getRepository(JobOrder::class)->find($id); - $user = $this->getUser(); - - // initialize error list $error_array = []; - - // make sure this object exists - if (empty($obj)) - throw $this->createNotFoundException('The item does not exist'); - - // check if lat and lng are provided - if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { - $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; - } - - if (empty($error_array)) { - // coordinates - $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); - - $stype = $req->request->get('service_type'); - - // set and save values - $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) - ->setCoordinates($point) - ->setAdvanceOrder($req->request->get('flag_advance') ?? false) - ->setServiceType($stype) - ->setWarrantyClass($req->request->get('warranty_class')) - ->setSource($req->request->get('source')) - ->setDeliveryInstructions($req->request->get('delivery_instructions')) - ->setTier1Notes($req->request->get('tier1_notes')) - ->setTier2Notes($req->request->get('tier2_notes')) - ->setDeliveryAddress($req->request->get('delivery_address')) - ->setORName($req->request->get('or_name')) - ->setPromoDetail($req->request->get('promo_detail')) - ->setModeOfPayment($req->request->get('mode_of_payment')) - ->setLandmark($req->request->get('landmark')); - - // did they change invoice? - $invoice_items = $req->request->get('invoice_items', []); - $invoice_change = $req->request->get('invoice_change', 0); - if ($invoice_change) - { - // instantiate invoice criteria - $criteria = new InvoiceCriteria(); - $criteria->setServiceType($stype) - ->setCustomerVehicle($obj->getCustomerVehicle()); - - $ierror = $this->invoicePromo($em, $criteria, $req->request->get('invoice_promo')); - - // check for trade in so we can mark it for mobile app - foreach ($invoice_items as $item) - { - // get first trade-in - if (!empty($item['trade_in'])) - { - $obj->setTradeInType($item['trade_in']); - break; - } - } - - if (!$ierror) - $ierror = $this->invoiceBatteries($em, $criteria, $invoice_items); - - if ($ierror) - { - $error_array['invoice'] = $ierror; - } - else - { - // generate the invoice - $iobj = $ic->processCriteria($criteria); - $iobj->setStatus(InvoiceStatus::DRAFT) - ->setCreatedBy($this->getUser()); - - // validate - $ierrors = $validator->validate($iobj); - - // add errors to list - foreach ($ierrors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } - - // remove previous invoice - $old_invoice = $obj->getInvoice(); - $em->remove($old_invoice); - $em->flush(); - - // add invoice to JO - $obj->setInvoice($iobj); - - // persist invoice - $em->persist($iobj); - } - } - - - // validate - $errors = $validator->validate($obj); - - // add errors to list - foreach ($errors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } - } + $error_array = $jo_handler->generateJobOrder($req, $id); // check if any errors were found if (!empty($error_array)) { @@ -372,17 +106,6 @@ class JobOrderController extends Controller ], 422); } - // the event - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::OPEN_EDIT) - ->setUser($this->getUser()) - ->setJobOrder($obj); - $em->persist($event); - - // validated! save the entity - $em->flush(); - // return successful response return $this->json([ 'success' => 'Changes have been saved!' @@ -392,164 +115,36 @@ class JobOrderController extends Controller /** * @Menu(selected="jo_in") */ - public function incomingVehicleForm($cvid) + public function incomingVehicleForm($cvid, JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_in.list', null, 'No access.'); - $params['mode'] = 'create_vehicle'; - $params['submit_url'] = $this->generateUrl('jo_in_submit'); - $params['return_url'] = $this->generateUrl('jo_in'); - $params['cvid'] = $cvid; - - $em = $this->getDoctrine()->getManager(); - - // get customer vehicle - $cv = $em->getRepository(CustomerVehicle::class)->find($cvid); - $params['vid'] = $cv->getVehicle()->getID(); - - // make sure this customer vehicle exists - if (empty($cv)) + try { - $em->getConnection()->rollback(); - throw $this->createNotFoundException('The job order does not exist'); + $params = $jo_handler->initializeIncomingVehicleForm($cvid); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); } - $jo = new JobOrder(); - $jo->setCustomerVehicle($cv) - ->setCustomer($cv->getCustomer()); + $params['submit_url'] = $this->generateUrl('jo_in_submit'); + $params['return_url'] = $this->generateUrl('jo_in'); - $params['obj'] = $jo; - $this->fillDropdownParameters($params); - $this->fillFormTags($params); + $template = $params['template']; // response - return $this->render('job-order/form.html.twig', $params); + return $this->render($template, $params); } - public function incomingSubmit(Request $req, ValidatorInterface $validator, InvoiceCreator $ic) + public function incomingSubmit(Request $req, JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_in.list', null, 'No access.'); // initialize error list $error_array = []; - - // create new row - $em = $this->getDoctrine()->getManager(); - $obj = new JobOrder(); - - // check if lat and lng are provided - if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { - $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; - } - - // check if customer vehicle is set - if (empty($req->request->get('customer_vehicle'))) { - $error_array['customer_vehicle'] = 'No vehicle selected.'; - } else { - // get customer vehicle - $cust_vehicle = $em->getRepository(CustomerVehicle::class)->find($req->request->get('customer_vehicle')); - - if (empty($cust_vehicle)) { - $error_array['customer_vehicle'] = 'Invalid vehicle specified.'; - } - } - - if (empty($error_array)) { - // coordinates - $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); - - $stype = $req->request->get('service_type'); - - // set and save values - $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) - ->setCoordinates($point) - ->setAdvanceOrder($req->request->get('flag_advance') ?? false) - ->setCreatedBy($this->getUser()) - ->setServiceType($stype) - ->setWarrantyClass($req->request->get('warranty_class')) - ->setCustomer($cust_vehicle->getCustomer()) - ->setCustomerVehicle($cust_vehicle) - ->setSource($req->request->get('source')) - ->setStatus(JOStatus::PENDING) - ->setDeliveryInstructions($req->request->get('delivery_instructions')) - ->setTier1Notes($req->request->get('tier1_notes')) - ->setTier2Notes($req->request->get('tier2_notes')) - ->setDeliveryAddress($req->request->get('delivery_address')) - ->setORName($req->request->get('or_name')) - ->setPromoDetail($req->request->get('promo_detail')) - ->setModeOfPayment($req->request->get('mode_of_payment')) - ->setLandmark($req->request->get('landmark')); - - // check if reference JO is set and validate - if (!empty($req->request->get('ref_jo'))) { - // get reference JO - $ref_jo = $em->getRepository(JobOrder::class)->find($req->request->get('ref_jo')); - - if (empty($ref_jo)) { - $error_array['ref_jo'] = 'Invalid reference job order specified.'; - } else { - $obj->setReferenceJO($ref_jo); - } - } - - // instantiate invoice criteria - $criteria = new InvoiceCriteria(); - $criteria->setServiceType($stype) - ->setCustomerVehicle($cust_vehicle); - - $ierror = $this->invoicePromo($em, $criteria, $req->request->get('invoice_promo')); - $invoice_items = $req->request->get('invoice_items'); - - if (!$ierror && !empty($invoice_items)) - { - // check for trade in so we can mark it for mobile app - foreach ($invoice_items as $item) - { - // get first trade-in - if (!empty($item['trade_in'])) - { - $obj->setTradeInType($item['trade_in']); - break; - } - } - - $ierror = $this->invoiceBatteries($em, $criteria, $invoice_items); - } - - if ($ierror) - { - $error_array['invoice'] = $ierror; - } - else - { - // generate the invoice - $iobj = $ic->processCriteria($criteria); - $iobj->setStatus(InvoiceStatus::DRAFT) - ->setCreatedBy($this->getUser()); - - // validate - $ierrors = $validator->validate($iobj); - - // add errors to list - foreach ($ierrors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } - - // add invoice to JO - $obj->setInvoice($iobj); - - // save - $em->persist($iobj); - } - - // validate - $errors = $validator->validate($obj); - - // add errors to list - foreach ($errors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } - } + $id = -1; + $error_array = $jo_handler->generateJobOrder($req, $id); // check if any errors were found if (!empty($error_array)) { @@ -560,156 +155,84 @@ class JobOrderController extends Controller ], 422); } - // validated! save the entity - $em->persist($obj); - - // the event - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::CREATE) - ->setUser($this->getUser()) - ->setJobOrder($obj); - $em->persist($event); - - $em->flush(); - // return successful response return $this->json([ 'success' => 'Changes have been saved!' ]); } - protected function checkTier($tier) - { - // check specified tier - switch ($tier) { - case 'proc': - $tier_key = 'jo_proc'; - $tier_name = 'Dispatch'; - $rows_route = 'jo_proc_rows'; - $edit_route = 'jo_proc_form'; - $unlock_route = 'jo_proc_unlock'; - $jo_status = JOStatus::PENDING; - break; - case 'assign': - $tier_key = 'jo_assign'; - $tier_name = 'Assigning'; - $rows_route = 'jo_assign_rows'; - $edit_route = 'jo_assign_form'; - $unlock_route = 'jo_assign_unlock'; - $jo_status = JOStatus::RIDER_ASSIGN; - break; - case 'fulfill': - $tier_key = 'jo_fulfill'; - $tier_name = 'Fullfillment'; - $rows_route = 'jo_fulfill_rows'; - $edit_route = 'jo_fulfill_form'; - $unlock_route = ''; - $jo_status = [ - JOStatus::ASSIGNED, - JOStatus::IN_PROGRESS - ]; - break; - case 'open': - $tier_key = 'jo_open'; - $tier_name = 'Open'; - $rows_route = 'jo_open_rows'; - $edit_route = ''; - $unlock_route = ''; - $jo_status = [ - JOStatus::PENDING, - JOStatus::RIDER_ASSIGN, - JOStatus::ASSIGNED, - JOStatus::IN_PROGRESS, - JOStatus::IN_TRANSIT, - ]; - break; - case 'all': - $tier_key = 'jo_open'; - $tier_name = 'Open'; - $rows_route = 'jo_open_rows'; - $edit_route = 'jo_all_form'; - $unlock_route = ''; - $jo_status = ''; - break; - default: - $exception = $this->createAccessDeniedException('No access.'); - throw $exception; - } - - // check acl - $this->denyAccessUnlessGranted($tier_key . '.list', null, 'No access.'); - - // return params if allowed access - return [ - 'key' => $tier_key, - 'name' => $tier_name, - 'rows_route' => $rows_route, - 'edit_route' => $edit_route, - 'unlock_route' => $unlock_route, - 'jo_status' => $jo_status - ]; - } - /** * @Menu(selected="jo_proc") */ - public function listProcessing() + public function listProcessing(JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.'); + $template = $jo_handler->getTwigTemplate('jo_list_processing'); + $params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval'); - return $this->render('job-order/list.processing.html.twig', $params); + return $this->render($template, $params); } /** * @Menu(selected="jo_assign") */ - public function listAssigning() + public function listAssigning(JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_assign.list', null, 'No access.'); + $template = $jo_handler->getTwigTemplate('jo_list_assigning'); + $params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval'); - return $this->render('job-order/list.assigning.html.twig', $params); + return $this->render($template, $params); } /** * @Menu(selected="jo_fulfill") */ - public function listFulfillment() + public function listFulfillment(JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_fulfill.list', null, 'No access.'); + $template = $jo_handler->getTwigTemplate('jo_list_fulfillment'); + + $params = $jo_handler->getOtherParameters(); $params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval'); - return $this->render('job-order/list.fulfillment.html.twig', $params); + return $this->render($template, $params); } /** * @Menu(selected="jo_open") */ - public function listOpen() + public function listOpen(JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); + $template = $jo_handler->getTwigTemplate('jo_list_open'); + + $params = $jo_handler->getOtherParameters(); $params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval'); $params['statuses'] = JOStatus::getCollection(); - return $this->render('job-order/list.open.html.twig', $params); + return $this->render($template, $params); } /** * @Menu(selected="jo_all") */ - public function listAll() + public function listAll(JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_all.list', null, 'No access.'); + $template = $jo_handler->getTwigTemplate('jo_list_all'); + + $params = $jo_handler->getOtherParameters(); $params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval'); - return $this->render('job-order/list.all.html.twig', $params); + return $this->render($template, $params); } /* @@ -729,125 +252,42 @@ class JobOrderController extends Controller } */ - public function getRows(Request $req, $tier) + public function getRows(Request $req, $tier, JobOrderHandlerInterface $jo_handler) { - // check which job order tier is being called for and confirm access - $tier_params = $this->checkTier($tier); - - // get current user - $user = $this->getUser(); - $hubs = $user->getHubs(); - - // get query builder - $qb = $this->getDoctrine() - ->getRepository(JobOrder::class) - ->createQueryBuilder('q'); - - // get datatable params - $datatable = $req->request->get('datatable'); - - // count total records - $tquery = $qb->select('COUNT(q)'); - - $this->setQueryFilters($datatable, $tquery, $qb, $hubs, $tier, $tier_params['jo_status']); - - $total = $tquery->getQuery() - ->getSingleScalarResult(); - - // get current page number - $page = $datatable['pagination']['page'] ?? 1; - - $perpage = $datatable['pagination']['perpage']; - $offset = ($page - 1) * $perpage; - - // add metadata - $meta = [ - 'page' => $page, - 'perpage' => $perpage, - 'pages' => ceil($total / $perpage), - 'total' => $total, - 'sort' => 'asc', - 'field' => 'id' - ]; - - // build query - $qb = $this->getDoctrine() - ->getRepository(JobOrder::class) - ->createQueryBuilder('q'); - $query = $qb->select('q'); - - $this->setQueryFilters($datatable, $query, $qb, $hubs, $tier, $tier_params['jo_status']); - - // check if sorting is present, otherwise use default - if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) { - $order = $datatable['sort']['sort'] ?? 'asc'; - $query->orderBy('q.' . $datatable['sort']['field'], $order); - } else { - $query->orderBy('q.date_schedule', 'asc'); + try + { + $params = $jo_handler->getRows($req, $tier); + } + catch (AccessDeniedHttpException $e) + { + throw $this->createAccessDeniedException($e->getMessage()); } - // get rows for this page - $query_obj = $query->setFirstResult($offset) - ->setMaxResults($perpage) - ->getQuery(); - - // error_log($query_obj->getSQL()); - - $obj_rows = $query_obj->getResult(); - /* - $obj_rows = $query->setFirstResult($offset) - ->setMaxResults($perpage) - ->getQuery() - ->getResult(); - */ - - $statuses = JOStatus::getCollection(); - $service_types = ServiceType::getCollection(); - - // process rows - $rows = []; - foreach ($obj_rows as $orow) { - // add row data - $row['id'] = $orow->getID(); - $row['customer_name'] = $orow->getCustomer()->getFirstName() . ' ' . $orow->getCustomer()->getLastName(); - $row['delivery_address'] = $orow->getDeliveryAddress(); - $row['date_schedule'] = $orow->getDateSchedule()->format("d M Y g:i A"); - $row['type'] = $orow->isAdvanceOrder() ? 'Advanced Order' : 'Immediate'; - $row['service_type'] = $service_types[$orow->getServiceType()]; - $row['status'] = $statuses[$orow->getStatus()]; - $row['flag_advance'] = $orow->isAdvanceOrder(); - $row['plate_number'] = $orow->getCustomerVehicle()->getPlateNumber(); - $row['is_mobile'] = $orow->getSource() == TransactionOrigin::MOBILE_APP; - - $processor = $orow->getProcessedBy(); - if ($processor == null) - $row['processor'] = ''; - else - $row['processor'] = $orow->getProcessedBy()->getFullName(); - - $assignor = $orow->getAssignedBy(); - if ($assignor == null) - $row['assignor'] = ''; - else - $row['assignor'] = $orow->getAssignedBy()->getFullName(); + $rows = $params['rows']; + $meta = $params['meta']; + $tier_params = $params['tier_params']; + foreach ($rows as $key => $data) { // add crud urls + $jo_id = $rows[$key]['id']; + if ($tier == 'open') { - $row['meta']['reassign_hub_url'] = $this->generateUrl('jo_open_hub_form', ['id' => $row['id']]); - $row['meta']['reassign_rider_url'] = $this->generateUrl('jo_open_rider_form', ['id' => $row['id']]); - $row['meta']['edit_url'] = $this->generateUrl('jo_open_edit_form', ['id' => $row['id']]); + $rows[$key]['meta']['reassign_hub_url'] = $this->generateUrl('jo_open_hub_form', ['id' => $jo_id]); + $rows[$key]['meta']['reassign_rider_url'] = $this->generateUrl('jo_open_rider_form', ['id' => $jo_id]); + $rows[$key]['meta']['edit_url'] = $this->generateUrl('jo_open_edit_form', ['id' => $jo_id]); + $rows[$key]['meta']['onestep_edit_url'] = $this->generateUrl('jo_onestep_edit_form', ['id' => $jo_id]); } else { - $row['meta']['update_url'] = $this->generateUrl($tier_params['edit_route'], ['id' => $row['id']]); - $row['meta']['pdf_url'] = $this->generateUrl('jo_pdf_form', ['id' => $row['id']]); + $rows[$key]['meta']['update_url'] = $this->generateUrl($tier_params['edit_route'], ['id' => $jo_id]); + $rows[$key]['meta']['onestep_edit_url'] = $this->generateUrl('jo_onestep_edit_form', ['id' => $jo_id]); + $rows[$key]['meta']['pdf_url'] = $this->generateUrl('jo_pdf_form', ['id' => $jo_id]); } if ($tier_params['unlock_route'] != '') - $row['meta']['unlock_url'] = $this->generateUrl($tier_params['unlock_route'], ['id' => $row['id']]); + $rows[$key]['meta']['unlock_url'] = $this->generateUrl($tier_params['unlock_route'], ['id' => $jo_id]); - $rows[] = $row; } // response @@ -860,231 +300,50 @@ class JobOrderController extends Controller /** * @Menu(selected="jo_proc") */ - public function processingForm(MapTools $map_tools, $id) + public function processingForm(MapTools $map_tools, $id, JobOrderHandlerInterface $jo_handler, GISManagerInterface $gis) { $this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.'); - $em = $this->getDoctrine()->getManager(); - - // manual transaction since we're locking - $em->getConnection()->beginTransaction(); - try { - - // lock and get data - $obj = $em->getRepository(JobOrder::class)->find($id, LockMode::PESSIMISTIC_READ); - - // make sure this job order exists - if (empty($obj)) - { - $em->getConnection()->rollback(); - throw $this->createNotFoundException('The job order does not exist'); - } - - // check status - if ($obj->getStatus() != JOStatus::PENDING) - { - $em->getConnection()->rollback(); - throw $this->createNotFoundException('The job order does not have a pending status'); - } - - // check if we are the processor - $processor = $obj->getProcessedBy(); - $user = $this->getUser(); - // TODO: go back to list page and display alert / flash that says they cannot access it because they - // are not the processor - if ($processor != null && $processor->getID() != $user->getID()) - { - $em->getConnection()->rollback(); - throw $this->createAccessDeniedException('Not the processor'); - } - - // make this user be the processor - $obj->setProcessedBy($user); - $em->flush(); - - $em->getConnection()->commit(); + $params = $jo_handler->initializeProcessingForm($id, $map_tools); } - catch(PessimisticLockException $e) + catch (AccessDeniedHttpException $e) { - throw $this->createAccessDeniedException('Not the processor'); + throw $this->createAccessDeniedException($e->getMessage()); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); } - // NOTE: we are able to lock, everything should be fine now - - $params['mode'] = 'update-processing'; - $params['status_cancelled'] = JOStatus::CANCELLED; - - $this->fillDropdownParameters($params); - $this->fillFormTags($params); - - // get rejections - $rejections = $obj->getHubRejections(); - - // get rejection reasons - $params['rejection_reasons'] = JORejectionReason::getCollection(); - - // get closest hubs - $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); - - $params['hubs'] = []; - - // format duration and distance into friendly time - foreach ($hubs as $hub) { - // duration - $seconds = $hub['duration']; - - if (!empty($seconds) && $seconds > 0) { - $hours = floor($seconds / 3600); - $minutes = ceil(($seconds / 60) % 60); - - $hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); - } else { - $hub['duration'] = false; - } - - // distance - $meters = $hub['distance']; - - if (!empty($meters) && $meters > 0) { - $hub['distance'] = round($meters / 1000) . " km"; - } else { - $hub['distance'] = false; - } - - // counters - $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); - $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); - - // check for rejection - $hub['flag_rejected'] = false; - $hub_id = $hub['hub']->getID(); - - foreach ($rejections as $robj) - { - if ($robj->getHub()->getID() === $hub_id) - { - $hub['flag_rejected'] = true; - break; - } - } - - $params['hubs'][] = $hub; - } - - $params['obj'] = $obj; - $params['submit_url'] = $this->generateUrl('jo_proc_submit', ['id' => $obj->getID()]); + $params['submit_url'] = $this->generateUrl('jo_proc_submit', ['id' => $id]); $params['return_url'] = $this->generateUrl('jo_proc'); + $params['map_js_file'] = $gis->getJSJOFile(); + + $template = $params['template']; // response - return $this->render('job-order/form.html.twig', $params); + return $this->render($template, $params); } - public function processingSubmit(Request $req, ValidatorInterface $validator, MQTTClient $mclient, $id) + public function processingSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, $id) { $this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.'); - // get object data - $em = $this->getDoctrine()->getManager(); - $obj = $em->getRepository(JobOrder::class)->find($id); - $processor = $obj->getProcessedBy(); - $user = $this->getUser(); - - // check if we're the one processing, return error otherwise - if ($processor == null) - throw $this->createAccessDeniedException('Not the processor'); - - if ($processor != null && $processor->getID() != $user->getID()) - throw $this->createAccessDeniedException('Not the processor'); - // initialize error list $error_array = []; - - - // make sure this object exists - if (empty($obj)) - throw $this->createNotFoundException('The item does not exist'); - - // check if cancelled already - if (!$obj->canDispatch()) + try { - throw $this->createNotFoundException('Could not dispatch. Job Order is not pending.'); - // TODO: have this handled better, so UI shows the error - // $error_array['dispatch'] = 'Could not dispatch. Job Order is not pending.'; + $error_array = $jo_handler->dispatchJobOrder($req, $id, $mclient); } - - // check if lat and lng are provided - if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) + catch (AccessDeniedHttpException $e) { - $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + throw $this->createAccessDeniedException($e->getMessage()); } - - // check if hub is set - if (empty($req->request->get('hub'))) + catch (NotFoundHttpException $e) { - $error_array['hub'] = 'No hub selected.'; - } - else - { - // get hub - $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); - - if (empty($hub)) - { - $error_array['hub'] = 'Invalid hub specified.'; - } - } - - // check facilitated type - $fac_type = $req->request->get('facilitated_type'); - if (!empty($fac_type)) - { - if (!FacilitatedType::validate($fac_type)) - $fac_type = null; - } - else - $fac_type = null; - - // check facilitated by - $fac_by_id = $req->request->get('facilitated_by'); - $fac_by = null; - if (!empty($fac_by_id)) - { - $fac_by = $em->getRepository(Hub::class)->find($fac_by_id); - if (empty($fac_by)) - $fac_by = null; - } - - - if (empty($error_array)) - { - // coordinates - $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); - - // set and save values - $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) - ->setCoordinates($point) - ->setAdvanceOrder($req->request->get('flag_advance') ?? false) - ->setServiceType($req->request->get('service_type')) - ->setWarrantyClass($req->request->get('warranty_class')) - ->setSource($req->request->get('source')) - ->setStatus(JOStatus::RIDER_ASSIGN) - ->setDeliveryInstructions($req->request->get('delivery_instructions')) - ->setTier1Notes($req->request->get('tier1_notes')) - ->setTier2Notes($req->request->get('tier2_notes')) - ->setDeliveryAddress($req->request->get('delivery_address')) - ->setFacilitatedType($fac_type) - ->setFacilitatedBy($fac_by) - ->setHub($hub); - - // validate - $errors = $validator->validate($obj); - - // add errors to list - foreach ($errors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } + throw $this->createNotFoundException($e->getMessage()); } // check if any errors were found @@ -1097,23 +356,6 @@ class JobOrderController extends Controller ], 422); } - // the event - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::HUB_ASSIGN) - ->setUser($this->getUser()) - ->setJobOrder($obj); - $em->persist($event); - - // validated! save the entity - $em->flush(); - - // send event to mobile app - $payload = [ - 'event' => 'outlet_assign' - ]; - $mclient->sendEvent($obj, $payload); - // return successful response return $this->json([ 'success' => 'Changes have been saved!' @@ -1123,149 +365,49 @@ class JobOrderController extends Controller /** * @Menu(selected="jo_assign") */ - public function assigningForm(MapTools $map_tools, $id) + public function assigningForm($id, JobOrderHandlerInterface $jo_handler, + GISManagerInterface $gis) { $this->denyAccessUnlessGranted('jo_assign.list', null, 'No access.'); - $em = $this->getDoctrine()->getManager(); - - // manual transaction since we're locking - $em->getConnection()->beginTransaction(); - - $params['mode'] = 'update-assigning'; - try { - // get row data - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this row exists - if (empty($obj)) - { - $em->getConnection()->rollback(); - throw $this->createNotFoundException('The job order does not exist'); - } - - // check status - if ($obj->getStatus() != JOStatus::RIDER_ASSIGN) - { - $em->getConnection()->rollback(); - throw $this->createNotFoundException('The job order does not have an assigning status'); - } - - // check if super user - $user = $this->getUser(); - if ($user->isSuperAdmin()) - { - // do nothing, just allow page to be accessed - } - else - { - // check if hub is assigned to current user - $user_hubs = $this->getUser()->getHubs(); - if (!in_array($obj->getHub()->getID(), $user_hubs)) - { - $em->getConnection()->rollback(); - throw $this->createNotFoundException('The job order is not on a hub assigned to this user'); - } - - // check if we are the assignor - $assignor = $obj->getAssignedBy(); - - if ($assignor != null && $assignor->getID() != $user->getID()) - { - $em->getConnection()->rollback(); - throw $this->createAccessDeniedException('Not the assignor'); - } - - // make this user be the assignor - $obj->setAssignedBy($user); - $em->flush(); - } - - $em->getConnection()->commit(); + $params = $jo_handler->initializeAssignForm($id); } - catch (PessimisticLockException $e) + catch (AccessDeniedHttpException $e) { - throw $this->createAccessDeniedException('Not the assignor'); + throw $this->createAccessDeniedException($e->getMessage()); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); } - $this->fillDropdownParameters($params); - $this->fillFormTags($params); - - $params['obj'] = $obj; - $params['status_cancelled'] = JOStatus::CANCELLED; - $params['submit_url'] = $this->generateUrl('jo_assign_submit', ['id' => $obj->getID()]); + $params['submit_url'] = $this->generateUrl('jo_assign_submit', ['id' => $id]); $params['return_url'] = $this->generateUrl('jo_assign'); + $params['map_js_file'] = $gis->getJSJOFile(); + + $template = $params['template']; // response - return $this->render('job-order/form.html.twig', $params); + return $this->render($template, $params); + } - public function assigningSubmit(Request $req, ValidatorInterface $validator, MQTTCLient $mclient, APNSClient $aclient, $id) + public function assigningSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTCLient $mclient, APNSClient $aclient, $id) { $this->denyAccessUnlessGranted('jo_assign.list', null, 'No access.'); // initialize error list $error_array = []; - // get object data - $em = $this->getDoctrine()->getManager(); - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this object exists - if (empty($obj)) - throw $this->createNotFoundException('The item does not exist'); - - // check if we can assign - if (!$obj->canAssign()) - throw $this->createNotFoundException('Cannot assign rider to this job order.'); - - - // check if lat and lng are provided - if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { - $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + try + { + $error_array = $jo_handler->assignJobOrder($req, $id, $mclient, $aclient); } - - // check if rider is set - if (empty($req->request->get('rider'))) { - $error_array['rider'] = 'No rider selected.'; - } else { - // get rider - $rider = $em->getRepository(Rider::class)->find($req->request->get('rider')); - - if (empty($rider)) { - $error_array['rider'] = 'Invalid rider specified.'; - } - } - - if (empty($error_array)) { - // coordinates - $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); - - // set and save values - $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) - ->setCoordinates($point) - ->setAdvanceOrder($req->request->get('flag_advance') ?? false) - ->setServiceType($req->request->get('service_type')) - ->setWarrantyClass($req->request->get('warranty_class')) - ->setSource($req->request->get('source')) - ->setStatus(JOStatus::ASSIGNED) - ->setDeliveryInstructions($req->request->get('delivery_instructions')) - ->setTier1Notes($req->request->get('tier1_notes')) - ->setTier2Notes($req->request->get('tier2_notes')) - ->setDeliveryAddress($req->request->get('delivery_address')) - ->setAssignedBy($this->getUser()) - ->setDateAssign(new DateTime()) - ->setRider($rider); - - // validate - $errors = $validator->validate($obj); - - // add errors to list - foreach ($errors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); } // check if any errors were found @@ -1277,31 +419,6 @@ class JobOrderController extends Controller ], 422); } - // set rider unavailable - $rider->setAvailable(false); - - // the event - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::RIDER_ASSIGN) - ->setUser($this->getUser()) - ->setJobOrder($obj); - $em->persist($event); - - // validated! save the entity - $em->flush(); - - // send event to mobile app - $payload = [ - 'event' => 'driver_assigned' - ]; - $mclient->sendEvent($obj, $payload); - $mclient->sendRiderEvent($obj, $payload); - - // sned push notification - $aclient->sendPush($obj, "A RESQ rider is on his way to you."); - - // return successful response return $this->json([ 'success' => 'Changes have been saved!' @@ -1311,149 +428,46 @@ class JobOrderController extends Controller /** * @Menu(selected="jo_fulfill") */ - public function fulfillmentForm(MapTools $map_tools, $id) + public function fulfillmentForm(JobOrderHandlerInterface $jo_handler, $id, + GISManagerInterface $gis) { $this->denyAccessUnlessGranted('jo_fulfill.list', null, 'No access.'); - $em = $this->getDoctrine()->getManager(); - - $params['mode'] = 'update-fulfillment'; - - // get row data - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this row exists - if (empty($obj)) + try { - throw $this->createNotFoundException('The job order does not exist'); + $params = $jo_handler->initializeFulfillmentForm($id); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); } - // check status - if (!in_array($obj->getStatus(), [JOStatus::ASSIGNED, JOStatus::IN_PROGRESS])) - { - throw $this->createNotFoundException('The job order does not have a fulfillment status'); - } - - // check if hub is assigned to current user - $user_hubs = $this->getUser()->getHubs(); - if (!in_array($obj->getHub()->getID(), $user_hubs)) - { - throw $this->createNotFoundException('The job order is not on a hub assigned to this user'); - } - - $this->fillDropdownParameters($params); - $this->fillFormTags($params); - - $params['obj'] = $obj; - $params['status_cancelled'] = JOStatus::CANCELLED; - $params['submit_url'] = $this->generateUrl('jo_fulfill_submit', ['id' => $obj->getID()]); + $params['submit_url'] = $this->generateUrl('jo_fulfill_submit', ['id' => $id]); $params['return_url'] = $this->generateUrl('jo_fulfill'); + $params['map_js_file'] = $gis->getJSJOFile(); + + $template = $params['template']; // response - return $this->render('job-order/form.html.twig', $params); + return $this->render($template, $params); } - protected function updateVehicleBattery(JobOrder $jo) - { - // check if new battery - if ($jo->getServiceType() != ServiceType::BATTERY_REPLACEMENT_NEW) - return; - - // customer vehicle - $cv = $jo->getCustomerVehicle(); - if ($cv == null) - return; - - // invoice - $invoice = $jo->getInvoice(); - if ($invoice == null) - return; - - // invoice items - $items = $invoice->getItems(); - if (count($items) <= 0) - return; - - // get first battery from invoice - $battery = null; - foreach ($items as $item) - { - $battery = $item->getBattery(); - if ($battery != null) - break; - } - - // no battery in order - if ($battery == null) - return; - - // warranty expiration - $warr = $jo->getWarrantyClass(); - if ($warr == WarrantyClass::WTY_PRIVATE) - $warr_months = $battery->getWarrantyPrivate(); - else if ($warr == WarrantyClass::WTY_COMMERCIAL) - $warr_months = $battery->getWarrantyCommercial(); - else if ($warr == WarrantyClass::WTY_TNV) - $warr_months = 12; - - $warr_date = new DateTime(); - $warr_date->add(new DateInterval('P' . $warr_months . 'M')); - - // update customer vehicle battery - $cv->setCurrentBattery($battery) - ->setHasMotoliteBattery(true) - ->setWarrantyExpiration($warr_date); - } - - public function fulfillmentSubmit(Request $req, ValidatorInterface $validator, - MQTTClient $mclient, $id, WarrantyHandler $wh) + public function fulfillmentSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, $id) { $this->denyAccessUnlessGranted('jo_fulfill.list', null, 'No access.'); // initialize error list $error_array = []; - // get object data - $em = $this->getDoctrine()->getManager(); - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this object exists - if (empty($obj)) - throw $this->createNotFoundException('The item does not exist'); - - // check if lat and lng are provided - if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { - $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + try + { + $error_array = $jo_handler->fulfillJobOrder($req, $id, $mclient); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); } - if (empty($error_array)) { - // coordinates - $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); - - // set and save values - $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) - ->setCoordinates($point) - ->setAdvanceOrder($req->request->get('flag_advance') ?? false) - ->setServiceType($req->request->get('service_type')) - ->setWarrantyClass($req->request->get('warranty_class')) - ->setSource($req->request->get('source')) - // ->setStatus(JOStatus::FULFILLED) - ->setDeliveryInstructions($req->request->get('delivery_instructions')) - ->setTier1Notes($req->request->get('tier1_notes')) - ->setTier2Notes($req->request->get('tier2_notes')) - ->setDeliveryAddress($req->request->get('delivery_address')); - // ->setDateFulfill(new DateTime()); - - // validate - $errors = $validator->validate($obj); - - // add errors to list - foreach ($errors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } - } - - $obj->fulfill(); // check if any errors were found if (!empty($error_array)) { @@ -1464,290 +478,6 @@ class JobOrderController extends Controller ], 422); } - /* - // set rider available - $rider = $obj->getRider(); - if ($rider != null) - $rider->setAvailable(); - */ - - // the event - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::FULFILL) - ->setUser($this->getUser()) - ->setJobOrder($obj); - $em->persist($event); - - // save to customer vehicle battery record - $this->updateVehicleBattery($obj); - - // create warranty - if ($obj->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) - { - $serial = null; - $warranty_class = $obj->getWarrantyClass(); - $first_name = $obj->getCustomer()->getFirstName(); - $last_name = $obj->getCustomer()->getLastName(); - $mobile_number = $obj->getCustomer()->getPhoneMobile(); - - // check if date fulfilled is null - if ($obj->getDateFulfill() == null) - $date_purchase = $obj->getDateCreate(); - else - $date_purchase = $obj->getDateFulfill(); - - $plate_number = $wh->cleanPlateNumber($obj->getCustomerVehicle()->getPlateNumber()); - - $batt_list = array(); - $invoice = $obj->getInvoice(); - if (!empty($invoice)) - { - // get battery - $invoice_items = $invoice->getItems(); - foreach ($invoice_items as $item) - { - $battery = $item->getBattery(); - if ($battery != null) - { - $batt_list[] = $item->getBattery(); - } - } - } - - $wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class); - } - - // validated! save the entity - $em->flush(); - - // get rider - $rider = $obj->getRider(); - - $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/assets/images/user.gif'; - if ($rider->getImageFile() != null) - $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/uploads/' . $rider->getImageFile(); - - // send to mqtt - $payload = [ - 'event' => 'fulfilled', - 'jo_id' => $obj->getID(), - 'driver_image' => $image_url, - 'driver_name' => $rider->getFullName(), - 'driver_id' => $rider->getID(), - ]; - $mclient->sendEvent($obj, $payload); - $mclient->sendRiderEvent($obj, $payload); - - // return successful response - return $this->json([ - 'success' => 'Changes have been saved!' - ]); - } - - protected function sendEvent(JobOrder $job_order, $payload) - { - $sessions = $job_order->getCustomer()->getMobileSessions(); - if (count($sessions) == 0) - return; - - $client = new MosquittoClient(); - $client->connect('localhost', 1883); - - foreach ($sessions as $sess) - { - $phone_num = $sess->getPhoneNumber(); - $channel = 'motolite.control.' . $phone_num; - $client->publish($channel, json_encode($payload)); - } - - $client->disconnect(); - } - - /** - * @Menu(selected="jo_open") - */ - public function openHubForm(MapTools $map_tools, $id) - { - $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); - - $em = $this->getDoctrine()->getManager(); - - $params['mode'] = 'update-reassign-hub'; - - // get row data - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this row exists - if (empty($obj)) - { - throw $this->createNotFoundException('The job order does not exist'); - } - - $this->fillDropdownParameters($params); - $this->fillFormTags($params); - - // get rejections - $rejections = $obj->getHubRejections(); - - // get rejection reasons - $params['rejection_reasons'] = JORejectionReason::getCollection(); - - // get closest hubs - $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); - - $params['status_cancelled'] = JOStatus::CANCELLED; - $params['hubs'] = []; - - // format duration and distance into friendly time - foreach ($hubs as $hub) { - // duration - $seconds = $hub['duration']; - - if (!empty($seconds) && $seconds > 0) { - $hours = floor($seconds / 3600); - $minutes = ceil(($seconds / 60) % 60); - - $hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); - } else { - $hub['duration'] = false; - } - - // distance - $meters = $hub['distance']; - - if (!empty($meters) && $meters > 0) { - $hub['distance'] = round($meters / 1000) . " km"; - } else { - $hub['distance'] = false; - } - - // counters - $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); - $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); - - // check for rejection - $hub['flag_rejected'] = false; - $hub_id = $hub['hub']->getID(); - - foreach ($rejections as $robj) - { - if ($robj->getHub()->getID() === $hub_id) - { - $hub['flag_rejected'] = true; - break; - } - } - - $params['hubs'][] = $hub; - } - - $params['obj'] = $obj; - $params['submit_url'] = $this->generateUrl('jo_open_hub_submit', ['id' => $obj->getID()]); - $params['return_url'] = $this->generateUrl('jo_open'); - - // response - return $this->render('job-order/form.html.twig', $params); - } - - public function openHubSubmit(Request $req, ValidatorInterface $validator, MQTTClient $mclient, $id) - { - $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); - - // get object data - $em = $this->getDoctrine()->getManager(); - $obj = $em->getRepository(JobOrder::class)->find($id); - $user = $this->getUser(); - - // initialize error list - $error_array = []; - - // make sure this object exists - if (empty($obj)) - throw $this->createNotFoundException('The item does not exist'); - - // check if lat and lng are provided - if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { - $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; - } - - // check if hub is set - if (empty($req->request->get('hub'))) { - $error_array['hub'] = 'No hub selected.'; - } else { - // get hub - $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); - - if (empty($hub)) { - $error_array['hub'] = 'Invalid hub specified.'; - } - } - - if (empty($error_array)) - { - // rider mqtt event - // NOTE: need to send this before saving because rider will be cleared - $rider_payload = [ - 'event' => 'cancelled', - 'reason' => 'Reassigned', - 'jo_id' => $obj->getID(), - ]; - $mclient->sendRiderEvent($obj, $rider_payload); - - // coordinates - $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); - - // set and save values - $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) - ->setCoordinates($point) - ->setAdvanceOrder($req->request->get('flag_advance') ?? false) - ->setServiceType($req->request->get('service_type')) - ->setWarrantyClass($req->request->get('warranty_class')) - ->setSource($req->request->get('source')) - ->setStatus(JOStatus::RIDER_ASSIGN) - ->setDeliveryInstructions($req->request->get('delivery_instructions')) - ->setTier1Notes($req->request->get('tier1_notes')) - ->setTier2Notes($req->request->get('tier2_notes')) - ->setDeliveryAddress($req->request->get('delivery_address')) - ->setHub($hub) - ->setProcessedBy($this->getUser()) - ->clearRider(); - - // validate - $errors = $validator->validate($obj); - - // add errors to list - foreach ($errors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } - } - - // check if any errors were found - if (!empty($error_array)) { - // return validation failure response - return $this->json([ - 'success' => false, - 'errors' => $error_array - ], 422); - } - - // add event - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::HUB_ASSIGN) - ->setUser($this->getUser()) - ->setJobOrder($obj); - $em->persist($event); - - // validated! save the entity - $em->flush(); - - // user mqtt event - $payload = [ - 'event' => 'outlet_assign' - ]; - $mclient->sendEvent($obj, $payload); - // return successful response return $this->json([ 'success' => 'Changes have been saved!' @@ -1757,112 +487,44 @@ class JobOrderController extends Controller /** * @Menu(selected="jo_open") */ - public function openRiderForm($id) + public function openHubForm(MapTools $map_tools, $id, JobOrderHandlerInterface $jo_handler, + GISManagerInterface $gis) { $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); - $em = $this->getDoctrine()->getManager(); - - $params['mode'] = 'update-reassign-rider'; - - // get row data - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this row exists - if (empty($obj)) + try { - $em->getConnection()->rollback(); - throw $this->createNotFoundException('The job order does not exist'); + $params = $jo_handler->initializeHubForm($id, $map_tools); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); } - // check status - if ($obj->getStatus() == JOStatus::PENDING) - { - $em->getConnection()->rollback(); - throw $this->createNotFoundException('The job order does not have an assigned hub'); - } - - $this->fillDropdownParameters($params); - $this->fillFormTags($params); - - $params['obj'] = $obj; - $params['status_cancelled'] = JOStatus::CANCELLED; - $params['submit_url'] = $this->generateUrl('jo_open_rider_submit', ['id' => $obj->getID()]); + $params['submit_url'] = $this->generateUrl('jo_open_hub_submit', ['id' => $id]); $params['return_url'] = $this->generateUrl('jo_open'); + $params['map_js_file'] = $gis->getJSJOFile(); + + $template = $params['template']; // response - return $this->render('job-order/form.html.twig', $params); + return $this->render($template, $params); } - public function openRiderSubmit(Request $req, ValidatorInterface $validator, MQTTClient $mclient, $id) + public function openHubSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, $id) { $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); // initialize error list $error_array = []; - // get object data - $em = $this->getDoctrine()->getManager(); - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this object exists - if (empty($obj)) - throw $this->createNotFoundException('The item does not exist'); - - // check if lat and lng are provided - if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { - $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + try + { + $error_array = $jo_handler->setHub($req, $id, $mclient); } - - // check if rider is set - if (empty($req->request->get('rider'))) { - $error_array['rider'] = 'No rider selected.'; - } else { - // get rider - $rider = $em->getRepository(Rider::class)->find($req->request->get('rider')); - - if (empty($rider)) { - $error_array['rider'] = 'Invalid rider specified.'; - } - } - - if (empty($error_array)) { - // rider mqtt event - // NOTE: need to send this before saving because rider will be cleared - $rider_payload = [ - 'event' => 'cancelled', - 'reason' => 'Reassigned', - 'jo_id' => $obj->getID(), - ]; - $mclient->sendRiderEvent($obj, $rider_payload); - - // coordinates - $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); - - // set and save values - $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) - ->setCoordinates($point) - ->setAdvanceOrder($req->request->get('flag_advance') ?? false) - ->setServiceType($req->request->get('service_type')) - ->setWarrantyClass($req->request->get('warranty_class')) - ->setSource($req->request->get('source')) - ->setStatus(JOStatus::ASSIGNED) - ->setDeliveryInstructions($req->request->get('delivery_instructions')) - ->setTier1Notes($req->request->get('tier1_notes')) - ->setTier2Notes($req->request->get('tier2_notes')) - ->setDeliveryAddress($req->request->get('delivery_address')) - ->setAssignedBy($this->getUser()) - ->setDateAssign(new DateTime()) - ->setAssignedBy($this->getUser()) - ->setRider($rider); - - // validate - $errors = $validator->validate($obj); - - // add errors to list - foreach ($errors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); } // check if any errors were found @@ -1874,23 +536,63 @@ class JobOrderController extends Controller ], 422); } - // add event - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::RIDER_ASSIGN) - ->setUser($this->getUser()) - ->setJobOrder($obj); - $em->persist($event); + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } - // validated! save the entity - $em->flush(); + /** + * @Menu(selected="jo_open") + */ + public function openRiderForm($id, JobOrderHandlerInterface $jo_handler, + GISManagerInterface $gis) + { + $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); - // send event to mobile app - $payload = [ - 'event' => 'driver_assigned' - ]; - $mclient->sendEvent($obj, $payload); - $mclient->sendRiderEvent($obj, $payload); + try + { + $params = $jo_handler->initializeRiderForm($id); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); + } + + $params['submit_url'] = $this->generateUrl('jo_open_rider_submit', ['id' => $id]); + $params['return_url'] = $this->generateUrl('jo_open'); + $params['map_js_file'] = $gis->getJSJOFile(); + + $template = $params['template']; + + // response + return $this->render($template, $params); + } + + public function openRiderSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, $id) + { + $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); + + // initialize error list + $error_array = []; + + try + { + $error_array = $jo_handler->setRider($req, $id, $mclient); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); + } + + // check if any errors were found + if (!empty($error_array)) { + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array + ], 422); + } // return successful response return $this->json([ @@ -1901,423 +603,47 @@ class JobOrderController extends Controller /** * @Menu(selected="jo_all") */ - public function allForm($id) + public function allForm($id, JobOrderHandlerInterface $jo_handler, + GISManagerInterface $gis) { $this->denyAccessUnlessGranted('jo_all.list', null, 'No access.'); - $em = $this->getDoctrine()->getManager(); + try + { + $params = $jo_handler->initializeAllForm($id); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); + } - $params['mode'] = 'update-all'; - - // get row data - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this row exists - if (empty($obj)) - throw $this->createNotFoundException('The job order does not exist'); - - $this->fillDropdownParameters($params); - $this->fillFormTags($params); - - $params['obj'] = $obj; - $params['status_cancelled'] = JOStatus::CANCELLED; $params['return_url'] = $this->generateUrl('jo_all'); $params['submit_url'] = ''; + $params['map_js_file'] = $gis->getJSJOFile(); - // timeline stuff (descending by time) - $params['timeline'] = [ - [ - 'date' => date("M j"), - 'time' => date("g:i A"), - 'event' => "Event 4", - 'color' => "#f4516c" - ], - [ - 'date' => date("M j"), - 'time' => date("g:i A"), - 'event' => "Event 3", - 'color' => "#34bfa3" - ], - [ - 'date' => date("M j"), - 'time' => date("g:i A"), - 'event' => "Event 2", - 'color' => "#716aca" - ], - [ - 'date' => date("M j"), - 'time' => date("g:i A"), - 'event' => "Event 1", - 'color' => "#ffb822" - ], - ]; + $template = $params['template']; // response - return $this->render('job-order/form.html.twig', $params); + return $this->render($template, $params); } - public function pdfForm(Request $req, $id, TranslatorInterface $translator) + public function pdfForm(Request $req, $id, JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_pdf.list', null, 'No access.'); - $em = $this->getDoctrine()->getManager(); - - // get row data - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this row exists - if (empty($obj)) - throw $this->createNotFoundException('The job order does not exist'); - - // set output filename - $filename = 'job-order-' . $obj->getID() . '.pdf'; - - // translate the title and the logo for the pdf - $translated_title = $translator->trans('jo_title_pdf'); - $translated_logo = $translator->trans('image_jo_pdf'); - - // get the country code from services.yaml - $country_code = $this->getParameter('country_code'); - - // generate the pdf - $pdf = new FPDF('P', 'mm', 'letter'); - $pdf->AddPage(); - $pdf->setTitle($translated_title . ' #' . $obj->getID()); - $pdf->SetFillColor(211, 211, 211); - - // style defaults - $margin = 10; - $page_width = $pdf->GetPageWidth() - ($margin * 2); - $table_col_width = $page_width / 12; - $line_height = 5; - $jo_line_height = 10; - $table_line_height = 7; - $font_face = 'Arial'; - $body_font_size = 9; - $header_font_size = 9; - $jo_font_size = 16; - $col1_x = $margin; - $col2_x = 120; - $label_width = 40; - $val_width = 60; - - // insert the logo - $image_path = $this->get('kernel')->getProjectDir() . $translated_logo; - $pdf->Image($image_path, $col1_x, 10); - - // insert JO number - $pdf->SetFont($font_face, 'B', $jo_font_size); - $pdf->SetX($col2_x); - $pdf->Cell($label_width, $jo_line_height, 'JO Number:'); - $pdf->SetTextColor(9, 65, 150); - $pdf->Cell(0, $jo_line_height, $obj->getID()); - - // insert customer info - $customer = $obj->getCustomer(); - $pdf->SetFont($font_face, '', $body_font_size); - $pdf->SetTextColor(0, 0, 0); - - $pdf->Ln($line_height * 7); - - // get current Y - $y = $pdf->GetY(); - - $pdf->SetXY($col1_x, $y); - $pdf->Cell($label_width, $line_height, 'Customer Name:'); - $pdf->MultiCell($val_width, $line_height, $customer ? $customer->getFirstName() . ' ' . $customer->getLastName() : '', 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->Cell($label_width, $line_height, 'Mobile Phone:'); - $pdf->MultiCell(0, $line_height, $customer && $customer->getPhoneMobile() ? $country_code . $customer->getPhoneMobile() : '', 0, 'L'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->SetXY($col1_x, $y); - $pdf->Cell($label_width, $line_height, 'Delivery Date:'); - $pdf->MultiCell($val_width, $line_height, $obj->getDateSchedule() ? $obj->getDateSchedule()->format("m/d/Y") : '', 0, 'left'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->Cell($label_width, $line_height, 'Landline:'); - $pdf->MultiCell(0, $line_height, $customer && $customer->getPhoneLandline() ? $country_code . $customer->getPhoneLandline() : '', 0, 'L'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->SetXY($col2_x, $y); - $pdf->Cell($label_width, $line_height, 'Office Phone:'); - $pdf->MultiCell(0, $line_height, $customer && $customer->getPhoneOffice() ? $country_code . $customer->getPhoneOffice() : '', 0, 'L'); - - $pdf->SetX($col2_x); - $pdf->Cell($label_width, $line_height, 'Fax:'); - $pdf->MultiCell($val_width, $line_height, $customer && $customer->getPhoneFax() ? $country_code . $customer->getPhoneFax() : '', 0, 'L'); - - // insert vehicle info - $cv = $obj->getCustomerVehicle(); - $vehicle = $cv->getVehicle(); - $pdf->Ln(); - $pdf->SetFont($font_face, 'B', $header_font_size); - $pdf->Cell($label_width, $line_height, 'Vehicle Details'); - $pdf->Ln($line_height * 2); - - // get current Y - $y = $pdf->GetY(); - - $pdf->SetFont($font_face, '', $body_font_size); - $pdf->Cell($label_width, $line_height, 'Plate Number:'); - $pdf->MultiCell($val_width, $line_height, $cv ? $cv->getPlateNumber() : '', 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->Cell($label_width, $line_height, 'Vehicle Color:'); - $pdf->MultiCell(0, $line_height, $cv ? $cv->getColor() : '', 0, 'L'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->SetXY($col1_x, $y); - $pdf->Cell($label_width, $line_height, 'Brand:'); - $pdf->MultiCell($val_width, $line_height, $vehicle && $vehicle->getManufacturer() ? $vehicle->getManufacturer()->getName() : '', 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->Cell($label_width, $line_height, 'Model / Year:'); - $pdf->MultiCell(0, $line_height, $cv ? $cv->getModelYear() : '', 0, 'L'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->SetXY($col1_x, $y); - $pdf->Cell($label_width, $line_height, 'Make:'); - $pdf->MultiCell($val_width, $line_height, $vehicle ? $vehicle->getMake() : '', 0, 'L'); - - // insert battery info - $battery = $cv->getCurrentBattery(); - $pdf->Ln(); - $pdf->SetFont($font_face, 'B', $header_font_size); - $pdf->Cell($label_width, $line_height, 'Battery Details'); - $pdf->Ln($line_height * 2); - - $pdf->SetFont($font_face, '', $body_font_size); - - // get current Y - $y = $pdf->GetY(); - - $pdf->Cell($label_width, $line_height, 'Current Battery:'); - $pdf->MultiCell($val_width, $line_height, $battery && $battery->getManufacturer() && $battery->getModel() && $battery->getSize() ? $battery->getManufacturer()->getName() . ' ' . $battery->getModel()->getName() . ' ' . $battery->getSize()->getName() . ' (' . $battery->getProductCode() . ')' : '', 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->Cell($label_width, $line_height, 'Serial Number:'); - $pdf->MultiCell(0, $line_height, $cv ? $cv->getWarrantyCode() : '', 0, 'L'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->SetXY($col1_x, $y); - $pdf->Cell($label_width, $line_height, 'Wty. Exp. Date:'); - $pdf->MultiCell($val_width, $line_height, $cv && $cv->getWarrantyExpiration() ? $cv->getWarrantyExpiration()->format("d/m/Y") : '', 0, 'L'); - - // insert transaction details - $pdf->Ln(); - $pdf->SetFont($font_face, 'B', $header_font_size); - $pdf->Cell($label_width, $line_height, 'Transaction Details'); - $pdf->Ln($line_height * 2); - - $pdf->SetFont($font_face, '', $body_font_size); - - // get current Y - $y = $pdf->GetY(); - - $pdf->Cell($label_width, $line_height, 'Warranty Class:'); - $pdf->MultiCell($val_width, $line_height, WarrantyClass::getName($obj->getWarrantyClass()), 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->Cell($label_width, $line_height, 'Mode of Payment:'); - $pdf->MultiCell(0, $line_height, ModeOfPayment::getName($obj->getModeOfPayment()), 0, 'L'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->Cell($label_width, $line_height, 'Delivery Address:'); - $pdf->MultiCell($val_width, $line_height, $obj->getDeliveryAddress(), 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->Cell($label_width, $line_height, 'Landmark:'); - $pdf->MultiCell(0, $line_height, $obj->getLandMark(), 0, 'L'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->SetXY($col1_x, $y); - $pdf->Cell($label_width, $line_height, 'Dispatch Time:'); - $pdf->MultiCell($val_width, $line_height, $obj->getDateSchedule() ? $obj->getDateSchedule()->format("g:i A") : '', 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->Cell($label_width, $line_height, 'Dispatched By:'); - $pdf->MultiCell(0, $line_height, $obj->getProcessedBy() ? $obj->getProcessedBy()->getFullName() : '', 0, 'L'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - // insert delivery instructions - $pdf->SetY($y); - $pdf->Ln(); - $pdf->SetFont($font_face, 'B', $header_font_size); - $pdf->Cell(0, $line_height, 'Delivery Instructions'); - $pdf->Ln(); - - $pdf->SetFont($font_face, '', $body_font_size); - $pdf->MultiCell(0, $line_height, $obj->getDeliveryInstructions(), 1, 'L'); - - // insert invoice details - $pdf->Ln(); - $pdf->SetFont($font_face, 'B', $header_font_size); - $pdf->Cell($label_width, $line_height, 'Invoice Details'); - $pdf->Ln(); - - // invoice table headers - $invoice = $obj->getInvoice(); - $pdf->SetFont($font_face, 'B', $header_font_size); - $pdf->Cell($table_col_width * 6, $table_line_height, 'Item', 1, 0, 'L', 1); - $pdf->Cell($table_col_width * 2, $table_line_height, 'Quantity', 1, 0, 'R', 1); - $pdf->Cell($table_col_width * 2, $table_line_height, 'Unit Price', 1, 0, 'R', 1); - $pdf->Cell($table_col_width * 2, $table_line_height, 'Amount', 1, 1, 'R', 1); - $pdf->SetFont($font_face, '', $body_font_size); - - // build invoice items table - if ($invoice && !empty($invoice->getItems())) + try { - foreach ($invoice->getItems() as $item) - { - $pdf->Cell($table_col_width * 6, $table_line_height, $item->getTitle(), 1); - $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getQuantity()), 1, 0, 'R'); - $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getPrice(), 2), 1, 0, 'R'); - $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getPrice() * $item->getQuantity(), 2), 1, 1, 'R'); - } + $proj_path = $this->get('kernel')->getProjectDir(); + $params = $jo_handler->generatePDFForm($req, $id, $proj_path); } - else + catch (NotFoundHttpException $e) { - $pdf->Cell($table_col_width * 12, 7, 'No items', 1, 1); + throw $this->createNotFoundException($e->getMessage()); } - $pdf->Ln($line_height * 2); - - // get current Y - $y = $pdf->GetY(); - - // insert invoice footer details - $pdf->Cell($label_width, $line_height, 'Transaction Type:'); - $pdf->MultiCell($val_width, $line_height, ServiceType::getName($obj->getServiceType()), 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->SetFont($font_face, 'B'); - $pdf->Cell($label_width, $line_height, 'SUBTOTAL:'); - $pdf->SetFont($font_face, ''); - $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getVATExclusivePrice(), 2) : '', 0, 'R'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->SetXY($col1_x, $y); - $pdf->Cell($label_width, $line_height, 'OR Name:'); - $pdf->MultiCell($val_width, $line_height, $obj->getORName(), 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->SetFont($font_face, 'B'); - $pdf->Cell($label_width, $line_height, 'TAX:'); - $pdf->SetFont($font_face, ''); - $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getVAT(), 2) : '', 0, 'R'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->SetXY($col1_x, $y); - $pdf->Cell($label_width, $line_height, 'Emp. ID/Card No./Ref. By:'); - $pdf->MultiCell($val_width, $line_height, $obj->getPromoDetail(), 0, 'L'); - - // get Y after left cell - $y1 = $pdf->GetY(); - - $pdf->SetXY($col2_x, $y); - $pdf->SetFont($font_face, 'B'); - $pdf->Cell($label_width, $line_height, 'DISCOUNT:'); - $pdf->SetFont($font_face, ''); - $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getDiscount(), 2) : '', 0, 'R'); - - // get Y after right cell - $y2 = $pdf->GetY(); - - // get row height - $y = max($y1, $y2); - - $pdf->SetXY($col1_x, $y); - $pdf->Cell($label_width, $line_height, 'Discount Type:'); - $pdf->MultiCell($val_width, $line_height, $invoice && $invoice->getPromo() ? $invoice->getPromo()->getName() : '', 0, 'L'); - - $pdf->SetXY($col2_x, $y); - $pdf->SetFont($font_face, 'B'); - $pdf->Cell($label_width, $line_height, 'FINAL AMOUNT:'); - $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getTotalPrice(), 2) : '', 0, 'R'); - $pdf->SetFont($font_face, ''); + $pdf = $params['obj']; + $filename = $params['filename']; // return response return new Response($pdf->Output('I', $filename), 200, [ @@ -2325,7 +651,7 @@ class JobOrderController extends Controller ]); } - public function cancelJobOrder(Request $req, MQTTClient $mclient, $id) + public function cancelJobOrder(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, $id) { $this->denyAccessUnlessGranted('joborder.cancel', null, 'No access.'); @@ -2340,190 +666,23 @@ class JobOrderController extends Controller ], 422); } - // get object data - $em = $this->getDoctrine()->getManager(); - $obj = $em->getRepository(JobOrder::class)->find($id); - - // make sure this object exists - if (empty($obj)) - throw $this->createNotFoundException('The item does not exist'); - - /* - // cancel job order - $obj->setStatus(JOStatus::CANCELLED) - ->setDateCancel(new DateTime()) - ->setCancelReason($cancel_reason); - - // set rider available - $rider = $obj->getRider(); - if ($rider != null) - $rider->setAvailable(); - */ - - $obj->cancel($cancel_reason); - - // the event - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::CANCEL) - ->setUser($this->getUser()) - ->setJobOrder($obj); - $em->persist($event); - - // save - $em->flush(); - - // send mobile app event - $payload = [ - 'event' => 'cancelled', - 'reason' => $cancel_reason, - 'jo_id' => $obj->getID(), - ]; - $mclient->sendEvent($obj, $payload); - $mclient->sendRiderEvent($obj, $payload); + try + { + $jo_handler->cancelJobOrder($req, $id, $mclient); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); + } // return successful response return $this->json([ 'success' => 'Job order has been cancelled!' ]); + } - // TODO: re-enable search, figure out how to group the orWhere filters into one, so can execute that plus the pending filter - // check if datatable filter is present and append to query - protected function setQueryFilters($datatable, &$query, $qb, $hubs, $tier, $status) - { - switch ($tier) - { - case 'fulfill': - $query->where('q.status IN (:statuses)') - ->andWhere('q.hub IN (:hubs)') - ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY) - ->setParameter('hubs', $hubs, Connection::PARAM_STR_ARRAY); - break; - case 'assign': - $query->where('q.status = :status') - ->andWhere('q.hub IN (:hubs)') - ->setParameter('status', $status) - ->setParameter('hubs', $hubs, Connection::PARAM_STR_ARRAY); - break; - case 'open': - if (isset($datatable['query']['data-rows-search'])) - { - $query->innerJoin('q.cus_vehicle', 'cv') - ->innerJoin('q.customer', 'c') - ->where('q.status IN (:statuses)') - ->andWhere('cv.plate_number like :filter or c.first_name like :filter or c.last_name like :filter or c.phone_mobile like :filter') - ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY) - ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); - } - else - { - $query->where('q.status IN (:statuses)') - ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY); - } - break; - case 'all': - if (isset($datatable['query']['data-rows-search'])) - { - $query->innerJoin('q.cus_vehicle', 'cv') - ->innerJoin('q.customer', 'c') - ->where('cv.plate_number like :filter') - ->orWhere('c.phone_mobile like :filter') - ->orWhere('c.first_name like :filter or c.last_name like :filter') - ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); - } - break; - default: - $query->where('q.status = :status') - ->setParameter('status', $status); - } - - // get only pending rows - /* - $query->where($qb->expr()->orX( - $qb->expr()where('q.status', 'pending'); - )); - - - // apply filters - if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) { - $query->where('q.delivery_address LIKE :filter') - ->orWhere($qb->expr()->concat('c.first_name', $qb->expr()->literal(' '), 'c.last_name') . ' LIKE :filter') - ->orWhere('cv.plate_number LIKE :filter') - ->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%'); - } - */ - } - - protected function invoicePromo($em, InvoiceCriteria $criteria, $promo_id) - { - // return error if there's a problem, false otherwise - - // check service type - $stype = $criteria->getServiceType(); - if ($stype != ServiceType::BATTERY_REPLACEMENT_NEW) - return null; - - - if (empty($promo_id)) - return false; - - // check if this is a valid promo - $promo = $em->getRepository(Promo::class)->find($promo_id); - - if (empty($promo)) - return 'Invalid promo specified.'; - - $criteria->addPromo($promo); - return false; - } - - protected function invoiceBatteries($em, InvoiceCriteria $criteria, $items) - { - // check service type - $stype = $criteria->getServiceType(); - if ($stype != ServiceType::BATTERY_REPLACEMENT_NEW && $stype != ServiceType::BATTERY_REPLACEMENT_WARRANTY) - return null; - - // return error if there's a problem, false otherwise - if (!empty($items)) - { - foreach ($items as $item) - { - // error_log('ITEMS'); - // check if this is a valid battery - $battery = $em->getRepository(Battery::class)->find($item['battery']); - - if (empty($battery)) - { - $error = 'Invalid battery specified.'; - return $error; - } - - // quantity - $qty = $item['quantity']; - if ($qty < 1) - continue; - - /* - // add to criteria - $criteria->addBattery($battery, $qty); - */ - - // if this is a trade in, add trade in - if (!empty($item['trade_in']) && TradeInType::validate($item['trade_in'])) - $trade_in = $item['trade_in']; - else - $trade_in = null; - - $criteria->addEntry($battery, $trade_in, $qty); - } - } - - return null; - } - - public function generateInvoice(Request $req, InvoiceCreator $ic) + public function generateInvoice(Request $req, InvoiceGeneratorInterface $ic) { // error_log('generating invoice...'); $error = false; @@ -2537,8 +696,10 @@ class JobOrderController extends Controller // get customer vehicle $cv = $em->getRepository(CustomerVehicle::class)->find($cvid); + /* if ($cv == null) throw new \Exception('Could not get customer vehicle'); + */ // instantiate invoice criteria @@ -2573,11 +734,11 @@ class JobOrderController extends Controller } */ - - $error = $this->invoicePromo($em, $criteria, $promo_id); + // TODO: this snippet should be in the invoice generator + $error = $ic->invoicePromo($criteria, $promo_id); if (!$error) - $error = $this->invoiceBatteries($em, $criteria, $items); + $error = $ic->invoiceBatteries($criteria, $items); if ($error) { @@ -2589,7 +750,7 @@ class JobOrderController extends Controller } // generate the invoice - $iobj = $ic->processCriteria($criteria); + $iobj = $ic->generateInvoice($criteria); // use invoice object values in a json friendly array $invoice = [ @@ -2618,117 +779,45 @@ class JobOrderController extends Controller ]); } - public function unlockProcessor($id) + public function unlockProcessor($id, JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_proc.unlock', null, 'No access.'); - // clear lock - $em = $this->getDoctrine()->getManager(); - $jo = $em->getRepository(JobOrder::class)->find($id); - if ($jo == null) - return $this->redirectToRoute('jo_proc'); - - $jo->setProcessedBy(null); - - $em->flush(); + // call unlockProcessor in job order service + $jo_handler->unlockProcessor($id); // redirect to list return $this->redirectToRoute('jo_proc'); } - public function unlockAssignor($id) + public function unlockAssignor($id, JobOrderHandlerInterface $jo_handler) { $this->denyAccessUnlessGranted('jo_assign.unlock', null, 'No access.'); - // clear lock - $em = $this->getDoctrine()->getManager(); - $jo = $em->getRepository(JobOrder::class)->find($id); - if ($jo == null) - return $this->redirectToRoute('jo_assign'); - - $jo->setAssignedBy(null); - $em->flush(); + // call unlockAssignor in job order service + $jo_handler->unlockAssignor($id); // redirect to list return $this->redirectToRoute('jo_assign'); } - public function rejectHubSubmit(Request $req, ValidatorInterface $validator, $id) + public function rejectHubSubmit(Request $req, JobOrderHandlerInterface $jo_handler, $id) { $this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.'); - // get object data - $em = $this->getDoctrine()->getManager(); - $jo = $em->getRepository(JobOrder::class)->find($id); - $processor = $jo->getProcessedBy(); - $user = $this->getUser(); - - // check if we're the one processing, return error otherwise - if ($processor == null) - throw $this->createAccessDeniedException('Not the processor'); - - if ($processor != null && $processor->getID() != $user->getID()) - throw $this->createAccessDeniedException('Not the processor'); - // initialize error list $error_array = []; - - // make sure job order exists - if (empty($jo)) - throw $this->createNotFoundException('The item does not exist'); - - // check if hub is set - if (empty($req->request->get('hub'))) + try { - $error_array['hub'] = 'No hub selected.'; + $error_array = $jo_handler->rejectHub($req, $id); } - else + catch (AccessDeniedHttpException $e) { - // get hub - $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); - - if (empty($hub)) - { - $error_array['hub'] = 'Invalid hub specified.'; - } + throw $this->createAccessDeniedException($e->getMessage()); } - - // check if this hub has already been rejected on this job order - $robj = $em->getRepository(JORejection::class)->findOneBy([ - 'job_order' => $jo, - 'hub' => $hub - ]); - - if (!empty($robj)) - $error_array['hub'] = 'This hub has already been rejected for the current job order.'; - - // check if reason is set - if (empty($req->request->get('reason'))) - $error_array['reason'] = 'No reason selected.'; - else if (!JORejectionReason::validate($req->request->get('reason'))) - $error_array['reason'] = 'Invalid reason specified.'; - - if (empty($error_array)) + catch (NotFoundHttpException $e) { - // coordinates - $obj = new JORejection(); - - // set and save values - $obj->setDateCreate(new DateTime()) - ->setHub($hub) - ->setUser($this->getUser()) - ->setJobOrder($jo) - ->setReason($req->request->get('reason')) - ->setRemarks($req->request->get('remarks')) - ->setContactPerson($req->request->get('contact_person')); - - // validate - $errors = $validator->validate($obj); - - // add errors to list - foreach ($errors as $error) { - $error_array[$error->getPropertyPath()] = $error->getMessage(); - } + throw $this->createNotFoundException($e->getMessage()); } // check if any errors were found @@ -2741,14 +830,136 @@ class JobOrderController extends Controller ], 422); } - // validated! save the entity - $em->persist($obj); - $em->flush(); - // return successful response return $this->json([ 'success' => 'Changes have been saved!', 'request' => $req->request->all() ]); } + + /** + * @Menu(selected="jo_onestep_form") + */ + public function oneStepForm(EntityManagerInterface $em, JobOrderHandlerInterface $jo_handler, GISManagerInterface $gis) + { + $this->denyAccessUnlessGranted('jo_onestep.form', null, 'No access.'); + + $params = $jo_handler->initializeOneStepForm(); + $params['submit_url'] = $this->generateUrl('jo_onestep_submit'); + $params['return_url'] = $this->generateUrl('jo_onestep_form'); + $params['map_js_file'] = $gis->getJSJOFile(); + $params['vmfgs'] = $em->getRepository(VehicleManufacturer::class)->findAll(); + $params['vmakes'] = $em->getRepository(Vehicle::class)->findAll(); + + $template = $params['template']; + + // response + return $this->render($template, $params); + } + + public function oneStepSubmit(Request $req, JobOrderHandlerInterface $jo_handler) + { + $this->denyAccessUnlessGranted('jo_onestep.form', null, 'No access.'); + + // initialize error list + $error_array = []; + $id = -1; + $error_array = $jo_handler->processOneStepJobOrder($req, $id); + + // check if any errors were found + if (!empty($error_array)) { + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array + ], 422); + } + + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + + } + + /** + * @Menu(selected="jo_onestep_edit_form") + */ + public function oneStepEditForm($id, EntityManagerInterface $em, JobOrderHandlerInterface $jo_handler, + GISManagerInterface $gis, MapTools $map_tools) + { + $this->denyAccessUnlessGranted('jo_onestep.edit', null, 'No access.'); + + $params = $jo_handler->initializeOneStepEditForm($id, $map_tools); + $params['submit_url'] = $this->generateUrl('jo_onestep_edit_submit', ['id' => $id]); + $params['return_url'] = $this->generateUrl('jo_open'); + $params['map_js_file'] = $gis->getJSJOFile(); + $params['vmfgs'] = $em->getRepository(VehicleManufacturer::class)->findAll(); + $params['vmakes'] = $em->getRepository(Vehicle::class)->findAll(); + + $template = $params['template']; + + // response + return $this->render($template, $params); + } + + public function oneStepEditSubmit(Request $req, JobOrderHandlerInterface $jo_handler, $id) + { + $this->denyAccessUnlessGranted('jo_onestep.edit', null, 'No access.'); + + $error_array = []; + $error_array = $jo_handler->processOneStepJobOrder($req, $id); + + // check if any errors were found + if (!empty($error_array)) { + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array + ], 422); + } + + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } + + /** + * @ParamConverter("jo", class="App\Entity\JobOrder") + */ + public function popupInfo(JobOrder $jo) + { + if ($jo == null) + return new Response('No job order data'); + + return $this->render('job-order/popup.html.twig', [ 'jo' => $jo ]); + } + + /** + * @ParamConverter("jo", class="App\Entity\JobOrder") + */ + public function tracker( + EntityManagerInterface $em, + RiderTracker $rider_tracker, + GISManagerInterface $gis_manager, + JobOrder $jo + ) + { + if ($jo === null) + return new Response('No job order data'); + + $rider = $jo->getRider(); + + // get map + $params['jo'] = $jo; + $params['rider'] = $rider; + $params['rider_pos'] = $rider_tracker->getRiderLocation($rider->getID()); + $params['service_type'] = CMBServiceType::getName($jo->getServiceType()); + $params['map_js_file'] = $gis_manager->getJSInitFile(); + + return $this->render('job-order/tracker.html.twig', $params); + } } diff --git a/src/Controller/RAPIController.php b/src/Controller/RAPIController.php index 89e06cff..3b8bdab1 100644 --- a/src/Controller/RAPIController.php +++ b/src/Controller/RAPIController.php @@ -2,964 +2,302 @@ namespace App\Controller; -use Doctrine\ORM\Query; -use Doctrine\ORM\QueryBuilder; -use Doctrine\DBAL\DBALException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\JsonResponse; -use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; - -use CrEOF\Spatial\PHP\Types\Geometry\Point; use App\Ramcar\APIResult; -use App\Ramcar\JOStatus; -use App\Ramcar\InvoiceCriteria; -use App\Ramcar\ServiceType; -use App\Ramcar\WarrantyClass; -use App\Ramcar\APIRiderStatus; -use App\Ramcar\TransactionOrigin; -use App\Ramcar\TradeInType; -use App\Ramcar\InvoiceStatus; -use App\Ramcar\ModeOfPayment; -use App\Ramcar\JOEventType; -use App\Service\InvoiceCreator; -use App\Service\MQTTClient; -use App\Service\WarrantyHandler; - -use App\Entity\RiderSession; -use App\Entity\Customer; -use App\Entity\VehicleManufacturer; -use App\Entity\Vehicle; -use App\Entity\CustomerVehicle; -use App\Entity\JobOrder; -use App\Entity\Promo; -use App\Entity\Battery; -use App\Entity\BatteryModel; -use App\Entity\BatterySize; -use App\Entity\RiderRating; -use App\Entity\Rider; -use App\Entity\User; -use App\Entity\JOEvent; - -use DateTime; -use DateInterval; +use App\Service\RiderAPIHandlerInterface; // Rider API controller class RAPIController extends Controller { - protected $session; - - public function __construct() - { - // one device = one session, since we have control over the devices - // when a rider logs in, we just change the rider assigned to the device - // when a rider logs out, we remove the rider assigned to the device - $this->session = null; - } - - protected function checkMissingParameters(Request $req, $params = []) - { - $missing = []; - - // check if parameters are there - foreach ($params as $param) - { - if ($req->getMethod() == 'GET') - { - $check = $req->query->get($param); - if ($check == null) - $missing[] = $param; - } - else if ($req->getMethod() == 'POST') - { - $check = $req->request->get($param); - if ($check == null) - $missing[] = $param; - } - else - return $params; - } - - return $missing; - } - - // TODO: type hint entity manager - protected function checkAPIKey($em, $api_key) - { - // find the api key (session id) - $session = $em->getRepository(RiderSession::class)->find($api_key); - if ($session == null) - return null; - - return $session; - } - - protected function checkParamsAndKey(Request $req, $em, $params) - { - // returns APIResult object - $res = new APIResult(); - - // check for api_key in query string - $api_key = $req->query->get('api_key'); - if (empty($api_key)) - { - $res->setError(true) - ->setErrorMessage('Missing API key'); - return $res; - } - - // check missing parameters - $missing = $this->checkMissingParameters($req, $params); - if (count($missing) > 0) - { - $miss_string = implode(', ', $missing); - $res->setError(true) - ->setErrorMessage('Missing parameter(s): ' . $miss_string); - return $res; - } - - // check api key - $sess = $this->checkAPIKey($em, $req->query->get('api_key')); - if ($sess == null) - { - $res->setError(true) - ->setErrorMessage('Invalid API Key'); - return $res; - } - - // store session - $this->session = $sess; - - return $res; - } - - public function register(Request $req) + public function register(Request $req, RiderAPIHandlerInterface $rapi_handler) { $res = new APIResult(); - // confirm parameters - $required_params = [ - 'phone_number', - 'device_push_id' - ]; + $data = $rapi_handler->register($req); - $missing = $this->checkMissingParameters($req, $required_params); - if (count($missing) > 0) + if (isset($data['error'])) { - $params = implode(', ', $missing); + $message = $data['error']; + $res->setError(true) - ->setErrorMessage('Missing parameter(s): ' . $params); - return $res->getReturnResponse(); + ->setErrorMessage($message); } - - $em = $this->getDoctrine()->getManager(); - - // retry until we get a unique id - while (true) + else { - try - { - // instantiate session - $sess = new RiderSession(); - $sess->setPhoneNumber($req->request->get('phone_number')) - ->setDevicePushID($req->request->get('device_push_id')); - - // reopen in case we get an exception - if (!$em->isOpen()) - { - $em = $em->create( - $em->getConnection(), - $em->getConfiguration() - ); - } - - // save - $em->persist($sess); - $em->flush(); - } - catch (DBALException $e) - { - error_log($e->getMessage()); - // delay one second and try again - sleep(1); - continue; - } - - break; + $res->setData($data); } - // return data - $data = [ - 'session_id' => $sess->getID() - ]; - $res->setData($data); - - // response return $res->getReturnResponse(); } - public function login(Request $req, EncoderFactoryInterface $ef) + public function login(Request $req, RiderAPIHandlerInterface $rapi_handler) { - $required_params = [ - 'user', - 'pass', - ]; - $em = $this->getDoctrine()->getManager(); - $res = $this->checkParamsAndKey($req, $em, $required_params); - if ($res->isError()) - return $res->getReturnResponse(); + $res = new APIResult(); - // check if session has a rider already - if ($this->session->hasRider()) + $data = $rapi_handler->login($req); + + if (isset($data['error'])) { + $message = $data['error']; + $res->setError(true) - ->setErrorMessage('Another rider is already logged in. Please logout first.'); - return $res->getReturnResponse(); - } - - // look for rider with username - $rider = $em->getRepository(Rider::class)->findOneBy(['username' => $req->request->get('user')]); - if ($rider == null) - { - $res->setError(true) - ->setErrorMessage('Invalid username or password.'); - return $res->getReturnResponse(); - } - - // check if rider password is correct - $encoder = $ef->getEncoder(new User()); - if (!$encoder->isPasswordValid($rider->getPassword(), $req->request->get('pass'), '')) - { - $res->setError(true) - ->setErrorMessage('Invalid username or password.'); - return $res->getReturnResponse(); - } - - // assign rider to session - $this->session->setRider($rider); - - $rider->setAvailable(true); - - // TODO: log rider logging in - - $em->flush(); - - $hub = $rider->getHub(); - if ($hub == null) - $hub_data = null; - else - { - $coord = $hub->getCoordinates(); - $hub_data = [ - 'id' => $hub->getID(), - 'name' => $hub->getName(), - 'branch' => $hub->getBranch(), - 'longitude' => $coord->getLongitude(), - 'latitude' => $coord->getLatitude(), - 'contact_nums' => $hub->getContactNumbers(), - ]; - } - - // data - $data = [ - 'hub' => $hub_data - ]; - - $res->setData($data); - - return $res->getReturnResponse(); - } - - public function logout(Request $req) - { - $required_params = []; - $em = $this->getDoctrine()->getManager(); - $res = $this->checkParamsAndKey($req, $em, $required_params); - if ($res->isError()) - return $res->getReturnResponse(); - - // make rider unavailable - $rider = $this->session->getRider(); - $rider->setAvailable(false); - - // remove rider from session - $this->session->setRider(null); - - // TODO: log rider logging out - - $em->flush(); - - return $res->getReturnResponse(); - } - - public function getJobOrder(Request $req) - { - // get the job order of the rider assigned to this session - $required_params = []; - $em = $this->getDoctrine()->getManager(); - $res = $this->checkParamsAndKey($req, $em, $required_params); - if ($res->isError()) - return $res->getReturnResponse(); - - // are we logged in? - if (!$this->session->hasRider()) - { - $res->setError(true) - ->setErrorMessage('No logged in rider.'); - return $res->getReturnResponse(); - } - - $rider = $this->session->getRider(); - - // do we have a job order? - $jo = $rider->getActiveJobOrder(); - if ($jo == null) - { - $data = [ - 'job_order' => null - ]; + ->setErrorMessage($message); } else { - $coord = $jo->getCoordinates(); - $cust = $jo->getCustomer(); - $cv = $jo->getCustomerVehicle(); - $v = $cv->getVehicle(); - $inv = $jo->getInvoice(); - $promo = $inv->getPromo(); - - // invoice items - $inv_items = []; - foreach ($inv->getItems() as $item) - { - $item_batt = $item->getBattery(); - if ($item_batt == null) - $batt_id = null; - else - $batt_id = $item_batt->getID(); - - $inv_items[] = [ - 'id' => $item->getID(), - 'title' => $item->getTitle(), - 'qty' => $item->getQuantity(), - 'price' => $item->getPrice(), - 'batt_id' => $batt_id, - ]; - } - - // promo - if ($promo != null) - { - $promo_data = [ - 'id' => $promo->getID(), - 'name' => $promo->getName(), - 'code' => $promo->getCode(), - 'discount_rate' => $promo->getDiscountRate(), - 'discount_apply' => $promo->getDiscountApply(), - ]; - } - else - { - $promo_data = null; - } - - $trade_in_type = $jo->getTradeInType(); - if (empty($trade_in_type)) - $trade_in_type = 'none'; - - - $data = [ - 'job_order' => [ - 'id' => $jo->getID(), - 'service_type' => $jo->getServiceType(), - 'date_schedule' => $jo->getDateSchedule()->format('Ymd H:i:s'), - 'longitude' => $coord->getLongitude(), - 'latitude' => $coord->getLatitude(), - 'status' => $jo->getStatus(), - 'customer' => [ - 'title' => $cust->getTitle(), - 'first_name' => $cust->getFirstName(), - 'last_name' => $cust->getLastName(), - 'phone_mobile' => '63' . $cust->getPhoneMobile(), - ], - 'vehicle' => [ - 'manufacturer' => $v->getManufacturer()->getName(), - 'make' => $v->getMake(), - 'model' => $cv->getModelYear(), - 'plate_number' => $cv->getPlateNumber(), - 'color' => $cv->getColor(), - ], - 'or_num' => $jo->getORNum(), - 'or_name' => $jo->getORName(), - 'delivery_instructions' => $jo->getDeliveryInstructions(), - 'delivery_address' => $jo->getDeliveryAddress(), - 'landmark' => $jo->getLandmark(), - 'invoice' => [ - 'discount' => $inv->getDiscount(), - 'trade_in' => $inv->getTradeIn(), - 'total_price' => $inv->getTotalPrice(), - 'vat' => $inv->getVat(), - 'items' => $inv_items, - ], - 'mode_of_payment' => $jo->getModeOfPayment(), - 'trade_in_type' => $trade_in_type, - 'promo' => $promo_data, - // TODO: load the actual - 'has_warranty_doc' => false, - 'flag_coolant' => $jo->hasCoolant(), - 'has_motolite' => $cv->hasMotoliteBattery(), - ] - ]; + $res->setData($data); } - $res->setData($data); - + // response return $res->getReturnResponse(); } - protected function checkJO(Request $req, $required_params, &$jo = null) + public function logout(Request $req, RiderAPIHandlerInterface $rapi_handler) { - // set jo status to in transit - $em = $this->getDoctrine()->getManager(); - $res = $this->checkParamsAndKey($req, $em, $required_params); - if ($res->isError()) - return $res; + $res = new APIResult(); - // are we logged in? - if (!$this->session->hasRider()) + $data = $rapi_handler->logout($req); + + if (isset($data['error'])) { + $message = $data['error']; + $res->setError(true) - ->setErrorMessage('No logged in rider.'); - return $res; + ->setErrorMessage($message); + } + else + { + $res->setData($data); } - $rider = $this->session->getRider(); + // response + return $res->getReturnResponse(); + } - // check if we have an active JO - $jo = $rider->getActiveJobOrder(); - if ($jo == null) + public function getJobOrder(Request $req, RiderAPIHandlerInterface $rapi_handler) + { + $res = new APIResult(); + + $data = $rapi_handler->getJobOrder($req); + + if (isset($data['error'])) { + $message = $data['error']; + $res->setError(true) - ->setErrorMessage('No active job order.'); - return $res; + ->setErrorMessage($message); + } + else + { + $res->setData($data); } - // check if the jo_id sent is the same as our active jo - if ($req->request->get('jo_id') != $jo->getID()) + // response + return $res->getReturnResponse(); + } + + public function acceptJobOrder(Request $req, RiderAPIHandlerInterface $rapi_handler) + { + $res = new APIResult(); + + $data = $rapi_handler->acceptJobOrder($req); + + if (isset($data['error'])) { + $message = $data['error']; + $res->setError(true) - ->setErrorMessage('Job order selected is not active job order.'); - return $res; + ->setErrorMessage($message); + } + else + { + $res->setData($data); } - return $res; - } - - public function acceptJobOrder(Request $req) - { - $em = $this->getDoctrine()->getManager(); - $required_params = ['jo_id']; - $res = $this->checkJO($req, $required_params, $jo); - if ($res->isError()) - return $res->getReturnResponse(); - - // TODO: refactor this into a jo handler class, so we don't have to repeat for control center - - // set jo status to in transit - $jo->setStatus(JOStatus::IN_TRANSIT); - - // TODO: send mqtt event (?) - - // add event log - $rider = $this->session->getRider(); - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::RIDER_ACCEPT) - ->setJobOrder($jo) - ->setRider($rider); - $em->persist($event); - - $em->flush(); - + // response return $res->getReturnResponse(); } - public function cancelJobOrder(Request $req, MQTTClient $mclient) + public function cancelJobOrder(Request $req, RiderAPIHandlerInterface $rapi_handler) { - $em = $this->getDoctrine()->getManager(); - $required_params = ['jo_id']; - $res = $this->checkJO($req, $required_params, $jo); - if ($res->isError()) - return $res->getReturnResponse(); + $res = new APIResult(); - // $jo->cancel("rider cancelled"); - // requeue it, instead of cancelling it - $jo->requeue(); + $data = $rapi_handler->cancelJobOrder($req); - // add event log - $rider = $this->session->getRider(); - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::REQUEUE) - ->setJobOrder($jo) - ->setRider($rider); - $em->persist($event); - - $em->flush(); - - // send mqtt event - // send outlet assign since order should go back to hub and await reassignment to another rider - $payload = [ - 'event' => 'outlet_assign', - 'jo_id' => $jo->getID(), - ]; - $mclient->sendEvent($jo, $payload); - - - - return $res->getReturnResponse(); - } - - public function arrive(Request $req, MQTTClient $mclient) - { - $em = $this->getDoctrine()->getManager(); - $required_params = ['jo_id']; - $res = $this->checkJO($req, $required_params, $jo); - if ($res->isError()) - return $res->getReturnResponse(); - - // TODO: refactor this into a jo handler class, so we don't have to repeat for control center - - // set jo status to in progress - $jo->setStatus(JOStatus::IN_PROGRESS); - - // add event log - $rider = $this->session->getRider(); - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::RIDER_ARRIVE) - ->setJobOrder($jo) - ->setRider($rider); - $em->persist($event); - - $em->flush(); - - // send mqtt event - $rider = $this->session->getRider(); - $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/assets/images/user.gif'; - if ($rider->getImageFile() != null) - $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/uploads/' . $rider->getImageFile(); - - $payload = [ - 'event' => 'driver_arrived', - 'jo_id' => $jo->getID(), - 'driver_image' => $image_url, - 'driver_name' => $rider->getFullName(), - 'driver_id' => $rider->getID(), - ]; - $mclient->sendEvent($jo, $payload); - - - return $res->getReturnResponse(); - } - - public function hubArrive(Request $req) - { - $required_params = []; - $em = $this->getDoctrine()->getManager(); - $res = $this->checkParamsAndKey($req, $em, $required_params); - if ($res->isError()) - return $res->getReturnResponse(); - - // are we logged in? - if (!$this->session->hasRider()) + if (isset($data['error'])) { + $message = $data['error']; + $res->setError(true) - ->setErrorMessage('No logged in rider.'); - return $res->getReturnResponse(); + ->setErrorMessage($message); + } + else + { + $res->setData($data); } - // TODO: tag rider as available + // response + return $res->getReturnResponse(); - $em->flush(); + } + public function arrive(Request $req, RiderAPIHandlerInterface $rapi_handler) + { + $res = new APIResult(); + $data = $rapi_handler->arrive($req); + + if (isset($data['error'])) + { + $message = $data['error']; + + $res->setError(true) + ->setErrorMessage($message); + } + else + { + $res->setData($data); + } + + // response return $res->getReturnResponse(); } - public function payment(Request $req, MQTTClient $mclient, WarrantyHandler $wh) + public function hubArrive(Request $req, RiderAPIHandlerInterface $rapi_handler) { - $em = $this->getDoctrine()->getManager(); - $required_params = ['jo_id']; - $res = $this->checkJO($req, $required_params, $jo); - if ($res->isError()) - return $res->getReturnResponse(); + $res = new APIResult(); - // set invoice to paid - $jo->getInvoice()->setStatus(InvoiceStatus::PAID); + $data = $rapi_handler->hubArrive($req); - /* - // set jo status to fulfilled - $jo->setStatus(JOStatus::FULFILLED); - */ - $jo->fulfill(); - - // add event log - $rider = $this->session->getRider(); - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::FULFILL) - ->setJobOrder($jo) - ->setRider($rider); - $em->persist($event); - - // TODO: tag rider as unavailable - - // save to customer vehicle battery record - // TODO: this has to move to JOHandler - $this->updateVehicleBattery($jo); - - // create warranty - if ($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) + if (isset($data['error'])) { - $serial = null; - $warranty_class = $jo->getWarrantyClass(); - $first_name = $jo->getCustomer()->getFirstName(); - $last_name = $jo->getCustomer()->getLastName(); - $mobile_number = $jo->getCustomer()->getPhoneMobile(); + $message = $data['error']; - // check if date fulfilled is null - if ($jo->getDateFulfill() == null) - $date_purchase = $jo->getDateCreate(); - else - $date_purchase = $jo->getDateFulfill(); - - $plate_number = $wh->cleanPlateNumber($jo->getCustomerVehicle()->getPlateNumber()); - - $batt_list = array(); - $invoice = $jo->getInvoice(); - if (!empty($invoice)) - { - // get battery - $invoice_items = $invoice->getItems(); - foreach ($invoice_items as $item) - { - $battery = $item->getBattery(); - if ($battery != null) - { - $batt_list[] = $item->getBattery(); - } - } - } - - $wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class); + $res->setError(true) + ->setErrorMessage($message); + } + else + { + $res->setData($data); } - $em->flush(); + // response + return $res->getReturnResponse(); + } - // send mqtt event (fulfilled) - $rider = $this->session->getRider(); - $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/assets/images/user.gif'; - if ($rider->getImageFile() != null) - $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/uploads/' . $rider->getImageFile(); - $payload = [ - 'event' => 'fulfilled', - 'jo_id' => $jo->getID(), - 'driver_image' => $image_url, - 'driver_name' => $rider->getFullName(), - 'driver_id' => $rider->getID(), - ]; - $mclient->sendEvent($jo, $payload); + public function payment(Request $req, RiderAPIHandlerInterface $rapi_handler) + { + $res = new APIResult(); + $data = $rapi_handler->payment($req); + + if (isset($data['error'])) + { + $message = $data['error']; + + $res->setError(true) + ->setErrorMessage($message); + } + else + { + $res->setData($data); + } + + // response return $res->getReturnResponse(); } - public function available(Request $req) + public function available(Request $req, RiderAPIHandlerInterface $rapi_handler) { - $em = $this->getDoctrine()->getManager(); - $required_params = []; - $res = $this->checkParamsAndKey($req, $em, $required_params); - if ($res->isError()) - return $res->getReturnResponse(); + $res = new APIResult(); - // make rider available - $this->session->getRider()->setAvailable(true); + $data = $rapi_handler->available($req); - // TODO: log rider available - $em->flush(); - - return $res->getReturnResponse(); - } - - public function getPromos(Request $req) - { - $em = $this->getDoctrine()->getManager(); - $required_params = []; - $res = $this->checkParamsAndKey($req, $em, $required_params); - if ($res->isError()) - return $res->getReturnResponse(); - - $promos = $em->getRepository(Promo::class)->findAll(); - - $promo_data = []; - foreach ($promos as $promo) + if (isset($data['error'])) { - $promo_data[] = [ - 'id' => $promo->getID(), - 'name' => $promo->getName(), - 'code' => $promo->getCode(), - ]; - } + $message = $data['error']; - $data = [ - 'promos' => $promo_data, - ]; - $res->setData($data); - return $res->getReturnResponse(); - } - - public function getBatteries(Request $req) - { - // get batteries, models, and sizes - $em = $this->getDoctrine()->getManager(); - $required_params = []; - $res = $this->checkParamsAndKey($req, $em, $required_params); - if ($res->isError()) - return $res->getReturnResponse(); - - $batts = $em->getRepository(Battery::class)->findAll(); - $models = $em->getRepository(BatteryModel::class)->findAll(); - $sizes = $em->getRepository(BatterySize::class)->findAll(); - - $batt_data = []; - foreach ($batts as $batt) - { - $batt_data[] = [ - 'id' => $batt->getID(), - 'model_id' => $batt->getModel()->getID(), - 'size_id' => $batt->getSize()->getID(), - 'sell_price' => $batt->getSellingPrice(), - ]; - } - - $model_data = []; - foreach ($models as $model) - { - $model_data[] = [ - 'id' => $model->getID(), - 'name' => $model->getName(), - ]; - } - - $size_data = []; - foreach ($sizes as $size) - { - $size_data[] = [ - 'id' => $size->getID(), - 'name' => $size->getName(), - ]; - } - - $data = [ - 'batteries' => $batt_data, - 'models' => $model_data, - 'sizes' => $size_data, - ]; - - $res->setData($data); - return $res->getReturnResponse(); - } - - protected function debugRequest(Request $req) - { - $all = $req->request->all(); - error_log(print_r($all, true)); - } - - public function changeService(Request $req, InvoiceCreator $ic) - { - $this->debugRequest($req); - - // allow rider to change service, promo, battery and trade-in options - $em = $this->getDoctrine()->getManager(); - $required_params = ['jo_id', 'stype_id', 'promo_id']; - $res = $this->checkJO($req, $required_params, $jo); - if ($res->isError()) - return $res->getReturnResponse(); - - // check service type - $stype_id = $req->request->get('stype_id'); - if (!ServiceType::validate($stype_id)) - { $res->setError(true) - ->setErrorMessage('Invalid service type - ' . $stype_id); - return $res->getReturnResponse(); + ->setErrorMessage($message); } - - // check promo id - $promo_id = $req->request->get('promo_id'); - // no promo - if ($promo_id == 0) - $promo = null; else { - $promo = $em->getRepository(Promo::class)->find($promo_id); - if ($promo == null) - { - $res->setError(true) - ->setErrorMessage('Invalid promo id - ' . $promo_id); - return $res->getReturnResponse(); - } + $res->setData($data); } - // check or number - $or_num = $req->request->get('or_num'); - if ($or_num != null) - $jo->setORNum($or_num); - - // coolant - $flag_coolant = $req->request->get('flag_coolant', 'false'); - if ($flag_coolant == 'true') - $jo->setHasCoolant(true); - else - $jo->setHasCoolant(false); - - // has motolite battery - $cv = $jo->getCustomerVehicle(); - $has_motolite = $req->request->get('has_motolite', 'false'); - if ($has_motolite == 'true') - $cv->setHasMotoliteBattery(true); - else - $cv->setHasMotoliteBattery(false); - $em->persist($cv); - - // check battery id - $batt_id = $req->request->get('batt_id', null); - // no battery - if ($batt_id == 0 || $batt_id == null) - $battery = null; - else - { - $battery = $em->getRepository(Battery::class)->find($batt_id); - if ($battery == null) - { - $res->setError(true) - ->setErrorMessage('Invalid battery id - ' . $batt_id); - return $res->getReturnResponse(); - } - } - - // check trade in - $trade_in = $req->request->get('trade_in'); - if (!TradeInType::validate($trade_in)) - $trade_in = null; - - // check mode of payment - $mode = $req->request->get('mode_of_payment'); - if (!ModeOfPayment::validate($mode)) - $mode = ModeOfPayment::CASH; - $jo->setModeOfPayment($mode); - - - // generate new invoice - $crit = new InvoiceCriteria(); - $crit->setServiceType($stype_id); - $crit->setCustomerVehicle($cv); - $crit->setHasCoolant($jo->hasCoolant()); - - if ($promo != null) - $crit->addPromo($promo); - - if ($battery != null) - { - $crit->addEntry($battery, $trade_in, 1); - error_log('adding entry for battery - ' . $battery->getID()); - } - - $invoice = $ic->processCriteria($crit); - $invoice->setStatus(InvoiceStatus::DRAFT); - - // remove previous invoice - $old_invoice = $jo->getInvoice(); - $em->remove($old_invoice); - $em->flush(); - - // save job order - $jo->setServiceType($stype_id); - - // save invoice - $jo->setInvoice($invoice); - $em->persist($invoice); - - // add event log - $rider = $this->session->getRider(); - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::RIDER_EDIT) - ->setJobOrder($jo) - ->setRider($rider); - $em->persist($event); - - $em->flush(); - // TODO: send mqtt event (?) - + // response return $res->getReturnResponse(); } - protected function updateVehicleBattery(JobOrder $jo) + public function getPromos(Request $req, RiderAPIHandlerInterface $rapi_handler) { - // check if new battery - if ($jo->getServiceType() != ServiceType::BATTERY_REPLACEMENT_NEW) - return; + $res = new APIResult(); - // customer vehicle - $cv = $jo->getCustomerVehicle(); - if ($cv == null) - return; + $data = $rapi_handler->getPromos($req); - // invoice - $invoice = $jo->getInvoice(); - if ($invoice == null) - return; - - // invoice items - $items = $invoice->getItems(); - if (count($items) <= 0) - return; - - // get first battery from invoice - $battery = null; - foreach ($items as $item) + if (isset($data['error'])) { - $battery = $item->getBattery(); - if ($battery != null) - break; + $message = $data['error']; + + $res->setError(true) + ->setErrorMessage($message); + } + else + { + $res->setData($data); } - // no battery in order - if ($battery == null) - return; + // response + return $res->getReturnResponse(); + } - // warranty expiration - $warr_months = 0; - $warr = $jo->getWarrantyClass(); - if ($warr == WarrantyClass::WTY_PRIVATE) - $warr_months = $battery->getWarrantyPrivate(); - else if ($warr == WarrantyClass::WTY_COMMERCIAL) - $warr_months = $battery->getWarrantyCommercial(); - else if ($warr == WarrantyClass::WTY_TNV) - $warr_months = 12; + public function getBatteries(Request $req, RiderAPIHandlerInterface $rapi_handler) + { + $res = new APIResult(); - $warr_date = new DateTime(); - $warr_date->add(new DateInterval('P' . $warr_months . 'M')); + $data = $rapi_handler->getBatteries($req); - // update customer vehicle battery - $cv->setCurrentBattery($battery) - ->setHasMotoliteBattery(true) - ->setWarrantyExpiration($warr_date); + if (isset($data['error'])) + { + $message = $data['error']; + + $res->setError(true) + ->setErrorMessage($message); + } + else + { + $res->setData($data); + } + + // response + return $res->getReturnResponse(); + } + + public function changeService(Request $req, RiderAPIHandlerInterface $rapi_handler) + { + $res = new APIResult(); + + $data = $rapi_handler->changeService($req); + + if (isset($data['error'])) + { + $message = $data['error']; + + $res->setError(true) + ->setErrorMessage($message); + } + else + { + $res->setData($data); + } + + // response + return $res->getReturnResponse(); } } diff --git a/src/Controller/RiderController.php b/src/Controller/RiderController.php index a62eebd1..838b558b 100644 --- a/src/Controller/RiderController.php +++ b/src/Controller/RiderController.php @@ -10,6 +10,7 @@ use App\Entity\User; use App\Service\FileUploader; use Doctrine\ORM\Query; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; @@ -500,4 +501,13 @@ class RiderController extends Controller ->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%'); } } + + public function popupInfo(EntityManagerInterface $em, $id) + { + $rider = $em->getRepository(Rider::class)->find($id); + if ($rider == null) + return new Response('No rider data'); + + return $this->render('rider/popup.html.twig', [ 'rider' => $rider ]); + } } diff --git a/src/Entity/Battery.php b/src/Entity/Battery.php index e05bff81..4c78fb08 100644 --- a/src/Entity/Battery.php +++ b/src/Entity/Battery.php @@ -60,14 +60,13 @@ class Battery // product code /** - * @ORM\Column(type="string", length=80) - * @Assert\NotBlank() + * @ORM\Column(type="string", length=80, nullable=true) */ protected $prod_code; // sap code /** - * @ORM\Column(type="string", length=80) + * @ORM\Column(type="string", length=80, nullable=true) */ protected $sap_code; diff --git a/src/Entity/Customer.php b/src/Entity/Customer.php index d9dd9b98..de60e772 100644 --- a/src/Entity/Customer.php +++ b/src/Entity/Customer.php @@ -165,6 +165,21 @@ class Customer */ protected $privpol_promo; + /** + * @ORM\Column(type="boolean", options={"default":false}) + */ + protected $flag_promo_email; + + /** + * @ORM\Column(type="boolean", options={"default":false}) + */ + protected $flag_promo_sms; + + /** + * @ORM\Column(type="boolean", options={"default":false}) + */ + protected $flag_dpa_consent; + public function __construct() { $this->numbers = new ArrayCollection(); @@ -191,6 +206,10 @@ class Customer $this->priv_promo = 0; $this->flag_csat = false; + + $this->flag_promo_email = false; + $this->flag_promo_sms = false; + $this->flag_dpa_consent = false; } public function getID() @@ -231,6 +250,11 @@ class Customer return $this->last_name; } + public function getNameDisplay() + { + return $this->first_name . ' ' . $this->last_name; + } + public function setCustomerClassification($customer_classification) { $this->customer_classification = $customer_classification; @@ -485,5 +509,36 @@ class Customer return $this->privpol_promo; } + public function setPromoEmail($flag_promo_email = true) + { + $this->flag_promo_email = $flag_promo_email; + return $this; + } + public function isPromoEmail() + { + return $this->flag_promo_email; + } + + public function setPromoSms($flag_promo_sms = true) + { + $this->flag_promo_sms = $flag_promo_sms; + return $this; + } + + public function isPromoSms() + { + return $this->flag_promo_sms; + } + + public function setDpaConsent($flag_dpa_consent = true) + { + $this->flag_dpa_consent = $flag_dpa_consent; + return $this; + } + + public function isDpaConsent() + { + return $this->flag_dpa_consent; + } } diff --git a/src/Entity/CustomerVehicle.php b/src/Entity/CustomerVehicle.php index 3e4c70a4..a5525a38 100644 --- a/src/Entity/CustomerVehicle.php +++ b/src/Entity/CustomerVehicle.php @@ -67,14 +67,12 @@ class CustomerVehicle // vehicle status (new / second-hand) /** * @ORM\Column(type="string", length=15) - * @Assert\NotBlank() */ protected $status_condition; // fuel type - diesel, gas /** * @ORM\Column(type="string", length=15) - * @Assert\NotBlank() */ protected $fuel_type; diff --git a/src/Entity/Rider.php b/src/Entity/Rider.php index 0f5a3116..f9331c03 100644 --- a/src/Entity/Rider.php +++ b/src/Entity/Rider.php @@ -319,4 +319,10 @@ class Rider { return $this->sessions; } + + public function getMapLabel() + { + $map_label = $this->first_name .' ' . $this->last_name; + return $map_label; + } } diff --git a/src/EventListener/JobOrderActiveCacheListener.php b/src/EventListener/JobOrderActiveCacheListener.php new file mode 100644 index 00000000..541ce158 --- /dev/null +++ b/src/EventListener/JobOrderActiveCacheListener.php @@ -0,0 +1,105 @@ +jo_cache = $jo_cache; + $this->mqtt = $mqtt; + } + + // when a new job order comes in + public function postPersist(JobOrder $jo, LifecycleEventArgs $args) + { + $status = $jo->getStatus(); + + switch ($status) + { + // active + case JOStatus::PENDING: + case JOStatus::RIDER_ASSIGN: + case JOStatus::ASSIGNED: + case JOStatus::IN_TRANSIT: + case JOStatus::IN_PROGRESS: + $this->processActiveJO($jo); + break; + // inactive + case JOStatus::CANCELLED: + $this->processInactiveJO($jo, 'cancel'); + break; + case JOStatus::FULFILLED: + $this->processInactiveJO($jo, 'fulfill'); + break; + } + } + + // when a job order is updated + public function postUpdate(JobOrder $jo, LifecycleEventArgs $args) + { + $status = $jo->getStatus(); + + switch ($status) + { + // active + case JOStatus::PENDING: + case JOStatus::RIDER_ASSIGN: + case JOStatus::ASSIGNED: + case JOStatus::IN_TRANSIT: + case JOStatus::IN_PROGRESS: + $this->processActiveJO($jo); + break; + // inactive + case JOStatus::CANCELLED: + $this->processInactiveJO($jo, 'cancel'); + break; + case JOStatus::FULFILLED: + $this->processInactiveJO($jo, 'fulfill'); + break; + } + } + + // when a job order is deleted + public function postRemove(JobOrder $jo, LifecycleEventArgs $args) + { + $this->processInactiveJO($jo, 'delete'); + } + + protected function processActiveJO($jo) + { + // save in cache + $this->jo_cache->addActiveJobOrder($jo); + + // publish to mqtt + $coords = $jo->getCoordinates(); + + // TODO: do we put the key in config? + $this->mqtt->publish( + 'jo/' . $jo->getID() . '/location', + $coords->getLatitude() . ':' . $coords->getLongitude() + ); + } + + protected function processInactiveJO($jo, $status = 'cancel') + { + // remove from redis cache + $this->jo_cache->removeActiveJobOrder($jo); + + // TODO: publich to mqtt + $this->mqtt->publish( + 'jo/' . $jo->getID() . '/status', + $status + ); + } +} + diff --git a/src/Ramcar/CMBModeOfPayment.php b/src/Ramcar/CMBModeOfPayment.php new file mode 100644 index 00000000..c7ae9908 --- /dev/null +++ b/src/Ramcar/CMBModeOfPayment.php @@ -0,0 +1,16 @@ + 'Cash', + 'credit_card' => 'Credit Card', + 'bank_transfer' => 'Bank Transfer', + ]; +} diff --git a/src/Ramcar/CMBServiceType.php b/src/Ramcar/CMBServiceType.php new file mode 100644 index 00000000..63d18f96 --- /dev/null +++ b/src/Ramcar/CMBServiceType.php @@ -0,0 +1,16 @@ + 'Battery Sales', + 'battery_warranty' => 'Under Warranty', + 'jumpstart' => 'Jumpstart', + ]; +} diff --git a/src/Ramcar/CMBTradeInType.php b/src/Ramcar/CMBTradeInType.php new file mode 100644 index 00000000..8813f441 --- /dev/null +++ b/src/Ramcar/CMBTradeInType.php @@ -0,0 +1,13 @@ + 'Yes', + ]; +} + diff --git a/src/Ramcar/CMBWarrantyClass.php b/src/Ramcar/CMBWarrantyClass.php new file mode 100644 index 00000000..1e2c69b4 --- /dev/null +++ b/src/Ramcar/CMBWarrantyClass.php @@ -0,0 +1,15 @@ + 'Passenger', + 'commercial' => 'Commercial', + ]; + +} diff --git a/src/Ramcar/InvoiceCriteria.php b/src/Ramcar/InvoiceCriteria.php index 684c5883..5ea5e623 100644 --- a/src/Ramcar/InvoiceCriteria.php +++ b/src/Ramcar/InvoiceCriteria.php @@ -104,7 +104,7 @@ class InvoiceCriteria return $this->entries; } - public function setCustomerVehicle(CustomerVehicle $cv) + public function setCustomerVehicle(CustomerVehicle $cv = null) { $this->cv = $cv; return $this; diff --git a/src/Service/CustomerHandler/CMBCustomerHandler.php b/src/Service/CustomerHandler/CMBCustomerHandler.php new file mode 100644 index 00000000..d8107514 --- /dev/null +++ b/src/Service/CustomerHandler/CMBCustomerHandler.php @@ -0,0 +1,699 @@ +em = $em; + $this->validator = $validator; + $this->country_code = $country_code; + + $this->loadTemplates(); + } + + // initialize form to display customer list + public function initializeCustomerIndexForm() + { + $params['template'] = $this->getTwigTemplate('cust_list'); + + return $params; + } + + // get customers + public function getCustomers(Request $req) + { + // build query + $tqb = $this->em->getRepository(Customer::class) + ->createQueryBuilder('q'); + + $qb = $this->em->getRepository(Customer::class) + ->createQueryBuilder('q'); + + // get datatable params + $datatable = $req->request->get('datatable'); + + // count total records + $tquery = $tqb->select('COUNT(q)'); + + // add filters to count query + $this->setQueryFilters($datatable, $tquery); + + $total = $tquery->getQuery() + ->getSingleScalarResult(); + + // get current page number + $page = $datatable['pagination']['page'] ?? 1; + + $perpage = $datatable['pagination']['perpage']; + $offset = ($page - 1) * $perpage; + + // add metadata + $meta = [ + 'page' => $page, + 'perpage' => $perpage, + 'pages' => ceil($total / $perpage), + 'total' => $total, + 'sort' => 'asc', + 'field' => 'id' + ]; + + // build query + $query = $qb->select('q'); + + // add filters to query + $this->setQueryFilters($datatable, $query); + + // check if sorting is present, otherwise use default + if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) { + $order = $datatable['sort']['sort'] ?? 'asc'; + $query->orderBy('q.' . $datatable['sort']['field'], $order); + } else { + $query->orderBy('q.first_name', 'asc'); + } + + // get rows for this page + $obj_rows = $query->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery() + ->getResult(); + + // process rows + $rows = []; + foreach ($obj_rows as $orow) { + // add row data + $row['id'] = $orow->getID(); + $row['title'] = $orow->getTitle(); + $row['first_name'] = $orow->getFirstName(); + $row['last_name'] = $orow->getLastName(); + $row['customer_classification'] = CustomerClassification::getName($orow->getCustomerClassification()); + $row['flag_mobile_app'] = $orow->hasMobileApp(); + $row['app_mobile_number'] = $orow->hasMobileApp() && !empty($orow->getMobileSessions()) ? $orow->getMobileSessions()[0]->getPhoneNumber() : ''; + $row['flag_active'] = $orow->isActive(); + $row['flag_csat'] = $orow->isCSAT(); + + // TODO: properly add mobile numbers and plate numbers as searchable/sortable fields, use doctrine events + // prepend the country code before each mobile number + $mobile_number_list = []; + $mobile_numbers = $orow->getMobileNumberList(); + foreach ($mobile_numbers as $mobile_number) + { + $mobile_number_list[] = $this->country_code . $mobile_number; + } + $row['mobile_numbers'] = implode("
", $mobile_number_list); + $row['plate_numbers'] = implode("
", $orow->getPlateNumberList()); + + $rows[] = $row; + } + + $params['meta'] = $meta; + $params['rows'] = $rows; + + return $params; + } + + // initialize add customer form + public function initializeAddCustomerForm() + { + $params['obj'] = new Customer(); + $params['mode'] = 'create'; + + // get dropdown parameters + $this->fillDropdownParameters($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('cust_add_form'); + + // return params + return $params; + } + + // add new customer and customer vehicle, if any + public function addCustomer(Request $req) + { + // create new row + $em = $this->em; + $row = new Customer(); + + $this->setObject($row, $req); + + // initialize error lists + $error_array = []; + $nerror_array = []; + $verror_array = []; + + // custom validation for vehicles + $vehicles = json_decode($req->request->get('vehicles')); + + if (!empty($vehicles)) + { + foreach ($vehicles as $vehicle) + { + // check if vehicle exists + $vobj = $em->getRepository(Vehicle::class)->find($vehicle->vehicle); + + if (empty($vobj)) + { + $verror_array[$vehicle->index]['vehicle'] = 'Invalid vehicle specified.'; + } + else + { + $cust_vehicle = new CustomerVehicle(); + $cust_vehicle->setName($vehicle->name) + ->setVehicle($vobj) + ->setPlateNumber($vehicle->plate_number) + ->setModelYear($vehicle->model_year) + ->setColor('') + ->setStatusCondition('') + ->setFuelType('') + ->setActive($vehicle->flag_active) + ->setCustomer($row); + + // if specified, check if battery exists + if ($vehicle->battery) + { + // check if battery exists + $bobj = $em->getRepository(Battery::class)->find($vehicle->battery); + + if (empty($bobj)) + { + $verror_array[$vehicle->index]['battery'] = 'Invalid battery specified.'; + } + else + { + // check if warranty expiration was specified + $warr_ex = DateTime::createFromFormat("d M Y", $vehicle->warranty_expiration); + if (!$warr_ex) + $warr_ex = null; + + $cust_vehicle->setHasMotoliteBattery(true) + ->setCurrentBattery($bobj) + ->setWarrantyCode($vehicle->warranty_code) + ->setWarrantyExpiration($warr_ex); + } + } + else + { + $cust_vehicle->setHasMotoliteBattery(false); + } + + $verrors = $this->validator->validate($cust_vehicle); + + // add errors to list + foreach ($verrors as $error) + { + if (!isset($verror_array[$vehicle->index])) + $verror_array[$vehicle->index] = []; + + $verror_array[$vehicle->index][$error->getPropertyPath()] = $error->getMessage(); + } + + // add to entity + if (!isset($verror_array[$vehicle->index])) + { + $row->addVehicle($cust_vehicle); + } + } + } + } + + // validate + $errors = $this->validator->validate($row); + + // add errors to list + foreach ($errors as $error) + { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + $result = []; + // check if any errors were found + if (!empty($error_array) || !empty($nerror_array) || !empty($verror_array)) + { + // return all error_arrays + $result = [ + 'error_array' => $error_array, + 'nerror_array' => $nerror_array, + 'verror_array' => $verror_array, + ]; + } + else + { + // validated! save the entity + $em->persist($row); + $em->flush(); + + $result = [ + 'id' => $row->getID(), + ]; + } + + return $result; + } + + // initialize update customer form + public function initializeUpdateCustomerForm($id) + { + $params['mode'] = 'update'; + + // get row data + $em = $this->em; + $row = $em->getRepository(Customer::class)->find($id); + + // make sure this row exists + if (empty($row)) + throw new NotFoundHttpException('The item does not exist'); + + // get dropdown parameters + $this->fillDropdownParameters($params); + // get template to display + $params['template'] = $this->getTwigTemplate('cust_update_form'); + + $params['obj'] = $row; + + return $params; + } + + // update customer and customer vehicle + public function updateCustomer(Request $req, $id) + { + // get row data + $em = $this->em; + $cust = $em->getRepository(Customer::class)->find($id); + + // make sure this row exists + if (empty($cust)) + throw $this->createNotFoundException('The item does not exist'); + + $this->setObject($cust, $req); + + // initialize error lists + $error_array = []; + $nerror_array = []; + $verror_array = []; + + // TODO: validate mobile numbers + // TODO: validate vehicles + + // custom validation for vehicles + $vehicles = json_decode($req->request->get('vehicles')); + try + { + $this->updateVehicles($em, $cust, $vehicles); + } + catch (CrudException $e) + { + throw new CrudException($e->getMessage()); + } + + // validate + $errors = $this->validator->validate($cust); + + // add errors to list + foreach ($errors as $error) + { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if any errors were found + $result = []; + if (!empty($error_array) || !empty($nerror_array) || !empty($verror_array)) + { + // return all error_arrays + $result = [ + 'error_array' => $error_array, + 'nerror_array' => $nerror_array, + 'verror_array' => $verror_array, + ]; + + } + else + { + // validated! save the entity. do a persist anyway to save child entities + $em->persist($cust); + $em->flush(); + + $result = [ + 'id' => $cust->getID(), + ]; + } + + return $result; + } + + // delete customer + public function deleteCustomer(int $id) + { + // get row data + $em = $this->em; + $row = $em->getRepository(Customer::class)->find($id); + + if (empty($row)) + throw new NotFoundHttpException('The item does not exist'); + + // delete this row + $em->remove($row); + $em->flush(); + } + + // get customer vehicles + public function getCustomerVehicles(Request $req) + { + // get search term + $term = $req->query->get('search'); + + // get querybuilder + $qb = $this->em + ->getRepository(CustomerVehicle::class) + ->createQueryBuilder('q'); + + /* + // build expression now since we're reusing it + $vehicle_label = $qb->expr()->concat( + 'q.plate_number', + $qb->expr()->literal(' - '), + 'c.first_name', + $qb->expr()->literal(' '), + 'c.last_name', + $qb->expr()->literal(' (+63'), + 'c.phone_mobile', + $qb->expr()->literal(')') + ); + */ + + // count total records + $tquery = $qb->select('COUNT(q)'); + + // add filters to count query + if (!empty($term)) { + $tquery->where('q.plate_number like :search') + ->setParameter('search', $term . '%'); + /* + $tquery->where('match_against (q.plate_number, :search \'in boolean mode\') > 0.1') + ->setParameter('search', $term . '*'); + */ + /* + $tquery->where('q.plate_number LIKE :filter') + ->setParameter('filter', '%' . $term . '%'); + */ + } + + $total = $tquery->getQuery() + ->getSingleScalarResult(); + + // pagination vars + $page = $req->query->get('page') ?? 1; + $perpage = 20; + $offset = ($page - 1) * $perpage; + $pages = ceil($total / $perpage); + $has_more_pages = $page < $pages ? true : false; + + // build main query + $query = $qb->select('q'); + /* + ->addSelect($vehicle_label . ' as vehicle_label') + ->addSelect('c.first_name as cust_first_name') + ->addSelect('c.last_name as cust_last_name'); + */ + + // add filters if needed + if (!empty($term)) { + $query->where('q.plate_number like :search') + ->setParameter('search', $term . '%'); + /* + $query->where('match_against (q.plate_number, :search \'in boolean mode\') > 0.1') + ->setParameter('search', $term . '*'); + */ + /* + $query->where('q.plate_number LIKE :filter') + ->setParameter('filter', '%' . $term . '%'); + */ + } + + // get rows + $query_obj = $query->orderBy('q.plate_number', 'asc') + ->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery(); + // error_log($query_obj->getSql()); + + $obj_rows = $query_obj->getResult(); + + // build vehicles array + $vehicles = []; + + foreach ($obj_rows as $cv) { + $cust = $cv->getCustomer(); + $vehicles[] = [ + 'id' => $cv->getID(), + 'text' => $cv->getPlateNumber() . ' ' . $cust->getFirstName() . ' ' . $cust->getLastName() . ' ('. $this->country_code . $cust->getPhoneMobile() . ')', + ]; + } + + $results = [ + 'vehicles' => $vehicles, + 'has_more_pages' => $has_more_pages, + ]; + + return $results; + } + + // get customer vehicle info + public function getCustomerVehicleInfo(Request $req) + { + // get id + $id = $req->query->get('id'); + + // get row data + $em = $this->em; + $obj = $em->getRepository(CustomerVehicle::class)->find($id); + + // make sure this row exists + if (empty($obj)) { + return null; + } + + $customer = $obj->getCustomer(); + $vehicle = $obj->getVehicle(); + $battery = $obj->getCurrentBattery(); + + // build response + $row = [ + 'customer' => [ + 'id' => $customer->getID(), + 'first_name' => $customer->getFirstName(), + 'last_name' => $customer->getLastName(), + 'customer_notes' => $customer->getCustomerNotes(), + 'phone_mobile' => $customer->getPhoneMobile(), + 'phone_landline' => $customer->getPhoneLandline(), + 'phone_office' => $customer->getPhoneOffice(), + 'phone_fax' => $customer->getPhoneFax(), + ], + 'vehicle' => [ + 'id' => $vehicle->getID(), + 'mfg_id' => $vehicle->getManufacturer()->getID(), + 'mfg_name' => $vehicle->getManufacturer()->getName(), + 'make' => $vehicle->getMake(), + 'model_year_from' => $vehicle->getModelYearFrom(), + 'model_year_to' => $vehicle->getModelYearTo(), + 'model_year' => $obj->getModelYear(), + 'color' => $obj->getColor(), + 'plate_number' => $obj->getPlateNumber(), + ] + ]; + + if (!empty($battery)) { + $row['battery'] = [ + 'id' => $battery->getID(), + 'mfg_name' => $battery->getManufacturer()->getName(), + 'model_name' => $battery->getModel()->getName(), + 'size_name' => $battery->getSize()->getName(), + 'prod_code' => $battery->getProductCode(), + 'warranty_code' => $obj->getWarrantyCode(), + 'warranty_expiration' => $obj->getWarrantyExpiration() ? $obj->getWarrantyExpiration()->format("d M Y") : "", + 'has_motolite_battery' => $obj->hasMotoliteBattery(), + 'is_active' => $obj->isActive() + ]; + } + + return $row; + } + + protected function getTwigTemplate($id) + { + if (isset($this->template_hash[$id])) + { + return $this->template_hash[$id]; + } + + return null; + } + + protected function setObject($obj, $req) + { + // set and save values + $obj->setTitle($req->request->get('title')) + ->setFirstName($req->request->get('first_name')) + ->setLastName($req->request->get('last_name')) + ->setCustomerClassification($req->request->get('customer_classification')) + ->setCustomerNotes($req->request->get('customer_notes')) + ->setEmail($req->request->get('email')) + ->setIsCSAT($req->request->get('flag_csat') ? true : false) + ->setActive($req->request->get('flag_active') ? true : false); + + // phone numbers + $obj->setPhoneMobile($req->request->get('phone_mobile')) + ->setPhoneLandline($req->request->get('phone_landline')) + ->setPhoneOffice($req->request->get('phone_office')) + ->setPhoneFax($req->request->get('phone_fax')); + } + + protected function fillDropdownParameters(&$params) + { + $em = $this->em; + + $params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); + $params['vmfgs'] = $em->getRepository(VehicleManufacturer::class)->findAll(); + + $params['classifications'] = CustomerClassification::getCollection(); + + $params['years'] = $this->generateYearOptions(); + $params['batteries'] = $em->getRepository(Battery::class)->findAll(); + } + + protected function generateYearOptions() + { + $start_year = 1950; + return range($start_year, date("Y") + 1); + } + + + protected function loadTemplates() + { + $this->template_hash = []; + + // add all twig templates for customer to hash + $this->template_hash['cust_add_form'] = 'customer/cmb.form.html.twig'; + $this->template_hash['cust_update_form'] = 'customer/cmb.form.html.twig'; + $this->template_hash['cust_list'] = 'customer/list.html.twig'; + } + + protected function updateVehicles($em, Customer $cust, $vehicles) + { + $vehicle_ids = []; + + foreach ($vehicles as $vehicle) + { + // check if customer vehicle exists + if (!empty($vehicle->id)) + { + $cust_vehicle = $em->getRepository(CustomerVehicle::class)->find($vehicle->id); + if ($cust_vehicle == null) + throw new CrudException("Could not find customer vehicle."); + + } + // this is a new vehicle + else + { + $cust_vehicle = new CustomerVehicle(); + $cust_vehicle->setCustomer($cust); + $cust->addVehicle($cust_vehicle); + $em->persist($cust_vehicle); + } + + // vehicle, because they could have changed vehicle type + $vobj = $em->getRepository(Vehicle::class)->find($vehicle->vehicle); + if ($vobj == null) + throw new CrudException("Could not find vehicle."); + + // TODO: validate details + + $cust_vehicle->setName($vehicle->name) + ->setVehicle($vobj) + ->setPlateNumber($vehicle->plate_number) + ->setModelYear($vehicle->model_year) + ->setColor('') + ->setStatusCondition('') + ->setFuelType('') + ->setActive($vehicle->flag_active); + + // if specified, check if battery exists + if ($vehicle->battery) + { + // check if battery exists + $bobj = $em->getRepository(Battery::class)->find($vehicle->battery); + if ($bobj == null) + throw new CrudException("Could not find battery."); + + // check if warranty expiration was specified + $warr_ex = DateTime::createFromFormat("d M Y", $vehicle->warranty_expiration); + if (!$warr_ex) + $warr_ex = null; + + $cust_vehicle->setHasMotoliteBattery(true) + ->setCurrentBattery($bobj) + ->setWarrantyCode($vehicle->warranty_code) + ->setWarrantyExpiration($warr_ex); + } + else + { + $cust_vehicle->setHasMotoliteBattery(false); + } + + // add to list of vehicles to keep + $vehicle_ids[$cust_vehicle->getID()] = true; + } + + // cleanup + // delete all vehicles not in list + $cvs = $cust->getVehicles(); + foreach ($cvs as $cv) + { + if (!isset($vehicle_ids[$cv->getID()])) + { + $cust->removeVehicle($cv); + $em->remove($cv); + } + } + } + + // check if datatable filter is present and append to query + protected function setQueryFilters($datatable, &$query) { + if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) { + $query->join('q.vehicles', 'cv') + ->where('q.first_name LIKE :filter') + ->orWhere('q.last_name LIKE :filter') + ->orWhere('q.customer_classification LIKE :filter') + ->orWhere('cv.plate_number LIKE :filter') + ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); + } + } +} diff --git a/src/Service/CustomerHandler/ResqCustomerHandler.php b/src/Service/CustomerHandler/ResqCustomerHandler.php new file mode 100644 index 00000000..e27399e3 --- /dev/null +++ b/src/Service/CustomerHandler/ResqCustomerHandler.php @@ -0,0 +1,707 @@ +em = $em; + $this->validator = $validator; + $this->country_code = $country_code; + + $this->loadTemplates(); + } + + // initialize form to display customer list + public function initializeCustomerIndexForm() + { + $params['template'] = $this->getTwigTemplate('cust_list'); + + return $params; + } + + // get customers + public function getCustomers(Request $req) + { + // build query + $tqb = $this->em->getRepository(Customer::class) + ->createQueryBuilder('q'); + + $qb = $this->em->getRepository(Customer::class) + ->createQueryBuilder('q'); + + // get datatable params + $datatable = $req->request->get('datatable'); + + // count total records + $tquery = $tqb->select('COUNT(q)'); + + // add filters to count query + $this->setQueryFilters($datatable, $tquery); + + $total = $tquery->getQuery() + ->getSingleScalarResult(); + + // get current page number + $page = $datatable['pagination']['page'] ?? 1; + + $perpage = $datatable['pagination']['perpage']; + $offset = ($page - 1) * $perpage; + + // add metadata + $meta = [ + 'page' => $page, + 'perpage' => $perpage, + 'pages' => ceil($total / $perpage), + 'total' => $total, + 'sort' => 'asc', + 'field' => 'id' + ]; + + // build query + $query = $qb->select('q'); + + // add filters to query + $this->setQueryFilters($datatable, $query); + + // check if sorting is present, otherwise use default + if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) { + $order = $datatable['sort']['sort'] ?? 'asc'; + $query->orderBy('q.' . $datatable['sort']['field'], $order); + } else { + $query->orderBy('q.first_name', 'asc'); + } + + // get rows for this page + $obj_rows = $query->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery() + ->getResult(); + + + // process rows + $rows = []; + foreach ($obj_rows as $orow) { + // add row data + $row['id'] = $orow->getID(); + $row['title'] = $orow->getTitle(); + $row['first_name'] = $orow->getFirstName(); + $row['last_name'] = $orow->getLastName(); + $row['customer_classification'] = CustomerClassification::getName($orow->getCustomerClassification()); + $row['flag_mobile_app'] = $orow->hasMobileApp(); + $row['app_mobile_number'] = $orow->hasMobileApp() && !empty($orow->getMobileSessions()) ? $orow->getMobileSessions()[0]->getPhoneNumber() : ''; + $row['flag_active'] = $orow->isActive(); + $row['flag_csat'] = $orow->isCSAT(); + + // TODO: properly add mobile numbers and plate numbers as searchable/sortable fields, use doctrine events + // prepend the country code before each mobile number + $mobile_number_list = []; + $mobile_numbers = $orow->getMobileNumberList(); + foreach ($mobile_numbers as $mobile_number) + { + $mobile_number_list[] = $this->country_code . $mobile_number; + } + $row['mobile_numbers'] = implode("
", $mobile_number_list); + $row['plate_numbers'] = implode("
", $orow->getPlateNumberList()); + + $rows[] = $row; + } + + $params['meta'] = $meta; + $params['rows'] = $rows; + + return $params; + } + + // initialize add customer form + public function initializeAddCustomerForm() + { + $params['obj'] = new Customer(); + $params['mode'] = 'create'; + + // get dropdown parameters + $this->fillDropdownParameters($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('cust_add_form'); + + // return params + return $params; + } + + + // add new customer and customer vehicle, if any + public function addCustomer(Request $req) + { + // create new row + $em = $this->em; + $row = new Customer(); + + $this->setObject($row, $req); + + // initialize error lists + $error_array = []; + $nerror_array = []; + $verror_array = []; + + // custom validation for vehicles + $vehicles = json_decode($req->request->get('vehicles')); + + if (!empty($vehicles)) + { + foreach ($vehicles as $vehicle) + { + // check if vehicle exists + $vobj = $em->getRepository(Vehicle::class)->find($vehicle->vehicle); + + if (empty($vobj)) + { + $verror_array[$vehicle->index]['vehicle'] = 'Invalid vehicle specified.'; + } + else + { + $cust_vehicle = new CustomerVehicle(); + $cust_vehicle->setName($vehicle->name) + ->setVehicle($vobj) + ->setPlateNumber($vehicle->plate_number) + ->setModelYear($vehicle->model_year) + ->setColor($vehicle->color) + ->setStatusCondition($vehicle->status_condition) + ->setFuelType($vehicle->fuel_type) + ->setActive($vehicle->flag_active) + ->setCustomer($row); + + // if specified, check if battery exists + if ($vehicle->battery) + { + // check if battery exists + $bobj = $em->getRepository(Battery::class)->find($vehicle->battery); + + if (empty($bobj)) + { + $verror_array[$vehicle->index]['battery'] = 'Invalid battery specified.'; + } + else + { + // check if warranty expiration was specified + $warr_ex = DateTime::createFromFormat("d M Y", $vehicle->warranty_expiration); + if (!$warr_ex) + $warr_ex = null; + + $cust_vehicle->setHasMotoliteBattery(true) + ->setCurrentBattery($bobj) + ->setWarrantyCode($vehicle->warranty_code) + ->setWarrantyExpiration($warr_ex); + } + } + else + { + $cust_vehicle->setHasMotoliteBattery(false); + } + + $verrors = $this->validator->validate($cust_vehicle); + + // add errors to list + foreach ($verrors as $error) + { + if (!isset($verror_array[$vehicle->index])) + $verror_array[$vehicle->index] = []; + + $verror_array[$vehicle->index][$error->getPropertyPath()] = $error->getMessage(); + } + + // add to entity + if (!isset($verror_array[$vehicle->index])) + { + $row->addVehicle($cust_vehicle); + } + } + } + } + + // validate + $errors = $this->validator->validate($row); + + // add errors to list + foreach ($errors as $error) + { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if any errors were found + $result = []; + if (!empty($error_array) || !empty($nerror_array) || !empty($verror_array)) + { + // return all error_arrays + $result = [ + 'error_array' => $error_array, + 'nerror_array' => $nerror_array, + 'verror_array' => $verror_array, + ]; + } + else + { + // validated! save the entity + $em->persist($row); + $em->flush(); + + $result = [ + 'id' => $row->getID(), + ]; + } + + return $result; + } + + // initialize update customer form + public function initializeUpdateCustomerForm($id) + { + $params['mode'] = 'update'; + + // get row data + $em = $this->em; + $row = $em->getRepository(Customer::class)->find($id); + + // make sure this row exists + if (empty($row)) + throw new NotFoundHttpException('The item does not exist'); + + // get dropdown parameters + $this->fillDropdownParameters($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('cust_update_form'); + + $params['obj'] = $row; + + return $params; + } + + // update customer and customer vehicle + public function updateCustomer(Request $req, $id) + { + // get row data + $em = $this->em; + $cust = $em->getRepository(Customer::class)->find($id); + + // make sure this row exists + if (empty($cust)) + throw $this->createNotFoundException('The item does not exist'); + + $this->setObject($cust, $req); + + // initialize error lists + $error_array = []; + $nerror_array = []; + $verror_array = []; + + // TODO: validate mobile numbers + // TODO: validate vehicles + + // custom validation for vehicles + $vehicles = json_decode($req->request->get('vehicles')); + $this->updateVehicles($em, $cust, $vehicles); + + // validate + $errors = $this->validator->validate($cust); + + // add errors to list + foreach ($errors as $error) + { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if any errors were found + $result = []; + if (!empty($error_array) || !empty($nerror_array) || !empty($verror_array)) + { + // return all error_arrays + $result = [ + 'error_array' => $error_array, + 'nerror_array' => $nerror_array, + 'verror_array' => $verror_array, + ]; + + } + else + { + // validated! save the entity. do a persist anyway to save child entities + $em->persist($cust); + $em->flush(); + + $result = [ + 'id' => $cust->getID(), + ]; + } + + return $result; + } + + // delete customer + public function deleteCustomer(int $id) + { + // get row data + $em = $this->em; + $row = $em->getRepository(Customer::class)->find($id); + + if (empty($row)) + throw new NotFoundHttpException('The item does not exist'); + + // delete this row + $em->remove($row); + $em->flush(); + } + + // get customer vehicles + public function getCustomerVehicles(Request $req) + { + // get search term + $term = $req->query->get('search'); + + // get querybuilder + $qb = $this->em + ->getRepository(CustomerVehicle::class) + ->createQueryBuilder('q'); + + /* + // build expression now since we're reusing it + $vehicle_label = $qb->expr()->concat( + 'q.plate_number', + $qb->expr()->literal(' - '), + 'c.first_name', + $qb->expr()->literal(' '), + 'c.last_name', + $qb->expr()->literal(' (+63'), + 'c.phone_mobile', + $qb->expr()->literal(')') + ); + */ + + // count total records + $tquery = $qb->select('COUNT(q)'); + + // add filters to count query + if (!empty($term)) { + $tquery->where('q.plate_number like :search') + ->setParameter('search', $term . '%'); + /* + $tquery->where('match_against (q.plate_number, :search \'in boolean mode\') > 0.1') + ->setParameter('search', $term . '*'); + */ + /* + $tquery->where('q.plate_number LIKE :filter') + ->setParameter('filter', '%' . $term . '%'); + */ + } + + $total = $tquery->getQuery() + ->getSingleScalarResult(); + + // pagination vars + $page = $req->query->get('page') ?? 1; + $perpage = 20; + $offset = ($page - 1) * $perpage; + $pages = ceil($total / $perpage); + $has_more_pages = $page < $pages ? true : false; + + // build main query + $query = $qb->select('q'); + /* + ->addSelect($vehicle_label . ' as vehicle_label') + ->addSelect('c.first_name as cust_first_name') + ->addSelect('c.last_name as cust_last_name'); + */ + + // add filters if needed + if (!empty($term)) { + $query->where('q.plate_number like :search') + ->setParameter('search', $term . '%'); + /* + $query->where('match_against (q.plate_number, :search \'in boolean mode\') > 0.1') + ->setParameter('search', $term . '*'); + */ + /* + $query->where('q.plate_number LIKE :filter') + ->setParameter('filter', '%' . $term . '%'); + */ + } + + // get rows + $query_obj = $query->orderBy('q.plate_number', 'asc') + ->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery(); + // error_log($query_obj->getSql()); + + $obj_rows = $query_obj->getResult(); + + // build vehicles array + $vehicles = []; + + foreach ($obj_rows as $cv) { + $cust = $cv->getCustomer(); + $vehicles[] = [ + 'id' => $cv->getID(), + 'text' => $cv->getPlateNumber() . ' ' . $cust->getFirstName() . ' ' . $cust->getLastName() . ' ('. $this->country_code . $cust->getPhoneMobile() . ')', + ]; + } + + $results = [ + 'vehicles' => $vehicles, + 'has_more_pages' => $has_more_pages, + ]; + + return $results; + } + + // get customer vehicle info + public function getCustomerVehicleInfo(Request $req) + { + // get id + $id = $req->query->get('id'); + + // get row data + $em = $this->em; + $obj = $em->getRepository(CustomerVehicle::class)->find($id); + + // make sure this row exists + if (empty($obj)) { + return null; + } + + $customer = $obj->getCustomer(); + $vehicle = $obj->getVehicle(); + $battery = $obj->getCurrentBattery(); + + // build response + $row = [ + 'customer' => [ + 'id' => $customer->getID(), + 'first_name' => $customer->getFirstName(), + 'last_name' => $customer->getLastName(), + 'customer_notes' => $customer->getCustomerNotes(), + 'phone_mobile' => $customer->getPhoneMobile(), + 'phone_landline' => $customer->getPhoneLandline(), + 'phone_office' => $customer->getPhoneOffice(), + 'phone_fax' => $customer->getPhoneFax(), + 'email' => $customer->getEmail(), + 'flag_dpa_consent' => $customer->isDpaConsent(), + 'flag_promo_sms' => $customer->isPromoSms(), + 'flag_promo_email' => $customer->isPromoEmail(), + ], + 'vehicle' => [ + 'id' => $vehicle->getID(), + 'mfg_name' => $vehicle->getManufacturer()->getName(), + 'make' => $vehicle->getMake(), + 'model_year_from' => $vehicle->getModelYearFrom(), + 'model_year_to' => $vehicle->getModelYearTo(), + 'model_year' => $obj->getModelYear(), + 'color' => $obj->getColor(), + 'plate_number' => $obj->getPlateNumber(), + 'fuel_type' => $obj->getFuelType(), + 'status_condition' => $obj->getStatusCondition(), + ] + ]; + + if (!empty($battery)) { + $row['battery'] = [ + 'id' => $battery->getID(), + 'mfg_name' => $battery->getManufacturer()->getName(), + 'model_name' => $battery->getModel()->getName(), + 'size_name' => $battery->getSize()->getName(), + 'prod_code' => $battery->getProductCode(), + 'warranty_code' => $obj->getWarrantyCode(), + 'warranty_expiration' => $obj->getWarrantyExpiration() ? $obj->getWarrantyExpiration()->format("d M Y") : "", + 'has_motolite_battery' => $obj->hasMotoliteBattery(), + 'is_active' => $obj->isActive() + ]; + } + + return $row; + } + + protected function getTwigTemplate($id) + { + if (isset($this->template_hash[$id])) + { + return $this->template_hash[$id]; + } + + return null; + } + + protected function setObject($obj, $req) + { + // set and save values + $obj->setTitle($req->request->get('title')) + ->setFirstName($req->request->get('first_name')) + ->setLastName($req->request->get('last_name')) + ->setCustomerClassification($req->request->get('customer_classification')) + ->setCustomerNotes($req->request->get('customer_notes')) + ->setEmail($req->request->get('email')) + ->setIsCSAT($req->request->get('flag_csat') ? true : false) + ->setActive($req->request->get('flag_active') ? true : false) + ->setPromoSms($req->request->get('flag_promo_sms', false)) + ->setPromoEmail($req->request->get('flag_promo_email', false)) + ->setDpaConsent($req->request->get('flag_dpa_consent', false)); + + // phone numbers + $obj->setPhoneMobile($req->request->get('phone_mobile')) + ->setPhoneLandline($req->request->get('phone_landline')) + ->setPhoneOffice($req->request->get('phone_office')) + ->setPhoneFax($req->request->get('phone_fax')); + } + + protected function fillDropdownParameters(&$params) + { + $em = $this->em; + + $params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); + $params['vmfgs'] = $em->getRepository(VehicleManufacturer::class)->findAll(); + + $params['classifications'] = CustomerClassification::getCollection(); + $params['fuel_types'] = FuelType::getCollection(); + $params['status_conditions'] = VehicleStatusCondition::getCollection(); + + $params['years'] = $this->generateYearOptions(); + $params['batteries'] = $em->getRepository(Battery::class)->findAll(); + } + + protected function generateYearOptions() + { + $start_year = 1950; + return range($start_year, date("Y") + 1); + } + + protected function loadTemplates() + { + $this->template_hash = []; + + // add all twig templates for customer to hash + $this->template_hash['cust_add_form'] = 'customer/form.html.twig'; + $this->template_hash['cust_update_form'] = 'customer/form.html.twig'; + $this->template_hash['cust_list'] = 'customer/list.html.twig'; + } + + protected function updateVehicles($em, Customer $cust, $vehicles) + { + $vehicle_ids = []; + + foreach ($vehicles as $vehicle) + { + // check if customer vehicle exists + if (!empty($vehicle->id)) + { + $cust_vehicle = $em->getRepository(CustomerVehicle::class)->find($vehicle->id); + if ($cust_vehicle == null) + throw new CrudException("Could not find customer vehicle."); + + } + // this is a new vehicle + else + { + $cust_vehicle = new CustomerVehicle(); + $cust_vehicle->setCustomer($cust); + $cust->addVehicle($cust_vehicle); + $em->persist($cust_vehicle); + } + + // vehicle, because they could have changed vehicle type + $vobj = $em->getRepository(Vehicle::class)->find($vehicle->vehicle); + if ($vobj == null) + throw new CrudException("Could not find vehicle."); + + // TODO: validate details + + $cust_vehicle->setName($vehicle->name) + ->setVehicle($vobj) + ->setPlateNumber($vehicle->plate_number) + ->setModelYear($vehicle->model_year) + ->setColor($vehicle->color) + ->setStatusCondition($vehicle->status_condition) + ->setFuelType($vehicle->fuel_type) + ->setActive($vehicle->flag_active); + + // if specified, check if battery exists + if ($vehicle->battery) + { + // check if battery exists + $bobj = $em->getRepository(Battery::class)->find($vehicle->battery); + if ($bobj == null) + throw new CrudException("Could not find battery."); + + // check if warranty expiration was specified + $warr_ex = DateTime::createFromFormat("d M Y", $vehicle->warranty_expiration); + if (!$warr_ex) + $warr_ex = null; + + $cust_vehicle->setHasMotoliteBattery(true) + ->setCurrentBattery($bobj) + ->setWarrantyCode($vehicle->warranty_code) + ->setWarrantyExpiration($warr_ex); + } + else + { + $cust_vehicle->setHasMotoliteBattery(false); + } + + // add to list of vehicles to keep + $vehicle_ids[$cust_vehicle->getID()] = true; + } + + // cleanup + // delete all vehicles not in list + $cvs = $cust->getVehicles(); + foreach ($cvs as $cv) + { + if (!isset($vehicle_ids[$cv->getID()])) + { + $cust->removeVehicle($cv); + $em->remove($cv); + } + } + } + + // check if datatable filter is present and append to query + protected function setQueryFilters($datatable, &$query) { + if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) { + $query->join('q.vehicles', 'cv') + ->where('q.first_name LIKE :filter') + ->orWhere('q.last_name LIKE :filter') + ->orWhere('q.customer_classification LIKE :filter') + ->orWhere('cv.plate_number LIKE :filter') + ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); + } + } + +} diff --git a/src/Service/CustomerHandlerInterface.php b/src/Service/CustomerHandlerInterface.php new file mode 100644 index 00000000..41256cb8 --- /dev/null +++ b/src/Service/CustomerHandlerInterface.php @@ -0,0 +1,35 @@ +security = $security; + $this->em = $em; + $this->validator = $validator; + } + + public function generateInvoice(InvoiceCriteria $criteria) + { + // initialize + $invoice = new Invoice(); + $total = [ + 'sell_price' => 0.0, + 'vat' => 0.0, + 'vat_ex_price' => 0.0, + 'ti_rate' => 0.0, + 'total_price' => 0.0, + 'discount' => 0.0, + ]; + + $stype = $criteria->getServiceType(); + $cv = $criteria->getCustomerVehicle(); + $has_coolant = $criteria->hasCoolant(); + switch ($stype) + { + case CMBServiceType::JUMPSTART: + $this->processJumpstart($total, $invoice); + break; + //case ServiceType::JUMPSTART_WARRANTY: + // $this->processJumpstartWarranty($total, $invoice); + + case CMBServiceType::BATTERY_REPLACEMENT_NEW: + $this->processEntries($total, $criteria, $invoice); + /* + $this->processBatteries($total, $criteria, $invoice); + $this->processTradeIns($total, $criteria, $invoice); + */ + $this->processDiscount($total, $criteria, $invoice); + break; + + case CMBServiceType::BATTERY_REPLACEMENT_WARRANTY: + $this->processWarranty($total, $criteria, $invoice); + break; + //case ServiceType::POST_RECHARGED: + // $this->processRecharge($total, $invoice); + // break; + //case ServiceType::POST_REPLACEMENT: + // $this->processReplacement($total, $invoice); + // break; + //case ServiceType::TIRE_REPAIR: + // $this->processTireRepair($total, $invoice, $cv); + // $this->processOtherServices($total, $invoice, $stype); + // break; + //case ServiceType::OVERHEAT_ASSISTANCE: + // $this->processOverheat($total, $invoice, $cv, $has_coolant); + // break; + //case ServiceType::EMERGENCY_REFUEL: + // error_log('processing refuel'); + // $ftype = $criteria->getCustomerVehicle()->getFuelType(); + // $this->processRefuel($total, $invoice, $cv); + // break; + } + + // TODO: check if any promo is applied + // apply discounts + $promos = $criteria->getPromos(); + + // get current user + $user = $this->security->getUser(); + if ($user != null) + { + $invoice->setCreatedBy($user); + } + + $invoice->setTotalPrice($total['total_price']) + ->setVATExclusivePrice($total['vat_ex_price']) + ->setVAT($total['vat']) + ->setDiscount($total['discount']) + ->setTradeIn($total['ti_rate']) + ->setStatus(InvoiceStatus::DRAFT); + + // dump + //Debug::dump($invoice, 1); + + return $invoice; + } + + // generate invoice criteria + public function generateInvoiceCriteria($jo, $promo_id, $invoice_items, &$error_array) + { + $em = $this->em; + + // instantiate the invoice criteria + $criteria = new InvoiceCriteria(); + $criteria->setServiceType($jo->getServiceType()) + ->setCustomerVehicle($jo->getCustomerVehicle()); + + $ierror = $this->invoicePromo($criteria, $promo_id); + + if (!$ierror && !empty($invoice_items)) + { + // check for trade-in so we can mark it for mobile app + foreach ($invoice_items as $item) + { + // get first trade-in + if (!empty($item['trade_in'])) + { + $jo->getTradeInType($item['trade_in']); + break; + } + } + + $ierror = $this->invoiceBatteries($criteria, $invoice_items); + } + + if ($ierror) + { + $error_array['invoice'] = $ierror; + } + + else + { + // generate the invoice + $iobj = $this->generateInvoice($criteria); + + // validate + $ierrors = $this->validator->validate($iobj); + + // add errors to list + foreach ($ierrors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if invoice already exists for JO + $old_invoice = $jo->getInvoice(); + if ($old_invoice != null) + { + // remove old invoice + $em->remove($old_invoice); + $em->flush(); + } + + // add invoice to JO + $jo->setInvoice($iobj); + + $em->persist($iobj); + + } + } + + protected function getTaxAmount($price) + { + $vat_ex_price = $this->getTaxExclusivePrice($price); + return $price - $vat_ex_price; + // return round($vat_ex_price * self::TAX_RATE, 2); + } + + protected function getTaxExclusivePrice($price) + { + return round($price / (1 + self::TAX_RATE), 2); + } + + protected function getTradeInRate($ti) + { + $size = $ti['size']; + $trade_in = $ti['trade_in']; + + if ($trade_in == null) + return 0; + + switch ($trade_in) + { + // TODO: for now, tradein uses getTIPriceMotolite. + // Might need to modify later + case CMBTradeInType::YES: + return $size->getTIPriceMotolite(); + } + + return 0; + } + + public function invoicePromo(InvoiceCriteria $criteria, $promo_id) + { + // return error if there's a problem, false otherwise + // check service type + $stype = $criteria->getServiceType(); + if ($stype != CMBServiceType::BATTERY_REPLACEMENT_NEW) + return null; + + + if (empty($promo_id)) + { + return false; + } + + // check if this is a valid promo + $promo = $this->em->getRepository(Promo::class)->find($promo_id); + + if (empty($promo)) + return 'Invalid promo specified.'; + + $criteria->addPromo($promo); + return false; + } + + public function invoiceBatteries(InvoiceCriteria $criteria, $items) + { + // check service type + $stype = $criteria->getServiceType(); + if ($stype != CMBServiceType::BATTERY_REPLACEMENT_NEW && $stype != CMBServiceType::BATTERY_REPLACEMENT_WARRANTY) + return null; + + // return error if there's a problem, false otherwise + if (!empty($items)) + { + foreach ($items as $item) + { + // check if this is a valid battery + $battery = $this->em->getRepository(Battery::class)->find($item['battery']); + + if (empty($battery)) + { + $error = 'Invalid battery specified.'; + return $error; + } + + // quantity + $qty = $item['quantity']; + if ($qty < 1) + continue; + + /* + // add to criteria + $criteria->addBattery($battery, $qty); + */ + + // if this is a trade in, add trade in + if (!empty($item['trade_in']) && CMBTradeInType::validate($item['trade_in'])) + $trade_in = $item['trade_in']; + else + $trade_in = null; + + $criteria->addEntry($battery, $trade_in, $qty); + } + } + + return null; + } + + + protected function processEntries(&$total, InvoiceCriteria $criteria, Invoice $invoice) + { + // error_log('processing entries...'); + $entries = $criteria->getEntries(); + + $con_batts = []; + $con_tis = []; + foreach ($entries as $entry) + { + $batt = $entry['battery']; + $qty = $entry['qty']; + $trade_in = $entry['trade_in']; + $size = $batt->getSize(); + + // consolidate batteries + $batt_id = $batt->getID(); + if (!isset($con_batts[$batt_id])) + $con_batts[$batt->getID()] = [ + 'batt' => $batt, + 'qty' => 0 + ]; + $con_batts[$batt_id]['qty']++; + + + // no trade-in + if ($trade_in == null) + continue; + + // consolidate trade-ins + $ti_key = $size->getID() . '|' . $trade_in; + if (!isset($con_tis[$ti_key])) + $con_tis[$ti_key] = [ + 'size' => $size, + 'trade_in' => $trade_in, + 'qty' => 0 + ]; + $con_tis[$ti_key]['qty']++; + } + + $this->processBatteries($total, $con_batts, $invoice); + $this->processTradeIns($total, $con_tis, $invoice); + } + + protected function processBatteries(&$total, $con_batts, Invoice $invoice) + { + // process batteries + foreach ($con_batts as $con_data) + { + $batt = $con_data['batt']; + $qty = $con_data['qty']; + + $sell_price = $batt->getSellingPrice(); + $vat = $this->getTaxAmount($sell_price); + // $vat_ex_price = $this->getTaxExclusivePrice($sell_price); + + $total['sell_price'] += $sell_price * $qty; + $total['vat'] += $vat * $qty; + $total['vat_ex_price'] += ($sell_price - $vat) * $qty; + + $total['total_price'] += $sell_price * $qty; + + // add item + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle($batt->getModel()->getName() . ' ' . $batt->getSize()->getName()) + ->setQuantity($qty) + ->setPrice($sell_price) + ->setBattery($batt); + + $invoice->addItem($item); + } + } + + protected function processTradeIns(&$total, $con_tis, Invoice $invoice) + { + foreach ($con_tis as $ti) + { + $qty = $ti['qty']; + $ti_rate = $this->getTradeInRate($ti); + + $total['ti_rate'] += $ti_rate * $qty; + $total['total_price'] -= $ti_rate * $qty; + + // add item + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Trade-in ' . CMBTradeInType::getName($ti['trade_in']) . ' ' . $ti['size']->getName() . ' battery') + ->setQuantity($qty) + ->setPrice($ti_rate * -1); + + $invoice->addItem($item); + } + } + + protected function processDiscount(&$total, InvoiceCriteria $criteria, Invoice $invoice) + { + $promos = $criteria->getPromos(); + if (count($promos) < 1) + return; + + // NOTE: only get first promo because only one is applicable anyway + $promo = $promos[0]; + + $rate = $promo->getDiscountRate(); + $apply_to = $promo->getDiscountApply(); + + switch ($apply_to) + { + case DiscountApply::SRP: + $discount = round($total['sell_price'] * $rate, 2); + break; + case DiscountApply::OPL: + // $discount = round($total['sell_price'] * 0.6 / 0.7 * $rate, 2); + $discount = round($total['sell_price'] * (1 - 1.5 / 0.7 * $rate), 2); + break; + } + + // if discount is higher than 0, display in invoice + if ($discount > 0) + { + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Promo discount') + ->setQuantity(1) + ->setPrice(-1 * $discount); + $invoice->addItem($item); + } + + $total['discount'] = $discount; + $total['total_price'] -= $discount; + + // process + $invoice->setPromo($promo); + } + + protected function processJumpstart(&$total, $invoice) + { + // add troubleshooting fee + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Troubleshooting fee') + ->setQuantity(1) + ->setPrice(self::TROUBLESHOOTING_FEE); + $invoice->addItem($item); + + $total['sell_price'] = self::TROUBLESHOOTING_FEE; + $total['vat_ex_price'] = self::TROUBLESHOOTING_FEE; + $total['total_price'] = self::TROUBLESHOOTING_FEE; + } + + protected function processJumpstartWarranty(&$total, $invoice) + { + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Troubleshooting fee') + ->setQuantity(1) + ->setPrice(0.00); + $invoice->addItem($item); + } + + protected function processRecharge(&$total, $invoice) + { + // add recharge fee + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Recharge fee') + ->setQuantity(1) + ->setPrice(self::RECHARGE_FEE); + $invoice->addItem($item); + + $total['sell_price'] = self::RECHARGE_FEE; + $total['vat_ex_price'] = self::RECHARGE_FEE; + $total['total_price'] = self::RECHARGE_FEE; + } + + protected function processReplacement(&$total, $invoice) + { + // add recharge fee + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Battery replacement') + ->setQuantity(1) + ->setPrice(self::BATT_REPLACEMENT_FEE); + $invoice->addItem($item); + } + + protected function processWarranty(&$total, InvoiceCriteria $criteria, $invoice) + { + // error_log('processing warranty'); + $entries = $criteria->getEntries(); + foreach ($entries as $entry) + { + $batt = $entry['battery']; + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle($batt->getModel()->getName() . ' ' . $batt->getSize()->getName() . ' - Service Unit') + ->setQuantity(1) + ->setPrice(self::WARRANTY_FEE) + ->setBattery($batt); + $invoice->addItem($item); + } + } + + protected function processOtherServices(&$total, $invoice, $stype) + { + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Service - ' . CMBServiceType::getName($stype)) + ->setQuantity(1) + ->setPrice(self::OTHER_SERVICES_FEE); + $invoice->addItem($item); + + $total['total_price'] = 200.00; + } + + protected function processOverheat(&$total, $invoice, $cv, $has_coolant) + { + // free if they have a motolite battery + if ($cv->hasMotoliteBattery()) + $fee = 0; + else + $fee = self::SERVICE_FEE; + + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Service - Overheat Assistance') + ->setQuantity(1) + ->setPrice($fee); + $invoice->addItem($item); + + $total_price = $fee; + + if ($has_coolant) + { + $coolant = new InvoiceItem(); + $coolant->setInvoice($invoice) + ->setTitle('4L Coolant') + ->setQuantity(1) + ->setPrice(self::COOLANT_FEE); + $invoice->addItem($coolant); + + $total_price += self::COOLANT_FEE; + //$total_price += 1600; + } + + $vat_ex_price = $this->getTaxExclusivePrice($total_price); + $vat = $total_price - $vat_ex_price; + $total['total_price'] = $total_price; + $total['vat_ex_price'] = $vat_ex_price; + $total['vat'] = $vat; + } + + protected function processTireRepair(&$total, $invoice, $cv) + { + // free if they have a motolite battery + if ($cv->hasMotoliteBattery()) + $fee = 0; + else + $fee = self::SERVICE_FEE; + + $item = new InvoiceItem(); + $item->setInvoice($invoice) + ->setTitle('Service - Flat Tire') + ->setQuantity(1) + ->setPrice($fee); + $invoice->addItem($item); + $total_price = $fee; + + $vat_ex_price = $this->getTaxExclusivePrice($total_price); + $vat = $total_price - $vat_ex_price; + $total['total_price'] = $total_price; + $total['vat_ex_price'] = $vat_ex_price; + $total['vat'] = $vat; + } + + protected function processRefuel(&$total, $invoice, $cv) + { + // free if they have a motolite battery + if ($cv->hasMotoliteBattery()) + $fee = 0; + else + $fee = self::SERVICE_FEE; + + $ftype = $cv->getFuelType(); + $item = new InvoiceItem(); + + // service charge + $item->setInvoice($invoice) + ->setTitle('Service - ' . CMBServiceType::getName(CMBServiceType::EMERGENCY_REFUEL)) + ->setQuantity(1) + ->setPrice($fee); + $invoice->addItem($item); + $total_price = $fee; + // $total['total_price'] = 200.00; + + $gas_price = self::REFUEL_FEE_GAS; + $diesel_price = self::REFUEL_FEE_DIESEL; + + $fuel = new InvoiceItem(); + error_log('fuel type - ' . $ftype); + switch ($ftype) + { + case FuelType::GAS: + $fuel->setInvoice($invoice) + ->setTitle('4L Fuel - Gas') + ->setQuantity(1) + ->setPrice($gas_price); + $invoice->addItem($fuel); + $total_price += $gas_price; + break; + case FuelType::DIESEL: + $fuel->setInvoice($invoice) + ->setTitle('4L Fuel - Diesel') + ->setQuantity(1) + ->setPrice($diesel_price); + $total_price += $diesel_price; + $invoice->addItem($fuel); + break; + default: + // NOTE: should never get to this point + $fuel->setInvoice($invoice) + ->setTitle('Fuel - Unknown') + ->setQuantity(1) + ->setPrice(0); + $total_price += 0.00; + $invoice->addItem($fuel); + break; + } + + $vat_ex_price = $this->getTaxExclusivePrice($total_price); + $vat = $total_price - $vat_ex_price; + $total['total_price'] = $total_price; + $total['vat_ex_price'] = $vat_ex_price; + $total['vat'] = $vat; + } + +} diff --git a/src/Service/InvoiceCreator.php b/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php similarity index 56% rename from src/Service/InvoiceCreator.php rename to src/Service/InvoiceGenerator/ResqInvoiceGenerator.php index 4576b531..c5e9c81c 100644 --- a/src/Service/InvoiceCreator.php +++ b/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php @@ -1,8 +1,14 @@ security = $security; + $this->em = $em; + $this->validator = $validator; } - public function getVATAmount($price) + public function generateInvoice(InvoiceCriteria $criteria) { - $vat_ex_price = $this->getVATExclusivePrice($price); + // initialize + $invoice = new Invoice(); + $total = [ + 'sell_price' => 0.0, + 'vat' => 0.0, + 'vat_ex_price' => 0.0, + 'ti_rate' => 0.0, + 'total_price' => 0.0, + 'discount' => 0.0, + ]; + + $stype = $criteria->getServiceType(); + $cv = $criteria->getCustomerVehicle(); + $has_coolant = $criteria->hasCoolant(); + // error_log($stype); + switch ($stype) + { + case ServiceType::JUMPSTART_TROUBLESHOOT: + $this->processJumpstart($total, $invoice); + break; + case ServiceType::JUMPSTART_WARRANTY: + $this->processJumpstartWarranty($total, $invoice); + + case ServiceType::BATTERY_REPLACEMENT_NEW: + $this->processEntries($total, $criteria, $invoice); + /* + $this->processBatteries($total, $criteria, $invoice); + $this->processTradeIns($total, $criteria, $invoice); + */ + $this->processDiscount($total, $criteria, $invoice); + break; + + case ServiceType::BATTERY_REPLACEMENT_WARRANTY: + $this->processWarranty($total, $criteria, $invoice); + break; + case ServiceType::POST_RECHARGED: + $this->processRecharge($total, $invoice); + break; + case ServiceType::POST_REPLACEMENT: + $this->processReplacement($total, $invoice); + break; + case ServiceType::TIRE_REPAIR: + $this->processTireRepair($total, $invoice, $cv); + // $this->processOtherServices($total, $invoice, $stype); + break; + case ServiceType::OVERHEAT_ASSISTANCE: + $this->processOverheat($total, $invoice, $cv, $has_coolant); + break; + case ServiceType::EMERGENCY_REFUEL: + error_log('processing refuel'); + $ftype = $criteria->getCustomerVehicle()->getFuelType(); + $this->processRefuel($total, $invoice, $cv); + break; + } + + // TODO: check if any promo is applied + // apply discounts + $promos = $criteria->getPromos(); + + // get current user + $user = $this->security->getUser(); + if ($user != null) + { + $invoice->setCreatedBy($user); + } + + $invoice->setTotalPrice($total['total_price']) + ->setVATExclusivePrice($total['vat_ex_price']) + ->setVAT($total['vat']) + ->setDiscount($total['discount']) + ->setTradeIn($total['ti_rate']) + ->setStatus(InvoiceStatus::DRAFT); + + // dump + //Debug::dump($invoice, 1); + + return $invoice; + } + + // generate invoice criteria + public function generateInvoiceCriteria($jo, $promo_id, $invoice_items, &$error_array) + { + $em = $this->em; + + // instantiate the invoice criteria + $criteria = new InvoiceCriteria(); + $criteria->setServiceType($jo->getServiceType()) + ->setCustomerVehicle($jo->getCustomerVehicle()); + + $ierror = $this->invoicePromo($criteria, $promo_id); + + if (!$ierror && !empty($invoice_items)) + { + // check for trade-in so we can mark it for mobile app + foreach ($invoice_items as $item) + { + // get first trade-in + if (!empty($item['trade_in'])) + { + $jo->getTradeInType($item['trade_in']); + break; + } + } + + $ierror = $this->invoiceBatteries($criteria, $invoice_items); + } + + if ($ierror) + { + $error_array['invoice'] = $ierror; + } + + else + { + // generate the invoice + $iobj = $this->generateInvoice($criteria); + + // validate + $ierrors = $this->validator->validate($iobj); + + // add errors to list + foreach ($ierrors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if invoice already exists for JO + $old_invoice = $jo->getInvoice(); + if ($old_invoice != null) + { + // remove old invoice + $em->remove($old_invoice); + $em->flush(); + } + + // add invoice to JO + $jo->setInvoice($iobj); + + $em->persist($iobj); + + } + } + + protected function getTaxAmount($price) + { + $vat_ex_price = $this->getTaxExclusivePrice($price); return $price - $vat_ex_price; - // return round($vat_ex_price * self::VAT_RATE, 2); + // return round($vat_ex_price * self::TAX_RATE, 2); } - public function getVATExclusivePrice($price) + protected function getTaxExclusivePrice($price) { - return round($price / (1 + self::VAT_RATE), 2); + return round($price / (1 + self::TAX_RATE), 2); } - public function getTradeInRate($ti) + protected function getTradeInRate($ti) { $size = $ti['size']; $trade_in = $ti['trade_in']; @@ -57,6 +226,75 @@ class InvoiceCreator return 0; } + public function invoicePromo(InvoiceCriteria $criteria, $promo_id) + { + // return error if there's a problem, false otherwise + // check service type + $stype = $criteria->getServiceType(); + if ($stype != ServiceType::BATTERY_REPLACEMENT_NEW) + return null; + + + if (empty($promo_id)) + { + return false; + } + + // check if this is a valid promo + $promo = $this->em->getRepository(Promo::class)->find($promo_id); + + if (empty($promo)) + return 'Invalid promo specified.'; + + $criteria->addPromo($promo); + return false; + } + + public function invoiceBatteries(InvoiceCriteria $criteria, $items) + { + // check service type + $stype = $criteria->getServiceType(); + if ($stype != ServiceType::BATTERY_REPLACEMENT_NEW && $stype != ServiceType::BATTERY_REPLACEMENT_WARRANTY) + return null; + + // return error if there's a problem, false otherwise + if (!empty($items)) + { + foreach ($items as $item) + { + // check if this is a valid battery + $battery = $this->em->getRepository(Battery::class)->find($item['battery']); + + if (empty($battery)) + { + $error = 'Invalid battery specified.'; + return $error; + } + + // quantity + $qty = $item['quantity']; + if ($qty < 1) + continue; + + /* + // add to criteria + $criteria->addBattery($battery, $qty); + */ + + // if this is a trade in, add trade in + if (!empty($item['trade_in']) && TradeInType::validate($item['trade_in'])) + $trade_in = $item['trade_in']; + else + $trade_in = null; + + $criteria->addEntry($battery, $trade_in, $qty); + } + } + + return null; + } + + protected function processEntries(&$total, InvoiceCriteria $criteria, Invoice $invoice) { // error_log('processing entries...'); @@ -109,8 +347,8 @@ class InvoiceCreator $qty = $con_data['qty']; $sell_price = $batt->getSellingPrice(); - $vat = $this->getVATAmount($sell_price); - // $vat_ex_price = $this->getVATExclusivePrice($sell_price); + $vat = $this->getTaxAmount($sell_price); + // $vat_ex_price = $this->getTaxExclusivePrice($sell_price); $total['sell_price'] += $sell_price * $qty; $total['vat'] += $vat * $qty; @@ -192,22 +430,22 @@ class InvoiceCreator $invoice->setPromo($promo); } - public function processJumpstart(&$total, $invoice) + protected function processJumpstart(&$total, $invoice) { // add troubleshooting fee $item = new InvoiceItem(); $item->setInvoice($invoice) ->setTitle('Troubleshooting fee') ->setQuantity(1) - ->setPrice(150.00); + ->setPrice(self::TROUBLESHOOTING_FEE); $invoice->addItem($item); - $total['sell_price'] = 150.00; - $total['vat_ex_price'] = 150.00; - $total['total_price'] = 150.00; + $total['sell_price'] = self::TROUBLESHOOTING_FEE; + $total['vat_ex_price'] = self::TROUBLESHOOTING_FEE; + $total['total_price'] = self::TROUBLESHOOTING_FEE; } - public function processJumpstartWarranty(&$total, $invoice) + protected function processJumpstartWarranty(&$total, $invoice) { $item = new InvoiceItem(); $item->setInvoice($invoice) @@ -217,33 +455,33 @@ class InvoiceCreator $invoice->addItem($item); } - public function processRecharge(&$total, $invoice) + protected function processRecharge(&$total, $invoice) { // add recharge fee $item = new InvoiceItem(); $item->setInvoice($invoice) ->setTitle('Recharge fee') ->setQuantity(1) - ->setPrice(300.00); + ->setPrice(self::RECHARGE_FEE); $invoice->addItem($item); - $total['sell_price'] = 300.00; - $total['vat_ex_price'] = 300.00; - $total['total_price'] = 300.00; + $total['sell_price'] = self::RECHARGE_FEE; + $total['vat_ex_price'] = self::RECHARGE_FEE; + $total['total_price'] = self::RECHARGE_FEE; } - public function processReplacement(&$total, $invoice) + protected function processReplacement(&$total, $invoice) { // add recharge fee $item = new InvoiceItem(); $item->setInvoice($invoice) ->setTitle('Battery replacement') ->setQuantity(1) - ->setPrice(0.00); + ->setPrice(self::BATT_REPLACEMENT_FEE); $invoice->addItem($item); } - public function processWarranty(&$total, InvoiceCriteria $criteria, $invoice) + protected function processWarranty(&$total, InvoiceCriteria $criteria, $invoice) { // error_log('processing warranty'); $entries = $criteria->getEntries(); @@ -254,25 +492,25 @@ class InvoiceCreator $item->setInvoice($invoice) ->setTitle($batt->getModel()->getName() . ' ' . $batt->getSize()->getName() . ' - Service Unit') ->setQuantity(1) - ->setPrice(0.00) + ->setPrice(self::WARRANTY_FEE) ->setBattery($batt); $invoice->addItem($item); } } - public function processOtherServices(&$total, $invoice, $stype) + protected function processOtherServices(&$total, $invoice, $stype) { $item = new InvoiceItem(); $item->setInvoice($invoice) ->setTitle('Service - ' . ServiceType::getName($stype)) ->setQuantity(1) - ->setPrice(200.00); + ->setPrice(self::OTHER_SERVICES_FEE); $invoice->addItem($item); $total['total_price'] = 200.00; } - public function processOverheat(&$total, $invoice, $cv, $has_coolant) + protected function processOverheat(&$total, $invoice, $cv, $has_coolant) { // free if they have a motolite battery if ($cv->hasMotoliteBattery()) @@ -295,20 +533,21 @@ class InvoiceCreator $coolant->setInvoice($invoice) ->setTitle('4L Coolant') ->setQuantity(1) - ->setPrice(1600); + ->setPrice(self::COOLANT_FEE); $invoice->addItem($coolant); - $total_price += 1600; + $total_price += self::COOLANT_FEE; + //$total_price += 1600; } - $vat_ex_price = $this->getVATExclusivePrice($total_price); + $vat_ex_price = $this->getTaxExclusivePrice($total_price); $vat = $total_price - $vat_ex_price; $total['total_price'] = $total_price; $total['vat_ex_price'] = $vat_ex_price; $total['vat'] = $vat; } - public function processTireRepair(&$total, $invoice, $cv) + protected function processTireRepair(&$total, $invoice, $cv) { // free if they have a motolite battery if ($cv->hasMotoliteBattery()) @@ -324,14 +563,14 @@ class InvoiceCreator $invoice->addItem($item); $total_price = $fee; - $vat_ex_price = $this->getVATExclusivePrice($total_price); + $vat_ex_price = $this->getTaxExclusivePrice($total_price); $vat = $total_price - $vat_ex_price; $total['total_price'] = $total_price; $total['vat_ex_price'] = $vat_ex_price; $total['vat'] = $vat; } - public function processRefuel(&$total, $invoice, $cv) + protected function processRefuel(&$total, $invoice, $cv) { // free if they have a motolite battery if ($cv->hasMotoliteBattery()) @@ -351,8 +590,8 @@ class InvoiceCreator $total_price = $fee; // $total['total_price'] = 200.00; - $gas_price = 260; - $diesel_price = 220; + $gas_price = self::REFUEL_FEE_GAS; + $diesel_price = self::REFUEL_FEE_DIESEL; $fuel = new InvoiceItem(); //error_log('fuel type - ' . $ftype); @@ -385,7 +624,7 @@ class InvoiceCreator break; } - $vat_ex_price = $this->getVATExclusivePrice($total_price); + $vat_ex_price = $this->getTaxExclusivePrice($total_price); $vat = $total_price - $vat_ex_price; $total['total_price'] = $total_price; $total['vat_ex_price'] = $vat_ex_price; diff --git a/src/Service/InvoiceGeneratorInterface.php b/src/Service/InvoiceGeneratorInterface.php new file mode 100644 index 00000000..85407c30 --- /dev/null +++ b/src/Service/InvoiceGeneratorInterface.php @@ -0,0 +1,18 @@ +redis = $redis_prov->getRedisClient(); + $this->active_jo_key = $active_jo_key; + } + + public function addActiveJobOrder(JobOrder $jo) + { + $coords = $jo->getCoordinates(); + + $this->redis->geoadd( + $this->active_jo_key, + $coords->getLongitude(), + $coords->getLatitude(), + $jo->getID() + ); + } + + public function getAllActiveJobOrders() + { + $all_jo = $this->redis->georadius( + $this->active_jo_key, + 0, + 0, + 41000, + 'km', + ['WITHCOORD' => true] + ); + + $jo_locs = []; + foreach ($all_jo as $jo_data) + { + $id = $jo_data[0]; + $lng = $jo_data[1][0]; + $lat = $jo_data[1][1]; + + $jo_locs[$id] = [ + 'longitude' => $lng, + 'latitude' => $lat, + ]; + } + + // error_log(print_r($all_jo, true)); + return $jo_locs; + } + + public function removeActiveJobOrder(JobOrder $jo) + { + $this->redis->zrem( + $this->active_jo_key, + $jo->getID() + ); + } +} diff --git a/src/Service/JobOrderHandler/CMBJobOrderHandler.php b/src/Service/JobOrderHandler/CMBJobOrderHandler.php new file mode 100644 index 00000000..40f428ee --- /dev/null +++ b/src/Service/JobOrderHandler/CMBJobOrderHandler.php @@ -0,0 +1,2644 @@ +em = $em; + $this->ic = $ic; + $this->security = $security; + $this->validator = $validator; + $this->translator = $translator; + $this->rah = $rah; + $this->country_code = $country_code; + $this->wh = $wh; + + $this->loadTemplates(); + } + + // get job order rows + public function getRows(Request $req, $tier) + { + // check which job order tier is being called for and confirm access + $tier_params = $this->checkTier($tier); + + // get current user + $user = $this->security->getUser(); + if ($user == null) + throw new AccessDeniedHttpException('No access.'); + + $hubs = $user->getHubs(); + + // get query builder + $qb = $this->em->getRepository(JobOrder::class) + ->createQueryBuilder('q'); + + // get datatable params + $datatable = $req->request->get('datatable'); + + // count total records + $tquery = $qb->select('COUNT(q)'); + + $this->setQueryFilters($datatable, $tquery, $qb, $hubs, $tier, $tier_params['jo_status']); + + $total = $tquery->getQuery() + ->getSingleScalarResult(); + + // get current page number + $page = $datatable['pagination']['page'] ?? 1; + + $perpage = $datatable['pagination']['perpage']; + $offset = ($page - 1) * $perpage; + + // add metadata + $meta = [ + 'page' => $page, + 'perpage' => $perpage, + 'pages' => ceil($total / $perpage), + 'total' => $total, + 'sort' => 'asc', + 'field' => 'id' + ]; + + // build query + $qb = $this->em->getRepository(JobOrder::class) + ->createQueryBuilder('q'); + $query = $qb->select('q'); + + $this->setQueryFilters($datatable, $query, $qb, $hubs, $tier, $tier_params['jo_status']); + + // check if sorting is present, otherwise use default + if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) { + $order = $datatable['sort']['sort'] ?? 'asc'; + $query->orderBy('q.' . $datatable['sort']['field'], $order); + } else { + $query->orderBy('q.date_schedule', 'asc'); + } + + // get rows for this page + $query_obj = $query->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery(); + + // error_log($query_obj->getSQL()); + + $obj_rows = $query_obj->getResult(); + + $statuses = JOStatus::getCollection(); + $service_types = CMBServiceType::getCollection(); + + // process rows + $rows = []; + foreach ($obj_rows as $orow) { + // add row data + $row['id'] = $orow->getID(); + $row['customer_name'] = $orow->getCustomer()->getFirstName() . ' ' . $orow->getCustomer()->getLastName(); + $row['delivery_address'] = $orow->getDeliveryAddress(); + $row['date_schedule'] = $orow->getDateSchedule()->format("d M Y g:i A"); + $row['type'] = $orow->isAdvanceOrder() ? 'Advanced Order' : 'Immediate'; + $row['service_type'] = $service_types[$orow->getServiceType()] ?? 'Unknown'; + $row['status'] = $statuses[$orow->getStatus()]; + $row['flag_advance'] = $orow->isAdvanceOrder(); + $row['plate_number'] = $orow->getCustomerVehicle()->getPlateNumber(); + $row['is_mobile'] = $orow->getSource() == TransactionOrigin::MOBILE_APP; + + $processor = $orow->getProcessedBy(); + if ($processor == null) + $row['processor'] = ''; + else + $row['processor'] = $orow->getProcessedBy()->getFullName(); + + $assignor = $orow->getAssignedBy(); + if ($assignor == null) + $row['assignor'] = ''; + else + $row['assignor'] = $orow->getAssignedBy()->getFullName(); + + $rows[] = $row; + } + + $params['meta'] = $meta; + $params['rows'] = $rows; + $params['tier_params'] = $tier_params; + + return $params; + + } + + // get job orders + public function getJobOrders(Request $req) + { + // get search term + $term = $req->query->get('search'); + + // get querybuilder + $qb = $this->em->getRepository(JobOrder::class) + ->createQueryBuilder('q'); + + // build expression now since we're reusing it + $jo_label = $qb->expr()->concat($qb->expr()->literal('#'), 'q.id', $qb->expr()->literal(' - '), 'c.first_name', $qb->expr()->literal(' '), 'c.last_name', $qb->expr()->literal(' (Plate No: '), 'v.plate_number', $qb->expr()->literal(')')); + + // count total records + $tquery = $qb->select('COUNT(q)') + ->join('q.customer', 'c') + ->join('q.cus_vehicle', 'v'); + + // add filters to count query + if (!empty($term)) { + $tquery->where($jo_label . ' LIKE :filter') + ->setParameter('filter', '%' . $term . '%'); + } + + $total = $tquery->getQuery() + ->getSingleScalarResult(); + + // pagination vars + $page = $req->query->get('page') ?? 1; + $perpage = 20; + $offset = ($page - 1) * $perpage; + $pages = ceil($total / $perpage); + $has_more_pages = $page < $pages ? true : false; + + // build main query + $query = $qb->select('q') + ->addSelect($jo_label . ' as jo_label') + ->addSelect('c.first_name as cust_first_name') + ->addSelect('c.last_name as cust_last_name') + ->addSelect('v.plate_number as vehicle_plate_number'); + + // add filters if needed + if (!empty($term)) { + $query->where($jo_label . ' LIKE :filter') + ->setParameter('filter', '%' . $term . '%'); + } + + // get rows + $obj_rows = $query->orderBy('q.id', 'asc') + ->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery() + ->getResult(); + + // build job order array + $job_orders = []; + + foreach ($obj_rows as $jo) { + $service_type = CMBServiceType::getName($jo[0]->getServiceType()); + + $job_orders[] = [ + 'id' => $jo[0]->getID(), + 'text' => $jo['jo_label'] . ' - ' . $service_type + ]; + } + + $params['job_orders'] = $job_orders; + $params['has_more_pages'] = $has_more_pages; + + return $params; + } + + // creates/updates job order + public function generateJobOrder(Request $req, $id) + { + // initialize error list + $error_array = []; + + $em = $this->em; + + $jo = $em->getRepository(JobOrder::class)->find($id); + if (empty($jo)) + { + // new job order + $jo = new JobOrder(); + } + + // check if lat and lng are provided + if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { + $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + } + + // check if customer vehicle is set + if (empty($req->request->get('customer_vehicle'))) { + $error_array['customer_vehicle'] = 'No vehicle selected.'; + } else { + // get customer vehicle + $cust_vehicle = $em->getRepository(CustomerVehicle::class)->find($req->request->get('customer_vehicle')); + + if (empty($cust_vehicle)) { + $error_array['customer_vehicle'] = 'Invalid vehicle specified.'; + } + } + + if (empty($error_array)) { + // get current user + $user = $this->security->getUser(); + + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + $stype = $req->request->get('service_type'); + + // set and save values + $jo->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($stype) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setCustomer($cust_vehicle->getCustomer()) + ->setCustomerVehicle($cust_vehicle) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::PENDING) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')) + ->setORName($req->request->get('or_name')) + ->setPromoDetail($req->request->get('promo_detail')) + ->setModeOfPayment($req->request->get('mode_of_payment')) + ->setLandmark($req->request->get('landmark')); + + // check if user is null, meaning call to create came from API + if ($user != null) + { + $jo->setCreatedBy($user); + } + + // check if reference JO is set and validate + if (!empty($req->request->get('ref_jo'))) { + // get reference JO + $ref_jo = $em->getRepository(JobOrder::class)->find($req->request->get('ref_jo')); + + if (empty($ref_jo)) { + $error_array['ref_jo'] = 'Invalid reference job order specified.'; + } else { + $jo->setReferenceJO($ref_jo); + } + } + + // call service to generate job order and invoice + $invoice_items = $req->request->get('invoice_items', []); + $promo_id = $req->request->get('invoice_promo'); + $invoice_change = $req->request->get('invoice_change', 0); + + // check if invoice changed + if ($invoice_change) + { + $this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $error_array); + } + + // validate + $errors = $this->validator->validate($jo); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if errors are found + if (empty($error_array)) + { + // validated, no error. save the job order + $em->persist($jo); + + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::CREATE) + ->setJobOrder($jo); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + $em->flush(); + } + } + + return $error_array; + } + + public function processOneStepJobOrder(Request $req, $id) + { + // initialize error list + $error_array = []; + + $em = $this->em; + + $jo = $em->getRepository(JobOrder::class)->find($id); + if (empty($jo)) + { + // new job order + $jo = new JobOrder(); + } + + // check if lat and lng are provided + if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { + $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + } + + // check if new customer + if ($req->request->get('new_customer')) + { + if (empty($req->request->get('customer_customer_notes'))) + { + $error_array['customer_customer_notes'] = 'Customer notes cannot be null.'; + } + + $new_cust = new Customer(); + $new_cv = new CustomerVehicle(); + + // find the vehicle using vid + $new_vehicle = $em->getRepository(Vehicle::class)->find($req->request->get('vid')); + if (empty($new_vehicle)) + { + $error_array['cv_mfg'] = 'Invalid manufacturer specified.'; + $error_array['cv_make'] = 'Invalid make specified.'; + } + else + { + + $new_cust->setLastName($req->request->get('customer_last_name')) + ->setFirstName($req->request->get('customer_first_name')) + ->setPhoneMobile($req->request->get('customer_phone_mobile')) + ->setPhoneLandline($req->request->get('customer_phone_landline')) + ->setPhoneOffice($req->request->get('customer_phone_office')) + ->setPhoneFax($req->request->get('customer_phone_fax')) + ->setCustomerNotes($req->request->get('customer_customer_notes')); + + $new_cv->setCustomer($new_cust) + ->setVehicle($new_vehicle) + ->setPlateNumber($req->request->get('cv_plate')) + ->setModelYear($req->request->get('cv_year')) + ->setColor('') + ->setStatusCondition('') + ->setFuelType('') + ->setActive() + ->setWarrantyCode($req->request->get('warranty_code')); + + if (($req->request->get('service_type')) == CMBServiceType::BATTERY_REPLACEMENT_NEW) + { + $new_cv->setHasMotoliteBattery(true); + } + else + { + $new_cv->setHasMotoliteBattery(false); + } + + // link JO to new customer + $jo->setCustomer($new_cust); + $jo->setCustomerVehicle($new_cv); + + $em->persist($new_cust); + $em->persist($new_cv); + } + } + else + { + // check if customer vehicle is set + if (empty($req->request->get('customer_vehicle'))) { + $error_array['customer_vehicle'] = 'No vehicle selected.'; + } else + { + // get customer vehicle + $cust_vehicle = $em->getRepository(CustomerVehicle::class)->find($req->request->get('customer_vehicle')); + + if (empty($cust_vehicle)) { + $error_array['customer_vehicle'] = 'Invalid vehicle specified.'; + } + else + { + $jo->setCustomerVehicle($cust_vehicle); + $jo->setCustomer($cust_vehicle->getCustomer()); + + // save serial into cv + $cust_vehicle->setWarrantyCode($req->request->get('warranty_code')); + + $em->persist($cust_vehicle); + } + } + } + + // check if hub AND rider is selected + if ((empty($req->request->get('hub_id'))) && + (empty($req->request->get('rider_id')))) { + $error_array['hub'] = 'No hub selected.'; + } else { + if (empty($req->request->get('rider_id'))) { + $error_array['rider'] = 'No rider selected.'; + } else { + // get hub + $hub = $em->getRepository(Hub::class)->find($req->request->get('hub_id')); + + if (empty($hub)) { + $error_array['hub'] = 'Invalid hub specified.'; + } else { + // get rider + $rider = $em->getRepository(Rider::class)->find($req->request->get('rider_id')); + + if (empty($rider)) { + $error_array['rider'] = 'Invalid rider specified.'; + } + } + } + } + + if (empty($error_array)) + { + // get current user + $user = $this->security->getUser(); + + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + $stype = $req->request->get('service_type'); + + // set and save values + $jo->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($stype) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::ASSIGNED) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')) + ->setORName($req->request->get('or_name')) + ->setPromoDetail($req->request->get('promo_detail')) + ->setModeOfPayment($req->request->get('mode_of_payment')) + ->setLandmark($req->request->get('landmark')) + ->setHub($hub) + ->setRider($rider); + + // check if user is null, meaning call to create came from API + if ($user != null) + { + $jo->setCreatedBy($user); + } + + // check if reference JO is set and validate + if (!empty($req->request->get('ref_jo'))) { + // get reference JO + $ref_jo = $em->getRepository(JobOrder::class)->find($req->request->get('ref_jo')); + + if (empty($ref_jo)) { + $error_array['ref_jo'] = 'Invalid reference job order specified.'; + } else { + $jo->setReferenceJO($ref_jo); + } + } + + // call service to generate job order and invoice + $invoice_items = $req->request->get('invoice_items', []); + $promo_id = $req->request->get('invoice_promo'); + $invoice_change = $req->request->get('invoice_change', 0); + + // check if invoice changed + if ($invoice_change) + { + $this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $error_array); + } + + // validate + $errors = $this->validator->validate($jo); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if errors are found + if (empty($error_array)) + { + // validated, no error. save the job order + $em->persist($jo); + + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::CREATE) + ->setJobOrder($jo); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + $em->flush(); + } + } + + return $error_array; + } + + // dispatch job order + public function dispatchJobOrder(Request $req, int $id, MQTTClient $mclient) + { + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + $processor = $obj->getProcessedBy(); + $user = $this->security->getUser();; + + // check if we're the one processing, return error otherwise + if ($processor == null) + throw new AccessDeniedHttpException('Not the processor'); + + if ($processor != null && $processor->getID() != $user->getID()) + throw new AccessDeniedHttpException('Not the processor'); + + // initialize error list + $error_array = []; + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // check if cancelled already + if (!$obj->canDispatch()) + { + throw new NotFoundHttpException('Could not dispatch. Job Order is not pending.'); + // TODO: have this handled better, so UI shows the error + // $error_array['dispatch'] = 'Could not dispatch. Job Order is not pending.'; + } + + // check if lat and lng are provided + if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) + { + $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + } + + // check if hub is set + if (empty($req->request->get('hub'))) + { + $error_array['hub'] = 'No hub selected.'; + } + else + { + // get hub + $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); + + if (empty($hub)) + { + $error_array['hub'] = 'Invalid hub specified.'; + } + } + + // check facilitated type + $fac_type = $req->request->get('facilitated_type'); + if (!empty($fac_type)) + { + if (!FacilitatedType::validate($fac_type)) + $fac_type = null; + } + else + $fac_type = null; + + // check facilitated by + $fac_by_id = $req->request->get('facilitated_by'); + $fac_by = null; + if (!empty($fac_by_id)) + { + $fac_by = $em->getRepository(Hub::class)->find($fac_by_id); + if (empty($fac_by)) + $fac_by = null; + } + + if (empty($error_array)) + { + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::RIDER_ASSIGN) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')) + ->setFacilitatedType($fac_type) + ->setFacilitatedBy($fac_by) + ->setHub($hub); + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + if (empty($error_array)) + { + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::HUB_ASSIGN) + ->setJobOrder($obj); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + + // validated! save the entity + $em->flush(); + + // send event to mobile app + $payload = [ + 'event' => 'outlet_assign' + ]; + $mclient->sendEvent($obj, $payload); + } + + return $error_array; + } + + // assign job order + public function assignJobOrder(Request $req, $id) + { + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + + // initialize error list + $error_array = []; + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // check if we can assign + if (!$obj->canAssign()) + throw new NotFoundHttpException('Cannot assign rider to this job order.'); + + // check if lat and lng are provided + if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { + $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + } + + // check if rider is set + if (empty($req->request->get('rider'))) { + $error_array['rider'] = 'No rider selected.'; + } else { + // get rider + $rider = $em->getRepository(Rider::class)->find($req->request->get('rider')); + + if (empty($rider)) { + $error_array['rider'] = 'Invalid rider specified.'; + } + } + + // get current user + $user = $this->security->getUser(); + + if (empty($error_array)) { + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::ASSIGNED) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')) + ->setDateAssign(new DateTime()) + ->setRider($rider); + + if ($user != null) + { + $obj->setAssignedBy($user); + } + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + if (empty($error_array)) + { + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ASSIGN) + ->setJobOrder($obj); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + + // validated! save the entity + $em->flush(); + + // call rider assignment handler's assignJobOrder + $this->rah->assignJobOrder($obj, $rider); + } + + return $error_array; + } + + // fulfill job order + public function fulfillJobOrder(Request $req, $id) + { + // initialize error list + $error_array = []; + + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // check if lat and lng are provided + if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { + $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + } + + if (empty($error_array)) { + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setSource($req->request->get('source')) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')); + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + $obj->fulfill(); + + if (empty($error_array)) + { + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::FULFILL) + ->setJobOrder($obj); + + // get current user + $user = $this->security->getUser(); + if ($user != null) + { + $event->setUser($user); + } + + $event->setUser($user); + $em->persist($event); + + // save to customer vehicle battery record + $this->updateVehicleBattery($obj); + + // save serial to customer vehicle + $cust_vehicle = $obj->getCustomerVehicle(); + $cust_vehicle->setWarrantyCode($req->request->get('warranty_code')); + + $em->persist($cust_vehicle); + + // get rider + $rider = $obj->getRider(); + + $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/assets/images/user.gif'; + if ($rider->getImageFile() != null) + $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/uploads/' . $rider->getImageFile(); + + // call rider assignment handler's fulfillJobOrder + $this->rah->fulfillJobOrder($obj, $image_url, $rider); + + // create the warranty if new battery only + if ($this->checkIfNewBattery($obj)) + { + $serial = $req->request->get('warranty_code') ; + $warranty_class = $obj->getWarrantyClass(); + $first_name = $obj->getCustomer()->getFirstName(); + $last_name = $obj->getCustomer()->getLastName(); + $mobile_number = $obj->getCustomer()->getPhoneMobile(); + + // check if date fulfilled is null + if ($obj->getDateFulfill() == null) + $date_purchase = $obj->getDateCreate(); + else + $date_purchase = $obj->getDateFulfill(); + + // validate plate number + // $plate_number = $this->wh->cleanPlateNumber($jo->getCustomerVehicle()->getPlateNumber()); + $plate_number = Warranty::cleanPlateNumber($obj->getCustomerVehicle()->getPlateNumber()); + if ($plate_number != false) + { + $batt_list = array(); + $invoice = $obj->getInvoice(); + if (!empty($invoice)) + { + // get battery + $invoice_items = $invoice->getItems(); + foreach ($invoice_items as $item) + { + $battery = $item->getBattery(); + if ($battery != null) + { + $batt_list[] = $item->getBattery(); + } + } + } + + $this->wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class); + } + } + + // validated! save the entity + $em->flush(); + } + } + + // cancel job order + public function cancelJobOrder(Request $req, int $id, MQTTClient $mclient) + { + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + $cancel_reason = $req->request->get('cancel_reason'); + $obj->cancel($cancel_reason); + + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::CANCEL) + ->setJobOrder($obj); + + // get current user + $user = $this->security->getUser(); + if ($user != null) + { + $event->setUser($user); + } + + $event->setUser($user); + + $em->persist($event); + + // save + $em->flush(); + + // send mobile app event + $payload = [ + 'event' => 'cancelled', + 'reason' => $cancel_reason, + 'jo_id' => $obj->getID(), + ]; + $mclient->sendEvent($obj, $payload); + $mclient->sendRiderEvent($obj, $payload); + } + + // set hub for job order + public function setHub($req, $id, $mclient) + { + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + $user = $this->security->getUser(); + + // initialize error list + $error_array = []; + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // check if lat and lng are provided + if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { + $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + } + + // check if hub is set + if (empty($req->request->get('hub'))) { + $error_array['hub'] = 'No hub selected.'; + } else { + // get hub + $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); + + if (empty($hub)) { + $error_array['hub'] = 'Invalid hub specified.'; + } + } + + if (empty($error_array)) + { + // rider mqtt event + // NOTE: need to send this before saving because rider will be cleared + $rider_payload = [ + 'event' => 'cancelled', + 'reason' => 'Reassigned', + 'jo_id' => $obj->getID(), + ]; + $mclient->sendRiderEvent($obj, $rider_payload); + + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::RIDER_ASSIGN) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')) + ->setHub($hub) + ->clearRider(); + + if ($user != null) + { + $obj->setProcessedBy($user); + } + + $em->persist($obj); + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + // check if any errors were found + if (empty($error_array)) { + + // add event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::HUB_ASSIGN) + ->setJobOrder($obj); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + + // validated! save the entity + $em->flush(); + + // user mqtt event + $payload = [ + 'event' => 'outlet_assign' + ]; + $mclient->sendEvent($obj, $payload); + } + + return $error_array; + } + + // reject hub for job order + public function rejectHub($req, $id) + { + // get object data + $em = $this->em; + $jo = $em->getRepository(JobOrder::class)->find($id); + $processor = $jo->getProcessedBy(); + $user = $this->security->getUser(); + + // check if we're the one processing, return error otherwise + if ($processor == null) + throw new AccessDeniedHttpException('Not the processor'); + + if ($user != null) + { + if ($processor != null && $processor->getID() != $user->getID()) + throw new AccessDeniedHttpException('Not the processor'); + } + + // initialize error list + $error_array = []; + + // make sure job order exists + if (empty($jo)) + throw new NotFoundHttpException('The item does not exist'); + + // check if hub is set + if (empty($req->request->get('hub'))) + { + $error_array['hub'] = 'No hub selected.'; + } + else + { + // get hub + $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); + + if (empty($hub)) + { + $error_array['hub'] = 'Invalid hub specified.'; + } + } + + // check if this hub has already been rejected on this job order + $robj = $em->getRepository(JORejection::class)->findOneBy([ + 'job_order' => $jo, + 'hub' => $hub + ]); + + if (!empty($robj)) + $error_array['hub'] = 'This hub has already been rejected for the current job order.'; + + // check if reason is set + if (empty($req->request->get('reason'))) + $error_array['reason'] = 'No reason selected.'; + else if (!JORejectionReason::validate($req->request->get('reason'))) + $error_array['reason'] = 'Invalid reason specified.'; + + if (empty($error_array)) + { + // coordinates + $obj = new JORejection(); + + // set and save values + $obj->setDateCreate(new DateTime()) + ->setHub($hub) + ->setJobOrder($jo) + ->setReason($req->request->get('reason')) + ->setRemarks($req->request->get('remarks')) + ->setContactPerson($req->request->get('contact_person')); + + if ($user != null) + { + $obj->setUser($user); + } + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + if (empty($error_array)) + { + // validated! save the entity + $em->persist($obj); + $em->flush(); + } + + return $error_array; + + } + + // set rider for job order + public function setRider($req, $id, $mclient) + { + // initialize error list + $error_array = []; + + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + $user = $this->security->getUser(); + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // check if lat and lng are provided + if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { + $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + } + + // check if rider is set + if (empty($req->request->get('rider'))) { + $error_array['rider'] = 'No rider selected.'; + } else { + // get rider + $rider = $em->getRepository(Rider::class)->find($req->request->get('rider')); + + if (empty($rider)) { + $error_array['rider'] = 'Invalid rider specified.'; + } + } + + if (empty($error_array)) { + // rider mqtt event + // NOTE: need to send this before saving because rider will be cleared + $rider_payload = [ + 'event' => 'cancelled', + 'reason' => 'Reassigned', + 'jo_id' => $obj->getID(), + ]; + $mclient->sendRiderEvent($obj, $rider_payload); + + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::ASSIGNED) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')) + ->setDateAssign(new DateTime()) + ->setRider($rider); + + if ($user != null) + { + $obj->setAssignedBy($user); + } + + // validate + $errors = $this->validator->validate($obj); + + $em->persist($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + // check if any errors were found + if (empty($error_array)) + { + // add event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ASSIGN) + ->setJobOrder($obj); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + + // validated! save the entity + $em->flush(); + + // send event to mobile app + $payload = [ + 'event' => 'driver_assigned' + ]; + $mclient->sendEvent($obj, $payload); + $mclient->sendRiderEvent($obj, $payload); + } + + return $error_array; + } + + // unlock processor + public function unlockProcessor($id) + { + // clear lock + $em = $this->em; + $jo = $em->getRepository(JobOrder::class)->find($id); + if ($jo != null) + { + $jo->setProcessedBy(null); + + $em->flush(); + } + } + + // unlock assignor + public function unlockAssignor($id) + { + // clear lock + $em = $this->em; + $jo = $em->getRepository(JobOrder::class)->find($id); + if ($jo != null) + { + $jo->setAssignedBy(null); + + $em->flush(); + } + + } + + + // initialize incoming job order form + public function initializeIncomingForm() + { + $params['obj'] = new JobOrder(); + $params['mode'] = 'create'; + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_incoming_form'); + + // return params + return $params; + } + + public function initializeOneStepForm() + { + $params['obj'] = new JobOrder(); + $params['mode'] = 'onestep'; + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_onestep'); + + // return params + return $params; + } + + public function initializeOneStepEditForm($id, $map_tools) + { + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + + $params['obj'] = $obj; + $params['mode'] = 'onestep-edit'; + $params['cvid'] = $obj->getCustomerVehicle()->getID(); + $params['vid'] = $obj->getCustomerVehicle()->getVehicle()->getID(); + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get the hubs + // TODO: move this snippet to a function + $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); + + $params['hubs'] = []; + + // format duration and distance into friendly time + foreach ($hubs as $hub) { + // duration + $seconds = $hub['duration']; + + if (!empty($seconds) && $seconds > 0) { + $hours = floor($seconds / 3600); + $minutes = ceil(($seconds / 60) % 60); + + $hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); + } else { + $hub['duration'] = false; + } + + // distance + $meters = $hub['distance']; + + if (!empty($meters) && $meters > 0) { + $hub['distance'] = round($meters / 1000) . " km"; + } else { + $hub['distance'] = false; + } + + // counters + $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); + $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); + + // check for rejection + $hub['flag_rejected'] = false; + $hub_id = $hub['hub']->getID(); + + $params['hubs'][] = $hub; + } + + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_onestep_edit_form'); + + return $params; + } + + // initialize open edit job order form + public function initializeOpenEditForm($id) + { + $em = $this->em; + $jo = $em->getRepository(JobOrder::class)->find($id); + + $params['obj'] = $jo; + $params['mode'] = 'open_edit'; + $params['cvid'] = $jo->getCustomerVehicle()->getID(); + $params['vid'] = $jo->getCustomerVehicle()->getVehicle()->getID(); + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_open_edit_form'); + + return $params; + } + + // initialize incoming vehicle form + public function initializeIncomingVehicleForm(int $cvid) + { + $params['mode'] = 'create_vehicle'; + $params['cvid'] = $cvid; + + $em = $this->em; + + // get customer vehicle + $cv = $em->getRepository(CustomerVehicle::class)->find($cvid); + $params['vid'] = $cv->getVehicle()->getID(); + + // make sure this customer vehicle exists + if (empty($cv)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not exist'); + } + + $jo = new JobOrder(); + $jo->setCustomerVehicle($cv) + ->setCustomer($cv->getCustomer()); + + $params['obj'] = $jo; + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_incoming_vehicle_form'); + + return $params; + } + + // initialize all job orders form for a specific job order id + public function initializeAllForm($id) + { + $em = $this->em; + + $params['mode'] = 'update-all'; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + throw new NotFoundHttpException('The job order does not exist'); + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_all_form'); + + $params['obj'] = $obj; + $params['status_cancelled'] = JOStatus::CANCELLED; + + // timeline stuff (descending by time) + $params['timeline'] = [ + [ + 'date' => date("M j"), + 'time' => date("g:i A"), + 'event' => "Event 4", + 'color' => "#f4516c" + ], + [ + 'date' => date("M j"), + 'time' => date("g:i A"), + 'event' => "Event 3", + 'color' => "#34bfa3" + ], + [ + 'date' => date("M j"), + 'time' => date("g:i A"), + 'event' => "Event 2", + 'color' => "#716aca" + ], + [ + 'date' => date("M j"), + 'time' => date("g:i A"), + 'event' => "Event 1", + 'color' => "#ffb822" + ], + ]; + + return $params; + } + + // initialize dispatch/processing job order form + public function initializeProcessingForm($id, $map_tools) + { + $em = $this->em; + + // manual transaction since we're locking + $em->getConnection()->beginTransaction(); + + try + { + // lock and get data + $obj = $em->getRepository(JobOrder::class)->find($id, LockMode::PESSIMISTIC_READ); + + // make sure this job order exists + if (empty($obj)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not exist'); + } + + // check status + if ($obj->getStatus() != JOStatus::PENDING) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not have a pending status'); + } + + // check if we are the processor + $processor = $obj->getProcessedBy(); + // get current user + $user = $this->security->getUser(); + if ($user != null) + { + // TODO: go back to list page and display alert / flash that says they cannot access it because they + // are not the processor + if ($processor != null && $processor->getID() != $user->getID()) + { + $em->getConnection()->rollback(); + throw new AccessDeniedHttpException('Not the processor'); + } + + // make this user be the processor + $obj->setProcessedBy($user); + } + $em->flush(); + + $em->getConnection()->commit(); + } + catch(PessimisticLockException $e) + { + throw new AccessDeniedHttpException('Not the processor'); + } + + // NOTE: we are able to lock, everything should be fine now + + $params['mode'] = 'update-processing'; + $params['status_cancelled'] = JOStatus::CANCELLED; + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get rejections + $rejections = $obj->getHubRejections(); + + // get rejection reasons + $params['rejection_reasons'] = JORejectionReason::getCollection(); + + // get closest hubs + $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); + + $params['hubs'] = []; + + // format duration and distance into friendly time + foreach ($hubs as $hub) { + // duration + $seconds = $hub['duration']; + + if (!empty($seconds) && $seconds > 0) { + $hours = floor($seconds / 3600); + $minutes = ceil(($seconds / 60) % 60); + + $hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); + } else { + $hub['duration'] = false; + } + + // distance + $meters = $hub['distance']; + + if (!empty($meters) && $meters > 0) { + $hub['distance'] = round($meters / 1000) . " km"; + } else { + $hub['distance'] = false; + } + + // counters + $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); + $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); + + // check for rejection + $hub['flag_rejected'] = false; + $hub_id = $hub['hub']->getID(); + + foreach ($rejections as $robj) + { + if ($robj->getHub()->getID() === $hub_id) + { + $hub['flag_rejected'] = true; + break; + } + } + + $params['hubs'][] = $hub; + } + + $params['obj'] = $obj; + // get template to display + $params['template'] = $this->getTwigTemplate('jo_processing_form'); + + return $params; + } + + // initialize assign job order form + public function initializeAssignForm($id) + { + $em = $this->em; + + // manual transaction since we're locking + $em->getConnection()->beginTransaction(); + + $params['mode'] = 'update-assigning'; + + try + { + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not exist'); + } + + // check status + if ($obj->getStatus() != JOStatus::RIDER_ASSIGN) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not have an assigning status'); + } + + // check if super user + $user = $this->security->getUser(); + if ($user != null) + { + if ($user->isSuperAdmin()) + { + // do nothing, just allow page to be accessed + } + else + { + // check if hub is assigned to current user + $user_hubs = $user->getHubs(); + if (!in_array($obj->getHub()->getID(), $user_hubs)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order is not on a hub assigned to this user'); + } + + // check if we are the assignor + $assignor = $obj->getAssignedBy(); + + if ($assignor != null && $assignor->getID() != $user->getID()) + { + $em->getConnection()->rollback(); + throw new AccessDeniedHttpException('Not the assignor'); + } + + // make this user be the assignor + $obj->setAssignedBy($user); + } + } + + $em->flush(); + + $em->getConnection()->commit(); + } + catch (PessimisticLockException $e) + { + throw new AccessDeniedHttpException('Not the assignor'); + } + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_assigning_form'); + $params['obj'] = $obj; + $params['status_cancelled'] = JOStatus::CANCELLED; + + return $params; + } + + // initialize fulflll job order form + public function initializeFulfillmentForm($id) + { + $em = $this->em; + + $params['mode'] = 'update-fulfillment'; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + { + throw new NotFoundHttpException('The job order does not exist'); + } + + // check status + if (!in_array($obj->getStatus(), [JOStatus::ASSIGNED, JOStatus::IN_PROGRESS])) + { + throw new NotFoundHttpException('The job order does not have a fulfillment status'); + } + + // get current user + $user = $this->security->getUser(); + + // check if hub is assigned to current user + $user_hubs = $user->getHubs(); + if (!in_array($obj->getHub()->getID(), $user_hubs)) + { + throw new NotFoundHttpException('The job order is not on a hub assigned to this user'); + } + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_fulfillment_form'); + $params['obj'] = $obj; + $params['status_cancelled'] = JOStatus::CANCELLED; + + return $params; + } + + // initialize hub form + public function initializeHubForm($id, $map_tools) + { + $em = $this->em; + + $params['mode'] = 'update-reassign-hub'; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + { + throw new NotFoundHttpException('The job order does not exist'); + } + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get rejections + $rejections = $obj->getHubRejections(); + + // get rejection reasons + $params['rejection_reasons'] = JORejectionReason::getCollection(); + + // get closest hubs + $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); + + $params['status_cancelled'] = JOStatus::CANCELLED; + $params['hubs'] = []; + + // format duration and distance into friendly time + foreach ($hubs as $hub) { + // duration + $seconds = $hub['duration']; + + if (!empty($seconds) && $seconds > 0) { + $hours = floor($seconds / 3600); + $minutes = ceil(($seconds / 60) % 60); + + $hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); + } else { + $hub['duration'] = false; + } + + // distance + $meters = $hub['distance']; + + if (!empty($meters) && $meters > 0) { + $hub['distance'] = round($meters / 1000) . " km"; + } else { + $hub['distance'] = false; + } + + // counters + $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); + $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); + + // check for rejection + $hub['flag_rejected'] = false; + $hub_id = $hub['hub']->getID(); + + foreach ($rejections as $robj) + { + if ($robj->getHub()->getID() === $hub_id) + { + $hub['flag_rejected'] = true; + break; + } + } + + $params['hubs'][] = $hub; + } + + $params['obj'] = $obj; + // get template to display + $params['template'] = $this->getTwigTemplate('jo_open_hub_form'); + + return $params; + } + + // initialize rider form + public function initializeRiderForm($id) + { + $em = $this->em; + + $params['mode'] = 'update-reassign-rider'; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not exist'); + } + + // check status + if ($obj->getStatus() == JOStatus::PENDING) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not have an assigned hub'); + } + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + $params['obj'] = $obj; + $params['status_cancelled'] = JOStatus::CANCELLED; + // get template to display + $params['template'] = $this->getTwigTemplate('jo_open_rider_form'); + + return $params; + } + + // generate pdf form for job order + public function generatePDFForm($req, $id, $proj_path) + { + $em = $this->em; + $translator = $this->translator; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + throw new NotFoundHttpException('The job order does not exist'); + + // set output filename + $filename = 'job-order-' . $obj->getID() . '.pdf'; + + // translate the title and the logo for the pdf + $translated_title = $translator->trans('jo_title_pdf'); + $translated_logo = $translator->trans('image_jo_pdf'); + $translated_delivery_instructions_label = $translator->trans('delivery_instructions_label'); + + // generate the pdf + $pdf = new FPDF('P', 'mm', 'letter'); + $pdf->AddPage(); + $pdf->setTitle($translated_title . ' #' . $obj->getID()); + $pdf->SetFillColor(211, 211, 211); + + // style defaults + $margin = 10; + $page_width = $pdf->GetPageWidth() - ($margin * 2); + $table_col_width = $page_width / 12; + $line_height = 5; + $jo_line_height = 10; + $table_line_height = 7; + $font_face = 'Arial'; + $body_font_size = 9; + $header_font_size = 9; + $jo_font_size = 16; + $col1_x = $margin; + $col2_x = 120; + $label_width = 40; + $val_width = 60; + + // insert the logo + $image_path = $proj_path . $translated_logo; + $pdf->Image($image_path, $col1_x, 10); + + // insert JO number + $pdf->SetFont($font_face, 'B', $jo_font_size); + $pdf->SetX($col2_x); + $pdf->Cell($label_width, $jo_line_height, 'JO Number:'); + $pdf->SetTextColor(9, 65, 150); + $pdf->Cell(0, $jo_line_height, $obj->getID()); + + // insert customer info + $customer = $obj->getCustomer(); + $pdf->SetFont($font_face, '', $body_font_size); + $pdf->SetTextColor(0, 0, 0); + + $pdf->Ln($line_height * 7); + + // get current Y + $y = $pdf->GetY(); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Customer Name:'); + $pdf->MultiCell($val_width, $line_height, $customer ? $customer->getFirstName() . ' ' . $customer->getLastName() : '', 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Mobile Phone:'); + $pdf->MultiCell(0, $line_height, $customer && $customer->getPhoneMobile() ? $this->country_code . $customer->getPhoneMobile() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Delivery Date:'); + $pdf->MultiCell($val_width, $line_height, $obj->getDateSchedule() ? $obj->getDateSchedule()->format("m/d/Y") : '', 0, 'left'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Landline:'); + $pdf->MultiCell(0, $line_height, $customer && $customer->getPhoneLandline() ? $this->country_code . $customer->getPhoneLandline() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Office Phone:'); + $pdf->MultiCell(0, $line_height, $customer && $customer->getPhoneOffice() ? $this->country_code . $customer->getPhoneOffice() : '', 0, 'L'); + + $pdf->SetX($col2_x); + $pdf->Cell($label_width, $line_height, 'Fax:'); + $pdf->MultiCell($val_width, $line_height, $customer && $customer->getPhoneFax() ? $this->country_code . $customer->getPhoneFax() : '', 0, 'L'); + + // insert vehicle info + $cv = $obj->getCustomerVehicle(); + $vehicle = $cv->getVehicle(); + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($label_width, $line_height, 'Vehicle Details'); + $pdf->Ln($line_height * 2); + + // get current Y + $y = $pdf->GetY(); + + $pdf->SetFont($font_face, '', $body_font_size); + $pdf->Cell($label_width, $line_height, 'Plate Number:'); + $pdf->MultiCell($val_width, $line_height, $cv ? $cv->getPlateNumber() : '', 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Vehicle Color:'); + $pdf->MultiCell(0, $line_height, $cv ? $cv->getColor() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Brand:'); + $pdf->MultiCell($val_width, $line_height, $vehicle && $vehicle->getManufacturer() ? $vehicle->getManufacturer()->getName() : '', 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Model / Year:'); + $pdf->MultiCell(0, $line_height, $cv ? $cv->getModelYear() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Make:'); + $pdf->MultiCell($val_width, $line_height, $vehicle ? $vehicle->getMake() : '', 0, 'L'); + + // insert battery info + $battery = $cv->getCurrentBattery(); + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($label_width, $line_height, 'Battery Details'); + $pdf->Ln($line_height * 2); + + $pdf->SetFont($font_face, '', $body_font_size); + + // get current Y + $y = $pdf->GetY(); + + $pdf->Cell($label_width, $line_height, 'Current Battery:'); + $pdf->MultiCell($val_width, $line_height, $battery && $battery->getManufacturer() && $battery->getModel() && $battery->getSize() ? $battery->getManufacturer()->getName() . ' ' . $battery->getModel()->getName() . ' ' . $battery->getSize()->getName() . ' (' . $battery->getProductCode() . ')' : '', 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Serial Number:'); + $pdf->MultiCell(0, $line_height, $cv ? $cv->getWarrantyCode() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Wty. Exp. Date:'); + $pdf->MultiCell($val_width, $line_height, $cv && $cv->getWarrantyExpiration() ? $cv->getWarrantyExpiration()->format("d/m/Y") : '', 0, 'L'); + + // insert transaction details + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($label_width, $line_height, 'Transaction Details'); + $pdf->Ln($line_height * 2); + + $pdf->SetFont($font_face, '', $body_font_size); + + // get current Y + $y = $pdf->GetY(); + + $pdf->Cell($label_width, $line_height, 'Warranty Class:'); + $pdf->MultiCell($val_width, $line_height, CMBWarrantyClass::getName($obj->getWarrantyClass()), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Mode of Payment:'); + $pdf->MultiCell(0, $line_height, CMBModeOfPayment::getName($obj->getModeOfPayment()), 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->Cell($label_width, $line_height, 'Delivery Address:'); + $pdf->MultiCell($val_width, $line_height, $obj->getDeliveryAddress(), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Landmark:'); + $pdf->MultiCell(0, $line_height, $obj->getLandMark(), 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Dispatch Time:'); + $pdf->MultiCell($val_width, $line_height, $obj->getDateSchedule() ? $obj->getDateSchedule()->format("g:i A") : '', 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Dispatched By:'); + $pdf->MultiCell(0, $line_height, $obj->getProcessedBy() ? $obj->getProcessedBy()->getFullName() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + // insert delivery instructions + $pdf->SetY($y); + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell(0, $line_height, $translated_delivery_instructions_label); + $pdf->Ln(); + + $pdf->SetFont($font_face, '', $body_font_size); + $pdf->MultiCell(0, $line_height, $obj->getDeliveryInstructions(), 1, 'L'); + + // insert invoice details + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($label_width, $line_height, 'Invoice Details'); + $pdf->Ln(); + + // invoice table headers + $invoice = $obj->getInvoice(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($table_col_width * 6, $table_line_height, 'Item', 1, 0, 'L', 1); + $pdf->Cell($table_col_width * 2, $table_line_height, 'Quantity', 1, 0, 'R', 1); + $pdf->Cell($table_col_width * 2, $table_line_height, 'Unit Price', 1, 0, 'R', 1); + $pdf->Cell($table_col_width * 2, $table_line_height, 'Amount', 1, 1, 'R', 1); + $pdf->SetFont($font_face, '', $body_font_size); + + // build invoice items table + if ($invoice && !empty($invoice->getItems())) + { + foreach ($invoice->getItems() as $item) + { + $pdf->Cell($table_col_width * 6, $table_line_height, $item->getTitle(), 1); + $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getQuantity()), 1, 0, 'R'); + $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getPrice(), 2), 1, 0, 'R'); + $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getPrice() * $item->getQuantity(), 2), 1, 1, 'R'); + } + } + else + { + $pdf->Cell($table_col_width * 12, 7, 'No items', 1, 1); + } + + $pdf->Ln($line_height * 2); + + // get current Y + $y = $pdf->GetY(); + + // insert invoice footer details + $pdf->Cell($label_width, $line_height, 'Transaction Type:'); + $pdf->MultiCell($val_width, $line_height, CMBServiceType::getName($obj->getServiceType()), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->SetFont($font_face, 'B'); + $pdf->Cell($label_width, $line_height, 'SUBTOTAL:'); + $pdf->SetFont($font_face, ''); + $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getVATExclusivePrice(), 2) : '', 0, 'R'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'OR Name:'); + $pdf->MultiCell($val_width, $line_height, $obj->getORName(), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->SetFont($font_face, 'B'); + $pdf->Cell($label_width, $line_height, 'TAX:'); + $pdf->SetFont($font_face, ''); + $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getVAT(), 2) : '', 0, 'R'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Emp. ID/Card No./Ref. By:'); + $pdf->MultiCell($val_width, $line_height, $obj->getPromoDetail(), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->SetFont($font_face, 'B'); + $pdf->Cell($label_width, $line_height, 'DISCOUNT:'); + $pdf->SetFont($font_face, ''); + $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getDiscount(), 2) : '', 0, 'R'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Discount Type:'); + $pdf->MultiCell($val_width, $line_height, $invoice && $invoice->getPromo() ? $invoice->getPromo()->getName() : '', 0, 'L'); + + $pdf->SetXY($col2_x, $y); + $pdf->SetFont($font_face, 'B'); + $pdf->Cell($label_width, $line_height, 'FINAL AMOUNT:'); + $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getTotalPrice(), 2) : '', 0, 'R'); + $pdf->SetFont($font_face, ''); + + $params['obj'] = $pdf; + $params['filename'] = $filename; + + return $params; + } + + public function getTwigTemplate($id) + { + if (isset($this->template_hash[$id])) + { + return $this->template_hash[$id]; + } + + return null; + } + + public function updateVehicleBattery(JobOrder $jo) + { + // check if new battery + if (!($this->checkIfNewBattery($jo))) + return; + + // customer vehicle + $cv = $jo->getCustomerVehicle(); + if ($cv == null) + return; + + // invoice + $invoice = $jo->getInvoice(); + if ($invoice == null) + return; + + // invoice items + $items = $invoice->getItems(); + if (count($items) <= 0) + return; + + // get first battery from invoice + $battery = null; + foreach ($items as $item) + { + $battery = $item->getBattery(); + if ($battery != null) + break; + } + + // no battery in order + if ($battery == null) + return; + + // warranty expiration + // use GetWarrantyPrivate for passenger warranty + $warr_months = 0; + $warr = $jo->getWarrantyClass(); + if ($warr == CMBWarrantyClass::WTY_PASSENGER) + $warr_months = $battery->getWarrantyPrivate(); + else if ($warr == CMBWarrantyClass::WTY_COMMERCIAL) + $warr_months = $battery->getWarrantyCommercial(); + + $warr_date = new DateTime(); + $warr_date->add(new DateInterval('P' . $warr_months . 'M')); + + // update customer vehicle battery + $cv->setCurrentBattery($battery) + ->setHasMotoliteBattery(true) + ->setWarrantyExpiration($warr_date); + } + + public function getOtherParameters() + { + // get riders for dropdown + $params['riders'] = $this->em->getRepository(Rider::class)->findAll(); + + return $params; + } + + public function checkIfNewBattery(JobOrder $jo) + { + if ($jo->getServiceType() == CMBServiceType::BATTERY_REPLACEMENT_NEW) + return true; + + return false; + } + + protected function fillDropdownParameters(&$params) + { + $em = $this->em; + + // db loaded + $params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); + $params['promos'] = $em->getRepository(Promo::class)->findAll(); + + // list of hubs + $hubs = $em->getRepository(Hub::class)->findBy([], ['name' => 'ASC']); + $fac_hubs = []; + foreach ($hubs as $hub) + { + $fac_hubs[$hub->getID()] = $hub->getName() . ' - ' . $hub->getBranch(); + } + + // name values + $params['service_types'] = CMBServiceType::getCollection(); + $params['warranty_classes'] = CMBWarrantyClass::getCollection(); + $params['modes_of_payment'] = CMBModeOfPayment::getCollection(); + $params['statuses'] = JOStatus::getCollection(); + $params['discount_apply'] = DiscountApply::getCollection(); + $params['trade_in_types'] = CMBTradeInType::getCollection(); + $params['facilitated_types'] = FacilitatedType::getCollection(); + $params['facilitated_hubs'] = $fac_hubs; + $params['sources'] = TransactionOrigin::getCollection(); + } + + protected function initFormTags(&$params) + { + // default to editing, as we have more forms editing than creating + $params['ftags'] = [ + 'title' => 'Job Order Form', + 'vehicle_dropdown' => false, + 'invoice_edit' => false, + 'set_map_coordinate' => true, + 'preset_vehicle' => false, + 'ticket_table' => true, + 'cancel_button' => true, + ]; + } + + protected function fillFormTags(&$params) + { + $this->initFormTags($params); + + switch ($params['mode']) + { + case 'create': + $params['ftags']['vehicle_dropdown'] = true; + $params['ftags']['set_map_coordinate'] = false; + $params['ftags']['invoice_edit'] = true; + $params['ftags']['ticket_table'] = false; + $params['ftags']['cancel_button'] = false; + break; + case 'create_vehicle': + $params['ftags']['set_map_coordinate'] = false; + $params['ftags']['invoice_edit'] = true; + $params['ftags']['preset_vehicle'] = true; + $params['ftags']['ticket_table'] = false; + $params['ftags']['cancel_button'] = false; + break; + case 'open_edit': + $params['ftags']['invoice_edit'] = true; + $params['ftags']['preset_vehicle'] = true; + break; + case 'onestep': + $params['ftags']['vehicle_dropdown'] = true; + $params['ftags']['set_map_coordinate'] = false; + $params['ftags']['invoice_edit'] = true; + $params['ftags']['ticket_table'] = false; + $params['ftags']['cancel_button'] = false; + break; + case 'onestep-edit': + $params['ftags']['invoice_edit'] = true; + $params['ftags']['preset_vehicle'] = true; + break; + } + } + + protected function loadTemplates() + { + $this->template_hash = []; + + // add all twig templates for job order to hash + // TODO: put this in an array declaration + // $this->template_hash = [ + // 'blah' => 'blah', + // ]; + $this->template_hash['jo_incoming_form'] = 'job-order/cmb.form.html.twig'; + $this->template_hash['jo_open_edit_form'] = 'job-order/cmb.form.html.twig'; + $this->template_hash['jo_incoming_vehicle_form'] = 'job-order/cmb.form.html.twig'; + $this->template_hash['jo_processing_form'] = 'job-order/cmb.form.html.twig'; + $this->template_hash['jo_assigning_form'] = 'job-order/cmb.form.html.twig'; + $this->template_hash['jo_fulfillment_form'] = 'job-order/cmb.form.html.twig'; + $this->template_hash['jo_open_hub_form'] = 'job-order/cmb.form.html.twig'; + $this->template_hash['jo_open_rider_form'] = 'job-order/cmb.form.html.twig'; + $this->template_hash['jo_all_form'] = 'job-order/cmb.form.html.twig'; + $this->template_hash['jo_list_processing'] = 'job-order/list.processing.html.twig'; + $this->template_hash['jo_list_assigning'] = 'job-order/list.assigning.html.twig'; + $this->template_hash['jo_list_fulfillment'] = 'job-order/list.fulfillment.html.twig'; + $this->template_hash['jo_list_open'] = 'job-order/list.open.html.twig'; + $this->template_hash['jo_list_all'] = 'job-order/list.all.html.twig'; + $this->template_hash['jo_onestep'] = 'job-order/cmb.form.onestep.html.twig'; + $this->template_hash['jo_onestep_edit_form'] = 'job-order/cmb.form.onestep.html.twig'; + } + + protected function checkTier($tier) + { + // check specified tier + switch ($tier) { + case 'proc': + $tier_key = 'jo_proc'; + $tier_name = 'Dispatch'; + $rows_route = 'jo_proc_rows'; + $edit_route = 'jo_proc_form'; + $unlock_route = 'jo_proc_unlock'; + $jo_status = JOStatus::PENDING; + break; + case 'assign': + $tier_key = 'jo_assign'; + $tier_name = 'Assigning'; + $rows_route = 'jo_assign_rows'; + $edit_route = 'jo_assign_form'; + $unlock_route = 'jo_assign_unlock'; + $jo_status = JOStatus::RIDER_ASSIGN; + break; + case 'fulfill': + $tier_key = 'jo_fulfill'; + $tier_name = 'Fullfillment'; + $rows_route = 'jo_fulfill_rows'; + $edit_route = 'jo_fulfill_form'; + $unlock_route = ''; + $jo_status = [ + JOStatus::ASSIGNED, + JOStatus::IN_PROGRESS + ]; + break; + case 'open': + $tier_key = 'jo_open'; + $tier_name = 'Open'; + $rows_route = 'jo_open_rows'; + $edit_route = ''; + $unlock_route = ''; + $jo_status = [ + JOStatus::PENDING, + JOStatus::RIDER_ASSIGN, + JOStatus::ASSIGNED, + JOStatus::IN_PROGRESS, + JOStatus::IN_TRANSIT, + ]; + break; + case 'all': + $tier_key = 'jo_open'; + $tier_name = 'Open'; + $rows_route = 'jo_open_rows'; + $edit_route = 'jo_all_form'; + $unlock_route = ''; + $jo_status = ''; + break; + default: + throw new AccessDeniedHttpException('No access.'); + } + + // check acl + if (!($this->security->isGranted($tier_key . '.list'))) + throw new AccessDeniedHttpException('No access.'); + + // return params if allowed access + return [ + 'key' => $tier_key, + 'name' => $tier_name, + 'rows_route' => $rows_route, + 'edit_route' => $edit_route, + 'unlock_route' => $unlock_route, + 'jo_status' => $jo_status + ]; + } + + // TODO: re-enable search, figure out how to group the orWhere filters into one, so can execute that plus the pending filter + // check if datatable filter is present and append to query + protected function setQueryFilters($datatable, &$query, $qb, $hubs, $tier, $status) + { + switch ($tier) + { + case 'fulfill': + if (isset($datatable['query']['data-rows-search'])) + { + $query->innerJoin('q.cus_vehicle', 'cv') + ->innerJoin('q.customer', 'c') + ->where('cv.plate_number like :filter') + ->orWhere('c.phone_mobile like :filter') + ->orWhere('c.first_name like :filter or c.last_name like :filter') + ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); + } + if (isset($datatable['query']['rider'])) + { + $query->innerJoin('q.rider', 'r') + ->andWhere('r.id = :rider_id') + ->setParameter('rider_id', $datatable['query']['rider']); + } + if (isset($datatable['query']['schedule_date'])) + { + $start = $datatable['query']['schedule_date'][0] . ' ' . '00:00:00'; + $end = $datatable['query']['schedule_date'][1] . ' ' . '23:59:00'; + + $date_start = DateTime::createFromFormat('m/d/Y H:i:s', $start); + $date_end = DateTime::createFromFormat('m/d/Y H:i:s', $end); + + $query->andWhere('q.date_schedule >= :date_start') + ->andWhere('q.date_schedule <= :date_end') + ->setParameter('date_start', $date_start) + ->setParameter('date_end', $date_end); + } + + $query->andWhere('q.status IN (:statuses)') + ->andWhere('q.hub IN (:hubs)') + ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY) + ->setParameter('hubs', $hubs, Connection::PARAM_STR_ARRAY); + break; + case 'assign': + $query->where('q.status = :status') + ->andWhere('q.hub IN (:hubs)') + ->setParameter('status', $status) + ->setParameter('hubs', $hubs, Connection::PARAM_STR_ARRAY); + break; + case 'open': + if (isset($datatable['query']['data-rows-search'])) + { + $query->innerJoin('q.cus_vehicle', 'cv') + ->innerJoin('q.customer', 'c') + ->where('q.status IN (:statuses)') + ->andWhere('cv.plate_number like :filter or c.first_name like :filter or c.last_name like :filter or c.phone_mobile like :filter') + ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY) + ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); + } + else + { + $query->where('q.status IN (:statuses)') + ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY); + } + if (isset($datatable['query']['rider'])) + { + $query->innerJoin('q.rider', 'r') + ->andWhere('r.id = :rider_id') + ->setParameter('rider_id', $datatable['query']['rider']); + } + if (isset($datatable['query']['schedule_date'])) + { + $start = $datatable['query']['schedule_date'][0] . ' ' . '00:00:00'; + $end = $datatable['query']['schedule_date'][1] . ' ' . '23:59:00'; + + $date_start = DateTime::createFromFormat('m/d/Y H:i:s', $start); + $date_end = DateTime::createFromFormat('m/d/Y H:i:s', $end); + + $query->andWhere('q.date_schedule >= :date_start') + ->andWhere('q.date_schedule <= :date_end') + ->setParameter('date_start', $date_start) + ->setParameter('date_end', $date_end); + } + break; + case 'all': + if (isset($datatable['query']['data-rows-search'])) + { + $query->innerJoin('q.cus_vehicle', 'cv') + ->innerJoin('q.customer', 'c') + ->where('cv.plate_number like :filter') + ->orWhere('c.phone_mobile like :filter') + ->orWhere('c.first_name like :filter or c.last_name like :filter') + ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); + } + if (isset($datatable['query']['rider'])) + { + $query->innerJoin('q.rider', 'r') + ->andWhere('r.id = :rider_id') + ->setParameter('rider_id', $datatable['query']['rider']); + } + if (isset($datatable['query']['schedule_date'])) + { + $start = $datatable['query']['schedule_date'][0] . ' ' . '00:00:00'; + $end = $datatable['query']['schedule_date'][1] . ' ' . '23:59:00'; + + $date_start = DateTime::createFromFormat('m/d/Y H:i:s', $start); + $date_end = DateTime::createFromFormat('m/d/Y H:i:s', $end); + + $query->andWhere('q.date_schedule >= :date_start') + ->andWhere('q.date_schedule <= :date_end') + ->setParameter('date_start', $date_start) + ->setParameter('date_end', $date_end); + } + break; + default: + $query->where('q.status = :status') + ->setParameter('status', $status); + } + } +} diff --git a/src/Service/JobOrderHandler/ResqJobOrderHandler.php b/src/Service/JobOrderHandler/ResqJobOrderHandler.php new file mode 100644 index 00000000..c6a6fa01 --- /dev/null +++ b/src/Service/JobOrderHandler/ResqJobOrderHandler.php @@ -0,0 +1,2590 @@ +em = $em; + $this->ic = $ic; + $this->security = $security; + $this->validator = $validator; + $this->translator = $translator; + $this->rah = $rah; + $this->country_code = $country_code; + $this->wh = $wh; + + $this->loadTemplates(); + } + + // get job order rows + public function getRows(Request $req, $tier) + { + // check which job order tier is being called for and confirm access + $tier_params = $this->checkTier($tier); + + // get current user + $user = $this->security->getUser(); + if ($user == null) + throw new AccessDeniedHttpException('No access.'); + + $hubs = $user->getHubs(); + + // get query builder + $qb = $this->em->getRepository(JobOrder::class) + ->createQueryBuilder('q'); + + // get datatable params + $datatable = $req->request->get('datatable'); + + // count total records + $tquery = $qb->select('COUNT(q)'); + + $this->setQueryFilters($datatable, $tquery, $qb, $hubs, $tier, $tier_params['jo_status']); + + $total = $tquery->getQuery() + ->getSingleScalarResult(); + + // get current page number + $page = $datatable['pagination']['page'] ?? 1; + + $perpage = $datatable['pagination']['perpage']; + $offset = ($page - 1) * $perpage; + + // add metadata + $meta = [ + 'page' => $page, + 'perpage' => $perpage, + 'pages' => ceil($total / $perpage), + 'total' => $total, + 'sort' => 'asc', + 'field' => 'id' + ]; + + // build query + $qb = $this->em->getRepository(JobOrder::class) + ->createQueryBuilder('q'); + $query = $qb->select('q'); + + $this->setQueryFilters($datatable, $query, $qb, $hubs, $tier, $tier_params['jo_status']); + + // check if sorting is present, otherwise use default + if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) { + $order = $datatable['sort']['sort'] ?? 'asc'; + $query->orderBy('q.' . $datatable['sort']['field'], $order); + } else { + $query->orderBy('q.date_schedule', 'asc'); + } + + // get rows for this page + $query_obj = $query->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery(); + + // error_log($query_obj->getSQL()); + + $obj_rows = $query_obj->getResult(); + + $statuses = JOStatus::getCollection(); + $service_types = ServiceType::getCollection(); + + // process rows + $rows = []; + foreach ($obj_rows as $orow) { + // add row data + $row['id'] = $orow->getID(); + $row['customer_name'] = $orow->getCustomer()->getFirstName() . ' ' . $orow->getCustomer()->getLastName(); + $row['delivery_address'] = $orow->getDeliveryAddress(); + $row['date_schedule'] = $orow->getDateSchedule()->format("d M Y g:i A"); + $row['type'] = $orow->isAdvanceOrder() ? 'Advanced Order' : 'Immediate'; + $row['service_type'] = $service_types[$orow->getServiceType()]; + $row['status'] = $statuses[$orow->getStatus()]; + $row['flag_advance'] = $orow->isAdvanceOrder(); + $row['plate_number'] = $orow->getCustomerVehicle()->getPlateNumber(); + $row['is_mobile'] = $orow->getSource() == TransactionOrigin::MOBILE_APP; + + $processor = $orow->getProcessedBy(); + if ($processor == null) + $row['processor'] = ''; + else + $row['processor'] = $orow->getProcessedBy()->getFullName(); + + $assignor = $orow->getAssignedBy(); + if ($assignor == null) + $row['assignor'] = ''; + else + $row['assignor'] = $orow->getAssignedBy()->getFullName(); + + $rows[] = $row; + } + + $params['meta'] = $meta; + $params['rows'] = $rows; + $params['tier_params'] = $tier_params; + + return $params; + + } + + // get job orders + public function getJobOrders(Request $req) + { + // get search term + $term = $req->query->get('search'); + + // get querybuilder + $qb = $this->em->getRepository(JobOrder::class) + ->createQueryBuilder('q'); + + // build expression now since we're reusing it + $jo_label = $qb->expr()->concat($qb->expr()->literal('#'), 'q.id', $qb->expr()->literal(' - '), 'c.first_name', $qb->expr()->literal(' '), 'c.last_name', $qb->expr()->literal(' (Plate No: '), 'v.plate_number', $qb->expr()->literal(')')); + + // count total records + $tquery = $qb->select('COUNT(q)') + ->join('q.customer', 'c') + ->join('q.cus_vehicle', 'v'); + + // add filters to count query + if (!empty($term)) { + $tquery->where($jo_label . ' LIKE :filter') + ->setParameter('filter', '%' . $term . '%'); + } + + $total = $tquery->getQuery() + ->getSingleScalarResult(); + + // pagination vars + $page = $req->query->get('page') ?? 1; + $perpage = 20; + $offset = ($page - 1) * $perpage; + $pages = ceil($total / $perpage); + $has_more_pages = $page < $pages ? true : false; + + // build main query + $query = $qb->select('q') + ->addSelect($jo_label . ' as jo_label') + ->addSelect('c.first_name as cust_first_name') + ->addSelect('c.last_name as cust_last_name') + ->addSelect('v.plate_number as vehicle_plate_number'); + + // add filters if needed + if (!empty($term)) { + $query->where($jo_label . ' LIKE :filter') + ->setParameter('filter', '%' . $term . '%'); + } + + // get rows + $obj_rows = $query->orderBy('q.id', 'asc') + ->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery() + ->getResult(); + + // build job order array + $job_orders = []; + + foreach ($obj_rows as $jo) { + $service_type = ServiceType::getName($jo[0]->getServiceType()); + + $job_orders[] = [ + 'id' => $jo[0]->getID(), + 'text' => $jo['jo_label'] . ' - ' . $service_type + ]; + } + + $params['job_orders'] = $job_orders; + $params['has_more_pages'] = $has_more_pages; + + return $params; + } + + // creates/updates job order + public function generateJobOrder(Request $req, $id) + { + // initialize error list + $error_array = []; + + $em = $this->em; + + $jo = $em->getRepository(JobOrder::class)->find($id); + if (empty($jo)) + { + // new job order + $jo = new JobOrder(); + } + + // find customer + $cust_id = $req->request->get('cid'); + $customer = $em->getRepository(Customer::class)->find($cust_id); + if (empty($customer)) + { + $error_array['customer_vehicle'] = 'Invalid customer specified.'; + } + else + { + // get email, dpa_consent, promo_sms, and promo_email, if set + $customer->setEmail($req->request->get('customer_email')) + ->setPromoSms($req->request->get('flag_promo_sms', false)) + ->setPromoEmail($req->request->get('flag_promo_email', false)) + ->setDpaConsent($req->request->get('flag_dpa_consent', false)); + } + + // check if lat and lng are provided + if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { + $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + } + + // check if customer vehicle is set + if (empty($req->request->get('customer_vehicle'))) { + $error_array['customer_vehicle'] = 'No vehicle selected.'; + } else { + // get customer vehicle + $cust_vehicle = $em->getRepository(CustomerVehicle::class)->find($req->request->get('customer_vehicle')); + + if (empty($cust_vehicle)) { + $error_array['customer_vehicle'] = 'Invalid vehicle specified.'; + } + } + + if (empty($error_array)) { + // get current user + $user = $this->security->getUser(); + + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + $stype = $req->request->get('service_type'); + + // set and save values + $jo->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($stype) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setCustomer($cust_vehicle->getCustomer()) + ->setCustomerVehicle($cust_vehicle) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::PENDING) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')) + ->setORName($req->request->get('or_name')) + ->setPromoDetail($req->request->get('promo_detail')) + ->setModeOfPayment($req->request->get('mode_of_payment')) + ->setLandmark($req->request->get('landmark')); + + // check if user is null, meaning call to create came from API + if ($user != null) + { + $jo->setCreatedBy($user); + } + + // check if reference JO is set and validate + if (!empty($req->request->get('ref_jo'))) { + // get reference JO + $ref_jo = $em->getRepository(JobOrder::class)->find($req->request->get('ref_jo')); + + if (empty($ref_jo)) { + $error_array['ref_jo'] = 'Invalid reference job order specified.'; + } else { + $jo->setReferenceJO($ref_jo); + } + } + + // call service to generate job order and invoice + $invoice_items = $req->request->get('invoice_items', []); + $promo_id = $req->request->get('invoice_promo'); + $invoice_change = $req->request->get('invoice_change', 0); + + // check if invoice changed + if ($invoice_change) + { + $this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $error_array); + } + + // validate + $errors = $this->validator->validate($jo); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if errors are found + if (empty($error_array)) + { + // validated, no error. save the job order and customer + $em->persist($jo); + $em->persist($customer); + + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::CREATE) + ->setJobOrder($jo); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + $em->flush(); + } + } + + return $error_array; + } + + // dispatch job order + public function dispatchJobOrder(Request $req, int $id, MQTTClient $mclient) + { + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + $processor = $obj->getProcessedBy(); + $user = $this->security->getUser();; + + // check if we're the one processing, return error otherwise + if ($processor == null) + throw new AccessDeniedHttpException('Not the processor'); + + if ($processor != null && $processor->getID() != $user->getID()) + throw new AccessDeniedHttpException('Not the processor'); + + // initialize error list + $error_array = []; + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // check if cancelled already + if (!$obj->canDispatch()) + { + throw new NotFoundHttpException('Could not dispatch. Job Order is not pending.'); + // TODO: have this handled better, so UI shows the error + // $error_array['dispatch'] = 'Could not dispatch. Job Order is not pending.'; + } + + // check if lat and lng are provided + if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) + { + $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + } + + // check if hub is set + if (empty($req->request->get('hub'))) + { + $error_array['hub'] = 'No hub selected.'; + } + else + { + // get hub + $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); + + if (empty($hub)) + { + $error_array['hub'] = 'Invalid hub specified.'; + } + } + + // check facilitated type + $fac_type = $req->request->get('facilitated_type'); + if (!empty($fac_type)) + { + if (!FacilitatedType::validate($fac_type)) + $fac_type = null; + } + else + $fac_type = null; + + // check facilitated by + $fac_by_id = $req->request->get('facilitated_by'); + $fac_by = null; + if (!empty($fac_by_id)) + { + $fac_by = $em->getRepository(Hub::class)->find($fac_by_id); + if (empty($fac_by)) + $fac_by = null; + } + + if (empty($error_array)) + { + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::RIDER_ASSIGN) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')) + ->setFacilitatedType($fac_type) + ->setFacilitatedBy($fac_by) + ->setHub($hub); + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + if (empty($error_array)) + { + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::HUB_ASSIGN) + ->setJobOrder($obj); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + + // validated! save the entity + $em->flush(); + + // send event to mobile app + $payload = [ + 'event' => 'outlet_assign' + ]; + $mclient->sendEvent($obj, $payload); + } + + return $error_array; + } + + // assign job order + public function assignJobOrder(Request $req, $id) + { + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + + // initialize error list + $error_array = []; + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // check if we can assign + if (!$obj->canAssign()) + throw new NotFoundHttpException('Cannot assign rider to this job order.'); + + // check if lat and lng are provided + if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { + $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + } + + // check if rider is set + if (empty($req->request->get('rider'))) { + $error_array['rider'] = 'No rider selected.'; + } else { + // get rider + $rider = $em->getRepository(Rider::class)->find($req->request->get('rider')); + + if (empty($rider)) { + $error_array['rider'] = 'Invalid rider specified.'; + } + } + + // get current user + $user = $this->security->getUser(); + + if (empty($error_array)) { + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::ASSIGNED) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')) + ->setDateAssign(new DateTime()) + ->setRider($rider); + + if ($user != null) + { + $obj->setAssignedBy($user); + } + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + if (empty($error_array)) + { + // set rider unavailable + $rider->setAvailable(false); + + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ASSIGN) + ->setJobOrder($obj); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + + // validated! save the entity + $em->flush(); + + // call rider assignment handler's assignJobOrder + $this->rah->assignJobOrder($obj, $rider); + } + + return $error_array; + } + + // fulfill job order + public function fulfillJobOrder(Request $req, $id) + { + // initialize error list + $error_array = []; + + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // check if lat and lng are provided + if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { + $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + } + + if (empty($error_array)) { + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setSource($req->request->get('source')) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')); + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + $obj->fulfill(); + + if (empty($error_array)) + { + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::FULFILL) + ->setJobOrder($obj); + + // get current user + $user = $this->security->getUser(); + if ($user != null) + { + $event->setUser($user); + } + + $event->setUser($user); + $em->persist($event); + + // save to customer vehicle battery record + $this->updateVehicleBattery($obj); + + // validated! save the entity + $em->flush(); + + // get rider + $rider = $obj->getRider(); + + $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/assets/images/user.gif'; + if ($rider->getImageFile() != null) + $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/uploads/' . $rider->getImageFile(); + + // call rider assignment handler's fulfillJobOrder + $this->rah->fulfillJobOrder($obj, $image_url, $rider); + + // create the warranty if new battery only + if ($this->checkIfNewBattery($obj)) + { + $serial = null; + $warranty_class = $obj->getWarrantyClass(); + $first_name = $obj->getCustomer()->getFirstName(); + $last_name = $obj->getCustomer()->getLastName(); + $mobile_number = $obj->getCustomer()->getPhoneMobile(); + + // check if date fulfilled is null + if ($obj->getDateFulfill() == null) + $date_purchase = $obj->getDateCreate(); + else + $date_purchase = $obj->getDateFulfill(); + + // validate plate number + // $plate_number = $this->wh->cleanPlateNumber($jo->getCustomerVehicle()->getPlateNumber()); + $plate_number = Warranty::cleanPlateNumber($obj->getCustomerVehicle()->getPlateNumber()); + if ($plate_number != false) + { + $batt_list = array(); + $invoice = $obj->getInvoice(); + if (!empty($invoice)) + { + // get battery + $invoice_items = $invoice->getItems(); + foreach ($invoice_items as $item) + { + $battery = $item->getBattery(); + if ($battery != null) + { + $batt_list[] = $item->getBattery(); + } + } + } + + $this->wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class); + } + } + } + } + + // cancel job order + public function cancelJobOrder(Request $req, int $id, MQTTClient $mclient) + { + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + $cancel_reason = $req->request->get('cancel_reason'); + $obj->cancel($cancel_reason); + + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::CANCEL) + ->setJobOrder($obj); + + // get current user + $user = $this->security->getUser(); + if ($user != null) + { + $event->setUser($user); + } + + $event->setUser($user); + + $em->persist($event); + + // save + $em->flush(); + + // send mobile app event + $payload = [ + 'event' => 'cancelled', + 'reason' => $cancel_reason, + 'jo_id' => $obj->getID(), + ]; + $mclient->sendEvent($obj, $payload); + $mclient->sendRiderEvent($obj, $payload); + } + + // set hub for job order + public function setHub($req, $id, $mclient) + { + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + $user = $this->security->getUser(); + + // initialize error list + $error_array = []; + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // check if lat and lng are provided + if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { + $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + } + + // check if hub is set + if (empty($req->request->get('hub'))) { + $error_array['hub'] = 'No hub selected.'; + } else { + // get hub + $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); + + if (empty($hub)) { + $error_array['hub'] = 'Invalid hub specified.'; + } + } + + if (empty($error_array)) + { + // rider mqtt event + // NOTE: need to send this before saving because rider will be cleared + $rider_payload = [ + 'event' => 'cancelled', + 'reason' => 'Reassigned', + 'jo_id' => $obj->getID(), + ]; + $mclient->sendRiderEvent($obj, $rider_payload); + + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::RIDER_ASSIGN) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')) + ->setHub($hub) + ->clearRider(); + + if ($user != null) + { + $obj->setProcessedBy($user); + } + + $em->persist($obj); + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + // check if any errors were found + if (empty($error_array)) { + + // add event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::HUB_ASSIGN) + ->setJobOrder($obj); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + + // validated! save the entity + $em->flush(); + + // user mqtt event + $payload = [ + 'event' => 'outlet_assign' + ]; + $mclient->sendEvent($obj, $payload); + } + + return $error_array; + } + + // reject hub for job order + public function rejectHub($req, $id) + { + // get object data + $em = $this->em; + $jo = $em->getRepository(JobOrder::class)->find($id); + $processor = $jo->getProcessedBy(); + $user = $this->security->getUser(); + + // check if we're the one processing, return error otherwise + if ($processor == null) + throw new AccessDeniedHttpException('Not the processor'); + + if ($user != null) + { + if ($processor != null && $processor->getID() != $user->getID()) + throw new AccessDeniedHttpException('Not the processor'); + } + + // initialize error list + $error_array = []; + + // make sure job order exists + if (empty($jo)) + throw new NotFoundHttpException('The item does not exist'); + + // check if hub is set + if (empty($req->request->get('hub'))) + { + $error_array['hub'] = 'No hub selected.'; + } + else + { + // get hub + $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); + + if (empty($hub)) + { + $error_array['hub'] = 'Invalid hub specified.'; + } + } + + // check if this hub has already been rejected on this job order + $robj = $em->getRepository(JORejection::class)->findOneBy([ + 'job_order' => $jo, + 'hub' => $hub + ]); + + if (!empty($robj)) + $error_array['hub'] = 'This hub has already been rejected for the current job order.'; + + // check if reason is set + if (empty($req->request->get('reason'))) + $error_array['reason'] = 'No reason selected.'; + else if (!JORejectionReason::validate($req->request->get('reason'))) + $error_array['reason'] = 'Invalid reason specified.'; + + if (empty($error_array)) + { + // coordinates + $obj = new JORejection(); + + // set and save values + $obj->setDateCreate(new DateTime()) + ->setHub($hub) + ->setJobOrder($jo) + ->setReason($req->request->get('reason')) + ->setRemarks($req->request->get('remarks')) + ->setContactPerson($req->request->get('contact_person')); + + if ($user != null) + { + $obj->setUser($user); + } + + // validate + $errors = $this->validator->validate($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + if (empty($error_array)) + { + // validated! save the entity + $em->persist($obj); + $em->flush(); + } + + return $error_array; + + } + + // set rider for job order + public function setRider($req, $id, $mclient) + { + // initialize error list + $error_array = []; + + // get object data + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + $user = $this->security->getUser(); + + // make sure this object exists + if (empty($obj)) + throw new NotFoundHttpException('The item does not exist'); + + // check if lat and lng are provided + if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { + $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + } + + // check if rider is set + if (empty($req->request->get('rider'))) { + $error_array['rider'] = 'No rider selected.'; + } else { + // get rider + $rider = $em->getRepository(Rider::class)->find($req->request->get('rider')); + + if (empty($rider)) { + $error_array['rider'] = 'Invalid rider specified.'; + } + } + + if (empty($error_array)) { + // rider mqtt event + // NOTE: need to send this before saving because rider will be cleared + $rider_payload = [ + 'event' => 'cancelled', + 'reason' => 'Reassigned', + 'jo_id' => $obj->getID(), + ]; + $mclient->sendRiderEvent($obj, $rider_payload); + + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::ASSIGNED) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')) + ->setDateAssign(new DateTime()) + ->setRider($rider); + + if ($user != null) + { + $obj->setAssignedBy($user); + } + + // validate + $errors = $this->validator->validate($obj); + + $em->persist($obj); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + } + + // check if any errors were found + if (empty($error_array)) + { + // add event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ASSIGN) + ->setJobOrder($obj); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + + // validated! save the entity + $em->flush(); + + // send event to mobile app + $payload = [ + 'event' => 'driver_assigned' + ]; + $mclient->sendEvent($obj, $payload); + $mclient->sendRiderEvent($obj, $payload); + } + + return $error_array; + } + + // unlock processor + public function unlockProcessor($id) + { + // clear lock + $em = $this->em; + $jo = $em->getRepository(JobOrder::class)->find($id); + if ($jo != null) + { + $jo->setProcessedBy(null); + + $em->flush(); + } + } + + // unlock assignor + public function unlockAssignor($id) + { + // clear lock + $em = $this->em; + $jo = $em->getRepository(JobOrder::class)->find($id); + if ($jo != null) + { + $jo->setAssignedBy(null); + + $em->flush(); + } + + } + + public function processOneStepJobOrder(Request $req, $id) + { + // initialize error list + $error_array = []; + + $em = $this->em; + + $jo = $em->getRepository(JobOrder::class)->find($id); + if (empty($jo)) + { + // new job order + $jo = new JobOrder(); + } + + // check if lat and lng are provided + if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { + $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + } + + // check if customer vehicle is set + if (empty($req->request->get('customer_vehicle'))) { + $error_array['customer_vehicle'] = 'No vehicle selected.'; + } else { + // get customer vehicle + $cust_vehicle = $em->getRepository(CustomerVehicle::class)->find($req->request->get('customer_vehicle')); + + if (empty($cust_vehicle)) { + $error_array['customer_vehicle'] = 'Invalid vehicle specified.'; + } + } + + // check if hub AND rider is selected + if ((empty($req->request->get('hub_id'))) && + (empty($req->request->get('rider_id')))) { + $error_array['hub'] = 'No hub selected.'; + } else { + if (empty($req->request->get('rider_id'))) { + $error_array['rider'] = 'No rider selected.'; + } else { + // get hub + $hub = $em->getRepository(Hub::class)->find($req->request->get('hub_id')); + + if (empty($hub)) { + $error_array['hub'] = 'Invalid hub specified.'; + } else { + // get rider + $rider = $em->getRepository(Rider::class)->find($req->request->get('rider_id')); + + if (empty($rider)) { + $error_array['rider'] = 'Invalid rider specified.'; + } + } + } + } + + if (empty($error_array)) + { + // get current user + $user = $this->security->getUser(); + + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + $stype = $req->request->get('service_type'); + + // set and save values + $jo->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($stype) + ->setWarrantyClass($req->request->get('warranty_class')) + ->setCustomer($cust_vehicle->getCustomer()) + ->setCustomerVehicle($cust_vehicle) + ->setSource($req->request->get('source')) + ->setStatus(JOStatus::ASSIGNED) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setTier1Notes($req->request->get('tier1_notes')) + ->setTier2Notes($req->request->get('tier2_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')) + ->setORName($req->request->get('or_name')) + ->setPromoDetail($req->request->get('promo_detail')) + ->setModeOfPayment($req->request->get('mode_of_payment')) + ->setLandmark($req->request->get('landmark')) + ->setHub($hub) + ->setRider($rider); + + // check if user is null, meaning call to create came from API + if ($user != null) + { + $jo->setCreatedBy($user); + } + + // check if reference JO is set and validate + if (!empty($req->request->get('ref_jo'))) { + // get reference JO + $ref_jo = $em->getRepository(JobOrder::class)->find($req->request->get('ref_jo')); + + if (empty($ref_jo)) { + $error_array['ref_jo'] = 'Invalid reference job order specified.'; + } else { + $jo->setReferenceJO($ref_jo); + } + } + + // call service to generate job order and invoice + $invoice_items = $req->request->get('invoice_items', []); + $promo_id = $req->request->get('invoice_promo'); + $invoice_change = $req->request->get('invoice_change', 0); + + // check if invoice changed + if ($invoice_change) + { + $this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $error_array); + } + + // validate + $errors = $this->validator->validate($jo); + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if errors are found + if (empty($error_array)) + { + // validated, no error. save the job order + $em->persist($jo); + + // the event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::CREATE) + ->setJobOrder($jo); + + if ($user != null) + { + $event->setUser($user); + } + + $em->persist($event); + $em->flush(); + } + } + + return $error_array; + + } + + + // initialize incoming job order form + public function initializeIncomingForm() + { + $params['obj'] = new JobOrder(); + $params['mode'] = 'create'; + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_incoming_form'); + + // return params + return $params; + } + + public function initializeOneStepForm() + { + $params['obj'] = new JobOrder(); + $params['mode'] = 'onestep'; + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_onestep'); + + // return params + return $params; + } + + public function initializeOneStepEditForm($id, $map_tools) + { + $em = $this->em; + $obj = $em->getRepository(JobOrder::class)->find($id); + + $params['obj'] = $obj; + $params['mode'] = 'onestep-edit'; + $params['cvid'] = $obj->getCustomerVehicle()->getID(); + $params['vid'] = $obj->getCustomerVehicle()->getVehicle()->getID(); + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get the hubs + // TODO: move this snippet to a function + $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); + + $params['hubs'] = []; + + // format duration and distance into friendly time + foreach ($hubs as $hub) { + // duration + $seconds = $hub['duration']; + + if (!empty($seconds) && $seconds > 0) { + $hours = floor($seconds / 3600); + $minutes = ceil(($seconds / 60) % 60); + + $hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); + } else { + $hub['duration'] = false; + } + + // distance + $meters = $hub['distance']; + + if (!empty($meters) && $meters > 0) { + $hub['distance'] = round($meters / 1000) . " km"; + } else { + $hub['distance'] = false; + } + + // counters + $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); + $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); + + // check for rejection + $hub['flag_rejected'] = false; + $hub_id = $hub['hub']->getID(); + + $params['hubs'][] = $hub; + } + + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_onestep_edit_form'); + + return $params; + } + + // initialize open edit job order form + public function initializeOpenEditForm($id) + { + $em = $this->em; + $jo = $em->getRepository(JobOrder::class)->find($id); + + $params['obj'] = $jo; + $params['mode'] = 'open_edit'; + $params['cvid'] = $jo->getCustomerVehicle()->getID(); + $params['vid'] = $jo->getCustomerVehicle()->getVehicle()->getID(); + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_open_edit_form'); + + return $params; + } + + // initialize incoming vehicle form + public function initializeIncomingVehicleForm(int $cvid) + { + $params['mode'] = 'create_vehicle'; + $params['cvid'] = $cvid; + + $em = $this->em; + + // get customer vehicle + $cv = $em->getRepository(CustomerVehicle::class)->find($cvid); + $params['vid'] = $cv->getVehicle()->getID(); + + // make sure this customer vehicle exists + if (empty($cv)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not exist'); + } + + $jo = new JobOrder(); + $jo->setCustomerVehicle($cv) + ->setCustomer($cv->getCustomer()); + + $params['obj'] = $jo; + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_incoming_vehicle_form'); + + return $params; + } + + // initialize all job orders form for a specific job order id + public function initializeAllForm($id) + { + $em = $this->em; + + $params['mode'] = 'update-all'; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + throw new NotFoundHttpException('The job order does not exist'); + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_all_form'); + + $params['obj'] = $obj; + $params['status_cancelled'] = JOStatus::CANCELLED; + + // timeline stuff (descending by time) + $params['timeline'] = [ + [ + 'date' => date("M j"), + 'time' => date("g:i A"), + 'event' => "Event 4", + 'color' => "#f4516c" + ], + [ + 'date' => date("M j"), + 'time' => date("g:i A"), + 'event' => "Event 3", + 'color' => "#34bfa3" + ], + [ + 'date' => date("M j"), + 'time' => date("g:i A"), + 'event' => "Event 2", + 'color' => "#716aca" + ], + [ + 'date' => date("M j"), + 'time' => date("g:i A"), + 'event' => "Event 1", + 'color' => "#ffb822" + ], + ]; + + return $params; + } + + // initialize dispatch/processing job order form + public function initializeProcessingForm($id, $map_tools) + { + $em = $this->em; + + // manual transaction since we're locking + $em->getConnection()->beginTransaction(); + + try + { + // lock and get data + $obj = $em->getRepository(JobOrder::class)->find($id, LockMode::PESSIMISTIC_READ); + + // make sure this job order exists + if (empty($obj)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not exist'); + } + + // check status + if ($obj->getStatus() != JOStatus::PENDING) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not have a pending status'); + } + + // check if we are the processor + $processor = $obj->getProcessedBy(); + // get current user + $user = $this->security->getUser(); + if ($user != null) + { + // TODO: go back to list page and display alert / flash that says they cannot access it because they + // are not the processor + if ($processor != null && $processor->getID() != $user->getID()) + { + $em->getConnection()->rollback(); + throw new AccessDeniedHttpException('Not the processor'); + } + + // make this user be the processor + $obj->setProcessedBy($user); + } + $em->flush(); + + $em->getConnection()->commit(); + } + catch(PessimisticLockException $e) + { + throw new AccessDeniedHttpException('Not the processor'); + } + + // NOTE: we are able to lock, everything should be fine now + + $params['mode'] = 'update-processing'; + $params['status_cancelled'] = JOStatus::CANCELLED; + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get rejections + $rejections = $obj->getHubRejections(); + + // get rejection reasons + $params['rejection_reasons'] = JORejectionReason::getCollection(); + + // get closest hubs + $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); + + $params['hubs'] = []; + + // format duration and distance into friendly time + foreach ($hubs as $hub) { + // duration + $seconds = $hub['duration']; + + if (!empty($seconds) && $seconds > 0) { + $hours = floor($seconds / 3600); + $minutes = ceil(($seconds / 60) % 60); + + $hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); + } else { + $hub['duration'] = false; + } + + // distance + $meters = $hub['distance']; + + if (!empty($meters) && $meters > 0) { + $hub['distance'] = round($meters / 1000) . " km"; + } else { + $hub['distance'] = false; + } + + // counters + $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); + $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); + + // check for rejection + $hub['flag_rejected'] = false; + $hub_id = $hub['hub']->getID(); + + foreach ($rejections as $robj) + { + if ($robj->getHub()->getID() === $hub_id) + { + $hub['flag_rejected'] = true; + break; + } + } + + $params['hubs'][] = $hub; + } + + $params['obj'] = $obj; + // get template to display + $params['template'] = $this->getTwigTemplate('jo_processing_form'); + + return $params; + } + + // initialize assign job order form + public function initializeAssignForm($id) + { + $em = $this->em; + + // manual transaction since we're locking + $em->getConnection()->beginTransaction(); + + $params['mode'] = 'update-assigning'; + + try + { + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not exist'); + } + + // check status + if ($obj->getStatus() != JOStatus::RIDER_ASSIGN) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not have an assigning status'); + } + + // check if super user + $user = $this->security->getUser(); + if ($user != null) + { + if ($user->isSuperAdmin()) + { + // do nothing, just allow page to be accessed + } + else + { + // check if hub is assigned to current user + $user_hubs = $user->getHubs(); + if (!in_array($obj->getHub()->getID(), $user_hubs)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order is not on a hub assigned to this user'); + } + + // check if we are the assignor + $assignor = $obj->getAssignedBy(); + + if ($assignor != null && $assignor->getID() != $user->getID()) + { + $em->getConnection()->rollback(); + throw new AccessDeniedHttpException('Not the assignor'); + } + + // make this user be the assignor + $obj->setAssignedBy($user); + } + } + + $em->flush(); + + $em->getConnection()->commit(); + } + catch (PessimisticLockException $e) + { + throw new AccessDeniedHttpException('Not the assignor'); + } + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_assigning_form'); + $params['obj'] = $obj; + $params['status_cancelled'] = JOStatus::CANCELLED; + + return $params; + } + + // initialize fulflll job order form + public function initializeFulfillmentForm($id) + { + $em = $this->em; + + $params['mode'] = 'update-fulfillment'; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + { + throw new NotFoundHttpException('The job order does not exist'); + } + + // check status + if (!in_array($obj->getStatus(), [JOStatus::ASSIGNED, JOStatus::IN_PROGRESS])) + { + throw new NotFoundHttpException('The job order does not have a fulfillment status'); + } + + // get current user + $user = $this->security->getUser(); + + // check if hub is assigned to current user + $user_hubs = $user->getHubs(); + if (!in_array($obj->getHub()->getID(), $user_hubs)) + { + throw new NotFoundHttpException('The job order is not on a hub assigned to this user'); + } + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_fulfillment_form'); + $params['obj'] = $obj; + $params['status_cancelled'] = JOStatus::CANCELLED; + + return $params; + } + + // initialize hub form + public function initializeHubForm($id, $map_tools) + { + $em = $this->em; + + $params['mode'] = 'update-reassign-hub'; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + { + throw new NotFoundHttpException('The job order does not exist'); + } + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + // get rejections + $rejections = $obj->getHubRejections(); + + // get rejection reasons + $params['rejection_reasons'] = JORejectionReason::getCollection(); + + // get closest hubs + $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); + + $params['status_cancelled'] = JOStatus::CANCELLED; + $params['hubs'] = []; + + // format duration and distance into friendly time + foreach ($hubs as $hub) { + // duration + $seconds = $hub['duration']; + + if (!empty($seconds) && $seconds > 0) { + $hours = floor($seconds / 3600); + $minutes = ceil(($seconds / 60) % 60); + + $hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); + } else { + $hub['duration'] = false; + } + + // distance + $meters = $hub['distance']; + + if (!empty($meters) && $meters > 0) { + $hub['distance'] = round($meters / 1000) . " km"; + } else { + $hub['distance'] = false; + } + + // counters + $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); + $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); + + // check for rejection + $hub['flag_rejected'] = false; + $hub_id = $hub['hub']->getID(); + + foreach ($rejections as $robj) + { + if ($robj->getHub()->getID() === $hub_id) + { + $hub['flag_rejected'] = true; + break; + } + } + + $params['hubs'][] = $hub; + } + + $params['obj'] = $obj; + // get template to display + $params['template'] = $this->getTwigTemplate('jo_open_hub_form'); + + return $params; + } + + // initialize rider form + public function initializeRiderForm($id) + { + $em = $this->em; + + $params['mode'] = 'update-reassign-rider'; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not exist'); + } + + // check status + if ($obj->getStatus() == JOStatus::PENDING) + { + $em->getConnection()->rollback(); + throw new NotFoundHttpException('The job order does not have an assigned hub'); + } + + $this->fillDropdownParameters($params); + $this->fillFormTags($params); + + $params['obj'] = $obj; + $params['status_cancelled'] = JOStatus::CANCELLED; + // get template to display + $params['template'] = $this->getTwigTemplate('jo_open_rider_form'); + + return $params; + } + + // generate pdf form for job order + public function generatePDFForm($req, $id, $proj_path) + { + $em = $this->em; + $translator = $this->translator; + + // get row data + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + throw new NotFoundHttpException('The job order does not exist'); + + // set output filename + $filename = 'job-order-' . $obj->getID() . '.pdf'; + + // translate the title and the logo for the pdf + $translated_title = $translator->trans('jo_title_pdf'); + $translated_logo = $translator->trans('image_jo_pdf'); + + // generate the pdf + $pdf = new FPDF('P', 'mm', 'letter'); + $pdf->AddPage(); + $pdf->setTitle($translated_title . ' #' . $obj->getID()); + $pdf->SetFillColor(211, 211, 211); + + // style defaults + $margin = 10; + $page_width = $pdf->GetPageWidth() - ($margin * 2); + $table_col_width = $page_width / 12; + $line_height = 5; + $jo_line_height = 10; + $table_line_height = 7; + $font_face = 'Arial'; + $body_font_size = 9; + $header_font_size = 9; + $jo_font_size = 16; + $col1_x = $margin; + $col2_x = 120; + $label_width = 40; + $val_width = 60; + + // insert the logo + $image_path = $proj_path . $translated_logo; + $pdf->Image($image_path, $col1_x, 10); + + // insert JO number + $pdf->SetFont($font_face, 'B', $jo_font_size); + $pdf->SetX($col2_x); + $pdf->Cell($label_width, $jo_line_height, 'JO Number:'); + $pdf->SetTextColor(9, 65, 150); + $pdf->Cell(0, $jo_line_height, $obj->getID()); + + // insert customer info + $customer = $obj->getCustomer(); + $pdf->SetFont($font_face, '', $body_font_size); + $pdf->SetTextColor(0, 0, 0); + + $pdf->Ln($line_height * 7); + + // get current Y + $y = $pdf->GetY(); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Customer Name:'); + $pdf->MultiCell($val_width, $line_height, $customer ? $customer->getFirstName() . ' ' . $customer->getLastName() : '', 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Mobile Phone:'); + $pdf->MultiCell(0, $line_height, $customer && $customer->getPhoneMobile() ? $this->country_code . $customer->getPhoneMobile() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Delivery Date:'); + $pdf->MultiCell($val_width, $line_height, $obj->getDateSchedule() ? $obj->getDateSchedule()->format("m/d/Y") : '', 0, 'left'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Landline:'); + $pdf->MultiCell(0, $line_height, $customer && $customer->getPhoneLandline() ? $this->country_code . $customer->getPhoneLandline() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Office Phone:'); + $pdf->MultiCell(0, $line_height, $customer && $customer->getPhoneOffice() ? $this->country_code . $customer->getPhoneOffice() : '', 0, 'L'); + + $pdf->SetX($col2_x); + $pdf->Cell($label_width, $line_height, 'Fax:'); + $pdf->MultiCell($val_width, $line_height, $customer && $customer->getPhoneFax() ? $this->country_code . $customer->getPhoneFax() : '', 0, 'L'); + + // insert vehicle info + $cv = $obj->getCustomerVehicle(); + $vehicle = $cv->getVehicle(); + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($label_width, $line_height, 'Vehicle Details'); + $pdf->Ln($line_height * 2); + + // get current Y + $y = $pdf->GetY(); + + $pdf->SetFont($font_face, '', $body_font_size); + $pdf->Cell($label_width, $line_height, 'Plate Number:'); + $pdf->MultiCell($val_width, $line_height, $cv ? $cv->getPlateNumber() : '', 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Vehicle Color:'); + $pdf->MultiCell(0, $line_height, $cv ? $cv->getColor() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Brand:'); + $pdf->MultiCell($val_width, $line_height, $vehicle && $vehicle->getManufacturer() ? $vehicle->getManufacturer()->getName() : '', 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Model / Year:'); + $pdf->MultiCell(0, $line_height, $cv ? $cv->getModelYear() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Make:'); + $pdf->MultiCell($val_width, $line_height, $vehicle ? $vehicle->getMake() : '', 0, 'L'); + + // insert battery info + $battery = $cv->getCurrentBattery(); + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($label_width, $line_height, 'Battery Details'); + $pdf->Ln($line_height * 2); + + $pdf->SetFont($font_face, '', $body_font_size); + + // get current Y + $y = $pdf->GetY(); + + $pdf->Cell($label_width, $line_height, 'Current Battery:'); + $pdf->MultiCell($val_width, $line_height, $battery && $battery->getManufacturer() && $battery->getModel() && $battery->getSize() ? $battery->getManufacturer()->getName() . ' ' . $battery->getModel()->getName() . ' ' . $battery->getSize()->getName() . ' (' . $battery->getProductCode() . ')' : '', 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Serial Number:'); + $pdf->MultiCell(0, $line_height, $cv ? $cv->getWarrantyCode() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Wty. Exp. Date:'); + $pdf->MultiCell($val_width, $line_height, $cv && $cv->getWarrantyExpiration() ? $cv->getWarrantyExpiration()->format("d/m/Y") : '', 0, 'L'); + + // insert transaction details + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($label_width, $line_height, 'Transaction Details'); + $pdf->Ln($line_height * 2); + + $pdf->SetFont($font_face, '', $body_font_size); + + // get current Y + $y = $pdf->GetY(); + + $pdf->Cell($label_width, $line_height, 'Warranty Class:'); + $pdf->MultiCell($val_width, $line_height, WarrantyClass::getName($obj->getWarrantyClass()), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Mode of Payment:'); + $pdf->MultiCell(0, $line_height, ModeOfPayment::getName($obj->getModeOfPayment()), 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->Cell($label_width, $line_height, 'Delivery Address:'); + $pdf->MultiCell($val_width, $line_height, $obj->getDeliveryAddress(), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Landmark:'); + $pdf->MultiCell(0, $line_height, $obj->getLandMark(), 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Dispatch Time:'); + $pdf->MultiCell($val_width, $line_height, $obj->getDateSchedule() ? $obj->getDateSchedule()->format("g:i A") : '', 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->Cell($label_width, $line_height, 'Dispatched By:'); + $pdf->MultiCell(0, $line_height, $obj->getProcessedBy() ? $obj->getProcessedBy()->getFullName() : '', 0, 'L'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + // insert delivery instructions + $pdf->SetY($y); + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell(0, $line_height, 'Delivery Instructions'); + $pdf->Ln(); + + $pdf->SetFont($font_face, '', $body_font_size); + $pdf->MultiCell(0, $line_height, $obj->getDeliveryInstructions(), 1, 'L'); + + // insert invoice details + $pdf->Ln(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($label_width, $line_height, 'Invoice Details'); + $pdf->Ln(); + + // invoice table headers + $invoice = $obj->getInvoice(); + $pdf->SetFont($font_face, 'B', $header_font_size); + $pdf->Cell($table_col_width * 6, $table_line_height, 'Item', 1, 0, 'L', 1); + $pdf->Cell($table_col_width * 2, $table_line_height, 'Quantity', 1, 0, 'R', 1); + $pdf->Cell($table_col_width * 2, $table_line_height, 'Unit Price', 1, 0, 'R', 1); + $pdf->Cell($table_col_width * 2, $table_line_height, 'Amount', 1, 1, 'R', 1); + $pdf->SetFont($font_face, '', $body_font_size); + + // build invoice items table + if ($invoice && !empty($invoice->getItems())) + { + foreach ($invoice->getItems() as $item) + { + $pdf->Cell($table_col_width * 6, $table_line_height, $item->getTitle(), 1); + $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getQuantity()), 1, 0, 'R'); + $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getPrice(), 2), 1, 0, 'R'); + $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getPrice() * $item->getQuantity(), 2), 1, 1, 'R'); + } + } + else + { + $pdf->Cell($table_col_width * 12, 7, 'No items', 1, 1); + } + + $pdf->Ln($line_height * 2); + + // get current Y + $y = $pdf->GetY(); + + // insert invoice footer details + $pdf->Cell($label_width, $line_height, 'Transaction Type:'); + $pdf->MultiCell($val_width, $line_height, ServiceType::getName($obj->getServiceType()), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->SetFont($font_face, 'B'); + $pdf->Cell($label_width, $line_height, 'SUBTOTAL:'); + $pdf->SetFont($font_face, ''); + $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getVATExclusivePrice(), 2) : '', 0, 'R'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'OR Name:'); + $pdf->MultiCell($val_width, $line_height, $obj->getORName(), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->SetFont($font_face, 'B'); + $pdf->Cell($label_width, $line_height, 'TAX:'); + $pdf->SetFont($font_face, ''); + $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getVAT(), 2) : '', 0, 'R'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Emp. ID/Card No./Ref. By:'); + $pdf->MultiCell($val_width, $line_height, $obj->getPromoDetail(), 0, 'L'); + + // get Y after left cell + $y1 = $pdf->GetY(); + + $pdf->SetXY($col2_x, $y); + $pdf->SetFont($font_face, 'B'); + $pdf->Cell($label_width, $line_height, 'DISCOUNT:'); + $pdf->SetFont($font_face, ''); + $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getDiscount(), 2) : '', 0, 'R'); + + // get Y after right cell + $y2 = $pdf->GetY(); + + // get row height + $y = max($y1, $y2); + + $pdf->SetXY($col1_x, $y); + $pdf->Cell($label_width, $line_height, 'Discount Type:'); + $pdf->MultiCell($val_width, $line_height, $invoice && $invoice->getPromo() ? $invoice->getPromo()->getName() : '', 0, 'L'); + + $pdf->SetXY($col2_x, $y); + $pdf->SetFont($font_face, 'B'); + $pdf->Cell($label_width, $line_height, 'FINAL AMOUNT:'); + $pdf->MultiCell(0, $line_height, $invoice ? number_format($invoice->getTotalPrice(), 2) : '', 0, 'R'); + $pdf->SetFont($font_face, ''); + + $params['obj'] = $pdf; + $params['filename'] = $filename; + + return $params; + } + + public function getTwigTemplate($id) + { + if (isset($this->template_hash[$id])) + { + return $this->template_hash[$id]; + } + + return null; + } + + public function getOtherParameters() + { + // get riders for dropdown + $params['riders'] = $this->em->getRepository(Rider::class)->findAll(); + + return $params; + } + + public function updateVehicleBattery(JobOrder $jo) + { + // check if new battery + if (!($this->checkIfNewBattery($jo))) + return; + + // customer vehicle + $cv = $jo->getCustomerVehicle(); + if ($cv == null) + return; + + // invoice + $invoice = $jo->getInvoice(); + if ($invoice == null) + return; + + // invoice items + $items = $invoice->getItems(); + if (count($items) <= 0) + return; + + // get first battery from invoice + $battery = null; + foreach ($items as $item) + { + $battery = $item->getBattery(); + if ($battery != null) + break; + } + + // no battery in order + if ($battery == null) + return; + + // warranty expiration + $warr = $jo->getWarrantyClass(); + $warr_months = 0; + if ($warr == WarrantyClass::WTY_PRIVATE) + $warr_months = $battery->getWarrantyPrivate(); + else if ($warr == WarrantyClass::WTY_COMMERCIAL) + $warr_months = $battery->getWarrantyCommercial(); + else if ($warr == WarrantyClass::WTY_TNV) + $warr_months = 12; + + $warr_date = new DateTime(); + $warr_date->add(new DateInterval('P' . $warr_months . 'M')); + + // update customer vehicle battery + $cv->setCurrentBattery($battery) + ->setHasMotoliteBattery(true) + ->setWarrantyExpiration($warr_date); + } + + public function checkIfNewBattery(JobOrder $jo) + { + if ($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) + return true; + + return false; + } + + protected function fillDropdownParameters(&$params) + { + $em = $this->em; + + // db loaded + $params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); + $params['promos'] = $em->getRepository(Promo::class)->findAll(); + + // list of hubs + $hubs = $em->getRepository(Hub::class)->findBy([], ['name' => 'ASC']); + $fac_hubs = []; + foreach ($hubs as $hub) + { + $fac_hubs[$hub->getID()] = $hub->getName() . ' - ' . $hub->getBranch(); + } + + // name values + $params['service_types'] = ServiceType::getCollection(); + $params['warranty_classes'] = WarrantyClass::getCollection(); + $params['modes_of_payment'] = ModeOfPayment::getCollection(); + $params['statuses'] = JOStatus::getCollection(); + $params['discount_apply'] = DiscountApply::getCollection(); + $params['trade_in_types'] = TradeInType::getCollection(); + $params['facilitated_types'] = FacilitatedType::getCollection(); + $params['facilitated_hubs'] = $fac_hubs; + $params['sources'] = TransactionOrigin::getCollection(); + } + + protected function initFormTags(&$params) + { + // default to editing, as we have more forms editing than creating + $params['ftags'] = [ + 'title' => 'Job Order Form', + 'vehicle_dropdown' => false, + 'invoice_edit' => false, + 'set_map_coordinate' => true, + 'preset_vehicle' => false, + 'ticket_table' => true, + 'cancel_button' => true, + ]; + } + + protected function fillFormTags(&$params) + { + $this->initFormTags($params); + + switch ($params['mode']) + { + case 'create': + $params['ftags']['vehicle_dropdown'] = true; + $params['ftags']['set_map_coordinate'] = false; + $params['ftags']['invoice_edit'] = true; + $params['ftags']['ticket_table'] = false; + $params['ftags']['cancel_button'] = false; + break; + case 'create_vehicle': + $params['ftags']['set_map_coordinate'] = false; + $params['ftags']['invoice_edit'] = true; + $params['ftags']['preset_vehicle'] = true; + $params['ftags']['ticket_table'] = false; + $params['ftags']['cancel_button'] = false; + break; + case 'open_edit': + $params['ftags']['invoice_edit'] = true; + $params['ftags']['preset_vehicle'] = true; + break; + case 'onestep': + $params['ftags']['vehicle_dropdown'] = true; + $params['ftags']['set_map_coordinate'] = false; + $params['ftags']['invoice_edit'] = true; + $params['ftags']['ticket_table'] = false; + $params['ftags']['cancel_button'] = false; + break; + case 'onestep-edit': + $params['ftags']['invoice_edit'] = true; + $params['ftags']['preset_vehicle'] = true; + break; + } + } + + protected function loadTemplates() + { + $this->template_hash = []; + + // add all twig templates for job order to hash + // TODO: put this in an array declaration + // $this->template_hash = [ + // 'blah' => 'blah', + // ]; + $this->template_hash['jo_incoming_form'] = 'job-order/form.html.twig'; + $this->template_hash['jo_open_edit_form'] = 'job-order/form.html.twig'; + $this->template_hash['jo_incoming_vehicle_form'] = 'job-order/form.html.twig'; + $this->template_hash['jo_processing_form'] = 'job-order/form.html.twig'; + $this->template_hash['jo_assigning_form'] = 'job-order/form.html.twig'; + $this->template_hash['jo_fulfillment_form'] = 'job-order/form.html.twig'; + $this->template_hash['jo_open_hub_form'] = 'job-order/form.html.twig'; + $this->template_hash['jo_open_rider_form'] = 'job-order/form.html.twig'; + $this->template_hash['jo_all_form'] = 'job-order/form.html.twig'; + $this->template_hash['jo_list_processing'] = 'job-order/list.processing.html.twig'; + $this->template_hash['jo_list_assigning'] = 'job-order/list.assigning.html.twig'; + $this->template_hash['jo_list_fulfillment'] = 'job-order/list.fulfillment.html.twig'; + $this->template_hash['jo_list_open'] = 'job-order/list.open.html.twig'; + $this->template_hash['jo_list_all'] = 'job-order/list.all.html.twig'; + $this->template_hash['jo_onestep'] = 'job-order/form.onestep.html.twig'; + $this->template_hash['jo_onestep_edit_form'] = 'job-order/form.onestep.html.twig'; + } + + protected function checkTier($tier) + { + // check specified tier + switch ($tier) { + case 'proc': + $tier_key = 'jo_proc'; + $tier_name = 'Dispatch'; + $rows_route = 'jo_proc_rows'; + $edit_route = 'jo_proc_form'; + $unlock_route = 'jo_proc_unlock'; + $jo_status = JOStatus::PENDING; + break; + case 'assign': + $tier_key = 'jo_assign'; + $tier_name = 'Assigning'; + $rows_route = 'jo_assign_rows'; + $edit_route = 'jo_assign_form'; + $unlock_route = 'jo_assign_unlock'; + $jo_status = JOStatus::RIDER_ASSIGN; + break; + case 'fulfill': + $tier_key = 'jo_fulfill'; + $tier_name = 'Fullfillment'; + $rows_route = 'jo_fulfill_rows'; + $edit_route = 'jo_fulfill_form'; + $unlock_route = ''; + $jo_status = [ + JOStatus::ASSIGNED, + JOStatus::IN_PROGRESS + ]; + break; + case 'open': + $tier_key = 'jo_open'; + $tier_name = 'Open'; + $rows_route = 'jo_open_rows'; + $edit_route = ''; + $unlock_route = ''; + $jo_status = [ + JOStatus::PENDING, + JOStatus::RIDER_ASSIGN, + JOStatus::ASSIGNED, + JOStatus::IN_PROGRESS, + JOStatus::IN_TRANSIT, + ]; + break; + case 'all': + $tier_key = 'jo_open'; + $tier_name = 'Open'; + $rows_route = 'jo_open_rows'; + $edit_route = 'jo_all_form'; + $unlock_route = ''; + $jo_status = ''; + break; + default: + throw new AccessDeniedHttpException('No access.'); + } + + // check acl + if (!($this->security->isGranted($tier_key . '.list'))) + throw new AccessDeniedHttpException('No access.'); + + // return params if allowed access + return [ + 'key' => $tier_key, + 'name' => $tier_name, + 'rows_route' => $rows_route, + 'edit_route' => $edit_route, + 'unlock_route' => $unlock_route, + 'jo_status' => $jo_status + ]; + } + + // TODO: re-enable search, figure out how to group the orWhere filters into one, so can execute that plus the pending filter + // check if datatable filter is present and append to query + protected function setQueryFilters($datatable, &$query, $qb, $hubs, $tier, $status) + { + switch ($tier) + { + case 'fulfill': + if (isset($datatable['query']['data-rows-search'])) + { + $query->innerJoin('q.cus_vehicle', 'cv') + ->innerJoin('q.customer', 'c') + ->where('cv.plate_number like :filter') + ->orWhere('c.phone_mobile like :filter') + ->orWhere('c.first_name like :filter or c.last_name like :filter') + ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); + } + if (isset($datatable['query']['rider'])) + { + $query->innerJoin('q.rider', 'r') + ->andWhere('r.id = :rider_id') + ->setParameter('rider_id', $datatable['query']['rider']); + } + if (isset($datatable['query']['schedule_date'])) + { + $start = $datatable['query']['schedule_date'][0] . ' ' . '00:00:00'; + $end = $datatable['query']['schedule_date'][1] . ' ' . '23:59:00'; + + $date_start = DateTime::createFromFormat('m/d/Y H:i:s', $start); + $date_end = DateTime::createFromFormat('m/d/Y H:i:s', $end); + + $query->andWhere('q.date_schedule >= :date_start') + ->andWhere('q.date_schedule <= :date_end') + ->setParameter('date_start', $date_start) + ->setParameter('date_end', $date_end); + } + + $query->andWhere('q.status IN (:statuses)') + ->andWhere('q.hub IN (:hubs)') + ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY) + ->setParameter('hubs', $hubs, Connection::PARAM_STR_ARRAY); + break; + case 'assign': + $query->where('q.status = :status') + ->andWhere('q.hub IN (:hubs)') + ->setParameter('status', $status) + ->setParameter('hubs', $hubs, Connection::PARAM_STR_ARRAY); + break; + case 'open': + if (isset($datatable['query']['data-rows-search'])) + { + $query->innerJoin('q.cus_vehicle', 'cv') + ->innerJoin('q.customer', 'c') + ->where('q.status IN (:statuses)') + ->andWhere('cv.plate_number like :filter or c.first_name like :filter or c.last_name like :filter or c.phone_mobile like :filter') + ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY) + ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); + } + else + { + $query->where('q.status IN (:statuses)') + ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY); + } + if (isset($datatable['query']['rider'])) + { + $query->innerJoin('q.rider', 'r') + ->andWhere('r.id = :rider_id') + ->setParameter('rider_id', $datatable['query']['rider']); + } + if (isset($datatable['query']['schedule_date'])) + { + $start = $datatable['query']['schedule_date'][0] . ' ' . '00:00:00'; + $end = $datatable['query']['schedule_date'][1] . ' ' . '23:59:00'; + + $date_start = DateTime::createFromFormat('m/d/Y H:i:s', $start); + $date_end = DateTime::createFromFormat('m/d/Y H:i:s', $end); + + $query->andWhere('q.date_schedule >= :date_start') + ->andWhere('q.date_schedule <= :date_end') + ->setParameter('date_start', $date_start) + ->setParameter('date_end', $date_end); + } + break; + case 'all': + if (isset($datatable['query']['data-rows-search'])) + { + $query->innerJoin('q.cus_vehicle', 'cv') + ->innerJoin('q.customer', 'c') + ->where('cv.plate_number like :filter') + ->orWhere('c.phone_mobile like :filter') + ->orWhere('c.first_name like :filter or c.last_name like :filter') + ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); + } + if (isset($datatable['query']['rider'])) + { + $query->innerJoin('q.rider', 'r') + ->andWhere('r.id = :rider_id') + ->setParameter('rider_id', $datatable['query']['rider']); + } + if (isset($datatable['query']['schedule_date'])) + { + $start = $datatable['query']['schedule_date'][0] . ' ' . '00:00:00'; + $end = $datatable['query']['schedule_date'][1] . ' ' . '23:59:00'; + + $date_start = DateTime::createFromFormat('m/d/Y H:i:s', $start); + $date_end = DateTime::createFromFormat('m/d/Y H:i:s', $end); + + $query->andWhere('q.date_schedule >= :date_start') + ->andWhere('q.date_schedule <= :date_end') + ->setParameter('date_start', $date_start) + ->setParameter('date_end', $date_end); + } + break; + default: + $query->where('q.status = :status') + ->setParameter('status', $status); + } + } +} diff --git a/src/Service/JobOrderHandlerInterface.php b/src/Service/JobOrderHandlerInterface.php new file mode 100644 index 00000000..5593d10c --- /dev/null +++ b/src/Service/JobOrderHandlerInterface.php @@ -0,0 +1,101 @@ +getBattery(); $cust_vehicle->setCurrentBattery($new_battery); + $cust_vehicle->setHasMotoliteBattery(true); } $this->em->flush(); diff --git a/src/Service/MQTTClient.php b/src/Service/MQTTClient.php index aa0932a3..c9c7a4f2 100644 --- a/src/Service/MQTTClient.php +++ b/src/Service/MQTTClient.php @@ -9,14 +9,15 @@ class MQTTClient { const PREFIX = 'motolite.control.'; const RIDER_PREFIX = 'motorider_'; - const REDIS_KEY = 'events'; // protected $mclient; protected $redis; + protected $key; - public function __construct(RedisClientProvider $redis_client) + public function __construct(RedisClientProvider $redis_client, $key) { $this->redis = $redis_client->getRedisClient(); + $this->key = $key; } public function __destruct() @@ -29,7 +30,7 @@ class MQTTClient // $this->mclient->publish($channel, $message); $data = $channel . '|' . $message; - $this->redis->lpush(self::REDIS_KEY, $data); + $this->redis->lpush($this->key, $data); } public function sendEvent(JobOrder $job_order, $payload) diff --git a/src/Service/MapTools.php b/src/Service/MapTools.php index f02618ba..6aa5848c 100644 --- a/src/Service/MapTools.php +++ b/src/Service/MapTools.php @@ -84,10 +84,23 @@ class MapTools { //error_log($row[0]->getName() . ' - ' . $row['dist']); $hubs[] = $row[0]; + + // get coordinates of hub + $hub_coordinates = $row[0]->getCoordinates(); + + $cust_lat = $point->getLatitude(); + $cust_lng = $point->getLongitude(); + + $hub_lat = $hub_coordinates->getLatitude(); + $hub_lng = $hub_coordinates->getLongitude(); + + // get distance in kilometers from customer point to hub point + $dist = $this->distance($cust_lat, $cust_lng, $hub_lat, $hub_lng); + $final_data[] = [ 'hub' => $row[0], 'db_distance' => $row['dist'], - 'distance' => 0, + 'distance' => $dist, 'duration' => 0, ]; } @@ -135,4 +148,18 @@ class MapTools return $final_data; */ } + + protected function distance($lat1, $lon1, $lat2, $lon2) + { + if (($lat1 == $lat2) && ($lon1 == $lon2)) + return 0; + + $theta = $lon1 - $lon2; + $dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta)); + $dist = acos($dist); + $dist = rad2deg($dist); + $miles = $dist * 60 * 1.1515; + + return round(($miles * 1.609344), 1); + } } diff --git a/src/Service/RedisClientProvider.php b/src/Service/RedisClientProvider.php index 04ac1f80..80dd06fa 100644 --- a/src/Service/RedisClientProvider.php +++ b/src/Service/RedisClientProvider.php @@ -11,34 +11,46 @@ class RedisClientProvider protected $host; protected $port; protected $password; - protected $env_flag; - public function __construct($scheme, $host, $port, $password, $env_flag) + public function __construct($scheme, $host, $port, $password) { $this->scheme = $scheme; $this->host = $host; $this->port = $port; $this->password = $password; - $this->env_flag = $env_flag; + $this->redis = null; + + $this->connect(); + } + + protected function connect() + { + // already connected + if ($this->redis != null) + return $this->redis; + + // if password is specified attempt connection + if (strlen($this->password) > 0) + { + $this->redis = new PredisClient([ + "scheme" => $this->scheme, + "host" => $this->host, + "port" => $this->port, + "password" => $this->password]); + + return $this->redis; + } + + $this->redis = new PredisClient([ + "scheme" => $this->scheme, + "host" => $this->host, + "port" => $this->port]); + + return $this->redis; } public function getRedisClient() { - if ($this->env_flag == 'dev') - { - $this->redis = new PredisClient([ - "scheme"=>$this->scheme, - "host"=>$this->host, - "port"=>$this->port]); - } - else - { - $this->redis = new PredisClient([ - "scheme"=>$this->scheme, - "host"=>$this->host, - "port"=>$this->port, - "password"=>$this->password]); - } return $this->redis; } } diff --git a/src/Service/RiderAPIHandler/CMBRiderAPIHandler.php b/src/Service/RiderAPIHandler/CMBRiderAPIHandler.php new file mode 100644 index 00000000..3c0236e1 --- /dev/null +++ b/src/Service/RiderAPIHandler/CMBRiderAPIHandler.php @@ -0,0 +1,928 @@ +em = $em; + $this->redis = $redis; + $this->ef = $ef; + $this->rcache = $rcache; + $this->country_code = $country_code; + $this->mclient = $mclient; + $this->wh = $wh; + $this->jo_handler = $jo_handler; + $this->ic = $ic; + + // one device = one session, since we have control over the devices + // when a rider logs in, we just change the rider assigned to the device + // when a rider logs out, we remove the rider assigned to the device + $this->session = null; + } + + public function register(Request $req) + { + // confirm parameters + $required_params = [ + 'phone_number', + 'device_push_id' + ]; + + $missing = $this->checkMissingParameters($req, $required_params); + if (count($missing) > 0) + { + $params = implode(', ', $missing); + $data = [ + 'error' => 'Missing parameter(s): ' . $params + ]; + return $data; + } + + // retry until we get a unique id + while (true) + { + try + { + // instantiate session + $sess = new RiderSession(); + $sess->setPhoneNumber($req->request->get('phone_number')) + ->setDevicePushID($req->request->get('device_push_id')); + + // reopen in case we get an exception + if (!$this->em->isOpen()) + { + $this->em = $this->em->create( + $this->em->getConnection(), + $this->em->getConfiguration() + ); + } + + // save + $this->em->persist($sess); + $this->em->flush(); + + // create redis entry for the session + $redis_client = $this->redis->getRedisClient(); + $redis_key = 'rider.id.' . $sess->getID(); + error_log('redis_key: ' . $redis_key); + $redis_client->set($redis_key, ''); + } + catch (DBALException $e) + { + error_log($e->getMessage()); + // delay one second and try again + sleep(1); + continue; + } + + break; + } + + // return data + $data = [ + 'session_id' => $sess->getID() + ]; + + return $data; + } + + public function login(Request $req) + { + $required_params = [ + 'user', + 'pass', + ]; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + // check if session has a rider already + if ($this->session->hasRider()) + { + $data = [ + 'error' => 'Another rider is already logged in. Please logout first.' + ]; + return $data; + } + + // look for rider with username + $rider = $this->em->getRepository(Rider::class)->findOneBy(['username' => $req->request->get('user')]); + if ($rider == null) + { + $data = [ + 'error' => 'Invalid username or password.' + ]; + return $data; + } + + // check if rider password is correct + $encoder = $this->ef->getEncoder(new User()); + if (!$encoder->isPasswordValid($rider->getPassword(), $req->request->get('pass'), '')) + { + $data = [ + 'error' => 'Invalid username or password.' + ]; + return $data; + } + + // assign rider to session + $this->session->setRider($rider); + + $rider->setAvailable(true); + + $rider_id = $rider->getID(); + // cache rider location (default to hub) + // TODO: figure out longitude / latitude default + $this->rcache->addActiveRider($rider_id, 0, 0); + + // TODO: log rider logging in + + $this->em->flush(); + + // update redis rider.id. with the rider id + $redis_client = $this->redis->getRedisClient(); + $redis_key = 'rider.id.' . $this->session->getID(); + $rider_id = $rider->getID(); + + $redis_client->set($redis_key, $rider_id); + + $hub = $rider->getHub(); + if ($hub == null) + $hub_data = null; + else + { + $coord = $hub->getCoordinates(); + $hub_data = [ + 'id' => $hub->getID(), + 'name' => $hub->getName(), + 'branch' => $hub->getBranch(), + 'longitude' => $coord->getLongitude(), + 'latitude' => $coord->getLatitude(), + 'contact_nums' => $hub->getContactNumbers(), + ]; + } + + // data + $data = [ + 'hub' => $hub_data, + 'rider_id' => $rider_id, + ]; + + return $data; + } + + public function logout(Request $req) + { + $required_params = []; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + // make rider unavailable + $rider = $this->session->getRider(); + $rider->setAvailable(false); + + // remove from cache + $this->rcache->removeActiveRider($rider->getID()); + + // remove rider from session + $this->session->setRider(null); + + // TODO: log rider logging out + + $this->em->flush(); + + return $data; + } + + public function getJobOrder(Request $req) + { + // get the job order of the rider assigned to this session + $required_params = []; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + // are we logged in? + if (!$this->session->hasRider()) + { + $data = [ + 'error' => 'No logged in rider.' + ]; + return $data; + } + + $rider = $this->session->getRider(); + + // do we have a job order? + $jo = $rider->getActiveJobOrder(); + if ($jo == null) + { + $data = [ + 'job_order' => null + ]; + } + else + { + $coord = $jo->getCoordinates(); + $cust = $jo->getCustomer(); + $cv = $jo->getCustomerVehicle(); + $v = $cv->getVehicle(); + $inv = $jo->getInvoice(); + $promo = $inv->getPromo(); + + // invoice items + $inv_items = []; + foreach ($inv->getItems() as $item) + { + $item_batt = $item->getBattery(); + if ($item_batt == null) + $batt_id = null; + else + $batt_id = $item_batt->getID(); + + $inv_items[] = [ + 'id' => $item->getID(), + 'title' => $item->getTitle(), + 'qty' => $item->getQuantity(), + 'price' => $item->getPrice(), + 'batt_id' => $batt_id, + ]; + } + + // promo + if ($promo != null) + { + $promo_data = [ + 'id' => $promo->getID(), + 'name' => $promo->getName(), + 'code' => $promo->getCode(), + 'discount_rate' => $promo->getDiscountRate(), + 'discount_apply' => $promo->getDiscountApply(), + ]; + } + else + { + $promo_data = null; + } + + $trade_in_type = $jo->getTradeInType(); + if (empty($trade_in_type)) + $trade_in_type = 'none'; + + $data = [ + 'job_order' => [ + 'id' => $jo->getID(), + 'service_type' => $jo->getServiceType(), + 'date_schedule' => $jo->getDateSchedule()->format('Ymd H:i:s'), + 'longitude' => $coord->getLongitude(), + 'latitude' => $coord->getLatitude(), + 'status' => $jo->getStatus(), + 'customer' => [ + 'title' => $cust->getTitle(), + 'first_name' => $cust->getFirstName(), + 'last_name' => $cust->getLastName(), + 'phone_mobile' => $this->country_code . $cust->getPhoneMobile(), + ], + 'vehicle' => [ + 'manufacturer' => $v->getManufacturer()->getName(), + 'make' => $v->getMake(), + 'model' => $cv->getModelYear(), + 'plate_number' => $cv->getPlateNumber(), + 'color' => $cv->getColor(), + ], + 'or_num' => $jo->getORNum(), + 'or_name' => $jo->getORName(), + 'delivery_instructions' => $jo->getDeliveryInstructions(), + 'delivery_address' => $jo->getDeliveryAddress(), + 'landmark' => $jo->getLandmark(), + 'invoice' => [ + 'discount' => $inv->getDiscount(), + 'trade_in' => $inv->getTradeIn(), + 'total_price' => $inv->getTotalPrice(), + 'vat' => $inv->getVat(), + 'items' => $inv_items, + ], + 'mode_of_payment' => $jo->getModeOfPayment(), + 'trade_in_type' => $trade_in_type, + 'promo' => $promo_data, + // TODO: load the actual + 'has_warranty_doc' => false, + 'flag_coolant' => $jo->hasCoolant(), + 'has_motolite' => $cv->hasMotoliteBattery(), + ] + ]; + } + + return $data; + } + + public function acceptJobOrder(Request $req) + { + $required_params = ['jo_id']; + $data = $this->checkJO($req, $required_params, $jo); + if (isset($data['error'])) + return $data; + + // TODO: refactor this into a jo handler class, so we don't have to repeat for control center + + // set jo status to in transit + $jo->setStatus(JOStatus::IN_TRANSIT); + + // TODO: send mqtt event (?) + + // add event log + $rider = $this->session->getRider(); + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ACCEPT) + ->setJobOrder($jo) + ->setRider($rider); + $this->em->persist($event); + + $this->em->flush(); + + return $data; + } + + public function cancelJobOrder(Request $req) + { + $required_params = ['jo_id']; + $data = $this->checkJO($req, $required_params, $jo); + if (isset($data['error'])) + return $data; + + // $jo->cancel("rider cancelled"); + // requeue it, instead of cancelling it + $jo->requeue(); + + // add event log + $rider = $this->session->getRider(); + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::REQUEUE) + ->setJobOrder($jo) + ->setRider($rider); + $this->em->persist($event); + + $this->em->flush(); + + // send mqtt event + // send outlet assign since order should go back to hub and await reassignment to another rider + $payload = [ + 'event' => 'outlet_assign', + 'jo_id' => $jo->getID(), + ]; + $this->mclient->sendEvent($jo, $payload); + + return $data; + } + + public function arrive(Request $req) + { + $required_params = ['jo_id']; + $data = $this->checkJO($req, $required_params, $jo); + if (isset($data['error'])) + return $data; + + // TODO: refactor this into a jo handler class, so we don't have to repeat for control center + + // set jo status to in progress + $jo->setStatus(JOStatus::IN_PROGRESS); + + // add event log + $rider = $this->session->getRider(); + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ARRIVE) + ->setJobOrder($jo) + ->setRider($rider); + $this->em->persist($event); + + $this->em->flush(); + + // send mqtt event + $rider = $this->session->getRider(); + $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/assets/images/user.gif'; + if ($rider->getImageFile() != null) + $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/uploads/' . $rider->getImageFile(); + + $payload = [ + 'event' => 'driver_arrived', + 'jo_id' => $jo->getID(), + 'driver_image' => $image_url, + 'driver_name' => $rider->getFullName(), + 'driver_id' => $rider->getID(), + ]; + $this->mclient->sendEvent($jo, $payload); + + return $data; + } + + public function hubArrive(Request $req) + { + $required_params = []; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + // are we logged in? + if (!$this->session->hasRider()) + { + $data = [ + 'error' => 'No logged in rider.' + ]; + return $data; + } + + // TODO: tag rider as available + + $this->em->flush(); + + return $data; + } + + public function payment(Request $req) + { + $required_params = ['jo_id']; + $data = $this->checkJO($req, $required_params, $jo); + if (isset($data['error'])) + return $data; + + // set invoice to paid + $jo->getInvoice()->setStatus(InvoiceStatus::PAID); + + /* + // set jo status to fulfilled + $jo->setStatus(JOStatus::FULFILLED); + */ + $jo->fulfill(); + + // add event log + $rider = $this->session->getRider(); + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::FULFILL) + ->setJobOrder($jo) + ->setRider($rider); + $this->em->persist($event); + + // TODO: tag rider as unavailable + + // save to customer vehicle battery record + $this->jo_handler->updateVehicleBattery($jo); + + $this->em->flush(); + + // create warranty + if($this->jo_handler->checkIfNewBattery($jo)) + { + $serial = $jo->getCustomerVehicle()->getWarrantyCode(); + $warranty_class = $jo->getWarrantyClass(); + $first_name = $jo->getCustomer()->getFirstName(); + $last_name = $jo->getCustomer()->getLastName(); + $mobile_number = $jo->getCustomer()->getPhoneMobile(); + + // check if date fulfilled is null + if ($jo->getDateFulfill() == null) + $date_purchase = $jo->getDateCreate(); + else + $date_purchase = $jo->getDateFulfill(); + + // validate plate number + // $plate_number = $this->wh->cleanPlateNumber($jo->getCustomerVehicle()->getPlateNumber()); + $plate_number = Warranty::cleanPlateNumber($jo->getCustomerVehicle()->getPlateNumber()); + if ($plate_number != false) + { + $batt_list = array(); + $invoice = $jo->getInvoice(); + if (!empty($invoice)) + { + // get battery + $invoice_items = $invoice->getItems(); + foreach ($invoice_items as $item) + { + $battery = $item->getBattery(); + if ($battery != null) + { + $batt_list[] = $item->getBattery(); + } + } + } + + $this->wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class); + } + } + + // send mqtt event (fulfilled) + $rider = $this->session->getRider(); + $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/assets/images/user.gif'; + if ($rider->getImageFile() != null) + $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/uploads/' . $rider->getImageFile(); + + $payload = [ + 'event' => 'fulfilled', + 'jo_id' => $jo->getID(), + 'driver_image' => $image_url, + 'driver_name' => $rider->getFullName(), + 'driver_id' => $rider->getID(), + ]; + $this->mclient->sendEvent($jo, $payload); + + return $data; + } + + public function available(Request $req) + { + $required_params = []; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + // make rider available + $this->session->getRider()->setAvailable(true); + + // TODO: log rider available + $this->em->flush(); + + return $data; + } + + public function getPromos(Request $req) + { + $required_params = []; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + $promos = $this->em->getRepository(Promo::class)->findAll(); + + $promo_data = []; + foreach ($promos as $promo) + { + $promo_data[] = [ + 'id' => $promo->getID(), + 'name' => $promo->getName(), + 'code' => $promo->getCode(), + ]; + } + + $data = [ + 'promos' => $promo_data, + ]; + + return $data; + } + + public function getBatteries(Request $req) + { + // get batteries, models, and sizes + $required_params = []; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + $batts = $this->em->getRepository(Battery::class)->findAll(); + $models = $this->em->getRepository(BatteryModel::class)->findAll(); + $sizes = $this->em->getRepository(BatterySize::class)->findAll(); + + $batt_data = []; + foreach ($batts as $batt) + { + $batt_data[] = [ + 'id' => $batt->getID(), + 'model_id' => $batt->getModel()->getID(), + 'size_id' => $batt->getSize()->getID(), + 'sell_price' => $batt->getSellingPrice(), + ]; + } + + $model_data = []; + foreach ($models as $model) + { + $model_data[] = [ + 'id' => $model->getID(), + 'name' => $model->getName(), + ]; + } + + $size_data = []; + foreach ($sizes as $size) + { + $size_data[] = [ + 'id' => $size->getID(), + 'name' => $size->getName(), + ]; + } + + $data = [ + 'batteries' => $batt_data, + 'models' => $model_data, + 'sizes' => $size_data, + ]; + + return $data; + } + + public function changeService(Request $req) + { + $this->debugRequest($req); + + // allow rider to change service, promo, battery and trade-in options + $required_params = ['jo_id', 'stype_id', 'promo_id']; + $data = $this->checkJO($req, $required_params, $jo); + if (isset($data['error'])) + return $data; + + // check service type + $stype_id = $req->request->get('stype_id'); + if (!CMBServiceType::validate($stype_id)) + { + $data = [ + 'error' => 'Invalid service type - ' . $stype_id + ]; + return $data; + } + + // check promo id + $promo_id = $req->request->get('promo_id'); + // no promo + if ($promo_id == 0) + $promo = null; + else + { + $promo = $this->em->getRepository(Promo::class)->find($promo_id); + if ($promo == null) + { + $data = [ + 'error' => 'Invalid promo id - ' . $promo_id + ]; + return $data; + } + } + + // check or number + $or_num = $req->request->get('or_num'); + if ($or_num != null) + $jo->setORNum($or_num); + + // coolant + $flag_coolant = $req->request->get('flag_coolant', 'false'); + if ($flag_coolant == 'true') + $jo->setHasCoolant(true); + else + $jo->setHasCoolant(false); + + // has motolite battery + $cv = $jo->getCustomerVehicle(); + $has_motolite = $req->request->get('has_motolite', 'false'); + if ($has_motolite == 'true') + $cv->setHasMotoliteBattery(true); + else + $cv->setHasMotoliteBattery(false); + $this->em->persist($cv); + + // check battery id + $batt_id = $req->request->get('batt_id', null); + // no battery + if ($batt_id == 0 || $batt_id == null) + $battery = null; + else + { + $battery = $this->em->getRepository(Battery::class)->find($batt_id); + if ($battery == null) + { + $data = [ + 'error' => 'Invalid battery id - ' . $batt_id + ]; + return $data; + } + } + + // check trade in + $trade_in = $req->request->get('trade_in'); + if (!CMBTradeInType::validate($trade_in)) + $trade_in = null; + + // check mode of payment + $mode = $req->request->get('mode_of_payment'); + if (!ModeOfPayment::validate($mode)) + $mode = ModeOfPayment::CASH; + $jo->setModeOfPayment($mode); + + // generate new invoice + $crit = new InvoiceCriteria(); + $crit->setServiceType($stype_id); + $crit->setCustomerVehicle($cv); + $crit->setHasCoolant($jo->hasCoolant()); + + if ($promo != null) + $crit->addPromo($promo); + + if ($battery != null) + { + $crit->addEntry($battery, $trade_in, 1); + error_log('adding entry for battery - ' . $battery->getID()); + } + + $invoice = $this->ic->generateInvoice($crit); + + // remove previous invoice + $old_invoice = $jo->getInvoice(); + $this->em->remove($old_invoice); + $this->em->flush(); + + // save job order + $jo->setServiceType($stype_id); + + // save invoice + $jo->setInvoice($invoice); + $this->em->persist($invoice); + + // add event log + $rider = $this->session->getRider(); + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_EDIT) + ->setJobOrder($jo) + ->setRider($rider); + $this->em->persist($event); + + $this->em->flush(); + // TODO: send mqtt event (?) + + return $data; + } + + protected function checkMissingParameters(Request $req, $params = []) + { + $missing = []; + + // check if parameters are there + foreach ($params as $param) + { + if ($req->getMethod() == 'GET') + { + $check = $req->query->get($param); + if ($check == null) + $missing[] = $param; + } + else if ($req->getMethod() == 'POST') + { + $check = $req->request->get($param); + if ($check == null) + $missing[] = $param; + } + else + return $params; + } + + return $missing; + } + + protected function checkParamsAndKey(Request $req, $params) + { + $data = []; + + // check for api_key in query string + $api_key = $req->query->get('api_key'); + if (empty($api_key)) + { + $data = [ + 'error' => 'Missing API key' + ]; + return $data; + } + + // check missing parameters + $missing = $this->checkMissingParameters($req, $params); + if (count($missing) > 0) + { + $miss_string = implode(', ', $missing); + $data = [ + 'error' => 'Missing parameter(s): ' . $miss_string + ]; + return $data; + } + + // check api key + $sess = $this->checkAPIKey($req->query->get('api_key')); + if ($sess == null) + { + $data = [ + 'error' => 'Invalid API Key' + ]; + return $data; + } + + // store session + $this->session = $sess; + + return $data; + } + + protected function checkAPIKey($api_key) + { + // find the api key (session id) + $session = $this->em->getRepository(RiderSession::class)->find($api_key); + if ($session == null) + return null; + + return $session; + } + + protected function checkJO(Request $req, $required_params, &$jo = null) + { + // set jo status to in transit + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + // are we logged in? + if (!$this->session->hasRider()) + { + $data = [ + 'error' => 'No logged in rider.' + ]; + return $data; + } + + $rider = $this->session->getRider(); + + // check if we have an active JO + $jo = $rider->getActiveJobOrder(); + if ($jo == null) + { + $data = [ + 'error' => 'No active job order.' + ]; + return $data; + } + + // check if the jo_id sent is the same as our active jo + if ($req->request->get('jo_id') != $jo->getID()) + { + $data = [ + 'error' => 'Job order selected is not active job order.' + ]; + return $data; + } + + return $data; + } + + protected function debugRequest(Request $req) + { + $all = $req->request->all(); + error_log(print_r($all, true)); + } +} diff --git a/src/Service/RiderAPIHandler/ResqRiderAPIHandler.php b/src/Service/RiderAPIHandler/ResqRiderAPIHandler.php new file mode 100644 index 00000000..b5bed63b --- /dev/null +++ b/src/Service/RiderAPIHandler/ResqRiderAPIHandler.php @@ -0,0 +1,923 @@ +em = $em; + $this->redis = $redis; + $this->ef = $ef; + $this->rcache = $rcache; + $this->country_code = $country_code; + $this->mclient = $mclient; + $this->wh = $wh; + $this->jo_handler = $jo_handler; + $this->ic = $ic; + + // one device = one session, since we have control over the devices + // when a rider logs in, we just change the rider assigned to the device + // when a rider logs out, we remove the rider assigned to the device + $this->session = null; + } + + public function register(Request $req) + { + // confirm parameters + $required_params = [ + 'phone_number', + 'device_push_id' + ]; + + $missing = $this->checkMissingParameters($req, $required_params); + if (count($missing) > 0) + { + $params = implode(', ', $missing); + $data = [ + 'error' => 'Missing parameter(s): ' . $params + ]; + return $data; + } + + // retry until we get a unique id + while (true) + { + try + { + // instantiate session + $sess = new RiderSession(); + $sess->setPhoneNumber($req->request->get('phone_number')) + ->setDevicePushID($req->request->get('device_push_id')); + + // reopen in case we get an exception + if (!$this->em->isOpen()) + { + $this->em = $this->em->create( + $this->em->getConnection(), + $this->em->getConfiguration() + ); + } + + // save + $this->em->persist($sess); + $this->em->flush(); + + // create redis entry for the session + $redis_client = $this->redis->getRedisClient(); + $redis_key = 'rider.id.' . $sess->getID(); + error_log('redis_key: ' . $redis_key); + $redis_client->set($redis_key, ''); + } + catch (DBALException $e) + { + error_log($e->getMessage()); + // delay one second and try again + sleep(1); + continue; + } + + break; + } + + // return data + $data = [ + 'session_id' => $sess->getID() + ]; + + return $data; + } + + public function login(Request $req) + { + $required_params = [ + 'user', + 'pass', + ]; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + // check if session has a rider already + if ($this->session->hasRider()) + { + $data = [ + 'error' => 'Another rider is already logged in. Please logout first.' + ]; + return $data; + } + + // look for rider with username + $rider = $this->em->getRepository(Rider::class)->findOneBy(['username' => $req->request->get('user')]); + if ($rider == null) + { + $data = [ + 'error' => 'Invalid username or password.' + ]; + return $data; + } + + // check if rider password is correct + $encoder = $this->ef->getEncoder(new User()); + if (!$encoder->isPasswordValid($rider->getPassword(), $req->request->get('pass'), '')) + { + $data = [ + 'error' => 'Invalid username or password.' + ]; + return $data; + } + + // assign rider to session + $this->session->setRider($rider); + + $rider->setAvailable(true); + + $rider_id = $rider->getID(); + // cache rider location (default to hub) + // TODO: figure out longitude / latitude default + $this->rcache->addActiveRider($rider_id, 0, 0); + + // TODO: log rider logging in + + $this->em->flush(); + + // update redis rider.id. with the rider id + $redis_client = $this->redis->getRedisClient(); + $redis_key = 'rider.id.' . $this->session->getID(); + $rider_id = $rider->getID(); + + $redis_client->set($redis_key, $rider_id); + + $hub = $rider->getHub(); + if ($hub == null) + $hub_data = null; + else + { + $coord = $hub->getCoordinates(); + $hub_data = [ + 'id' => $hub->getID(), + 'name' => $hub->getName(), + 'branch' => $hub->getBranch(), + 'longitude' => $coord->getLongitude(), + 'latitude' => $coord->getLatitude(), + 'contact_nums' => $hub->getContactNumbers(), + ]; + } + + // data + $data = [ + 'hub' => $hub_data, + 'rider_id' => $rider_id, + ]; + + return $data; + } + + public function logout(Request $req) + { + $required_params = []; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + // make rider unavailable + $rider = $this->session->getRider(); + $rider->setAvailable(false); + + // remove from cache + $this->rcache->removeActiveRider($rider->getID()); + + // remove rider from session + $this->session->setRider(null); + + // TODO: log rider logging out + + $this->em->flush(); + + return $data; + } + + public function getJobOrder(Request $req) + { + // get the job order of the rider assigned to this session + $required_params = []; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + // are we logged in? + if (!$this->session->hasRider()) + { + $data = [ + 'error' => 'No logged in rider.' + ]; + return $data; + } + + $rider = $this->session->getRider(); + + // do we have a job order? + $jo = $rider->getActiveJobOrder(); + if ($jo == null) + { + $data = [ + 'job_order' => null + ]; + } + else + { + $coord = $jo->getCoordinates(); + $cust = $jo->getCustomer(); + $cv = $jo->getCustomerVehicle(); + $v = $cv->getVehicle(); + $inv = $jo->getInvoice(); + $promo = $inv->getPromo(); + + // invoice items + $inv_items = []; + foreach ($inv->getItems() as $item) + { + $item_batt = $item->getBattery(); + if ($item_batt == null) + $batt_id = null; + else + $batt_id = $item_batt->getID(); + + $inv_items[] = [ + 'id' => $item->getID(), + 'title' => $item->getTitle(), + 'qty' => $item->getQuantity(), + 'price' => $item->getPrice(), + 'batt_id' => $batt_id, + ]; + } + + // promo + if ($promo != null) + { + $promo_data = [ + 'id' => $promo->getID(), + 'name' => $promo->getName(), + 'code' => $promo->getCode(), + 'discount_rate' => $promo->getDiscountRate(), + 'discount_apply' => $promo->getDiscountApply(), + ]; + } + else + { + $promo_data = null; + } + + $trade_in_type = $jo->getTradeInType(); + if (empty($trade_in_type)) + $trade_in_type = 'none'; + + $data = [ + 'job_order' => [ + 'id' => $jo->getID(), + 'service_type' => $jo->getServiceType(), + 'date_schedule' => $jo->getDateSchedule()->format('Ymd H:i:s'), + 'longitude' => $coord->getLongitude(), + 'latitude' => $coord->getLatitude(), + 'status' => $jo->getStatus(), + 'customer' => [ + 'title' => $cust->getTitle(), + 'first_name' => $cust->getFirstName(), + 'last_name' => $cust->getLastName(), + 'phone_mobile' => $this->country_code . $cust->getPhoneMobile(), + ], + 'vehicle' => [ + 'manufacturer' => $v->getManufacturer()->getName(), + 'make' => $v->getMake(), + 'model' => $cv->getModelYear(), + 'plate_number' => $cv->getPlateNumber(), + 'color' => $cv->getColor(), + ], + 'or_num' => $jo->getORNum(), + 'or_name' => $jo->getORName(), + 'delivery_instructions' => $jo->getDeliveryInstructions(), + 'delivery_address' => $jo->getDeliveryAddress(), + 'landmark' => $jo->getLandmark(), + 'invoice' => [ + 'discount' => $inv->getDiscount(), + 'trade_in' => $inv->getTradeIn(), + 'total_price' => $inv->getTotalPrice(), + 'vat' => $inv->getVat(), + 'items' => $inv_items, + ], + 'mode_of_payment' => $jo->getModeOfPayment(), + 'trade_in_type' => $trade_in_type, + 'promo' => $promo_data, + // TODO: load the actual + 'has_warranty_doc' => false, + 'flag_coolant' => $jo->hasCoolant(), + 'has_motolite' => $cv->hasMotoliteBattery(), + ] + ]; + } + + return $data; + } + + public function acceptJobOrder(Request $req) + { + $required_params = ['jo_id']; + $data = $this->checkJO($req, $required_params, $jo); + if (isset($data['error'])) + return $data; + + // TODO: refactor this into a jo handler class, so we don't have to repeat for control center + + // set jo status to in transit + $jo->setStatus(JOStatus::IN_TRANSIT); + + // TODO: send mqtt event (?) + + // add event log + $rider = $this->session->getRider(); + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ACCEPT) + ->setJobOrder($jo) + ->setRider($rider); + $this->em->persist($event); + + $this->em->flush(); + + return $data; + } + + public function cancelJobOrder(Request $req) + { + $required_params = ['jo_id']; + $data = $this->checkJO($req, $required_params, $jo); + if (isset($data['error'])) + return $data; + + // $jo->cancel("rider cancelled"); + // requeue it, instead of cancelling it + $jo->requeue(); + + // add event log + $rider = $this->session->getRider(); + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::REQUEUE) + ->setJobOrder($jo) + ->setRider($rider); + $this->em->persist($event); + + $this->em->flush(); + + // send mqtt event + // send outlet assign since order should go back to hub and await reassignment to another rider + $payload = [ + 'event' => 'outlet_assign', + 'jo_id' => $jo->getID(), + ]; + $this->mclient->sendEvent($jo, $payload); + + return $data; + } + + public function arrive(Request $req) + { + $required_params = ['jo_id']; + $data = $this->checkJO($req, $required_params, $jo); + if (isset($data['error'])) + return $data; + + // TODO: refactor this into a jo handler class, so we don't have to repeat for control center + + // set jo status to in progress + $jo->setStatus(JOStatus::IN_PROGRESS); + + // add event log + $rider = $this->session->getRider(); + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ARRIVE) + ->setJobOrder($jo) + ->setRider($rider); + $this->em->persist($event); + + $this->em->flush(); + + // send mqtt event + $rider = $this->session->getRider(); + $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/assets/images/user.gif'; + if ($rider->getImageFile() != null) + $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/uploads/' . $rider->getImageFile(); + + $payload = [ + 'event' => 'driver_arrived', + 'jo_id' => $jo->getID(), + 'driver_image' => $image_url, + 'driver_name' => $rider->getFullName(), + 'driver_id' => $rider->getID(), + ]; + $this->mclient->sendEvent($jo, $payload); + + return $data; + } + + public function hubArrive(Request $req) + { + $required_params = []; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + // are we logged in? + if (!$this->session->hasRider()) + { + $data = [ + 'error' => 'No logged in rider.' + ]; + return $data; + } + + // TODO: tag rider as available + + $this->em->flush(); + + return $data; + } + + public function payment(Request $req) + { + $required_params = ['jo_id']; + $data = $this->checkJO($req, $required_params, $jo); + if (isset($data['error'])) + return $data; + + // set invoice to paid + $jo->getInvoice()->setStatus(InvoiceStatus::PAID); + + /* + // set jo status to fulfilled + $jo->setStatus(JOStatus::FULFILLED); + */ + $jo->fulfill(); + + // add event log + $rider = $this->session->getRider(); + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::FULFILL) + ->setJobOrder($jo) + ->setRider($rider); + $this->em->persist($event); + + // TODO: tag rider as unavailable + + // save to customer vehicle battery record + $this->jo_handler->updateVehicleBattery($jo); + + $this->em->flush(); + + // create warranty + if($this->jo_handler->checkIfNewBattery($jo)) + { + $serial = null; + $warranty_class = $jo->getWarrantyClass(); + $first_name = $jo->getCustomer()->getFirstName(); + $last_name = $jo->getCustomer()->getLastName(); + $mobile_number = $jo->getCustomer()->getPhoneMobile(); + + // check if date fulfilled is null + if ($jo->getDateFulfill() == null) + $date_purchase = $jo->getDateCreate(); + else + $date_purchase = $jo->getDateFulfill(); + + $plate_number = $this->wh->cleanPlateNumber($jo->getCustomerVehicle()->getPlateNumber()); + + $batt_list = array(); + $invoice = $jo->getInvoice(); + if (!empty($invoice)) + { + // get battery + $invoice_items = $invoice->getItems(); + foreach ($invoice_items as $item) + { + $battery = $item->getBattery(); + if ($battery != null) + { + $batt_list[] = $item->getBattery(); + } + } + } + + $this->wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class); + } + + // send mqtt event (fulfilled) + $rider = $this->session->getRider(); + $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/assets/images/user.gif'; + if ($rider->getImageFile() != null) + $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/uploads/' . $rider->getImageFile(); + + $payload = [ + 'event' => 'fulfilled', + 'jo_id' => $jo->getID(), + 'driver_image' => $image_url, + 'driver_name' => $rider->getFullName(), + 'driver_id' => $rider->getID(), + ]; + $this->mclient->sendEvent($jo, $payload); + + return $data; + } + + public function available(Request $req) + { + $required_params = []; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + // make rider available + $this->session->getRider()->setAvailable(true); + + // TODO: log rider available + $this->em->flush(); + + return $data; + } + + public function getPromos(Request $req) + { + $required_params = []; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + $promos = $this->em->getRepository(Promo::class)->findAll(); + + $promo_data = []; + foreach ($promos as $promo) + { + $promo_data[] = [ + 'id' => $promo->getID(), + 'name' => $promo->getName(), + 'code' => $promo->getCode(), + ]; + } + + $data = [ + 'promos' => $promo_data, + ]; + + return $data; + } + + public function getBatteries(Request $req) + { + // get batteries, models, and sizes + $required_params = []; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + $batts = $this->em->getRepository(Battery::class)->findAll(); + $models = $this->em->getRepository(BatteryModel::class)->findAll(); + $sizes = $this->em->getRepository(BatterySize::class)->findAll(); + + $batt_data = []; + foreach ($batts as $batt) + { + $batt_data[] = [ + 'id' => $batt->getID(), + 'model_id' => $batt->getModel()->getID(), + 'size_id' => $batt->getSize()->getID(), + 'sell_price' => $batt->getSellingPrice(), + ]; + } + + $model_data = []; + foreach ($models as $model) + { + $model_data[] = [ + 'id' => $model->getID(), + 'name' => $model->getName(), + ]; + } + + $size_data = []; + foreach ($sizes as $size) + { + $size_data[] = [ + 'id' => $size->getID(), + 'name' => $size->getName(), + ]; + } + + $data = [ + 'batteries' => $batt_data, + 'models' => $model_data, + 'sizes' => $size_data, + ]; + + return $data; + } + + public function changeService(Request $req) + { + $this->debugRequest($req); + + // allow rider to change service, promo, battery and trade-in options + $required_params = ['jo_id', 'stype_id', 'promo_id']; + $data = $this->checkJO($req, $required_params, $jo); + if (isset($data['error'])) + return $data; + + // check service type + $stype_id = $req->request->get('stype_id'); + if (!ServiceType::validate($stype_id)) + { + $data = [ + 'error' => 'Invalid service type - ' . $stype_id + ]; + return $data; + } + + // check promo id + $promo_id = $req->request->get('promo_id'); + // no promo + if ($promo_id == 0) + $promo = null; + else + { + $promo = $this->em->getRepository(Promo::class)->find($promo_id); + if ($promo == null) + { + $data = [ + 'error' => 'Invalid promo id - ' . $promo_id + ]; + return $data; + } + } + + // check or number + $or_num = $req->request->get('or_num'); + if ($or_num != null) + $jo->setORNum($or_num); + + // coolant + $flag_coolant = $req->request->get('flag_coolant', 'false'); + if ($flag_coolant == 'true') + $jo->setHasCoolant(true); + else + $jo->setHasCoolant(false); + + // has motolite battery + $cv = $jo->getCustomerVehicle(); + $has_motolite = $req->request->get('has_motolite', 'false'); + if ($has_motolite == 'true') + $cv->setHasMotoliteBattery(true); + else + $cv->setHasMotoliteBattery(false); + $this->em->persist($cv); + + // check battery id + $batt_id = $req->request->get('batt_id', null); + // no battery + if ($batt_id == 0 || $batt_id == null) + $battery = null; + else + { + $battery = $this->em->getRepository(Battery::class)->find($batt_id); + if ($battery == null) + { + $data = [ + 'error' => 'Invalid battery id - ' . $batt_id + ]; + return $data; + } + } + + // check trade in + $trade_in = $req->request->get('trade_in'); + if (!TradeInType::validate($trade_in)) + $trade_in = null; + + // check mode of payment + $mode = $req->request->get('mode_of_payment'); + if (!ModeOfPayment::validate($mode)) + $mode = ModeOfPayment::CASH; + $jo->setModeOfPayment($mode); + + // generate new invoice + $crit = new InvoiceCriteria(); + $crit->setServiceType($stype_id); + $crit->setCustomerVehicle($cv); + $crit->setHasCoolant($jo->hasCoolant()); + + if ($promo != null) + $crit->addPromo($promo); + + if ($battery != null) + { + $crit->addEntry($battery, $trade_in, 1); + error_log('adding entry for battery - ' . $battery->getID()); + } + + $invoice = $this->ic->generateInvoice($crit); + + // remove previous invoice + $old_invoice = $jo->getInvoice(); + $this->em->remove($old_invoice); + $this->em->flush(); + + // save job order + $jo->setServiceType($stype_id); + + // save invoice + $jo->setInvoice($invoice); + $this->em->persist($invoice); + + // add event log + $rider = $this->session->getRider(); + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_EDIT) + ->setJobOrder($jo) + ->setRider($rider); + $this->em->persist($event); + + $this->em->flush(); + // TODO: send mqtt event (?) + + return $data; + } + + protected function checkMissingParameters(Request $req, $params = []) + { + $missing = []; + + // check if parameters are there + foreach ($params as $param) + { + if ($req->getMethod() == 'GET') + { + $check = $req->query->get($param); + if ($check == null) + $missing[] = $param; + } + else if ($req->getMethod() == 'POST') + { + $check = $req->request->get($param); + if ($check == null) + $missing[] = $param; + } + else + return $params; + } + + return $missing; + } + + protected function checkParamsAndKey(Request $req, $params) + { + $data = []; + + // check for api_key in query string + $api_key = $req->query->get('api_key'); + if (empty($api_key)) + { + $data = [ + 'error' => 'Missing API key' + ]; + return $data; + } + + // check missing parameters + $missing = $this->checkMissingParameters($req, $params); + if (count($missing) > 0) + { + $miss_string = implode(', ', $missing); + $data = [ + 'error' => 'Missing parameter(s): ' . $miss_string + ]; + return $data; + } + + // check api key + $sess = $this->checkAPIKey($req->query->get('api_key')); + if ($sess == null) + { + $data = [ + 'error' => 'Invalid API Key' + ]; + return $data; + } + + // store session + $this->session = $sess; + + return $data; + } + + protected function checkAPIKey($api_key) + { + // find the api key (session id) + $session = $this->em->getRepository(RiderSession::class)->find($api_key); + if ($session == null) + return null; + + return $session; + } + + protected function checkJO(Request $req, $required_params, &$jo = null) + { + // set jo status to in transit + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + // are we logged in? + if (!$this->session->hasRider()) + { + $data = [ + 'error' => 'No logged in rider.' + ]; + return $data; + } + + $rider = $this->session->getRider(); + + // check if we have an active JO + $jo = $rider->getActiveJobOrder(); + if ($jo == null) + { + $data = [ + 'error' => 'No active job order.' + ]; + return $data; + } + + // check if the jo_id sent is the same as our active jo + if ($req->request->get('jo_id') != $jo->getID()) + { + $data = [ + 'error' => 'Job order selected is not active job order.' + ]; + return $data; + } + + return $data; + } + + protected function debugRequest(Request $req) + { + $all = $req->request->all(); + error_log(print_r($all, true)); + } +} diff --git a/src/Service/RiderAPIHandlerInterface.php b/src/Service/RiderAPIHandlerInterface.php new file mode 100644 index 00000000..753ee5cb --- /dev/null +++ b/src/Service/RiderAPIHandlerInterface.php @@ -0,0 +1,34 @@ +em = $em; + $this->mclient = $mclient; + $this->aclient = $aclient; + } + + // assign job order to rider + public function assignJobOrder(JobOrder $obj, Rider $rider) + { + // create the payload + $payload = [ + 'event' => 'driver_assigned' + ]; + + // send event + $this->mclient->sendEvent($obj, $payload); + + // check if rider is available + if ($rider->isAvailable()) + { + error_log('set rider availability to false'); + // set rider to unavailable + $rider->setAvailable(false); + + // send event to rider + $this->mclient->sendRiderEvent($obj, $payload); + + // send push notification + $this->aclient->sendPush($obj, "A RESQ rider is on his way to you."); + } + } + + // complete job order + public function fulfillJobOrder(JobOrder $obj, string $image_url, Rider $rider) + { + // send to mqtt + $payload = [ + 'event' => 'fulfilled', + 'jo_id' => $obj->getID(), + 'driver_image' => $image_url, + 'driver_name' => $rider->getFullName(), + 'driver_id' => $rider->getID(), + ]; + $this->mclient->sendEvent($obj, $payload); + + // send fulfill/complete event to rider + $this->mclient->sendRiderEvent($obj, $payload); + + // search for the JO assigned to rider with JOStatus::ASSIGNED and sort by assign date + $jo_results = $this->em->getRepository(JobOrder::class)->findBy(['status' => JOStatus::ASSIGNED, 'rider' => $rider->getID()], + ['date_assign' => 'ASC']); + + // check if jo_results is empty + if (!empty($jo_results)) + { + error_log('rider has another JO in queue'); + // get first entry + $jo = current($jo_results); + + // form the payload for the next job order + $jo_payload = [ + 'event' => 'driver_assigned' + ]; + + // set rider to unavailable + $rider->setAvailable(false); + + // send event to rider + $this->mclient->sendRiderEvent($jo, $jo_payload); + + // send push notification + $this->aclient->sendPush($obj, "A RESQ rider is on his way to you."); + } + } +} diff --git a/src/Service/RiderAssignmentHandler/ResqRiderAssignmentHandler.php b/src/Service/RiderAssignmentHandler/ResqRiderAssignmentHandler.php new file mode 100644 index 00000000..0b101a17 --- /dev/null +++ b/src/Service/RiderAssignmentHandler/ResqRiderAssignmentHandler.php @@ -0,0 +1,72 @@ +em = $em; + $this->mclient = $mclient; + $this->aclient = $aclient; + } + + // assign job order to rider + public function assignJobOrder(JobOrder $obj, Rider $rider) + { + // create the payload + $payload = [ + 'event' => 'driver_assigned' + ]; + + // send event + $this->mclient->sendEvent($obj, $payload); + + // check if rider is available + if ($rider->isAvailable()) + { + error_log('set rider availability to false'); + // set rider to unavailable + $rider->setAvailable(false); + + // send event to rider + $this->mclient->sendRiderEvent($obj, $payload); + + // send push notification + $this->aclient->sendPush($obj, "A RESQ rider is on his way to you."); + } + } + + // complete job order + public function fulfillJobOrder(JobOrder $obj, string $image_url, Rider $rider) + { + // send to mqtt + $payload = [ + 'event' => 'fulfilled', + 'jo_id' => $obj->getID(), + 'driver_image' => $image_url, + 'driver_name' => $rider->getFullName(), + 'driver_id' => $rider->getID(), + ]; + $this->mclient->sendEvent($obj, $payload); + + // send fulfill/complete event to rider + $this->mclient->sendRiderEvent($obj, $payload); + } +} diff --git a/src/Service/RiderAssignmentHandlerInterface.php b/src/Service/RiderAssignmentHandlerInterface.php new file mode 100644 index 00000000..08e0e816 --- /dev/null +++ b/src/Service/RiderAssignmentHandlerInterface.php @@ -0,0 +1,15 @@ +redis = $redis_prov->getRedisClient(); + $this->loc_key = $loc_key; + $this->status_key = $status_key; + } + + public function addActiveRider($id, $lat, $lng) + { + $this->redis->geoadd( + $this->loc_key, + $lng, + $lat, + $id + ); + } + + public function getAllActiveRiders() + { + $all_riders = $this->redis->georadius( + $this->loc_key, + 0, + 0, + 41000, + 'km', + ['WITHCOORD' => true] + ); + + $locs = []; + foreach ($all_riders as $data) + { + $id = $data[0]; + $lng = $data[1][0]; + $lat = $data[1][1]; + + $locs[$id] = [ + 'longitude' => $lng, + 'latitude' => $lat, + ]; + } + + // error_log(print_r($all_riders, true)); + return $locs; + } + + public function removeActiveRider($id) + { + $this->redis->zrem( + $this->loc_key, + $id + ); + } + + public function incJobOrderCount($id, $status) + { + $this->redis->hincrby($this->status_key, $id, 1); + } + + public function decJobOrderCount($id, $status) + { + $this->redis->hincrby($this->status_key, $id, -1); + } +} diff --git a/src/Service/RiderTracker.php b/src/Service/RiderTracker.php index 1e377ec7..905171e4 100644 --- a/src/Service/RiderTracker.php +++ b/src/Service/RiderTracker.php @@ -38,15 +38,18 @@ class RiderTracker $long = $this->redis->hget($key, 'longitude'); $lat = $this->redis->hget($key, 'latitude'); - $coordinates = new Point($long, $lat); - } - else - { - $rider = $this->em->getRepository(Rider::class)->find($rider_id); - $coordinates = $rider->getHub()->getCoordinates(); + return new Point($long, $lat); } - return $coordinates; + // not in cache, get hub + $rider = $this->em->getRepository(Rider::class)->find($rider_id); + $hub = $rider->getHub(); + // no hub + // TODO: return valid coordinate + if ($hub == null) + return new Point(0, 0); + + return $hub->getCoordinates(); } } diff --git a/symfony.lock b/symfony.lock index 88d10d33..116ed675 100644 --- a/symfony.lock +++ b/symfony.lock @@ -122,6 +122,9 @@ "ocramius/proxy-manager": { "version": "2.2.0" }, + "php": { + "version": "7.2" + }, "predis/predis": { "version": "v1.1.1" }, @@ -152,6 +155,9 @@ "setasign/fpdf": { "version": "1.8.1" }, + "symfony/asset": { + "version": "v4.4.3" + }, "symfony/cache": { "version": "v4.0.2" }, @@ -182,6 +188,9 @@ "symfony/dotenv": { "version": "v4.0.2" }, + "symfony/error-handler": { + "version": "v4.4.4" + }, "symfony/event-dispatcher": { "version": "v4.0.2" }, diff --git a/templates/base.html.twig b/templates/base.html.twig index 251d8f55..ec13aa91 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -35,12 +35,16 @@ - {% block stylesheets %}{% endblock %} + {% block stylesheets %} + + {% endblock %} - +
@@ -695,30 +699,6 @@
-
-
-
-
- - {{ "now"|date("Y") }} © {% trans %}copyright{% endtrans %} - -
-
- -
-
-
-
diff --git a/templates/base_minimal.html.twig b/templates/base_minimal.html.twig new file mode 100644 index 00000000..8a51bcc6 --- /dev/null +++ b/templates/base_minimal.html.twig @@ -0,0 +1,70 @@ +{% import 'menu.html.twig' as menu %} + + + + + + + {% block title %}{% trans %}block_title{% endtrans %}{% endblock %} + + + + + + + + + + + + + + + + + + + + + + + + + {% block stylesheets %} + + {% endblock %} + + + + + + + {% block body %}{% endblock %} + + + + + + + + + + + + + + + + {% block scripts %}{% endblock %} + + + diff --git a/templates/customer/cmb.form.html.twig b/templates/customer/cmb.form.html.twig new file mode 100644 index 00000000..e417f3bb --- /dev/null +++ b/templates/customer/cmb.form.html.twig @@ -0,0 +1,1127 @@ +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+
+

Customers

+
+
+
+ +
+ +
+
+
+
+
+
+ + + +

+ {% if mode == 'update' %} + Edit Customer + {{ obj.getFirstName() ~ ' ' ~ obj.getLastName() }} + {% else %} + New Customer + {% endif %} +

+
+
+
+
+
+
+
+

+ Customer Info +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + +
+
+
+
+ + + +
+
+
+
+ + + + +
+
+
+
+ + + + +
+
+
+ +
+ +
+
+

+ Contact Numbers +

+
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ + {# +
+
+
+
+
+
+
+ + + +
+
+ +
+
+ #} +
+ +
+ +
+
+

+ Vehicles +

+
+
+
+
+
+
+
+
+ +
+
+
+ + {% if mode == 'update' %} +
+ +
+
+

+ Tickets +

+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+

+ Job Orders +

+
+
+
+
+
+
+
+
+ {% endif %} +
+
+
+
+
+ + Back +
+
+
+
+
+
+
+
+
+ +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/templates/customer/form.html.twig b/templates/customer/form.html.twig index af85fd94..cb07b5a3 100644 --- a/templates/customer/form.html.twig +++ b/templates/customer/form.html.twig @@ -1,1189 +1,1214 @@ -{% extends 'base.html.twig' %} - -{% block body %} - -
-
-
-

Customers

-
-
-
- -
- -
-
-
-
-
-
- - - -

- {% if mode == 'update' %} - Edit Customer - {{ obj.getFirstName() ~ ' ' ~ obj.getLastName() }} - {% else %} - New Customer - {% endif %} -

-
-
-
-
-
-
-
-

- Customer Info -

-
-
-
- - - -
-
- - - -
-
- - - -
-
-
-
- - - -
-
- - -
-
-
-
- - - -
-
-
-
- - - - -
-
-
-
- - - - -
-
-
- -
- -
-
-

- Contact Numbers -

-
-
-
- -
- {% trans %}country_code_prefix{% endtrans %} - - -
-
-
- -
- {% trans %}country_code_prefix{% endtrans %} - - -
-
-
-
-
- -
- {% trans %}country_code_prefix{% endtrans %} - - -
-
-
- -
- {% trans %}country_code_prefix{% endtrans %} - - -
-
-
- - {# -
-
-
-
-
-
-
- - - -
-
- -
-
- #} -
- -
- -
-
-

- Vehicles -

-
-
-
-
-
-
-
-
- -
-
-
- - {% if mode == 'update' %} -
- -
-
-

- Tickets -

-
-
-
-
-
-
-
- -
-
- -
-
- -
-
-

- Job Orders -

-
-
-
-
-
-
-
-
- {% endif %} -
-
-
-
-
- - Back -
-
-
-
-
-
-
-
-
- -{% endblock %} - -{% block scripts %} - -{% endblock %} +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+
+

Customers

+
+
+
+ +
+ +
+
+
+
+
+
+ + + +

+ {% if mode == 'update' %} + Edit Customer + {{ obj.getFirstName() ~ ' ' ~ obj.getLastName() }} + {% else %} + New Customer + {% endif %} +

+
+
+
+
+
+
+
+

+ Customer Info +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + +
+
+
+
+ + + +
+
+
+
+ + + + +
+
+
+ +
+ + +
+
+
+
+ + + +
+
+
+
+ + + + +
+
+
+ +
+ +
+
+

+ Contact Numbers +

+
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ + {# +
+
+
+
+
+
+
+ + + +
+
+ +
+
+ #} +
+ +
+ +
+
+

+ Vehicles +

+
+
+
+
+
+
+
+
+ +
+
+
+ + {% if mode == 'update' %} +
+ +
+
+

+ Tickets +

+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+

+ Job Orders +

+
+
+
+
+
+
+
+
+ {% endif %} +
+
+
+
+
+ + Back +
+
+
+
+
+
+
+
+
+ +{% endblock %} + +{% block scripts %} + +{% endblock %} + diff --git a/templates/geofence/list.html.twig b/templates/geofence/list.html.twig index 1b9598bc..f8f59a16 100644 --- a/templates/geofence/list.html.twig +++ b/templates/geofence/list.html.twig @@ -96,7 +96,10 @@ initMap(); function initMap() { var map = new google.maps.Map(document.getElementById('m_gmap'), { - center: {lat: 14.6091, lng: 121.0223}, + center: { + lat: {% trans %}default_lat{% endtrans %}, + lng: {% trans %}default_long{% endtrans %}, + }, mapTypeId: 'roadmap', zoom: 13 }); diff --git a/templates/home.html.twig b/templates/home.html.twig index 2907f228..b7ac2c24 100644 --- a/templates/home.html.twig +++ b/templates/home.html.twig @@ -1,32 +1,94 @@ {% extends 'base.html.twig' %} -{% block body %} - -
-
-
-

- Dashboard -

-
-
- - - - - - - - - -
-
-
- -
- - - - -
+{% block stylesheets %} + +{% endblock %} + +{% block body %} +
+ + +{% endblock %} + + +{% block scripts %} + + + +{% if dashboard_enable == 'true' %} + {{ include('map/' ~ map_js_file) }} +{% endif %} + {% endblock %} diff --git a/templates/hub/form.html.twig b/templates/hub/form.html.twig index bb943166..df8a7190 100644 --- a/templates/hub/form.html.twig +++ b/templates/hub/form.html.twig @@ -164,12 +164,34 @@ {% block scripts %} - + + + + +{% endblock %} diff --git a/templates/job-order/cmb.form.onestep.html.twig b/templates/job-order/cmb.form.onestep.html.twig new file mode 100644 index 00000000..e0714676 --- /dev/null +++ b/templates/job-order/cmb.form.onestep.html.twig @@ -0,0 +1,1607 @@ +{% extends 'base.html.twig' %} + +{% block body %} + + + +
+ +
+
+
+
+
+
+ + + +

+ One-step Job Order +

+
+
+
+
+ + +
+ {%if ftags.vehicle_dropdown %} +
+
+
+ + + +
+ +
+
+
+
+
+ + + +
+
+
+ {% else %} + + {% endif %} + {% if obj.getReferenceJO %} +
+
+
+ + + +
+
+
+ {% endif %} + +
+
+

+ Customer Details +

+ + + +
+
+
+ + + +
+
+ + + +
+
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+
+
+ + + +
+
+
+
+
+

+ Vehicle Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+ +
+
+
+
+

+ Battery Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+
+
+

+ Transaction Details +

+ + + +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ +
+ + + + +
+ +
+
+ +
+ + + + +
+ +
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+ + + +
+
+
+ + +
+
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+
+
+
+

+ Location +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + + +
+ + + + +
+
+
+
+
+ +
+
+

+ Nearest Hubs +

+
+
+
+ + + +
+ + + + + + + + + + + + + +
HubBranchContact NumbersDistance in KMAction
+
+
+
+
+
+
+

+ Rider Assignment +

+
+
+
+ + + +
+ + + + + + + + + + + + {% if mode in ['onestep-edit'] %} + {% set avail_riders = obj.getHub.getAvailableRiders|default([]) %} + + + + + {% if obj.getHub %} + {% for rider in avail_riders %} + + + + + + {% endfor %} + {% endif %} + {% endif %} + +
First NameLast NameContact No.Plate NumberAction
+ No riders available. +
{{ rider.getFirstName }}{{ rider.getLastName }}{{ rider.getContactNumber }}{{ rider.getPlateNumber }}
+
+
+
+
+ +
+
+
+

+ Invoice +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ + {% if ftags.invoice_edit %} + + + {% else %} + + {% endif %} +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + + + + + + + + + {% if not obj.getInvoice or (obj.getInvoice and obj.getInvoice.getItems|length == 0) %} + + + + {% else %} + {% for item in obj.getInvoice.getItems %} + + + + + + + {% endfor %} + {% endif %} + +
ItemQuantityUnit PriceAmount
+ No items to display. +
{{ item.getTitle }}{{ item.getQuantity|number_format }}{{ item.getPrice|number_format(2) }}{{ (item.getPrice * item.getQuantity)|number_format(2) }}
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + +
+
+
+
+
+
+
+
+ + {% if ftags.set_map_coordinate and is_granted('joborder.cancel') and not obj.isCancelled %} + Cancel Job Order + {% endif %} + Back +
+
+
+
+ +
+
+
+
+{% endblock %} + +{% block scripts %} +{{ include('map/' ~ map_js_file) }} + + + + +{% endblock %} + diff --git a/templates/job-order/form.html.twig b/templates/job-order/form.html.twig index 1914dfe9..a0e5985c 100644 --- a/templates/job-order/form.html.twig +++ b/templates/job-order/form.html.twig @@ -1,1674 +1,1771 @@ -{% extends 'base.html.twig' %} - -{% block body %} - -
-
-
-

Job Order

-
-
-
- -
- -
-
-
-
-
-
- - - -

- {% if mode == 'update-processing' %} - Dispatch - {{ obj.getID() }} - {% elseif mode == 'update-assigning' %} - Rider Assignment - {{ obj.getID() }} - {% elseif mode == 'update-reassign-hub' %} - Re-assign Hub - {{ obj.getID() }} - {% elseif mode == 'update-reassign-rider' %} - Re-assign Rider - {{ obj.getID() }} - {% elseif mode == 'update-all' %} - Viewing - {{ obj.getID() }} - {% else %} - Incoming - {% endif %} -

-
-
-
-
- -
- - {% if ftags.vehicle_dropdown %} -
-
-
- - - -
- -
-
-
-
-
- - - -
-
-
- {% else %} - - {% endif %} - {% if obj.getReferenceJO %} -
-
-
- - - -
-
-
- {% endif %} - -
-
-

- Customer Details -

-
-
-
- - - -
-
- - - -
-
-
-
- -
- {% trans %}country_code_prefix{% endtrans %} - - -
-
-
- -
- {% trans %}country_code_prefix{% endtrans %} - - -
-
-
-
-
- -
- {% trans %}country_code_prefix{% endtrans %} - - -
-
-
- -
- {% trans %}country_code_prefix{% endtrans %} - - -
-
-
-
-
- - - -
-
-
-
-
-

- Vehicle Details -

-
-
-
- - - -
-
- - - -
-
- - - -
-
-
-
- - - -
-
- - - -
-
-
-
-
-

- Battery Details -

-
-
-
- - - -
-
- - - -
-
- - - -
-
-
-
-
-
-

- Transaction Details -

- - - -
-
-
- - - -
-
- - - -
-
- - - -
-
-
-
- - - -
-
- -
- - - - -
- -
-
- -
- - - - -
- -
-
-
-
- - - -
-
- - - -
-
-
-
-
- - - -
-
-
- - -
-
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
-
- -
-
-
-
-

- Location -

-
-
-
- - - -
-
- - - -
-
-
-
- - - - -
- - - - -
-
-
-
-
-
-
-
-

- Invoice -

-
-
-
- - - -
-
- - - -
-
-
-
- - {% if ftags.invoice_edit %} - - - {% else %} - - {% endif %} -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
-
-
- - - - - - - - - - - - {% if not obj.getInvoice or (obj.getInvoice and obj.getInvoice.getItems|length == 0) %} - - - - {% else %} - {% for item in obj.getInvoice.getItems %} - - - - - - - {% endfor %} - {% endif %} - -
ItemQuantityUnit PriceAmount
- No items to display. -
{{ item.getTitle }}{{ item.getQuantity|number_format }}{{ item.getPrice|number_format(2) }}{{ (item.getPrice * item.getQuantity)|number_format(2) }}
-
-
- {% if ftags.invoice_edit %} -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- - - -
-
- {% endif %} -
- - {% if mode in ['update-processing', 'update-reassign-hub'] %} -
-
-
-

- Nearest Hubs -

-
-
-
- - -
- - - - - - - - - - - - - - - - - - {% for hub in hubs %} - - - - - - - - - - {% endfor %} - -
HubBranchAvailable RidersJobs For AssignmentContact NumbersAction
- No items to display. -
{{ hub.hub.getName }}{{ hub.hub.getBranch }}{{ hub.rider_count }}{{ hub.jo_count }}{{ hub.hub.getContactNumbers|replace({"\n": ', '}) }} - {% if hub.flag_rejected %} - - {% else %} - - {% endif %} -
-
-
-
-
-
-
-
- - -
-
- - -
-
-
-
-
-
-
-
-
-
- {% endif %} - - {% if mode in ['update-assigning', 'update-fulfillment', 'update-reassign-rider', 'update-all'] %} -
- {% if obj.getHub %} -
-
-

- Hub Details -

-
-
-
- - - -
-
- - - -
-
- - - -
-
-
-
- - - -
-
- - - -
-
-
- {% endif %} - -
- - {% if mode in ['update-assigning', 'update-reassign-rider'] %} -
-
-

- Rider Assignment -

-
-
-
- - -
- - - - - - - - - - - - - {% set avail_riders = obj.getHub.getAvailableRiders|default([]) %} - - - - - {% if obj.getHub %} - {% for rider in avail_riders %} - - - - - - - - - {% endfor %} - {% endif %} - -
First NameLast NameContact No.Plate NumberStatus
- No riders available. -
-
-
{{ rider.getFirstName }}{{ rider.getLastName }}{{ rider.getContactNumber }}{{ rider.getPlateNumber }}
-
-
-
-
- {% endif %} - - {% if mode in ['update-fulfillment', 'update-all'] %} - {% if obj.getRider %} -
-
-

- Rider Details -

-
-
-
- - - -
-
- - - -
-
-
-
- - - -
-
- - - -
-
-
-
- -
-
-
-
- {% endif %} - {% endif %} - {% endif %} - - {% if mode == 'update-all' %} -
- -
-
-

- Timeline -

-
-
-
-
-
- {% for event in obj.getEvents %} -
- - {{ event.getDateHappen|date("M j, Y") }} -
{{ event.getDateHappen|date("h:i:s a") }}
-
-
- -
-
- {{ event.getTypeName }} by {{ event.getUser.getFullName|default('Application') }} {% if event.getRider %} - Rider - {{ event.getRider.getFullName }}{% endif %} -
-
- {% endfor %} -
-
-
-
-
- {% endif %} - - {% if ftags.ticket_table %} -
- -
-
-

- Tickets -

-
-
-
-
-
-
-
- -
-
- {% endif %} - -
-
-
-
-
- {% if mode != 'update-all' %} - - {% endif %} - {% if ftags.set_map_coordinate and is_granted('joborder.cancel') and not obj.isCancelled %} - Cancel Job Order - {% endif %} - {% if mode != 'create' %} - Back - {% endif %} -
-
-
-
-
-
-
-
-
- - {% if mode in ['update-processing', 'update-reassign-hub'] %} - - - {% endif %} -{% endblock %} - -{% block scripts %} - - - - - -{% endblock %} +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+
+

Job Order

+
+
+
+ +
+ +
+
+
+
+
+
+ + + +

+ {% if mode == 'update-processing' %} + Dispatch + {{ obj.getID() }} + {% elseif mode == 'update-assigning' %} + Rider Assignment + {{ obj.getID() }} + {% elseif mode == 'update-reassign-hub' %} + Re-assign Hub + {{ obj.getID() }} + {% elseif mode == 'update-reassign-rider' %} + Re-assign Rider + {{ obj.getID() }} + {% elseif mode == 'update-all' %} + Viewing + {{ obj.getID() }} + {% else %} + Incoming + {% endif %} +

+
+
+
+
+ + +
+ + {% if ftags.vehicle_dropdown %} +
+
+
+ + + +
+ +
+
+
+
+
+ + + +
+
+
+ {% else %} + + {% endif %} + {% if obj.getReferenceJO %} +
+
+
+ + + +
+
+
+ {% endif %} + +
+
+

+ Customer Details +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+

+ Vehicle Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+

+ Battery Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+
+
+

+ Transaction Details +

+ + + +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ +
+ + + + +
+ +
+
+ +
+ + + + +
+ +
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+ + + +
+
+
+ + +
+
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+
+
+ +
+ + +
+
+
+
+
+ + + +
+
+
+ +
+
+
+
+

+ Location +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + + +
+ + + + +
+
+
+
+
+
+
+
+

+ Invoice +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ + {% if ftags.invoice_edit %} + + + {% else %} + + {% endif %} +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + + + + + + + + + + {% if not obj.getInvoice or (obj.getInvoice and obj.getInvoice.getItems|length == 0) %} + + + + {% else %} + {% for item in obj.getInvoice.getItems %} + + + + + + + {% endfor %} + {% endif %} + +
ItemQuantityUnit PriceAmount
+ No items to display. +
{{ item.getTitle }}{{ item.getQuantity|number_format }}{{ item.getPrice|number_format(2) }}{{ (item.getPrice * item.getQuantity)|number_format(2) }}
+
+
+ {% if ftags.invoice_edit %} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + +
+
+ {% endif %} +
+ + {% if mode in ['update-processing', 'update-reassign-hub'] %} +
+
+
+

+ Nearest Hubs +

+
+
+
+ + +
+ + + + + + + + + + + + + + + + + + {% for hub in hubs %} + + + + + + + + + + {% endfor %} + +
HubBranchAvailable RidersJobs For AssignmentContact NumbersAction
+ No items to display. +
{{ hub.hub.getName }}{{ hub.hub.getBranch }}{{ hub.rider_count }}{{ hub.jo_count }}{{ hub.hub.getContactNumbers|replace({"\n": ', '}) }} + {% if hub.flag_rejected %} + + {% else %} + + {% endif %} +
+
+
+
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+
+
+ {% endif %} + + {% if mode in ['update-assigning', 'update-fulfillment', 'update-reassign-rider', 'update-all'] %} +
+ {% if obj.getHub %} +
+
+

+ Hub Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+ {% endif %} + +
+ + {% if mode in ['update-assigning', 'update-reassign-rider'] %} +
+
+

+ Rider Assignment +

+
+
+
+ + +
+ + + + + + + + + + + + + {% set avail_riders = obj.getHub.getAvailableRiders|default([]) %} + + + + + {% if obj.getHub %} + {% for rider in avail_riders %} + + + + + + + + + {% endfor %} + {% endif %} + +
First NameLast NameContact No.Plate NumberStatus
+ No riders available. +
+
+
{{ rider.getFirstName }}{{ rider.getLastName }}{{ rider.getContactNumber }}{{ rider.getPlateNumber }}
+
+
+
+
+ {% endif %} + + {% if mode in ['update-fulfillment', 'update-all'] %} + {% if obj.getRider %} +
+
+

+ Rider Details +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+ +
+
+
+
+ {% endif %} + {% endif %} + {% endif %} + + {% if mode == 'update-all' %} +
+ +
+
+

+ Timeline +

+
+
+
+
+
+ {% for event in obj.getEvents %} +
+ + {{ event.getDateHappen|date("M j, Y") }} +
{{ event.getDateHappen|date("h:i:s a") }}
+
+
+ +
+
+ {{ event.getTypeName }} by {{ event.getUser.getFullName|default('Application') }} {% if event.getRider %} - Rider - {{ event.getRider.getFullName }}{% endif %} +
+
+ {% endfor %} +
+
+
+
+
+ {% endif %} + + {% if ftags.ticket_table %} +
+ +
+
+

+ Tickets +

+
+
+
+
+
+
+
+ +
+
+ {% endif %} + +
+
+
+
+
+ {% if mode != 'update-all' %} + + {% endif %} + {% if ftags.set_map_coordinate and is_granted('joborder.cancel') and not obj.isCancelled %} + Cancel Job Order + {% endif %} + {% if mode != 'create' %} + Back + {% endif %} +
+
+
+
+
+
+
+
+
+ + {% if mode in ['update-processing', 'update-reassign-hub'] %} + + + {% endif %} +{% endblock %} + +{% block scripts %} +{{ include('map/' ~ map_js_file) }} + + + + +{% endblock %} + diff --git a/templates/job-order/form.onestep.html.twig b/templates/job-order/form.onestep.html.twig new file mode 100644 index 00000000..8e4ac45d --- /dev/null +++ b/templates/job-order/form.onestep.html.twig @@ -0,0 +1,1514 @@ +{% extends 'base.html.twig' %} + +{% block body %} + + + +
+ +
+
+
+
+
+
+ + + +

+ One-step Job Order +

+
+
+
+
+ +
+ {%if ftags.vehicle_dropdown %} +
+
+
+ + + +
+ +
+
+
+
+
+ + + +
+
+
+ {% else %} + + {% endif %} + {% if obj.getReferenceJO %} +
+
+
+ + + +
+
+
+ {% endif %} + +
+
+

+ Customer Details +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+
+
+ + + +
+
+
+
+
+

+ Vehicle Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+

+ Battery Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+
+
+

+ Transaction Details +

+ + + +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ +
+ + + + +
+ +
+
+ +
+ + + + +
+ +
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+ + + +
+
+
+ + +
+
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+
+
+
+

+ Location +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + + +
+ + + + +
+
+
+
+
+ +
+
+

+ Nearest Hubs +

+
+
+
+ + + +
+ + + + + + + + + + + + + +
HubBranchContact NumbersDistance in KMAction
+
+
+
+
+
+
+

+ Rider Assignment +

+
+
+
+ + + +
+ + + + + + + + + + + + {% if mode in ['onestep-edit'] %} + {% set avail_riders = obj.getHub.getAvailableRiders|default([]) %} + + + + + {% if obj.getHub %} + {% for rider in avail_riders %} + + + + + + {% endfor %} + {% endif %} + {% endif %} + +
First NameLast NameContact No.Plate NumberAction
+ No riders available. +
{{ rider.getFirstName }}{{ rider.getLastName }}{{ rider.getContactNumber }}{{ rider.getPlateNumber }}
+
+
+
+
+ +
+
+
+

+ Invoice +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ + {% if ftags.invoice_edit %} + + + {% else %} + + {% endif %} +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + + + + + + + + + {% if not obj.getInvoice or (obj.getInvoice and obj.getInvoice.getItems|length == 0) %} + + + + {% else %} + {% for item in obj.getInvoice.getItems %} + + + + + + + {% endfor %} + {% endif %} + +
ItemQuantityUnit PriceAmount
+ No items to display. +
{{ item.getTitle }}{{ item.getQuantity|number_format }}{{ item.getPrice|number_format(2) }}{{ (item.getPrice * item.getQuantity)|number_format(2) }}
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + +
+
+
+
+
+
+
+
+ + {% if ftags.set_map_coordinate and is_granted('joborder.cancel') and not obj.isCancelled %} + Cancel Job Order + {% endif %} + Back +
+
+
+
+ +
+
+
+
+{% endblock %} + +{% block scripts %} +{{ include('map/' ~ map_js_file) }} + + + + +{% endblock %} + diff --git a/templates/job-order/form.pdf.html.twig b/templates/job-order/form.pdf.html.twig index 5499f1fb..4602b6e6 100644 --- a/templates/job-order/form.pdf.html.twig +++ b/templates/job-order/form.pdf.html.twig @@ -221,7 +221,7 @@
- +
@@ -651,8 +651,8 @@ $(function() { var map = new GMaps({ div: '#m_gmap', - lat: 14.6091, - lng: 121.0223, + lat: {% trans %}default_lat{% endtrans %}, + lng: {% trans %}default_long{% endtrans %}, click: function(e) { // handle click in map selectPoint(map, e.latLng); diff --git a/templates/job-order/list.all.html.twig b/templates/job-order/list.all.html.twig index a4cd4afe..01ff98d5 100644 --- a/templates/job-order/list.all.html.twig +++ b/templates/job-order/list.all.html.twig @@ -19,21 +19,44 @@
-
-
-
-
-
- - - - -
-
-
-
-
-
+
+
+
+
+
+ + + + +
+
+
+
+
+ +
+
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+
+
@@ -45,93 +68,119 @@ {% endblock %} {% block scripts %} - + // auto refresh table + setInterval(function() { + table.reload(); + }, {{ table_refresh_rate }}); + + $("#rider_list").on("change", function() { + table.search($(this).val(), "rider"); + }); + + $("#date_start").on("change", function() { + var date_start = $(this).val(); + var date_end = $("[name='date_end']").val(); + var date_array = [date_start, date_end]; + + table.search(date_array, "schedule_date"); + }); + + $("#date_end").on("change", function() { + console.log($(this).val()); + + var date_end = $(this).val(); + var date_start = $("[name='date_start']").val(); + var date_array = [date_start, date_end]; + + table.search(date_array, "schedule_date"); + }); + }); + {% endblock %} diff --git a/templates/job-order/list.fulfillment.html.twig b/templates/job-order/list.fulfillment.html.twig index f38bd4a5..8115866d 100644 --- a/templates/job-order/list.fulfillment.html.twig +++ b/templates/job-order/list.fulfillment.html.twig @@ -19,21 +19,44 @@
-
-
-
-
-
- - - - -
-
-
-
-
-
+
+
+
+
+
+ + + + +
+
+
+
+
+ +
+
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+
+
@@ -45,88 +68,116 @@ {% endblock %} {% block scripts %} - + // auto refresh table + setInterval(function() { + table.reload(); + }, {{ table_refresh_rate }}); + + + $("#rider_list").on("change", function() { + console.log($(this).val()); + table.search($(this).val(), "rider"); + }); + + $("#date_start").on("change", function() { + var date_start = $(this).val(); + var date_end = $("[name='date_end']").val(); + var date_array = [date_start, date_end]; + + table.search(date_array, "schedule_date"); + }); + + $("#date_end").on("change", function() { + console.log($(this).val()); + + var date_end = $(this).val(); + var date_start = $("[name='date_start']").val(); + var date_array = [date_start, date_end]; + + table.search(date_array, "schedule_date"); + }); + }); + {% endblock %} diff --git a/templates/job-order/list.open.html.twig b/templates/job-order/list.open.html.twig index cea8df51..451d8492 100644 --- a/templates/job-order/list.open.html.twig +++ b/templates/job-order/list.open.html.twig @@ -19,21 +19,44 @@
-
-
-
-
-
- - - - -
-
-
-
-
-
+
+
+
+
+
+ + + + +
+
+
+
+
+ +
+
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+
+
@@ -45,101 +68,129 @@ {% endblock %} {% block scripts %} - + // auto refresh table + setInterval(function() { + table.reload(); + }, {{ table_refresh_rate }}); + + $("#rider_list").on("change", function() { + table.search($(this).val(), "rider"); + }); + + $("#date_start").on("change", function() { + var date_start = $(this).val(); + var date_end = $("[name='date_end']").val(); + var date_array = [date_start, date_end]; + + table.search(date_array, "schedule_date"); + }); + + $("#date_end").on("change", function() { + console.log($(this).val()); + + var date_end = $(this).val(); + var date_start = $("[name='date_start']").val(); + var date_array = [date_start, date_end]; + + table.search(date_array, "schedule_date"); + }); + }); + {% endblock %} diff --git a/templates/job-order/popup.html.twig b/templates/job-order/popup.html.twig new file mode 100644 index 00000000..d4e67d48 --- /dev/null +++ b/templates/job-order/popup.html.twig @@ -0,0 +1,13 @@ +{% set cust = jo.getCustomer %} +{% set cv = jo.getCustomerVehicle %} +{{ cust.getNameDisplay }}
+{{ cv.getPlateNumber }}
+Job Order #{{ jo.getID }}
+{{ jo.getServiceTypeName }}
+{{ jo.getStatusText }} +{% if jo.getRider != null %} +

+{% set rider = jo.getRider %} +{{ rider.getFullName }}
+{{ rider.getPlateNumber }} +{% endif %} diff --git a/templates/job-order/tracker.html.twig b/templates/job-order/tracker.html.twig new file mode 100644 index 00000000..661bcc64 --- /dev/null +++ b/templates/job-order/tracker.html.twig @@ -0,0 +1,118 @@ +{% extends 'base_minimal.html.twig' %} + +{% block body %} +
+
+
+
+
+
+ +
+
Order #{{ jo.getID }}
+
{{ rider.getFullName }}
+
{{ service_type }}
+
+
+
+
+
+
+{% endblock %} + +{% block scripts %} + + + +{{ include('map/' ~ map_js_file) }} + +{% endblock %} diff --git a/templates/map/initBingMap.js b/templates/map/initBingMap.js new file mode 100644 index 00000000..0183fe54 --- /dev/null +++ b/templates/map/initBingMap.js @@ -0,0 +1,36 @@ + + + + diff --git a/templates/map/initGoogleMap.js b/templates/map/initGoogleMap.js new file mode 100644 index 00000000..51349ae2 --- /dev/null +++ b/templates/map/initGoogleMap.js @@ -0,0 +1,22 @@ + + + diff --git a/templates/map/initOpenStreetMap.js b/templates/map/initOpenStreetMap.js new file mode 100644 index 00000000..8eab44bd --- /dev/null +++ b/templates/map/initOpenStreetMap.js @@ -0,0 +1,4 @@ + diff --git a/templates/map/joOpenStreetMap.js b/templates/map/joOpenStreetMap.js new file mode 100644 index 00000000..3d92c330 --- /dev/null +++ b/templates/map/joOpenStreetMap.js @@ -0,0 +1,26 @@ + + + diff --git a/templates/outlet/form.html.twig b/templates/outlet/form.html.twig index b5abfd4b..89f94857 100644 --- a/templates/outlet/form.html.twig +++ b/templates/outlet/form.html.twig @@ -143,12 +143,34 @@ {% block scripts %} - + +