Jobs typisiert und erweitert, nächtliche Bereinigung, Kunden-Popup bei Anruf
Job-Klassen in Frontend (Job/JobCustomer/JobCourier/JobTour) und Backend auf die neue Webhook-Form erweitert (url, finished, service, canceled, global, phone statt number – alte Schreibweise bleibt verstanden, remark je Station); Stationen legen ihre Kennung wieder als "id" ab. Adressen tragen zusätzlich role, csc_id, job_ids und url. Ein Zeitplan löscht jede Nacht um 0 Uhr Jobs, deren ordertime über vier Wochen zurückliegt. Meldet SwyxIt! einen Anruf, sucht die Startseite die Rufnummer in der Ablage (Schreibweisen-Abgleich über die Endziffern) und zeigt bei einem Kunden ein Popup mit dessen Daten; die Auftrags-URL öffnet als Knopf einen neuen Tab und schließt das Popup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ee81f25942
commit
3a86bc1eee
16 files changed
+933
-90
No files matched your search
@@ -54,7 +54,7 @@ class AddressControllerTests {
|
||||
@Test
|
||||
void listPassesQueryToArchive() throws Exception {
|
||||
when(archive.addresses("muster"))
|
||||
.thenReturn(List.of(new AddressEntry("1", "+493012345", "Muster GmbH", null)));
|
||||
.thenReturn(List.of(new AddressEntry("1", "+493012345", "Muster GmbH", null, null, null, null, null)));
|
||||
|
||||
mvc.perform(get("/api/addresses").param("q", "muster"))
|
||||
.andExpect(status().isOk())
|
||||
|
||||
@@ -27,14 +27,34 @@ class AddressEntryTests {
|
||||
@Test
|
||||
void readsSingleWebhookEntry() {
|
||||
List<AddressEntry> entries = parse("""
|
||||
{"name":"SYSGEN GmbH","number":"+49421409660",
|
||||
"description":"Kunde | SYSGEN, SYSTEME UND | NL Bremen | csc 100164"}""");
|
||||
{"name":"SYSGEN GmbH","number":"+491602107449",
|
||||
"description":"Kunde | SYSGEN, SYSTEME UND | NL Bremen",
|
||||
"role":"customer","csc_id":100164,"job_ids":[21891263,21891262],
|
||||
"url":"https://test.sb.assecutor.de/admin/tapi_wrapper.php?phoneNo=01602107449"}""");
|
||||
|
||||
assertThat(entries).hasSize(1);
|
||||
AddressEntry entry = entries.getFirst();
|
||||
assertThat(entry.name()).isEqualTo("SYSGEN GmbH");
|
||||
assertThat(entry.number()).isEqualTo("+49421409660");
|
||||
assertThat(entry.description()).isEqualTo("Kunde | SYSGEN, SYSTEME UND | NL Bremen | csc 100164");
|
||||
assertThat(entry.number()).isEqualTo("+491602107449");
|
||||
assertThat(entry.description()).isEqualTo("Kunde | SYSGEN, SYSTEME UND | NL Bremen");
|
||||
assertThat(entry.role()).isEqualTo("customer");
|
||||
assertThat(entry.cscId()).isEqualTo(100164);
|
||||
assertThat(entry.jobIds()).containsExactly(21891263L, 21891262L);
|
||||
assertThat(entry.url())
|
||||
.isEqualTo("https://test.sb.assecutor.de/admin/tapi_wrapper.php?phoneNo=01602107449");
|
||||
}
|
||||
|
||||
/** Die Felder ab {@code role} darf das Fremdsystem auch weglassen. */
|
||||
@Test
|
||||
void extraFieldsStayNullWhenAbsent() {
|
||||
List<AddressEntry> entries = parse("""
|
||||
{"name":"SYSGEN GmbH","number":"+49421409660","description":"Kunde | …"}""");
|
||||
|
||||
AddressEntry entry = entries.getFirst();
|
||||
assertThat(entry.role()).isNull();
|
||||
assertThat(entry.cscId()).isNull();
|
||||
assertThat(entry.jobIds()).isNull();
|
||||
assertThat(entry.url()).isNull();
|
||||
}
|
||||
|
||||
/** Kuriere und Jobs kommen analog – auch als Liste. */
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package de.appcreation.swyxweb;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import de.appcreation.swyxweb.storage.JobCleanupService;
|
||||
|
||||
/**
|
||||
* Die nächtliche Job-Bereinigung vergleicht die {@code ordertime} als Text –
|
||||
* hier wird die Grenze und der Vergleich gegen das Format der Nutzlast geprüft.
|
||||
*/
|
||||
class JobCleanupServiceTests {
|
||||
|
||||
@Test
|
||||
void cutoffLiesFourWeeksBackInOrdertimeFormat() {
|
||||
ZonedDateTime now = ZonedDateTime.of(2026, 8, 28, 0, 0, 0, 0, JobCleanupService.ZONE);
|
||||
|
||||
// Vier Wochen vor dem 28.08. ist der 31.07.; im Sommer gilt +02:00.
|
||||
assertThat(JobCleanupService.cutoffOrdertime(now)).isEqualTo("2026-07-31T00:00:00+02:00");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ordertimeStringsCompareChronologically() {
|
||||
String cutoff = "2026-07-31T00:00:00+02:00";
|
||||
|
||||
assertThat("2026-07-30T18:14:02+02:00").isLessThan(cutoff);
|
||||
assertThat("2026-08-01T08:00:00+02:00").isGreaterThan(cutoff);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,9 @@ import de.appcreation.swyxweb.storage.JobEntry;
|
||||
|
||||
/**
|
||||
* {@link JobEntry} wird aus der Webhook-Nutzlast mit {@code "type":"job"}
|
||||
* gefüllt – samt Kunde, Kurier und Stationen.
|
||||
* gefüllt – samt Kunde, Kurier und Stationen. Eine ältere Form der Nutzlast
|
||||
* nannte die Rufnummern {@code number} statt {@code phone}; auch sie wird
|
||||
* weiterhin verstanden.
|
||||
*/
|
||||
class JobEntryTests {
|
||||
|
||||
@@ -22,21 +24,30 @@ class JobEntryTests {
|
||||
private static final String SAMPLE = """
|
||||
{
|
||||
"type": "job",
|
||||
"id": 21891253,
|
||||
"state": 2,
|
||||
"ordertime": "2026-07-31T16:30:00+02:00",
|
||||
"orderdate": "2026-07-31",
|
||||
"modified": "2026-07-30T18:14:02+02:00",
|
||||
"vehicle": "Transporter",
|
||||
"customer": { "csc_id": 868374, "name": "B+M isol nord GmbH", "hq": "Bremen" },
|
||||
"courier": { "cr_id": 14116, "sid": "B1006", "name": "CA Kurier B1006", "number": "+4917xxxxxxx" },
|
||||
"id": 21891263,
|
||||
"url": "https://test.sb.assecutor.de/admin/jb_detail.php?job_id=21891263",
|
||||
"state": 9,
|
||||
"ordertime": "2026-08-13T13:35:27",
|
||||
"orderdate": "2026-08-13",
|
||||
"modified": "2026-08-13T13:35:27",
|
||||
"finished": null,
|
||||
"vehicle": "Transporter XL",
|
||||
"service": null,
|
||||
"canceled": false,
|
||||
"global": false,
|
||||
"customer": { "csc_id": 100164, "name": "SYSGEN GmbH", "hq": "Bremen",
|
||||
"phone": "+491602107449" },
|
||||
"courier": null,
|
||||
"tours": [
|
||||
{ "id": 22396738, "sort": 1, "state": 1, "mode": "", "comp": "B+M isol nord GmbH",
|
||||
"person": "", "number": "+4942037010xx", "street": "Löwenhof 2", "zip": "28844",
|
||||
"city": "Weyhe-Dreye", "com": "", "finished": "2026-07-30T18:13:11+02:00" },
|
||||
{ "id": 22396739, "sort": 2, "state": 1, "mode": "", "comp": "Reichspräsident Ebert Kaserne",
|
||||
"person": "", "number": null, "street": "Osdorfer Landstr. 365", "zip": "22589",
|
||||
"city": "Hamburg", "com": "", "finished": "2026-07-30T18:13:51+02:00" }
|
||||
{ "id": 22396758, "sort": 1, "state": 0, "mode": "del", "comp": "SYSGEN GmbH",
|
||||
"person": "Frau Hoffmann", "phone": "+49421409660", "com": null,
|
||||
"remark": "~~~\\nHebebühne benötigt.\\n~~~", "street": "Am Hallacker 48",
|
||||
"zip": "28327", "city": "Bremen", "finished": null },
|
||||
{ "id": 22396759, "sort": 2, "state": 0, "mode": "pu",
|
||||
"comp": "Super Micro Computer B.V.", "person": null, "phone": null, "com": null,
|
||||
"remark": "Abholreferenz: 8801420234\\nAnzahl an Paletten: 8",
|
||||
"street": "Het Sterrenbeeld 12-16", "zip": "5215", "city": "'s-Hertogenbosch",
|
||||
"finished": null }
|
||||
]
|
||||
}""";
|
||||
|
||||
@@ -49,33 +60,60 @@ class JobEntryTests {
|
||||
|
||||
@Test
|
||||
void fillsAllFieldsFromSample() {
|
||||
Instant receivedAt = Instant.parse("2026-08-26T10:00:00Z");
|
||||
Instant receivedAt = Instant.parse("2026-08-28T10:00:00Z");
|
||||
JsonNode json = mapper.readTree(SAMPLE);
|
||||
|
||||
JobEntry job = JobEntry.from(json, mapper, receivedAt);
|
||||
|
||||
assertThat(job.id()).isEqualTo(21891253L);
|
||||
assertThat(job.id()).isEqualTo(21891263L);
|
||||
assertThat(job.receivedAt()).isEqualTo(receivedAt);
|
||||
assertThat(job.state()).isEqualTo(2);
|
||||
assertThat(job.ordertime()).isEqualTo("2026-07-31T16:30:00+02:00");
|
||||
assertThat(job.orderdate()).isEqualTo("2026-07-31");
|
||||
assertThat(job.vehicle()).isEqualTo("Transporter");
|
||||
assertThat(job.url()).isEqualTo("https://test.sb.assecutor.de/admin/jb_detail.php?job_id=21891263");
|
||||
assertThat(job.state()).isEqualTo(9);
|
||||
assertThat(job.ordertime()).isEqualTo("2026-08-13T13:35:27");
|
||||
assertThat(job.orderdate()).isEqualTo("2026-08-13");
|
||||
assertThat(job.finished()).isNull();
|
||||
assertThat(job.vehicle()).isEqualTo("Transporter XL");
|
||||
assertThat(job.service()).isNull();
|
||||
assertThat(job.canceled()).isFalse();
|
||||
assertThat(job.global()).isFalse();
|
||||
|
||||
assertThat(job.customer().cscId()).isEqualTo(868374);
|
||||
assertThat(job.customer().name()).isEqualTo("B+M isol nord GmbH");
|
||||
assertThat(job.customer().cscId()).isEqualTo(100164);
|
||||
assertThat(job.customer().name()).isEqualTo("SYSGEN GmbH");
|
||||
assertThat(job.customer().hq()).isEqualTo("Bremen");
|
||||
assertThat(job.customer().phone()).isEqualTo("+491602107449");
|
||||
|
||||
assertThat(job.courier().crId()).isEqualTo(14116);
|
||||
assertThat(job.courier().sid()).isEqualTo("B1006");
|
||||
assertThat(job.courier().number()).isEqualTo("+4917xxxxxxx");
|
||||
// Noch kein Kurier zugeteilt.
|
||||
assertThat(job.courier()).isNull();
|
||||
|
||||
assertThat(job.tours()).hasSize(2);
|
||||
JobEntry.Tour first = job.tours().getFirst();
|
||||
assertThat(first.id()).isEqualTo(22396738L);
|
||||
assertThat(first.id()).isEqualTo(22396758L);
|
||||
assertThat(first.sort()).isEqualTo(1);
|
||||
assertThat(first.street()).isEqualTo("Löwenhof 2");
|
||||
assertThat(first.finished()).isEqualTo("2026-07-30T18:13:11+02:00");
|
||||
assertThat(first.mode()).isEqualTo("del");
|
||||
assertThat(first.person()).isEqualTo("Frau Hoffmann");
|
||||
assertThat(first.phone()).isEqualTo("+49421409660");
|
||||
assertThat(first.remark()).contains("Hebebühne benötigt.");
|
||||
assertThat(first.street()).isEqualTo("Am Hallacker 48");
|
||||
// Fehlende Rufnummer der zweiten Station bleibt null.
|
||||
assertThat(job.tours().get(1).number()).isNull();
|
||||
assertThat(job.tours().get(1).phone()).isNull();
|
||||
assertThat(job.tours().get(1).city()).isEqualTo("'s-Hertogenbosch");
|
||||
}
|
||||
|
||||
/** Die ältere Form nannte die Rufnummern {@code number} statt {@code phone}. */
|
||||
@Test
|
||||
void acceptsLegacyNumberFields() {
|
||||
JsonNode json = mapper.readTree("""
|
||||
{
|
||||
"type": "job",
|
||||
"id": 21891253,
|
||||
"courier": { "cr_id": 14116, "sid": "B1006", "name": "CA Kurier B1006",
|
||||
"number": "+4917xxxxxxx" },
|
||||
"tours": [ { "id": 22396738, "sort": 1, "number": "+4942037010xx" } ]
|
||||
}""");
|
||||
|
||||
JobEntry job = JobEntry.from(json, mapper, Instant.parse("2026-08-28T10:00:00Z"));
|
||||
|
||||
assertThat(job.courier().phone()).isEqualTo("+4917xxxxxxx");
|
||||
assertThat(job.tours().getFirst().phone()).isEqualTo("+4942037010xx");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user