Erweitere VoTiAn NG um Auftrags-, Stamm- und Verwaltungsfunktionen
Ergänzt Auftragserfassung und -bearbeitung, Unternehmerbelege, Stornos, Lagerkorrekturen, Stammdaten, Abrechnung, CRM, Formulare, Berichte und Preisverwaltung auf Basis der aktuellen PHP-Anwendung. Enthält Integrations- und Oberflächentests sowie dokumentierte Abgleiche und Prüfstände.
This commit is contained in:
1 parent
42c87282ee
commit
363bc27c26
190 files changed
+14815
-1857
No files matched your search
@@ -0,0 +1,74 @@
|
||||
package de.stadtbote.votian;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import de.stadtbote.votian.database.DatabaseService;
|
||||
import jakarta.validation.Validation;
|
||||
import org.junit.jupiter.api.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static de.stadtbote.votian.OrderEntryService.fields;
|
||||
import static de.stadtbote.votian.OrderEntryFixtures.USER;
|
||||
import static de.stadtbote.votian.OrderEntryModels.*;
|
||||
|
||||
class OrderRecalculationTest {
|
||||
final DatabaseService db=mock(DatabaseService.class);
|
||||
final OrderEntryCatalog catalog=mock(OrderEntryCatalog.class);
|
||||
final LegacyOrderPricing prices=mock(LegacyOrderPricing.class);
|
||||
final OrderRoutingService routing=mock(OrderRoutingService.class);
|
||||
final OrderOperations orders=mock(OrderOperations.class);
|
||||
final BusinessSupport support=new BusinessSupport(db,new ObjectMapper().findAndRegisterModules());
|
||||
final OrderRecalculation service=new OrderRecalculation(db,support,orders,catalog,prices,routing,Validation.buildDefaultValidatorFactory().getValidator());
|
||||
final Map<String,Object> job=fields("jb_id",5L,"hq_id",7,"hq_id_dispo",7,"jb_ordertime",LocalDateTime.of(2026,10,10,10,0),"csc_id_payer",41,"jb_totalprice",100,"jb_cr_price",70,"jb_status",9,"jb_version",2,"jb_weight",0);
|
||||
OrderOperations.Snapshot snapshot;
|
||||
OrderRecalculation.Input input=new OrderRecalculation.Input(new Pricing(PriceMode.FIXED,new BigDecimal("100"),BigDecimal.TEN,null,null,null,null,false,4L),2,List.of(),false,false);
|
||||
@BeforeEach void setup() {
|
||||
when(db.readTransaction(any())).thenAnswer(c->((Supplier<?>)c.getArgument(0)).get());
|
||||
when(db.writeTransaction(any())).thenAnswer(c->((Supplier<?>)c.getArgument(0)).get());
|
||||
when(db.lockRows(anyString(),any(Object[].class))).thenReturn(List.of(fields("hq_id",7)));
|
||||
when(db.lockRows(startsWith("SELECT jb_id,hq_id FROM job WHERE jb_id_parent="),any(Object[].class))).thenReturn(List.of());
|
||||
when(catalog.costCenter(USER,41,true)).thenReturn(fields("cs_id",31));
|
||||
snapshot=new OrderOperations.Snapshot(job,List.of(fields("tr_id",6,"tr_sort",1,"tr_ware_from_to",3,"tr_comp","Test","ad_street","Teststraße","tr_hsno","1","ad_city","Berlin","ad_zipcode","10115","ad_country","DE")),List.of(),List.of(),List.of(),List.of());
|
||||
when(orders.snapshot(eq(USER),eq(5L),anyBoolean())).thenAnswer(c->snapshot);
|
||||
when(orders.view(USER,5)).thenReturn(fields("id",5));
|
||||
when(prices.calculate(eq(USER),any(),anyMap(),any(),any(),anyList())).thenReturn(OrderEntryFixtures.quote());
|
||||
}
|
||||
OrderRecalculation.Command command(String fingerprint) {return new OrderRecalculation.Command(UUID.randomUUID().toString(),AdminSupport.revision(snapshot),input,fingerprint);}
|
||||
@Test void previewIsReadOnlyAndUsesSavedDateStationAndPayer() {
|
||||
var result=service.preview(USER,5,command(null));assertThat(result).containsEntry("previousNet",new BigDecimal("100"));
|
||||
verify(prices).calculate(eq(USER),argThat(r->r.stops().size()==1&&r.payerCostCenterId()==41&&r.pricing().mode()==PriceMode.FIXED),anyMap(),eq(LocalDateTime.of(2026,10,10,10,0)),isNull(),anyList());
|
||||
verify(db,never()).update(anyString(),any(Object[].class));verify(db,never()).insert(anyString(),anyMap());
|
||||
}
|
||||
@Test void staleRevisionAndChangedTariffsCannotBeSaved() {
|
||||
assertThatThrownBy(()->service.preview(USER,5,new OrderRecalculation.Command(null,"old",input,null))).hasMessageContaining("geändert");
|
||||
assertThatThrownBy(()->service.save(USER,5,command("old"))).hasMessageContaining("Preisvorschau");
|
||||
verify(db,never()).update(anyString(),any(Object[].class));
|
||||
}
|
||||
@Test void writesOnlyPricesAndMetadataWithoutReplayingOperationalState() {
|
||||
var c=command(null);var preview=service.preview(USER,5,c);
|
||||
service.save(USER,5,new OrderRecalculation.Command(c.requestId(),c.revision(),input,(String)preview.get("fingerprint")));
|
||||
verify(db).update(eq("DELETE FROM tourservice WHERE jb_id=?"),eq(5L));
|
||||
verify(db).update(startsWith("UPDATE job SET"),any(Object[].class));
|
||||
verify(db,never()).insert(eq("job"),anyMap());verify(db,never()).insert(eq("tour"),anyMap());
|
||||
verify(db,never()).update(contains("stock"),any(Object[].class));
|
||||
verify(db,never()).update(contains("mt_sort=16"),any(Object[].class));
|
||||
verify(db,never()).update(contains("jb_incomplete"),any(Object[].class));
|
||||
}
|
||||
@Test void paymentsAndBrokenChildLinksAreNotSilentlyOverwritten() {
|
||||
snapshot=new OrderOperations.Snapshot(job,snapshot.stops(),List.of(),List.of(),List.of(fields("jbp_id",1)),List.of());
|
||||
assertThatThrownBy(()->service.preview(USER,5,command(null))).hasMessageContaining("Einnahmenbuchungen");
|
||||
snapshot=new OrderOperations.Snapshot(job,snapshot.stops(),List.of(),List.of(),List.of(),List.of());job.put("jb_id_parent",-1);
|
||||
assertThatThrownBy(()->service.preview(USER,5,command(null))).hasMessageContaining("fehlende Unteraufträge");
|
||||
}
|
||||
@Test void invalidInputsAndMissingRightsFailBeforeExternalCalls() {
|
||||
var none=new LegacyUser(11,12,7,"no-rights","0".repeat(40));
|
||||
assertThatThrownBy(()->service.preview(none,5,command(null))).isInstanceOf(org.springframework.security.access.AccessDeniedException.class);
|
||||
input=new OrderRecalculation.Input(new Pricing(PriceMode.FIXED,new BigDecimal("-1"),null,null,null,null,null,false,4L),2,List.of(),false,false);
|
||||
assertThatThrownBy(()->service.preview(USER,5,command(null))).hasMessageContaining("Kalkulationswerte");
|
||||
verifyNoInteractions(routing);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user