Files
octopus/myproject/pos/static/pos/js/customer.js

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;
}
}