Merge branch '609-car-club-final-changes' into 'master-fix'
Resolve "Car club final changes" See merge request jankstudio/resq!723
This commit is contained in:
commit
31047deb4a
5 changed files with 555 additions and 5 deletions
392
src/Command/ImportCarClubCustomerHubCommand.php
Normal file
392
src/Command/ImportCarClubCustomerHubCommand.php
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
<?php
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\CustomerTag;
|
||||
use App\Entity\CarClubCustomerHub;
|
||||
use App\Entity\Hub;
|
||||
|
||||
class ImportCarClubCustomerHubCommand extends Command
|
||||
{
|
||||
// field index in csv file
|
||||
const F_HUB_NAME = 0;
|
||||
const F_HUB_ADDRESS = 1;
|
||||
const F_CAR_CLUB = 2;
|
||||
const F_FIRST_NAME = 3;
|
||||
const F_LAST_NAME = 4;
|
||||
const F_CONTACT_NUMBER = 5;
|
||||
const F_BATT_SIZE = 6;
|
||||
const F_SERIAL = 7;
|
||||
|
||||
protected $em;
|
||||
protected $cust_tag_hash;
|
||||
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->loadCustomerTags();
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('customer:importcarclubhub')
|
||||
->setDescription('Import assigned hubs for customers for car club.')
|
||||
->setHelp('Import assigned hubs for customers for car club.')
|
||||
->addArgument('promo_tag', InputArgument::REQUIRED, 'Promo customer tag ID to use.')
|
||||
->addArgument('input_file', InputArgument::REQUIRED, 'Path to the input CSV file with the customer and hub info.')
|
||||
->addArgument('output_file', InputArgument::REQUIRED, 'Output filename');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$csv_file = $input->getArgument('input_file');
|
||||
$output_file = $input->getArgument('output_file');
|
||||
$promo_tag_id = $input->getArgument('promo_tag');
|
||||
|
||||
// fetch promo tag
|
||||
$promo_tag = $this->em->getRepository(CustomerTag::class)->find($promo_tag_id);
|
||||
if ($promo_tag == null)
|
||||
{
|
||||
throw new Exception('Could not find promo tag - ' . $promo_tag_id);
|
||||
}
|
||||
|
||||
// attempt to open file
|
||||
try
|
||||
{
|
||||
$fh = fopen($csv_file, "r");
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
throw new Exception('The file "' . $csv_file . '" could be opened.');
|
||||
}
|
||||
|
||||
$row_num = 1;
|
||||
$output_info = [];
|
||||
while (($fields = fgetcsv($fh)) !== false)
|
||||
{
|
||||
// ignore first row
|
||||
if ($row_num == 1)
|
||||
{
|
||||
$row_num++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// process row
|
||||
$output_info[] = $this->processRow($fields, $promo_tag);
|
||||
|
||||
$row_num++;
|
||||
}
|
||||
|
||||
// write to output file
|
||||
$this->outputCarClubCustomerHubInfo($output_file, $output_info);
|
||||
|
||||
fclose($fh);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function processRow($fields, $promo_tag)
|
||||
{
|
||||
$contact_number = trim($fields[SELF::F_CONTACT_NUMBER]);
|
||||
$car_club = trim($fields[self::F_CAR_CLUB]);
|
||||
|
||||
if (empty($contact_number))
|
||||
{
|
||||
// add info to output array
|
||||
return $this->setOutputInfo($fields, 'NOT ADDED', 'No mobile number', '');
|
||||
}
|
||||
|
||||
$hub_name = trim($fields[SELF::F_HUB_NAME]);
|
||||
|
||||
// find hub using name
|
||||
$hub = $this->findHubByName($hub_name);
|
||||
|
||||
if ($hub == null)
|
||||
{
|
||||
// add info to output array
|
||||
return $this->setOutputInfo($fields, 'NOT ADDED', 'No hub found', '');
|
||||
}
|
||||
|
||||
// clean up contact number
|
||||
$valid_contact_numbers = $this->getValidContactNumbers($contact_number);
|
||||
|
||||
// check customer tag
|
||||
$cust_tag = $this->findCustomerTag($car_club);
|
||||
$cust_id = '';
|
||||
|
||||
// get first customer that matches any of the numbers
|
||||
$customer = $this->findCustomerByNumbers($valid_contact_numbers);
|
||||
|
||||
// if no customer found, create one and add the hub
|
||||
if ($customer == null)
|
||||
{
|
||||
error_log('Creating customer...');
|
||||
|
||||
// create new customer
|
||||
$new_cust = $this->createNewCustomer($fields, $valid_contact_numbers, $cust_tag, $promo_tag);
|
||||
|
||||
// get customer id of new customer here
|
||||
$cust_id = $new_cust->getID();
|
||||
|
||||
// create the carclubcustomerhub
|
||||
$this->createCarClubCustomerHub($fields, $new_cust, $hub);
|
||||
|
||||
// add info to output array
|
||||
return $this->setOutputInfo($fields, 'CUSTOMER CREATED AND CUSTOMER HUB ADDED', '', $cust_id);
|
||||
}
|
||||
|
||||
//customer exists, we just need to add the hub and the promo tag
|
||||
// NOTE: for now, we add the promo tag and the hub
|
||||
// to the first customer found with the mobile number
|
||||
$customer->addCustomerTag($promo_tag)
|
||||
->addCustomerTag($cust_tag);
|
||||
|
||||
// create the carclubcustomerhub
|
||||
$this->createCarClubCustomerHub($fields, $customer, $hub);
|
||||
|
||||
return $this->setOutputInfo($fields, 'CUSTOMER HUB ADDED', '', $customer->getID());
|
||||
}
|
||||
|
||||
protected function createCarClubCustomerHub($fields, $cust, $hub)
|
||||
{
|
||||
$car_club_cust_hub = new CarClubCustomerHub();
|
||||
$car_club_cust_hub->setHub($hub);
|
||||
|
||||
$cust->setCarClubCustomerHub($car_club_cust_hub);
|
||||
|
||||
$this->em->persist($car_club_cust_hub);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
protected function createNewCustomer($fields, $contact_numbers, $cust_tag, $promo_tag)
|
||||
{
|
||||
$fname = trim($fields[self::F_FIRST_NAME]);
|
||||
$lname = trim($fields[self::F_LAST_NAME]);
|
||||
|
||||
if (count($contact_numbers) > 0)
|
||||
$clean_number = $contact_numbers[0];
|
||||
else
|
||||
$clean_number = '';
|
||||
|
||||
// create new customer
|
||||
$new_cust = new Customer();
|
||||
$new_cust->setFirstName($fname)
|
||||
->setLastName($lname)
|
||||
->setPhoneMobile($clean_number)
|
||||
->setCreateSource('car_club_file')
|
||||
->addCustomerTag($cust_tag)
|
||||
->addCustomerTag($promo_tag);
|
||||
|
||||
$this->em->persist($new_cust);
|
||||
$this->em->flush();
|
||||
|
||||
return $new_cust;
|
||||
}
|
||||
|
||||
protected function setOutputInfo($fields, $status, $reason, $cust_id)
|
||||
{
|
||||
$hub_name = trim($fields[SELF::F_HUB_NAME]);
|
||||
$hub_address = trim($fields[SELF::F_HUB_ADDRESS]);
|
||||
$car_club_name = trim($fields[SELF::F_CAR_CLUB]);
|
||||
$first_name = trim($fields[self::F_FIRST_NAME]);
|
||||
$last_name = trim($fields[self::F_LAST_NAME]);
|
||||
$contact_number = trim($fields[SELF::F_CONTACT_NUMBER]);
|
||||
$battery_size = trim($fields[SELF::F_BATT_SIZE]);
|
||||
$serial = trim($fields[SELF::F_SERIAL]);
|
||||
|
||||
return [
|
||||
$hub_name,
|
||||
$hub_address,
|
||||
$car_club_name,
|
||||
$first_name,
|
||||
$last_name,
|
||||
$contact_number,
|
||||
$battery_size,
|
||||
$serial,
|
||||
$status,
|
||||
$reason,
|
||||
$cust_id
|
||||
];
|
||||
}
|
||||
|
||||
protected function outputCarClubCustomerHubInfo($output_file, $entries)
|
||||
{
|
||||
try
|
||||
{
|
||||
$fh = fopen($output_file, "w");
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
throw new Exception('The file "' . $report_file . '" could be opened.');
|
||||
}
|
||||
|
||||
// write the headers
|
||||
fputcsv($fh, [
|
||||
'Hub Name',
|
||||
'Hub Address',
|
||||
'Car Club Name',
|
||||
'First Name',
|
||||
'Last Name',
|
||||
'Contact Number',
|
||||
'Battery Size',
|
||||
'Serial',
|
||||
'Status',
|
||||
'Reason',
|
||||
'Customer ID',
|
||||
]);
|
||||
|
||||
foreach($entries as $row)
|
||||
{
|
||||
fputcsv($fh, $row);
|
||||
}
|
||||
|
||||
fclose($fh);
|
||||
|
||||
}
|
||||
protected function findCustomerByNumbers($numbers)
|
||||
{
|
||||
error_log(print_r($numbers, true));
|
||||
$customer = $this->em->getRepository(Customer::class)->findOneBy(['phone_mobile' => $numbers]);
|
||||
return $customer;
|
||||
}
|
||||
|
||||
protected function findHubByName($hub_name)
|
||||
{
|
||||
$hname = strtoupper($hub_name);
|
||||
$hub = $this->em->getRepository(Hub::class)->findOneBy(['name' => $hname]);
|
||||
return $hub;
|
||||
}
|
||||
|
||||
protected function findCustomerTag($car_club)
|
||||
{
|
||||
// find the customer tag for club
|
||||
$tag_name = $this->normalizeClubName($car_club);
|
||||
error_log($tag_name);
|
||||
if (isset($this->cust_tag_hash[$tag_name]))
|
||||
return $this->cust_tag_hash[$tag_name];
|
||||
|
||||
error_log('customer tag not in hash...');
|
||||
// create the customer tag
|
||||
$new_cust_tag = new CustomerTag();
|
||||
$tag_details = json_decode('{"type":"car club"}', true);
|
||||
$new_cust_tag->setID($tag_name)
|
||||
->setName(strtoupper($car_club))
|
||||
->setTagDetails($tag_details);
|
||||
$this->em->persist($new_cust_tag);
|
||||
|
||||
// need to flush before adding to hash
|
||||
$this->em->flush();
|
||||
|
||||
$this->cust_tag_hash[$tag_name] = $new_cust_tag;
|
||||
|
||||
return $new_cust_tag;
|
||||
}
|
||||
|
||||
protected function getValidContactNumbers($contact_number)
|
||||
{
|
||||
// check contact number if mobile or not
|
||||
// check for spaces, slash, and forward slash
|
||||
$contact_num_array = [];
|
||||
if (preg_match('/[\\\s\/]/', $contact_number))
|
||||
{
|
||||
$contact_num_array = preg_split('/[\\\s\/]/', $contact_number);
|
||||
}
|
||||
else
|
||||
{
|
||||
// only one mobile number
|
||||
$contact_num_array[] = $contact_number;
|
||||
}
|
||||
|
||||
// collect clean numbers
|
||||
$clean_nums = [];
|
||||
foreach ($contact_num_array as $contact_num)
|
||||
{
|
||||
$c_num = trim($contact_num);
|
||||
|
||||
// clean the numbers
|
||||
$cleaned_number = $this->normalizeContactNumber($c_num);
|
||||
|
||||
// not a blank, save it
|
||||
if ($cleaned_number != '')
|
||||
{
|
||||
$clean_nums[] = $cleaned_number;
|
||||
}
|
||||
}
|
||||
|
||||
return $clean_nums;
|
||||
}
|
||||
|
||||
protected function normalizeContactNumber($c_num)
|
||||
{
|
||||
// remove any non digit character from string
|
||||
$clean_number = preg_replace('~\D~', '', $c_num);
|
||||
error_log('cleaned ' . $clean_number);
|
||||
|
||||
// does it fit our 09XXXXXXXXX pattern?
|
||||
if (preg_match('/^09[0-9]{9}$/', $clean_number))
|
||||
{
|
||||
// remove first '0'
|
||||
$clean_number = substr($clean_number, 1);
|
||||
error_log("CONVERTED TO $clean_number");
|
||||
return $clean_number;
|
||||
}
|
||||
// does it fit our 63XXXXXXXXXX pattern?
|
||||
else if (preg_match('/^63[0-9]{10}$/', $clean_number))
|
||||
{
|
||||
// remove the 63
|
||||
$clean_number = substr($clean_number, 2);
|
||||
error_log("CONVERTED TO $clean_number");
|
||||
return $clean_number;
|
||||
}
|
||||
// does it fit our 9XXXXXXXXX pattern?
|
||||
else if (preg_match('/^9[0-9]{9}$/', $clean_number))
|
||||
{
|
||||
error_log("already cleaned $clean_number");
|
||||
return $clean_number;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
protected function loadCustomerTags()
|
||||
{
|
||||
$this->cust_tag_hash = [];
|
||||
|
||||
$cust_tags = $this->em->getRepository(CustomerTag::class)->findAll();
|
||||
foreach ($cust_tags as $cust_tag)
|
||||
{
|
||||
$tag_id = $cust_tag->getID();
|
||||
$this->cust_tag_hash[$tag_id] = $cust_tag;
|
||||
}
|
||||
}
|
||||
|
||||
protected function normalizeClubName($name)
|
||||
{
|
||||
// uppercase the name
|
||||
$clean_name = trim(strtoupper($name));
|
||||
|
||||
// remove apostrophes
|
||||
$clean_name = str_replace("'", '', $clean_name);
|
||||
|
||||
// replace all special characters except ampersand (&) with whitespace
|
||||
$clean_name = trim(preg_replace('/[^A-Za-z0-9&]/', ' ', $clean_name));
|
||||
|
||||
// replace spaces with underscore (_)
|
||||
$clean_name = preg_replace('/\s+/', '_', $clean_name);
|
||||
|
||||
// prepend 'TAG_'
|
||||
$tag_name = 'TAG_' . $clean_name;
|
||||
|
||||
return $tag_name;
|
||||
}
|
||||
}
|
||||
|
|
@ -2555,13 +2555,39 @@ class APIController extends Controller implements LoggedController
|
|||
return $res->getReturnResponse();
|
||||
|
||||
$coordinates = new Point($req->query->get('longitude'), $req->query->get('latitude'));
|
||||
$nearest_hub_slots = $this->findAdvanceNearestHubAndSlots($coordinates, $em, $map_tools);
|
||||
|
||||
if (empty($nearest_hub_slots['hub']))
|
||||
// add checking if customer has a pre-registered hub
|
||||
$cust = $this->session->getCustomer();
|
||||
if ($cust == null)
|
||||
{
|
||||
$res->setError(true)
|
||||
->setErrorMessage('Thank you for reaching out to us. Due to the General Community Quarantine, our Operations are from 8AM to 6PM only. Please expect a call from us tomorrow and we will assist you with your request. Thank you and stay safe!');
|
||||
->setErrorMessage('No customer information found');
|
||||
return $res->getReturnResponse();
|
||||
}
|
||||
|
||||
// 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, $em, $map_tools, $hub);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$nearest_hub_slots = $this->findAdvanceNearestHubAndSlots($coordinates, $em, $map_tools);
|
||||
|
||||
if (empty($nearest_hub_slots['hub']))
|
||||
{
|
||||
$res->setError(true)
|
||||
->setErrorMessage('Thank you for reaching out to us. Due to the General Community Quarantine, our Operations are from 8AM to 6PM only. Please expect a call from us tomorrow and we will assist you with your request. Thank you and stay safe!');
|
||||
return $res->getReturnResponse();
|
||||
}
|
||||
}
|
||||
|
||||
// make hub data
|
||||
|
|
@ -3111,6 +3137,26 @@ class APIController extends Controller implements LoggedController
|
|||
if (($hour < 8) || ($hour > 16))
|
||||
$schedule_choice = false;
|
||||
|
||||
// add checking if customer has a pre-registered hub
|
||||
$cust = $this->session->getCustomer();
|
||||
if ($cust == null)
|
||||
{
|
||||
$res->setError(true)
|
||||
->setErrorMessage('No customer information found');
|
||||
return $res->getReturnResponse();
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'display_schedule_choice' => $schedule_choice,
|
||||
];
|
||||
|
|
@ -3764,10 +3810,23 @@ class APIController extends Controller implements LoggedController
|
|||
return $selected_rider;
|
||||
}
|
||||
|
||||
protected function findAdvanceNearestHubAndSlots(Point $coordinates, EntityManagerInterface $em, MapTools $map_tools)
|
||||
protected function findAdvanceNearestHubAndSlots(Point $coordinates, EntityManagerInterface $em, MapTools $map_tools, $hub=null)
|
||||
{
|
||||
// get the nearest 10 hubs
|
||||
$hub_data = [];
|
||||
|
||||
if ($hub != null)
|
||||
{
|
||||
// get the slots of hub
|
||||
$hub_slots = $this->getHubRiderSlots($hub, $em);
|
||||
|
||||
$hub_data = [
|
||||
'hub' => $hub,
|
||||
'slots' => $hub_slots,
|
||||
];
|
||||
return $hub_data;
|
||||
}
|
||||
|
||||
// get the nearest 10 hubs
|
||||
$nearest_hubs_with_distance = [];
|
||||
$hubs = $map_tools->getClosestOpenHubs($coordinates, 10);
|
||||
|
||||
|
|
|
|||
42
src/Entity/CarClubCustomerHub.php
Normal file
42
src/Entity/CarClubCustomerHub.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="car_club_customer_hub")
|
||||
*/
|
||||
class CarClubCustomerHub
|
||||
{
|
||||
// unique id
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="Hub")
|
||||
*/
|
||||
protected $hub;
|
||||
|
||||
public function getID()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setHub(Hub $hub)
|
||||
{
|
||||
$this->hub = $hub;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHub()
|
||||
{
|
||||
return $this->hub;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -215,6 +215,13 @@ class Customer
|
|||
*/
|
||||
protected $customer_tags;
|
||||
|
||||
// customer car club hub
|
||||
/**
|
||||
* One customer has one car club customer info
|
||||
* @ORM\OneToOne(targetEntity="CarClubCustomerHub")
|
||||
*/
|
||||
protected $car_club_customer_hub;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->numbers = new ArrayCollection();
|
||||
|
|
@ -656,4 +663,15 @@ class Customer
|
|||
$this->customer_tags->removeElement($customer_tag);
|
||||
$customer_tag->removeCustomer($this);
|
||||
}
|
||||
|
||||
public function setCarClubCustomerHub(CarClubCustomerHub $car_club_customer_hub)
|
||||
{
|
||||
$this->car_club_customer_hub = $car_club_customer_hub;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCarClubCustomerHub()
|
||||
{
|
||||
return $this->car_club_customer_hub;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -446,6 +446,29 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
|
|||
}
|
||||
}
|
||||
|
||||
// check service type if new battery
|
||||
// check if new JO
|
||||
if (($stype == ServiceType::BATTERY_REPLACEMENT_NEW) &&
|
||||
($flag_new_jo))
|
||||
{
|
||||
// check if customer has customer tag promo
|
||||
if (($customer->getCustomerTag('TAG_CAR_CLUB_OFFICER_PROMO')) ||
|
||||
($customer->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 = $customer->getCarClubCustomerHub();
|
||||
if ($car_club_hub != null)
|
||||
{
|
||||
// assign hub, change the jo status
|
||||
$hub = $car_club_hub->getHub();
|
||||
$jo->setHub($hub)
|
||||
->setStatus(JOStatus::RIDER_ASSIGN);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// call service to generate job order and invoice
|
||||
$invoice_items = $req->request->get('invoice_items', []);
|
||||
$promo_id = $req->request->get('invoice_promo');
|
||||
|
|
@ -484,6 +507,22 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
|
|||
}
|
||||
|
||||
$em->persist($event);
|
||||
|
||||
// check if JOStatus is rider assign
|
||||
if ($jo->getStatus() == JOStatus::RIDER_ASSIGN)
|
||||
{
|
||||
$rider_assign_event = new JOEvent();
|
||||
$rider_assign_event->setDateHappen(new DateTime())
|
||||
->setTypeID(JOEventType::HUB_ASSIGN)
|
||||
->setJobOrder($jo);
|
||||
|
||||
if ($user != null)
|
||||
{
|
||||
$rider_assign_event->setUser($user);
|
||||
}
|
||||
$em->persist($rider_assign_event);
|
||||
}
|
||||
|
||||
$em->flush();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue