first commit
commit
f1415db47c
|
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules
|
||||||
|
.vscode
|
||||||
|
config.js
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# UpdateTrackingCodes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Dieses Programm liest eine Excel-Datei mit Sendungsnummern ein. Anhand der Lieferantenbestellnummer (LB-*****) wird der zugehörige Auftrag ermittelt.
|
||||||
|
|
||||||
|
Handelt es sich um einen Auftrag vom Otto Marketplace (kShop = 7), wird eine Rücksende-Sendungsnummer bei DHL generiert. Dies erfolgt durch den Aufruf (per exec) eines dafür bereitgestellten Programms.
|
||||||
|
|
||||||
|
Bei Aufträgen von anderen Marktplätzen wird derzeit keine Rücksende-Sendungsnummer benötigt.
|
||||||
|
|
||||||
|
Anschließend werden die relevanten Daten in die JTL-Datenbank geschrieben. Das Versanddatum wird dabei für alle Aufträge gesetzt – mit Ausnahme der Aufträge vom Otto Marketplace. In diesem Fall wird das Versanddatum erst eingetragen, nachdem die Versandinformationen über ein externes Programm an Otto übermittelt wurden.
|
||||||
|
|
||||||
|
### Executing program
|
||||||
|
|
||||||
|
```
|
||||||
|
node index.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
|
||||||
|
|
||||||
|
* 1.0.0
|
||||||
|
* Initial Release
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
@ -0,0 +1,195 @@
|
||||||
|
|
||||||
|
const FileManager = require("./file_manager")
|
||||||
|
const config = require("./config")
|
||||||
|
const execSync = require("child_process").execSync
|
||||||
|
const JtlSql = require("./jtlsql")
|
||||||
|
const DhlReturns = require("./dhl_returns")
|
||||||
|
const fileManager = new FileManager()
|
||||||
|
const jtlSql = new JtlSql()
|
||||||
|
const CsvLogger = require('./csv_logger')
|
||||||
|
const CsvExport = require('./csv_export')
|
||||||
|
const PATH_LOGFILES = require('./config').PATH_LOGFILES;
|
||||||
|
const FILE_PATH_CSV_EXPORT = require('./config').FILE_PATH_CSV_EXPORT;
|
||||||
|
const EXPECTED_PROPERTIES = require('./config').EXPECTED_PROPERTIES
|
||||||
|
const logger = new CsvLogger(PATH_LOGFILES, ['timestamp', 'level', 'message'], 30); // 30 Tage aufbewahren
|
||||||
|
const csvExport = new CsvExport(FILE_PATH_CSV_EXPORT)
|
||||||
|
const EmailSender = require("./email_sender")
|
||||||
|
const CsvToHtmlConverter = require("./csv_to_html_converter")
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
|
||||||
|
class App {
|
||||||
|
|
||||||
|
#warnOrErrOccured = false
|
||||||
|
#timestamp
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.#timestamp = Math.round(parseInt(Date.now()) / 1000)
|
||||||
|
logger.timestamp = this.#timestamp
|
||||||
|
|
||||||
|
process.on('uncaughtException', (err)=>{
|
||||||
|
this.#warnOrErrOccured = err
|
||||||
|
console.log(err)
|
||||||
|
this.#shutdown()
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
|
||||||
|
logger.log('info',"Pogramm gestartet")
|
||||||
|
logger.log('info',"Lade Datei "+config.FILE_PATH_SENDUNGSNUMMERN)
|
||||||
|
let data = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = fileManager.readExcelFile(config.FILE_PATH_SENDUNGSNUMMERN)
|
||||||
|
} catch (err) {
|
||||||
|
logger.log("err",err.message)
|
||||||
|
this.#warnOrErrOccured = err
|
||||||
|
await this.#shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
await this.#checkDataStructure(Object.getOwnPropertyNames(data[0]))
|
||||||
|
|
||||||
|
logger.log('info', data.length+" Datensätze gefunden")
|
||||||
|
|
||||||
|
for (let i=0; i < data.length; i++) {
|
||||||
|
const liefBestNr = data[i]['Auftrags-Nr._1']
|
||||||
|
|
||||||
|
const order = await jtlSql.getOrderOnLiefBest(liefBestNr)
|
||||||
|
if (!order) {
|
||||||
|
logger.log('warn',"Keinen Auftrag gefunden zur Lieferantenbestellung: "+liefBestNr)
|
||||||
|
this.#warnOrErrOccured = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
logger.log('info',"Auftrag '"+ order.cBestellNr+"' gefunden zur Lieferantenbestellung: "+liefBestNr)
|
||||||
|
await this.#handleShipping(data[i], order)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.MOVE_FILE_TO_PROCESSED_FOLDER) {
|
||||||
|
await this.#moveFileToProcessedFolder(config.FILE_PATH_SENDUNGSNUMMERN)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.#shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
async #shutdown() {
|
||||||
|
if (this.#warnOrErrOccured) {
|
||||||
|
//logger.log("err",this.#warnOrErrOccured.message)
|
||||||
|
if (config.SEND_MAIL_ON_EROR) {
|
||||||
|
let emailSender = new EmailSender()
|
||||||
|
let csvToHtmlConverter = new CsvToHtmlConverter()
|
||||||
|
await emailSender.sendMail({
|
||||||
|
to: defaultEmailConfig.to,
|
||||||
|
subject: "[Problem] Sendungsnummern Frank Flechtwaren",
|
||||||
|
html: `Last Error: <p style="font-weight: bold"">${this.#warnOrErrOccured.toString()}</p><br><br> ${await csvToHtmlConverter.convertToHtml(logger.getLogFilePath())}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
logger.log("info","Proramm beendet")
|
||||||
|
process.exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
async #handleShipping(excelFileData, jtlOrderData) {
|
||||||
|
const sendDate = FileManager.convertDateExcel(excelFileData.Versanddatum)
|
||||||
|
console.log(excelFileData)
|
||||||
|
const lieferschein = await jtlSql.getLieferscheinOnAuftragsnummer(jtlOrderData.cBestellNr)
|
||||||
|
console.log(lieferschein)
|
||||||
|
const data = {
|
||||||
|
id: lieferschein.cLieferscheinNr,
|
||||||
|
date: sendDate.getDate()+'.'+ (sendDate.getMonth() +1) +'.'+sendDate.getFullYear(),
|
||||||
|
trackingNr: excelFileData['Sendungs-Nr.'],
|
||||||
|
infos: excelFileData['Lieferschein-Nr.'],
|
||||||
|
returnTrackingNr:''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jtlOrderData.kShop == 7) {
|
||||||
|
const paket = {}
|
||||||
|
paket.itemWeight = 1
|
||||||
|
paket.cReference = jtlOrderData.cBestellNr + " / " + jtlOrderData.cInetBestellNr
|
||||||
|
const labelData = await this.#createLabelData(excelFileData, jtlOrderData, paket)
|
||||||
|
|
||||||
|
data.returnTrackingNr = this.#getDhlReturnTrackingCode(labelData).toString()
|
||||||
|
console.log(labelData)
|
||||||
|
}
|
||||||
|
|
||||||
|
csvExport.add(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async #createLabelData(excelFileData, jtlOrderData, paketData) {
|
||||||
|
|
||||||
|
const liefAddress = await jtlSql.getAdresse(jtlOrderData.tAdresse_kAdresse)
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
firma: liefAddress.cFirma,
|
||||||
|
name: excelFileData['Liefer-Name'] ? excelFileData['Liefer-Name'] : liefAddress.cVorname + ' '+ liefAddress.cNachname ,
|
||||||
|
strasse: excelFileData['Liefer-Str.'] ? excelFileData['Liefer-Str.'] : liefAddress.cStrasse,
|
||||||
|
plz: excelFileData['Liefer-Plz'] ? excelFileData['Liefer-Plz'] : liefAddress.cPLZ,
|
||||||
|
ort: excelFileData['Liefer-Stadt'] ? excelFileData['Liefer-Stadt'] : liefAddress.cOrt,
|
||||||
|
landISO2: liefAddress.cISO,
|
||||||
|
email: liefAddress.cMail,
|
||||||
|
|
||||||
|
itemWeight: paketData.fGewicht * 1000,
|
||||||
|
customerReference: paketData.cReference.replaceAll(" "," "),
|
||||||
|
shipmentReference: paketData.cReference.replaceAll(" "," "),
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#getDhlReturnTrackingCode(labelData) {
|
||||||
|
const dhlReturns = new DhlReturns()
|
||||||
|
const dhlShippingData = dhlReturns.createDhlShippingDataObject(labelData)
|
||||||
|
|
||||||
|
const returnTrackingCode = execSync(
|
||||||
|
"node ../dhlApi/index.js func=ReturnShipping data=\"" +
|
||||||
|
JSON.stringify(dhlShippingData).replaceAll('"', '\\"') +
|
||||||
|
"\""
|
||||||
|
)
|
||||||
|
return returnTrackingCode
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async #checkDataStructure(proerties) {
|
||||||
|
|
||||||
|
if (proerties) {
|
||||||
|
try {
|
||||||
|
if (this.#hasDataStructureChanged(proerties)) {
|
||||||
|
throw new Error("DATA_STRUCTURE_OF_FILE_HAS_CHANGED")
|
||||||
|
}
|
||||||
|
logger.log("info", "Datenstruktur ist OK")
|
||||||
|
} catch (err) {
|
||||||
|
this.#warnOrErrOccured = err
|
||||||
|
logger.log('err', "Fehler in der Datenstruktur: " + err.message)
|
||||||
|
await this.#shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#hasDataStructureChanged(properties) {
|
||||||
|
return JSON.stringify(properties) !== JSON.stringify(EXPECTED_PROPERTIES)
|
||||||
|
}
|
||||||
|
|
||||||
|
async #moveFileToProcessedFolder(file) {
|
||||||
|
//Move file to processed
|
||||||
|
let fileManager = new FileManager()
|
||||||
|
let filename = this.#timestamp + "_" + path.basename(file)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fileManager.moveFile(file, config.PATH_TO_MOVE_PROCESSED_FILES + filename)
|
||||||
|
} catch (err) {
|
||||||
|
this.#warnOrErrOccured = err
|
||||||
|
logger.log('error', "Fehler beim verschieben: " + err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = App
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
class CsvExport {
|
||||||
|
/**
|
||||||
|
* @param {string} logDir - Directory where log files will be saved
|
||||||
|
* @param {string[]} headers - CSV header row
|
||||||
|
*/
|
||||||
|
constructor(filePath, headers = ['ID-Nummer', 'Datum (TT.MM.JJJ)', 'Tracking-Nr.','Versandinfos','Beilegeretoure']) {
|
||||||
|
this.filePath = filePath;
|
||||||
|
this.headers = headers;
|
||||||
|
|
||||||
|
|
||||||
|
if (!fs.existsSync(path.dirname(filePath))) {
|
||||||
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||||
|
} else {
|
||||||
|
fs.unlinkSync(filePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a new log entry to today's CSV file
|
||||||
|
|
||||||
|
*/
|
||||||
|
add(data) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
this.ensureFileWithHeaders( this.filePath );
|
||||||
|
|
||||||
|
const row = [data.id, data.date, data.trackingNr, data.infos, data.returnTrackingNr]
|
||||||
|
.map(this.escapeCsv)
|
||||||
|
.join(';') + '\n';
|
||||||
|
|
||||||
|
fs.appendFileSync( this.filePath , row, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Failed to write to csv file:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures headers are written if file doesn't exist
|
||||||
|
*/
|
||||||
|
ensureFileWithHeaders(filePath) {
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
const headerRow = this.headers.join(';') + '\n';
|
||||||
|
fs.writeFileSync(filePath, headerRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes a value for safe CSV output
|
||||||
|
*/
|
||||||
|
escapeCsv(value) {
|
||||||
|
if (value == null) return '';
|
||||||
|
const str = String(value);
|
||||||
|
return /[",\n]/.test(str) ? `"${str.replace(/"/g, '""')}"` : str;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = CsvExport;
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
class CsvLogger {
|
||||||
|
/**
|
||||||
|
* @param {string} logDir - Directory where log files will be saved
|
||||||
|
* @param {string[]} headers - CSV header row
|
||||||
|
* @param {number} maxFileAgeDays - How many days log files should be kept
|
||||||
|
*/
|
||||||
|
constructor(logDir, headers = ['timestamp', 'level', 'message'], maxFileAgeDays = 30) {
|
||||||
|
this.logDir = logDir;
|
||||||
|
this.headers = headers;
|
||||||
|
this.maxFileAgeDays = maxFileAgeDays;
|
||||||
|
this.timestamp = null
|
||||||
|
|
||||||
|
if (!fs.existsSync(logDir)) {
|
||||||
|
fs.mkdirSync(logDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup old log files once on startup
|
||||||
|
this.deleteOldLogFiles().catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a new log entry to today's CSV file
|
||||||
|
* @param {string} level - Log level (e.g., 'info', 'error')
|
||||||
|
* @param {string} message - Log message
|
||||||
|
* @param {string[]} extraFields - Optional additional fields
|
||||||
|
*/
|
||||||
|
log(level, message, extraFields = []) {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const filePath = this.getLogFilePath();
|
||||||
|
|
||||||
|
this.ensureFileWithHeaders(filePath);
|
||||||
|
|
||||||
|
const row = [timestamp, level, message, ...extraFields]
|
||||||
|
.map(this.escapeCsv)
|
||||||
|
.join(',') + '\n';
|
||||||
|
|
||||||
|
fs.appendFileSync(filePath, row, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Failed to write to log file:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the log file path for today
|
||||||
|
*/
|
||||||
|
getLogFilePath() {
|
||||||
|
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
||||||
|
let filename = "logs.csv"
|
||||||
|
|
||||||
|
if (this.timestamp == null) {
|
||||||
|
filename = `logs_${today}.csv`
|
||||||
|
} else {
|
||||||
|
filename = `${this.timestamp}_logs.csv`
|
||||||
|
}
|
||||||
|
return path.join(this.logDir, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures headers are written if file doesn't exist
|
||||||
|
*/
|
||||||
|
ensureFileWithHeaders(filePath) {
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
const headerRow = this.headers.join(',') + '\n';
|
||||||
|
fs.writeFileSync(filePath, headerRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes a value for safe CSV output
|
||||||
|
*/
|
||||||
|
escapeCsv(value) {
|
||||||
|
if (value == null) return '';
|
||||||
|
const str = String(value);
|
||||||
|
return /[",\n]/.test(str) ? `"${str.replace(/"/g, '""')}"` : str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes log files older than maxFileAgeDays
|
||||||
|
*/
|
||||||
|
async deleteOldLogFiles() {
|
||||||
|
const files = await fs.promises.readdir(this.logDir);
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const maxAgeMs = this.maxFileAgeDays * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(this.logDir, file);
|
||||||
|
if (!/^logs_\d{4}-\d{2}-\d{2}\.csv$/.test(file)) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stats = await fs.promises.stat(filePath);
|
||||||
|
const age = now - stats.mtimeMs;
|
||||||
|
|
||||||
|
if (age > maxAgeMs) {
|
||||||
|
await fs.promises.unlink(filePath);
|
||||||
|
console.log(`Deleted old log file: ${file}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`Could not process file ${file}: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = CsvLogger;
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const readline = require('readline');
|
||||||
|
|
||||||
|
class CsvToHtmlConverter {
|
||||||
|
/**
|
||||||
|
* Erstellt eine HTML-Tabelle aus einer CSV-Datei
|
||||||
|
* @param {string} csvFilePath Pfad zur CSV-Datei
|
||||||
|
* @returns {Promise<string>} HTML-Tabelle als String
|
||||||
|
*/
|
||||||
|
async convertToHtml(csvFilePath) {
|
||||||
|
if (!fs.existsSync(csvFilePath)) {
|
||||||
|
throw new Error(`CSV file not found: ${csvFilePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileStream = fs.createReadStream(csvFilePath);
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: fileStream,
|
||||||
|
crlfDelay: Infinity,
|
||||||
|
});
|
||||||
|
|
||||||
|
let html = this.getHtmlHeader();
|
||||||
|
let isFirstLine = true;
|
||||||
|
|
||||||
|
for await (const line of rl) {
|
||||||
|
const cells = this.parseCsvLine(line);
|
||||||
|
|
||||||
|
if (isFirstLine) {
|
||||||
|
html += '<thead><tr>';
|
||||||
|
cells.forEach(cell => {
|
||||||
|
html += `<th>${this.escapeHtml(cell)}</th>`;
|
||||||
|
});
|
||||||
|
html += '</tr></thead><tbody>';
|
||||||
|
isFirstLine = false;
|
||||||
|
} else {
|
||||||
|
html += '<tr>';
|
||||||
|
cells.forEach(cell => {
|
||||||
|
html += `<td>${this.escapeHtml(cell)}</td>`;
|
||||||
|
});
|
||||||
|
html += '</tr>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '</tbody></table>';
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt das HTML- und CSS-Headergerüst für die Tabelle zurück
|
||||||
|
*/
|
||||||
|
getHtmlHeader() {
|
||||||
|
return `
|
||||||
|
<style>
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 6px 12px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
thead {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
tbody tr:nth-child(odd) {
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
tbody tr:nth-child(even) {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<table>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wandelt eine CSV-Zeile in Felder um
|
||||||
|
* @param {string} line
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
parseCsvLine(line) {
|
||||||
|
// Für einfache Fälle (Kommas, kein Quote-Parsing)
|
||||||
|
return line.split(',').map(field => field.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wandelt Sonderzeichen in HTML-Entities um
|
||||||
|
* @param {string} text
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
escapeHtml(text) {
|
||||||
|
return String(text)
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = CsvToHtmlConverter;
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
class DhlReturns {
|
||||||
|
|
||||||
|
createDhlShippingDataObject(data) {
|
||||||
|
const orderData = {
|
||||||
|
receiverId: 'deu',
|
||||||
|
customerReference: data.customerReference,
|
||||||
|
shipmentReference: data.shipmentReference,
|
||||||
|
shipper: {
|
||||||
|
name1: data.name,
|
||||||
|
name2: data.firma,
|
||||||
|
name3: '',
|
||||||
|
addressStreet: this.#getStreet(data.strasse),
|
||||||
|
addressHouse: this.#getHouseNr(data.strasse),
|
||||||
|
city: data.ort,
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
postalCode: data.plz,
|
||||||
|
state: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
itemWeight: {
|
||||||
|
uom: 'g',
|
||||||
|
value: data.itemWeight
|
||||||
|
},
|
||||||
|
|
||||||
|
itemValue: {
|
||||||
|
currency: 'EUR',
|
||||||
|
value: 100
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return orderData
|
||||||
|
}
|
||||||
|
|
||||||
|
#getStreet(str) {
|
||||||
|
let parts = str.split(" ")
|
||||||
|
let street = ''
|
||||||
|
for (let i = 0; i < parts.length-1; i++) {
|
||||||
|
street = street + parts[i] + " "
|
||||||
|
}
|
||||||
|
return street.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
#getHouseNr(str) {
|
||||||
|
let parts = str.split(" ")
|
||||||
|
return parts[parts.length -1].trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = DhlReturns
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
const nodemailer = require('nodemailer');
|
||||||
|
const defaultEmailConfig = require('./config').defaultEmailConfig
|
||||||
|
|
||||||
|
class EmailSender {
|
||||||
|
/**
|
||||||
|
* Konstruktor initialisiert Transport-Konfiguration.
|
||||||
|
* @param {object} config - Konfiguration für den SMTP-Transport.
|
||||||
|
*/
|
||||||
|
constructor(config = undefined) {
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
config = defaultEmailConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
this.transporter = nodemailer.createTransport({
|
||||||
|
host: config.host,
|
||||||
|
port: config.port,
|
||||||
|
secure: config.secure, // true für 465, false für andere Ports
|
||||||
|
auth: {
|
||||||
|
user: config.user,
|
||||||
|
pass: config.pass
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.from = config.from;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sendet eine E-Mail.
|
||||||
|
* @param {object} options - Details zur E-Mail.
|
||||||
|
* @param {string} options.to - Empfängeradresse.
|
||||||
|
* @param {string} options.subject - Betreff der E-Mail.
|
||||||
|
* @param {string} options.text - Nur-Text-Version.
|
||||||
|
* @param {string} [options.html] - HTML-Version (optional).
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async sendMail({ to, subject, text, html }) {
|
||||||
|
try {
|
||||||
|
const info = await this.transporter.sendMail({
|
||||||
|
from: this.from,
|
||||||
|
to,
|
||||||
|
subject,
|
||||||
|
text,
|
||||||
|
html
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('E-Mail gesendet:', info.messageId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Senden der E-Mail:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = EmailSender;
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
const fsPromises = require('fs').promises;
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const XLSX = require('xlsx');
|
||||||
|
|
||||||
|
class FileManager {
|
||||||
|
/**
|
||||||
|
* Moves a file from sourcePath to destinationPath.
|
||||||
|
* @param {string} sourcePath - Full path to the source file.
|
||||||
|
* @param {string} destinationPath - Destination directory or full file path.
|
||||||
|
*/
|
||||||
|
async moveFile(sourcePath, destinationPath) {
|
||||||
|
try {
|
||||||
|
const destinationStat = await fsPromises.lstat(destinationPath).catch(() => null);
|
||||||
|
const isDirectory = destinationStat && destinationStat.isDirectory();
|
||||||
|
|
||||||
|
const finalDestination = isDirectory
|
||||||
|
? path.join(destinationPath, path.basename(sourcePath))
|
||||||
|
: destinationPath;
|
||||||
|
|
||||||
|
await fsPromises.rename(sourcePath, finalDestination);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readExcelFile(filePath) {
|
||||||
|
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
throw new Error(`Datei nicht gefunden: ${filePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ext = path.extname(filePath).toLowerCase();
|
||||||
|
if (ext !== '.xlsx' && ext !== '.xls') {
|
||||||
|
throw new Error(`Ungültiger Dateityp: ${ext}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const workbook = XLSX.readFile(filePath);
|
||||||
|
const sheetName = workbook.SheetNames[0];
|
||||||
|
const worksheet = workbook.Sheets[sheetName];
|
||||||
|
const data = XLSX.utils.sheet_to_json(worksheet, { defval: null });
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async readJson(filePath) {
|
||||||
|
try {
|
||||||
|
const data = await fsPromises.readFile(filePath, 'utf-8');
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Fehler beim Lesen der JSON-Datei: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async writeJson(filePath, data) {
|
||||||
|
try {
|
||||||
|
const jsonString = JSON.stringify(data, null, 4); // schön formatiert
|
||||||
|
await fsPromises.writeFile(filePath, jsonString, 'utf-8');
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Fehler beim Schreiben der JSON-Datei: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async copyFile(sourcePath, destinationPath) {
|
||||||
|
try {
|
||||||
|
await fsPromises.copyFile(sourcePath, destinationPath);
|
||||||
|
//console.log(`Datei erfolgreich kopiert von ${sourcePath} nach ${destinationPath}`);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Fehler beim Kopieren der Datei: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static convertDateExcel (excelDate) {
|
||||||
|
// Get the number of milliseconds from Unix epoch.
|
||||||
|
const unixTime = (excelDate - 25569) * 86400 * 1000;
|
||||||
|
return new Date(unixTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = FileManager;
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
const sql = require('mssql');
|
||||||
|
const MSSQL_CONFIG = require('./config').MSSQL_CONFIG
|
||||||
|
const DRY_RUN = require('./config').DRY_RUN
|
||||||
|
|
||||||
|
class JtlSql {
|
||||||
|
|
||||||
|
async getOrderOnLiefBest(liefBestNr) {
|
||||||
|
try {
|
||||||
|
const auftrag = await this.getBezugsAuftragsNrOnLiefBestNr(liefBestNr)
|
||||||
|
if (!auftrag) return null
|
||||||
|
|
||||||
|
await sql.connect(MSSQL_CONFIG);
|
||||||
|
|
||||||
|
const request = new sql.Request();
|
||||||
|
request.input('cBestellNr', sql.VarChar, auftrag.cBezugsAuftragsNummer);
|
||||||
|
|
||||||
|
const query = `SELECT * FROM tBestellung WHERE cBestellNr = @cBestellNr`;
|
||||||
|
|
||||||
|
const result = await request.query(query);
|
||||||
|
return result.recordset[0]
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching BezugsAuftragsNummer:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBezugsAuftragsNrOnLiefBestNr(liefBestNr) {
|
||||||
|
try {
|
||||||
|
await sql.connect(MSSQL_CONFIG);
|
||||||
|
|
||||||
|
const request = new sql.Request();
|
||||||
|
request.input('liefBestNr', sql.VarChar, liefBestNr);
|
||||||
|
|
||||||
|
const query = `SELECT cBezugsAuftragsNummer FROM tLieferantenBestellung WHERE cEigeneBestellnummer = @liefBestNr`;
|
||||||
|
const result = await request.query(query);
|
||||||
|
return result.recordset[0]
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching BezugsAuftragsNummer:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLieferscheinOnAuftragsnummer(auftragsNummer) {
|
||||||
|
try {
|
||||||
|
await sql.connect(MSSQL_CONFIG);
|
||||||
|
|
||||||
|
const request = new sql.Request();
|
||||||
|
request.input('auftragsNummer', sql.VarChar, auftragsNummer+'%');
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
SELECT TOP 1 *
|
||||||
|
FROM tLieferschein
|
||||||
|
WHERE cLieferscheinNr LIKE @auftragsNummer
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await request.query(query);
|
||||||
|
|
||||||
|
return result.recordset[0]|| null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching Lieferschein:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getVersandPaketeOnLieferscheinId(kLieferschein) {
|
||||||
|
try {
|
||||||
|
await sql.connect(MSSQL_CONFIG);
|
||||||
|
|
||||||
|
const request = new sql.Request();
|
||||||
|
request.input('kLieferschein', sql.Int, kLieferschein);
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
SELECT * FROM tVersand
|
||||||
|
WHERE kLieferschein = @kLieferschein
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await request.query(query);
|
||||||
|
|
||||||
|
return result.recordset
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching Versand:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveVersandPaket(paket) {
|
||||||
|
if ('kVersand' in paket) {
|
||||||
|
return await this.updateVersandPaket(paket)
|
||||||
|
} else {
|
||||||
|
return await this.insertVersandPaket(paket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async insertVersandPaket(paket) {
|
||||||
|
try {
|
||||||
|
await sql.connect(MSSQL_CONFIG);
|
||||||
|
|
||||||
|
const request = new sql.Request();
|
||||||
|
|
||||||
|
request.input('kLieferschein', sql.Int, paket.kLieferschein);
|
||||||
|
request.input('kBenutzer', sql.Int, paket.kBenutzer);
|
||||||
|
request.input('kLogistik', sql.Int, paket.kLogistik);
|
||||||
|
request.input('kVersandArt', sql.Int, paket.kVersandArt);
|
||||||
|
request.input('cIdentCode', sql.VarChar, paket.cIdentCode.toString());
|
||||||
|
request.input('cEnclosedReturnIdentCode', sql.VarChar, paket.cEnclosedReturnIdentCode.toString());
|
||||||
|
request.input('dVersendet', sql.DateTime, paket.dVersendet);
|
||||||
|
request.input('nStatus', sql.TinyInt, paket.nStatus);
|
||||||
|
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
INSERT INTO tVersand
|
||||||
|
(kLieferschein, kBenutzer, kLogistik, nStatus, kVersandArt, cIdentCode, cEnclosedReturnIdentCode, dVersendet)
|
||||||
|
VALUES
|
||||||
|
(@kLieferschein, @kBenutzer, @kLogistik, @nStatus, @kVersandArt, @cIdentCode, @cEnclosedReturnIdentCode, @dVersendet)
|
||||||
|
`;
|
||||||
|
|
||||||
|
let fullQuery = DRY_RUN
|
||||||
|
? `BEGIN TRANSACTION;\n${query}\nROLLBACK TRANSACTION;`
|
||||||
|
: query;
|
||||||
|
|
||||||
|
const result = await request.query(fullQuery);
|
||||||
|
|
||||||
|
return result.rowsAffected[0]
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateVersandPaket(paket) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sql.connect(MSSQL_CONFIG);
|
||||||
|
|
||||||
|
const request = new sql.Request();
|
||||||
|
request.input('kVersand', sql.Int, paket.kVersand);
|
||||||
|
request.input('kLieferschein', sql.Int, paket.kLieferschein);
|
||||||
|
request.input('cIdentCode', sql.VarChar, paket.cIdentCode ? paket.cIdentCode.toString() : null);
|
||||||
|
request.input('cEnclosedReturnIdentCode', sql.VarChar, paket.cEnclosedReturnIdentCode ? paket.cEnclosedReturnIdentCode.toString() : null);
|
||||||
|
request.input('dVersendet', sql.DateTime, paket.dVersendet);
|
||||||
|
request.input('nStatus', sql.TinyInt, paket.nStatus);
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
UPDATE tVersand SET
|
||||||
|
kLieferschein = @kLieferschein,
|
||||||
|
nStatus = @nStatus,
|
||||||
|
cIdentCode = @cIdentCode,
|
||||||
|
cEnclosedReturnIdentCode = @cEnclosedReturnIdentCode,
|
||||||
|
dVersendet = @dVersendet
|
||||||
|
WHERE
|
||||||
|
kVersand = @kVersand
|
||||||
|
`;
|
||||||
|
|
||||||
|
let fullQuery = DRY_RUN
|
||||||
|
? `BEGIN TRANSACTION;\n${query}\nROLLBACK TRANSACTION;`
|
||||||
|
: query;
|
||||||
|
|
||||||
|
const result = await request.query(fullQuery);
|
||||||
|
|
||||||
|
return result.rowsAffected[0]
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAdresse(kAdresse) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
await sql.connect(MSSQL_CONFIG);
|
||||||
|
|
||||||
|
const request = new sql.Request();
|
||||||
|
request.input('kAdresse', sql.Int, kAdresse);
|
||||||
|
|
||||||
|
const query = `SELECT * FROM tAdresse WHERE kAdresse = @kAdresse`;
|
||||||
|
|
||||||
|
const result = await request.query(query);
|
||||||
|
return result.recordset[0]
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = JtlSql
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "flechtware_updatetrackingcodes",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"mssql": "^11.0.1",
|
||||||
|
"nodemailer": "^7.0.3",
|
||||||
|
"path": "^0.12.7",
|
||||||
|
"readline": "^1.3.0",
|
||||||
|
"sqlite3": "^5.1.7",
|
||||||
|
"xlsx": "^0.18.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue