Wizard: 2 cases génériques Prix marketing / Prix original (barré) sur toutes les lignes + aperçu client WYSIWYG
- Prix original (regular_price) disponible sur lignes récurrentes ET ponctuelles (avant: ponctuelles seulement) - Si Prix original > Prix marketing → barré affiché (sommaire + récap + aperçu live dans l'éditeur) - Forfait récurrent barré remonte au contrat (monthly_regular) → page d'acceptation affiche <s>99.95</s> 79.95/mois - Cohérent avec install (install_regular 360→240). Aucun impact CRTC (rabais go-forward, jamais récupéré) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4b2e6fb698
commit
e2104c93f2
|
|
@ -582,11 +582,11 @@
|
|||
<span class="sommaire-sep">·</span>
|
||||
<span class="text-grey-6">{{ item.contract_months }} mois</span>
|
||||
</template>
|
||||
<template v-if="item.billing === 'onetime' && item.regular_price > item.rate">
|
||||
<template v-if="item.regular_price > item.rate">
|
||||
<span class="sommaire-sep">·</span>
|
||||
<q-chip dense square color="orange-1" text-color="orange-9"
|
||||
icon="celebration" size="sm" style="margin:0">
|
||||
Promo {{ ((item.regular_price - item.rate) * item.qty).toFixed(2) }}$
|
||||
Promo {{ ((item.regular_price - item.rate) * item.qty).toFixed(2) }}${{ item.billing === 'recurring' ? '/mois' : '' }}
|
||||
</q-chip>
|
||||
</template>
|
||||
</div>
|
||||
|
|
@ -623,13 +623,14 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row q-col-gutter-sm q-mt-xs items-center">
|
||||
<div class="col-3">
|
||||
<div :class="item.billing === 'recurring' ? 'col-2' : 'col-3'">
|
||||
<q-input v-model.number="item.qty" dense outlined type="number" min="1"
|
||||
label="Qté" :input-style="{ fontSize: '0.82rem' }" />
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<div :class="item.billing === 'recurring' ? 'col-4' : 'col-5'">
|
||||
<q-input v-model.number="item.rate" dense outlined type="number" step="0.01"
|
||||
label="Prix $" :input-style="{ fontSize: '0.82rem' }">
|
||||
label="Prix marketing $" :input-style="{ fontSize: '0.82rem' }"
|
||||
title="Prix réellement facturé / affiché au client">
|
||||
<template v-slot:append>
|
||||
<button type="button" class="billing-pill" :class="billingPillClass(item)"
|
||||
@click.stop="cycleBilling(item)"
|
||||
|
|
@ -640,19 +641,32 @@
|
|||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<q-input v-if="item.billing === 'recurring'" v-model.number="item.contract_months"
|
||||
<div v-if="item.billing === 'recurring'" class="col-3">
|
||||
<q-input v-model.number="item.contract_months"
|
||||
dense outlined type="number" label="Mois" title="Durée du contrat en mois"
|
||||
:input-style="{ fontSize: '0.78rem', textAlign: 'center' }" />
|
||||
<q-input v-else v-model.number="item.regular_price" dense outlined type="number" step="0.01"
|
||||
label="Prix régulier (si promo)" placeholder="0 = sans promo"
|
||||
:input-style="{ fontSize: '0.75rem' }">
|
||||
</div>
|
||||
<div :class="item.billing === 'recurring' ? 'col-3' : 'col-4'">
|
||||
<q-input v-model.number="item.regular_price" dense outlined type="number" step="0.01"
|
||||
label="Prix original" placeholder="vide = aucun"
|
||||
:input-style="{ fontSize: '0.75rem' }"
|
||||
title="Prix barré affiché au client. Laisser vide (ou ≤ prix marketing) = aucun prix barré.">
|
||||
<template v-slot:append>
|
||||
<q-icon name="local_offer" size="14px" color="orange-7" />
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Aperçu client WYSIWYG : le prix marketing barre le prix original -->
|
||||
<div v-if="Number(item.regular_price) > Number(item.rate)" class="q-mt-xs"
|
||||
style="font-size:0.78rem;display:flex;align-items:center;gap:6px;flex-wrap:wrap">
|
||||
<span class="text-grey-6">Aperçu client :</span>
|
||||
<span style="text-decoration:line-through;color:#9ca3af">{{ Number(item.regular_price).toFixed(2) }}$</span>
|
||||
<span class="text-weight-bold text-green-8">{{ Number(item.rate).toFixed(2) }}${{ item.billing === 'recurring' ? '/mois' : '' }}</span>
|
||||
<q-chip dense square color="orange-1" text-color="orange-9" size="sm" style="margin:0">
|
||||
−{{ ((Number(item.regular_price) - Number(item.rate)) * (item.qty || 1)).toFixed(2) }}${{ item.billing === 'recurring' ? '/mois' : '' }}
|
||||
</q-chip>
|
||||
</div>
|
||||
|
||||
<!-- Bundle steps (installation pill) kept here so editing a
|
||||
service still lets the rep tweak which install steps come
|
||||
|
|
@ -716,9 +730,9 @@
|
|||
<q-icon :name="item.billing === 'recurring' ? 'autorenew' : 'shopping_cart'" size="14px"
|
||||
:color="item.billing === 'recurring' ? 'orange-7' : 'indigo-6'" />
|
||||
<span class="col">{{ item.qty }}x {{ item.item_name }}</span>
|
||||
<span v-if="item.billing === 'onetime' && item.regular_price > item.rate"
|
||||
<span v-if="item.regular_price > item.rate"
|
||||
class="text-caption text-orange-8" style="text-decoration:line-through;opacity:0.7">
|
||||
{{ (item.qty * item.regular_price).toFixed(2) }}$
|
||||
{{ (item.qty * item.regular_price).toFixed(2) }}${{ item.billing === 'recurring' ? '/mois' : '' }}
|
||||
</span>
|
||||
<span class="text-weight-bold">{{ (item.qty * item.rate).toFixed(2) }}${{ item.billing === 'recurring' ? '/mois' : '' }}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -244,6 +244,9 @@ export function useWizardPublish ({ props, emit, state }) {
|
|||
try {
|
||||
const durationMonths = Math.max(...recurringItems.map(i => i.contract_months || 12))
|
||||
const monthlyRate = recurringItems.reduce((s, i) => s + (i.qty * i.rate), 0)
|
||||
// Prix original (barré) du forfait : somme des prix originaux si > marketing, sinon le marketing.
|
||||
const monthlyRegular = recurringItems.reduce(
|
||||
(s, i) => s + (i.qty * ((i.regular_price || 0) > i.rate ? i.regular_price : i.rate)), 0)
|
||||
const benefits = onetimeItems
|
||||
.filter(i => (i.regular_price || 0) > i.rate)
|
||||
.map(i => ({
|
||||
|
|
@ -261,6 +264,7 @@ export function useWizardPublish ({ props, emit, state }) {
|
|||
contract_type: contractType,
|
||||
duration_months: durationMonths,
|
||||
monthly_rate: monthlyRate,
|
||||
monthly_regular: monthlyRegular > monthlyRate ? monthlyRegular : 0,
|
||||
service_location: serviceLocation,
|
||||
quotation: orderDocName,
|
||||
start_date: today,
|
||||
|
|
|
|||
|
|
@ -186,6 +186,7 @@ async function handle (req, res, method, path) {
|
|||
end_date: endDate,
|
||||
duration_months: duration,
|
||||
monthly_rate: body.monthly_rate || 0,
|
||||
monthly_regular: body.monthly_regular || 0,
|
||||
install_fee: body.install_fee || 0,
|
||||
install_regular: body.install_regular || 0,
|
||||
service_location: body.service_location || '',
|
||||
|
|
@ -942,6 +943,8 @@ function renderAcceptancePage (contract, token) {
|
|||
const installFee = contract.install_fee || 0
|
||||
const installRegular = contract.install_regular || 0
|
||||
const installMonthly = installFee ? round2(installFee / duration) : 0
|
||||
const monthlyRegular = contract.monthly_regular || 0
|
||||
const mr = monthlyRegular > (contract.monthly_rate || 0)
|
||||
|
||||
return `<!DOCTYPE html><html lang="fr"><head>
|
||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
|
|
@ -999,7 +1002,7 @@ tfoot td{font-weight:700;color:#00733a;background:#f4fff6;border-top:1px solid #
|
|||
</div>
|
||||
|
||||
<div class="field"><span class="label">Client</span><span class="value">${escapeHtml(contract.customer_name || contract.customer || '')}</span></div>
|
||||
<div class="field"><span class="label">Mensualité</span><span class="value">${contract.monthly_rate || 0} $/mois <span style="color:#9ca3af;font-weight:400;font-size:12px">(+taxes)</span></span></div>
|
||||
<div class="field"><span class="label">Mensualité</span><span class="value">${mr ? `<s style="color:#9ca3af;font-weight:400">${monthlyRegular} $</s> ` : ''}${contract.monthly_rate || 0} $/mois <span style="color:#9ca3af;font-weight:400;font-size:12px">(+taxes)</span></span></div>
|
||||
<div class="field"><span class="label">Durée</span><span class="value">${duration} mois</span></div>
|
||||
<div class="field"><span class="label">Début prévu</span><span class="value">${contract.start_date || 'À déterminer'}</span></div>
|
||||
|
||||
|
|
@ -1007,7 +1010,7 @@ tfoot td{font-weight:700;color:#00733a;background:#f4fff6;border-top:1px solid #
|
|||
<h3>Détail mensuel</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><td>Forfait</td><td class="r">${contract.monthly_rate || 0} $</td></tr>
|
||||
<tr><td>Forfait${mr ? ` (<s style="color:#9ca3af">${monthlyRegular} $</s>)` : ''}</td><td class="r">${contract.monthly_rate || 0} $</td></tr>
|
||||
<tr><td>Installation ${installRegular > installFee ? `(<s style="color:#9ca3af">${installRegular} $</s> ${installFee} $)` : `(${installFee} $)`} — financement ${duration} mois</td><td class="r">+${installMonthly} $</td></tr>
|
||||
<tr><td>Crédit installation nouveau client (${duration} mois)</td><td class="r" style="color:#019547">−${installMonthly} $</td></tr>
|
||||
</tbody>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user