New CustomerInventory,
New Mitgliederverwaltung für Vereine New Bank Auto Complete
This commit is contained in:
3512
backend/src/utils/deBankBics.ts
Normal file
3512
backend/src/utils/deBankBics.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -698,7 +698,7 @@ export const DE_BANK_CODE_TO_NAME: Record<string, string> = {
|
||||
"28022822": "Oldenburgische Landesbank AG",
|
||||
"28023224": "Oldenburgische Landesbank AG",
|
||||
"28023325": "Oldenburgische Landesbank AG",
|
||||
"28030300": "Oldenburgische Landesbank AG (vormals W. Fortmann & Söhne",
|
||||
"28030300": "Oldenburgische Landesbank AG (vormals W. Fortmann & Söhne)",
|
||||
"28040046": "Commerzbank",
|
||||
"28042865": "Commerzbank",
|
||||
"28050100": "Landessparkasse zu Oldenburg",
|
||||
@@ -1186,7 +1186,7 @@ export const DE_BANK_CODE_TO_NAME: Record<string, string> = {
|
||||
"39570061": "Deutsche Bank",
|
||||
"39580041": "Commerzbank vormals Dresdner Bank",
|
||||
"40022000": "NRW.BANK",
|
||||
"40030000": "Münsterländische Bank Thie,Zndl. d.VR-Bank Westmünst.-a",
|
||||
"40030000": "Münsterländische Bank Thie,Zndl. d.VR-Bank Westmünst.-alt-",
|
||||
"40040028": "Commerzbank",
|
||||
"40050000": "Landesbank Hessen-Thüringen Girozentrale NL. Düsseldorf",
|
||||
"40050150": "Sparkasse Münsterland Ost",
|
||||
@@ -3214,7 +3214,7 @@ export const DE_BANK_CODE_TO_NAME: Record<string, string> = {
|
||||
"76260451": "Raiffeisen-Volksbank Fürth -alt-",
|
||||
"76320072": "UniCredit Bank - HypoVereinsbank",
|
||||
"76340061": "Commerzbank Erlangen",
|
||||
"76350000": "Stadt- u. Kreissparkasse Erlangen Höchstadt Herzogenaurac",
|
||||
"76350000": "Stadt- u. Kreissparkasse Erlangen Höchstadt Herzogenaurach",
|
||||
"76351040": "Sparkasse Forchheim",
|
||||
"76351560": "Kreissparkasse Höchstadt",
|
||||
"76360033": "VR-Bank Erlangen-Höchstadt-Herzogenaurach -alt-",
|
||||
|
||||
@@ -236,8 +236,11 @@ export const diffTranslations: Record<
|
||||
sellingPriceComposed: { label: "Verkaufspreis Zusammensetzung" },
|
||||
purchaseDate: { label: "Kaufdatum" },
|
||||
serialNumber: { label: "Seriennummer" },
|
||||
customerInventoryId: { label: "Kundeninventar-ID" },
|
||||
customerinventoryitems: { label: "Kundeninventar" },
|
||||
usePlanning: { label: "In Plantafel verwenden" },
|
||||
currentSpace: { label: "Lagerplatz" },
|
||||
customerspace: { label: "Kundenlagerplatz" },
|
||||
|
||||
customer: {
|
||||
label: "Kunde",
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import {FastifyInstance} from "fastify";
|
||||
// import { PNG } from 'pngjs'
|
||||
// import { ready as zplReady } from 'zpl-renderer-js'
|
||||
// import { Utils } from '@mmote/niimbluelib'
|
||||
// import { createCanvas } from 'canvas'
|
||||
// import bwipjs from 'bwip-js'
|
||||
// import Sharp from 'sharp'
|
||||
// import fs from 'fs'
|
||||
import { FastifyInstance } from "fastify"
|
||||
import { PNG } from "pngjs"
|
||||
import { Utils } from "@mmote/niimbluelib"
|
||||
import bwipjs from "bwip-js"
|
||||
import Sharp from "sharp"
|
||||
|
||||
import { tenants } from "../../db/schema"
|
||||
import { eq } from "drizzle-orm"
|
||||
@@ -15,7 +12,6 @@ export const useNextNumberRangeNumber = async (
|
||||
tenantId: number,
|
||||
numberRange: string
|
||||
) => {
|
||||
// 1️⃣ Tenant laden
|
||||
const [tenant] = await server.db
|
||||
.select()
|
||||
.from(tenants)
|
||||
@@ -33,23 +29,20 @@ export const useNextNumberRangeNumber = async (
|
||||
|
||||
const current = numberRanges[numberRange]
|
||||
|
||||
// 2️⃣ Used Number generieren
|
||||
const usedNumber =
|
||||
(current.prefix || "") +
|
||||
current.nextNumber +
|
||||
(current.suffix || "")
|
||||
|
||||
// 3️⃣ nextNumber erhöhen
|
||||
const updatedRanges = {
|
||||
// @ts-ignore
|
||||
...numberRanges,
|
||||
[numberRange]: {
|
||||
...current,
|
||||
nextNumber: current.nextNumber + 1
|
||||
}
|
||||
nextNumber: current.nextNumber + 1,
|
||||
},
|
||||
}
|
||||
|
||||
// 4️⃣ Tenant aktualisieren
|
||||
await server.db
|
||||
.update(tenants)
|
||||
.set({ numberRanges: updatedRanges })
|
||||
@@ -58,24 +51,17 @@ export const useNextNumberRangeNumber = async (
|
||||
return { usedNumber }
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
export async function encodeBase64ToNiimbot(base64Png, printDirection = 'top') {
|
||||
// 1️⃣ PNG dekodieren
|
||||
const buffer = Buffer.from(base64Png, 'base64')
|
||||
const png = PNG.sync.read(buffer) // liefert {width, height, data: Uint8Array(RGBA)}
|
||||
export async function encodeBase64ToNiimbot(base64Png: string, printDirection: "top" | "left" = "top") {
|
||||
const buffer = Buffer.from(base64Png, "base64")
|
||||
const png = PNG.sync.read(buffer)
|
||||
|
||||
const { width, height, data } = png
|
||||
console.log(width, height, data)
|
||||
const cols = printDirection === 'left' ? height : width
|
||||
const rows = printDirection === 'left' ? width : height
|
||||
const rowsData = []
|
||||
const cols = printDirection === "left" ? height : width
|
||||
const rows = printDirection === "left" ? width : height
|
||||
const rowsData: any[] = []
|
||||
|
||||
console.log(cols)
|
||||
if (cols % 8 !== 0) throw new Error("Column count must be multiple of 8")
|
||||
|
||||
if (cols % 8 !== 0) throw new Error('Column count must be multiple of 8')
|
||||
|
||||
// 2️⃣ Zeilenweise durchgehen und Bits bilden
|
||||
for (let row = 0; row < rows; row++) {
|
||||
let isVoid = true
|
||||
let blackPixelsCount = 0
|
||||
@@ -84,8 +70,8 @@ export async function encodeBase64ToNiimbot(base64Png, printDirection = 'top') {
|
||||
for (let colOct = 0; colOct < cols / 8; colOct++) {
|
||||
let pixelsOctet = 0
|
||||
for (let colBit = 0; colBit < 8; colBit++) {
|
||||
const x = printDirection === 'left' ? row : colOct * 8 + colBit
|
||||
const y = printDirection === 'left' ? height - 1 - (colOct * 8 + colBit) : row
|
||||
const x = printDirection === "left" ? row : colOct * 8 + colBit
|
||||
const y = printDirection === "left" ? height - 1 - (colOct * 8 + colBit) : row
|
||||
const idx = (y * width + x) * 4
|
||||
const lum = 0.299 * data[idx] + 0.587 * data[idx + 1] + 0.114 * data[idx + 2]
|
||||
const isBlack = lum < 128
|
||||
@@ -99,7 +85,7 @@ export async function encodeBase64ToNiimbot(base64Png, printDirection = 'top') {
|
||||
}
|
||||
|
||||
const newPart = {
|
||||
dataType: isVoid ? 'void' : 'pixels',
|
||||
dataType: isVoid ? "void" : "pixels",
|
||||
rowNumber: row,
|
||||
repeat: 1,
|
||||
rowData: isVoid ? undefined : rowData,
|
||||
@@ -111,14 +97,15 @@ export async function encodeBase64ToNiimbot(base64Png, printDirection = 'top') {
|
||||
} else {
|
||||
const last = rowsData[rowsData.length - 1]
|
||||
let same = newPart.dataType === last.dataType
|
||||
if (same && newPart.dataType === 'pixels') {
|
||||
if (same && newPart.dataType === "pixels") {
|
||||
same = Utils.u8ArraysEqual(newPart.rowData, last.rowData)
|
||||
}
|
||||
if (same) last.repeat++
|
||||
else rowsData.push(newPart)
|
||||
|
||||
if (row % 200 === 199) {
|
||||
rowsData.push({
|
||||
dataType: 'check',
|
||||
dataType: "check",
|
||||
rowNumber: row,
|
||||
repeat: 0,
|
||||
rowData: undefined,
|
||||
@@ -131,44 +118,69 @@ export async function encodeBase64ToNiimbot(base64Png, printDirection = 'top') {
|
||||
return { cols, rows, rowsData }
|
||||
}
|
||||
|
||||
export async function generateLabel(context,width,height) {
|
||||
// Canvas für Hintergrund & Text
|
||||
const canvas = createCanvas(width, height)
|
||||
const ctx = canvas.getContext('2d')
|
||||
function escapeXml(value: string) {
|
||||
return String(value)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/\"/g, """)
|
||||
.replace(/'/g, "'")
|
||||
}
|
||||
|
||||
// Hintergrund weiß
|
||||
ctx.fillStyle = '#FFFFFF'
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
export async function generateLabel(context: any = {}, width = 584, height = 354) {
|
||||
const normalizedWidth = Math.ceil(Number(width) / 8) * 8
|
||||
const normalizedHeight = Math.max(1, Number(height) || 203)
|
||||
|
||||
// Überschrift
|
||||
ctx.fillStyle = '#000000'
|
||||
ctx.font = '32px Arial'
|
||||
ctx.fillText(context.text, 20, 40)
|
||||
const idFont = Math.max(24, Math.round(normalizedHeight * 0.125))
|
||||
const nameFont = Math.max(17, Math.round(normalizedHeight * 0.078))
|
||||
const customerFont = Math.max(14, Math.round(normalizedHeight * 0.06))
|
||||
const serialFont = Math.max(12, Math.round(normalizedHeight * 0.052))
|
||||
|
||||
const labelId = context.customerInventoryId || context.datamatrix || context.id || "N/A"
|
||||
const labelName = context.name || context.text || "Kundeninventarartikel"
|
||||
const customerName = context.customerName || ""
|
||||
const serial = context.serialNumber ? `SN: ${context.serialNumber}` : ""
|
||||
const nameLine1 = String(labelName).slice(0, 30)
|
||||
const nameLine2 = String(labelName).slice(30, 60)
|
||||
|
||||
// 3) DataMatrix
|
||||
const dataMatrixPng = await bwipjs.toBuffer({
|
||||
bcid: 'datamatrix',
|
||||
text: context.datamatrix,
|
||||
scale: 6,
|
||||
bcid: "datamatrix",
|
||||
text: String(labelId),
|
||||
scale: normalizedWidth >= 560 ? 7 : 5,
|
||||
includetext: false,
|
||||
})
|
||||
const dataMatrixMeta = await Sharp(dataMatrixPng).metadata()
|
||||
const dataMatrixWidth = dataMatrixMeta.width || 0
|
||||
const dataMatrixHeight = dataMatrixMeta.height || 0
|
||||
const dmLeft = Math.max(8, normalizedWidth - dataMatrixWidth - 28)
|
||||
const dmTop = Math.max(8, Math.floor((normalizedHeight - dataMatrixHeight) / 2))
|
||||
const textMaxWidth = Math.max(120, dmLeft - 20)
|
||||
|
||||
// Basisbild aus Canvas
|
||||
const base = await Sharp(canvas.toBuffer())
|
||||
.png()
|
||||
.toBuffer()
|
||||
const textSvg = `
|
||||
<svg width="${normalizedWidth}" height="${normalizedHeight}" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="100%" height="100%" fill="white"/>
|
||||
<text x="12" y="${Math.round(normalizedHeight * 0.15)}" font-size="${idFont}" font-family="Arial, Helvetica, sans-serif" font-weight="700" fill="black">${escapeXml(String(labelId).slice(0, 26))}</text>
|
||||
<text x="12" y="${Math.round(normalizedHeight * 0.29)}" font-size="${nameFont}" font-family="Arial, Helvetica, sans-serif" fill="black">${escapeXml(nameLine1)}</text>
|
||||
<text x="12" y="${Math.round(normalizedHeight * 0.37)}" font-size="${nameFont}" font-family="Arial, Helvetica, sans-serif" fill="black">${escapeXml(nameLine2)}</text>
|
||||
<text x="12" y="${Math.round(normalizedHeight * 0.49)}" font-size="${customerFont}" font-family="Arial, Helvetica, sans-serif" fill="black">${escapeXml(String(customerName).slice(0, 40))}</text>
|
||||
<text x="12" y="${Math.round(normalizedHeight * 0.58)}" font-size="${serialFont}" font-family="Arial, Helvetica, sans-serif" fill="black">${escapeXml(String(serial).slice(0, 42))}</text>
|
||||
<rect x="0" y="0" width="${textMaxWidth}" height="${normalizedHeight}" fill="none"/>
|
||||
</svg>`.trim()
|
||||
|
||||
// Alles zusammen compositen
|
||||
const final = await Sharp(base)
|
||||
const final = await Sharp({
|
||||
create: {
|
||||
width: normalizedWidth,
|
||||
height: normalizedHeight,
|
||||
channels: 3,
|
||||
background: { r: 255, g: 255, b: 255 },
|
||||
},
|
||||
})
|
||||
.composite([
|
||||
{ input: dataMatrixPng, top: 60, left: 20 },
|
||||
{ input: Buffer.from(textSvg), top: 0, left: 0 },
|
||||
{ input: dataMatrixPng, top: dmTop, left: dmLeft },
|
||||
])
|
||||
.png()
|
||||
.toBuffer()
|
||||
|
||||
fs.writeFileSync('label.png', final)
|
||||
|
||||
// Optional: Base64 zurückgeben (z.B. für API)
|
||||
const base64 = final.toString('base64')
|
||||
|
||||
return base64
|
||||
}*/
|
||||
return final.toString("base64")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,43 @@
|
||||
import { FastifyInstance } from "fastify"
|
||||
import { historyitems } from "../../db/schema";
|
||||
|
||||
const HISTORY_ENTITY_LABELS: Record<string, string> = {
|
||||
customers: "Kunden",
|
||||
members: "Mitglieder",
|
||||
vendors: "Lieferanten",
|
||||
projects: "Projekte",
|
||||
plants: "Objekte",
|
||||
contacts: "Kontakte",
|
||||
inventoryitems: "Inventarartikel",
|
||||
customerinventoryitems: "Kundeninventar",
|
||||
products: "Artikel",
|
||||
profiles: "Mitarbeiter",
|
||||
absencerequests: "Abwesenheiten",
|
||||
events: "Termine",
|
||||
tasks: "Aufgaben",
|
||||
vehicles: "Fahrzeuge",
|
||||
costcentres: "Kostenstellen",
|
||||
ownaccounts: "zusätzliche Buchungskonten",
|
||||
documentboxes: "Dokumentenboxen",
|
||||
hourrates: "Stundensätze",
|
||||
services: "Leistungen",
|
||||
roles: "Rollen",
|
||||
checks: "Überprüfungen",
|
||||
spaces: "Lagerplätze",
|
||||
customerspaces: "Kundenlagerplätze",
|
||||
trackingtrips: "Fahrten",
|
||||
createddocuments: "Dokumente",
|
||||
inventoryitemgroups: "Inventarartikelgruppen",
|
||||
bankstatements: "Buchungen",
|
||||
incominginvoices: "Eingangsrechnungen",
|
||||
files: "Dateien",
|
||||
memberrelations: "Mitgliedsverhältnisse",
|
||||
}
|
||||
|
||||
export function getHistoryEntityLabel(entity: string) {
|
||||
return HISTORY_ENTITY_LABELS[entity] || entity
|
||||
}
|
||||
|
||||
export async function insertHistoryItem(
|
||||
server: FastifyInstance,
|
||||
params: {
|
||||
@@ -14,16 +51,18 @@ export async function insertHistoryItem(
|
||||
text?: string
|
||||
}
|
||||
) {
|
||||
const entityLabel = getHistoryEntityLabel(params.entity)
|
||||
const textMap = {
|
||||
created: `Neuer Eintrag in ${params.entity} erstellt`,
|
||||
updated: `Eintrag in ${params.entity} geändert`,
|
||||
unchanged: `Eintrag in ${params.entity} unverändert`,
|
||||
archived: `Eintrag in ${params.entity} archiviert`,
|
||||
deleted: `Eintrag in ${params.entity} gelöscht`
|
||||
created: `Neuer Eintrag in ${entityLabel} erstellt`,
|
||||
updated: `Eintrag in ${entityLabel} geändert`,
|
||||
unchanged: `Eintrag in ${entityLabel} unverändert`,
|
||||
archived: `Eintrag in ${entityLabel} archiviert`,
|
||||
deleted: `Eintrag in ${entityLabel} gelöscht`
|
||||
}
|
||||
|
||||
const columnMap: Record<string, string> = {
|
||||
customers: "customer",
|
||||
members: "customer",
|
||||
vendors: "vendor",
|
||||
projects: "project",
|
||||
plants: "plant",
|
||||
@@ -43,12 +82,15 @@ export async function insertHistoryItem(
|
||||
roles: "role",
|
||||
checks: "check",
|
||||
spaces: "space",
|
||||
customerspaces: "customerspace",
|
||||
customerinventoryitems: "customerinventoryitem",
|
||||
trackingtrips: "trackingtrip",
|
||||
createddocuments: "createddocument",
|
||||
inventoryitemgroups: "inventoryitemgroup",
|
||||
bankstatements: "bankstatement",
|
||||
incominginvoices: "incomingInvoice",
|
||||
files: "file",
|
||||
memberrelations: "memberrelation",
|
||||
}
|
||||
|
||||
const fkColumn = columnMap[params.entity]
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
contracttypes,
|
||||
costcentres,
|
||||
createddocuments,
|
||||
customerinventoryitems,
|
||||
customerspaces,
|
||||
customers,
|
||||
files,
|
||||
filetags,
|
||||
@@ -18,6 +20,7 @@ import {
|
||||
inventoryitemgroups,
|
||||
inventoryitems,
|
||||
letterheads,
|
||||
memberrelations,
|
||||
ownaccounts,
|
||||
plants,
|
||||
productcategories,
|
||||
@@ -46,7 +49,7 @@ export const resourceConfig = {
|
||||
},
|
||||
customers: {
|
||||
searchColumns: ["name", "customerNumber", "firstname", "lastname", "notes"],
|
||||
mtmLoad: ["contacts","projects","plants","createddocuments","contracts"],
|
||||
mtmLoad: ["contacts","projects","plants","createddocuments","contracts","customerinventoryitems","customerspaces"],
|
||||
table: customers,
|
||||
numberRangeHolder: "customerNumber",
|
||||
},
|
||||
@@ -57,6 +60,10 @@ export const resourceConfig = {
|
||||
numberRangeHolder: "customerNumber",
|
||||
relationKey: "customer",
|
||||
},
|
||||
memberrelations: {
|
||||
table: memberrelations,
|
||||
searchColumns: ["type", "billingInterval"],
|
||||
},
|
||||
contacts: {
|
||||
searchColumns: ["firstName", "lastName", "email", "phone", "notes"],
|
||||
table: contacts,
|
||||
@@ -99,6 +106,12 @@ export const resourceConfig = {
|
||||
table: inventoryitems,
|
||||
numberRangeHolder: "articleNumber",
|
||||
},
|
||||
customerinventoryitems: {
|
||||
table: customerinventoryitems,
|
||||
numberRangeHolder: "customerInventoryId",
|
||||
mtoLoad: ["customer", "customerspace", "product", "vendor"],
|
||||
searchColumns: ["name", "customerInventoryId", "serialNumber", "description", "manufacturer", "manufacturerNumber"],
|
||||
},
|
||||
inventoryitemgroups: {
|
||||
table: inventoryitemgroups
|
||||
},
|
||||
@@ -133,6 +146,13 @@ export const resourceConfig = {
|
||||
searchColumns: ["name","space_number","type","info_data"],
|
||||
numberRangeHolder: "spaceNumber",
|
||||
},
|
||||
customerspaces: {
|
||||
table: customerspaces,
|
||||
searchColumns: ["name","space_number","type","info_data","description"],
|
||||
numberRangeHolder: "space_number",
|
||||
mtoLoad: ["customer"],
|
||||
mtmLoad: ["customerinventoryitems"],
|
||||
},
|
||||
ownaccounts: {
|
||||
table: ownaccounts,
|
||||
searchColumns: ["name","description","number"],
|
||||
|
||||
Reference in New Issue
Block a user