diff --git a/catalyst/api-bundle/Access/Generator.php b/catalyst/api-bundle/Access/Generator.php deleted file mode 100644 index 20d60b74..00000000 --- a/catalyst/api-bundle/Access/Generator.php +++ /dev/null @@ -1,9 +0,0 @@ -setName('api:test-connector-all') - ->setDescription('Test API connector with all commands.') - ->setHelp('Test API Connector with all commands.') - ->addArgument('protocol', InputArgument::REQUIRED, 'protocol') - ->addArgument('server', InputArgument::REQUIRED, 'server') - ->addArgument('api_key', InputArgument::REQUIRED, 'api_key') - ->addArgument('secret_key', InputArgument::REQUIRED, 'secret_key'); - } - - protected function execute(InputInterface $input, OutputInterface $output) - { - $protocol = $input->getArgument('protocol'); - $server = $input->getArgument('server'); - $api_key = $input->getArgument('api_key'); - $secret_key = $input->getArgument('secret_key'); - - // api client - $api = new APIClient($server, $api_key, $secret_key); - $api->setProtocol($protocol); - - // test - $api->get('/capi/test'); - - // TODO: shift this out of the bundle, since it's project specific - - // warranty register - $serial = 'AJ34LJADR12134LKJL5'; - $plate_num = 'XEN918'; - $params = [ - 'serial' => $serial, - 'plate_number' => $plate_num, - 'warranty_class' => 'private', - 'sku' => 'WMEB24CB-CPN00-LX', - 'date_purchase' => '20181001', - 'date_expire' => '20191001', - 'first_name' => 'First', - 'last_name' => 'Last', - 'mobile_number' => '09231234567', - ]; - //$api->post('/capi/warranties', $params); - - // get all warranties - $params = [ - 'order' => 'DESC', - 'limit' => '5', - 'start' => '1', - ]; - - //$api->get('/capi/warranties', $params); - - - // warranty find - //$api->get('/capi/warranties/' . $serial); - - // warranty update - $id = 86811; - $params = [ - 'serial' => $serial, - 'plate_number' => $plate_num, - 'warranty_class' => 'private', - 'sku' => 'WMEB24CB-CPN00-LX', - 'date_purchase' => '20181001', - 'date_expire' => '20191001', - 'first_name' => 'First', - 'last_name' => 'Last', - 'mobile_number' => '123456789111', - ]; - //$api->post('/capi/warranties/'. $id, $params); - - // warranty set privacy policy - $id = 86811; - $policy_id = 2; - $params = [ - 'privacy_policy_id' => $policy_id, - ]; - //$api->post('/capi/warranties/' . $id .'/privacypolicy', $params); - - // warranty claim - $id = 86811; - $serial = 'AJ34LJADR12134LKJL5'; - $params = [ - 'serial' => $serial, - ]; - //$api->post('/capi/warranties/' . $id . '/claim', $params); - - // warranty cancel - $id = 86811; - //$api->get('/capi/warranties/' . $id . '/cancel'); - - // plate warranty - //$api->get('/capi/plates/' . $plate_num . '/warranties'); - - // warranty delete - $id = 86811; - //$api->post('/capi/warranties/' . $id . '/delete'); - - // battery - //$api->get('/capi/battery_brands'); - //$api->get('/capi/battery_sizes'); - //$api->get('/capi/batteries'); - - // vehicle - //$api->get('/capi/vehicle_manufacturers'); - //$api->get('/capi/vehicles'); - - // privacy policy - $privacy_policy_id = 2; - //$api->get('/capi/privacy_policy/' . $privacy_policy_id ); - - // register new customer - $params = [ - 'first_name' => 'Krispups', - 'last_name' =>'Porzindog', - 'mobile_number' => '9221111111', - 'v_make_id' => '22241', - 'v_model_year' => '2018', - 'v_plate_number' => 'KPP1234', - 'v_color' => 'White', - 'v_condition' => 'new', - 'v_fuel_type' => 'gas', - ]; - //$api->post('/capi/quick_registration', $params); - - // get warranties given list of serial numbers - $serial_list = [ - 'AJ34LJADR12134LKJM4', - 'AJ34LJADR12134LKJL5', - 'test', - ]; - - $params = [ - 'serial_list' => $serial_list, - ]; - - $api->post('/capi/warranties_list', $params); - } -} diff --git a/catalyst/api-bundle/Command/TestCommand.php b/catalyst/api-bundle/Command/TestCommand.php deleted file mode 100644 index 3d03ae0d..00000000 --- a/catalyst/api-bundle/Command/TestCommand.php +++ /dev/null @@ -1,104 +0,0 @@ -setName('api:test-connector') - ->setDescription('Test API connector.') - ->setHelp('Test API Connector.') - ->addArgument('protocol', InputArgument::REQUIRED, 'protocol') - ->addArgument('server', InputArgument::REQUIRED, 'server') - ->addArgument('api_key', InputArgument::REQUIRED, 'api_key') - ->addArgument('secret_key', InputArgument::REQUIRED, 'secret_key'); - } - - protected function execute(InputInterface $input, OutputInterface $output) - { - $protocol = $input->getArgument('protocol'); - $server = $input->getArgument('server'); - $api_key = $input->getArgument('api_key'); - $secret_key = $input->getArgument('secret_key'); - - // api client - $api = new APIClient($server, $api_key, $secret_key); - $api->setProtocol($protocol); - - // test - $api->get('/capi/test'); - - // TODO: shift this out of the bundle, since it's project specific - - - // warranty register - $serial = 'AJ34LJADR12134LKJL5'; - $plate_num = 'XEN918'; - $params = [ - 'serial' => $serial, - 'plate_number' => $plate_num, - 'warranty_class' => 'private', - 'sku' => 'WMEB24CB-CPN00-LX', - 'date_purchase' => '20181001', - 'date_expire' => '20191001', - 'first_name' => 'First', - 'last_name' => 'Last', - 'mobile_number' => '12345678910', - ]; - $api->post('/capi/warranties', $params); - - // get all warranties - $api->get('/capi/warranties'); - - - /* - // warranty find - $api->get('/capi/warranties/' . $serial); - */ - - // warranty claim - $id = 86811; - $serial = 'AJ34LJADR12134LKJL'; - $params = [ - 'serial' => $serial, - ]; - $api->post('/capi/warranties/' . $id . '/claim', $params); - - // add battery - $sku = 'WZMB31QT-CPP00-S'; - $brand_id = '4'; - $size_id = '1'; - $params = [ - 'sku' => $sku, - 'brand_id' => $brand_id, - 'size_id' => $size_id, - ]; - $api->post('/capi/batteries', $params); - - /* - - // plate warranty - $api->get('/capi/plates/' . $plate_num . '/warranties'); - - // battery - $api->get('/capi/battery_brands'); - $api->get('/capi/battery_sizes'); - $api->get('/capi/batteries'); - - // vehicle - // $api->get('/capi/vehicle_manufacturers'); - // $api->get('/capi/vehicles'); - */ - } -} diff --git a/catalyst/api-bundle/Command/UserCreateCommand.php b/catalyst/api-bundle/Command/UserCreateCommand.php deleted file mode 100644 index cea9682c..00000000 --- a/catalyst/api-bundle/Command/UserCreateCommand.php +++ /dev/null @@ -1,49 +0,0 @@ -em = $em; - - parent::__construct(); - } - - protected function configure() - { - $this->setName('api:user-create') - ->setDescription('Create new API user.') - ->setHelp('Creates new API user and saves to database.') - ->addArgument('name', InputArgument::REQUIRED, 'name'); - } - - protected function execute(InputInterface $input, OutputInterface $output) - { - $name = $input->getArgument('name'); - - $user = new User(); - $user->setName($name); - - $this->em->persist($user); - $this->em->flush(); - - $output->write('API Key - ' . $user->getAPIKey() . "\n"); - $output->write('Secret Key - ' . $user->getSecretKey() . "\n"); - } - - -} diff --git a/catalyst/api-bundle/Connector/Client.php b/catalyst/api-bundle/Connector/Client.php deleted file mode 100644 index 904b7cad..00000000 --- a/catalyst/api-bundle/Connector/Client.php +++ /dev/null @@ -1,151 +0,0 @@ -protocol = 'https'; - $this->port = null; - - $this->server = $server; - $this->api_key = $api_key; - $this->secret_key = $secret_key; - $this->curl = curl_init(); - } - - public function __destruct() - { - curl_close($this->curl); - } - - public function setProtocol($protocol) - { - if ($protocol != 'http' && $protocol != 'https') - return $this; - - $this->protocol = $protocol; - return $this; - } - - protected function getDateString() - { - $date = new DateTime(); - return $date->format(self::DATE_FORMAT); - } - - public function get($url, $params = []) - { - curl_reset($this->curl); - - $date_string = $this->getDateString(); - $headers = $this->generateHeaders('GET', $url, $date_string); - - // build query string - if (count($params) > 0) - $query_string = '?' . http_build_query($params); - else - $query_string = ''; - - // build url - if ($this->port == null) - $full_url = $this->protocol . '://' . $this->server . $url . $query_string; - else - $full_url = $this->protocol . '://' . $this->server . ':' . $this->port . $url . $query_string; - - error_log($full_url); - - // curl - // curl_setopt($this->curl, CURLOPT_VERBOSE, true); - curl_setopt($this->curl, CURLOPT_URL, $full_url); - curl_setopt($this->curl, CURLOPT_HTTPHEADER, $headers); - curl_setopt($this->curl, CURLOPT_USERAGENT, self::USER_AGENT); - curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true); - curl_setopt($this->curl, CURLOPT_TIMEOUT, 0); - - $res = curl_exec($this->curl); - error_log($res); - } - - public function post($url, $params = []) - { - curl_reset($this->curl); - - $date_string = $this->getDateString(); - $headers = $this->generateHeaders('POST', $url, $date_string); - - // build query string - $query_string = http_build_query($params); - - // build url - if ($this->port == null) - $full_url = $this->protocol . '://' . $this->server . $url; - else - $full_url = $this->protocol . '://' . $this->server . ':' . $this->port . $url; - - error_log($full_url); - - // curl - // curl_setopt($this->curl, CURLOPT_VERBOSE, true); - curl_setopt($this->curl, CURLOPT_URL, $full_url); - curl_setopt($this->curl, CURLOPT_POST, true); - curl_setopt($this->curl, CURLOPT_POSTFIELDS, $query_string); - curl_setopt($this->curl, CURLOPT_HTTPHEADER, $headers); - curl_setopt($this->curl, CURLOPT_USERAGENT, self::USER_AGENT); - curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true); - curl_setopt($this->curl, CURLOPT_TIMEOUT, 0); - - $res = curl_exec($this->curl); - error_log($res); - } - - protected function generateSignature($method, $url, $date_string) - { - $creds = [ - $method, - $url, - $date_string, - $this->secret_key, - ]; - $sig_source = implode('|', $creds); - - error_log('SIG SOURCE - ' . $sig_source); - - $raw_sig = hash_hmac('sha1', $sig_source, $this->secret_key, true); - $enc_sig = base64_encode($raw_sig); - - return $enc_sig; - } - - protected function generateHeaders($method, $url, $date_string) - { - $sig = $this->generateSignature($method, $url, $date_string); - - $headers = [ - self::HEADER_API_KEY . ': ' . $this->api_key, - self::HEADER_SIGNATURE . ': ' . $sig, - self::HEADER_DATE . ': ' . $date_string, - ]; - - return $headers; - } -} diff --git a/catalyst/api-bundle/Controller/APIController.php b/catalyst/api-bundle/Controller/APIController.php deleted file mode 100644 index 74c63d3c..00000000 --- a/catalyst/api-bundle/Controller/APIController.php +++ /dev/null @@ -1,42 +0,0 @@ -getMethod() == 'GET') - { - $check = $req->query->get($param); - if (empty($check)) - $missing[] = $param; - } - // else if ($req->getMethod() == 'POST') - else - { - $check = $req->request->get($param); - //if (empty($check)) - if (!isset($check)) - $missing[] = $param; - } - } - - // check missing parameters - if (count($missing) > 0) - { - $miss_string = implode(', ', $missing); - return 'Missing required parameter(s): ' . $miss_string; - } - - return false; - } -} diff --git a/catalyst/api-bundle/DataFixtures/APIRoleFixtures.php b/catalyst/api-bundle/DataFixtures/APIRoleFixtures.php deleted file mode 100644 index 00b4c3e0..00000000 --- a/catalyst/api-bundle/DataFixtures/APIRoleFixtures.php +++ /dev/null @@ -1,20 +0,0 @@ -setID(Role::SUPER_ADMIN) - ->setName('Super Administrator'); - $em->persist($role); - $em->flush(); - } -} diff --git a/catalyst/api-bundle/Entity/Role.php b/catalyst/api-bundle/Entity/Role.php deleted file mode 100644 index 89637045..00000000 --- a/catalyst/api-bundle/Entity/Role.php +++ /dev/null @@ -1,28 +0,0 @@ -setAPIKey($this->generateAPIKey()) - ->setSecretKey($this->generateSecretKey()); - - // set date created - $this->date_create = new DateTime(); - $this->metadata = []; - } - - public function getID() - { - return $this->id; - } - - public function setAPIKey($api_key) - { - $this->api_key = $api_key; - return $this; - } - - public function getAPIKey() - { - return $this->api_key; - } - - public function setSecretKey($key) - { - $this->secret_key = $key; - return $this; - } - - public function getSecretKey() - { - return $this->secret_key; - } - - public function setName($name) - { - $this->name = $name; - return $this; - } - - public function getName() - { - return $this->name; - } - - public function getDateCreate() - { - return $this->date_create; - } - - public function getPassword() - { - // we don't need this for API - return 'notneeded'; - } - - public function getSalt() - { - return null; - } - - public function getUsername() - { - // since it's an api, the api key IS the username - return $this->api_key; - } - - public function eraseCredentials() - { - return; - } - - public function generateAPIKey() - { - return $this->generateKey('api'); - } - - public function generateSecretKey() - { - return $this->generateKey('secret'); - } - - public function setMetadata($meta) - { - $this->metadata = $meta; - return $this; - } - - public function getMetadata() - { - if ($this->metadata == null) - return []; - - return $this->metadata; - } - - protected function generateKey($prefix = '') - { - return md5(uniqid($prefix, true)); - } - - public function setRider($rider) - { - $this->rider = $rider; - return $this; - } - - public function getRider() - { - return $this->rider; - } -} - diff --git a/catalyst/api-bundle/Response/APIResponse.php b/catalyst/api-bundle/Response/APIResponse.php deleted file mode 100644 index baa10603..00000000 --- a/catalyst/api-bundle/Response/APIResponse.php +++ /dev/null @@ -1,20 +0,0 @@ - (bool) $success, - 'message' => (string) $message, - 'data' => $data, - ]; - parent::__construct($data, $status, $headers); - } -} - - diff --git a/catalyst/api-bundle/Security/APIKeyAuthenticator.php b/catalyst/api-bundle/Security/APIKeyAuthenticator.php deleted file mode 100644 index 27d1eaf1..00000000 --- a/catalyst/api-bundle/Security/APIKeyAuthenticator.php +++ /dev/null @@ -1,160 +0,0 @@ -em = $em; - } - - protected function validateSignature($creds, $secret_key) - { - $elements = [ - $creds['method'], - $creds['uri'], - $creds['date'], - $secret_key, - ]; - $sig_source = implode('|', $elements); - - error_log($sig_source); - - // generate signature - $raw_sig = hash_hmac('sha1', $sig_source, $secret_key, true); - $enc_sig = base64_encode($raw_sig); - - error_log($enc_sig); - - if ($enc_sig != trim($creds['signature'])) - throw new CustomUserMessageAuthenticationException('Invalid signature.'); - - } - - public function createToken(Request $req, $provider_key) - { - // api key header - $api_key = $req->headers->get(self::HEADER_API_KEY); - if ($api_key == null) - throw new BadCredentialsException('No API key sent.'); - - // check date from headers - $hdate_string = $req->headers->get(self::HEADER_DATE); - if ($hdate_string == null) - throw new BadCredentialsException('No date specified.'); - - $hdate = DateTime::createFromFormat(self::DATE_FORMAT, $hdate_string); - if ($hdate == null) - throw new BadCredentialsException('Invalid date specified.'); - - // get number of seconds difference - $date_now = new DateTime(); - $date_diff = abs($date_now->getTimestamp() - $hdate->getTimestamp()); - - // time difference is too much - if ($date_diff > self::TIME_LIMIT) - throw new BadCredentialsException('Clock synchronization error.'); - - // signature header - $sig = $req->headers->get(self::HEADER_SIGNATURE); - if ($sig == null) - throw new BadCredentialsException('No signature sent.'); - - // credentials - $creds = [ - 'api_key' => $api_key, - 'date' => $hdate_string, - 'signature' => $sig, - 'method' => $req->getRealMethod(), - 'uri' => $req->getPathInfo(), - ]; - - return new PreAuthenticatedToken( - 'anonymous', - $creds, - $provider_key - ); - } - - public function supportsToken(TokenInterface $token, $provider_key) - { - return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $provider_key; - } - - public function authenticateToken(TokenInterface $token, UserProviderInterface $user_provider, $provider_key) - { - if (!$user_provider instanceof APIKeyUserProvider) - { - throw new \InvalidArgumentException( - sprintf( - 'The user provider must be an instance of APIKeyUserProvider (%s was given).', - get_class($user_provider) - ) - ); - } - - $creds = $token->getCredentials(); - $api_key = $creds['api_key']; - $user = $user_provider->getUserByAPIKey($api_key); - - // check if api key is valid - if (!$user) - throw new CustomUserMessageAuthenticationException('Invalid API Key'); - - // check if signature is valid - $this->validateSignature($creds, $user->getSecretKey()); - - // check if user is enabled - if (!$user->isEnabled()) - { - throw new CustomUserMessageAuthenticationException('User account is disabled'); - } - - // $user = $user_provider->loadUserByUsername($username); - - return new PreAuthenticatedToken( - $user, - $api_key, - $provider_key, - $user->getRoles() - ); - } - - public function onAuthenticationFailure(Request $req, AuthenticationException $exception) - { - $data = [ - 'success' => false, - 'error' => [ - 'message' => $exception->getMessage(), - ], - ]; - return new JsonResponse($data, 401); - } -} diff --git a/catalyst/api-bundle/Security/APIKeyUserProvider.php b/catalyst/api-bundle/Security/APIKeyUserProvider.php deleted file mode 100644 index 742e8d63..00000000 --- a/catalyst/api-bundle/Security/APIKeyUserProvider.php +++ /dev/null @@ -1,62 +0,0 @@ -em = $em; - } - - public function getUserByAPIKey($api_key) - { - $user = $this->em->getRepository(User::class)->findOneBy(array('api_key' => $api_key)); - - return $user; - } - - public function getUsernameForAPIKey($apiKey) - { - // Look up the username based on the token in the database, via - // an API call, or do something entirely different - $username = 'test'; - - return $username; - } - - public function loadUserByUsername($username) - { - return new User( - $username, - null, - // the roles for the user - you may choose to determine - // these dynamically somehow based on the user - array('ROLE_API') - ); - } - - public function refreshUser(UserInterface $user) - { - // this is used for storing authentication in the session - // but in this example, the token is sent in each request, - // so authentication can be stateless. Throwing this exception - // is proper to make things stateless - throw new UnsupportedUserException(); - } - - public function supportsClass($class) - { - return User::class === $class; - } -} diff --git a/catalyst/api-bundle/Service/AccessDeniedHandler.php b/catalyst/api-bundle/Service/AccessDeniedHandler.php deleted file mode 100644 index 7cf5be90..00000000 --- a/catalyst/api-bundle/Service/AccessDeniedHandler.php +++ /dev/null @@ -1,18 +0,0 @@ -getMessage(); - - return new Response($content, 403); - } -} diff --git a/composer.json b/composer.json index 5ade4a34..562145d8 100644 --- a/composer.json +++ b/composer.json @@ -4,28 +4,41 @@ "repositories": [ { "type": "vcs", - "url": "git@gitlab.com:jankstudio-catalyst/auth-bundle.git" + "url": "https://github.com/jankstudio/doctrine2-spatial.git" }, { "type": "vcs", - "url": "git@gitlab.com:jankstudio-catalyst/menu-bundle.git" + "url": "git@gitlab.com:jankstudio1/catalyst-2/api-bundle.git" + }, + { + "type": "vcs", + "url": "git@gitlab.com:jankstudio1/catalyst-2/auth-bundle.git" + }, + { + "type": "vcs", + "url": "git@gitlab.com:jankstudio1/catalyst-2/menu-bundle.git" + }, + { + "type": "vcs", + "url": "https://github.com/arcticzero/php-fcm.git" } ], "require": { "php": "^7.1.3", "ext-iconv": "*", - "catalyst/auth-bundle": "dev-master", - "catalyst/menu-bundle": "dev-master", "composer/package-versions-deprecated": "1.11.99.4", - "creof/doctrine2-spatial": "^1.2", "data-dog/audit-bundle": "^0.1.10", "doctrine/common": "^2", "doctrine/doctrine-bundle": "^2", "doctrine/doctrine-migrations-bundle": "^2", "doctrine/orm": "^2", - "edwinhoksberg/php-fcm": "^1.0", + "edwinhoksberg/php-fcm": "dev-notif-priority-hotfix", "guzzlehttp/guzzle": "^6.3", - "hashids/hashids": "^4.1", + "hashids/hashids": "^4.1", + "jankstudio/catalyst-api-bundle": "dev-master", + "jankstudio/catalyst-auth-bundle": "dev-master", + "jankstudio/catalyst-menu-bundle": "dev-master", + "jankstudio/doctrine-spatial": "dev-master", "microsoft/azure-storage-blob": "^1.5", "predis/predis": "^1.1", "sensio/framework-extra-bundle": "^5.1", @@ -65,8 +78,7 @@ }, "autoload": { "psr-4": { - "App\\": "src/", - "Catalyst\\APIBundle\\": "catalyst/api-bundle/" + "App\\": "src/" } }, "autoload-dev": { diff --git a/composer.lock b/composer.lock index e34b61f9..91636cc2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,90 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5b5acb546514b41ab347ef37b85425c9", + "content-hash": "653f8558c75614dd65421cb3eb48c29b", "packages": [ - { - "name": "catalyst/auth-bundle", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://gitlab.com/jankstudio-catalyst/auth-bundle.git", - "reference": "803e38f58907513c8df30c31d51303b68c645a17" - }, - "dist": { - "type": "zip", - "url": "https://gitlab.com/api/v4/projects/jankstudio-catalyst%2Fauth-bundle/repository/archive.zip?sha=803e38f58907513c8df30c31d51303b68c645a17", - "reference": "803e38f58907513c8df30c31d51303b68c645a17", - "shasum": "" - }, - "require": { - "doctrine/dbal": "^2.5.12", - "doctrine/doctrine-cache-bundle": "~1.2", - "php": "^7.0", - "symfony/framework-bundle": "~4.0" - }, - "default-branch": true, - "type": "symfony-bundle", - "autoload": { - "psr-4": { - "Catalyst\\AuthBundle\\": "" - } - }, - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kendrick Chan", - "email": "kc@jankstudio.com" - } - ], - "support": { - "issues": "https://gitlab.com/api/v4/projects/12709261/issues" - }, - "time": "2019-07-01T09:45:41+00:00" - }, - { - "name": "catalyst/menu-bundle", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://gitlab.com/jankstudio-catalyst/menu-bundle.git", - "reference": "2cf9a05862fcc6f956da4b095962915e07f14f64" - }, - "dist": { - "type": "zip", - "url": "https://gitlab.com/api/v4/projects/jankstudio-catalyst%2Fmenu-bundle/repository/archive.zip?sha=2cf9a05862fcc6f956da4b095962915e07f14f64", - "reference": "2cf9a05862fcc6f956da4b095962915e07f14f64", - "shasum": "" - }, - "require": { - "doctrine/dbal": "^2.5.12", - "doctrine/doctrine-cache-bundle": "~1.2", - "php": "^7.0", - "symfony/framework-bundle": "~4.0" - }, - "default-branch": true, - "type": "symfony-bundle", - "autoload": { - "psr-4": { - "Catalyst\\MenuBundle\\": "" - } - }, - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kendrick Chan", - "email": "kc@jankstudio.com" - } - ], - "support": { - "issues": "https://gitlab.com/api/v4/projects/12712039/issues" - }, - "time": "2019-06-14T09:43:30+00:00" - }, { "name": "composer/package-versions-deprecated", "version": "1.11.99.4", @@ -161,67 +79,6 @@ ], "time": "2021-09-13T08:41:34+00:00" }, - { - "name": "creof/doctrine2-spatial", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/creof/doctrine2-spatial.git", - "reference": "58ea5fae1c1b450ee08d7dac25cd9e8f5e6fdebd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/creof/doctrine2-spatial/zipball/58ea5fae1c1b450ee08d7dac25cd9e8f5e6fdebd", - "reference": "58ea5fae1c1b450ee08d7dac25cd9e8f5e6fdebd", - "shasum": "" - }, - "require": { - "creof/geo-parser": "~2.0", - "creof/wkb-parser": "~2.0", - "creof/wkt-parser": "~2.0", - "doctrine/orm": ">=2.3" - }, - "require-dev": { - "phpunit/phpcov": "*", - "phpunit/phpunit": "<5.0", - "satooshi/php-coveralls": "~1.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "CrEOF\\Spatial": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Derek Lambert", - "email": "dlambert@dereklambert.com" - } - ], - "description": "Doctrine2 multi-platform support for spatial types and functions", - "keywords": [ - "database", - "dbal", - "geography", - "geometry", - "gis", - "mysql", - "opengis", - "orm", - "postgis", - "postgresql", - "spatial" - ], - "support": { - "issues": "https://github.com/creof/doctrine2-spatial/issues", - "source": "https://github.com/creof/doctrine2-spatial/tree/master" - }, - "time": "2017-07-13T16:48:25+00:00" - }, { "name": "creof/geo-parser", "version": "2.2.1", @@ -1069,102 +926,6 @@ ], "time": "2021-05-06T19:21:22+00:00" }, - { - "name": "doctrine/doctrine-cache-bundle", - "version": "1.4.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/DoctrineCacheBundle.git", - "reference": "6bee2f9b339847e8a984427353670bad4e7bdccb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineCacheBundle/zipball/6bee2f9b339847e8a984427353670bad4e7bdccb", - "reference": "6bee2f9b339847e8a984427353670bad4e7bdccb", - "shasum": "" - }, - "require": { - "doctrine/cache": "^1.4.2", - "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": "^7.0", - "predis/predis": "~0.8", - "satooshi/php-coveralls": "^1.0", - "squizlabs/php_codesniffer": "~1.5", - "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" - }, - "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Bundle\\DoctrineCacheBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Fabio B. Silva", - "email": "fabio.bat.silva@gmail.com" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@hotmail.com" - }, - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Doctrine Project", - "homepage": "http://www.doctrine-project.org/" - } - ], - "description": "Symfony Bundle for Doctrine Cache", - "homepage": "https://www.doctrine-project.org", - "keywords": [ - "cache", - "caching" - ], - "support": { - "issues": "https://github.com/doctrine/DoctrineCacheBundle/issues", - "source": "https://github.com/doctrine/DoctrineCacheBundle/tree/1.4.0" - }, - "abandoned": true, - "time": "2019-11-29T11:22:01+00:00" - }, { "name": "doctrine/doctrine-migrations-bundle", "version": "2.2.3", @@ -2011,26 +1772,26 @@ }, { "name": "edwinhoksberg/php-fcm", - "version": "v1.2.0", + "version": "dev-notif-priority-hotfix", "source": { "type": "git", - "url": "https://github.com/EdwinHoksberg/php-fcm.git", - "reference": "660bbe4ae71e02090f16b11b4a993dfebab83f7f" + "url": "https://github.com/ArcticZero/php-fcm.git", + "reference": "eabbfbc5df8df908ea7d13f0446202fda81e65fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/EdwinHoksberg/php-fcm/zipball/660bbe4ae71e02090f16b11b4a993dfebab83f7f", - "reference": "660bbe4ae71e02090f16b11b4a993dfebab83f7f", + "url": "https://api.github.com/repos/ArcticZero/php-fcm/zipball/eabbfbc5df8df908ea7d13f0446202fda81e65fd", + "reference": "eabbfbc5df8df908ea7d13f0446202fda81e65fd", "shasum": "" }, "require": { "guzzlehttp/guzzle": "^6.3 || ^7.0", - "php": ">= 7.2" + "php": ">= 7.3" }, "require-dev": { - "mockery/mockery": "^1.0", - "php-coveralls/php-coveralls": "^2.3", - "phpunit/phpunit": "^6.5" + "mockery/mockery": "^1.5", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^9.5" }, "type": "library", "autoload": { @@ -2038,7 +1799,11 @@ "Fcm\\": "src/" } }, - "notification-url": "https://packagist.org/downloads/", + "scripts": { + "test": [ + "XDEBUG_MODE=coverage vendor/bin/phpunit -c phpunit.dist.xml" + ] + }, "license": [ "MIT" ], @@ -2051,17 +1816,16 @@ "description": "A library for sending Firebase cloud messages and managing user topic subscriptions, device groups and devices.", "homepage": "https://github.com/EdwinHoksberg/php-fcm", "keywords": [ - "FCM", - "Firebase Cloud Messaging", + "fcm", "firebase", + "firebase cloud messaging", "google", "notifications" ], "support": { - "issues": "https://github.com/EdwinHoksberg/php-fcm/issues", - "source": "https://github.com/EdwinHoksberg/php-fcm/tree/v1.2.0" + "source": "https://github.com/ArcticZero/php-fcm/tree/master" }, - "time": "2021-01-19T01:15:30+00:00" + "time": "2023-07-19T09:04:27+00:00" }, { "name": "friendsofphp/proxy-manager-lts", @@ -2480,6 +2244,177 @@ }, "time": "2020-11-26T19:24:33+00:00" }, + { + "name": "jankstudio/catalyst-api-bundle", + "version": "dev-master", + "source": { + "type": "git", + "url": "git@gitlab.com:jankstudio1/catalyst-2/api-bundle.git", + "reference": "042d16534265adf81ae67c803b1ae8f0a94f3bb6" + }, + "dist": { + "type": "zip", + "url": "https://gitlab.com/api/v4/projects/jankstudio1%2Fcatalyst-2%2Fapi-bundle/repository/archive.zip?sha=042d16534265adf81ae67c803b1ae8f0a94f3bb6", + "reference": "042d16534265adf81ae67c803b1ae8f0a94f3bb6", + "shasum": "" + }, + "default-branch": true, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Catalyst\\ApiBundle\\": "" + } + }, + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Kendrick Chan", + "email": "kc@jankstudio.com" + } + ], + "support": { + "source": "https://gitlab.com/jankstudio1/catalyst-2/api-bundle/-/tree/master", + "issues": "https://gitlab.com/jankstudio1/catalyst-2/api-bundle/-/issues" + }, + "time": "2022-10-09T14:02:41+08:00" + }, + { + "name": "jankstudio/catalyst-auth-bundle", + "version": "dev-master", + "source": { + "type": "git", + "url": "git@gitlab.com:jankstudio1/catalyst-2/auth-bundle.git", + "reference": "50f18ac6d4f6198b7bfe888487e0a4009193403e" + }, + "dist": { + "type": "zip", + "url": "https://gitlab.com/api/v4/projects/jankstudio1%2Fcatalyst-2%2Fauth-bundle/repository/archive.zip?sha=50f18ac6d4f6198b7bfe888487e0a4009193403e", + "reference": "50f18ac6d4f6198b7bfe888487e0a4009193403e", + "shasum": "" + }, + "require": { + "symfony/cache": "^5.0" + }, + "default-branch": true, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Catalyst\\AuthBundle\\": "" + } + }, + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Kendrick Chan", + "email": "kc@jankstudio.com" + } + ], + "support": { + "source": "https://gitlab.com/jankstudio1/catalyst-2/auth-bundle/-/tree/master", + "issues": "https://gitlab.com/jankstudio1/catalyst-2/auth-bundle/-/issues" + }, + "time": "2022-01-19T16:21:57+08:00" + }, + { + "name": "jankstudio/catalyst-menu-bundle", + "version": "dev-master", + "source": { + "type": "git", + "url": "git@gitlab.com:jankstudio1/catalyst-2/menu-bundle.git", + "reference": "9cddeecbbbb7d61868e1b319d9abc8eda4f34ab0" + }, + "dist": { + "type": "zip", + "url": "https://gitlab.com/api/v4/projects/jankstudio1%2Fcatalyst-2%2Fmenu-bundle/repository/archive.zip?sha=9cddeecbbbb7d61868e1b319d9abc8eda4f34ab0", + "reference": "9cddeecbbbb7d61868e1b319d9abc8eda4f34ab0", + "shasum": "" + }, + "default-branch": true, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Catalyst\\MenuBundle\\": "" + } + }, + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Kendrick Chan", + "email": "kc@jankstudio.com" + } + ], + "support": { + "source": "https://gitlab.com/jankstudio1/catalyst-2/menu-bundle/-/tree/master", + "issues": "https://gitlab.com/jankstudio1/catalyst-2/menu-bundle/-/issues" + }, + "time": "2022-10-07T11:55:32+08:00" + }, + { + "name": "jankstudio/doctrine-spatial", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/jankstudio/doctrine-spatial.git", + "reference": "32b0fd126f3d81758ee74363535a44ccb3adae19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jankstudio/doctrine-spatial/zipball/32b0fd126f3d81758ee74363535a44ccb3adae19", + "reference": "32b0fd126f3d81758ee74363535a44ccb3adae19", + "shasum": "" + }, + "require": { + "creof/geo-parser": "~2.0", + "creof/wkb-parser": "~2.0", + "creof/wkt-parser": "~2.0", + "doctrine/orm": ">=2.3" + }, + "require-dev": { + "phpunit/phpcov": "*", + "phpunit/phpunit": "<5.0", + "satooshi/php-coveralls": "~1.0" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-0": { + "CrEOF\\Spatial": "lib/" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Derek Lambert", + "email": "dlambert@dereklambert.com" + } + ], + "description": "Doctrine2 multi-platform support for spatial types and functions", + "keywords": [ + "database", + "dbal", + "geography", + "geometry", + "gis", + "mysql", + "opengis", + "orm", + "postgis", + "postgresql", + "spatial" + ], + "support": { + "source": "https://github.com/jankstudio/doctrine-spatial/tree/master" + }, + "time": "2021-08-23T01:41:57+00:00" + }, { "name": "laminas/laminas-code", "version": "3.4.1", @@ -8021,8 +7956,11 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { - "catalyst/auth-bundle": 20, - "catalyst/menu-bundle": 20 + "edwinhoksberg/php-fcm": 20, + "jankstudio/catalyst-api-bundle": 20, + "jankstudio/catalyst-auth-bundle": 20, + "jankstudio/catalyst-menu-bundle": 20, + "jankstudio/doctrine-spatial": 20 }, "prefer-stable": false, "prefer-lowest": false, @@ -8031,5 +7969,5 @@ "ext-iconv": "*" }, "platform-dev": [], - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.3.0" } diff --git a/config/bundles.php b/config/bundles.php index 79841817..cfbe640b 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -2,7 +2,6 @@ return [ Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], - Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle::class => ['all' => true], Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], @@ -11,8 +10,8 @@ return [ Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], - Catalyst\APIBundle\CatalystAPIBundle::class => ['all' => true], + Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], + Catalyst\ApiBundle\CatalystApiBundle::class => ['all' => true], Catalyst\AuthBundle\CatalystAuthBundle::class => ['all' => true], Catalyst\MenuBundle\CatalystMenuBundle::class => ['all' => true], - Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], ]; diff --git a/config/cmb.services.yaml b/config/cmb.services.yaml index 5f24e525..5f7feb52 100644 --- a/config/cmb.services.yaml +++ b/config/cmb.services.yaml @@ -42,20 +42,6 @@ services: $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%' @@ -114,50 +100,6 @@ services: 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: ~ diff --git a/config/packages/catalyst_auth.yaml b/config/packages/catalyst_auth.yaml new file mode 100644 index 00000000..dfaf31e0 --- /dev/null +++ b/config/packages/catalyst_auth.yaml @@ -0,0 +1,895 @@ +catalyst_auth: + main: + user_entity: "App\\Entity\\User" + acl_data: + - id: dashboard + label: Dashboard Access + acls: + - id: dashboard.menu + label: Menu + - id: user + label: User Access + acls: + - id: user.menu + label: Menu + - id: user.list + label: List + - id: user.add + label: Add + - id: user.update + label: Update + - id: user.delete + label: Delete + - id: user.role.sadmin + label: Super Admin Role + - id: user.profile + label: User Profile + - id: role + label: Role Access + acls: + - id: role.menu + label: Menu + - id: role.list + label: List + - id: role.add + label: Add + - id: role.update + label: Update + - id: role.delete + label: Delete + - id: apiuser + label: API User Access + acls: + - id: apiuser.menu + label: Menu + - id: apiuser.list + label: List + - id: apiuser.add + label: Add + - id: apiuser.update + label: Update + - id: apiuser.delete + label: Delete + - id: apirole + label: API Role Access + acls: + - id: apirole.menu + label: Menu + - id: apirole.list + label: List + - id: apirole.add + label: Add + - id: apirole.update + label: Update + - id: apirole.delete + label: Delete + - id: logistics + label: Logistics Access + acls: + - id: logistics.menu + label: Menu + - id: battery + label: Battery Access + acls: + - id: battery.menu + label: Menu + - id: battery.list + label: List + - id: battery.add + label: Add + - id: battery.update + label: Update + - id: battery.delete + label: Delete + - id: bmfg + label: Battery Manufacturer Access + acls: + - id: bmfg.menu + label: Menu + - id: bmfg.list + label: List + - id: bmfg.add + label: Add + - id: bmfg.update + label: Update + - id: bmfg.delete + label: Delete + - id: bmodel + label: Battery Model Access + acls: + - id: bmodel.menu + label: Menu + - id: bmodel.list + label: List + - id: bmodel.add + label: Add + - id: bmodel.update + label: Update + - id: bmodel.delete + label: Delete + - id: bsize + label: Battery Size Access + acls: + - id: bsize.menu + label: Menu + - id: bsize.list + label: List + - id: bsize.add + label: Add + - id: bsize.update + label: Update + - id: bsize.delete + label: Delete + - id: vehicle + label: Vehicle Access + acls: + - id: vehicle.menu + label: Menu + - id: vehicle.list + label: List + - id: vehicle.add + label: Add + - id: vehicle.update + label: Update + - id: vehicle.delete + label: Delete + - id: vmfg + label: Vehicle Manufacturer Access + acls: + - id: vmfg.menu + label: Menu + - id: vmfg.list + label: List + - id: vmfg.add + label: Add + - id: vmfg.update + label: Update + - id: vmfg.delete + label: Delete + - id: customer + label: Customer Access + acls: + - id: customer.menu + label: Menu + - id: customer.list + label: List + - id: customer.add + label: Add + - id: customer.update + label: Update + - id: customer.delete + label: Delete + - id: customer.dpa + label: Display DPA + + + - id: location + label: Location Access + acls: + - id: location.menu + label: Menu + - id: outlet + label: Outlet Access + acls: + - id: outlet.menu + label: Menu + - id: outlet.list + label: List + - id: outlet.add + label: Add + - id: outlet.update + label: Update + - id: outlet.delete + label: Delete + - id: hub + label: Hub Access + acls: + - id: hub.menu + label: Menu + - id: hub.list + label: List + - id: hub.add + label: Add + - id: hub.update + label: Update + - id: hub.delete + label: Delete + - id: geofence + label: Geofence + acls: + - id: geofence.menu + label: Menu + - id: geofence.list + label: List + - id: geofence.add + label: Add + - id: geofence.delete + label: Delete + + - id: rider + label: Rider Access + acls: + - id: rider.menu + label: Menu + - id: rider.list + label: List + - id: rider.add + label: Add + - id: rider.update + label: Update + - id: rider.delete + label: Delete + + - id: servicecharge + label: Service Charge + acls: + - id: service_charge.menu + label: Menu + - id: service_charge.list + label: List + - id: service_charge.add + label: Add + - id: service_charge.update + label: Update + - id: service_charge.delete + label: Delete + + - id: joborder + label: Job Order + acls: + - id: joborder.menu + label: Menu + - id: jo_in.list + label: Incoming + - id: jo_proc.list + label: Dispatch + - id: jo_proc.unlock + label: Dispatch Unlock + - id: jo_assign.list + label: Rider Assignment + - id: jo_assign.unlock + label: Rider Assignment Unlock + - id: jo_fulfill.list + label: Fulfillment + - id: jo_open.list + label: Open + - id: jo_all.list + label: View All + - id: jo_pdf.list + label: PDF + - id: jo_open.edit + 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: jo_walkin.form + label: Walk-in + - id: jo_walkin.edit + label: Walk-in Edit + - id: jo_autoassign.test + label: Autoassign Test + - id: jo_hub.list + label: Hub View + - id: jo_cancel.fulfill + label: Fulfill Cancelled JO + - id: jo_resq_proc.list + label: RESQ Dispatch + - id: jo_resq_all.list + label: RESQ All + + - id: support + label: Customer Support Access + acls: + - id: support.menu + label: Menu + - id: general.search + label: Search + - id: warranty.search + label: Customer Battery Search + - id: warranty.upload + label: Warranty Upload + + - id: ticket + label: Ticket Access + acls: + - id: ticket.menu + label: Menu + - id: ticket.list + label: List + - id: ticket.add + label: Add + - id: ticket.update + label: Update + - id: ticket.delete + label: Delete + + - id: promo + label: Promo Access + acls: + - id: promo.menu + label: Menu + - id: promo.list + label: List + - id: promo.add + label: Add + - id: promo.update + label: Update + - id: promo.delete + label: Delete + + - id: report + label: Reports + acls: + - id: report.menu + label: Menu + - id: report.reject + label: Rejection Report + - id: report.battery.conflict + label: Battery Conflict Report + - id: report.popapp.comparison + label: Popapp Comparison Report + - id: report.meh.customer + label: RESQ MEH Customer Report + - id: report.warranty.class + label: Warranty Class Report + - id: report.vehicle.battery.compatibility + label: Vehicle Battery Compatibility Report + - id: report.warranty.details + label: Warranty Details Report + - id: report.jo.details + label: Job Order Details Report + - id: report.jo_events + label: Job Order Events Report + - id: report.sms_messages + label: SMS Messages Report + - id: report.jo.auto_assign + label: Auto Assigned Job Order Report + - id: report.jo.advance_order + label: Advance Order Job Order Report + - id: report.customer.source + label: Customer Source Report + - id: report.hub.filter + label: Hub Filter Report + - id: report.warranty.raffle + label: Warranty Raffle Report + - id: report.jo.raffle + label: JO Raffle Report + + - id: service + label: Other Services + acls: + - id: service.menu + label: Menu + - id: service.list + label: List + - id: service.add + label: Add + - id: service.update + label: Update + - id: service.delete + label: Delete + + - id: partner + label: Partners + acls: + - id: partner.menu + label: Menu + - id: partner.list + label: List + - id: partner.add + label: Add + - id: partner.update + label: Update + - id: partner.delete + label: Delete + + - id: motolite_event + label: Motolite Events + acls: + - id: motolite_event.menu + label: Menu + - id: motolite_event.list + label: List + - id: motolite_event.add + label: Add + - id: motolite_event.update + label: Update + - id: motolite_event.delete + label: Delete + + - id: review + label: Reviews + acls: + - id: review.menu + label: Menu + - id: review.list + label: List + - id: review.view + label: View + - id: review.delete + label: Delete + + - id: privacypolicy + label: Privacy Policy + acls: + - id: privacy_policy.menu + label: Menu + - id: privacy_policy.list + label: List + - id: privacy_policy.add + label: Add + - id: privacy_policy.update + label: Update + - id: privacy_policy.delete + label: Delete + + - id: warranty + label: Warranty + acls: + - id: warranty.menu + label: Menu + - id: warranty.list + label: List + - id: warranty.add + label: Add + - id: warranty.update + label: Update + + - id: staticcontent + label: Static Content + acls: + - id: static_content.menu + label: Menu + - id: static_content.list + label: List + - id: static_content.add + label: Add + - id: static_content.update + label: Update + - id: static_content.delete + label: Delete + + - id: analytics + label: Analytics + acls: + - id: analytics.menu + label: Menu + - id: analytics.forecast + label: Forecasting + + - id: sap_battery + label: SAP Battery Access + acls: + - id: sap_battery.menu + label: Menu + - id: sap_battery.list + label: List + - id: sap_battery.add + label: Add + - id: sap_battery.update + label: Update + - id: sap_battery.delete + label: Delete + + - id: sap_brand + label: SAP Battery Brand Access + acls: + - id: sap_brand.menu + label: Menu + - id: sap_brand.list + label: List + - id: sap_brand.add + label: Add + - id: sap_brand.update + label: Update + - id: sap_brand.delete + label: Delete + + - id: sap_bsize + label: SAP Battery Size Access + acls: + - id: sap_bsize.menu + label: Menu + - id: sap_bsize.list + label: List + - id: sap_bsize.add + label: Add + - id: sap_bsize.update + label: Update + - id: sap_bsize.delete + label: Delete + + - id: sap_csize + label: SAP Battery Container Size Access + acls: + - id: sap_csize.menu + label: Menu + - id: sap_csize.list + label: List + - id: sap_csize.add + label: Add + - id: sap_csize.update + label: Update + - id: sap_csize.delete + label: Delete + + - id: customer_tag + label: Customer Tags Access + acls: + - id: customer_tag.menu + label: Menu + - id: customer_tag.list + label: List + - id: customer_tag.add + label: Add + - id: customer_tag.update + label: Update + - id: customer_tag.delete + label: Delete + + - id: review_tag + label: Review Tags Access + acls: + - id: review_tag.menu + label: Menu + - id: review_tag.list + label: List + - id: review_tag.add + label: Add + - id: review_tag.update + label: Update + - id: review_tag.delete + label: Delete + + - id: dealer + label: Dealer Access + acls: + - id: dealer.menu + label: Menu + - id: dealer.list + label: List + - id: dealer.add + label: Add + - id: dealer.update + label: Update + - id: dealer.delete + label: Delete + + - id: database + label: Database Access + acls: + - id: database.menu + label: Menu + + - id: ticket_type + label: Ticket Type Access + acls: + - id: ticket_type.menu + label: Menu + - id: ticket_type.list + label: List + - id: ticket_type.add + label: Add + - id: ticket_type.update + label: Update + - id: ticket_type.delete + label: Delete + + - id: subticket_type + label: Sub Ticket Type Access + acls: + - id: subticket_type.menu + label: Menu + - id: subticket_type.list + label: List + - id: subticket_type.add + label: Add + - id: subticket_type.update + label: Update + - id: subticket_type.delete + label: Delete + + - id: emergency_type + label: Emergency Type Access + acls: + - id: emergency_type.menu + label: Menu + - id: emergency_type.list + label: List + - id: emergency_type.add + label: Add + - id: emergency_type.update + label: Update + - id: emergency_type.delete + label: Delete + + - id: ownership_type + label: Ownership Type Access + acls: + - id: ownership_type.menu + label: Menu + - id: ownership_type.list + label: List + - id: ownership_type.add + label: Add + - id: ownership_type.update + label: Update + - id: ownership_type.delete + label: Delete + + - id: service_offering + label: Service Offering Access + acls: + - id: service_offering.menu + label: Menu + - id: service_offering.list + label: List + - id: service_offering.add + label: Add + - id: service_offering.update + label: Update + - id: service_offering.delete + label: Delete + + api: + user_entity: "App\\Entity\\ApiUser" + acl_data: + - id: warranty + label: Warranty Access + acls: + - id: warranty.list + label: List + - id: warranty.find.serial + label: Find by Serial + - id: warranty.find.platenumber + label: Find by Plate Number + - id: warranty.register.battery + label: Register Battery + - id: warranty.claim + label: Claim + - id: warranty.update + label: Update + - id: warranty.cancel + label: Cancel + - id: warranty.delete + label: Delete + - id: warranty.set.privacypolicy + label: Set Privacy Policy + - id: warranty.list.serial + label: List by Serial + - id: batterybrand + label: Battery Brand Access + acls: + - id: batterybrand.list + label: List + - id: batterysize + label: Battery Size Access + acls: + - id: batterysize.list + label: List + - id: battery + label: Battery Access + acls: + - id: battery.list + label: List + - id: vmanufacturer + label: Vehicle Manufacturer Access + acls: + - id: vmanufacturer.list + label: List + - id: vehicle + label: Vehicle Access + acls: + - id: vehicle.list + label: List + - id: privacypolicy + label: Privacy Policy + acls: + - id: privacypolicy.find + label: Find Privacy Policy + - id: customer + label: Customer + acls: + - id: customer.register + label: Register Customer + - id: customer.verify + label: Verify Customer + - id: municipality + label: Municipality + acls: + - id: municipality.list + label: List + - id: dealer + label: Dealer + acls: + - id: dealer.list + label: List + - id: warrantyserial + label: Warranty Serial + acls: + - id: warrantyserial.upload + label: Upload + + - id: tapi_vmanufacturer + label: Third Party Vehicle Manufacturer Access + acls: + - id: tapi_vmanufacturer.list + label: List Third Party Vehicle Manufacturers + - id: tapi_vehicle + label: Third Party Vehicle Make Access + acls: + - id: tapi_vehicle.list + label: List Third Party Vehicles + - id: tapi_promo + label: Third Party Promo Access + acls: + - id: tapi_promo.list + label: List Third Party Promos + - id: tapi_battery + label: Third Party Battery Access + acls: + - id: tapi_battery_compatible.list + label: List Third Party Compatible Batteries + - id: tapi_jo + label: Third Party Job Order Access + acls: + - id: tapi_jo.request + label: Third Party Request Job Order + - id: tapi_jo.get.estimate + label: Third Party Get Estimate + - id: tapi_jo.get.ongoing + label: Third Party Get Ongoing Job Order + - id: tapi_jo.cancel + label: Third Party Cancel Job Order + - id: tapi_jo.get.invoice + label: Third Party Get Job Order Invoice + - id: tapi_jo.location.support + label: Third Party Check Location Support + - id: tapi_jo.nearest_hub.get + label: Third Party Get Nearest Hub and Slots + - id: tapi_jo.schedule_option.status + label: Third Party Schedule Option Status + - id: tapi_jo.get.info + label: Third Party Get Job Order Info + - id: tapi_service + label: Third Party Service Access + acls: + - id: tapi_service.list + label: List Third Party Services + + cust_api_v2: + user_entity: "App\\Entity\\CustomerUser" + acl_data: + - id: cust_api_v2.auth + label: Authentication + acls: + - id: cust_api_v2.auth.register + label: Register + - id: cust_api_v2.auth.confirm + label: Confirm Number + - id: cust_api_v2.auth.validate + label: Validate Code + - id: cust_api_v2.auth.resend_code + label: Resend Code + - id: cust_api_v2.customer + label: Customer + acls: + - id: cust_api_v2.customer.info + label: Info + - id: cust_api_v2.customer.update + label: Update + - id: cust_api_v2.customer.status + label: Status + - id: cust_api_v2.customer.hash + label: Hash + - id: cust_api_v2.device + label: Device + acls: + - id: cust_api_v2.device.id + label: Update + - id: cust_api_v2.invoice + label: Invoice + acls: + - id: cust_api_v2.invoice.estimate + label: Estimate + - id: cust_api_v2.jo + label: Job Order + acls: + - id: cust_api_v2.jo.ongoing + label: List Ongoing + - id: cust_api_v2.jo.invoice + label: Get Invoice + - id: cust_api_v2.jo.cancel + label: Cancel + - id: cust_api_v2.jo.info + label: Info + - id: cust_api_v2.jo.history + label: History + - id: cust_api_v2.jo.latest + label: Latest + - id: cust_api_v2.jo.all_ongoing + label: List All Ongoing + - id: cust_api_v2.jo.ongoing_count + label: List Ongoing Count + - id: cust_api_v2.jo.create + label: Create + - id: cust_api_v2.jo.request + label: Request + - id: cust_api_v2.jo.completed + label: List Completed + - id: cust_api_v2.location + label: Location + acls: + - id: cust_api_v2.location.support + label: Get Support Status + - id: cust_api_v2.location.nearest_hub_and_slots + label: List Nearest Hub and Slots + - id: cust_api_v2.location.create + label: Create + - id: cust_api_v2.location.list + label: List + - id: cust_api_v2.partner + label: Partner + acls: + - id: cust_api_v2.partner.info + label: Info + - id: cust_api_v2.partner.closest + label: List Closest Partners + - id: cust_api_v2.partner.review + label: Review + - id: cust_api_v2.privacy + label: Privacy + acls: + - id: cust_api_v2.privacy.settings + label: Get Privacy Settings + - id: cust_api_v2.promo + label: Promo + acls: + - id: cust_api_v2.promo.list + label: Get Promos + - id: cust_api_v2.rider + label: Rider + acls: + - id: cust_api_v2.rider.status + label: Status + - id: cust_api_v2.rider.rating + label: Rate + - id: cust_api_v2.schedule + label: Schedule + acls: + - id: cust_api_v2.schedule.status + label: Get Schedule Option Status + - id: cust_api_v2.service + label: Service + acls: + - id: cust_api_v2.service.list + label: List + - id: cust_api_v2.vehicle + label: Vehicle + acls: + - id: cust_api_v2.vehicle.mfgs + label: List Manufacturers + - id: cust_api_v2.vehicle.makes + label: List Makes + - id: cust_api_v2.vehicle.create + label: Add + - id: cust_api_v2.vehicle.update + label: Update + - id: cust_api_v2.vehicle.list + label: List + - id: cust_api_v2.vehicle.batteries + label: Compatible Batteries + - id: cust_api_v2.vehicle.delete + label: Delete + - id: cust_api_v2.warranty + label: Warranty + acls: + - id: cust_api_v2.warranty.activate + label: Activate + - id: cust_api_v2.warranty.check + label: Check Status + - id: cust_api_v2.warranty.register + label: Register diff --git a/config/packages/catalyst_menu.yaml b/config/packages/catalyst_menu.yaml new file mode 100644 index 00000000..deb8c43f --- /dev/null +++ b/config/packages/catalyst_menu.yaml @@ -0,0 +1,290 @@ +catalyst_menu: + main: + - id: home + acl: dashboard.menu + label: '[menu.dashboard]' + icon: flaticon-line-graph + order: 1 + + + - id: user + acl: user.menu + label: '[menu.user]' + icon: flaticon-users + order: 2 + - id: user_list + acl: user.list + label: '[menu.user.users]' + parent: user + - id: role_list + acl: role.list + label: '[menu.user.roles]' + parent: user + + - id: apiuser + acl: apiuser.menu + label: '[menu.apiuser]' + icon: flaticon-users + order: 3 + - id: api_user_list + acl: apiuser.list + label: '[menu.apiuser.users]' + parent: apiuser + - id: api_role_list + acl: apirole.list + label: '[menu.apiuser.roles]' + parent: apiuser + + - id: logistics + acl: logistics.menu + label: '[menu.logistics]' + icon: fa fa-truck + order: 4 + - id: rider_list + acl: rider.list + label: '[menu.logistics.riders]' + parent: logistics + + - id: battery + acl: battery.menu + label: '[menu.battery]' + icon: fa fa-battery-3 + order: 5 + - id: battery_list + acl: battery.list + label: '[menu.battery.batteries]' + parent: battery + - id: bmfg_list + acl: bmfg.list + label: '[menu.battery.manufacturers]' + parent: battery + - id: bmodel_list + acl: bmodel.list + label: '[menu.battery.models]' + parent: battery + - id: bsize_list + acl: bsize.list + label: '[menu.battery.sizes]' + parent: battery + - id: promo_list + acl: promo.list + label: '[menu.battery.promos]' + parent: battery + + - id: sapbattery + acl: sap_battery.menu + label: '[menu.sapbattery]' + icon: fa fa-battery + order: 6 + - id: sapbattery_list + acl: sap_battery.list + label: '[menu.sapbattery.batteries]' + parent: sapbattery + - id: sapbrand_list + acl: sap_brand.list + label: '[menu.sapbattery.brands]' + parent: sapbattery + - id: sapbsize_list + acl: sap_bsize.list + label: '[menu.sapbattery.sizes]' + parent: sapbattery + - id: sapcsize_list + acl: sap_csize.list + label: '[menu.sapbattery.csizes]' + parent: sapbattery + + + - id: vehicle + acl: vehicle.menu + label: '[menu.vehicle]' + icon: fa fa-car + order: 7 + - id: vehicle_list + acl: vehicle.list + label: '[menu.vehicle.vehicles]' + parent: vehicle + - id: vmfg_list + acl: vmfg.list + label: '[menu.vehicle.manufacturers]' + parent: vehicle + + - id: location + acl: location.menu + label: '[menu.location]' + icon: fa fa-home + order: 8 + - id: outlet_list + acl: outlet.menu + label: '[menu.location.outlets]' + parent: location + - id: hub_list + acl: hub.menu + label: '[menu.location.hubs]' + parent: location + - id: dealer_list + acl: dealer.list + label: '[menu.location.dealers]' + parent: location + - id: geofence_list + acl: geofence.menu + label: '[menu.location.geofence]' + parent: location + + + - id: joborder + acl: joborder.menu + label: '[menu.joborder]' + icon: flaticon-calendar-3 + order: 9 + - id: jo_in + acl: jo_in.list + label: '[menu.joborder.incoming]' + parent: joborder + - id: jo_proc + acl: jo_proc.list + label: '[menu.joborder.dispatch]' + parent: joborder + - id: jo_resq_proc + acl: jo_resq_proc.list + label: '[menu.joborder.resqdispatch]' + parent: joborder + - id: jo_assign + acl: jo_assign.list + label: '[menu.joborder.assignment]' + parent: joborder + - id: jo_fulfill + acl: jo_fulfill.list + label: '[menu.joborder.fulfillment]' + parent: joborder + - id: jo_open + acl: jo_open.list + label: '[menu.joborder.open]' + parent: joborder + - id: jo_all + acl: jo_all.list + label: '[menu.joborder.viewall]' + parent: joborder + - id: jo_hub_view + acl: jo_hub.list + label: '[menu.joborder.hubview]' + parent: joborder + - id: jo_resq_all + acl: jo_resq_all.list + label: '[menu.joborder.resqall]' + parent: joborder + + - id: support + acl: support.menu + label: '[menu.support]' + icon: flaticon-support + order: 10 + - id: customer_list + acl: customer.list + label: '[menu.support.customers]' + parent: support + - id: ticket_list + acl: ticket.list + label: '[menu.support.tickets]' + parent: support + - id: general_search + acl: general.search + label: '[menu.support.search]' + parent: support + - id: warranty_search + acl: warranty.search + label: '[menu.support.warrantysearch]' + parent: support + - id: privacy_policy_list + acl: privacy_policy.list + label: '[menu.support.privacypolicy]' + parent: support + - id: warranty_list + acl: warranty.list + label: '[menu.support.warranty]' + parent: support + - id: warranty_upload + acl: warranty.upload + label: '[menu.support.warrantyupload]' + parent: support + - id: static_content_list + acl: static_content.list + label: '[menu.support.staticcontent]' + parent: support + - id: customertag_list + acl: customer_tag.list + label: '[menu.support.customertags]' + parent: support + - id: reviewtag_list + acl: review_tag.list + label: '[menu.support.reviewtags]' + parent: support + + - id: service + acl: service.menu + label: '[menu.service]' + icon: flaticon-squares + order: 11 + - id: service_list + acl: service.list + label: '[menu.service.services]' + parent: service + + - id: partner + acl: partner.menu + label: '[menu.partner]' + icon: flaticon-network + order: 12 + - id: partner_list + acl: partner.list + label: '[menu.partner.partners]' + parent: partner + - id: review_list + acl: review.list + label: '[menu.partner.reviews]' + parent: partner + + - id: motolite_event + acl: motolite_event.menu + label: '[menu.motolite_event]' + icon: flaticon-event-calendar-symbol + order: 13 + - id: motolite_event_list + acl: motolite_event.list + label: '[menu.motolite_event.events]' + parent: motolite_event + + - id: analytics + acl: analytics.menu + label: '[menu.analytics]' + icon: flaticon-graphic + order: 14 + - id: analytics_forecast_form + acl: analytics.forecast + label: '[menu.analytics.forecasting]' + parent: analytics + + - id: database + acl: database.menu + label: '[menu.database]' + icon: fa fa-database + order: 15 + - id: ticket_type_list + acl: ticket_type.menu + label: '[menu.database.tickettypes]' + parent: database + - id: subticket_type_list + acl: subticket_type.menu + label: '[menu.database.subtickettypes]' + parent: database + - id: emergency_type_list + acl: emergency_type.menu + label: '[menu.database.emergencytypes]' + parent: database + - id: ownership_type_list + acl: ownership_type.menu + label: '[menu.database.ownershiptypes]' + parent: database + - id: service_offering_list + acl: service_offering.menu + label: '[menu.database.serviceofferings]' + parent: database diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 1b0f6824..7e9140dc 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -9,8 +9,14 @@ security: entity: class: App\Entity\User property: username - api_key_user_provider: - id: Catalyst\APIBundle\Security\APIKeyUserProvider + api_provider: + entity: + class: App\Entity\ApiUser + property: api_key + api_v2_provider: + entity: + class: App\Entity\CustomerUser + property: api_key firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ @@ -47,29 +53,53 @@ security: pattern: ^\/test_capi\/ security: false + insurance: + pattern: ^\/insurance\/ + security: false + + paymongo: + pattern: ^\/paymongo\/ + security: false + + cust_api_v2: + pattern: ^\/apiv2\/(?!register|register\/|number_confirm|number_confirm\/|code_validate|code_validate\/|resend_code|resend_code\/|version_check|version_check\/|account|account\/|account_code_validate|account_code_validate\/|account_resend_code|account_resend_code\/) + provider: api_v2_provider + access_denied_handler: Catalyst\ApiBundle\Service\AccessDeniedHandler + stateless: true + guard: + authenticators: + - Catalyst\ApiBundle\Security\Authenticator + + cust_api_v2_guest: + pattern: ^\/apiv2\/(register|register\/|number_confirm|number_confirm\/|code_validate|code_validate\/|resend_code|resend_code\/|version_check|version_check\/|account|account\/|account_code_validate|account_code_validate\/|account_resend_code|account_resend_code\/) + security: false + warranty_api: pattern: ^\/capi\/ + provider: api_provider + access_denied_handler: Catalyst\ApiBundle\Service\AccessDeniedHandler stateless: true - simple_preauth: - authenticator: Catalyst\APIBundle\Security\APIKeyAuthenticator - provider: api_key_user_provider - user_checker: Catalyst\AuthBundle\Service\UserChecker + guard: + authenticators: + - Catalyst\ApiBundle\Security\Authenticator new_rider_api: pattern: ^\/rider_api\/ + provider: api_provider + access_denied_handler: Catalyst\ApiBundle\Service\AccessDeniedHandler stateless: true - simple_preauth: - authenticator: Catalyst\APIBundle\Security\APIKeyAuthenticator - provider: api_key_user_provider - user_checker: Catalyst\AuthBundle\Service\UserChecker + guard: + authenticators: + - Catalyst\ApiBundle\Security\Authenticator third_party_api: pattern: ^\/tapi\/ + provider: api_provider + access_denied_handler: Catalyst\ApiBundle\Service\AccessDeniedHandler stateless: true - simple_preauth: - authenticator: Catalyst\APIBundle\Security\APIKeyAuthenticator - provider: api_key_user_provider - user_checker: Catalyst\AuthBundle\Service\UserChecker + guard: + authenticators: + - Catalyst\ApiBundle\Security\Authenticator main: provider: user_provider @@ -84,6 +114,7 @@ security: lifetime: 604800 path: / user_checker: Catalyst\AuthBundle\Service\UserChecker + switch_user: { role: ROLE_SUPER_ADMIN } # activate different ways to authenticate diff --git a/config/resq.services.yaml b/config/resq.services.yaml index 82eb4cc5..177d68b5 100644 --- a/config/resq.services.yaml +++ b/config/resq.services.yaml @@ -43,20 +43,6 @@ services: $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%' @@ -115,50 +101,6 @@ services: 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: ~ diff --git a/config/routes/apiv2.yaml b/config/routes/apiv2.yaml new file mode 100644 index 00000000..9f183530 --- /dev/null +++ b/config/routes/apiv2.yaml @@ -0,0 +1,305 @@ +# api + +apiv2_register: + path: /apiv2/register + controller: App\Controller\CustomerAppAPI\AuthController::register + methods: [POST] + +apiv2_confirm: + path: /apiv2/number_confirm + controller: App\Controller\CustomerAppAPI\AuthController::confirmNumber + methods: [POST] + +apiv2_validate: + path: /apiv2/code_validate + controller: App\Controller\CustomerAppAPI\AuthController::validateCode + methods: [POST] + +apiv2_info_get: + path: /apiv2/info + controller: App\Controller\CustomerAppAPI\CustomerController::getInfo + methods: [GET] + +apiv2_info_update: + path: /apiv2/info + controller: App\Controller\CustomerAppAPI\CustomerController::updateInfo + methods: [POST] + +apiv2_status: + path: /apiv2/status + controller: App\Controller\CustomerAppAPI\CustomerController::getStatus + methods: [GET] + +apiv2_vehicle_mfg_list: + path: /apiv2/vehicle/mfgs + controller: App\Controller\CustomerAppAPI\VehicleController::listVehicleManufacturers + methods: [GET] + +apiv2_vehicle_make_list: + path: /apiv2/vehicle/mfgs/{mfg_id}/makes + controller: App\Controller\CustomerAppAPI\VehicleController::listVehicleMakes + methods: [GET] + +apiv2_cust_vehicle_add: + path: /apiv2/vehicles + controller: App\Controller\CustomerAppAPI\VehicleController::addVehicle + methods: [POST] + +apiv2_cust_vehicle_info: + path: /apiv2/vehicles/{id} + controller: App\Controller\CustomerAppAPI\VehicleController::getVehicle + methods: [GET] + +apiv2_cust_vehicle_update: + path: /apiv2/vehicles/{id} + controller: App\Controller\CustomerAppAPI\VehicleController::updateVehicle + methods: [POST] + +apiv2_cust_vehicle_list: + path: /apiv2/vehicles + controller: App\Controller\CustomerAppAPI\VehicleController::listVehicles + methods: [GET] + +apiv2_promo_list: + path: /apiv2/promos + controller: App\Controller\CustomerAppAPI\PromoController::listPromos + methods: [GET] + +apiv2_battery_list: + path: /apiv2/vehicles/{vid}/compatible_batteries + controller: App\Controller\CustomerAppAPI\VehicleController::getCompatibleBatteries + methods: [GET] + +apiv2_jo_request: + path: /apiv2/job_order + controller: App\Controller\CustomerAppAPI\JobOrderController::requestJobOrder + methods: [POST] + +apiv2_estimate: + path: /apiv2/estimate + controller: App\Controller\CustomerAppAPI\InvoiceController::getEstimate + methods: [POST] + +apiv2_ongoing: + path: /apiv2/job_order/ongoing + controller: App\Controller\CustomerAppAPI\JobOrderController::getOngoing + methods: [GET] + +apiv2_rider_status: + path: /apiv2/rider + controller: App\Controller\CustomerAppAPI\RiderController::getRiderStatus + methods: [GET] + +apiv2_rider_rating_add: + path: /apiv2/rider_rating + controller: App\Controller\CustomerAppAPI\RiderController::addRiderRating + methods: [POST] + +apiv2_jo_cancel: + path: /apiv2/job_order/cancel + controller: App\Controller\CustomerAppAPI\JobOrderController:cancelJobOrder + methods: [POST] + +apiv2_jo_history: + path: /apiv2/job_order/history + controller: App\Controller\CustomerAppAPI\JobOrderController:getJOHistory + methods: [GET] + +apiv2_jo_invoice: + path: /apiv2/job_order/invoice + controller: App\Controller\CustomerAppAPI\JobOrderController:getJOInvoice + methods: [GET] + +apiv2_device_id: + path: /apiv2/device_id + controller: App\Controller\CustomerAppAPI\DeviceController:updateDeviceID + methods: [POST] + +apiv2_privacy: + path: /apiv2/privacy + controller: App\Controller\CustomerAppAPI\PrivacyController:privacySettings + methods: [POST] + +apiv2_resend_code: + path: /apiv2/resend_code + controller: App\Controller\CustomerAppAPI\AuthController:resendCode + methods: [POST] + +apiv2_location_support: + path: /apiv2/location_support + controller: App\Controller\CustomerAppAPI\LocationController:locationSupport + methods: [GET] + +apiv2_activate_warranty: + path: /apiv2/activate_warranty + controller: App\Controller\CustomerAppAPI\WarrantyController:activateWarranty + methods: [POST] + +apiv2_service_list: + path: /apiv2/services + controller: App\Controller\CustomerAppAPI\ServiceController:listServices + methods: [GET] + +apiv2_partner_info: + path: /apiv2/partners/{pid} + controller: App\Controller\CustomerAppAPI\PartnerController:getPartnerInformation + methods: [GET] + +apiv2_partner: + path: /apiv2/partners + controller: App\Controller\CustomerAppAPI\PartnerController:getClosestPartners + methods: [GET] + +apiv2_partner_review: + path: /apiv2/partners/{pid}/review + controller: App\Controller\CustomerAppAPI\PartnerController:reviewPartner + methods: [POST] + +apiv2_nearest_hub_slots: + path: /apiv2/hub_slots + controller: App\Controller\CustomerAppAPI\LocationController::getNearestHubAndSlots + methods: [GET] + +apiv2_new_jo_request: + path: /apiv2/new_job_order + controller: App\Controller\CustomerAppAPI\JobOrderController::newRequestJobOrder + methods: [POST] + +apiv2_version_check: + path: /apiv2/version_check + controller: App\Controller\CustomerAppAPI\AppController::versionCheck + methods: [GET] + +apiv2_schedule_option_status: + path: /apiv2/schedule_option_status + controller: App\Controller\CustomerAppAPI\ScheduleController::scheduleOptionStatus + methods: [GET] + +# paperless warranty / qr code +apiv2_warr_serial_check: + path: /apiv2/warranty/{serial} + controller: App\Controller\CustomerAppAPI\WarrantyController::warrantyCheck + methods: [GET] + +apiv2_warr_serial_register: + path: /apiv2/warranty/{serial} + controller: App\Controller\CustomerAppAPI\WarrantyController::warrantyRegister + methods: [POST] + +apiv2_jo_info: + path: /apiv2/job_order/{id}/info + controller: App\Controller\CustomerAppAPI\JobOrderController::getJobOrderInfo + methods: [GET] + +apiv2_ongoing_job_orders: + path: /apiv2/job_orders/ongoing + controller: App\Controller\CustomerAppAPI\JobOrderController::getAllOngoingJobOrders + methods: [GET] + +apiv2_ongoing_jo_count: + path: /apiv2/job_orders/ongoing/count + controller: App\Controller\CustomerAppAPI\JobOrderController::getOngoingJobOrderCount + methods: [GET] + +apiv2_new_location: + path: /apiv2/new_location + controller: App\Controller\CustomerAppAPI\LocationController::addLocation + methods: [POST] + +apiv2_locations: + path: /apiv2/locations + controller: App\Controller\CustomerAppAPI\LocationController::getLocations + methods: [GET] + +apiv2_location_remove: + path: /apiv2/locations/{id}/remove + controller: App\Controller\CustomerAppAPI\LocationController::removeLocation + methods: [POST] + +apiv2_cust_vehicle_remove: + path: /apiv2/vehicles/{id}/remove + controller: App\Controller\CustomerAppAPI\VehicleController::removeVehicle + methods: [POST] + +apiv2_latest_job_order: + path: /apiv2/job_order/latest + controller: App\Controller\CustomerAppAPI\JobOrderController::getLatestJobOrder + methods: [GET] + +apiv2_customer_hash_get: + path: /apiv2/customer_hash + controller: App\Controller\CustomerAppAPI\CustomerController::getCustomerHash + methods: [GET] + +#apiv2_completed_job_orders: +# path: /apiv2/job_orders/completed +# controller: App\Controller\CustomerAppAPI\JobOrderController::getCompletedJobOrders +# methods: [GET] + +# motolite events +apiv2_motolite_events: + path: /apiv2/motolite_events + controller: App\Controller\CustomerAppAPI\MotoliteEventController::getEvents + methods: [GET] + +# review tags +apiv2_partner_review_tags: + path: /apiv2/review_tags/partner + controller: App\Controller\CustomerAppAPI\ReviewTagController::getPartnerReviewTags + +apiv2_rider_review_tags: + path: /apiv2/review_tags/rider + controller: App\Controller\CustomerAppAPI\ReviewTagController::getRiderReviewTags + +# account deletion +apiv2_account_delete: + path: /apiv2/account_delete + controller: App\Controller\CustomerAppAPI\AccountController::deleteAccount + methods: [POST] + +apiv2_account_delete_resend_code: + path: /apiv2/account_delete_resend_code + controller: App\Controller\CustomerAppAPI\AccountController:resendCode + methods: [POST] + +apiv2_account_delete_code_validate: + path: /apiv2/account_delete_code_validate + controller: App\Controller\CustomerAppAPI\AccountController::validateDeleteCode + methods: [POST] + +# trade-in support +apiv2_cust_vehicle_trade_in_estimate: + path: /apiv2/vehicles/{id}/trade_in_estimate + controller: App\Controller\CustomerAppAPI\VehicleController::getTradeInEstimate + methods: [GET] + +# insurance +apiv2_insurance_vehicle_maker_list: + path: /apiv2/insurance/vehicles/makers + controller: App\Controller\CustomerAppAPI\InsuranceController::getVehicleMakers + methods: [GET] + +apiv2_insurance_vehicle_model_list: + path: /apiv2/insurance/vehicles/models/{maker_id} + controller: App\Controller\CustomerAppAPI\InsuranceController::getVehicleModels + methods: [GET] + +apiv2_insurance_vehicle_trim_list: + path: /apiv2/insurance/vehicles/trims/{model_id} + controller: App\Controller\CustomerAppAPI\InsuranceController::getVehicleTrims + methods: [GET] + +apiv2_insurance_vehicle_mv_type_list: + path: /apiv2/insurance/mvtypes + controller: App\Controller\CustomerAppAPI\InsuranceController::getMVTypes + methods: [GET] + +apiv2_insurance_vehicle_client_type_list: + path: /apiv2/insurance/clienttypes + controller: App\Controller\CustomerAppAPI\InsuranceController::getClientTypes + methods: [GET] + +apiv2_insurance_application_create: + path: /apiv2/insurance/application + controller: App\Controller\CustomerAppAPI\InsuranceController::createApplication + methods: [POST] diff --git a/config/routes/customer_api.yaml b/config/routes/customer_api.yaml new file mode 100644 index 00000000..86aae804 --- /dev/null +++ b/config/routes/customer_api.yaml @@ -0,0 +1,227 @@ +# api + +cust_api_register: + path: /apiv2/register + controller: App\Controller\CustomerAppAPI\AuthController::register + methods: [POST] + +cust_api_confirm: + path: /apiv2/number_confirm + controller: App\Controller\CustomerAppAPI\AuthController::confirmNumber + methods: [POST] + +cust_api_validate: + path: /apiv2/code_validate + controller: App\Controller\CustomerAppAPI\AuthController::validateCode + methods: [POST] + +cust_api_info_get: + path: /apiv2/info + controller: App\Controller\CustomerAppAPI\CustomerController::getInfo + methods: [GET] + +cust_api_info_update: + path: /apiv2/info + controller: App\Controller\CustomerAppAPI\CustomerController::updateInfo + methods: [POST] + +cust_api_status: + path: /apiv2/status + controller: App\Controller\CustomerAppAPI\CustomerController::getStatus + methods: [GET] + +cust_api_vehicle_mfg_list: + path: /apiv2/vehicle/mfgs + controller: App\Controller\CustomerAppAPI\VehicleController::listVehicleManufacturers + methods: [GET] + +cust_api_vehicle_make_list: + path: /apiv2/vehicle/mfgs/{mfg_id}/makes + controller: App\Controller\CustomerAppAPI\VehicleController::listVehicleMakes + methods: [GET] + +cust_api_cust_vehicle_add: + path: /apiv2/vehicles + controller: App\Controller\CustomerAppAPI\VehicleController::addVehicle + methods: [POST] + +cust_api_cust_vehicle_update: + path: /apiv2/vehicles/{id} + controller: App\Controller\CustomerAppAPI\VehicleController::updateVehicle + methods: [POST] + +cust_api_cust_vehicle_list: + path: /apiv2/vehicles + controller: App\Controller\CustomerAppAPI\VehicleController::listVehicles + methods: [GET] + +cust_api_promo_list: + path: /apiv2/promos + controller: App\Controller\CustomerAppAPI\PromoController::listPromos + methods: [GET] + +cust_api_battery_list: + path: /apiv2/vehicles/{vid}/compatible_batteries + controller: App\Controller\CustomerAppAPI\VehicleController::getCompatibleBatteries + methods: [GET] + +cust_api_jo_request: + path: /apiv2/job_order + controller: App\Controller\CustomerAppAPI\JobOrderController::requestJobOrder + methods: [POST] + +cust_api_estimate: + path: /apiv2/estimate + controller: App\Controller\CustomerAppAPI\EstimateController::getEstimate + methods: [POST] + +cust_api_ongoing: + path: /apiv2/job_order/ongoing + controller: App\Controller\CustomerAppAPI\JobOrderController::getOngoing + methods: [GET] + +cust_api_rider_status: + path: /apiv2/rider + controller: App\Controller\CustomerAppAPI\RiderController::getRiderStatus + methods: [GET] + +cust_api_rider_rating_add: + path: /apiv2/rider_rating + controller: App\Controller\CustomerAppAPI\RiderController::addRiderRating + methods: [POST] + +cust_api_jo_cancel: + path: /apiv2/job_order/cancel + controller: App\Controller\CustomerAppAPI\JobOrderController:cancelJobOrder + methods: [POST] + +cust_api_jo_history: + path: /apiv2/job_order/history + controller: App\Controller\CustomerAppAPI\JobOrderController:getJOHistory + methods: [GET] + +cust_api_jo_invoice: + path: /apiv2/job_order/invoice + controller: App\Controller\CustomerAppAPI\JobOrderController:getJOInvoice + methods: [GET] + +cust_api_device_id: + path: /apiv2/device_id + controller: App\Controller\CustomerAppAPI\DeviceController:updateDeviceID + methods: [POST] + +cust_api_privacy: + path: /apiv2/privacy + controller: App\Controller\CustomerAppAPI\PrivacyController:privacySettings + methods: [POST] + +cust_api_resend_code: + path: /apiv2/resend_code + controller: App\Controller\CustomerAppAPI\AuthController:resendCode + methods: [POST] + +cust_api_location_support: + path: /apiv2/location_support + controller: App\Controller\CustomerAppAPI\LocationController:locationSupport + methods: [GET] + +cust_api_activate_warranty: + path: /apiv2/activate_warranty + controller: App\Controller\CustomerAppAPI\WarrantyController:activateWarranty + methods: [POST] + +cust_api_service_list: + path: /apiv2/services + controller: App\Controller\CustomerAppAPI\ServiceController:listServices + methods: [GET] + +cust_api_partner_info: + path: /apiv2/partners/{pid} + controller: App\Controller\CustomerAppAPI\PartnerController:getPartnerInformation + methods: [GET] + +cust_api_partner: + path: /apiv2/partners + controller: App\Controller\CustomerAppAPI\PartnerController:getClosestPartners + methods: [GET] + +cust_api_partner_review: + path: /apiv2/partners/{pid}/review + controller: App\Controller\CustomerAppAPI\PartnerController:reviewPartner + methods: [POST] + +cust_api_nearest_hub_slots: + path: /apiv2/hub_slots + controller: App\Controller\CustomerAppAPI\LocationController::getNearestHubAndSlots + methods: [GET] + +cust_api_new_jo_request: + path: /apiv2/new_job_order + controller: App\Controller\CustomerAppAPI\JobOrderController::newRequestJobOrder + methods: [POST] + +cust_api_version_check: + path: /apiv2/version_check + controller: App\Controller\CustomerAppAPI\AppController::versionCheck + methods: [GET] + +cust_api_schedule_option_status: + path: /apiv2/schedule_option_status + controller: App\Controller\CustomerAppAPI\ScheduleController::scheduleOptionStatus + methods: [GET] + +# paperless warranty / qr code +cust_api_warr_serial_check: + path: /apiv2/warranty/{serial} + controller: App\Controller\CustomerAppAPI\WarrantyController::warrantyCheck + methods: [GET] + +cust_api_warr_serial_register: + path: /apiv2/warranty/{serial} + controller: App\Controller\CustomerAppAPI\WarrantyController::warrantyRegister + methods: [POST] + +cust_api_jo_info: + path: /apiv2/job_order/{id}/info + controller: App\Controller\CustomerAppAPI\JobOrderController::getJobOrderInfo + methods: [GET] + +cust_api_ongoing_job_orders: + path: /apiv2/job_orders/ongoing + controller: App\Controller\CustomerAppAPI\JobOrderController::getAllOngoingJobOrders + methods: [GET] + +cust_api_ongoing_jo_count: + path: /apiv2/job_orders/ongoing/count + controller: App\Controller\CustomerAppAPI\JobOrderController::getOngoingJobOrderCount + methods: [GET] + +cust_api_new_location: + path: /apiv2/new_location + controller: App\Controller\CustomerAppAPI\LocationController::addLocation + methods: [POST] + +cust_api_locations: + path: /apiv2/locations + controller: App\Controller\CustomerAppAPI\LocationController::getLocations + methods: [GET] + +cust_api_cust_vehicle_remove: + path: /apiv2/vehicles/{id}/remove + controller: App\Controller\CustomerAppAPI\VehicleController::removeVehicle + methods: [POST] + +cust_api_latest_job_order: + path: /apiv2/job_order/latest + controller: App\Controller\CustomerAppAPI\JobOrderController::getLatestJobOrder + methods: [GET] + +cust_api_customer_hash_get: + path: /apiv2/customer_hash + controller: App\Controller\CustomerAppAPI\CustomerController::getCustomerHash + methods: [GET] + +#cust_api_completed_job_orders: +# path: /apiv2/job_orders/completed +# controller: App\Controller\CustomerAppAPI\JobOrderController::getCompletedJobOrders +# methods: [GET] diff --git a/config/routes/insurance.yaml b/config/routes/insurance.yaml new file mode 100644 index 00000000..27a40144 --- /dev/null +++ b/config/routes/insurance.yaml @@ -0,0 +1,6 @@ +# insurance + +insurance_listener: + path: /insurance/listen + controller: App\Controller\InsuranceController::listen + methods: [POST] diff --git a/config/routes/job_order.yaml b/config/routes/job_order.yaml index 6ea3a3c5..861aab78 100644 --- a/config/routes/job_order.yaml +++ b/config/routes/job_order.yaml @@ -267,3 +267,8 @@ jo_geofence: path: /ajax/job-order/geofence controller: App\Controller\JobOrderController::checkGeofence methods: [GET] + +jo_all_view_form: + path: /job-order/all/view/{id} + controller: App\Controller\JobOrderController::allViewForm + methods: [GET] diff --git a/config/routes/motolite_event.yaml b/config/routes/motolite_event.yaml new file mode 100644 index 00000000..865a6162 --- /dev/null +++ b/config/routes/motolite_event.yaml @@ -0,0 +1,38 @@ +motolite_event_list: + path: /motolite_events + controller: App\Controller\MotoliteEventController::index + +motolite_event_rows: + path: /motolite_events/rows + controller: App\Controller\MotoliteEventController::rows + methods: [POST] + +motolite_event_create: + path: /motolite_events/create + controller: App\Controller\MotoliteEventController::addForm + methods: [GET] + +motolite_event_create_submit: + path: /motolite_events/create + controller: App\Controller\MotoliteEventController::addSubmit + methods: [POST] + +motolite_event_upload_image: + path: /motolite_events/upload + controller: App\Controller\MotoliteEventController::uploadImage + methods: [POST] + +motolite_event_update: + path: /motolite_events/{id} + controller: App\Controller\MotoliteEventController::updateForm + methods: [GET] + +motolite_event_update_submit: + path: /motolite_events/{id} + controller: App\Controller\MotoliteEventController::updateSubmit + methods: [POST] + +motolite_event_delete: + path: /motolite_events/{id} + controller: App\Controller\MotoliteEventController::destroy + methods: [DELETE] diff --git a/config/routes/paymongo.yaml b/config/routes/paymongo.yaml new file mode 100644 index 00000000..df0ced68 --- /dev/null +++ b/config/routes/paymongo.yaml @@ -0,0 +1,16 @@ +# paymongo + +paymongo_listener: + path: /paymongo/listen + controller: App\Controller\PayMongoController::listen + methods: [POST] + +paymongo_payment_success: + path: /paymongo/success + controller: App\Controller\PayMongoController::paymentSuccess + methods: [GET] + +paymongo_payment_cancelled: + path: /paymongo/cancelled + controller: App\Controller\PayMongoController::paymentCancelled + methods: [GET] \ No newline at end of file diff --git a/config/routes/resq_job_order.yaml b/config/routes/resq_job_order.yaml index 5c506621..0d1ea9ba 100644 --- a/config/routes/resq_job_order.yaml +++ b/config/routes/resq_job_order.yaml @@ -7,3 +7,17 @@ jo_resq_proc_rows: path: /resq-job-order/processing-rows controller: App\Controller\ResqJobOrderController::datatableRows methods: [POST] + defaults: + tier: "proc" + +jo_resq_all: + path: /resq-job-order/all + controller: App\Controller\ResqJobOrderController::listAll + methods: [GET] + +jo_resq_all_rows: + path: /resq-job-orer/all + controller: App\Controller\ResqJobOrderController::datatableRows + methods: [POST] + defaults: + tier: "all" diff --git a/config/routes/review_tag.yaml b/config/routes/review_tag.yaml new file mode 100644 index 00000000..85964581 --- /dev/null +++ b/config/routes/review_tag.yaml @@ -0,0 +1,33 @@ +reviewtag_list: + path: /review_tags + controller: App\Controller\ReviewTagController::index + +reviewtag_rows: + path: /review_tags/rows + controller: App\Controller\ReviewTagController::rows + methods: [POST] + +reviewtag_create: + path: /review_tags/create + controller: App\Controller\ReviewTagController::addForm + methods: [GET] + +reviewtag_create_submit: + path: /review_tags/create + controller: App\Controller\ReviewTagController::addSubmit + methods: [POST] + +reviewtag_update: + path: /review_tags/{id} + controller: App\Controller\ReviewTagController::updateForm + methods: [GET] + +reviewtag_update_submit: + path: /review_tags/{id} + controller: App\Controller\ReviewTagController::updateSubmit + methods: [POST] + +reviewtag_delete: + path: /review_tags/{id} + controller: App\Controller\ReviewTagController::destroy + methods: [DELETE] diff --git a/config/routes/service_offering.yaml b/config/routes/service_offering.yaml new file mode 100644 index 00000000..4f87ca89 --- /dev/null +++ b/config/routes/service_offering.yaml @@ -0,0 +1,34 @@ +service_offering_list: + path: /service-offerings + controller: App\Controller\ServiceOfferingController::index + methods: [GET] + +service_offering_rows: + path: /service-offerings/rowdata + controller: App\Controller\ServiceOfferingController::datatableRows + methods: [POST] + +service_offering_add_form: + path: /service-offerings/newform + controller: App\Controller\ServiceOfferingController::addForm + methods: [GET] + +service_offering_add_submit: + path: /service-offerings + controller: App\Controller\ServiceOfferingController::addSubmit + methods: [POST] + +service_offering_update_form: + path: /service-offerings/{id} + controller: App\Controller\ServiceOfferingController::updateForm + methods: [GET] + +service_offering_update_submit: + path: /service-offerings/{id} + controller: App\Controller\ServiceOfferingController::updateSubmit + methods: [POST] + +service_offering_delete: + path: /service-offerings/{id} + controller: App\Controller\ServiceOfferingController::deleteSubmit + methods: [DELETE] diff --git a/config/services.yaml b/config/services.yaml index a5d7d05f..b19ecabc 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -13,6 +13,8 @@ parameters: cvu_brand_id: "%env(CVU_BRAND_ID)%" country_code: "%env(COUNTRY_CODE)%" api_version: "%env(API_VERSION)%" + android_app_version: "%env(ANDROID_APP_VERSION)%" + ios_app_version: "%env(IOS_APP_VERSION)%" services: # default configuration for services in *this* file @@ -43,20 +45,6 @@ services: $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%' @@ -81,6 +69,11 @@ services: $redis_client: "@App\\Service\\RedisClientProvider" $key: "mqtt_events" + App\Service\MQTTClientApiv2: + arguments: + $redis_client: "@App\\Service\\RedisClientProvider" + $key: "mqtt_events" + App\Service\APNSClient: arguments: $redis_client: "@App\\Service\\RedisClientProvider" @@ -120,55 +113,14 @@ services: 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" + App\Service\InvoiceGeneratorInterface: "@App\\Service\\InvoiceManager" + + # invoice manager + App\Service\InvoiceManager: ~ # job order generator App\Service\JobOrderHandler\ResqJobOrderHandler: @@ -257,6 +209,30 @@ services: $sub_key: "%env(MOTIV_KEY)%" $token: "%env(MOTIV_TOKEN)%" + # insurance connector + App\Service\InsuranceConnector: + arguments: + $base_url: "%env(INSURANCE_BASE_URL)%" + $username: "%env(INSURANCE_USERNAME)%" + $password: "%env(INSURANCE_PASSWORD)%" + + # entity listener for gateway transactions + App\EntityListener\GatewayTransactionListener: + arguments: + $em: "@doctrine.orm.entity_manager" + $ic: "@App\\Service\\InsuranceConnector" + tags: + - name: doctrine.orm.entity_listener + event: 'postUpdate' + entity: 'App\Entity\GatewayTransaction' + + # paymongo connector + App\Service\PayMongoConnector: + arguments: + $base_url: "%env(PAYMONGO_BASE_URL)%" + $public_key: "%env(PAYMONGO_PUBLIC_KEY)%" + $secret_key: "%env(PAYMONGO_SECRET_KEY)%" + # entity listener for customer vehicle warranty code history App\EntityListener\CustomerVehicleSerialListener: arguments: @@ -328,3 +304,9 @@ services: App\Service\WarrantySerialLoadLogger: arguments: $em: "@doctrine.orm.entity_manager" + + # FCM sender + App\Service\FCMSender: + arguments: + $server_key: "%env(FCM_SERVER_KEY)%" + $sender_id: "%env(FCM_SENDER_ID)%" diff --git a/public/assets/images/image.gif b/public/assets/images/image.gif new file mode 100755 index 00000000..c32e7886 Binary files /dev/null and b/public/assets/images/image.gif differ diff --git a/public/assets/js/csv_export.js b/public/assets/js/csv_export.js new file mode 100644 index 00000000..6910ba54 --- /dev/null +++ b/public/assets/js/csv_export.js @@ -0,0 +1,74 @@ +$(function() { + // export table to csv + $(document).on('click', '[data-export-csv]', async function(e) { + const el = e.target.closest('[data-export-csv]'); + const oldLabel = el.innerHTML; + + // set loading status + el.disabled = true; + el.innerHTML = 'Exporting...'; + + const formData = new FormData(); + + formData.append('datatable[pagination][page]', 1); + formData.append('datatable[pagination][perpage]', 10000000); + + // get all rows + const response = await fetch(el.dataset.url, { + method: el.dataset.method, + body: formData, + }); + + const result = await response.json(); + + if (response.status === 200) { + // empty set returned + if (parseInt(result.meta.total) === 0) { + swal({ + title: 'Whoops', + html: 'No data to export!', + type: 'warning', + }); + } + + // build csv data + const csvRows = []; + const fieldList = el.dataset.fields.split(','); + csvRows.push(el.dataset.headers); + + result.data.forEach((row) => { + const fieldData = []; + fieldList.forEach((field) => { + fieldData.push('"' + row[field] + '"'); + }); + + csvRows.push(fieldData.join(',')); + }); + + const csvData = csvRows.join('\n'); + + // build the csv file + const csvFile = new Blob([csvData], { + type: 'text/csv', + }); + + // create a link to the file and download it + const url = window.URL.createObjectURL(csvFile); + const a = document.createElement('a'); + a.href = url; + a.download = el.dataset.filename + '.csv'; + a.click(); + } else { + // something went wrong on the server + swal({ + title: 'Whoops', + html: 'An error has occurred while retrieving data.', + type: 'error', + }); + } + + // remove loading status + el.disabled = false; + el.innerHTML = oldLabel; + }); +}); \ No newline at end of file diff --git a/public/battery/enduro_mobile.jpg b/public/battery/enduro_mobile.jpg old mode 100644 new mode 100755 index 06137302..2b738abc Binary files a/public/battery/enduro_mobile.jpg and b/public/battery/enduro_mobile.jpg differ diff --git a/public/battery/excel_mobile.jpg b/public/battery/excel_mobile.jpg old mode 100644 new mode 100755 index 89048661..31bf0da1 Binary files a/public/battery/excel_mobile.jpg and b/public/battery/excel_mobile.jpg differ diff --git a/public/battery/gold_mobile.jpg b/public/battery/gold_mobile.jpg old mode 100644 new mode 100755 index cdcb2e82..953012db Binary files a/public/battery/gold_mobile.jpg and b/public/battery/gold_mobile.jpg differ diff --git a/public/battery/premium_excel_mobile.jpg b/public/battery/premium_excel_mobile.jpg new file mode 100755 index 00000000..31bf0da1 Binary files /dev/null and b/public/battery/premium_excel_mobile.jpg differ diff --git a/src/Command/CreateRiderAPIUserCommand.php b/src/Command/CreateRiderAPIUserCommand.php index 96508fd8..1f60b668 100644 --- a/src/Command/CreateRiderAPIUserCommand.php +++ b/src/Command/CreateRiderAPIUserCommand.php @@ -10,8 +10,8 @@ use Symfony\Component\Console\Output\OutputInterface; use Doctrine\ORM\EntityManagerInterface; -use Catalyst\APIBundle\Entity\User as APIUser; -use Catalyst\APIBundle\Entity\Role as APIRole; +use App\Entity\ApiUser as APIUser; +use Catalyst\ApiBundle\Entity\Role as APIRole; use App\Entity\Rider; diff --git a/src/Command/SetJobOrderCustNewCommand.php b/src/Command/SetJobOrderCustNewCommand.php new file mode 100644 index 00000000..8eba6b6b --- /dev/null +++ b/src/Command/SetJobOrderCustNewCommand.php @@ -0,0 +1,80 @@ +em = $em; + $this->jo_manager = $jo_manager; + + parent::__construct(); + } + + protected function configure() + { + $this->setName('joborder:setcustomernew') + ->setDescription('Set job order\'s customer new flag for existing job orders.') + ->setHelp('Set job order\'s customer new flag for existing job orders'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $em = $this->em; + + // pdo connection + $db = $em->getConnection(); + + // get all the ids for all job orders + $all_query_sql = 'SELECT id AS jo_id, customer_id AS cust_id FROM job_order ORDER BY id'; + + $all_query_stmt = $db->prepare($all_query_sql); + $all_query_stmt->execute(); + + $all_jo_results = $all_query_stmt->fetchAll(); + + $output->writeln('Processing job orders...'); + + foreach ($all_jo_results as $jo_row) + { + // for each jo id, get the customer id + $jo_id = $jo_row['jo_id']; + $cust_id = $jo_row['cust_id']; + + // check how many JOs have that customer id + $jo_count = $this->jo_manager->getCustomerJobOrderCount($cust_id); + + // if one or less, set flag_cust_new to true + if ($jo_count <= 1) + $this->updateCustNew($db, $jo_id); + } + + $output->writeln('All done!'); + + return 0; + } + + protected function updateCustNew($db, $jo_id) + { + $update_jo_sql = 'UPDATE job_order SET flag_cust_new = :flag_cust_new WHERE id = :jo_id'; + + $update_jo_stmt = $db->prepare($update_jo_sql); + $update_jo_stmt->execute([ + 'flag_cust_new' => true, + 'jo_id' => $jo_id + ]); + } +} diff --git a/src/Command/TestInvoiceManagerCommand.php b/src/Command/TestInvoiceManagerCommand.php new file mode 100644 index 00000000..6f223edb --- /dev/null +++ b/src/Command/TestInvoiceManagerCommand.php @@ -0,0 +1,2106 @@ +setName('test:generateinvoice') + ->setDescription('Test invoice manager service.') + ->setHelp('Test invoice manager service.'); + } + + public function __construct(InvoiceGeneratorInterface $inv_manager, EntityManagerInterface $em) + { + $this->em = $em; + $this->inv_manager = $inv_manager; + + parent::__construct(); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + // battery sales with tax + $this->testBatterySalesNoTradeInNoDiscountWithTax(); + $this->testBatterySalesQuantityNoTradeInNoDiscountWithTax(); + + + // battery sales without tax + $this->testBatterySalesNoTradeInNoDiscountWithoutTax(); + $this->testBatterySalesQuantityNoTradeInNoDiscountWithoutTax(); + + // battery sales with trade in with tax + $this->testBatterySalesTradeInSameBatteryPremiumNoDiscountWithTax(); + + $this->testBatterySalesTradeInSameBatteryMotoliteNoDiscountWithTax(); + $this->testBatterySalesTradeInSameBatteryOtherNoDiscountWithTax(); + $this->testBatterySalesTradeInDifferentBatteryPremiumNoDiscountWithTax(); + $this->testBatterySalesTradeInDifferentBatteryMotoliteNoDiscountWithTax(); + $this->testBatterySalesTradeInDifferentBatteryOtherNoDiscountWithTax(); + $this->testBatterySalesTradeInQuantityNoDiscountWithTax(); + + // battery sales with trade in without tax + $this->testBatterySalesTradeInSameBatteryPremiumNoDiscountWithoutTax(); + $this->testBatterySalesTradeInSameBatteryMotoliteNoDiscountWithoutTax(); + $this->testBatterySalesTradeInSameBatteryOtherNoDiscountWithoutTax(); + $this->testBatterySalesTradeInDifferentBatteryPremiumNoDiscountWithoutTax(); + $this->testBatterySalesTradeInDifferentBatteryMotoliteNoDiscountWithoutTax(); + $this->testBatterySalesTradeInDifferentBatteryOtherNoDiscountWithoutTax(); + $this->testBatterySalesTradeInQuantityNoDiscountWithoutTax(); + + // battery sales with discount with tax + $this->testBatterySalesNoTradeInWithDiscountWithTax(); + + // battery sales with discount without tax + $this->testBatterySalesNoTradeInWithDiscountWithoutTax(); + + // battery sales with discount and trade in with tax + $this->testBatterySalesTradeInSameBatteryWithDiscountWithTax(); + $this->testBatterySalesTradeInDifferentBatteryWithDiscountWithTax(); + + // battery sales with discount and trade in without tax + $this->testBatterySalesTradeInSameBatteryWithDiscountWithoutTax(); + $this->testBatterySalesTradeInDifferentBatteryWithDiscountWithoutTax(); + + // battery replacement warranty with tax + $this->testBatteryReplacementWarrantyWithTax(); + + // battery replacement warranty without tax + $this->testBatteryReplacementWarrantyWithoutTax(); + + // fuel with tax + $this->testFuelGasWithServiceFeeWithTax(); + $this->testFuelDieselWithServiceFeeWithTax(); + $this->testFuelGasWithNoServiceFeeWithTax(); + $this->testFuelDieselWithNoServiceFeeWithTax(); + + // fuel without tax + $this->testFuelGasWithServiceFeeWithoutTax(); + $this->testFuelDieselWithServiceFeeWithoutTax(); + $this->testFuelGasWithNoServiceFeeWithoutTax(); + $this->testFuelDieselWithNoServiceFeeWithoutTax(); + + // jumpstart with tax + $this->testJumpstartWithTax(); + + // jumpstart without tax + $this->testJumpstartWithoutTax(); + + // jumpstart warranty with tax + $this->testJumpstartWarrantyWithTax(); + + // jumpstart warranty without tax + $this->testJumpstartWarrantyWithoutTax(); + + // overheat with tax + $this->testOverheatAssistanceWithCoolantWithTax(); + $this->testOverheatAssistanceWithoutCoolantWithTax(); + + // overheat without tax + $this->testOverheatAssistanceWithCoolantWithoutTax(); + $this->testOverheatAssistanceWithoutCoolantWithoutTax(); + + // post-recharged with tax + $this->testPostRechargedWithTax(); + + // post-recharged without tax + $this->testPostRechargedWithoutTax(); + + // post replacement with tax + $this->testPostReplacementWithTax(); + + // post replacement without tax + $this->testPostReplacementWithoutTax(); + + // tire repair with tax + $this->testTireRepairWithServiceFeeWithTax(); + $this->testTireRepairWithoutServiceFeeWithTax(); + + // tire repair without tax + $this->testTireRepairWithServiceFeeWithoutTax(); + $this->testTireRepairWithoutServiceFeeWithoutTax(); + + // test creation of invoice criteria when JO is submitted + $this->testGenerateInvoiceCriteria(); + $this->testGenerateInvoiceCriteriaInvalidPromo(); + $this->testGenerateInvoiceCriteriaInvalidBattery(); + + // test generateDraftInvoice call from ajax call + $this->testGenerateDraftInvoiceNoErrorWithTradeIn(); + $this->testGenerateDraftInvoiceNoErrorNoTradeIn(); + $this->testGenerateDraftInvoiceInvalidBattery(); + $this->testGenerateDraftInvoiceInvalidPromo(); + $this->testGenerateDraftInvoiceNonBatterySales(); + $this->testGenerateDraftInvoiceBatterySalesNoPromo(); + + // test generateInvoiceInvoice call from ajax call + $this->testGenerateInvoice(); + + return 0; + } + + // 1battery sales with tax + protected function testBatterySalesNoTradeInNoDiscountWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, no trade-in, no discount, with tax + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + $criteria->addEntry($battery, null, 1); + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + error_log(print_r(json_encode($invoice_data), true)); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' NO TRADE IN NO DISCOUNT WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testBatterySalesQuantityNoTradeInNoDiscountWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, more than 1, no trade-in, no discount, with tax + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + $criteria->addEntry($battery, null, 2); + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + // error_log(print_r(json_encode($invoice_data), true)); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' QUANTITY NO TRADE IN NO DISCOUNT WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // battery sales without tax + protected function testBatterySalesNoTradeInNoDiscountWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, no trade-in, no discount, no tax + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + $criteria->addEntry($battery, null, 1); + + $invoice_data = $this->inv_manager->compute($criteria); + + // error_log(print_r(json_encode($invoice_data), true)); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' NO TRADE IN NO DISCOUNT WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testBatterySalesQuantityNoTradeInNoDiscountWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, more than 1, no trade-in, no discount, without tax + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + $criteria->addEntry($battery, null, 2); + + $invoice_data = $this->inv_manager->compute($criteria); + + // error_log(print_r(json_encode($invoice_data), true)); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' QUANTITY NO TRADE IN NO DISCOUNT WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // battery sales with trade in with tax + protected function testBatterySalesTradeInSameBatteryPremiumNoDiscountWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, trade-in same battery, premium, no discount + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + // add battery + $criteria->addEntry($battery, null, 1); + + // add battery for trade in + $criteria->addEntry($battery, 'premium', 1); + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + error_log(print_r(json_encode($invoice_data), true)); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' PREMIUM TRADE IN SAME BATTERY NO DISCOUNT WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testBatterySalesTradeInSameBatteryMotoliteNoDiscountWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, trade-in same battery, motolite, no discount + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + // add battery + $criteria->addEntry($battery, null, 1); + + // add battery for trade in + $criteria->addEntry($battery, 'motolite', 1); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' MOTOLITE TRADE IN SAME BATTERY NO DISCOUNT WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testBatterySalesTradeInSameBatteryOtherNoDiscountWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, trade-in same battery, other, no discount + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + // add battery + $criteria->addEntry($battery, null, 1); + + // add battery for trade in + $criteria->addEntry($battery, 'other', 1); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' OTHER TRADE IN SAME BATTERY NO DISCOUNT WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testBatterySalesTradeInDifferentBatteryPremiumNoDiscountWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, trade-in different battery, premium, no discount + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + // add battery + $criteria->addEntry($battery, null, 1); + + $trade_battery_id = 1037; + $trade_battery = $this->em->getRepository(Battery::class)->find($trade_battery_id); + + // add battery for trade in + $criteria->addEntry($trade_battery, 'premium', 1); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' PREMIUM TRADE IN DIFFERENT BATTERY NO DISCOUNT WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testBatterySalesTradeInDifferentBatteryMotoliteNoDiscountWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, trade-in different battery, motolite, no discount + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + // add battery + $criteria->addEntry($battery, null, 1); + + $trade_battery_id = 1037; + $trade_battery = $this->em->getRepository(Battery::class)->find($trade_battery_id); + + // add battery for trade in + $criteria->addEntry($trade_battery, 'motolite', 1); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' MOTOLITE TRADE IN DIFFERENT BATTERY NO DISCOUNT WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testBatterySalesTradeInDifferentBatteryOtherNoDiscountWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, trade-in different battery, other, no discount + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + // add battery + $criteria->addEntry($battery, null, 1); + + $trade_battery_id = 1037; + $trade_battery = $this->em->getRepository(Battery::class)->find($trade_battery_id); + + // add battery for trade in + $criteria->addEntry($trade_battery, 'other', 1); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' OTHER TRADE IN DIFFERENT BATTERY NO DISCOUNT WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testBatterySalesTradeInQuantityNoDiscountWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, trade-in different battery, premium, no discount + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + // add battery + $criteria->addEntry($battery, null, 1); + + $trade_battery_id = 1037; + $trade_battery = $this->em->getRepository(Battery::class)->find($trade_battery_id); + + // add battery for trade in + $criteria->addEntry($trade_battery, 'other', 3); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' QUANTITY TRADE IN NO DISCOUNT WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // start: battery sales with trade in without tax + protected function testBatterySalesTradeInSameBatteryPremiumNoDiscountWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, trade-in same battery, premium, no discount + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + // add battery + $criteria->addEntry($battery, null, 1); + + // add battery for trade in + $criteria->addEntry($battery, 'premium', 1); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' PREMIUM TRADE IN SAME BATTERY NO DISCOUNT WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testBatterySalesTradeInSameBatteryMotoliteNoDiscountWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, trade-in same battery, motolite, no discount + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + // add battery + $criteria->addEntry($battery, null, 1); + + // add battery for trade in + $criteria->addEntry($battery, 'motolite', 1); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' MOTOLITE TRADE IN SAME BATTERY NO DISCOUNT WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testBatterySalesTradeInSameBatteryOtherNoDiscountWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, trade-in same battery, other, no discount + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + // add battery + $criteria->addEntry($battery, null, 1); + + // add battery for trade in + $criteria->addEntry($battery, 'other', 1); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' OTHER TRADE IN SAME BATTERY NO DISCOUNT WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testBatterySalesTradeInDifferentBatteryPremiumNoDiscountWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, trade-in different battery, premium, no discount + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + // add battery + $criteria->addEntry($battery, null, 1); + + $trade_battery_id = 1037; + $trade_battery = $this->em->getRepository(Battery::class)->find($trade_battery_id); + + // add battery for trade in + $criteria->addEntry($trade_battery, 'premium', 1); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' PREMIUM TRADE IN DIFFERENT BATTERY NO DISCOUNT WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testBatterySalesTradeInDifferentBatteryMotoliteNoDiscountWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, trade-in different battery, motolite, no discount + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + // add battery + $criteria->addEntry($battery, null, 1); + + $trade_battery_id = 1037; + $trade_battery = $this->em->getRepository(Battery::class)->find($trade_battery_id); + + // add battery for trade in + $criteria->addEntry($trade_battery, 'motolite', 1); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' MOTOLITE TRADE IN DIFFERENT BATTERY NO DISCOUNT WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testBatterySalesTradeInDifferentBatteryOtherNoDiscountWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, trade-in different battery, other, no discount + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + // add battery + $criteria->addEntry($battery, null, 1); + + $trade_battery_id = 1037; + $trade_battery = $this->em->getRepository(Battery::class)->find($trade_battery_id); + + // add battery for trade in + $criteria->addEntry($trade_battery, 'other', 1); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' OTHER TRADE IN DIFFERENT BATTERY NO DISCOUNT WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testBatterySalesTradeInQuantityNoDiscountWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, trade-in different battery, premium, no discount + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + // add battery + $criteria->addEntry($battery, null, 1); + + $trade_battery_id = 1037; + $trade_battery = $this->em->getRepository(Battery::class)->find($trade_battery_id); + + // add battery for trade in + $criteria->addEntry($trade_battery, 'other', 3); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' QUANTITY TRADE IN NO DISCOUNT WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // battery sales with discount with tax + protected function testBatterySalesNoTradeInWithDiscountWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, no trade-in, with discount, with tax + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + $promo_id = 1; + $promo = $this->em->getRepository(Promo::class)->find($promo_id); + + $criteria->addEntry($battery, null, 1); + + $criteria->addPromo($promo); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + // error_log(print_r(json_encode($invoice_data), true)); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + $promo = $data['promo']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' NO TRADE IN WITH DISCOUNT WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + if ($promo != null) + error_log('PROMO ' . $promo->getName()); + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // battery sales with discount without tax + protected function testBatterySalesNoTradeInWithDiscountWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, no trade-in, with discount, without tax + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + $promo_id = 10; + $promo = $this->em->getRepository(Promo::class)->find($promo_id); + + $criteria->addEntry($battery, null, 1); + + $criteria->addPromo($promo); + + $invoice_data = $this->inv_manager->compute($criteria); + + // error_log(print_r(json_encode($invoice_data), true)); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + $promo = $data['promo']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' NO TRADE IN WITH DISCOUNT WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + if ($promo != null) + error_log('PROMO ' . $promo->getName()); + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // battery sales with discount and trade in with tax + protected function testBatterySalesTradeInSameBatteryWithDiscountWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, no trade-in, with discount, with tax + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + $promo_id = 1; + $promo = $this->em->getRepository(Promo::class)->find($promo_id); + + $criteria->addEntry($battery, null, 1); + + // add battery for trade in + $criteria->addEntry($battery, 'premium', 1); + + $criteria->addPromo($promo); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + // error_log(print_r(json_encode($invoice_data), true)); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + $promo = $data['promo']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' TRADE IN SAME BATTERY WITH DISCOUNT WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + if ($promo != null) + error_log('PROMO ' . $promo->getName()); + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testBatterySalesTradeInDifferentBatteryWithDiscountWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, trade-in different battery, premium, with discount, with tax + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + $promo_id = 1; + $promo = $this->em->getRepository(Promo::class)->find($promo_id); + + // add battery + $criteria->addEntry($battery, null, 1); + + $trade_battery_id = 1037; + $trade_battery = $this->em->getRepository(Battery::class)->find($trade_battery_id); + + // add battery for trade in + $criteria->addEntry($trade_battery, 'premium', 1); + + $criteria->addPromo($promo); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' TRADE IN DIFFERENT BATTERY WITH DISCOUNT WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // battery sales with discount and trade in without tax + protected function testBatterySalesTradeInSameBatteryWithDiscountWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, no trade-in, with discount, with tax + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + $promo_id = 1; + $promo = $this->em->getRepository(Promo::class)->find($promo_id); + + $criteria->addEntry($battery, null, 1); + + // add battery for trade in + $criteria->addEntry($battery, 'premium', 1); + + $criteria->addPromo($promo); + + $invoice_data = $this->inv_manager->compute($criteria); + + // error_log(print_r(json_encode($invoice_data), true)); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + $promo = $data['promo']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' TRADE IN SAME BATTERY WITH DISCOUNT WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + if ($promo != null) + error_log('PROMO ' . $promo->getName()); + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testBatterySalesTradeInDifferentBatteryWithDiscountWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, trade-in different battery, premium, with discount, without tax + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + $promo_id = 1; + $promo = $this->em->getRepository(Promo::class)->find($promo_id); + + // add battery + $criteria->addEntry($battery, null, 1); + + $trade_battery_id = 1037; + $trade_battery = $this->em->getRepository(Battery::class)->find($trade_battery_id); + + // add battery for trade in + $criteria->addEntry($trade_battery, 'premium', 1); + + $criteria->addPromo($promo); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_NEW) . ' TRADE IN DIFFERENT BATTERY WITH DISCOUNT WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // battery replacement warranty with tax + protected function testBatteryReplacementWarrantyWithTax() + { + // TEST SCENARIO: battery replacement warranty with tax + $criteria = new InvoiceCriteria(); + + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_WARRANTY); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + $criteria->addEntry($battery, null, 1); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_WARRANTY) . ' WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // battery replacement warranty without tax + protected function testBatteryReplacementWarrantyWithoutTax() + { + // TEST SCENARIO: battery replacement warranty without tax + $criteria = new InvoiceCriteria(); + + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_WARRANTY); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + $criteria->addEntry($battery, null, 1); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::BATTERY_REPLACEMENT_WARRANTY) . ' WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // fuel with tax + protected function testFuelGasWithServiceFeeWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: fuel, gas, service fee, with tax + $criteria->setServiceType(ServiceType::EMERGENCY_REFUEL); + + // set customer vehicle + $cv_id = 1306614; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $criteria->setCustomerVehicle($cv); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::EMERGENCY_REFUEL) . ' GAS WITH SERVICE FEE WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testFuelDieselWithServiceFeeWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: fuel, diesel, service fee, with tax + $criteria->setServiceType(ServiceType::EMERGENCY_REFUEL); + + // set customer vehicle + $cv_id = 1306612; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $criteria->setCustomerVehicle($cv); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::EMERGENCY_REFUEL) . ' DIESEL WITH SERVICE FEE WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testFuelGasWithNoServiceFeeWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: fuel, gas, no service fee, with tax + $criteria->setServiceType(ServiceType::EMERGENCY_REFUEL); + + // set customer vehicle + $cv_id = 1306604; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $criteria->setCustomerVehicle($cv); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::EMERGENCY_REFUEL) . ' GAS WITH NO SERVICE FEE WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testFuelDieselWithNoServiceFeeWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: fuel, diesel, no service fee, with tax + $criteria->setServiceType(ServiceType::EMERGENCY_REFUEL); + + // set customer vehicle + $cv_id = 1306581; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $criteria->setCustomerVehicle($cv); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::EMERGENCY_REFUEL) . ' DIESEL WITH NO SERVICE FEE WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // fuel without tax + protected function testFuelGasWithServiceFeeWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: fuel, gas, service fee, without tax + $criteria->setServiceType(ServiceType::EMERGENCY_REFUEL); + + // set customer vehicle + $cv_id = 1306614; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $criteria->setCustomerVehicle($cv); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::EMERGENCY_REFUEL) . ' GAS WITH SERVICE FEE WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testFuelDieselWithServiceFeeWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: fuel, diesel, service fee, without tax + $criteria->setServiceType(ServiceType::EMERGENCY_REFUEL); + + // set customer vehicle + $cv_id = 1306612; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $criteria->setCustomerVehicle($cv); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::EMERGENCY_REFUEL) . ' DIESEL WITH SERVICE FEE WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testFuelGasWithNoServiceFeeWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: fuel, gas, no service fee, without tax + $criteria->setServiceType(ServiceType::EMERGENCY_REFUEL); + + // set customer vehicle + $cv_id = 1306604; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $criteria->setCustomerVehicle($cv); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::EMERGENCY_REFUEL) . ' GAS WITH NO SERVICE FEE WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testFuelDieselWithNoServiceFeeWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: fuel, diesel, no service fee, without tax + $criteria->setServiceType(ServiceType::EMERGENCY_REFUEL); + + // set customer vehicle + $cv_id = 1306581; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $criteria->setCustomerVehicle($cv); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::EMERGENCY_REFUEL) . ' DIESEL WITH NO SERVICE FEE WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // jumpstart with tax + protected function testJumpstartWithTax() + { + // TEST SCENARIO: jumpstart with tax + $criteria = new InvoiceCriteria(); + + $criteria->setServiceType(ServiceType::JUMPSTART_TROUBLESHOOT); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::JUMPSTART_TROUBLESHOOT) . ' WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // jumpstart without tax + protected function testJumpstartWithoutTax() + { + // TEST SCENARIO: jumpstart without tax + $criteria = new InvoiceCriteria(); + + $criteria->setServiceType(ServiceType::JUMPSTART_TROUBLESHOOT); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::JUMPSTART_TROUBLESHOOT) . ' WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // jumpstart warranty with tax + protected function testJumpstartWarrantyWithTax() + { + // TEST SCENARIO: jumpstart warranty with tax + $criteria = new InvoiceCriteria(); + + $criteria->setServiceType(ServiceType::JUMPSTART_WARRANTY); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::JUMPSTART_WARRANTY) . ' WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // jumpstart warranty without tax + protected function testJumpstartWarrantyWithoutTax() + { + // TEST SCENARIO: jumpstart warranty without tax + $criteria = new InvoiceCriteria(); + + $criteria->setServiceType(ServiceType::JUMPSTART_WARRANTY); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::JUMPSTART_WARRANTY) . ' WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // overheat assistance with tax + protected function testOverheatAssistanceWithCoolantWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: overheat assistance with coolant with tax + $criteria->setServiceType(ServiceType::OVERHEAT_ASSISTANCE); + + // set customer vehicle + $cv_id = 1306614; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $criteria->setCustomerVehicle($cv); + $criteria->setHasCoolant(); + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::OVERHEAT_ASSISTANCE) . ' WITH COOLANT WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testOverheatAssistanceWithoutCoolantWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: overheat assistance without coolant with tax + $criteria->setServiceType(ServiceType::OVERHEAT_ASSISTANCE); + + // set customer vehicle + $cv_id = 1306614; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $criteria->setCustomerVehicle($cv); + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::OVERHEAT_ASSISTANCE) . ' WITHOUT COOLANT WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // overheat assistance without tax + protected function testOverheatAssistanceWithCoolantWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: overheat assistance with coolant without tax + $criteria->setServiceType(ServiceType::OVERHEAT_ASSISTANCE); + + // set customer vehicle + $cv_id = 1306614; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $criteria->setCustomerVehicle($cv); + $criteria->setHasCoolant(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::OVERHEAT_ASSISTANCE) . ' WITH COOLANT WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testOverheatAssistanceWithoutCoolantWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: overheat assistance without coolant without tax + $criteria->setServiceType(ServiceType::OVERHEAT_ASSISTANCE); + + // set customer vehicle + $cv_id = 1306614; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $criteria->setCustomerVehicle($cv); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::OVERHEAT_ASSISTANCE) . ' WITHOUT COOLANT WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // post recharged with tax + protected function testPostRechargedWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: post recharged with tax + $criteria->setServiceType(ServiceType::POST_RECHARGED); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::POST_RECHARGED) . ' WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // post recharged without tax + protected function testPostRechargedWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: post recharged with tax + $criteria->setServiceType(ServiceType::POST_RECHARGED); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::POST_RECHARGED) . ' WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // post replacement with tax + protected function testPostReplacementWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: post replacement with tax + $criteria->setServiceType(ServiceType::POST_REPLACEMENT); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::POST_REPLACEMENT) . ' WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // post replacement without tax + protected function testPostReplacementWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: post replacement without tax + $criteria->setServiceType(ServiceType::POST_REPLACEMENT); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::POST_REPLACEMENT) . ' WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // tire repair with tax + protected function testTireRepairWithServiceFeeWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: tire repair with service fee with tax + $criteria->setServiceType(ServiceType::TIRE_REPAIR); + + // set customer vehicle + $cv_id = 1306612; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $criteria->setCustomerVehicle($cv); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::TIRE_REPAIR) . ' WITH SERVICE FEE WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testTireRepairWithoutServiceFeeWithTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: tire repair no service fee with tax + $criteria->setServiceType(ServiceType::TIRE_REPAIR); + + // set customer vehicle + $cv_id = 1306604; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $criteria->setCustomerVehicle($cv); + + $criteria->setIsTaxable(); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::TIRE_REPAIR) . ' WITHOUT SERVICE FEE WITH TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // tire repair without tax + protected function testTireRepairWithServiceFeeWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: tire repair with service fee without tax + $criteria->setServiceType(ServiceType::TIRE_REPAIR); + + // set customer vehicle + $cv_id = 1306612; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $criteria->setCustomerVehicle($cv); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::TIRE_REPAIR) . ' WITH SERVICE FEE WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + protected function testTireRepairWithoutServiceFeeWithoutTax() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: tire repair no service fee without tax + $criteria->setServiceType(ServiceType::TIRE_REPAIR); + + // set customer vehicle + $cv_id = 1306604; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $criteria->setCustomerVehicle($cv); + + $invoice_data = $this->inv_manager->compute($criteria); + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + foreach ($invoice_items as $invoice_item) + { + error_log('TEST: ' . strtoupper(ServiceType::TIRE_REPAIR) . ' WITHOUT SERVICE FEE WITHOUT TAX ' . $invoice_item['title'] . ' ' . $invoice_item['quantity'] . ' ' . $invoice_item['price']); + } + + error_log('TOTAL ' . print_r(json_encode($total), true)); + } + } + + // test creation of invoice criteria when JO is submitted + protected function testGenerateInvoiceCriteria() + { + // create JO, set service type and customer vehicle + $jo = new JobOrder(); + + $jo->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + // set customer vehicle + $cv_id = 1306604; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $jo->setCustomerVehicle($cv); + + // create error_array + $error_array = []; + + // create array of invoice items + $invoice_items = [[ + 'battery' => 1038, + 'quantity' => 1, + 'trade_in' => '', + ], [ + 'battery' => 1038, + 'quantity' => 1, + 'trade_in' => 'premium' + ]]; + + // error_log(print_r(json_encode($invoice_items), true)); + // promo id + $promo_id = 10; + + $this->inv_manager->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $error_array); + + if (!empty($error_array)) + { + error_log('TEST GENERATE INVOICE CRITERIA: Errors found when generating invoice ' . print_r(json_encode($error_array), true)); + } + } + + protected function testGenerateInvoiceCriteriaInvalidPromo() + { + // create JO, set service type and customer vehicle + $jo = new JobOrder(); + + $jo->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + // set customer vehicle + $cv_id = 1306604; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $jo->setCustomerVehicle($cv); + + // create error_array + $error_array = []; + + // create array of invoice items + $invoice_items = [[ + 'battery' => 1038, + 'quantity' => 1, + 'trade_in' => '', + ], [ + 'battery' => 1038, + 'quantity' => 1, + 'trade_in' => 'premium' + ]]; + + // error_log(print_r(json_encode($invoice_items), true)); + // promo id + $promo_id = 99; + + $this->inv_manager->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $error_array); + + if (!empty($error_array)) + { + error_log('TEST GENERATE INVOICE CRITERIA INVALID PROMO: Errors found when generating invoice ' . print_r(json_encode($error_array), true)); + } + } + + protected function testGenerateInvoiceCriteriaInvalidBattery() + { + // create JO, set service type and customer vehicle + $jo = new JobOrder(); + + $jo->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + // set customer vehicle + $cv_id = 1306604; + $cv = $this->em->getRepository(CustomerVehicle::class)->find($cv_id); + + $jo->setCustomerVehicle($cv); + + // create error_array + $error_array = []; + + // create array of invoice items + $invoice_items = [[ + 'battery' => 1, + 'quantity' => 1, + 'trade_in' => '', + ], [ + 'battery' => 1038, + 'quantity' => 1, + 'trade_in' => 'premium' + ]]; + + // error_log(print_r(json_encode($invoice_items), true)); + // promo id + $promo_id = 1; + + $this->inv_manager->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $error_array); + + if (!empty($error_array)) + { + error_log('TEST GENERATE INVOICE CRITERIA INVALID BATTERY: Errors found when generating invoice ' . print_r(json_encode($error_array), true)); + } + } + + // test generateDraftInvoice call from ajax call + protected function testGenerateDraftInvoiceNoErrorWithTradeIn() + { + $criteria = new InvoiceCriteria(); + + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + $criteria->setIsTaxable(); + + $promo_id = 1; + $service_charges = []; + + // create array of invoice items + $items = [[ + 'battery' => 1038, + 'quantity' => 1, + 'trade_in' => '', + ], [ + 'battery' => 1038, + 'quantity' => 1, + 'trade_in' => 'premium' + ]]; + + $error = $this->inv_manager->generateDraftInvoice($criteria, $promo_id, $service_charges, $items); + + if ($error) + error_log('TEST GENERATE DRAFT INVOICE NO ERROR WITH TRADE IN: Errors found when generating draft invoice ' . print_r($error, true)); + else + error_log('TEST GENERATE DRAFT INVOICE NO ERROR WITH TRADE IN: No errors here'); + } + + protected function testGenerateDraftInvoiceNoErrorNoTradeIn() + { + $criteria = new InvoiceCriteria(); + + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + $criteria->setIsTaxable(); + + $promo_id = 1; + $service_charges = []; + + // create array of invoice items + $items = [[ + 'battery' => 1038, + 'quantity' => 1, + 'trade_in' => '', + ]]; + + $error = $this->inv_manager->generateDraftInvoice($criteria, $promo_id, $service_charges, $items); + + if ($error) + error_log('TEST GENERATE DRAFT INVOICE NO ERROR NO TRADE IN: Errors found when generating draft invoice ' . print_r($error, true)); + else + error_log('TEST GENERATE DRAFT INVOICE NO ERROR NO TRADE IN: No errors here'); + } + + protected function testGenerateDraftInvoiceInvalidBattery() + { + $criteria = new InvoiceCriteria(); + + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + $criteria->setIsTaxable(); + + $promo_id = 1; + $service_charges = []; + + // create array of invoice items + $items = [[ + 'battery' => 1038, + 'quantity' => 1, + 'trade_in' => '', + ], [ + 'battery' => 2, + 'quantity' => 1, + 'trade_in' => 'premium' + ]]; + + $error = $this->inv_manager->generateDraftInvoice($criteria, $promo_id, $service_charges, $items); + + if ($error) + error_log('TEST GENERATE DRAFT INVOICE BATTERY ERROR: Errors found when generating draft invoice: ' . print_r($error, true)); + else + error_log('TEST GENERATE DRAFT INVOICE BATTERY ERROR: No errors here'); + } + + protected function testGenerateDraftInvoiceInvalidPromo() + { + $criteria = new InvoiceCriteria(); + + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + $criteria->setIsTaxable(); + + $promo_id = 99; + $service_charges = []; + + // create array of invoice items + $items = [[ + 'battery' => 1038, + 'quantity' => 1, + 'trade_in' => '', + ]]; + + $error = $this->inv_manager->generateDraftInvoice($criteria, $promo_id, $service_charges, $items); + + if ($error) + error_log('TEST GENERATE DRAFT INVOICE PROMO ERROR: Errors found when generating draft invoice ' . print_r($error, true)); + else + error_log('TEST GENERATE DRAFT INVOICE PROMO ERROR: No errors here'); + } + + protected function testGenerateDraftInvoiceNonBatterySales() + { + $criteria = new InvoiceCriteria(); + + $criteria->setServiceType(ServiceType::JUMPSTART_TROUBLESHOOT); + $criteria->setIsTaxable(); + + $promo_id = 1; + $service_charges = []; + + // create array of invoice items + $items = [[ + 'battery' => 1038, + 'quantity' => 1, + 'trade_in' => '', + ]]; + + $error = $this->inv_manager->generateDraftInvoice($criteria, $promo_id, $service_charges, $items); + + if ($error) + error_log('TEST GENERATE DRAFT NON BATTERY SALES: Errors found when generating draft invoice ' . print_r($error, true)); + else + error_log('TEST GENERATE DRAFT NON BATTERY SALES: No errors here'); + } + + protected function testGenerateDraftInvoiceBatterySalesNoPromo() + { + $criteria = new InvoiceCriteria(); + + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + $criteria->setIsTaxable(); + + $promo_id = ''; + $service_charges = []; + + // create array of invoice items + $items = [[ + 'battery' => 1038, + 'quantity' => 1, + 'trade_in' => '', + ]]; + + $error = $this->inv_manager->generateDraftInvoice($criteria, $promo_id, $service_charges, $items); + + if ($error) + error_log('TEST GENERATE DRAFT INVOICE BATTERY SALES NO PROMO: Errors found when generating draft invoice ' . print_r($error, true)); + else + error_log('TEST GENERATE DRAFT INVOICE BATTERY SALES NO PROMO: No errors here'); + } + + + // test generateInvoiceInvoice call from ajax call + protected function testGenerateInvoice() + { + $criteria = new InvoiceCriteria(); + + // TEST SCENARIO: new battery, trade-in different battery, premium, with discount, with tax + $criteria->setServiceType(ServiceType::BATTERY_REPLACEMENT_NEW); + + $battery_id = 1038; + $battery = $this->em->getRepository(Battery::class)->find($battery_id); + + $promo_id = 1; + $promo = $this->em->getRepository(Promo::class)->find($promo_id); + + // add battery + $criteria->addEntry($battery, null, 1); + + $trade_battery_id = 1037; + $trade_battery = $this->em->getRepository(Battery::class)->find($trade_battery_id); + + // add battery for trade in + $criteria->addEntry($trade_battery, 'premium', 1); + + $criteria->addPromo($promo); + + $criteria->setIsTaxable(); + + $invoice = $this->inv_manager->generateInvoice($criteria); + + if ($invoice != null) + { + error_log('TEST GENERATEINVOICE:'); + error_log('INVOICE'); + error_log('TOTAL PRICE: ' . $invoice->getTotalPrice()); + error_log('VAT EXCLUSIVE PRICE: ' . $invoice->getVATExclusivePrice()); + error_log('VAT: ' . $invoice->getVAT()); + error_log('DISCOUNT: ' . $invoice->getDiscount()); + error_log('TRADE-IN: ' . $invoice->getTradeIn()); + error_log('STATUS: ' . $invoice->getStatus()); + + $invoice_items = $invoice->getItems(); + + foreach ($invoice_items as $invoice_item) + { + error_log($invoice_item->getTitle() . ' ' . $invoice_item->getQuantity() . ' ' . $invoice_item->getPrice()); + } + } + else + { + error_log('TEST GENERATE INVOICE: Error found. '); + } + } + +} diff --git a/src/Command/UpdateUnacceptedJobOrdersCommand.php b/src/Command/UpdateUnacceptedJobOrdersCommand.php index 2a9ba2c6..b7bfeb0c 100644 --- a/src/Command/UpdateUnacceptedJobOrdersCommand.php +++ b/src/Command/UpdateUnacceptedJobOrdersCommand.php @@ -10,6 +10,8 @@ use Symfony\Component\Console\Output\OutputInterface; use Doctrine\ORM\EntityManagerInterface; use App\Service\MQTTClient; +use App\Service\MQTTClientApiv2; +use App\Service\FCMSender; use App\Entity\JobOrder; use App\Entity\Rider; @@ -21,11 +23,19 @@ class UpdateUnacceptedJobOrdersCommand extends Command protected $em; protected $mclient; - public function __construct(EntityManagerInterface $em, MQTTClient $mclient) + // NOTE: for resq2 app + protected $mclientv2; + protected $fcmclient; + + public function __construct(EntityManagerInterface $em, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient) { $this->em = $em; $this->mclient = $mclient; + // NOTE: for resq2 app + $this->mclientv2 = $mclientv2; + $this->fcmclient = $fcmclient; + parent::__construct(); } @@ -41,6 +51,10 @@ class UpdateUnacceptedJobOrdersCommand extends Command $em = $this->em; $mclient = $this->mclient; + // NOTE: for resq2 app + $mclientv2 = $this->mclientv2; + $fcmclient = $this->fcmclient; + // TODO: get the timeout limit from .env $timeout = 3; $current_status = 'assigned'; @@ -107,6 +121,10 @@ class UpdateUnacceptedJobOrdersCommand extends Command 'jo_id' => $id, ]; $mclient->sendEvent($jo, $payload); + + // NOTE: for resq2 app + $mclientv2->sendEvent($jo, $payload); + $fcmclient->sendJoEvent($jo, "jo_fcm_title_outlet_assign", "jo_fcm_body_outlet_assign"); } $rider = $jo->getRider(); diff --git a/src/Controller/APIController.php b/src/Controller/APIController.php index 05f50243..16a9cdf5 100644 --- a/src/Controller/APIController.php +++ b/src/Controller/APIController.php @@ -49,6 +49,7 @@ use App\Service\HubDistributor; use App\Service\HubFilterLogger; use App\Service\HubFilteringGeoChecker; use App\Service\HashGenerator; +use App\Service\JobOrderManager; use App\Entity\MobileSession; use App\Entity\Customer; @@ -867,7 +868,7 @@ class APIController extends Controller implements LoggedController MapTools $map_tools, InventoryManager $im, MQTTClient $mclient, RiderAssignmentHandlerInterface $rah, PromoLogger $promo_logger, HubSelector $hub_select, HubDistributor $hub_dist, HubFilterLogger $hub_filter_logger, - HubFilteringGeoChecker $hub_geofence) + HubFilteringGeoChecker $hub_geofence, JobOrderManager $jo_manager) { // check required parameters and api key $required_params = [ @@ -928,7 +929,15 @@ class APIController extends Controller implements LoggedController ->setErrorMessage('No customer information found'); return $res->getReturnResponse(); } + + // check if customer has more than one job order already + $flag_cust_new = false; + $cust_jo_count = $jo_manager->getCustomerJobOrderCount($cust->getID()); + if ($cust_jo_count <= 1) + $flag_cust_new = true; + $jo->setCustomer($cust); + $jo->setCustNew($flag_cust_new); // validate service type $stype = $req->request->get('service_type'); @@ -1026,7 +1035,18 @@ class APIController extends Controller implements LoggedController break; } - $icrit->addEntry($batt, $trade_in, 1); + // right now, the app does not include trade-ins but this might change in the future + if (empty($trade_in)) + $icrit->addEntry($batt, null, 1); + else + $icrit->addEntry($batt, $trade_in, 1); + + // set if taxable + $icrit->setIsTaxable(); + + // set JO source + // old app doesn't have separate jumpstart + $icrit->setSource(TransactionOrigin::CALL); // send to invoice generator $invoice = $ic->generateInvoice($icrit); @@ -1307,6 +1327,13 @@ class APIController extends Controller implements LoggedController $icrit->addBattery($batt); */ + // set taxable + $icrit->setIsTaxable(true); + + // set JO source + // old app doesn't have separate jumpstart + $icrit->setSource(TransactionOrigin::CALL); + // check trade-in // only allow motolite, other, none $trade_in = $req->request->get('trade_in'); @@ -1321,7 +1348,11 @@ class APIController extends Controller implements LoggedController break; } - $icrit->addEntry($batt, $trade_in, 1); + // right now, the app does not include trade-ins but this might change in the future + if (empty($trade_in)) + $icrit->addEntry($batt, null, 1); + else + $icrit->addEntry($batt, $trade_in, 1); // send to invoice generator $invoice = $ic->generateInvoice($icrit); @@ -2710,7 +2741,7 @@ class APIController extends Controller implements LoggedController MapTools $map_tools, InventoryManager $im, MQTTClient $mclient, RiderAssignmentHandlerInterface $rah, PromoLogger $promo_logger, HubSelector $hub_select, HubDistributor $hub_dist, HubFilterLogger $hub_filter_logger, - HubFilteringGeoChecker $hub_geofence) + HubFilteringGeoChecker $hub_geofence, JobOrderManager $jo_manager) { // check required parameters and api key $required_params = [ @@ -2835,7 +2866,15 @@ class APIController extends Controller implements LoggedController // ->setErrorMessage('No customer information found'); // return $res->getReturnResponse(); // } + + // check if customer has more than one job order already + $flag_cust_new = false; + $cust_jo_count = $jo_manager->getCustomerJobOrderCount($cust->getID()); + if ($cust_jo_count <= 1) + $flag_cust_new = true; + $jo->setCustomer($cust); + $jo->setCustNew($flag_cust_new); // validate service type $stype = $req->request->get('service_type'); @@ -2865,6 +2904,13 @@ class APIController extends Controller implements LoggedController $icrit = new InvoiceCriteria(); $icrit->setServiceType($stype); + // set taxable + $icrit->setIsTaxable(true); + + // set JO source + // old app doesn't have separate jumpstart + $icrit->setSource(TransactionOrigin::CALL); + // check promo $promo_id = $req->request->get('promo_id'); if (!empty($promo_id)) @@ -2933,7 +2979,11 @@ class APIController extends Controller implements LoggedController break; } - $icrit->addEntry($batt, $trade_in, 1); + // right now, the app does not include trade-ins but this might change in the future + if (empty($trade_in)) + $icrit->addEntry($batt, null, 1); + else + $icrit->addEntry($batt, $trade_in, 1); // send to invoice generator $invoice = $ic->generateInvoice($icrit); diff --git a/src/Controller/APIRoleController.php b/src/Controller/APIRoleController.php index f347d890..4c60b4cd 100644 --- a/src/Controller/APIRoleController.php +++ b/src/Controller/APIRoleController.php @@ -2,8 +2,8 @@ namespace App\Controller; -use Catalyst\APIBundle\Entity\Role as APIRole; -use Catalyst\APIBundle\Access\Generator as APIACLGenerator; +use Catalyst\ApiBundle\Entity\Role as APIRole; +use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -19,7 +19,7 @@ class APIRoleController extends Controller { protected $api_acl_gen; - public function __construct(APIACLGenerator $api_acl_gen) + public function __construct(ACLGenerator $api_acl_gen) { $this->api_acl_gen = $api_acl_gen; } diff --git a/src/Controller/APIUserController.php b/src/Controller/APIUserController.php index 122d3830..d707e32c 100644 --- a/src/Controller/APIUserController.php +++ b/src/Controller/APIUserController.php @@ -2,8 +2,8 @@ namespace App\Controller; -use Catalyst\APIBundle\Entity\User as APIUser; -use Catalyst\APIBundle\Entity\Role as APIRole; +use App\Entity\ApiUser as APIUser; +use Catalyst\ApiBundle\Entity\Role as APIRole; use Doctrine\ORM\Query; use Symfony\Component\HttpFoundation\Request; diff --git a/src/Controller/CAPI/BatteryController.php b/src/Controller/CAPI/BatteryController.php index 5a03c379..7a9aa7b1 100644 --- a/src/Controller/CAPI/BatteryController.php +++ b/src/Controller/CAPI/BatteryController.php @@ -6,14 +6,14 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Doctrine\ORM\Query; use Doctrine\ORM\EntityManagerInterface; -use Catalyst\APIBundle\Controller\APIController; -use Catalyst\APIBundle\Response\APIResponse; +use Catalyst\ApiBundle\Controller\ApiController; +use Catalyst\ApiBundle\Component\Response as APIResponse; use App\Entity\SAPBattery; use App\Entity\SAPBatterySize; use App\Entity\SAPBatteryBrand; -use Catalyst\APIBundle\Access\Generator as ACLGenerator; +use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator; class BatteryController extends APIController { diff --git a/src/Controller/CAPI/CustomerController.php b/src/Controller/CAPI/CustomerController.php index 912e0235..553939e3 100644 --- a/src/Controller/CAPI/CustomerController.php +++ b/src/Controller/CAPI/CustomerController.php @@ -8,8 +8,8 @@ use Symfony\Component\HttpFoundation\Request; use Doctrine\ORM\Query; use Doctrine\ORM\EntityManagerInterface; -use Catalyst\APIBundle\Controller\APIController; -use Catalyst\APIBundle\Response\APIResponse; +use Catalyst\ApiBundle\Controller\ApiController; +use Catalyst\ApiBundle\Component\Response as APIResponse; use App\Entity\Customer; use App\Entity\CustomerVehicle; @@ -17,9 +17,9 @@ use App\Entity\Vehicle; use App\Service\HashGenerator; -use Catalyst\APIBundle\Access\Generator as ACLGenerator; +use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator; -class CustomerController extends APIController +class CustomerController extends ApiController { protected $acl_gen; diff --git a/src/Controller/CAPI/CustomerWarrantyController.php b/src/Controller/CAPI/CustomerWarrantyController.php index c5562d40..b22309f2 100644 --- a/src/Controller/CAPI/CustomerWarrantyController.php +++ b/src/Controller/CAPI/CustomerWarrantyController.php @@ -10,8 +10,8 @@ use Doctrine\ORM\Query; use Doctrine\ORM\EntityManagerInterface; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; -use Catalyst\APIBundle\Controller\APIController; -use Catalyst\APIBundle\Response\APIResponse; +use Catalyst\ApiBundle\Controller\ApiController; +use Catalyst\ApiBundle\Component\Response as APIResponse; use App\Service\RisingTideGateway; use App\Service\WarrantyAPILogger; @@ -37,10 +37,10 @@ use App\Ramcar\WarrantySource; use DateTime; -use Catalyst\APIBundle\Access\Generator as ACLGenerator; +use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator; // third party API -class CustomerWarrantyController extends APIController +class CustomerWarrantyController extends ApiController { protected $acl_gen; diff --git a/src/Controller/CAPI/DealerController.php b/src/Controller/CAPI/DealerController.php index c59e4a54..78d26402 100644 --- a/src/Controller/CAPI/DealerController.php +++ b/src/Controller/CAPI/DealerController.php @@ -6,14 +6,14 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Doctrine\ORM\Query; use Doctrine\ORM\EntityManagerInterface; -use Catalyst\APIBundle\Controller\APIController; -use Catalyst\APIBundle\Response\APIResponse; +use Catalyst\ApiBundle\Controller\ApiController; +use Catalyst\ApiBundle\Component\Response as APIResponse; use App\Entity\Dealer; -use Catalyst\APIBundle\Access\Generator as ACLGenerator; +use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator; -class DealerController extends APIController +class DealerController extends ApiController { protected $acl_gen; diff --git a/src/Controller/CAPI/MunicipalityController.php b/src/Controller/CAPI/MunicipalityController.php index 8345cd45..7391aa82 100644 --- a/src/Controller/CAPI/MunicipalityController.php +++ b/src/Controller/CAPI/MunicipalityController.php @@ -6,14 +6,14 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Doctrine\ORM\Query; use Doctrine\ORM\EntityManagerInterface; -use Catalyst\APIBundle\Controller\APIController; -use Catalyst\APIBundle\Response\APIResponse; +use Catalyst\ApiBundle\Controller\ApiController; +use Catalyst\ApiBundle\Component\Response as APIResponse; use App\Entity\Municipality; -use Catalyst\APIBundle\Access\Generator as ACLGenerator; +use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator; -class MunicipalityController extends APIController +class MunicipalityController extends ApiController { protected $acl_gen; diff --git a/src/Controller/CAPI/PrivacyPolicyController.php b/src/Controller/CAPI/PrivacyPolicyController.php index 034d12ff..46b4dade 100644 --- a/src/Controller/CAPI/PrivacyPolicyController.php +++ b/src/Controller/CAPI/PrivacyPolicyController.php @@ -7,15 +7,15 @@ use Symfony\Component\HttpFoundation\Request; use Doctrine\ORM\Query; use Doctrine\ORM\EntityManagerInterface; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; -use Catalyst\APIBundle\Controller\APIController; -use Catalyst\APIBundle\Response\APIResponse; +use Catalyst\ApiBundle\Controller\ApiController; +use Catalyst\ApiBundle\Component\Response as APIResponse; use App\Entity\PrivacyPolicy; -use Catalyst\APIBundle\Access\Generator as ACLGenerator; +use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator; // third party API -class PrivacyPolicyController extends APIController +class PrivacyPolicyController extends ApiController { protected $acl_gen; diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php index 6a6d3d91..d29f6a04 100644 --- a/src/Controller/CAPI/RiderAppController.php +++ b/src/Controller/CAPI/RiderAppController.php @@ -11,8 +11,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; use Doctrine\ORM\Query; use Doctrine\ORM\EntityManagerInterface; -use Catalyst\APIBundle\Controller\APIController; -use Catalyst\APIBundle\Response\APIResponse; +use Catalyst\ApiBundle\Controller\ApiController; +use Catalyst\ApiBundle\Component\Response as APIResponse; use App\Entity\Rider; use App\Entity\JOEvent; @@ -22,11 +22,13 @@ use App\Entity\BatteryModel; use App\Entity\BatterySize; use App\Entity\RiderAPISession; use App\Entity\User; -use Catalyst\APIBundle\Entity\User as APIUser; +use App\Entity\ApiUser as APIUser; use App\Service\RedisClientProvider; use App\Service\RiderCache; use App\Service\MQTTClient; +use App\Service\MQTTClientApiv2; +use App\Service\FCMSender; use App\Service\WarrantyHandler; use App\Service\JobOrderHandlerInterface; use App\Service\InvoiceGeneratorInterface; @@ -46,7 +48,7 @@ use App\Ramcar\DeliveryStatus; use DateTime; // third party API for rider -class RiderAppController extends APIController +class RiderAppController extends ApiController { /* public function register(Request $req, EntityManagerInterface $em, RedisClientProvider $redis) @@ -437,7 +439,7 @@ class RiderAppController extends APIController } - public function cancelJobOrder(Request $req, EntityManagerInterface $em, MQTTClient $mclient) + public function cancelJobOrder(Request $req, EntityManagerInterface $em, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient) { $required_params = ['jo_id']; @@ -483,6 +485,10 @@ class RiderAppController extends APIController ]; $mclient->sendEvent($jo, $payload); + // NOTE: for resq2 app + $mclientv2->sendEvent($jo, $payload); + $fcmclient->sendJoEvent($jo, "jo_fcm_title_outlet_assign", "jo_fcm_body_outlet_assign"); + $data = []; return new APIResponse(true, 'Job order requeued.', $data); } @@ -648,7 +654,7 @@ class RiderAppController extends APIController } - public function arrive(Request $req, EntityManagerInterface $em, MQTTClient $mclient) + public function arrive(Request $req, EntityManagerInterface $em, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient) { $required_params = ['jo_id']; @@ -698,6 +704,10 @@ class RiderAppController extends APIController ]; $mclient->sendEvent($jo, $payload); + // NOTE: for resq2 app + $mclientv2->sendEvent($jo, $payload); + $fcmclient->sendJoEvent($jo, "jo_fcm_title_driver_arrived", "jo_fcm_body_driver_arrived"); + $data = []; return new APIResponse(true, 'Rider arrived at customer location.', $data); } @@ -749,7 +759,7 @@ class RiderAppController extends APIController } public function payment(Request $req, EntityManagerInterface $em, JobOrderHandlerInterface $jo_handler, - RisingTideGateway $rt, WarrantyHandler $wh, MQTTClient $mclient, TranslatorInterface $translator) + RisingTideGateway $rt, WarrantyHandler $wh, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient, TranslatorInterface $translator) { $required_params = ['jo_id']; @@ -871,6 +881,10 @@ class RiderAppController extends APIController ]; $mclient->sendEvent($jo, $payload); + // NOTE: for resq2 app + $mclientv2->sendEvent($jo, $payload); + $fcmclient->sendJoEvent($jo, "jo_fcm_title_fulfilled", "jo_fcm_body_fulfilled"); + $data = []; return new APIResponse(true, 'Job order paid and fulfilled.', $data); } @@ -1187,6 +1201,7 @@ class RiderAppController extends APIController $crit->setServiceType($stype_id); $crit->setCustomerVehicle($cv); $crit->setHasCoolant($jo->hasCoolant()); + $crit->setIsTaxable(); if ($promo != null) $crit->addPromo($promo); diff --git a/src/Controller/CAPI/TestController.php b/src/Controller/CAPI/TestController.php index 9604c056..6fa1007a 100644 --- a/src/Controller/CAPI/TestController.php +++ b/src/Controller/CAPI/TestController.php @@ -5,10 +5,10 @@ namespace App\Controller\CAPI; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Doctrine\ORM\Query; -use Catalyst\APIBundle\Controller\APIController; -use Catalyst\APIBundle\Response\APIResponse; +use Catalyst\ApiBundle\Controller\ApiController; +use Catalyst\ApiBundle\Response\APIResponse; -class TestController extends APIController +class TestController extends ApiController { public function test() { diff --git a/src/Controller/CAPI/VehicleController.php b/src/Controller/CAPI/VehicleController.php index 1f3b13b4..547e3faf 100644 --- a/src/Controller/CAPI/VehicleController.php +++ b/src/Controller/CAPI/VehicleController.php @@ -6,14 +6,14 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Doctrine\ORM\Query; use Doctrine\ORM\EntityManagerInterface; -use Catalyst\APIBundle\Controller\APIController; -use Catalyst\APIBundle\Response\APIResponse; +use Catalyst\ApiBundle\Controller\ApiController; +use Catalyst\ApiBundle\Component\Response as APIResponse; use App\Entity\Vehicle; use App\Entity\VehicleManufacturer; -use Catalyst\APIBundle\Access\Generator as ACLGenerator; +use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator; -class VehicleController extends APIController +class VehicleController extends ApiController { protected $acl_gen; @@ -48,7 +48,7 @@ class VehicleController extends APIController { $this->denyAccessUnlessGranted('vehicle.list', null, 'No access.'); - $mfg = $this->em->getRepository(VehicleManufacturer::class)->find($mfg_id); + $mfg = $em->getRepository(VehicleManufacturer::class)->find($mfg_id); // manufacturer not found if ($mfg == null) @@ -69,6 +69,9 @@ class VehicleController extends APIController } // TODO: need to add manufacturer details + $data = [ + 'vehicles' => $make_data, + ]; return new APIResponse(true, 'Vehicles loaded.', $data); } diff --git a/src/Controller/CAPI/WarrantyController.php b/src/Controller/CAPI/WarrantyController.php index 3d9ab97b..581e22f5 100644 --- a/src/Controller/CAPI/WarrantyController.php +++ b/src/Controller/CAPI/WarrantyController.php @@ -7,8 +7,8 @@ use Symfony\Component\HttpFoundation\Request; use Doctrine\ORM\Query; use Doctrine\ORM\EntityManagerInterface; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; -use Catalyst\APIBundle\Controller\APIController; -use Catalyst\APIBundle\Response\APIResponse; +use Catalyst\ApiBundle\Controller\ApiController; +use Catalyst\ApiBundle\Component\Response as APIResponse; use App\Entity\Warranty; use App\Entity\BatteryModel; @@ -33,10 +33,10 @@ use App\Ramcar\WarrantySource; use DateTime; -use Catalyst\APIBundle\Access\Generator as ACLGenerator; +use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator; // third party API -class WarrantyController extends APIController +class WarrantyController extends ApiController { protected $acl_gen; @@ -330,7 +330,7 @@ class WarrantyController extends APIController // get the api_user that made the call so that it gets added to the source // source becomes CAPI_USER_ - $username = $this->getAPIUsername($em, $user_id); + $username = $this->getUser()->getName(); $source = 'CAPI_USER_' . $username; diff --git a/src/Controller/CAPI/WarrantySerialController.php b/src/Controller/CAPI/WarrantySerialController.php index c845e367..419a5e9c 100644 --- a/src/Controller/CAPI/WarrantySerialController.php +++ b/src/Controller/CAPI/WarrantySerialController.php @@ -10,8 +10,8 @@ use Doctrine\ORM\Query; use Doctrine\ORM\EntityManagerInterface; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; -use Catalyst\APIBundle\Controller\APIController; -use Catalyst\APIBundle\Response\APIResponse; +use Catalyst\ApiBundle\Controller\ApiController; +use Catalyst\ApiBundle\Component\Response as APIResponse; use App\Entity\WarrantySerialQueue; @@ -19,10 +19,10 @@ use App\Service\WarrantySerialUploadLogger; use DateTime; -use Catalyst\APIBundle\Access\Generator as ACLGenerator; +use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator; // third party API -class WarrantySerialController extends APIController +class WarrantySerialController extends ApiController { protected $acl_gen; protected $upload_logger; diff --git a/src/Controller/CustomerAppAPI/AccountController.php b/src/Controller/CustomerAppAPI/AccountController.php new file mode 100644 index 00000000..20d628cd --- /dev/null +++ b/src/Controller/CustomerAppAPI/AccountController.php @@ -0,0 +1,207 @@ +hasMissingParams($req, [ + 'phone_number', + ]); + + if ($missing) { + return new ApiResponse(false, $missing); + } + + // user input + $phone_number = $req->request->get('phone_number'); + $reason = $req->request->get('reason'); + + // use the test code if we're using a test number or are on test mode + $code = $this->getConfirmCode($phone_number); + + $success_msg = 'We have sent a confirmation code to the submitted phone number if it is valid.'; + + // initialize model + $obj = new CustomerDeleteRequest(); + + // check if a customer record exists for this phone number + $cust_obj = $this->findCustomerByNumber($phone_number); + if (empty($cust_obj)) { + // return a random id anyway if we don't find this customer + return new ApiResponse(true, $success_msg, [ + 'request_id' => $obj->getID(), + ]); + } + + // phone number is valid, we continue building the model + $obj->setPhoneNumber($phone_number); + $obj->setReason($reason); + $obj->setConfirmCode($code); + + // send sms to number if not in test mode + if ($this->getOtpMode() != 'test') { + $this->sendConfirmationCode($rt, $phone_number, $code, $translator); + } + + // save the model + $obj->setDateCodeSent(new DateTime()); + $this->em->persist($obj); + $this->em->flush(); + + // response + return new ApiResponse(true, $success_msg, [ + 'request_id' => $obj->getID(), + ]); + } + + public function validateDeleteCode(Request $req) + { + // validate params + $missing = $this->hasMissingParams($req, [ + 'request_id', + 'code', + ]); + + if ($missing) { + return new ApiResponse(false, $missing); + } + + // user input + $code = $req->request->get('code'); + $request_id = $req->request->get('request_id'); + + // get the request + $obj = $this->em->getRepository(CustomerDeleteRequest::class)->findOneBy([ + 'id' => $request_id, + 'confirm_code' => $code, + 'flag_confirmed' => false, + 'flag_completed' => false, + ]); + if (empty($obj)) { + return new ApiResponse(false, 'Your confirmation code is invalid.'); + } + + // check if a customer record exists for this phone number + $cust_obj = $this->findCustomerByNumber($obj->getPhoneNumber()); + if (empty($cust_obj)) { + return new ApiResponse(false, 'No account exists for this phone number.'); + } + + // confirm the request + $obj->setConfirmed(true); + $obj->setDateConfirmed(new DateTime()); + $obj->setCustomer($cust_obj); + $this->em->flush(); + + // response + return new ApiResponse(true, 'Your request has been submitted for processing.'); + } + + public function resendCode(Request $req, RisingTideGateway $rt, TranslatorInterface $translator) + { + // validate params + $missing = $this->hasMissingParams($req, [ + 'request_id', + ]); + + if ($missing) { + return new ApiResponse(false, $missing); + } + + // user input + $request_id = $req->request->get('request_id'); + $now = time(); + + // get the request + $obj = $this->em->getRepository(CustomerDeleteRequest::class)->findOneBy([ + 'id' => $request_id, + 'flag_confirmed' => false, + 'flag_completed' => false, + ]); + if (empty($obj)) { + return new ApiResponse(false, 'Invalid request details provided.'); + } + + // prevent resend spamming + if ($now - $obj->getDateCodeSent()->getTimestamp() < 300) { + return new ApiResponse(false, 'You can only request a confirm code every 5 mins.'); + } + + $success_msg = 'We have re-sent a confirmation code to the submitted phone number if it is valid.'; + + // check if a customer record exists for this phone number + $cust_obj = $this->findCustomerByNumber($obj->getPhoneNumber()); + if (empty($cust_obj)) { + // return successful without resending code if we don't find this customer + return new ApiResponse(true, $success_msg); + } + + // use the test code if we're using a test number or are on test mode + $phone_number = $obj->getPhoneNumber(); + $code = $this->getConfirmCode($phone_number); + + // send sms to number if not in test mode + if ($this->getOtpMode()!= 'test') { + $this->sendConfirmationCode($rt, $phone_number, $code, $translator); + } + + // update last sent timestamp + $obj->setDateCodeSent(new DateTime()); + $this->em->flush(); + + // response + return new ApiResponse(true, $success_msg); + } + + protected function getConfirmCode($phone_number) + { + // check for hardcoded phone number for app store testing + $test_numbers = explode(",", $_ENV['TEST_PHONE_NUMBERS']); + + if (in_array($phone_number, $test_numbers) || $this->getOtpMode() == 'test') { + $code = "123456"; + } else { + // generate code + $code = $this->generateConfirmCode(); + } + + return $code; + } + + protected function generateConfirmCode() + { + return sprintf("%06d", mt_rand(100000, 999999)); + } + + protected function sendConfirmationCode(RisingTideGateway $rt, $phone_number, $code, TranslatorInterface $translator) + { + // send sms to number + $message = $translator->trans('message.confirmation_code') . ' ' . $code; + $rt->sendSMS($phone_number, $translator->trans('message.battery_brand_allcaps'), $message); + } + + protected function findCustomerByNumber($number) + { + $cust_obj = $this->em->getRepository(Customer::class)->findOneBy([ + 'phone_mobile' => $number, + ]); + + return $cust_obj; + } + + protected function getOtpMode() + { + return $_ENV['OTP_MODE']; + } +} diff --git a/src/Controller/CustomerAppAPI/ApiController.php b/src/Controller/CustomerAppAPI/ApiController.php new file mode 100644 index 00000000..6a25eb80 --- /dev/null +++ b/src/Controller/CustomerAppAPI/ApiController.php @@ -0,0 +1,167 @@ +session = new CustomerSession; // NOTE: original was null + $this->em = $em; + + // load env file + $dotenv = new Dotenv(); + $dotenv->loadEnv($kernel->getProjectDir() . '/.env'); + } + + protected function debugRequest(Request $req) + { + $all = $req->request->all(); + error_log(print_r($all, true)); + } + + protected function hasMissingParams(Request $req, $params = []) + { + return $this->checkRequiredParameters($req, $params); + } + + protected function validateSession($session_key) + { + // check if the session exists + $session = $this->em->getRepository(CustomerSession::class)->find($session_key); + if ($session === null) { + return false; + } + + $this->session = $session; + return true; + } + + protected function validateRequest(Request $req, $params = []) + { + $error = $this->hasMissingParams($req, $params); + $session_key = $req->query->get('session_key'); + + if (!$error) { + if (empty($session_key) || !$this->validateSession($session_key)) { + $error = 'Invalid session key.'; + } + } + + return [ + 'is_valid' => !$error, + 'error' => $error, + ]; + } + + protected function findWarranty($plate_number) + { + // NOTE: Modify the search for the latest warranty. This seems hacky. + // get latest warranty using plate number + $warranty_results = $this->em->getRepository(Warranty::class)->findBy( + ['plate_number' => $plate_number], + ['date_create' => 'desc'] + ); + + $warr = []; + + // check if warranty_results is empty + if (empty($warranty_results)) { + /* + $res->setError(true) + ->setErrorMessage('No warranty found for plate number'); + return $res->getReturnResponse(); + */ + + return $warr; + } + + // get first entry + $warranty = current($warranty_results); + + // check for null values for battery and date claim and date expire + $batt_model = ''; + $batt_size = ''; + $sap_batt = ''; + $claim_date = ''; + $expiry_date = ''; + + if (!(is_null($warranty->getBatteryModel()))) { + $batt_model = $warranty->getBatteryModel()->getName(); + } + if (!(is_null($warranty->getBatterySize()))) { + $batt_size = $warranty->getBatterySize()->getName(); + } + if (!(is_null($warranty->getSAPBattery()))) { + $sap_batt = $warranty->getSAPBattery()->getID(); + } + if (!(is_null($warranty->getDateClaim()))) { + $claim_date = $warranty->getDateClaim()->format("d M Y"); + } + if (!(is_null($warranty->getDateExpire()))) { + $expiry_date = $warranty->getDateExpire()->format("d M Y"); + } + + $warr[] = [ + 'id' => $warranty->getID(), + 'serial' => $warranty->getSerial(), + 'warranty_class' => $warranty->getWarrantyClass(), + 'plate_number' => $warranty->getPlateNumber(), + 'first_name' => $warranty->getFirstName(), + 'last_name' => $warranty->getLastName(), + 'mobile_number' => $warranty->getMobileNumber(), + 'battery_model' => $batt_model, + 'battery_size' => $batt_size, + 'sap_battery' => $sap_batt, + 'status' => $warranty->getStatus(), + 'date_create' => $warranty->getDateCreate()->format("d M Y g:i A"), + 'date_purchase' => $warranty->getDatePurchase()->format("d M Y"), + 'date_expire' => $expiry_date, + 'date_claim' => $claim_date, + 'claim_from' => $warranty->getClaimedFrom(), + 'is_activated' => $warranty->isActivated() ? 1 : 0, + ]; + + return $warr; + } + + protected function getBatteryImageURL($req, $batt) + { + // TODO: workaround for now, we get static image of battery based on model name + $filename = trim(strtolower($batt->getModel()->getName())) . '_mobile.jpg'; + $filename = str_replace(" ", "_", $filename); + $file_path = $req->getSchemeAndHttpHost() . $this->generateUrl('static_battery_image') . '/' . $filename; + + return $file_path; + } + + protected function getOngoingJobOrders($cust) + { + $ongoing_jos = $this->em->getRepository(JobOrder::class)->findBy([ + 'customer' => $cust, + 'status' => [JOStatus::PENDING, JOStatus::RIDER_ASSIGN, JOStatus::IN_TRANSIT, JOStatus::ASSIGNED, JOStatus::IN_PROGRESS], + ], ['date_schedule' => 'desc']); + + return $ongoing_jos; + } + + protected function getGeoErrorMessage() + { + return 'Oops! Our service is limited to some areas in Metro Manila, Laguna, Cavite, Pampanga and Baguio only. We will update you as soon as we are able to cover your area'; + } +} diff --git a/src/Controller/CustomerAppAPI/AppController.php b/src/Controller/CustomerAppAPI/AppController.php new file mode 100644 index 00000000..553036dd --- /dev/null +++ b/src/Controller/CustomerAppAPI/AppController.php @@ -0,0 +1,53 @@ +hasMissingParams($req, [ + 'version', + 'os', + ]); + + if ($missing) { + return new ApiResponse(false, $missing); + } + + $need_update = false; + $msg = 'Version is up to date.'; + + $os = $req->query->get('os'); + + // putting this in for the future, in case we have diverging versions + //$platform = $req->query->get('platform'); + + // get only the major version numbers + $app_version = $req->query->get('version'); + $app_major = substr($app_version, 0, strripos($app_version, ".")); + + $latest_version = $this->getParameter($os . '_app_version'); + $latest_major = substr($latest_version, 0, strripos($latest_version, ".")); + + if ($latest_major < $app_major) { + return new ApiResponse(false, 'Invalid application version: ' . $app_version); + } + + if ($latest_major > $app_major) { + $need_update = true; + $msg = 'Your version is outdated and needs an update to use the latest features RES-Q has to offer.'; + } + + // response + return new ApiResponse(true, '', [ + 'need_update' => $need_update, + 'latest_version' => $latest_version, + 'message' => $msg, + ]); + } +} diff --git a/src/Controller/CustomerAppAPI/AuthController.php b/src/Controller/CustomerAppAPI/AuthController.php new file mode 100644 index 00000000..1aa5bcb9 --- /dev/null +++ b/src/Controller/CustomerAppAPI/AuthController.php @@ -0,0 +1,292 @@ +hasMissingParams($req, [ + 'phone_model', + 'os_type', + 'os_version', + 'phone_id', + ]); + + if ($missing) { + return new ApiResponse(false, $missing); + } + + // retry until we get a unique id + while (true) { + try { + // instantiate session + $sess = new CustomerSession(); + $sess->setPhoneModel($req->request->get('phone_model')) + ->setOSType($req->request->get('os_type')) + ->setOSVersion($req->request->get('os_version')) + ->setPhoneID($req->request->get('phone_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(); + } catch (DBALException $e) { + error_log($e->getMessage()); + // delay one second and try again + sleep(1); + continue; + } + + break; + } + + // return data + return new ApiResponse(true, '', [ + 'session_key' => $sess->getID(), + ]); + } + + public function confirmNumber(RisingTideGateway $rt, Request $req, TranslatorInterface $translator) + { + // validate request + $validity = $this->validateRequest($req, [ + 'phone_number' + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // phone number + $phone_number = $req->request->get('phone_number'); + + // get otp_mode from .env + $otp_mode = $_ENV['OTP_MODE']; + + // check for hardcoded phone number for app store testing + $test_numbers = explode(",", $_ENV['TEST_PHONE_NUMBERS']); + + if (in_array($phone_number, $test_numbers)) { + $code = '123456'; + $this->session->setConfirmCode($code) + ->setPhoneNumber($phone_number); + $this->em->flush(); + + return new ApiResponse(); + } + + // check if otp_mode is test + if ($otp_mode == 'test') { + $code = '123456'; + $this->session->setConfirmCode($code) + ->setPhoneNumber($phone_number); + $this->em->flush(); + + return new ApiResponse(); + } + + // TODO: spam protection + + // TODO: validate phone number + + // generate code and save + $code = $this->generateConfirmCode(); + $this->session->setConfirmCode($code) + ->setPhoneNumber($phone_number); + $this->em->flush(); + + if ($otp_mode != 'test') { + // send sms to number + $this->sendConfirmationCode($rt, $phone_number, $code, $translator); + } + + // response + return new ApiResponse(); + } + + public function validateCode(Request $req) + { + // validate request + $validity = $this->validateRequest($req, [ + 'code', + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // already confirmed + if ($this->session->isConfirmed()) { + return new ApiResponse(false, 'User is already confirmed.'); + } + + // code is wrong + $code = $req->request->get('code'); + if ($this->session->getConfirmCode() != $code) { + return new ApiResponse(false, 'Wrong confirm code.'); + } + + // set confirm date + $date = new DateTime(); + $this->session->setDateConfirmed($date) + ->setConfirmed(); + + // figure out if we have customer and customer user already + $customer_user = false; + + // TODO: check if we have the number registered before and merge + $dupe_sess = $this->findNumberSession($this->session->getPhoneNumber()); + if ($dupe_sess != null) { + error_log("Found existing customer session for " . $this->session->getPhoneNumber()); + $dupe_cust = $dupe_sess->getCustomer(); + $this->session->setCustomer($dupe_cust); + + // set customer user if it exists + $customer_user = $dupe_sess->getCustomerUser() ?? $dupe_cust->getCustomerUser(); + } + + // TODO: check if mobile matches mobile of customer + $customer = $this->findCustomerByNumber($this->session->getPhoneNumber()); + if ($customer != null) { + // TODO: if there is a dupe_sess, do we need to check if + // dupe_cust is the same as the customer we found? + $this->session->setCustomer($customer); + } + + if (!$customer_user) { + error_log("We don't have a customer user for session " . $this->session->getID()); + + $customer_user = $this->findCustomerUserByNumber($this->session->getPhoneNumber()); + + if ($customer_user === null) { + error_log("Creating a new customer user for " . $this->session->getPhoneNumber()); + $customer_user = new CustomerUser(); + $customer_user->setCustomer($this->session->getCustomer()) + ->setPhoneNumber($this->session->getPhoneNumber()); + + // save + $this->em->persist($customer_user); + $this->em->flush(); + } else { + error_log("Found existing customer user for " . $this->session->getPhoneNumber()); + } + } + + error_log("Customer user ID is " . $customer_user->getID()); + + // set session customer user + $this->session->setCustomerUser($customer_user); + $this->em->flush(); + + // response + return new ApiResponse(true, '', [ + 'api_key' => $customer_user->getApiKey(), + 'secret_key'=> $customer_user->getSecretKey(), + ]); + } + + public function resendCode(Request $req, RisingTideGateway $rt, TranslatorInterface $translator) + { + // validate request + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // already confirmed + if ($this->session->isConfirmed()) { + return new ApiResponse(false, 'User is already confirmed.'); + } + + // have sent code before + if ($this->session->getDateCodeSent() != null) { + return new ApiResponse(false, 'Can only send confirm code every 5 mins.'); + } + + // TODO: send via sms + $phone_number = $this->session->getPhoneNumber(); + $code = $this->session->getConfirmCode(); + $this->sendConfirmationCode($rt, $phone_number, $code, $translator); + + // response + return new ApiResponse(); + } + + protected function generateConfirmCode() + { + return sprintf("%06d", mt_rand(100000, 999999)); + } + + protected function sendConfirmationCode(RisingTideGateway $rt, $phone_number, $code, TranslatorInterface $translator) + { + // send sms to number + $message = $translator->trans('message.confirmation_code') . ' ' . $code; + $rt->sendSMS($phone_number, $translator->trans('message.battery_brand_allcaps'), $message); + } + + // TODO: find session customer by phone number + protected function findNumberSession($number) + { + $query = $this->em->getRepository(CustomerSession::class)->createQueryBuilder('s') + ->where('s.phone_number = :number') + ->andWhere('s.customer is not null') + ->andWhere('s.customer_user is not null') + ->andWhere('s.confirm_flag = 1') + ->setParameter('number', $number) + ->setMaxResults(1) + ->getQuery(); + + // we just need one + $res = $query->getOneOrNullResult(); + + return $res; + } + + protected function findCustomerByNumber($number) + { + $customers = $this->em->getRepository(Customer::class)->findBy(['phone_mobile' => $number]); + + // find the customer with the most number of cars + $car_count = 0; + $cust = null; + + foreach ($customers as $customer) { + $vehicles = $customer->getVehicles(); + if (count($vehicles) > $car_count) { + $car_count = count($vehicles); + + // "save" customer object + $cust = $customer; + } + } + + return $cust; + } + + protected function findCustomerUserByNumber($number) + { + return $this->em->getRepository(CustomerUser::class)->findOneBy(['phone_number' => $number]); + } +} diff --git a/src/Controller/CustomerAppAPI/CustomerController.php b/src/Controller/CustomerAppAPI/CustomerController.php new file mode 100644 index 00000000..4ba7279c --- /dev/null +++ b/src/Controller/CustomerAppAPI/CustomerController.php @@ -0,0 +1,145 @@ +validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // if no customer found + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(true, '', [ + 'first_name' => '', + 'last_name' => '', + 'priv_third_party' => (bool) false, + 'priv_promo' => (bool) false, + ]); + } + + // send back customer details + return new ApiResponse(true, '', [ + 'first_name' => $cust->getFirstName(), + 'last_name' => $cust->getLastName(), + 'priv_third_party' => (bool) $cust->getPrivacyThirdParty(), + 'priv_promo' => (bool) $cust->getPrivacyPromo(), + ]); + } + + public function updateInfo(Request $req) + { + // validate params + $validity = $this->validateRequest($req, [ + 'first_name', + 'last_name', + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + $cust = $this->updateCustomerInfo($req); + + $policy_mobile_id = $_ENV['POLICY_MOBILE']; + $mobile_policy = $this->em->getRepository(PrivacyPolicy::class)->find($policy_mobile_id); + + // set policy id + if ($mobile_policy != null) { + $cust->setPrivacyPolicyMobile($mobile_policy); + } + + $this->em->flush(); + + // response + return new ApiResponse(); + } + + public function getStatus(Request $req) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // set data + $data = []; + if ($this->session->isConfirmed()) { + $data['status'] = 'confirmed'; + } else { + $data['status'] = 'unconfirmed'; + } + + return new ApiResponse(true, '', $data); + } + + public function getCustomerHash(Request $req, HashGenerator $hash) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // hash customer id + $hashed_id = $hash->getHash($cust->getID()); + + // response + return new ApiResponse(true, '', [ + 'cust_hash' => $hashed_id, + ]); + } + + protected function updateCustomerInfo(Request $req) + { + // create new customer if it's not there + $cust = $this->session->getCustomer(); + if ($cust == null) { + $cust = new Customer(); + + // set customer source + $cust->setCreateSource(CustomerSource::MOBILE); + $this->em->persist($cust); + + $this->session->setCustomer($cust); + } + + $cust->setFirstName($req->request->get('first_name')) + ->setLastName($req->request->get('last_name')) + ->setEmail($req->request->get('email', '')) + ->setConfirmed($this->session->isConfirmed()); + + // if customer user isn't set, set it now + if ($cust->getCustomerUser() == null) { + $cust->setCustomerUser($this->session->getCustomerUser()); + } + + // update mobile phone of customer + $cust->setPhoneMobile($this->session->getPhoneNumber()); + + return $cust; + } +} diff --git a/src/Controller/CustomerAppAPI/DeviceController.php b/src/Controller/CustomerAppAPI/DeviceController.php new file mode 100644 index 00000000..b09acf18 --- /dev/null +++ b/src/Controller/CustomerAppAPI/DeviceController.php @@ -0,0 +1,29 @@ +validateRequest($req, [ + 'device_id', + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + $device_id = $req->request->get('device_id'); + $this->session->setDevicePushID($device_id); + + $this->em->flush(); + + // response + return new ApiResponse(); + } +} diff --git a/src/Controller/CustomerAppAPI/InsuranceController.php b/src/Controller/CustomerAppAPI/InsuranceController.php new file mode 100644 index 00000000..a3c5624a --- /dev/null +++ b/src/Controller/CustomerAppAPI/InsuranceController.php @@ -0,0 +1,324 @@ +client = $client; + } + + public function createApplication(Request $req, PayMongoConnector $paymongo, UrlGeneratorInterface $router) + { + // validate params + $validity = $this->validateRequest($req, [ + // internal + 'customer_vehicle_id', + + // client info + 'client_type', + 'first_name', + //'middle_name', // not required + 'surname', + 'corporate_name', + + // client contact info + 'address_number', + //'address_street', // not required + //'address_building', // not required + 'address_barangay', + 'address_city', + 'address_province', + 'zipcode', + 'mobile_number', + 'email_address', + + // car info + 'make', + 'model', + 'series', + 'color', + //'plate_number', // NOTE: we get this from the internal cv record instead + 'mv_file_number', + 'motor_number', + 'serial_chasis', // NOTE: this is how it's spelled on their API + 'year_model', + 'mv_type_id', + 'body_type', + //'is_public', // not required, boolean, only show field if mv_type_id in [4, 13] + //'orcr_file', // this is a file + + // mv_type_id specific fields + //'vehicle_use_type', // not required, only show field if mv_type_id is not in [4, 13]. accepted values are: 'commercial', 'private' + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // conditionally require is_public or vehicle_use_type + switch ($req->request->get('mv_type_id')) { + case 4: + case 13: + if (empty($req->request->get('is_public'))) { + return new ApiResponse(false, 'Missing required parameter(s): is_public is required when mv_type_id is in [4, 13]'); + } + break; + default: + if (empty($req->request->get('vehicle_use_type'))) { + return new ApiResponse(false, 'Missing required parameter(s): vehicle_use_type is required when mv_type_id is not in [4, 13]'); + } + break; + } + + // require the orcr file + if ($req->files->get('orcr_file') === null) { + return new ApiResponse(false, 'Missing required file: orcr_file'); + } + + // get our listener url + $notif_url = $router->generate('insurance_listener', [], UrlGeneratorInterface::ABSOLUTE_URL); + + // get customer and cv info + $cust = $this->session->getCustomer(); + $cv = $this->em->getRepository(CustomerVehicle::class)->find($req->request->get('customer_vehicle_id')); + if ($cv == null) { + return new ApiResponse(false, 'Invalid customer vehicle id.'); + } + + // confirm that customer vehicle belongs to customer + if ($cv->getCustomer()->getID() != $cust->getID()) { + return new ApiResponse(false, 'Vehicle does not belong to customer.'); + } + + // process all our inputs first + $input = $req->request->all(); + + if (!isset($input['is_public'])) { + $input['is_public'] = false; + } + + $input['line'] = $this->getLineType($input['mv_type_id'], $input['vehicle_use_type'], $input['is_public']); + + // submit insurance application + $result = $this->client->createApplication( + $cv, + $notif_url, + $input, + $req->files->get('orcr_file') + ); + if (!$result['success']) { + return new ApiResponse(false, $result['error']['message']); + } + + $premium_amount_int = (int)bcmul($result['response']['premium'], 100); + + // build checkout item and metadata + $items = [ + [ + 'name' => "Insurance Premium", + 'description' => "Premium fee for vehicle insurance", + 'quantity' => 1, + 'amount' => $premium_amount_int, + 'currency' => 'PHP', + ], + ]; + + $now = new DateTime(); + + // create gateway transaction + $gt = new GatewayTransaction(); + $gt->setCustomer($cust); + $gt->setDateCreate($now); + $gt->setAmount($premium_amount_int); + $gt->setStatus(TransactionStatus::PENDING); + $gt->setGateway('paymongo'); // TODO: define values elsewhere + $gt->setType('insurance_premium'); // TODO: define values elsewhere + $this->em->persist($gt); + $this->em->flush(); + + // create paymongo checkout resource + $checkout = $paymongo->createCheckout( + $cust, + $items, + $gt->getID(), + "Motolite RES-Q Vehicle Insurance", + $router->generate('paymongo_payment_success', [], UrlGeneratorInterface::ABSOLUTE_URL), + $router->generate('paymongo_payment_cancelled', [], UrlGeneratorInterface::ABSOLUTE_URL), + ['transaction_id' => $gt->getID()], // NOTE: passing this here too for payment resource metadata + ); + if (!$checkout['success']) { + return new ApiResponse(false, $checkout['error']['message']); + } + + $checkout_url = $checkout['response']['data']['attributes']['checkout_url']; + + // add checkout url and id to transaction metadata + $gt->setExtTransactionId($checkout['response']['data']['id']); + $gt->setMetadata([ + 'checkout_url' => $checkout_url, + ]); + + // store application in db + $app = new InsuranceApplication(); + $app->setDateSubmit($now); + $app->setCustomer($cust); + $app->setCustomerVehicle($cv); + $app->setGatewayTransaction($gt); + $app->setStatus(InsuranceApplicationStatus::CREATED); + $app->setExtTransactionId($result['response']['id']); + $app->setMetadata($input); + $this->em->persist($app); + + // save everything + $this->em->flush(); + + // return + return new ApiResponse(true, '', [ + 'app_id' => $app->getID(), + 'checkout_url' => $checkout_url, + 'premium_amount' => (string)$result['response']['premium'], + ]); + } + + public function getVehicleMakers(Request $req) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get maker list + $result = $this->client->getVehicleMakers(); + if (!$result['success']) { + return new ApiResponse(false, $result['error']['message']); + } + + return new ApiResponse(true, '', [ + 'makers' => $result['response']['data']['vehicleMakers'], + ]); + } + + public function getVehicleModels($maker_id, Request $req) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get maker list + $result = $this->client->getVehicleModels($maker_id); + if (!$result['success']) { + return new ApiResponse(false, $result['error']['message']); + } + + return new ApiResponse(true, '', [ + 'models' => $result['response']['data']['vehicleModels'], + ]); + } + + public function getVehicleTrims($model_id, Request $req) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get maker list + $result = $this->client->getVehicleTrims($model_id); + if (!$result['success']) { + return new ApiResponse(false, $result['error']['message']); + } + + return new ApiResponse(true, '', [ + 'trims' => $result['response']['data']['vehicleTrims'], + ]); + } + + public function getMVTypes(Request $req) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + return new ApiResponse(true, '', [ + 'mv_types' => InsuranceMVType::getCollection(), + ]); + } + + public function getClientTypes(Request $req) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + return new ApiResponse(true, '', [ + 'mv_types' => InsuranceClientType::getCollection(), + ]); + } + + protected function getLineType($mv_type_id, $vehicle_use_type, $is_public = false) + { + $line = ''; + + // NOTE: this is a bit of a hack since we're hardcoding values, but this is fine for now + switch ($mv_type_id) { + case '3': + $line = 'mcoc'; + break; + case '4': + case '13': + if ($is_public) { + $line = 'lcoc'; + } else { + $line = 'mcoc'; + } + break; + default: + if ($vehicle_use_type === 'commercial') { + $line = 'ccoc'; + } else { + $line = 'pcoc'; + } + break; + } + + return $line; + } +} diff --git a/src/Controller/CustomerAppAPI/InvoiceController.php b/src/Controller/CustomerAppAPI/InvoiceController.php new file mode 100644 index 00000000..a5c3a8b8 --- /dev/null +++ b/src/Controller/CustomerAppAPI/InvoiceController.php @@ -0,0 +1,151 @@ +debugRequest($req); + + // validate params + $validity = $this->validateRequest($req, [ + 'service_type', + 'cv_id', + // 'batt_id', + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // make invoice criteria + $icrit = new InvoiceCriteria(); + $icrit->setServiceType($req->request->get('service_type')); + + // check promo + $promo_id = $req->request->get('promo_id'); + if (!empty($promo_id)) { + $promo = $this->em->getRepository(Promo::class)->find($promo_id); + if ($promo == null) { + return new ApiResponse(false, 'Invalid promo id.'); + } + + // put in criteria + $icrit->addPromo($promo); + } + + // check customer vehicle + $cv = $this->em->getRepository(CustomerVehicle::class)->find($req->request->get('cv_id')); + if ($cv == null) { + return new ApiResponse(false, 'Invalid customer vehicle id.'); + } + $icrit->setCustomerVehicle($cv); + + // check if customer owns vehicle + if ($cust->getID() != $cv->getCustomer()->getID()) { + return new ApiResponse(false, 'Customer does not own vehicle.'); + } + + // check battery + $batt_id = $req->request->get('batt_id'); + if ($batt_id != null) { + $batt = $this->em->getRepository(Battery::class)->find($batt_id); + if ($batt == null) { + return new ApiResponse(false, 'Invalid battery id.'); + } + } else + $batt = null; + + /* + // put battery in criteria + $icrit->addBattery($batt); + */ + + // check trade-in + // only allow motolite, other, none + $trade_in_batt = $req->request->get('trade_in_batt'); + $trade_in_type = $req->request->get('trade_in_type'); + + switch ($trade_in_type) { + case TradeInType::MOTOLITE: + case TradeInType::OTHER: + break; + + default: + $trade_in_type = ''; + break; + } + + // add the actual battery item first + $icrit->addEntry($batt, null, 1); + + // DEBUG + + // if we have a trade in, add it as well + if (!empty($trade_in_type) && !empty($trade_in_batt)) { + $ti_batt_obj = $this->em->getRepository(Battery::class)->find($trade_in_batt); + if (!empty($ti_batt_obj)) { + $icrit->addEntry($ti_batt_obj, $trade_in_type, 1); + } + } + + // set if taxable + $icrit->setIsTaxable(); + + // set JO source + $icrit->setSource(TransactionOrigin::MOBILE_APP); + + // send to invoice generator + $invoice = $ic->generateInvoice($icrit); + + // make invoice json data + $data = [ + 'total_price' => (float) $invoice->getTotalPrice(), + 'vat_ex_price' => (float) $invoice->getVATExclusivePrice(), + 'vat' => (float) $invoice->getVAT(), + 'discount' => (float) $invoice->getDiscount(), + 'trade_in' => (float) $invoice->getTradeIn(), + ]; + $items = $invoice->getItems(); + $items_data = []; + foreach ($items as $item) { + $my_data = [ + 'title' => $item->getTitle(), + 'qty' => (int) $item->getQuantity() + 0, + 'price' => (float) $item->getPrice() + 0.0, + ]; + + $item_batt = $item->getBattery(); + if ($item_batt != null) { + $my_data['image_url'] = $this->getBatteryImageURL($req, $item_batt); + } + + $items_data[] = $my_data; + } + + $data['items'] = $items_data; + + // error_log(print_r($data, true)); + + // response + return new ApiResponse(true, '', $data); + } +} diff --git a/src/Controller/CustomerAppAPI/JobOrderController.php b/src/Controller/CustomerAppAPI/JobOrderController.php new file mode 100644 index 00000000..143f2a2e --- /dev/null +++ b/src/Controller/CustomerAppAPI/JobOrderController.php @@ -0,0 +1,1721 @@ +validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + /* + // check if we have an ongoing job order + $ongoing_jos = $this->em->getRepository(JobOrder::class)->findBy([ + 'customer' => $cust, + 'status' => [JOStatus::PENDING, JOStatus::RIDER_ASSIGN, JOStatus::IN_TRANSIT, JOStatus::ASSIGNED, JOStatus::IN_PROGRESS], + ]); + */ + $ongoing_jos = $this->getOngoingJobOrders($cust); + + // initialize data + $data = []; + + // no ongoing + if (count($ongoing_jos) <= 0) { + $data = [ + 'has_ongoing' => false, + ]; + } else { + $data = [ + 'has_ongoing' => true, + ]; + } + + // response + return new ApiResponse(true, '', $data); + } + + public function getJOInvoice(Request $req) + { + // validate params + $validity = $this->validateRequest($req, [ + 'jo_id', + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get job order + $jo_id = $req->query->get('jo_id'); + $jo = $this->em->getRepository(JobOrder::class)->find($jo_id); + if ($jo == null) { + return new ApiResponse(false, 'No job order found.'); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // check that the customer owns the job order + $jo_cust = $jo->getCustomer(); + if ($jo_cust->getID() != $cust->getID()) { + return new ApiResponse(false, 'Job order was not initiated by customer.'); + } + + $invoice = $jo->getInvoice(); + + // make invoice json data + $data = [ + 'total_price' => (float) $invoice->getTotalPrice(), + 'vat_ex_price' => (float) $invoice->getVATExclusivePrice(), + 'vat' => (float) $invoice->getVAT(), + 'discount' => (float) $invoice->getDiscount(), + 'trade_in' => (float) $invoice->getTradeIn(), + ]; + $items = $invoice->getItems(); + $items_data = []; + foreach ($items as $item) { + $my_data = [ + 'title' => $item->getTitle(), + 'qty' => (int) $item->getQuantity() + 0, + 'price' => (float) $item->getPrice() + 0.0, + ]; + + $item_batt = $item->getBattery(); + if ($item_batt != null) { + $my_data['image_url'] = $this->getBatteryImageURL($req, $item_batt); + } + + $items_data[] = $my_data; + } + + $data['items'] = $items_data; + + /* + // 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, + ]; + } + + $data = [ + 'invoice' => [ + 'discount' => $inv->getDiscount(), + 'trade_in' => $inv->getTradeIn(), + 'total_price' => $inv->getTotalPrice(), + 'vat' => $inv->getVat(), + 'items' => $inv_items, + ], + ]; + + */ + + // response + return new ApiResponse(true, '', $data); + } + + public function cancelJobOrder(Request $req, MQTTClient $mclient) + { + // validate params + $validity = $this->validateRequest($req, [ + 'jo_id', + 'reason', + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get job order + $jo_id = $req->request->get('jo_id'); + $jo = $this->em->getRepository(JobOrder::class)->find($jo_id); + if ($jo == null) { + return new ApiResponse(false, 'No job order found.'); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // check that the customer owns the job order + $jo_cust = $jo->getCustomer(); + if ($jo_cust->getID() != $cust->getID()) { + return new ApiResponse(false, 'Job order was not initiated by customer.'); + } + + // TODO: check job order status, if it's cancellable + $cancel_reason = $req->request->get('reason'); + + $jo->cancel($cancel_reason); + + // add event log + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::CANCEL) + ->setJobOrder($jo); + $this->em->persist($event); + + $this->em->flush(); + + // send mobile app event + $payload = [ + 'event' => 'cancelled', + 'reason' => $cancel_reason, + 'jo_id' => $jo->getID(), + ]; + // $mclient->sendEvent($jo, $payload); + $mclient->sendRiderEvent($jo, $payload); + + // response + return new ApiResponse(); + } + + // we can't use param converter for now because we want to output the proper 404 + public function getJobOrderInfo($id, Request $req, RiderTracker $rt) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // get job order data + $jo = $this->em->getRepository(JobOrder::class)->find($id); + if ($jo == null) { + return new ApiResponse(false, 'No job order information found.'); + } + + // check if job order belongs to customer / user + if ($cust->getID() != $jo->getCustomer()->getID()) { + return new ApiResponse(false, 'No job order information found.'); + } + + // put into job order data array + $jo_data = $this->generateJobOrderData($req, $jo, $rt); + + // response + return new ApiResponse(true, '', [ + 'job_order' => $jo_data, + ]); + } + + public function getJOHistory(Request $req) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // get job orders + $all_jo_data = []; + // $jos = $cust->getJobOrders(); + // get the fulfilled and cancelled job orders, since ongoing jos are not yet part of history + $jos = $this->em->getRepository(JobOrder::class)->findBy([ + 'customer' => $cust, + 'status' => [JOStatus::CANCELLED, JOStatus::FULFILLED] + ], ['date_schedule' => 'DESC']); + foreach ($jos as $jo) { + // NOTE: use generateJobOrderData method, maybe? + $status = $jo->getStatus(); + + $jo_data = [ + 'id' => $jo->getID(), + 'date_create' => $jo->getDateCreate()->format('M d, Y'), + 'service_type' => $jo->getServiceType(), + 'status' => $status, + ]; + + // customer vehicle and warranty + $cv = $jo->getCustomerVehicle(); + + // get latest warranty using plate number + $warranty = $this->findWarranty($cv->getPlateNumber()); + + $jo_data['customer_vehicle'] = [ + 'id' => $cv->getID(), + 'plate_number' => $cv->getPlateNumber(), + 'warranty' => $warranty, + ]; + + // rider + $rider = $jo->getRider(); + + // check if jo has rider rating set to true + $has_rider_rating = $jo->hasRiderRating(); + $rating = 0; + $comment = ''; + if ($rider != null) { + $jo_data['rider'] = $rider->getFullName(); + + // find the rider rating if any + if ($has_rider_rating) { + $jo_rating = $jo->getRating(); + if ($jo_rating != null) { + $rating = $jo_rating->getRating(); + + // get comment + $comment = $jo_rating->getComment(); + } + } + } + + // rider rating for jo + $jo_data['has_rider_rating'] = $has_rider_rating; + $jo_data['rider_rating'] = $rating; + $jo_data['comment'] = $comment; + + // invoice items + $items = []; + $jo_items = $jo->getInvoice()->getItems(); + foreach ($jo_items as $item) { + $items[] = [ + 'id' => $item->getID(), + 'title' => $item->getTitle(), + 'qty' => $item->getQuantity(), + 'price' => $item->getPrice(), + ]; + } + + $jo_data['items'] = $items; + + // dates depending on status + switch ($status) { + case JOStatus::FULFILLED: + if ($jo->getDateFulfill() == null) + $jo_data['date_fulfilled'] = ''; + else + $jo_data['date_fulfilled'] = $jo->getDateFulfill()->format('M d, Y'); + break; + case JOStatus::CANCELLED: + $date_cancel = $jo->getDateCancel(); + if ($date_cancel == null) + $date_cancel = new DateTime(); + $jo_data['date_cancelled'] = $date_cancel->format('M d, Y'); + break; + } + + $all_jo_data[] = $jo_data; + } + + // response + return new ApiResponse(true, '', [ + 'job_orders' => $all_jo_data, + ]); + } + + public function getLatestJobOrder(Request $req, RiderTracker $rt) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // get the latest job order for customer + $latest_jo = $this->em->getRepository(JobOrder::class)->findOneBy(['customer' => $cust], ['id' => 'DESC']); + + $jo_data = null; + if ($latest_jo != null) { + // TODO: clean the response up to just return what is needed + $jo_data = $this->generateLatestJobOrderData($req, $latest_jo, $rt); + } + + // response + return new ApiResponse(true, '', [ + 'latest_job_order' => $jo_data, + ]); + } + + public function getAllOngoingJobOrders(Request $req, RiderTracker $rt) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + $ongoing_jos = $this->getOngoingJobOrders($cust); + + // initialize data + $jo_data = []; + foreach ($ongoing_jos as $jo) { + $jo_data[] = $this->generateJobOrderData($req, $jo, $rt); + } + + // response + return new ApiResponse(true, '', [ + 'ongoing_job_orders' => $jo_data, + ]); + } + + public function getOngoingJobOrderCount(Request $req) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + $ongoing_jos = $this->getOngoingJobOrders($cust); + + // response + return new ApiResponse(true, '', [ + 'ongoing_job_order_count' => count($ongoing_jos), + ]); + } + + public function newRequestJobOrder( + Request $req, + InvoiceGeneratorInterface $ic, + GeofenceTracker $geo, + MapTools $map_tools, + InventoryManager $im, + MQTTClientApiv2 $mclientv2, + FCMSender $fcmclient, + RiderAssignmentHandlerInterface $rah, + PromoLogger $promo_logger, + HubSelector $hub_select, + HubDistributor $hub_dist, + HubFilterLogger $hub_filter_logger, + HubFilteringGeoChecker $hub_geofence, + JobOrderManager $jo_manager + ) { + // validate params + $validity = $this->validateRequest($req, [ + 'service_type', + 'cv_id', + 'long', + 'lat', + 'warranty', + 'mode_of_payment', + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // trade in type + $trade_in_batt = $req->request->get('trade_in_batt'); + $trade_in_type = $req->request->get('trade_in_type', ''); + + // address + $address = $req->request->get('delivery_address', 'Set by mobile application'); + + // instructions + $instructions = $req->request->get('delivery_instructions', ''); + + // landmark + $landmark = $req->request->get('landmark', ''); + + // longitude and latitude + $long = $req->request->get('long'); + $lat = $req->request->get('lat'); + + // NOTE: had to move this up so we can check for promo before geofence + // customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + $is_covered = false; + // check if customer still has promo + if (($cust->getCustomerTag('TAG_CAR_CLUB_OFFICER_PROMO')) || + ($cust->getCustomerTag('TAG_CAR_CLUB_MEMBER_PROMO')) + ) { + // if has customer tag, customer has not availed of promo + $is_covered = true; + } else { + // geofence + $is_covered = $geo->isCovered($long, $lat); + } + + if (!$is_covered) { + // TODO: put geofence error message in config file somewhere + return new ApiResponse(false, $this->getGeoErrorMessage()); + } + + $hub = null; + $hub_id = $req->request->get('hub_id'); + // check if hub_id is -1 which means user clicked Book Now before 5 PM + // but confirmed the order after 5 PM + if ($hub_id == -1) { + return new ApiResponse(false, 'Book Now no longer available.'); + } + if (strlen($hub_id) > 0) + $hub = $this->em->getRepository(Hub::class)->find($hub_id); + + $schedule_date = $req->request->get('date_schedule'); + $slot_id = $req->request->get('slot_id'); + + // process the jo date schedule + $date_schedule = null; + if ((strlen($schedule_date) > 0) && (strlen($slot_id) > 0)) { + $time_schedule = $this->getTimeFromSlot($slot_id); + if (!empty($time_schedule)) { + $s_date = $schedule_date . ' ' . $time_schedule; + $date_schedule = DateTime::createFromFormat('Y-m-d H:i', $s_date); + // error_log($date_schedule->format('Y-m-d H:i')); + } + } + + $advance_order = $req->request->get('flag_advance_order'); + // check for 'false' text + if ($advance_order === false || $advance_order === 0 || $advance_order === '0' || $advance_order == 'false') + $flag_advance_order = false; + else + $flag_advance_order = true; + // $flag_advance_order = $advance_order ? true : false; + + $jo = new JobOrder(); + $jo->setSource(TransactionOrigin::MOBILE_APP) + ->setStatus(JOStatus::PENDING) + ->setDeliveryInstructions('') + ->setTier1Notes('') + ->setTier2Notes('') + ->setDeliveryAddress($address) + ->setTradeInType($trade_in_type) + ->setDeliveryInstructions($instructions) + // TODO: error check for valid mode of payment + ->setModeOfPayment($req->request->get('mode_of_payment')) + ->setAdvanceOrder($flag_advance_order) + ->setStatusAutoAssign(AutoAssignStatus::NOT_ASSIGNED) + ->setLandmark($landmark); + + // customer + // $cust = $this->session->getCustomer(); + // if ($cust == null) + // { + // $res->setError(true) + // ->setErrorMessage('No customer information found'); + // return $res->getReturnResponse(); + // } + + // check if customer has more than one job order already + $flag_cust_new = false; + + $cust_jo_count = $jo_manager->getCustomerJobOrderCount($cust->getID()); + if ($cust_jo_count <= 1) + $flag_cust_new = true; + + $jo->setCustomer($cust); + $jo->setCustNew($flag_cust_new); + + // validate service type + $stype = $req->request->get('service_type'); + if (!ServiceType::validate($stype)) { + return new ApiResponse(false, 'Invalid service type.'); + } + $jo->setServiceType($stype); + + // validate warranty + $warr = $req->request->get('warranty'); + if (!WarrantyClass::validate($warr)) { + return new ApiResponse(false, 'Invalid warranty class.'); + } + $jo->setWarrantyClass($warr); + + // set coordinates + $point = new Point($long, $lat); + $jo->setCoordinates($point); + + // make invoice criteria + $icrit = new InvoiceCriteria(); + $icrit->setServiceType($stype); + + // check promo + $promo_id = $req->request->get('promo_id'); + if (!empty($promo_id)) { + $promo = $this->em->getRepository(Promo::class)->find($promo_id); + if ($promo == null) { + return new ApiResponse(false, 'Invalid promo id.'); + } + + // put in criteria + $icrit->addPromo($promo); + } + + // check customer vehicle + $cv = $this->em->getRepository(CustomerVehicle::class)->find($req->request->get('cv_id')); + if ($cv == null) { + return new ApiResponse(false, 'Invalid customer vehicle id.'); + } + $icrit->setCustomerVehicle($cv); + $jo->setCustomerVehicle($cv); + + // check if customer owns vehicle + if ($cust->getID() != $cv->getCustomer()->getID()) { + return new ApiResponse(false, 'Customer does not own vehicle.'); + } + + // check battery + $batt_id = $req->request->get('batt_id'); + if ($batt_id != null) { + $batt = $this->em->getRepository(Battery::class)->find($batt_id); + if ($batt == null) { + return new ApiResponse(false, 'Invalid battery id.'); + } + } else + $batt = null; + + /* + // put battery in criteria + $icrit->addBattery($batt); + */ + + // check trade-in + // only allow motolite, other, none + switch ($trade_in_type) { + case TradeInType::MOTOLITE: + case TradeInType::OTHER: + break; + + default: + $trade_in_type = ''; + break; + } + + // add the actual battery item first + $icrit->addEntry($batt, null, 1); + + // if we have a trade in, add it as well + if (!empty($trade_in_type) && !empty($trade_in_batt)) { + $ti_batt_obj = $this->em->getRepository(Battery::class)->find($trade_in_batt); + if (!empty($ti_batt_obj)) { + $icrit->addEntry($ti_batt_obj, $trade_in_type, 1); + } + } + + // set taxable + $icrit->setIsTaxable(); + + // set JO source + $icrit->setSource(TransactionOrigin::MOBILE_APP); + + // send to invoice generator + $invoice = $ic->generateInvoice($icrit); + $jo->setInvoice($invoice); + + // assign hub and rider + // check if hub is null + if ($hub == null) { + // TODO: need to factor out the setting of HubCriteria fields + $hub_criteria = new HubCriteria(); + $hub_criteria->setPoint($jo->getCoordinates()); + + // get distance limit for mobile from env + // get value of hub_filter_enable from env + $limit_distance = $_ENV['CUST_DISTANCE_LIMIT']; + $hub_filter_enabled = $_ENV['HUB_FILTER_ENABLE']; + + // set distance limit + $hub_criteria->setLimitDistance($limit_distance); + + // check if hub filter is enabled. If not, use default values + // for the rest of the HubCriteria fields + if ($hub_filter_enabled == 'true') { + // error_log('hub filter is enabled'); + // check if customer location is in hub filter area + if ($hub_geofence->isCovered($long, $lat)) { + // if true, set other values for HubCriteria + // TODO: set this properly, since the other flags + // are on default values + // error_log('Area is covered by hub filtering'); + $hub_criteria->setJoType($jo->getServiceType()) + ->setPaymentMethod($jo->getModeOfPayment()) + ->setRoundRobin(true); + } + } + + // check if batt is null + if ($batt != null) { + // add battery to items + $sku = $batt->getSAPCode(); + if (!empty($sku)) + $hub_criteria->addItem($batt->getSAPCode(), 1); + } + + // get customer id. No JO id at this point + $customer_id = $cust->getID(); + + $hub_criteria->setCustomerId($customer_id); + + // find nearest hubs + $nearest_hubs = $hub_select->find($hub_criteria); + + if (!empty($nearest_hubs)) { + // go through the hub list, find the nearest hub + // with an available rider + // error_log('found nearest hub ' . $nearest_hub->getID()); + foreach ($nearest_hubs as $nearest_hub) { + // check if hub can be auto assigned + // if not, move on to the next hub in the list + if (($nearest_hub['hub']->isHubAutoAssign())) { + // check if hub has riders that can be auto assigned + // if not, move on to the next hub + if (($nearest_hub['hub']->isRiderAutoAssign())) { + $available_riders = $nearest_hub['hub']->getAvailableRiders(); + if (count($available_riders) >= 1) { + $assigned_rider = null; + if (count($available_riders) == 1) { + $assigned_rider = $available_riders[0]; + } else { + // TODO: the setting of riders into an array + // will no longer be necessary when the contents + // of randomizeRider changes + $riders = []; + foreach ($available_riders as $rider) { + $riders[] = $rider; + } + + $assigned_rider = $this->randomizeRider($riders); + } + + $jo->setHub($nearest_hub['hub']); + $jo->setRider($assigned_rider); + $jo->setStatus(JOStatus::ASSIGNED); + $jo->setStatusAutoAssign(AutoAssignStatus::HUB_AND_RIDER_ASSIGNED); + $jo->setDeliveryStatus(DeliveryStatus::RIDER_ASSIGN); + + // set date_assigned for job order + $jo->setDateAssign(new DateTime()); + + $assigned_rider->setAvailable(false); + + // set rider's current job order + $assigned_rider->setCurrentJobOrder($jo); + + // update redis hub_jo_count for hub + $hub_dist->incrementJoCountForHub($nearest_hub['hub']); + + // break out of loop + break; + } else { + // we just create the JO and let admin panel handle the hub assignment + // log hub into hub_filter_log + $hub_filter_logger->logFilteredHub($nearest_hub['hub'], 'no_available_rider', null, $cust->getID()); + // continue to go through list to find hub with an available rider + } + } else { + // TODO: log hub as cannot be auto rider assigned somewhere + // assign hub + // error_log('Rider cannot be auto assigned ' . $nearest_hub['hub']->getID()); + $jo->setHub($nearest_hub['hub']); + $jo->setStatus(JOStatus::RIDER_ASSIGN); + $jo->setStatusAutoAssign(AutoAssignStatus::HUB_ASSIGNED); + + // update redis hub_jo_count for hub + $hub_dist->incrementJoCountForHub($nearest_hub['hub']); + + break; + } + } else { + // TODO: log hub as cannot be auto assigned somewhere + // move to next hub + error_log('Hub cannot be auto-assigned ' . $nearest_hub['hub']->getID()); + continue; + } + } + } + } else { + $jo->setHub($hub); + $jo->setStatus(JOStatus::RIDER_ASSIGN); + $jo->setStatusAutoAssign(AutoAssignStatus::HUB_ASSIGNED); + + if ($date_schedule != null) + $jo->setDateSchedule($date_schedule); + + // update redis hub_jo_count for hub + $hub_dist->incrementJoCountForHub($hub); + } + + $this->em->persist($jo); + $this->em->persist($invoice); + + // add event log for JO + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::CREATE) + ->setJobOrder($jo); + $this->em->persist($event); + + $this->em->flush(); + + // check JO status + if ($jo->getStatus() == JOStatus::ASSIGNED) { + // add event logs for hub and rider assignments + $hub_assign_event = new JOEvent(); + $hub_assign_event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::HUB_ASSIGN) + ->setJobOrder($jo); + + $this->em->persist($hub_assign_event); + + $rider_assign_event = new JOEvent(); + $rider_assign_event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ASSIGN) + ->setJobOrder($jo); + + $this->em->persist($rider_assign_event); + + // user mqtt event + $payload = [ + 'event' => 'outlet_assign', + ]; + $mclientv2->sendEvent($jo, $payload); + $fcmclient->sendJoEvent($jo, "jo_fcm_title_outlet_assign", "jo_fcm_body_outlet_assign"); + + $rah->assignJobOrder($jo, $jo->getRider()); + } + + if ($jo->getStatus() == JOStatus::RIDER_ASSIGN) { + // add event logs for hub assignments + $hub_assign_event = new JOEvent(); + $hub_assign_event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::HUB_ASSIGN) + ->setJobOrder($jo); + + $this->em->persist($hub_assign_event); + + // user mqtt event + $payload = [ + 'event' => 'outlet_assign', + ]; + $mclientv2->sendEvent($jo, $payload); + $fcmclient->sendJoEvent($jo, "jo_fcm_title_outlet_assign", "jo_fcm_body_outlet_assign"); + } + + $this->em->flush(); + + // make invoice json data + $invoice_data = [ + 'total_price' => $invoice->getTotalPrice(), + 'vat_ex_price' => (float) $invoice->getVATExclusivePrice(), + 'vat' => $invoice->getVAT(), + 'discount' => $invoice->getDiscount(), + 'trade_in' => $invoice->getTradeIn(), + ]; + $items = $invoice->getItems(); + $items_data = []; + foreach ($items as $item) { + $items_data[] = [ + 'title' => $item->getTitle(), + 'qty' => $item->getQuantity() + 0, + 'price' => $item->getPrice() + 0.0, + ]; + } + $invoice_data['items'] = $items_data; + + // need to check for customer tag/promo + // check service type + if ($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) { + $customer = $cv->getCustomer(); + $customer_tags = $customer->getCustomerTagObjects(); + if (!empty($customer_tags)) { + foreach ($customer_tags as $customer_tag) { + // TODO: not too comfy with this being hardcoded + if ($customer_tag->getID() == $invoice->getUsedCustomerTagId()) { + // remove associated entity + $customer->removeCustomerTag($customer_tag); + + // log the availment of promo from customer + $created_by = $req->query->get('session_key'); + $cust_id = $jo->getCustomer()->getID(); + $cust_fname = $jo->getCustomer()->getFirstName(); + $cust_lname = $jo->getCustomer()->getLastName(); + $jo_id = $jo->getID(); + $invoice_id = $jo->getInvoice()->getID(); + // TODO: check if we store total price of invoice or just the discounted amount + $amount = $jo->getInvoice()->getTotalPrice(); + $promo_logger->logPromoInfo( + $created_by, + $cust_id, + $cust_fname, + $cust_lname, + $jo_id, + $invoice_id, + $amount + ); + } + } + } + } + + // response + return new ApiResponse(true, '', [ + 'jo_id' => $jo->getID(), + 'invoice' => $invoice_data, + ]); + } + + // TODO: remove later + // mobile app no longer calls this + public function requestJobOrder( + Request $req, + InvoiceGeneratorInterface $ic, + GeofenceTracker $geo, + MapTools $map_tools, + InventoryManager $im, + MQTTClientApiv2 $mclientv2, + FCMSender $fcmclient, + RiderAssignmentHandlerInterface $rah, + PromoLogger $promo_logger, + HubSelector $hub_select, + HubDistributor $hub_dist, + HubFilterLogger $hub_filter_logger, + HubFilteringGeoChecker $hub_geofence, + JobOrderManager $jo_manager + ) { + // validate params + $validity = $this->validateRequest($req, [ + 'service_type', + 'cv_id', + // 'batt_id', + 'long', + 'lat', + 'warranty', + 'mode_of_payment', + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // trade in type + $trade_in_batt = $req->request->get('trade_in_batt'); + $trade_in_type = $req->request->get('trade_in_type', ''); + + // address + $address = $req->request->get('delivery_address', 'Set by mobile application'); + + // instructions + $instructions = $req->request->get('delivery_instructions', ''); + + // longitude and latitude + $long = $req->request->get('long'); + $lat = $req->request->get('lat'); + + // geofence + $is_covered = $geo->isCovered($long, $lat); + if (!$is_covered) { + // TODO: put geofence error message in config file somewhere + return new ApiResponse(false, $this->getGeoErrorMessage()); + } + + $jo = new JobOrder(); + $jo->setSource(TransactionOrigin::MOBILE_APP) + ->setStatus(JOStatus::PENDING) + ->setDeliveryInstructions('') + ->setTier1Notes('') + ->setTier2Notes('') + ->setDeliveryAddress($address) + ->setTradeInType($trade_in_type) + ->setDeliveryInstructions($instructions) + // TODO: error check for valid mode of payment + ->setModeOfPayment($req->request->get('mode_of_payment')); + + // customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // check if customer has more than one job order already + $flag_cust_new = false; + + $cust_jo_count = $jo_manager->getCustomerJobOrderCount($cust->getID()); + if ($cust_jo_count <= 1) + $flag_cust_new = true; + + $jo->setCustomer($cust); + $jo->setCustNew($flag_cust_new); + + // validate service type + $stype = $req->request->get('service_type'); + if (!ServiceType::validate($stype)) { + return new ApiResponse(false, 'Invalid service type.'); + } + $jo->setServiceType($stype); + + // validate warranty + $warr = $req->request->get('warranty'); + if (!WarrantyClass::validate($warr)) { + return new ApiResponse(false, 'Invalid warranty class.'); + } + $jo->setWarrantyClass($warr); + + // set coordinates + $point = new Point($long, $lat); + $jo->setCoordinates($point); + + // make invoice criteria + $icrit = new InvoiceCriteria(); + $icrit->setServiceType($stype); + + // check promo + $promo_id = $req->request->get('promo_id'); + if (!empty($promo_id)) { + $promo = $this->em->getRepository(Promo::class)->find($promo_id); + if ($promo == null) { + return new ApiResponse(false, 'Invalid promo id.'); + } + + // put in criteria + $icrit->addPromo($promo); + } + + // check customer vehicle + $cv = $this->em->getRepository(CustomerVehicle::class)->find($req->request->get('cv_id')); + if ($cv == null) { + return new ApiResponse(false, 'Invalid customer vehicle id.'); + } + $icrit->setCustomerVehicle($cv); + $jo->setCustomerVehicle($cv); + + // check if customer owns vehicle + if ($cust->getID() != $cv->getCustomer()->getID()) { + return new ApiResponse(false, 'Customer does not own vehicle.'); + } + + // check battery + $batt_id = $req->request->get('batt_id'); + if ($batt_id != null) { + $batt = $this->em->getRepository(Battery::class)->find($batt_id); + if ($batt == null) { + return new ApiResponse(false, 'Invalid battery id.'); + } + } else + $batt = null; + + /* + // put battery in criteria + $icrit->addBattery($batt); + */ + + // check trade-in + // only allow motolite, other, none + switch ($trade_in_type) { + case TradeInType::MOTOLITE: + case TradeInType::OTHER: + break; + + default: + $trade_in_type = ''; + break; + } + + // add the actual battery item first + $icrit->addEntry($batt, null, 1); + + // if we have a trade in, add it as well + if (!empty($trade_in_type) && !empty($trade_in_batt)) { + $ti_batt_obj = $this->em->getRepository(Battery::class)->find($trade_in_batt); + if (!empty($ti_batt_obj)) { + $icrit->addEntry($ti_batt_obj, $trade_in_type, 1); + } + } + + // set taxable + $icrit->setIsTaxable(); + + // set JO source + $icrit->setSource(TransactionOrigin::MOBILE_APP); + + // send to invoice generator + $invoice = $ic->generateInvoice($icrit); + $jo->setInvoice($invoice); + + // set more hub criteria fields + $hub_criteria = new HubCriteria(); + $hub_criteria->setPoint($jo->getCoordinates()); + + // get distance limit for mobile from env + $limit_distance = $_ENV['CUST_DISTANCE_LIMIT']; + + // set distance limit + $hub_criteria->setLimitDistance($limit_distance); + + if ($hub_geofence->isCovered($long, $lat)) { + // TODO: set this properly, since the other flags + // are on default values. + // if true, set other values for HubCriteria + // error_log('Area is covered by hub filtering'); + $hub_criteria->setJoType($jo->getServiceType()) + ->setPaymentMethod($jo->getModeOfPayment()) + ->setRoundRobin(true); + } + + // add battery to items + $sku = $batt->getSAPCode(); + if (!empty($sku)) + $hub_criteria->addItem($batt->getSAPCode(), 1); + + // get customer id. No JO id at this point + $customer_id = $cust->getID(); + + $hub_criteria->setCustomerId($customer_id); + + // find nearest hubs + $nearest_hubs = $hub_select->find($hub_criteria); + + $assigned_rider = null; + if (!empty($nearest_hubs)) { + // go through the hub list, find the nearest hub + // with an available rider + //error_log('found nearest hub ' . $nearest_hub->getID()); + foreach ($nearest_hubs as $nearest_hub) { + $available_riders = $nearest_hub['hub']->getAvailableRiders(); + if (count($available_riders) >= 1) { + if (count($available_riders) == 1) { + $assigned_rider = $available_riders[0]; + } else { + // TODO: the setting of riders into an array + // will no longer be necessary when the contents + // of randomizeRider changes + $riders = []; + foreach ($available_riders as $rider) { + $riders[] = $rider; + } + + $assigned_rider = $this->randomizeRider($riders); + } + + $jo->setHub($nearest_hub['hub']); + $jo->setRider($assigned_rider); + $jo->setStatus(JOStatus::ASSIGNED); + $jo->setStatusAutoAssign(AutoAssignStatus::HUB_AND_RIDER_ASSIGNED); + + $assigned_rider->setAvailable(false); + + // set rider's current job order + $assigned_rider->setCurrentJobOrder($jo); + + // update redis hub_jo_count for hub + $hub_dist->incrementJoCountForHub($nearest_hub['hub']); + + // break out of loop + break; + } else { + // log hub into hub_filter_log + $hub_filter_logger->logFilteredHub($nearest_hub['hub'], 'no_available_rider', null, $cust->getID()); + // continue to go through list to find hub with an available rider + } + } + } + + $this->em->persist($jo); + $this->em->persist($invoice); + + // add event log for JO + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::CREATE) + ->setJobOrder($jo); + $this->em->persist($event); + + // check JO status + if ($jo->getStatus() == JOStatus::ASSIGNED) { + // add event logs for hub and rider assignments + $hub_assign_event = new JOEvent(); + $hub_assign_event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::HUB_ASSIGN) + ->setJobOrder($jo); + + $this->em->persist($hub_assign_event); + + $rider_assign_event = new JOEvent(); + $rider_assign_event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ASSIGN) + ->setJobOrder($jo); + + $this->em->persist($rider_assign_event); + + // user mqtt event + $payload = [ + 'event' => 'outlet_assign', + ]; + $mclientv2->sendEvent($jo, $payload); + $fcmclient->sendJoEvent($jo, "jo_fcm_title_outlet_assign", "jo_fcm_body_outlet_assign"); + + $rah->assignJobOrder($jo, $jo->getRider()); + } + + $this->em->flush(); + + // make invoice json data + $invoice_data = [ + 'total_price' => $invoice->getTotalPrice(), + 'vat_ex_price' => (float) $invoice->getVATExclusivePrice(), + 'vat' => $invoice->getVAT(), + 'discount' => $invoice->getDiscount(), + 'trade_in' => $invoice->getTradeIn(), + ]; + $items = $invoice->getItems(); + $items_data = []; + foreach ($items as $item) { + $items_data[] = [ + 'title' => $item->getTitle(), + 'qty' => $item->getQuantity() + 0, + 'price' => $item->getPrice() + 0.0, + ]; + } + $invoice_data['items'] = $items_data; + + // check service type + if ($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) { + $customer = $cv->getCustomer(); + $customer_tags = $customer->getCustomerTagObjects(); + if (!empty($customer_tags)) { + foreach ($customer_tags as $customer_tag) { + // TODO: not too comfy with this being hardcoded + if ($customer_tag->getID() == $invoice->getUsedCustomerTagId()) { + // remove associated entity + $customer->removeCustomerTag($customer_tag); + + // log the availment of promo from customer + $created_by = $req->query->get('session_key'); + $cust_id = $jo->getCustomer()->getID(); + $cust_fname = $jo->getCustomer()->getFirstName(); + $cust_lname = $jo->getCustomer()->getLastName(); + $jo_id = $jo->getID(); + $invoice_id = $jo->getInvoice()->getID(); + // TODO: check if we store total price of invoice or just the discounted amount + $amount = $jo->getInvoice()->getTotalPrice(); + $promo_logger->logPromoInfo( + $created_by, + $cust_id, + $cust_fname, + $cust_lname, + $jo_id, + $invoice_id, + $amount + ); + } + } + } + } + + // response + return new ApiResponse(true, '', [ + 'jo_id' => $jo->getID(), + 'invoice' => $invoice_data, + ]); + } + + // commenting it out. Modify the getJOHistory instead to just get the fulfilled + // and cancelled job orders, since ongoing is not yet part of history + /* + public function getCompletedJobOrders(Request $req, EntityManagerInterface $em, RiderTracker $rt) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) + { + return new ApiResponse(false, 'No customer information found.'); + } + + $completed_jos = $this->getCompletedJOs($cust); + + // initialize data + $jo_data = []; + foreach ($completed_jos as $jo) + { + $jo_data[] = $this->generateJobOrderData($req, $jo, $rt); + } + + // response + return new ApiResponse(true, '', [ + 'completed_job_orders' => $jo_data, + ]); + } + + protected function getCompletedJOs($cust) + { + $completed_jos = $this->em->getRepository(JobOrder::class)->findBy([ + 'customer' => $cust, + 'status' => [JOStatus::CANCELLED, JOStatus::FULFILLED], + ], ['date_schedule' => 'desc']); + + return $completed_jos; + } + */ + + protected function generateJobOrderData($req, $jo, $rt) + { + $status = $jo->getStatus(); + + $dest = $jo->getCoordinates(); + + $jo_data = [ + 'id' => $jo->getID(), + 'date_create' => $jo->getDateCreate()->format('M d, Y'), + 'date_schedule' => $jo->getDateSchedule()->format('M d, Y H:i'), + 'service_type' => $jo->getServiceType(), + 'destination' => [ + 'long' => $dest->getLongitude(), + 'lat' => $dest->getLatitude(), + ], + 'delivery_address' => $jo->getDeliveryAddress(), + 'delivery_instructions' => $jo->getDeliveryInstructions(), + 'landmark' => $jo->getLandmark(), + 'jo_status' => $status, + 'status' => $this->generateAPIRiderStatus($status), + ]; + + // customer vehicle and warranty + $cv = $jo->getCustomerVehicle(); + + // get latest warranty using plate number + $warranty = $this->findWarranty($cv->getPlateNumber()); + + $jo_data['customer_vehicle'] = [ + 'id' => $cv->getID(), + 'plate_number' => $cv->getPlateNumber(), + 'warranty' => $warranty, + ]; + + // customer information + $customer = $jo->getCustomer(); + $jo_data['customer'] = [ + 'first_name' => $customer->getFirstName(), + 'last_name' => $customer->getLastName(), + 'mobile_number' => $customer->getPhoneMobile(), + ]; + + // rider + $rider = $jo->getRider(); + if ($rider != null) { + // default image url + $url_prefix = $req->getSchemeAndHttpHost(); + $image_url = $url_prefix . '/assets/images/user.gif'; + if ($rider->getImageFile() != null) + $image_url = $url_prefix . '/uploads/' . $rider->getImageFile(); + + $coord = $rt->getRiderLocation($rider->getID()); + + $jo_data['rider'] = [ + 'id' => $rider->getID(), + 'name' => $rider->getFullName(), + 'plate_num' => $rider->getPlateNumber(), + 'contact_num' => $rider->getContactNumber(), + 'curr_rating' => $rider->getCurrentRating(), + 'image_url' => $image_url, + 'location' => [ + 'long' => $coord->getLongitude(), + 'lat' => $coord->getLatitude() + ] + ]; + + // check if jo has rider rating set to true + $has_rider_rating = $jo->hasRiderRating(); + $rating = 0; + $comment = ''; + if ($rider != null) { + // find the rider rating if any + if ($has_rider_rating) { + $jo_rating = $jo->getRating(); + if ($jo_rating != null) { + $rating = $jo_rating->getRating(); + + // get comment + $comment = $jo_rating->getComment(); + } + } + } + + // rider rating for jo + $jo_data['has_rider_rating'] = $has_rider_rating; + $jo_data['rider_rating'] = $rating; + $jo_data['comment'] = $comment; + } else { + $jo_data['rider'] = null; + $jo_data['has_rider_rating'] = null; + $jo_data['rider_rating'] = null; + $jo_data['comment'] = null; + } + + // invoice items + $items = []; + $jo_items = $jo->getInvoice()->getItems(); + foreach ($jo_items as $item) { + $items[] = [ + 'id' => $item->getID(), + 'title' => $item->getTitle(), + 'qty' => $item->getQuantity(), + 'price' => $item->getPrice(), + ]; + } + + $jo_data['items'] = $items; + + + // dates depending on status + switch ($status) { + case JOStatus::FULFILLED: + if ($jo->getDateFulfill() == null) + $jo_data['date_fulfilled'] = ''; + else + $jo_data['date_fulfilled'] = $jo->getDateFulfill()->format('M d, Y'); + break; + case JOStatus::CANCELLED: + $date_cancel = $jo->getDateCancel(); + if ($date_cancel == null) + $date_cancel = new DateTime(); + $jo_data['date_cancelled'] = $date_cancel->format('M d, Y'); + break; + } + + return $jo_data; + } + + protected function generateAPIRiderStatus($status) + { + switch ($status) { + case JOStatus::PENDING: + return APIRiderStatus::OUTLET_ASSIGN; + case JOStatus::RIDER_ASSIGN: + return APIRiderStatus::RIDER_ASSIGN; + case JOStatus::ASSIGNED: + case JOStatus::IN_TRANSIT: + case JOStatus::IN_PROGRESS: + return APIRiderStatus::RIDER_PICK_UP; + } + return 'unknown'; + } + + protected function generateLatestJobOrderData($req, $jo, $rt) + { + $status = $jo->getStatus(); + + $dest = $jo->getCoordinates(); + + $jo_data = [ + 'id' => $jo->getID(), + 'date_create' => $jo->getDateCreate()->format('M d, Y'), + 'service_type' => $jo->getServiceType(), + 'destination' => [ + 'long' => $dest->getLongitude(), + 'lat' => $dest->getLatitude(), + ], + 'delivery_address' => $jo->getDeliveryAddress(), + 'delivery_instructions' => $jo->getDeliveryInstructions(), + 'jo_status' => $status, + 'status' => $this->generateAPIRiderStatus($status), + 'landmark' => $jo->getLandmark(), + ]; + + // customer vehicle and warranty + $cv = $jo->getCustomerVehicle(); + + // get latest warranty using plate number + $warranty = $this->findWarranty($cv->getPlateNumber()); + + $jo_data['customer_vehicle'] = [ + 'id' => $cv->getID(), + 'plate_number' => $cv->getPlateNumber(), + 'warranty' => $warranty, + ]; + + // customer information + $customer = $jo->getCustomer(); + $jo_data['customer'] = [ + 'first_name' => $customer->getFirstName(), + 'last_name' => $customer->getLastName(), + 'mobile_number' => $customer->getPhoneMobile(), + ]; + + // rider + $rider = $jo->getRider(); + if ($rider != null) { + // default image url + $url_prefix = $req->getSchemeAndHttpHost(); + $image_url = $url_prefix . '/assets/images/user.gif'; + if ($rider->getImageFile() != null) + $image_url = $url_prefix . '/uploads/' . $rider->getImageFile(); + + $coord = $rt->getRiderLocation($rider->getID()); + + $jo_data['rider'] = [ + 'id' => $rider->getID(), + 'name' => $rider->getFullName(), + 'plate_num' => $rider->getPlateNumber(), + 'contact_num' => $rider->getContactNumber(), + 'image_url' => $image_url, + 'location' => [ + 'long' => $coord->getLongitude(), + 'lat' => $coord->getLatitude() + ] + ]; + } else { + $jo_data['rider'] = null; + } + + // invoice items + $items = []; + $jo_items = $jo->getInvoice()->getItems(); + foreach ($jo_items as $item) { + $items[] = [ + 'id' => $item->getID(), + 'title' => $item->getTitle(), + 'qty' => $item->getQuantity(), + 'price' => $item->getPrice(), + ]; + } + + $jo_data['items'] = $items; + + + // dates depending on status + switch ($status) { + case JOStatus::FULFILLED: + if ($jo->getDateFulfill() == null) + $jo_data['date_fulfilled'] = ''; + else + $jo_data['date_fulfilled'] = $jo->getDateFulfill()->format('M d, Y'); + break; + case JOStatus::CANCELLED: + $date_cancel = $jo->getDateCancel(); + if ($date_cancel == null) + $date_cancel = new DateTime(); + $jo_data['date_cancelled'] = $date_cancel->format('M d, Y'); + break; + } + + return $jo_data; + } + + protected function findWarranty($plate_number) + { + // NOTE: Modify the search for the latest warranty. This seems hacky. + // get latest warranty using plate number + $warranty_results = $this->em->getRepository(Warranty::class)->findBy( + ['plate_number' => $plate_number], + ['date_create' => 'desc'] + ); + + $warr = []; + + // check if warranty_results is empty + if (empty($warranty_results)) { + /* + $res->setError(true) + ->setErrorMessage('No warranty found for plate number'); + return $res->getReturnResponse(); + */ + + return $warr; + } + + // get first entry + $warranty = current($warranty_results); + + // check for null values for battery and date claim and date expire + $batt_model = ''; + $batt_size = ''; + $sap_batt = ''; + $claim_date = ''; + $expiry_date = ''; + + if (!(is_null($warranty->getBatteryModel()))) { + $batt_model = $warranty->getBatteryModel()->getName(); + } + if (!(is_null($warranty->getBatterySize()))) { + $batt_size = $warranty->getBatterySize()->getName(); + } + if (!(is_null($warranty->getSAPBattery()))) { + $sap_batt = $warranty->getSAPBattery()->getID(); + } + if (!(is_null($warranty->getDateClaim()))) { + $claim_date = $warranty->getDateClaim()->format("d M Y"); + } + if (!(is_null($warranty->getDateExpire()))) { + $expiry_date = $warranty->getDateExpire()->format("d M Y"); + } + + $warr[] = [ + 'id' => $warranty->getID(), + 'serial' => $warranty->getSerial(), + 'warranty_class' => $warranty->getWarrantyClass(), + 'plate_number' => $warranty->getPlateNumber(), + 'first_name' => $warranty->getFirstName(), + 'last_name' => $warranty->getLastName(), + 'mobile_number' => $warranty->getMobileNumber(), + 'battery_model' => $batt_model, + 'battery_size' => $batt_size, + 'sap_battery' => $sap_batt, + 'status' => $warranty->getStatus(), + 'date_create' => $warranty->getDateCreate()->format("d M Y g:i A"), + 'date_purchase' => $warranty->getDatePurchase()->format("d M Y"), + 'date_expire' => $expiry_date, + 'date_claim' => $claim_date, + 'claim_from' => $warranty->getClaimedFrom(), + 'is_activated' => $warranty->isActivated() ? 1 : 0, + ]; + + return $warr; + } + + protected function getTimeFromSlot($slot_id) + { + $time_selected = ''; + + switch ($slot_id) { + case '08_09': + $time_selected = AdvanceOrderSlot::_08_09; + break; + case '09_10': + $time_selected = AdvanceOrderSlot::_09_10; + break; + case '10_11': + $time_selected = AdvanceOrderSlot::_10_11; + break; + case '11_12': + $time_selected = AdvanceOrderSlot::_11_12; + break; + case '12_13': + $time_selected = AdvanceOrderSlot::_12_13; + break; + case '13_14': + $time_selected = AdvanceOrderSlot::_13_14; + break; + case '14_15': + $time_selected = AdvanceOrderSlot::_14_15; + break; + case '15_16': + $time_selected = AdvanceOrderSlot::_15_16; + break; + case '16_17': + $time_selected = AdvanceOrderSlot::_16_17; + break; + default: + error_log('Invalid slot id ' . $slot_id); + } + + return $time_selected; + } + + protected function randomizeRider($riders) + { + // TODO: get redis to track the sales per rider per day + // check the time they came in + // for now, randomize the rider + $selected_index = array_rand($riders); + + $selected_rider = $riders[$selected_index]; + + return $selected_rider; + } +} diff --git a/src/Controller/CustomerAppAPI/LocationController.php b/src/Controller/CustomerAppAPI/LocationController.php new file mode 100644 index 00000000..b3d3ecb3 --- /dev/null +++ b/src/Controller/CustomerAppAPI/LocationController.php @@ -0,0 +1,633 @@ +validateRequest($req, [ + 'longitude', + 'latitude', + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + $long = $req->query->get('longitude'); + $lat = $req->query->get('latitude'); + + // NOTE: had to add this for promo tag + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + $is_covered = false; + // check if customer still has promo + if (($cust->getCustomerTag('TAG_CAR_CLUB_OFFICER_PROMO')) || + ($cust->getCustomerTag('TAG_CAR_CLUB_MEMBER_PROMO')) + ) { + // if has customer tag, customer has not availed of promo + $is_covered = true; + } else { + // geofence + $is_covered = $geo->isCovered($long, $lat); + } + + // geofence + // $is_covered = $geo->isCovered($long, $lat); + + $data = [ + 'longitude' => $long, + 'latitude' => $lat, + 'supported' => $is_covered, + ]; + + // check if is_covered is false. If so, we need to set the error part in the response + if (!$is_covered) { + return new ApiResponse(false, $this->getGeoErrorMessage(), $data); + } + + // response + return new ApiResponse(true, '', $data); + } + + public function getNearestHubAndSlots( + Request $req, + MapTools $map_tools + ) { + // validate params + $validity = $this->validateRequest($req, [ + 'longitude', + 'latitude', + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + $coordinates = new Point($req->query->get('longitude'), $req->query->get('latitude')); + + // add checking if customer has a pre-registered hub + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // check if customer has customer tag promo + if (($cust->getCustomerTag('TAG_CAR_CLUB_OFFICER_PROMO')) || + ($cust->getCustomerTag('TAG_CAR_CLUB_MEMBER_PROMO')) + ) { + // if has customer tag, customer has not availed of promo, get the hub where customer is pre-registered + $car_club_cust_hub = $cust->getCarClubCustomerHub(); + if ($car_club_cust_hub != null) { + // need to get the rider slots for the pre-registered hub + $hub = $car_club_cust_hub->getHub(); + $nearest_hub_slots = $this->findAdvanceNearestHubAndSlots($coordinates, $map_tools, $hub); + } else { + $nearest_hub_slots = $this->findAdvanceNearestHubAndSlots($coordinates, $map_tools); + + if (empty($nearest_hub_slots['hub'])) { + return new ApiResponse(false, 'Thank you for reaching out to us. Please expect a call from us and we will assist you with your request. Thank you and stay safe!'); + } + } + } else { + $nearest_hub_slots = $this->findAdvanceNearestHubAndSlots($coordinates, $map_tools); + + if (empty($nearest_hub_slots['hub'])) { + return new ApiResponse(false, 'Thank you for reaching out to us. Please expect a call from us and we will assist you with your request. Thank you and stay safe!'); + } + } + + // response + return new ApiResponse(true, '', [ + 'hub_id' => $nearest_hub_slots['hub']->getID(), + 'hub_slots' => $nearest_hub_slots['slots'], + ]); + } + + public function addLocation(Request $req) + { + // validate params + $validity = $this->validateRequest($req, [ + 'name', + 'address', + 'longitude', + 'latitude', + 'landmark', + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // get the information + $name = $req->request->get('name'); + $address = $req->request->get('address'); + $lng = $req->request->get('longitude'); + $lat = $req->request->get('latitude'); + $landmark = $req->request->get('landmark'); + + $loc_info = [ + 'address' => $address, + 'longitude' => $lng, + 'latitude' => $lat, + 'landmark' => $landmark, + ]; + + // check if customer already has existing metadata + $c_meta = $this->em->getRepository(CustomerMetadata::class)->findOneBy(['customer' => $cust]); + if ($c_meta == null) { + // create new customer meta + $cust_meta = new CustomerMetadata(); + $cust_meta->setCustomer($cust); + $cust_meta->addMetaInfo($name, $loc_info); + + $this->em->persist($cust_meta); + } else { + // limit locations to 6. If more than 6, pop the first one out + // add location to existing customer meta + $meta_count = count($c_meta->getAllMetaInfo()); + + if ($meta_count >= 6) + $c_meta->popMetaInfo(); + + $c_meta->addMetaInfo($name, $loc_info); + } + + $this->em->flush(); + + // response + return new ApiResponse(); + } + + public function removeLocation($id, Request $req) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // find customer metadata and delete entry if present + $cv = $this->em->getRepository(CustomerMetadata::class)->findOneBy(['customer' => $cust]); + if ($cv != null) { + $cv->deleteMetadataInfo(base64_decode($id)); + } + + $this->em->flush(); + + // response + return new ApiResponse(); + } + + public function getLocations(Request $req) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // get the customer meta for customer + $locations = []; + $cust_meta = $this->em->getRepository(CustomerMetadata::class)->findOneBy(['customer' => $cust]); + if ($cust_meta != null) { + $locations = $cust_meta->getAllMetaInfo(); + } + + $data = [ + 'locations' => $locations, + ]; + + // response + return new ApiResponse(true, '', $data); + } + + protected function findAdvanceNearestHubAndSlots(Point $coordinates, MapTools $map_tools, $hub = null) + { + $hub_data = []; + + if ($hub != null) { + // get the slots of hub + $hub_slots = $this->getHubRiderSlots($hub); + + $slots = $hub_slots['slot_data']; + + $hub_data = [ + 'hub' => $hub, + 'slots' => $slots, + ]; + return $hub_data; + } + + // get the nearest 10 hubs + $nearest_hubs_with_distance = []; + $hubs = $map_tools->getClosestOpenHubs($coordinates, 10); + + foreach ($hubs as $hub) { + $nearest_hubs_with_distance[] = $hub; + // TODO: insert checking for branch code here when inventory manager is up + } + + $nearest = null; + $hub_slots = []; + $slot_found = false; + // find the nearest hub + if (!empty($nearest_hubs_with_distance)) { + // get slots of nearest hub right after getting nearest hub. + // then check if hub has available slots. If not, get next nearest hub. + foreach ($nearest_hubs_with_distance as $nhd) { + if (empty($nearest)) { + // get the slots for the hub to check if hub is available for assignment + $hub_slots = $this->getHubRiderSlots($nhd['hub']); + + $flag_hub_available = $hub_slots['flag_hub_available']; + if ($flag_hub_available == true) { + $nearest = $nhd; + } + } else { + if ($nhd['distance'] < $nearest['distance']) { + // get the slots for nearest which is nhd right now + $hub_slots = $this->getHubRiderSlots($nhd['hub']); + + $flag_hub_available = $hub_slots['flag_hub_available']; + + // if hub is available, set hub to nearest + if ($flag_hub_available == true) { + $nearest = $nhd; + } + } + } + } + } + + if ($nearest != null) { + // set hub data to what is in nearest + $hub_data = [ + 'hub' => $nearest['hub'], + 'slots' => $hub_slots['slot_data'], + ]; + } + + return $hub_data; + } + + protected function getHubRiderSlots(Hub $hub) + { + // check hub's advance orders for the day + + /* + // get number of advance orders for the next day if request came in before midnight + // or for current day if request came in after midnight + // check request_time + $request_time = time(); + $midnight = strtotime('00:00'); + */ + $start_date = new DateTime(); + $end_date = new DateTime(); + + // to keep things simple, just start on next day regardless of midnight timer + $start_date->add(new DateInterval('P1D')); + $end_date->add(new DateInterval('P3D')); + + /* + if ($request_time < $midnight) + { + // add +1 to start date to get the next day + // add +3 to date to end date to get the advance orders for the next three days + $start_date->add(new DateInterval('P1D')); + $end_date->add(new DateInterval('P1D')); + } + $end_date->add(new DateInterval('P2D')); + */ + + // set time bounds for the start and end date + $start_date->setTime(0, 1); + $end_date->setTime(23, 59); + + // NOTE: get advance orders via query + // get JOs assigned to hub that are advance orders and scheduled for the next three days with + // for hub assignment status + $query = $this->em->createQuery('select jo from App\Entity\JobOrder jo where jo.hub = :hub and jo.flag_advance = true and + jo.date_schedule >= :date_start and jo.date_schedule <= :date_end and jo.status != :status_cancelled + and jo.status != :status_fulfilled'); + $jos_advance_orders = $query->setParameters([ + 'hub' => $hub, + 'date_start' => $start_date, + 'date_end' => $end_date, + 'status_cancelled' => JOStatus::CANCELLED, + 'status_fulfilled' => JOStatus::FULFILLED, + ]) + ->getResult(); + // check request_time + + // define slots + $slots = [ + '08_09' => '8:00 AM', + '09_10' => '9:00 AM', + '10_11' => '10:00 AM', + '11_12' => '11:00 AM', + '12_13' => '12:00 PM', + '13_14' => '1:00 PM', + '14_15' => '2:00 PM', + '15_16' => '3:00 PM', + '16_17' => '4:00 PM', + ]; + + + // get the dates for the next three days + $first_date = $start_date->format('Y-m-d'); + $second_date = $start_date->add(new DateInterval('P1D')); + $sec_date = $second_date->format('Y-m-d'); + $third_date = $end_date->format('Y-m-d'); + + // define days + $days = [ + $first_date => $first_date, + $sec_date => $sec_date, + $third_date => $third_date, + ]; + + // initialize hub rider slots + $hub_rider_slots = []; + foreach ($days as $day) { + foreach ($slots as $slot_key => $slot) { + $hub_rider_slots[$day][$slot_key] = $hub->getRiderSlots(); + } + } + + // check each JO's date_schedule, decrement rider_slots if date schedule falls in that slot + foreach ($jos_advance_orders as $jo) { + // get date key + $date_sched = $jo->getDateSchedule(); + $date_string = $date_sched->format('Y-m-d'); + $hour = $date_sched->format('H'); + $slot_id = sprintf('%02d_%02d', $hour, $hour + 1); + + // error_log("SLOT - $date_string - $slot_id"); + + // decrement rider slot + if (isset($hub_rider_slots[$date_string][$slot_id])) + $hub_rider_slots[$date_string][$slot_id]--; + + // check if it goes through next slot (10 min allowance) + $mins = $date_sched->format('i'); + if ($mins > 10) { + $next_slot_id = sprintf('%02d_%02d', $hour + 1, $hour + 2); + // error_log("NEXT SLOT - $date_string - $next_slot_id"); + // decrement rider slot + if (isset($hub_rider_slots[$date_string][$next_slot_id])) + $hub_rider_slots[$date_string][$next_slot_id]--; + } + } + + // error_log(print_r($hub_rider_slots, true)); + + $hub_slots = $this->generateHubSlots($hub_rider_slots, $slots); + + // error_log(print_r($hub_slots, true)); + + return $hub_slots; + } + + protected function generateHubSlots($rider_slots, $slots) + { + $data = []; + $total_rslots = 0; + $total_unavailable_rslots = 0; + foreach ($rider_slots as $day_id => $rslot) { + $data[$day_id] = []; + + foreach ($rslot as $slot_id => $avail_slots) { + // increment total rider slots + $total_rslots++; + + $slot_data = [ + 'id' => $slot_id, + 'label' => $slots[$slot_id], + 'available' => true, + ]; + + // mark unavailable ones + if ($avail_slots <= 0) { // increment total number of unavailable slots + $total_unavailable_rslots++; + $slot_data['available'] = false; + } + + // add to day data + $data[$day_id][] = $slot_data; + } + } + + // check if hub has available slots + $hub_available = true; + // error_log('total rider slots ' . $total_rslots); + // error_log('total unavailable slots ' . $total_unavailable_rslots); + if ($total_rslots == $total_unavailable_rslots) { + // error_log('hub has no available slots'); + $hub_available = false; + } + + $hs_data = [ + 'flag_hub_available' => $hub_available, + 'slot_data' => $data, + ]; + + return $hs_data; + } + + protected function findNearestHub($jo, MapTools $map_tools) + { + // get the nearest 10 hubs + $selected_hub = null; + $hubs = $map_tools->getClosestOpenHubs($jo->getCoordinates(), 10, date("H:i:s")); + + $nearest_hubs_with_distance = []; + $nearest_branch_codes = []; + foreach ($hubs as $hub) { + $nearest_hubs_with_distance[] = $hub; + //if (!empty($hub['hub']->getBranchCode())) + // $nearest_branch_codes[] = $hub['hub']->getBranchCode(); + } + + // check if nearest hubs have branch codes + //if (count($nearest_branch_codes) == 0) + // return $selected_hub; + + // assume all 10 have stock + // find the nearest hub with available riders + $nearest = null; + foreach ($nearest_hubs_with_distance as $nhd) { + // get number of available riders + $count_riders = count($nhd['hub']->getAvailableRiders()); + + // get number of advance orders in the next 3 hours + $time_now = new DateTime(); + $date_end = new DateTime(); + $date_end->add(new DateInterval('PT2H')); + + // NOTE: get advance orders via query + // get JOs assigned to hub that are advance orders and scheduled within X hours with + // for rider assignment status + $query = $this->em->createQuery('select count(jo) from App\Entity\JobOrder jo where jo.hub = :hub and jo.flag_advance = true and jo.date_schedule <= :date_end and jo.status = :status'); + $count_advance_orders = $query->setParameters([ + 'hub' => $nhd['hub'], + 'date_end' => $date_end, + 'status' => JOStatus::RIDER_ASSIGN, + ]) + ->setMaxResults(1) + ->getSingleScalarResult(); + + // error_log('HUB - ' . $nhd['hub']->getID()); + // error_log('RIDER COUNT - ' . $count_riders); + // error_log('ADVANCE ORDER COUNT - ' . $count_advance_orders); + + // if (count($nhd['hub']->getAvailableRiders()) > 0) + // if we have more riders than we have advance orders + if ($count_riders - $count_advance_orders > 0) { + if (empty($nearest)) + $nearest = $nhd; + else { + if ($nhd['distance'] < $nearest['distance']) + $nearest = $nhd; + } + } + } + + $selected_hub = $nearest['hub']; + + return $selected_hub; + } + + protected function findNearestHubWithInventory( + $jo, + Battery $batt, + MapTools $map_tools, + InventoryManager $im + ) { + // get the nearest 10 hubs + $selected_hub = null; + $hubs = $map_tools->getClosestOpenHubs($jo->getCoordinates(), 10, date("H:i:s")); + + $nearest_hubs_with_distance = []; + $nearest_branch_codes = []; + foreach ($hubs as $hub) { + $nearest_hubs_with_distance[] = $hub; + //if (!empty($hub['hub']->getBranchCode())) + // $nearest_branch_codes[] = $hub['hub']->getBranchCode(); + } + + // check if nearest hubs have branch codes + //if (count($nearest_branch_codes) == 0) + // return $selected_hub; + + // assume all 10 have stock + // find the nearest hub with available riders + $nearest = null; + foreach ($nearest_hubs_with_distance as $nhd) { + if (count($nhd['hub']->getAvailableRiders()) > 0) { + if (empty($nearest)) + $nearest = $nhd; + else { + if ($nhd['distance'] < $nearest['distance']) + $nearest = $nhd; + } + } + } + + $selected_hub = $nearest['hub']; + + // uncomment this snippet when inventory check becomes active + // get battery sku + /* + if ($batt != null) + { + $skus[] = $batt->getSAPCode(); + + // api call to check inventory + // pass the list of branch codes of nearest hubs and the skus + // go through returned list of branch codes + // bypass inventory check for now + // $hubs_with_inventory = $im->getBranchesInventory($nearest_branch_codes, $skus); + if (!empty($hubs_with_inventory)) + { + $nearest = []; + $flag_hub_found = false; + foreach ($hubs_with_inventory as $hub_with_inventory) + { + // find hub according to branch code + $found_hub = $this->em->getRepository(Hub::class)->findOneBy(['branch_code' => $hub_with_inventory['BranchCode']]); + if ($found_hub != null) + { + // check rider availability + if (count($found_hub->getAvailableRiders()) > 0) + { + // check against nearest hubs with distance + foreach ($nearest_hubs_with_distance as $nhd) + { + // get distance of hub from location, compare with $nearest. if less, replace nearest + if ($found_hub->getID() == $nhd['hub']->getID()) + { + if (empty($nearest)) + { + $nearest = $nhd; + $flag_hub_found = true; + } + else + { + if ($nhd['distance'] < $nearest['distance']) + { + $nearest = $nhd; + $flag_hub_found = true; + } + } + } + } + } + } + } + $selected_hub = $nearest['hub']; + } + } */ + return $selected_hub; + } +} diff --git a/src/Controller/CustomerAppAPI/MotoliteEventController.php b/src/Controller/CustomerAppAPI/MotoliteEventController.php new file mode 100644 index 00000000..9565f656 --- /dev/null +++ b/src/Controller/CustomerAppAPI/MotoliteEventController.php @@ -0,0 +1,51 @@ +validateRequest($req, [ + 'limit', + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + $limit = $req->query->get('limit'); + + // get all events + $results = $this->em->getRepository(MotoliteEvent::class) + ->findBy([], ['event_time' => 'asc'], $limit); + + if (empty($results)) { + return new ApiResponse(false, 'No events available.'); + } + + $events = []; + foreach ($results as $result) { + $events[] = [ + 'id' => $result->getID(), + 'name' => $result->getName(), + 'event_type' => MotoliteEventType::getName($result->getEventType()), + 'event_time' => $result->getEventTime()->format("d M Y g:i A"), + 'url' => $result->getUrl(), + 'image_file' => $result->getImageFile(), + ]; + } + + // response + return new ApiResponse(true, '', [ + 'events' => $events, + ]); + } +} diff --git a/src/Controller/CustomerAppAPI/PartnerController.php b/src/Controller/CustomerAppAPI/PartnerController.php new file mode 100644 index 00000000..77636b86 --- /dev/null +++ b/src/Controller/CustomerAppAPI/PartnerController.php @@ -0,0 +1,164 @@ +validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get partner + $partner = $this->em->getRepository(Partner::class)->findOneBy(['id' => $pid]); + if ($partner == null) { + return new ApiResponse(false, 'No partner found.'); + } + + // get reviews for partner + $reviews = $this->em->getRepository(Review::class)->findBy(['partner' => $partner]); + + // get average rating for all reviews + $average_rating = 0; + if (!empty($reviews)) { + $rating = 0; + foreach ($reviews as $review) { + $rating = $rating + $review->getRating(); + } + + $average_rating = $rating / sizeof($reviews); + } + + $data = []; + $data['partner'] = [ + 'id' => $partner->getID(), + 'name' => $partner->getName(), + 'branch' => $partner->getBranch(), + 'address' => $partner->getAddress(), + 'contact_nums' => $partner->getContactNumbers(), + 'time_open' => $partner->getTimeOpen()->format("g:i A"), + 'time_close' => $partner->getTimeClose()->format("g:i A"), + 'longitude' => $partner->getCoordinates()->getLongitude(), + 'latitude' => $partner->getCoordinates()->getLatitude(), + 'average_rating' => $average_rating, + ]; + + // response + return new ApiResponse(true, '', $data); + } + + public function getClosestPartners(Request $req) + { + // validate params + $validity = $this->validateRequest($req, [ + 'longitude', + 'latitude', + 'service_id', + 'limit', + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + $long = $req->query->get('longitude'); + $lat = $req->query->get('latitude'); + $service_id = $req->query->get('service_id'); + $limit = $req->query->get('limit'); + + // get partners within range + $query = $this->em->createQuery('SELECT p, st_distance(p.coordinates, point(:lng, :lat)) as dist FROM App\Entity\Partner p + JOIN App\Entity\Service s where s.id = :service_id ORDER BY dist') + ->setParameter('lat', $lat) + ->setParameter('lng', $long) + ->setParameter('service_id', $service_id); + + $query->setMaxResults($limit); + $result = $query->getResult(); + + $data = []; + $partners = []; + foreach ($result as $row) { + // get all the reviews for each partner and average the ratings + $partner_id = $row[0]->getID(); + $partner = $this->em->getRepository(Partner::class)->find($partner_id); + $partner_reviews = $this->em->getRepository(Review::class)->findBy(['partner' => $partner]); + + $average_rating = 0; + if (count($partner_reviews) > 0) { + $rating = 0; + foreach ($partner_reviews as $review) { + $rating = $rating + $review->getRating(); + } + + $average_rating = $rating / sizeof($partner_reviews); + } + + $partners[] = [ + 'id' => $row[0]->getID(), + 'name' => $row[0]->getName(), + 'branch' => $row[0]->getBranch(), + 'address' => $row[0]->getAddress(), + 'contact_nums' => $row[0]->getContactNumbers(), + 'time_open' => $row[0]->getTimeOpen()->format("g:i A"), + 'time_close' => $row[0]->getTimeClose()->format("g:i A"), + 'longitude' => $row[0]->getCoordinates()->getLongitude(), + 'latitude' => $row[0]->getCoordinates()->getLatitude(), + 'db_distance' => $row['dist'], + 'rating' => $average_rating, + ]; + } + + $data['partners'] = $partners; + + // response + return new ApiResponse(true, '', $data); + } + + public function reviewPartner($pid, Request $req) + { + // validate params + $validity = $this->validateRequest($req, [ + 'rating', + 'message', + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + $rating = $req->request->get('rating'); + $msg = $req->request->get('message'); + + // TODO: check rating if 1 - 5 + + // check if partner exists + $partner = $this->em->getRepository(Partner::class)->find($pid); + if ($partner == null) { + return new ApiResponse(false, 'No partner found.'); + } + + $rev = new Review(); + $rev->setRating($rating) + ->setMessage($msg) + ->setPartner($partner) + ->setCustomerSession($this->session); // NOTE: using new customer session entity + + // save to db + $this->em->persist($rev); + $this->em->flush(); + + // response + return new ApiResponse(); + } +} diff --git a/src/Controller/CustomerAppAPI/PrivacyController.php b/src/Controller/CustomerAppAPI/PrivacyController.php new file mode 100644 index 00000000..005ba097 --- /dev/null +++ b/src/Controller/CustomerAppAPI/PrivacyController.php @@ -0,0 +1,67 @@ +validateRequest($req, [ + 'priv_third_party', + // 'priv_promo', + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // set privacy settings + $priv_promo = $req->request->get('priv_promo', false); + $priv_third_party = $req->request->get('priv_third_party'); + $cust->setPrivacyThirdParty($priv_third_party) + ->setPrivacyPromo($priv_promo); + + // get the policy ids from .env + $policy_promo_id = $_ENV['POLICY_PROMO']; + $policy_third_party_id = $_ENV['POLICY_THIRD_PARTY']; + + // check if privacy settings are true + // if true, set the private policy for the customer + if ($priv_promo) { + // find the promo policy + $policy = $this->em->getRepository(PrivacyPolicy::class)->find($policy_promo_id); + + // set policy id + if ($policy != null) { + $cust->setPrivacyPolicyPromo($policy); + } + } + + if ($priv_third_party) { + // find the third party policy + $policy = $this->em->getRepository(PrivacyPolicy::class)->find($policy_third_party_id); + + // set policy id + if ($policy != null) { + $cust->setPrivacyPolicyThirdParty($policy); + } + } + + $this->em->flush(); + + // response + return new ApiResponse(); + } +} diff --git a/src/Controller/CustomerAppAPI/PromoController.php b/src/Controller/CustomerAppAPI/PromoController.php new file mode 100644 index 00000000..7a5d9360 --- /dev/null +++ b/src/Controller/CustomerAppAPI/PromoController.php @@ -0,0 +1,22 @@ +validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // response + return new ApiResponse(); + } +} diff --git a/src/Controller/CustomerAppAPI/ReviewTagController.php b/src/Controller/CustomerAppAPI/ReviewTagController.php new file mode 100644 index 00000000..76f68bef --- /dev/null +++ b/src/Controller/CustomerAppAPI/ReviewTagController.php @@ -0,0 +1,46 @@ +getTags($req, "partner"); + } + + public function getRiderReviewTags(Request $req) + { + return $this->getTags($req, "rider"); + } + + protected function getTags(Request $req, $tag_type) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get manufacturer list + $tags = $this->em->getRepository(ReviewTag::class)->findBy(['type' => $tag_type], ['name' => 'asc']); + $tag_list = []; + + foreach ($tags as $tag) { + $tag_list[] = [ + 'id' => $tag->getID(), + 'name' => $tag->getName(), + ]; + } + + return new ApiResponse(true, '', [ + 'tags' => $tag_list, + ]); + } +} diff --git a/src/Controller/CustomerAppAPI/RiderController.php b/src/Controller/CustomerAppAPI/RiderController.php new file mode 100644 index 00000000..96557a54 --- /dev/null +++ b/src/Controller/CustomerAppAPI/RiderController.php @@ -0,0 +1,241 @@ +validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // check if we have an ongoing job order + /* + $ongoing_jos = $em->getRepository(JobOrder::class)->findBy([ + 'customer' => $cust, + 'status' => [JOStatus::PENDING, JOStatus::RIDER_ASSIGN, JOStatus::IN_TRANSIT, JOStatus::ASSIGNED, JOStatus::IN_PROGRESS], + ]); + */ + $ongoing_jos = $this->getOngoingJobOrders($cust); + + // $res->setData(['here' => count($ongoing_jos)]); + // return $res->getReturnResponse(); + if (count($ongoing_jos) <= 0) { + try { + // check if the latest fulfilled jo they have needs rider rating + $query = $this->em->createQuery('select jo from App\Entity\JobOrder jo where jo.customer = :cust and jo.status = :status order by jo.date_fulfill desc'); + $fulfill_jo = $query->setParameters([ + 'cust' => $cust, + 'status' => JOStatus::FULFILLED, + ]) + ->setMaxResults(1) + ->getSingleResult(); + } catch (Exception $e) { + // response + return new ApiResponse(true, '', [ + 'status' => APIRiderStatus::NO_PENDING_JO, + ]); + } + + // we got a recently fulfilled job order + if ($fulfill_jo) { + // check if the rider has been rated + if (!$fulfill_jo->hasRiderRating()) { + $dest = $fulfill_jo->getCoordinates(); + + $data = [ + 'jo_id' => $fulfill_jo->getID(), + 'service_type' => $fulfill_jo->getServiceType(), + 'destination' => [ + 'long' => $dest->getLongitude(), + 'lat' => $dest->getLatitude(), + ], + 'delivery_address' => $fulfill_jo->getDeliveryAddress(), + 'delivery_instructions' => $fulfill_jo->getDeliveryInstructions(), + ]; + + $rider = $fulfill_jo->getRider(); + + // default image url + $url_prefix = $req->getSchemeAndHttpHost(); + $image_url = $url_prefix . '/assets/images/user.gif'; + if ($rider->getImageFile() != null) + $image_url = $url_prefix . '/uploads/' . $rider->getImageFile(); + + $data['status'] = APIRiderStatus::RIDER_RATING; + // default rider location to hub + $data['rider'] = [ + 'id' => $rider->getID(), + 'name' => $rider->getFullName(), + 'plate_num' => $rider->getPlateNumber(), + 'contact_num' => $rider->getContactNumber(), + 'image_url' => $image_url, + ]; + + // response + return new ApiResponse(true, '', $data); + } + } + + // response, no pending + return new ApiResponse(true, '', [ + 'status' => APIRiderStatus::NO_PENDING_JO, + ]); + } + + // get first jo that's pending + $jo = $ongoing_jos[0]; + $dest = $jo->getCoordinates(); + + $data = [ + 'jo_id' => $jo->getID(), + 'service_type' => $jo->getServiceType(), + 'destination' => [ + 'long' => $dest->getLongitude(), + 'lat' => $dest->getLatitude(), + ], + 'delivery_address' => $jo->getDeliveryAddress(), + 'delivery_instructions' => $jo->getDeliveryInstructions(), + ]; + + switch ($jo->getStatus()) { + case JOStatus::PENDING: + $data['status'] = APIRiderStatus::OUTLET_ASSIGN; + break; + case JOStatus::RIDER_ASSIGN: + $data['status'] = APIRiderStatus::RIDER_ASSIGN; + break; + case JOStatus::ASSIGNED: + case JOStatus::IN_TRANSIT: + case JOStatus::IN_PROGRESS: + $rider = $jo->getRider(); + // get rider coordinates from redis + $coord = $rt->getRiderLocation($rider->getID()); + + // default image url + $url_prefix = $req->getSchemeAndHttpHost(); + $image_url = $url_prefix . '/assets/images/user.gif'; + if ($rider->getImageFile() != null) + $image_url = $url_prefix . '/uploads/' . $rider->getImageFile(); + + $data['status'] = APIRiderStatus::RIDER_PICK_UP; + // TODO: fix this to actual location of rider + // default rider location to hub + $data['rider'] = [ + 'id' => $rider->getID(), + 'name' => $rider->getFullName(), + 'plate_num' => $rider->getPlateNumber(), + 'contact_num' => $rider->getContactNumber(), + 'image_url' => $image_url, + 'location' => [ + 'long' => $coord->getLongitude(), + 'lat' => $coord->getLatitude() + ] + ]; + + break; + } + + // response + return new ApiResponse(true, '', $data); + } + + public function addRiderRating(Request $req) + { + // validate params + $validity = $this->validateRequest($req, [ + 'jo_id', + 'rating', + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // get job order + $jo_id = $req->request->get('jo_id'); + $jo = $this->em->getRepository(JobOrder::class)->find($jo_id); + if ($jo == null) { + return new ApiResponse(false, 'No job order found.'); + } + + // get rider + $rider = $jo->getRider(); + if ($rider == null) { + return new ApiResponse(false, 'No rider found.'); + } + + // check that the customer owns the job order + $jo_cust = $jo->getCustomer(); + if ($jo_cust->getID() != $cust->getID()) { + return new ApiResponse(false, 'Job order was not initiated by customer.'); + } + + // TODO: check job order status, if it's complete + + // add rider rating + $rating_num = $req->request->get('rating', -1); + + // if rating is -1 + if ($rating_num == -1) { + $jo->setHasRiderRating(); + $this->em->flush(); + + // response + return new ApiResponse(); + } + + + $rating = new RiderRating(); + $rating->setRider($rider) + ->setCustomer($cust) + ->setJobOrder($jo) + ->setRating($rating_num); + + // rider rating comment + $comment = $req->request->get('comment'); + if (!empty($comment)) + $rating->setComment($comment); + + // mark jo as rider rated already + $jo->setHasRiderRating(); + + $this->em->persist($rating); + $this->em->flush(); + + // TODO: preliminary rating computation on the entity for now + $rider->updateRatingAverage(); + $this->em->persist($rider); + $this->em->flush(); + + // response + return new ApiResponse(); + } +} diff --git a/src/Controller/CustomerAppAPI/ScheduleController.php b/src/Controller/CustomerAppAPI/ScheduleController.php new file mode 100644 index 00000000..6edf8a68 --- /dev/null +++ b/src/Controller/CustomerAppAPI/ScheduleController.php @@ -0,0 +1,61 @@ +validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + $schedule_choice = true; + + // remove the time check after ECQ. This will then always return true + // get current time + $current_datetime = new DateTime(); + //$current_datetime = DateTime::createFromFormat('Y-m-d H:i', '2020-04-30 17:01'); + + // get the hour + $hour = $current_datetime->format('G'); + + // commenting out the time check since we can now book 24/7 + // this will get uncommented out if and when ECQ will kick in + //if (($hour < 8) || ($hour > 16)) + // $schedule_choice = false; + + // add checking if customer has a pre-registered hub + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + // check if customer has customer tag promo + if (($cust->getCustomerTag('TAG_CAR_CLUB_OFFICER_PROMO')) || + ($cust->getCustomerTag('TAG_CAR_CLUB_MEMBER_PROMO')) + ) { + // if has customer tag, customer has not availed of promo, get the hub where customer is pre-registered + $car_club_hub = $cust->getCarClubCustomerHub(); + if ($car_club_hub != null) { + $schedule_choice = false; + } + } + + // schedule_choice will always be true aka customer can opt to + // Book Now or Schedule Order EXCEPT if customer has customer tag promo + // or ECQ comes back + + // response + return new ApiResponse(true, '', [ + 'display_schedule_choice' => $schedule_choice, + ]); + } +} diff --git a/src/Controller/CustomerAppAPI/ServiceController.php b/src/Controller/CustomerAppAPI/ServiceController.php new file mode 100644 index 00000000..5e8abfdb --- /dev/null +++ b/src/Controller/CustomerAppAPI/ServiceController.php @@ -0,0 +1,60 @@ +validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // services + $results = $this->em->getRepository(Service::class)->findAll(); + if (empty($results)) { + return new ApiResponse(false, 'No services available.'); + } + + $services = []; + foreach ($results as $result) { + /* + // get partners + $partners = []; + $service_partners = $result->getPartners(); + foreach($service_partners as $sp) + { + $partners[] = [ + 'id' => $sp->getID(), + 'name' => $sp->getName(), + 'branch' => $sp->getBranch(), + 'address' => $sp->getAddress(), + 'contact_nums' => $sp->getContactNumbers(), + 'time_open' => $sp->getTimeOpen()->format("g:i A"), + 'time_close' => $sp->getTimeClose()->format("g:i A"), + ]; + } + */ + + $services[] = [ + 'id' => $result->getID(), + 'name' => $result->getName(), + 'icon' => $result->getIcon(), + // 'partners' => $partners, + ]; + } + + // response + return new ApiResponse(true, '', [ + 'services' => $services, + ]); + } +} diff --git a/src/Controller/CustomerAppAPI/VehicleController.php b/src/Controller/CustomerAppAPI/VehicleController.php new file mode 100644 index 00000000..bc1e29c2 --- /dev/null +++ b/src/Controller/CustomerAppAPI/VehicleController.php @@ -0,0 +1,564 @@ +validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get manufacturer list + $mfgs = $this->em->getRepository(VehicleManufacturer::class)->findBy(['flag_mobile' => true], ['name' => 'asc']); + $mfg_list = []; + + foreach ($mfgs as $mfg) { + $mfg_list[] = [ + 'id' => $mfg->getID(), + 'name' => $mfg->getName(), + ]; + } + + return new ApiResponse(true, '', [ + 'manufacturers' => $mfg_list, + ]); + } + + public function listVehicleMakes(Request $req, $mfg_id) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get manufacturer + $mfg = $this->em->getRepository(VehicleManufacturer::class)->find($mfg_id); + if ($mfg == null) { + // response + return new ApiResponse(false, 'Invalid vehicle manufacturer id.'); + } + + // get makes + $vehicles = $this->em->getRepository(Vehicle::class)->findBy( + [ + 'flag_mobile' => true, + 'manufacturer' => $mfg_id, + ], + ['make' => 'asc'] + ); + // $vehicles = $mfg->getVehicles(); + $vlist = []; + foreach ($vehicles as $v) { + $vlist[] = [ + 'id' => $v->getID(), + 'make' => trim($v->getMake() . ' ' . $v->getModelYearFormatted(false)), + // 'make' => $v->getMake() . ' ' . $v->getModelYearFrom() . '-' . $v->getModelYearTo(), + ]; + } + + // response + return new ApiResponse(true, '', [ + 'manufacturer' => [ + 'id' => $mfg->getID(), + 'name' => $mfg->getName(), + ], + 'makes' => $vlist, + ]); + } + + public function addVehicle(Request $req) + { + // check requirements + $validity = $this->checkVehicleRequirements($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // customer vehicle + $cv = new CustomerVehicle(); + + // set object + $res = $this->setCustomerVehicleObject($req, $cv); + if (!$res['success']) { + return new ApiResponse(false, $res['error']); + } + + // response + return new ApiResponse(true, '', [ + 'cv_id' => $res['cv_id'], + ]); + + } + + public function getVehicle(Request $req, $id, PayMongoConnector $paymongo) + { + // check requirements + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get customer vehicle + $cv = $this->em->getRepository(CustomerVehicle::class)->find($id); + + // check if it exists + if ($cv == null) { + return new ApiResponse(false, 'Vehicle does not exist.'); + } + + // check if it's owned by customer + if ($cv->getCustomer()->getID() != $this->session->getCustomer()->getID()) { + return new ApiResponse(false, 'Invalid vehicle.'); + } + + // response + return new ApiResponse(true, '', [ + 'vehicle' => $this->generateVehicleInfo($cv, true, $paymongo), + ]); + } + + public function updateVehicle(Request $req, $id) + { + // check requirements + $validity = $this->checkVehicleRequirements($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get customer vehicle + $cv = $this->em->getRepository(CustomerVehicle::class)->find($id); + + // check if it exists + if ($cv == null) { + return new ApiResponse(false, 'Vehicle does not exist.'); + } + + // check if it's owned by customer + if ($cv->getCustomer()->getID() != $this->session->getCustomer()->getID()) { + return new ApiResponse(false, 'Invalid vehicle.'); + } + + // set object + $res = $this->setCustomerVehicleObject($req, $cv); + if (!$res['success']) { + return new ApiResponse(false, $res['error']); + } + + // response + return new ApiResponse(true, '', [ + 'cv_id' => $res['cv_id'], + ]); + } + + public function getTradeInEstimate(Request $req, $id) + { + // check requirements + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get customer vehicle + $cv = $this->em->getRepository(CustomerVehicle::class)->find($id); + + // check if it exists + if ($cv == null) { + return new ApiResponse(false, 'Vehicle does not exist.'); + } + + // check if it's owned by customer + if ($cv->getCustomer()->getID() != $this->session->getCustomer()->getID()) { + return new ApiResponse(false, 'Invalid vehicle.'); + } + + // check trade in value + $result = $this->getTIEstimateByCV($cv); + + // response + return new ApiResponse(true, '', [ + 'trade_in_batt' => $result['trade_in_batt'], + 'trade_in_type' => $result['trade_in_type'], + 'trade_in_value' => $result['trade_in_value'], + ]); + } + + public function listVehicles(Request $req, PayMongoConnector $paymongo) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // vehicles + $cv_list = []; + // $cvs = $cust->getVehicles(); + // only get the customer's vehicles whose flag_active is true + $cvs = $this->em->getRepository(CustomerVehicle::class)->findBy(['flag_active' => true, 'customer' => $cust]); + foreach ($cvs as $cv) { + $cv_list[] = $this->generateVehicleInfo($cv, true, $paymongo); + } + + // response + return new ApiResponse(true, '', [ + 'vehicles' => $cv_list, + ]); + } + + public function getCompatibleBatteries(Request $req, $vid) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get vehicle + $vehicle = $this->em->getRepository(Vehicle::class)->find($vid); + if ($vehicle == null) { + return new ApiResponse(false, 'Invalid vehicle.'); + } + + // batteries + $batt_list = []; + $batts = $vehicle->getBatteries(); + foreach ($batts as $batt) { + // TODO: Add warranty_tnv to battery information + $batt_list[] = [ + 'id' => $batt->getID(), + 'mfg_id' => $batt->getManufacturer()->getID(), + 'mfg_name' => $batt->getManufacturer()->getName(), + 'model_id' => $batt->getModel()->getID(), + 'model_name' => $batt->getModel()->getName(), + 'size_id' => $batt->getSize()->getID(), + 'size_name' => $batt->getSize()->getName(), + 'price' => $batt->getSellingPrice(), + 'wty_private' => $batt->getWarrantyPrivate(), + 'wty_commercial' => $batt->getWarrantyCommercial(), + 'image_url' => $this->getBatteryImageURL($req, $batt), + ]; + } + + // response + return new ApiResponse(true, '', [ + '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(), + ], + 'batteries' => $batt_list, + ]); + } + + public function removeVehicle($id, Request $req) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // find customer vehicle + $cv = $this->em->getRepository(CustomerVehicle::class)->find($id); + if ($cv == null) { + return new ApiResponse(false, 'Invalid customer vehicle id.'); + } + + // confirm that customer vehicle belongs to customer + if ($cv->getCustomer()->getID() != $cust->getID()) { + return new ApiResponse(false, 'Vehicle does not belong to customer.'); + } + + // we cannot remove a vehicle from customer if customer vehicle has already has JOs for it. + // instead we set the customer vehicle's flag_active to false + $cv->setActive(false); + $this->em->flush(); + + // response + return new ApiResponse(); + } + + protected function getTIEstimateByCV($cv) + { + // compute for trade in value + $trade_in_batt = null; + $trade_in_value = 0; + $trade_in_type = TradeInType::OTHER; + $previous_jo_found = false; + + // check for last battery replacement JO + $last_jo = $this->em->getRepository(JobOrder::class)->findOneBy([ + 'service_type' => [ + ServiceType::BATTERY_REPLACEMENT_NEW, ServiceType::BATTERY_REPLACEMENT_WARRANTY + ], + 'status' => JOStatus::FULFILLED, + 'cus_vehicle' => $cv, + ], ['date_create' => 'desc']); + + if (!empty($last_jo)) { + $items = $last_jo->getInvoice()->getItems(); + + foreach ($items as $item) { + // find the first battery item and get its trade-in value + $item_battery = $item->getBattery(); + if (!empty($item_battery)) { + $trade_in_type = TradeInType::MOTOLITE; + $trade_in_batt = $item_battery->getID(); + $previous_jo_found = true; + $size = $item_battery->getSize(); + $trade_in_value = $size->getTIPriceMotolite(); + break; + } + } + } + + // TODO: possibly refactor this bit + // if no valid previous JO is found, base the trade-in value on recommended batteries + if (!$previous_jo_found) { + $comp_batteries = $cv->getVehicle()->getBatteries(); + + // get the lowest trade-in value from the list of batteries + if (!empty($comp_batteries)) { + foreach ($comp_batteries as $battery) { + $size = $battery->getSize(); + $size_ti = $size->getTIPriceOther(); + + // get the lowest value or set if not set yet + if ($size_ti < $trade_in_value || $trade_in_value == 0) { + $trade_in_value = $size_ti; + $trade_in_batt = $battery->getID(); + } + } + } + } + + return [ + 'trade_in_batt' => $trade_in_batt, + 'trade_in_type' => $trade_in_type, + 'trade_in_value' => $trade_in_value, + ]; + } + + protected function generateVehicleInfo(CustomerVehicle $cv, $include_insurance = false, PayMongoConnector $paymongo) + { + $battery_id = null; + if ($cv->getCurrentBattery() != null) + $battery_id = $cv->getCurrentBattery()->getID(); + + $wty_ex = null; + if ($cv->getWarrantyExpiration() != null) + $wty_ex = $cv->getWarrantyExpiration()->format('Y-m-d'); + + $warranty = $this->findWarranty($cv->getPlateNumber()); + + $cv_name = ''; + if ($cv->getName() != null) + $cv_name = $cv->getName(); + + $row = [ + 'cv_id' => $cv->getID(), + 'mfg_id' => $cv->getVehicle()->getManufacturer()->getID(), + 'make_id' => $cv->getVehicle()->getID(), + 'name' => $cv_name, + 'plate_num' => $cv->getPlateNumber(), + 'model_year' => $cv->getModelYear(), + 'color' => $cv->getColor(), + 'condition' => $cv->getStatusCondition(), + 'fuel_type' => $cv->getFuelType(), + 'wty_code' => $cv->getWarrantyCode(), + 'wty_expire' => $wty_ex, + 'curr_batt_id' => $battery_id, + 'is_motolite' => $cv->hasMotoliteBattery() ? 1 : 0, + 'is_active' => $cv->isActive() ? 1 : 0, + 'warranty' => $warranty, + ]; + + // get latest insurance row + if ($include_insurance) { + $insurance = null; + $iobj = $cv->getLatestInsuranceApplication(); + if (!empty($iobj)) { + $gt = $iobj->getGatewayTransaction(); + $date_complete = $iobj->getDateComplete(); + $date_expire = $iobj->getDateExpire(); + $status = $iobj->getStatus(); + + error_log("\r\nTHIS IS THE CURRENT STATUS: " . $status . "\r\n"); + + // handle the very transient state between a payment being made and receiving the paymongo webhook + // TODO: maybe handle this more elegantly. issue is not sure it is a good idea to update the db for this very transient status as the webhook listener also updates this status right away + switch ($status) { + case InsuranceApplicationStatus::CREATED: + // get latest status on this checkout from paymongo + $checkout = $paymongo->getCheckout($gt->getExtTransactionId()); + + if ($checkout['success']) { + $payment_intent = $checkout['response']['data']['attributes']['payment_intent'] ?? null; + if (!empty($payment_intent)) { + $intent_status = $payment_intent['attributes']['status'] ?? null; + + // TODO: define these paymongo payment intent statuses elsewhere + if ($intent_status === 'processing' || $intent_status === 'succeeded') { + $status = InsuranceApplicationStatus::PAID; + } + } + } + break; + default: + break; + } + + $insurance = [ + 'id' => $iobj->getID(), + 'ext_transaction_id' => $iobj->getExtTransactionId(), + 'status' => $status, + 'coc_url' => $iobj->getCOC(), + 'checkout_url' => $gt->getMetadata()['checkout_url'], + 'transaction_status' => $gt->getStatus(), + 'premium_amount' => (string)bcdiv($gt->getAmount(), 100), // NOTE: hard expressing as string so it's consistent + 'date_submit' => $iobj->getDateSubmit()->format('Y-m-d H:i:s'), + 'date_complete' => $date_complete ? $date_complete->format('Y-m-d H:i:s') : null, + 'date_expire' => $date_expire ? $date_expire->format('Y-m-d H:i:s') : null, + ]; + + // get information changelog + $insurance['changelog'] = $iobj->getMetadata()['changes'] ?? []; + } + + $row['latest_insurance'] = $insurance; + } + + return $row; + } + + protected function checkVehicleRequirements(Request $req) + { + // validate params + return $this->validateRequest($req, [ + 'make_id', + 'name', + 'plate_num', + 'model_year', + 'color', + 'condition', + 'fuel_type', + ]); + + // TODO: check valid plate number + // TODO: check valid fuel type (gas / diesel) + // TODO: check current battery id + // TODO: check condition (brand new / second-hand) + // TODO: check is_motolite and is_active (1 or 0) + // TODO: check warranty expiration date (YYYYMMDD) + // TODO: check model year coverage if it fits in between + } + + protected function setCustomerVehicleObject(Request $req, CustomerVehicle $cv) + { + // check customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return [ + 'success' => false, + 'error' => 'No customer information found.', + ]; + } + + // get vehicle + $vehicle = $this->em->getRepository(Vehicle::class)->find($req->request->get('make_id')); + if ($vehicle == null) { + return [ + 'success' => false, + 'error' => 'Invalid vehicle make id.', + ]; + } + + $cv->setCustomer($cust) + ->setVehicle($vehicle) + ->setName($req->request->get('name')) + ->setPlateNumber($req->request->get('plate_num')) + ->setModelYear($req->request->get('model_year')) + ->setColor($req->request->get('color')) + ->setFuelType($this->normalizeString($req->request->get('fuel_type'))) + ->setStatusCondition($this->normalizeString($req->request->get('condition'))); + + // set warranty code and expiration + // TODO: check warranty requirements + if (!empty($req->request->get('wty_code'))) + $cv->setWarrantyCode($req->request->get('wty_code')); + if (!empty($req->request->get('wty_expire'))) + $cv->setWarrantyExpiration(new DateTime($req->request->get('wty_expire'))); + + // TODO: get current battery + + // is motolite + if ($req->request->get('is_motolite') == 0) + $cv->setHasMotoliteBattery(false); + else + $cv->setHasMotoliteBattery(true); + + // is active + if ($req->request->get('is_active') == 0) + $cv->setActive(false); + else + $cv->setActive(true); + + // save + $this->em->persist($cv); + $this->em->flush(); + + // response + return [ + 'success' => true, + 'cv_id' => $cv->getID(), + ]; + } + + protected function normalizeString($string) + { + return trim(strtolower($string)); + } +} diff --git a/src/Controller/CustomerAppAPI/WarrantyController.php b/src/Controller/CustomerAppAPI/WarrantyController.php new file mode 100644 index 00000000..ff1e9d81 --- /dev/null +++ b/src/Controller/CustomerAppAPI/WarrantyController.php @@ -0,0 +1,614 @@ +validateRequest($req, [ + 'plate_number', + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + $plate_number = $req->request->get('plate_number'); + + // find warranty using plate number + $warranty_results = $this->em->getRepository(Warranty::class)->findBy( + ['plate_number' => $plate_number], + ['date_create' => 'desc'] + ); + + // check if warranty_results is empty + if (empty($warranty_results)) { + return new ApiResponse(false, 'No warranty found for plate number.'); + } + + // activate all entries + foreach ($warranty_results as $warranty) { + $warranty->setActivated(); + } + + $this->em->flush(); + + // response + return new ApiResponse(); + } + + public function warrantyCheck($serial, Request $req, WarrantyRaffleLogger $raffle_logger) + { + // validate params + $validity = $this->validateRequest($req); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // check if warranty serial is there + $serial = $this->cleanSerial($serial); + $warr_serial = $this->em->getRepository(WarrantySerial::class)->find($serial); + $warr = $this->em->getRepository(Warranty::class)->findOneBy(['serial' => $serial]); + $batt = null; + $is_registered = false; + + if ($warr_serial == null) { + return new ApiResponse(false, 'Invalid warranty serial code.'); + } + + $today = new DateTime(); + + $user_id = $req->query->get('session_key'); + $raffle_data = [ + 'user_id' => $user_id, + 'serial' => $serial, + 'warranty_id' => null, + 'action' => '', + 'bmodel_name' => '', + 'bsize_name' => '', + 'first_name' => '', + 'last_name' => '', + 'plate_number' => '', + 'contact_num' => '', + 'email' => '', + 'address' => '', + ]; + + $data_sent = []; + + // if we have a warranty entry for the serial already + if ($warr != null) { + $warr_plate = $warr->getPlateNumber(); + $is_registered = true; + $is_customer_warranty = false; + + // check if the warranty is registered to a car owned by the customer + $cust = $this->session->getCustomer(); + $is_customer_warranty = $this->checkCustomerPlateNumber($warr_plate, $cust); + + // null mobile number should be blank string instead + if ($warr->getMobileNumber() == null) + $mobile_num = ''; + else + $mobile_num = $warr->getMobileNumber(); + + $can_edit = $is_customer_warranty; + + // if customer plate number matches the one registered on the warranty + if ($is_customer_warranty) { + // purchase date of customer + if ($warr->getDatePurchaseCustomer() != null) + $date_purchase_cust = $warr->getDatePurchaseCustomer()->format('Y-m-d'); + else + $date_purchase_cust = $today->format('Y-m-d'); + + + // invoice + if ($warr->getFileInvoice() != null) + $invoice_url = $req->getSchemeAndHttpHost() . '/warranty_uploads/' . $warr->getFileInvoice(); + else + $invoice_url = ''; + + // warranty card + if ($warr->getFileWarrantyCard() != null) + $warr_card_url = $req->getSchemeAndHttpHost() . '/warranty_uploads/' . $warr->getFileWarrantyCard(); + else + $warr_card_url = ''; + + $customer = [ + 'first_name' => $warr->getFirstName() ?? '', + 'last_name' => $warr->getLastName() ?? '', + 'mobile_number' => $mobile_num, + 'plate_number' => $warr_plate, + 'email' => $warr->getEmail() ?? '', + 'contact_num' => $warr->getContactNumber() ?? '', + 'address' => $warr->getCustomerAddress() ?? '', + ]; + $other_data = [ + 'odometer' => (int) $warr->getOdometer() ?? 0, + 'date_purchase' => $date_purchase_cust, + 'invoice' => $invoice_url, + 'warr_card' => $warr_card_url, + 'dealer_name' => $warr->getDealerName() ?? '', + 'dealer_address' => $warr->getDealerAddress() ?? '', + 'branch_code' => $warr->getDealerBranchCode() ?? '', + ]; + + // set customer info and action for raffle log + $raffle_data['action'] = 'serial_check_customer'; + $raffle_data['first_name'] = $customer['first_name']; + $raffle_data['last_name'] = $customer['last_name']; + $raffle_data['plate_number'] = $customer['plate_number']; + $raffle_data['email'] = $customer['email']; + $raffle_data['contact_num'] = $customer['contact_num']; + $raffle_data['address'] = $customer['address']; + $raffle_data['warranty_id'] = $warr->getID(); + } else { + // hide customer information if customer is not the one registered + $customer = [ + 'first_name' => '', + 'last_name' => '', + 'mobile_number' => '', + 'plate_number' => '', + 'email' => '', + 'contact_num' => '', + 'address' => '', + ]; + $other_data = [ + 'odometer' => 0, + 'date_purchase' => $today->format('Y-m-d'), + 'invoice' => '', + 'warr_card' => '', + 'dealer_name' => '', + 'dealer_address' => '', + 'branch_code' => '', + ]; + + // set action for raffle log + $raffle_data['action'] = 'serial_check_not_customer'; + } + } else { + $can_edit = true; + $customer = [ + 'first_name' => '', + 'last_name' => '', + 'mobile_number' => '', + 'plate_number' => '', + 'email' => '', + 'contact_num' => '', + 'address' => '', + ]; + $other_data = [ + 'odometer' => 0, + 'date_purchase' => $today->format('Y-m-d'), + 'invoice' => '', + 'warr_card' => '', + 'dealer_name' => '', + 'dealer_address' => '', + 'branch_code' => '', + ]; + + // set action for raffle log + $raffle_data['action'] = 'serial_check_customer'; + } + + $sku = $warr_serial->getSKU(); + $batt = null; + $cat_name = ''; + if ($sku != null) + $batt = $this->em->getRepository(SAPBattery::class)->find($sku); + else { + // get the category name of the serial + $cat_name = $warr_serial->getMetaInfo('category_name'); + } + + // TODO: put this in a config file + $image_url = $req->getSchemeAndHttpHost() . '/battery/generic.png'; + if ($batt != null) { + $battery = [ + 'brand' => $batt->getBrand()->getName(), + 'size' => $batt->getSize()->getName(), + 'image_url' => $image_url, + ]; + } else { + $battery = [ + 'brand' => $cat_name, + 'size' => '', + 'image_url' => '', + ]; + } + + // set the rest of the raffle log entry + $raffle_data['bmodel_name'] = $battery['brand']; + $raffle_data['bsize_name'] = $battery['size']; + + // log the raffle log + $raffle_logger->logRaffleInfo($data_sent, $raffle_data); + + // response + return new ApiResponse(true, '', [ + 'is_valid' => true, + 'is_registered' => $is_registered, + 'can_edit' => $can_edit, + 'customer' => $customer, + 'battery' => $battery, + 'odometer' => $other_data['odometer'], + 'invoice' => $other_data['invoice'], + 'warr_card' => $other_data['warr_card'], + 'date_purchase' => $other_data['date_purchase'], + 'dealer_name' => $other_data['dealer_name'], + 'dealer_address' => $other_data['dealer_address'], + 'branch_code' => $other_data['branch_code'], + 'message' => [ + 'register_error' => 'Warranty serial code has already been registered.', + 'edit_error' => 'Sorry, warranty is registered under another vehicle not in your list of vehicles.', + ], + ]); + } + + public function warrantyRegister( + $serial, + Request $req, + KernelInterface $kernel, + RisingTideGateway $rt, + TranslatorInterface $trans, + WarrantyRaffleLogger $raffle_logger, + WarrantyAPILogger $logger + ) { + // validate params + $validity = $this->validateRequest($req, [ + 'first_name', + 'last_name', + 'plate_number', + 'date_purchase', + ]); + + if (!$validity['is_valid']) { + return new ApiResponse(false, $validity['error']); + } + + // handle file uploads + $invoice = $req->files->get('invoice'); + $warr_card = $req->files->get('warr_card'); + + // normalize serial + $serial = $this->cleanSerial($serial); + // $serial = trim(strtoupper($serial)); + + // process picture uploads + $upload_dir = $kernel->getProjectDir() . '/public/warranty_uploads'; + $inv_filename = $this->handlePictureUpload($invoice, $upload_dir, $serial, 'invoice'); + $wcard_filename = $this->handlePictureUpload($warr_card, $upload_dir, $serial, 'wcard'); + + $user_id = $req->query->get('session_key'); + $log_data = [ + 'plate_number' => $req->request->get('plate_num'), + 'first_name' => $req->request->get('first_name'), + 'last_name' => $req->request->get('last_name'), + 'date_purchase' => $req->request->get('date_purchase'), + ]; + $action = 'create/update'; + $source = WarrantySource::MOBILE; + + // update customer information + // $cust = $this->updateCustomerInfo($req, $em); + + // update warranty + $res = $this->updateWarranty( + $rt, + $trans, + $req, + $serial, + $inv_filename, + $wcard_filename, + $logger, + $log_data, + $user_id, + $action, + $source, + $raffle_logger + ); + if (!$res['success']) { + return new ApiResponse(false, $res['error']); + } + + $this->em->flush(); + + return new ApiResponse(); + } + + protected function handlePictureUpload($file, $target_dir, $serial, $name) + { + // error_log("handling $name upload"); + // no file sent + if ($file == null) { + error_log("handling $name upload - no file"); + return null; + } + + // create target dir if it doesn't exist + if (!file_exists($target_dir)) { + if (!mkdir($target_dir, 0744, true)) { + error_log('failed to create folder for warranty pictures'); + return null; + } + } + + // move file + $filename = $name . '.' . $file->getClientOriginalExtension(); + $file->move($target_dir . '/' . $serial, $filename); + + // error_log("filename - $filename"); + // error_log($target_dir . '/' . $serial . '/' . $filename); + + return $serial . '/' . $filename; + } + + // TODO: put this in a service + protected function cleanSerial($serial) + { + // trim and make everything upper case + $clean_serial = trim(strtoupper($serial)); + + + // remove QR prefix if it exists + // $prefix = substr($clean_serial, 0, 2); + // if ($prefix == 'QR') + // $clean_serial = substr($clean_serial, 2); + + return $clean_serial; + } + + protected function checkCustomerPlateNumber($plate_number, $cust) + { + // strip spaces and make all caps + $plate_number = preg_replace('/\s+/', '', strtoupper($plate_number)); + + // if there's no customer linked to session + if ($cust != null) { + // check all the customer vehicles + $cvs = $cust->getVehicles(); + foreach ($cvs as $cv) { + $cv_plate = preg_replace('/\s+/', '', strtoupper($cv->getPlateNumber())); + + // did we find a match? + if ($cv_plate == $plate_number) { + return true; + } + } + } + + return false; + } + + protected function updateWarranty($rt, $trans, $req, $serial, $inv_filename = null, $wcard_filename = null, $logger, $log_data, $user_id, $action, $source, $raffle_logger) + { + // prepare raffle log entry + $raffle_data = [ + 'user_id' => $user_id, + 'serial' => $serial, + 'warranty_id' => null, + 'action' => '', + 'bmodel_name' => '', + 'bsize_name' => '', + 'first_name' => '', + 'last_name' => '', + 'plate_number' => '', + 'contact_num' => '', + 'email' => '', + 'address' => '', + ]; + + // get serial + $warr_serial = $this->em->getRepository(WarrantySerial::class)->find($serial); + if ($warr_serial == null) { + return [ + 'success' => false, + 'error' => 'Invalid warranty serial code.', + ]; + } + + // check if warranty exists already + $warr = $this->em->getRepository(Warranty::class)->findOneBy(['serial' => $serial]); + + // skip warranty if it already exists + if ($warr != null) { + /* + // NOTE: we could not update in the old version + $res->setError(true) + ->setErrorMessage('Warranty registration entry already exists.'); + return $res; + */ + + // check if warranty is registered to a serial owned by customer + $warr_plate = $warr->getPlateNumber(); + $cust = $this->session->getCustomer(); + $is_customer_warranty = $this->checkCustomerPlateNumber($warr_plate, $cust); + + if (!$is_customer_warranty) { + $error_msg = 'Warranty registered to a vehicle not in your list of vehicles.'; + + // set action to update + $action = 'update'; + $logger->logWarrantyInfo($log_data, $error_msg, $user_id, $action, $source); + + // response + return [ + 'success' => false, + 'error' => $error_msg, + ]; + } + + $sms_msg = $trans->trans('warranty_update_confirm'); + + // update raffle data action + $raffle_data['action'] = 'warranty_update'; + } else { + $warr = new Warranty(); + $sms_msg = $trans->trans('warranty_register_confirm'); + + // set warranty source + $warr->setCreateSource($source); + + // update raffle data action + $raffle_data['action'] = 'warranty_create'; + } + + // get sap battery + $sku = $warr_serial->getSKU(); + $sap_bty = null; + if ($sku != null) { + $sap_bty = $this->em->getRepository(SAPBattery::class)->find($sku); + if ($sap_bty == null) { + $error_msg = 'Could not find battery entry for warranty.'; + $logger->logWarrantyInfo($log_data, $error_msg, $user_id, $action, $source); + + // response + return [ + 'success' => false, + 'error' => $error_msg, + ]; + } + } + + // default date purchase to today + // NOTE: might need to change this later + $date_pur = new DateTime(); + + // get date purchase specified by customer + $date_pur_cust = DateTime::createFromFormat('Y-m-d', $req->request->get('date_purchase')); + if (!$date_pur_cust) { + $error_msg = 'Invalid date format for date of purchase.'; + $logger->logWarrantyInfo($log_data, $error_msg, $user_id, $action, $source); + + // response + return [ + 'success' => false, + 'error' => $error_msg, + ]; + } + + $customer = $this->session->getCustomer(); + if ($customer != null) { + $warr->setCustomer($customer); + // get customer vehicles + + $vehicle = $this->findCustomerVehicle($customer, $req->request->get('plate_number')); + if ($vehicle != null) + $warr->setVehicle($vehicle); + } + + // TODO: make a standard clean plate number service + // clean plate number + $plate = $req->request->get('plate_number'); + // upper case and remove spaces + $plate = strtoupper(str_replace(' ', '', $plate)); + // remove special characters + $plate = preg_replace('/[^A-Za-z0-9. -]/', '', $plate); + + // create or update warranty entry + $warr->setSerial($serial) + ->setFirstName($req->request->get('first_name')) + ->setLastName($req->request->get('last_name')) + ->setEmail($req->request->get('email')) + ->setPlateNumber($plate) + // TODO: figure out how to compute date of purchase + ->setDatePurchase($date_pur) + // TODO: set status + // ->setStatus() + // TODO: set battery model and size id + // ->setBatterySize() + // ->setBatteryModel() + ->setSAPBattery($sap_bty) + ->setMobileNumber(substr($this->session->getPhoneNumber(), 2)) + ->setActivated(true) + + // files + ->setFileInvoice($inv_filename) + ->setFileWarrantyCard($wcard_filename) + + // new fields + ->setOdometer($req->request->get('odometer', 0)) + ->setDatePurchaseCustomer($date_pur_cust) + ->setContactNumber($req->request->get('contact_num')) + ->setCustomerAddress($req->request->get('cust_address')) + ->setDealerName($req->request->get('dealer_name')) + ->setDealerAddress($req->request->get('dealer_address')) + ->setDealerBranchCode($req->request->get('branch_code')) + ->setValidated(false); + + // TODO: check for date purchase and date expire + + $this->em->persist($warr); + + // TODO: check if we need to do anyting else + $logger->logWarrantyInfo($log_data, '', $user_id, $action, $source); + + // send sms + // error_log('sending sms to - ' . $this->session->getPhoneNumber()); + $rt->sendSMS($this->session->getPhoneNumber(), $trans->trans('message.battery_brand_allcaps'), $sms_msg); + + // prepare the rest of the raffle log entry + $raffle_data['warranty_id'] = $warr->getID(); + $raffle_data['bmodel_name'] = $sap_bty->getBrand()->getName(); + $raffle_data['bsize_name'] = $sap_bty->getSize()->getName(); + $raffle_data['first_name'] = $req->request->get('first_name', ''); + $raffle_data['last_name'] = $req->request->get('last_name', ''); + $raffle_data['plate_number'] = $plate; + $raffle_data['contact_num'] = $req->request->get('contact_num', ''); + $raffle_data['email'] = $req->request->get('email', ''); + $raffle_data['address'] = $req->request->get('cust_address', ''); + + $data_sent = [ + 'plate_number' => $req->request->get('plate_number'), + 'first_name' => $req->request->get('first_name'), + 'last_name' => $req->request->get('last_name'), + 'date_purchase' => $req->request->get('date_purchase'), + 'address' => $req->request->get('cust_address', ''), + 'email' => $req->request->get('email', ''), + 'contact_num' => $req->request->get('contact_num', ''), + ]; + + // log raffle data + $raffle_logger->logRaffleInfo($data_sent, $raffle_data); + + // response + return [ + 'success' => true, + ]; + } + + protected function findCustomerVehicle($customer, $plate_number) + { + $clean_plate = Warranty::cleanPlateNumber($plate_number); + if ($clean_plate) { + // find the customer vehicle and get the vehicle + $cv = $this->em->getRepository(CustomerVehicle::class)->findOneBy(['plate_number' => $clean_plate, 'customer' => $customer]); + if ($cv != null) { + $vehicle = $cv->getVehicle(); + return $vehicle; + } + } + + return null; + } +} diff --git a/src/Controller/InsuranceController.php b/src/Controller/InsuranceController.php new file mode 100644 index 00000000..97744d27 --- /dev/null +++ b/src/Controller/InsuranceController.php @@ -0,0 +1,135 @@ +em = $em; + $this->fcmclient = $fcmclient; + } + + public function listen(Request $req) + { + $payload = $req->request->all(); + + // DEBUG + @file_put_contents(__DIR__ . '/../../var/log/insurance.log', print_r($payload, true) . "\r\n----------------------------------------\r\n\r\n", FILE_APPEND); + error_log(print_r($payload, true)); + + /* + return $this->json([ + 'success' => true, + ]); + */ + + // END DEBUG + + // if no transaction code given, silently fail + if (empty($payload['transaction_code'])) { + error_log("Invalid insurance callback received: " . print_r($payload, true)); + + return $this->json([ + 'success' => true, + ]); + } + + // get event type and process accordingly + $event_name = $payload['transaction_code']; + + switch ($event_name) { + case 'GR002': + return $this->handleAuthenticated($payload); + break; + case 'GR003': + return $this->handleUpdateMade($payload); + break; + default: + break; + } + + return $this->json([ + 'success' => true, + 'payload' => $payload, + ]); + } + + protected function handleAuthenticated($payload) + { + $obj = $this->getApplication($payload['id']); + $now = new DateTime(); + $expiry = DateTime::createFromFormat("Y-m-d", $payload['expiry_date']); + + if (!empty($obj)) { + // mark as completed + $obj->setStatus(InsuranceApplicationStatus::COMPLETED); + $obj->setDateComplete($now); + $obj->setDateExpire($expiry); + $obj->setCOC($payload['coc_url']); + $this->em->flush(); + + // send notification + $this->fcmclient->sendEvent($obj->getCustomer(), "insurance_fcm_title_completed", "insurance_fcm_body_completed", [ + 'cv_id' => $obj->getCustomerVehicle()->getID(), + ]); + } + + return $this->json([ + 'success' => true, + ]); + } + + protected function handleUpdateMade($payload) + { + $obj = $this->getApplication($payload['id']); + + if (!empty($obj)) { + $metadata = $obj->getMetadata(); + + // initialize change list if not present + if (empty($metadata['changes'])) { + $metadata['changes'] = []; + } + + $now = new DateTime; + $metadata['changes'][$now->format('Y-m-d H:i:s')] = $payload['data']; + + // update metadata to record change + $obj->setMetadata($metadata); + $this->em->flush(); + + // send notification + $this->fcmclient->sendEvent($obj->getCustomer(), "insurance_fcm_title_updated", "insurance_fcm_body_updated", [ + 'cv_id' => $obj->getCustomerVehicle()->getID(), + ]); + } + + return $this->json([ + 'success' => true, + ]); + } + + protected function getApplication($transaction_id) + { + $result = $this->em->getRepository(InsuranceApplication::class)->findBy([ + 'ext_transaction_id' => $transaction_id, + ], [], 1); + + return !empty($result) ? $result[0] : false; + } +} diff --git a/src/Controller/JobOrderController.php b/src/Controller/JobOrderController.php index 485709e4..91921a0f 100644 --- a/src/Controller/JobOrderController.php +++ b/src/Controller/JobOrderController.php @@ -7,6 +7,7 @@ use App\Ramcar\InvoiceCriteria; use App\Ramcar\CMBServiceType; use App\Ramcar\ServiceType; use App\Ramcar\JOCancelReasons; +use App\Ramcar\TransactionOrigin; use App\Entity\CustomerVehicle; use App\Entity\Promo; @@ -21,6 +22,8 @@ use App\Service\JobOrderHandlerInterface; use App\Service\GISManagerInterface; use App\Service\MapTools; use App\Service\MQTTClient; +use App\Service\MQTTClientApiv2; +use App\Service\FCMSender; use App\Service\APNSClient; use App\Service\InventoryManager; use App\Service\HubSelector; @@ -29,8 +32,6 @@ use App\Service\RiderTracker; use App\Service\MotivConnector; use App\Service\GeofenceTracker; - - use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Bundle\FrameworkBundle\Controller\Controller; @@ -299,6 +300,7 @@ class JobOrderController extends Controller $rows[$key]['meta']['update_url'] = $this->generateUrl($jo_handler->getEditRoute($jo_id, $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]); + $rows[$key]['meta']['view_url'] = $this->generateUrl('jo_all_view_form',['id' => $jo_id]); } if ($tier_params['unlock_route'] != '') @@ -355,7 +357,7 @@ class JobOrderController extends Controller return $this->render($template, $params); } - public function processingSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, $id) + public function processingSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient, $id) { $this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.'); @@ -363,7 +365,7 @@ class JobOrderController extends Controller $error_array = []; try { - $error_array = $jo_handler->dispatchJobOrder($req, $id, $mclient); + $error_array = $jo_handler->dispatchJobOrder($req, $id, $mclient, $mclientv2, $fcmclient); } catch (AccessDeniedHttpException $e) { @@ -422,7 +424,7 @@ class JobOrderController extends Controller } - public function assigningSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTCLient $mclient, APNSClient $aclient, $id) + public function assigningSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTCLient $mclient, FCMSender $fcmclient, APNSClient $aclient, $id) { $this->denyAccessUnlessGranted('jo_assign.list', null, 'No access.'); @@ -482,7 +484,7 @@ class JobOrderController extends Controller return $this->render($template, $params); } - public function fulfillmentSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, $id) + public function fulfillmentSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, FCMSender $fcmclient, $id) { $this->denyAccessUnlessGranted('jo_fulfill.list', null, 'No access.'); @@ -541,7 +543,7 @@ class JobOrderController extends Controller return $this->render($template, $params); } - public function openHubSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, $id) + public function openHubSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient, $id) { $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); @@ -550,7 +552,7 @@ class JobOrderController extends Controller try { - $error_array = $jo_handler->setHub($req, $id, $mclient); + $error_array = $jo_handler->setHub($req, $id, $mclient, $mclientv2, $fcmclient); } catch (NotFoundHttpException $e) { @@ -599,7 +601,7 @@ class JobOrderController extends Controller return $this->render($template, $params); } - public function openRiderSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, $id) + public function openRiderSubmit(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient, $id) { $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); @@ -608,7 +610,7 @@ class JobOrderController extends Controller try { - $error_array = $jo_handler->setRider($req, $id, $mclient); + $error_array = $jo_handler->setRider($req, $id, $mclient, $mclientv2, $fcmclient); } catch (NotFoundHttpException $e) { @@ -657,6 +659,32 @@ class JobOrderController extends Controller // response return $this->render($template, $params); } + + /** + * @Menu(selected="jo_all") + */ + public function allViewForm($id, JobOrderHandlerInterface $jo_handler, + GISManagerInterface $gis, EntityManagerInterface $em) + { + $this->denyAccessUnlessGranted('jo_all.list', null, 'No access.'); + + try + { + $params = $jo_handler->initializeAllViewForm($id); + } + catch (NotFoundHttpException $e) + { + throw $this->createNotFoundException($e->getMessage()); + } + + $params['return_url'] = $this->generateUrl('jo_all'); + $params['map_js_file'] = $gis->getJSJOFile(); + + $template = $params['template']; + + // response + return $this->render($template, $params); + } public function pdfForm(Request $req, $id, JobOrderHandlerInterface $jo_handler) @@ -682,7 +710,7 @@ class JobOrderController extends Controller ]); } - public function cancelJobOrder(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, $id) + public function cancelJobOrder(Request $req, JobOrderHandlerInterface $jo_handler, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient, $id) { $this->denyAccessUnlessGranted('joborder.cancel', null, 'No access.'); @@ -699,7 +727,7 @@ class JobOrderController extends Controller try { - $jo_handler->cancelJobOrder($req, $id, $mclient); + $jo_handler->cancelJobOrder($req, $id, $mclient, $mclientv2, $fcmclient); } catch (NotFoundHttpException $e) { @@ -737,7 +765,9 @@ class JobOrderController extends Controller // instantiate invoice criteria $criteria = new InvoiceCriteria(); $criteria->setServiceType($stype) - ->setCustomerVehicle($cv); + ->setCustomerVehicle($cv) + ->setIsTaxable() + ->setSource(TransactionOrigin::CALL); /* diff --git a/src/Controller/MotoliteEventController.php b/src/Controller/MotoliteEventController.php new file mode 100644 index 00000000..e0770b05 --- /dev/null +++ b/src/Controller/MotoliteEventController.php @@ -0,0 +1,304 @@ +denyAccessUnlessGranted('motolite_event.list', null, 'No access.'); + + return $this->render('motolite-event/list.html.twig'); + } + + public function rows(Request $req) + { + $this->denyAccessUnlessGranted('motolite_event.list', null, 'No access.'); + + // get query builder + $qb = $this->getDoctrine() + ->getRepository(MotoliteEvent::class) + ->createQueryBuilder('q'); + + // get datatable params + $datatable = $req->request->get('datatable'); + + // count total records + $tquery = $qb->select('COUNT(q)'); + $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'); + $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.event_time', '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['name'] = $orow->getName(); + $row['event_type'] = MotoliteEventType::getName($orow->getEventType()); + $row['event_time'] = $orow->getEventTime()->format("d M Y g:i A"); + $row['url'] = 'Link'; + $row['image_file'] = $orow->getImageFile(); + + // add row metadata + $row['meta'] = [ + 'update_url' => '', + 'delete_url' => '' + ]; + + // add crud urls + if ($this->isGranted('motolite_event.update')) + $row['meta']['update_url'] = $this->generateUrl('motolite_event_update', ['id' => $row['id']]); + if ($this->isGranted('motolite_event.delete')) + $row['meta']['delete_url'] = $this->generateUrl('motolite_event_delete', ['id' => $row['id']]); + + $rows[] = $row; + } + + // response + return $this->json([ + 'meta' => $meta, + 'data' => $rows + ]); + } + + /** + * @Menu(selected="motolite_event_list") + */ + public function addForm() + { + $this->denyAccessUnlessGranted('motolite_event.add', null, 'No access.'); + + $params = []; + $params['obj'] = new MotoliteEvent(); + $params['mode'] = 'create'; + + $params['event_types'] = MotoliteEventType::getCollection(); + + $em = $this->getDoctrine()->getManager(); + + // response + return $this->render('motolite-event/form.html.twig', $params); + } + + public function addSubmit(Request $req, EncoderFactoryInterface $ef, ValidatorInterface $validator) + { + $this->denyAccessUnlessGranted('motolite_event.add', null, 'No access.'); + + // create new object + $em = $this->getDoctrine()->getManager(); + $obj = new MotoliteEvent(); + + $this->setObject($obj, $req); + + // validate + $errors = $validator->validate($obj); + + // initialize error list + $error_array = []; + + // 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); + } + + // validated! save the entity + $em->persist($obj); + $em->flush(); + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } + + /** + * @Menu(selected="motolite_event_list") + */ + public function updateForm($id) + { + $this->denyAccessUnlessGranted('motolite_event.update', null, 'No access.'); + + // get row data + $em = $this->getDoctrine()->getManager(); + $obj = $em->getRepository(MotoliteEvent::class)->find($id); + + // make sure this row exists + if (empty($obj)) + throw $this->createNotFoundException('The item does not exist'); + + $params = []; + $params['obj'] = $obj; + $params['mode'] = 'update'; + + $params['event_types'] = MotoliteEventType::getCollection(); + + // response + return $this->render('motolite-event/form.html.twig', $params); + } + + public function updateSubmit(Request $req, EncoderFactoryInterface $ef, ValidatorInterface $validator, $id) + { + $this->denyAccessUnlessGranted('motolite_event.update', null, 'No access.'); + + // get object data + $em = $this->getDoctrine()->getManager(); + $obj = $em->getRepository(MotoliteEvent::class)->find($id); + + // make sure this object exists + if (empty($obj)) + throw $this->createNotFoundException('The item does not exist'); + + $this->setObject($obj, $req); + + // validate + $errors = $validator->validate($obj); + + // initialize error list + $error_array = []; + + // 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); + } + + // validated! save the entity + $em->flush(); + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } + + public function destroy($id) + { + $this->denyAccessUnlessGranted('motolite_event.delete', null, 'No access.'); + + // get object data + $em = $this->getDoctrine()->getManager(); + $obj = $em->getRepository(MotoliteEvent::class)->find($id); + + if (empty($obj)) + throw $this->createNotFoundException('The item does not exist'); + + // delete this object + $em->remove($obj); + $em->flush(); + + // response + $response = new Response(); + $response->setStatusCode(Response::HTTP_OK); + $response->send(); + } + + protected function setQueryFilters($datatable, QueryBuilder $query) + { + if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) { + $query->where('q.name LIKE :filter') + ->orWhere('q.url LIKE :filter') + ->orWhere('q.event_type LIKE :filter') + ->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%'); + } + } + + protected function setObject(MotoliteEvent $obj, Request $req) + { + // times + $format = "Y-m-d\TH:i"; + $event_time = DateTime::createFromFormat($format, $req->request->get('event_time')); + + $event_type = $req->request->get('event_type'); + + // set and save values + $obj->setName($req->request->get('name')) + ->setEventType($event_type) + ->setEventTime($event_time) + ->setUrl($req->request->get('url')) + ->setImageFile($req->request->get('image_file')); + } + + public function uploadImage(Request $req, FileUploader $uploader) + { + // retrieve temporary info for file + $file = $req->files->get('image_file'); + + // upload the file + $filename = $uploader->upload($file); + + // return response + return $this->json([ + 'success' => true, + 'filename' => $filename + ]); + } +} diff --git a/src/Controller/PayMongoController.php b/src/Controller/PayMongoController.php new file mode 100644 index 00000000..02f75928 --- /dev/null +++ b/src/Controller/PayMongoController.php @@ -0,0 +1,110 @@ +em = $em; + } + + public function listen(Request $req) + { + $payload = json_decode($req->getContent(), true); + + // DEBUG + @file_put_contents(__DIR__ . '/../../var/log/paymongo.log', print_r($payload, true) . "\r\n----------------------------------------\r\n\r\n", FILE_APPEND); + + /* + return $this->json([ + 'success' => true, + ]); + */ + + // END DEBUG + + // if no event type given, silently fail + if (empty($payload['data'])) { + error_log("Invalid paymongo callback received: " . print_r($payload, true)); + return $this->json([ + 'success' => true, + ]); + } + + // get event type and process accordingly + $attr = $payload['data']['attributes']; + $event = $attr['data']; + $event_name = $attr['type']; + + switch ($event_name) { + case "payment.paid": + return $this->handlePaymentPaid($event); + break; + case "payment.failed": + return $this->handlePaymentPaid($event); + break; + case "payment.refunded": // TODO: handle refunds + case "payment.refund.updated": + case "checkout_session.payment.paid": + default: + break; + } + + return $this->json([ + 'success' => true, + ]); + } + + protected function handlePaymentPaid($event) + { + $metadata = $event['attributes']['metadata']; + $obj = $this->getTransaction($metadata['transaction_id']); + + if (!empty($obj)) { + // mark as paid + $obj->setStatus(TransactionStatus::PAID); + $this->em->flush(); + } + + return $this->json([ + 'success' => true, + ]); + } + + protected function handlePaymentFailed(Request $req) + { + // TODO: do something about failed payments? + return $this->json([ + 'success' => true, + ]); + } + + protected function getTransaction($id) + { + //$class_name = 'App\\Entity\\' . $type; + //$instance = new $class_name; + + return $this->em->getRepository(GatewayTransaction::class)->find($id); + } + + public function paymentSuccess(Request $req) + { + return $this->render('paymongo/success.html.twig'); + } + + public function paymentCancelled(Request $req) + { + return $this->render('paymongo/cancelled.html.twig'); + } +} diff --git a/src/Controller/ReportController.php b/src/Controller/ReportController.php index c9876bd0..4f3ed31e 100644 --- a/src/Controller/ReportController.php +++ b/src/Controller/ReportController.php @@ -536,7 +536,7 @@ class ReportController extends Controller 'Mobile Number', 'Landline Number', 'Office Number', - 'Fax Number', + 'Alternative Phone Number', 'Plate Number', 'Date Mobile App Downloaded', 'Mobile Number Using Mobile App', @@ -706,7 +706,7 @@ class ReportController extends Controller 'Customer Mobile Phone', 'Customer Landline Phone', 'Customer Office Phone', - 'Customer Fax Phone', + 'Customer Alternative Phone', 'Customer Email Address', 'Customer Notes', 'Customer Has Third Party Privacy Policy?', @@ -813,6 +813,7 @@ class ReportController extends Controller 'Plate Number', 'SKU', 'Serial Number', + 'Inventory', 'Invoice/DR No.', 'Existing Battery', 'Rider Name', @@ -1141,7 +1142,7 @@ class ReportController extends Controller 'Mobile Phone', 'Landline', 'Office Phone', - 'Fax', + 'Alternative Phone', 'Email Address', 'Vehicle Manufacturer', 'Vehicle Make', @@ -1932,6 +1933,9 @@ class ReportController extends Controller $fac_hub_coord_lat = $fac_hub->getCoordinates()->getLatitude(); } + // get inventory count + $inventory = $jo->getInventoryCount(); + // find date and time when JO was assigned a hub $datetime_hub_assign_jo = ''; $date_hub_assign_jo = ''; @@ -2254,6 +2258,7 @@ class ReportController extends Controller $plate_number, $sku, $serial, + $inventory, $jo->getORName(), $existing_batt, $rider_name, diff --git a/src/Controller/ResqJobOrderController.php b/src/Controller/ResqJobOrderController.php index 8295077d..2462706a 100644 --- a/src/Controller/ResqJobOrderController.php +++ b/src/Controller/ResqJobOrderController.php @@ -14,6 +14,7 @@ use Doctrine\ORM\EntityManagerInterface; use Catalyst\MenuBundle\Annotation\Menu; use App\Entity\JobOrder; +use App\Entity\Rider; use App\Service\JobOrderHandlerInterface; @@ -23,6 +24,8 @@ use App\Ramcar\ServiceType; use App\Ramcar\WillingToWaitContent; use App\Ramcar\CustomerClassification; +use DateTime; + class ResqJobOrderController extends Controller { /** @@ -39,7 +42,7 @@ class ResqJobOrderController extends Controller /** * @IsGranted("jo_resq_proc.list") */ - public function datatableRows(Request $req, JobOrderHandlerInterface $jo_handler) + public function datatableRows(EntityManagerInterface $em, Request $req, $tier, JobOrderHandlerInterface $jo_handler) { // get query builder $qb = $this->getDoctrine() @@ -52,7 +55,14 @@ class ResqJobOrderController extends Controller // count total records $tquery = $qb->select('COUNT(q)'); - $this->setQueryFilters($datatable, $tquery, $qb, JOStatus::PENDING, TransactionOrigin::MOBILE_APP); + // right now, tier is either proc or all + if ($tier == 'proc') + $this->setQueryFilters($datatable, $tquery, $qb, $tier); + else + { + // default to all + $this->setQueryFilters($datatable, $tquery, $qb, $tier); + } $total = $tquery->getQuery() ->getSingleScalarResult(); @@ -74,9 +84,17 @@ class ResqJobOrderController extends Controller ]; // build query + $qb = $em->getRepository(JobOrder::class) + ->createQueryBuilder('q'); $query = $qb->select('q'); - $this->setQueryFilters($datatable, $query, $qb, JOStatus::PENDING, TransactionOrigin::MOBILE_APP); + if ($tier == 'proc') + $this->setQueryFilters($datatable, $query, $qb, $tier); + else + { + // default to all + $this->setQueryFilters($datatable, $query, $qb, $tier); + } // check if sorting is present, otherwise use default if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) { @@ -110,7 +128,13 @@ class ResqJobOrderController extends Controller // check if customer is not willing to wait $will_not_wait = $orow->getWillWait(); if ($will_not_wait == WillingToWaitContent::NOT_WILLING_TO_WAIT) - $is_emergency = true; + { + // check if reason is emergency or rush + $reason = $orow->getReasonNotWait(); + if (($reason == CustomerNotWaitReason::EMERGENCY) || + ($reason == CustomerNotWaitReason::RUSH_REQUEST)) + $is_emergency = true; + } // add row data $row['id'] = $orow->getID(); @@ -125,6 +149,9 @@ class ResqJobOrderController extends Controller $row['is_mobile'] = $orow->getSource() == TransactionOrigin::MOBILE_APP; $row['is_vip'] = $is_vip; $row['is_emergency'] = $is_emergency; + $row['flag_cust_new'] = $orow->isCustNew(); + $row['date_assign'] = !empty($orow->getDateAssign()) ? $orow->getDateAssign()->format("c") : null; + $row['date_fulfill'] = !empty($orow->getDateFulfill()) ? $orow->getDateFulfill()->format("c") : null; $processor = $orow->getProcessedBy(); if ($processor == null) @@ -132,11 +159,28 @@ class ResqJobOrderController extends Controller else $row['processor'] = $orow->getProcessedBy()->getFullName(); + // get the assigned hub, if any + $assigned_hub = $orow->getHub(); + if ($assigned_hub == null) + $row['assigned_hub'] = ''; + else + $row['assigned_hub'] = $orow->getHub()->getName(); + // add the items for Actions $jo_id = $orow->getID(); - $row['meta']['update_url'] = $this->generateUrl('jo_proc_form', ['id' => $jo_id, 'origin' => 'resq']); - $row['meta']['unlock_url'] = $this->generateUrl('jo_proc_unlock', ['id' => $jo_id, 'origin' => 'resq']); + if ($tier == 'proc') + { + $row['meta']['update_url'] = $this->generateUrl('jo_proc_form', ['id' => $jo_id, 'origin' => 'resq']); + $row['meta']['unlock_url'] = $this->generateUrl('jo_proc_unlock', ['id' => $jo_id, 'origin' => 'resq']); + } + else + { + // default to all + $row['meta']['update_url'] = $this->generateUrl('jo_all_form', ['id' => $jo_id, 'origin' => 'resq']); + $row['meta']['pdf_url'] = $this->generateUrl('jo_pdf_form', ['id' => $jo_id, 'origin' => 'resq']); + $row['meta']['view_url'] = $this->generateUrl('jo_all_view_form',['id' => $jo_id, 'origin' => 'resq']); + } $rows[] = $row; } @@ -148,11 +192,77 @@ class ResqJobOrderController extends Controller ]); } - protected function setQueryFilters($datatable, &$query, $qb, $status, $source) + /** + * @Menu(selected="jo_resq_all") + * @IsGranted("jo_resq_all.list") + */ + public function listAll(EntityManagerInterface $em) { - $query->where('q.status = :status') - ->andWhere('q.source = :source') - ->setParameter('status', $status) - ->setParameter('source', $source); + $params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval'); + + // get riders for dropdown + $params['riders'] = $em->getRepository(Rider::class)->findAll(); + + return $this->render('resq-job-order/list.all.html.twig', $params); + } + + protected function setQueryFilters($datatable, &$query, $qb, $tier) + { + switch ($tier) + { + case 'proc': + $query->where('q.status = :status') + ->andWhere('q.source = :source') + ->setParameter('status', JOStatus::PENDING) + ->setParameter('source', TransactionOrigin::MOBILE_APP); + + if (isset($datatable['query']['data-rows-search'])) + { + $query->andWhere('q.plate_number like :filter') + ->orWhere('q.phone_mobile like :filter') + ->orWhere('q.first_name like :filter') + ->orWhere('q.last_name like :filter') + ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); + } + + break; + case 'all': + if (isset($datatable['query']['data-rows-search'])) + { + $query->andWhere('q.plate_number like :filter') + ->orWhere('q.phone_mobile like :filter') + ->orWhere('q.first_name like :filter') + ->orWhere('q.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.source = :source') + ->setParameter('source', TransactionOrigin::MOBILE_APP); + + break; + default: + $query->where('q.source = :source') + ->setParameter('source', TransactionOrigin::MOBILE_APP); + break; + } } } diff --git a/src/Controller/ReviewController.php b/src/Controller/ReviewController.php index 7d6d6e7d..ba0024e8 100644 --- a/src/Controller/ReviewController.php +++ b/src/Controller/ReviewController.php @@ -82,12 +82,22 @@ class ReviewController extends Controller $rows = []; foreach ($obj_rows as $orow) { + // get appropriate mobile session + $mobile_number = ""; + $mobile_session = $orow->getMobileSession(); + + if (!empty($mobile_session)) { + $mobile_number = $mobile_session->getPhoneNumber(); + } else { + $mobile_number = $orow->getCustomerSession()->getPhoneNumber(); + } + // add row data $row['id'] = $orow->getID(); $row['partner'] = $orow->getPartner()->getName(); $row['rating'] = $orow->getRating(); $row['message'] = $orow->getMessage(); - $row['mobile_number'] = $orow->getMobileSession()->getPhoneNumber(); + $row['mobile_number'] = $mobile_number; // add row metadata $row['meta'] = [ diff --git a/src/Controller/ReviewTagController.php b/src/Controller/ReviewTagController.php new file mode 100644 index 00000000..b53b796c --- /dev/null +++ b/src/Controller/ReviewTagController.php @@ -0,0 +1,267 @@ +denyAccessUnlessGranted('review_tag.list', null, 'No access.'); + + return $this->render('review-tag/list.html.twig'); + } + + public function rows(Request $req) + { + $this->denyAccessUnlessGranted('review_tag.list', null, 'No access.'); + + // get query builder + $qb = $this->getDoctrine() + ->getRepository(ReviewTag::class) + ->createQueryBuilder('q'); + + // get datatable params + $datatable = $req->request->get('datatable'); + + // count total records + $tquery = $qb->select('COUNT(q)'); + $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'); + $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.id', '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['name'] = $orow->getName(); + $row['type'] = ucfirst($orow->getType()); + + // add row metadata + $row['meta'] = [ + 'update_url' => '', + 'delete_url' => '' + ]; + + // add crud urls + if ($this->isGranted('review_tag.update')) + $row['meta']['update_url'] = $this->generateUrl('reviewtag_update', ['id' => $row['id']]); + if ($this->isGranted('review_tag.delete')) + $row['meta']['delete_url'] = $this->generateUrl('reviewtag_delete', ['id' => $row['id']]); + + $rows[] = $row; + } + + // response + return $this->json([ + 'meta' => $meta, + 'data' => $rows + ]); + } + + protected function setQueryFilters($datatable, QueryBuilder $query) + { + if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) { + $query->where('q.name LIKE :filter') + ->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%'); + } + } + + /** + * @Menu(selected="review_tag_list") + */ + public function addForm() + { + $this->denyAccessUnlessGranted('review_tag.add', null, 'No access.'); + + $params['obj'] = new ReviewTag(); + $params['mode'] = 'create'; + + $params['types'] = ReviewTagType::getCollection(); + + // response + return $this->render('review-tag/form.html.twig', $params); + } + + public function addSubmit(Request $req, ValidatorInterface $validator) + { + $this->denyAccessUnlessGranted('review_tag.add', null, 'No access.'); + + // create new object + $em = $this->getDoctrine()->getManager(); + $obj = new ReviewTag(); + + $obj->setName($req->request->get('name')) + ->setType($req->request->get('type')); + + // validate + $errors = $validator->validate($obj); + + // initialize error list + $error_array = []; + + // 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); + } else { + // validated! save the entity + $em->persist($obj); + $em->flush(); + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } + } + + /** + * @Menu(selected="review_tag_list") + */ + public function updateForm($id) + { + $this->denyAccessUnlessGranted('review_tag.update', null, 'No access.'); + + // get row data + $em = $this->getDoctrine()->getManager(); + $obj = $em->getRepository(ReviewTag::class)->find($id); + + // make sure this row exists + if (empty($obj)) + throw $this->createNotFoundException('The item does not exist'); + + $params['obj'] = $obj; + $params['mode'] = 'update'; + + $params['types'] = ReviewTagType::getCollection(); + + // response + return $this->render('review-tag/form.html.twig', $params); + } + + public function updateSubmit(Request $req, ValidatorInterface $validator, $id) + { + $this->denyAccessUnlessGranted('review_tag.update', null, 'No access.'); + + // get object data + $em = $this->getDoctrine()->getManager(); + $obj = $em->getRepository(ReviewTag::class)->find($id); + + // make sure this object exists + if (empty($obj)) + throw $this->createNotFoundException('The item does not exist'); + + $obj->setName($req->request->get('name')) + ->setType($req->request->get('type')); + + // validate + $errors = $validator->validate($obj); + + // initialize error list + $error_array = []; + + // 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); + } + + // validated! save the entity + $em->flush(); + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } + + public function destroy($id) + { + $this->denyAccessUnlessGranted('review_tag.delete', null, 'No access.'); + + // get object data + $em = $this->getDoctrine()->getManager(); + $obj = $em->getRepository(ReviewTag::class)->find($id); + + if (empty($obj)) + throw $this->createNotFoundException('The item does not exist'); + + // delete this object + $em->remove($obj); + $em->flush(); + + // response + $response = new Response(); + $response->setStatusCode(Response::HTTP_OK); + $response->send(); + } +} + diff --git a/src/Controller/RiderController.php b/src/Controller/RiderController.php index c53ac626..25e63a4c 100644 --- a/src/Controller/RiderController.php +++ b/src/Controller/RiderController.php @@ -25,8 +25,8 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Catalyst\MenuBundle\Annotation\Menu; -use Catalyst\APIBundle\Entity\User as APIUser; -use Catalyst\APIBundle\Entity\Role as APIRole; +use App\Entity\ApiUser as APIUser; +use Catalyst\ApiBundle\Entity\Role as APIRole; use DateTime; diff --git a/src/Controller/ServiceOfferingController.php b/src/Controller/ServiceOfferingController.php new file mode 100644 index 00000000..66a447a7 --- /dev/null +++ b/src/Controller/ServiceOfferingController.php @@ -0,0 +1,252 @@ +render('service-offering/list.html.twig'); + } + + /** + * @IsGranted("service_offering.list") + */ + public function datatableRows(Request $req) + { + // get query builder + $qb = $this->getDoctrine() + ->getRepository(ServiceOffering::class) + ->createQueryBuilder('q'); + + // get datatable params + $datatable = $req->request->get('datatable'); + + // count total records + $tquery = $qb->select('COUNT(q)'); + $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'); + $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.id', '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['name'] = $orow->getName(); + $row['fee'] = $orow->getFee(); + + // add row metadata + $row['meta'] = [ + 'update_url' => '', + 'delete_url' => '' + ]; + + // add crud urls + if ($this->isGranted('service_offering.update')) + $row['meta']['update_url'] = $this->generateUrl('service_offering_update_form', ['id' => $row['id']]); + if ($this->isGranted('service_offering.delete')) + $row['meta']['delete_url'] = $this->generateUrl('service_offering_delete', ['id' => $row['id']]); + + $rows[] = $row; + } + + // response + return $this->json([ + 'meta' => $meta, + 'data' => $rows + ]); + } + + /** + * @Menu(selected="service_offering.list") + * @IsGranted("service_offering.add") + */ + public function addForm() + { + $service_offering = new ServiceOffering(); + $params = [ + 'service_offering' => $service_offering, + 'mode' => 'create', + ]; + + // response + return $this->render('service-offering/form.html.twig', $params); + } + + /** + * @IsGranted("service_offering.add") + */ + public function addSubmit(Request $req, EntityManagerInterface $em, ValidatorInterface $validator) + { + $service_offering = new ServiceOffering(); + + $this->setObject($service_offering, $req); + + // validate + $errors = $validator->validate($service_offering); + + // initialize error list + $error_array = []; + + // 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); + } + + // validated! save the entity + $em->persist($service_offering); + $em->flush(); + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } + + /** + * @Menu(selected="service_offering_list") + * @ParamConverter("service_offering", class="App\Entity\ServiceOffering") + * @IsGranted("service_offering.update") + */ + public function updateForm($id, EntityManagerInterface $em, ServiceOffering $service_offering) + { + $params = []; + $params['service_offering'] = $service_offering; + $params['mode'] = 'update'; + + // response + return $this->render('service-offering/form.html.twig', $params); + } + + /** + * @ParamConverter("service_offering", class="App\Entity\ServiceOffering") + * @IsGranted("service_offering.update") + */ + public function updateSubmit(Request $req, EntityManagerInterface $em, ValidatorInterface $validator, ServiceOffering $service_offering) + { + $this->setObject($service_offering, $req); + + // validate + $errors = $validator->validate($service_offering); + + // initialize error list + $error_array = []; + + // 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); + } + + // validated! save the entity + $em->flush(); + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } + + /** + * @ParamConverter("service_offering", class="App\Entity\ServiceOffering") + * @IsGranted("service_offering.delete") + */ + public function deleteSubmit(EntityManagerInterface $em, ServiceOffering $service_offering) + { + // delete this object + $em->remove($service_offering); + $em->flush(); + + // response + $response = new Response(); + $response->setStatusCode(Response::HTTP_OK); + $response->send(); + } + + protected function setObject(ServiceOffering $obj, Request $req) + { + // set and save values + $obj->setName($req->request->get('name')) + ->setCode($req->request->get('code')) + ->setFee($req->request->get('fee')); + } + + protected function setQueryFilters($datatable, QueryBuilder $query) + { + if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) { + $query->where('q.name LIKE :filter') + ->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%'); + } + } +} diff --git a/src/Controller/TAPI/BatteryController.php b/src/Controller/TAPI/BatteryController.php index 0874b792..0705a3ab 100644 --- a/src/Controller/TAPI/BatteryController.php +++ b/src/Controller/TAPI/BatteryController.php @@ -8,15 +8,15 @@ use Symfony\Component\HttpFoundation\Request; use Doctrine\ORM\Query; use Doctrine\ORM\EntityManagerInterface; -use Catalyst\APIBundle\Controller\APIController; -use Catalyst\APIBundle\Response\APIResponse; +use Catalyst\ApiBundle\Controller\ApiController; +use Catalyst\ApiBundle\Component\Response as APIResponse; use App\Ramcar\APIResult; use App\Entity\Vehicle; -use Catalyst\APIBundle\Access\Generator as ACLGenerator; +use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator; -class BatteryController extends APIController +class BatteryController extends ApiController { protected $acl_gen; diff --git a/src/Controller/TAPI/JobOrderController.php b/src/Controller/TAPI/JobOrderController.php index e70928f8..f7541b50 100644 --- a/src/Controller/TAPI/JobOrderController.php +++ b/src/Controller/TAPI/JobOrderController.php @@ -11,8 +11,8 @@ use Doctrine\ORM\EntityManagerInterface; use CrEOF\Spatial\PHP\Types\Geometry\Point; -use Catalyst\APIBundle\Controller\APIController; -use Catalyst\APIBundle\Response\APIResponse; +use Catalyst\ApiBundle\Controller\ApiController; +use Catalyst\ApiBundle\Component\Response as APIResponse; use App\Ramcar\WarrantyClass; use App\Ramcar\JOStatus; @@ -33,6 +33,8 @@ use App\Ramcar\DeliveryStatus; use App\Service\InvoiceGeneratorInterface; use App\Service\RisingTideGateway; use App\Service\MQTTClient; +use App\Service\MQTTClientApiv2; +use App\Service\FCMSender; use App\Service\GeofenceTracker; use App\Service\InventoryManager; use App\Service\RiderAssignmentHandlerInterface; @@ -43,6 +45,7 @@ use App\Service\HubFilteringGeoChecker; use App\Service\RiderTracker; use App\Service\PromoLogger; use App\Service\MapTools; +use App\Service\JobOrderManager; use App\Entity\JobOrder; use App\Entity\CustomerVehicle; @@ -59,9 +62,9 @@ use App\Entity\Warranty; use DateTime; use DateInterval; -use Catalyst\APIBundle\Access\Generator as ACLGenerator; +use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator; -class JobOrderController extends APIController +class JobOrderController extends ApiController { protected $acl_gen; @@ -72,10 +75,11 @@ class JobOrderController extends APIController // TODO: break this monolithic method down public function requestJobOrder(Request $req, InvoiceGeneratorInterface $ic, GeofenceTracker $geo, - InventoryManager $im, MQTTClient $mclient, + InventoryManager $im, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, + FCMSender $fcmclient, RiderAssignmentHandlerInterface $rah, PromoLogger $promo_logger, HubSelector $hub_select, HubDistributor $hub_dist, HubFilterLogger $hub_filter_logger, - HubFilteringGeoChecker $hub_geofence, EntityManagerInterface $em) + HubFilteringGeoChecker $hub_geofence, EntityManagerInterface $em, JobOrderManager $jo_manager) { $this->denyAccessUnlessGranted('tapi_jo.request', null, 'No access.'); @@ -104,7 +108,7 @@ class JobOrderController extends APIController // get data from request $data = []; - $status = $this->getJobOrderRequestInfo($req, $em, $data); + $status = $this->getJobOrderRequestInfo($req, $em, $jo_manager, $data); if ($status != null) return new APIResponse(false, $status); @@ -133,7 +137,8 @@ class JobOrderController extends APIController ->setModeOfPayment($data['payment_mode']) ->setAdvanceOrder($data['is_advance_order']) ->setStatusAutoAssign(AutoAssignStatus::NOT_ASSIGNED) - ->setLandmark($data['landmark']); + ->setLandmark($data['landmark']) + ->setCustNew($data['flag_cust_new']); $jo->setCustomer($data['customer']); $jo->setCustomerVehicle($data['customer_vehicle']); @@ -154,6 +159,12 @@ class JobOrderController extends APIController $icrit->setCustomerVehicle($data['customer_vehicle']); + // set taxable + $icrit->setIsTaxable(); + + // set JO source + $icrit->setSource(TransactionOrigin::THIRD_PARTY); + $icrit->addEntry($data['batt'], $data['trade_in_type'], 1); // send to invoice generator @@ -353,6 +364,10 @@ class JobOrderController extends APIController ]; $mclient->sendEvent($jo, $payload); + // NOTE: for resq2 app + $mclientv2->sendEvent($jo, $payload); + $fcmclient->sendJoEvent($jo, "jo_fcm_title_outlet_assign", "jo_fcm_body_outlet_assign"); + $rah->assignJobOrder($jo, $jo->getRider()); } @@ -371,6 +386,10 @@ class JobOrderController extends APIController 'event' => 'outlet_assign' ]; $mclient->sendEvent($jo, $payload); + + // NOTE: for resq2 app + $mclientv2->sendEvent($jo, $payload); + $fcmclient->sendJoEvent($jo, "jo_fcm_title_outlet_assign", "jo_fcm_body_outlet_assign"); } $em->flush(); @@ -435,6 +454,12 @@ class JobOrderController extends APIController $icrit->addEntry($data['battery'], $data['trade_in_type'], 1); + // set taxable + $icrit->setIsTaxable(); + + // set JO source + $icrit->setSource('third_party'); + // send to invoice generator $invoice = $ic->generateInvoice($icrit); @@ -1222,7 +1247,7 @@ class JobOrderController extends APIController return $file_path; } - protected function getJobOrderRequestInfo(Request $req, EntityManagerInterface $em, &$data) + protected function getJobOrderRequestInfo(Request $req, EntityManagerInterface $em, JobOrderManager $jo_manager, &$data) { $error = $this->validateJORequest($req, $em); if ($error != null) @@ -1345,7 +1370,7 @@ class JobOrderController extends APIController ]; // process customer and vehicle information - $cust_data = $this->processCustomerAndVehicleInformation($c_data, $em); + $cust_data = $this->processCustomerAndVehicleInformation($c_data, $em, $jo_manager); $data = [ 'trade_in_type' => $trade_in_type, @@ -1365,6 +1390,7 @@ class JobOrderController extends APIController 'customer_vehicle' => $cust_data['customer_vehicle'], 'source' => TransactionOrigin::THIRD_PARTY, 'warranty_class' => $warranty_class, + 'flag_cust_new' => $cust_data['flag_cust_new'], ]; return null; @@ -1521,7 +1547,7 @@ class JobOrderController extends APIController return null; } - protected function processCustomerAndVehicleInformation($data, EntityManagerInterface $em) + protected function processCustomerAndVehicleInformation($data, EntityManagerInterface $em, JobOrderManager $jo_manager) { $c_data = []; @@ -1538,6 +1564,7 @@ class JobOrderController extends APIController // find customer + customer vehicle combo $cust_vehicle = $this->findCustomerAndCustomerVehicle($data, $em); + $flag_cust_new = false; if ($cust_vehicle == null) { // find customer given phone number @@ -1573,7 +1600,8 @@ class JobOrderController extends APIController // add customer vehicle $cust_vehicle = $this->createCustomerVehicle($em, $cust, $data); - + + $flag_cust_new = true; } else { @@ -1584,9 +1612,18 @@ class JobOrderController extends APIController $em->flush(); } + // check if customer has more than one job order already + if ($cust != null) + { + $cust_jo_count = $jo_manager->getCustomerJobOrderCount($cust->getID()); + if ($cust_jo_count <= 1) + $flag_cust_new = true; + } + $c_data = [ 'customer' => $cust_vehicle->getCustomer(), 'customer_vehicle' => $cust_vehicle, + 'flag_cust_new' => $flag_cust_new, ]; return $c_data; diff --git a/src/Controller/TAPI/PromoController.php b/src/Controller/TAPI/PromoController.php index 90cbaf0a..65a6606f 100644 --- a/src/Controller/TAPI/PromoController.php +++ b/src/Controller/TAPI/PromoController.php @@ -8,14 +8,14 @@ use Symfony\Component\HttpFoundation\Request; use Doctrine\ORM\Query; use Doctrine\ORM\EntityManagerInterface; -use Catalyst\APIBundle\Controller\APIController; -use Catalyst\APIBundle\Response\APIResponse; +use Catalyst\ApiBundle\Controller\ApiController; +use Catalyst\ApiBundle\Component\Response as APIResponse; use App\Entity\Promo; -use Catalyst\APIBundle\Access\Generator as ACLGenerator; +use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator; -class PromoController extends APIController +class PromoController extends ApiController { protected $acl_gen; diff --git a/src/Controller/TAPI/ServiceController.php b/src/Controller/TAPI/ServiceController.php index 5f468cd8..bbe7fbba 100644 --- a/src/Controller/TAPI/ServiceController.php +++ b/src/Controller/TAPI/ServiceController.php @@ -8,14 +8,14 @@ use Symfony\Component\HttpFoundation\Request; use Doctrine\ORM\Query; use Doctrine\ORM\EntityManagerInterface; -use Catalyst\APIBundle\Controller\APIController; -use Catalyst\APIBundle\Response\APIResponse; +use Catalyst\ApiBundle\Controller\ApiController; +use Catalyst\ApiBundle\Component\Response as APIResponse; use App\Entity\Service; -use Catalyst\APIBundle\Access\Generator as ACLGenerator; +use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator; -class ServiceController extends APIController +class ServiceController extends ApiController { protected $acl_gen; diff --git a/src/Controller/TAPI/VehicleController.php b/src/Controller/TAPI/VehicleController.php index 5e8bf122..b808a9cd 100644 --- a/src/Controller/TAPI/VehicleController.php +++ b/src/Controller/TAPI/VehicleController.php @@ -8,15 +8,15 @@ use Symfony\Component\HttpFoundation\Request; use Doctrine\ORM\Query; use Doctrine\ORM\EntityManagerInterface; -use Catalyst\APIBundle\Controller\APIController; -use Catalyst\APIBundle\Response\APIResponse; +use Catalyst\ApiBundle\Controller\ApiController; +use Catalyst\ApiBundle\Component\Response as APIResponse; use App\Entity\VehicleManufacturer; use App\Entity\Vehicle; -use Catalyst\APIBundle\Access\Generator as ACLGenerator; +use Catalyst\AuthBundle\Service\ACLGenerator as ACLGenerator; -class VehicleController extends APIController +class VehicleController extends ApiController { protected $acl_gen; diff --git a/src/Controller/TicketController.php b/src/Controller/TicketController.php index 0f918058..9752db3d 100644 --- a/src/Controller/TicketController.php +++ b/src/Controller/TicketController.php @@ -105,6 +105,7 @@ class TicketController extends Controller $row['status'] = TicketStatus::getName($orow->getStatus()); $row['ticket_type'] = $orow->getTicketTypeText(); $row['plate_number'] = $orow->getPlateNumber(); + $row['flag_late_doc'] = $orow->isLateDoc(); // add row metadata $row['meta'] = [ @@ -260,6 +261,12 @@ class TicketController extends Controller // get remarks $remarks = $req->request->get('remarks', ''); + // is this a late documentation + $flag_late_doc = $req->request->get('flag_late_doc', false); + + // reason for late documentation + $late_doc_reason = $flag_late_doc ? $req->request->get('late_doc_reason') : null; + // set and save values $obj->setFirstName($first_name) ->setLastName($last_name) @@ -273,7 +280,9 @@ class TicketController extends Controller ->setCreatedBy($this->getUser()) ->setSourceOfAwareness($soa_type) ->setRemarks($remarks) - ->setOtherDescription($other_desc); + ->setOtherDescription($other_desc) + ->setLateDoc($flag_late_doc) + ->setLateDocReason($late_doc_reason); // if assigned to customer, set association if ($customer_id) { @@ -471,6 +480,12 @@ class TicketController extends Controller // get remarks $remarks = $req->request->get('remarks', ''); + // is this a late documentation + $flag_late_doc = $req->request->get('flag_late_doc', false); + + // reason for late documentation + $late_doc_reason = $flag_late_doc ? $req->request->get('late_doc_reason') : null; + // set and save values $obj->setFirstName($first_name) ->setLastName($last_name) @@ -482,7 +497,9 @@ class TicketController extends Controller ->setPlateNumber($req->request->get('plate_number')) ->setSourceOfAwareness($soa_type) ->setRemarks($remarks) - ->setOtherDescription($other_desc); + ->setOtherDescription($other_desc) + ->setLateDoc($flag_late_doc) + ->setLateDocReason($late_doc_reason); // initialize error list $error_array = []; diff --git a/src/Controller/VehicleController.php b/src/Controller/VehicleController.php index afe45fc5..4cac6c27 100644 --- a/src/Controller/VehicleController.php +++ b/src/Controller/VehicleController.php @@ -70,11 +70,14 @@ class VehicleController extends Controller // build query $query = $qb->select('q') - ->addSelect('mfg.name as mfg_name'); + ->addSelect('mfg.name as mfg_name') + ->addSelect('mfg.id as mfg_id'); // add filters to query $this->setQueryFilters($datatable, $query); + error_log("test"); + // check if sorting is present, otherwise use default if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) { $prefix = ''; @@ -85,7 +88,8 @@ class VehicleController extends Controller $order = $datatable['sort']['sort'] ?? 'asc'; $query->orderBy($prefix . $datatable['sort']['field'], $order); } else { - $query->orderBy('mfg_name', 'asc'); + $query->orderBy('mfg_name', 'asc') + ->addOrderBy('q.make', 'asc'); } // get rows for this page @@ -100,6 +104,7 @@ class VehicleController extends Controller // add row data $row['id'] = $orow[0]->getID(); $row['mfg_name'] = $orow['mfg_name']; + $row['mfg_id'] = $orow['mfg_id']; $row['make'] = $orow[0]->getMake(); $row['model_year_from'] = $orow[0]->getModelYearFrom(); $row['model_year_to'] = $orow[0]->getModelYearTo(); diff --git a/src/Entity/ApiUser.php b/src/Entity/ApiUser.php new file mode 100644 index 00000000..bb79df89 --- /dev/null +++ b/src/Entity/ApiUser.php @@ -0,0 +1,66 @@ +name = $name; + return $this; + } + + public function getName() + { + return $this->name; + } + + public function setMetadata($meta) + { + $this->metadata = $meta; + return $this; + } + + public function getMetadata() + { + if ($this->metadata == null) + return []; + + return $this->metadata; + } + + public function setRider($rider) + { + $this->rider = $rider; + return $this; + } + + public function getRider() + { + return $this->rider; + } +} diff --git a/src/Entity/CreditTransaction.php b/src/Entity/CreditTransaction.php new file mode 100644 index 00000000..599a4851 --- /dev/null +++ b/src/Entity/CreditTransaction.php @@ -0,0 +1,92 @@ +date_create = new DateTime(); + } + + public function getID() + { + return $this->id; + } + + public function setCustomer(Customer $customer) + { + $this->customer = $customer; + return $this; + } + + public function setType($transaction_type) + { + $this->transaction_type = $transaction_type; + return $this; + } + + public function getType() + { + return $this->transaction_type; + } + + public function setAmount($amount) + { + $this->amount = $amount; + return $this; + } + + public function getAmount() + { + return $this->amount; + } + + public function getCustomer() + { + return $this->customer; + } +} diff --git a/src/Entity/Customer.php b/src/Entity/Customer.php index de2cd92e..271ef95e 100644 --- a/src/Entity/Customer.php +++ b/src/Entity/Customer.php @@ -96,6 +96,13 @@ class Customer */ protected $sessions; + // link to customer user + /** + * @ORM\OneToOne(targetEntity="CustomerUser", inversedBy="customer") + * @ORM\JoinColumn(name="customer_user_id", referencedColumnName="id", nullable=true) + */ + protected $customer_user; + // vehicles linked to customer /** * @ORM\OneToMany(targetEntity="CustomerVehicle", mappedBy="customer", cascade={"persist"}) @@ -208,6 +215,11 @@ class Customer */ protected $create_source; + /** + * @ORM\Column(type="integer", options={"unsigned": true, "default":0}) + */ + protected $credits; + // customer tags /** * @ORM\ManyToMany(targetEntity="CustomerTag", inversedBy="customers", indexBy="id") @@ -267,6 +279,8 @@ class Customer $this->create_source = 'unknown'; $this->ratings = new ArrayCollection(); + + $this->credits = 0; } public function getID() @@ -682,4 +696,26 @@ class Customer { return $this->car_club_customer_hub; } + + public function setCustomerUser(CustomerUser $cust_user = null) + { + $this->customer_user = $cust_user; + return $this; + } + + public function getCustomerUser() + { + return $this->customer_user; + } + + public function modifyCredits($amount) + { + $this->credits = bcadd($this->credits, $amount); + return $this; + } + + public function getCredits() + { + return $this->credits; + } } diff --git a/src/Entity/CustomerDeleteRequest.php b/src/Entity/CustomerDeleteRequest.php new file mode 100644 index 00000000..17bca994 --- /dev/null +++ b/src/Entity/CustomerDeleteRequest.php @@ -0,0 +1,209 @@ +id = $this->generateKeyID(); + $this->flag_confirmed = false; + $this->flag_completed = false; + $this->date_created = new DateTime(); + $this->date_code_sent = null; + $this->date_confirmed = null; + $this->date_completed = null; + $this->reason = null; + $this->customer = null; + } + + public function generateKeyID() + { + // use uniqid with unix timestamp prefix for now + return uniqid(time()); + } + + public function getID() + { + return $this->id; + } + + public function setPhoneNumber($num) + { + $this->phone_number = $num; + return $this; + } + + public function getPhoneNumber() + { + return $this->phone_number; + } + + public function setReason($reason) + { + $this->reason = $reason; + return $this; + } + + public function getReason() + { + return $this->reason; + } + + public function setConfirmCode($code) + { + $this->confirm_code = $code; + return $this; + } + + public function getConfirmCode() + { + return $this->confirm_code; + } + + public function setConfirmed($flag = true) + { + $this->flag_confirmed = $flag; + return $this; + } + + public function isConfirmed() + { + return $this->flag_confirmed; + } + + public function setCompleted($flag = true) + { + $this->flag_completed = $flag; + return $this; + } + + public function isCompleted() + { + return $this->flag_completed; + } + + public function getDateCreated() + { + return $this->date_created; + } + + public function setDateCodeSent(DateTime $date) + { + $this->date_code_sent = $date; + return $this; + } + + public function getDateCodeSent() + { + return $this->date_code_sent; + } + + public function setDateConfirmed(DateTime $date) + { + $this->date_confirmed = $date; + return $this; + } + + public function getDateConfirmed() + { + return $this->date_confirmed; + } + + public function setDateCompleted(DateTime $date) + { + $this->date_completed = $date; + return $this; + } + + public function getDateCompleted() + { + return $this->date_completed; + } + + public function setCustomer(Customer $cust = null) + { + $this->customer = $cust; + return $this; + } + + public function getCustomer() + { + return $this->customer; + } +} diff --git a/src/Entity/CustomerMetadata.php b/src/Entity/CustomerMetadata.php index 5ef9d518..af0fc6a7 100644 --- a/src/Entity/CustomerMetadata.php +++ b/src/Entity/CustomerMetadata.php @@ -63,6 +63,12 @@ class CustomerMetadata return $this; } + public function deleteMetadataInfo($id) + { + unset($this->meta_info[$id]); + return $this; + } + public function getMetaInfo($id) { // return null if we don't have it diff --git a/src/Entity/CustomerSession.php b/src/Entity/CustomerSession.php new file mode 100644 index 00000000..a8e2d6c6 --- /dev/null +++ b/src/Entity/CustomerSession.php @@ -0,0 +1,273 @@ +id = $this->generateKeyID(); + $this->date_generated = new DateTime(); + $this->customer_user = null; + $this->confirm_flag = false; + $this->date_confirmed = null; + $this->date_code_sent = null; + $this->reviews = new ArrayCollection(); + } + + public function generateKeyID() + { + // use uniqid for now, since primary key dupes will trigger exceptions + return uniqid(); + } + + public function getID() + { + return $this->id; + } + + public function setPhoneNumber($num) + { + $this->phone_number = $num; + return $this; + } + + public function getPhoneNumber() + { + return $this->phone_number; + } + + public function setPhoneModel($model) + { + $this->phone_model = $model; + return $this; + } + + public function getPhoneModel() + { + return $this->phone_model; + } + + public function setOSType($type) + { + $this->os_type = $type; + return $this; + } + + public function getOSType() + { + return $this->os_type; + } + + public function setOSVersion($version) + { + $this->os_version = $version; + return $this; + } + + public function getOSVersion() + { + return $this->os_version; + } + + public function setPhoneID($id) + { + $this->phone_id = $id; + return $this; + } + + public function getPhoneID() + { + return $this->phone_id; + } + + public function setDevicePushID($id) + { + $this->device_push_id = $id; + return $this; + } + + public function getDevicePushID() + { + return $this->device_push_id; + } + + public function setCustomer(Customer $cust = null) + { + $this->customer = $cust; + return $this; + } + + public function getCustomer() + { + return $this->customer; + } + + public function setCustomerUser(CustomerUser $cust_user = null) + { + $this->customer_user = $cust_user; + return $this; + } + + public function getCustomerUser() + { + return $this->customer_user; + } + + public function getDateGenerated() + { + return $this->date_generated; + } + + public function setConfirmCode($code) + { + $this->confirm_code = $code; + return $this; + } + + public function getConfirmCode() + { + return $this->confirm_code; + } + + public function setConfirmed($flag = true) + { + $this->confirm_flag = $flag; + return $this; + } + + public function isConfirmed() + { + return $this->confirm_flag; + } + + public function setDateConfirmed(DateTime $date) + { + $this->date_confirmed = $date; + return $this; + } + + public function getDateConfirmed() + { + return $this->date_confirmed; + } + + public function setDateCodeSent(DateTime $date) + { + $this->date_code_sent = $date; + return $this; + } + + public function getDateCodeSent() + { + return $this->date_code_sent; + } + + public function getReviews() + { + return $this->reviews; + } +} diff --git a/src/Entity/CustomerUser.php b/src/Entity/CustomerUser.php new file mode 100644 index 00000000..3f8c3b6b --- /dev/null +++ b/src/Entity/CustomerUser.php @@ -0,0 +1,106 @@ +metadata = []; + $this->sessions = new ArrayCollection(); + } + + public function setMetadata($meta) + { + $this->metadata = $meta; + return $this; + } + + public function getMetadata() + { + if ($this->metadata == null) + return []; + + return $this->metadata; + } + + public function setPhoneNumber($num) + { + $this->phone_number = $num; + return $this; + } + + public function getPhoneNumber() + { + return $this->phone_number; + } + + public function addMobileSession(MobileSession $session) + { + $this->sessions->add($session); + return $this; + } + + public function clearMobileSessions() + { + $this->sessions->clear(); + return $this; + } + + public function getMobileSessions() + { + return $this->sessions; + } + + public function setCustomer(Customer $cust = null) + { + $this->customer = $cust; + return $this; + } + + public function getCustomer() + { + return $this->customer; + } +} diff --git a/src/Entity/CustomerVehicle.php b/src/Entity/CustomerVehicle.php index a5525a38..a569d4b2 100644 --- a/src/Entity/CustomerVehicle.php +++ b/src/Entity/CustomerVehicle.php @@ -2,11 +2,13 @@ namespace App\Entity; +use App\Ramcar\InsuranceApplicationStatus; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\ArrayCollection; use Symfony\Component\Validator\Constraints as Assert; use DateTime; +use Doctrine\Common\Collections\Criteria; /** * @ORM\Entity @@ -114,10 +116,15 @@ class CustomerVehicle */ protected $flag_active; + // link to insurance + /** + * @ORM\OneToMany(targetEntity="InsuranceApplication", mappedBy="customer_vehicle") + */ + protected $insurance_applications; + public function __construct() { $this->flag_active = true; - $this->job_orders = new ArrayCollection(); } @@ -282,4 +289,21 @@ class CustomerVehicle { return $this->flag_active; } + + public function getInsuranceApplications() + { + return $this->insurance_applications; + } + + public function getLatestInsuranceApplication() + { + $criteria = Criteria::create() + ->where(Criteria::expr()->notIn('status', [InsuranceApplicationStatus::CANCELLED])) + ->orderBy(['date_submit' => Criteria::DESC]) + ->setMaxResults(1); + + $result = $this->insurance_applications->matching($criteria); + + return !empty($result) ? $result[0] : null; + } } diff --git a/src/Entity/GatewayTransaction.php b/src/Entity/GatewayTransaction.php new file mode 100644 index 00000000..e53a7f4b --- /dev/null +++ b/src/Entity/GatewayTransaction.php @@ -0,0 +1,193 @@ +date_create = new DateTime(); + $this->status = TransactionStatus::PENDING; + $this->metadata = []; + } + + public function getID() + { + return $this->id; + } + + public function setCustomer(Customer $customer) + { + $this->customer = $customer; + return $this; + } + + public function getCustomer() + { + return $this->customer; + } + + public function setDateCreate(DateTime $date) + { + $this->date_create = $date; + return $this; + } + + public function getDateCreate() + { + return $this->date_create; + } + + public function setDatePay(DateTime $date) + { + $this->date_pay = $date; + return $this; + } + + public function getDatePay() + { + return $this->date_pay; + } + + public function setAmount($amount) + { + $this->amount = $amount; + return $this; + } + + public function getAmount() + { + return $this->amount; + } + + public function setStatus($status) + { + $this->status = $status; + return $this; + } + + public function getStatus() + { + return $this->status; + } + + public function setType($type) + { + $this->type = $type; + return $this; + } + + public function getType() + { + return $this->type; + } + + public function setGateway($gateway) + { + $this->gateway = $gateway; + return $this; + } + + public function getGateway() + { + return $this->gateway; + } + + public function setExtTransactionId($transaction_id) + { + $this->ext_transaction_id = $transaction_id; + return $this; + } + + public function getExtTransactionId() + { + return $this->ext_transaction_id; + } + + public function setMetadata($metadata) + { + $this->metadata = $metadata; + return $this; + } + + public function getMetadata() + { + return $this->metadata; + } +} diff --git a/src/Entity/InsuranceApplication.php b/src/Entity/InsuranceApplication.php new file mode 100644 index 00000000..3887e259 --- /dev/null +++ b/src/Entity/InsuranceApplication.php @@ -0,0 +1,226 @@ +date_submit = new DateTime(); + $this->date_pay = null; + $this->date_complete = null; + $this->date_expire = null; + $this->metadata = []; + } + + public function getID() + { + return $this->id; + } + + public function setCustomer(Customer $cust = null) + { + $this->customer = $cust; + return $this; + } + + public function getCustomer() + { + return $this->customer; + } + + public function setCustomerVehicle(CustomerVehicle $cv = null) + { + $this->customer_vehicle = $cv; + return $this; + } + + public function getCustomerVehicle() + { + return $this->customer_vehicle; + } + + public function setDateSubmit(DateTime $date) + { + $this->date_submit = $date; + return $this; + } + + public function getDateSubmit() + { + return $this->date_submit; + } + + public function setGatewayTransaction(GatewayTransaction $transaction) + { + $this->gateway_transaction = $transaction; + return $this; + } + + public function getGatewayTransaction() + { + return $this->gateway_transaction; + } + + public function setStatus($status) + { + return $this->status = $status; + } + + public function getStatus() + { + return $this->status; + } + + public function setCOC($url) + { + return $this->coc_url = $url; + } + + public function getCOC() + { + return $this->coc_url; + } + + public function setDatePay(DateTime $date) + { + $this->date_pay = $date; + return $this; + } + + public function getDatePay() + { + return $this->date_pay; + } + + public function setDateComplete(DateTime $date) + { + $this->date_complete = $date; + return $this; + } + + public function getDateComplete() + { + return $this->date_complete; + } + + public function setDateExpire(DateTime $date) + { + $this->date_expire = $date; + return $this; + } + + public function getDateExpire() + { + return $this->date_expire; + } + + public function setExtTransactionId($transaction_id) + { + $this->ext_transaction_id = $transaction_id; + return $this; + } + + public function getExtTransactionId() + { + return $this->ext_transaction_id; + } + + public function setMetadata($metadata) + { + return $this->metadata = $metadata; + } + + public function getMetadata() + { + return $this->metadata; + } +} diff --git a/src/Entity/JobOrder.php b/src/Entity/JobOrder.php index b3b5a62e..885bc295 100644 --- a/src/Entity/JobOrder.php +++ b/src/Entity/JobOrder.php @@ -429,6 +429,18 @@ class JobOrder */ protected $cust_location; + // inventory count of hub at time of hub assignment + /** + * @ORM\Column(type="smallint") + */ + protected $inventory_count; + + // flag to indicate if customer is new aka has one or less job order in system + /** + * @ORM\Column(type="boolean") + */ + protected $flag_cust_new; + public function __construct() { $this->date_create = new DateTime(); @@ -453,6 +465,10 @@ class JobOrder $this->phone_mobile = ''; $this->will_wait = WillingToWaitContent::WILLING_TO_WAIT; + + $this->inventory_count = 0; + + $this->flag_cust_new = false; } public function getID() @@ -1217,4 +1233,27 @@ class JobOrder { return $this->cust_location; } + + public function setInventoryCount($inventory_count) + { + $this->inventory_count = $inventory_count; + return $this; + } + + public function getInventoryCount() + { + return $this->inventory_count; + } + + public function setCustNew($flag_cust_new = true) + { + $this->flag_cust_new = $flag_cust_new; + return $this; + } + + public function isCustNew() + { + return $this->flag_cust_new; + } + } diff --git a/src/Entity/MotoliteEvent.php b/src/Entity/MotoliteEvent.php new file mode 100644 index 00000000..ccdb367b --- /dev/null +++ b/src/Entity/MotoliteEvent.php @@ -0,0 +1,123 @@ +event_time = new DateTime('today noon'); + } + + public function setName($name) + { + $this->name = $name; + return $this; + } + + public function getID() + { + return $this->id; + } + + public function setID($id) + { + $this->id = $id; + return $this; + } + + public function getName() + { + return $this->name; + } + + public function setEventType($event_type) + { + $this->event_type = $event_type; + return $this; + } + + public function getEventType() + { + return $this->event_type; + } + + public function setEventTime($event_time) + { + $this->event_time = $event_time; + return $this; + } + + public function setUrl($url) + { + $this->url = $url; + return $this; + } + + public function getUrl() + { + return $this->url; + } + + public function getEventTime() + { + return $this->event_time; + } + + public function setImageFile($image_file = null) + { + $this->image_file = $image_file; + return $this; + } + + public function getImageFile() + { + return $this->image_file; + } +} diff --git a/src/Entity/Review.php b/src/Entity/Review.php index 9903784e..c34f7647 100644 --- a/src/Entity/Review.php +++ b/src/Entity/Review.php @@ -48,10 +48,17 @@ class Review // mobile session that sent review /** * @ORM\ManyToOne(targetEntity="MobileSession", inversedBy="reviews") - * @ORM\JoinColumn(name="mobile_session_id", referencedColumnName="id") + * @ORM\JoinColumn(name="mobile_session_id", referencedColumnName="id", nullable=true) */ protected $mobile_session; + // customer session (new) that sent review + /** + * @ORM\ManyToOne(targetEntity="CustomerSession", inversedBy="reviews") + * @ORM\JoinColumn(name="customer_session_id", referencedColumnName="id", nullable=true) + */ + protected $customer_session; + public function __construct() { $this->date_create = new DateTime(); @@ -113,5 +120,15 @@ class Review { return $this->mobile_session; } + + public function setCustomerSession(CustomerSession $customer_session) + { + $this->customer_session = $customer_session; + return $this; + } + public function getCustomerSession() + { + return $this->customer_session; + } } diff --git a/src/Entity/ReviewTag.php b/src/Entity/ReviewTag.php new file mode 100644 index 00000000..577686ff --- /dev/null +++ b/src/Entity/ReviewTag.php @@ -0,0 +1,62 @@ +id; + } + + public function setName($name) + { + $this->name = $name; + return $this; + } + + public function getName() + { + return $this->name; + } + + public function setType($type) + { + $this->type = $type; + return $this; + } + + public function getType() + { + return $this->type; + } + +} diff --git a/src/Entity/Rider.php b/src/Entity/Rider.php index 020393da..fcdb3ed2 100644 --- a/src/Entity/Rider.php +++ b/src/Entity/Rider.php @@ -9,7 +9,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Criteria; use App\Ramcar\JOStatus; -use Catalyst\APIBundle\Entity\User as APIUser; +use App\Entity\ApiUser as APIUser; /** * @ORM\Entity @@ -81,7 +81,7 @@ class Rider // current rating of rider /** - * @ORM\Column(type="integer") + * @ORM\Column(type="float") */ protected $curr_rating; @@ -131,7 +131,7 @@ class Rider protected $current_job_order; /** - * @ORM\OneToOne(targetEntity="Catalyst\APIBundle\Entity\User", inversedBy="rider") + * @ORM\OneToOne(targetEntity="App\Entity\ApiUser", inversedBy="rider") * @ORM\JoinColumn(name="api_user_id", referencedColumnName="id", nullable=true) */ protected $api_user; @@ -241,6 +241,17 @@ class Rider return $this->image_file; } + public function updateRatingAverage() + { + $total = 0; + + foreach ($this->ratings as $rating) { + $total += $rating->getRating(); + } + + $this->setCurrentRating(round($total / $this->ratings->count(), 2)); + } + public function setCurrentRating($rating) { $this->curr_rating = $rating; diff --git a/src/Entity/Service.php b/src/Entity/Service.php index e24cd672..cd373964 100644 --- a/src/Entity/Service.php +++ b/src/Entity/Service.php @@ -28,6 +28,12 @@ class Service */ protected $name; + // icon used by service on app + /** + * @ORM\Column(type="string", length=80, nullable=true) + */ + protected $icon; + // link to partners with this service /** * @ORM\ManyToMany(targetEntity="Partner", mappedBy="services", fetch="EXTRA_LAZY") @@ -55,6 +61,17 @@ class Service return $this->name; } + public function setIcon($icon) + { + $this->icon = $icon; + return $this; + } + + public function getIcon() + { + return $this->icon; + } + public function addPartner(Partner $partner) { $this->partners[$partner->getID()] = $partner; diff --git a/src/Entity/ServiceOffering.php b/src/Entity/ServiceOffering.php new file mode 100644 index 00000000..3e8a935f --- /dev/null +++ b/src/Entity/ServiceOffering.php @@ -0,0 +1,81 @@ +id; + } + + public function setName($name) + { + $this->name = $name; + return $this; + } + + public function getName() + { + return $this->name; + } + + public function setCode($code) + { + $this->code = $code; + return $this; + } + + public function getCode() + { + return $this->code; + } + + public function setFee($fee) + { + $this->fee = $fee; + return $this; + } + + public function getFee() + { + return $this->fee; + } +} + diff --git a/src/Entity/Ticket.php b/src/Entity/Ticket.php index 0249d985..a3704526 100644 --- a/src/Entity/Ticket.php +++ b/src/Entity/Ticket.php @@ -139,9 +139,22 @@ class Ticket */ protected $other_description; + // is it a late documentation? + /** + * @ORM\Column(type="boolean", nullable=true) + */ + protected $flag_late_doc; + + // reason for late documentation + /** + * @ORM\Column(type="text", nullable=true) + */ + protected $late_doc_reason; + public function __construct() { $this->date_create = new DateTime(); + $this->flag_late_doc = false; } public function getID() @@ -354,4 +367,26 @@ class Ticket { return $this->other_description; } + + public function setLateDoc($flag_late_doc = true) + { + $this->flag_late_doc = $flag_late_doc; + return $this; + } + + public function isLateDoc() + { + return $this->flag_late_doc; + } + + public function setLateDocReason($late_doc_reason) + { + $this->late_doc_reason = $late_doc_reason; + return $this; + } + + public function getLateDocReason() + { + return $this->late_doc_reason; + } } diff --git a/src/EntityListener/GatewayTransactionListener.php b/src/EntityListener/GatewayTransactionListener.php new file mode 100644 index 00000000..745fc5f9 --- /dev/null +++ b/src/EntityListener/GatewayTransactionListener.php @@ -0,0 +1,75 @@ +em = $em; + $this->ic = $ic; + } + + public function postUpdate(GatewayTransaction $gt_obj, LifecycleEventArgs $args) + { + // get transaction changes + $em = $args->getEntityManager(); + $uow = $em->getUnitOfWork(); + $changeset = $uow->getEntityChangeSet($gt_obj); + + if (array_key_exists('status', $changeset)) { + $field_changes = $changeset['status']; + + $prev_value = $field_changes[0] ?? null; + $new_value = $field_changes[1] ?? null; + + // only do something if the status has changed to paid + if ($prev_value !== $new_value && $new_value === TransactionStatus::PAID) { + // handle based on type + // TODO: add types here as we go. there's probably a better way to do this. + switch ($gt_obj->getType()) { + case 'insurance_premium': + return $this->handleInsurancePremium($gt_obj); + break; + default: + break; + } + } + } + } + + protected function handleInsurancePremium($gt_obj) + { + // get insurance application object + $obj = $this->em->getRepository(InsuranceApplication::class)->findOneBy([ + 'gateway_transaction' => $gt_obj, + ]); + + if (!empty($obj)) { + // mark as paid + $obj->setDatePay(new DateTime()); + $obj->setStatus(InsuranceApplicationStatus::PAID); + $this->em->flush(); + } + + // flag on api as paid + $result = $this->ic->tagApplicationPaid($obj->getID()); + if (!$result['success'] || $result['response']['transaction_code'] !== 'GR004') { + error_log("INSURANCE MARK AS PAID FAILED FOR " . $obj->getID() . ": " . $result['error']['message']); + } + } +} + diff --git a/src/InvoiceRule/BatteryReplacementWarranty.php b/src/InvoiceRule/BatteryReplacementWarranty.php new file mode 100644 index 00000000..ee1497b5 --- /dev/null +++ b/src/InvoiceRule/BatteryReplacementWarranty.php @@ -0,0 +1,127 @@ +em = $em; + } + + public function getID() + { + return 'battery_warranty'; + } + + public function compute($criteria, &$total) + { + $stype = $criteria->getServiceType(); + + $items = []; + if ($stype == $this->getID()) + { + // get the entries + $entries = $criteria->getEntries(); + + foreach($entries as $entry) + { + $batt = $entry['battery']; + $qty = 1; + $price = $this->getServiceTypeFee(); + + $items[] = [ + 'service_type' => $this->getID(), + 'battery' => $batt, + 'qty' => $qty, + 'title' => $this->getTitle($batt), + 'price' => $price, + ]; + + $qty_price = bcmul($price, $qty, 2); + $total['total_price'] = bcadd($total['total_price'], $qty_price, 2); + } + } + + return $items; + } + + public function getServiceTypeFee() + { + $code = 'battery_replacement_warranty_fee'; + + // find the service fee using the code + // if we can't find the fee, return 0 + $fee = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + if ($fee == null) + return 0; + + return $fee->getFee(); + } + + public function validatePromo($criteria, $promo_id) + { + return false; + } + + public function validateInvoiceItems($criteria, $invoice_items) + { + // check service type. Only battery sales and battery warranty should have invoice items. Since this is the + // battery replacement warranty rule, we only check for battery replacement warranty. + $stype = $criteria->getServiceType(); + if ($stype != ServiceType::BATTERY_REPLACEMENT_WARRANTY) + return null; + + // return error if there's a problem, false otherwise + if (!empty($invoice_items)) + { + // check if this is a valid battery + foreach ($invoice_items as $item) + { + $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; + + // 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 getTitle($battery) + { + $title = $battery->getModel()->getName() . ' ' . $battery->getSize()->getName() . ' - Service Unit'; + + return $title; + } +} + diff --git a/src/InvoiceRule/BatterySales.php b/src/InvoiceRule/BatterySales.php new file mode 100644 index 00000000..45b060d1 --- /dev/null +++ b/src/InvoiceRule/BatterySales.php @@ -0,0 +1,123 @@ +em = $em; + } + + public function getID() + { + return 'battery_new'; + } + + public function compute($criteria, &$total) + { + $stype = $criteria->getServiceType(); + + $items = []; + if ($stype == $this->getID()) + { + // get the entries + $entries = $criteria->getEntries(); + foreach($entries as $entry) + { + $batt = $entry['battery']; + $qty = $entry['qty']; + $trade_in = null; + + if (isset($entry['trade_in'])) + $trade_in = $entry['trade_in']; + + $size = $batt->getSize(); + + if ($trade_in == null) + { + // battery purchase + $price = $batt->getSellingPrice(); + + $items[] = [ + 'service_type' => $this->getID(), + 'battery' => $batt, + 'qty' => $qty, + 'title' => $this->getTitle($batt), + 'price' => $price, + ]; + + $qty_price = bcmul($price, $qty, 2); + + $total['sell_price'] = bcadd($total['sell_price'], $qty_price, 2); + $total['total_price'] = bcadd($total['total_price'], $qty_price, 2); + } + } + } + + return $items; + } + + public function validatePromo($criteria, $promo_id) + { + return false; + } + + public function validateInvoiceItems($criteria, $invoice_items) + { + // check service type. Only battery sales and battery warranty should have invoice items. Since this is the battery sales + // rule, we only check for battery sales. + $stype = $criteria->getServiceType(); + if ($stype != ServiceType::BATTERY_REPLACEMENT_NEW) + return null; + + // return error if there's a problem, false otherwise + if (!empty($invoice_items)) + { + // check if this is a valid battery + foreach ($invoice_items as $item) + { + $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; + + // 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 getTitle($battery) + { + $title = $battery->getModel()->getName() . ' ' . $battery->getSize()->getName(); + + return $title; + } +} diff --git a/src/InvoiceRule/DiscountType.php b/src/InvoiceRule/DiscountType.php new file mode 100644 index 00000000..8c89e9e7 --- /dev/null +++ b/src/InvoiceRule/DiscountType.php @@ -0,0 +1,116 @@ +em = $em; + } + + public function getID() + { + return 'discount'; + } + + public function compute($criteria, &$total) + { + $items = []; + + $promos = $criteria->getPromos(); + + if (empty($promos)) + return $items; + + // 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 = bcmul($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); + $num1 = bcdiv(1.5, 0.7, 9); + $num1_rate = bcmul($num1, $rate, 9); + $multiplier = bcsub(1, $num1_rate, 9); + + $discount = bcmul($total['sell_price'], $multiplier, 2); + break; + } + + // if discount is higher than 0, add to items + if ($discount > 0) + { + $qty = 1; + $price = bcmul(-1, $discount, 2); + + $items[] = [ + 'promo' => $promo, + 'title' => $this->getTitle(), + 'qty' => $qty, + 'price' => $price, + ]; + } + + $total['discount'] = $discount; + $total['total_price'] = bcsub($total['total_price'], $discount, 2); + + return $items; + } + + public function validatePromo($criteria, $promo_id) + { + // return error if there's a problem, false otherwise + // check service type + $stype = $criteria->getServiceType(); + + // discount/promo only applies for battery sales + 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 validateInvoiceItems($criteria, $invoice_items) + { + return null; + } + + protected function getTitle() + { + $title = 'Promo discount'; + + return $title; + } +} + diff --git a/src/InvoiceRule/Fuel.php b/src/InvoiceRule/Fuel.php new file mode 100644 index 00000000..f843e1c0 --- /dev/null +++ b/src/InvoiceRule/Fuel.php @@ -0,0 +1,154 @@ +em = $em; + } + + public function getID() + { + return 'fuel'; + } + + public function compute($criteria, &$total) + { + $stype = $criteria->getServiceType(); + + $items = []; + + if ($stype == $this->getID()) + { + $cv = $criteria->getCustomerVehicle(); + + $fee = $this->getServiceTypeFee($cv); + + $ftype = $cv->getFuelType(); + + // add the service fee to items + $qty = 1; + $items[] = [ + 'service_type' => $this->getID(), + 'qty' => $qty, + 'title' => $this->getServiceTitle($ftype), + 'price' => $fee, + ]; + + $qty_fee = bcmul($qty, $fee, 2); + $total_price = $qty_fee; + + switch ($ftype) + { + case FuelType::GAS: + case FuelType::DIESEL: + $qty = 1; + $price = $this->getFuelFee($ftype); + $items[] = [ + 'service_type' => $this->getID(), + 'qty' => $qty, + 'title' => $this->getTitle($ftype), + 'price' => $price, + ]; + + $qty_price = bcmul($price, $qty, 2); + $total_price = bcadd($total_price, $qty_price, 2); + + break; + default: + $qty = 1; + $price = 0; + $items[] = [ + 'service_type' => $this->getID(), + 'qty' => $qty, + 'title' => $this->getTitle('Unknown'), + 'price' => $price, + ]; + + $qty_price = bcmul($price, $qty, 2); + $total_price = bcadd($total_price, $qty_price, 2); + + break; + } + + $total['total_price'] = bcadd($total['total_price'], $total_price, 2); + } + + return $items; + } + + public function getServiceTypeFee(CustomerVehicle $cv) + { + // check if customer vehicle has a motolite battery + // if yes, set the code to the motolite user service fee + if ($cv->hasMotoliteBattery()) + $code = 'motolite_user_service_fee'; + else + $code = 'fuel_service_fee'; + + // find the service fee using the code + // if we can't find the fee, return 0 + $fee = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + if ($fee == null) + return 0; + + return $fee->getFee(); + } + + public function getFuelFee($fuel_type) + { + $code = ''; + if ($fuel_type == FuelType::GAS) + $code = 'fuel_gas_fee'; + if ($fuel_type == FuelType::DIESEL) + $code = 'fuel_diesel_fee'; + + // find the fuel fee for the specific fuel type using the code + // if we can't find the fee, return 0 + $fee = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + if ($fee == null) + return 0; + + return $fee->getFee(); + } + + public function validatePromo($criteria, $promo_id) + { + return false; + } + + public function validateInvoiceItems($criteria, $invoice_items) + { + return null; + } + + protected function getTitle($fuel_type) + { + $title = '4L - ' . ucfirst($fuel_type); + + return $title; + } + + protected function getServiceTitle($fuel_type) + { + $title = 'Service - ' . ServiceType::getName(ServiceType::EMERGENCY_REFUEL); + + return $title; + } +} diff --git a/src/InvoiceRule/Jumpstart.php b/src/InvoiceRule/Jumpstart.php new file mode 100644 index 00000000..dce41d99 --- /dev/null +++ b/src/InvoiceRule/Jumpstart.php @@ -0,0 +1,95 @@ +em = $em; + } + + public function getID() + { + return 'jumpstart_troubleshoot'; + } + + public function compute($criteria, &$total) + { + $stype = $criteria->getServiceType(); + $source = $criteria->getSource(); + + $items = []; + + if ($stype == $this->getID()) + { + $cv = $criteria->getCustomerVehicle(); + $fee = $this->getServiceTypeFee($source, $cv); + + // add the service fee to items + $qty = 1; + $items[] = [ + 'service_type' => $this->getID(), + 'qty' => $qty, + 'title' => $this->getServiceTitle(), + 'price' => $fee, + ]; + + $qty_price = bcmul($fee, $qty, 2); + $total['total_price'] = bcadd($total['total_price'], $qty_price, 2); + } + + return $items; + } + + public function getServiceTypeFee($source, CustomerVehicle $cv) + { + // check the source of JO + // (1) if from app, service fee is 0 if motolite user. jumpstart fee for app if non-motolite user. + // (2) any other source, jumpstart fees are charged whether motolite user or not + if ($source == TransactionOrigin::MOBILE_APP) + { + if ($cv->hasMotoliteBattery()) + $code = 'motolite_user_service_fee'; + else + $code = 'jumpstart_fee_mobile_app'; + } + else + $code = 'jumpstart_fee'; + + $fee = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + if ($fee == null) + return 0; + + return $fee->getFee(); + } + + public function validatePromo($criteria, $promo_id) + { + return false; + } + + public function validateInvoiceItems($criteria, $invoice_items) + { + return null; + } + + protected function getServiceTitle() + { + $title = 'Service - Troubleshooting fee'; + + return $title; + } +} diff --git a/src/InvoiceRule/JumpstartWarranty.php b/src/InvoiceRule/JumpstartWarranty.php new file mode 100644 index 00000000..9423da44 --- /dev/null +++ b/src/InvoiceRule/JumpstartWarranty.php @@ -0,0 +1,81 @@ +em = $em; + } + + public function getID() + { + return 'jumpstart_warranty'; + } + + public function compute($criteria, &$total) + { + $stype = $criteria->getServiceType(); + + $items = []; + + if ($stype == $this->getID()) + { + $fee = $this->getServiceTypeFee(); + + // add the service fee to items + $qty = 1; + $items[] = [ + 'service_type' => $this->getID(), + 'qty' => $qty, + 'title' => $this->getServiceTitle(), + 'price' => $fee, + ]; + + $qty_price = bcmul($fee, $qty, 2); + $total['total_price'] = bcadd($total['total_price'], $qty_price, 2); + } + + return $items; + } + + public function getServiceTypeFee() + { + $code = 'jumpstart_warranty_fee'; + + // find the service fee using the code + // if we can't find the fee, return 0 + $fee = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + if ($fee == null) + return 0; + + return $fee->getFee(); + } + + public function validatePromo($criteria, $promo_id) + { + return false; + } + + public function validateInvoiceItems($criteria, $invoice_items) + { + return null; + } + + protected function getServiceTitle() + { + $title = 'Service - Troubleshooting fee'; + + return $title; + } +} diff --git a/src/InvoiceRule/Overheat.php b/src/InvoiceRule/Overheat.php new file mode 100644 index 00000000..4c06bddb --- /dev/null +++ b/src/InvoiceRule/Overheat.php @@ -0,0 +1,128 @@ +em = $em; + } + + public function getID() + { + return 'overheat'; + } + + public function compute($criteria, &$total) + { + $stype = $criteria->getServiceType(); + $has_coolant = $criteria->hasCoolant(); + + $items = []; + + if ($stype == $this->getID()) + { + $cv = $criteria->getCustomerVehicle(); + $fee = $this->getServiceTypeFee($cv); + + // add the service fee to items + $qty = 1; + $items[] = [ + 'service_type' => $this->getID(), + 'qty' => $qty, + 'title' => $this->getServiceTitle(), + 'price' => $fee, + ]; + + $qty_fee = bcmul($qty, $fee, 2); + $total_price = $qty_fee; + + if ($has_coolant) + { + $coolant_fee = $this->getCoolantFee(); + $items[] = [ + 'service_type' => $this->getID(), + 'qty' => $qty, + 'title' => $this->getServiceCoolantTitle(), + 'price' => $coolant_fee, + ]; + + $qty_price = bcmul($coolant_fee, $qty, 2); + $total_price = bcadd($total_price, $qty_price, 2); + } + + $total['total_price'] = bcadd($total['total_price'], $total_price, 2); + } + + return $items; + } + + public function getServiceTypeFee(CustomerVehicle $cv) + { + $code = 'overheat_fee'; + + // check if customer vehicle has a motolite battery + // if yes, set the code to the motolite user service fee + if ($cv->hasMotoliteBattery()) + $code = 'motolite_user_service_fee'; + + // find the service fee for overheat using the code + // if we can't find the fee, return 0 + $fee = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + if ($fee == null) + return 0; + + return $fee->getFee(); + } + + public function getCoolantFee() + { + $code = 'coolant_fee'; + + // find the service fee using the code + // if we can't find the fee, return 0 + $fee = $this->em->getRepository(ServiceOffering::class)->findOneBy($code); + + if ($fee == null) + return 0; + + return $fee->getFee(); + } + + public function validatePromo($criteria, $promo_id) + { + return false; + } + + public function validateInvoiceItems($criteria, $invoice_items) + { + return null; + } + + protected function getServiceTitle() + { + $title = 'Service - ' . ServiceType::getName(ServiceType::OVERHEAT_ASSISTANCE); + + return $title; + } + + protected function getServiceCoolantTitle() + { + $title = '4L Coolant'; + + return $title; + } +} diff --git a/src/InvoiceRule/PostRecharged.php b/src/InvoiceRule/PostRecharged.php new file mode 100644 index 00000000..808f2340 --- /dev/null +++ b/src/InvoiceRule/PostRecharged.php @@ -0,0 +1,81 @@ +em = $em; + } + + public function getID() + { + return 'post_recharged'; + } + + public function compute($criteria, &$total) + { + $stype = $criteria->getServiceType(); + + $items = []; + + if ($stype == $this->getID()) + { + $fee = $this->getServiceTypeFee(); + + $qty = 1; + $items[] = [ + 'service_type' => $this->getID(), + 'qty' => $qty, + 'title' => $this->getServiceTitle(), + 'price' => $fee, + ]; + + $qty_price = bcmul($fee, $qty, 2); + $total['total_price'] = bcadd($total['total_price'], $qty_price, 2); + + } + + return $items; + } + + public function getServiceTypeFee() + { + $code = 'post_recharged_fee'; + + // find the service fee for post recharged using the code + // if we can't find the fee, return 0 + $fee = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + if ($fee == null) + return 0; + + return $fee->getFee(); + } + + public function validatePromo($criteria, $promo_id) + { + return false; + } + + public function validateInvoiceItems($criteria, $invoice_items) + { + return null; + } + + protected function getServiceTitle() + { + $title = 'Recharge fee'; + + return $title; + } +} diff --git a/src/InvoiceRule/PostReplacement.php b/src/InvoiceRule/PostReplacement.php new file mode 100644 index 00000000..aba6d9aa --- /dev/null +++ b/src/InvoiceRule/PostReplacement.php @@ -0,0 +1,80 @@ +em = $em; + } + + public function getID() + { + return 'post_replacement'; + } + + public function compute($criteria, &$total) + { + $stype = $criteria->getServiceType(); + + $items = []; + + if ($stype == $this->getID()) + { + $fee = $this->getServiceTypeFee(); + + $qty = 1; + $items[] = [ + 'service_type' => $this->getID(), + 'qty' => $qty, + 'title' => $this->getServiceTitle(), + 'price' => $fee, + ]; + + $qty_price = bcmul($fee, $qty, 2); + $total['total_price'] = bcadd($total['total_price'], $qty_price, 2); + } + + return $items; + } + + public function getServiceTypeFee() + { + $code = 'post_replacement_fee'; + + // find the service fee using the code + // if we can't find the fee, return 0 + $fee = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + if ($fee == null) + return 0; + + return $fee->getFee(); + } + + public function validatePromo($criteria, $promo_id) + { + return false; + } + + public function validateInvoiceItems($criteria, $invoice_items) + { + return null; + } + + protected function getServiceTitle() + { + $title = 'Battery replacement'; + + return $title; + } +} diff --git a/src/InvoiceRule/Tax.php b/src/InvoiceRule/Tax.php new file mode 100644 index 00000000..b1e6a600 --- /dev/null +++ b/src/InvoiceRule/Tax.php @@ -0,0 +1,127 @@ +em = $em; + } + + public function getID() + { + return 'tax'; + } + + public function compute($criteria, &$total) + { + // check if taxable + if (!$criteria->isTaxable()) + { + // nothing to compute + return []; + } + + $tax_rate = $this->getTaxRate(); + + $is_battery_sales = false; + $total_price = 0; + + // compute tax per item if service type is battery sales + $stype = $criteria->getServiceType(); + + if ($stype == ServiceType::BATTERY_REPLACEMENT_NEW) + { + // get the entries + $entries = $criteria->getEntries(); + + foreach($entries as $entry) + { + // check if entry is trade-in + if (isset($entry['trade_in'])) + { + // continue to next entry + continue; + } + + $battery = $entry['battery']; + $qty = $entry['qty']; + + $price = $battery->getSellingPrice(); + + $vat = $this->getTaxAmount($price, $tax_rate); + + $qty_price = bcmul($price, $qty, 2); + $qty_vat = bcmul($vat, $qty, 2); + $price_minus_vat = bcsub($price, $vat, 2); + $qty_price_minus_vat = bcmul($price_minus_vat, $qty, 2); + + $total['vat'] = bcadd($total['vat'], $qty_vat, 2); + $total['vat_ex_price'] = bcadd($total['vat_ex_price'], $qty_price_minus_vat, 2); + } + } + else + { + // compute VAT after adding all item costs, if service type is not battery sales + $total_price = $total['total_price']; + + $vat_ex_price = $this->getTaxExclusivePrice($total_price, $tax_rate); + $vat = bcsub($total_price, $vat_ex_price, 2); + + $total['vat_ex_price'] = $vat_ex_price; + $total['vat'] = $vat; + } + + return []; + } + + public function validatePromo($criteria, $promo_id) + { + return false; + } + + public function validateInvoiceItems($criteria, $invoice_items) + { + return null; + } + + protected function getTaxAmount($price, $tax_rate) + { + $vat_ex_price = $this->getTaxExclusivePrice($price, $tax_rate); + $tax_amount = bcsub($price, $vat_ex_price, 2); + + return $tax_amount; + } + + protected function getTaxExclusivePrice($price, $tax_rate) + { + $tax_ex_price = bcdiv($price, bcadd(1, $tax_rate, 2), 2); + return $tax_ex_price; + } + + protected function getTaxRate() + { + $code = 'tax'; + + // find the service fee using the code + // if we can't find the fee, return 0 + $fee = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + if ($fee == null) + return 0; + + return $fee->getFee(); + } +} + diff --git a/src/InvoiceRule/TireRepair.php b/src/InvoiceRule/TireRepair.php new file mode 100644 index 00000000..755c11bd --- /dev/null +++ b/src/InvoiceRule/TireRepair.php @@ -0,0 +1,88 @@ +em = $em; + } + + public function getID() + { + return 'tire'; + } + + public function compute($criteria, &$total) + { + $stype = $criteria->getServiceType(); + + $items = []; + + if ($stype == $this->getID()) + { + $cv = $criteria->getCustomerVehicle(); + $fee = $this->getServiceTypeFee($cv); + + // add the service fee to items + $qty = 1; + $items[] = [ + 'service_type' => $this->getID(), + 'qty' => $qty, + 'title' => $this->getServiceTitle(), + 'price' => $fee, + ]; + + $qty_price = bcmul($fee, $qty, 2); + $total['total_price'] = bcadd($total['total_price'], $qty_price, 2); + } + + return $items; + } + + protected function getServiceTypeFee(CustomerVehicle $cv) + { + $code = 'tire_repair_fee'; + + // check if customer vehicle has a motolite battery + // if yes, set the code to the motolite user service fee + if ($cv->hasMotoliteBattery()) + $code = 'motolite_user_service_fee'; + + // find the service fee using the code + // if we can't find the fee, return 0 + $fee = $this->em->getRepository(ServiceOffering::class)->findOneBy(['code' => $code]); + + if ($fee == null) + return 0; + + return $fee->getFee(); + } + + public function validatePromo($criteria, $promo_id) + { + return false; + } + + public function validateInvoiceItems($criteria, $invoice_items) + { + return null; + } + + protected function getServiceTitle() + { + $title = 'Service - Flat Tire'; + + return $title; + } +} diff --git a/src/InvoiceRule/TradeIn.php b/src/InvoiceRule/TradeIn.php new file mode 100644 index 00000000..8e3c6063 --- /dev/null +++ b/src/InvoiceRule/TradeIn.php @@ -0,0 +1,87 @@ +getEntries(); + foreach($entries as $entry) + { + $batt = $entry['battery']; + $qty = $entry['qty']; + $trade_in_type = null; + + if (isset($entry['trade_in'])) + $trade_in_type = $entry['trade_in']; + + if ($trade_in_type != null) + { + $ti_rate = $this->getTradeInRate($batt, $trade_in_type); + + $qty_ti = bcmul($ti_rate, $qty, 2); + + $total['ti_rate'] = bcadd($total['ti_rate'], $qty_ti, 2); + $total['total_price'] = bcsub($total['total_price'], $qty_ti, 2); + + $price = bcmul($ti_rate, -1, 2); + + $items[] = [ + 'qty' => $qty, + 'title' => $this->getTitle($batt, $trade_in_type), + 'price' => $price, + ]; + } + } + + return $items; + } + + public function validatePromo($criteria, $promo_id) + { + return false; + } + + public function validateInvoiceItems($criteria, $invoice_items) + { + return null; + } + + protected function getTradeInRate($battery, $trade_in_type) + { + $size = $battery->getSize(); + + switch ($trade_in_type) + { + case TradeInType::MOTOLITE: + return $size->getTIPriceMotolite(); + case TradeInType::PREMIUM: + return $size->getTIPricePremium(); + case TradeInType::OTHER: + return $size->getTIPriceOther(); + } + + return 0; + } + + protected function getTitle($battery, $trade_in_type) + { + $title = 'Trade-in ' . TradeInType::getName($trade_in_type) . ' ' . $battery->getSize()->getName() . ' battery'; + + return $title; + } +} + diff --git a/src/InvoiceRuleInterface.php b/src/InvoiceRuleInterface.php new file mode 100644 index 00000000..46f0d015 --- /dev/null +++ b/src/InvoiceRuleInterface.php @@ -0,0 +1,16 @@ + 'Purchase', + 'redemption' => 'Redemption', + ]; +} diff --git a/src/Ramcar/InsuranceApplicationStatus.php b/src/Ramcar/InsuranceApplicationStatus.php new file mode 100644 index 00000000..8480239c --- /dev/null +++ b/src/Ramcar/InsuranceApplicationStatus.php @@ -0,0 +1,18 @@ + 'Created', + 'paid' => 'Paid', + 'completed' => 'Completed', + 'cancelled' => 'Cancelled', + ]; +} diff --git a/src/Ramcar/InsuranceClientType.php b/src/Ramcar/InsuranceClientType.php new file mode 100644 index 00000000..1532ef7b --- /dev/null +++ b/src/Ramcar/InsuranceClientType.php @@ -0,0 +1,14 @@ + 'Individual', + 'c' => 'Corporate', + ]; +} diff --git a/src/Ramcar/InsuranceMVType.php b/src/Ramcar/InsuranceMVType.php new file mode 100644 index 00000000..73755821 --- /dev/null +++ b/src/Ramcar/InsuranceMVType.php @@ -0,0 +1,32 @@ + "Car", + '2' => "Shuttle Bus", + '3' => "Motorcycle", + '4' => "Motorcycle with Sidecar", + '5' => "Non-Conventional MV", + '8' => "Sports Utility Vehicle", + '9' => "Truck", + '10' => "Trailer", + '11' => "UV Private", + '12' => "UV Commercial", + '13' => "Tricycle", + ]; +} diff --git a/src/Ramcar/InsuranceVehicleLine.php b/src/Ramcar/InsuranceVehicleLine.php new file mode 100644 index 00000000..7e66426d --- /dev/null +++ b/src/Ramcar/InsuranceVehicleLine.php @@ -0,0 +1,18 @@ + 'Private Car', + 'mcoc' => 'Motorcycle / Motorcycle with Sidecar / Tricycle (Private)', + 'ccoc' => 'Commercial Vehicle', + 'lcoc' => 'Motorcycle / Motorcycle with Sidecar / Tricycle (Public)', + ]; +} diff --git a/src/Ramcar/InvoiceCriteria.php b/src/Ramcar/InvoiceCriteria.php index 6665226d..b27395da 100644 --- a/src/Ramcar/InvoiceCriteria.php +++ b/src/Ramcar/InvoiceCriteria.php @@ -15,6 +15,8 @@ class InvoiceCriteria protected $flag_coolant; protected $discount; protected $service_charges; + protected $flag_taxable; + protected $source; // use Ramcar's TransactionOrigin // entries are battery and trade-in combos protected $entries; @@ -28,6 +30,8 @@ class InvoiceCriteria $this->flag_coolant = false; $this->discount = 0; $this->service_charges = []; + $this->flag_taxable = false; + $this->source = ''; } public function setServiceType($stype) @@ -153,4 +157,26 @@ class InvoiceCriteria return $this->service_charges; } + public function setIsTaxable($flag = true) + { + $this->flag_taxable = $flag; + return $this; + } + + public function isTaxable() + { + return $this->flag_taxable; + } + + public function setSource($source) + { + $this->source = $source; + return $this; + } + + public function getSource() + { + return $this->source; + } + } diff --git a/src/Ramcar/MotoliteEventType.php b/src/Ramcar/MotoliteEventType.php new file mode 100644 index 00000000..3f771ae0 --- /dev/null +++ b/src/Ramcar/MotoliteEventType.php @@ -0,0 +1,16 @@ + 'Event', + 'news' => 'News', + 'blog' => 'Blog', + ]; +} diff --git a/src/Ramcar/ReviewTagType.php b/src/Ramcar/ReviewTagType.php new file mode 100644 index 00000000..a659a29c --- /dev/null +++ b/src/Ramcar/ReviewTagType.php @@ -0,0 +1,14 @@ + 'Rider', + 'partner' => 'Partner', + ]; +} diff --git a/src/Ramcar/ServiceType.php b/src/Ramcar/ServiceType.php index 83768bdc..0a25e721 100644 --- a/src/Ramcar/ServiceType.php +++ b/src/Ramcar/ServiceType.php @@ -17,7 +17,7 @@ class ServiceType extends NameValue const COLLECTION = [ 'battery_new' => 'Battery Sales', 'battery_warranty' => 'Under Warranty', - 'jumpstart_troubleshoot' => 'General Service', + 'jumpstart_troubleshoot' => 'General Service - Jumpstart', 'jumpstart_warranty' => 'General Service - Warranty', 'post_recharged' => 'Post - Recharged', 'post_replacement' => 'Post - Replacement', diff --git a/src/Ramcar/TransactionOrigin.php b/src/Ramcar/TransactionOrigin.php index fabf2900..a204b35f 100644 --- a/src/Ramcar/TransactionOrigin.php +++ b/src/Ramcar/TransactionOrigin.php @@ -12,6 +12,7 @@ class TransactionOrigin extends NameValue const WALK_IN = 'walk_in'; const LAZADA = 'lazada'; const THIRD_PARTY = 'third_party'; + const SHOPIFY = 'shopify'; const YOKOHAMA_OP_FACEBOOK = 'yokohama_op_facebook'; const YOKOHAMA_TWITTER = 'yokohama_twitter'; const YOKOHAMA_INSTAGRAM = 'yokohama_instagram'; @@ -28,6 +29,7 @@ class TransactionOrigin extends NameValue 'walk_in' => 'Walk-in', 'lazada' => 'Lazada', 'third_party' => 'Third Party', + 'shopify' => 'Shopify', 'yokohama_op_facebook' => 'Yokohama OP Facebook', 'yokohama_twitter' => 'Yokohama Twitter', 'yokohama_instagram' => 'Yokohama Instagram', diff --git a/src/Ramcar/TransactionStatus.php b/src/Ramcar/TransactionStatus.php new file mode 100644 index 00000000..37c55061 --- /dev/null +++ b/src/Ramcar/TransactionStatus.php @@ -0,0 +1,18 @@ + 'Pending', + 'paid' => 'Paid', + 'cancelled' => 'Cancelled', + 'refunded' => 'Refunded', + ]; +} diff --git a/src/Service/CreditManager.php b/src/Service/CreditManager.php new file mode 100644 index 00000000..275261bb --- /dev/null +++ b/src/Service/CreditManager.php @@ -0,0 +1,47 @@ +cust = $cust; + $this->em = $em; + } + + public function recordTransaction($type, $amount) + { + // make sure this is a valid transaction type + $this->validateType($type); + + // update customer + $this->cust->modifyCredits($amount); + + // create new record + $trans = new CreditTransaction(); + $trans->setCustomer($this->cust) + ->setAmount($amount) + ->setType($type); + + $this->em->persist($trans); + } + + protected function validateType($type) + { + if (!CreditTransactionType::validate($type)) { + throw new Exception('Invalid transaction type'); + } + } +} diff --git a/src/Service/FCMSender.php b/src/Service/FCMSender.php new file mode 100644 index 00000000..4b2c646d --- /dev/null +++ b/src/Service/FCMSender.php @@ -0,0 +1,115 @@ +client = new FcmClient($server_key, $sender_id); + $this->translator = $translator; + } + + public function send($recipients, $title, $body, $data = [], $color = null, $sound = null, $badge = null) + { + $notification = new Notification(); + $notification->setTitle($title) + ->setBody($body); + + foreach ($recipients as $recipient) { + $notification->addRecipient($recipient); + } + + if (!empty($color)) { + $notification->setColor($color); + } + + if (!empty($sound)) { + $notification->setSound($sound); + } + + if (!empty($color)) { + $notification->setColor($color); + } + + if (!empty($badge)) { + $notification->setBadge($badge); + } + + if (!empty($data)) { + $notification->addDataArray($data); + } + + return $this->client->send($notification); + } + + public function sendJoEvent(JobOrder $job_order, $title, $body, $data = []) + { + // get customer object + $cust = $job_order->getCustomer(); + + // attach jo info + $data['jo_id'] = $job_order->getID(); + $data['jo_status'] = $job_order->getStatus(); + + // send the event + return $this->sendEvent($cust, $title, $body, $data); + } + + public function sendEvent(Customer $cust, $title, $body, $data = []) + { + // get all v2 devices + $devices = $this->getDevices($cust); + + if (empty($devices)) { + return false; + } + + // send fcm notification + $result = $this->send(array_keys($devices), $this->translator->trans($title), $this->translator->trans($body), $data); + + return $result; + } + + protected function getDevices(Customer $cust) + { + $sessions = []; + $device_ids = []; + + $cust_user = $cust->getCustomerUser(); + if (!empty($cust_user)) { + $sessions = $cust_user->getMobileSessions(); + } + + if (empty($sessions)) { + error_log("no sessions to send fcm notification to"); + return false; + } + + // send to every customer session + foreach ($sessions as $sess) { + $device_id = $sess->getDevicePushID(); + + if (!empty($device_id) && !isset($device_ids[$device_id])) { + // send to this device + $device_ids[$device_id] = true; + } + } + + if (empty($device_ids)) { + error_log("no devices to send fcm notification to"); + return false; + } + + return $device_ids; + } +} diff --git a/src/Service/InsuranceConnector.php b/src/Service/InsuranceConnector.php new file mode 100644 index 00000000..b56415c0 --- /dev/null +++ b/src/Service/InsuranceConnector.php @@ -0,0 +1,132 @@ +base_url = $base_url; + $this->username = $username; + $this->password = $password; + $this->hash = $this->generateHash(); + } + + public function createApplication(CustomerVehicle $cv, $notif_url, $data, $orcr_file) + { + $body = [ + 'notif_url' => $notif_url, + 'client_info' => [ + 'client_type' => $data['client_type'], + 'first_name' => $data['first_name'], + 'middle_name' => $data['middle_name'] ?? null, + 'surname' => $data['surname'], + 'corporate_name' => $data['corporate_name'], + ], + 'client_contact_info' => [ + 'address_number' => $data['address_number'], + 'address_street' => $data['address_street'] ?? null, + 'address_building' => $data['address_building'] ?? null, + 'address_barangay' => $data['address_barangay'], + 'address_city' => $data['address_city'], + 'address_province' => $data['address_province'], + 'zipcode' => (int)$data['zipcode'], + 'mobile_number' => $data['mobile_number'], + 'email_address' => $data['email_address'], + ], + 'car_info' => [ + 'make' => $data['make'], + 'model' => $data['model'], + 'series' => $data['series'], + 'color' => $data['color'], + 'plate_number' => $cv->getPlateNumber(), + 'mv_file_number' => $data['mv_file_number'], + 'motor_number' => $data['motor_number'], + 'serial_chasis' => $data['serial_chasis'], + 'year_model' => (int)$data['year_model'], + 'mv_type_id' => (int)$data['mv_type_id'], + 'body_type' => $data['body_type'], + 'is_public' => (bool)$data['is_public'], + 'line' => $data['line'], + 'orcr_file' => base64_encode(file_get_contents($orcr_file->getPathname())), + ], + ]; + + return $this->doRequest('/api/v1/ctpl/applications', 'POST', $body); + } + + public function tagApplicationPaid($application_id) + { + $url = '/api/v1/ctpl/application/' . $application_id . '/paid'; + return $this->doRequest($url, 'POST'); + } + + public function getVehicleMakers() + { + return $this->doRequest('/api/v1/ctpl/vehicle-makers', 'GET'); + } + + public function getVehicleModels($maker_id) + { + return $this->doRequest('/api/v1/ctpl/vehicle-models?maker_id='. $maker_id, 'GET'); + } + + public function getVehicleTrims($model_id) + { + return $this->doRequest('/api/v1/ctpl/vehicle-trims?model_id='. $model_id, 'GET'); + } + + protected function generateHash() + { + return base64_encode($this->username . ":" . $this->password); + } + + protected function doRequest($url, $method, $body = []) + { + $client = new Client(); + $headers = [ + 'Content-Type' => 'application/json', + 'accept' => 'application/json', + 'authorization' => 'Basic '. $this->hash, + ]; + + try { + $response = $client->request($method, $this->base_url . '/' . $url, [ + 'json' => $body, + 'headers' => $headers, + ]); + } catch (RequestException $e) { + $error = ['message' => $e->getMessage()]; + + error_log("Insurance API Error: " . $error['message']); + error_log(Psr7\Message::toString($e->getRequest())); + error_log($e->getResponse()->getBody()->getContents()); + + if ($e->hasResponse()) { + $error['response'] = Psr7\Message::toString($e->getResponse()); + } + + return [ + 'success' => false, + 'error' => $error, + ]; + } + + error_log(print_r(json_decode($response->getBody(), true), true)); + + return [ + 'success' => true, + 'response' => json_decode($response->getBody(), true) + ]; + } +} diff --git a/src/Service/InvoiceGenerator/CMBInvoiceGenerator.php b/src/Service/InvoiceGenerator/CMBInvoiceGenerator.php index 534600fb..5dc86f8d 100644 --- a/src/Service/InvoiceGenerator/CMBInvoiceGenerator.php +++ b/src/Service/InvoiceGenerator/CMBInvoiceGenerator.php @@ -134,7 +134,7 @@ class CMBInvoiceGenerator implements InvoiceGeneratorInterface } // generate invoice criteria - public function generateInvoiceCriteria($jo, $discount, $invoice_items, &$error_array) + public function generateInvoiceCriteria($jo, $discount, $invoice_items, $source = null, &$error_array) { $em = $this->em; diff --git a/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php b/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php index d3a30762..3809133b 100644 --- a/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php +++ b/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php @@ -144,7 +144,7 @@ class ResqInvoiceGenerator implements InvoiceGeneratorInterface } // generate invoice criteria - public function generateInvoiceCriteria($jo, $promo_id, $invoice_items, &$error_array) + public function generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source = null, &$error_array) { $em = $this->em; diff --git a/src/Service/InvoiceGeneratorInterface.php b/src/Service/InvoiceGeneratorInterface.php index 3e1c5f88..e2cb2cc3 100644 --- a/src/Service/InvoiceGeneratorInterface.php +++ b/src/Service/InvoiceGeneratorInterface.php @@ -13,7 +13,7 @@ interface InvoiceGeneratorInterface public function generateInvoice(InvoiceCriteria $criteria); // generate invoice criteria - public function generateInvoiceCriteria(JobOrder $jo, int $promo_id, array $invoice_items, array &$error_array); + public function generateInvoiceCriteria(JobOrder $jo, int $promo_id, array $invoice_items, $source, array &$error_array); // prepare draft for invoice public function generateDraftInvoice(InvoiceCriteria $criteria, int $promo_id, array $service_charges, array $items); diff --git a/src/Service/InvoiceManager.php b/src/Service/InvoiceManager.php new file mode 100644 index 00000000..65fc3a43 --- /dev/null +++ b/src/Service/InvoiceManager.php @@ -0,0 +1,293 @@ +em = $em; + $this->security = $security; + $this->validator = $validator; + + $this->available_rules = $this->getAvailableRules(); + } + + public function getAvailableRules() + { + // TODO: get list of invoice rules from .env or a json file? + return [ + new InvoiceRule\BatterySales($this->em), + new InvoiceRule\BatteryReplacementWarranty($this->em), + new InvoiceRule\Jumpstart($this->em), + new InvoiceRule\JumpstartWarranty($this->em), + new InvoiceRule\PostRecharged($this->em), + new InvoiceRule\PostReplacement($this->em), + new InvoiceRule\Overheat($this->em), + new InvoiceRule\Fuel($this->em), + new InvoiceRule\TireRepair($this->em), + new InvoiceRule\DiscountType($this->em), + new InvoiceRule\TradeIn(), + new InvoiceRule\Tax($this->em), + ]; + } + + // this is called when JO is submitted + public function generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source, &$error_array) + { + // instantiate the invoice criteria + $criteria = new InvoiceCriteria(); + $criteria->setServiceType($jo->getServiceType()) + ->setCustomerVehicle($jo->getCustomerVehicle()); + + // set if taxable + // NOTE: ideally, this should be a parameter when calling generateInvoiceCriteria. But that + // would mean adding it as a parameter to the call, impacting all calls + $criteria->setIsTaxable(); + + // set JO source + $criteria->setSource($source); + + foreach ($this->available_rules as $avail_rule) + { + $ierror = $avail_rule->validatePromo($criteria, $promo_id); + + // break out of loop when error found + if ($ierror) + break; + } + + if (!$ierror && !empty($invoice_items)) + { + // validate the invoice items (batteries and trade ins) + foreach ($this->available_rules as $avail_rule) + { + $ierror = $avail_rule->validateInvoiceItems($criteria, $invoice_items); + + // break out of loop when error found + if ($ierror) + break; + } + } + + if ($ierror) + { + $error_array['invoice'] = $ierror; + } + else + { + // generate the invoice + $invoice = $this->generateInvoice($criteria); + + // validate + $ierrors = $this->validator->validate($invoice); + + // 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 + $this->em->remove($old_invoice); + $this->em->flush(); + } + + // add invoice to JO + $jo->setInvoice($invoice); + + $this->em->persist($invoice); + } + } + + // this is called by JobOrderController when JS script generateInvoice is called + public function generateDraftInvoice($criteria, $promo_id, $service_charges, $items) + { + foreach ($this->available_rules as $avail_rule) + { + $ierror = $avail_rule->validatePromo($criteria, $promo_id); + + // break out of loop when error found + if ($ierror) + break; + } + + if (!$ierror && !empty($items)) + { + // validate the invoice items (batteries and trade ins) + foreach ($this->available_rules as $avail_rule) + { + $ierror = $avail_rule->validateInvoiceItems($criteria, $items); + + // break out of loop when error found + if ($ierror) + break; + } + } + + return $ierror; + } + + // called by the following: + // (1) JobOrderController when JS script generateInvoice is called + // (2) APIController from newRequestJobOrder + // (3) generateInvoiceCriteria + // (4) RiderAPIHandler's changeService + // (5) TAPI's JobOrderController + public function generateInvoice($criteria) + { + // no need to validate since generateDraftInvoice was called before this was called + // generate the invoice and from APIController, the fields were validated + $invoice_data = $this->compute($criteria); + + $invoice = $this->createInvoice($invoice_data); + + $invoice_items = $invoice->getItems(); + + return $invoice; + } + + public function compute($criteria) + { + // initialize + $total = [ + 'sell_price' => 0.0, + 'vat' => 0.0, + 'vat_ex_price' => 0.0, + 'ti_rate' => 0.0, + 'total_price' => 0.0, + 'discount' => 0.0, + ]; + + // get what is in criteria + // NOTE: is this snippet still needed? if not, remove + $stype = $criteria->getServiceType(); + $entries = $criteria->getEntries(); + $promos = $criteria->getPromos(); + $is_taxable = $criteria->isTaxable(); + + $invoice_items = []; + $data = []; + $promo = null; + foreach ($this->available_rules as $rule) + { + $items = $rule->compute($criteria, $total); + + if (count($items) > 0) + { + foreach ($items as $item) + { + $title = $item['title']; + $quantity = $item['qty']; + $price = $item['price']; + + $battery = null; + if (isset($item['battery'])) + $battery = $item['battery']; + + if (isset($item['promo'])) + $promo = $item['promo']; + + $invoice_items[] = [ + 'title' => $title, + 'quantity' => $quantity, + 'price' => $price, + 'battery' => $battery, + ]; + } + } + } + + // also need to return the total and the promo + // promo is set per invoice, not per item + $data[] = [ + 'promo' => $promo, + 'invoice_items' => $invoice_items, + 'total' => $total, + ]; + + return $data; + } + + protected function createInvoice($invoice_data) + { + $invoice = new Invoice(); + + // get current user + $user = $this->security->getUser(); + // check if user is User or APIUser + if ($user instanceof User) + { + $invoice->setCreatedBy($user); + } + + foreach ($invoice_data as $data) + { + $invoice_items = $data['invoice_items']; + $total = $data['total']; + + // check if promo is set + if (isset($data['promo'])) + { + $promo = $data['promo']; + + $invoice->setPromo($promo); + } + + foreach ($invoice_items as $item) + { + $invoice_item = new InvoiceItem(); + + $invoice_item->setInvoice($invoice) + ->setTitle($item['title']) + ->setQuantity($item['quantity']) + ->setPrice((float)$item['price']); + + if ($item['battery'] != null) + $invoice_item->setBattery($item['battery']); + + $invoice->addItem($invoice_item); + } + + // RULE: TYPECAST these values since bc operations return a string + // and these fields are going to be placed in a JSON response + $invoice->setTotalPrice((float)$total['total_price']) + ->setVATExclusivePrice((float)$total['vat_ex_price']) + ->setVAT((float)$total['vat']) + ->setDiscount((float)$total['discount']) + ->setTradeIn((float)$total['ti_rate']) + ->setStatus(InvoiceStatus::DRAFT); + } + + return $invoice; + } + +} diff --git a/src/Service/JobOrderHandler/CMBJobOrderHandler.php b/src/Service/JobOrderHandler/CMBJobOrderHandler.php index 24933e8f..acd84d62 100644 --- a/src/Service/JobOrderHandler/CMBJobOrderHandler.php +++ b/src/Service/JobOrderHandler/CMBJobOrderHandler.php @@ -46,6 +46,8 @@ use App\Service\RiderAssignmentHandlerInterface; use App\Service\CustomerHandlerInterface; use App\Service\WarrantyHandler; use App\Service\MQTTClient; +use App\Service\MQTTClientApiv2; +use App\Service\FCMSender; use App\Service\APNSClient; use App\Service\MapTools; @@ -198,6 +200,8 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface $row['car_model'] = $car_model; $row['rider_name'] = $rider_name; $row['rider_plate_number'] = $rider_plate_number; + $row['date_assign'] = !empty($orow->getDateAssign()) ? $orow->getDateAssign()->format("c") : null; + $row['date_fulfill'] = !empty($orow->getDateFulfill()) ? $orow->getDateFulfill()->format("c") : null; $processor = $orow->getProcessedBy(); if ($processor == null) @@ -668,7 +672,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface } // dispatch job order - public function dispatchJobOrder(Request $req, int $id, MQTTClient $mclient) + public function dispatchJobOrder(Request $req, int $id, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient) { // get object data $em = $this->em; @@ -793,6 +797,10 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface 'event' => 'outlet_assign' ]; $mclient->sendEvent($obj, $payload); + + // NOTE: for resq2 app + $mclientv2->sendEvent($obj, $payload); + $fcmclient->sendJoEvent($obj, "jo_fcm_title_outlet_assign", "jo_fcm_body_outlet_assign"); } return $error_array; @@ -1027,7 +1035,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface } // cancel job order - public function cancelJobOrder(Request $req, int $id, MQTTClient $mclient) + public function cancelJobOrder(Request $req, int $id, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient) { // get object data $em = $this->em; @@ -1068,10 +1076,14 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface ]; $mclient->sendEvent($obj, $payload); $mclient->sendRiderEvent($obj, $payload); + + // NOTE: for resq2 app + $mclientv2->sendEvent($obj, $payload); + $fcmclient->sendJoEvent($obj, "jo_fcm_title_cancelled", "jo_fcm_body_cancelled", ['reason' => $cancel_reason]); } // set hub for job order - public function setHub($req, $id, $mclient) + public function setHub($req, $id, $mclient, $mclientv2, $fcmclient) { // get object data $em = $this->em; @@ -1171,6 +1183,10 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface 'event' => 'outlet_assign' ]; $mclient->sendEvent($obj, $payload); + + // NOTE: for resq2 app + $mclientv2->sendEvent($obj, $payload); + $fcmclient->sendJoEvent($obj, "jo_fcm_title_outlet_assign", "jo_fcm_body_outlet_assign"); } return $error_array; @@ -1272,7 +1288,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface } // set rider for job order - public function setRider($req, $id, $mclient) + public function setRider($req, $id, $mclient, $mclientv2, $fcmclient) { // initialize error list $error_array = []; @@ -1372,6 +1388,10 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface ]; $mclient->sendEvent($obj, $payload); $mclient->sendRiderEvent($obj, $payload); + + // NOTE: for resq2 app + $mclientv2->sendEvent($obj, $payload); + $fcmclient->sendJoEvent($obj, "jo_fcm_title_driver_assigned", "jo_fcm_body_driver_assigned"); } return $error_array; diff --git a/src/Service/JobOrderHandler/ResqJobOrderHandler.php b/src/Service/JobOrderHandler/ResqJobOrderHandler.php index 642f4215..361c0b22 100644 --- a/src/Service/JobOrderHandler/ResqJobOrderHandler.php +++ b/src/Service/JobOrderHandler/ResqJobOrderHandler.php @@ -29,12 +29,13 @@ use App\Entity\CustomerTag; use App\Entity\EmergencyType; use App\Entity\OwnershipType; use App\Entity\CustomerLocation; +use App\Entity\Battery; -use App\Ramcar\InvoiceCriteria; use App\Ramcar\ServiceType; use App\Ramcar\TradeInType; use App\Ramcar\JOEventType; use App\Ramcar\JOStatus; +use App\Ramcar\InvoiceCriteria; use App\Ramcar\WarrantyClass; use App\Ramcar\DiscountApply; use App\Ramcar\ModeOfPayment; @@ -58,6 +59,8 @@ use App\Service\JobOrderHandlerInterface; use App\Service\RiderAssignmentHandlerInterface; use App\Service\WarrantyHandler; use App\Service\MQTTClient; +use App\Service\MQTTClientApiv2; +use App\Service\FCMSender; use App\Service\APNSClient; use App\Service\MapTools; use App\Service\RisingTideGateway; @@ -65,6 +68,7 @@ use App\Service\PromoLogger; use App\Service\HubSelector; use App\Service\HubDistributor; use App\Service\HubFilteringGeoChecker; +use App\Service\JobOrderManager; use CrEOF\Spatial\PHP\Types\Geometry\Point; @@ -91,6 +95,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface protected $hub_geofence; protected $cust_distance_limit; protected $hub_filter_enable; + protected $jo_manager; protected $template_hash; @@ -99,7 +104,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface TranslatorInterface $translator, RiderAssignmentHandlerInterface $rah, string $country_code, WarrantyHandler $wh, RisingTideGateway $rt, PromoLogger $promo_logger, HubDistributor $hub_dist, HubFilteringGeoChecker $hub_geofence, - string $cust_distance_limit, string $hub_filter_enabled) + string $cust_distance_limit, string $hub_filter_enabled, JobOrderManager $jo_manager) { $this->em = $em; $this->ic = $ic; @@ -115,6 +120,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface $this->hub_geofence = $hub_geofence; $this->cust_distance_limit = $cust_distance_limit; $this->hub_filter_enabled = $hub_filter_enabled; + $this->jo_manager = $jo_manager; $this->loadTemplates(); } @@ -211,7 +217,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface ($reason == CustomerNotWaitReason::RUSH_REQUEST)) $is_emergency = true; } - + // add row data $row['id'] = $orow->getID(); $row['customer_name'] = $orow->getCustomer()->getFirstName() . ' ' . $orow->getCustomer()->getLastName(); @@ -225,6 +231,9 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface $row['is_mobile'] = $orow->getSource() == TransactionOrigin::MOBILE_APP; $row['is_vip'] = $is_vip; $row['is_emergency'] = $is_emergency; + $row['flag_cust_new'] = $orow->isCustNew(); + $row['date_assign'] = !empty($orow->getDateAssign()) ? $orow->getDateAssign()->format("c") : null; + $row['date_fulfill'] = !empty($orow->getDateFulfill()) ? $orow->getDateFulfill()->format("c") : null; $processor = $orow->getProcessedBy(); if ($processor == null) @@ -238,12 +247,20 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface else $row['assignor'] = $orow->getAssignedBy()->getFullName(); + // not removing this since this might be used by other pages since getRows is also used by other pages $hub_facilitated = $orow->getFacilitatedBy(); if ($hub_facilitated == null) $row['hub_facilitated'] = ''; else $row['hub_facilitated'] = $orow->getFacilitatedBy()->getName(); + // get the assigned hub, if any + $assigned_hub = $orow->getHub(); + if ($assigned_hub == null) + $row['assigned_hub'] = ''; + else + $row['assigned_hub'] = $orow->getHub()->getName(); + $rows[] = $row; } @@ -348,6 +365,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface // find customer $cust_id = $req->request->get('cid'); $customer = $em->getRepository(Customer::class)->find($cust_id); + $flag_cust_new = false; if (empty($customer)) { $error_array['customer_vehicle'] = 'Invalid customer specified.'; @@ -377,6 +395,11 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface ->setDpaConsent($is_dpa_checked); } + // check if customer has more than one job order already + $cust_jo_count = $this->jo_manager->getCustomerJobOrderCount($customer->getID()); + if ($cust_jo_count <= 1) + $flag_cust_new = true; + // 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.'; @@ -395,8 +418,8 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface } // check if landmark is set - if (empty($req->request->get('landmark'))) - $error_array['landmark'] = 'Landmark is required.'; + //if (empty($req->request->get('landmark'))) + // $error_array['landmark'] = 'Landmark is required.'; // check if customer is not willing to wait $will_wait = $req->request->get('flag_willing_to_wait'); @@ -508,7 +531,8 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface ->setGender($gender) ->setEmergencyType($etype) ->setOwnershipType($owner_type) - ->setCustomerLocation($cust_location); + ->setCustomerLocation($cust_location) + ->setCustNew($flag_cust_new); // check if user is null, meaning call to create came from API if ($user != null) @@ -559,7 +583,9 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface // check if invoice changed if ($invoice_change) { - $this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $error_array); + $source = $jo->getSource(); + + $this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source, $error_array); } // validate @@ -673,8 +699,8 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface } // check if landmark is set - if (empty($req->request->get('landmark'))) - $error_array['landmark'] = 'Landmark is required.'; + //if (empty($req->request->get('landmark'))) + // $error_array['landmark'] = 'Landmark is required.'; // check if customer is not willing to wait $will_wait = $req->request->get('flag_willing_to_wait'); @@ -789,7 +815,9 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface $invoice_change = $req->request->get('invoice_change', 0); if ($invoice_change) { - $this->ic->generateInvoiceCriteria($obj, $promo_id, $invoice_items, $error_array); + $source = $obj->getSource(); + + $this->ic->generateInvoiceCriteria($obj, $promo_id, $invoice_items, $source, $error_array); } // validate @@ -840,7 +868,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface } // dispatch job order - public function dispatchJobOrder(Request $req, int $id, MQTTClient $mclient) + public function dispatchJobOrder(Request $req, int $id, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient) { // get object data $em = $this->em; @@ -913,8 +941,8 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface } // check if landmark is set - if (empty($req->request->get('landmark'))) - $error_array['landmark'] = 'Landmark is required.'; + //if (empty($req->request->get('landmark'))) + // $error_array['landmark'] = 'Landmark is required.'; // check if customer is not willing to wait $will_wait = $req->request->get('flag_willing_to_wait'); @@ -1002,7 +1030,8 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface ->setCallerClassification($caller_class) ->setEmergencyType($etype) ->setOwnershipType($owner_type) - ->setCustomerLocation($cust_location); + ->setCustomerLocation($cust_location) + ->setInventoryCount($req->request->get('hub_inv_count', 0)); // validate $errors = $this->validator->validate($obj); @@ -1037,6 +1066,10 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface ]; $mclient->sendEvent($obj, $payload); + // NOTE: for resq2 app + $mclientv2->sendEvent($obj, $payload); + $fcmclient->sendJoEvent($obj, "jo_fcm_title_outlet_assign", "jo_fcm_body_outlet_assign"); + // update redis hub jo count $this->hub_dist->incrementJoCountForHub($hub); } @@ -1080,8 +1113,8 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface } // check if landmark is set - if (empty($req->request->get('landmark'))) - $error_array['landmark'] = 'Landmark is required.'; + //if (empty($req->request->get('landmark'))) + // $error_array['landmark'] = 'Landmark is required.'; // check if customer is not willing to wait $will_wait = $req->request->get('flag_willing_to_wait'); @@ -1238,8 +1271,8 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface } // check if landmark is set - if (empty($req->request->get('landmark'))) - $error_array['landmark'] = 'Landmark is required.'; + //if (empty($req->request->get('landmark'))) + // $error_array['landmark'] = 'Landmark is required.'; // check if customer is not willing to wait $will_wait = $req->request->get('flag_willing_to_wait'); @@ -1428,7 +1461,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface } // cancel job order - public function cancelJobOrder(Request $req, int $id, MQTTClient $mclient) + public function cancelJobOrder(Request $req, int $id, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient) { // get object data $em = $this->em; @@ -1473,10 +1506,14 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface ]; $mclient->sendEvent($obj, $payload); $mclient->sendRiderEvent($obj, $payload); + + // NOTE: for resq2 app + $mclientv2->sendEvent($obj, $payload); + $fcmclient->sendJoEvent($obj, "jo_fcm_title_cancelled", "jo_fcm_body_cancelled", ['reason' => $cancel_reason]); } // set hub for job order - public function setHub($req, $id, $mclient) + public function setHub($req, $id, $mclient, $mclientv2, $fcmclient) { // get object data $em = $this->em; @@ -1508,8 +1545,8 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface } // check if landmark is set - if (empty($req->request->get('landmark'))) - $error_array['landmark'] = 'Landmark is required.'; + //if (empty($req->request->get('landmark'))) + // $error_array['landmark'] = 'Landmark is required.'; // error_log($req->request->get('landmark')); @@ -1626,6 +1663,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface ->setEmergencyType($etype) ->setOwnershipType($owner_type) ->setCustomerLocation($cust_location) + ->setInventoryCount($req->request->get('hub_inv_count', 0)) ->clearRider(); if ($user != null) @@ -1669,6 +1707,10 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface ]; $mclient->sendEvent($obj, $payload); + // NOTE: for resq2 app + $mclientv2->sendEvent($obj, $payload); + $fcmclient->sendJoEvent($obj, "jo_fcm_title_outlet_assign", "jo_fcm_body_outlet_assign"); + // update redis hub_jo_count for hub // decrement old hub's count and increment new hub's count if ($old_hub != null) @@ -1778,7 +1820,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface } // set rider for job order - public function setRider($req, $id, $mclient) + public function setRider($req, $id, $mclient, $mclientv2, $fcmclient) { // initialize error list $error_array = []; @@ -1810,8 +1852,8 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface } // check if landmark is set - if (empty($req->request->get('landmark'))) - $error_array['landmark'] = 'Landmark is required.'; + //if (empty($req->request->get('landmark'))) + // $error_array['landmark'] = 'Landmark is required.'; // check if customer is not willing to wait $will_wait = $req->request->get('flag_willing_to_wait'); @@ -1969,6 +2011,10 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface ]; $mclient->sendEvent($obj, $payload); $mclient->sendRiderEvent($obj, $payload); + + // NOTE: for resq2 app + $mclientv2->sendEvent($obj, $payload); + $fcmclient->sendJoEvent($obj, "jo_fcm_title_driver_assigned", "jo_fcm_body_driver_assigned"); } return $error_array; @@ -2116,7 +2162,10 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface // check if invoice changed if ($invoice_change) { - $this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $error_array); + // NOTE: this is CMB code but for compilation purposes we need to add this + $source = $jo->getSource(); + + $this->ic->generateInvoiceCriteria($jo, $promo_id, $invoice_items, $source, $error_array); } // validate @@ -2366,6 +2415,27 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface return $params; } + public function initializeAllViewform($id) + { + $em = $this->em; + + // 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); + + $params['obj'] = $obj; + + // get template to display + $params['template'] = $this->getTwigTemplate('jo_all_view_form'); + + return $params; + } + // initialize dispatch/processing job order form public function initializeProcessingForm($id, HubSelector $hub_selector, $motiv) { @@ -3469,8 +3539,19 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface // db loaded $params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); + $params['trade_in_bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); $params['promos'] = $em->getRepository(Promo::class)->findAll(); + // list of batteries for trade-in + $ti_batteries = $em->getRepository(Battery::class)->findAll(); + $trade_in_batteries = []; + foreach ($ti_batteries as $ti_battery) + { + $battery_name = $ti_battery->getModel()->getName() . ' ' . $ti_battery->getSize()->getName(); + $trade_in_batteries[$ti_battery->getID()] = $battery_name; + } + $params['trade_in_batteries'] = $trade_in_batteries; + // list of emergency types $e_types = $em->getRepository(EmergencyType::class)->findBy([], ['name' => 'ASC']); $emergency_types = []; @@ -3606,6 +3687,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface $this->template_hash['jo_popup'] = 'job-order/popup.html.twig'; $this->template_hash['jo_hub_list'] = 'job-order/list.hubview.html.twig'; $this->template_hash['jo_hub_view_form'] = 'job-order/form.html.twig'; + $this->template_hash['jo_all_view_form'] = 'job-order/form.view.html.twig'; } protected function checkTier($tier) diff --git a/src/Service/JobOrderHandlerInterface.php b/src/Service/JobOrderHandlerInterface.php index 644db54a..17041c06 100644 --- a/src/Service/JobOrderHandlerInterface.php +++ b/src/Service/JobOrderHandlerInterface.php @@ -5,6 +5,8 @@ namespace App\Service; use Symfony\Component\HttpFoundation\Request; use App\Service\MQTTClient; +use App\Service\MQTTClientApiv2; +use App\Service\FCMSender; use App\Service\APNSClient; use App\Service\MapTools; use App\Service\HubSelector; @@ -29,7 +31,7 @@ interface JobOrderHandlerInterface public function processOneStepJobOrder(Request $req, int $id); // dispatch job order - public function dispatchJobOrder(Request $req, int $id, MQTTClient $mclient); + public function dispatchJobOrder(Request $req, int $id, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient); // assign job order public function assignJobOrder(Request $req, int $id); @@ -38,16 +40,16 @@ interface JobOrderHandlerInterface public function fulfillJobOrder(Request $req, int $id); // cancel job order - public function cancelJobOrder(Request $req, int $id, MQTTClient $mclient); + public function cancelJobOrder(Request $req, int $id, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient); // set hub for job order - public function setHub(Request $req, int $id, MQTTClient $mclient); + public function setHub(Request $req, int $id, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient); // reject hub for job order public function rejectHub(Request $req, int $id); // set rider for job order - public function setRider(Request $req, int $id, MQTTClient $mclient); + public function setRider(Request $req, int $id, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient); // unlock processor public function unlockProcessor(int $id); diff --git a/src/Service/JobOrderManager.php b/src/Service/JobOrderManager.php index c54557d6..e592b6ca 100644 --- a/src/Service/JobOrderManager.php +++ b/src/Service/JobOrderManager.php @@ -5,9 +5,12 @@ namespace App\Service; use App\Entity\JobOrder; use App\Ramcar\ServiceType; +use App\Ramcar\JOStatus; use Doctrine\ORM\EntityManagerInterface; +use PDO; + class JobOrderManager { protected $em; @@ -36,6 +39,22 @@ class JobOrderManager return false; } + public function getCustomerJobOrderCount($customer_id) + { + $db = $this->em->getConnection(); + + $query_sql = 'SELECT COUNT(*) AS jo_count FROM job_order WHERE customer_id = :cust_id AND status != :status_cancelled'; + + $query_stmt = $db->prepare($query_sql); + $query_stmt->bindValue('cust_id', $customer_id); + $query_stmt->bindValue('status_cancelled', JOStatus::CANCELLED); + + $jo_results = $query_stmt->executeQuery(); + $results = $jo_results->fetchAssociative(); + + return $results['jo_count']; + } + protected function updateCustomerVehicleBattery($cust_vehicle, $invoice) { if (($cust_vehicle != null) && ($invoice != null)) diff --git a/src/Service/MQTTClientApiv2.php b/src/Service/MQTTClientApiv2.php new file mode 100644 index 00000000..42b85648 --- /dev/null +++ b/src/Service/MQTTClientApiv2.php @@ -0,0 +1,71 @@ +redis = $redis_client->getRedisClient(); + $this->key = $key; + } + + public function __destruct() + { + // $this->mclient->disconnect(); + } + + public function publish($channel, $message) + { + // $this->mclient->publish($channel, $message); + + $data = $channel . '|' . $message; + $this->redis->lpush($this->key, $data); + } + + public function sendEvent(JobOrder $job_order, $payload) + { + //error_log('sending mqtt event: '); + //error_log(print_r($payload, true)); + + // get all v2 sessions + $sessions = []; + $cust_user = $job_order->getCustomer()->getCustomerUser(); + if (!empty($cust_user)) { + $sessions = $cust_user->getMobileSessions(); + } + + if (empty($sessions)) { + error_log("no sessions to send mqtt event to"); + return; + } + + $channels = []; + + // send to every customer session + foreach ($sessions as $sess) { + $phone_num = $sess->getPhoneNumber(); + $channel = self::PREFIX . $phone_num; + + // attach jo id to all payloads + $payload['jo_id'] = $job_order->getID(); + + // gather channels, so we only send once + $channels[$channel] = json_encode($payload); + } + + foreach ($channels as $channel => $json_payload) { + $this->publish($channel, $json_payload); + // error_log('sent to ' . $channel); + } + } +} diff --git a/src/Service/PayMongoConnector.php b/src/Service/PayMongoConnector.php new file mode 100644 index 00000000..ad2750a2 --- /dev/null +++ b/src/Service/PayMongoConnector.php @@ -0,0 +1,125 @@ +base_url = $base_url; + $this->public_key = $public_key; + $this->secret_key = $secret_key; + $this->hash = $this->generateHash(); + } + + public function createCheckout(Customer $cust, $items, $ref_no = null, $description = null, $success_url = null, $cancel_url = null, $metadata = []) + { + // build billing info + $billing = [ + 'name' => implode(" ", [$cust->getFirstName(), $cust->getLastName()]), + 'phone' => $cust->getPhoneMobile(), + ]; + + if ($cust->getEmail()) { + $billing['email'] = $cust->getEmail(); + } + + // build the request body + $body = [ + 'data' => [ + 'attributes' => [ + 'description' => $description, + 'billing' => $billing, + // NOTE: this may be variable later, hardcoding for now + 'payment_method_types' => [ + 'card', + 'paymaya', + 'gcash', + ], + /* NOTE: format for line items: + * ['name', 'description', 'quantity', 'amount', 'currency'] + */ + 'line_items' => $items, + 'reference_number' => (string)$ref_no, + 'cancel_url' => $cancel_url, + 'success_url' => $success_url, + 'statement_descriptor' => $description, + 'send_email_receipt' => true, + 'show_description' => true, + 'show_line_items' => false, + ], + ], + ]; + + if (!empty($metadata)) { + $body['data']['attributes']['metadata'] = $metadata; + } + + return $this->doRequest('/v1/checkout_sessions', 'POST', $body); + } + + public function getCheckout($checkout_id) + { + return $this->doRequest('/v1/checkout_sessions/' . $checkout_id, 'GET'); + } + + protected function generateHash() + { + return base64_encode($this->secret_key); + } + + protected function doRequest($url, $method, $body = []) + { + $client = new Client(); + $headers = [ + 'Content-Type' => 'application/json', + 'accept' => 'application/json', + 'authorization' => 'Basic '. $this->hash, + ]; + + try { + $response = $client->request($method, $this->base_url . '/' . $url, [ + 'json' => $body, + 'headers' => $headers, + ]); + } catch (RequestException $e) { + $error = ['message' => $e->getMessage()]; + + ob_start(); + var_dump($body); + $varres = ob_get_clean(); + error_log($varres); + + error_log("--------------------------------------"); + error_log($e->getResponse()->getBody()->getContents()); + + error_log("PayMongo API Error: " . $error['message']); + error_log(Psr7\Message::toString($e->getRequest())); + + if ($e->hasResponse()) { + $error['response'] = Psr7\Message::toString($e->getResponse()); + } + + return [ + 'success' => false, + 'error' => $error, + ]; + } + + return [ + 'success' => true, + 'response' => json_decode($response->getBody(), true) + ]; + } +} diff --git a/src/Service/RiderAPIHandler/CMBRiderAPIHandler.php b/src/Service/RiderAPIHandler/CMBRiderAPIHandler.php index 3c0236e1..e35ab643 100644 --- a/src/Service/RiderAPIHandler/CMBRiderAPIHandler.php +++ b/src/Service/RiderAPIHandler/CMBRiderAPIHandler.php @@ -18,6 +18,8 @@ use App\Service\RiderAPIHandlerInterface; use App\Service\RedisClientProvider; use App\Service\RiderCache; use App\Service\MQTTClient; +use App\Service\MQTTClientApiv2; +use App\Service\FCMSender; use App\Service\WarrantyHandler; use App\Service\JobOrderHandlerInterface; use App\Service\InvoiceGeneratorInterface; @@ -42,6 +44,8 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface protected $rcache; protected $country_code; protected $mclient; + protected $mclientv2; + protected $fcmclient; protected $wh; protected $jo_handler; protected $ic; @@ -49,7 +53,7 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface public function __construct(EntityManagerInterface $em, RedisClientProvider $redis, EncoderFactoryInterface $ef, RiderCache $rcache, - string $country_code, MQTTClient $mclient, + string $country_code, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient, WarrantyHandler $wh, JobOrderHandlerInterface $jo_handler, InvoiceGeneratorInterface $ic) { @@ -59,6 +63,8 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface $this->rcache = $rcache; $this->country_code = $country_code; $this->mclient = $mclient; + $this->mclientv2 = $mclientv2; + $this->fcmclient = $fcmclient; $this->wh = $wh; $this->jo_handler = $jo_handler; $this->ic = $ic; @@ -424,6 +430,10 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface ]; $this->mclient->sendEvent($jo, $payload); + // NOTE: for resq2 app + $this->mclientv2->sendEvent($jo, $payload); + $this->fcmclient->sendJoEvent($jo, "jo_fcm_title_outlet_assign", "jo_fcm_body_outlet_assign"); + return $data; } @@ -465,6 +475,10 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface ]; $this->mclient->sendEvent($jo, $payload); + // NOTE: for resq2 app + $this->mclientv2->sendEvent($jo, $payload); + $this->fcmclient->sendJoEvent($jo, "jo_fcm_title_driver_arrived", "jo_fcm_body_driver_arrived"); + return $data; } @@ -578,6 +592,10 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface ]; $this->mclient->sendEvent($jo, $payload); + // NOTE: for resq2 app + $this->mclientv2->sendEvent($jo, $payload); + $this->fcmclient->sendJoEvent($jo, "jo_fcm_title_fulfilled", "jo_fcm_body_fulfilled"); + return $data; } diff --git a/src/Service/RiderAPIHandler/ResqRiderAPIHandler.php b/src/Service/RiderAPIHandler/ResqRiderAPIHandler.php index a543f39d..7a112913 100644 --- a/src/Service/RiderAPIHandler/ResqRiderAPIHandler.php +++ b/src/Service/RiderAPIHandler/ResqRiderAPIHandler.php @@ -16,11 +16,14 @@ use App\Ramcar\InvoiceStatus; use App\Ramcar\ModeOfPayment; use App\Ramcar\InvoiceCriteria; use App\Ramcar\WarrantySource; +use App\Ramcar\TransactionOrigin; use App\Service\RiderAPIHandlerInterface; use App\Service\RedisClientProvider; use App\Service\RiderCache; use App\Service\MQTTClient; +use App\Service\MQTTClientApiv2; +use App\Service\FCMSender; use App\Service\WarrantyHandler; use App\Service\JobOrderHandlerInterface; use App\Service\InvoiceGeneratorInterface; @@ -47,6 +50,8 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface protected $rcache; protected $country_code; protected $mclient; + protected $mclientv2; + protected $fcmclient; protected $wh; protected $jo_handler; protected $ic; @@ -56,7 +61,7 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface public function __construct(EntityManagerInterface $em, RedisClientProvider $redis, EncoderFactoryInterface $ef, RiderCache $rcache, - string $country_code, MQTTClient $mclient, + string $country_code, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient, WarrantyHandler $wh, JobOrderHandlerInterface $jo_handler, InvoiceGeneratorInterface $ic, RisingTideGateway $rt, RiderTracker $rider_tracker, TranslatorInterface $translator) @@ -67,6 +72,8 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface $this->rcache = $rcache; $this->country_code = $country_code; $this->mclient = $mclient; + $this->mclientv2 = $mclientv2; + $this->fcmclient = $fcmclient; $this->wh = $wh; $this->jo_handler = $jo_handler; $this->ic = $ic; @@ -466,6 +473,10 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface ]; $this->mclient->sendEvent($jo, $payload); + // NOTE: for resq2 app + $this->mclientv2->sendEvent($jo, $payload); + $this->fcmclient->sendJoEvent($jo, "jo_fcm_title_outlet_assign", "jo_fcm_body_outlet_assign"); + return $data; } @@ -507,6 +518,10 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface ]; $this->mclient->sendEvent($jo, $payload); + // NOTE: for resq2 app + $this->mclientv2->sendEvent($jo, $payload); + $this->fcmclient->sendJoEvent($jo, "jo_fcm_title_driver_arrived", "jo_fcm_body_driver_arrived"); + return $data; } @@ -661,6 +676,10 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface ]; $this->mclient->sendEvent($jo, $payload); + // NOTE: for resq2 app + $this->mclientv2->sendEvent($jo, $payload); + $fcmclient->sendJoEvent($jo, "jo_fcm_title_fulfilled", "jo_fcm_body_fulfilled"); + return $data; } @@ -848,12 +867,24 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface $crit->setCustomerVehicle($cv); $crit->setHasCoolant($jo->hasCoolant()); + // set istaxable + $crit->setIsTaxable(); + + // set JO source + // set to anything other than mobile app + $icrit->setSource(TransactionOrigin::WALK_IN); + if ($promo != null) $crit->addPromo($promo); if ($battery != null) { - $crit->addEntry($battery, $trade_in, 1); + // right now, the app does not include trade-ins but this might change in the future + if ($trade_in == null) + $crit->addEntry($battery, null, 1); + else + $crit->addEntry($battery, $trade_in, 1); + // error_log('adding entry for battery - ' . $battery->getID()); } diff --git a/src/Service/RiderAssignmentHandler/CMBRiderAssignmentHandler.php b/src/Service/RiderAssignmentHandler/CMBRiderAssignmentHandler.php index 0a8a8112..e6ff0664 100644 --- a/src/Service/RiderAssignmentHandler/CMBRiderAssignmentHandler.php +++ b/src/Service/RiderAssignmentHandler/CMBRiderAssignmentHandler.php @@ -6,6 +6,8 @@ use Doctrine\ORM\EntityManagerInterface; use App\Service\RiderAssignmentHandlerInterface; use App\Service\MQTTClient; +use App\Service\MQTTClientApiv2; +use App\Service\FCMSender; use App\Service\APNSClient; use App\Entity\JobOrder; @@ -18,12 +20,15 @@ class CMBRiderAssignmentHandler implements RiderAssignmentHandlerInterface protected $em; protected $aclient; protected $mclient; + protected $mclientv2; + protected $fcmclient; - public function __construct(EntityManagerInterface $em, MQTTClient $mclient, - APNSClient $aclient) + public function __construct(EntityManagerInterface $em, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient, APNSClient $aclient) { $this->em = $em; $this->mclient = $mclient; + $this->mclientv2 = $mclientv2; + $this->fcmclient = $fcmclient; $this->aclient = $aclient; } @@ -38,6 +43,10 @@ class CMBRiderAssignmentHandler implements RiderAssignmentHandlerInterface // send event $this->mclient->sendEvent($obj, $payload); + // NOTE: for resq2 app + $this->mclientv2->sendEvent($obj, $payload); + $this->fcmclient->sendJoEvent($obj, "jo_fcm_title_driver_assigned", "jo_fcm_body_driver_assigned"); + // check if rider is available if ($rider->isAvailable()) { @@ -66,6 +75,10 @@ class CMBRiderAssignmentHandler implements RiderAssignmentHandlerInterface ]; $this->mclient->sendEvent($obj, $payload); + // NOTE: for resq2 app + $this->mclientv2->sendEvent($obj, $payload); + $this->fcmclient->sendJoEvent($obj, "jo_fcm_title_fulfilled", "jo_fcm_body_fulfilled"); + // send fulfill/complete event to rider $this->mclient->sendRiderEvent($obj, $payload); diff --git a/src/Service/RiderAssignmentHandler/ResqRiderAssignmentHandler.php b/src/Service/RiderAssignmentHandler/ResqRiderAssignmentHandler.php index 6592734a..80e8d89c 100644 --- a/src/Service/RiderAssignmentHandler/ResqRiderAssignmentHandler.php +++ b/src/Service/RiderAssignmentHandler/ResqRiderAssignmentHandler.php @@ -8,6 +8,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; use App\Service\RiderAssignmentHandlerInterface; use App\Service\MQTTClient; +use App\Service\MQTTClientApiv2; +use App\Service\FCMSender; use App\Service\APNSClient; use App\Entity\JobOrder; @@ -20,13 +22,16 @@ class ResqRiderAssignmentHandler implements RiderAssignmentHandlerInterface protected $em; protected $aclient; protected $mclient; + protected $mclientv2; + protected $fcmclient; protected $translator; - public function __construct(EntityManagerInterface $em, MQTTClient $mclient, - APNSClient $aclient, TranslatorInterface $translator) + public function __construct(EntityManagerInterface $em, MQTTClient $mclient, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient, APNSClient $aclient, TranslatorInterface $translator) { $this->em = $em; $this->mclient = $mclient; + $this->mclientv2 = $mclientv2; + $this->fcmclient = $fcmclient; $this->aclient = $aclient; $this->translator = $translator; } @@ -42,6 +47,10 @@ class ResqRiderAssignmentHandler implements RiderAssignmentHandlerInterface // send event $this->mclient->sendEvent($obj, $payload); + // NOTE: for resq2 app + $this->mclientv2->sendEvent($obj, $payload); + $this->fcmclient->sendJoEvent($obj, "jo_fcm_title_driver_assigned", "jo_fcm_body_driver_assigned"); + // check if rider is available if ($rider->isAvailable()) { @@ -72,6 +81,10 @@ class ResqRiderAssignmentHandler implements RiderAssignmentHandlerInterface ]; $this->mclient->sendEvent($obj, $payload); + // NOTE: for resq2 app + $this->mclientv2->sendEvent($obj, $payload); + $this->fcmclient->sendJoEvent($obj, "jo_fcm_title_fulfilled", "jo_fcm_body_fulfilled"); + // send fulfill/complete event to rider $this->mclient->sendRiderEvent($obj, $payload); } diff --git a/symfony.lock b/symfony.lock index 6a96a3a8..82c192d2 100644 --- a/symfony.lock +++ b/symfony.lock @@ -1,13 +1,4 @@ { - "catalyst/auth-bundle": { - "version": "dev-master" - }, - "catalyst/menu-bundle": { - "version": "dev-master" - }, - "creof/doctrine2-spatial": { - "version": "1.2.0" - }, "creof/geo-parser": { "version": "2.1.0" }, @@ -53,9 +44,6 @@ "ref": "44d3aa7752dd46f77ba11af2297a25e1dedfb4d0" } }, - "doctrine/doctrine-cache-bundle": { - "version": "1.3.2" - }, "doctrine/doctrine-fixtures-bundle": { "version": "3.0", "recipe": { @@ -113,6 +101,18 @@ "hashids/hashids": { "version": "4.1.0" }, + "jankstudio/catalyst-api-bundle": { + "version": "dev-master" + }, + "jankstudio/catalyst-auth-bundle": { + "version": "dev-master" + }, + "jankstudio/catalyst-menu-bundle": { + "version": "dev-master" + }, + "jankstudio/doctrine-spatial": { + "version": "dev-master" + }, "jdorn/sql-formatter": { "version": "v1.2.17" }, diff --git a/templates/base.html.twig b/templates/base.html.twig index 50e72b20..d679555b 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -770,7 +770,8 @@
- {{ menu.main_menu(main_menu.menu) }} + {% set main_menu = menu_get('main') %} + {{ menu.main_menu(main_menu) }}
diff --git a/templates/customer/form.html.twig b/templates/customer/form.html.twig index a68640df..08d527d3 100644 --- a/templates/customer/form.html.twig +++ b/templates/customer/form.html.twig @@ -219,7 +219,7 @@
{% trans %}country_code_prefix{% endtrans %} diff --git a/templates/invoice/trade_in.html.twig b/templates/invoice/trade_in.html.twig new file mode 100644 index 00000000..e033143d --- /dev/null +++ b/templates/invoice/trade_in.html.twig @@ -0,0 +1,46 @@ +
+
+ + +
+
+ + +
+
+ + +
+
+ + + +
+
+ + +
+
+
+ +
+
diff --git a/templates/invoice/trade_in.js.twig b/templates/invoice/trade_in.js.twig new file mode 100644 index 00000000..a93fce56 --- /dev/null +++ b/templates/invoice/trade_in.js.twig @@ -0,0 +1,17 @@ +// add trade in battery to invoice +$('#btn-add-trade-in-to-invoice').click(function() { + var bmfg = $("#invoice-trade-in-bmfg").val(); + var battery = $("#invoice-trade-in-battery").val(); + var tradeIn = $("#invoice-trade-in-type").val(); + var qty = $("#invoice-trade-in-quantity").val(); + + // add to invoice array + invoiceItems.push({ + battery: battery, + quantity: qty, + trade_in: tradeIn, + }); + + // regenerate the invoice + generateInvoice(); +}); diff --git a/templates/job-order/autoassign.form.html.twig b/templates/job-order/autoassign.form.html.twig index bba70b39..95dbad6c 100644 --- a/templates/job-order/autoassign.form.html.twig +++ b/templates/job-order/autoassign.form.html.twig @@ -138,7 +138,7 @@
- +
{% trans %}country_code_prefix{% endtrans %} diff --git a/templates/job-order/form.html.twig b/templates/job-order/form.html.twig index 9a8387b5..ca68e922 100644 --- a/templates/job-order/form.html.twig +++ b/templates/job-order/form.html.twig @@ -138,7 +138,7 @@
- +
{% trans %}country_code_prefix{% endtrans %} @@ -206,6 +206,7 @@ {% endif %}
+
@@ -715,6 +716,7 @@
+
@@ -748,6 +751,7 @@
+ {% include('invoice/trade_in.html.twig') %} {% endif %} @@ -791,7 +795,7 @@ {% for hub in hubs %} - + {{ hub.hub.getName }} {{ hub.hub.getBranch }} + + +
+
+

+ Vehicle Details +

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

+ Battery Details +

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

+ Transaction Details +

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

+ Location +

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

+ Invoice +

+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + + + + + + + + + {% 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) }}
+
+
+
+
+
+
+

+ 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 %} +
+
+
+
+
+
+
+
+

+ Tickets +

+
+
+
+
+
+
+
+
+
+
+
+ Back +
+
+
+
+ + + + + +{% endblock %} + +{% block scripts %} +{{ include('map/' ~ map_js_file) }} + + + + +{% endblock %} diff --git a/templates/job-order/list.all.html.twig b/templates/job-order/list.all.html.twig index a2c6b7c6..0eeddd1f 100644 --- a/templates/job-order/list.all.html.twig +++ b/templates/job-order/list.all.html.twig @@ -97,7 +97,20 @@ if (data.flag_advance) { $(row).addClass('m-table__row--danger'); } - } + }, + afterTemplate: function (row, data, index) { + // if this row has an incrementing timer, handle the behavior + const timer = row.find('[data-incrementing]')[0]; + + if (timer) { + setInterval(() => { + const dateStart = new moment(data.date_assign, moment.ISO_8601); + const dateEnd = moment(); + + timer.innerHTML = timeDiff(dateStart, dateEnd); + }, 1000) + } + }, }, columns: [ { @@ -108,6 +121,21 @@ field: 'plate_number', title: 'Plate #' }, + { + field: 'flag_cust_new', + title: 'New', + template: function (row, index, datatable) { + var tag = ''; + + if (row.flag_cust_new === true) { + tag = 'Yes'; + } else { + tag = 'No'; + } + + return tag; + } + }, { field: 'customer_name', title: 'Customer' @@ -117,12 +145,12 @@ title: 'Customer Area' }, { - field: 'hub_facilitated', - title: 'Battery Facilitated By' + field: 'assigned_hub', + title: 'Assigned Hub' }, { - field: 'type', - title: 'Schedule' + field: 'service_type', + title: 'Transaction Type' }, { field: 'date_schedule', @@ -136,6 +164,42 @@ field: 'processor', title: 'Dispatcher' }, + { + field: '', + title: 'Dispatch Time', + sortable: false, + searchable: false, + textAlign: 'right', + template: function (row, index, datatable) { + let dateAssign; + let dateEnd; + let incrementing = false; + + // only display timer for these statuses + const statusesWithTimer = [ + 'Assigned', + 'In Transit', + 'In Progress', + 'Completed', // NOTE: completed would be based off the fulfillment time + ]; + + if (row.date_assign && statusesWithTimer.includes(row.status)) { + dateAssign = new moment(row.date_assign, moment.ISO_8601); + + // on completed jos, use date_fulfill as end date and do not increment + if (row.status == 'Completed') { + if (row.date_fulfill) { + dateEnd = new moment(row.date_fulfill, moment.ISO_8601); + } + } else { + dateEnd = moment(); + incrementing = true; + } + } + + return dateEnd ? '' + timeDiff(dateAssign, dateEnd) + '' : '-'; + }, + }, { field: 'Actions', width: 110, @@ -143,18 +207,19 @@ sortable: false, overflow: 'visible', template: function (row, index, datatable) { - var actions = ''; + var actions = ''; + actions += ''; actions += ''; return actions; }, - } + }, ], search: { onEnter: false, input: $('#data-rows-search'), delay: 400 - } + }, }; var table = $("#data-rows").mDatatable(options); @@ -186,5 +251,16 @@ table.search(date_array, "schedule_date"); }); }); + + // get difference in hh:mm:ss format + const timeDiff = (startDate, endDate) => { + const seconds = Math.abs(startDate.diff(endDate, 'seconds')); + + const hh = Math.floor(seconds / 3600).toString().padStart(2, "0"); + const mm = Math.floor((seconds % 3600) / 60).toString().padStart(2, "0"); + const ss = (seconds % 60).toString().padStart(2, "0"); + + return [hh, mm, ss].join(':'); + } {% endblock %} diff --git a/templates/menu.html.twig b/templates/menu.html.twig index 4f200a7d..1d0a27d0 100644 --- a/templates/menu.html.twig +++ b/templates/menu.html.twig @@ -2,36 +2,38 @@ {% import _self as menu %} {% endmacro %} @@ -47,7 +49,7 @@ {% else %}