added quote
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Preventivi per matrice:
|
||||
* - permesso sales.quote.view (pagina sales_preventivo.php: mostra costi e margini)
|
||||
* - tabella sales_quotes: preventivi salvati con costo, ricarico, prezzo e commento
|
||||
*/
|
||||
final class AddSalesQuotes extends AbstractMigration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (!$this->fetchRow("SELECT id FROM auth_permissions WHERE name = 'sales.quote.view'")) {
|
||||
$this->table('auth_permissions')->insert([
|
||||
'name' => 'sales.quote.view',
|
||||
'display_name' => 'Preventivi matrici',
|
||||
'description' => 'Calcolare preventivi: costo mescola, ricarico, prezzo suggerito',
|
||||
'removable' => 1,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
])->saveData();
|
||||
}
|
||||
|
||||
if (!$this->hasTable('sales_quotes')) {
|
||||
$this->table('sales_quotes', ['collation' => 'utf8mb4_general_ci'])
|
||||
->addColumn('created_at', 'datetime')
|
||||
->addColumn('user_id', 'integer', ['null' => true])
|
||||
->addColumn('idmatrice', 'integer')
|
||||
->addColumn('matrice_name', 'string', ['limit' => 150])
|
||||
->addColumn('customer_code', 'string', ['limit' => 20, 'null' => true])
|
||||
->addColumn('customer_name', 'string', ['limit' => 200, 'null' => true])
|
||||
->addColumn('uom', 'string', ['limit' => 4])
|
||||
->addColumn('cost_mix', 'decimal', ['precision' => 12, 'scale' => 4, 'null' => true])
|
||||
->addColumn('scrap_pct', 'decimal', ['precision' => 6, 'scale' => 2, 'default' => 0])
|
||||
->addColumn('other_costs', 'decimal', ['precision' => 12, 'scale' => 4, 'default' => 0])
|
||||
->addColumn('cost_total', 'decimal', ['precision' => 12, 'scale' => 4, 'null' => true])
|
||||
->addColumn('markup_pct', 'decimal', ['precision' => 7, 'scale' => 2, 'null' => true])
|
||||
->addColumn('price_suggested', 'decimal', ['precision' => 12, 'scale' => 4, 'null' => true])
|
||||
->addColumn('price_offered', 'decimal', ['precision' => 12, 'scale' => 4, 'null' => true])
|
||||
->addColumn('ref_last_price', 'decimal', ['precision' => 12, 'scale' => 4, 'null' => true])
|
||||
->addColumn('ref_avg_price', 'decimal', ['precision' => 12, 'scale' => 4, 'null' => true])
|
||||
->addColumn('mix_detail', 'text', ['null' => true])
|
||||
->addColumn('comment', 'text', ['null' => true])
|
||||
->addIndex(['idmatrice'])
|
||||
->addIndex(['customer_code'])
|
||||
->addIndex(['created_at'])
|
||||
->create();
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if ($this->hasTable('sales_quotes')) $this->table('sales_quotes')->drop()->save();
|
||||
$this->execute("DELETE pr FROM auth_permission_role pr JOIN auth_permissions p ON p.id = pr.permission_id WHERE p.name = 'sales.quote.view'");
|
||||
$this->execute("DELETE FROM auth_permissions WHERE name = 'sales.quote.view'");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Preventivo per matrice: costo della mescola, storico prezzi di vendita, preventivi salvati.
|
||||
* Usa MagoMatriceStats (riconoscimento matrici, schede di lavoro, costo) e MagoMixPurchases (€/kg pagati).
|
||||
*/
|
||||
require_once __DIR__ . '/MagoMatriceStats.php';
|
||||
|
||||
class MagoQuote
|
||||
{
|
||||
/** Mesi di storico per prezzi e costo nel grafico */
|
||||
const HISTORY_MONTHS = 24;
|
||||
|
||||
/** Ricerca matrici per la tendina (nome, con foto e indicazione della scheda di lavoro) */
|
||||
public static function searchMatrici(PDO $pdo, string $q, int $limit = 30): array
|
||||
{
|
||||
$q = trim($q);
|
||||
$p = [];
|
||||
$w = '';
|
||||
if ($q !== '') {
|
||||
// cerco anche senza la M iniziale: "M00786S" trova "00786S"
|
||||
$q2 = preg_replace('/^M(?=\d)/i', '', $q);
|
||||
$w = "WHERE m.nome LIKE :q OR m.nome LIKE :q2";
|
||||
$p['q'] = '%' . $q . '%';
|
||||
$p['q2'] = '%' . $q2 . '%';
|
||||
}
|
||||
$st = $pdo->prepare("SELECT m.id, m.nome, m.photo,
|
||||
(SELECT COUNT(*) FROM work_sheets ws JOIN work_sheet_mescole wm ON wm.worksheet_id = ws.id
|
||||
WHERE ws.idmatrice = m.id AND wm.mix_weight_g_m > 0) AS n_gm
|
||||
FROM matrice m $w
|
||||
ORDER BY (m.nome LIKE :start) DESC, m.nome
|
||||
LIMIT " . (int)$limit);
|
||||
$p['start'] = ($q !== '' ? preg_replace('/^M(?=\d)/i', '', $q) : '') . '%';
|
||||
$st->execute($p);
|
||||
$out = [];
|
||||
foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
||||
$out[] = ['id' => (int)$r['id'], 'text' => $r['nome'], 'photo' => MagoMatriceStats::photoUrl($r['photo']), 'has_gm' => (int)$r['n_gm'] > 0];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** Valori Mago del campo Matrice/Misura che portano a questa matrice */
|
||||
public static function rawsFor(PDO $pdo, int $id): array
|
||||
{
|
||||
$raws = [];
|
||||
foreach ($pdo->query("SELECT DISTINCT matrice_misura FROM mago_sale_ord_detail WHERE matrice_misura <> ''")->fetchAll(PDO::FETCH_COLUMN) as $raw) {
|
||||
if (MagoMatriceStats::resolve($pdo, $raw)['id'] === $id) $raws[] = $raw;
|
||||
}
|
||||
return $raws;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tutti i dati per il preventivo.
|
||||
* @param string $kind m | kg
|
||||
*/
|
||||
public static function data(PDO $pdo, int $id, string $kind, ?string $customer): array
|
||||
{
|
||||
$kind = $kind === 'kg' ? 'kg' : 'm';
|
||||
$st = $pdo->prepare("SELECT id, nome, photo FROM matrice WHERE id = ?");
|
||||
$st->execute([$id]);
|
||||
$mat = $st->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$mat) return ['success' => false, 'message' => 'Matrice non trovata'];
|
||||
|
||||
$mi = MagoMatriceStats::mixInfo($pdo, [$id])[$id] ?? null;
|
||||
$prices = MagoMatriceStats::mixPrices($pdo);
|
||||
$cost = MagoMatriceStats::costOf($mi, $kind, $prices);
|
||||
|
||||
// mescole con tutti i dati per il calcolo modificabile nella pagina
|
||||
$mix = [];
|
||||
foreach ($mi['mix'] ?? [] as $x) {
|
||||
$p = $prices[$x['id']] ?? null;
|
||||
$mix[] = [
|
||||
'id' => $x['id'],
|
||||
'name' => $x['name'],
|
||||
'out' => $x['out'],
|
||||
'gm' => $x['gm'] !== null && $x['gm'] > 0 ? (float)$x['gm'] : null,
|
||||
'eur_kg' => $p ? $p['price'] : null,
|
||||
'last_price' => $p['last_price'] ?? null,
|
||||
'last_date' => $p['last_date'] ?? null,
|
||||
'old' => $p ? $p['old'] : null,
|
||||
'supplier' => $p['last_supplier'] ?? null,
|
||||
'check' => $p['check'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
// costo mese per mese
|
||||
$months = [];
|
||||
for ($i = self::HISTORY_MONTHS - 1; $i >= 0; $i--) $months[] = date('Y-m', strtotime("first day of -$i months"));
|
||||
$costMonths = [];
|
||||
if ($cost['cost'] !== null) {
|
||||
foreach ($months as $ym) {
|
||||
$c = MagoMatriceStats::costOf($mi, $kind, $prices, $ym);
|
||||
if ($c['cost'] !== null) $costMonths[$ym] = $c['cost'];
|
||||
}
|
||||
}
|
||||
|
||||
// vendite della matrice nell'unità scelta
|
||||
$raws = self::rawsFor($pdo, $id);
|
||||
$since = $months[0] . '-01';
|
||||
$d12 = date('Y-m-d', strtotime('-365 days'));
|
||||
$lines = [];
|
||||
foreach ($raws ? MagoMatriceStats::fetchLines($pdo, $since, [], $raws) : [] as $l) {
|
||||
if ($l['order_date'] < $since) continue;
|
||||
if (MagoMatriceStats::uomKind($l['uom']) !== $kind) continue;
|
||||
if ((float)$l['value'] <= 0 || (float)$l['qty'] <= 0) continue;
|
||||
$lines[] = $l;
|
||||
}
|
||||
$names = MagoMatriceStats::customerNames($pdo, array_column($lines, 'customer'));
|
||||
|
||||
$all = ['m' => [], 'v12' => 0, 'q12' => 0, 'min' => null, 'max' => null, 'clients' => []];
|
||||
$cus = ['m' => [], 'v12' => 0, 'q12' => 0, 'last_price' => null, 'last_date' => null, 'lines' => []];
|
||||
foreach ($lines as $l) {
|
||||
$q = (float)$l['qty'];
|
||||
$v = (float)$l['value'];
|
||||
$pr = $v / $q;
|
||||
$ym = substr($l['order_date'], 0, 7);
|
||||
$all['m'][$ym]['v'] = ($all['m'][$ym]['v'] ?? 0) + $v;
|
||||
$all['m'][$ym]['q'] = ($all['m'][$ym]['q'] ?? 0) + $q;
|
||||
if ($l['order_date'] > $d12) {
|
||||
$all['v12'] += $v;
|
||||
$all['q12'] += $q;
|
||||
$all['min'] = $all['min'] === null ? $pr : min($all['min'], $pr);
|
||||
$all['max'] = $all['max'] === null ? $pr : max($all['max'], $pr);
|
||||
$c = &$all['clients'][$l['customer']];
|
||||
$c['v'] = ($c['v'] ?? 0) + $v;
|
||||
$c['q'] = ($c['q'] ?? 0) + $q;
|
||||
unset($c);
|
||||
}
|
||||
if ($customer !== null && $l['customer'] === $customer) {
|
||||
$cus['m'][$ym]['v'] = ($cus['m'][$ym]['v'] ?? 0) + $v;
|
||||
$cus['m'][$ym]['q'] = ($cus['m'][$ym]['q'] ?? 0) + $q;
|
||||
if ($l['order_date'] > $d12) {
|
||||
$cus['v12'] += $v;
|
||||
$cus['q12'] += $q;
|
||||
}
|
||||
$cus['last_price'] = $pr;
|
||||
$cus['last_date'] = $l['order_date'];
|
||||
$cus['lines'][] = ['date' => $l['order_date'], 'ord' => $l['internal_ord_no'], 'qty' => round($q, 3), 'price' => round($pr, 4)];
|
||||
}
|
||||
}
|
||||
$avgM = function (array $m) {
|
||||
$o = [];
|
||||
ksort($m);
|
||||
foreach ($m as $ym => $x) $o[$ym] = round($x['v'] / $x['q'], 4);
|
||||
return $o;
|
||||
};
|
||||
$clients = [];
|
||||
foreach ($all['clients'] as $code => $c) $clients[] = ['code' => $code, 'name' => $names[$code] ?? $code, 'price' => round($c['v'] / $c['q'], 4), 'qty' => round($c['q'], 1)];
|
||||
usort($clients, fn($a, $b) => $b['qty'] <=> $a['qty']);
|
||||
|
||||
$avg12 = $all['q12'] > 0 ? $all['v12'] / $all['q12'] : null;
|
||||
return [
|
||||
'success' => true,
|
||||
'matrice' => [
|
||||
'id' => (int)$mat['id'],
|
||||
'name' => $mat['nome'],
|
||||
'photo' => MagoMatriceStats::photoUrl($mat['photo']),
|
||||
'raws' => $raws,
|
||||
'ws' => $mi['ws'] ?? null,
|
||||
'mix_source' => $mi['source'] ?? 'nessuna'
|
||||
],
|
||||
'kind' => $kind,
|
||||
'uom' => $kind === 'kg' ? 'KG' : 'MT',
|
||||
'price_months' => MagoMixPurchases::PRICE_MONTHS,
|
||||
'cost' => ['cost' => $cost['cost'], 'status' => $cost['status'], 'missing' => $cost['missing'], 'check' => $cost['check']],
|
||||
'mix' => $mix,
|
||||
'months' => $months,
|
||||
'cost_months' => $costMonths,
|
||||
'all' => [
|
||||
'months' => $avgM($all['m']),
|
||||
'avg12' => $avg12 !== null ? round($avg12, 4) : null,
|
||||
'min12' => $all['min'] !== null ? round($all['min'], 4) : null,
|
||||
'max12' => $all['max'] !== null ? round($all['max'], 4) : null,
|
||||
'clients' => array_slice($clients, 0, 12),
|
||||
'n_clients' => count($clients),
|
||||
'markup' => ($avg12 && $cost['cost'] && $cost['status'] === 'ok') ? round(($avg12 / $cost['cost'] - 1) * 100, 0) : null,
|
||||
],
|
||||
'customer' => $customer === null ? null : [
|
||||
'code' => $customer,
|
||||
'name' => $names[$customer] ?? self::customerName($pdo, $customer),
|
||||
'months' => $avgM($cus['m']),
|
||||
'avg12' => $cus['q12'] > 0 ? round($cus['v12'] / $cus['q12'], 4) : null,
|
||||
'last_price' => $cus['last_price'] !== null ? round($cus['last_price'], 4) : null,
|
||||
'last_date' => $cus['last_date'],
|
||||
'lines' => array_slice(array_reverse($cus['lines']), 0, 12),
|
||||
],
|
||||
'quotes' => self::quotes($pdo, $id, null),
|
||||
];
|
||||
}
|
||||
|
||||
public static function customerName(PDO $pdo, string $code): string
|
||||
{
|
||||
return MagoMatriceStats::customerNames($pdo, [$code])[$code] ?? $code;
|
||||
}
|
||||
|
||||
/** Preventivi salvati di una matrice (tutti i clienti), più recenti prima */
|
||||
public static function quotes(PDO $pdo, ?int $idmatrice, ?string $customer, int $limit = 50): array
|
||||
{
|
||||
$w = [];
|
||||
$p = [];
|
||||
if ($idmatrice) {
|
||||
$w[] = 'q.idmatrice = :m';
|
||||
$p['m'] = $idmatrice;
|
||||
}
|
||||
if ($customer) {
|
||||
$w[] = 'q.customer_code = :c';
|
||||
$p['c'] = $customer;
|
||||
}
|
||||
$user = "TRIM(CONCAT(IFNULL(u.first_name, ''), ' ', IFNULL(u.last_name, '')))";
|
||||
try {
|
||||
$st = $pdo->prepare("SELECT q.*, NULLIF($user, '') AS user_name, u.username
|
||||
FROM sales_quotes q LEFT JOIN auth_users u ON u.id = q.user_id
|
||||
" . ($w ? 'WHERE ' . implode(' AND ', $w) : '') . "
|
||||
ORDER BY q.created_at DESC, q.id DESC LIMIT " . (int)$limit);
|
||||
$st->execute($p);
|
||||
return $st->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** Salva un preventivo. I numeri arrivano dalla pagina; il costo è ricalcolato dalle mescole inviate. */
|
||||
public static function save(PDO $pdo, array $in, ?int $userId): array
|
||||
{
|
||||
$id = (int)($in['idmatrice'] ?? 0);
|
||||
$st = $pdo->prepare("SELECT nome FROM matrice WHERE id = ?");
|
||||
$st->execute([$id]);
|
||||
$name = $st->fetchColumn();
|
||||
if (!$name) return ['success' => false, 'message' => 'Matrice non trovata'];
|
||||
$kind = ($in['kind'] ?? 'm') === 'kg' ? 'kg' : 'm';
|
||||
$num = function ($v) {
|
||||
if ($v === null || $v === '') return null;
|
||||
return (float)str_replace(',', '.', (string)$v);
|
||||
};
|
||||
|
||||
// mescole inviate dalla pagina (g/m e €/kg eventualmente corretti a mano)
|
||||
$mix = json_decode((string)($in['mix'] ?? '[]'), true) ?: [];
|
||||
$cost = 0.0;
|
||||
$ok = 0;
|
||||
$gmTot = 0.0;
|
||||
foreach ($mix as $x) $gmTot += max(0, (float)($x['gm'] ?? 0));
|
||||
foreach ($mix as &$x) {
|
||||
$gm = $num($x['gm'] ?? null);
|
||||
$eur = $num($x['eur_kg'] ?? null);
|
||||
$x = ['id' => (int)($x['id'] ?? 0), 'name' => (string)($x['name'] ?? ''), 'gm' => $gm, 'eur_kg' => $eur, 'edited' => !empty($x['edited'])];
|
||||
if ($eur === null) continue;
|
||||
if ($kind === 'm') {
|
||||
if ($gm) {
|
||||
$cost += $gm / 1000 * $eur;
|
||||
$ok++;
|
||||
}
|
||||
} else {
|
||||
$share = $gmTot > 0 ? max(0, (float)$gm) / $gmTot : 1 / max(1, count($mix));
|
||||
$cost += $share * $eur;
|
||||
$ok++;
|
||||
}
|
||||
}
|
||||
unset($x);
|
||||
$costMix = $ok ? $cost : null;
|
||||
$scrap = $num($in['scrap_pct'] ?? 0) ?? 0;
|
||||
$other = $num($in['other_costs'] ?? 0) ?? 0;
|
||||
$markup = $num($in['markup_pct'] ?? null);
|
||||
$costTotal = $costMix !== null ? $costMix * (1 + $scrap / 100) + $other : ($other ?: null);
|
||||
$suggested = ($costTotal !== null && $markup !== null) ? $costTotal * (1 + $markup / 100) : null;
|
||||
$offered = $num($in['price_offered'] ?? null) ?? $suggested;
|
||||
if ($offered === null) return ['success' => false, 'message' => 'Manca il prezzo: indica il ricarico o il prezzo proposto'];
|
||||
|
||||
$cust = trim((string)($in['customer'] ?? ''));
|
||||
$st = $pdo->prepare("INSERT INTO sales_quotes
|
||||
(created_at, user_id, idmatrice, matrice_name, customer_code, customer_name, uom, cost_mix, scrap_pct, other_costs, cost_total,
|
||||
markup_pct, price_suggested, price_offered, ref_last_price, ref_avg_price, mix_detail, comment)
|
||||
VALUES (NOW(), :u, :m, :mn, :cc, :cn, :uom, :cm, :sp, :oc, :ct, :mk, :ps, :po, :rl, :ra, :md, :cmt)");
|
||||
$st->execute([
|
||||
'u' => $userId ?: null,
|
||||
'm' => $id,
|
||||
'mn' => $name,
|
||||
'cc' => $cust !== '' ? $cust : null,
|
||||
'cn' => $cust !== '' ? self::customerName($pdo, $cust) : null,
|
||||
'uom' => $kind === 'kg' ? 'KG' : 'MT',
|
||||
'cm' => $costMix,
|
||||
'sp' => $scrap,
|
||||
'oc' => $other,
|
||||
'ct' => $costTotal,
|
||||
'mk' => $markup,
|
||||
'ps' => $suggested,
|
||||
'po' => $offered,
|
||||
'rl' => $num($in['ref_last_price'] ?? null),
|
||||
'ra' => $num($in['ref_avg_price'] ?? null),
|
||||
'md' => json_encode($mix, JSON_UNESCAPED_UNICODE),
|
||||
'cmt' => trim((string)($in['comment'] ?? '')) ?: null,
|
||||
]);
|
||||
return ['success' => true, 'id' => (int)$pdo->lastInsertId()];
|
||||
}
|
||||
|
||||
public static function delete(PDO $pdo, int $id): bool
|
||||
{
|
||||
$st = $pdo->prepare("DELETE FROM sales_quotes WHERE id = ?");
|
||||
$st->execute([$id]);
|
||||
return $st->rowCount() > 0;
|
||||
}
|
||||
}
|
||||
@@ -484,6 +484,57 @@
|
||||
<p>🟢 da 60% · 🟡 da 40 a 60% · 🔴 sotto 40%.</p>
|
||||
<p class="sh-note">Contano solo le matrici con costo completo. Clic su una matrice per il dettaglio con la linea del costo nel grafico.</p>`,
|
||||
},
|
||||
/* ===================== PREVENTIVO ===================== */
|
||||
"page.quote": {
|
||||
t: "Preventivo: come si usa",
|
||||
b: `<ol class="ps-3">
|
||||
<li>Scegli la <b>matrice</b>, il <b>cliente</b> (facoltativo) e se vendi <b>al metro o al kg</b>.</li>
|
||||
<li><b>Costo</b>: le mescole della scheda di lavoro con g/m ed €/kg pagati in Mago. Puoi correggere i numeri (diventano gialli) e aggiungere scarto e altri costi.</li>
|
||||
<li><b>Prezzo</b>: scegli il ricarico sul costo. Il prezzo suggerito si calcola da solo; il <b>prezzo proposto</b> si può arrotondare a mano.</li>
|
||||
<li>Confronta con <b>ultimo prezzo al cliente</b>, media di mercato e forbice, poi scrivi un commento e <b>Salva</b>.</li>
|
||||
</ol>
|
||||
<p class="sh-note">I preventivi salvati restano in fondo alla pagina e si riaprono con un clic. Stampa per avere un foglio pulito.</p>`,
|
||||
},
|
||||
"pv.cost": {
|
||||
t: "Costo",
|
||||
b: `<ul>
|
||||
<li><b>€/metro</b> = somma di g/m × €/kg ÷ 1000 di ogni mescola della scheda di lavoro.</li>
|
||||
<li><b>€/kg</b> (vendita a chili) = media dei €/kg pesata sui g/m.</li>
|
||||
<li>€/kg = media pagata in Mago negli ultimi 3 mesi. <span class="sh-chip sh-amber">prezzo vecchio</span> = nessun acquisto recente, si usa l'ultimo. <span class="sh-chip sh-orange">da verificare</span> = fornitore in conto lavoro.</li>
|
||||
</ul>
|
||||
<p class="sh-note">Le correzioni valgono solo per questo preventivo e vengono salvate con lui.</p>`,
|
||||
},
|
||||
"pv.scrap": {
|
||||
t: "Scarto %",
|
||||
b: `<p>Percentuale di mescola persa in produzione (avvii, sfridi, scarti). Il costo della mescola viene aumentato di questa percentuale.</p>`,
|
||||
},
|
||||
"pv.other": {
|
||||
t: "Altri costi",
|
||||
b: `<p>Costi per metro o per kg da aggiungere alla mescola: lavorazioni esterne (biadesivo, giunzione, raspatura), imballo, trasporto, quota di lavorazione.</p>`,
|
||||
},
|
||||
"pv.price": {
|
||||
t: "Prezzo suggerito",
|
||||
b: `<p><b>Prezzo suggerito</b> = costo totale × (1 + ricarico %). Es. costo 0,50 €/m con ricarico 100% → 1,00 €/m.</p>
|
||||
<p>Il pulsante <b>come mercato</b> usa il ricarico che oggi si ottiene con il prezzo medio di vendita della matrice.</p>
|
||||
<p>🟢 margine da 60% · 🟡 da 40 a 60% · 🔴 sotto 40% (margine = (prezzo − costo) ÷ prezzo).</p>`,
|
||||
},
|
||||
"pv.offered": {
|
||||
t: "Prezzo proposto",
|
||||
b: `<p>È il prezzo che finisce nel preventivo: parte dal suggerito e si può arrotondare a mano. Accanto, il margine che ne risulta.</p>`,
|
||||
},
|
||||
"pv.refs": {
|
||||
t: "Confronto",
|
||||
b: `<ul>
|
||||
<li><b>Ultimo prezzo al cliente</b> e <b>media cliente</b>: cosa ha pagato finora.</li>
|
||||
<li><b>Media mercato</b>: prezzo medio di tutti i clienti negli ultimi 12 mesi.</li>
|
||||
<li>La percentuale verde/rossa dice quanto il prezzo proposto è sopra o sotto.</li>
|
||||
</ul>`,
|
||||
},
|
||||
"pv.chart": {
|
||||
t: "Costo e prezzi nel tempo",
|
||||
b: `<p>Mese per mese: <b>costo della mescola</b> (con i prezzi pagati in quel mese), <b>prezzo medio</b> di tutti i clienti e del cliente scelto. Le righe orizzontali sono il costo totale e il prezzo proposto.</p>
|
||||
<p class="sh-note">Se la linea del costo sale e quella dei prezzi no, il margine si sta riducendo.</p>`,
|
||||
},
|
||||
"mx.map": {
|
||||
t: "Mappa dei prezzi",
|
||||
b: `<p>Ogni bolla è una matrice venduta nel periodo (le prime 150 per valore).</p>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user