1/** 2 * Canje FarmaPremium a NIVEL CARRITO (BUG-655). 3 * 4 * Depende de newCart.js (formatCurrency, formatPoints, getDiscountLines y los selectores 5 * de resumen definidos en su cabecera): cargar SIEMPRE después de newCart.js. 6 * Solo se incluye en el layout cuando el canje está disponible ($fpRedemptionAvailable), 7 * por lo que las farmacias del club de marca (Orissens/Nakama) nunca descargan este fichero. 8 */ 9 10$(document).ready(function () { 11 12 // Refresca el resumen del carrito tras aplicar/quitar canje (mismo shape que cart.quantity). 13 function fpRefreshCartSummary(data) { 14 $('.cart-count').html('(' + (data['count'] || 0) + ')'); 15 $('#sticky-Cart-Count').html(data['cartInfo'] || data['count'] || 0); 16 $('.count-product-carts').html(data['count'] || 0); 17 18 $(classUpdateShippingAmountCheckbox).html(formatCurrency(data.shipping_amount)).fadeIn(); 19 $('.shipping-amount-delivery').html(formatCurrency(data.shipping_amount_delivery || data.shipping_amount)).fadeIn(); 20 $(classUpdateTotalBeforeDiscount).html(data.totalBeforeDiscount + 'â¬').fadeIn(); 21 $('.layette-total-net-shipping').html(formatCurrency(data.total)).fadeIn(); 22 23 getDiscountLines(data); 24 25 if (data.points) { 26 $(summaryAccumulatedPoints).html('+' + formatPoints(data.points.accumulated_points)); 27 $(summaryRedeemedPoints).html('-' + formatPoints(data.points.redeemed_points)); 28 $(summaryTotalPoints).html(formatPoints(data.points.total_points)); 29 $(summaryRemainingPoints).html(formatPoints(data.points.remaining_points)); 30 } 31 } 32 33 // Fuente de verdad del importe a canjear: el input editable (admite cualquier valor). 34 function fpCartEuros($c) { 35 var v = parseFloat($c.find('.fp-redeem-cart-input').val()); 36 return isNaN(v) ? 0 : v; 37 } 38 39 function fpCartToken() { 40 return $('input[name=_token]').first().val(); 41 } 42 43 // Muestra el motivo del rechazo del back. 'code' es el error que devuelve el back 44 // (insufficient_balance tiene texto propio); cualquier otro cae al mensaje genérico. 45 function fpCartShowError($c, code) { 46 var texto = (code === 'insufficient_balance') 47 ? $c.attr('data-error-insufficient-text') 48 : $c.attr('data-error-text'); 49 50 if (!texto) { 51 return; 52 } 53 54 $c.find('.fp-redeem-cart-error').text(texto).css('display', ''); 55 } 56 57 function fpCartHideError($c) { 58 $c.find('.fp-redeem-cart-error').text('').css('display', 'none'); 59 } 60 61 function fpCartBounds($c) { 62 return { 63 min: parseFloat($c.attr('data-min-euros')) || 0, 64 max: parseFloat($c.attr('data-max-euros')) || 0 65 }; 66 } 67 68 // Abrir/cerrar el bloque de canje (cabecera clic) 69 $('body').on('click', '.fp-redeem-cart-head', function () { 70 var $cart = $(this).closest('.fp-redeem-cart'); 71 $cart.toggleClass('is-open'); 72 $cart.find('.fp-redeem-cart-body').stop(true, true).slideToggle(180); 73 }); 74 75 // Slider -> input (sincroniza al arrastrar) 76 $('body').on('input change', '.fp-redeem-cart-slider', function () { 77 var $c = $(this).closest('.fp-redeem-cart'); 78 $c.find('.fp-redeem-cart-input').val(parseFloat($(this).val()).toFixed(2)); 79 // Al tocar el importe el error anterior deja de aplicar: si no, se queda pegado. 80 fpCartHideError($c); 81 }); 82 83 // Input -> slider. Mientras escribe solo refleja en el slider; al salir (change) se acota. 84 $('body').on('input', '.fp-redeem-cart-input', function () { 85 fpCartHideError($(this).closest('.fp-redeem-cart')); 86 var $c = $(this).closest('.fp-redeem-cart'); 87 var v = parseFloat($(this).val()); 88 if (!isNaN(v)) { 89 $c.find('.fp-redeem-cart-slider').val(v); 90 } 91 }); 92 $('body').on('change', '.fp-redeem-cart-input', function () { 93 var $c = $(this).closest('.fp-redeem-cart'); 94 var b = fpCartBounds($c); 95 var v = parseFloat($(this).val()); 96 if (isNaN(v)) { v = b.min; } 97 v = Math.min(b.max, Math.max(b.min, Math.round(v * 100) / 100)); 98 $(this).val(v.toFixed(2)); 99 $c.find('.fp-redeem-cart-slider').val(v); 100 }); 101 102 // Aplicar canje (a nivel carrito) 103 $('body').on('click', '.fp-redeem-cart-apply', function () { 104 var $btn = $(this); 105 var $c = $btn.closest('.fp-redeem-cart'); 106 var ratio = parseFloat($c.attr('data-ratio')) || 0.01; 107 var euros = fpCartEuros($c); 108 var points = Math.round(euros / ratio); 109 if (points <= 0 || $btn.prop('disabled')) { 110 return; 111 } 112 $btn.prop('disabled', true); 113 114 $.ajax({ 115 url: $c.attr('data-apply-url'), 116 method: 'post', 117 data: { points: points, _token: fpCartToken() }, 118 success: function (data) { 119 // El importe que manda es el CONFIRMADO por el back, no el que se pidió: si el 120 // back lo rechaza o lo clampa, pintar el pedido mentirÃa al usuario (se veÃa 121 // "Canje aplicado: 5,00 â¬" con el total sin descontar). Sin confirmación 122 // (fpRedeem ausente o sin applied) el fallback es 0 -> rama de error; NUNCA 123 // el importe pedido, que resucitarÃa exactamente esa mentira. 124 var applied = 0; 125 if (data && data.fpRedeem && typeof data.fpRedeem.applied !== 'undefined') { 126 applied = parseFloat(data.fpRedeem.applied) || 0; 127 } 128 129 fpRefreshCartSummary(data); 130 $btn.prop('disabled', false); 131 132 if (!applied || applied <= 0) { 133 fpCartShowError($c, null); 134 return; 135 } 136 137 fpCartHideError($c); 138 var tpl = $c.attr('data-applied-text') || ':euros â¬'; 139 $c.find('.fp-redeem-cart-applied-text').text(tpl.replace(':euros', applied.toFixed(2).replace('.', ',')));
140 // Estado aplicado: oculta cabecera/flecha y controles (lo gestiona la clase + SCSS). 141 $c.removeClass('is-open').addClass('is-applied'); 142 $c.find('.fp-redeem-cart-body').css('display', 'block'); 143 // Convergencia de descuentos: código y canje COEXISTEN. Ya NO se oculta el mensaje 144 // del código promocional al canjear (ambos descuentos conviven en el resumen). 145 }, 146 error: function (xhr) { 147 $btn.prop('disabled', false); 148 149 // 422 = el back rechaza el canje con un motivo. Antes se ignoraba en silencio. 150 var code = null; 151 try { 152 code = (xhr.responseJSON || JSON.parse(xhr.responseText || '{}')).error || null; 153 } catch (e) { 154 code = null; 155 } 156 157 fpCartShowError($c, code); 158 } 159 }); 160 }); 161 162 // Quita el canje vÃa back y devuelve el bloque al estado sin canje (cabecera y controles). 163 function fpCartRemoveRedeem($c, onDone) { 164 $.ajax({ 165 url: $c.attr('data-remove-url'), 166 method: 'post', 167 data: { _token: fpCartToken() }, 168 success: function (data) { 169 fpRefreshCartSummary(data); 170 $c.removeClass('is-applied').addClass('is-open'); 171 $c.find('.fp-redeem-cart-applied').css('display', ''); 172 $c.find('.fp-redeem-cart-controls').css('display', ''); 173 $c.find('.fp-redeem-cart-body').css('display', 'block'); 174 if (onDone) { onDone(); } 175 }, 176 error: function () { 177 if (onDone) { onDone(); } 178 } 179 }); 180 } 181 182 // Quitar canje (a nivel carrito) 183 $('body').on('click', '.fp-redeem-cart-remove', function () { 184 var $btn = $(this); 185 var $c = $btn.closest('.fp-redeem-cart'); 186 if ($btn.prop('disabled')) { 187 return; 188 } 189 $btn.prop('disabled', true); 190 fpCartRemoveRedeem($c, function () { 191 $btn.prop('disabled', false); 192 }); 193 }); 194 195 // El canje NO está soportado con transferencia bancaria (el CRM no dispara la transacción 196 // FP al confirmar el pago): al elegirla se quita el canje aplicado y se oculta el bloque. 197 function fpSyncRedeemWithPaymentMethod() { 198 var $c = $('.fp-redeem-cart'); 199 if (!$c.length) { return; } 200 if (fpTransferPaymentSelected()) { 201 if ($c.hasClass('is-applied')) { 202 fpCartRemoveRedeem($c); 203 } 204 $c.hide(); 205 } else { 206 $c.show(); 207 } 208 } 209 210 $('body').on('change', 'input[name=payment_method]', fpSyncRedeemWithPaymentMethod); 211 // Estado inicial: la transferencia puede venir preseleccionada (farmacia sin TPV). 212 fpSyncRedeemWithPaymentMethod(); 213 214}); 215 216// ¿Está seleccionada la transferencia bancaria como método de pago? 217function fpTransferPaymentSelected() { 218 return $('input[name=payment_method]:checked').val() === 'transfer'; 219} 220 221// Reajusta el tope del canje FP (slider + input) cuando cambia el neto del carrito. 222// GLOBAL: la invoca getDiscountLines (newCart.js) tras cualquier cambio de carrito. 223function fpApplyRedeemState(state) { 224 var $cart = $('.fp-redeem-cart'); 225 if (!$cart.length) { return; } 226 227 if (!state || !state.available || fpTransferPaymentSelected()) { 228 $cart.hide(); 229 return; 230 } 231 $cart.show(); 232 233 var max = parseFloat(state.max) || 0; 234 var min = parseFloat($cart.attr('data-min-euros')) || 0; 235 $cart.attr('data-max-euros', max); 236 237 var $slider = $cart.find('.fp-redeem-cart-slider'); 238 var $input = $cart.find('.fp-redeem-cart-input'); 239 $slider.attr('max', max); 240 $input.attr('max', max); 241 242 // Si el valor seleccionado supera el nuevo tope, acotarlo. 243 var cur = parseFloat($input.val()); 244 if (isNaN(cur)) { cur = min; } 245 if (cur > max) { 246 cur = Math.max(min, max); 247 $slider.val(cur); 248 $input.val(cur.toFixed(2)); 249 } 250 251 // Reflejar el estado del canje recalculado por el back tras cualquier cambio de carrito. 252 var applied = parseFloat(state.applied) || 0; 253 if (applied >= min) { 254 // Sigue aplicado: refrescar el importe mostrado (el back lo reclampa al neto). 255 if ($cart.hasClass('is-applied')) { 256 var tpl = $cart.attr('data-applied-text') || ':euros â¬'; 257 $cart.find('.fp-redeem-cart-applied-text').text(tpl.replace(':euros', applied.toFixed(2).replace('.', ','))); 258 } 259 } else if ($cart.hasClass('is-applied')) { 260 // El back ELIMINà el canje (ya no cabe): volver al estado no-aplicado (reaparece el slider). 261 $cart.removeClass('is-applied').addClass('is-open'); 262 $cart.find('.fp-redeem-cart-applied').css('display', '');
263 $cart.find('.fp-redeem-cart-controls').css('display', ''); 264 $cart.find('.fp-redeem-cart-body').css('display', 'block'); 265 } 266}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.