diff --git a/components/LabelPrintModal.vue b/components/LabelPrintModal.vue new file mode 100644 index 0000000..e660a6d --- /dev/null +++ b/components/LabelPrintModal.vue @@ -0,0 +1,130 @@ + + + diff --git a/components/LabelPrinterButton.vue b/components/LabelPrinterButton.vue new file mode 100644 index 0000000..2a29fee --- /dev/null +++ b/components/LabelPrinterButton.vue @@ -0,0 +1,53 @@ + + + + + \ No newline at end of file diff --git a/components/PublicDynamicForm.vue b/components/PublicDynamicForm.vue new file mode 100644 index 0000000..a3cdd07 --- /dev/null +++ b/components/PublicDynamicForm.vue @@ -0,0 +1,201 @@ + + + \ No newline at end of file diff --git a/components/displayBankaccounts.vue b/components/displayBankaccounts.vue index cb4ac08..07978a7 100644 --- a/components/displayBankaccounts.vue +++ b/components/displayBankaccounts.vue @@ -37,7 +37,7 @@ const calculateOpenSum = (statement) => { {{ account.name }}: - {{dayjs(account.synced_at).format("DD.MM.YY HH:mm")}} + {{dayjs(account.syncedAt).format("DD.MM.YY HH:mm")}} {{useCurrency(account.balance)}} diff --git a/components/nimbot.vue b/components/nimbot.vue new file mode 100644 index 0000000..7ceb36a --- /dev/null +++ b/components/nimbot.vue @@ -0,0 +1,60 @@ + + + diff --git a/layouts/default.vue b/layouts/default.vue index e994ab7..4993db6 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -6,6 +6,7 @@ import dayjs from "dayjs"; import {useCapacitor} from "../composables/useCapacitor.js"; import GlobalMessages from "~/components/GlobalMessages.vue"; import TenantDropdown from "~/components/TenantDropdown.vue"; +import LabelPrinterButton from "~/components/LabelPrinterButton.vue"; const dataStore = useDataStore() const colorMode = useColorMode() @@ -14,6 +15,7 @@ const router = useRouter() const route = useRoute() const auth = useAuthStore() +const labelPrinter = useLabelPrinterStore() const month = dayjs().format("MM") @@ -240,13 +242,16 @@ const footerLinks = [ diff --git a/pages/createDocument/edit/[[id]].vue b/pages/createDocument/edit/[[id]].vue index dad37e4..5312576 100644 --- a/pages/createDocument/edit/[[id]].vue +++ b/pages/createDocument/edit/[[id]].vue @@ -697,11 +697,11 @@ const findDocumentErrors = computed(() => { if (["normal", "service", "free"].includes(row.mode)) { - if (!row.taxPercent && typeof row.taxPercent !== "number") errors.push({ + if (!row.taxPercent && typeof row.taxPercent !== "number" && itemInfo.value.type !== "deliveryNotes") errors.push({ message: `In Position ${row.pos} ist kein Steuersatz hinterlegt`, type: "breaking" }) - if (!row.price && typeof row.price !== "number") errors.push({ + if (!row.price && typeof row.price !== "number" && itemInfo.value.type !== "deliveryNotes") errors.push({ message: `In Position ${row.pos} ist kein Preis hinterlegt`, type: "breaking" }) diff --git a/pages/export/create/sepa.vue b/pages/export/create/sepa.vue new file mode 100644 index 0000000..9e17a7a --- /dev/null +++ b/pages/export/create/sepa.vue @@ -0,0 +1,47 @@ + + + + + \ No newline at end of file diff --git a/pages/index.vue b/pages/index.vue index 03bb084..41cb992 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -56,16 +56,36 @@ > + + + Label Drucken + + diff --git a/pages/workflows/[token].vue b/pages/workflows/[token].vue new file mode 100644 index 0000000..80b1697 --- /dev/null +++ b/pages/workflows/[token].vue @@ -0,0 +1,121 @@ + + + \ No newline at end of file diff --git a/stores/labelPrinter.ts b/stores/labelPrinter.ts new file mode 100644 index 0000000..60a96a9 --- /dev/null +++ b/stores/labelPrinter.ts @@ -0,0 +1,190 @@ +import { defineStore } from "pinia" +import { + Utils, + RequestCommandId, + ResponseCommandId, + NiimbotBluetoothClient, + NiimbotSerialClient +} from "@mmote/niimbluelib" +import { useToast } from "#imports" + +export const useLabelPrinterStore = defineStore("labelPrinter", { + state: () => ({ + client: null as NiimbotBluetoothClient | NiimbotSerialClient | null, + connected: false, + connectLoading: false, + transportLastUsed: "", + printProgress: 0, + info: {} as any + }), + + actions: { + + /** Logging Helper */ + logger(...args: any[]) { + console.debug("[Printer]", ...args) + }, + + /** --- Client erzeugen --- */ + newClient(transport: "ble" | "serial" = "serial") { + const toast = useToast() + + // alten Client trennen + if (this.client) { + try { this.client.disconnect() } catch {} + } + + // neuen Client erzeugen + this.client = + transport === "ble" + ? new NiimbotBluetoothClient() + : new NiimbotSerialClient() + + /** Events registrieren */ + + this.client.on("printerinfofetched", (e) => { + console.log("printerInfoFetched") + console.log(e.info) + this.info = e.info + }) + + this.client.on("connect", () => { + this.connected = true + toast.add({ title: "Drucker verbunden" }) + this.logger("connected") + }) + + this.client.on("disconnect", () => { + this.connected = false + toast.add({ title: "Drucker getrennt" }) + this.logger("disconnected") + }) + + this.client.on("printprogress", (e) => { + if (e.pagePrintProgress) this.printProgress = e.pagePrintProgress + this.logger( + `Page ${e.page}/${e.pagesTotal}, Page print ${e.pagePrintProgress}%, Page feed ${e.pageFeedProgress}%` + ) + }) + + return this.client + }, + + /** --- Verbinden --- */ + async connect(transport: "ble" | "serial" = "serial") { + const toast = useToast() + + this.connectLoading = true + + this.newClient(transport) + + try { + await this.client!.connect() + this.transportLastUsed = transport + this.connectLoading = false + } catch (err) { + console.error("[Printer] Connect failed:", err) + toast.add({ title: "Verbindung fehlgeschlagen", color: "red" }) + this.connectLoading = false + } + }, + + /** --- Trennen --- */ + async disconnect({ forget = false } = {}) { + const toast = useToast() + this.logger("Disconnect requested…") + + if (!this.client) return + + try { + // Timer stoppen + try { + if (this.client.heartbeatTimer) { + clearInterval(this.client.heartbeatTimer) + this.client.heartbeatTimer = null + } + if (this.client.abstraction?.statusPollTimer) { + clearInterval(this.client.abstraction.statusPollTimer) + this.client.abstraction.statusPollTimer = null + } + } catch {} + + await this.client.disconnect?.() + + // Serial-Port schließen + const port = (this.client as any).port + if (port) { + try { + if (port.readable) port.readable.cancel?.() + if (port.writable) await port.writable.abort?.() + await port.close?.() + + if (forget && navigator.serial?.forgetPort) { + await navigator.serial.forgetPort(port) + } + } catch (err) { + this.logger("Error closing port:", err) + } + } + + // BLE GATT + if ( + this.client instanceof NiimbotBluetoothClient && + this.client.device?.gatt?.connected + ) { + try { + this.client.device.gatt.disconnect() + } catch {} + } + + this.connected = false + this.client = null + toast.add({ title: "Drucker getrennt" }) + } catch (err) { + console.error("[Printer] Disconnect error", err) + toast.add({ title: "Fehler beim Trennen", color: "red" }) + } + }, + + /** Hilfsfunktion: EncodedImage reparieren */ + reviveEncodedImage(encoded: any) { + if (!encoded?.rowsData) return encoded + for (const row of encoded.rowsData) { + if (row.rowData && !(row.rowData instanceof Uint8Array)) { + row.rowData = new Uint8Array(Object.values(row.rowData)) + } + } + return encoded + }, + + /** --- Drucken --- */ + async print(encoded: any, options?: { density?: number; pages?: number }) { + const toast = useToast() + + if (!this.client) throw new Error("Kein Drucker verbunden") + + const fixed = this.reviveEncodedImage(encoded) + const taskName = this.client.getPrintTaskType() ?? "B1" + + const task = this.client.abstraction.newPrintTask(taskName, { + totalPages: options?.pages ?? 1, + statusPollIntervalMs: 100, + statusTimeoutMs: 8000, + density: options?.density ?? 5 + }) + + try { + this.printProgress = 0 + await task.printInit() + await task.printPage(fixed, options?.pages ?? 1) + await task.waitForFinished() + toast.add({ title: "Druck abgeschlossen" }) + } catch (e) { + console.error("[Printer] print error", e) + toast.add({ title: "Druckfehler", color: "red" }) + } finally { + await task.printEnd() + } + } + } +})