Add invoice and customer API methods
This commit is contained in:
@@ -62,4 +62,12 @@ The store can read both the connector format and the older Telefonbuch cookie-ar
|
|||||||
- `checkRegistriert()` checks whether the page still contains `Neues Gerät registrieren`.
|
- `checkRegistriert()` checks whether the page still contains `Neues Gerät registrieren`.
|
||||||
- `registerDevice({ agrarmonitorId, pcName })` loads `/freischaltung/`, extracts the nonce, and posts the registration request.
|
- `registerDevice({ agrarmonitorId, pcName })` loads `/freischaltung/`, extracts the nonce, and posts the registration request.
|
||||||
- `fetchCustomers()` loads customers from `https://api.agrarmonitor.de/v1/kunden`.
|
- `fetchCustomers()` loads customers from `https://api.agrarmonitor.de/v1/kunden`.
|
||||||
|
- `getCustomerById(id)` loads one customer from the token-based Agrarmonitor API.
|
||||||
|
- `eingangsrechnungenLivesearch(suchstring)` searches invoice files and enriches matching Eingangsrechnungen with edit/detail metadata.
|
||||||
|
- `eingangsrechnungVorhanden(suchstring)` checks whether invoice file search has rows.
|
||||||
|
- `eingangsrechnungImDateieingangVorhanden(suchstring)` checks whether file inbox search has rows.
|
||||||
|
- `getRechnungsdaten(rechnungId)` reads editable invoice fields.
|
||||||
|
- `setRechnungsdaten(rechnungId, daten)` updates editable invoice fields.
|
||||||
|
- `setLieferscheinNummer(rechnungId, nummer)` updates only the delivery note number.
|
||||||
|
- `setEingangsdatum(rechnungId, datum)` updates the received date.
|
||||||
- `saveSession()` explicitly persists the current cookie jar.
|
- `saveSession()` explicitly persists the current cookie jar.
|
||||||
|
|||||||
Vendored
+32
-1
@@ -1,8 +1,9 @@
|
|||||||
import { type AxiosInstance } from 'axios';
|
import { type AxiosInstance } from 'axios';
|
||||||
import type { AgrarmonitorApiCustomer, AgrarmonitorConnectorOptions, AgrarmonitorConnectorResult, AgrarmonitorDeviceRegistrationOptions, AgrarmonitorDeviceRegistrationResult, AgrarmonitorFetchCustomersOptions, AgrarmonitorFreischaltungStatus, AgrarmonitorRegistrierungStatus } from './types';
|
import type { AgrarmonitorApiCustomer, AgrarmonitorConnectorOptions, AgrarmonitorConnectorResult, AgrarmonitorDeviceRegistrationOptions, AgrarmonitorDeviceRegistrationResult, AgrarmonitorFetchCustomersOptions, AgrarmonitorFreischaltungStatus, AgrarmonitorRegistrierungStatus, EingangsrechnungLivesearchResult, Rechnungsdaten } from './types';
|
||||||
export declare class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
|
export declare class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
|
||||||
private readonly options;
|
private readonly options;
|
||||||
http: AxiosInstance;
|
http: AxiosInstance;
|
||||||
|
private static readonly s3DateienBaseUrl;
|
||||||
private readonly baseUrl;
|
private readonly baseUrl;
|
||||||
private readonly apiBaseUrl;
|
private readonly apiBaseUrl;
|
||||||
private readonly timeoutMs;
|
private readonly timeoutMs;
|
||||||
@@ -11,6 +12,7 @@ export declare class AgrarmonitorConnector implements AgrarmonitorConnectorResul
|
|||||||
private readonly loginStrategy;
|
private readonly loginStrategy;
|
||||||
private readonly logger?;
|
private readonly logger?;
|
||||||
private cookieJar;
|
private cookieJar;
|
||||||
|
private apiHttp;
|
||||||
private loginInProgress;
|
private loginInProgress;
|
||||||
constructor(options: AgrarmonitorConnectorOptions);
|
constructor(options: AgrarmonitorConnectorOptions);
|
||||||
init(): Promise<this>;
|
init(): Promise<this>;
|
||||||
@@ -22,7 +24,17 @@ export declare class AgrarmonitorConnector implements AgrarmonitorConnectorResul
|
|||||||
checkRegistriert(): Promise<AgrarmonitorRegistrierungStatus>;
|
checkRegistriert(): Promise<AgrarmonitorRegistrierungStatus>;
|
||||||
registerDevice(registration: AgrarmonitorDeviceRegistrationOptions): Promise<AgrarmonitorDeviceRegistrationResult>;
|
registerDevice(registration: AgrarmonitorDeviceRegistrationOptions): Promise<AgrarmonitorDeviceRegistrationResult>;
|
||||||
fetchCustomers(options?: AgrarmonitorFetchCustomersOptions): Promise<AgrarmonitorApiCustomer[]>;
|
fetchCustomers(options?: AgrarmonitorFetchCustomersOptions): Promise<AgrarmonitorApiCustomer[]>;
|
||||||
|
eingangsrechnungenLivesearch(suchstring: string): Promise<EingangsrechnungLivesearchResult[]>;
|
||||||
|
eingangsrechnungVorhanden(suchstring: string): Promise<boolean>;
|
||||||
|
eingangsrechnungImDateieingangVorhanden(suchstring: string): Promise<boolean>;
|
||||||
|
getRechnungsdaten(rechnungId: number): Promise<Rechnungsdaten>;
|
||||||
|
setRechnungsdaten(rechnungId: number, daten: Rechnungsdaten): Promise<boolean>;
|
||||||
|
setLieferscheinNummer(rechnungId: number, nummer: string): Promise<void>;
|
||||||
|
setEingangsdatum(rechnungId: number, datum: Date): Promise<boolean>;
|
||||||
|
getCustomerById(id: number): Promise<AgrarmonitorApiCustomer>;
|
||||||
private createHttpClient;
|
private createHttpClient;
|
||||||
|
private createApiHttpClient;
|
||||||
|
private apiRequest;
|
||||||
private performLogin;
|
private performLogin;
|
||||||
private performAutoLogin;
|
private performAutoLogin;
|
||||||
private performAuthLogin;
|
private performAuthLogin;
|
||||||
@@ -30,6 +42,25 @@ export declare class AgrarmonitorConnector implements AgrarmonitorConnectorResul
|
|||||||
private isSessionValid;
|
private isSessionValid;
|
||||||
private isLoginRequiredResponse;
|
private isLoginRequiredResponse;
|
||||||
private retryAfterLogin;
|
private retryAfterLogin;
|
||||||
|
private createDateienLivesearchParams;
|
||||||
|
private getEingangsrechnungEditMeta;
|
||||||
|
private getEingangsrechnungDetailMeta;
|
||||||
|
private formPostConfig;
|
||||||
|
private parseHtmlDocument;
|
||||||
|
private hasTableRows;
|
||||||
|
private inputValue;
|
||||||
|
private selectedNumberValue;
|
||||||
|
private parseNumber;
|
||||||
|
private parseGermanShortDate;
|
||||||
|
private requireDate;
|
||||||
|
private formatGermanShortDate;
|
||||||
|
private lastPathSegment;
|
||||||
|
private fileBasename;
|
||||||
|
private normalizeApiBaseUrl;
|
||||||
|
private isWrappedApiCustomer;
|
||||||
|
private isApiCustomer;
|
||||||
|
private parseGermanShortDateFromText;
|
||||||
|
private logDebug;
|
||||||
private getResponseUrl;
|
private getResponseUrl;
|
||||||
private getHeader;
|
private getHeader;
|
||||||
private isFreischaltungUrl;
|
private isFreischaltungUrl;
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"AgrarmonitorConnector.d.ts","sourceRoot":"","sources":["../src/AgrarmonitorConnector.ts"],"names":[],"mappings":"AAAA,OAAc,EAAE,KAAK,aAAa,EAA+C,MAAM,OAAO,CAAC;AAI/F,OAAO,KAAK,EACV,uBAAuB,EACvB,4BAA4B,EAC5B,2BAA2B,EAC3B,qCAAqC,EACrC,oCAAoC,EACpC,iCAAiC,EACjC,+BAA+B,EAE/B,+BAA+B,EAEhC,MAAM,SAAS,CAAC;AAMjB,qBAAa,qBAAsB,YAAW,2BAA2B;IAa3D,OAAO,CAAC,QAAQ,CAAC,OAAO;IAZ7B,IAAI,EAAG,aAAa,CAAC;IAE5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAU;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAU;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA4B;IAC1D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;IACjC,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,eAAe,CAA8B;gBAExB,OAAO,EAAE,4BAA4B;IAU5D,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAerB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAYtB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAM7B,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,cAAc,CAAC,GAAG,SAAe,GAAG,OAAO,CAAC,MAAM,CAAC;IAInD,mBAAmB,IAAI,OAAO,CAAC,+BAA+B,CAAC;IAqB/D,gBAAgB,IAAI,OAAO,CAAC,+BAA+B,CAAC;IAoB5D,cAAc,CAClB,YAAY,EAAE,qCAAqC,GAClD,OAAO,CAAC,oCAAoC,CAAC;IA6C1C,cAAc,CAAC,OAAO,GAAE,iCAAsC,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAyBzG,OAAO,CAAC,gBAAgB;YAwCV,YAAY;YAmBZ,gBAAgB;YAShB,gBAAgB;YAsBhB,kBAAkB;YA4BlB,cAAc;IAS5B,OAAO,CAAC,uBAAuB;YAejB,eAAe;IAa7B,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,SAAS;CAGlB"}
|
{"version":3,"file":"AgrarmonitorConnector.d.ts","sourceRoot":"","sources":["../src/AgrarmonitorConnector.ts"],"names":[],"mappings":"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"}
|
||||||
Vendored
+275
-8
@@ -11,6 +11,7 @@ const tough_cookie_1 = require("tough-cookie");
|
|||||||
class AgrarmonitorConnector {
|
class AgrarmonitorConnector {
|
||||||
options;
|
options;
|
||||||
http;
|
http;
|
||||||
|
static s3DateienBaseUrl = 'https://s3-eu-central-1.amazonaws.com/dateien.agrarmonitor.de/07';
|
||||||
baseUrl;
|
baseUrl;
|
||||||
apiBaseUrl;
|
apiBaseUrl;
|
||||||
timeoutMs;
|
timeoutMs;
|
||||||
@@ -19,11 +20,12 @@ class AgrarmonitorConnector {
|
|||||||
loginStrategy;
|
loginStrategy;
|
||||||
logger;
|
logger;
|
||||||
cookieJar;
|
cookieJar;
|
||||||
|
apiHttp;
|
||||||
loginInProgress = null;
|
loginInProgress = null;
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.baseUrl = options.baseUrl ?? 'https://admin7.agrarmonitor.de';
|
this.baseUrl = options.baseUrl ?? 'https://admin7.agrarmonitor.de';
|
||||||
this.apiBaseUrl = options.apiBaseUrl ?? 'https://api.agrarmonitor.de';
|
this.apiBaseUrl = this.normalizeApiBaseUrl(options.apiBaseUrl ?? 'https://api.agrarmonitor.de/v1');
|
||||||
this.timeoutMs = options.timeoutMs ?? 15000;
|
this.timeoutMs = options.timeoutMs ?? 15000;
|
||||||
this.autoLogin = options.autoLogin ?? true;
|
this.autoLogin = options.autoLogin ?? true;
|
||||||
this.autoRetry = options.autoRetry ?? true;
|
this.autoRetry = options.autoRetry ?? true;
|
||||||
@@ -33,6 +35,7 @@ class AgrarmonitorConnector {
|
|||||||
async init() {
|
async init() {
|
||||||
this.cookieJar = await this.options.cookieStore.load();
|
this.cookieJar = await this.options.cookieStore.load();
|
||||||
this.http = this.createHttpClient();
|
this.http = this.createHttpClient();
|
||||||
|
this.apiHttp = this.createApiHttpClient();
|
||||||
if (this.autoLogin) {
|
if (this.autoLogin) {
|
||||||
const valid = await this.isSessionValid();
|
const valid = await this.isSessionValid();
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
@@ -54,6 +57,7 @@ class AgrarmonitorConnector {
|
|||||||
this.cookieJar = new tough_cookie_1.CookieJar();
|
this.cookieJar = new tough_cookie_1.CookieJar();
|
||||||
await this.options.cookieStore.clear();
|
await this.options.cookieStore.clear();
|
||||||
this.http = this.createHttpClient();
|
this.http = this.createHttpClient();
|
||||||
|
this.apiHttp = this.createApiHttpClient();
|
||||||
}
|
}
|
||||||
async saveSession() {
|
async saveSession() {
|
||||||
await this.options.cookieStore.save(this.cookieJar);
|
await this.options.cookieStore.save(this.cookieJar);
|
||||||
@@ -129,23 +133,136 @@ class AgrarmonitorConnector {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
async fetchCustomers(options = {}) {
|
async fetchCustomers(options = {}) {
|
||||||
const apiToken = options.apiToken ?? this.options.apiToken;
|
const response = await this.apiRequest('/kunden', {
|
||||||
if (!apiToken) {
|
|
||||||
throw new Error('Agrarmonitor API-Token nicht konfiguriert');
|
|
||||||
}
|
|
||||||
const response = await this.http.get(`${this.apiBaseUrl}/v1/kunden`, {
|
|
||||||
params: {
|
params: {
|
||||||
per_page: options.perPage ?? 99999,
|
per_page: options.perPage ?? 99999,
|
||||||
api_token: apiToken,
|
|
||||||
},
|
},
|
||||||
|
apiToken: options.apiToken,
|
||||||
});
|
});
|
||||||
await this.saveSession();
|
|
||||||
const responseData = response.data;
|
const responseData = response.data;
|
||||||
if (!responseData || !Array.isArray(responseData.data)) {
|
if (!responseData || !Array.isArray(responseData.data)) {
|
||||||
throw new Error('Ungueltige Agrarmonitor API-Antwort');
|
throw new Error('Ungueltige Agrarmonitor API-Antwort');
|
||||||
}
|
}
|
||||||
return responseData.data;
|
return responseData.data;
|
||||||
}
|
}
|
||||||
|
async eingangsrechnungenLivesearch(suchstring) {
|
||||||
|
const response = await this.http.get('/module/dateien/livesearch.php', {
|
||||||
|
params: this.createDateienLivesearchParams(suchstring),
|
||||||
|
});
|
||||||
|
await this.saveSession();
|
||||||
|
const document = this.parseHtmlDocument(response.data);
|
||||||
|
const rows = Array.from(document.querySelectorAll('table#dateien tbody tr'));
|
||||||
|
const results = [];
|
||||||
|
for (const row of rows) {
|
||||||
|
const cells = Array.from(row.querySelectorAll('td'));
|
||||||
|
const typText = cells[3]?.textContent?.trim() ?? '';
|
||||||
|
if (!typText.startsWith('Eingangsrechnungen')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const dokumentId = this.parseNumber(row.getAttribute('data-file_id'));
|
||||||
|
const dataFile = row.getAttribute('data-file') ?? '';
|
||||||
|
const dokumentName = cells[2]?.querySelector('b > a')?.textContent?.trim() ?? '';
|
||||||
|
const dateiName = cells[2]?.querySelector('span')?.textContent?.trim() ?? '';
|
||||||
|
const belegLink = cells[3]?.querySelector('a');
|
||||||
|
const belegTextParts = (belegLink?.textContent ?? '').split(',').map(part => part.trim()).filter(Boolean);
|
||||||
|
const belegNummer = belegTextParts[0] ?? '';
|
||||||
|
const belegDatum = this.parseGermanShortDateFromText(belegTextParts.at(-1) ?? '');
|
||||||
|
const eingangId = this.parseNumber(this.lastPathSegment(belegLink?.getAttribute('href') ?? ''));
|
||||||
|
const { interneBelegNummer, kundenId, betriebId, dokumentTyp } = await this.getEingangsrechnungEditMeta(eingangId);
|
||||||
|
const { eingangsDatum, buchungsDatum } = await this.getEingangsrechnungDetailMeta(eingangId);
|
||||||
|
results.push({
|
||||||
|
dokumentId,
|
||||||
|
vorschauUrl: `${AgrarmonitorConnector.s3DateienBaseUrl}/v_${this.fileBasename(dataFile)}.png`,
|
||||||
|
dokumentUrl: `${AgrarmonitorConnector.s3DateienBaseUrl}/${dataFile}`,
|
||||||
|
dokumentName,
|
||||||
|
dateiName,
|
||||||
|
belegNummer,
|
||||||
|
interneBelegNummer,
|
||||||
|
belegDatum,
|
||||||
|
buchungsDatum,
|
||||||
|
eingangsDatum,
|
||||||
|
eingangId,
|
||||||
|
kundenId,
|
||||||
|
betriebId,
|
||||||
|
dokumentTyp,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
async eingangsrechnungVorhanden(suchstring) {
|
||||||
|
const response = await this.http.get('/module/dateien/livesearch.php', {
|
||||||
|
params: this.createDateienLivesearchParams(suchstring),
|
||||||
|
});
|
||||||
|
await this.saveSession();
|
||||||
|
return this.hasTableRows(response.data, 'table#dateien tbody tr');
|
||||||
|
}
|
||||||
|
async eingangsrechnungImDateieingangVorhanden(suchstring) {
|
||||||
|
const response = await this.http.get('/module/dateien/eingang/livesearch.php', {
|
||||||
|
params: {
|
||||||
|
suchstring,
|
||||||
|
seite: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await this.saveSession();
|
||||||
|
return this.hasTableRows(response.data, 'table#dateien_eingang tbody tr');
|
||||||
|
}
|
||||||
|
async getRechnungsdaten(rechnungId) {
|
||||||
|
const response = await this.http.get('/module/eingangsrechnungen/api/eingangsrechnungen.php', {
|
||||||
|
params: {
|
||||||
|
id: 'edit',
|
||||||
|
rechnungId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await this.saveSession();
|
||||||
|
const document = this.parseHtmlDocument(response.data);
|
||||||
|
return {
|
||||||
|
lieferschein: this.inputValue(document, 'lieferscheinnummer'),
|
||||||
|
rechnung: this.inputValue(document, 'rechnungsnummer'),
|
||||||
|
datum: this.requireDate(this.parseGermanShortDate(this.inputValue(document, 'rechnungsdatum')), 'rechnungsdatum'),
|
||||||
|
kundenId: this.selectedNumberValue(document, 'rgempf'),
|
||||||
|
adresstext: this.inputValue(document, 'addressName'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async setRechnungsdaten(rechnungId, daten) {
|
||||||
|
const response = await this.http.post(`/module/eingangsrechnungen/api/eingangsrechnungen.php?id=update&rechnungId=${encodeURIComponent(rechnungId)}`, new URLSearchParams({
|
||||||
|
lieferscheinnummer: daten.lieferschein,
|
||||||
|
rechnungsnummer: daten.rechnung,
|
||||||
|
rechnungsdatum: this.formatGermanShortDate(daten.datum),
|
||||||
|
rgempf: String(daten.kundenId),
|
||||||
|
adresstext: daten.adresstext,
|
||||||
|
}), this.formPostConfig(`/eingangsrechnungen/detail/${rechnungId}`));
|
||||||
|
await this.saveSession();
|
||||||
|
return response.status >= 200 && response.status < 300;
|
||||||
|
}
|
||||||
|
async setLieferscheinNummer(rechnungId, nummer) {
|
||||||
|
const rechnungsdaten = await this.getRechnungsdaten(rechnungId);
|
||||||
|
const success = await this.setRechnungsdaten(rechnungId, {
|
||||||
|
...rechnungsdaten,
|
||||||
|
lieferschein: nummer,
|
||||||
|
});
|
||||||
|
if (!success) {
|
||||||
|
throw new Error('Lieferscheinnummer konnte nicht gespeichert werden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async setEingangsdatum(rechnungId, datum) {
|
||||||
|
const response = await this.http.post('/module/eingangsrechnungen/api/updateReceived.php', new URLSearchParams({
|
||||||
|
datum: this.formatGermanShortDate(datum),
|
||||||
|
receiptID: String(rechnungId),
|
||||||
|
}), this.formPostConfig(`/eingangsrechnungen/detail/${rechnungId}`));
|
||||||
|
await this.saveSession();
|
||||||
|
return response.status >= 200 && response.status < 300;
|
||||||
|
}
|
||||||
|
async getCustomerById(id) {
|
||||||
|
const response = await this.apiRequest(`/kunden/${id}`);
|
||||||
|
this.logDebug('Agrarmonitor customer API raw response', response.data);
|
||||||
|
if (this.isWrappedApiCustomer(response.data)) {
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
if (this.isApiCustomer(response.data)) {
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
throw new Error('Ungueltige Agrarmonitor Kunden-API-Antwort');
|
||||||
|
}
|
||||||
createHttpClient() {
|
createHttpClient() {
|
||||||
const client = (0, axios_cookiejar_support_1.wrapper)(axios_1.default.create({
|
const client = (0, axios_cookiejar_support_1.wrapper)(axios_1.default.create({
|
||||||
baseURL: this.baseUrl,
|
baseURL: this.baseUrl,
|
||||||
@@ -173,6 +290,26 @@ class AgrarmonitorConnector {
|
|||||||
});
|
});
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
createApiHttpClient(apiToken = this.options.apiToken) {
|
||||||
|
return axios_1.default.create({
|
||||||
|
baseURL: this.apiBaseUrl,
|
||||||
|
timeout: this.timeoutMs,
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
...(apiToken ? { Authorization: `Bearer ${apiToken}` } : {}),
|
||||||
|
},
|
||||||
|
validateStatus: status => status >= 200 && status < 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async apiRequest(url, config = {}) {
|
||||||
|
const apiToken = config.apiToken ?? this.options.apiToken;
|
||||||
|
if (!apiToken) {
|
||||||
|
throw new Error('Agrarmonitor API-Token nicht konfiguriert');
|
||||||
|
}
|
||||||
|
const { apiToken: _apiToken, ...axiosConfig } = config;
|
||||||
|
const client = apiToken === this.options.apiToken ? this.apiHttp : this.createApiHttpClient(apiToken);
|
||||||
|
return client.get(url, axiosConfig);
|
||||||
|
}
|
||||||
async performLogin() {
|
async performLogin() {
|
||||||
if (!this.options.username || !this.options.password) {
|
if (!this.options.username || !this.options.password) {
|
||||||
throw new Error('Agrarmonitor-Credentials nicht konfiguriert');
|
throw new Error('Agrarmonitor-Credentials nicht konfiguriert');
|
||||||
@@ -267,6 +404,136 @@ class AgrarmonitorConnector {
|
|||||||
await this.login();
|
await this.login();
|
||||||
return this.http.request(config);
|
return this.http.request(config);
|
||||||
}
|
}
|
||||||
|
createDateienLivesearchParams(suchstring) {
|
||||||
|
return {
|
||||||
|
suchstring,
|
||||||
|
stammdatum_typ: -1,
|
||||||
|
mobil: -1,
|
||||||
|
sensibel: -1,
|
||||||
|
firma: 0,
|
||||||
|
itemsperpage: 100000,
|
||||||
|
seite: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async getEingangsrechnungEditMeta(rechnungId) {
|
||||||
|
const response = await this.http.get('/module/eingangsrechnungen/api/eingangsrechnungen.php', {
|
||||||
|
params: {
|
||||||
|
id: 'edit',
|
||||||
|
rechnungId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await this.saveSession();
|
||||||
|
const document = this.parseHtmlDocument(response.data);
|
||||||
|
return {
|
||||||
|
interneBelegNummer: this.inputValue(document, 'lieferscheinnummer'),
|
||||||
|
kundenId: this.selectedNumberValue(document, 'rgempf'),
|
||||||
|
betriebId: this.selectedNumberValue(document, 'firma_id'),
|
||||||
|
dokumentTyp: this.selectedNumberValue(document, 'typ'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async getEingangsrechnungDetailMeta(rechnungId) {
|
||||||
|
const response = await this.http.get(`/eingangsrechnungen/detail/${rechnungId}`);
|
||||||
|
await this.saveSession();
|
||||||
|
const document = this.parseHtmlDocument(response.data);
|
||||||
|
const receivedStatus = document.querySelector('#receivedStatus');
|
||||||
|
const receivedText = receivedStatus?.textContent?.trim() ?? '';
|
||||||
|
const parentParts = (receivedStatus?.parentElement?.textContent ?? '').split('-');
|
||||||
|
const bookingText = parentParts.at(-1)?.trim() ?? '';
|
||||||
|
return {
|
||||||
|
eingangsDatum: !receivedText || receivedText === 'Nicht empfangen'
|
||||||
|
? null
|
||||||
|
: this.parseGermanShortDateFromText(receivedText.slice(13).trim()),
|
||||||
|
buchungsDatum: !bookingText || bookingText === 'Nicht gebucht'
|
||||||
|
? null
|
||||||
|
: this.parseGermanShortDateFromText(bookingText.slice(11).trim()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
formPostConfig(refererPath) {
|
||||||
|
return {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
Referer: `${this.baseUrl}${refererPath}`,
|
||||||
|
Origin: this.baseUrl,
|
||||||
|
},
|
||||||
|
validateStatus: status => status >= 200 && status < 400,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
parseHtmlDocument(data) {
|
||||||
|
return new jsdom_1.JSDOM(typeof data === 'string' ? data : String(data ?? '')).window.document;
|
||||||
|
}
|
||||||
|
hasTableRows(data, selector) {
|
||||||
|
return this.parseHtmlDocument(data).querySelectorAll(selector).length > 0;
|
||||||
|
}
|
||||||
|
inputValue(document, name) {
|
||||||
|
return document.querySelector(`input[name="${name}"]`)?.value.trim() ?? '';
|
||||||
|
}
|
||||||
|
selectedNumberValue(document, name) {
|
||||||
|
return this.parseNumber(document.querySelector(`select[name="${name}"] option:checked`)?.value);
|
||||||
|
}
|
||||||
|
parseNumber(value) {
|
||||||
|
const numberValue = Number(String(value ?? '').trim());
|
||||||
|
return Number.isFinite(numberValue) ? numberValue : 0;
|
||||||
|
}
|
||||||
|
parseGermanShortDate(value) {
|
||||||
|
const match = value.trim().match(/^(\d{2})\.(\d{2})\.(\d{2})$/);
|
||||||
|
if (!match) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const [, day, month, year] = match;
|
||||||
|
const parsed = new Date(Number(`20${year}`), Number(month) - 1, Number(day));
|
||||||
|
if (parsed.getFullYear() !== Number(`20${year}`) ||
|
||||||
|
parsed.getMonth() !== Number(month) - 1 ||
|
||||||
|
parsed.getDate() !== Number(day)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
requireDate(value, fieldName) {
|
||||||
|
if (!value) {
|
||||||
|
throw new Error(`Ungueltiges Datumsformat fuer ${fieldName}`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
formatGermanShortDate(date) {
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const year = String(date.getFullYear()).slice(-2);
|
||||||
|
return `${day}.${month}.${year}`;
|
||||||
|
}
|
||||||
|
lastPathSegment(value) {
|
||||||
|
return value.split('?')[0]?.split('/').filter(Boolean).at(-1) ?? '';
|
||||||
|
}
|
||||||
|
fileBasename(fileName) {
|
||||||
|
const lastDotIndex = fileName.lastIndexOf('.');
|
||||||
|
return lastDotIndex === -1 ? fileName : fileName.slice(0, lastDotIndex);
|
||||||
|
}
|
||||||
|
normalizeApiBaseUrl(value) {
|
||||||
|
const withoutTrailingSlash = value.replace(/\/+$/, '');
|
||||||
|
return withoutTrailingSlash.endsWith('/v1') ? withoutTrailingSlash : `${withoutTrailingSlash}/v1`;
|
||||||
|
}
|
||||||
|
isWrappedApiCustomer(value) {
|
||||||
|
return (typeof value === 'object' &&
|
||||||
|
value !== null &&
|
||||||
|
'data' in value &&
|
||||||
|
this.isApiCustomer(value.data));
|
||||||
|
}
|
||||||
|
isApiCustomer(value) {
|
||||||
|
return (typeof value === 'object' &&
|
||||||
|
value !== null &&
|
||||||
|
'id' in value &&
|
||||||
|
(typeof value.id === 'string' || typeof value.id === 'number'));
|
||||||
|
}
|
||||||
|
parseGermanShortDateFromText(value) {
|
||||||
|
const match = value.match(/(\d{2}\.\d{2}\.\d{2})/);
|
||||||
|
return match ? this.parseGermanShortDate(match[1]) : null;
|
||||||
|
}
|
||||||
|
logDebug(message, meta) {
|
||||||
|
if (this.logger?.debug) {
|
||||||
|
this.logger.debug(message, meta);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.logger?.info?.(message, meta);
|
||||||
|
}
|
||||||
getResponseUrl(response) {
|
getResponseUrl(response) {
|
||||||
const request = response.request;
|
const request = response.request;
|
||||||
return request?.res?.responseUrl ?? '';
|
return request?.res?.responseUrl ?? '';
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+31
@@ -39,6 +39,14 @@ export interface AgrarmonitorConnectorResult {
|
|||||||
checkRegistriert(): Promise<AgrarmonitorRegistrierungStatus>;
|
checkRegistriert(): Promise<AgrarmonitorRegistrierungStatus>;
|
||||||
registerDevice(options: AgrarmonitorDeviceRegistrationOptions): Promise<AgrarmonitorDeviceRegistrationResult>;
|
registerDevice(options: AgrarmonitorDeviceRegistrationOptions): Promise<AgrarmonitorDeviceRegistrationResult>;
|
||||||
fetchCustomers(options?: AgrarmonitorFetchCustomersOptions): Promise<AgrarmonitorApiCustomer[]>;
|
fetchCustomers(options?: AgrarmonitorFetchCustomersOptions): Promise<AgrarmonitorApiCustomer[]>;
|
||||||
|
eingangsrechnungenLivesearch(suchstring: string): Promise<EingangsrechnungLivesearchResult[]>;
|
||||||
|
eingangsrechnungVorhanden(suchstring: string): Promise<boolean>;
|
||||||
|
eingangsrechnungImDateieingangVorhanden(suchstring: string): Promise<boolean>;
|
||||||
|
getRechnungsdaten(rechnungId: number): Promise<Rechnungsdaten>;
|
||||||
|
setRechnungsdaten(rechnungId: number, daten: Rechnungsdaten): Promise<boolean>;
|
||||||
|
setLieferscheinNummer(rechnungId: number, nummer: string): Promise<void>;
|
||||||
|
setEingangsdatum(rechnungId: number, datum: Date): Promise<boolean>;
|
||||||
|
getCustomerById(id: number): Promise<AgrarmonitorApiCustomer>;
|
||||||
}
|
}
|
||||||
export interface AgrarmonitorFreischaltungStatus {
|
export interface AgrarmonitorFreischaltungStatus {
|
||||||
freigeschaltet: boolean;
|
freigeschaltet: boolean;
|
||||||
@@ -84,4 +92,27 @@ export interface AgrarmonitorApiCustomer {
|
|||||||
bearbeitet_am?: string | number;
|
bearbeitet_am?: string | number;
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
export interface EingangsrechnungLivesearchResult {
|
||||||
|
dokumentId: number;
|
||||||
|
vorschauUrl: string;
|
||||||
|
dokumentUrl: string;
|
||||||
|
dokumentName: string;
|
||||||
|
dateiName: string;
|
||||||
|
belegNummer: string;
|
||||||
|
interneBelegNummer: string;
|
||||||
|
belegDatum: Date | null;
|
||||||
|
buchungsDatum: Date | null;
|
||||||
|
eingangsDatum: Date | null;
|
||||||
|
eingangId: number;
|
||||||
|
kundenId: number;
|
||||||
|
betriebId: number;
|
||||||
|
dokumentTyp: number;
|
||||||
|
}
|
||||||
|
export interface Rechnungsdaten {
|
||||||
|
lieferschein: string;
|
||||||
|
rechnung: string;
|
||||||
|
datum: Date;
|
||||||
|
kundenId: number;
|
||||||
|
adresstext: string;
|
||||||
|
}
|
||||||
//# sourceMappingURL=types.d.ts.map
|
//# sourceMappingURL=types.d.ts.map
|
||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
||||||
+374
-12
@@ -12,7 +12,9 @@ import type {
|
|||||||
AgrarmonitorFreischaltungStatus,
|
AgrarmonitorFreischaltungStatus,
|
||||||
AgrarmonitorLoginStrategy,
|
AgrarmonitorLoginStrategy,
|
||||||
AgrarmonitorRegistrierungStatus,
|
AgrarmonitorRegistrierungStatus,
|
||||||
|
EingangsrechnungLivesearchResult,
|
||||||
Logger,
|
Logger,
|
||||||
|
Rechnungsdaten,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
type RetryableAxiosRequestConfig = AxiosRequestConfig & {
|
type RetryableAxiosRequestConfig = AxiosRequestConfig & {
|
||||||
@@ -22,6 +24,8 @@ type RetryableAxiosRequestConfig = AxiosRequestConfig & {
|
|||||||
export class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
|
export class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
|
||||||
public http!: AxiosInstance;
|
public http!: AxiosInstance;
|
||||||
|
|
||||||
|
private static readonly s3DateienBaseUrl = 'https://s3-eu-central-1.amazonaws.com/dateien.agrarmonitor.de/07';
|
||||||
|
|
||||||
private readonly baseUrl: string;
|
private readonly baseUrl: string;
|
||||||
private readonly apiBaseUrl: string;
|
private readonly apiBaseUrl: string;
|
||||||
private readonly timeoutMs: number;
|
private readonly timeoutMs: number;
|
||||||
@@ -30,11 +34,12 @@ export class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
|
|||||||
private readonly loginStrategy: AgrarmonitorLoginStrategy;
|
private readonly loginStrategy: AgrarmonitorLoginStrategy;
|
||||||
private readonly logger?: Logger;
|
private readonly logger?: Logger;
|
||||||
private cookieJar!: CookieJar;
|
private cookieJar!: CookieJar;
|
||||||
|
private apiHttp!: AxiosInstance;
|
||||||
private loginInProgress: Promise<void> | null = null;
|
private loginInProgress: Promise<void> | null = null;
|
||||||
|
|
||||||
constructor(private readonly options: AgrarmonitorConnectorOptions) {
|
constructor(private readonly options: AgrarmonitorConnectorOptions) {
|
||||||
this.baseUrl = options.baseUrl ?? 'https://admin7.agrarmonitor.de';
|
this.baseUrl = options.baseUrl ?? 'https://admin7.agrarmonitor.de';
|
||||||
this.apiBaseUrl = options.apiBaseUrl ?? 'https://api.agrarmonitor.de';
|
this.apiBaseUrl = this.normalizeApiBaseUrl(options.apiBaseUrl ?? 'https://api.agrarmonitor.de/v1');
|
||||||
this.timeoutMs = options.timeoutMs ?? 15000;
|
this.timeoutMs = options.timeoutMs ?? 15000;
|
||||||
this.autoLogin = options.autoLogin ?? true;
|
this.autoLogin = options.autoLogin ?? true;
|
||||||
this.autoRetry = options.autoRetry ?? true;
|
this.autoRetry = options.autoRetry ?? true;
|
||||||
@@ -45,6 +50,7 @@ export class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
|
|||||||
async init(): Promise<this> {
|
async init(): Promise<this> {
|
||||||
this.cookieJar = await this.options.cookieStore.load();
|
this.cookieJar = await this.options.cookieStore.load();
|
||||||
this.http = this.createHttpClient();
|
this.http = this.createHttpClient();
|
||||||
|
this.apiHttp = this.createApiHttpClient();
|
||||||
|
|
||||||
if (this.autoLogin) {
|
if (this.autoLogin) {
|
||||||
const valid = await this.isSessionValid();
|
const valid = await this.isSessionValid();
|
||||||
@@ -73,6 +79,7 @@ export class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
|
|||||||
this.cookieJar = new CookieJar();
|
this.cookieJar = new CookieJar();
|
||||||
await this.options.cookieStore.clear();
|
await this.options.cookieStore.clear();
|
||||||
this.http = this.createHttpClient();
|
this.http = this.createHttpClient();
|
||||||
|
this.apiHttp = this.createApiHttpClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveSession(): Promise<void> {
|
async saveSession(): Promise<void> {
|
||||||
@@ -172,22 +179,14 @@ export class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fetchCustomers(options: AgrarmonitorFetchCustomersOptions = {}): Promise<AgrarmonitorApiCustomer[]> {
|
async fetchCustomers(options: AgrarmonitorFetchCustomersOptions = {}): Promise<AgrarmonitorApiCustomer[]> {
|
||||||
const apiToken = options.apiToken ?? this.options.apiToken;
|
const response = await this.apiRequest<{ data?: unknown }>('/kunden', {
|
||||||
|
|
||||||
if (!apiToken) {
|
|
||||||
throw new Error('Agrarmonitor API-Token nicht konfiguriert');
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await this.http.get(`${this.apiBaseUrl}/v1/kunden`, {
|
|
||||||
params: {
|
params: {
|
||||||
per_page: options.perPage ?? 99999,
|
per_page: options.perPage ?? 99999,
|
||||||
api_token: apiToken,
|
|
||||||
},
|
},
|
||||||
|
apiToken: options.apiToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.saveSession();
|
const responseData = response.data;
|
||||||
|
|
||||||
const responseData = response.data as { data?: unknown };
|
|
||||||
|
|
||||||
if (!responseData || !Array.isArray(responseData.data)) {
|
if (!responseData || !Array.isArray(responseData.data)) {
|
||||||
throw new Error('Ungueltige Agrarmonitor API-Antwort');
|
throw new Error('Ungueltige Agrarmonitor API-Antwort');
|
||||||
@@ -196,6 +195,163 @@ export class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
|
|||||||
return responseData.data as AgrarmonitorApiCustomer[];
|
return responseData.data as AgrarmonitorApiCustomer[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async eingangsrechnungenLivesearch(suchstring: string): Promise<EingangsrechnungLivesearchResult[]> {
|
||||||
|
const response = await this.http.get('/module/dateien/livesearch.php', {
|
||||||
|
params: this.createDateienLivesearchParams(suchstring),
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.saveSession();
|
||||||
|
|
||||||
|
const document = this.parseHtmlDocument(response.data);
|
||||||
|
const rows = Array.from(document.querySelectorAll<HTMLTableRowElement>('table#dateien tbody tr'));
|
||||||
|
const results: EingangsrechnungLivesearchResult[] = [];
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
const cells = Array.from(row.querySelectorAll<HTMLTableCellElement>('td'));
|
||||||
|
const typText = cells[3]?.textContent?.trim() ?? '';
|
||||||
|
|
||||||
|
if (!typText.startsWith('Eingangsrechnungen')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dokumentId = this.parseNumber(row.getAttribute('data-file_id'));
|
||||||
|
const dataFile = row.getAttribute('data-file') ?? '';
|
||||||
|
const dokumentName = cells[2]?.querySelector('b > a')?.textContent?.trim() ?? '';
|
||||||
|
const dateiName = cells[2]?.querySelector('span')?.textContent?.trim() ?? '';
|
||||||
|
const belegLink = cells[3]?.querySelector<HTMLAnchorElement>('a');
|
||||||
|
const belegTextParts = (belegLink?.textContent ?? '').split(',').map(part => part.trim()).filter(Boolean);
|
||||||
|
const belegNummer = belegTextParts[0] ?? '';
|
||||||
|
const belegDatum = this.parseGermanShortDateFromText(belegTextParts.at(-1) ?? '');
|
||||||
|
const eingangId = this.parseNumber(this.lastPathSegment(belegLink?.getAttribute('href') ?? ''));
|
||||||
|
const { interneBelegNummer, kundenId, betriebId, dokumentTyp } =
|
||||||
|
await this.getEingangsrechnungEditMeta(eingangId);
|
||||||
|
const { eingangsDatum, buchungsDatum } = await this.getEingangsrechnungDetailMeta(eingangId);
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
dokumentId,
|
||||||
|
vorschauUrl: `${AgrarmonitorConnector.s3DateienBaseUrl}/v_${this.fileBasename(dataFile)}.png`,
|
||||||
|
dokumentUrl: `${AgrarmonitorConnector.s3DateienBaseUrl}/${dataFile}`,
|
||||||
|
dokumentName,
|
||||||
|
dateiName,
|
||||||
|
belegNummer,
|
||||||
|
interneBelegNummer,
|
||||||
|
belegDatum,
|
||||||
|
buchungsDatum,
|
||||||
|
eingangsDatum,
|
||||||
|
eingangId,
|
||||||
|
kundenId,
|
||||||
|
betriebId,
|
||||||
|
dokumentTyp,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
async eingangsrechnungVorhanden(suchstring: string): Promise<boolean> {
|
||||||
|
const response = await this.http.get('/module/dateien/livesearch.php', {
|
||||||
|
params: this.createDateienLivesearchParams(suchstring),
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.saveSession();
|
||||||
|
|
||||||
|
return this.hasTableRows(response.data, 'table#dateien tbody tr');
|
||||||
|
}
|
||||||
|
|
||||||
|
async eingangsrechnungImDateieingangVorhanden(suchstring: string): Promise<boolean> {
|
||||||
|
const response = await this.http.get('/module/dateien/eingang/livesearch.php', {
|
||||||
|
params: {
|
||||||
|
suchstring,
|
||||||
|
seite: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.saveSession();
|
||||||
|
|
||||||
|
return this.hasTableRows(response.data, 'table#dateien_eingang tbody tr');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRechnungsdaten(rechnungId: number): Promise<Rechnungsdaten> {
|
||||||
|
const response = await this.http.get('/module/eingangsrechnungen/api/eingangsrechnungen.php', {
|
||||||
|
params: {
|
||||||
|
id: 'edit',
|
||||||
|
rechnungId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.saveSession();
|
||||||
|
|
||||||
|
const document = this.parseHtmlDocument(response.data);
|
||||||
|
|
||||||
|
return {
|
||||||
|
lieferschein: this.inputValue(document, 'lieferscheinnummer'),
|
||||||
|
rechnung: this.inputValue(document, 'rechnungsnummer'),
|
||||||
|
datum: this.requireDate(this.parseGermanShortDate(this.inputValue(document, 'rechnungsdatum')), 'rechnungsdatum'),
|
||||||
|
kundenId: this.selectedNumberValue(document, 'rgempf'),
|
||||||
|
adresstext: this.inputValue(document, 'addressName'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async setRechnungsdaten(rechnungId: number, daten: Rechnungsdaten): Promise<boolean> {
|
||||||
|
const response = await this.http.post(
|
||||||
|
`/module/eingangsrechnungen/api/eingangsrechnungen.php?id=update&rechnungId=${encodeURIComponent(rechnungId)}`,
|
||||||
|
new URLSearchParams({
|
||||||
|
lieferscheinnummer: daten.lieferschein,
|
||||||
|
rechnungsnummer: daten.rechnung,
|
||||||
|
rechnungsdatum: this.formatGermanShortDate(daten.datum),
|
||||||
|
rgempf: String(daten.kundenId),
|
||||||
|
adresstext: daten.adresstext,
|
||||||
|
}),
|
||||||
|
this.formPostConfig(`/eingangsrechnungen/detail/${rechnungId}`)
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.saveSession();
|
||||||
|
|
||||||
|
return response.status >= 200 && response.status < 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setLieferscheinNummer(rechnungId: number, nummer: string): Promise<void> {
|
||||||
|
const rechnungsdaten = await this.getRechnungsdaten(rechnungId);
|
||||||
|
const success = await this.setRechnungsdaten(rechnungId, {
|
||||||
|
...rechnungsdaten,
|
||||||
|
lieferschein: nummer,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
throw new Error('Lieferscheinnummer konnte nicht gespeichert werden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async setEingangsdatum(rechnungId: number, datum: Date): Promise<boolean> {
|
||||||
|
const response = await this.http.post(
|
||||||
|
'/module/eingangsrechnungen/api/updateReceived.php',
|
||||||
|
new URLSearchParams({
|
||||||
|
datum: this.formatGermanShortDate(datum),
|
||||||
|
receiptID: String(rechnungId),
|
||||||
|
}),
|
||||||
|
this.formPostConfig(`/eingangsrechnungen/detail/${rechnungId}`)
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.saveSession();
|
||||||
|
|
||||||
|
return response.status >= 200 && response.status < 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCustomerById(id: number): Promise<AgrarmonitorApiCustomer> {
|
||||||
|
const response = await this.apiRequest<unknown>(`/kunden/${id}`);
|
||||||
|
this.logDebug('Agrarmonitor customer API raw response', response.data);
|
||||||
|
|
||||||
|
if (this.isWrappedApiCustomer(response.data)) {
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isApiCustomer(response.data)) {
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Ungueltige Agrarmonitor Kunden-API-Antwort');
|
||||||
|
}
|
||||||
|
|
||||||
private createHttpClient(): AxiosInstance {
|
private createHttpClient(): AxiosInstance {
|
||||||
const client = wrapper(
|
const client = wrapper(
|
||||||
axios.create({
|
axios.create({
|
||||||
@@ -236,6 +392,33 @@ export class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createApiHttpClient(apiToken = this.options.apiToken): AxiosInstance {
|
||||||
|
return axios.create({
|
||||||
|
baseURL: this.apiBaseUrl,
|
||||||
|
timeout: this.timeoutMs,
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
...(apiToken ? { Authorization: `Bearer ${apiToken}` } : {}),
|
||||||
|
},
|
||||||
|
validateStatus: status => status >= 200 && status < 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async apiRequest<TData>(
|
||||||
|
url: string,
|
||||||
|
config: AxiosRequestConfig & { apiToken?: string } = {}
|
||||||
|
): Promise<AxiosResponse<TData>> {
|
||||||
|
const apiToken = config.apiToken ?? this.options.apiToken;
|
||||||
|
|
||||||
|
if (!apiToken) {
|
||||||
|
throw new Error('Agrarmonitor API-Token nicht konfiguriert');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { apiToken: _apiToken, ...axiosConfig } = config;
|
||||||
|
const client = apiToken === this.options.apiToken ? this.apiHttp : this.createApiHttpClient(apiToken);
|
||||||
|
return client.get<TData>(url, axiosConfig);
|
||||||
|
}
|
||||||
|
|
||||||
private async performLogin(): Promise<void> {
|
private async performLogin(): Promise<void> {
|
||||||
if (!this.options.username || !this.options.password) {
|
if (!this.options.username || !this.options.password) {
|
||||||
throw new Error('Agrarmonitor-Credentials nicht konfiguriert');
|
throw new Error('Agrarmonitor-Credentials nicht konfiguriert');
|
||||||
@@ -351,6 +534,185 @@ export class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
|
|||||||
return this.http.request(config);
|
return this.http.request(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createDateienLivesearchParams(suchstring: string): Record<string, string | number> {
|
||||||
|
return {
|
||||||
|
suchstring,
|
||||||
|
stammdatum_typ: -1,
|
||||||
|
mobil: -1,
|
||||||
|
sensibel: -1,
|
||||||
|
firma: 0,
|
||||||
|
itemsperpage: 100000,
|
||||||
|
seite: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getEingangsrechnungEditMeta(rechnungId: number): Promise<{
|
||||||
|
interneBelegNummer: string;
|
||||||
|
kundenId: number;
|
||||||
|
betriebId: number;
|
||||||
|
dokumentTyp: number;
|
||||||
|
}> {
|
||||||
|
const response = await this.http.get('/module/eingangsrechnungen/api/eingangsrechnungen.php', {
|
||||||
|
params: {
|
||||||
|
id: 'edit',
|
||||||
|
rechnungId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.saveSession();
|
||||||
|
|
||||||
|
const document = this.parseHtmlDocument(response.data);
|
||||||
|
|
||||||
|
return {
|
||||||
|
interneBelegNummer: this.inputValue(document, 'lieferscheinnummer'),
|
||||||
|
kundenId: this.selectedNumberValue(document, 'rgempf'),
|
||||||
|
betriebId: this.selectedNumberValue(document, 'firma_id'),
|
||||||
|
dokumentTyp: this.selectedNumberValue(document, 'typ'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getEingangsrechnungDetailMeta(rechnungId: number): Promise<{
|
||||||
|
eingangsDatum: Date | null;
|
||||||
|
buchungsDatum: Date | null;
|
||||||
|
}> {
|
||||||
|
const response = await this.http.get(`/eingangsrechnungen/detail/${rechnungId}`);
|
||||||
|
|
||||||
|
await this.saveSession();
|
||||||
|
|
||||||
|
const document = this.parseHtmlDocument(response.data);
|
||||||
|
const receivedStatus = document.querySelector<HTMLElement>('#receivedStatus');
|
||||||
|
const receivedText = receivedStatus?.textContent?.trim() ?? '';
|
||||||
|
const parentParts = (receivedStatus?.parentElement?.textContent ?? '').split('-');
|
||||||
|
const bookingText = parentParts.at(-1)?.trim() ?? '';
|
||||||
|
|
||||||
|
return {
|
||||||
|
eingangsDatum:
|
||||||
|
!receivedText || receivedText === 'Nicht empfangen'
|
||||||
|
? null
|
||||||
|
: this.parseGermanShortDateFromText(receivedText.slice(13).trim()),
|
||||||
|
buchungsDatum:
|
||||||
|
!bookingText || bookingText === 'Nicht gebucht'
|
||||||
|
? null
|
||||||
|
: this.parseGermanShortDateFromText(bookingText.slice(11).trim()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private formPostConfig(refererPath: string): AxiosRequestConfig {
|
||||||
|
return {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
Referer: `${this.baseUrl}${refererPath}`,
|
||||||
|
Origin: this.baseUrl,
|
||||||
|
},
|
||||||
|
validateStatus: status => status >= 200 && status < 400,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseHtmlDocument(data: unknown): Document {
|
||||||
|
return new JSDOM(typeof data === 'string' ? data : String(data ?? '')).window.document;
|
||||||
|
}
|
||||||
|
|
||||||
|
private hasTableRows(data: unknown, selector: string): boolean {
|
||||||
|
return this.parseHtmlDocument(data).querySelectorAll(selector).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private inputValue(document: Document, name: string): string {
|
||||||
|
return document.querySelector<HTMLInputElement>(`input[name="${name}"]`)?.value.trim() ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private selectedNumberValue(document: Document, name: string): number {
|
||||||
|
return this.parseNumber(
|
||||||
|
document.querySelector<HTMLOptionElement>(`select[name="${name}"] option:checked`)?.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseNumber(value: unknown): number {
|
||||||
|
const numberValue = Number(String(value ?? '').trim());
|
||||||
|
return Number.isFinite(numberValue) ? numberValue : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseGermanShortDate(value: string): Date | null {
|
||||||
|
const match = value.trim().match(/^(\d{2})\.(\d{2})\.(\d{2})$/);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, day, month, year] = match;
|
||||||
|
const parsed = new Date(Number(`20${year}`), Number(month) - 1, Number(day));
|
||||||
|
|
||||||
|
if (
|
||||||
|
parsed.getFullYear() !== Number(`20${year}`) ||
|
||||||
|
parsed.getMonth() !== Number(month) - 1 ||
|
||||||
|
parsed.getDate() !== Number(day)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private requireDate(value: Date | null, fieldName: string): Date {
|
||||||
|
if (!value) {
|
||||||
|
throw new Error(`Ungueltiges Datumsformat fuer ${fieldName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatGermanShortDate(date: Date): string {
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const year = String(date.getFullYear()).slice(-2);
|
||||||
|
return `${day}.${month}.${year}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private lastPathSegment(value: string): string {
|
||||||
|
return value.split('?')[0]?.split('/').filter(Boolean).at(-1) ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private fileBasename(fileName: string): string {
|
||||||
|
const lastDotIndex = fileName.lastIndexOf('.');
|
||||||
|
return lastDotIndex === -1 ? fileName : fileName.slice(0, lastDotIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeApiBaseUrl(value: string): string {
|
||||||
|
const withoutTrailingSlash = value.replace(/\/+$/, '');
|
||||||
|
return withoutTrailingSlash.endsWith('/v1') ? withoutTrailingSlash : `${withoutTrailingSlash}/v1`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isWrappedApiCustomer(value: unknown): value is { data: AgrarmonitorApiCustomer } {
|
||||||
|
return (
|
||||||
|
typeof value === 'object' &&
|
||||||
|
value !== null &&
|
||||||
|
'data' in value &&
|
||||||
|
this.isApiCustomer((value as { data?: unknown }).data)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private isApiCustomer(value: unknown): value is AgrarmonitorApiCustomer {
|
||||||
|
return (
|
||||||
|
typeof value === 'object' &&
|
||||||
|
value !== null &&
|
||||||
|
'id' in value &&
|
||||||
|
(typeof (value as { id?: unknown }).id === 'string' || typeof (value as { id?: unknown }).id === 'number')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseGermanShortDateFromText(value: string): Date | null {
|
||||||
|
const match = value.match(/(\d{2}\.\d{2}\.\d{2})/);
|
||||||
|
return match ? this.parseGermanShortDate(match[1]) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private logDebug(message: string, meta?: unknown): void {
|
||||||
|
if (this.logger?.debug) {
|
||||||
|
this.logger.debug(message, meta);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger?.info?.(message, meta);
|
||||||
|
}
|
||||||
|
|
||||||
private getResponseUrl(response: AxiosResponse): string {
|
private getResponseUrl(response: AxiosResponse): string {
|
||||||
const request = response.request as { res?: { responseUrl?: string } } | undefined;
|
const request = response.request as { res?: { responseUrl?: string } } | undefined;
|
||||||
return request?.res?.responseUrl ?? '';
|
return request?.res?.responseUrl ?? '';
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ export interface AgrarmonitorConnectorResult {
|
|||||||
checkRegistriert(): Promise<AgrarmonitorRegistrierungStatus>;
|
checkRegistriert(): Promise<AgrarmonitorRegistrierungStatus>;
|
||||||
registerDevice(options: AgrarmonitorDeviceRegistrationOptions): Promise<AgrarmonitorDeviceRegistrationResult>;
|
registerDevice(options: AgrarmonitorDeviceRegistrationOptions): Promise<AgrarmonitorDeviceRegistrationResult>;
|
||||||
fetchCustomers(options?: AgrarmonitorFetchCustomersOptions): Promise<AgrarmonitorApiCustomer[]>;
|
fetchCustomers(options?: AgrarmonitorFetchCustomersOptions): Promise<AgrarmonitorApiCustomer[]>;
|
||||||
|
eingangsrechnungenLivesearch(suchstring: string): Promise<EingangsrechnungLivesearchResult[]>;
|
||||||
|
eingangsrechnungVorhanden(suchstring: string): Promise<boolean>;
|
||||||
|
eingangsrechnungImDateieingangVorhanden(suchstring: string): Promise<boolean>;
|
||||||
|
getRechnungsdaten(rechnungId: number): Promise<Rechnungsdaten>;
|
||||||
|
setRechnungsdaten(rechnungId: number, daten: Rechnungsdaten): Promise<boolean>;
|
||||||
|
setLieferscheinNummer(rechnungId: number, nummer: string): Promise<void>;
|
||||||
|
setEingangsdatum(rechnungId: number, datum: Date): Promise<boolean>;
|
||||||
|
getCustomerById(id: number): Promise<AgrarmonitorApiCustomer>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgrarmonitorFreischaltungStatus {
|
export interface AgrarmonitorFreischaltungStatus {
|
||||||
@@ -96,3 +104,28 @@ export interface AgrarmonitorApiCustomer {
|
|||||||
bearbeitet_am?: string | number;
|
bearbeitet_am?: string | number;
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EingangsrechnungLivesearchResult {
|
||||||
|
dokumentId: number;
|
||||||
|
vorschauUrl: string;
|
||||||
|
dokumentUrl: string;
|
||||||
|
dokumentName: string;
|
||||||
|
dateiName: string;
|
||||||
|
belegNummer: string;
|
||||||
|
interneBelegNummer: string;
|
||||||
|
belegDatum: Date | null;
|
||||||
|
buchungsDatum: Date | null;
|
||||||
|
eingangsDatum: Date | null;
|
||||||
|
eingangId: number;
|
||||||
|
kundenId: number;
|
||||||
|
betriebId: number;
|
||||||
|
dokumentTyp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Rechnungsdaten {
|
||||||
|
lieferschein: string;
|
||||||
|
rechnung: string;
|
||||||
|
datum: Date;
|
||||||
|
kundenId: number;
|
||||||
|
adresstext: string;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user