feat: Adressbuch mit Kundennummer, Update-Flow und interne Einträge

- Menüpunkt "Kunden" in "Adressbuch" umbenannt und App-Label
  "Verfügbare Jobs" zu "Auftragsliste" geändert (alle 10 Sprachen)
- Fortlaufende Kundennummer (usrId) ab 10000 über neuen
  SequenceGeneratorService und Counter-Dokument in misc-Collection
- Abholung/Lieferstation-Dialog: Änderungen an verknüpften
  Stammdaten aktualisieren den bestehenden Adressbuch-Eintrag
  statt einen neuen zu erzeugen; Checkbox-Label wechselt zu
  "Adresse im Adressbuch aktualisieren"
- Geänderte Adressen ohne Checkbox werden als interner Customer
  (internal=true) gesichert und im Adressbuch ausgeblendet
- E-Mail in AddCustomer und in Stations-Dialogen kein Pflichtfeld
  mehr; "(Login)" aus profile.email entfernt
- Manuelles Beenden eines Auftrags öffnet neue Seite
  JobManualCompleteView statt eines Dialogs
This commit is contained in:
2026-04-20 12:42:56 +02:00
parent 6e8bedd9b4
commit 704d1e7378
42 changed files with 720 additions and 196 deletions

View File

@@ -55,7 +55,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get jobs => 'Jobs'; String get jobs => 'Jobs';
@override @override
String get availableJobs => 'Verfügbare Jobs'; String get availableJobs => 'Auftragsliste';
@override @override
String get chats => 'Chats'; String get chats => 'Chats';

View File

@@ -55,7 +55,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get jobs => 'Jobs'; String get jobs => 'Jobs';
@override @override
String get availableJobs => 'Available Jobs'; String get availableJobs => 'Order List';
@override @override
String get chats => 'Chats'; String get chats => 'Chats';

View File

@@ -42,7 +42,7 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get jobs => 'Trabajos'; String get jobs => 'Trabajos';
@override @override
String get availableJobs => 'Trabajos Disponibles'; String get availableJobs => 'Lista de pedidos';
@override @override
String get chats => 'Chats'; String get chats => 'Chats';
@override @override

View File

@@ -42,7 +42,7 @@ class AppLocalizationsEt extends AppLocalizations {
@override @override
String get jobs => 'Tööd'; String get jobs => 'Tööd';
@override @override
String get availableJobs => 'Saadaolevad tööd'; String get availableJobs => 'Tellimuste loend';
@override @override
String get chats => 'Vestlused'; String get chats => 'Vestlused';
@override @override

View File

@@ -42,7 +42,7 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get jobs => 'Emplois'; String get jobs => 'Emplois';
@override @override
String get availableJobs => 'Emplois Disponibles'; String get availableJobs => 'Liste des commandes';
@override @override
String get chats => 'Discussions'; String get chats => 'Discussions';
@override @override

View File

@@ -42,7 +42,7 @@ class AppLocalizationsLt extends AppLocalizations {
@override @override
String get jobs => 'Darbai'; String get jobs => 'Darbai';
@override @override
String get availableJobs => 'Galimi darbai'; String get availableJobs => 'Užsakymų sąrašas';
@override @override
String get chats => 'Pokalbiai'; String get chats => 'Pokalbiai';
@override @override

View File

@@ -42,7 +42,7 @@ class AppLocalizationsLv extends AppLocalizations {
@override @override
String get jobs => 'Darbi'; String get jobs => 'Darbi';
@override @override
String get availableJobs => 'Pieejamie darbi'; String get availableJobs => 'Pasūtījumu saraksts';
@override @override
String get chats => 'Tērzēšanas'; String get chats => 'Tērzēšanas';
@override @override

View File

@@ -42,7 +42,7 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get jobs => 'Zadania'; String get jobs => 'Zadania';
@override @override
String get availableJobs => 'Dostępne Zadania'; String get availableJobs => 'Lista zleceń';
@override @override
String get chats => 'Czaty'; String get chats => 'Czaty';
@override @override

View File

@@ -42,7 +42,7 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get jobs => 'Задания'; String get jobs => 'Задания';
@override @override
String get availableJobs => 'Доступные задания'; String get availableJobs => 'Список заказов';
@override @override
String get chats => 'Чаты'; String get chats => 'Чаты';
@override @override

View File

@@ -42,7 +42,7 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get jobs => 'İşler'; String get jobs => 'İşler';
@override @override
String get availableJobs => 'Mevcut İşler'; String get availableJobs => 'Sipariş Listesi';
@override @override
String get chats => 'Sohbetler'; String get chats => 'Sohbetler';
@override @override

View File

@@ -2,6 +2,8 @@ import '../task.dart';
// Signature Task // Signature Task
class SignatureTask extends Task { class SignatureTask extends Task {
final String? note;
SignatureTask({ SignatureTask({
required super.id, required super.id,
required super.jobId, required super.jobId,
@@ -14,11 +16,19 @@ class SignatureTask extends Task {
super.title, super.title,
super.description, super.description,
super.displayName, super.displayName,
this.note,
}); });
factory SignatureTask.fromJson(Map<String, dynamic> json) { factory SignatureTask.fromJson(Map<String, dynamic> json) {
final commonProps = Task.parseCommonProperties(json); final commonProps = Task.parseCommonProperties(json);
String? note;
final taskSpecificData = json['taskSpecificData'];
if (taskSpecificData is Map<String, dynamic>) {
final n = taskSpecificData['note'];
if (n is String) note = n;
}
return SignatureTask( return SignatureTask(
id: commonProps['id'], id: commonProps['id'],
jobId: commonProps['jobId'], jobId: commonProps['jobId'],
@@ -31,6 +41,7 @@ class SignatureTask extends Task {
title: commonProps['title'], title: commonProps['title'],
description: commonProps['description'], description: commonProps['description'],
displayName: commonProps['displayName'], displayName: commonProps['displayName'],
note: note,
); );
} }
@@ -47,7 +58,11 @@ class SignatureTask extends Task {
'taskOrder': taskOrder, 'taskOrder': taskOrder,
'description': description, 'description': description,
'displayName': displayName, 'displayName': displayName,
'taskSpecificData': {'taskType': 'SIGNATURE', 'title': title}, 'taskSpecificData': {
'taskType': 'SIGNATURE',
'title': title,
'note': note,
},
}; };
} }
@@ -64,6 +79,7 @@ class SignatureTask extends Task {
String? title, String? title,
String? description, String? description,
String? displayName, String? displayName,
String? note,
}) { }) {
return SignatureTask( return SignatureTask(
id: id ?? this.id, id: id ?? this.id,
@@ -77,6 +93,7 @@ class SignatureTask extends Task {
title: title ?? this.title, title: title ?? this.title,
description: description ?? this.description, description: description ?? this.description,
displayName: displayName ?? this.displayName, displayName: displayName ?? this.displayName,
note: note ?? this.note,
); );
} }
} }

View File

@@ -831,6 +831,42 @@ class DatabaseService {
} }
} }
/// Save signature note (Bemerkung) for a task into user_data table
Future<void> saveTaskSignatureNote(String taskId, String note) async {
try {
if (_store == null) {
developer.log('Database not initialized', name: 'DatabaseService');
return;
}
final key = 'task_signature_note:$taskId';
await saveKeyValue(key, note);
} catch (e, st) {
developer.log(
'Error saving task signature note: $e',
name: 'DatabaseService',
);
developer.log('Stack trace: $st', name: 'DatabaseService');
}
}
/// Load signature note (Bemerkung) for a task from user_data table
Future<String?> loadTaskSignatureNote(String taskId) async {
try {
if (_store == null) {
developer.log('Database not initialized', name: 'DatabaseService');
return null;
}
return await loadKeyValue('task_signature_note:$taskId');
} catch (e, st) {
developer.log(
'Error loading task signature note: $e',
name: 'DatabaseService',
);
developer.log('Stack trace: $st', name: 'DatabaseService');
return null;
}
}
/// Load signature SVG for a task from user_data table /// Load signature SVG for a task from user_data table
Future<String?> loadTaskSignature(String taskId) async { Future<String?> loadTaskSignature(String taskId) async {
try { try {

View File

@@ -622,10 +622,11 @@ class _TaskViewState extends State<TaskView> {
builder: builder:
(context) => SignatureCaptureScreen( (context) => SignatureCaptureScreen(
task: task, task: task,
onSignatureCompleted: (String svg) async { onSignatureCompleted: (String svg, String note) async {
try { try {
// Persist SVG only (no PNG) // Persist SVG only (no PNG)
await _databaseService.saveTaskSignature(task.id, svg); await _databaseService.saveTaskSignature(task.id, svg);
await _databaseService.saveTaskSignatureNote(task.id, note);
} catch (e, stackTrace) { } catch (e, stackTrace) {
developer.log( developer.log(
'Error saving task signature: $e', 'Error saving task signature: $e',
@@ -649,6 +650,7 @@ class _TaskViewState extends State<TaskView> {
'signatureSvg': svg, 'signatureSvg': svg,
'svgLength': svg.length, 'svgLength': svg.length,
'hasSignature': true, 'hasSignature': true,
'signatureNote': note,
}, },
); );
}, },
@@ -896,6 +898,10 @@ class _TaskViewState extends State<TaskView> {
task.description != null task.description != null
? localizeKnownText(context, task.description!) ? localizeKnownText(context, task.description!)
: null; : null;
final String? signatureNote =
(task is SignatureTask && task.note != null && task.note!.trim().isNotEmpty)
? task.note!.trim()
: null;
if (displayName?.isNotEmpty == true) { if (displayName?.isNotEmpty == true) {
return Column( return Column(
@@ -906,14 +912,39 @@ class _TaskViewState extends State<TaskView> {
const SizedBox(height: 2), const SizedBox(height: 2),
Text(description!, style: subtitleStyle), Text(description!, style: subtitleStyle),
], ],
if (signatureNote != null) ...[
const SizedBox(height: 2),
Text(signatureNote, style: subtitleStyle),
],
], ],
); );
} }
if (description?.isNotEmpty == true) { if (description?.isNotEmpty == true) {
if (signatureNote != null) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(description!, style: titleStyle),
const SizedBox(height: 2),
Text(signatureNote, style: subtitleStyle),
],
);
}
return Text(description!, style: titleStyle); return Text(description!, style: titleStyle);
} }
if (signatureNote != null) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_getStandardTaskDisplayText(task), style: titleStyle),
const SizedBox(height: 2),
Text(signatureNote, style: subtitleStyle),
],
);
}
// Fall back to standard text based on task type // Fall back to standard text based on task type
return Text(_getStandardTaskDisplayText(task), style: titleStyle); return Text(_getStandardTaskDisplayText(task), style: titleStyle);
} }

View File

@@ -9,7 +9,7 @@ import '../widgets/offline_banner.dart';
class SignatureCaptureScreen extends StatefulWidget { class SignatureCaptureScreen extends StatefulWidget {
final SignatureTask task; final SignatureTask task;
final void Function(String svg) onSignatureCompleted; final void Function(String svg, String note) onSignatureCompleted;
const SignatureCaptureScreen({ const SignatureCaptureScreen({
super.key, super.key,
@@ -23,6 +23,7 @@ class SignatureCaptureScreen extends StatefulWidget {
class _SignatureCaptureScreenState extends State<SignatureCaptureScreen> { class _SignatureCaptureScreenState extends State<SignatureCaptureScreen> {
late final SignatureController _controller; late final SignatureController _controller;
final TextEditingController _noteController = TextEditingController();
bool _hasSignature = false; bool _hasSignature = false;
bool _isMobilePlatform = false; bool _isMobilePlatform = false;
@@ -84,6 +85,7 @@ class _SignatureCaptureScreenState extends State<SignatureCaptureScreen> {
void dispose() { void dispose() {
_controller.removeListener(_onSignatureChanged); _controller.removeListener(_onSignatureChanged);
_controller.dispose(); _controller.dispose();
_noteController.dispose();
_restoreOrientation(); _restoreOrientation();
super.dispose(); super.dispose();
} }
@@ -155,14 +157,15 @@ class _SignatureCaptureScreenState extends State<SignatureCaptureScreen> {
// Build SVG from the captured signature points // Build SVG from the captured signature points
final String svg = _buildSvgFromPoints(_controller.points); final String svg = _buildSvgFromPoints(_controller.points);
final String note = _noteController.text.trim();
// Close this screen first to show the updated TaskView quickly // Close this screen first to show the updated TaskView quickly
if (!mounted) return; if (!mounted) return;
_restoreOrientation(); _restoreOrientation();
Navigator.of(context).pop(); Navigator.of(context).pop();
// Then notify the caller (SVG only) // Then notify the caller (SVG + Bemerkung)
widget.onSignatureCompleted(svg); widget.onSignatureCompleted(svg, note);
} catch (e) { } catch (e) {
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@@ -233,6 +236,16 @@ class _SignatureCaptureScreenState extends State<SignatureCaptureScreen> {
), ),
), ),
), ),
const SizedBox(height: 12),
TextField(
controller: _noteController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).completeTaskNote,
border: const OutlineInputBorder(),
),
maxLines: 2,
minLines: 1,
),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
children: [ children: [

View File

@@ -8,6 +8,7 @@ import de.assecutor.votianlt.model.AppUser;
import de.assecutor.votianlt.model.CargoItem; import de.assecutor.votianlt.model.CargoItem;
import de.assecutor.votianlt.model.Job; import de.assecutor.votianlt.model.Job;
import de.assecutor.votianlt.model.task.BaseTask; import de.assecutor.votianlt.model.task.BaseTask;
import de.assecutor.votianlt.model.task.SignatureTask;
import de.assecutor.votianlt.pages.service.AppUserService; import de.assecutor.votianlt.pages.service.AppUserService;
import de.assecutor.votianlt.repository.AppUserRepository; import de.assecutor.votianlt.repository.AppUserRepository;
import de.assecutor.votianlt.repository.CargoItemRepository; import de.assecutor.votianlt.repository.CargoItemRepository;
@@ -133,6 +134,14 @@ public class MessageController {
List<JobWithRelatedDataDTO> jobsWithRelatedData = assignedJobs.stream().map(job -> { List<JobWithRelatedDataDTO> jobsWithRelatedData = assignedJobs.stream().map(job -> {
List<CargoItem> cargoItems = cargoItemRepository.findByJobId(job.getId()); List<CargoItem> cargoItems = cargoItemRepository.findByJobId(job.getId());
List<BaseTask> tasks = taskAssignmentService.findTasksForJob(job); List<BaseTask> tasks = taskAssignmentService.findTasksForJob(job);
for (BaseTask task : tasks) {
if (task instanceof SignatureTask signatureTask && task.getId() != null) {
List<Signature> signatures = signatureRepository.findByTaskIdOrderByCreatedAtDesc(task.getId());
if (!signatures.isEmpty()) {
signatureTask.setNote(signatures.get(0).getNote());
}
}
}
return new JobWithRelatedDataDTO(job, cargoItems, tasks); return new JobWithRelatedDataDTO(job, cargoItems, tasks);
}).toList(); }).toList();
@@ -246,13 +255,18 @@ public class MessageController {
Object extra = payload.get("extraData"); Object extra = payload.get("extraData");
if (extra instanceof Map<?, ?> extraData) { if (extra instanceof Map<?, ?> extraData) {
Object signatureSvgObj = extraData.get("signatureSvg"); Object signatureSvgObj = extraData.get("signatureSvg");
Object signatureNoteObj = extraData.get("signatureNote");
String signatureNote = signatureNoteObj instanceof String s ? s : null;
if (signatureSvgObj instanceof String signatureSvg) { if (signatureSvgObj instanceof String signatureSvg) {
if (!signatureSvg.isBlank()) { if (!signatureSvg.isBlank()) {
String completedBy = task.getCompletedBy() != null ? task.getCompletedBy() : "Unknown"; String completedBy = task.getCompletedBy() != null ? task.getCompletedBy() : "Unknown";
Signature signatureEntry = new Signature(new ObjectId(taskId.toString()), signatureSvg, Signature signatureEntry = new Signature(new ObjectId(taskId.toString()), signatureSvg,
completedBy); signatureNote, completedBy);
signatureRepository.save(signatureEntry); signatureRepository.save(signatureEntry);
extraDataSummary = "Unterschrift erfasst (SVG, " + signatureSvg.length() + " Zeichen)"; extraDataSummary = "Unterschrift erfasst (SVG, " + signatureSvg.length() + " Zeichen)";
if (signatureNote != null && !signatureNote.isBlank()) {
extraDataSummary += ", Bemerkung: " + signatureNote;
}
} else { } else {
extraDataSummary = "Leere Unterschrift"; extraDataSummary = "Leere Unterschrift";
} }

View File

@@ -0,0 +1,13 @@
package de.assecutor.votianlt.model;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Data
@Document(collection = "misc")
public class Counter {
@Id
private String id;
private long sequence;
}

View File

@@ -53,4 +53,10 @@ public class Customer {
@Field("owner") @Field("owner")
private ObjectId owner; private ObjectId owner;
@Field("internal")
private boolean internal;
@Field("usrId")
private Integer usrId;
} }

View File

@@ -20,6 +20,7 @@ public class Signature {
private ObjectId taskId; private ObjectId taskId;
private String signatureSvg; private String signatureSvg;
private String note;
private LocalDateTime createdAt; private LocalDateTime createdAt;
private String completedBy; private String completedBy;
@@ -35,4 +36,9 @@ public class Signature {
this.signatureSvg = signatureSvg; this.signatureSvg = signatureSvg;
this.completedBy = completedBy; this.completedBy = completedBy;
} }
public Signature(ObjectId taskId, String signatureSvg, String note, String completedBy) {
this(taskId, signatureSvg, completedBy);
this.note = note;
}
} }

View File

@@ -1,14 +1,20 @@
package de.assecutor.votianlt.model.task; package de.assecutor.votianlt.model.task;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Transient;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class SignatureTask extends BaseTask { public class SignatureTask extends BaseTask {
@Transient
@JsonIgnore
private String note;
@Override @Override
public String getTaskType() { public String getTaskType() {
return "SIGNATURE"; return "SIGNATURE";
@@ -21,11 +27,17 @@ public class SignatureTask extends BaseTask {
@Override @Override
public Object getTaskSpecificData() { public Object getTaskSpecificData() {
return new TaskSpecificData(); return new TaskSpecificData(note);
} }
@Data
@NoArgsConstructor
public class TaskSpecificData { public class TaskSpecificData {
public String taskType = getTaskType(); public String taskType = getTaskType();
// No specific data for signature task public String note;
public TaskSpecificData(String note) {
this.note = note;
}
} }
} }

View File

@@ -56,10 +56,28 @@ public class DeliveryStationDialog extends Dialog {
private String zip; private String zip;
private String city; private String city;
private boolean saveAddress; private boolean saveAddress;
private boolean addressDiffersFromCustomer;
private org.bson.types.ObjectId customerId;
public boolean isAddressDiffersFromCustomer() {
return addressDiffersFromCustomer;
}
public void setAddressDiffersFromCustomer(boolean addressDiffersFromCustomer) {
this.addressDiffersFromCustomer = addressDiffersFromCustomer;
}
private List<BaseTask> tasks = new ArrayList<>(); private List<BaseTask> tasks = new ArrayList<>();
private boolean addressValidatedByGoogle; private boolean addressValidatedByGoogle;
private AddressValidationResult addressValidationResult; private AddressValidationResult addressValidationResult;
public org.bson.types.ObjectId getCustomerId() {
return customerId;
}
public void setCustomerId(org.bson.types.ObjectId customerId) {
this.customerId = customerId;
}
public boolean isAddressValidatedByGoogle() { public boolean isAddressValidatedByGoogle() {
return addressValidatedByGoogle; return addressValidatedByGoogle;
} }
@@ -214,6 +232,7 @@ public class DeliveryStationDialog extends Dialog {
private final DeliveryStationTile.TranslationHelper translationHelper; private final DeliveryStationTile.TranslationHelper translationHelper;
private final java.util.Map<String, Customer> companyAddressOptions = new java.util.LinkedHashMap<>(); private final java.util.Map<String, Customer> companyAddressOptions = new java.util.LinkedHashMap<>();
private org.bson.types.ObjectId selectedCustomerId;
public DeliveryStationDialog(String dialogTitle, List<Customer> customers, public DeliveryStationDialog(String dialogTitle, List<Customer> customers,
DeliveryStationTile.TranslationHelper translationHelper, SaveListener saveListener, DeliveryStationTile.TranslationHelper translationHelper, SaveListener saveListener,
@@ -512,6 +531,13 @@ public class DeliveryStationDialog extends Dialog {
zip.setValue(data.getZip()); zip.setValue(data.getZip());
if (data.getCity() != null) if (data.getCity() != null)
city.setValue(data.getCity()); city.setValue(data.getCity());
selectedCustomerId = data.getCustomerId();
if (selectedCustomerId == null && customerSelectedFromOptions) {
Customer matched = companyAddressOptions.get(companyOption);
if (matched != null) {
selectedCustomerId = matched.getId();
}
}
saveAddress.setValue(customerSelectedFromOptions ? false : data.isSaveAddress()); saveAddress.setValue(customerSelectedFromOptions ? false : data.isSaveAddress());
updateSaveAddressState(); updateSaveAddressState();
@@ -548,10 +574,43 @@ public class DeliveryStationDialog extends Dialog {
data.setZip(zip.getValue()); data.setZip(zip.getValue());
data.setCity(city.getValue()); data.setCity(city.getValue());
data.setSaveAddress(saveAddress.getValue()); data.setSaveAddress(saveAddress.getValue());
data.setCustomerId(selectedCustomerId);
data.setAddressDiffersFromCustomer(computeAddressDiffers());
data.setTasks(new ArrayList<>(tasksState)); data.setTasks(new ArrayList<>(tasksState));
return data; return data;
} }
private boolean computeAddressDiffers() {
boolean hasAnyValue = !isBlank(resolveCompanyValue(company.getValue())) || !isBlank(firstName.getValue())
|| !isBlank(lastName.getValue()) || !isBlank(phone.getValue()) || !isBlank(mail.getValue())
|| !isBlank(street.getValue()) || !isBlank(houseNumber.getValue())
|| !isBlank(addressAddition.getValue()) || !isBlank(zip.getValue()) || !isBlank(city.getValue());
if (!hasAnyValue) {
return false;
}
if (selectedCustomerId == null) {
return true;
}
Customer linked = findCustomerById(selectedCustomerId);
return linked == null || !matchesCurrentCustomer(linked);
}
private Customer findCustomerById(org.bson.types.ObjectId id) {
if (id == null) {
return null;
}
for (Customer c : companyAddressOptions.values()) {
if (c != null && id.equals(c.getId())) {
return c;
}
}
return null;
}
private boolean isBlank(String value) {
return value == null || value.trim().isEmpty();
}
private boolean validateRequiredFields() { private boolean validateRequiredFields() {
// Address tab validation // Address tab validation
boolean addressValid = true; boolean addressValid = true;
@@ -601,11 +660,9 @@ public class DeliveryStationDialog extends Dialog {
String value = mail.getValue(); String value = mail.getValue();
String normalizedValue = value == null ? "" : value.trim(); String normalizedValue = value == null ? "" : value.trim();
boolean empty = normalizedValue.isEmpty(); boolean empty = normalizedValue.isEmpty();
boolean required = Boolean.TRUE.equals(saveAddress.getValue());
boolean invalid = !empty && !normalizedValue.contains("@"); boolean invalid = !empty && !normalizedValue.contains("@");
boolean hasError = invalid || (required && empty); applyErrorStyling(mail, invalid);
applyErrorStyling(mail, hasError); return !invalid;
return !hasError;
} }
private void applyErrorStyling(com.vaadin.flow.component.Component field, boolean error) { private void applyErrorStyling(com.vaadin.flow.component.Component field, boolean error) {
@@ -648,10 +705,12 @@ public class DeliveryStationDialog extends Dialog {
companyField.addValueChangeListener(event -> { companyField.addValueChangeListener(event -> {
Customer customer = companyAddressOptions.get(event.getValue()); Customer customer = companyAddressOptions.get(event.getValue());
if (customer == null) { if (customer == null) {
selectedCustomerId = null;
updateSaveAddressState(); updateSaveAddressState();
return; return;
} }
selectedCustomerId = customer.getId();
if (customer.getTitle() != null if (customer.getTitle() != null
&& ("Herr".equalsIgnoreCase(customer.getTitle()) || "Frau".equalsIgnoreCase(customer.getTitle()) && ("Herr".equalsIgnoreCase(customer.getTitle()) || "Frau".equalsIgnoreCase(customer.getTitle())
|| "Divers".equalsIgnoreCase(customer.getTitle()))) { || "Divers".equalsIgnoreCase(customer.getTitle()))) {
@@ -680,27 +739,34 @@ public class DeliveryStationDialog extends Dialog {
companyField.addCustomValueSetListener(event -> { companyField.addCustomValueSetListener(event -> {
companyField.setValue(event.getDetail()); companyField.setValue(event.getDetail());
selectedCustomerId = null;
updateSaveAddressState(); updateSaveAddressState();
}); });
} }
private void updateSaveAddressState() { private void updateSaveAddressState() {
Customer selectedCustomer = companyAddressOptions.get(company.getValue()); Customer selectedCustomer = companyAddressOptions.get(company.getValue());
boolean customerSelectedFromOptions = selectedCustomer != null && matchesCurrentCustomer(selectedCustomer); boolean customerDataMatches = selectedCustomer != null && matchesCurrentCustomer(selectedCustomer);
if (customerSelectedFromOptions) { if (customerDataMatches) {
saveAddress.setValue(false); saveAddress.setValue(false);
saveAddress.setEnabled(false); saveAddress.setEnabled(false);
saveAddress.setLabel(translationHelper.getTranslation("addjob.address.save"));
updateMailRequirement(); updateMailRequirement();
return; return;
} }
saveAddress.setEnabled(true); saveAddress.setEnabled(true);
if (selectedCustomerId != null) {
saveAddress.setLabel(translationHelper.getTranslation("addjob.address.update"));
} else {
saveAddress.setLabel(translationHelper.getTranslation("addjob.address.save"));
}
updateMailRequirement(); updateMailRequirement();
} }
private void updateMailRequirement() { private void updateMailRequirement() {
mail.setRequiredIndicatorVisible(Boolean.TRUE.equals(saveAddress.getValue())); mail.setRequiredIndicatorVisible(false);
} }
private String buildCompanyAddressLabel(Customer customer) { private String buildCompanyAddressLabel(Customer customer) {
@@ -1343,10 +1409,14 @@ public class DeliveryStationDialog extends Dialog {
break; break;
case SIGNATURE: case SIGNATURE:
Span info = new Span(translationHelper.getTranslation("addjob.tasks.signature.noconfig")); TextField signatureNoteField = new TextField(
info.getStyle().set("color", "var(--lumo-secondary-text-color)"); translationHelper.getTranslation("addjob.tasks.signature.notelabel"));
info.getStyle().set("font-style", "italic"); signatureNoteField.setPlaceholder(
configContainer.add(info); translationHelper.getTranslation("addjob.tasks.signature.notelabel.placeholder"));
signatureNoteField.setWidthFull();
signatureNoteField.setValue(task.getDescription() != null ? task.getDescription() : "");
signatureNoteField.addValueChangeListener(ev -> task.setDescription(ev.getValue()));
configContainer.add(signatureNoteField);
break; break;
case TODOLIST: case TODOLIST:

View File

@@ -228,6 +228,25 @@ public class PickupStationDialog extends Dialog {
public void setCargoItems(List<CargoItem> cargoItems) { public void setCargoItems(List<CargoItem> cargoItems) {
this.cargoItems = cargoItems != null ? cargoItems : new ArrayList<>(); this.cargoItems = cargoItems != null ? cargoItems : new ArrayList<>();
} }
private org.bson.types.ObjectId customerId;
private boolean addressDiffersFromCustomer;
public org.bson.types.ObjectId getCustomerId() {
return customerId;
}
public void setCustomerId(org.bson.types.ObjectId customerId) {
this.customerId = customerId;
}
public boolean isAddressDiffersFromCustomer() {
return addressDiffersFromCustomer;
}
public void setAddressDiffersFromCustomer(boolean addressDiffersFromCustomer) {
this.addressDiffersFromCustomer = addressDiffersFromCustomer;
}
} }
public interface SaveListener { public interface SaveListener {
@@ -250,6 +269,7 @@ public class PickupStationDialog extends Dialog {
private final ComboBox<String> customerComboBox; private final ComboBox<String> customerComboBox;
private final Map<String, Customer> customerLabelMap = new LinkedHashMap<>(); private final Map<String, Customer> customerLabelMap = new LinkedHashMap<>();
private final Map<String, Customer> companyCustomerMap = new LinkedHashMap<>(); private final Map<String, Customer> companyCustomerMap = new LinkedHashMap<>();
private org.bson.types.ObjectId selectedCustomerId;
private DatePicker appointmentDatePicker; private DatePicker appointmentDatePicker;
private TimePicker appointmentTimePicker; private TimePicker appointmentTimePicker;
private Checkbox digitalProcessingCheckbox; private Checkbox digitalProcessingCheckbox;
@@ -431,14 +451,17 @@ public class PickupStationDialog extends Dialog {
customerComboBox.addValueChangeListener(ev -> { customerComboBox.addValueChangeListener(ev -> {
String selected = ev.getValue(); String selected = ev.getValue();
if (selected == null) { if (selected == null) {
selectedCustomerId = null;
updateSaveAddressState(); updateSaveAddressState();
return; return;
} }
Customer c = customerLabelMap.get(selected); Customer c = customerLabelMap.get(selected);
if (c == null) { if (c == null) {
selectedCustomerId = null;
updateSaveAddressState(); updateSaveAddressState();
return; return;
} }
selectedCustomerId = c.getId();
if (c.getCompanyName() != null) if (c.getCompanyName() != null)
company.setValue(c.getCompanyName()); company.setValue(c.getCompanyName());
else else
@@ -663,6 +686,15 @@ public class PickupStationDialog extends Dialog {
} }
} }
if (data.getCustomerId() != null) {
selectedCustomerId = data.getCustomerId();
} else {
Customer matched = customerLabelMap.get(data.getCustomerSelection());
if (matched == null) {
matched = companyCustomerMap.get(normalizeValue(data.getCompany()));
}
selectedCustomerId = matched != null ? matched.getId() : null;
}
saveAddress.setValue(data.isSaveAddress()); saveAddress.setValue(data.isSaveAddress());
updateSaveAddressState(); updateSaveAddressState();
} }
@@ -681,6 +713,8 @@ public class PickupStationDialog extends Dialog {
data.setZip(zip.getValue()); data.setZip(zip.getValue());
data.setCity(city.getValue()); data.setCity(city.getValue());
data.setSaveAddress(saveAddress.getValue()); data.setSaveAddress(saveAddress.getValue());
data.setCustomerId(selectedCustomerId);
data.setAddressDiffersFromCustomer(computeAddressDiffers());
data.setCustomerSelection(customerComboBox.getValue()); data.setCustomerSelection(customerComboBox.getValue());
if (appointmentDatePicker != null) { if (appointmentDatePicker != null) {
data.setAppointmentDate(appointmentDatePicker.getValue()); data.setAppointmentDate(appointmentDatePicker.getValue());
@@ -820,6 +854,7 @@ public class PickupStationDialog extends Dialog {
companyField.addValueChangeListener(event -> { companyField.addValueChangeListener(event -> {
String selectedCompany = event.getValue(); String selectedCompany = event.getValue();
if (selectedCompany == null || selectedCompany.trim().isEmpty()) { if (selectedCompany == null || selectedCompany.trim().isEmpty()) {
selectedCustomerId = null;
updateSaveAddressState(); updateSaveAddressState();
return; return;
} }
@@ -829,6 +864,7 @@ public class PickupStationDialog extends Dialog {
if (matchingCustomer.isPresent()) { if (matchingCustomer.isPresent()) {
Customer customer = matchingCustomer.get(); Customer customer = matchingCustomer.get();
selectedCustomerId = customer.getId();
if (customer.getTitle() != null if (customer.getTitle() != null
&& ("Herr".equalsIgnoreCase(customer.getTitle()) || "Frau".equalsIgnoreCase(customer.getTitle()) && ("Herr".equalsIgnoreCase(customer.getTitle()) || "Frau".equalsIgnoreCase(customer.getTitle())
|| "Divers".equalsIgnoreCase(customer.getTitle()))) { || "Divers".equalsIgnoreCase(customer.getTitle()))) {
@@ -859,6 +895,7 @@ public class PickupStationDialog extends Dialog {
companyField.addCustomValueSetListener(event -> { companyField.addCustomValueSetListener(event -> {
companyField.setValue(event.getDetail()); companyField.setValue(event.getDetail());
selectedCustomerId = null;
updateSaveAddressState(); updateSaveAddressState();
}); });
} }
@@ -866,17 +903,23 @@ public class PickupStationDialog extends Dialog {
private void updateSaveAddressState() { private void updateSaveAddressState() {
Customer selectedCustomer = customerLabelMap.get(customerComboBox.getValue()); Customer selectedCustomer = customerLabelMap.get(customerComboBox.getValue());
Customer selectedCompanyCustomer = companyCustomerMap.get(normalizeValue(company.getValue())); Customer selectedCompanyCustomer = companyCustomerMap.get(normalizeValue(company.getValue()));
boolean existingCustomerSelected = selectedCustomer != null && matchesCustomer(selectedCustomer); boolean customerDataMatches = selectedCustomer != null && matchesCustomer(selectedCustomer);
boolean existingCompanySelected = selectedCompanyCustomer != null && matchesCustomer(selectedCompanyCustomer); boolean companyDataMatches = selectedCompanyCustomer != null && matchesCustomer(selectedCompanyCustomer);
if (existingCustomerSelected || existingCompanySelected) { if (customerDataMatches || companyDataMatches) {
saveAddress.setValue(false); saveAddress.setValue(false);
saveAddress.setEnabled(false); saveAddress.setEnabled(false);
saveAddress.setLabel(translationHelper.getTranslation("addjob.address.save"));
updateMailRequirement(); updateMailRequirement();
return; return;
} }
saveAddress.setEnabled(true); saveAddress.setEnabled(true);
if (selectedCustomerId != null) {
saveAddress.setLabel(translationHelper.getTranslation("addjob.address.update"));
} else {
saveAddress.setLabel(translationHelper.getTranslation("addjob.address.save"));
}
updateMailRequirement(); updateMailRequirement();
} }
@@ -906,6 +949,40 @@ public class PickupStationDialog extends Dialog {
return value == null ? "" : value.trim(); return value == null ? "" : value.trim();
} }
private boolean computeAddressDiffers() {
boolean hasAnyValue = !normalizeValue(company.getValue()).isEmpty()
|| !normalizeValue(firstName.getValue()).isEmpty() || !normalizeValue(lastName.getValue()).isEmpty()
|| !normalizeValue(phone.getValue()).isEmpty() || !normalizeValue(mail.getValue()).isEmpty()
|| !normalizeValue(street.getValue()).isEmpty() || !normalizeValue(houseNumber.getValue()).isEmpty()
|| !normalizeValue(addressAddition.getValue()).isEmpty() || !normalizeValue(zip.getValue()).isEmpty()
|| !normalizeValue(city.getValue()).isEmpty();
if (!hasAnyValue) {
return false;
}
if (selectedCustomerId == null) {
return true;
}
Customer linked = findCustomerById(selectedCustomerId);
return linked == null || !matchesCustomer(linked);
}
private Customer findCustomerById(org.bson.types.ObjectId id) {
if (id == null) {
return null;
}
for (Customer c : customerLabelMap.values()) {
if (c != null && id.equals(c.getId())) {
return c;
}
}
for (Customer c : companyCustomerMap.values()) {
if (c != null && id.equals(c.getId())) {
return c;
}
}
return null;
}
// ============================================ // ============================================
// Appointments & Processing Tab // Appointments & Processing Tab
// ============================================ // ============================================

View File

@@ -13,4 +13,6 @@ public interface CustomerRepository extends MongoRepository<Customer, ObjectId>
Slice<Customer> findAllBy(Pageable pageable); Slice<Customer> findAllBy(Pageable pageable);
List<Customer> findByOwner(ObjectId owner); List<Customer> findByOwner(ObjectId owner);
List<Customer> findByOwnerAndInternalFalse(ObjectId owner);
} }

View File

@@ -12,10 +12,13 @@ import org.springframework.transaction.annotation.Transactional;
public class AddCustomerService { public class AddCustomerService {
private final AddCustomerRepository addCustomerRepository; private final AddCustomerRepository addCustomerRepository;
private final SecurityService securityService; private final SecurityService securityService;
private final SequenceGeneratorService sequenceGeneratorService;
AddCustomerService(AddCustomerRepository addCustomerRepository, SecurityService securityService) { AddCustomerService(AddCustomerRepository addCustomerRepository, SecurityService securityService,
SequenceGeneratorService sequenceGeneratorService) {
this.addCustomerRepository = addCustomerRepository; this.addCustomerRepository = addCustomerRepository;
this.securityService = securityService; this.securityService = securityService;
this.sequenceGeneratorService = sequenceGeneratorService;
} }
public void addCustomer(Customer customer) { public void addCustomer(Customer customer) {
@@ -25,6 +28,35 @@ public class AddCustomerService {
de.assecutor.votianlt.model.User currentUser = securityService.getCurrentDatabaseUser(); de.assecutor.votianlt.model.User currentUser = securityService.getCurrentDatabaseUser();
customer.setCreatedBy(currentUser.getId()); customer.setCreatedBy(currentUser.getId());
customer.setOwner(currentUser.getId()); customer.setOwner(currentUser.getId());
if (customer.getUsrId() == null) {
customer.setUsrId(sequenceGeneratorService.nextCustomerNumber());
}
addCustomerRepository.save(customer);
}
public void addInternalCustomer(Customer customer) {
if (customer == null) {
return;
}
customer.setId(null);
customer.setInternal(true);
addCustomer(customer);
}
public void updateCustomer(Customer customer) {
if (customer == null || customer.getId() == null) {
throw new IllegalArgumentException("Kunden-ID fehlt");
}
validateCustomer(customer);
Customer existing = addCustomerRepository.findById(customer.getId())
.orElseThrow(() -> new IllegalArgumentException("Kunde nicht gefunden"));
customer.setCreatedBy(existing.getCreatedBy());
customer.setOwner(existing.getOwner());
if (customer.getUsrId() == null) {
customer.setUsrId(existing.getUsrId());
}
addCustomerRepository.save(customer); addCustomerRepository.save(customer);
} }
@@ -35,13 +67,10 @@ public class AddCustomerService {
} }
String mail = customer.getMail() != null ? customer.getMail().trim() : ""; String mail = customer.getMail() != null ? customer.getMail().trim() : "";
if (mail.isEmpty()) { if (!mail.isEmpty() && !mail.contains("@")) {
throw new IllegalArgumentException("E-Mail-Adresse ist ein Pflichtfeld");
}
if (!mail.contains("@")) {
throw new IllegalArgumentException("Bitte geben Sie eine gültige E-Mail-Adresse ein"); throw new IllegalArgumentException("Bitte geben Sie eine gültige E-Mail-Adresse ein");
} }
customer.setMail(mail); customer.setMail(mail.isEmpty() ? null : mail);
} }
} }

View File

@@ -32,7 +32,7 @@ public class CustomerService {
public List<Customer> findAllForCurrentOwner() { public List<Customer> findAllForCurrentOwner() {
ObjectId ownerId = securityService.getCurrentUserId(); ObjectId ownerId = securityService.getCurrentUserId();
return todoRepository.findByOwner(ownerId); return todoRepository.findByOwnerAndInternalFalse(ownerId);
} }
public Customer save(Customer customer) { public Customer save(Customer customer) {

View File

@@ -0,0 +1,54 @@
package de.assecutor.votianlt.pages.service;
import de.assecutor.votianlt.model.Counter;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
@Service
public class SequenceGeneratorService {
public static final String CUSTOMER_NUMBER_SEQ = "customerNumber";
public static final int CUSTOMER_NUMBER_START = 10000;
private final MongoTemplate mongoTemplate;
public SequenceGeneratorService(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
public int nextCustomerNumber() {
return (int) nextSequence(CUSTOMER_NUMBER_SEQ, CUSTOMER_NUMBER_START);
}
private long nextSequence(String sequenceId, long startValue) {
ensureInitialized(sequenceId, startValue - 1);
Counter updated = mongoTemplate.findAndModify(
Query.query(Criteria.where("_id").is(sequenceId)),
new Update().inc("sequence", 1),
FindAndModifyOptions.options().returnNew(true),
Counter.class);
return updated != null ? updated.getSequence() : startValue;
}
private void ensureInitialized(String sequenceId, long initialValue) {
boolean exists = mongoTemplate.exists(
Query.query(Criteria.where("_id").is(sequenceId)),
Counter.class);
if (exists) {
return;
}
Counter counter = new Counter();
counter.setId(sequenceId);
counter.setSequence(initialValue);
try {
mongoTemplate.insert(counter);
} catch (DuplicateKeyException ignored) {
// Ein anderer Thread hat den Counter gleichzeitig angelegt — passt.
}
}
}

View File

@@ -81,9 +81,8 @@ public class AddCustomerView extends Main implements HasDynamicTitle {
fax = new TextField(getTranslation("profile.fax")); fax = new TextField(getTranslation("profile.fax"));
fax.setWidthFull(); fax.setWidthFull();
// E-Mail (Pflichtfeld) // E-Mail (optional)
mail = new TextField(getTranslation("profile.email")); mail = new TextField(getTranslation("profile.email"));
mail.setRequiredIndicatorVisible(true);
mail.setWidthFull(); mail.setWidthFull();
mail.addBlurListener(e -> validateEmail()); mail.addBlurListener(e -> validateEmail());
@@ -179,8 +178,9 @@ public class AddCustomerView extends Main implements HasDynamicTitle {
binder.forField(fax).bind(Customer::getFax, Customer::setFax); binder.forField(fax).bind(Customer::getFax, Customer::setFax);
binder.forField(mail).asRequired(getTranslation("profile.validation.email.required")) binder.forField(mail)
.withValidator(email -> email.contains("@"), getTranslation("profile.validation.email.invalid")) .withValidator(email -> email == null || email.isBlank() || email.contains("@"),
getTranslation("profile.validation.email.invalid"))
.bind(Customer::getMail, Customer::setMail); .bind(Customer::getMail, Customer::setMail);
binder.forField(street).asRequired(getTranslation("profile.validation.street.required")) binder.forField(street).asRequired(getTranslation("profile.validation.street.required"))
@@ -247,10 +247,7 @@ public class AddCustomerView extends Main implements HasDynamicTitle {
private void validateEmail() { private void validateEmail() {
String value = mail.getValue(); String value = mail.getValue();
if (value == null || value.trim().isEmpty()) { if (value != null && !value.trim().isEmpty() && !value.contains("@")) {
mail.setInvalid(true);
mail.setErrorMessage(getTranslation("profile.validation.email.required"));
} else if (!value.contains("@")) {
mail.setInvalid(true); mail.setInvalid(true);
mail.setErrorMessage(getTranslation("profile.validation.email.invalid")); mail.setErrorMessage(getTranslation("profile.validation.email.invalid"));
} else { } else {

View File

@@ -137,12 +137,16 @@ public class AddJobView extends Main implements HasDynamicTitle {
private TextField pickupZip; private TextField pickupZip;
private TextField pickupCity; private TextField pickupCity;
private Checkbox savePickupAddress; private Checkbox savePickupAddress;
private org.bson.types.ObjectId pickupCustomerId;
private boolean pickupAddressDiffers;
// Delivery stations as tiles in a 3x3 grid (max 7 delivery + 1 pickup + 1 plus // Delivery stations as tiles in a 3x3 grid (max 7 delivery + 1 pickup + 1 plus
// = 9) // = 9)
private final List<StationTile> deliveryStationTilesList = new ArrayList<>(); private final List<StationTile> deliveryStationTilesList = new ArrayList<>();
private final List<DeliveryStation> deliveryStationsState = new ArrayList<>(); private final List<DeliveryStation> deliveryStationsState = new ArrayList<>();
private final List<Boolean> deliveryStationsSaveAddress = new ArrayList<>(); private final List<Boolean> deliveryStationsSaveAddress = new ArrayList<>();
private final List<org.bson.types.ObjectId> deliveryStationsCustomerId = new ArrayList<>();
private final List<Boolean> deliveryStationsAddressDiffers = new ArrayList<>();
private final List<String> deliveryStationsMailState = new ArrayList<>(); private final List<String> deliveryStationsMailState = new ArrayList<>();
private final List<Div> deliveryStationSlotList = new ArrayList<>(); private final List<Div> deliveryStationSlotList = new ArrayList<>();
private final List<Span> deliveryStationDistanceChips = new ArrayList<>(); private final List<Span> deliveryStationDistanceChips = new ArrayList<>();
@@ -721,6 +725,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
// Add empty state for this station // Add empty state for this station
deliveryStationsState.add(new DeliveryStation()); deliveryStationsState.add(new DeliveryStation());
deliveryStationsSaveAddress.add(true); deliveryStationsSaveAddress.add(true);
deliveryStationsCustomerId.add(null);
deliveryStationsAddressDiffers.add(false);
deliveryStationsMailState.add(null); deliveryStationsMailState.add(null);
deliveryStationsValidatedByGoogle.add(false); deliveryStationsValidatedByGoogle.add(false);
@@ -769,6 +775,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
deliveryStationTilesList.remove(removeIdx); deliveryStationTilesList.remove(removeIdx);
deliveryStationsState.remove(removeIdx); deliveryStationsState.remove(removeIdx);
deliveryStationsSaveAddress.remove(removeIdx); deliveryStationsSaveAddress.remove(removeIdx);
deliveryStationsCustomerId.remove(removeIdx);
deliveryStationsAddressDiffers.remove(removeIdx);
deliveryStationsMailState.remove(removeIdx); deliveryStationsMailState.remove(removeIdx);
deliveryStationsValidatedByGoogle.remove(removeIdx); deliveryStationsValidatedByGoogle.remove(removeIdx);
deliveryStationTasksState.remove(removeIdx); deliveryStationTasksState.remove(removeIdx);
@@ -867,6 +875,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
pickupZip.setValue(data.getZip() != null ? data.getZip() : ""); pickupZip.setValue(data.getZip() != null ? data.getZip() : "");
pickupCity.setValue(data.getCity() != null ? data.getCity() : ""); pickupCity.setValue(data.getCity() != null ? data.getCity() : "");
savePickupAddress.setValue(data.isSaveAddress()); savePickupAddress.setValue(data.isSaveAddress());
pickupCustomerId = data.getCustomerId();
pickupAddressDiffers = data.isAddressDiffersFromCustomer();
// Sync appointment fields for binder/submit // Sync appointment fields for binder/submit
pickupDate.setValue(data.getAppointmentDate()); pickupDate.setValue(data.getAppointmentDate());
@@ -913,6 +923,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
currentData.setZip(pickupZip.getValue()); currentData.setZip(pickupZip.getValue());
currentData.setCity(pickupCity.getValue()); currentData.setCity(pickupCity.getValue());
currentData.setSaveAddress(savePickupAddress.getValue()); currentData.setSaveAddress(savePickupAddress.getValue());
currentData.setCustomerId(pickupCustomerId);
currentData.setCustomerSelection(customerSelection.getValue()); currentData.setCustomerSelection(customerSelection.getValue());
// Pre-fill pickup-specific fields // Pre-fill pickup-specific fields
currentData.setAppointmentDate(pickupDate.getValue()); currentData.setAppointmentDate(pickupDate.getValue());
@@ -1137,6 +1148,14 @@ public class AddJobView extends Main implements HasDynamicTitle {
station.setCity(data.getCity()); station.setCity(data.getCity());
station.setTasks(data.getTasks() != null ? new ArrayList<>(data.getTasks()) : new ArrayList<>()); station.setTasks(data.getTasks() != null ? new ArrayList<>(data.getTasks()) : new ArrayList<>());
deliveryStationsSaveAddress.set(idx, data.isSaveAddress()); deliveryStationsSaveAddress.set(idx, data.isSaveAddress());
while (deliveryStationsCustomerId.size() <= idx) {
deliveryStationsCustomerId.add(null);
}
deliveryStationsCustomerId.set(idx, data.getCustomerId());
while (deliveryStationsAddressDiffers.size() <= idx) {
deliveryStationsAddressDiffers.add(false);
}
deliveryStationsAddressDiffers.set(idx, data.isAddressDiffersFromCustomer());
deliveryStationsMailState.set(idx, trimToNull(data.getMail())); deliveryStationsMailState.set(idx, trimToNull(data.getMail()));
// Store tasks for this delivery station // Store tasks for this delivery station
@@ -1182,6 +1201,9 @@ public class AddJobView extends Main implements HasDynamicTitle {
currentData.setZip(station.getZip()); currentData.setZip(station.getZip());
currentData.setCity(station.getCity()); currentData.setCity(station.getCity());
currentData.setSaveAddress(deliveryStationsSaveAddress.get(actualIndex)); currentData.setSaveAddress(deliveryStationsSaveAddress.get(actualIndex));
if (actualIndex < deliveryStationsCustomerId.size()) {
currentData.setCustomerId(deliveryStationsCustomerId.get(actualIndex));
}
if (actualIndex < deliveryStationsValidatedByGoogle.size()) { if (actualIndex < deliveryStationsValidatedByGoogle.size()) {
currentData.setAddressValidatedByGoogle(deliveryStationsValidatedByGoogle.get(actualIndex)); currentData.setAddressValidatedByGoogle(deliveryStationsValidatedByGoogle.get(actualIndex));
} }
@@ -1817,39 +1839,60 @@ public class AddJobView extends Main implements HasDynamicTitle {
return; return;
} }
// NEU: Kunden anlegen, wenn Checkboxen aktiviert // Kunden anlegen/aktualisieren bzw. intern sichern
Customer pickupCustomer = new Customer();
pickupCustomer.setCompanyName(pickupCompany.getValue());
pickupCustomer.setTitle(pickupSalutation.getValue());
pickupCustomer.setFirstname(pickupFirstName.getValue());
pickupCustomer.setLastName(pickupLastName.getValue());
pickupCustomer.setTelephone(pickupPhone.getValue());
pickupCustomer.setMail(pickupMail);
pickupCustomer.setStreet(pickupStreet.getValue());
pickupCustomer.setHouseNumber(pickupHouseNumber.getValue());
pickupCustomer.setAddressAddition(pickupAddressAddition.getValue());
pickupCustomer.setZip(pickupZip.getValue());
pickupCustomer.setCity(pickupCity.getValue());
if (savePickupAddress.getValue()) { if (savePickupAddress.getValue()) {
Customer pickupCustomer = new Customer(); if (pickupCustomerId != null) {
pickupCustomer.setCompanyName(pickupCompany.getValue()); pickupCustomer.setId(pickupCustomerId);
pickupCustomer.setTitle(pickupSalutation.getValue()); addCustomerService.updateCustomer(pickupCustomer);
pickupCustomer.setFirstname(pickupFirstName.getValue()); } else {
pickupCustomer.setLastName(pickupLastName.getValue()); addCustomerService.addCustomer(pickupCustomer);
pickupCustomer.setTelephone(pickupPhone.getValue()); }
pickupCustomer.setMail(pickupMail); } else if (pickupAddressDiffers) {
pickupCustomer.setStreet(pickupStreet.getValue()); addCustomerService.addInternalCustomer(pickupCustomer);
pickupCustomer.setHouseNumber(pickupHouseNumber.getValue());
pickupCustomer.setAddressAddition(pickupAddressAddition.getValue());
pickupCustomer.setZip(pickupZip.getValue());
pickupCustomer.setCity(pickupCity.getValue());
addCustomerService.addCustomer(pickupCustomer);
} }
// Save delivery station addresses as customers if checkbox is checked // Delivery-Stationen: anlegen, aktualisieren oder als intern sichern
for (int i = 0; i < deliveryStationsState.size(); i++) { for (int i = 0; i < deliveryStationsState.size(); i++) {
if (i < deliveryStationsSaveAddress.size() && deliveryStationsSaveAddress.get(i)) { DeliveryStation ds = deliveryStationsState.get(i);
DeliveryStation ds = deliveryStationsState.get(i); Customer deliveryCustomer = new Customer();
Customer deliveryCustomer = new Customer(); deliveryCustomer.setCompanyName(ds.getCompany());
deliveryCustomer.setCompanyName(ds.getCompany()); deliveryCustomer.setTitle(ds.getSalutation());
deliveryCustomer.setTitle(ds.getSalutation()); deliveryCustomer.setFirstname(ds.getFirstName());
deliveryCustomer.setFirstname(ds.getFirstName()); deliveryCustomer.setLastName(ds.getLastName());
deliveryCustomer.setLastName(ds.getLastName()); deliveryCustomer.setTelephone(ds.getPhone());
deliveryCustomer.setTelephone(ds.getPhone()); deliveryCustomer.setMail(i < deliveryStationsMailState.size() ? deliveryStationsMailState.get(i) : null);
deliveryCustomer.setMail(i < deliveryStationsMailState.size() ? deliveryStationsMailState.get(i) : null); deliveryCustomer.setStreet(ds.getStreet());
deliveryCustomer.setStreet(ds.getStreet()); deliveryCustomer.setHouseNumber(ds.getHouseNumber());
deliveryCustomer.setHouseNumber(ds.getHouseNumber()); deliveryCustomer.setAddressAddition(ds.getAddressAddition());
deliveryCustomer.setAddressAddition(ds.getAddressAddition()); deliveryCustomer.setZip(ds.getZip());
deliveryCustomer.setZip(ds.getZip()); deliveryCustomer.setCity(ds.getCity());
deliveryCustomer.setCity(ds.getCity()); boolean saveRequested = i < deliveryStationsSaveAddress.size()
addCustomerService.addCustomer(deliveryCustomer); && deliveryStationsSaveAddress.get(i);
org.bson.types.ObjectId existingId = i < deliveryStationsCustomerId.size()
? deliveryStationsCustomerId.get(i)
: null;
boolean addressDiffers = i < deliveryStationsAddressDiffers.size()
&& deliveryStationsAddressDiffers.get(i);
if (saveRequested) {
if (existingId != null) {
deliveryCustomer.setId(existingId);
addCustomerService.updateCustomer(deliveryCustomer);
} else {
addCustomerService.addCustomer(deliveryCustomer);
}
} else if (addressDiffers) {
addCustomerService.addInternalCustomer(deliveryCustomer);
} }
} }
@@ -2119,6 +2162,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
deliveryStationTilesList.remove(idx); deliveryStationTilesList.remove(idx);
deliveryStationsState.remove(idx); deliveryStationsState.remove(idx);
deliveryStationsSaveAddress.remove(idx); deliveryStationsSaveAddress.remove(idx);
deliveryStationsCustomerId.remove(idx);
deliveryStationsAddressDiffers.remove(idx);
deliveryStationsMailState.remove(idx); deliveryStationsMailState.remove(idx);
deliveryStationsValidatedByGoogle.remove(idx); deliveryStationsValidatedByGoogle.remove(idx);
deliveryStationTasksState.remove(idx); deliveryStationTasksState.remove(idx);

View File

@@ -36,7 +36,8 @@ public class CustomersView extends Main implements HasDynamicTitle {
createBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY); createBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
todoGrid = new Grid<>(); todoGrid = new Grid<>();
todoGrid.setItems(query -> todoService.list(toSpringPageRequest(query)).stream()); todoGrid.setItems(query -> todoService.list(toSpringPageRequest(query)).stream()
.filter(c -> !c.isInternal()));
todoGrid.addColumn(Customer::getCompanyName).setHeader(getTranslation("customers.column.company")); todoGrid.addColumn(Customer::getCompanyName).setHeader(getTranslation("customers.column.company"));
todoGrid.setSizeFull(); todoGrid.setSizeFull();
todoGrid.addClassName("data-grid"); todoGrid.addClassName("data-grid");

View File

@@ -0,0 +1,152 @@
package de.assecutor.votianlt.pages.view;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasDynamicTitle;
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.theme.lumo.LumoUtility;
import de.assecutor.votianlt.model.Job;
import de.assecutor.votianlt.model.JobHistoryType;
import de.assecutor.votianlt.model.JobStatus;
import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar;
import de.assecutor.votianlt.repository.JobRepository;
import de.assecutor.votianlt.security.SecurityService;
import de.assecutor.votianlt.service.JobHistoryService;
import jakarta.annotation.security.RolesAllowed;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
import java.time.LocalDateTime;
@Route(value = "job_manual_complete", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
@RolesAllowed("USER")
@Slf4j
public class JobManualCompleteView extends Main implements HasUrlParameter<String>, HasDynamicTitle {
private final JobRepository jobRepository;
private final JobHistoryService jobHistoryService;
private final SecurityService securityService;
private final VerticalLayout content;
public JobManualCompleteView(JobRepository jobRepository, JobHistoryService jobHistoryService,
SecurityService securityService) {
this.jobRepository = jobRepository;
this.jobHistoryService = jobHistoryService;
this.securityService = securityService;
setSizeFull();
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL);
addClassName("data-view");
add(new ViewToolbar(getTranslation("jobsummary.dialog.manualcomplete.title")));
content = new VerticalLayout();
content.setSpacing(true);
content.setPadding(true);
content.setWidthFull();
content.addClassNames("form-shell", "form-card");
add(content);
}
@Override
public String getPageTitle() {
return getTranslation("jobsummary.dialog.manualcomplete.title");
}
@Override
public void setParameter(BeforeEvent event, String parameter) {
content.removeAll();
if (parameter == null || parameter.isBlank()) {
content.add(new Span(getTranslation("jobhistory.error.no.id")));
return;
}
ObjectId jobId;
try {
jobId = new ObjectId(parameter);
} catch (Exception e) {
content.add(new Span(getTranslation("jobhistory.error.invalid.id", parameter)));
return;
}
Job job = jobRepository.findById(jobId).orElse(null);
if (job == null) {
content.add(new Span(getTranslation("jobhistory.error.not.found", parameter)));
return;
}
render(job);
}
private void render(Job job) {
Span warningText = new Span(getTranslation("jobsummary.dialog.manualcomplete.text", job.getJobNumber()));
warningText.getStyle().set("color", "var(--lumo-error-text-color)");
TextArea reasonField = new TextArea(getTranslation("jobsummary.dialog.manualcomplete.reason"));
reasonField.setWidthFull();
reasonField.setMinHeight("100px");
reasonField.setRequired(true);
content.add(warningText, reasonField);
HorizontalLayout buttonBar = new HorizontalLayout();
buttonBar.setWidthFull();
buttonBar.setJustifyContentMode(HorizontalLayout.JustifyContentMode.END);
buttonBar.setSpacing(true);
Button cancelButton = new Button(getTranslation("jobsummary.dialog.manualcomplete.cancel"),
e -> getUI().ifPresent(ui -> ui.navigate("job_summary/" + job.getId().toHexString())));
Button confirmButton = new Button(getTranslation("jobsummary.dialog.manualcomplete.confirm"));
confirmButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
confirmButton.addClickListener(e -> {
String reason = reasonField.getValue();
if (reason == null || reason.trim().isEmpty()) {
reasonField.setInvalid(true);
reasonField.setErrorMessage(getTranslation("jobsummary.dialog.manualcomplete.reason.required"));
return;
}
try {
JobStatus oldStatus = job.getStatus();
job.setStatus(JobStatus.COMPLETED);
job.setUpdatedAt(LocalDateTime.now());
jobRepository.save(job);
String currentUser = securityService.getCurrentUsername();
jobHistoryService.logStatusChange(job, oldStatus, JobStatus.COMPLETED, currentUser);
String description = String.format("Auftrag manuell beendet von %s. Begründung: %s",
currentUser, reason.trim());
jobHistoryService.logCustomEvent(job.getId(),
getTranslation("jobsummary.history.manualcomplete.reason"),
description, currentUser, JobHistoryType.STATUS_CHANGE);
Notification
.show(getTranslation("jobsummary.notification.completed", job.getJobNumber()), 3000,
Notification.Position.BOTTOM_END)
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
getUI().ifPresent(ui -> ui.navigate("job_summary/" + job.getId().toHexString()));
} catch (Exception ex) {
Notification
.show(getTranslation("jobsummary.notification.complete.error", ex.getMessage()), 5000,
Notification.Position.BOTTOM_END)
.addThemeVariants(NotificationVariant.LUMO_ERROR);
}
});
buttonBar.add(cancelButton, confirmButton);
content.add(buttonBar);
}
}

View File

@@ -14,7 +14,6 @@ import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.NotificationVariant; import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.icon.Icon; import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.AttachEvent; import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.DetachEvent; import com.vaadin.flow.component.DetachEvent;
import com.vaadin.flow.component.UI; import com.vaadin.flow.component.UI;
@@ -60,8 +59,6 @@ import de.assecutor.votianlt.service.JobUpdateBroadcaster;
import de.assecutor.votianlt.service.LocationService; import de.assecutor.votianlt.service.LocationService;
import de.assecutor.votianlt.service.MessageService; import de.assecutor.votianlt.service.MessageService;
import de.assecutor.votianlt.service.TaskAssignmentService; import de.assecutor.votianlt.service.TaskAssignmentService;
import de.assecutor.votianlt.model.JobHistoryType;
import de.assecutor.votianlt.security.SecurityService;
import de.assecutor.votianlt.util.DateTimeFormatUtil; import de.assecutor.votianlt.util.DateTimeFormatUtil;
import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RolesAllowed;
import org.bson.types.ObjectId; import org.bson.types.ObjectId;
@@ -91,7 +88,6 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
private final LocationService locationService; private final LocationService locationService;
private final ServiceRepository serviceRepository; private final ServiceRepository serviceRepository;
private final TaskAssignmentService taskAssignmentService; private final TaskAssignmentService taskAssignmentService;
private final SecurityService securityService;
@Value("${app.google.maps.api-key}") @Value("${app.google.maps.api-key}")
private String googleMapsApiKey; private String googleMapsApiKey;
@@ -107,8 +103,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
PhotoRepository photoRepository, CommentRepository commentRepository, AppUserService appUserService, PhotoRepository photoRepository, CommentRepository commentRepository, AppUserService appUserService,
MessageService messageService, JobHistoryService jobHistoryService, MessageService messageService, JobHistoryService jobHistoryService,
JobUpdateBroadcaster jobUpdateBroadcaster, LocationService locationService, JobUpdateBroadcaster jobUpdateBroadcaster, LocationService locationService,
ServiceRepository serviceRepository, TaskAssignmentService taskAssignmentService, ServiceRepository serviceRepository, TaskAssignmentService taskAssignmentService) {
SecurityService securityService) {
this.jobRepository = jobRepository; this.jobRepository = jobRepository;
this.cargoItemRepository = cargoItemRepository; this.cargoItemRepository = cargoItemRepository;
this.signatureRepository = signatureRepository; this.signatureRepository = signatureRepository;
@@ -121,7 +116,6 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
this.locationService = locationService; this.locationService = locationService;
this.serviceRepository = serviceRepository; this.serviceRepository = serviceRepository;
this.taskAssignmentService = taskAssignmentService; this.taskAssignmentService = taskAssignmentService;
this.securityService = securityService;
setSizeFull(); setSizeFull();
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN, addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
@@ -224,7 +218,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
&& job.getStatus() != JobStatus.CANCELLED) { && job.getStatus() != JobStatus.CANCELLED) {
manualCompleteButton = new Button(getTranslation("jobsummary.button.manualcomplete")); manualCompleteButton = new Button(getTranslation("jobsummary.button.manualcomplete"));
manualCompleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR); manualCompleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
manualCompleteButton.addClickListener(e -> openManualCompleteDialog(job)); manualCompleteButton.addClickListener(e -> getUI()
.ifPresent(ui -> ui.navigate("job_manual_complete/" + job.getId().toHexString())));
} }
// Create Job History Button for toolbar // Create Job History Button for toolbar
@@ -379,75 +374,6 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
} }
} }
private void openManualCompleteDialog(Job job) {
Dialog dialog = DialogStylingHelper.createStyledDialog(
getTranslation("jobsummary.dialog.manualcomplete.title"), "560px");
VerticalLayout dialogContent = DialogStylingHelper.createContentLayout("520px");
Span warningText = new Span(getTranslation("jobsummary.dialog.manualcomplete.text", job.getJobNumber()));
warningText.getStyle().set("color", "var(--lumo-error-text-color)");
TextArea reasonField = new TextArea(getTranslation("jobsummary.dialog.manualcomplete.reason"));
reasonField.setWidthFull();
reasonField.setMinHeight("100px");
reasonField.setRequired(true);
dialogContent.add(warningText, reasonField);
dialog.add(DialogStylingHelper.wrapContent(dialogContent));
HorizontalLayout buttonBar = new HorizontalLayout();
buttonBar.setWidthFull();
buttonBar.setJustifyContentMode(HorizontalLayout.JustifyContentMode.END);
buttonBar.setSpacing(true);
Button cancelButton = new Button(getTranslation("jobsummary.dialog.manualcomplete.cancel"),
e -> dialog.close());
Button confirmButton = new Button(getTranslation("jobsummary.dialog.manualcomplete.confirm"));
confirmButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
confirmButton.addClickListener(e -> {
String reason = reasonField.getValue();
if (reason == null || reason.trim().isEmpty()) {
reasonField.setInvalid(true);
reasonField.setErrorMessage(getTranslation("jobsummary.dialog.manualcomplete.reason.required"));
return;
}
try {
JobStatus oldStatus = job.getStatus();
job.setStatus(JobStatus.COMPLETED);
job.setUpdatedAt(LocalDateTime.now());
jobRepository.save(job);
String currentUser = securityService.getCurrentUsername();
jobHistoryService.logStatusChange(job, oldStatus, JobStatus.COMPLETED, currentUser);
String description = String.format("Auftrag manuell beendet von %s. Begründung: %s",
currentUser, reason.trim());
jobHistoryService.logCustomEvent(job.getId(),
getTranslation("jobsummary.history.manualcomplete.reason"),
description, currentUser, JobHistoryType.STATUS_CHANGE);
dialog.close();
Notification
.show(getTranslation("jobsummary.notification.completed", job.getJobNumber()), 3000,
Notification.Position.BOTTOM_END)
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
getUI().ifPresent(ui -> ui.navigate("job_summary/" + job.getId().toHexString()));
} catch (Exception ex) {
Notification
.show(getTranslation("jobsummary.notification.complete.error", ex.getMessage()), 5000,
Notification.Position.BOTTOM_END)
.addThemeVariants(NotificationVariant.LUMO_ERROR);
}
});
buttonBar.add(cancelButton, confirmButton);
dialog.getFooter().add(buttonBar);
dialog.open();
}
private VerticalLayout borderedBox() { private VerticalLayout borderedBox() {
VerticalLayout box = new VerticalLayout(); VerticalLayout box = new VerticalLayout();
box.addClassName("summary-card"); box.addClassName("summary-card");

View File

@@ -86,7 +86,8 @@ public class ShowCustomersView extends VerticalLayout implements HasDynamicTitle
var customers = customerService.findAll(); var customers = customerService.findAll();
var currentUserId = securityService.getCurrentUserId(); var currentUserId = securityService.getCurrentUserId();
var ownCustomers = customers.stream() var ownCustomers = customers.stream()
.filter(c -> c.getCreatedBy() != null && c.getCreatedBy().equals(currentUserId)).toList(); .filter(c -> c.getCreatedBy() != null && c.getCreatedBy().equals(currentUserId))
.filter(c -> !c.isInternal()).toList();
grid.setItems(ownCustomers); grid.setItems(ownCustomers);
} }

View File

@@ -10,4 +10,6 @@ import java.util.List;
@Repository @Repository
public interface SignatureRepository extends MongoRepository<Signature, ObjectId> { public interface SignatureRepository extends MongoRepository<Signature, ObjectId> {
List<Signature> findByTaskId(ObjectId taskId); List<Signature> findByTaskId(ObjectId taskId);
List<Signature> findByTaskIdOrderByCreatedAtDesc(ObjectId taskId);
} }

View File

@@ -5,7 +5,7 @@ dialog.confirm=Bestätigen
# Navigation and Main Layout # Navigation and Main Layout
nav.jobs=Aufträge nav.jobs=Aufträge
nav.job.create=Auftragserstellung nav.job.create=Auftragserstellung
nav.customers=Kunden nav.customers=Adressbuch
nav.appusers=App-Nutzer nav.appusers=App-Nutzer
nav.statistics=Statistiken nav.statistics=Statistiken
nav.invoices=Rechnungen nav.invoices=Rechnungen
@@ -31,7 +31,7 @@ profile.lastname=Nachname
profile.phone=Telefonnummer profile.phone=Telefonnummer
profile.fax=Telefon (Fax) profile.fax=Telefon (Fax)
profile.mobile=Telefon (Mobil) profile.mobile=Telefon (Mobil)
profile.email=E-Mail-Adresse (Login)* profile.email=E-Mail-Adresse
profile.street=Straße profile.street=Straße
profile.housenr=Hausnr profile.housenr=Hausnr
profile.addressadd=Adresszusatz profile.addressadd=Adresszusatz
@@ -447,7 +447,8 @@ addjob.address.city.placeholder.pickup=Ort (Abholung)
addjob.address.city.placeholder.delivery=Ort (Lieferung) addjob.address.city.placeholder.delivery=Ort (Lieferung)
addjob.address.delivery.street.placeholder=Straße (Lieferung) addjob.address.delivery.street.placeholder=Straße (Lieferung)
addjob.address.delivery.addition.placeholder=Adresszusatz (Lieferung) addjob.address.delivery.addition.placeholder=Adresszusatz (Lieferung)
addjob.address.save=Adresse speichern addjob.address.save=Adresse in Adressbuch übernehmen
addjob.address.update=Adresse im Adressbuch aktualisieren
addjob.section.pickup=Abholung addjob.section.pickup=Abholung
addjob.section.delivery=Lieferung addjob.section.delivery=Lieferung
addjob.stations.apply=Stationen \u00fcbernehmen addjob.stations.apply=Stationen \u00fcbernehmen
@@ -513,7 +514,8 @@ addjob.tasks.photo.min=Min. Fotos
addjob.tasks.photo.max=Max. Fotos addjob.tasks.photo.max=Max. Fotos
addjob.tasks.barcode.min=Min. Barcodes addjob.tasks.barcode.min=Min. Barcodes
addjob.tasks.barcode.max=Max. Barcodes addjob.tasks.barcode.max=Max. Barcodes
addjob.tasks.signature.noconfig=Keine Konfiguration erforderlich addjob.tasks.signature.notelabel=Bemerkung (optional)
addjob.tasks.signature.notelabel.placeholder=Hinweistext für die Bemerkung eingeben
addjob.tasks.todolist.title=To-Do Liste addjob.tasks.todolist.title=To-Do Liste
addjob.tasks.todolist.item.placeholder=To-Do eingeben addjob.tasks.todolist.item.placeholder=To-Do eingeben
addjob.tasks.todolist.add=To-Do hinzufügen addjob.tasks.todolist.add=To-Do hinzufügen

View File

@@ -3,7 +3,7 @@ dialog.cancel=T\u00fchista
dialog.confirm=Kinnita dialog.confirm=Kinnita
nav.jobs=Tellimused nav.jobs=Tellimused
nav.job.create=Tellimuse loomine nav.job.create=Tellimuse loomine
nav.customers=Kliendid nav.customers=Aadressiraamat
nav.appusers=\u00c4pikasutajad nav.appusers=\u00c4pikasutajad
nav.statistics=Statistika nav.statistics=Statistika
nav.invoices=Arved nav.invoices=Arved
@@ -27,7 +27,7 @@ profile.lastname=Perekonnanimi
profile.phone=Telefoninumber profile.phone=Telefoninumber
profile.fax=Telefon (faks) profile.fax=Telefon (faks)
profile.mobile=Telefon (mobiil) profile.mobile=Telefon (mobiil)
profile.email=E-posti aadress (sisselogimine)* profile.email=E-posti aadress
profile.street=T\u00e4nav profile.street=T\u00e4nav
profile.housenr=Majanumber profile.housenr=Majanumber
profile.addressadd=Aadressi t\u00e4iend profile.addressadd=Aadressi t\u00e4iend
@@ -396,7 +396,8 @@ addjob.address.city.placeholder.pickup=Asukoht (pealekorje)
addjob.address.city.placeholder.delivery=Asukoht (kohaletoimetamine) addjob.address.city.placeholder.delivery=Asukoht (kohaletoimetamine)
addjob.address.delivery.street.placeholder=T\u00e4nav (kohaletoimetamine) addjob.address.delivery.street.placeholder=T\u00e4nav (kohaletoimetamine)
addjob.address.delivery.addition.placeholder=Aadressi t\u00e4iend (kohaletoimetamine) addjob.address.delivery.addition.placeholder=Aadressi t\u00e4iend (kohaletoimetamine)
addjob.address.save=Salvesta aadress addjob.address.save=Lisa aadress aadressiraamatusse
addjob.address.update=Uuenda aadressi aadressiraamatus
addjob.section.pickup=Pealekorje addjob.section.pickup=Pealekorje
addjob.section.delivery=Kohaletoimetamine addjob.section.delivery=Kohaletoimetamine
addjob.stations.apply=Rakenda jaamad addjob.stations.apply=Rakenda jaamad
@@ -462,7 +463,8 @@ addjob.tasks.photo.min=Min. fotosid
addjob.tasks.photo.max=Max. fotosid addjob.tasks.photo.max=Max. fotosid
addjob.tasks.barcode.min=Min. v\u00f6\u00f6tkoode addjob.tasks.barcode.min=Min. v\u00f6\u00f6tkoode
addjob.tasks.barcode.max=Max. v\u00f6\u00f6tkoode addjob.tasks.barcode.max=Max. v\u00f6\u00f6tkoode
addjob.tasks.signature.noconfig=Seadistamine pole vajalik addjob.tasks.signature.notelabel=Märkus (valikuline)
addjob.tasks.signature.notelabel.placeholder=Sisestage vihje tekst märkusele
addjob.tasks.todolist.title=\u00dclesannete nimekiri addjob.tasks.todolist.title=\u00dclesannete nimekiri
addjob.tasks.todolist.item.placeholder=Sisestage \u00fclesanne addjob.tasks.todolist.item.placeholder=Sisestage \u00fclesanne
addjob.tasks.todolist.add=Lisa \u00fclesanne addjob.tasks.todolist.add=Lisa \u00fclesanne

View File

@@ -5,7 +5,7 @@ dialog.confirm=Confirm
# Navigation and Main Layout # Navigation and Main Layout
nav.jobs=Jobs nav.jobs=Jobs
nav.job.create=Create Job nav.job.create=Create Job
nav.customers=Customers nav.customers=Address Book
nav.appusers=App Users nav.appusers=App Users
nav.statistics=Statistics nav.statistics=Statistics
nav.invoices=Invoices nav.invoices=Invoices
@@ -31,7 +31,7 @@ profile.lastname=Last Name
profile.phone=Phone Number profile.phone=Phone Number
profile.fax=Phone (Fax) profile.fax=Phone (Fax)
profile.mobile=Phone (Mobile) profile.mobile=Phone (Mobile)
profile.email=Email Address (Login)* profile.email=Email Address
profile.street=Street profile.street=Street
profile.housenr=House No. profile.housenr=House No.
profile.addressadd=Address Suffix profile.addressadd=Address Suffix
@@ -447,7 +447,8 @@ addjob.address.city.placeholder.pickup=City (Pickup)
addjob.address.city.placeholder.delivery=City (Delivery) addjob.address.city.placeholder.delivery=City (Delivery)
addjob.address.delivery.street.placeholder=Street (Delivery) addjob.address.delivery.street.placeholder=Street (Delivery)
addjob.address.delivery.addition.placeholder=Address suffix (Delivery) addjob.address.delivery.addition.placeholder=Address suffix (Delivery)
addjob.address.save=Save Address addjob.address.save=Add address to address book
addjob.address.update=Update address in address book
addjob.section.pickup=Pickup addjob.section.pickup=Pickup
addjob.section.delivery=Delivery addjob.section.delivery=Delivery
addjob.stations.apply=Apply Stations addjob.stations.apply=Apply Stations
@@ -513,7 +514,8 @@ addjob.tasks.photo.min=Min. Photos
addjob.tasks.photo.max=Max. Photos addjob.tasks.photo.max=Max. Photos
addjob.tasks.barcode.min=Min. Barcodes addjob.tasks.barcode.min=Min. Barcodes
addjob.tasks.barcode.max=Max. Barcodes addjob.tasks.barcode.max=Max. Barcodes
addjob.tasks.signature.noconfig=No configuration required addjob.tasks.signature.notelabel=Note (optional)
addjob.tasks.signature.notelabel.placeholder=Enter hint text for the note
addjob.tasks.todolist.title=To-Do List addjob.tasks.todolist.title=To-Do List
addjob.tasks.todolist.item.placeholder=Enter to-do addjob.tasks.todolist.item.placeholder=Enter to-do
addjob.tasks.todolist.add=Add To-Do addjob.tasks.todolist.add=Add To-Do

View File

@@ -5,7 +5,7 @@ dialog.confirm=Confirmar
# Navigation and Main Layout # Navigation and Main Layout
nav.jobs=Pedidos nav.jobs=Pedidos
nav.job.create=Crear pedido nav.job.create=Crear pedido
nav.customers=Clientes nav.customers=Libreta de direcciones
nav.appusers=Usuarios de la app nav.appusers=Usuarios de la app
nav.statistics=Estad\u00edsticas nav.statistics=Estad\u00edsticas
nav.invoices=Facturas nav.invoices=Facturas
@@ -31,7 +31,7 @@ profile.lastname=Apellido
profile.phone=N\u00famero de tel\u00e9fono profile.phone=N\u00famero de tel\u00e9fono
profile.fax=Tel\u00e9fono (Fax) profile.fax=Tel\u00e9fono (Fax)
profile.mobile=Tel\u00e9fono (M\u00f3vil) profile.mobile=Tel\u00e9fono (M\u00f3vil)
profile.email=Direcci\u00f3n de correo electr\u00f3nico (Login)* profile.email=Direcci\u00f3n de correo electr\u00f3nico
profile.street=Calle profile.street=Calle
profile.housenr=N\u00famero profile.housenr=N\u00famero
profile.addressadd=Complemento de direcci\u00f3n profile.addressadd=Complemento de direcci\u00f3n
@@ -447,7 +447,8 @@ addjob.address.city.placeholder.pickup=Localidad (Recogida)
addjob.address.city.placeholder.delivery=Localidad (Entrega) addjob.address.city.placeholder.delivery=Localidad (Entrega)
addjob.address.delivery.street.placeholder=Calle (Entrega) addjob.address.delivery.street.placeholder=Calle (Entrega)
addjob.address.delivery.addition.placeholder=Complemento de direcci\u00f3n (Entrega) addjob.address.delivery.addition.placeholder=Complemento de direcci\u00f3n (Entrega)
addjob.address.save=Guardar direcci\u00f3n addjob.address.save=A\u00f1adir direcci\u00f3n a la libreta de direcciones
addjob.address.update=Actualizar direcci\u00f3n en la libreta de direcciones
addjob.section.pickup=Recogida addjob.section.pickup=Recogida
addjob.section.delivery=Entrega addjob.section.delivery=Entrega
addjob.stations.apply=Aplicar estaciones addjob.stations.apply=Aplicar estaciones
@@ -513,7 +514,8 @@ addjob.tasks.photo.min=M\u00edn. fotos
addjob.tasks.photo.max=M\u00e1x. fotos addjob.tasks.photo.max=M\u00e1x. fotos
addjob.tasks.barcode.min=M\u00edn. c\u00f3digos de barras addjob.tasks.barcode.min=M\u00edn. c\u00f3digos de barras
addjob.tasks.barcode.max=M\u00e1x. c\u00f3digos de barras addjob.tasks.barcode.max=M\u00e1x. c\u00f3digos de barras
addjob.tasks.signature.noconfig=No se requiere configuraci\u00f3n addjob.tasks.signature.notelabel=Nota (opcional)
addjob.tasks.signature.notelabel.placeholder=Introducir texto de sugerencia para la nota
addjob.tasks.todolist.title=Lista de tareas pendientes addjob.tasks.todolist.title=Lista de tareas pendientes
addjob.tasks.todolist.item.placeholder=Introducir tarea pendiente addjob.tasks.todolist.item.placeholder=Introducir tarea pendiente
addjob.tasks.todolist.add=A\u00f1adir tarea pendiente addjob.tasks.todolist.add=A\u00f1adir tarea pendiente

View File

@@ -5,7 +5,7 @@ dialog.confirm=Confirmer
# Navigation and Main Layout # Navigation and Main Layout
nav.jobs=Missions nav.jobs=Missions
nav.job.create=Cr\u00e9ation de mission nav.job.create=Cr\u00e9ation de mission
nav.customers=Clients nav.customers=Carnet d'adresses
nav.appusers=Utilisateurs d'app nav.appusers=Utilisateurs d'app
nav.statistics=Statistiques nav.statistics=Statistiques
nav.invoices=Factures nav.invoices=Factures
@@ -31,7 +31,7 @@ profile.lastname=Nom
profile.phone=Num\u00e9ro de t\u00e9l\u00e9phone profile.phone=Num\u00e9ro de t\u00e9l\u00e9phone
profile.fax=T\u00e9l\u00e9phone (fax) profile.fax=T\u00e9l\u00e9phone (fax)
profile.mobile=T\u00e9l\u00e9phone (mobile) profile.mobile=T\u00e9l\u00e9phone (mobile)
profile.email=Adresse e-mail (connexion)* profile.email=Adresse e-mail
profile.street=Rue profile.street=Rue
profile.housenr=N\u00b0 profile.housenr=N\u00b0
profile.addressadd=Compl\u00e9ment d'adresse profile.addressadd=Compl\u00e9ment d'adresse
@@ -447,7 +447,8 @@ addjob.address.city.placeholder.pickup=Ville (enl\u00e8vement)
addjob.address.city.placeholder.delivery=Ville (livraison) addjob.address.city.placeholder.delivery=Ville (livraison)
addjob.address.delivery.street.placeholder=Rue (livraison) addjob.address.delivery.street.placeholder=Rue (livraison)
addjob.address.delivery.addition.placeholder=Compl\u00e9ment d'adresse (livraison) addjob.address.delivery.addition.placeholder=Compl\u00e9ment d'adresse (livraison)
addjob.address.save=Enregistrer l'adresse addjob.address.save=Ajouter l'adresse au carnet d'adresses
addjob.address.update=Mettre \u00e0 jour l'adresse dans le carnet d'adresses
addjob.section.pickup=Enl\u00e8vement addjob.section.pickup=Enl\u00e8vement
addjob.section.delivery=Livraison addjob.section.delivery=Livraison
addjob.stations.apply=Appliquer les stations addjob.stations.apply=Appliquer les stations
@@ -513,7 +514,8 @@ addjob.tasks.photo.min=Min. photos
addjob.tasks.photo.max=Max. photos addjob.tasks.photo.max=Max. photos
addjob.tasks.barcode.min=Min. codes-barres addjob.tasks.barcode.min=Min. codes-barres
addjob.tasks.barcode.max=Max. codes-barres addjob.tasks.barcode.max=Max. codes-barres
addjob.tasks.signature.noconfig=Aucune configuration n\u00e9cessaire addjob.tasks.signature.notelabel=Note (optionnelle)
addjob.tasks.signature.notelabel.placeholder=Saisir le texte d'indication pour la note
addjob.tasks.todolist.title=Liste de t\u00e2ches addjob.tasks.todolist.title=Liste de t\u00e2ches
addjob.tasks.todolist.item.placeholder=Saisir la t\u00e2che addjob.tasks.todolist.item.placeholder=Saisir la t\u00e2che
addjob.tasks.todolist.add=Ajouter une t\u00e2che addjob.tasks.todolist.add=Ajouter une t\u00e2che

View File

@@ -5,7 +5,7 @@ dialog.confirm=Patvirtinti
# Navigation and Main Layout # Navigation and Main Layout
nav.jobs=Užsakymai nav.jobs=Užsakymai
nav.job.create=Užsakymo kūrimas nav.job.create=Užsakymo kūrimas
nav.customers=Klientai nav.customers=Adres\u0173 knyga
nav.appusers=Programėlės naudotojai nav.appusers=Programėlės naudotojai
nav.statistics=Statistika nav.statistics=Statistika
nav.invoices=Sąskaitos faktūros nav.invoices=Sąskaitos faktūros
@@ -31,7 +31,7 @@ profile.lastname=Pavardė
profile.phone=Telefono numeris profile.phone=Telefono numeris
profile.fax=Telefonas (faksas) profile.fax=Telefonas (faksas)
profile.mobile=Telefonas (mob.) profile.mobile=Telefonas (mob.)
profile.email=El. pašto adresas (prisijungimas)* profile.email=El. pašto adresas*
profile.street=Gatvė profile.street=Gatvė
profile.housenr=Namo nr. profile.housenr=Namo nr.
profile.addressadd=Adreso priedas profile.addressadd=Adreso priedas
@@ -447,7 +447,8 @@ addjob.address.city.placeholder.pickup=Vietovė (atsiėmimas)
addjob.address.city.placeholder.delivery=Vietovė (pristatymas) addjob.address.city.placeholder.delivery=Vietovė (pristatymas)
addjob.address.delivery.street.placeholder=Gatvė (pristatymas) addjob.address.delivery.street.placeholder=Gatvė (pristatymas)
addjob.address.delivery.addition.placeholder=Adreso priedas (pristatymas) addjob.address.delivery.addition.placeholder=Adreso priedas (pristatymas)
addjob.address.save=Išsaugoti adresą addjob.address.save=Pridėti adresą į adresų knygą
addjob.address.update=Atnaujinti adresą adresų knygoje
addjob.section.pickup=Atsiėmimas addjob.section.pickup=Atsiėmimas
addjob.section.delivery=Pristatymas addjob.section.delivery=Pristatymas
addjob.stations.apply=Pritaikyti stotis addjob.stations.apply=Pritaikyti stotis
@@ -513,7 +514,8 @@ addjob.tasks.photo.min=Min. nuotraukų
addjob.tasks.photo.max=Maks. nuotraukų addjob.tasks.photo.max=Maks. nuotraukų
addjob.tasks.barcode.min=Min. brūkšninių kodų addjob.tasks.barcode.min=Min. brūkšninių kodų
addjob.tasks.barcode.max=Maks. brūkšninių kodų addjob.tasks.barcode.max=Maks. brūkšninių kodų
addjob.tasks.signature.noconfig=Konfigūracija nereikalinga addjob.tasks.signature.notelabel=Pastaba (neprivaloma)
addjob.tasks.signature.notelabel.placeholder=Įveskite patarimo tekstą pastabai
addjob.tasks.todolist.title=Užduočių sąrašas addjob.tasks.todolist.title=Užduočių sąrašas
addjob.tasks.todolist.item.placeholder=Įveskite užduotį addjob.tasks.todolist.item.placeholder=Įveskite užduotį
addjob.tasks.todolist.add=Pridėti užduotį addjob.tasks.todolist.add=Pridėti užduotį

View File

@@ -5,7 +5,7 @@ dialog.confirm=Apstiprināt
# Navigation and Main Layout # Navigation and Main Layout
nav.jobs=Uzdevumi nav.jobs=Uzdevumi
nav.job.create=Izveidot uzdevumu nav.job.create=Izveidot uzdevumu
nav.customers=Klienti nav.customers=Adrešu gr\u0101mata
nav.appusers=Lietotnes lietotāji nav.appusers=Lietotnes lietotāji
nav.statistics=Statistika nav.statistics=Statistika
nav.invoices=Rēķini nav.invoices=Rēķini
@@ -31,7 +31,7 @@ profile.lastname=Uzvārds
profile.phone=Tālruņa numurs profile.phone=Tālruņa numurs
profile.fax=Tālrunis (fakss) profile.fax=Tālrunis (fakss)
profile.mobile=Tālrunis (mobilais) profile.mobile=Tālrunis (mobilais)
profile.email=E-pasta adrese (pieteikšanās)* profile.email=E-pasta adrese
profile.street=Iela profile.street=Iela
profile.housenr=Mājas nr. profile.housenr=Mājas nr.
profile.addressadd=Adreses papildinājums profile.addressadd=Adreses papildinājums
@@ -447,7 +447,8 @@ addjob.address.city.placeholder.pickup=Vieta (saņemšana)
addjob.address.city.placeholder.delivery=Vieta (piegāde) addjob.address.city.placeholder.delivery=Vieta (piegāde)
addjob.address.delivery.street.placeholder=Iela (piegāde) addjob.address.delivery.street.placeholder=Iela (piegāde)
addjob.address.delivery.addition.placeholder=Adreses papildinājums (piegāde) addjob.address.delivery.addition.placeholder=Adreses papildinājums (piegāde)
addjob.address.save=Saglabāt adresi addjob.address.save=Pievienot adresi adrešu grāmatai
addjob.address.update=Atjaunin\u0101t adresi adrešu gr\u0101mat\u0101
addjob.section.pickup=Saņemšana addjob.section.pickup=Saņemšana
addjob.section.delivery=Piegāde addjob.section.delivery=Piegāde
addjob.stations.apply=Pārņemt stacijas addjob.stations.apply=Pārņemt stacijas
@@ -513,7 +514,8 @@ addjob.tasks.photo.min=Min. fotogrāfijas
addjob.tasks.photo.max=Maks. fotogrāfijas addjob.tasks.photo.max=Maks. fotogrāfijas
addjob.tasks.barcode.min=Min. svītrkodi addjob.tasks.barcode.min=Min. svītrkodi
addjob.tasks.barcode.max=Maks. svītrkodi addjob.tasks.barcode.max=Maks. svītrkodi
addjob.tasks.signature.noconfig=Konfigurācija nav nepieciešama addjob.tasks.signature.notelabel=Piezīme (neobligāta)
addjob.tasks.signature.notelabel.placeholder=Ievadiet padoma tekstu piezīmei
addjob.tasks.todolist.title=Uzdevumu saraksts addjob.tasks.todolist.title=Uzdevumu saraksts
addjob.tasks.todolist.item.placeholder=Ievadiet uzdevumu addjob.tasks.todolist.item.placeholder=Ievadiet uzdevumu
addjob.tasks.todolist.add=Pievienot uzdevumu addjob.tasks.todolist.add=Pievienot uzdevumu

View File

@@ -5,7 +5,7 @@ dialog.confirm=Potwierd\u017a
# Navigation and Main Layout # Navigation and Main Layout
nav.jobs=Zlecenia nav.jobs=Zlecenia
nav.job.create=Tworzenie zlecenia nav.job.create=Tworzenie zlecenia
nav.customers=Klienci nav.customers=Ksi\u0105\u017cka adresowa
nav.appusers=U\u017cytkownicy aplikacji nav.appusers=U\u017cytkownicy aplikacji
nav.statistics=Statystyki nav.statistics=Statystyki
nav.invoices=Faktury nav.invoices=Faktury
@@ -31,7 +31,7 @@ profile.lastname=Nazwisko
profile.phone=Numer telefonu profile.phone=Numer telefonu
profile.fax=Telefon (faks) profile.fax=Telefon (faks)
profile.mobile=Telefon (kom\u00f3rkowy) profile.mobile=Telefon (kom\u00f3rkowy)
profile.email=Adres e-mail (login)* profile.email=Adres e-mail
profile.street=Ulica profile.street=Ulica
profile.housenr=Nr domu profile.housenr=Nr domu
profile.addressadd=Dodatek do adresu profile.addressadd=Dodatek do adresu
@@ -447,7 +447,8 @@ addjob.address.city.placeholder.pickup=Miejscowo\u015b\u0107 (odbi\u00f3r)
addjob.address.city.placeholder.delivery=Miejscowo\u015b\u0107 (dostawa) addjob.address.city.placeholder.delivery=Miejscowo\u015b\u0107 (dostawa)
addjob.address.delivery.street.placeholder=Ulica (dostawa) addjob.address.delivery.street.placeholder=Ulica (dostawa)
addjob.address.delivery.addition.placeholder=Dodatek do adresu (dostawa) addjob.address.delivery.addition.placeholder=Dodatek do adresu (dostawa)
addjob.address.save=Zapisz adres addjob.address.save=Dodaj adres do ksi\u0105\u017cki adresowej
addjob.address.update=Zaktualizuj adres w ksi\u0105\u017cce adresowej
addjob.section.pickup=Odbi\u00f3r addjob.section.pickup=Odbi\u00f3r
addjob.section.delivery=Dostawa addjob.section.delivery=Dostawa
addjob.stations.apply=Zastosuj stacje addjob.stations.apply=Zastosuj stacje
@@ -513,7 +514,8 @@ addjob.tasks.photo.min=Min. zdj\u0119\u0107
addjob.tasks.photo.max=Maks. zdj\u0119\u0107 addjob.tasks.photo.max=Maks. zdj\u0119\u0107
addjob.tasks.barcode.min=Min. kod\u00f3w kreskowych addjob.tasks.barcode.min=Min. kod\u00f3w kreskowych
addjob.tasks.barcode.max=Maks. kod\u00f3w kreskowych addjob.tasks.barcode.max=Maks. kod\u00f3w kreskowych
addjob.tasks.signature.noconfig=Konfiguracja nie jest wymagana addjob.tasks.signature.notelabel=Notatka (opcjonalnie)
addjob.tasks.signature.notelabel.placeholder=Wprowadź tekst podpowiedzi dla notatki
addjob.tasks.todolist.title=Lista zada\u0144 addjob.tasks.todolist.title=Lista zada\u0144
addjob.tasks.todolist.item.placeholder=Wprowad\u017a zadanie addjob.tasks.todolist.item.placeholder=Wprowad\u017a zadanie
addjob.tasks.todolist.add=Dodaj zadanie addjob.tasks.todolist.add=Dodaj zadanie

View File

@@ -5,7 +5,7 @@ dialog.confirm=Подтвердить
# Navigation and Main Layout # Navigation and Main Layout
nav.jobs=Заказы nav.jobs=Заказы
nav.job.create=Создание заказа nav.job.create=Создание заказа
nav.customers=Клиенты nav.customers=Адресная книга
nav.appusers=Пользователи приложения nav.appusers=Пользователи приложения
nav.statistics=Статистика nav.statistics=Статистика
nav.invoices=Счета nav.invoices=Счета
@@ -31,7 +31,7 @@ profile.lastname=Фамилия
profile.phone=Номер телефона profile.phone=Номер телефона
profile.fax=Телефон (факс) profile.fax=Телефон (факс)
profile.mobile=Телефон (мобильный) profile.mobile=Телефон (мобильный)
profile.email=Адрес электронной почты (логин)* profile.email=Адрес электронной почты
profile.street=Улица profile.street=Улица
profile.housenr=Дом profile.housenr=Дом
profile.addressadd=Дополнение к адресу profile.addressadd=Дополнение к адресу
@@ -447,7 +447,8 @@ addjob.address.city.placeholder.pickup=Город (забор)
addjob.address.city.placeholder.delivery=Город (доставка) addjob.address.city.placeholder.delivery=Город (доставка)
addjob.address.delivery.street.placeholder=Улица (доставка) addjob.address.delivery.street.placeholder=Улица (доставка)
addjob.address.delivery.addition.placeholder=Дополнение к адресу (доставка) addjob.address.delivery.addition.placeholder=Дополнение к адресу (доставка)
addjob.address.save=Сохранить адрес addjob.address.save=Добавить адрес в адресную книгу
addjob.address.update=Обновить адрес в адресной книге
addjob.section.pickup=Забор addjob.section.pickup=Забор
addjob.section.delivery=Доставка addjob.section.delivery=Доставка
addjob.stations.apply=Применить станции addjob.stations.apply=Применить станции
@@ -513,7 +514,8 @@ addjob.tasks.photo.min=Мин. фото
addjob.tasks.photo.max=Макс. фото addjob.tasks.photo.max=Макс. фото
addjob.tasks.barcode.min=Мин. штрих-кодов addjob.tasks.barcode.min=Мин. штрих-кодов
addjob.tasks.barcode.max=Макс. штрих-кодов addjob.tasks.barcode.max=Макс. штрих-кодов
addjob.tasks.signature.noconfig=Настройка не требуется addjob.tasks.signature.notelabel=Примечание (необязательно)
addjob.tasks.signature.notelabel.placeholder=Введите текст подсказки для примечания
addjob.tasks.todolist.title=Список дел addjob.tasks.todolist.title=Список дел
addjob.tasks.todolist.item.placeholder=Введите задачу addjob.tasks.todolist.item.placeholder=Введите задачу
addjob.tasks.todolist.add=Добавить задачу addjob.tasks.todolist.add=Добавить задачу

View File

@@ -5,7 +5,7 @@ dialog.confirm=Onayla
# Navigation and Main Layout # Navigation and Main Layout
nav.jobs=\u0130\u015fler nav.jobs=\u0130\u015fler
nav.job.create=\u0130\u015f Olu\u015ftur nav.job.create=\u0130\u015f Olu\u015ftur
nav.customers=M\u00fc\u015fteriler nav.customers=Adres Defteri
nav.appusers=Uygulama Kullan\u0131c\u0131lar\u0131 nav.appusers=Uygulama Kullan\u0131c\u0131lar\u0131
nav.statistics=\u0130statistikler nav.statistics=\u0130statistikler
nav.invoices=Faturalar nav.invoices=Faturalar
@@ -31,7 +31,7 @@ profile.lastname=Soyad
profile.phone=Telefon Numaras\u0131 profile.phone=Telefon Numaras\u0131
profile.fax=Telefon (Faks) profile.fax=Telefon (Faks)
profile.mobile=Telefon (Mobil) profile.mobile=Telefon (Mobil)
profile.email=E-Posta Adresi (Giri\u015f)* profile.email=E-Posta Adresi*
profile.street=Sokak profile.street=Sokak
profile.housenr=Kap\u0131 No profile.housenr=Kap\u0131 No
profile.addressadd=Adres Eki profile.addressadd=Adres Eki
@@ -447,7 +447,8 @@ addjob.address.city.placeholder.pickup=\u015eehir (Al\u0131m)
addjob.address.city.placeholder.delivery=\u015eehir (Teslimat) addjob.address.city.placeholder.delivery=\u015eehir (Teslimat)
addjob.address.delivery.street.placeholder=Sokak (Teslimat) addjob.address.delivery.street.placeholder=Sokak (Teslimat)
addjob.address.delivery.addition.placeholder=Adres eki (Teslimat) addjob.address.delivery.addition.placeholder=Adres eki (Teslimat)
addjob.address.save=Adresi Kaydet addjob.address.save=Adresi adres defterine ekle
addjob.address.update=Adres defterindeki adresi g\u00fcncelle
addjob.section.pickup=Al\u0131m addjob.section.pickup=Al\u0131m
addjob.section.delivery=Teslimat addjob.section.delivery=Teslimat
addjob.stations.apply=\u0130stasyonlar\u0131 \u00fcbernehmennehmen addjob.stations.apply=\u0130stasyonlar\u0131 \u00fcbernehmennehmen
@@ -513,7 +514,8 @@ addjob.tasks.photo.min=Min. Foto\u011fraf
addjob.tasks.photo.max=Maks. Foto\u011fraf addjob.tasks.photo.max=Maks. Foto\u011fraf
addjob.tasks.barcode.min=Min. Barkod addjob.tasks.barcode.min=Min. Barkod
addjob.tasks.barcode.max=Maks. Barkod addjob.tasks.barcode.max=Maks. Barkod
addjob.tasks.signature.noconfig=Yap\u0131land\u0131rma gerekli de\u011fil addjob.tasks.signature.notelabel=Not (iste\u011fe ba\u011fl\u0131)
addjob.tasks.signature.notelabel.placeholder=Not i\u00e7in ipucu metnini girin
addjob.tasks.todolist.title=Yap\u0131lacaklar Listesi addjob.tasks.todolist.title=Yap\u0131lacaklar Listesi
addjob.tasks.todolist.item.placeholder=Yap\u0131lacak \u00f6\u011feyi girin addjob.tasks.todolist.item.placeholder=Yap\u0131lacak \u00f6\u011feyi girin
addjob.tasks.todolist.add=Yap\u0131lacak \u00d6\u011fe Ekle addjob.tasks.todolist.add=Yap\u0131lacak \u00d6\u011fe Ekle