Add support for custom fields on customers (#1133)

This commit is contained in:
Alex Tselegidis 2023-12-11 09:54:29 +01:00
parent aa10b57b3a
commit e6b3ffd66b
47 changed files with 769 additions and 434 deletions

View file

@ -43,6 +43,7 @@ developers to maintain and readjust their custom modifications on the main proje
- Create new layout structure for the markup, so that common HTML markup is being reused (#1152) - Create new layout structure for the markup, so that common HTML markup is being reused (#1152)
- Have an option to hide customer data fields during booking (#1081) - Have an option to hide customer data fields during booking (#1081)
- Add a SECURITY.md file to the repository (#1122) - Add a SECURITY.md file to the repository (#1122)
- Add support for custom fields on customers (#1133)
### Changed ### Changed

View file

@ -18,7 +18,8 @@
* *
* @package Controllers * @package Controllers
*/ */
class Customers extends EA_Controller { class Customers extends EA_Controller
{
/** /**
* Customers constructor. * Customers constructor.
*/ */
@ -49,10 +50,8 @@ class Customers extends EA_Controller {
$user_id = session('user_id'); $user_id = session('user_id');
if (cannot('view', PRIV_CUSTOMERS)) if (cannot('view', PRIV_CUSTOMERS)) {
{ if ($user_id) {
if ($user_id)
{
abort(403, 'Forbidden'); abort(403, 'Forbidden');
} }
@ -75,8 +74,7 @@ class Customers extends EA_Controller {
$secretary_providers = []; $secretary_providers = [];
if ($role_slug === DB_SLUG_SECRETARY) if ($role_slug === DB_SLUG_SECRETARY) {
{
$secretary = $this->secretaries_model->find($user_id); $secretary = $this->secretaries_model->find($user_id);
$secretary_providers = $secretary['providers']; $secretary_providers = $secretary['providers'];
@ -88,7 +86,7 @@ class Customers extends EA_Controller {
'date_format' => $date_format, 'date_format' => $date_format,
'time_format' => $time_format, 'time_format' => $time_format,
'timezones' => $this->timezones->to_array(), 'timezones' => $this->timezones->to_array(),
'secretary_providers' => $secretary_providers, 'secretary_providers' => $secretary_providers
]); ]);
html_vars([ html_vars([
@ -105,21 +103,45 @@ class Customers extends EA_Controller {
'require_address' => $require_address, 'require_address' => $require_address,
'require_city' => $require_city, 'require_city' => $require_city,
'require_zip_code' => $require_zip_code, 'require_zip_code' => $require_zip_code,
'available_languages' => config('available_languages'), 'available_languages' => config('available_languages')
]); ]);
$this->load->view('pages/customers'); $this->load->view('pages/customers');
} }
/**
* Find a customer.
*/
public function find()
{
try {
if (cannot('view', PRIV_CUSTOMERS)) {
abort(403, 'Forbidden');
}
$user_id = session('user_id');
$customer_id = request('customer_id');
if (!$this->permissions->has_customer_access($user_id, $customer_id)) {
abort(403, 'Forbidden');
}
$customer = $this->customers_model->find($customer_id);
json_response($customer);
} catch (Throwable $e) {
json_exception($e);
}
}
/** /**
* Filter customers by the provided keyword. * Filter customers by the provided keyword.
*/ */
public function search() public function search()
{ {
try try {
{ if (cannot('view', PRIV_CUSTOMERS)) {
if (cannot('view', PRIV_CUSTOMERS))
{
abort(403, 'Forbidden'); abort(403, 'Forbidden');
} }
@ -135,10 +157,8 @@ class Customers extends EA_Controller {
$user_id = session('user_id'); $user_id = session('user_id');
foreach ($customers as $index => &$customer) foreach ($customers as $index => &$customer) {
{ if (!$this->permissions->has_customer_access($user_id, $customer['id'])) {
if ( ! $this->permissions->has_customer_access($user_id, $customer['id']))
{
unset($customers[$index]); unset($customers[$index]);
continue; continue;
@ -146,21 +166,15 @@ class Customers extends EA_Controller {
$appointments = $this->appointments_model->get(['id_users_customer' => $customer['id']]); $appointments = $this->appointments_model->get(['id_users_customer' => $customer['id']]);
foreach ($appointments as &$appointment) foreach ($appointments as &$appointment) {
{ $this->appointments_model->load($appointment, ['service', 'provider']);
$this->appointments_model->load($appointment, [
'service',
'provider',
]);
} }
$customer['appointments'] = $appointments; $customer['appointments'] = $appointments;
} }
json_response(array_values($customers)); json_response(array_values($customers));
} } catch (Throwable $e) {
catch (Throwable $e)
{
json_exception($e); json_exception($e);
} }
} }
@ -170,15 +184,12 @@ class Customers extends EA_Controller {
*/ */
public function store() public function store()
{ {
try try {
{ if (cannot('add', PRIV_CUSTOMERS)) {
if (cannot('add', PRIV_CUSTOMERS))
{
abort(403, 'Forbidden'); abort(403, 'Forbidden');
} }
if (session('role_slug') !== DB_SLUG_ADMIN && setting('limit_customer_visibility')) if (session('role_slug') !== DB_SLUG_ADMIN && setting('limit_customer_visibility')) {
{
abort(403); abort(403);
} }
@ -196,6 +207,11 @@ class Customers extends EA_Controller {
'notes', 'notes',
'timezone', 'timezone',
'language', 'language',
'custom_field_1',
'custom_field_2',
'custom_field_3',
'custom_field_4',
'custom_field_5'
]); ]);
$customer_id = $this->customers_model->save($customer); $customer_id = $this->customers_model->save($customer);
@ -205,12 +221,10 @@ class Customers extends EA_Controller {
$this->webhooks_client->trigger(WEBHOOK_CUSTOMER_SAVE, $customer); $this->webhooks_client->trigger(WEBHOOK_CUSTOMER_SAVE, $customer);
json_response([ json_response([
'success' => TRUE, 'success' => true,
'id' => $customer_id 'id' => $customer_id
]); ]);
} } catch (Throwable $e) {
catch (Throwable $e)
{
json_exception($e); json_exception($e);
} }
} }
@ -220,10 +234,8 @@ class Customers extends EA_Controller {
*/ */
public function update() public function update()
{ {
try try {
{ if (cannot('edit', PRIV_CUSTOMERS)) {
if (cannot('edit', PRIV_CUSTOMERS))
{
abort(403, 'Forbidden'); abort(403, 'Forbidden');
} }
@ -231,8 +243,7 @@ class Customers extends EA_Controller {
$customer = request('customer'); $customer = request('customer');
if ( ! $this->permissions->has_customer_access($user_id, $customer['id'])) if (!$this->permissions->has_customer_access($user_id, $customer['id'])) {
{
abort(403, 'Forbidden'); abort(403, 'Forbidden');
} }
@ -249,6 +260,11 @@ class Customers extends EA_Controller {
'notes', 'notes',
'timezone', 'timezone',
'language', 'language',
'custom_field_1',
'custom_field_2',
'custom_field_3',
'custom_field_4',
'custom_field_5'
]); ]);
$customer_id = $this->customers_model->save($customer); $customer_id = $this->customers_model->save($customer);
@ -258,12 +274,10 @@ class Customers extends EA_Controller {
$this->webhooks_client->trigger(WEBHOOK_CUSTOMER_SAVE, $customer); $this->webhooks_client->trigger(WEBHOOK_CUSTOMER_SAVE, $customer);
json_response([ json_response([
'success' => TRUE, 'success' => true,
'id' => $customer_id 'id' => $customer_id
]); ]);
} } catch (Throwable $e) {
catch (Throwable $e)
{
json_exception($e); json_exception($e);
} }
} }
@ -273,10 +287,8 @@ class Customers extends EA_Controller {
*/ */
public function destroy() public function destroy()
{ {
try try {
{ if (cannot('delete', PRIV_CUSTOMERS)) {
if (cannot('delete', PRIV_CUSTOMERS))
{
abort(403, 'Forbidden'); abort(403, 'Forbidden');
} }
@ -284,8 +296,7 @@ class Customers extends EA_Controller {
$customer_id = request('customer_id'); $customer_id = request('customer_id');
if ( ! $this->permissions->has_customer_access($user_id, $customer_id)) if (!$this->permissions->has_customer_access($user_id, $customer_id)) {
{
abort(403, 'Forbidden'); abort(403, 'Forbidden');
} }
@ -296,42 +307,9 @@ class Customers extends EA_Controller {
$this->webhooks_client->trigger(WEBHOOK_CUSTOMER_DELETE, $customer); $this->webhooks_client->trigger(WEBHOOK_CUSTOMER_DELETE, $customer);
json_response([ json_response([
'success' => TRUE, 'success' => true
]); ]);
} } catch (Throwable $e) {
catch (Throwable $e)
{
json_exception($e);
}
}
/**
* Find a customer.
*/
public function find()
{
try
{
if (cannot('view', PRIV_CUSTOMERS))
{
abort(403, 'Forbidden');
}
$user_id = session('user_id');
$customer_id = request('customer_id');
if ( ! $this->permissions->has_customer_access($user_id, $customer_id))
{
abort(403, 'Forbidden');
}
$customer = $this->customers_model->find($customer_id);
json_response($customer);
}
catch (Throwable $e)
{
json_exception($e); json_exception($e);
} }
} }

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -446,5 +446,7 @@ $lang['blocked_periods'] = 'Blocked Periods';
$lang['blocked_period_save'] = 'Blocked Period Save'; $lang['blocked_period_save'] = 'Blocked Period Save';
$lang['blocked_period_delete'] = 'Blocked Period Delete'; $lang['blocked_period_delete'] = 'Blocked Period Delete';
$lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).'; $lang['blocked_periods_hint'] = 'Define periods of time where public bookings will be disabled for all providers (e.g. closed dates, holidays etc.).';
$lang['auxiliary_field'] = 'Auxiliary Field'; $lang['custom_field'] = 'Custom Field';
$lang['custom_fields'] = 'Custom Fields';
$lang['label'] = 'Label';
// End // End

View file

@ -0,0 +1,56 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/* ----------------------------------------------------------------------------
* Easy!Appointments - Online Appointment Scheduler
*
* @package EasyAppointments
* @author A.Tselegidis <alextselegidis@gmail.com>
* @copyright Copyright (c) Alex Tselegidis
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
* @link https://easyappointments.org
* @since v1.4.0
* ---------------------------------------------------------------------------- */
class Migration_Add_custom_fields_columns_to_users_table extends EA_Migration
{
/**
* @var int
*/
private const FIELD_NUMBER = 5;
/**
* Upgrade method.
*/
public function up()
{
for ($i = self::FIELD_NUMBER; $i > 0; $i--) {
$field_name = 'custom_field_' . $i;
if (!$this->db->field_exists($field_name, 'users')) {
$fields = [
$field_name => [
'type' => 'TEXT',
'null' => true,
'after' => 'language'
]
];
$this->dbforge->add_column('users', $fields);
}
}
}
/**
* Downgrade method.
*/
public function down()
{
for ($i = self::FIELD_NUMBER; $i > 0; $i--) {
$field_name = 'custom_fields_' . $i;
if ($this->db->field_exists($field_name, 'users')) {
$this->dbforge->drop_column('users', $field_name);
}
}
}
}

View file

@ -0,0 +1,65 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/* ----------------------------------------------------------------------------
* Easy!Appointments - Online Appointment Scheduler
*
* @package EasyAppointments
* @author A.Tselegidis <alextselegidis@gmail.com>
* @copyright Copyright (c) Alex Tselegidis
* @license https://opensource.org/licenses/GPL-3.0 - GPLv3
* @link https://easyappointments.org
* @since v1.4.0
* ---------------------------------------------------------------------------- */
class Migration_Insert_custom_field_rows_to_settings_table extends EA_Migration
{
/**
* @var int
*/
private const FIELD_NUMBER = 5;
private const SETTINGS = [
'display' => '0',
'require' => '0',
'label' => ''
];
/**
* Upgrade method.
*/
public function up()
{
for ($i = 1; $i <= self::FIELD_NUMBER; $i++) {
$field_name = 'custom_field_' . $i;
foreach (self::SETTINGS as $name => $default_value) {
$setting_name = $name . '_' . $field_name;
if (!$this->db->get_where('settings', ['name' => $setting_name])->num_rows()) {
$this->db->insert('settings', [
'name' => $setting_name,
'value' => $default_value
]);
}
}
}
}
/**
* Downgrade method.
*/
public function down()
{
for ($i = 1; $i >= self::FIELD_NUMBER; $i++) {
$field_name = 'custom_field_' . $i;
foreach (self::SETTINGS as $name => $default_value) {
$setting_name = $name . '_' . $field_name;
if ($this->db->get_where('settings', ['name' => $setting_name])->num_rows()) {
$this->db->delete('settings', ['name' => $setting_name]);
}
}
}
}
}

View file

@ -18,13 +18,14 @@
* *
* @package Models * @package Models
*/ */
class Customers_model extends EA_Model { class Customers_model extends EA_Model
{
/** /**
* @var array * @var array
*/ */
protected array $casts = [ protected array $casts = [
'id' => 'integer', 'id' => 'integer',
'id_roles' => 'integer', 'id_roles' => 'integer'
]; ];
/** /**
@ -42,7 +43,12 @@ class Customers_model extends EA_Model {
'zip' => 'zip_code', 'zip' => 'zip_code',
'timezone' => 'timezone', 'timezone' => 'timezone',
'language' => 'language', 'language' => 'language',
'notes' => 'notes', 'customField1' => 'custom_field_1',
'customField2' => 'custom_field_2',
'customField3' => 'custom_field_3',
'customField4' => 'custom_field_4',
'customField5' => 'custom_field_5',
'notes' => 'notes'
]; ];
/** /**
@ -58,17 +64,13 @@ class Customers_model extends EA_Model {
{ {
$this->validate($customer); $this->validate($customer);
if ($this->exists($customer) && empty($customer['id'])) if ($this->exists($customer) && empty($customer['id'])) {
{
$customer['id'] = $this->find_record_id($customer); $customer['id'] = $this->find_record_id($customer);
} }
if (empty($customer['id'])) if (empty($customer['id'])) {
{
return $this->insert($customer); return $this->insert($customer);
} } else {
else
{
return $this->update($customer); return $this->update($customer);
} }
} }
@ -83,13 +85,13 @@ class Customers_model extends EA_Model {
public function validate(array $customer) public function validate(array $customer)
{ {
// If a customer ID is provided then check whether the record really exists in the database. // If a customer ID is provided then check whether the record really exists in the database.
if ( ! empty($customer['id'])) if (!empty($customer['id'])) {
{
$count = $this->db->get_where('users', ['id' => $customer['id']])->num_rows(); $count = $this->db->get_where('users', ['id' => $customer['id']])->num_rows();
if ( ! $count) if (!$count) {
{ throw new InvalidArgumentException(
throw new InvalidArgumentException('The provided customer ID does not exist in the database: ' . $customer['id']); 'The provided customer ID does not exist in the database: ' . $customer['id']
);
} }
} }
@ -103,31 +105,27 @@ class Customers_model extends EA_Model {
$require_zip_code = filter_var(setting('require_zip_code'), FILTER_VALIDATE_BOOLEAN); $require_zip_code = filter_var(setting('require_zip_code'), FILTER_VALIDATE_BOOLEAN);
if ( if (
(empty($customer['first_name']) && $require_first_name) (empty($customer['first_name']) && $require_first_name) ||
|| (empty($customer['last_name']) && $require_last_name) (empty($customer['last_name']) && $require_last_name) ||
|| (empty($customer['email']) && $require_email) (empty($customer['email']) && $require_email) ||
|| (empty($customer['phone_number']) && $require_phone_number) (empty($customer['phone_number']) && $require_phone_number) ||
|| (empty($customer['address']) && $require_address) (empty($customer['address']) && $require_address) ||
|| (empty($customer['city']) && $require_city) (empty($customer['city']) && $require_city) ||
|| (empty($customer['zip_code']) && $require_zip_code) (empty($customer['zip_code']) && $require_zip_code)
) ) {
{ throw new InvalidArgumentException('Not all required fields are provided: ' . print_r($customer, true));
throw new InvalidArgumentException('Not all required fields are provided: ' . print_r($customer, TRUE));
} }
if ( ! empty($customer['email'])) if (!empty($customer['email'])) {
{
// Validate the email address. // Validate the email address.
if ( ! filter_var($customer['email'], FILTER_VALIDATE_EMAIL)) if (!filter_var($customer['email'], FILTER_VALIDATE_EMAIL)) {
{
throw new InvalidArgumentException('Invalid email address provided: ' . $customer['email']); throw new InvalidArgumentException('Invalid email address provided: ' . $customer['email']);
} }
// Make sure the email address is unique. // Make sure the email address is unique.
$customer_id = $customer['id'] ?? NULL; $customer_id = $customer['id'] ?? null;
$count = $this $count = $this->db
->db
->select() ->select()
->from('users') ->from('users')
->join('roles', 'roles.id = users.id_roles', 'inner') ->join('roles', 'roles.id = users.id_roles', 'inner')
@ -137,13 +135,123 @@ class Customers_model extends EA_Model {
->get() ->get()
->num_rows(); ->num_rows();
if ($count > 0) if ($count > 0) {
{ throw new InvalidArgumentException(
throw new InvalidArgumentException('The provided email address is already in use, please use a different one.'); 'The provided email address is already in use, please use a different one.'
);
} }
} }
} }
/**
* Get all customers that match the provided criteria.
*
* @param array|string|null $where Where conditions.
* @param int|null $limit Record limit.
* @param int|null $offset Record offset.
* @param string|null $order_by Order by.
*
* @return array Returns an array of customers.
*/
public function get(
array|string $where = null,
int $limit = null,
int $offset = null,
string $order_by = null
): array {
$role_id = $this->get_customer_role_id();
if ($where !== null) {
$this->db->where($where);
}
if ($order_by !== null) {
$this->db->order_by($order_by);
}
$customers = $this->db->get_where('users', ['id_roles' => $role_id], $limit, $offset)->result_array();
foreach ($customers as &$customer) {
$this->cast($customer);
}
return $customers;
}
/**
* Get the customer role ID.
*
* @return int Returns the role ID.
*/
public function get_customer_role_id(): int
{
$role = $this->db->get_where('roles', ['slug' => DB_SLUG_CUSTOMER])->row_array();
if (empty($role)) {
throw new RuntimeException('The customer role was not found in the database.');
}
return $role['id'];
}
/**
* Check if a particular customer record already exists in the database.
*
* @param array $customer Associative array with the customer data.
*
* @return bool Returns whether there is a record matching the provided one or not.
*
* @throws InvalidArgumentException
*/
public function exists(array $customer): bool
{
if (empty($customer['email'])) {
return false;
}
$count = $this->db
->select()
->from('users')
->join('roles', 'roles.id = users.id_roles', 'inner')
->where('users.email', $customer['email'])
->where('roles.slug', DB_SLUG_CUSTOMER)
->get()
->num_rows();
return $count > 0;
}
/**
* Find the record ID of a customer.
*
* @param array $customer Associative array with the customer data.
*
* @return int Returns the ID of the record that matches the provided argument.
*
* @throws InvalidArgumentException
*/
public function find_record_id(array $customer): int
{
if (empty($customer['email'])) {
throw new InvalidArgumentException('The customer email was not provided: ' . print_r($customer, true));
}
$customer = $this->db
->select('users.id')
->from('users')
->join('roles', 'roles.id = users.id_roles', 'inner')
->where('users.email', $customer['email'])
->where('roles.slug', DB_SLUG_CUSTOMER)
->get()
->row_array();
if (empty($customer)) {
throw new InvalidArgumentException('Could not find customer record id.');
}
return (int) $customer['id'];
}
/** /**
* Insert a new customer into the database. * Insert a new customer into the database.
* *
@ -159,8 +267,7 @@ class Customers_model extends EA_Model {
$customer['update_datetime'] = date('Y-m-d H:i:s'); $customer['update_datetime'] = date('Y-m-d H:i:s');
$customer['id_roles'] = $this->get_customer_role_id(); $customer['id_roles'] = $this->get_customer_role_id();
if ( ! $this->db->insert('users', $customer)) if (!$this->db->insert('users', $customer)) {
{
throw new RuntimeException('Could not insert customer.'); throw new RuntimeException('Could not insert customer.');
} }
@ -180,8 +287,7 @@ class Customers_model extends EA_Model {
{ {
$customer['update_datetime'] = date('Y-m-d H:i:s'); $customer['update_datetime'] = date('Y-m-d H:i:s');
if ( ! $this->db->update('users', $customer, ['id' => $customer['id']])) if (!$this->db->update('users', $customer, ['id' => $customer['id']])) {
{
throw new RuntimeException('Could not update customer.'); throw new RuntimeException('Could not update customer.');
} }
@ -211,9 +317,10 @@ class Customers_model extends EA_Model {
{ {
$customer = $this->db->get_where('users', ['id' => $customer_id])->row_array(); $customer = $this->db->get_where('users', ['id' => $customer_id])->row_array();
if ( ! $customer) if (!$customer) {
{ throw new InvalidArgumentException(
throw new InvalidArgumentException('The provided customer ID was not found in the database: ' . $customer_id); 'The provided customer ID was not found in the database: ' . $customer_id
);
} }
$this->cast($customer); $this->cast($customer);
@ -233,22 +340,21 @@ class Customers_model extends EA_Model {
*/ */
public function value(int $customer_id, string $field): mixed public function value(int $customer_id, string $field): mixed
{ {
if (empty($field)) if (empty($field)) {
{
throw new InvalidArgumentException('The field argument is cannot be empty.'); throw new InvalidArgumentException('The field argument is cannot be empty.');
} }
if (empty($customer_id)) if (empty($customer_id)) {
{
throw new InvalidArgumentException('The customer ID argument cannot be empty.'); throw new InvalidArgumentException('The customer ID argument cannot be empty.');
} }
// Check whether the customer exists. // Check whether the customer exists.
$query = $this->db->get_where('users', ['id' => $customer_id]); $query = $this->db->get_where('users', ['id' => $customer_id]);
if ( ! $query->num_rows()) if (!$query->num_rows()) {
{ throw new InvalidArgumentException(
throw new InvalidArgumentException('The provided customer ID was not found in the database: ' . $customer_id); 'The provided customer ID was not found in the database: ' . $customer_id
);
} }
// Check if the required field is part of the customer data. // Check if the required field is part of the customer data.
@ -256,128 +362,13 @@ class Customers_model extends EA_Model {
$this->cast($customer); $this->cast($customer);
if ( ! array_key_exists($field, $customer)) if (!array_key_exists($field, $customer)) {
{
throw new InvalidArgumentException('The requested field was not found in the customer data: ' . $field); throw new InvalidArgumentException('The requested field was not found in the customer data: ' . $field);
} }
return $customer[$field]; return $customer[$field];
} }
/**
* Get all customers that match the provided criteria.
*
* @param array|string|null $where Where conditions.
* @param int|null $limit Record limit.
* @param int|null $offset Record offset.
* @param string|null $order_by Order by.
*
* @return array Returns an array of customers.
*/
public function get(array|string $where = NULL, int $limit = NULL, int $offset = NULL, string $order_by = NULL): array
{
$role_id = $this->get_customer_role_id();
if ($where !== NULL)
{
$this->db->where($where);
}
if ($order_by !== NULL)
{
$this->db->order_by($order_by);
}
$customers = $this->db->get_where('users', ['id_roles' => $role_id], $limit, $offset)->result_array();
foreach ($customers as &$customer)
{
$this->cast($customer);
}
return $customers;
}
/**
* Get the customer role ID.
*
* @return int Returns the role ID.
*/
public function get_customer_role_id(): int
{
$role = $this->db->get_where('roles', ['slug' => DB_SLUG_CUSTOMER])->row_array();
if (empty($role))
{
throw new RuntimeException('The customer role was not found in the database.');
}
return $role['id'];
}
/**
* Check if a particular customer record already exists in the database.
*
* @param array $customer Associative array with the customer data.
*
* @return bool Returns whether there is a record matching the provided one or not.
*
* @throws InvalidArgumentException
*/
public function exists(array $customer): bool
{
if (empty($customer['email']))
{
return FALSE;
}
$count = $this
->db
->select()
->from('users')
->join('roles', 'roles.id = users.id_roles', 'inner')
->where('users.email', $customer['email'])
->where('roles.slug', DB_SLUG_CUSTOMER)
->get()
->num_rows();
return $count > 0;
}
/**
* Find the record ID of a customer.
*
* @param array $customer Associative array with the customer data.
*
* @return int Returns the ID of the record that matches the provided argument.
*
* @throws InvalidArgumentException
*/
public function find_record_id(array $customer): int
{
if (empty($customer['email']))
{
throw new InvalidArgumentException('The customer email was not provided: ' . print_r($customer, TRUE));
}
$customer = $this
->db
->select('users.id')
->from('users')
->join('roles', 'roles.id = users.id_roles', 'inner')
->where('users.email', $customer['email'])
->where('roles.slug', DB_SLUG_CUSTOMER)
->get()
->row_array();
if (empty($customer))
{
throw new InvalidArgumentException('Could not find customer record id.');
}
return (int)$customer['id'];
}
/** /**
* Get the query builder interface, configured for use with the users (customer-filtered) table. * Get the query builder interface, configured for use with the users (customer-filtered) table.
* *
@ -400,12 +391,11 @@ class Customers_model extends EA_Model {
* *
* @return array Returns an array of customers. * @return array Returns an array of customers.
*/ */
public function search(string $keyword, int $limit = NULL, int $offset = NULL, string $order_by = NULL): array public function search(string $keyword, int $limit = null, int $offset = null, string $order_by = null): array
{ {
$role_id = $this->get_customer_role_id(); $role_id = $this->get_customer_role_id();
$customers = $this $customers = $this->db
->db
->select() ->select()
->from('users') ->from('users')
->where('id_roles', $role_id) ->where('id_roles', $role_id)
@ -428,8 +418,7 @@ class Customers_model extends EA_Model {
->get() ->get()
->result_array(); ->result_array();
foreach ($customers as &$customer) foreach ($customers as &$customer) {
{
$this->cast($customer); $this->cast($customer);
} }
@ -457,7 +446,7 @@ class Customers_model extends EA_Model {
public function api_encode(array &$customer) public function api_encode(array &$customer)
{ {
$encoded_resource = [ $encoded_resource = [
'id' => array_key_exists('id', $customer) ? (int)$customer['id'] : NULL, 'id' => array_key_exists('id', $customer) ? (int) $customer['id'] : null,
'firstName' => $customer['first_name'], 'firstName' => $customer['first_name'],
'lastName' => $customer['last_name'], 'lastName' => $customer['last_name'],
'email' => $customer['email'], 'email' => $customer['email'],
@ -467,6 +456,11 @@ class Customers_model extends EA_Model {
'zip' => $customer['zip_code'], 'zip' => $customer['zip_code'],
'notes' => $customer['notes'], 'notes' => $customer['notes'],
'timezone' => $customer['timezone'], 'timezone' => $customer['timezone'],
'customField1' => $customer['custom_field_1'],
'customField2' => $customer['custom_field_2'],
'customField3' => $customer['custom_field_3'],
'customField4' => $customer['custom_field_4'],
'customField5' => $customer['custom_field_5']
]; ];
$customer = $encoded_resource; $customer = $encoded_resource;
@ -478,52 +472,67 @@ class Customers_model extends EA_Model {
* @param array $customer API resource. * @param array $customer API resource.
* @param array|null $base Base customer data to be overwritten with the provided values (useful for updates). * @param array|null $base Base customer data to be overwritten with the provided values (useful for updates).
*/ */
public function api_decode(array &$customer, array $base = NULL) public function api_decode(array &$customer, array $base = null)
{ {
$decoded_resource = $base ?: []; $decoded_resource = $base ?: [];
if (array_key_exists('id', $customer)) if (array_key_exists('id', $customer)) {
{
$decoded_resource['id'] = $customer['id']; $decoded_resource['id'] = $customer['id'];
} }
if (array_key_exists('firstName', $customer)) if (array_key_exists('firstName', $customer)) {
{
$decoded_resource['first_name'] = $customer['firstName']; $decoded_resource['first_name'] = $customer['firstName'];
} }
if (array_key_exists('lastName', $customer)) if (array_key_exists('lastName', $customer)) {
{
$decoded_resource['last_name'] = $customer['lastName']; $decoded_resource['last_name'] = $customer['lastName'];
} }
if (array_key_exists('email', $customer)) if (array_key_exists('email', $customer)) {
{
$decoded_resource['email'] = $customer['email']; $decoded_resource['email'] = $customer['email'];
} }
if (array_key_exists('phone', $customer)) if (array_key_exists('phone', $customer)) {
{
$decoded_resource['phone_number'] = $customer['phone']; $decoded_resource['phone_number'] = $customer['phone'];
} }
if (array_key_exists('address', $customer)) if (array_key_exists('address', $customer)) {
{
$decoded_resource['address'] = $customer['address']; $decoded_resource['address'] = $customer['address'];
} }
if (array_key_exists('city', $customer)) if (array_key_exists('city', $customer)) {
{
$decoded_resource['city'] = $customer['city']; $decoded_resource['city'] = $customer['city'];
} }
if (array_key_exists('zip', $customer)) if (array_key_exists('zip', $customer)) {
{
$decoded_resource['zip_code'] = $customer['zip']; $decoded_resource['zip_code'] = $customer['zip'];
} }
if (array_key_exists('notes', $customer)) if (array_key_exists('language', $customer)) {
{ $decoded_resource['language'] = $customer['language'];
}
if (array_key_exists('customField1', $customer)) {
$decoded_resource['custom_field_1'] = $customer['customField1'];
}
if (array_key_exists('customField2', $customer)) {
$decoded_resource['custom_field_2'] = $customer['customField2'];
}
if (array_key_exists('customField3', $customer)) {
$decoded_resource['custom_field_3'] = $customer['customField3'];
}
if (array_key_exists('customField4', $customer)) {
$decoded_resource['custom_field_4'] = $customer['customField4'];
}
if (array_key_exists('customField5', $customer)) {
$decoded_resource['custom_field_5'] = $customer['customField5'];
}
if (array_key_exists('notes', $customer)) {
$decoded_resource['notes'] = $customer['notes']; $decoded_resource['notes'] = $customer['notes'];
} }

View file

@ -43,27 +43,21 @@
<?php <?php
// Group services by category, only if there is at least one service // Group services by category, only if there is at least one service
// with a parent category. // with a parent category.
$has_category = FALSE; $has_category = false;
foreach ($available_services as $service) foreach ($available_services as $service) {
{ if (!empty($service['category_id'])) {
if ( ! empty($service['category_id'])) $has_category = true;
{
$has_category = TRUE;
break; break;
} }
} }
if ($has_category) if ($has_category) {
{
$grouped_services = []; $grouped_services = [];
foreach ($available_services as $service) foreach ($available_services as $service) {
{ if (!empty($service['category_id'])) {
if ( ! empty($service['category_id'])) if (!isset($grouped_services[$service['category_name']])) {
{
if ( ! isset($grouped_services[$service['category_name']]))
{
$grouped_services[$service['category_name']] = []; $grouped_services[$service['category_name']] = [];
} }
@ -75,40 +69,39 @@
// another iteration only for the uncategorized services. // another iteration only for the uncategorized services.
$grouped_services['uncategorized'] = []; $grouped_services['uncategorized'] = [];
foreach ($available_services as $service) foreach ($available_services as $service) {
{ if ($service['category_id'] == null) {
if ($service['category_id'] == NULL)
{
$grouped_services['uncategorized'][] = $service; $grouped_services['uncategorized'][] = $service;
} }
} }
foreach ($grouped_services as $key => $group) foreach ($grouped_services as $key => $group) {
{ $group_label =
$group_label = $key !== 'uncategorized' $key !== 'uncategorized'
? e($group[0]['category_name']) ? e($group[0]['category_name'])
: 'Uncategorized'; : 'Uncategorized';
if (count($group) > 0) if (count($group) > 0) {
{
echo '<optgroup label="' . $group_label . '">'; echo '<optgroup label="' . $group_label . '">';
foreach ($group as $service) foreach ($group as $service) {
{ echo '<option value="' .
echo '<option value="' . $service['id'] . '">' $service['id'] .
. e($service['name']) . '</option>'; '">' .
e($service['name']) .
'</option>';
} }
echo '</optgroup>'; echo '</optgroup>';
} }
} }
} } else {
else foreach ($available_services as $service) {
{ echo '<option value="' .
foreach ($available_services as $service) $service['id'] .
{ '">' .
echo '<option value="' . $service['id'] . '">' e($service['name']) .
. e($service['name']) . '</option>'; '</option>';
} }
} }
?> ?>
@ -124,7 +117,7 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<?php component('color_selection', ['attributes' => 'id="appointment-color"']) ?> <?php component('color_selection', ['attributes' => 'id="appointment-color"']); ?>
</div> </div>
<div class="mb-3"> <div class="mb-3">
@ -143,7 +136,7 @@
<option value="<?= e($appointment_status_option) ?>"> <option value="<?= e($appointment_status_option) ?>">
<?= e($appointment_status_option) ?> <?= e($appointment_status_option) ?>
</option> </option>
<?php endforeach ?> <?php endforeach; ?>
</select> </select>
</div> </div>
</div> </div>
@ -228,7 +221,7 @@
<?= lang('first_name') ?> <?= lang('first_name') ?>
<?php if ($require_first_name): ?> <?php if ($require_first_name): ?>
<span class="text-danger">*</span> <span class="text-danger">*</span>
<?php endif ?> <?php endif; ?>
</label> </label>
<input type="text" id="first-name" <input type="text" id="first-name"
class="<?= $require_first_name ? 'required' : '' ?> form-control" class="<?= $require_first_name ? 'required' : '' ?> form-control"
@ -240,7 +233,7 @@
<?= lang('last_name') ?> <?= lang('last_name') ?>
<?php if ($require_last_name): ?> <?php if ($require_last_name): ?>
<span class="text-danger">*</span> <span class="text-danger">*</span>
<?php endif ?> <?php endif; ?>
</label> </label>
<input type="text" id="last-name" <input type="text" id="last-name"
class="<?= $require_last_name ? 'required' : '' ?> form-control" class="<?= $require_last_name ? 'required' : '' ?> form-control"
@ -252,7 +245,7 @@
<?= lang('email') ?> <?= lang('email') ?>
<?php if ($require_email): ?> <?php if ($require_email): ?>
<span class="text-danger">*</span> <span class="text-danger">*</span>
<?php endif ?> <?php endif; ?>
</label> </label>
<input type="text" id="email" <input type="text" id="email"
class="<?= $require_email ? 'required' : '' ?> form-control" class="<?= $require_email ? 'required' : '' ?> form-control"
@ -264,7 +257,7 @@
<?= lang('phone_number') ?> <?= lang('phone_number') ?>
<?php if ($require_phone_number): ?> <?php if ($require_phone_number): ?>
<span class="text-danger">*</span> <span class="text-danger">*</span>
<?php endif ?> <?php endif; ?>
</label> </label>
<input type="text" id="phone-number" maxlength="60" <input type="text" id="phone-number" maxlength="60"
class="<?= $require_phone_number ? 'required' : '' ?> form-control"/> class="<?= $require_phone_number ? 'required' : '' ?> form-control"/>
@ -280,7 +273,7 @@
<option value="<?= $available_language ?>"> <option value="<?= $available_language ?>">
<?= ucfirst($available_language) ?> <?= ucfirst($available_language) ?>
</option> </option>
<?php endforeach ?> <?php endforeach; ?>
</select> </select>
</div> </div>
</div> </div>
@ -290,7 +283,7 @@
<?= lang('address') ?> <?= lang('address') ?>
<?php if ($require_address): ?> <?php if ($require_address): ?>
<span class="text-danger">*</span> <span class="text-danger">*</span>
<?php endif ?> <?php endif; ?>
</label> </label>
<input type="text" id="address" <input type="text" id="address"
class="<?= $require_address ? 'required' : '' ?> form-control" class="<?= $require_address ? 'required' : '' ?> form-control"
@ -302,7 +295,7 @@
<?= lang('city') ?> <?= lang('city') ?>
<?php if ($require_city): ?> <?php if ($require_city): ?>
<span class="text-danger">*</span> <span class="text-danger">*</span>
<?php endif ?> <?php endif; ?>
</label> </label>
<input type="text" id="city" <input type="text" id="city"
class="<?= $require_city ? 'required' : '' ?> form-control" class="<?= $require_city ? 'required' : '' ?> form-control"
@ -314,7 +307,7 @@
<?= lang('zip_code') ?> <?= lang('zip_code') ?>
<?php if ($require_zip_code): ?> <?php if ($require_zip_code): ?>
<span class="text-danger">*</span> <span class="text-danger">*</span>
<?php endif ?> <?php endif; ?>
</label> </label>
<input type="text" id="zip-code" <input type="text" id="zip-code"
class="<?= $require_zip_code ? 'required' : '' ?> form-control" class="<?= $require_zip_code ? 'required' : '' ?> form-control"
@ -329,7 +322,7 @@
<?php component('timezone_dropdown', [ <?php component('timezone_dropdown', [
'attributes' => 'id="timezone" class="form-control required"', 'attributes' => 'id="timezone" class="form-control required"',
'grouped_timezones' => vars('grouped_timezones') 'grouped_timezones' => vars('grouped_timezones')
]) ?> ]); ?>
</div> </div>
<div class="mb-3"> <div class="mb-3">
@ -337,12 +330,16 @@
<?= lang('notes') ?> <?= lang('notes') ?>
<?php if ($require_notes): ?> <?php if ($require_notes): ?>
<span class="text-danger">*</span> <span class="text-danger">*</span>
<?php endif ?> <?php endif; ?>
</label> </label>
<textarea id="customer-notes" rows="2" <textarea id="customer-notes" rows="2"
class="<?= $require_notes ? 'required' : '' ?> form-control"></textarea> class="<?= $require_notes ? 'required' : '' ?> form-control"></textarea>
</div> </div>
</div> </div>
<div class="mb-3">
<?php component('custom_fields'); ?>
</div>
</div> </div>
</fieldset> </fieldset>
</form> </form>
@ -361,8 +358,8 @@
</div> </div>
</div> </div>
<?php section('scripts') ?> <?php section('scripts'); ?>
<script src="<?= asset_url('assets/js/components/appointments_modal.js') ?>"></script> <script src="<?= asset_url('assets/js/components/appointments_modal.js') ?>"></script>
<?php end_section('scripts') ?> <?php end_section('scripts'); ?>

View file

@ -131,6 +131,10 @@
</div> </div>
<?php endif; ?> <?php endif; ?>
</div> </div>
<div class="mb-3">
<?php component('custom_fields'); ?>
</div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,24 @@
<?php
/**
* Local variables.
*
* @var bool $disabled (false)
*/
$disabled = $disabled ?? false; ?>
<?php for ($i = 1; $i <= 5; $i++): ?>
<?php if (setting('display_custom_field_' . $i)): ?>
<div class="mb-3">
<label for="custom-field-<?= $i ?>" class="form-label">
<?= setting('label_custom_field_' . $i) ?: lang('custom_field') . ' #' . $i ?>
<?php if (vars('require_custom_field_' . $i)): ?>
<span class="text-danger" hidden>*</span>
<?php endif; ?>
</label>
<input type="text" id="custom-field-<?= $i ?>"
class="<?= vars('require_custom_field_' . $i) ? 'required' : '' ?> form-control"
maxlength="120" <?= $disabled ? 'disabled' : '' ?>/>
</div>
<?php endif; ?>
<?php endfor; ?>

View file

@ -246,6 +246,50 @@
</div> </div>
</div> </div>
<h5 class="text-black-50 mb-3 fw-light">
<?= lang('custom_fields') ?>
</h5>
<div class="row mb-5 fields-row">
<?php for ($i = 1; $i <= 5; $i++): ?>
<div class="col-sm-6">
<div class="form-group mb-5">
<label for="first-name" class="form-label">
<?= lang('custom_field') ?> #<?= $i ?>
<span class="text-danger">*</span>
</label>
<input type="text" id="custom-field-<?= $i ?>" class="form-control mb-2"
placeholder="<?= lang('label') ?>"
data-field="label_custom_field_<?= $i ?>"
aria-label="label"
/>
<div class="d-flex">
<div class="form-check form-switch me-4">
<input class="form-check-input display-switch" type="checkbox"
id="display-custom-field-<?= $i ?>"
data-field="display_custom_field_<?= $i ?>">
<label class="form-check-label" for="display-custom-field-<?= $i ?>">
<?= lang('display') ?>
</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input require-switch" type="checkbox"
id="require-custom-field-<?= $i ?>"
data-field="require_custom_field_<?= $i ?>">
<label class="form-check-label" for="require-custom-field-<?= $i ?>">
<?= lang('require') ?>
</label>
</div>
</div>
</div>
</div>
<?php endfor; ?>
</div>
<h5 class="text-black-50 mb-3 fw-light"> <h5 class="text-black-50 mb-3 fw-light">
<?= lang('options') ?> <?= lang('options') ?>
</h5> </h5>

View file

@ -181,7 +181,9 @@
]); ?> ]); ?>
</div> </div>
<?php component('auxiliary_fields'); ?> <?php component('custom_fields', [
'disabled' => true
]); ?>
<div class="mb-3"> <div class="mb-3">
<label class="form-label" for="notes"> <label class="form-label" for="notes">

View file

@ -46,6 +46,11 @@ App.Components.AppointmentsModal = (function () {
const $insertAppointment = $('#insert-appointment'); const $insertAppointment = $('#insert-appointment');
const $existingCustomersList = $('#existing-customers-list'); const $existingCustomersList = $('#existing-customers-list');
const $newCustomer = $('#new-customer'); const $newCustomer = $('#new-customer');
const $customField1 = $('#custom-field-1');
const $customField2 = $('#custom-field-2');
const $customField3 = $('#custom-field-3');
const $customField4 = $('#custom-field-4');
const $customField5 = $('#custom-field-5');
/** /**
* Update the displayed timezone. * Update the displayed timezone.
@ -110,7 +115,12 @@ App.Components.AppointmentsModal = (function () {
zip_code: $zipCode.val(), zip_code: $zipCode.val(),
language: $language.val(), language: $language.val(),
timezone: $timezone.val(), timezone: $timezone.val(),
notes: $customerNotes.val() notes: $customerNotes.val(),
custom_field_1: $customField1.val(),
custom_field_2: $customField2.val(),
custom_field_3: $customField3.val(),
custom_field_4: $customField4.val(),
custom_field_5: $customField5.val()
}; };
if ($customerId.val() !== '') { if ($customerId.val() !== '') {
@ -196,7 +206,7 @@ App.Components.AppointmentsModal = (function () {
$startDatetime[0]._flatpickr.setDate(startMoment.toDate()); $startDatetime[0]._flatpickr.setDate(startMoment.toDate());
$endDatetime[0]._flatpickr.setDate(startMoment.add(duration, 'minutes').toDate()); $endDatetime[0]._flatpickr.setDate(startMoment.add(duration, 'minutes').toDate());
// Display modal form. // Display modal form.
$appointmentsModal.find('.modal-header h3').text(lang('new_appointment_title')); $appointmentsModal.find('.modal-header h3').text(lang('new_appointment_title'));
@ -217,7 +227,8 @@ App.Components.AppointmentsModal = (function () {
vars('customers').forEach((customer) => { vars('customers').forEach((customer) => {
$('<div/>', { $('<div/>', {
'data-id': customer.id, 'data-id': customer.id,
'text': (customer.first_name || '[No First Name]') + ' ' + (customer.last_name || '[No Last Name]') 'text':
(customer.first_name || '[No First Name]') + ' ' + (customer.last_name || '[No Last Name]')
}).appendTo($existingCustomersList); }).appendTo($existingCustomersList);
}); });
} else { } else {
@ -249,6 +260,11 @@ App.Components.AppointmentsModal = (function () {
$language.val(customer.language); $language.val(customer.language);
$timezone.val(customer.timezone); $timezone.val(customer.timezone);
$customerNotes.val(customer.notes); $customerNotes.val(customer.notes);
$customField1.val(customer.custom_field_1);
$customField2.val(customer.custom_field_2);
$customField3.val(customer.custom_field_3);
$customField4.val(customer.custom_field_4);
$customField5.val(customer.custom_field_5);
} }
$selectCustomer.trigger('click'); // Hide the list. $selectCustomer.trigger('click'); // Hide the list.
@ -278,7 +294,10 @@ App.Components.AppointmentsModal = (function () {
response.forEach((customer) => { response.forEach((customer) => {
$('<div/>', { $('<div/>', {
'data-id': customer.id, 'data-id': customer.id,
'text': (customer.first_name || '[No First Name]') + ' ' + (customer.last_name || '[No Last Name]') 'text':
(customer.first_name || '[No First Name]') +
' ' +
(customer.last_name || '[No Last Name]')
}).appendTo($existingCustomersList); }).appendTo($existingCustomersList);
// Verify if this customer is on the old customer list. // Verify if this customer is on the old customer list.
@ -309,7 +328,10 @@ App.Components.AppointmentsModal = (function () {
) { ) {
$('<div/>', { $('<div/>', {
'data-id': customer.id, 'data-id': customer.id,
'text': (customer.first_name || '[No First Name]') + ' ' + (customer.last_name || '[No Last Name]') 'text':
(customer.first_name || '[No First Name]') +
' ' +
(customer.last_name || '[No Last Name]')
}).appendTo($existingCustomersList); }).appendTo($existingCustomersList);
} }
}); });
@ -328,8 +350,8 @@ App.Components.AppointmentsModal = (function () {
*/ */
$selectService.on('change', () => { $selectService.on('change', () => {
const serviceId = $selectService.val(); const serviceId = $selectService.val();
const providerId = $selectProvider.val(); const providerId = $selectProvider.val();
$selectProvider.empty(); $selectProvider.empty();
@ -341,7 +363,7 @@ App.Components.AppointmentsModal = (function () {
const duration = service ? service.duration : 60; const duration = service ? service.duration : 60;
const start = $startDatetime[0]._flatpickr.selectedDates[0]; const start = $startDatetime[0]._flatpickr.selectedDates[0];
$endDatetime[0]._flatpickr.setDate( new Date(start.getTime() + duration * 60000)); $endDatetime[0]._flatpickr.setDate(new Date(start.getTime() + duration * 60000));
// Update the providers select box. // Update the providers select box.
@ -366,7 +388,7 @@ App.Components.AppointmentsModal = (function () {
$selectProvider.append(new Option(provider.first_name + ' ' + provider.last_name, provider.id)); $selectProvider.append(new Option(provider.first_name + ' ' + provider.last_name, provider.id));
} }
}); });
if ($selectProvider.find(`option[value="${providerId}"]`).length) { if ($selectProvider.find(`option[value="${providerId}"]`).length) {
$selectProvider.val(providerId); $selectProvider.val(providerId);
} }
@ -395,6 +417,11 @@ App.Components.AppointmentsModal = (function () {
$language.val('english'); $language.val('english');
$timezone.val('UTC'); $timezone.val('UTC');
$customerNotes.val(''); $customerNotes.val('');
$customField1.val('');
$customField2.val('');
$customField3.val('');
$customField4.val('');
$customField5.val('');
}); });
} }
@ -464,14 +491,14 @@ App.Components.AppointmentsModal = (function () {
); );
const start = $startDatetime[0]._flatpickr.selectedDates[0]; const start = $startDatetime[0]._flatpickr.selectedDates[0];
$endDatetime[0]._flatpickr.setDate( new Date(start.getTime() + service.duration * 60000)); $endDatetime[0]._flatpickr.setDate(new Date(start.getTime() + service.duration * 60000));
} }
}); });
$startDatetime[0]._flatpickr.setDate( startDatetime); $startDatetime[0]._flatpickr.setDate(startDatetime);
App.Utils.UI.initializeDatetimepicker($endDatetime); App.Utils.UI.initializeDatetimepicker($endDatetime);
$endDatetime[0]._flatpickr.setDate( endDatetime); $endDatetime[0]._flatpickr.setDate(endDatetime);
} }
/** /**
@ -502,7 +529,10 @@ App.Components.AppointmentsModal = (function () {
} }
// Check email address. // Check email address.
if ($appointmentsModal.find('#email').val() && !App.Utils.Validation.email($appointmentsModal.find('#email').val())) { if (
$appointmentsModal.find('#email').val() &&
!App.Utils.Validation.email($appointmentsModal.find('#email').val())
) {
$appointmentsModal.find('#email').addClass('is-invalid'); $appointmentsModal.find('#email').addClass('is-invalid');
throw new Error(lang('invalid_email')); throw new Error(lang('invalid_email'));
} }

View file

@ -27,9 +27,15 @@ App.Pages.Customers = (function () {
const $zipCode = $('#zip-code'); const $zipCode = $('#zip-code');
const $timezone = $('#timezone'); const $timezone = $('#timezone');
const $language = $('#language'); const $language = $('#language');
const $customField1 = $('#custom-field-1');
const $customField2 = $('#custom-field-2');
const $customField3 = $('#custom-field-3');
const $customField4 = $('#custom-field-4');
const $customField5 = $('#custom-field-5');
const $notes = $('#notes'); const $notes = $('#notes');
const $formMessage = $('#form-message'); const $formMessage = $('#form-message');
const $customerAppointments = $('#customer-appointments'); const $customerAppointments = $('#customer-appointments');
let filterResults = {}; let filterResults = {};
let filterLimit = 20; let filterLimit = 20;
@ -124,7 +130,12 @@ App.Pages.Customers = (function () {
zip_code: $zipCode.val(), zip_code: $zipCode.val(),
notes: $notes.val(), notes: $notes.val(),
timezone: $timezone.val(), timezone: $timezone.val(),
language: $language.val() || 'english' language: $language.val() || 'english',
custom_field_1: $customField1.val(),
custom_field_2: $customField2.val(),
custom_field_3: $customField3.val(),
custom_field_4: $customField4.val(),
custom_field_5: $customField5.val()
}; };
if ($id.val()) { if ($id.val()) {
@ -276,6 +287,11 @@ App.Pages.Customers = (function () {
$notes.val(customer.notes); $notes.val(customer.notes);
$timezone.val(customer.timezone); $timezone.val(customer.timezone);
$language.val(customer.language || 'english'); $language.val(customer.language || 'english');
$customField1.val(customer.custom_field_1);
$customField2.val(customer.custom_field_2);
$customField3.val(customer.custom_field_3);
$customField4.val(customer.custom_field_4);
$customField5.val(customer.custom_field_5);
$customerAppointments.empty(); $customerAppointments.empty();

View file

@ -158,6 +158,11 @@ App.Utils.CalendarDefaultView = (function () {
$appointmentsModal.find('#appointment-status').val(appointment.status); $appointmentsModal.find('#appointment-status').val(appointment.status);
$appointmentsModal.find('#appointment-notes').val(appointment.notes); $appointmentsModal.find('#appointment-notes').val(appointment.notes);
$appointmentsModal.find('#customer-notes').val(customer.notes); $appointmentsModal.find('#customer-notes').val(customer.notes);
$appointmentsModal.find('#custom-field-1').val(customer.custom_field_1);
$appointmentsModal.find('#custom-field-2').val(customer.custom_field_2);
$appointmentsModal.find('#custom-field-3').val(customer.custom_field_3);
$appointmentsModal.find('#custom-field-4').val(customer.custom_field_4);
$appointmentsModal.find('#custom-field-5').val(customer.custom_field_5);
App.Components.ColorSelection.setColor( App.Components.ColorSelection.setColor(
$appointmentsModal.find('#appointment-color'), $appointmentsModal.find('#appointment-color'),
@ -287,7 +292,10 @@ App.Utils.CalendarDefaultView = (function () {
*/ */
$selectFilterItem.on('change', () => { $selectFilterItem.on('change', () => {
// If current value is service, then the sync buttons must be disabled. // If current value is service, then the sync buttons must be disabled.
if ($selectFilterItem.find('option:selected').attr('type') === FILTER_TYPE_SERVICE || $selectFilterItem.val() === 'all') { if (
$selectFilterItem.find('option:selected').attr('type') === FILTER_TYPE_SERVICE ||
$selectFilterItem.val() === 'all'
) {
$('#google-sync, #enable-sync, #insert-appointment, #insert-dropdown').prop('disabled', true); $('#google-sync, #enable-sync, #insert-appointment, #insert-dropdown').prop('disabled', true);
fullCalendar.setOption('selectable', false); fullCalendar.setOption('selectable', false);
fullCalendar.setOption('editable', false); fullCalendar.setOption('editable', false);
@ -317,7 +325,9 @@ App.Utils.CalendarDefaultView = (function () {
$('#google-sync').prop('disabled', true); $('#google-sync').prop('disabled', true);
} }
$('#insert-working-plan-exception').toggle(providerId !== App.Utils.CalendarDefaultView.FILTER_TYPE_ALL); $('#insert-working-plan-exception').toggle(
providerId !== App.Utils.CalendarDefaultView.FILTER_TYPE_ALL
);
} }
$reloadAppointments.trigger('click'); $reloadAppointments.trigger('click');
@ -382,10 +392,10 @@ App.Utils.CalendarDefaultView = (function () {
$target.hasClass('fc-custom') && vars('privileges').appointments.edit === true ? '' : 'd-none'; $target.hasClass('fc-custom') && vars('privileges').appointments.edit === true ? '' : 'd-none';
displayDelete = displayDelete =
$target.hasClass('fc-custom') && vars('privileges').appointments.delete === true ? 'me-2' : 'd-none'; // Same value at the time. $target.hasClass('fc-custom') && vars('privileges').appointments.delete === true ? 'me-2' : 'd-none'; // Same value at the time.
let startDateTimeObject = info.event.start; let startDateTimeObject = info.event.start;
let endDateTimeObject = info.event.end || info.event.start; let endDateTimeObject = info.event.end || info.event.start;
if (info.event.extendedProps.data) { if (info.event.extendedProps.data) {
startDateTimeObject = new Date(info.event.extendedProps.data.start_datetime); startDateTimeObject = new Date(info.event.extendedProps.data.start_datetime);
endDateTimeObject = new Date(info.event.extendedProps.data.end_datetime); endDateTimeObject = new Date(info.event.extendedProps.data.end_datetime);
@ -496,11 +506,14 @@ App.Utils.CalendarDefaultView = (function () {
'text': lang('start') 'text': lang('start')
}), }),
$('<span/>', { $('<span/>', {
'text': startTime ? App.Utils.Date.format(`${date} ${startTime}`, 'text': startTime
vars('date_format'), ? App.Utils.Date.format(
vars('time_format'), `${date} ${startTime}`,
true vars('date_format'),
) : '-' vars('time_format'),
true
)
: '-'
}), }),
$('<br/>'), $('<br/>'),
@ -509,11 +522,14 @@ App.Utils.CalendarDefaultView = (function () {
'text': lang('end') 'text': lang('end')
}), }),
$('<span/>', { $('<span/>', {
'text': endTime ? App.Utils.Date.format(`${date} ${endTime}`, 'text': endTime
vars('date_format'), ? App.Utils.Date.format(
vars('time_format'), `${date} ${endTime}`,
true vars('date_format'),
) : '-' vars('time_format'),
true
)
: '-'
}), }),
$('<br/>'), $('<br/>'),
@ -626,7 +642,7 @@ App.Utils.CalendarDefaultView = (function () {
'text': lang('status') 'text': lang('status')
}), }),
$('<span/>', { $('<span/>', {
'text': info.event.extendedProps.data.status || '-', 'text': info.event.extendedProps.data.status || '-'
}), }),
$('<br/>'), $('<br/>'),
@ -1025,8 +1041,7 @@ App.Utils.CalendarDefaultView = (function () {
return; return;
} }
const isProviderDisplayed = const isProviderDisplayed = $selectFilterItem.find('option:selected').attr('type') === FILTER_TYPE_PROVIDER;
$selectFilterItem.find('option:selected').attr('type') === FILTER_TYPE_PROVIDER;
const buttons = [ const buttons = [
{ {
@ -1059,8 +1074,7 @@ App.Utils.CalendarDefaultView = (function () {
if (isProviderDisplayed) { if (isProviderDisplayed) {
const provider = vars('available_providers').find( const provider = vars('available_providers').find(
(availableProvider) => (availableProvider) => Number(availableProvider.id) === Number($selectFilterItem.val())
Number(availableProvider.id) === Number($selectFilterItem.val())
); );
if (provider) { if (provider) {
@ -1090,8 +1104,7 @@ App.Utils.CalendarDefaultView = (function () {
$appointmentsModal.find('#select-provider').trigger('change'); $appointmentsModal.find('#select-provider').trigger('change');
} else { } else {
service = vars('available_services').find( service = vars('available_services').find(
(availableService) => (availableService) => Number(availableService.id) === Number($selectFilterItem.val())
Number(availableService.id) === Number($selectFilterItem.val())
); );
if (service) { if (service) {
@ -1182,9 +1195,7 @@ App.Utils.CalendarDefaultView = (function () {
// Add appointments to calendar. // Add appointments to calendar.
response.appointments.forEach((appointment) => { response.appointments.forEach((appointment) => {
const title = [ const title = [appointment.service.name];
appointment.service.name
];
const customerInfo = []; const customerInfo = [];
@ -1236,7 +1247,7 @@ App.Utils.CalendarDefaultView = (function () {
calendarEventSource.push(unavailabilityEvent); calendarEventSource.push(unavailabilityEvent);
}); });
response.blocked_periods.forEach(blockedPeriod => { response.blocked_periods.forEach((blockedPeriod) => {
const blockedPeriodEvent = { const blockedPeriodEvent = {
title: blockedPeriod.name, title: blockedPeriod.name,
start: moment(blockedPeriod.start_datetime).toDate(), start: moment(blockedPeriod.start_datetime).toDate(),
@ -1263,7 +1274,9 @@ App.Utils.CalendarDefaultView = (function () {
(availableProvider) => Number(availableProvider.id) === Number(recordId) (availableProvider) => Number(availableProvider.id) === Number(recordId)
); );
const workingPlan = JSON.parse(provider ? provider.settings.working_plan : vars('company_working_plan')); const workingPlan = JSON.parse(
provider ? provider.settings.working_plan : vars('company_working_plan')
);
const workingPlanExceptions = JSON.parse(provider ? provider.settings.working_plan_exceptions : '{}'); const workingPlanExceptions = JSON.parse(provider ? provider.settings.working_plan_exceptions : '{}');
let unavailabilityEvent; let unavailabilityEvent;
let breakStart; let breakStart;
@ -1304,9 +1317,7 @@ App.Utils.CalendarDefaultView = (function () {
workingPlanExceptionEvent = { workingPlanExceptionEvent = {
title: lang('working_plan_exception'), title: lang('working_plan_exception'),
start: moment(workingPlanExceptionStart, 'YYYY-MM-DD HH:mm', true).toDate(), start: moment(workingPlanExceptionStart, 'YYYY-MM-DD HH:mm', true).toDate(),
end: moment(workingPlanExceptionEnd, 'YYYY-MM-DD HH:mm', true) end: moment(workingPlanExceptionEnd, 'YYYY-MM-DD HH:mm', true).add(1, 'day').toDate(),
.add(1, 'day')
.toDate(),
allDay: true, allDay: true,
color: '#879DB4', color: '#879DB4',
editable: false, editable: false,
@ -1352,10 +1363,7 @@ App.Utils.CalendarDefaultView = (function () {
title: lang('not_working'), title: lang('not_working'),
start: calendarDate.clone().toDate(), start: calendarDate.clone().toDate(),
end: moment( end: moment(
calendarDate.format('YYYY-MM-DD') + calendarDate.format('YYYY-MM-DD') + ' ' + sortedWorkingPlan[weekdayName].start + ':00'
' ' +
sortedWorkingPlan[weekdayName].start +
':00'
).toDate(), ).toDate(),
allDay: false, allDay: false,
color: '#BEBEBE', color: '#BEBEBE',
@ -1376,10 +1384,7 @@ App.Utils.CalendarDefaultView = (function () {
unavailabilityEvent = { unavailabilityEvent = {
title: lang('not_working'), title: lang('not_working'),
start: moment( start: moment(
calendarDate.format('YYYY-MM-DD') + calendarDate.format('YYYY-MM-DD') + ' ' + sortedWorkingPlan[weekdayName].end + ':00'
' ' +
sortedWorkingPlan[weekdayName].end +
':00'
).toDate(), ).toDate(),
end: calendarDate.clone().add(1, 'day').toDate(), end: calendarDate.clone().add(1, 'day').toDate(),
allDay: false, allDay: false,
@ -1405,9 +1410,7 @@ App.Utils.CalendarDefaultView = (function () {
const unavailabilityEvent = { const unavailabilityEvent = {
title: lang('break'), title: lang('break'),
start: moment( start: moment(calendarDate.format('YYYY-MM-DD') + ' ' + breakPeriod.start).toDate(),
calendarDate.format('YYYY-MM-DD') + ' ' + breakPeriod.start
).toDate(),
end: moment(calendarDate.format('YYYY-MM-DD') + ' ' + breakPeriod.end).toDate(), end: moment(calendarDate.format('YYYY-MM-DD') + ' ' + breakPeriod.end).toDate(),
allDay: false, allDay: false,
color: '#BEBEBE', color: '#BEBEBE',
@ -1420,7 +1423,6 @@ App.Utils.CalendarDefaultView = (function () {
calendarDate.add(1, 'day'); calendarDate.add(1, 'day');
} }
}) })
.always(() => { .always(() => {
$('#loading').css('visibility', ''); $('#loading').css('visibility', '');
@ -1562,7 +1564,10 @@ App.Utils.CalendarDefaultView = (function () {
const localSelectFilterItemValue = window.localStorage.getItem('EasyAppointments.SelectFilterItem'); const localSelectFilterItemValue = window.localStorage.getItem('EasyAppointments.SelectFilterItem');
if (localSelectFilterItemValue && $selectFilterItem.find(`option[value="${localSelectFilterItemValue}"]`).length) { if (
localSelectFilterItemValue &&
$selectFilterItem.find(`option[value="${localSelectFilterItemValue}"]`).length
) {
$selectFilterItem.val(localSelectFilterItemValue).trigger('change'); $selectFilterItem.val(localSelectFilterItemValue).trigger('change');
} else { } else {
$reloadAppointments.trigger('click'); $reloadAppointments.trigger('click');
@ -1601,6 +1606,11 @@ App.Utils.CalendarDefaultView = (function () {
$appointmentsModal.find('#appointment-status').val(appointment.status); $appointmentsModal.find('#appointment-status').val(appointment.status);
$appointmentsModal.find('#appointment-notes').val(appointment.notes); $appointmentsModal.find('#appointment-notes').val(appointment.notes);
$appointmentsModal.find('#customer-notes').val(customer.notes); $appointmentsModal.find('#customer-notes').val(customer.notes);
$appointmentsModal.find('#custom-field-1').val(customer.custom_field_1);
$appointmentsModal.find('#custom-field-2').val(customer.custom_field_2);
$appointmentsModal.find('#custom-field-3').val(customer.custom_field_3);
$appointmentsModal.find('#custom-field-4').val(customer.custom_field_4);
$appointmentsModal.find('#custom-field-5').val(customer.custom_field_5);
App.Components.ColorSelection.setColor($appointmentsModal.find('#appointment-color'), appointment.color); App.Components.ColorSelection.setColor($appointmentsModal.find('#appointment-color'), appointment.color);

View file

@ -116,7 +116,7 @@ App.Utils.CalendarTableView = (function () {
createUnavailabilities($providerColumn, response.unavailabilities); createUnavailabilities($providerColumn, response.unavailabilities);
// Add the blocked periods to the column. // Add the blocked periods to the column.
createBlockedPeriods($providerColumn, response.blocked_periods); createBlockedPeriods($providerColumn, response.blocked_periods);
// Add the provider breaks to the column. // Add the provider breaks to the column.
const workingPlan = JSON.parse(provider.settings.working_plan); const workingPlan = JSON.parse(provider.settings.working_plan);
@ -232,6 +232,11 @@ App.Utils.CalendarTableView = (function () {
$appointmentsModal.find('#appointment-status').val(appointment.status); $appointmentsModal.find('#appointment-status').val(appointment.status);
$appointmentsModal.find('#appointment-notes').val(appointment.notes); $appointmentsModal.find('#appointment-notes').val(appointment.notes);
$appointmentsModal.find('#customer-notes').val(customer.notes); $appointmentsModal.find('#customer-notes').val(customer.notes);
$appointmentsModal.find('#custom-field-1').val(customer.custom_field_1);
$appointmentsModal.find('#custom-field-2').val(customer.custom_field_2);
$appointmentsModal.find('#custom-field-3').val(customer.custom_field_3);
$appointmentsModal.find('#custom-field-4').val(customer.custom_field_4);
$appointmentsModal.find('#custom-field-5').val(customer.custom_field_5);
App.Components.ColorSelection.setColor( App.Components.ColorSelection.setColor(
$appointmentsModal.find('#appointment-color'), $appointmentsModal.find('#appointment-color'),
@ -387,7 +392,9 @@ App.Utils.CalendarTableView = (function () {
App.Utils.UI.initializeDatepicker($calendarHeader.find('.select-date'), { App.Utils.UI.initializeDatepicker($calendarHeader.find('.select-date'), {
onChange(selectedDates) { onChange(selectedDates) {
const startDate = selectedDates[0]; const startDate = selectedDates[0];
const endDate = moment(startDate).add(parseInt($selectFilterItem.val()) - 1, 'days').toDate(); const endDate = moment(startDate)
.add(parseInt($selectFilterItem.val()) - 1, 'days')
.toDate();
createView(startDate, endDate); createView(startDate, endDate);
} }
}); });
@ -853,7 +860,7 @@ App.Utils.CalendarTableView = (function () {
return; return;
} }
const filterServiceIds = $filterService.val().map(serviceId => Number(serviceId)); const filterServiceIds = $filterService.val().map((serviceId) => Number(serviceId));
appointments = appointments.filter( appointments = appointments.filter(
(appointment) => !filterServiceIds.length || filterServiceIds.includes(appointment.id_services) (appointment) => !filterServiceIds.length || filterServiceIds.includes(appointment.id_services)
@ -868,9 +875,7 @@ App.Utils.CalendarTableView = (function () {
continue; continue;
} }
const title = [ const title = [appointment.service.name];
appointment.service.name
];
const customerInfo = []; const customerInfo = [];
@ -938,7 +943,7 @@ App.Utils.CalendarTableView = (function () {
$providerColumn.find('.calendar-wrapper').data('fullCalendar').addEventSource(calendarEventSource); $providerColumn.find('.calendar-wrapper').data('fullCalendar').addEventSource(calendarEventSource);
} }
/** /**
* Create Blocked Period Events * Create Blocked Period Events
* *
@ -1015,11 +1020,11 @@ App.Utils.CalendarTableView = (function () {
$event.html( $event.html(
lang('break') + lang('break') +
' <span class="hour">' + ' <span class="hour">' +
moment(eventDate).format('HH:mm') + moment(eventDate).format('HH:mm') +
'</span> (' + '</span> (' +
eventDuration + eventDuration +
"')" "')"
); );
$event.data(entry); $event.data(entry);
@ -1199,8 +1204,8 @@ App.Utils.CalendarTableView = (function () {
$('<span/>', { $('<span/>', {
'text': info.event.extendedProps.data 'text': info.event.extendedProps.data
? info.event.extendedProps.data.provider.first_name + ? info.event.extendedProps.data.provider.first_name +
' ' + ' ' +
info.event.extendedProps.data.provider.last_name info.event.extendedProps.data.provider.last_name
: '-' : '-'
}), }),
$('<br/>'), $('<br/>'),
@ -1212,8 +1217,8 @@ App.Utils.CalendarTableView = (function () {
$('<span/>', { $('<span/>', {
'text': App.Utils.Date.format( 'text': App.Utils.Date.format(
info.event.extendedProps.data.date + info.event.extendedProps.data.date +
' ' + ' ' +
info.event.extendedProps.data.workingPlanException.start, info.event.extendedProps.data.workingPlanException.start,
vars('date_format'), vars('date_format'),
vars('time_format'), vars('time_format'),
true true
@ -1228,8 +1233,8 @@ App.Utils.CalendarTableView = (function () {
$('<span/>', { $('<span/>', {
'text': App.Utils.Date.format( 'text': App.Utils.Date.format(
info.event.extendedProps.data.date + info.event.extendedProps.data.date +
' ' + ' ' +
info.event.extendedProps.data.workingPlanException.end, info.event.extendedProps.data.workingPlanException.end,
vars('date_format'), vars('date_format'),
vars('time_format'), vars('time_format'),
true true

View file

@ -1902,6 +1902,16 @@ components:
type: string type: string
language: language:
type: string type: string
customField1:
type: string
customField2:
type: string
customField3:
type: string
customField4:
type: string
customField5:
type: string
notes: notes:
type: string type: string
example: example:
@ -1914,6 +1924,11 @@ components:
zip: '12345' zip: '12345'
timezone: UTC timezone: UTC
language: english language: english
customField1: Value1
customField2: Value2
customField3: Value3
customField4: Value4
customField5: Value5
notes: This is a test customer. notes: This is a test customer.
CustomerPayload: CustomerPayload:
type: object type: object
@ -1936,6 +1951,16 @@ components:
type: string type: string
language: language:
type: string type: string
customField1:
type: string
customField2:
type: string
customField3:
type: string
customField4:
type: string
customField5:
type: string
notes: notes:
type: string type: string
example: example:
@ -1947,6 +1972,11 @@ components:
zip: '12345' zip: '12345'
timezone: UTC timezone: UTC
language: english language: english
customField1: Value1
customField2: Value2
customField3: Value3
customField4: Value4
customField5: Value5
notes: This is a test customer. notes: This is a test customer.
CustomerCollection: CustomerCollection:
type: array type: array