291 lines
8.5 KiB
JavaScript
291 lines
8.5 KiB
JavaScript
/**
|
|
* Модуль управления клиентами POS Terminal
|
|
*/
|
|
|
|
import CONFIG from './config.js';
|
|
import { getCsrfToken, safeFetch, showToast } from './utils.js';
|
|
|
|
/**
|
|
* @typedef {Object} Customer
|
|
* @property {number} id - ID клиента
|
|
* @property {string} name - Имя клиента
|
|
* @property {number} [wallet_balance] - Баланс кошелька
|
|
* @property {string} [phone] - Телефон
|
|
* @property {string} [email] - Email
|
|
*/
|
|
|
|
export class CustomerManager {
|
|
/**
|
|
* @param {Customer} systemCustomer - Системный клиент по умолчанию
|
|
*/
|
|
constructor(systemCustomer) {
|
|
/** @type {Customer} */
|
|
this.systemCustomer = systemCustomer;
|
|
|
|
/** @type {Customer} */
|
|
this.selectedCustomer = systemCustomer;
|
|
|
|
/** @type {Function[]} */
|
|
this.listeners = [];
|
|
}
|
|
|
|
/**
|
|
* Добавляет слушатель изменений
|
|
* @param {Function} callback - Функция обратного вызова
|
|
*/
|
|
addListener(callback) {
|
|
this.listeners.push(callback);
|
|
}
|
|
|
|
/**
|
|
* Удаляет слушатель изменений
|
|
* @param {Function} callback - Функция обратного вызова
|
|
*/
|
|
removeListener(callback) {
|
|
this.listeners = this.listeners.filter(cb => cb !== callback);
|
|
}
|
|
|
|
/**
|
|
* Уведомляет всех слушателей
|
|
* @private
|
|
*/
|
|
_notify() {
|
|
this.listeners.forEach(callback => {
|
|
try {
|
|
callback(this.selectedCustomer);
|
|
} catch (error) {
|
|
console.error('Ошибка в слушателе клиента:', error);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Проверяет, выбран ли системный клиент
|
|
* @returns {boolean}
|
|
*/
|
|
isSystemCustomer() {
|
|
return Number(this.selectedCustomer.id) === Number(this.systemCustomer.id);
|
|
}
|
|
|
|
/**
|
|
* Получает текущего клиента
|
|
* @returns {Customer}
|
|
*/
|
|
getCurrentCustomer() {
|
|
return this.selectedCustomer;
|
|
}
|
|
|
|
/**
|
|
* Получает отображаемое имя клиента
|
|
* @returns {string}
|
|
*/
|
|
getDisplayName() {
|
|
return this.isSystemCustomer() ? 'Системный клиент' : this.selectedCustomer.name;
|
|
}
|
|
|
|
/**
|
|
* Устанавливает клиента
|
|
* @param {Customer} customer - Данные клиента
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async selectCustomer(customer) {
|
|
this.selectedCustomer = customer;
|
|
this._notify();
|
|
|
|
// Сохраняем выбор на сервере
|
|
try {
|
|
const response = await safeFetch(
|
|
`${CONFIG.API.SET_CUSTOMER}${customer.id}/`,
|
|
{
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': getCsrfToken(),
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}
|
|
);
|
|
|
|
const data = await response.json();
|
|
|
|
if (!data.success) {
|
|
console.error('Ошибка сохранения клиента:', data.error);
|
|
showToast('error', 'Ошибка сохранения выбора клиента');
|
|
return false;
|
|
}
|
|
|
|
// Обновляем баланс из ответа сервера
|
|
this.selectedCustomer.wallet_balance = data.wallet_balance || 0;
|
|
this._notify();
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Ошибка при сохранении клиента:', error);
|
|
showToast('error', 'Ошибка сети при сохранении клиента');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Сбрасывает на системного клиента
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async resetToSystem() {
|
|
return this.selectCustomer(this.systemCustomer);
|
|
}
|
|
|
|
/**
|
|
* Создаёт нового клиента
|
|
* @param {Object} data - Данные клиента
|
|
* @param {string} data.name - Имя
|
|
* @param {string} [data.phone] - Телефон
|
|
* @param {string} [data.email] - Email
|
|
* @returns {Promise<{success: boolean, customer?: Customer, error?: string}>}
|
|
*/
|
|
async createCustomer(data) {
|
|
try {
|
|
const response = await safeFetch(
|
|
CONFIG.API.CUSTOMER_CREATE,
|
|
{
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': getCsrfToken()
|
|
},
|
|
body: JSON.stringify({
|
|
name: data.name,
|
|
phone: data.phone || null,
|
|
email: data.email || null
|
|
})
|
|
}
|
|
);
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
const customer = {
|
|
id: result.id,
|
|
name: result.name,
|
|
wallet_balance: result.wallet_balance || 0
|
|
};
|
|
|
|
// Автоматически выбираем созданного клиента
|
|
await this.selectCustomer(customer);
|
|
|
|
showToast('success', `Клиент "${result.name}" успешно создан!`);
|
|
return { success: true, customer };
|
|
} else {
|
|
return { success: false, error: result.error || 'Ошибка при создании клиента' };
|
|
}
|
|
} catch (error) {
|
|
console.error('Error creating customer:', error);
|
|
return { success: false, error: 'Ошибка сети при создании клиента' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Инициализирует Select2 для поиска клиента
|
|
* @param {string} selector - CSS селектор
|
|
* @param {Object} options - Дополнительные опции
|
|
*/
|
|
initSelect2(selector, options = {}) {
|
|
const $searchInput = $(selector);
|
|
const modalId = options.modalId || '#selectCustomerModal';
|
|
|
|
$searchInput.select2({
|
|
theme: 'bootstrap-5',
|
|
dropdownParent: $(modalId),
|
|
placeholder: 'Начните вводить имя, телефон или email (минимум 3 символа)',
|
|
minimumInputLength: 3,
|
|
allowClear: true,
|
|
ajax: {
|
|
url: CONFIG.API.CUSTOMER_SEARCH,
|
|
dataType: 'json',
|
|
delay: CONFIG.TIMEOUTS.SELECT2_DELAY,
|
|
data: function (params) {
|
|
return { q: params.term };
|
|
},
|
|
processResults: function (data) {
|
|
return { results: data.results };
|
|
},
|
|
cache: true
|
|
},
|
|
templateResult: this._formatCustomerOption,
|
|
templateSelection: this._formatCustomerSelection
|
|
});
|
|
|
|
// Обработка выбора
|
|
$searchInput.on('select2:select', async (e) => {
|
|
const data = e.params.data;
|
|
|
|
// Проверяем это не опция "Создать нового клиента"
|
|
if (data.id === 'create_new') {
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('selectCustomerModal'));
|
|
modal.hide();
|
|
|
|
if (options.onCreateNew) {
|
|
options.onCreateNew(data.text);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Выбираем клиента
|
|
await this.selectCustomer({
|
|
id: parseInt(data.id),
|
|
name: data.name,
|
|
wallet_balance: data.wallet_balance || 0
|
|
});
|
|
|
|
// Закрываем модалку
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('selectCustomerModal'));
|
|
modal.hide();
|
|
|
|
// Очищаем Select2
|
|
$searchInput.val(null).trigger('change');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Форматирует опцию клиента в выпадающем списке
|
|
* @private
|
|
*/
|
|
_formatCustomerOption(customer) {
|
|
if (customer.loading) {
|
|
return customer.text;
|
|
}
|
|
|
|
// Если это опция "Создать нового клиента"
|
|
if (customer.id === 'create_new') {
|
|
return $('<span><i class="bi bi-person-plus"></i> ' + customer.text + '</span>');
|
|
}
|
|
|
|
// Формируем текст в одну строку
|
|
const parts = [];
|
|
|
|
// Имя
|
|
const name = customer.name || customer.text;
|
|
parts.push('<span class="fw-bold">' + $('<div>').text(name).html() + '</span>');
|
|
|
|
// Телефон и Email
|
|
const contactInfo = [];
|
|
if (customer.phone) {
|
|
contactInfo.push($('<div>').text(customer.phone).html());
|
|
}
|
|
if (customer.email) {
|
|
contactInfo.push($('<div>').text(customer.email).html());
|
|
}
|
|
|
|
if (contactInfo.length > 0) {
|
|
parts.push('<span class="text-muted small"> (' + contactInfo.join(', ') + ')</span>');
|
|
}
|
|
|
|
return $('<span>' + parts.join('') + '</span>');
|
|
}
|
|
|
|
/**
|
|
* Форматирует выбранного клиента
|
|
* @private
|
|
*/
|
|
_formatCustomerSelection(customer) {
|
|
return customer.name || customer.text;
|
|
}
|
|
}
|