').text(value == null ? '' : String(value)).html();
}
function cartShortages() {
if (cartCutStockActive()) return cartCutStockResult ? cartCutStockResult.shortages : [];
var shortages = [];
$('#ordercard-list').children('.position').each(function() {
var $row = $(this);
if ($row.attr('data-made-to-order') === '1') return;
var requested = Number(String($row.find('.ordercard-quantity').val() || '0').replace(',', '.').replace(/\s/g, ''));
var available = Number(String($row.children('.remains').text() || '0').replace(',', '.').replace(/\s/g, ''));
if ($row.attr('data-panel-spec') && cartWarehouseFields()[13] == '1') requested = cartPanelSheets($row, JSON.parse($row.find('.ordercard-panel-spec').val()));
if (!isNaN(requested) && requested > Math.max(0, isNaN(available) ? 0 : available)) {
shortages.push({
name: $row.children('.name').text(),
articul: $row.children('.articul').text(),
code: $row.children('.code').text(),
requested: requested,
available: Math.max(0, isNaN(available) ? 0 : available),
waiting: requested - Math.max(0, isNaN(available) ? 0 : available)
});
}
});
return shortages;
}
function showWaitingListConfirmation(shortages) {
shortages = Array.isArray(shortages) ? shortages : cartShortages();
var hasAvailable = $('#ordercard-list').children('.position').length > shortages.length || shortages.some(function(item) {
return Number(item.available || 0) > 0;
});
var details = '
';
popup('atten', 'Не все товары есть на складе', details, true, false, '', 'submitOrderWithWaitingList');
var $popup = $('.popup').last().addClass('waiting-list-confirmation-popup');
$popup.find('button[rel="yesconfirm"]').html('
Оформить заказ').attr('aria-label', 'Подтвердить оформление заказа');
$popup.find('.close-popup').text('Вернуться в корзину');
$popup.find('.close').attr({ alt: 'Вернуться в корзину', title: 'Вернуться в корзину' });
$popup.find('button[rel="yesconfirm"]').parent().addClass('waiting-list-actions');
}
function showOrderCompletion(result, invoices) {
var newOrderId = Number(result.order_id || 0);
var waitingOrderId = Number(result.waiting_order_id || 0);
var waitingCount = Number(result.waiting_list_count || 0);
var httpOrderErrors = Array.isArray(result.http_order_errors) ? result.http_order_errors : [];
var httpOrderNumbers = result.http_order_numbers || {};
var failedInvoices = (invoices || []).filter(function(invoice) { return !invoice.success; });
var readyInvoices = (invoices || []).filter(function(invoice) { return invoice.success && invoice.url; });
var redirectUrl = '/ordering';
var orderCards = '';
if (newOrderId > 0) {
orderCards += '
';
popup(failedInvoices.length || httpOrderErrors.length ? 'bad' : 'good', failedInvoices.length || httpOrderErrors.length ? 'Заказ сохранён в CRM' : (readyInvoices.length ? 'Счёт сформирован' : 'Заказ создан'), message, true, false);
var $popup = $('.popup').last().addClass('order-completion-popup').attr('data-close-redirect', redirectUrl);
$popup.find('.close').attr({ alt: 'Открыть созданный заказ', title: 'Открыть созданный заказ', 'aria-label': 'Открыть созданный заказ' });
}
function cartWarehouseSubmissionMessage() {
return $.trim($('#order-warehouse-status span').text()) || 'Проверка наличия товаров ещё не завершена.';
}
function submitOrderRequest() {
if (orderSubmissionInProgress) return;
var scheduleState = $('#order-delivery-date[data-delivery-schedule]').data('schedule-state');
if (scheduleState === 'loading' || scheduleState === 'error') {
popup('bad', 'Дата доставки', 'Дождитесь проверки адреса и повторите выбор даты доставки.', true, false);
return;
}
if (cartClientPriceState === 'loading' || cartClientPriceState === 'error') {
popup('bad', 'Внимание!', 'Дождитесь пересчёта цен клиента или повторите расчёт в корзине.', true, false);
return;
}
if (cutServiceBlocked()) {
popup('bad', 'Внимание!', 'Стоимость распила ещё не подтверждена. Повторите расчёт в корзине.', true, false);
return;
}
if (_cartWarehouseRequest || cartCutStockBlocked() || $('#ordercard-list .remains[data-warehouse-unknown="1"]').length) {
popup('bad', 'Внимание!', cartWarehouseSubmissionMessage(), true, false);
return;
}
var $form = $('#ordercard_form');
var isNewOrder = $.trim($form.find('#ordercard-edit-flag').val()) === '';
if (isNewOrder && !$form.find('#order-request-key').val()) {
var cartStamp = localStorage['JustCRM-OrderCart'] || '';
var requestKey = localStorage['JustCRM-OrderCart-request-key-cart'] === cartStamp && localStorage['JustCRM-OrderCart-request-key'] || (window.crypto && typeof window.crypto.randomUUID === 'function'
? window.crypto.randomUUID().replace(/-/g, '')
: String(Date.now()) + String(Math.random()).slice(2));
$form.find('#order-request-key').val(requestKey);
localStorage['JustCRM-OrderCart-request-key'] = requestKey;
localStorage['JustCRM-OrderCart-request-key-cart'] = cartStamp;
}
var shouldMakeInvoice = $form.data('auto-invoice') === true && isNewOrder;
var payload = $form.serialize();
if (plisseCartActive()) $form.data('auto-invoice', false);
setOrderSubmitState(true);
var $progress = $('#order-submit-status');
if (!$progress.length) $progress = $('
', { id: 'order-submit-status', role: 'status', 'aria-live': 'polite' }).insertBefore($form.find('.add-order'));
$progress.text('Создаём заказ…');
function failOrder(result, message) {
$progress.text('');
setOrderSubmitState(false);
if (result && result.error === 'stock_changed') {
$('#waiting-list-confirmed').val('0');
showWaitingListConfirmation(result.shortages || []);
} else {
popup('bad', 'Внимание!', result && result.message ? result.message : message, true, false);
}
}
function completeOrder(result, invoices) {
if (typeof result.waiting_list_total_count !== 'undefined') window.updateWaitingListBadge(result.waiting_list_total_count);
localStorage['JustCRM-OrderCart-field'] = '[]';
deleteOrderAllPos(true);
delete localStorage['JustCRM-OrderCart-promocode'];
delete localStorage['JustCRM-OrderCart-promocode-data'];
delete localStorage['JustCRM-OrderCart-promoOrig'];
delete localStorage['JustCRM-OrderCart-promoRestore'];
$form.find('#order-request-key').val('');
delete localStorage['JustCRM-OrderCart-request-key'];
delete localStorage['JustCRM-OrderCart-request-key-cart'];
_warehouseRemainsCache = [];
$progress.text('');
setOrderSubmitState(false);
showOrderCompletion(result, invoices);
}
function makeInvoices(orderIds, done) {
var results = [];
function next(index) {
if (index >= orderIds.length) {
done(results);
return;
}
var orderId = orderIds[index];
$.ajax({
url: '/ajax-send/data-make-invoice.php?guid=' + encodeURIComponent(guid),
type: 'POST',
dataType: 'json',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify([String(orderId)]),
timeout: 60000
}).done(function(response) {
results.push({ orderId: orderId, success: Boolean(response && (response.success === 'true' || response.success === true) && response.invoice_url), url: response && response.invoice_url });
}).fail(function() {
results.push({ orderId: orderId, success: false });
}).always(function() {
next(index + 1);
});
}
next(0);
}
$.ajax({
url: '/ajax-send/data-add-order.php?guid=' + encodeURIComponent(guid),
type: 'POST',
dataType: 'json',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify([payload]),
timeout: 60000
}).done(function(result) {
if (!result || (result.success !== true && result.success !== 'true') || (result.error && result.error !== 'false')) {
failOrder(result, 'Сервер не подтвердил создание заказа.');
return;
}
var newOrderId = Number(result.order_id || 0);
var waitingOrderId = Number(result.waiting_order_id || 0);
if (newOrderId <= 0 && waitingOrderId <= 0) {
failOrder(result, 'Сервер не вернул результат создания заказа.');
return;
}
var orderIds = newOrderId > 0 ? [newOrderId] : [];
var needsInvoice = result.auto_invoice === true || result.auto_invoice === 'true';
if (!needsInvoice || !orderIds.length) {
completeOrder(result, []);
return;
}
$progress.text('Заказ создан. Формируем счёт…');
makeInvoices(orderIds, function(invoices) {
completeOrder(result, invoices);
});
}).fail(function(xhr, status) {
failOrder(xhr.responseJSON, status === 'timeout'
? 'Не удалось дождаться ответа. Проверьте список заказов перед повторной отправкой; корзина сохранена.'
: 'Не удалось получить подтверждение. Проверьте список заказов перед повторной отправкой; корзина сохранена.');
});
}
window.submitOrderWithWaitingList = function() {
delayClosePopup(0);
$('#waiting-list-confirmed').val('1');
submitOrderRequest();
};
$('body').on('click', '.add-order', function(event) {
event.preventDefault();
if (orderSubmissionInProgress) return;
var $form = $(this).parents('form');
$form.validationEngine('detach');
$form.validationEngine('attach', {
promptPosition: 'bottomLeft',
onValidationComplete: function(form, status) {
if (status === true) {
if (_cartWarehouseRequest || cartCutStockBlocked() || $('#ordercard-list .remains[data-warehouse-unknown="1"]').length) {
popup('bad', 'Внимание!', cartWarehouseSubmissionMessage(), true, false);
return;
}
var shortages = $.trim($form.find('#ordercard-edit-flag').val()) === '' ? cartShortages() : [];
if (shortages.length) {
$('#waiting-list-confirmed').val('0');
showWaitingListConfirmation(shortages);
return;
}
$('#waiting-list-confirmed').val('0');
submitOrderRequest();
}
}
});
$form.validationEngine('validate');
});
$('body').on('input', '.ordercard-length-mm', function() {
var $length = $(this).closest('.position').find('.ordercard-length');
var value = String($(this).val()).replace(',', '.');
$length.val(value === '' ? '' : String(Number(value) / 1000));
$('#waiting-list-confirmed').val('0');
orderStat($length);
});
$('body').on('input', '.ordercard-quantity, .ordercard-qty, .ordercard-length, .ordercard-panel-width, .ordercard-num-order', function() {
$this = $(this);
if ($this.hasClass('ordercard-length') && cutLengthInputApplicable($this)) cutLengthRestrictInput($this);
$('#waiting-list-confirmed').val('0');
//setTimeout(function() {
orderStat($this);
//}, 500);
});
$('body').on('focusout change', '#ordercard-list .ordercard-length, #ordercard-list .ordercard-length-mm, #ordercard-list .ordercard-qty', function() {
var $field = $(this);
if ($field.closest('.position').attr('data-made-to-order') !== '1') return;
if ($field.hasClass('ordercard-length-mm')) $field = $field.closest('.position').find('.ordercard-length');
var value = Number(String($field.val()).replace(',', '.'));
var length = $field.hasClass('ordercard-length');
value = isFinite(value) ? value : 0;
$field.val(length ? Math.max(0.02, Math.min(3, Math.round(value * 100) / 100)) : Math.max(1, Math.min(1000, Math.round(value))));
orderStat($field);
cartLengthRender($field.closest('.position'), true);
});
$('body').on('focusin', '.ordercard-quantity', function() {
$(this).data('prev-val', $(this).val());
});
$('body').on('focusout', '.ordercard-quantity', function() {
$this = $(this);
orderStat($this, 'minShipment');
});
$('body').on('click', '.order_prod', function() {
orderStat($(this));
});
$('body').on('input', '.ordercard-price', function() {
orderStat($(this));
});
$('body').on('change', '#order-warehouse', function() {
$('#waiting-list-confirmed').val('0');
refreshCartWarehouse($(this).val());
});
// Распознавание адреса
$('.search-map').on('click', function() {
send_request($(this).parent().prev('input').val(), 'data-exchange', 'send_request_dadata');
var waiting = setInterval(function() {
if (typeof(dataResult) != 'undefined' && typeof(dataResult.suggestions) != 'undefined' && typeof(dataResult.suggestions[0]) != 'undefined') {
if (typeof(dataResult.error) != 'undefined') {
clearTimeout(waiting);
dataResult = {};
}
popup('good', 'Успешно!', 'Адрес распознан и обработан!', true, false);
$('#order-delivery-address').val(dataResult.suggestions[0].unrestricted_value);
$('#order-delivery-coords').val(dataResult.suggestions[0].data.geo_lat + ',' + dataResult.suggestions[0].data.geo_lon);
$('#order-delivery-address-list').val(dataResult.suggestions[0].unrestricted_value);
$('#order-delivery-coords-list').val(dataResult.suggestions[0].data.geo_lat + ',' + dataResult.suggestions[0].data.geo_lon);
clearTimeout(waiting);
dataResult = {};
$('#order-delivery-address').trigger('input');
$('#order-delivery-address-list').trigger('input');
}
}, 100);
});
// Если выбрали не поле Новый адрес доставки, то очищаем введенное ранее в поле Новый адрес доставки
$(document).on('change', '#delivery_places', function() {
if ($('#delivery_places').val() != '%%') {
$('#order-delivery-address').val('');
$('#order-delivery-coords').val('');
}
// Обновляем предпоказ стоимости доставки
deliveryPricePreview(100);
// Если поле нового адреса не пустое и не выбран Самовывоз, то скрываем поля Дата доставки и Время доставки
if ($(this).val() == '%' && $.trim($('body').find('#order-delivery-address').val()) == '') {
$('#order-delivery-date').parent('div').hide();
} else {
$('#order-delivery-date').parent('div').show();
}
// Если выбран новый адрес, то делаем его поле обязательным, иначе необязательным
if ($(this).val() == '%%') {
$('#order-delivery-address').attr('class', 'validate[required,minSize[10],maxSize[500]] text-input');
} else {
$('#order-delivery-address').attr('class', 'validate[optional,minSize[3],maxSize[500]] text-input');
}
});
// Подсказки адресов Яндекса (Геосаджест) под полем Новый адрес доставки — та же схема,
// что в зонах доставки (delivery-zones): запросы через серверный прокси, ключи не покидают сервер
function orderAddressSuggest(inputSel, dropSel, coordsSel) {
var $input = $(inputSel);
var $drop = $(dropSel);
if (!$input.length || !$drop.length) return;
var state = { timer: null, seq: 0, skipNext: false };
function hide() { $drop.hide().empty(); }
$input.on('input', function() {
// Программные trigger('input') (выбор подсказки, распознавание по маркеру) выпадашку не открывают
if (state.skipNext || !$input.is(':focus')) { state.skipNext = false; return; }
clearTimeout(state.timer);
var text = $.trim($input.val());
if (text.length < 3) { state.seq++; hide(); return; }
state.timer = setTimeout(function() {
var mySeq = ++state.seq;
$.post('/ajax-send/send_request_yandex_geo.php?guid=' + guid,
{ action: 'suggest', text: text, types: '' },
function(resp) {
// Отбрасываем устаревшие ответы (защита от гонки запросов)
if (mySeq !== state.seq) return;
if (!resp || !resp.ok || !resp.results || !resp.results.length) { hide(); return; }
$drop.empty();
$.each(resp.results, function(i, item) {
var title = (item.title && item.title.text) ? item.title.text : '';
var subtitle = (item.subtitle && item.subtitle.text) ? item.subtitle.text : '';
if (!title) return;
var $item = $('
');
$item.append($('
').text(title));
if (subtitle) $item.append($('
').text(subtitle));
// mousedown вместо click — чтобы blur не закрыл выпадашку раньше выбора
$item.on('mousedown', function(e) {
e.preventDefault();
// Полный адрес: formatted_address приходит благодаря print_address=1 на сервере
var full = (item.address && item.address.formatted_address)
? item.address.formatted_address
: (subtitle ? subtitle + ', ' + title : title);
clearTimeout(state.timer);
state.seq++;
hide();
$input.val(full);
$(coordsSel).val('');
// Запускаем штатную логику поля (выбор «Новый адрес доставки», предпоказ стоимости)
state.skipNext = true;
$input.trigger('input');
// Геокодируем выбранный адрес, чтобы сразу заполнить координаты
$.post('/ajax-send/send_request_yandex_geo.php?guid=' + guid,
{ action: 'geocode', text: full },
function(geo) {
if (geo && geo.ok && geo.coords) {
$(coordsSel).val(geo.coords[0] + ',' + geo.coords[1]);
if (typeof deliveryPricePreview === 'function') deliveryPricePreview(100);
}
}, 'json');
});
$drop.append($item);
});
$drop.show();
}, 'json').fail(function() {
if (mySeq === state.seq) hide();
});
}, 300);
});
$input.on('blur', function() { setTimeout(hide, 200); });
}
orderAddressSuggest('#order-delivery-address', '#order-delivery-suggest', '#order-delivery-coords');
orderAddressSuggest('#order-delivery-address-list', '#order-delivery-suggest-list', '#order-delivery-coords-list');
// Если производится ввод в поле Новый адрес доставки, то выбираем в списке Новый адрес доставки
$('#order-delivery-address').on('input', function() {
$('#delivery_places option[value="%%"]').prop('selected', true);
if ($(this).val() == '') $('#delivery_places option[value="%"]').prop('selected', true);
// Обновляем предпоказ стоимости доставки (адрес геокодируется на сервере — ждём паузу в вводе)
deliveryPricePreview(800);
});
$(document).on('input change', '#id_client, #client_cat_price, #delivery_places, #order_client_comments, #order-delivery-address, #order-delivery-coords, #order_status, #order-delivery-date, #order-delivery-time, #order-pay, #order_prod, #order-delivery-tk', function() {
if (orderCardRestoringFields) return;
var thisForm = $(this).closest('form');
// Заполняем данные
var orderF1 = thisForm.find('#order-client').val();
var orderF2 = thisForm.find('#id_client').val();
var orderF3 = thisForm.find('#client_cat_price').val();
var orderF4 = [thisForm.find('#delivery_places').val(), thisForm.find('#delivery_places option:selected').text()];
var orderF5 = thisForm.find('#order_client_comments').val();
if (thisForm.find('#delivery_places').val() != '%%') {
thisForm.find('#order-delivery-address').val('');
thisForm.find('#order-delivery-coords').val('');
}
var orderF6 = thisForm.find('#order-delivery-address').val();
var orderF7 = thisForm.find('#order-delivery-coords').val();
var orderF8 = thisForm.find('#order_status').val();
var orderF9 = thisForm.find('#order-warehouse').val();
var orderF10 = thisForm.find('#order-delivery-date').val();
var orderF11 = thisForm.find('#order-delivery-time').val();
var orderF12 = thisForm.find('#order-pay').val();
var orderF13 = thisForm.find('#ordercard-edit-flag').val();
var orderF14 = (thisForm.find('#order_prod').prop('checked')) ? 1 : 0;
var orderF15 = thisForm.find('#order-delivery-tk').val() || '';
var orderFAll = [orderF1, orderF2, orderF3, orderF4, orderF5, orderF6, orderF7, orderF8, orderF9, orderF10, orderF11, orderF12, orderF13, orderF14, orderF15];
localStorage['JustCRM-OrderCart-field'] = JSON.stringify(orderFAll);
if (this.id === 'id_client' || this.id === 'client_cat_price') {
cartClientPricePreview();
cutPricePreview();
cutServicePreview();
deliveryPricePreview();
}
});
// --- Промокоды: работа с массивом кодов ---
$('body').on('click', '#order-promocode-add', applyPromoInput);
$('body').on('keydown', '#order-promocode-input', function(e) {
if (e.key === 'Enter') { e.preventDefault(); applyPromoInput(); }
});
// Удаление тега промокода
$('body').on('click', '.promo-tag-remove', function() {
var codeToRemove = $(this).attr('data-code');
var codes = promoGetCodes().filter(function(c) { return c !== codeToRemove; });
promoSetCodes(codes);
if (codes.length === 0) {
promoSetData({});
// Восстанавливаем цены до промо (со скидкой клиента) из promoRestore
var restoreMap = {};
try { restoreMap = JSON.parse(localStorage['JustCRM-OrderCart-promoRestore'] || '{}'); } catch(e) {}
if (Object.keys(restoreMap).length > 0) {
var cartRestore = JSON.parse(localStorage['JustCRM-OrderCart'] || '{}');
for (var rk in restoreMap) {
if (typeof cartRestore[rk] !== 'undefined') cartRestore[rk][10]['value'] = restoreMap[rk];
}
localStorage['JustCRM-OrderCart'] = JSON.stringify(cartRestore);
}
delete localStorage['JustCRM-OrderCart-promoOrig'];
delete localStorage['JustCRM-OrderCart-promoRestore'];
}
promoRenderTags();
orderStat();
});
});
// Поиск клиента должен работать и для формы, перенесённой в popup после загрузки страницы.
(function() {
var targetInputClient = '#order-client, #order-client-list, #clientName, #clientNameFile, #nomen-import-client';
var delAddrObj = {};
var clientSearchRequest = null;
$(document).off('input.orderClientSearch', targetInputClient).on('input.orderClientSearch', targetInputClient, function() {
var $input = $(this);
var query = $input.val();
var $result = $input.next('.search-result');
$input.nextAll('input').val('');
if ($input.attr('id') === 'order-client') $('#id_client').trigger('change');
if ($input.attr('id') === 'nomen-import-client') nomenImportUpdateFileState();
if (clientSearchRequest) clientSearchRequest.abort();
if (query.length < 3) {
$result.empty().fadeOut();
return;
}
var request = $.ajax({
url: '/ajax-send/data-clients-search.php?guid=' + guid,
type: 'POST',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify([query])
});
clientSearchRequest = request;
request.done(function(clients) {
if ($input.val() !== query || !clients || $.isEmptyObject(clients)) {
$result.empty().fadeOut();
return;
}
var result = '';
for (var key in clients) {
var clientName = clients[key].fullName || clients[key].name;
if (!clientName) continue;
result += '
' + clientName + ' ИНН:' + clients[key].INN + ' | КПП:' + clients[key].KPP + '
';
if (typeof clients[key].delivery_addresses != 'undefined' && Object.keys(clients[key].delivery_addresses).length > 0) {
var delAddrStr = '';
for (var k in clients[key].delivery_addresses) {
delAddrStr += '
' + clients[key].delivery_addresses[k].name + ' ';
}
delAddrObj[clients[key].UID] = delAddrStr;
}
}
$result.html(result).addClass('who').fadeIn();
}).fail(function(jqXHR, status) {
if (status !== 'abort') $result.empty().fadeOut();
}).always(function() {
if (clientSearchRequest === request) clientSearchRequest = null;
});
});
$(document).off('mousedown.orderClientSearch', '.search-result.who span').on('mousedown.orderClientSearch', '.search-result.who span', function(e) {
if ($(this).hasClass('blocked')) return;
e.preventDefault();
var $choice = $(this);
var $result = $choice.parent();
var $input = $result.prev('input');
var clientId = $choice.attr('data-clients');
var orderFormId = $input.attr('id');
$input.val($choice.text()).css({ 'background': 'lightgreen' });
$result.next().val(clientId);
$result.next().next().val($choice.attr('data-cat-price'));
if (orderFormId === 'nomen-import-client') {
nomenImportUpdateFileState();
$result.removeClass('who').fadeOut();
return;
}
if (orderFormId == 'order-client') {
setCookie('cat_search_price_client_uid', decodeURIComponent(clientId));
} else {
setCookie('cat_search_price_client_uid_list', decodeURIComponent(clientId));
}
if (typeof delAddrObj[clientId] != 'undefined') {
if (orderFormId == 'order-client') {
$('#delivery_places').html('
Самовывоз Новый адрес доставки ' + delAddrObj[clientId]);
} else {
$('#delivery_places_list').html('
Самовывоз Новый адрес доставки ' + delAddrObj[clientId]);
}
}
$result.removeClass('who').fadeOut();
if (orderFormId === 'order-client') $('#id_client').trigger('change');
if (orderFormId === 'order-client-list') $('#id_client_list').trigger('change');
$('#order_client_comments').trigger('input');
});
$(document).off('focusout.orderClientSearch', targetInputClient).on('focusout.orderClientSearch', targetInputClient, function() {
var $input = $(this);
if ($input.nextAll('input').val() == '') {
$input.val('').css({ 'background': '#F8F9FB' });
}
$input.next('.search-result').fadeOut();
});
$(document).off('keydown.orderClientSearch', targetInputClient).on('keydown.orderClientSearch', targetInputClient, function(e) {
var keycode = (e.keyCode ? e.keyCode : e.which);
if (keycode === 13) $(this).nextAll('input').trigger('focusout');
});
})();
// --- Промокоды: хелперы ---
function promoGetCodes() {
try {
var v = localStorage['JustCRM-OrderCart-promocode'];
if (!v) return [];
var parsed = JSON.parse(v);
return Array.isArray(parsed) ? parsed : (parsed ? [parsed] : []);
} catch(e) { return []; }
}
function promoSetCodes(arr) {
localStorage['JustCRM-OrderCart-promocode'] = JSON.stringify(arr);
$('#order-promocode').val(arr.join(','));
}
var cartClientPriceRequest = null;
var cartClientPriceVersion = 0;
var cartClientPriceState = 'idle';
function cartClientPricePreview() {
var version = ++cartClientPriceVersion;
if (cartClientPriceRequest) cartClientPriceRequest.abort();
cartClientPriceRequest = null;
var client = $('#id_client').val() || '';
var category = $('#client_cat_price').val() || '';
var lines = [];
$('#ordercard-list').children('.position').each(function() {
var row = $(this);
if (row.attr('data-cut-included') === '1' || row.attr('data-panel-spec') || row.attr('data-made-to-order') === '1') return;
lines.push({key: row.attr('data-id-pos-prefix'), id: row.attr('data-id-pos')});
});
var status = $('#cart-client-price-status');
cartClientPriceState = 'idle';
if (!client || !lines.length || plisseCartActive()) {
delete localStorage['JustCRM-OrderCart-client-recalc'];
status.hide();
return;
}
if (!status.length) status = $('
', {id: 'cart-client-price-status', role: 'status', 'aria-live': 'polite'})
.insertBefore($('#ordercard-list').closest('table'));
cartClientPriceState = 'loading';
localStorage['JustCRM-OrderCart-client-recalc'] = '1';
status.removeClass('red').text('Пересчитываем цены клиента…').show();
function fail(message) {
cartClientPriceState = 'error';
status.addClass('red').text(message + ' ')
.append($('', {type: 'button', class: 'cart-client-price-retry', text: 'Повторить расчёт'})).show();
}
cartClientPriceRequest = $.ajax({
url: '/ajax-send/data-cart-recalc.php?guid=' + encodeURIComponent(guid),
method: 'POST', dataType: 'json', timeout: 30000,
data: {client_recalc: 1, client: client, category: category, nomen_ids: lines.map(function(line) { return line.id; })}
}).done(function(response) {
if (version !== cartClientPriceVersion) return;
if (client !== ($('#id_client').val() || '') || category !== ($('#client_cat_price').val() || '')) return;
var prices = response && response.prices || {};
var cart = JSON.parse(localStorage['JustCRM-OrderCart'] || '{}');
var bad = lines.find(function(line) {
if (!cart[line.key]) return false;
var price = prices[line.id];
return !price || !isFinite(Number(price.price_number)) || Number(price.price_number) < 0 || !(Number(price.original_price) > 0);
});
if (!response || !response.ok || bad) { fail('Не удалось получить цены выбранного клиента.'); return; }
var promos = promoGetData();
var promoOrig = JSON.parse(localStorage['JustCRM-OrderCart-promoOrig'] || '{}');
var promoRestore = JSON.parse(localStorage['JustCRM-OrderCart-promoRestore'] || '{}');
lines.forEach(function(line) {
var entry = cart[line.key];
if (!entry || String(entry[1].value) !== String(line.id)) return;
var current = prices[line.id];
var original = Number(current.original_price);
var price = Number(current.price_number);
var promo = promos[line.key] !== undefined ? promos[line.key] : promos[line.id];
if (promo !== undefined) {
promoOrig[line.key] = String(original);
promoRestore[line.key] = String(price);
price = Number(String(promo).replace(/\s/g, '').replace(',', '.'));
} else {
delete promoOrig[line.key];
delete promoRestore[line.key];
}
entry[8].value = String(price);
entry[9].value = response.category;
entry[10].value = String(price);
if (!entry[15]) entry[15] = {name: 'original-price', value: ''};
entry[15].value = String(original);
var row = $('#ordercard-list').children('.position').filter(function() { return $(this).attr('data-id-pos-prefix') === String(line.key); });
var formatted = price.toFixed(2).replace('.', ',');
row.children('.base-price').html(original > price
? '' + formatted + ' ' + original.toFixed(2).replace('.', ',') + ' ' : formatted);
row.children('.price').attr({'data-price': price, 'data-orig-price': original});
row.find('.ordercard-base-price').val(price);
row.find('.ordercard-cat-price').val(response.category);
row.find('.ordercard-price').val((Number(row.find('.ordercard-quantity').val()) * price).toFixed(2).replace('.', ','));
});
localStorage['JustCRM-OrderCart'] = JSON.stringify(cart);
localStorage['JustCRM-OrderCart-promoOrig'] = JSON.stringify(promoOrig);
localStorage['JustCRM-OrderCart-promoRestore'] = JSON.stringify(promoRestore);
var fields = JSON.parse(localStorage['JustCRM-OrderCart-field'] || '[]');
fields[2] = response.category;
localStorage['JustCRM-OrderCart-field'] = JSON.stringify(fields);
$('#client_cat_price').val(response.category);
delete localStorage['JustCRM-OrderCart-client-recalc'];
cartClientPriceState = 'ready';
status.hide();
orderStat();
cutServicePreview();
}).fail(function(xhr, reason) {
if (version !== cartClientPriceVersion || reason === 'abort') return;
fail(xhr.responseJSON && xhr.responseJSON.error || 'Не удалось пересчитать цены. Корзина сохранена.');
}).always(function() {
if (version === cartClientPriceVersion) cartClientPriceRequest = null;
});
}
$(document).on('click', '.cart-client-price-retry', cartClientPricePreview);
function promoGetData() {
try { return JSON.parse(localStorage['JustCRM-OrderCart-promocode-data'] || '{}'); } catch(e) { return {}; }
}
function promoSetData(obj) {
localStorage['JustCRM-OrderCart-promocode-data'] = JSON.stringify(obj);
}
function promoRenderTags() {
var codes = promoGetCodes();
var $tags = $('#promo-codes-tags');
$tags.empty();
codes.forEach(function(code) {
$tags.append(
$('').text(code)
.append($('').html('×').attr('data-code', code))
);
});
$('#order-promocode').val(codes.join(','));
}
function applyPromoInput() {
if (plisseCartActive()) return;
var val = $.trim($('#order-promocode-input').val()).toUpperCase();
if (val.length < 3) return;
var existingCodes = promoGetCodes();
if (existingCodes.indexOf(val) !== -1) {
popup('bad', 'Промокод уже применён', 'Этот промокод уже добавлен в заказ.', true, false);
return;
}
dataResult = {};
send_request(val, 'data-exchange', 'data-promocode-search');
var waitTicks = 0;
var waitResult = setInterval(function() {
waitTicks++;
if (waitTicks > 50) {
clearInterval(waitResult);
popup('bad', 'Промокод не найден', 'Промокод '+val+' не найден или недействителен.', true, false);
return;
}
if (!$.isEmptyObject(dataResult) && typeof dataResult.code !== 'undefined') {
if (dataResult.code.toUpperCase() !== val) {
clearInterval(waitResult);
popup('bad', 'Промокод не найден', 'Промокод '+val+' не найден или недействителен.', true, false);
return;
}
clearInterval(waitResult);
var positions = JSON.parse(dataResult.positions);
// Merge positions (последний введённый перекрывает предыдущий)
var existingData = promoGetData();
$.extend(existingData, positions);
promoSetData(existingData);
// Добавляем код в список
var codes = promoGetCodes();
codes.push(val);
promoSetCodes(codes);
promoRenderTags();
// Обновляем цены в localStorage корзины, сохраняя исходную цену
var cartAll = JSON.parse(localStorage['JustCRM-OrderCart'] || '{}');
// promoOrig — базовая цена БЕЗ клиентской скидки (для отображения зачёркнутой старой цены)
var promoOrig = {};
try { promoOrig = JSON.parse(localStorage['JustCRM-OrderCart-promoOrig'] || '{}'); } catch(e) {}
// promoRestore — цена ДО промо (со скидкой клиента, если была) для восстановления при удалении кода
var promoRestore = {};
try { promoRestore = JSON.parse(localStorage['JustCRM-OrderCart-promoRestore'] || '{}'); } catch(e) {}
for (var k in positions) {
if (typeof cartAll[k] !== 'undefined') {
if (typeof promoOrig[k] === 'undefined') {
// Берём original-price [15] (до клиентской скидки); если нет — берём текущую [10]
var _basePrice = (typeof cartAll[k][15] !== 'undefined' && cartAll[k][15]['value'] !== '')
? cartAll[k][15]['value']
: cartAll[k][10]['value'];
promoOrig[k] = _basePrice; // для отображения "старой" цены
promoRestore[k] = cartAll[k][10]['value']; // для восстановления при удалении промо
}
cartAll[k][10]['value'] = positions[k];
}
}
localStorage['JustCRM-OrderCart-promoOrig'] = JSON.stringify(promoOrig);
localStorage['JustCRM-OrderCart-promoRestore'] = JSON.stringify(promoRestore);
localStorage['JustCRM-OrderCart'] = JSON.stringify(cartAll);
// Показываем popup с изменёнными ценами
var priceList = '';
for (var k in positions) {
var oldPrice = $('#ordercard-list').find('[data-id-pos="'+k+'"]').children('.base-price').html();
if (typeof oldPrice === 'undefined') continue;
var posName = $('#ordercard-list').find('[data-id-pos="'+k+'"]').children('.name').html();
priceList += ''+posName+' '+oldPrice+' > '+positions[k]+' руб. ';
}
if (priceList) {
popup('good', 'Промокод применён!',
'Применён промокод:
'+val+' Понятно ',
false, false);
}
$('#order-promocode-input').val('');
dataResult = {};
orderStat();
}
}, 100);
}
// Применяем промокод
$('body').on('click', '.reload-basket', function() {
$('.popup').find('.close').trigger('click');
setTimeout(function() {
$('#marker-cart').trigger('click');
}, 500);
})
function editOrder(orderId, isCopy = false) {
if ($('[data-plisse-order-map]').length || $('[data-plisse-local-order="' + orderId + '"]').length) { popup('bad', 'Внимание!', 'Редактирование и копирование плиссе пока недоступно. Создайте новый расчёт.', true, false); return; }
send_request(orderId, 'data-exchange', 'data-edit-order');
var waitOrder = setInterval(function() {
if (typeof dataResultThree != 'undefined' && !$.isEmptyObject(dataResultThree)) {
clearInterval(waitOrder);
// Если это копирование заказа, то удаляем 12 элемент массива, где содержится GUID заказа
if (isCopy === true) delete dataResultThree.data[12];
// Записываем все данные по основным полям
localStorage['JustCRM-OrderCart-field'] = JSON.stringify(dataResultThree.data);
localStorage['JustCRM-OrderCart'] = JSON.stringify(dataResultThree.table);
localStorage['JustCRM-OrderCart-Sort'] = JSON.stringify(dataResultThree.sort);
localStorage['JustCRM-OrderCart-Shipment'] = JSON.stringify(dataResultThree.shipment);
// Если есть промокод(ы)
var editCodes = dataResultThree.promo_code ? dataResultThree.promo_code.split(',').filter(Boolean) : [];
promoSetCodes(editCodes);
promoSetData(dataResultThree.promo_code_data || {});
// Устанавливаем куки клиента
setCookie('cat_search_price_client_uid', decodeURIComponent(dataResultThree.data[1]));
setCookie('cat_search_price_uid', decodeURIComponent(dataResultThree.data[2]));
// Вы водим полный список адресов доставки по клиенту
var deliveryAddressStr = 'Самовывоз Новый адрес доставки ';
if (Object.keys(dataResultThree.delivery_address).length > 0) {
for (key in dataResultThree.delivery_address) {
var selectedAddr = '';
if (dataResultThree.data[3][0] == dataResultThree.delivery_address[key]['UID']) selectedAddr = 'selected="selected"';
deliveryAddressStr += '' + dataResultThree.delivery_address[key]['name'] + ' '
}
}
$('#delivery_places').html(deliveryAddressStr);
// Выбираем склад
$('#order-warehouse option[value="' + dataResultThree.data[8] + '"]').prop('selected', true);
// Выбираем время доставки
$('#order-delivery-time option[value="' + dataResultThree.data[12] + '"]').prop('selected', true);
// Выбираем тип оплаты
$('#order-pay option[value="' + dataResultThree.data[13] + '"]').prop('selected', true);
dataResultThree = '{}';
//orderStat(); // Последние изменения
orderMarkerView();
setTimeout(function() {
$('#marker-cart').trigger('click');
}, 1000);
}
}, 500);
}
// function editOrderList(orderId) {
// sessionStorage['JustCRM-EditOrderList'] = orderId;
// location.href = '/nomendraw';
// }
// function editOrderListView(orderId) {
// sessionStorage.removeItem('JustCRM-EditOrderList');
// send_request(orderId, 'data-exchange', 'data-edit-order-list');
// var waitOrder = setInterval(function() {
// if (typeof dataResultThree != 'undefined' && !$.isEmptyObject(dataResultThree)) {
// clearInterval(waitOrder);
// // Записываем все данные по основным полям
// localStorage['JustCRM-OrderCartList-field'] = JSON.stringify(dataResultThree.data);
// localStorage['JustCRM-OrderCartList'] = JSON.stringify(dataResultThree.table);
// // Устанавливаем куки клиента
// setCookie('cat_search_price_client_uid_list', decodeURIComponent(dataResultThree.data[1]));
// // Вы водим полный список адресов доставки по клиенту
// var deliveryAddressStr = 'Самовывоз Новый адрес доставки ';
// if (Object.keys(dataResultThree.delivery_address).length > 0) {
// for (key in dataResultThree.delivery_address) {
// var selectedAddr = '';
// if (dataResultThree.data[3][0] == dataResultThree.delivery_address[key]['UID']) selectedAddr = 'selected="selected"';
// deliveryAddressStr += '' + dataResultThree.delivery_address[key]['name'] + ' '
// }
// }
// $('#delivery_places_list').html(deliveryAddressStr);
// // Выбираем время доставки
// $('#order-delivery-time-list option[value="' + dataResultThree.data[12] + '"]').prop('selected', true);
// // Выбираем тип оплаты
// $('#order-pay-list option[value="' + dataResultThree.data[13] + '"]').prop('selected', true);
// dataResultThree = '{}';
// orderStatList(); // Последние изменения
// orderMarkerView();
// setTimeout(function() {
// $('#marker-cart-list').trigger('click');
// }, 1000);
// }
// }, 500);
// }
function editOrderList(orderId) {
sessionStorage['JustCRM-EditOrderList'] = orderId;
send_request(orderId, 'data-exchange', 'data-edit-order-list');
var waitOrder = setInterval(function() {
if (typeof dataResultThree != 'undefined' && !$.isEmptyObject(dataResultThree)) {
clearInterval(waitOrder);
// Записываем все данные по основным полям
localStorage['JustCRM-OrderCartList-field'] = JSON.stringify(dataResultThree.data);
localStorage['JustCRM-OrderCartList'] = JSON.stringify(dataResultThree.table);
// Устанавливаем куки клиента
setCookie('cat_search_price_client_uid_list', decodeURIComponent(dataResultThree.data[1]));
setCookie('cat_search_price_uid_list', decodeURIComponent(dataResultThree.data[2]));
dataResultThree = '{}';
setTimeout(function() {
location.href = '/nomendraw';
}, 1000);
}
}, 500);
}
function editOrderListView(orderId) {
sessionStorage.removeItem('JustCRM-EditOrderList');
orderStatList(); // Последние изменения
orderMarkerView();
setTimeout(function() {
openCartList();
}, 1000);
}
// Предпоказ стоимости доставки в корзине: строка «Доставка» в таблице товаров + учёт в подитоге.
// Дебаунс + кэш ответа, чтобы не дёргать сервер без изменений
var deliveryPreviewTimer = null;
var deliveryPreviewLast = '';
var deliveryPreviewData = null;
// Пересчёт подитога корзины (та же логика, что в orderStat) с добавлением строки доставки
function deliveryTotalsRender() {
var allQuant = 0;
var allSumm = 0;
var allSummOrig = 0;
$('body').find('#ordercard-list').find('.ordercard-quantity').each(function(index, element) {
var qty = Number($(element)[0].value);
allQuant += qty;
allSumm += Number($(element).parent().next().children('input').val().replace(',', '.'));
var origPriceStr = $(element).closest('.position').find('.price').attr('data-orig-price') || '';
var origPrice = parseFloat(origPriceStr.replace(',', '.'));
if (!isNaN(origPrice) && origPrice > 0) {
allSummOrig += origPrice * qty;
}
});
var deliveryPrice = 0;
if (deliveryPreviewData && deliveryPreviewData.status == 'ok' && Number(deliveryPreviewData.price) > 0) {
deliveryPrice = deliveryRoundPrice(deliveryPreviewData.price);
allQuant += 1; // доставка — одна позиция, как и в заказе после сохранения
} else if (deliveryPreviewData && deliveryPreviewData.status == 'no_zone' && Number(deliveryPreviewData.tk_price) > 0) {
// Адрес вне зон — доставка до транспортной компании (тариф от суммы товаров, бэк: dz_tk_price)
deliveryPrice = deliveryRoundPrice(deliveryPreviewData.tk_price);
allQuant += 1;
}
var cutPrice = cutServicePrice();
if (cutPrice > 0) {
allSumm += cutPrice;
allQuant += Number(cutServiceData.cuts);
}
$('.all-quant').children('strong').html(allQuant.toFixed(2));
var summHtml = (allSumm + deliveryPrice).toFixed(2).replace('.', ',');
if (!plisseCartActive() && allSummOrig > 0 && allSummOrig > allSumm + 0.01) {
summHtml += ' ' + (allSummOrig + deliveryPrice).toFixed(2).replace('.', ',') + ' ';
}
$('.all-summ').children('strong').html(summHtml);
}
// Текст подсказки «как считается доставка» у строки «Доставка» в корзине
var deliveryHelpTooltip = 'Стоимость доставки рассчитывается автоматически: адрес определяется на карте и попадает в одну из зон доставки. У каждой зоны свой тариф — фиксированная сумма или процент от суммы товаров в заказе, но не меньше минимальной стоимости доставки — 900 руб. Если адрес не попал ни в одну зону, доставка выполняется до терминала выбранной транспортной компании: при сумме заказа до 55 000 руб. — 3,5% от суммы, но не менее 900 руб.; свыше 55 000 руб. — фиксированно 1 700 руб.';
// Показ селекта транспортной компании (только для адресов вне всех зон доставки)
function deliveryTkToggle(show) {
var wrap = $('#order-delivery-tk-wrap');
if (!wrap.length) return;
if (show) {
wrap.show();
$('#order-delivery-tk').attr('class', 'validate[required]');
} else {
wrap.hide();
$('#order-delivery-tk').attr('class', 'validate[optional]');
}
}
// Отрисовка строки «Доставка» в таблице товаров корзины по кэшу deliveryPreviewData
function deliveryRowRender() {
var box = $('#delivery-price-preview');
$('#ordercard-list').children('.delivery-position').remove();
var d = deliveryPreviewData;
if (!d) {
deliveryTkToggle(false);
box.hide().empty();
deliveryTotalsRender();
return;
}
if (d.status == 'ok' && Number(d.price) > 0) {
deliveryTkToggle(false);
var priceStr = deliveryRoundPrice(d.price).toFixed(2).replace('.', ',').replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
// Строка в формате товарной позиции, но без input-полей — при сохранении её добавит сервер
var row = $('' +
' ' +
' ' +
'Доставка ' +
'' + priceStr + ' ' +
'услуга ' +
' ' +
' ' +
' ' +
' ' +
'1 ' +
'' + priceStr + ' ' +
' ');
// Производственные колонки — в том же состоянии, что и у шапки таблицы.
// Явный css(): по умолчанию .prod-field скрыт стилями, и при включённой
// галочке производства ячейкам нужен инлайновый table-cell, иначе строка съезжает
row.find('.prod-field').css('display', ($('#ordercard_form th.prod-field').first().css('display') != 'none') ? 'table-cell' : 'none');
$('#ordercard-list').append(row);
box.hide().empty();
} else if (d.status == 'pickup') {
deliveryTkToggle(false);
box.hide().empty();
} else if (d.status == 'no_zone' && Number(d.tk_price) > 0) {
// Адрес вне всех зон — доставка до транспортной компании: тариф от суммы товаров
// (бэк: dz_tk_price), показываем селект выбора ТК рядом с датой доставки
deliveryTkToggle(true);
var tkPriceStr = deliveryRoundPrice(d.tk_price).toFixed(2).replace('.', ',').replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
var tkRow = $('' +
' ' +
' ' +
'Доставка до транспортной компании ' +
'' + tkPriceStr + ' ' +
'услуга ' +
' ' +
' ' +
' ' +
' ' +
'1 ' +
'' + tkPriceStr + ' ' +
' ');
tkRow.find('.prod-field').css('display', ($('#ordercard_form th.prod-field').first().css('display') != 'none') ? 'table-cell' : 'none');
$('#ordercard-list').append(tkRow);
box.hide().empty();
} else {
// no_coords / geo_error — адрес не распознан: строка «Доставка» без цены,
// стоимость посчитает менеджер при обработке заказа
deliveryTkToggle(false);
var infoRow = $('' +
' ' +
' ' +
'Доставка дополнительно будет рассчитана менеджером ' +
' ' +
'услуга ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ' +
' ');
infoRow.find('.prod-field').css('display', ($('#ordercard_form th.prod-field').first().css('display') != 'none') ? 'table-cell' : 'none');
$('#ordercard-list').append(infoRow);
box.hide().empty();
}
deliveryTotalsRender();
}
function deliveryScheduleFields($input) {
var $form = $input.closest('form');
return {
places: $form.find('[name="delivery_places"]').val() || '%',
address: $.trim($form.find('[name="delivery_address"]').val() || ''),
coords: $.trim($form.find('[name="delivery_coords"]').val() || '')
};
}
function deliveryScheduleRefresh($input, openCalendar) {
clearTimeout($input.data('schedule-timer'));
var fields = deliveryScheduleFields($input);
var signature = JSON.stringify(fields);
var previous = $input.data('schedule-request');
if (previous) previous.abort();
var $note = $input.siblings('.delivery-schedule-note');
if (!$note.length) $note = $('', { 'class': 'delivery-schedule-note', 'aria-live': 'polite' }).css('display', 'block').insertAfter($input);
if ((fields.places === '%' || fields.places === '') && !fields.address) {
$input.prop('disabled', false).removeAttr('aria-busy').data('schedule-state', 'ready');
$input.datepicker('option', { minDate: 0, beforeShowDay: null });
$input.parent('div').hide();
$note.empty();
return;
}
$input.parent('div').show();
if (fields.places === '%%' && fields.address.length < 3) {
$input.prop('disabled', false).removeAttr('aria-busy').data('schedule-state', 'error');
$note.text('Укажите адрес, чтобы выбрать дату доставки.');
return;
}
$input.data('schedule-state', 'loading').attr('aria-busy', 'true');
$note.text('Проверяем доступные даты…');
var request = $.ajax({
url: '/ajax-send/data-delivery-schedule.php?guid=' + encodeURIComponent(guid),
type: 'POST', dataType: 'json', data: fields
}).done(function(data) {
if (JSON.stringify(deliveryScheduleFields($input)) !== signature) return;
if (!data.ok) return failed(data.message);
$input.data('schedule-state', 'ready').removeAttr('aria-busy');
$note.empty();
if (!data.active) return;
var minDate = $.datepicker.parseDate('yy-mm-dd', data.min_date);
var value = $input.val();
var selected = null;
try { selected = $.datepicker.parseDate('dd.mm.yy', value); } catch (error) {}
$input.datepicker('option', {
minDate: minDate,
beforeShowDay: function(date) { return [date.getDay() !== 0 && date.getDay() !== 6, '']; }
});
var text = '';
if (value && (!selected || selected < minDate || selected.getDay() === 0 || selected.getDay() === 6)) {
if ($input.closest('form').find('[name="edit"]').val()) {
$input.val(value);
text = 'Сохранённая дата не меняется. Для переноса выберите доступный день.';
} else {
$input.val('').trigger('input');
text = 'Прежняя дата недоступна — выберите новую.';
}
}
$note.text(text);
if (openCalendar) {
$input.data('schedule-open', true);
$input.datepicker('show');
}
}).fail(function(xhr, status) {
if (status === 'abort' || JSON.stringify(deliveryScheduleFields($input)) !== signature) return;
failed(xhr.responseJSON && xhr.responseJSON.message);
}).always(function() {
if ($input.data('schedule-request') === request) $input.removeData('schedule-request');
});
$input.data('schedule-request', request);
function failed(message) {
$input.data('schedule-state', 'error').removeAttr('aria-busy');
$note.text(message || 'Не удалось проверить даты. Нажмите поле даты, чтобы повторить.');
}
}
function deliveryScheduleInit() {
$('.date-order[data-delivery-schedule]').each(function() {
var $input = $(this);
if ($input.data('schedule-initialized')) return;
$input.data('schedule-initialized', true).datepicker({ minDate: 0 }).on('click', function() {
if (!$.datepicker._datepickerShowing && $input.data('schedule-state') !== 'loading') $input.datepicker('show');
});
$input.datepicker('option', {
beforeShow: function() {
if ($input.data('schedule-open')) {
$input.removeData('schedule-open');
$input.after($input.datepicker('widget'));
return;
}
var fields = deliveryScheduleFields($input);
if ((fields.places === '%' || fields.places === '') && !fields.address) return;
deliveryScheduleRefresh($input, true);
return false;
},
onSelect: function() { $input.trigger('input'); }
});
});
}
$(function() {
deliveryScheduleInit();
$(document).on('input change', '[name="delivery_places"], [name="delivery_address"], [name="delivery_coords"]', function() {
var $input = $(this).closest('form').find('.date-order[data-delivery-schedule]');
if (!$input.length) return;
clearTimeout($input.data('schedule-timer'));
$input.data('schedule-state', 'loading');
$input.datepicker('hide');
$input.data('schedule-timer', setTimeout(function() { deliveryScheduleRefresh($input, false); }, 500));
});
});
// Округление стоимости доставки вверх до кратности 10 руб. — дублирует бэковое dz_round_price
function deliveryRoundPrice(price) {
return Math.ceil(Math.round(Number(price) * 100) / 1000) * 10;
}
function deliveryPricePreview(delay) {
if ($('#delivery-price-preview').length === 0) return;
clearTimeout(deliveryPreviewTimer);
deliveryPreviewTimer = setTimeout(function() {
var places = $('#delivery_places').val() || '%';
var address = $.trim($('#order-delivery-address').val());
var coords = $.trim($('#order-delivery-coords').val());
// Сумма товаров корзины — база для процентных зон (строка доставки не участвует: в ней нет input-полей)
var summ = 0;
$('#ordercard-list').children('.position').each(function() {
var v = Number(String($(this).find('.ordercard-price').val()).replace(',', '.').replace(/\s/g, ''));
if (!isNaN(v)) summ += v;
});
// Распил
summ += cutServicePrice();
// Самовывоз или адрес ещё не указан
if ((places == '%' || places == '%%') && address == '' && coords == '') {
deliveryPreviewLast = '';
deliveryPreviewData = null;
deliveryRowRender();
return;
}
var signature = [places, address, coords, summ.toFixed(2)].join('|');
if (signature === deliveryPreviewLast) {
// Данные не менялись — только перерисовываем (корзина могла быть перестроена)
deliveryRowRender();
return;
}
deliveryPreviewLast = signature;
$.post('/ajax-send/data-delivery-price.php?guid=' + guid, { places: places, address: address, coords: coords, summ: summ.toFixed(2) }, function(data) {
deliveryPreviewData = (data && data.ok === true) ? data : null;
deliveryRowRender();
}, 'json').fail(function() {
deliveryPreviewLast = '';
deliveryPreviewData = null;
deliveryRowRender();
});
}, (typeof delay != 'undefined') ? delay : 600);
}
function cartCutIncluded(entry) {
if (!entry) return false;
for (var i = 0; i < entry.length; i++) if (entry[i] && entry[i].name === 'cut-included') return String(entry[i].value) === '1';
return entry[2] && /НАРЕЗКА/i.test(entry[2].value) && entry[5] && /^шт/.test(entry[5].value);
}
function cartPanelSpec(entry) {
if (!entry) return null;
for (var i = 0; i < entry.length; i++) {
if (entry[i] && entry[i].name === 'panel-spec') {
try {
var panel = JSON.parse(entry[i].value);
panel.included = cartCutIncluded(entry);
return panel;
} catch (error) { return null; }
}
}
return null;
}
function cartPanelRender($row, entry) {
var panel = cartPanelSpec(entry);
if (!panel) return;
$row.attr('data-panel-spec', '1');
$row.append($(' ', { type: 'hidden', name: 'panel_spec', 'class': 'ordercard-panel-spec', value: JSON.stringify(panel) }));
$row.find('.ordercard-length').attr({ min: '0.001', step: '0.001' });
$row.children('.length').append($('').text('Ширина, мм:'))
.append($(' ', { type: 'number', 'class': 'ordercard-panel-width', min: 1, step: 1, value: panel.width, 'aria-label': 'Ширина детали, мм' }));
$row.find('.ordercard-qty').attr({ min: 1, max: 10000, step: 1 });
$row.find('.ordercard-quantity').attr({ min: '0.0000000001', step: 'any' });
$row.children('.name').append($('', { 'class': 'panel-area' }));
cartPanelQuantity($row, panel);
}
function cartPanelSheets($row, panel) {
var length = Math.round(Number($row.find('.ordercard-length').val()) * 1000);
var width = Number(panel.width);
if (!(length > 0 && width > 0)) return 0;
var capacity = Math.floor(panel.sheet_length / length) * Math.floor(panel.sheet_width / width);
if (panel.rotate) capacity = Math.max(capacity, Math.floor(panel.sheet_length / width) * Math.floor(panel.sheet_width / length));
return capacity > 0 ? Math.ceil(Number($row.find('.ordercard-qty').val()) / capacity) : 0;
}
function cartPanelQuantity($row, panel) {
var pieces = Number($row.find('.ordercard-qty').val());
var length = Number($row.find('.ordercard-length').val());
var area = pieces * length * Number(panel.width) / 1000;
$row.find('.panel-area').text('Площадь деталей: ' + String(Number(area.toFixed(6))).replace('.', ',') + ' м²');
return panel.included ? pieces : cartPanelSheets($row, panel);
}
function cartLengthRender($row, force) {
if ($row.find('.ordercard-prod').val() != '2') return;
var $length = $row.find('.ordercard-length').attr('type', 'hidden');
var $display = $row.find('.ordercard-length-mm');
if (!$display.length) {
$display = $('
', { type: 'number', 'class': 'ordercard-length-mm', inputmode: 'numeric', 'aria-label': 'Длина, мм' });
$length.after($display);
if ($row.attr('data-panel-spec')) $display.before($('
').text('Длина, мм:'));
}
['min', 'max', 'step'].forEach(function(attribute) {
var value = $length.attr(attribute);
if (typeof value === 'undefined') $display.removeAttr(attribute);
else $display.attr(attribute, value === 'any' ? value : Number(value) * 1000);
});
$display.prop('readonly', $length.prop('readonly')).prop('disabled', $length.prop('disabled'));
if (force || !$display.is(':focus')) {
$display.val($length.val() === '' ? '' : String(Math.round(Number($length.val()) * 1000)));
}
}
function cutLengthInputApplicable($input) {
var $row = $input.closest('.position');
return $input.attr('id') === 'production-cut-length' || $input.hasClass('production-cut-length') || ($row.find('.ordercard-prod').val() == '2' && $row.attr('data-made-to-order') !== '1' && !$row.attr('data-panel-spec'));
}
function cutLengthRestrictInput($input) {
var value = String($input.val() || '').replace(',', '.');
if (/[eE]/.test(value)) value = String(Number(value));
var millimeters = $input.attr('id') === 'production-cut-length' || $input.hasClass('production-cut-length') || $input.hasClass('ordercard-length-mm');
var limited = millimeters ? value.replace(/\..*$/, '') : value.replace(/(\.\d)\d+.*$/, '$1');
if (limited !== $input.val()) $input.val(limited);
}
$(document).on('paste', '#production-cut-length, .production-cut-length, .ordercard-length, .ordercard-length-mm', function(event) {
var $input = $(this);
if (!cutLengthInputApplicable($input)) return;
var clipboard = event.originalEvent.clipboardData;
if (!clipboard) return;
var value = clipboard.getData('text').trim();
if (!/^\d+(?:[.,]\d*)?$/.test(value)) return;
event.preventDefault();
$input.val(value.replace(',', '.'));
cutLengthRestrictInput($input);
$input.trigger('input');
});
// распил
var cutServiceTimer = null;
var cutServiceLast = '';
var cutServiceData = null;
var cutServiceState = 'idle';
var cutServiceError = '';
var cutServiceRequest = 0;
var cutServiceHelpTooltip = 'В цену НАРЕЗКИ распил включён. Для обычных сэндвич-панелей оплачиваются целые листы и 30 ₽ за рез. Ширина пропила не учитывается.';
var cutPriceState = 'idle';
var cutPriceLast = '';
var cutPriceError = '';
var cutPriceRequest = 0;
var cutPriceTimer = null;
function cutPriceLines() {
var lines = [];
$('#ordercard-list').children('.position').each(function() {
var row = $(this);
if (row.attr('data-cut-included') !== '1' && !row.attr('data-panel-spec')) return;
var panel = row.attr('data-panel-spec') ? JSON.parse(row.find('.ordercard-panel-spec').val()) : null;
lines.push({key: row.attr('data-id-pos-prefix'), id: row.attr('data-id-pos'),
qty: row.find('.ordercard-qty').val(), length: row.find('.ordercard-length').val(), width: panel ? panel.width : ''});
});
return {lines: lines, client: $('#id_client').val() || undefined, category: $('#client_cat_price').val() || undefined};
}
function cutPriceBlocked() {
var data = cutPriceLines();
return data.lines.length > 0 && (cutPriceState !== 'ready' || cutPriceLast !== JSON.stringify(data));
}
function cutPricePreview() {
var data = cutPriceLines();
var signature = JSON.stringify(data);
if (!data.lines.length) { cutPriceRequest++; cutPriceState = 'idle'; cutPriceLast = ''; return; }
if (signature === cutPriceLast && (cutPriceState === 'ready' || cutPriceState === 'loading')) return;
clearTimeout(cutPriceTimer);
cutPriceLast = signature;
cutPriceState = 'loading';
cutPriceError = '';
cutServiceStatusRender();
var version = ++cutPriceRequest;
cutPriceTimer = setTimeout(function() {
data.nomen_ids = data.lines.map(function(line) { return line.id; });
$.post('/ajax-send/data-cart-recalc.php?guid=' + encodeURIComponent(guid), data, function(response) {
if (version !== cutPriceRequest) return;
if (JSON.stringify(cutPriceLines()) !== signature) { cutPricePreview(); return; }
var prices = response && response.lines || {};
var bad = data.lines.find(function(line) { return !prices[line.key] || prices[line.key].error; });
if (bad) {
cutPriceState = 'error';
cutPriceError = prices[bad.key] && prices[bad.key].error || 'Не удалось получить цену изделия.';
cutServiceStatusRender();
return;
}
var cart = JSON.parse(localStorage['JustCRM-OrderCart'] || '{}');
data.lines.forEach(function(line) {
var entry = cart[line.key];
if (!entry) return;
var price = Number(prices[line.key].price_number);
var row = $('#ordercard-list').children('.position').filter(function() { return $(this).attr('data-id-pos-prefix') === String(line.key); });
entry[8].value = String(price);
entry[10].value = String(price);
row.children('.base-price').text(price.toFixed(2).replace('.', ','));
row.children('.price').attr('data-price', price);
row.find('.ordercard-base-price').val(price);
var quantity = prices[line.key].included ? Number(line.qty) : Number(row.find('.ordercard-quantity').val());
entry[0].value = String(quantity);
row.find('.ordercard-quantity').val(quantity);
row.find('.ordercard-price').val((quantity * price).toFixed(2));
});
localStorage['JustCRM-OrderCart'] = JSON.stringify(cart);
cutPriceState = 'ready';
orderStat();
}, 'json').fail(function(xhr) {
if (version !== cutPriceRequest) return;
cutPriceState = 'error';
cutPriceError = xhr.responseJSON && xhr.responseJSON.error || 'Не удалось загрузить цены изделий. Повторите расчёт.';
cutServiceStatusRender();
});
}, 300);
}
function cutServicePrice() {
return (cutServiceData && Number(cutServiceData.price) > 0) ? Number(cutServiceData.price) : 0;
}
function cutServiceValidData(data) {
var cuts = Number(data && data.cuts);
var price = Number(data && data.price);
return data && data.ok === true && isFinite(cuts) && cuts >= 0 && Math.floor(cuts) === cuts &&
isFinite(price) && ((cuts === 0 && price === 0) || (cuts > 0 && price > 0));
}
// Позиции в нарезку — только при включённой галочке производства
function cutServiceLines() {
var fields = [];
try { fields = JSON.parse(localStorage['JustCRM-OrderCart-field'] || '[]'); } catch (e) {}
if (!Array.isArray(fields) || fields[13] != '1') return [];
var lines = [];
$('#ordercard-list').children('.position').each(function() {
if ($(this).find('.ordercard-prod').val() != '2') return;
var panel = $(this).attr('data-panel-spec') ? JSON.parse($(this).find('.ordercard-panel-spec').val()) : null;
lines.push({
id: String($(this).attr('data-id-pos') || ''),
qty: String($(this).find('.ordercard-qty').val() || '0'),
length: String($(this).find('.ordercard-length').val() || '0').replace(',', '.'),
width: panel ? panel.width : '', rotate: panel && panel.rotate ? 1 : 0,
summ: String($(this).find('.ordercard-price').val() || '0').replace(',', '.').replace(/\s/g, '')
});
});
return lines;
}
function cutServiceBlocked() {
var lines = cutServiceLines();
return cutPriceBlocked() || (lines.length > 0 && (cutServiceState !== 'ready' || cutServiceLast !== JSON.stringify(lines)));
}
function cutServiceStatusRender() {
var $form = $('#ordercard_form');
if (!$form.length) return;
var blocked = cutServiceBlocked();
var $status = $('#cut-service-status');
if (blocked && !$status.length) {
$status = $('', { id: 'cut-service-status', role: 'status', 'aria-live': 'polite' });
$form.find('.add-order').first().before($status);
}
if ($status.length) {
if (!blocked) $status.hide().empty();
else if (cutPriceState === 'error' || cutServiceState === 'error') {
$status.addClass('red').empty().append((cutPriceError || cutServiceError || 'Не удалось подтвердить стоимость распила.') + ' ')
.append($('
', { type: 'button', id: 'cut-service-retry', text: 'Повторить расчёт' })).show();
} else $status.removeClass('red').text(cutPriceBlocked() ? 'Загружаем цену по размерам…' : 'Рассчитываем распил…').show();
}
$form.find('.add-order').prop('disabled', Boolean($form.data('order-submitting')));
}
function cutServiceRowRender() {
$('#ordercard-list').children('.cut-service-position').remove();
var price = cutServicePrice();
if (price > 0) {
var priceStr = price.toFixed(2).replace('.', ',').replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
var cutPriceStr = (price / Number(cutServiceData.cuts)).toFixed(2).replace('.', ',');
var row = $('' +
' ' +
' ' +
'Распил (услуга) ' +
'' + cutPriceStr + ' ' +
'рез ' +
' ' +
' ' +
' ' +
' ' +
'' + Number(cutServiceData.cuts) + ' ' +
'' + priceStr + ' ' +
' ');
row.find('.prod-field').css('display', ($('#ordercard_form th.prod-field').first().css('display') != 'none') ? 'table-cell' : 'none');
var delivery = $('#ordercard-list').children('.delivery-position').first();
if (delivery.length) delivery.before(row); else $('#ordercard-list').append(row);
}
deliveryTotalsRender();
}
function cutServicePreview() {
if ($('#ordercard-list').length === 0) return;
var lines = cutServiceLines();
var signature = JSON.stringify(lines);
if (!lines.length) {
clearTimeout(cutServiceTimer);
var hadCut = cutServicePrice() > 0;
cutServiceRequest++;
cutServiceLast = '';
cutServiceState = 'idle';
cutServiceData = null;
localStorage.removeItem('JustCRM-OrderCart-cut-preview');
cutServiceRowRender();
cutServiceStatusRender();
if (hadCut) deliveryPricePreview();
return;
}
if (signature === cutServiceLast && (cutServiceState === 'ready' || cutServiceState === 'loading')) {
cutServiceRowRender();
cutServiceStatusRender();
return;
}
clearTimeout(cutServiceTimer);
cutServiceLast = signature;
cutServiceState = 'loading';
cutServiceError = '';
cutServiceData = null;
try {
var cached = JSON.parse(localStorage.getItem('JustCRM-OrderCart-cut-preview') || 'null');
if (cached && cached.signature === signature && cutServiceValidData(cached.data)) cutServiceData = cached.data;
} catch (e) {}
cutServiceRowRender();
cutServiceStatusRender();
var request = ++cutServiceRequest;
cutServiceTimer = setTimeout(function() {
$.post('/ajax-send/data-cut-service-price.php?guid=' + encodeURIComponent(guid), { lines: lines }, function(data) {
if (request !== cutServiceRequest || JSON.stringify(cutServiceLines()) !== signature) return;
if (cutServiceValidData(data)) {
cutServiceData = data;
cutServiceState = 'ready';
localStorage.setItem('JustCRM-OrderCart-cut-preview', JSON.stringify({ signature: signature, data: data }));
} else cutServiceState = 'error';
cutServiceRowRender();
cutServiceStatusRender();
deliveryPricePreview();
}, 'json').fail(function(xhr) {
if (request !== cutServiceRequest || JSON.stringify(cutServiceLines()) !== signature) return;
cutServiceState = 'error';
cutServiceError = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : '';
cutServiceRowRender();
cutServiceStatusRender();
});
}, 300);
}
$(document).on('click', '#cut-service-retry', function() {
cutPriceLast = '';
cutPricePreview();
cutServicePreview();
});
var _warehouseRemainsCache = [];
var _warehouseRemainsPending = [];
var _warehouseRemainsSequence = 0;
function loadWarehouseRemains(warehouse, nomenIds, options) {
options = options || {};
var orderId = Number(options.orderId || 0);
if (!(orderId > 0 && orderId % 1 === 0)) orderId = 0;
var ids = [];
$.each(nomenIds, function(_, value) {
var id = Number(value);
if (id > 0 && id % 1 === 0 && ids.indexOf(id) === -1) ids.push(id);
});
function covers(record) {
return record.warehouse === warehouse && record.guid === guid && record.orderId === orderId && ids.every(function(id) { return record.ids.indexOf(id) !== -1; });
}
var now = Date.now();
_warehouseRemainsCache = _warehouseRemainsCache.filter(function(record) { return now - record.time < 30000; });
var record = null;
if (!options.force) {
$.each(_warehouseRemainsPending, function(_, pending) {
if (covers(pending) && (!record || pending.sequence > record.sequence)) record = pending;
});
for (var index = _warehouseRemainsCache.length - 1; index >= 0; index--) {
if (covers(_warehouseRemainsCache[index]) && (!record || _warehouseRemainsCache[index].sequence > record.sequence)) {
var cached = $.Deferred().resolve(_warehouseRemainsCache[index].result).promise();
cached.abort = function() {};
return cached;
}
}
}
if (!record) {
record = { warehouse: warehouse, guid: guid, orderId: orderId, ids: ids, users: 0, sequence: ++_warehouseRemainsSequence, request: requestWarehouseRemains(warehouse, ids, orderId) };
_warehouseRemainsPending.push(record);
record.request.done(function(result) {
function overlaps(cached) {
return cached.warehouse === warehouse && cached.guid === record.guid && cached.orderId === orderId && ids.some(function(id) { return cached.ids.indexOf(id) !== -1; });
}
if (_warehouseRemainsCache.some(function(cached) { return overlaps(cached) && cached.sequence > record.sequence; })) return;
_warehouseRemainsCache = _warehouseRemainsCache.filter(function(cached) { return !overlaps(cached); });
_warehouseRemainsCache.push({ warehouse: warehouse, guid: record.guid, orderId: orderId, ids: ids, result: result, time: Date.now(), sequence: record.sequence });
_warehouseRemainsCache = _warehouseRemainsCache.slice(-16);
}).always(function() {
_warehouseRemainsPending = _warehouseRemainsPending.filter(function(pending) { return pending !== record; });
});
}
var result = $.Deferred();
var active = true;
record.users++;
var promise = result.promise();
promise.abort = function() {
if (!active) return;
active = false;
record.users--;
result.reject('abort');
if (!record.users && record.request.state() === 'pending') record.request.abort();
};
record.request.done(function(response) {
if (active) result.resolve(response);
}).fail(function(message) {
if (active) result.reject(message);
});
result.always(function() {
if (active) {
active = false;
record.users--;
}
});
return promise;
}
function requestWarehouseRemains(warehouse, nomenIds, orderId) {
var result = $.Deferred();
var request = null;
var cancelled = false;
var retryTimer = null;
var retryDelays = [2000, 5000];
var attempt = 0;
var requestGuid = guid;
var ids = [];
$.each(nomenIds, function(_, id) {
id = Number(id);
if (id > 0 && id % 1 === 0 && ids.indexOf(id) === -1) ids.push(id);
});
var promise = result.promise();
promise.abort = function() {
if (cancelled || result.state() !== 'pending') return;
cancelled = true;
clearTimeout(retryTimer);
retryTimer = null;
if (request) request.abort();
result.reject('abort');
};
if (!warehouse || warehouse === 'all' || !ids.length) {
result.resolve({ remains: {}, source: 'crm', message: '' });
return promise;
}
function validQuantity(entry) {
return entry && (typeof entry.quantity === 'number' || typeof entry.quantity === 'string') &&
String(entry.quantity).trim() !== '' && isFinite(Number(entry.quantity));
}
function failedResponse(response) {
return response && ((response.error && response.error !== 'false' && response.error !== '0') ||
response.success === false || response.success === 'false' || response.success === 0 || response.success === '0' ||
response.code === 'unauthorized' || response.code === 'invalid_request');
}
function fetchRemains(endpoint, timeout) {
return $.ajax({
url: '/ajax-send/' + endpoint + '.php?guid=' + encodeURIComponent(requestGuid),
type: 'POST',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
data: JSON.stringify({ warehouse: warehouse, nomen: ids, order_id: orderId }),
timeout: timeout
});
}
function fallback(live, received) {
if (cancelled) return;
request = fetchRemains('send_request_remains', 10000);
request.done(function(cached) {
if (cancelled) return;
if (failedResponse(cached)) {
result.reject('Не удалось проверить наличие товаров. Попробуйте позже.');
return;
}
var map = {};
var liveCount = 0;
var complete = true;
$.each(ids, function(_, id) {
if (validQuantity(live[id])) {
map[id] = live[id];
liveCount++;
} else if (cached && validQuantity(cached[id])) {
map[id] = cached[id];
} else {
complete = false;
}
});
if (!complete) {
result.reject('Не удалось проверить наличие товаров. Попробуйте позже.');
return;
}
result.resolve({
remains: map,
source: liveCount ? 'mixed' : 'crm',
message: received ? '1С вернула неполные остатки. Для недостающих товаров показаны сохранённые данные выбранного склада.' :
'1С недоступна. Показаны сохранённые остатки выбранного склада; уточните наличие у менеджера.'
});
}).fail(function(_, status) {
if (!cancelled) result.reject(status === 'abort' ? 'abort' : 'Не удалось проверить наличие товаров. Попробуйте позже.');
});
}
function retry(live, received) {
if (cancelled) return;
if (attempt > retryDelays.length) {
fallback(live, received);
return;
}
retryTimer = setTimeout(fetchLive, retryDelays[attempt - 1]);
}
function fetchLive() {
retryTimer = null;
if (cancelled) return;
attempt++;
request = fetchRemains('send_request_remains_1C', 28000);
request.done(function(live) {
if (cancelled) return;
if (live && (live.code === 'unauthorized' || live.code === 'invalid_request')) {
result.reject('Не удалось выполнить запрос остатков. Обновите страницу.');
return;
}
var failed = failedResponse(live);
var map = {};
var complete = !failed;
$.each(ids, function(_, id) {
if (!failed && live && validQuantity(live[id])) map[id] = live[id];
else complete = false;
});
if (complete) result.resolve({ remains: map, source: '1c', message: 'Остатки обновлены из 1С.' });
else retry(map, Boolean(live && !failed));
}).fail(function(xhr, status) {
if (cancelled) return;
if (status === 'abort') result.reject('abort');
else if (xhr.status === 400 || xhr.status === 401 || xhr.status === 403) result.reject('Не удалось выполнить запрос остатков. Обновите страницу.');
else retry({}, false);
});
}
fetchLive();
return promise;
}
var _cartWarehouseRequest = null;
var cartCutStockResult = null;
var cartCutStockSignature = '';
var cartCutStockRequest = null;
var cartCutStockTimer = null;
function cartCutStockActive() {
return $('#ordercard-list .position[data-cut-included="1"]').length > 0;
}
function cartCutStockData(warehouse) {
var positions = {};
$('#ordercard-list').children('.position').each(function() {
var row = $(this);
positions[row.attr('data-id-pos-prefix')] = [0,
String(row.find('.ordercard-quantity').val() || '').replace(',', '.'), '', 0,
row.find('.ordercard-prod').val(), row.find('.ordercard-qty').val(),
row.find('.ordercard-length').val(), '', row.find('.ordercard-panel-spec').val() || ''];
});
return {warehouse: warehouse || $('#order-warehouse').val() || '', positions: positions,
production: $('#order_prod').prop('checked')};
}
function cartCutStockBlocked() {
return cartCutStockActive() && (!cartCutStockResult || cartCutStockSignature !== JSON.stringify(cartCutStockData()));
}
function requestCartCutStock(data) {
return $.ajax({url: '/ajax-send/data-cart-stock.php?guid=' + encodeURIComponent(guid),
type: 'POST', contentType: 'application/json', dataType: 'json', data: JSON.stringify(data)});
}
function cartCutStockApply(data, signature) {
if (signature !== JSON.stringify(cartCutStockData())) return;
cartCutStockResult = data;
cartCutStockSignature = signature;
$('#ordercard-list').children('.position').each(function() {
var row = $(this);
var line = data.lines[row.attr('data-id-pos-prefix')];
if (!line) return;
row.children('.remains').text(line.quantity + ' ' + line.unit + ' материала')
.attr('data-stock-quantity', line.quantity).attr('data-warehouse-unknown', '0')
.attr('title', line.source_name);
row.css('background-color', line.quantity === 0 ? 'tomato' : 'unset');
});
}
function cartCutStockPreview() {
if (!cartCutStockActive()) { cartCutStockResult = null; cartCutStockSignature = ''; return; }
if (_cartWarehouseRequest) return;
var data = cartCutStockData();
if (!data.warehouse) return;
var signature = JSON.stringify(data);
if (signature === cartCutStockSignature && cartCutStockResult) return;
clearTimeout(cartCutStockTimer);
if (cartCutStockRequest) cartCutStockRequest.abort();
cartCutStockResult = null;
cartWarehouseStatus('Проверяем наличие материала для нарезки…', 'loading');
cartCutStockTimer = setTimeout(function() {
cartCutStockRequest = requestCartCutStock(data).done(function(result) {
if (signature !== JSON.stringify(cartCutStockData())) return;
cartCutStockApply(result, signature);
cartWarehouseStatus('Наличие материала для нарезки проверено.', 'crm');
}).fail(function(xhr, status) {
if (status === 'abort' || signature !== JSON.stringify(cartCutStockData())) return;
cartWarehouseStatus(xhr.responseJSON && xhr.responseJSON.error || 'Не удалось проверить наличие материала.', 'error');
});
}, 250);
}
function loadCartWarehouse(warehouse, ids, options) {
if (!cartCutStockActive()) return loadWarehouseRemains(warehouse, ids, options);
var deferred = $.Deferred();
var current;
var data = cartCutStockData(warehouse);
var signature = JSON.stringify(data);
cartCutStockResult = null;
clearTimeout(cartCutStockTimer);
if (cartCutStockRequest) cartCutStockRequest.abort();
var promise = deferred.promise();
promise.abort = function() { deferred.reject('abort'); if (current) current.abort(); };
function failed(xhr, status) {
deferred.reject(status === 'abort' ? 'abort' : xhr.responseJSON && xhr.responseJSON.error || 'Не удалось проверить наличие материала.');
}
current = requestCartCutStock(data).done(function(plan) {
if (deferred.state() !== 'pending') return;
current = loadWarehouseRemains(warehouse, ids.concat(plan.material_ids), options).done(function(result) {
if (deferred.state() !== 'pending') return;
current = requestCartCutStock(data).done(function(allocation) {
result.cutStock = allocation;
result.cutSignature = signature;
deferred.resolve(result);
}).fail(failed);
}).fail(function(message) { deferred.reject(message); });
}).fail(failed);
return promise;
}
var _cartWarehouseSequence = 0;
function cancelCartWarehouse() {
_cartWarehouseSequence++;
var request = _cartWarehouseRequest;
_cartWarehouseRequest = null;
if (request) request.abort();
}
// Добавление позиции в общую корзину отдельной строкой
function orderCartAddEntry(nomenId, entry, minShipment) {
var plisseEntry = entry.some(function(field) { return field.name === 'plisse-quote'; });
var hasCart = Object.keys(JSON.parse(localStorage.getItem('JustCRM-OrderCart') || '{}')).length > 0;
if (hasCart && plisseCartActive() !== plisseEntry) { popup('bad', 'Внимание!', 'Плиссе оформляются отдельным заказом. Сначала оформите или очистите корзину.', true, false); return null; }
nomenId = String(nomenId);
var cart = JSON.parse(localStorage.getItem('JustCRM-OrderCart') || '{}');
var sort = JSON.parse(localStorage.getItem('JustCRM-OrderCart-Sort') || '{}');
var shipment = JSON.parse(localStorage.getItem('JustCRM-OrderCart-Shipment') || '{}');
var maxSuffix = 0;
var maxSort = 0;
$.each(cart, function(key) {
var parts = key.split('_');
if (parts[0] === nomenId && parts.length > 1) maxSuffix = Math.max(maxSuffix, Number(parts[1]) || 0);
});
$.each(sort, function(_, value) { maxSort = Math.max(maxSort, Number(value) || 0); });
var key = nomenId;
if (typeof cart[nomenId] !== 'undefined') {
var renamed = nomenId + '_' + (maxSuffix + 1);
cart[renamed] = cart[nomenId];
sort[renamed] = sort[nomenId];
shipment[renamed] = shipment[nomenId];
delete cart[nomenId];
delete sort[nomenId];
delete shipment[nomenId];
key = nomenId + '_' + (maxSuffix + 2);
} else if (maxSuffix) {
key = nomenId + '_' + (maxSuffix + 1);
}
cart[key] = entry;
sort[key] = maxSort + 1;
shipment[key] = String(minShipment || 1);
localStorage.setItem('JustCRM-OrderCart', JSON.stringify(cart));
localStorage.setItem('JustCRM-OrderCart-Sort', JSON.stringify(sort));
localStorage.setItem('JustCRM-OrderCart-Shipment', JSON.stringify(shipment));
return key;
}
function orderCartProductionDefaults(entry, minShipment, plainQuantity) {
if (!entry || !entry[11] || entry[11].value != '2' || cartCutIncluded(entry)) return false;
var quantity = Number(String(entry[0].value).replace(',', '.'));
if (!entry.some(function(field) { return field && field.name === 'plain-quantity'; })) {
entry.push({ name: 'plain-quantity', value: String(typeof plainQuantity === 'undefined' ? quantity : plainQuantity) });
}
if (!entry[12] || !entry[13] || (Number(entry[12].value) > 0 && Number(entry[13].value) > 0)) return false;
var bar = Number(String(minShipment || '0').replace(',', '.'));
var pieces = 1;
var length = quantity;
if (bar > 1) {
pieces = Math.max(1, Math.round(quantity / bar));
length = bar;
}
entry[12].value = String(pieces);
entry[13].value = Number(length).toFixed(2);
entry.push({ name: 'cut-auto', value: entry[12].value + '|' + entry[13].value });
return true;
}
// Включает в корзине галочку «Есть позиции для производства»
function orderCartEnableProduction() {
if (plisseCartActive()) return;
var cart = JSON.parse(localStorage.getItem('JustCRM-OrderCart') || '{}');
var shipment = JSON.parse(localStorage.getItem('JustCRM-OrderCart-Shipment') || '{}');
$.each(cart, function(key, entry) { orderCartProductionDefaults(entry, shipment[key]); });
localStorage.setItem('JustCRM-OrderCart', JSON.stringify(cart));
var fields = cartWarehouseFields();
if (!Array.isArray(fields[3]) || (fields[3][0] === '%' && !fields[3][1])) fields[3] = ['%', 'Самовывоз'];
fields[13] = 1;
localStorage.setItem('JustCRM-OrderCart-field', JSON.stringify(fields));
}
// Позиция корзины делается под заказ (гибка): остатков нет, в лист ожидания не уходит
function cartEntryMadeToOrder(entry) {
if (!Array.isArray(entry)) return false;
for (var i = 0; i < entry.length; i++) {
if (entry[i] && entry[i].name === 'made-to-order' && String(entry[i].value) === '1') return true;
}
return false;
}
function cartSyncBendingLength(entry) {
if (!cartEntryMadeToOrder(entry) || !entry[13] || !entry[14]) return false;
var length = Number(String(entry[13].value).replace(',', '.'));
if (!isFinite(length) || length <= 0) return false;
var comment = String(entry[14].value || '');
var marker = comment.lastIndexOf('Гибка:');
if (marker === -1) return false;
var updated = comment.slice(0, marker) + comment.slice(marker).replace(/(, длина )\d+( мм)/, function(_, prefix, suffix) {
return prefix + Math.round(length * 1000) + suffix;
});
if (updated === comment) return false;
entry[14].value = updated;
return true;
}
function cartWarehouseFields() {
var fields;
try { fields = JSON.parse(localStorage['JustCRM-OrderCart-field'] || '[]'); } catch (e) { fields = []; }
if (!Array.isArray(fields)) fields = [];
if (!fields.length) {
fields = [$('#order-client').val() || '', $('#id_client').val() || '', $('#client_cat_price').val() || '',
[$('#delivery_places').val() || '%', $('#delivery_places option:selected').text() || ''],
$('#order_client_comments').val() || '', $('#order-delivery-address').val() || '',
$('#order-delivery-coords').val() || '', $('#order_status').val() || '', $('#order-warehouse').val() || '',
$('#order-delivery-date').val() || '', $('#order-delivery-time').val() || '', $('#order-pay').val() || '',
$('#ordercard-edit-flag').val() || '', $('#order_prod').prop('checked') ? 1 : 0, $('#order-delivery-tk').val() || ''];
}
return fields;
}
function cartWarehouseStatus(message, state) {
var $select = $('#order-warehouse');
var $form = $('#ordercard_form');
var clientCart = $form.data('client-cart') === true;
var $status = $('#order-warehouse-status');
if (!$status.length && $select.length && !clientCart) {
$status = $('', { id: 'order-warehouse-status', role: 'status', 'aria-live': 'polite' });
$status.append($('
'));
$select.closest('form').find('.add-order').first().before($status);
}
$status.attr('data-state', state).toggleClass('red', state === 'error');
$status.children('span').text(message);
$status.toggle(!clientCart && Boolean(message));
$('#ordercard-list').attr('aria-busy', state === 'loading' ? 'true' : 'false');
$('#ordercard_form .add-order').prop('disabled', Boolean($('#ordercard_form').data('order-submitting')));
}
function cartWarehouseApply(remains, loading) {
var cart;
try { cart = JSON.parse(localStorage['JustCRM-OrderCart'] || '{}'); } catch (e) { cart = {}; }
$.each(cart, function(key, row) {
if (!row || !row[1] || !row[6]) return;
var entry = remains[String(row[1].value)];
var quantity = entry && entry.quantity !== null ? Number(entry.quantity) : null;
row[6].value = quantity;
});
localStorage['JustCRM-OrderCart'] = JSON.stringify(cart);
$('#ordercard-list').children('.position').each(function() {
var $row = $(this);
if ($row.attr('data-made-to-order') === '1') {
$row.children('.remains').text('под заказ').attr('data-warehouse-unknown', '0');
$row.css('background-color', 'unset');
return;
}
var entry = remains[String($row.attr('data-id-pos'))];
var quantity = entry && entry.quantity !== null ? Number(entry.quantity) : null;
$row.children('.remains').text(quantity === null ? (loading ? '…' : '—') : quantity)
.attr('data-warehouse-unknown', quantity === null ? '1' : '0').removeAttr('data-stock-quantity');
$row.css('background-color', quantity === 0 ? 'tomato' : 'unset');
});
}
function refreshCartWarehouse(warehouse, options) {
options = options || {};
warehouse = warehouse || '';
var sequence = ++_cartWarehouseSequence;
var previousRequest = _cartWarehouseRequest;
_cartWarehouseRequest = null;
var fields = cartWarehouseFields();
var orderId = Number(fields[12] || 0);
options = $.extend({}, options, { orderId: orderId > 0 && orderId % 1 === 0 ? orderId : 0 });
fields[8] = warehouse;
localStorage['JustCRM-OrderCart-field'] = JSON.stringify(fields);
$('#order-warehouse').val(warehouse);
if (!warehouse) {
if (previousRequest) previousRequest.abort();
cartWarehouseApply({}, false);
cartWarehouseStatus('Выберите склад, чтобы обновить остатки.', 'idle');
return;
}
var cart;
try { cart = JSON.parse(localStorage['JustCRM-OrderCart'] || '{}'); } catch (e) { cart = {}; }
var ids = [];
$.each(cart, function(_, row) {
if (row && row[1] && !cartEntryMadeToOrder(row) && ids.indexOf(Number(row[1].value)) === -1) ids.push(Number(row[1].value));
});
if (!ids.length) {
if (previousRequest) previousRequest.abort();
cartWarehouseApply({}, false);
cartWarehouseStatus('', 'idle');
return;
}
cartWarehouseApply({}, true);
cartWarehouseStatus('Обновляем остатки выбранного склада…', 'loading');
var pending = loadCartWarehouse(warehouse, ids, options);
if (previousRequest) previousRequest.abort();
_cartWarehouseRequest = pending;
pending.done(function(result) {
if (sequence !== _cartWarehouseSequence) return;
var currentCart;
try { currentCart = JSON.parse(localStorage['JustCRM-OrderCart'] || '{}'); } catch (e) { currentCart = {}; }
var added = false;
$.each(currentCart, function(_, row) {
if (row && row[1] && !cartEntryMadeToOrder(row) && ids.indexOf(Number(row[1].value)) === -1) added = true;
});
if (added) {
refreshCartWarehouse(warehouse, options);
return;
}
cartWarehouseApply(result.remains, false);
if (result.cutStock) cartCutStockApply(result.cutStock, result.cutSignature);
cartWarehouseStatus(result.message, result.source);
}).fail(function(message) {
if (sequence !== _cartWarehouseSequence || message === 'abort') return;
cartWarehouseApply({}, false);
cartWarehouseStatus(message, 'error');
if (!options.silent && $('#ordercard_form').data('client-cart') !== true && $('#ordercard_form').is(':visible')) popup('bad', 'Внимание!', message, true, false);
}).always(function() {
if (sequence === _cartWarehouseSequence) {
_cartWarehouseRequest = null;
cartCutStockPreview();
}
});
return pending;
}
var orderCardRestoringFields = false;
function orderStat(quantityField, flag) {
console.log('Обновление корзины:', quantityField, flag)
// Отображаем применённые промокоды
promoRenderTags();
// Все позиции корзины
if (typeof localStorage['JustCRM-OrderCart'] == 'undefined') localStorage['JustCRM-OrderCart'] = '{}';
if (typeof localStorage['JustCRM-OrderCart-field'] == 'undefined') localStorage['JustCRM-OrderCart-field'] = '[]';
var orderCartAll = JSON.parse(localStorage['JustCRM-OrderCart']);
var orderCartAllField = JSON.parse(localStorage['JustCRM-OrderCart-field']);
var orderCartAllShipment = JSON.parse(localStorage['JustCRM-OrderCart-Shipment']);
updateOrderCardAction();
if (typeof quantityField != 'undefined') {
// Изменение кол-ва товара c пересчетом
if (quantityField.hasClass('ordercard-quantity')) {
// Проверки только если установлен нужый флаг
if (typeof flag != 'undefined' && flag == 'minShipment' && quantityField.closest('.position').attr('data-made-to-order') !== '1' && !(quantityField.closest('.position').attr('data-panel-spec') && orderCartAllField[13] == '1')) {
// Проверяем мин. отгрузочную норму
var minShipment = quantityField.parents('tr').find('.ordercard-min-shipment').val();
if (Number(quantityField.val()) < Number(minShipment)) {
popup('bad', 'Ошибка!', 'Кол-во не может быть менее отгрузочной нормы! Минимум: ' + minShipment + ' ед.', true, false);
quantityField.attr('value', minShipment);
quantityField.val(minShipment);
}
// Округляем к большему исходя из кратности — с подтверждением
var currentVal = parseNomenQuantityValue(quantityField.val());
var roundedVal = roundMultiple(currentVal, minShipment);
if (roundedVal !== currentVal) {
var prevVal = quantityField.data('prev-val') || currentVal;
window.nomenRoundConfirm = { $input: quantityField, originalVal: currentVal, roundedVal: roundedVal, minShip: parseNomenQuantityValue(minShipment), prevVal: prevVal };
popup('atten', 'Округление количества', 'Значение ' + formatNomenQuantityValue(currentVal) + ' будет округлено до ' + formatNomenQuantityValue(roundedVal) + ' ед. (кратность/партия: ' + formatNomenQuantityValue(minShipment) + ' ед.). Применить?', false, false, '', 'nomenRoundConfirmAcceptCart', '', '');
$('.popup').last().find('.wind').css('width', '25%');
// «Нет» или закрытие крестиком — вернуть предыдущее кратное значение
$('.popup').last().find('.close-popup').one('click', function() {
nomenRoundConfirmRejectCart();
});
$(document).one('click.nomenRoundCart', '.close', function() {
nomenRoundConfirmRejectCart();
});
return;
}
orderCartAll[quantityField.parents('.position').data('id-pos-prefix')][0].value = quantityField.val();
}
// Пересчитываем цену в связи с изменениями кол-ва
var summPos = Number(quantityField.parent().next('[data-price]').attr('data-price').replace(',', '.').replace(' ', '')) * Number(quantityField.val());
quantityField.parent().next('.price').children('input').val(summPos.toFixed(2).replace('.', ',').replace(' ', ''));
}
// Изменение кол-ва и длины для нарезки в производство
if (quantityField.hasClass('ordercard-qty')) {
orderCartAll[quantityField.parents('.position').data('id-pos-prefix')][12].value = quantityField.val().replace(',', '.').replace(' ', '');
}
if (quantityField.hasClass('ordercard-length')) {
orderCartAll[quantityField.parents('.position').data('id-pos-prefix')][13].value = Number(quantityField.val().replace(',', '.').replace(' ', '')).toFixed(quantityField.closest('.position').attr('data-panel-spec') ? 3 : 2);
}
if (quantityField.hasClass('ordercard-qty') || quantityField.hasClass('ordercard-length') || quantityField.hasClass('ordercard-panel-width')) {
var curPosLine = quantityField.parents('.position');
var entry = orderCartAll[curPosLine.attr('data-id-pos-prefix')];
var panel = cartPanelSpec(entry);
if (panel) {
panel.width = Number(curPosLine.find('.ordercard-panel-width').val());
for (var pi = 0; pi < entry.length; pi++) if (entry[pi].name === 'panel-spec') entry[pi].value = JSON.stringify(panel);
curPosLine.find('.ordercard-panel-spec').val(JSON.stringify(panel));
}
var prodQty = Number(curPosLine.find('.ordercard-qty').val().replace(',', '.').replace(' ', ''));
var prodLength = Number(curPosLine.find('.ordercard-length').val().replace(',', '.').replace(' ', '')).toFixed(panel ? 3 : 2);
var fullQty = panel ? cartPanelQuantity(curPosLine, panel) : (curPosLine.attr('data-cut-included') === '1' ? prodQty : prodQty * prodLength);
var fullSumm = fullQty * Number(curPosLine.find('[data-price]').attr('data-price').replace(',', '.').replace(' ', ''));
if (isFinite(fullQty) && (fullQty !== 0 || curPosLine.attr('data-made-to-order') === '1')) curPosLine.find('.ordercard-quantity').val(panel ? String(Number(fullQty.toFixed(10))) : fullQty.toFixed(2));
if (!isNaN(fullSumm)) curPosLine.find('.ordercard-price').val(String(fullSumm.toFixed(2)).replace(',', '.'));
// Пересчитываем цену в связи с изменениями кол-ва
orderCartAll[quantityField.parents('.position').data('id-pos-prefix')][0].value = curPosLine.find('.ordercard-quantity').val();
}
// Включение/Отключение режима заказов для производства
if (quantityField.hasClass('order_prod')) {
var orderProd = 0;
if (quantityField.prop('checked')) {
orderProd = 1;
}
if (quantityField.is('input')) orderProdToggleQuantity(orderCartAll, orderProd === 1);
orderCartAllField[13] = orderProd;
}
// Комментарий - номер заказа (в строке товара)
if (quantityField.hasClass('ordercard-num-order')) {
orderCartAll[quantityField.parents('.position').data('id-pos-prefix')][14].value = quantityField.val();
}
}
function calcPrice(el) {
var curPosLine = el.parents('.position');
var panel = cartPanelSpec(orderCartAll[curPosLine.attr('data-id-pos-prefix')]);
var prodQty = Number(curPosLine.find('.ordercard-qty').val().replace(',', '.').replace(' ', ''));
var prodLength = Number(curPosLine.find('.ordercard-length').val().replace(',', '.').replace(' ', '')).toFixed(panel ? 3 : 2);
var fullQty = panel ? cartPanelQuantity(curPosLine, panel) : (curPosLine.attr('data-cut-included') === '1' ? prodQty : prodQty * prodLength);
var fullSumm = fullQty * Number(curPosLine.find('[data-price]').attr('data-price').replace(',', '.').replace(' ', ''));
if (isFinite(fullQty) && (fullQty !== 0 || curPosLine.attr('data-made-to-order') === '1')) curPosLine.find('.ordercard-quantity').val(panel ? String(Number(fullQty.toFixed(10))) : fullQty.toFixed(2));
return (!isNaN(fullSumm)) ? String(fullSumm.toFixed(2)).replace(',', '.') : 0;
}
// Блокируем/разблокируем поле ввода кол-во позиций, которые заказываются на производство
// если стоит галочка для производства
// Пересчитываем цену
$('#ordercard-list').find('.ordercard-prod[value="2"]').each(function(i, el) {
var $cutLength = $(el).closest('.position').find('.ordercard-length');
if (cutLengthInputApplicable($cutLength)) $cutLength.attr({ min: '0.1', step: '0.1' });
var bendingEntry = orderCartAll[$(el).closest('.position').data('id-pos-prefix')];
if (cartEntryMadeToOrder(bendingEntry)) {
$cutLength.attr({ min: '0.02', max: '3', step: '0.01' });
$(el).closest('.position').find('.ordercard-qty').attr({ min: '1', max: '1000', step: '1' });
}
if (cartSyncBendingLength(bendingEntry)) {
$(el).closest('.position').find('.ordercard-num-order').val(bendingEntry[14].value);
}
if (orderCartAllField[13] == '1') {
if (typeof quantityField == 'undefined' || (typeof quantityField != 'undefined' && !quantityField.hasClass('ordercard-price'))) {
$(el).parents('.position').find('.ordercard-price').val(calcPrice($(el)));
var prodEntry = orderCartAll[$(el).parents('.position').data('id-pos-prefix')];
if (prodEntry) prodEntry[0].value = $(el).parents('.position').find('.ordercard-quantity').val();
}
}
cartLengthRender($(el).closest('.position'));
if (orderCartAllField[13] == '1') {
$(el).parents('.position').find('.ordercard-quantity').prop('readonly', true);
} else {
$(el).parents('.position').find('.ordercard-quantity').prop('readonly', $(el).parents('.position').attr('data-cut-included') === '1');
}
});
// Скрываем поля ввода позиций, которые заказываются на производство
$('#ordercard-list').find('.ordercard-prod[value="1"]').each(function(index, element) {
$(element).parents('.position').find('.ordercard-qty').hide();
$(element).parents('.position').find('.ordercard-length, .ordercard-length-mm').hide();
$(element).parents('.position').find('.ordercard-num-order').hide();
});
// Сохранение остатков — только по товарным позициям: у строки предпоказа
// «Доставка» (tr.delivery-position) тоже есть td.remains, но её нет в orderCartAll
$('body').find('#ordercard-list').children('.position').find('.remains').each(function(index, element) {
var searchKey = $(element).parents('.position').attr('data-id-pos-prefix');
if (typeof searchKey == 'undefined' || typeof orderCartAll[searchKey] == 'undefined') return;
if ($(element).parents('.position').attr('data-made-to-order') === '1') return;
orderCartAll[searchKey][6].value = $(element).attr('data-warehouse-unknown') === '1' ? null : Number($(element).attr('data-stock-quantity') || $(element).text());
// Если нет доступного остатка, то подсвечиваем строку
if (Number($(element).text()) === 0) {
$(element).parent('tr').css({ 'background-color': 'tomato' });
} else {
$(element).parent('tr').css({ 'background-color': 'unset' });
}
});
// Записываем изменения
localStorage['JustCRM-OrderCart-field'] = JSON.stringify(orderCartAllField);
localStorage['JustCRM-OrderCart'] = JSON.stringify(orderCartAll);
// Пересчет сумм: подытог сразу с учётом закэшированной строки доставки
deliveryTotalsRender();
cartCutStockPreview();
// Сумма корзины могла измениться — обновляем предпоказ стоимости доставки:
// у процентных зон стоимость зависит от суммы, строка «Доставка» и подытог
// перерисуются в deliveryRowRender (дебаунс и кэш внутри deliveryPricePreview)
deliveryPricePreview();
// Изменение полей кроме списка товаров
if (typeof localStorage['JustCRM-OrderCart-field'] != 'undefined' && localStorage['JustCRM-OrderCart-field'] != '[]') {
var orderFAll = JSON.parse(localStorage['JustCRM-OrderCart-field']);
orderCardRestoringFields = true;
if (orderFAll[0] != '') $('#order-client').val(orderFAll[0]);
if (orderFAll[1] != '') $('#id_client').val(orderFAll[1]);
if (orderFAll[2] != '') $('#client_cat_price').val(orderFAll[2]);
// Удаляем старое значение и добавляем новое
//console.log(orderFAll, orderFAll[3][0]);
if (typeof orderFAll[3] != 'undefined' && orderFAll[3] !== null && typeof orderFAll[3][0] != 'undefined' && orderFAll[3][0] != '') {
$('#delivery_places').children('[value="' + orderFAll[3][0] + '"]').remove();
$('#delivery_places').append('' + orderFAll[3][1] + ' ');
}
$('#order_client_comments').val(orderFAll[4]);
$('#order-delivery-address').val(orderFAll[5]);
$('#order-delivery-coords').val(orderFAll[6]);
if (typeof orderFAll[7] == 'undefined' && orderFAll[7] == '') {
$('#order_status option[value="1"]').prop('selected', true);
} else {
$('#order_status option[value="' + orderFAll[7] + '"]').prop('selected', true);
}
$('#order_status').trigger('change');
/*
if (typeof orderFAll[8] == 'undefined' && orderFAll[8] == '') {
$('#order-warehouse option[value="1"]').prop('selected', true);
} else {
$('#order-warehouse option[value="'+orderFAll[8]+'"]').prop('selected', true);
console.log('adasdasd');
}
*/
//$('#order-warehouse').trigger('change');
/*
if (typeof orderFAll[3][0] == 'undefined' || orderFAll[3][0] == '') {
$('#delivery_places[value=""]').prop('selected', true);
} else {
$('#delivery_places[value="'+orderFAll[3][0]+'"]').prop('selected', true);
}
$('#delivery_places').trigger('change');
*/
$('#order-delivery-date').val(orderFAll[9]);
if (typeof orderFAll[10] == 'undefined' && orderFAll[10] == '') {
$('#order-delivery-time option[value=""]').prop('selected', true);
} else {
$('#order-delivery-time option[value="' + orderFAll[10] + '"]').prop('selected', true);
}
$('#order-delivery-time').trigger('change');
if (typeof orderFAll[11] == 'undefined' && orderFAll[11] == '') {
$('#order-pay option[value=""]').prop('selected', true);
} else {
$('#order-pay option[value="' + orderFAll[11] + '"]').prop('selected', true);
}
$('#order-pay').trigger('change');
$('#ordercard-edit-flag').val(orderFAll[12]);
updateOrderCardAction();
// Если поле нового адреса заполнено, то выбираем Новый адрес доставки из списка
if ($.trim($('#order-delivery-address').val()) != '') {
$('#delivery_places option[value="%%"]').prop('selected', true);
}
/*else {
$('#delivery_places option[value="%"]').prop('selected', true);
}*/
// Восстанавливаем транспортную компанию (адрес вне зон доставки);
// видимость селекта выставит deliveryRowRender по статусу предпоказа
if (typeof orderFAll[14] != 'undefined' && orderFAll[14] != '') {
$('#order-delivery-tk option[value="' + orderFAll[14] + '"]').prop('selected', true);
}
// Заполняем значение производства
if (orderFAll[13] == '1') {
$('#order_prod').prop('checked', true);
$('.prod-field').show();
$('#order-colspan').attr('colspan', '9');
} else {
$('#order_prod').prop('checked', false);
$('.prod-field').hide();
$('#order-colspan').attr('colspan', '6');
}
}
orderCardRestoringFields = false;
deliveryScheduleInit();
$('#ordercard-sort').val(localStorage['JustCRM-OrderCart-Sort'] || '{}');
$('#ordercard-shipment').val(localStorage['JustCRM-OrderCart-Shipment'] || '{}');
// Поле «Дата доставки» должно быть видно сразу при открытии корзины, если выбрана
// доставка (не самовывоз) или заполнен адрес. На загрузке страницы блок скрывается
// по дефолтному «Самовывозу», а восстановление полей выше change не триггерит
if ($('#delivery_places').val() == '%' && $.trim($('#order-delivery-address').val()) == '') {
$('#order-delivery-date').parent('div').hide();
} else {
$('#order-delivery-date').parent('div').show();
}
cutPricePreview();
cutServicePreview();
// Предпоказ стоимости доставки при каждом обновлении корзины
deliveryPricePreview();
plisseCartLock();
}
function orderProdToggleQuantity(orderCartAll, enabled) {
function entryField(entry, name) {
for (var i = 0; i < entry.length; i++) if (entry[i] && entry[i].name === name) return entry[i];
return null;
}
$('#ordercard-list').children('.position').each(function() {
var $row = $(this);
if ($row.find('.ordercard-prod').val() != '2') return;
var entry = orderCartAll[$row.attr('data-id-pos-prefix')];
if (!entry) return;
var price = Number(String($row.children('.price').attr('data-price') || '0').replace(',', '.').replace(/\s/g, ''));
var plain = entryField(entry, 'plain-quantity');
var panel = cartPanelSpec(entry);
$row.find('.panel-area').toggle(enabled);
if (cartCutIncluded(entry)) return;
if (!enabled && panel) plain = { value: String(cartPanelSheets($row, panel)) };
if (enabled) {
if (orderCartProductionDefaults(entry, $row.find('.ordercard-min-shipment').val(), panel ? cartPanelSheets($row, panel) : undefined)) {
$row.find('.ordercard-qty').val(entry[12].value);
$row.find('.ordercard-length').val(entry[13].value);
}
} else if (plain) {
entry[0].value = plain.value;
var plainIndex = entry.indexOf(plain);
if (plainIndex !== -1) entry.splice(plainIndex, 1);
// Подставленную автоматически нарезку, которую не меняли, сбрасываем — при следующем включении пересчитается от кол-ва
var auto = entryField(entry, 'cut-auto');
if (auto) {
if (entry[12] && entry[13] && auto.value === entry[12].value + '|' + entry[13].value) {
entry[12].value = '';
entry[13].value = '';
}
entry.splice(entry.indexOf(auto), 1);
}
$row.find('.ordercard-quantity').val(plain.value);
$row.find('.ordercard-price').val((Number(plain.value) * price).toFixed(2).replace('.', ','));
}
});
}
function searchKeyInOrder(searchKey, orderObj) {
for (key in orderObj) {
if (searchKey == key) {
return key;
}
if (key.indexOf(searchKey + '_') > -1) {
return key;
}
}
}
function addOrder(orderClient, idClient, clientCatPrice, first, isFranchiseeClient = false) {
if (isFranchiseeClient === true) {
var orderF4 = ["%", "Самовывоз"],
orderF5 = '',
orderF6 = '',
orderF7 = '',
orderF8 = '',
orderF9 = '',
orderF10 = '',
orderF11 = '',
orderF12 = '',
orderF13 = '',
orderF14 = '';
var orderFAll = [decodeURIComponent(orderClient), decodeURIComponent(idClient), decodeURIComponent(clientCatPrice), orderF4, orderF5, orderF6, orderF7, orderF8, orderF9, orderF10, orderF11, orderF12, orderF13, orderF14];
localStorage['JustCRM-OrderCart-field-Franchisee'] = JSON.stringify(orderFAll);
console.log('Вызов на добавление данных.', orderClient, idClient, clientCatPrice, first)
location.href = '/nomen?cat_price=' + encodeURI(clientCatPrice) + '&cat_price_client_uid=' + encodeURI(idClient);
}
if (!first) {
var orderF4 = ["%", "Самовывоз"],
orderF5 = '',
orderF6 = '',
orderF7 = '',
orderF8 = '',
orderF9 = '',
orderF10 = '',
orderF11 = '',
orderF12 = '',
orderF13 = '',
orderF14 = '';
var orderFAll = [decodeURIComponent(orderClient), decodeURIComponent(idClient), decodeURIComponent(clientCatPrice), orderF4, orderF5, orderF6, orderF7, orderF8, orderF9, orderF10, orderF11, orderF12, orderF13, orderF14];
localStorage['JustCRM-OrderCart-field'] = JSON.stringify(orderFAll);
console.log('Вызов на добавление данных.', orderClient, idClient, clientCatPrice, first)
location.href = '/nomen?cat_price=' + encodeURI(clientCatPrice) + '&cat_price_client_uid=' + encodeURI(idClient);
} else {
console.log('Обновление данных для заказа:', orderClient, idClient, clientCatPrice, first);
if (typeof localStorage['JustCRM-OrderCart-field'] == 'undefined') localStorage['JustCRM-OrderCart-field'] = '[]';
try {
var orderFAll = JSON.parse(localStorage['JustCRM-OrderCart-field']);
} catch (e) {
var orderFAll = [];
}
orderFAll[0] = decodeURIComponent(orderClient);
orderFAll[1] = decodeURIComponent(idClient);
orderFAll[2] = decodeURIComponent(clientCatPrice);
orderFAll[3] = ["%", "Самовывоз"];
orderFAll[4] = '';
orderFAll[5] = '';
orderFAll[6] = '';
orderFAll[7] = '1';
orderFAll[8] = '';
orderFAll[9] = '';
orderFAll[10] = '';
orderFAll[11] = '';
orderFAll[12] = '';
orderFAll[13] = 0;
localStorage['JustCRM-OrderCart-field'] = JSON.stringify(orderFAll);
}
}
function addOrderList(orderClient, idClient, clientCatPrice, first, isFranchiseeClient = false) {
if (isFranchiseeClient === true) {
var orderF4 = ["%", "Самовывоз"],
orderF5 = '',
orderF6 = '',
orderF7 = '',
orderF8 = '',
orderF9 = '',
orderF10 = '',
orderF11 = '',
orderF12 = '',
orderF13 = '',
orderF14 = '';
var orderFAll = [decodeURIComponent(orderClient), decodeURIComponent(idClient), decodeURIComponent(clientCatPrice), orderF4, orderF5, orderF6, orderF7, orderF8, orderF9, orderF10, orderF11, orderF12, orderF13, orderF14];
localStorage['JustCRM-OrderCartList-field-Franchisee'] = JSON.stringify(orderFAll);
console.log('Вызов на добавление данных (клиент франчайзи).', orderClient, idClient, clientCatPrice, first)
location.href = '/nomendraw?cat_price=' + encodeURI(clientCatPrice) + '&cat_price_client_uid=' + encodeURI(idClient);
}
if (!first) {
var orderF4 = ["%", "Самовывоз"],
orderF5 = '',
orderF6 = '',
orderF7 = '',
orderF8 = '',
orderF9 = '',
orderF10 = '',
orderF11 = '',
orderF12 = '',
orderF13 = '',
orderF14 = '';
var orderFAll = [decodeURIComponent(orderClient), decodeURIComponent(idClient), decodeURIComponent(clientCatPrice), orderF4, orderF5, orderF6, orderF7, orderF8, orderF9, orderF10, orderF11, orderF12, orderF13, orderF14];
localStorage['JustCRM-OrderCartList-field'] = JSON.stringify(orderFAll);
console.log('Вызов на добавление данных.', orderClient, idClient, clientCatPrice, first)
location.href = '/nomendraw?cat_price=' + encodeURI(clientCatPrice) + '&cat_price_client_uid=' + encodeURI(idClient);
} else {
console.log('Обновление данных для заказа:', orderClient, idClient, clientCatPrice, first);
if (typeof localStorage['JustCRM-OrderCartList-field'] == 'undefined') localStorage['JustCRM-OrderCartList-field'] = '[]';
try {
var orderFAll = JSON.parse(localStorage['JustCRM-OrderCartList-field']);
} catch (e) {
var orderFAll = [];
}
orderFAll[0] = decodeURIComponent(orderClient);
orderFAll[1] = decodeURIComponent(idClient);
orderFAll[2] = decodeURIComponent(clientCatPrice);
orderFAll[3] = ["%", "Самовывоз"];
orderFAll[4] = '';
orderFAll[5] = '';
orderFAll[6] = '';
orderFAll[7] = '1';
orderFAll[8] = '';
orderFAll[9] = '';
orderFAll[10] = '';
orderFAll[11] = '';
orderFAll[12] = '';
orderFAll[13] = 0;
localStorage['JustCRM-OrderCartList-field'] = JSON.stringify(orderFAll);
}
}
function deleteOrderAllPos(skipReload) {
cancelCartWarehouse();
// Удаляем выбранного клиента и сведения о нём
$('.client_warning').hide();
deleteCookie('cat_search_price_client_uid');
deleteCookie('cat_search_price_client_uid_franchisee');
deleteCookie('JustCRM_open_master_order');
// Очищаем корзину заказа
$('#ordercard_form').find('#ordercard-list').children().remove();
localStorage['JustCRM-OrderCart'] = '{}';
localStorage['JustCRM-OrderCart-Sort'] = '{}';
localStorage['JustCRM-OrderCart-Shipment'] = '{}';
localStorage['JustCRM-OrderCart-field'] = '[]';
localStorage['JustCRM-OrderCart-field-Franchisee'] = '[]';
delete localStorage['JustCRM-OrderCart-promocode'];
delete localStorage['JustCRM-OrderCart-promocode-data'];
delete localStorage['JustCRM-OrderCart-promoOrig'];
delete localStorage['JustCRM-OrderCart-promoRestore'];
$('body').find('.select-cart-pos').removeClass('select-cart-pos');
orderMarkerView();
// skipReload: при переходе на страницу созданного заказа перезагрузку не планируем —
// отложенный location.reload() отменял уже начатую навигацию, и вместо заказа открывалась номенклатура
if (skipReload === true) return;
delayClosePopup(0);
setTimeout(function() {
delayClosePopup(500);
setTimeout(function() {
location.reload();
}, 500);
}, 500);
}
function deleteOrderAllPosList(skipReload) {
$('.client_warning').hide();
deleteCookie('cat_search_price_client_uid_list');
deleteCookie('cat_search_price_client_uid_list_franchisee');
deleteCookie('JustCRM_open_master_order_list');
// Очищаем корзину заказа
$('#ordercardlist_form').find('#ordercardlist-list').children().remove();
localStorage['JustCRM-OrderCartList'] = '{}';
localStorage['JustCRM-OrderCartList-field'] = '{}';
localStorage['JustCRM-OrderCartList-field-Franchisee'] = '[]';
$('body').find('.select-cart-pos').removeClass('select-cart-pos');
// skipReload: см. deleteOrderAllPos — reload отменял навигацию на страницу созданного заказа
if (skipReload === true) return;
delayClosePopup(0);
setTimeout(function() {
delayClosePopup(500);
setTimeout(function() {
location.reload();
}, 500);
}, 500);
}
function deleteOrderPos(storeVarId, storeVar, templateId) {
var orderCartAll = JSON.parse(localStorage['JustCRM-OrderCart']);
var orderCartAllSort = JSON.parse(localStorage['JustCRM-OrderCart-Sort']);
var orderCartAllShipment = JSON.parse(localStorage['JustCRM-OrderCart-Shipment']);
delete orderCartAll[storeVarId];
delete orderCartAllSort[storeVarId];
delete orderCartAllShipment[storeVarId];
localStorage['JustCRM-OrderCart'] = JSON.stringify(orderCartAll);
localStorage['JustCRM-OrderCart-Sort'] = JSON.stringify(orderCartAllSort);
localStorage['JustCRM-OrderCart-Shipment'] = JSON.stringify(orderCartAllShipment);
if (!Object.keys(orderCartAll).length) cancelCartWarehouse();
//$(templateId).children('[data-id-pos="' + storeVarId + '"]').remove();
$(templateId).children('[data-id-pos-prefix="' + storeVarId + '"]').remove();
$(this).parents('td').first().find('input').removeClass('select-cart-pos');
$('#toBackAncor_' + storeVarId).children('[data-add-cart]').find('input').removeClass('select-cart-pos');
orderMarkerView();
delayClosePopup(0);
setTimeout(function() {
if ($(templateId).children('tr').length === 0) {
delete localStorage['JustCRM-OrderCart-promocode'];
delete localStorage['JustCRM-OrderCart-promocode-data'];
delete localStorage['JustCRM-OrderCart-promoOrig'];
delete localStorage['JustCRM-OrderCart-promoRestore'];
delayClosePopup(500);
}
}, 500);
orderStat();
}
function orderMarkerView() {
if (typeof localStorage['JustCRM-OrderCart'] != 'undefined') {
var orderCartAll = JSON.parse(localStorage['JustCRM-OrderCart']);
if (Object.keys(orderCartAll).length > 0) {
$('#marker-cart').show();
$('#marker-cart').find('.marker').html(Object.keys(orderCartAll).length);
} else {
$('#marker-cart').hide();
}
}
}
function updateOrderCardAction() {
if (plisseCartActive()) { $('#ordercard_form .add-order').text('Создать заказ в CRM'); return; }
var $form = $('#ordercard_form');
if (!$form.length) return;
var isEditing = $.trim($form.find('#ordercard-edit-flag').val()) !== '';
var shouldMakeInvoice = $form.data('auto-invoice') === true && !isEditing;
$form.find('.add-order').text(isEditing ? 'Изменить заказ' : (shouldMakeInvoice ? 'Сформировать счёт' : 'Создать заказ'));
}
// Корзина заказа - НОВАЯ КОРЗИНА - Конец
// Телефон - вызов номера
function makeCall(phone) {
var phoneNumber = (typeof(phone) == 'object') ? $(phone).parent('div').children('input').val() : phone;
if (/^\+?\d{10,18}$/.test(phoneNumber)) {
send_request(phoneNumber, 'data-exchange', 'data-make-call');
delayClosePopup(1000);
delayClosePopup(2000);
} else {
popup('bad', 'Ошибка!', 'Номер некорректен! Исправьте для успешного набора.', true, false);
}
}
// Уведомления
// Проверка и запрос разрешений на уведомления
function notifyMe(subj, mess, tag) {
// Теперь давайте проверим есть ли у нас разрешение для отображения уведомления
if (Notification.permission === "granted") {
// Если все в порядке, то создадим уведомление
notify(subj, mess, tag);
}
// В противном случае, мы должны спросить у пользователя разрешение
else if (Notification.permission === 'default') {
Notification.requestPermission(function(permission) {
// Не зависимо от ответа, сохраняем его в настройках
if (!('permission' in Notification)) {
Notification.permission = permission;
}
// Если разрешение получено, то создадим уведомление
if (permission === "granted") {
notify(subj, mess, tag);
}
});
}
}
// Вывод уведомления
function notify(subj, mess, tag) {
var notification = new Notification(subj, {
lang: 'ru-RU',
body: mess,
icon: '/img-vendor/logo_note.png',
tag: tag,
requireInteraction: true
});
// Действие по клику
notification.onclick = function() {
var info = JSON.parse(notification.tag);
// Формируем ссылку на Юр.лиц, Физ. лиц, либо Лидов в зависимости от clientType и lidasType
var linkType = (info.clientType != '2') ? 'clients' : 'fiz';
if (info.lidasType == '1') linkType = 'lidas';
// Если клиент идентифицирован, то открываен форму редактирования
if (info.clientId && info.clientId != 'new') {
window.location.href = '/' + linkType + '/_details/' + info.clientId;
} else if (info.clientId == 'new') {
// Иначе пустая форма клиента c входящим номером
setCookie('call_client', info.clientNumber);
location.href = '/lidas/client';
}
// Если иные события
if (info.command) {
switch (info.command) {
case 'new_task':
window.location.href = info.task_url;
break;
case 'dropped_call':
window.location.href = '/tasks';
break;
case 'new_letter':
window.location.href = '/mailbox?filter_parametres=filter_all';
break;
}
}
}
}
// Уведомления - Конец
// Вывод на печать
function toPrint(obj, zag, nologo) {
var objPrint = $(obj).clone();
if (!nologo) {
var newWin = window.open('/components/inc/print.html', 'Печать и сохранение', 'width=1200,height=600,resizable=yes,scrollbars=yes,status=no,toolbar=no,location=no,menubar=no');
} else {
var newWin = window.open('/components/inc/print.html', 'Печать и сохранение', 'width=1200,height=600,resizable=yes,scrollbars=yes,status=no,toolbar=no,location=no,menubar=yes');
}
newWin.onload = function() {
var div = newWin.document.createElement('div');
body = newWin.document.body;
div.innerHTML = objPrint[0].innerHTML;
body.insertBefore(div, body.firstChild);
$(body).find('.print').remove();
$(body).find('.delete-print').remove();
$(body).find('.clear-print').attr('class', '').attr('id', '').attr('style', '');
$(body).children('div').css({ 'margin': '5em' });
$(body).children('div').prepend('' + zag + ' ');
if (!nologo) {
$(body).children('div').prepend(' ');
setTimeout(function() {
newWin.print();
}, 500);
}
}
}
// Скролл до объекта
function scr(obj, delay) {
try {
if (!delay) var delay = 0;
setTimeout(function() {
$('html, body').animate({ scrollTop: $('#' + obj).offset().top }, 250, 'swing');
preloader();
}, delay);
} catch (e) {
console.log('Нет объекта для возврата.');
}
}
function scrInDiv(obj, delay) {
try {
if (!delay) var delay = 0;
var container = $('#' + obj).parents('.scroll');
$('html, body').animate({ scrollTop: container.offset().top }, 250, 'swing'); //jqBar.position().top
setTimeout(function() {
container.animate({ scrollTop: document.getElementById(obj).offsetTop }, 250, 'swing');
}, delay);
} catch (e) {
console.log('Нет объекта для возврата.');
}
}
/*
setTimeout(function() {
sendSocket('{"command":"new_task", "id_client":"'+guid+'"}');
}, 2000);
*/
// Отправка сообщения в сокет
function sendSocket(command) {
if (window.parent.socket) window.parent.socket.send(command);
}
// Декодирование HTML-сущностей
function htmlDecode(input) {
var e = document.createElement('div');
e.innerHTML = input;
return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
}
// Парсинг параметров GET
function findGetParameter(findGet) {
var result = {},
temp = null,
findGetValue = false;
location.search.substr(1).split('&').forEach(function(item) {
item.split('=').forEach(function(item, i) {
if (i === 0) {
temp = decodeURIComponent(item);
if (temp != '') result[temp] = 0;
};
if (i === 1) result[temp] = decodeURIComponent(item);
// Если задан параметр поиска значения GET по ключу
if (typeof(findGet) != 'undefined' && findGet == temp && result[temp] != '') {
findGetValue = true;
return false;
}
});
});
if (findGet && findGetValue) {
return result[temp];
} else {
if (Object.keys(result).length !== 0 && !findGet) return result;
else return false;
}
}
// Переход по указанному URL
function proceedURL(url) {
location.href = url;
}
function parseNomenQuantityValue(value) {
return Number(String(value).replace(/\s/g, '').replace(',', '.'));
}
function formatNomenQuantityValue(value) {
var quantity = parseNomenQuantityValue(value);
if (isNaN(quantity)) return '';
return String(Number(quantity.toFixed(10))).replace('.', ',');
}
function formatNomenQuantityInputValue(value) {
var quantity = parseNomenQuantityValue(value);
if (isNaN(quantity)) return '';
return String(Number(quantity.toFixed(10)));
}
// Округление в большую сторону к требуемому числу
function roundMultiple(num, multiple) {
var rounded = Math.ceil(parseNomenQuantityValue(num) / parseNomenQuantityValue(multiple)) * parseNomenQuantityValue(multiple);
return Number(rounded.toFixed(10));
}
// «Да» в попапе округления количества в корзине — принять округлённое значение и пересчитать
window.nomenRoundConfirmAcceptCart = function() {
var $input = null;
if (window.nomenRoundConfirm && window.nomenRoundConfirm.$input) {
$input = window.nomenRoundConfirm.$input;
$input.val(formatNomenQuantityInputValue(window.nomenRoundConfirm.roundedVal));
}
// Очищаем состояние ДО закрытия попапа, чтобы обработчик «Нет» не сработал повторно
window.nomenRoundConfirm = null;
$(document).off('click.nomenRoundCart');
// Пересчитываем корзину с округлённым значением
if ($input) {
orderStat($input, 'minShipment');
}
$('.popup').last().find('.close').trigger('click');
};
// «Нет» в попапе округления количества в корзине — вернуть предыдущее кратное значение
window.nomenRoundConfirmRejectCart = function() {
if (!window.nomenRoundConfirm) return; // уже обработано (Да или повторный вызов)
if (window.nomenRoundConfirm.$input) {
// Возвращаем значение, которое было до редактирования
window.nomenRoundConfirm.$input.val(formatNomenQuantityInputValue(window.nomenRoundConfirm.prevVal));
}
window.nomenRoundConfirm = null;
};
// Управление видимостью столбцов таблиц
// Инициализация компонента для всех таблиц с классом col-toggle-table
function initColumnToggle() {
$('.col-toggle-table').each(function() {
var $table = $(this);
var tableId = $table.attr('id') || 'table_' + Math.random().toString(36).substr(2, 9);
// Если уже инициализирована - пропускаем
if ($table.data('col-toggle-init')) return;
$table.data('col-toggle-init', true);
// Создаем обертку с кнопкой
var $wrapper = $('.ssk-column-pending').filter(function() {
return this.getAttribute('data-ssk-table') === tableId;
}).first();
if ($wrapper.length) $wrapper.removeClass('ssk-column-pending').removeAttr('data-ssk-table').empty();
else $wrapper = $('
');
var $btn = $(' ');
var $popup = $('');
// Заголовок popup
var $popupHeader = $('');
var $popupBody = $('
');
// Получаем заголовки столбцов
var $headers = $table.find('thead th, tr:first-child th');
var settings = loadColumnSettings(tableId);
$headers.each(function(index) {
var $th = $(this);
var colName = $th.text().trim();
// Пропускаем пустые столбцы (действия) - не показываем в списке
if (colName === '') {
return true; // continue
}
// Проверяем сохраненное состояние
var isVisible = settings[index] !== false;
// Создаем элемент списка
var $item = $('
');
var $label = $('' + colName + ' ');
var $toggle = $(' ');
$item.append($label).append($toggle);
$popupBody.append($item);
// Применяем начальное состояние через классы
if (!isVisible) {
toggleColumn($table, index, false);
}
});
$popup.append($popupHeader).append($popupBody);
$wrapper.append($btn).append($popup);
// Вставляем в блок пагинации (если есть) или перед таблицей
var $container = $table.closest('#headcol-fix').length ? $table.closest('#headcol-fix') : $table;
var $toolbar = $container.prevAll('.ssk-filter-toolbar').first();
var $paging = $container.prevAll('.paging').first();
if ($toolbar.length && document.body.classList.contains('ssk-m')) {
$toolbar.append($wrapper);
} else if ($paging.length) {
// Вставляем в пагинацию справа
$paging.append($wrapper);
} else {
// Иначе перед таблицей
$container.before($wrapper);
}
// Обработчик кнопки
$btn.on('click', function(e) {
e.stopPropagation();
$('.col-toggle-popup').not($popup).removeClass('active');
$popup.toggleClass('active');
});
// Закрытие по крестику
$popupHeader.find('.fa-times').on('click', function() {
$popup.removeClass('active');
});
// Обработчик тумблеров (сохраняем ссылки в closure)
(function(tbl, tblId) {
$popupBody.on('change', 'input[type="checkbox"]', function() {
var colIdx = parseInt($(this).attr('data-col-idx'));
var isChecked = $(this).is(':checked');
toggleColumn(tbl, colIdx, isChecked);
// Обновляем настройки напрямую
var settings = loadColumnSettings(tblId);
settings[colIdx] = isChecked;
saveColumnSettings(tblId, settings);
});
})($table, tableId);
});
// Закрытие popup при клике вне его
$(document).on('click', function(e) {
if (!$(e.target).closest('.col-toggle-wrap').length) {
$('.col-toggle-popup').removeClass('active');
}
});
}
// Переключение видимости столбца (классы)
function toggleColumn($table, colIndex, isVisible) {
// Используем CSS селектор nth-child для надёжности
var nthChild = colIndex + 1;
var selector = 'tr th:nth-child(' + nthChild + '), tr td:nth-child(' + nthChild + ')';
var $cells = $table.find(selector);
if (isVisible) {
$cells.removeClass('col-hidden');
} else {
$cells.addClass('col-hidden');
}
}
// Получение текущих настроек столбцов
function getColumnSettings($table) {
var settings = {};
var $headers = $table.find('thead th, tr:first-child th');
$headers.each(function(index) {
var $th = $(this);
settings[index] = !$th.hasClass('col-hidden');
});
return settings;
}
// Сохранение настроек в localStorage
function saveColumnSettings(tableId, settings) {
localStorage['JustCRM_colToggle_' + tableId] = JSON.stringify(settings);
}
// Загрузка настроек из localStorage
function loadColumnSettings(tableId) {
if (window.SSKColumnSettings) return window.SSKColumnSettings.load(tableId);
var stored = localStorage['JustCRM_colToggle_' + tableId];
if (stored) {
try {
return JSON.parse(stored);
} catch (e) {
return {};
}
}
return {};
}
// Сброс настроек столбцов для таблицы
function resetColumnSettings(tableId) {
delete localStorage['JustCRM_colToggle_' + tableId];
location.reload();
}
// Инициализация при загрузке страницы
$(document).ready(function() {
initColumnToggle();
});
// Управление видимостью столбцов таблиц - Конец
// Сообщить об ошибке - Начало
$(document).ready(function() {
// popup('form', ...) переносит узел #report-error-form внутрь окна, а закрытие
// попапа (Отмена/крестик) этот узел удаляет — поэтому сохраняем исходную разметку
// один раз и восстанавливаем её перед каждым открытием (иначе со второго раза
// и на страницах, где узел уже был перемещён/удалён, попап открывается пустым).
var reportErrorFormHTML = (function() {
var el = document.getElementById('report-error-form');
return el ? el.outerHTML : null;
})();
// Обработчик клика по кнопке "Сообщить об ошибке"
$('#report-error-btn').on('click', function() {
if (reportErrorFormHTML) {
$('#report-error-form').remove(); // убираем возможный «осиротевший» экземпляр
$('body').append(reportErrorFormHTML); // свежая копия формы (поля чистые)
}
popup('form', '', 'report-error-form', false, false);
});
// Обработчик клика по кнопке отправки (основной обработчик, минуя submit формы)
$(document).on('click', '#report-error-form_form button[type="submit"], #report-error-form_form .orange-but', function(e) {
console.log('Кнопка отправки нажата');
e.preventDefault();
e.stopImmediatePropagation();
e.stopPropagation();
// Находим форму
var $form = $(this).closest('form');
if (!$form.length || $form.attr('id') !== 'report-error-form_form') {
console.log('Форма не найдена!');
return;
}
console.log('Форма найдена, начинаем обработку');
// Отключаем validationEngine
try {
if ($form.data('jqv')) {
$form.validationEngine('detach');
}
$form.validationEngine('detach');
$form.find('input, textarea, select').removeClass('validate');
$form.off('submit.validationEngine');
} catch(e) {
console.log('Ошибка при отключении validationEngine:', e);
}
// Ищем textarea в контексте этой формы
var description = $form.find('#error-description').val().trim();
console.log('Описание:', description, 'Длина:', description.length);
if (!description || description.length < 20) {
popup('bad', 'Ошибка!', 'Пожалуйста, опишите проблему. Минимальная длина описания - 20 символов.', true, false);
return;
}
// Собираем данные формы
var formData = new FormData();
formData.append('error_description', description);
// Добавляем файлы
var fileInput = $form.find('#error-files')[0];
console.log('File input:', fileInput, 'Files:', fileInput ? fileInput.files : 'не найден');
if (fileInput && fileInput.files && fileInput.files.length > 0) {
var files = fileInput.files;
console.log('Файлов выбрано:', files.length);
for (var i = 0; i < files.length; i++) {
formData.append('error_files[]', files[i]);
}
} else {
console.log('Файлы не выбраны');
}
// Получаем информацию о системе
var os = navigator.platform || 'Неизвестно';
var browser = navigator.userAgent || 'Неизвестно';
var url = window.location.href;
var user = (typeof id !== 'undefined' && typeof guid !== 'undefined') ? 'ID: ' + id + ', GUID: ' + guid : 'Неизвестно';
var time = new Date().toLocaleString('ru-RU');
formData.append('os', os);
formData.append('browser', browser);
formData.append('url', url);
formData.append('user', user);
formData.append('time', time);
// Отправляем данные
console.log('Начинаем отправку AJAX запроса');
preloader();
preloader('show');
// Проверяем наличие guid
var requestGuid = (typeof guid !== 'undefined' && guid) ? guid : '';
var requestUrl = '/ajax-send/data-report-error.php' + (requestGuid ? '?guid=' + requestGuid : '');
console.log('URL запроса:', requestUrl);
$.ajax({
url: requestUrl,
type: 'POST',
data: formData,
cache: false,
processData: false,
contentType: false,
success: function(response) {
console.log('Ответ сервера:', response);
preloader();
// Закрываем попап с формой напрямую, без лишних обработчиков
var formPopup = $('.popup').has('#report-error-form_form').last();
if (formPopup.length) {
// Возвращаем форму обратно и скрываем
var reportErrorForm = formPopup.find('#report-error-form');
if (reportErrorForm.length) {
reportErrorForm.css('display', 'none');
$('body').append(reportErrorForm);
}
formPopup.find('.wind').removeClass('zoomIn').addClass('zoomOut');
formPopup.fadeOut('slow', function() {
formPopup.remove();
$('html').css({ 'overflow-y': 'unset' });
});
}
if (response && response.indexOf('Успешно') !== -1) {
popup('good', 'Успешно!', 'Сообщение об ошибке отправлено. Спасибо за обратную связь!', true, false);
} else {
popup('bad', 'Ошибка!', 'Не удалось отправить сообщение. Попробуйте позже.', true, false);
}
},
error: function(xhr, status, error) {
console.log('Ошибка AJAX:', status, error, xhr.responseText);
preloader();
var errorMsg = 'Произошла ошибка при отправке сообщения.';
if (xhr.responseText) {
errorMsg += ' ' + xhr.responseText.substring(0, 100);
}
popup('bad', 'Ошибка!', errorMsg, true, false);
}
});
return false;
});
// Обработчик отправки формы (делегирование для динамически добавленных элементов)
// Используем более высокий приоритет, чтобы перехватить до validationEngine
$(document).on('submit', '#report-error-form_form', function(e) {
console.log('Обработчик submit сработал для #report-error-form_form');
// Останавливаем событие сразу, чтобы validationEngine не перехватил
e.preventDefault();
e.stopImmediatePropagation();
e.stopPropagation();
// Отключаем validationEngine для этой формы, если он еще активен
var $form = $(this);
// Отключаем validationEngine несколькими способами для надежности
try {
if ($form.data('jqv')) {
$form.validationEngine('detach');
}
$form.validationEngine('detach');
$form.find('input, textarea, select').removeClass('validate');
// Удаляем все обработчики validationEngine
$form.off('submit.validationEngine');
$form.unbind('submit');
} catch(e) {
// Игнорируем ошибки при отключении validationEngine
}
// Ищем textarea в контексте этой формы
var description = $form.find('#error-description').val().trim();
if (!description || description.length < 20) {
popup('bad', 'Ошибка!', 'Пожалуйста, опишите проблему. Минимальная длина описания - 20 символов.', true, false);
return;
}
// Проверяем, что форма найдена
if (!$form.length) {
popup('bad', 'Ошибка!', 'Форма не найдена.', true, false);
return;
}
// Собираем данные формы
var formData = new FormData();
formData.append('error_description', description);
// Добавляем файлы
// Форма не клонируется, а перемещается, поэтому файлы должны быть доступны
var fileInput = $form.find('#error-files')[0];
if (fileInput && fileInput.files && fileInput.files.length > 0) {
var files = fileInput.files;
for (var i = 0; i < files.length; i++) {
formData.append('error_files[]', files[i]);
}
}
// Получаем информацию о системе
var os = navigator.platform || 'Неизвестно';
var browser = navigator.userAgent || 'Неизвестно';
var url = window.location.href;
var user = (typeof id !== 'undefined' && typeof guid !== 'undefined') ? 'ID: ' + id + ', GUID: ' + guid : 'Неизвестно';
var time = new Date().toLocaleString('ru-RU');
formData.append('os', os);
formData.append('browser', browser);
formData.append('url', url);
formData.append('user', user);
formData.append('time', time);
// Отправляем данные
preloader();
preloader('show');
// Проверяем наличие guid
var requestGuid = (typeof guid !== 'undefined' && guid) ? guid : '';
var requestUrl = '/ajax-send/data-report-error.php' + (requestGuid ? '?guid=' + requestGuid : '');
$.ajax({
url: requestUrl,
type: 'POST',
data: formData,
cache: false,
processData: false,
contentType: false,
success: function(response) {
preloader();
// Закрываем попап с формой напрямую, без лишних обработчиков
var formPopup = $('.popup').has('#report-error-form_form').last();
if (formPopup.length) {
// Возвращаем форму обратно и скрываем
var reportErrorForm = formPopup.find('#report-error-form');
if (reportErrorForm.length) {
reportErrorForm.css('display', 'none');
$('body').append(reportErrorForm);
}
formPopup.find('.wind').removeClass('zoomIn').addClass('zoomOut');
formPopup.fadeOut('slow', function() {
formPopup.remove();
$('html').css({ 'overflow-y': 'unset' });
});
}
if (response && response.indexOf('Успешно') !== -1) {
popup('good', 'Успешно!', 'Сообщение об ошибке отправлено. Спасибо за обратную связь!', true, false);
} else {
popup('bad', 'Ошибка!', 'Не удалось отправить сообщение. Попробуйте позже.', true, false);
}
},
error: function(xhr, status, error) {
preloader();
var errorMsg = 'Произошла ошибка при отправке сообщения.';
if (xhr.responseText) {
errorMsg += ' ' + xhr.responseText.substring(0, 100);
}
popup('bad', 'Ошибка!', errorMsg, true, false);
}
});
});
});
// Сообщить об ошибке - Конец
var cartImportLastResult = null;
function cartImportRenderResult() {
if (!cartImportLastResult) return;
$('#nomenFileImport .cart-import-result').each(function() {
var box = $(this).empty().prop('hidden', false);
var result = cartImportLastResult;
if (result.error) {
$('').text(result.error).appendTo(box);
return;
}
$('').text('Добавлено в корзину позиций: ' + result.added + '. Не сопоставлено: ' + result.missing.filter(function(item) { return !item.resolved; }).length + '.').appendTo(box);
if (result.adjustments && result.adjustments.length) {
var notices = $('').appendTo(box);
$('').append($('').text('Количество увеличено до минимальной партии')).appendTo(notices);
var table = $('Товар Запрошено Добавлено в корзину Причина
').appendTo($('').appendTo(notices));
result.adjustments.forEach(function(item) {
var row = $('
').appendTo(table.find('tbody'));
var product = $('').appendTo(row);
$('').text(item.article || item.name || '').appendTo(product);
if (item.article && item.name) $('').text(item.name).appendTo(product);
$('
').text(item.requested).appendTo(row);
$(' ').append($('').text(item.quantity)).appendTo(row);
$('').text('Минимальная партия: ').append($('').text(item.minimum)).appendTo(row);
});
}
if (result.missing.length) {
var list = $('').appendTo(box);
result.missing.forEach(function(item) {
if (item.resolved) return;
$('').text((item.article ? item.article + ' — ' : '') + (item.name || '') + ': ' + item.reason).appendTo(list);
});
}
if (result.token && typeof nomenImportRows === 'function') nomenImportRows(box, result);
$('#nomenFileImport .nomen-import-open-cart').prop('hidden', !Object.keys(JSON.parse(localStorage['JustCRM-OrderCart'] || '{}')).length);
});
}
function cartImportResult(added, missing, error, token, context, adjustments) {
cartImportLastResult = { added: added, missing: missing, error: error, token: token, context: context, adjustments: adjustments || [] };
cartImportRenderResult();
}
function cartImportPriceContext() {
var fields = JSON.parse(localStorage['JustCRM-OrderCart-field'] || '[]');
var client = fields[1] || getCookie('cat_search_price_client_uid') || '';
var category = fields[1] ? fields[2] || '' : getCookie('cat_search_price_uid') || '';
return { client: client, category: category };
}
function cartImportRow(item, quantity, price, category, originalPrice) {
function field(name, value) { return { name: name, value: String(value) }; }
return [
field('quantity', quantity),
field('id', item.id),
field('name', item.name),
field('code', item.code),
field('articul', item.article),
field('item', item.item),
field('remains', '—'),
field('country', item.country || ''),
field('price', price),
field('cat-price', category),
field('base-price', price),
field('prod-shop', '1'),
field('prod-qty', '1'),
field('prod-length', '1'),
field('prod-num-order', ''),
field('original-price', originalPrice)
];
}
$(document).on('click', '.cart-import-button', function() {
if ($(this).prop('disabled')) return;
$(this).closest('.cart-import-control').find('.cart-import-input').trigger('click');
});
$(document).on('change', '.cart-import-input', function() {
var file = this.files && this.files[0];
this.value = '';
if (!file || $('#nomenFileImport .cart-import-button').prop('disabled')) return;
cartImportLastResult = null;
$('.cart-import-result').empty().prop('hidden', true);
cartImportUpload(file);
});
function cartImportUpload(file) {
var button = $('#nomenFileImport .cart-import-button');
if (button.prop('disabled')) return;
if (!/\.(xls|xlsx|doc|docx|odt|pdf)$/i.test(file.name)) {
cartImportResult(0, [], 'Поддерживаются XLS, XLSX, DOC, DOCX, ODT и PDF.');
return;
}
if (file.size > 20 * 1024 * 1024) {
cartImportResult(0, [], 'Размер файла должен быть до 20 МБ.');
return;
}
var priceContext;
try {
priceContext = { client: $('#nomen-import-client-guid').val() || '', category: $('#nomen-import-client-category').val() || '' };
if (!priceContext.client) { cartImportResult(0, [], 'Выберите клиента из списка перед загрузкой файла.'); return; }
var current = cartImportPriceContext();
if (Object.keys(JSON.parse(localStorage['JustCRM-OrderCart'] || '{}')).length && current.client !== priceContext.client) {
cartImportResult(0, [], 'В корзине есть товары другого клиента. Выберите того же клиента или сначала оформите либо очистите корзину.');
return;
}
var fields = JSON.parse(localStorage['JustCRM-OrderCart-field'] || '[]');
fields[0] = $('#nomen-import-client').val();
fields[1] = priceContext.client;
fields[2] = priceContext.category;
localStorage['JustCRM-OrderCart-field'] = JSON.stringify(fields);
setCookie('cat_search_price_client_uid', priceContext.client);
setCookie('cat_search_price_uid', priceContext.category);
$('#order-client').val(fields[0]);
$('#id_client').val(fields[1]);
$('#client_cat_price').val(fields[2]);
} catch (error) {
cartImportResult(0, [], 'Не удалось прочитать клиента текущей корзины. Корзина не изменена.');
return;
}
var previous = button.html();
nomenImportSetBusy(true);
button.text('Чтение файла…');
var data = new FormData();
data.append('file', file);
data.append('client', priceContext.client);
data.append('category', priceContext.category);
$.ajax({
url: '/ajax-send/data-cart-file-import.php?guid=' + encodeURIComponent(guid),
method: 'POST',
data: data,
processData: false,
contentType: false,
dataType: 'json',
timeout: 120000
}).done(function(response) {
if (!response || !response.ok) {
cartImportResult(0, [], response && response.error || 'Не удалось прочитать файл.');
return;
}
var positions = response.positions || [];
var missing = response.missing || [];
if (!positions.length) {
cartImportResult(0, missing, null, response.token, priceContext);
return;
}
if (plisseCartActive()) { cartImportResult(0, missing, 'Плиссе оформляются отдельным заказом. Сначала оформите или очистите корзину.'); return; }
try {
var result = cartImportAddPositions(response, priceContext, false);
cartImportResult(result.added, result.missing, null, response.token, priceContext, result.adjustments);
} catch (error) {
cartImportResult(0, missing, error.message);
}
}).fail(function(xhr) {
cartImportResult(0, [], xhr.responseJSON && xhr.responseJSON.error || 'Не удалось загрузить файл.');
}).always(function() {
nomenImportSetBusy(false);
button.html(previous);
});
}
function cartImportAddPositions(response, priceContext, showCart) {
if (plisseCartActive()) throw new Error('Плиссе оформляются отдельным заказом. Сначала оформите или очистите корзину.');
var positions = response.positions || [];
var missing = (response.missing || []).slice();
var prices = response.prices || {};
var adjustments = [];
var serverAdjustments = (response.adjustments || []).slice();
var table, sort, shipment;
try {
if (JSON.stringify(cartImportPriceContext()) !== JSON.stringify(priceContext)) {
throw new Error('Клиент или прайс изменился во время импорта. Повторите импорт. Корзина не изменена.');
}
table = JSON.parse(localStorage['JustCRM-OrderCart'] || '{}');
sort = JSON.parse(localStorage['JustCRM-OrderCart-Sort'] || '{}');
shipment = JSON.parse(localStorage['JustCRM-OrderCart-Shipment'] || '{}');
} catch (error) {
throw new Error('Не удалось прочитать текущую корзину или изменился клиент. Повторите импорт.');
}
var nextSort = Object.keys(sort).reduce(function(max, key) { return Math.max(max, Number(sort[key]) || 0); }, 0) + 1;
var added = 0;
positions.forEach(function(item) {
var currentPrice = prices[String(item.id)];
var quantity = Number(item.quantity);
var minimum = Number(item.minimum);
if (!(minimum > 0) || !isFinite(minimum)) minimum = 1;
if (!currentPrice || !(Number(currentPrice.price_number) > 0)) {
missing.push({ article: item.article, name: item.name, reason: 'Нет актуальной цены' });
return;
}
if (!(quantity > 0) || !isFinite(quantity)) {
missing.push({ article: item.article, name: item.name, reason: 'Некорректное количество' });
return;
}
var requested = quantity;
quantity = Math.max(quantity, minimum);
var adjustmentIndex = serverAdjustments.findIndex(function(adjustment) {
return String(adjustment.article || '') === String(item.article || '') && String(adjustment.name || '') === String(item.name || '') && Number(adjustment.quantity) === quantity;
});
if (adjustmentIndex >= 0) adjustments.push(serverAdjustments.splice(adjustmentIndex, 1)[0]);
else if (quantity > requested) adjustments.push({ article: item.article, name: item.name, requested: requested, quantity: quantity, minimum: minimum });
var key = String(item.id);
if (Object.prototype.hasOwnProperty.call(table, key)) {
var suffix = 1;
while (Object.prototype.hasOwnProperty.call(table, key + '_' + suffix)) suffix++;
key += '_' + suffix;
}
table[key] = cartImportRow(item, quantity, currentPrice.price_number, response.category, currentPrice.original_price);
sort[key] = nextSort++;
shipment[key] = minimum;
added++;
});
if (added) {
localStorage['JustCRM-OrderCart'] = JSON.stringify(table);
localStorage['JustCRM-OrderCart-Sort'] = JSON.stringify(sort);
localStorage['JustCRM-OrderCart-Shipment'] = JSON.stringify(shipment);
$('#marker-cart').show().find('.marker').text(Object.keys(table).length);
if (showCart !== false) openCart();
}
return { added: added, missing: missing, adjustments: adjustments };
}