Erweitere VoTiAn NG um Fachfunktionen und verbessere die Listenoberfläche

Ergänze Auftragsbearbeitung, Disposition, Stammdaten, Kunden- und
Transporteurportale, Lagerverwaltung, Abrechnung, Exporte und Statistik.
Füge Kontoeinstellungen, Mitteilungen, Automatikdienste und Betriebsstatus
sowie die zugehörigen Tests und PHP-Abgleichsdokumentation hinzu.

Vergrößere die persönlichen Auftragslisten auf einheitliche Höhen und
richte Statusanzeigen sowie die Ranking-Vorschau-Steuerelemente bündig aus.

Validierung: 44 Frontendtests und Produktionsbuild erfolgreich.
Backend funktional unverändert gegenüber dem vollständigen Lauf mit 1.624
erfolgreichen und drei übersprungenen Tests. Layout im Browser und lokale
Bereitstellung geprüft.
This commit is contained in:
Sven committed 2026-10-20 11:19:43 +02:00
1 parent 363bc27c26
commit 781a2b82dc
1165 files changed
+74986 -1028

No files matched your search

@@ -0,0 +1,36 @@
package de.stadtbote.votian;
import de.stadtbote.votian.database.DatabaseService;
import java.util.*;
import static de.stadtbote.votian.AdminSupport.*;
import static de.stadtbote.votian.LegacyMapping.*;
import static de.stadtbote.votian.OrderEntryService.fields;
/** Classic jobs2 photo quantities; independent of the external workflow service. */
final class CustomerEntryPhotos {
record Requirement(int station,int minimum,int maximum){}
final DatabaseService db;
CustomerEntryPhotos(DatabaseService db){this.db=db;}
List<Map<String,Object>> settings(long customer){return db.queryForList("SELECT par_id,par_key,md_id,hq_id,emp_id,par_value FROM parameter WHERE par_key IN (?,?) ORDER BY par_key,par_id",ExtrasCustomerSettings.photoKey(customer),ExtrasCustomerSettings.rangeKey(customer));}
long cashCustomer(long payer){if(payer<=0)return 0;var rows=db.queryForList("SELECT cs_id FROM costcenter WHERE csc_id=?",payer);return rows.size()==1?id(rows.getFirst(),"cs_id"):0;}
List<Object> revisionBasis(long customer,long cashPayer){var basis=new ArrayList<Object>();var own=settings(customer);if(!own.isEmpty())basis.add(own);long cash=cashCustomer(cashPayer);if(cash>0&&cash!=customer){var rows=settings(cash);if(!rows.isEmpty()){basis.add(cash);basis.add(rows);}}return basis;}
ExtrasCustomerSettings.Values config(long customer){var rows=settings(customer).stream().map(r->{var next=new LinkedHashMap<>(r);if(text(r,"par_key").equals(ExtrasCustomerSettings.rangeKey(customer))&&text(r,"par_value").isBlank())next.put("par_value","1|1");return (Map<String,Object>)next;}).toList();return ExtrasCustomerSettings.decode(ExtrasCustomerSettings.Type.PHOTO,customer,rows);}
List<Requirement> plan(CustomerEntryAccess.State state,CustomerOrderEntry.Draft draft){
var own=config(state.customer());boolean systemCash=draft.payment()==OrderEntryModels.Payment.CASH&&!state.cashPayerSelect();
var initial=systemCash?config(cashCustomer(state.cashPayer())):own;
return plan(draft.stops().size(),initial,own,systemCash?draft.cashStop():null);
}
static List<Requirement> plan(int stations,ExtrasCustomerSettings.Values initial,ExtrasCustomerSettings.Values own,Integer cashStop){
var result=new ArrayList<Requirement>();
for(int i=1;i<stations;i++){var config=cashStop!=null&&i>=cashStop?own:initial;if(config.mode()!=null&&(config.mode()==2||config.mode()==3))result.add(new Requirement(i+1,config.minimum(),config.maximum()));}
return List.copyOf(result);
}
void created(long job,List<Requirement> plan){
if(plan.isEmpty())return;
var stops=db.queryForList("SELECT tr_id,tr_sort FROM tour WHERE jb_id=? ORDER BY tr_sort,tr_id",job);
for(var r:plan){var matching=stops.stream().filter(t->id(t,"tr_sort")==r.station()).toList();if(matching.size()!=1)throw bad("Die Fotoanforderung kann keiner eindeutigen Station zugeordnet werden.");long station=id(matching.getFirst(),"tr_id");
if(!db.queryForList("SELECT gdc_id FROM genericdatacontainer WHERE gdc_obj_type='tr' AND gdc_obj_id=? AND gdc_gen_fieldname='tr_photo'",station).isEmpty())throw bad("Die Station besitzt bereits eine Fotoanforderung.");
db.insert("genericdatacontainer",fields("gdc_obj_type","tr","gdc_obj_id",station,"gdc_gen_fieldname","tr_photo","gdc_content",r.minimum()+"|"+r.maximum(),"gdc_context",Long.toString(job)));
}
}
}