first version

This commit is contained in:
Gerhard Scheikl
2026-04-28 21:56:11 +02:00
parent 0f75dbaccb
commit 5b2aa5d62b
50 changed files with 5514 additions and 481 deletions
@@ -0,0 +1,117 @@
-- CreateTable
CREATE TABLE "ShopSettings" (
"id" TEXT NOT NULL PRIMARY KEY,
"shopDomain" TEXT NOT NULL,
"companyName" TEXT NOT NULL DEFAULT '',
"legalForm" TEXT NOT NULL DEFAULT '',
"ownerName" TEXT NOT NULL DEFAULT '',
"addressLine1" TEXT NOT NULL DEFAULT '',
"addressLine2" TEXT NOT NULL DEFAULT '',
"postalCode" TEXT NOT NULL DEFAULT '',
"city" TEXT NOT NULL DEFAULT '',
"countryCode" TEXT NOT NULL DEFAULT 'AT',
"phone" TEXT NOT NULL DEFAULT '',
"email" TEXT NOT NULL DEFAULT '',
"website" TEXT NOT NULL DEFAULT '',
"vatId" TEXT NOT NULL DEFAULT '',
"taxNumber" TEXT NOT NULL DEFAULT '',
"registrationNo" TEXT NOT NULL DEFAULT '',
"registrationCourt" TEXT NOT NULL DEFAULT '',
"bankName" TEXT NOT NULL DEFAULT '',
"iban" TEXT NOT NULL DEFAULT '',
"bic" TEXT NOT NULL DEFAULT '',
"giroCodeEnabled" BOOLEAN NOT NULL DEFAULT true,
"numberingMode" TEXT NOT NULL DEFAULT 'shopify_order_number',
"invoicePrefix" TEXT NOT NULL DEFAULT 'RE-',
"invoiceSeed" INTEGER NOT NULL DEFAULT 1000,
"defaultLanguage" TEXT NOT NULL DEFAULT 'de',
"paymentTermDays" INTEGER NOT NULL DEFAULT 14,
"footerNote" TEXT NOT NULL DEFAULT '',
"kleinunternehmer" BOOLEAN NOT NULL DEFAULT false,
"logoUrl" TEXT NOT NULL DEFAULT '',
"smtpHost" TEXT NOT NULL DEFAULT '',
"smtpPort" INTEGER NOT NULL DEFAULT 587,
"smtpSecure" BOOLEAN NOT NULL DEFAULT false,
"smtpUser" TEXT NOT NULL DEFAULT '',
"smtpPassword" TEXT NOT NULL DEFAULT '',
"smtpFromName" TEXT NOT NULL DEFAULT '',
"smtpFromEmail" TEXT NOT NULL DEFAULT '',
"smtpReplyTo" TEXT NOT NULL DEFAULT '',
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
-- CreateTable
CREATE TABLE "Invoice" (
"id" TEXT NOT NULL PRIMARY KEY,
"shopDomain" TEXT NOT NULL,
"orderId" TEXT NOT NULL,
"orderName" TEXT NOT NULL,
"orderNumber" INTEGER NOT NULL,
"invoiceNumber" TEXT NOT NULL,
"language" TEXT NOT NULL DEFAULT 'de',
"kind" TEXT NOT NULL DEFAULT 'invoice',
"version" INTEGER NOT NULL DEFAULT 1,
"cancelsInvoiceId" TEXT,
"pdfFileGid" TEXT NOT NULL DEFAULT '',
"pdfUrl" TEXT NOT NULL DEFAULT '',
"totalsJson" TEXT NOT NULL DEFAULT '{}',
"customerJson" TEXT NOT NULL DEFAULT '{}',
"issuedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"sentAt" DATETIME,
"cancelledAt" DATETIME,
"status" TEXT NOT NULL DEFAULT 'issued',
"lastError" TEXT NOT NULL DEFAULT '',
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Invoice_shopDomain_fkey" FOREIGN KEY ("shopDomain") REFERENCES "ShopSettings" ("shopDomain") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "InvoiceCounter" (
"id" TEXT NOT NULL PRIMARY KEY,
"shopDomain" TEXT NOT NULL,
"lastValue" INTEGER NOT NULL DEFAULT 0,
"updatedAt" DATETIME NOT NULL
);
-- CreateTable
CREATE TABLE "EmailLog" (
"id" TEXT NOT NULL PRIMARY KEY,
"shopDomain" TEXT NOT NULL,
"invoiceId" TEXT NOT NULL,
"toAddress" TEXT NOT NULL,
"subject" TEXT NOT NULL,
"status" TEXT NOT NULL,
"error" TEXT NOT NULL DEFAULT '',
"sentAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- CreateTable
CREATE TABLE "LogoCache" (
"id" TEXT NOT NULL PRIMARY KEY,
"shopDomain" TEXT NOT NULL,
"sourceUrl" TEXT NOT NULL,
"bytes" BLOB NOT NULL,
"contentType" TEXT NOT NULL DEFAULT 'image/png',
"etag" TEXT NOT NULL DEFAULT '',
"fetchedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- CreateIndex
CREATE UNIQUE INDEX "ShopSettings_shopDomain_key" ON "ShopSettings"("shopDomain");
-- CreateIndex
CREATE INDEX "Invoice_shopDomain_orderId_idx" ON "Invoice"("shopDomain", "orderId");
-- CreateIndex
CREATE INDEX "Invoice_shopDomain_invoiceNumber_idx" ON "Invoice"("shopDomain", "invoiceNumber");
-- CreateIndex
CREATE UNIQUE INDEX "InvoiceCounter_shopDomain_key" ON "InvoiceCounter"("shopDomain");
-- CreateIndex
CREATE INDEX "EmailLog_shopDomain_invoiceId_idx" ON "EmailLog"("shopDomain", "invoiceId");
-- CreateIndex
CREATE UNIQUE INDEX "LogoCache_shopDomain_key" ON "LogoCache"("shopDomain");
+3
View File
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "sqlite"
+145
View File
@@ -32,3 +32,148 @@ model Session {
refreshToken String?
refreshTokenExpires DateTime?
}
// Per-shop issuer/configuration data. One row per installed shop.
model ShopSettings {
id String @id @default(cuid())
shopDomain String @unique
// Issuer / company data
companyName String @default("")
legalForm String @default("")
ownerName String @default("")
addressLine1 String @default("")
addressLine2 String @default("")
postalCode String @default("")
city String @default("")
countryCode String @default("AT")
phone String @default("")
email String @default("")
website String @default("")
// Legal identifiers (Austria)
vatId String @default("") // UID, e.g. ATU12345678
taxNumber String @default("") // Steuernummer
registrationNo String @default("") // FN (Firmenbuchnummer)
registrationCourt String @default("") // Firmenbuchgericht
// Bank
bankName String @default("")
iban String @default("")
bic String @default("")
giroCodeEnabled Boolean @default(true)
// Invoice numbering
// shopify_order_number | prefix_sequential
numberingMode String @default("shopify_order_number")
invoicePrefix String @default("RE-")
// Used only for prefix_sequential (the next number to issue minus 1)
invoiceSeed Int @default(1000)
// Defaults
defaultLanguage String @default("de")
paymentTermDays Int @default(14)
footerNote String @default("")
// Kleinunternehmer (§ 6 Abs. 1 Z 27 UStG)
kleinunternehmer Boolean @default(false)
// Logo (URL on Shopify Files or any reachable URL)
logoUrl String @default("")
// SMTP for email (used in later phase)
smtpHost String @default("")
smtpPort Int @default(587)
smtpSecure Boolean @default(false)
smtpUser String @default("")
smtpPassword String @default("")
smtpFromName String @default("")
smtpFromEmail String @default("")
smtpReplyTo String @default("")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
invoices Invoice[]
}
// Generated invoice record. One row per invoice document (versions and stornos
// each create new rows; the latest is linked on the order via metafield).
model Invoice {
id String @id @default(cuid())
shopDomain String
settings ShopSettings @relation(fields: [shopDomain], references: [shopDomain])
// Shopify order references
orderId String // gid://shopify/Order/...
orderName String // e.g. "#1004"
orderNumber Int // numeric order_number
// Invoice identity
invoiceNumber String // e.g. "RE-1004"
language String @default("de")
// invoice | storno
kind String @default("invoice")
// Increments per regeneration of the same invoiceNumber (always 1 for storno)
version Int @default(1)
// For storno rows: points to the cancelled Invoice.id
cancelsInvoiceId String?
// PDF storage
pdfFileGid String @default("") // gid://shopify/GenericFile/...
pdfUrl String @default("")
// Snapshots (JSON strings on sqlite)
totalsJson String @default("{}")
customerJson String @default("{}")
// Lifecycle
issuedAt DateTime @default(now())
sentAt DateTime?
cancelledAt DateTime?
status String @default("issued") // issued | sent | cancelled | failed
lastError String @default("")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([shopDomain, orderId])
@@index([shopDomain, invoiceNumber])
}
// Per-shop atomic counter for prefix_sequential numbering mode.
model InvoiceCounter {
id String @id @default(cuid())
shopDomain String @unique
// Last issued numeric value; allocate new = lastValue + 1 atomically.
lastValue Int @default(0)
updatedAt DateTime @updatedAt
}
// Email delivery log (used by later Flow send action; declared now so the
// generator can record manual sends as well).
model EmailLog {
id String @id @default(cuid())
shopDomain String
invoiceId String
toAddress String
subject String
status String // queued | sent | failed
error String @default("")
sentAt DateTime @default(now())
@@index([shopDomain, invoiceId])
}
// Per-shop logo bytes cache. Avoids fetching the logo from Shopify Files on
// every PDF render.
model LogoCache {
id String @id @default(cuid())
shopDomain String @unique
sourceUrl String
bytes Bytes
contentType String @default("image/png")
etag String @default("")
fetchedAt DateTime @default(now())
}