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