ALVA deployment config
Guidad Felsökning / test-och-bygg (push) Has been cancelled
Guidad Felsökning / tjanster (push) Has been cancelled
Guidad Felsökning / terraform (push) Has been cancelled
Guidad Felsökning / leverantorskedja (push) Has been cancelled
Guidad Felsökning / aterstallning (push) Has been cancelled
Guidad Felsökning / publicera (push) Has been cancelled
Guidad Felsökning / driftsatt (push) Has been cancelled
Guidad Felsökning / test-och-bygg (push) Has been cancelled
Guidad Felsökning / tjanster (push) Has been cancelled
Guidad Felsökning / terraform (push) Has been cancelled
Guidad Felsökning / leverantorskedja (push) Has been cancelled
Guidad Felsökning / aterstallning (push) Has been cancelled
Guidad Felsökning / publicera (push) Has been cancelled
Guidad Felsökning / driftsatt (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
// ALVA-PROC-0002 · Abonnemang.
|
||||
//
|
||||
// ---- Hållningen ---------------------------------------------------------
|
||||
//
|
||||
// Vi litar på kunden. Det är inte en artighet utan ett designbeslut med
|
||||
// följder: inget kort krävs, ingen förskottsbetalning, ingen spärr innan
|
||||
// någon fått veta att något är obetalt. Kunden är en verkstad med
|
||||
// anställda och kunder i sin tur — den försvinner inte över natten, och
|
||||
// att behandla den som om den kunde det gör produkten sämre för alla som
|
||||
// betalar i tid.
|
||||
//
|
||||
// Vid utebliven betalning varnar vi, räknar ned synligt, och låser sedan.
|
||||
//
|
||||
// ---- Vad en låsning ALDRIG får göra -------------------------------------
|
||||
//
|
||||
// Låsning stänger nya ärenden. Den rör inte historiken, och den får inte
|
||||
// göra det.
|
||||
//
|
||||
// Skälet är inte generositet. Ärendeloggen är verkstadens underlag i en
|
||||
// garanti- eller försäkringstvist som gäller DERAS kund — en tredje part
|
||||
// som inte har någon del i vår obetalda faktura. Att göra det underlaget
|
||||
// oåtkomligt vore att använda någon annans rättsliga ställning som
|
||||
// påtryckningsmedel. Läsning och export förblir därför öppna i låst läge,
|
||||
// och det är inte förhandlingsbart.
|
||||
//
|
||||
// ---- Gratis är en nivå, inte en provperiod ------------------------------
|
||||
//
|
||||
// Kontot startar på Free och stannar där tills någon väljer annat. En
|
||||
// provperiod som tyst börjar kosta är samma sorts fälla som produkten
|
||||
// finns för att undvika. Fakturan skapas ändå vid registreringen — på
|
||||
// noll kronor — därför att den etablerar serien och visar kunden exakt
|
||||
// vad den inte betalar för.
|
||||
|
||||
import { PRISLISTA } from "./fakturering.mjs";
|
||||
|
||||
/** Dagar efter förfallodagen innan låsning. Synlig nedräkning hela tiden. */
|
||||
export const RESPITDAGAR = 14;
|
||||
|
||||
/**
|
||||
* Nivåerna.
|
||||
*
|
||||
* `basavgift` och `anvandaravgift` är i öre, som resten av faktureringen.
|
||||
* Värdena är PLATSHÅLLARE på samma villkor som PRISLISTA — mekanismen är
|
||||
* oberoende av dem.
|
||||
*/
|
||||
export const NIVAER = [
|
||||
{
|
||||
id: "free",
|
||||
namn: "Free",
|
||||
basavgift: 0,
|
||||
anvandaravgift: 0,
|
||||
maxAnvandare: 2,
|
||||
text: "Full method, full evidence model, full report. Two accounts.",
|
||||
ingar: [
|
||||
"The complete ALVA methodology and quality gate",
|
||||
"Append-only case log and evidence report",
|
||||
"Two active accounts",
|
||||
],
|
||||
saknas: ["Fleet analytics", "Outgoing integrations", "Knowledge source connectors"],
|
||||
},
|
||||
{
|
||||
id: "standard",
|
||||
namn: "Standard",
|
||||
basavgift: PRISLISTA.plattform_ar / 12,
|
||||
anvandaravgift: PRISLISTA.anvandare_manad,
|
||||
maxAnvandare: null,
|
||||
text: "Platform fee plus a fee per active account.",
|
||||
ingar: [
|
||||
"Everything in Free, without the account limit",
|
||||
"Fleet analytics and fault-cause statistics",
|
||||
"Live Share with customer, partner and internal levels",
|
||||
"Instrument register with calibration tracking",
|
||||
],
|
||||
saknas: ["Outgoing integrations", "Knowledge source connectors"],
|
||||
},
|
||||
{
|
||||
id: "premium",
|
||||
namn: "Premium",
|
||||
basavgift: (PRISLISTA.plattform_ar / 12) * 2,
|
||||
anvandaravgift: PRISLISTA.anvandare_manad,
|
||||
maxAnvandare: null,
|
||||
text: "For workshops that report into someone else's system.",
|
||||
ingar: [
|
||||
"Everything in Standard",
|
||||
"Outgoing integrations and event subscriptions",
|
||||
"Protocol ingest from external test equipment",
|
||||
"Knowledge source connectors",
|
||||
],
|
||||
saknas: ["Dedicated instance", "Custom methodology authoring"],
|
||||
},
|
||||
{
|
||||
id: "enterprise",
|
||||
namn: "Enterprise",
|
||||
basavgift: null, // Avtalas. Inte "kontakta oss" som prissignal — det finns inget listpris.
|
||||
anvandaravgift: null,
|
||||
maxAnvandare: null,
|
||||
text: "Chains and fleets. Priced per agreement because the scope is.",
|
||||
ingar: [
|
||||
"Everything in Premium",
|
||||
"Own methodologies and own rule package",
|
||||
"Dedicated instance, own key management",
|
||||
"Signed rule package and audit support",
|
||||
],
|
||||
saknas: [],
|
||||
},
|
||||
];
|
||||
|
||||
export const nivaFor = (id) => NIVAER.find((n) => n.id === id) ?? NIVAER[0];
|
||||
|
||||
/** Abonnemangets tillstånd. Härlett, aldrig lagrat. */
|
||||
export const TILLSTAND = ["aktiv", "varning", "last"];
|
||||
|
||||
/**
|
||||
* Tillståndet, härlett ur fakturorna.
|
||||
*
|
||||
* Tre utfall och inget fjärde:
|
||||
*
|
||||
* aktiv Inget är förfallet.
|
||||
* varning Något är förfallet men respiten löper. Nedräkningen visas.
|
||||
* last Respiten är slut.
|
||||
*
|
||||
* En faktura på noll kronor kan inte försätta någon i varning. Free-nivån
|
||||
* fakturerar noll, och en obetald nollfaktura är inte en skuld — den är
|
||||
* en bokföringspost. Att låsa någon för den vore absurt, och det är
|
||||
* precis den sortens absurditet som uppstår när tillståndet räknas fram
|
||||
* utan att någon tänkt på specialfallet.
|
||||
*
|
||||
* @param fakturor [{ forfaller, totalt, status }]
|
||||
* @param nu tidpunkt; injicerad för testbarhet
|
||||
*/
|
||||
export function abonnemangstillstand(fakturor = [], nu = new Date()) {
|
||||
const obetalda = fakturor.filter(
|
||||
(f) => f.status === "utfardad" && Number(f.totalt) > 0 && new Date(f.forfaller) < nu,
|
||||
);
|
||||
if (obetalda.length === 0) return { tillstand: "aktiv", dagarKvar: null, aldsta: null };
|
||||
|
||||
// Den äldsta förfallna styr. Betalar kunden den senaste men inte den
|
||||
// första har respiten ändå löpt sedan den första.
|
||||
const aldsta = obetalda.reduce((a, b) => (new Date(a.forfaller) < new Date(b.forfaller) ? a : b));
|
||||
const forfalloDygn = Math.floor((nu - new Date(aldsta.forfaller)) / 86_400_000);
|
||||
const dagarKvar = RESPITDAGAR - forfalloDygn;
|
||||
|
||||
return {
|
||||
tillstand: dagarKvar > 0 ? "varning" : "last",
|
||||
dagarKvar: Math.max(0, dagarKvar),
|
||||
aldsta: aldsta.beteckning ?? null,
|
||||
belopp: obetalda.reduce((s, f) => s + Number(f.totalt), 0),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Vad ett tillstånd tillåter.
|
||||
*
|
||||
* Läsning och export är sanna i ALLA tillstånd. Se filhuvudet — det är
|
||||
* verkstadens kunds underlag, inte verkstadens, och inte vårt.
|
||||
*/
|
||||
export function behorighet(tillstand) {
|
||||
const last = tillstand === "last";
|
||||
return {
|
||||
lasa: true,
|
||||
exportera: true,
|
||||
nyttArende: !last,
|
||||
dokumentera: !last,
|
||||
avsluta: !last,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Beskedet till kunden. Rak text, ingen förhandling, inget hot.
|
||||
*
|
||||
* Statusspråket gäller även här: ett konstaterande och vad som händer
|
||||
* härnäst.
|
||||
*/
|
||||
export function besked(status) {
|
||||
if (status.tillstand === "aktiv") return null;
|
||||
if (status.tillstand === "varning") {
|
||||
return (
|
||||
`Invoice ${status.aldsta} is past due. New cases can be started for ${status.dagarKvar} more ` +
|
||||
`day${status.dagarKvar === 1 ? "" : "s"}. Reading and export are never affected.`
|
||||
);
|
||||
}
|
||||
return (
|
||||
`Invoice ${status.aldsta} is past due and the grace period has ended. New cases cannot be started. ` +
|
||||
"Existing cases remain readable and exportable — that record belongs to your customer, not to us."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nästa fakturaperiod.
|
||||
*
|
||||
* Månadsvis från registreringsdagen, inte från den första i månaden: en
|
||||
* organisation som registrerar sig den 20:e ska inte få en faktura på
|
||||
* elva dagar som sin första upplevelse av produkten.
|
||||
*/
|
||||
export function nastaPeriod(registrerad, senastFakturerad = null) {
|
||||
const start = new Date(senastFakturerad ?? registrerad);
|
||||
if (Number.isNaN(start.getTime())) return null;
|
||||
const fran = new Date(start);
|
||||
if (senastFakturerad) fran.setMonth(fran.getMonth() + 1);
|
||||
const till = new Date(fran);
|
||||
till.setMonth(till.getMonth() + 1);
|
||||
till.setDate(till.getDate() - 1);
|
||||
return { fran: fran.toISOString().slice(0, 10), till: till.toISOString().slice(0, 10) };
|
||||
}
|
||||
|
||||
/** Är det dags att fakturera perioden? */
|
||||
export function forfallenFakturering(registrerad, senastFakturerad, nu = new Date()) {
|
||||
const period = nastaPeriod(registrerad, senastFakturerad);
|
||||
if (!period) return null;
|
||||
return new Date(period.fran) <= nu ? period : null;
|
||||
}
|
||||
Reference in New Issue
Block a user