From 81213f043458afbcbb3d10313043674dae8650ac Mon Sep 17 00:00:00 2001 From: WShells <26513147+WebShells@users.noreply.github.com> Date: Thu, 7 May 2026 21:53:25 +0300 Subject: [PATCH 01/13] Assignable Keyboard Shortcuts Updates (#4532) * Add configurable sales shortcuts * Fix sales shortcut payment flow * Resolve shortcut keys review comment * Sanitize shortcut config notifications * Clarify keyboard shortcut configuration labels --------- Co-authored-by: WShells <26513147+WShells@users.noreply.github.com> --- app/Controllers/Config.php | 41 ++++++++ app/Controllers/Sales.php | 5 +- .../20260506000000_AddShortcutKeys.php | 46 +++++++++ app/Language/en/Config.php | 4 + app/Libraries/Sale_lib.php | 51 ++++++++++ app/Views/configs/manage.php | 6 ++ app/Views/configs/shortcuts_config.php | 88 +++++++++++++++++ app/Views/sales/help.php | 68 ++++++-------- app/Views/sales/register.php | 94 ++++++++++++------- 9 files changed, 328 insertions(+), 75 deletions(-) create mode 100644 app/Database/Migrations/20260506000000_AddShortcutKeys.php create mode 100644 app/Views/configs/shortcuts_config.php diff --git a/app/Controllers/Config.php b/app/Controllers/Config.php index 38f2ca899..6731938ab 100644 --- a/app/Controllers/Config.php +++ b/app/Controllers/Config.php @@ -221,6 +221,7 @@ class Config extends Secure_Controller */ public function getIndex(): string { + $data['config'] = $this->config; $data['stock_locations'] = $this->stock_location->get_all()->getResultArray(); $data['dinner_tables'] = $this->dinner_table->get_all()->getResultArray(); $data['customer_rewards'] = $this->customer_rewards->get_all()->getResultArray(); @@ -231,6 +232,8 @@ class Config extends Secure_Controller $data['line_sequence_options'] = $this->sale_lib->get_line_sequence_options(); $data['register_mode_options'] = $this->sale_lib->get_register_mode_options(); $data['invoice_type_options'] = $this->sale_lib->get_invoice_type_options(); + $data['keyboardShortcutOptions'] = $this->sale_lib->getKeyShortcutsOptions(); + $data['keyboardShortcuts'] = $this->sale_lib->getKeyShortcuts(); $data['rounding_options'] = rounding_mode::get_rounding_options(); $data['tax_code_options'] = $this->tax_lib->get_tax_code_options(); $data['tax_category_options'] = $this->tax_lib->get_tax_category_options(); @@ -946,6 +949,44 @@ class Config extends Secure_Controller return $this->response->setJSON(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]); } + /** + * Saves keyboard shortcut bindings. + * + * @return ResponseInterface + * @noinspection PhpUnused + */ + public function postSaveShortcuts(): ResponseInterface + { + $allowedShortcuts = array_keys($this->sale_lib->getKeyShortcutsOptions()); + $currentShortcuts = $this->sale_lib->getKeyShortcuts(); + $batchSaveData = []; + + foreach ($currentShortcuts as $name => $shortcut) { + $postedValue = trim((string)$this->request->getPost('key_' . $name)); + + if (!in_array($postedValue, $allowedShortcuts, true)) { + $postedValue = $shortcut['value']; + } + + $batchSaveData['key_' . $name] = $postedValue; + } + + $duplicateValues = array_filter(array_count_values($batchSaveData), static fn(int $count): bool => $count > 1); + if (!empty($duplicateValues)) { + return $this->response->setJSON([ + 'success' => false, + 'message' => lang('Config.shortcuts_duplicate_bindings') + ]); + } + + $success = $this->appconfig->batch_save($batchSaveData); + + return $this->response->setJSON([ + 'success' => $success, + 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully') + ]); + } + /** * Saves invoice configuration. Used in app/Views/configs/invoice_config.php. * diff --git a/app/Controllers/Sales.php b/app/Controllers/Sales.php index 76b3a8bfe..555bef6ba 100644 --- a/app/Controllers/Sales.php +++ b/app/Controllers/Sales.php @@ -1253,6 +1253,7 @@ class Sales extends Secure_Controller $data['quote_number'] = $this->sale_lib->get_quote_number(); $data['work_order_number'] = $this->sale_lib->get_work_order_number(); + $data['keyboardShortcuts'] = $this->sale_lib->getKeyShortcuts(); // TODO: the if/else set below should be converted to a switch if ($this->sale_lib->get_mode() == 'sale_invoice') { // TODO: Duplicated code. @@ -1641,7 +1642,9 @@ class Sales extends Secure_Controller */ public function getSalesKeyboardHelp(): string { - return view('sales/help'); + return view('sales/help', [ + 'keyboardShortcuts' => $this->sale_lib->getKeyShortcuts() + ]); } /** diff --git a/app/Database/Migrations/20260506000000_AddShortcutKeys.php b/app/Database/Migrations/20260506000000_AddShortcutKeys.php new file mode 100644 index 000000000..66f5347af --- /dev/null +++ b/app/Database/Migrations/20260506000000_AddShortcutKeys.php @@ -0,0 +1,46 @@ + 'key_cancel', 'value' => '27 | ESC'], + ['key' => 'key_items', 'value' => '49 | ALT + 1'], + ['key' => 'key_customers', 'value' => '50 | ALT + 2'], + ['key' => 'key_suspend', 'value' => '51 | ALT + 3'], + ['key' => 'key_suspended', 'value' => '52 | ALT + 4'], + ['key' => 'key_amount', 'value' => '53 | ALT + 5'], + ['key' => 'key_payment', 'value' => '54 | ALT + 6'], + ['key' => 'key_complete', 'value' => '55 | ALT + 7'], + ['key' => 'key_finish', 'value' => '56 | ALT + 8'], + ['key' => 'key_help', 'value' => '57 | ALT + 9'], + ]; + + $this->db->table('app_config')->ignore(true)->insertBatch($shortcutValues); + } + + public function down(): void + { + $shortcutKeys = [ + 'key_cancel', + 'key_items', + 'key_customers', + 'key_suspend', + 'key_suspended', + 'key_amount', + 'key_payment', + 'key_complete', + 'key_finish', + 'key_help', + ]; + + $this->db->table('app_config') + ->whereIn('key', $shortcutKeys) + ->delete(); + } +} diff --git a/app/Language/en/Config.php b/app/Language/en/Config.php index c96e9bcb1..1d32112b9 100644 --- a/app/Language/en/Config.php +++ b/app/Language/en/Config.php @@ -302,6 +302,10 @@ return [ "suggestions_layout" => "Search Suggestions Layout", "suggestions_second_column" => "Column 2", "suggestions_third_column" => "Column 3", + "shortcuts" => "Shortcuts", + "shortcuts_configuration" => "Sales Keyboard Shortcut Configuration", + "shortcuts_duplicate_bindings" => "Shortcut bindings must be unique.", + "shortcuts_save_error" => "Unable to save shortcut settings.", "system_conf" => "Setup & Conf", "system_info" => "System Info", "table" => "Table", diff --git a/app/Libraries/Sale_lib.php b/app/Libraries/Sale_lib.php index d2a4ac273..44a79449d 100644 --- a/app/Libraries/Sale_lib.php +++ b/app/Libraries/Sale_lib.php @@ -23,6 +23,19 @@ use ReflectionException; */ class Sale_lib { + private const KEY_SHORTCUT_DEFAULTS = [ + 'cancel' => ['value' => '27 | ESC', 'code' => 27, 'label' => 'ESC'], + 'items' => ['value' => '49 | ALT + 1', 'code' => 49, 'label' => 'ALT + 1'], + 'customers' => ['value' => '50 | ALT + 2', 'code' => 50, 'label' => 'ALT + 2'], + 'suspend' => ['value' => '51 | ALT + 3', 'code' => 51, 'label' => 'ALT + 3'], + 'suspended' => ['value' => '52 | ALT + 4', 'code' => 52, 'label' => 'ALT + 4'], + 'amount' => ['value' => '53 | ALT + 5', 'code' => 53, 'label' => 'ALT + 5'], + 'payment' => ['value' => '54 | ALT + 6', 'code' => 54, 'label' => 'ALT + 6'], + 'complete' => ['value' => '55 | ALT + 7', 'code' => 55, 'label' => 'ALT + 7'], + 'finish' => ['value' => '56 | ALT + 8', 'code' => 56, 'label' => 'ALT + 8'], + 'help' => ['value' => '57 | ALT + 9', 'code' => 57, 'label' => 'ALT + 9'], + ]; + private Attribute $attribute; private Customer $customer; private Dinner_table $dinner_table; @@ -105,6 +118,44 @@ class Sale_lib return $invoice_types; } + /** + * Returns the available keyboard shortcut choices for the configuration screen. + * + * @return array + */ + public function getKeyShortcutsOptions(): array + { + $keyShortcuts = []; + + foreach (self::KEY_SHORTCUT_DEFAULTS as $shortcut) { + $keyShortcuts[$shortcut['value']] = $shortcut['label']; + } + + return $keyShortcuts; + } + + /** + * Returns parsed shortcut bindings from app_config with sensible defaults. + * + * @return array + */ + public function getKeyShortcuts(): array + { + $keyboardShortcuts = []; + + foreach (self::KEY_SHORTCUT_DEFAULTS as $name => $default) { + $value = $this->config["key_$name"] ?? $default['value']; + $parts = array_map('trim', explode('|', $value, 2)); + $keyboardShortcuts[$name] = [ + 'value' => $value, + 'code' => (int)($parts[0] ?? $default['code']), + 'label' => $parts[1] ?? $default['label'] + ]; + } + + return $keyboardShortcuts; + } + public static function isValidInvoiceType(string $invoice_type): bool { return in_array($invoice_type, self::ALLOWED_INVOICE_TYPES, true); diff --git a/app/Views/configs/manage.php b/app/Views/configs/manage.php index e7eb81411..af71fc87b 100644 --- a/app/Views/configs/manage.php +++ b/app/Views/configs/manage.php @@ -29,6 +29,9 @@
  • +
  • + +
  • @@ -65,6 +68,9 @@
    +
    + +
    diff --git a/app/Views/configs/shortcuts_config.php b/app/Views/configs/shortcuts_config.php new file mode 100644 index 000000000..8ccb356f6 --- /dev/null +++ b/app/Views/configs/shortcuts_config.php @@ -0,0 +1,88 @@ + lang('Sales.key_cancel'), + 'items' => lang('Sales.key_item_search'), + 'customers' => lang('Sales.key_customer_search'), + 'suspend' => lang('Sales.key_suspend'), + 'suspended' => lang('Sales.key_suspended'), + 'amount' => lang('Sales.key_tendered'), + 'payment' => lang('Sales.key_payment'), + 'complete' => lang('Sales.key_finish_sale'), + 'finish' => lang('Sales.key_finish_quote'), + 'help' => lang('Sales.key_help_modal') +]; +?> + + 'shortcuts_config_form', 'class' => 'form-horizontal']) ?> +
    +
    +
    +
    +
    +
      + + $label): ?> +
      + 'control-label col-xs-3']) ?> +
      + + +
      +
      + + +
      + 'submit_shortcuts', + 'id' => 'submit_shortcuts', + 'value' => lang('Common.submit'), + 'class' => 'btn btn-primary btn-sm pull-right' + ]) ?> +
      +
      +
      +
      +
      + + + diff --git a/app/Views/sales/help.php b/app/Views/sales/help.php index a4715d026..5c9cc82e8 100644 --- a/app/Views/sales/help.php +++ b/app/Views/sales/help.php @@ -1,3 +1,24 @@ + lang('Sales.key_cancel'), + 'items' => lang('Sales.key_item_search'), + 'customers' => lang('Sales.key_customer_search'), + 'suspend' => lang('Sales.key_suspend'), + 'suspended' => lang('Sales.key_suspended'), + 'amount' => lang('Sales.key_tendered'), + 'payment' => lang('Sales.key_payment'), + 'complete' => lang('Sales.key_finish_sale'), + 'finish' => lang('Sales.key_finish_quote'), + 'help' => lang('Sales.key_help_modal') +]; +?> +
      diff --git a/app/Views/sales/register.php b/app/Views/sales/register.php index 6b4f275e8..685b78fe5 100644 --- a/app/Views/sales/register.php +++ b/app/Views/sales/register.php @@ -405,6 +405,7 @@ helper('url');
      'add_payment_form', 'class' => 'form-horizontal']) ?> + @@ -445,6 +446,7 @@ helper('url'); ?> 'add_payment_form', 'class' => 'form-horizontal']) ?> +
      @@ -565,6 +567,21 @@ helper('url');