Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Service sorting #1329

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 43 additions & 2 deletions application/controllers/Services.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public function search(): void

$keyword = request('keyword', '');

$order_by = request('order_by', 'update_datetime DESC');
$order_by = 'update_datetime DESC';

$limit = request('limit', 1000);

Expand Down Expand Up @@ -227,4 +227,45 @@ public function destroy(): void
json_exception($e);
}
}
}

/**
* Arrange service order
*/
public function sort()
{
try {

if (cannot('edit', PRIV_SERVICES))
{
abort(403, 'Forbidden');
}

$service_id = request('service_id');
if (($service_id = filter_var($service_id, FILTER_VALIDATE_INT)) === FALSE)
{
abort(400,'Invalid ID value');
}

$insertAfterId = request('after');
if (($insertAfterId = filter_var($insertAfterId,FILTER_VALIDATE_INT)) === FALSE)
{
abort(400,'Invalid after value, must be ID or -1');
}

if ($insertAfterId <= 0)
{
$insertAfterId = FALSE;
}

$service = $this->services_model->find($service_id);

$service['row_order'] = $this->services_model->set_service_order($service['id'], $insertAfterId);

return json_response($service);
}
catch (Throwable $e)
{
json_exception($e);
}
}
}
157 changes: 157 additions & 0 deletions application/core/EA_Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,161 @@ public function db_field(string $api_field): ?string
{
return $this->api_resource[$api_field] ?? null;
}

/**
* Sort column orders from table
*
* @param string $table Table
* @param string $column Column to sort (Default is row_order)
*/
public function sort_column(string $table, string $column = 'row_order')
{
if (empty($table))
throw new InvalidArgumentException("Table parameter must be defined");
if (empty($column))
throw new InvalidArgumentException("Column parameter must be defined");


$rows = $this->db
->select(['id', $column])
->from($table)
->order_by($column)
->get()
->result_array();

for($i=0; $i < count($rows); $i++)
{
if ($rows[$i][$column] != $i)
{
$rows[$i][$column] = $i;
if (! $this->db->update($table, [$column => $i], [ 'id'=> $rows[$i]['id'] ]))
{
throw new RuntimeException('Could not sort table '.$table . ": Db error");
}
}
else {

}
}
}


/**
* Inserts entry order after defined entry
*
* @param string $table Table
* @param array $entry Entity to insert
* @param mixed|bool $afterId ID of entry where to insert at. Or false to set at beginning
* @param string [$order_column] Ordering Column name (Default: row_order)
*
* @throws RuntimeException
* @throws InvalidArgumentException
*/

public function insert_row_order_after(string $table, array &$entry, $afterId, string $order_column='row_order')
{
if (empty($table))
throw new InvalidArgumentException("Table parameter must be defined");
if (empty($order_column))
throw new InvalidArgumentException("Column parameter must be defined");

if (!array_key_exists('id', $entry))
throw new InvalidArgumentException('Entry does not contain ID column');
if (!array_key_exists($order_column,$entry))
throw new InvalidArgumentException('Entry does not contain sorting column');


// Get position of desired entry:
if (is_int($afterId) && $afterId > 0)
{
$position = $this->db->from($table)
->select([$order_column])
->where('id',$afterId)
->get()
->row_array();
if ($position === false)
{
throw new InvalidArgumentException("Could not found service with ID $afterId");
}
$position = intval($position[$order_column]);
}
else {
$position = FALSE;
}

if (! $this->insert_row_order($table,$entry,$position,$order_column))
{
throw new RuntimeException('Could not update order, database error');
}

}


/**
* Inserts entry to specified order in table
*
* @param string $table Table
* @param array $entry Entry, should be associative array containing columns 'Id' and desired $column sort value.
* @param int|bool $position Position to set entry to. If set to False, it will be positioned to first.
* @param string $column Column name that contains sorting data (default is 'row_order').
*
* @return bool TRUE on success, FALSE on failure
* @throws InvalidArgumentException
*/

protected function insert_row_order(string $table, array &$entry, $position, string $column = 'row_order')
{
if (empty($table))
throw new InvalidArgumentException("Table parameter must be defined");
if (empty($column))
throw new InvalidArgumentException("Column parameter must be defined");

if (!array_key_exists('id', $entry))
throw new InvalidArgumentException('Entry does not contain ID column');
if (!array_key_exists($column,$entry))
throw new InvalidArgumentException('Entry does not contain sorting column');


if (is_int($position))
{
$newOr = $position +1;
}
else
{
$newOr = 0;
}

$this->db->update($table, [$column => $newOr ], [ 'id'=> $entry['id'] ]);


$rows = $this->db
->select(['id', $column])
->from($table)
->where($column .'>=', $newOr)
->order_by($column)
->get()
->result_array();

// Move entries after inserted:
foreach ($rows as $row)
{
$id = $row['id'];
if ($id == $entry['id'])
{
continue;
}

$newOr++;
if ($this->db->update($table, [$column => $newOr], [ 'id'=> $row['id'] ]) === FALSE)
{ // Failed!
return FALSE;
}

}

// And fix empty gaps:
$this->sort_column($table,$column);

return TRUE;
}
}
45 changes: 45 additions & 0 deletions application/migrations/059_add_sort_column_to_service_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');

/* ----------------------------------------------------------------------------
* Easy!Appointments - Open Source Web Scheduler
*
* @package EasyAppointments
* @author Eero Jääskeläinen <eero.jaaskelainen@gmail.com>
* @copyright Copyright (c) 2013 - 2020, Alex Tselegidis
* @license http://opensource.org/licenses/GPL-3.0 - GPLv3
* @link http://easyappointments.org
* @since v1.4.0
* ---------------------------------------------------------------------------- */

class Migration_Add_sort_column_to_service_table extends EA_Migration {
/**
* Upgrade method.
*/
public function up()
{
if ( ! $this->db->field_exists('row_order', 'services'))
{
$fields = [
'row_order' => [
'type' => 'INT',
'constraint' => '11',
'default' => '0',
'after' => 'id'
]
];

$this->dbforge->add_column('services', $fields);
}
}

/**
* Downgrade method.
*/
public function down()
{
if ( ! $this->db->field_exists('row_order', 'services'))
{
$this->dbforge->drop_column('services', 'row_order');
}
}
}
44 changes: 41 additions & 3 deletions application/models/Services_model.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Services_model extends EA_Model
'attendants_number' => 'integer',
'is_private' => 'boolean',
'id_service_categories' => 'integer',
'row_order' => 'integer',
];

/**
Expand All @@ -37,6 +38,7 @@ class Services_model extends EA_Model
protected array $api_resource = [
'id' => 'id',
'name' => 'name',
'order' => 'row_order',
'duration' => 'duration',
'price' => 'price',
'currency' => 'currency',
Expand Down Expand Up @@ -164,6 +166,7 @@ protected function insert(array $service): int
{
$service['create_datetime'] = date('Y-m-d H:i:s');
$service['update_datetime'] = date('Y-m-d H:i:s');
$service['row_order'] = $this->db->count_all('services');

if (!$this->db->insert('services', $service)) {
throw new RuntimeException('Could not insert service.');
Expand All @@ -188,7 +191,7 @@ protected function update(array $service): int
if (!$this->db->update('services', $service, ['id' => $service['id']])) {
throw new RuntimeException('Could not update service.');
}

return $service['id'];
}

Expand All @@ -202,6 +205,7 @@ protected function update(array $service): int
public function delete(int $service_id): void
{
$this->db->delete('services', ['id' => $service_id]);
$this->sort_column('services');
}

/**
Expand Down Expand Up @@ -286,7 +290,7 @@ public function get_available_services(bool $without_private = false): array
->from('services')
->join('services_providers', 'services_providers.id_services = services.id', 'inner')
->join('service_categories', 'service_categories.id = services.id_service_categories', 'left')
->order_by('name ASC')
->order_by('row_order,name ASC')
->get()
->result_array();

Expand Down Expand Up @@ -320,6 +324,10 @@ public function get(
if ($order_by !== null) {
$this->db->order_by($order_by);
}
else
{
$this->db->order_by('row_order');
}

$services = $this->db->get('services', $limit, $offset)->result_array();

Expand Down Expand Up @@ -352,6 +360,11 @@ public function query(): CI_DB_query_builder
*/
public function search(string $keyword, int $limit = null, int $offset = null, string $order_by = null): array
{
if ($order_by === NULL)
{
$order_by = 'row_order';
}

$services = $this->db
->select()
->from('services')
Expand Down Expand Up @@ -410,6 +423,7 @@ public function api_encode(array &$service): void
$encoded_resource = [
'id' => array_key_exists('id', $service) ? (int) $service['id'] : null,
'name' => $service['name'],
'order' => $service['row_order'],
'duration' => (int) $service['duration'],
'price' => (float) $service['price'],
'currency' => $service['currency'],
Expand Down Expand Up @@ -443,7 +457,13 @@ public function api_decode(array &$service, array $base = null): void
$decoded_resource['name'] = $service['name'];
}

if (array_key_exists('duration', $service)) {
if (array_key_exists('order', $service))
{
$decoded_resource['row_order'] = $service['order'];
}

if (array_key_exists('duration', $service))
{
$decoded_resource['duration'] = $service['duration'];
}

Expand Down Expand Up @@ -481,4 +501,22 @@ public function api_decode(array &$service, array $base = null): void

$service = $decoded_resource;
}

/**
* Sort service
*
* @param int $service_id Service ID
* @param int|bool $afterServiceId ID of service that service should be inserted after, or FALSE to set to beginning
*
* @return int New place in table
*/

public function set_service_order(int $service_id, $afterServiceId)
{
$service = $this->find($service_id);

$this->insert_row_order_after('services',$service,$afterServiceId);

return $this->value($service['id'],'row_order');
}
}
1 change: 1 addition & 0 deletions application/views/layouts/backend_layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<script src="<?= asset_url('assets/vendor/trumbowyg/trumbowyg.min.js') ?>"></script>
<script src="<?= asset_url('assets/vendor/select2/select2.min.js') ?>"></script>
<script src="<?= asset_url('assets/vendor/flatpickr/flatpickr.min.js') ?>"></script>
<script src="<?= asset_url('assets/vendor/sortablejs/Sortable.min.js') ?>"></script>

<script src="<?= asset_url('assets/js/app.js') ?>"></script>
<script src="<?= asset_url('assets/js/utils/date.js') ?>"></script>
Expand Down
1 change: 1 addition & 0 deletions assets/css/components/color_selection.css.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions assets/css/general.css.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.