PageSourceSearch

https://snooze.pub/assets/controllers/offer_convert_form_controlle…cea18a5210768438d6018300653d1f7592879aca.js

js snooze.pub collected 2026-10-07 06:56:04 UTC 2,831 bytes, 88 lines download raw bytes

1import { Controller } from "@hotwired/stimulus";
2
3function parseNumber(value) {
4  const parsed = parseFloat(String(value ?? "").replace(",", "."));
5  return Number.isNaN(parsed) ? 0 : parsed;
6}
7
8function round(amount) {
9  return Math.round(amount * 100) / 100;
10}
11
12function formatCurrency(amount) {
13  return new Intl.NumberFormat("de-LU", {
14    style: "currency",
15    currency: "EUR",
16    minimumFractionDigits: 2,
17    maximumFractionDigits: 2,
18  }).format(amount);
19}
20
21function lineAmounts(quantity, priceInclVat, taxRate, discountPercent, discountAmount) {
22  const grossBefore = round(quantity * priceInclVat);
23  let discount = 0;
24
25  if (discountAmount > 0) {
26    discount = Math.min(discountAmount, grossBefore);
27  } else if (discountPercent > 0) {
28    discount = round((grossBefore * discountPercent) / 100);
29  }
30
31  const gross = Math.max(grossBefore - discount, 0);
32  const divisor = 1 + taxRate / 100;
33  const net = divisor > 0 ? round(gross / divisor) : gross;
34  const vat = round(gross - net);
35
36  return { gross, vat, net };
37}
38
39export default class extends Controller {
40  connect() {
41    this.recalculate();
42    this._onInput = () => this.recalculate();
43    this.element.querySelectorAll("[data-offer-qty], [data-offer-price]").forEach((input) => {
44      input.addEventListener("input", this._onInput);
45      input.addEventListener("change", this._onInput);
46    });
47  }
48
49  disconnect() {
50    if (!this._onInput) return;
51    this.element.querySelectorAll("[data-offer-qty], [data-offer-price]").forEach((input) => {
52      input.removeEventListener("input", this._onInput);
53      input.removeEventListener("change", this._onInput);
54    });
55  }
56
57  recalculate() {
58    let invoiceGross = 0;
59    let invoiceVat = 0;
60
61    this.element.querySelectorAll("[data-offer-line]").forEach((row) => {
62      const quantity = parseNumber(row.querySelector("[data-offer-qty]")?.value);
63      const price = parseNumber(row.querySelector("[data-offer-price]")?.value);
64      const taxRate = parseNumber(row.dataset.taxRate);
65      const discountPercent = parseNumber(row.dataset.discountPercent);
66      const discountAmount = parseNumber(row.dataset.discountAmount);
67
68      const { gross, vat } = lineAmounts(quantity, price, taxRate, discountPercent, discountAmount);
69      invoiceGross += gross;
70      invoiceVat += vat;
71
72      const lineTotal = row.querySelector("[data-offer-line-total]");
73      if (lineTotal) {
74        lineTotal.textContent = formatCurrency(gross);
75      }
76    });
77
78    const invoiceTotal = this.element.querySelector("[data-offer-invoice-total]");
79    if (invoiceTotal) {
80      invoiceTotal.textContent = formatCurrency(invoiceGross);
81    }
82
83    const invoiceVatEl = this.element.querySelector("[data-offer-invoice-vat]");
84    if (invoiceVatEl) {
85      invoiceVatEl.textContent = formatCurrency(invoiceVat);
86    }
87  }
88};

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.