feat: Umstellung auf Anthropic Claude SDK, Rechnungsgenerator-Systemtemplate und Task-Status-Sync

- LLM-Integration von LM Studio (Spring AI OpenAI) auf Anthropic Java SDK umgestellt
- Rechnungsgenerator: System-Template für Rechnungen an Nutzer, Canvas leeren, PDF-Vorschau
- Job manuell beenden sendet job_deleted an verbundene App-Clients
- MessageController: nur offene Jobs ausliefern, station_completed per jobId auflösen
- App: Task-Erledigungsstatus aus Server-Tasks auf Stations-Snapshots übernehmen, lokale Task-Status bei Offline-Pufferung erhalten
- Adressbuch-Wording in allen Sprachdateien, Version 0.9.18

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 11:16:34 +02:00
parent 31b18e1f52
commit 09db861064
21 changed files with 1087 additions and 652 deletions

View File

@@ -258,6 +258,40 @@ class Job {
return (a.taskOrder ?? 0).compareTo(b.taskOrder ?? 0); return (a.taskOrder ?? 0).compareTo(b.taskOrder ?? 0);
} }
// The tasks embedded in deliveryStations are snapshots from job creation
// and never reflect later completions. The top-level tasks list comes from
// the server's task collection and carries the current completion state,
// so overlay it onto the station tasks by id.
final topLevelTasksRaw = json['tasks'] ?? jobJson['tasks'];
if (deliveryStations.isNotEmpty && topLevelTasksRaw is List) {
final freshTasksById = <String, Task>{};
for (final rawTask in topLevelTasksRaw) {
if (rawTask is! Map) {
continue;
}
try {
final task = Task.fromJson(Map<String, dynamic>.from(rawTask));
if (task.id.isNotEmpty) {
freshTasksById[task.id] = task;
}
} catch (_) {
// Ignore unparseable entries; the station snapshot stays in place.
}
}
for (final station in deliveryStations) {
for (var i = 0; i < station.tasks.length; i++) {
final fresh = freshTasksById[station.tasks[i].id];
if (fresh != null && fresh.completed && !station.tasks[i].completed) {
station.tasks[i] = station.tasks[i].copyWith(
completed: true,
completedAt: fresh.completedAt,
completedBy: fresh.completedBy,
);
}
}
}
}
// Parse tasks array // Parse tasks array
List<Task> tasks = []; List<Task> tasks = [];
if (deliveryStations.isNotEmpty) { if (deliveryStations.isNotEmpty) {

View File

@@ -116,9 +116,16 @@ class DatabaseService {
final jobBox = _store!.box<JobEntity>(); final jobBox = _store!.box<JobEntity>();
final taskStatusBox = _store!.box<TaskStatusEntity>(); final taskStatusBox = _store!.box<TaskStatusEntity>();
// Clear existing jobs and related task statuses before inserting new ones // Clear existing jobs before inserting new ones. Task statuses are NOT
// cleared wholesale: locally completed tasks may not be confirmed by the
// server yet (offline buffering), so only statuses of tasks that no
// longer exist in any job are removed below.
jobBox.removeAll(); jobBox.removeAll();
taskStatusBox.removeAll();
final existingStatuses = {
for (final entity in taskStatusBox.getAll()) entity.taskId: entity,
};
final knownTaskIds = <String>{};
// Save new jobs // Save new jobs
for (final job in jobs) { for (final job in jobs) {
@@ -134,7 +141,8 @@ class DatabaseService {
// Also persist task completion states from JSON (adopt status on load) // Also persist task completion states from JSON (adopt status on load)
// Only set completed=true entries to avoid overwriting local progress with false // Only set completed=true entries to avoid overwriting local progress with false
for (final task in normalized.tasks) { for (final task in normalized.tasks) {
if (task.completed) { knownTaskIds.add(task.id);
if (task.completed && !existingStatuses.containsKey(task.id)) {
final taskStatusEntity = TaskStatusEntity( final taskStatusEntity = TaskStatusEntity(
taskId: task.id, taskId: task.id,
completed: true, completed: true,
@@ -147,6 +155,13 @@ class DatabaseService {
} }
} }
// Drop statuses of tasks that no longer belong to any assigned job
for (final entry in existingStatuses.entries) {
if (!knownTaskIds.contains(entry.key)) {
taskStatusBox.remove(entry.value.id);
}
}
developer.log( developer.log(
'Jobs and task statuses saved successfully', 'Jobs and task statuses saved successfully',
name: 'DatabaseService', name: 'DatabaseService',

View File

@@ -11,7 +11,7 @@
<packaging>jar</packaging> <packaging>jar</packaging>
<properties> <properties>
<revision>0.9.17</revision> <revision>0.9.18</revision>
<java.version>21</java.version> <java.version>21</java.version>
<maven.compiler.source>21</maven.compiler.source> <maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target> <maven.compiler.target>21</maven.compiler.target>
@@ -184,10 +184,11 @@
<version>5.0.5</version> <version>5.0.5</version>
</dependency> </dependency>
<!-- Spring AI OpenAI (LM Studio kompatibel) --> <!-- Anthropic Claude SDK -->
<dependency> <dependency>
<groupId>org.springframework.ai</groupId> <groupId>com.anthropic</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId> <artifactId>anthropic-java</artifactId>
<version>2.34.0</version>
</dependency> </dependency>
<!-- MCP Server mit WebMVC Transport --> <!-- MCP Server mit WebMVC Transport -->

Binary file not shown.

View File

@@ -1,124 +1,57 @@
package de.assecutor.votianlt.ai.config; package de.assecutor.votianlt.ai.config;
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCountTokensParams;
import com.anthropic.models.messages.MessageTokensCount;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.util.Base64;
/** /**
* Configuration for LLM integration via LM Studio. The LM Studio instance * Configuration for LLM integration via the Anthropic Claude API.
* exposes an OpenAI-compatible API at {@code /v1/chat/completions}.
*/ */
@Configuration @Configuration
@Slf4j @Slf4j
public class LlmConfig { public class LlmConfig {
@Value("${app.ai.lmstudio.base-url}") @Value("${app.ai.anthropic.api-key}")
private String lmstudioBaseUrl; private String apiKey;
@Value("${app.ai.lmstudio.model}") @Value("${app.ai.anthropic.model:claude-opus-4-8}")
private String lmstudioModel; private String model;
@Value("${app.ai.lmstudio.htaccess-username}")
private String lmstudioHtaccessUsername;
@Value("${app.ai.lmstudio.htaccess-password}")
private String lmstudioHtaccessPassword;
@PostConstruct @PostConstruct
public void logConfig() { public void logConfig() {
log.info("=== LLM Configuration ==="); log.info("=== LLM Configuration ===");
log.info("Provider: lmstudio"); log.info("Provider: Anthropic Claude");
log.info("Base URL: {}", lmstudioBaseUrl); log.info("Model: {}", model);
log.info("Model: {}", lmstudioModel); log.info("API key: {}", apiKey != null && !apiKey.isBlank() ? "configured" : "NOT configured");
log.info("HTACCESS auth: {}", hasHtaccessCredentials() ? "configured" : "not configured"); testConnection();
testConnection(lmstudioBaseUrl, lmstudioModel);
} }
private void testConnection(String baseUrl, String model) { /**
log.info("Testing LLM connection to: {}", baseUrl); * Validate API key and connectivity using the free count_tokens endpoint (no
* tokens are billed).
// Test 1: Models endpoint */
testEndpoint(baseUrl + "/v1/models", "GET", null); private void testConnection() {
if (apiKey == null || apiKey.isBlank()) {
// Test 2: Chat completions (no streaming) log.warn("Skipping Claude connection test - no API key configured");
String testPayload = "{\"model\":\"" + model return;
+ "\",\"messages\":[{\"role\":\"user\",\"content\":\"ping\"}],\"max_tokens\":1,\"stream\":false}"; }
testEndpoint(baseUrl + "/v1/chat/completions", "POST", testPayload);
}
private void testEndpoint(String endpoint, String method, String payload) {
try { try {
log.info("Testing endpoint: {} {}", method, endpoint); AnthropicClient client = AnthropicOkHttpClient.builder().apiKey(apiKey).build();
URL url = URI.create(endpoint).toURL(); MessageTokensCount count = client.messages()
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); .countTokens(MessageCountTokensParams.builder().model(model).addUserMessage("ping").build());
connection.setRequestMethod(method); log.info("Claude connection test -> SUCCESS (count_tokens returned {} input tokens)",
connection.setConnectTimeout(5000); count.inputTokens());
connection.setReadTimeout(10000);
if (hasHtaccessCredentials()) {
String credentials = lmstudioHtaccessUsername + ":" + lmstudioHtaccessPassword;
String encoded = Base64.getEncoder().encodeToString(credentials.getBytes());
connection.setRequestProperty("Authorization", "Basic " + encoded);
}
if (payload != null) {
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "application/json");
try (var os = connection.getOutputStream()) {
os.write(payload.getBytes());
}
}
int responseCode = connection.getResponseCode();
String responseMessage = connection.getResponseMessage();
if (responseCode >= 200 && responseCode < 300) {
log.info(" -> SUCCESS (HTTP {} {})", responseCode, responseMessage);
} else {
String errorBody = "";
try (var is = connection.getErrorStream()) {
if (is != null) {
errorBody = new String(is.readAllBytes());
}
}
log.warn(" -> HTTP {} {} - {}", responseCode, responseMessage, errorBody);
}
connection.disconnect();
} catch (java.net.ConnectException e) {
log.error(" -> FAILED - Connection refused: {}", e.getMessage());
} catch (java.net.SocketTimeoutException e) {
log.error(" -> FAILED - Timeout: {}", e.getMessage());
} catch (java.net.UnknownHostException e) {
log.error(" -> FAILED - Unknown host: {}", e.getMessage());
} catch (Exception e) { } catch (Exception e) {
log.error(" -> FAILED: {} - {}", e.getClass().getSimpleName(), e.getMessage()); log.error("Claude connection test -> FAILED: {} - {}", e.getClass().getSimpleName(), e.getMessage());
} }
} }
public String getBaseUrl() {
return lmstudioBaseUrl;
}
public String getModel() { public String getModel() {
return lmstudioModel; return model;
}
public boolean hasHtaccessCredentials() {
return lmstudioHtaccessUsername != null && !lmstudioHtaccessUsername.isBlank()
&& lmstudioHtaccessPassword != null && !lmstudioHtaccessPassword.isBlank();
}
public String getLmstudioHtaccessUsername() {
return lmstudioHtaccessUsername;
}
public String getLmstudioHtaccessPassword() {
return lmstudioHtaccessPassword;
} }
} }

View File

@@ -1,24 +1,20 @@
package de.assecutor.votianlt.ai.service; package de.assecutor.votianlt.ai.service;
import com.fasterxml.jackson.databind.JsonNode; import com.anthropic.client.AnthropicClient;
import com.fasterxml.jackson.databind.ObjectMapper; import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors;
/** /**
* Direct REST client for LM Studio LLM API. Communicates via the * LLM client backed by the Anthropic Claude API (official Java SDK). Keeps the
* OpenAI-compatible /v1/chat/completions endpoint. * same public interface as the previous LM-Studio-based implementation so
* callers (AiStatisticsService, TranslationService) stay unchanged.
*/ */
@Component @Component
@Slf4j @Slf4j
@@ -27,34 +23,14 @@ public class LlmRestClient {
private static final Pattern THINK_BLOCK_PATTERN = Pattern.compile("(?is)<think>.*?</think>"); private static final Pattern THINK_BLOCK_PATTERN = Pattern.compile("(?is)<think>.*?</think>");
private static final Pattern THINK_TAG_PATTERN = Pattern.compile("(?is)</?think>"); private static final Pattern THINK_TAG_PATTERN = Pattern.compile("(?is)</?think>");
private final WebClient webClient; private final AnthropicClient client;
private final ObjectMapper objectMapper;
private final String model; private final String model;
public LlmRestClient(@Value("${app.ai.lmstudio.base-url}") String lmstudioBaseUrl, public LlmRestClient(@Value("${app.ai.anthropic.api-key}") String apiKey,
@Value("${app.ai.lmstudio.model}") String lmstudioModel, @Value("${app.ai.anthropic.model:claude-opus-4-8}") String model) {
@Value("${app.ai.lmstudio.htaccess-username}") String lmstudioHtaccessUsername, this.model = model;
@Value("${app.ai.lmstudio.htaccess-password}") String lmstudioHtaccessPassword, ObjectMapper objectMapper) { this.client = AnthropicOkHttpClient.builder().apiKey(apiKey).build();
log.info("LlmRestClient initialized - Provider: Anthropic Claude, Model: {}", model);
this.model = lmstudioModel;
this.objectMapper = objectMapper;
WebClient.Builder builder = WebClient.builder();
builder.baseUrl(lmstudioBaseUrl + "/v1/chat/completions");
if (lmstudioHtaccessUsername != null && !lmstudioHtaccessUsername.isBlank() && lmstudioHtaccessPassword != null
&& !lmstudioHtaccessPassword.isBlank()) {
String credentials = lmstudioHtaccessUsername + ":" + lmstudioHtaccessPassword;
String encoded = Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8));
builder.defaultHeader(HttpHeaders.AUTHORIZATION, "Basic " + encoded);
log.info("LlmRestClient initialized (with HTACCESS auth) - URL: {}/v1/chat/completions, Model: {}",
lmstudioBaseUrl, lmstudioModel);
} else {
log.info("LlmRestClient initialized - URL: {}/v1/chat/completions, Model: {}", lmstudioBaseUrl,
lmstudioModel);
}
this.webClient = builder.build();
} }
/** /**
@@ -78,32 +54,45 @@ public class LlmRestClient {
* @param userMessage * @param userMessage
* User message/question * User message/question
* @param temperature * @param temperature
* Temperature for response randomness (0.0-1.0) * Ignored - current Claude models do not accept sampling
* parameters (kept for signature compatibility)
* @param maxTokens * @param maxTokens
* Maximum tokens in response * Maximum tokens in response
* @return LLM response text, or null on error * @return LLM response text, or null on error
*/ */
public String chat(String systemPrompt, String userMessage, double temperature, int maxTokens) { public String chat(String systemPrompt, String userMessage, double temperature, int maxTokens) {
try { try {
Map<String, Object> request = Map.of("model", model, "messages", MessageCreateParams.Builder builder = MessageCreateParams.builder().model(model).maxTokens(maxTokens);
List.of(Map.of("role", "system", "content", systemPrompt != null ? systemPrompt : ""), if (systemPrompt != null && !systemPrompt.isBlank()) {
Map.of("role", "user", "content", userMessage)), builder.system(systemPrompt);
"temperature", temperature, "max_tokens", maxTokens, "stream", false); }
builder.addUserMessage(userMessage);
log.info("Sending request to LLM (model: {}, prompt length: {} chars)...", model, userMessage.length()); log.info("Sending request to Claude (model: {}, prompt length: {} chars)...", model, userMessage.length());
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
String response = webClient.post().contentType(MediaType.APPLICATION_JSON).bodyValue(request).retrieve() Message response = client.messages().create(builder.build());
.bodyToMono(String.class).timeout(Duration.ofSeconds(120)).block();
long duration = System.currentTimeMillis() - startTime; long duration = System.currentTimeMillis() - startTime;
log.info("LLM response received in {}ms", duration); log.info("Claude response received in {}ms (stop_reason: {})", duration, response.stopReason());
log.debug("LLM response payload received ({} chars)", response != null ? response.length() : 0);
return extractContent(response); String content = response.content().stream().flatMap(block -> block.text().stream())
.map(textBlock -> textBlock.text()).collect(Collectors.joining("\n"));
if (content.isBlank()) {
log.warn("Claude response content is empty");
return null;
}
String sanitizedContent = sanitizeAssistantContent(content);
if (sanitizedContent.isBlank()) {
log.warn("Claude response content is empty after sanitization");
return null;
}
return sanitizedContent;
} catch (Exception e) { } catch (Exception e) {
log.error("Error calling LLM API: {} - {}", e.getClass().getSimpleName(), e.getMessage()); log.error("Error calling Claude API: {} - {}", e.getClass().getSimpleName(), e.getMessage());
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Full stack trace:", e); log.debug("Full stack trace:", e);
} }
@@ -122,35 +111,6 @@ public class LlmRestClient {
return model; return model;
} }
private String extractContent(String response) {
if (response == null || response.isBlank()) {
log.warn("LLM returned null or blank response");
return null;
}
try {
JsonNode root = objectMapper.readTree(response);
JsonNode choices = root.path("choices");
if (choices.isArray() && !choices.isEmpty()) {
String content = choices.get(0).path("message").path("content").asText();
if (content == null || content.isBlank()) {
log.warn("LLM response content is empty");
return null;
}
String sanitizedContent = sanitizeAssistantContent(content);
if (sanitizedContent.isBlank()) {
log.warn("LLM response content is empty after sanitization");
return null;
}
return sanitizedContent;
}
log.warn("Unexpected response structure (no choices): {}", response);
return null;
} catch (Exception e) {
log.error("Error parsing LLM response: {}", e.getMessage());
return null;
}
}
private String sanitizeAssistantContent(String content) { private String sanitizeAssistantContent(String content) {
String sanitized = THINK_BLOCK_PATTERN.matcher(content).replaceAll(" "); String sanitized = THINK_BLOCK_PATTERN.matcher(content).replaceAll(" ");
sanitized = THINK_TAG_PATTERN.matcher(sanitized).replaceAll(" "); sanitized = THINK_TAG_PATTERN.matcher(sanitized).replaceAll(" ");

View File

@@ -128,8 +128,10 @@ public class MessageController {
log.info("[JOBS] Retrieving assigned jobs for appUserId: {}", appUserId); log.info("[JOBS] Retrieving assigned jobs for appUserId: {}", appUserId);
List<Job> assignedJobs = jobRepository.findByAppUser(appUserId); List<Job> assignedJobs = jobRepository.findByAppUser(appUserId).stream()
log.info("[JOBS] Found {} jobs for appUserId: {}", assignedJobs.size(), appUserId); .filter(job -> job.getStatus() != JobStatus.COMPLETED && job.getStatus() != JobStatus.CANCELLED)
.toList();
log.info("[JOBS] Found {} open jobs for appUserId: {}", assignedJobs.size(), appUserId);
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());
@@ -462,15 +464,19 @@ public class MessageController {
*/ */
public void handleStationCompleted(String appUserId, Map<String, Object> payload) { public void handleStationCompleted(String appUserId, Map<String, Object> payload) {
try { try {
String jobIdStr = payload.get("jobId") != null ? payload.get("jobId").toString() : null;
String jobNumber = payload.get("jobNumber") != null ? payload.get("jobNumber").toString() : null; String jobNumber = payload.get("jobNumber") != null ? payload.get("jobNumber").toString() : null;
if (jobNumber == null || jobNumber.isBlank()) {
log.warn("[STATION] station_completed without jobNumber");
return;
}
Optional<Job> jobOpt = jobRepository.findByJobNumber(jobNumber); Optional<Job> jobOpt = Optional.empty();
if (jobIdStr != null && ObjectId.isValid(jobIdStr)) {
jobOpt = jobRepository.findById(new ObjectId(jobIdStr));
}
if (jobOpt.isEmpty() && jobNumber != null && !jobNumber.isBlank()) {
jobOpt = jobRepository.findByJobNumber(jobNumber);
}
if (jobOpt.isEmpty()) { if (jobOpt.isEmpty()) {
log.warn("[STATION] Job with jobNumber {} not found", jobNumber); log.warn("[STATION] Job not found for station_completed (jobId={}, jobNumber={})", jobIdStr,
jobNumber);
return; return;
} }

View File

@@ -31,11 +31,13 @@ import de.assecutor.votianlt.model.JobServiceSelection;
import de.assecutor.votianlt.model.JobStatus; import de.assecutor.votianlt.model.JobStatus;
import de.assecutor.votianlt.model.Service; import de.assecutor.votianlt.model.Service;
import de.assecutor.votianlt.model.User; import de.assecutor.votianlt.model.User;
import de.assecutor.votianlt.messaging.MessagingPublisher;
import de.assecutor.votianlt.pages.base.ui.component.DialogStylingHelper; import de.assecutor.votianlt.pages.base.ui.component.DialogStylingHelper;
import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar; import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar;
import de.assecutor.votianlt.repository.ServiceRepository; import de.assecutor.votianlt.repository.ServiceRepository;
import de.assecutor.votianlt.repository.JobRepository; import de.assecutor.votianlt.repository.JobRepository;
import de.assecutor.votianlt.security.SecurityService; import de.assecutor.votianlt.security.SecurityService;
import de.assecutor.votianlt.service.ClientConnectionService;
import de.assecutor.votianlt.service.JobHistoryService; import de.assecutor.votianlt.service.JobHistoryService;
import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RolesAllowed;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -47,6 +49,7 @@ import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
@Route(value = "job_manual_complete", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class) @Route(value = "job_manual_complete", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
@RolesAllowed("USER") @RolesAllowed("USER")
@@ -67,6 +70,8 @@ public class JobManualCompleteView extends Main implements HasUrlParameter<Strin
private final JobHistoryService jobHistoryService; private final JobHistoryService jobHistoryService;
private final SecurityService securityService; private final SecurityService securityService;
private final ServiceRepository serviceRepository; private final ServiceRepository serviceRepository;
private final ClientConnectionService clientConnectionService;
private final MessagingPublisher messagingPublisher;
private final VerticalLayout content; private final VerticalLayout content;
private Job job; private Job job;
@@ -80,11 +85,14 @@ public class JobManualCompleteView extends Main implements HasUrlParameter<Strin
private Integer manualDurationSeconds; private Integer manualDurationSeconds;
public JobManualCompleteView(JobRepository jobRepository, JobHistoryService jobHistoryService, public JobManualCompleteView(JobRepository jobRepository, JobHistoryService jobHistoryService,
SecurityService securityService, ServiceRepository serviceRepository) { SecurityService securityService, ServiceRepository serviceRepository,
ClientConnectionService clientConnectionService, MessagingPublisher messagingPublisher) {
this.jobRepository = jobRepository; this.jobRepository = jobRepository;
this.jobHistoryService = jobHistoryService; this.jobHistoryService = jobHistoryService;
this.securityService = securityService; this.securityService = securityService;
this.serviceRepository = serviceRepository; this.serviceRepository = serviceRepository;
this.clientConnectionService = clientConnectionService;
this.messagingPublisher = messagingPublisher;
setSizeFull(); setSizeFull();
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN, addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
@@ -683,6 +691,8 @@ public class JobManualCompleteView extends Main implements HasUrlParameter<Strin
jobHistoryService.logCustomEvent(job.getId(), getTranslation("jobsummary.history.manualcomplete.reason"), jobHistoryService.logCustomEvent(job.getId(), getTranslation("jobsummary.history.manualcomplete.reason"),
description, currentUser, JobHistoryType.STATUS_CHANGE); description, currentUser, JobHistoryType.STATUS_CHANGE);
notifyClientJobDeleted(job);
Notification Notification
.show(getTranslation("jobsummary.notification.completed", job.getJobNumber()), 3000, .show(getTranslation("jobsummary.notification.completed", job.getJobNumber()), 3000,
Notification.Position.BOTTOM_END) Notification.Position.BOTTOM_END)
@@ -695,4 +705,26 @@ public class JobManualCompleteView extends Main implements HasUrlParameter<Strin
.addThemeVariants(NotificationVariant.LUMO_ERROR); .addThemeVariants(NotificationVariant.LUMO_ERROR);
} }
} }
private void notifyClientJobDeleted(Job job) {
if (!job.isDigitalProcessing()) {
return;
}
String appUserId = job.getAppUser();
if (appUserId == null || appUserId.isBlank()) {
return;
}
if (!clientConnectionService.isClientConnected(appUserId)) {
log.info("[JOB] Client {} not online, skipping job_deleted notification", appUserId);
return;
}
Map<String, Object> payload = Map.of("type", "job_deleted", "jobId", job.getId().toHexString(), "jobNumber",
job.getJobNumber() != null ? job.getJobNumber() : "", "deletedAt", LocalDateTime.now().toString());
log.info("[JOB] Sending job_deleted to {}: {}", appUserId, payload);
messagingPublisher.publishAsJson(appUserId, "job_deleted", payload);
}
} }

View File

@@ -248,7 +248,7 @@ public class CustomerInvoiceService {
* representation of the canvas elements and converts it to PDF. * representation of the canvas elements and converts it to PDF.
*/ */
public byte[] generatePdfFromCanvasTemplate(String jsonTemplateData) throws Exception { public byte[] generatePdfFromCanvasTemplate(String jsonTemplateData) throws Exception {
return generatePdfFromCanvasTemplate(jsonTemplateData, null, null); return generatePdfFromCanvasTemplate(jsonTemplateData, (de.assecutor.votianlt.model.User) null, null);
} }
public byte[] generatePdfFromCanvasTemplate(String jsonTemplateData, de.assecutor.votianlt.model.User user) public byte[] generatePdfFromCanvasTemplate(String jsonTemplateData, de.assecutor.votianlt.model.User user)
@@ -265,27 +265,6 @@ public class CustomerInvoiceService {
String invoicePrefix, BigDecimal vatRate) throws Exception { String invoicePrefix, BigDecimal vatRate) throws Exception {
BigDecimal effectiveVatRate = vatRate != null ? vatRate BigDecimal effectiveVatRate = vatRate != null ? vatRate
: (user != null && user.getVatRate() != null ? user.getVatRate() : new BigDecimal("0.19")); : (user != null && user.getVatRate() != null ? user.getVatRate() : new BigDecimal("0.19"));
// Parse the JSON template data
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
com.fasterxml.jackson.databind.JsonNode rootNode = mapper.readTree(jsonTemplateData);
com.fasterxml.jackson.databind.JsonNode elements = rootNode.get("elements");
// Build HTML content from canvas elements
StringBuilder htmlBuilder = new StringBuilder();
htmlBuilder.append("<!DOCTYPE html>");
htmlBuilder.append("<html><head>");
htmlBuilder.append("<meta charset='UTF-8'>");
htmlBuilder.append("<style>");
htmlBuilder.append("@page { size: A4; margin: 0; }");
htmlBuilder.append(
"body { margin: 0; padding: 0; width: 210mm; height: 297mm; position: relative; font-family: Arial, sans-serif; }");
htmlBuilder.append(".element { position: absolute; box-sizing: border-box; overflow: hidden; }");
htmlBuilder.append(".text { white-space: nowrap; overflow: visible; }");
htmlBuilder.append(".line { border-top: 1px solid #333; }");
htmlBuilder.append(".image { overflow: hidden; }");
htmlBuilder.append(".image img { width: 100%; height: 100%; object-fit: contain; display: block; }");
htmlBuilder.append("</style>");
htmlBuilder.append("</head><body>");
// Prepare variable substitution map // Prepare variable substitution map
java.util.Map<String, String> variables = new java.util.HashMap<>(); java.util.Map<String, String> variables = new java.util.HashMap<>();
@@ -321,6 +300,39 @@ public class CustomerInvoiceService {
variables.put("customer.email", "kunde@beispiel.de"); variables.put("customer.email", "kunde@beispiel.de");
variables.put("customer.phone", "0987 654321"); variables.put("customer.phone", "0987 654321");
return generatePdfFromCanvasTemplate(jsonTemplateData, variables, effectiveVatRate);
}
/**
* Render a canvas template PDF using a pre-built variable substitution map.
* Used e.g. for the system invoice template where the issuer data comes from
* SystemInvoiceData instead of a User.
*/
public byte[] generatePdfFromCanvasTemplate(String jsonTemplateData, java.util.Map<String, String> variables,
BigDecimal vatRate) throws Exception {
BigDecimal effectiveVatRate = vatRate != null ? vatRate : new BigDecimal("0.19");
// Parse the JSON template data
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
com.fasterxml.jackson.databind.JsonNode rootNode = mapper.readTree(jsonTemplateData);
com.fasterxml.jackson.databind.JsonNode elements = rootNode.get("elements");
// Build HTML content from canvas elements
StringBuilder htmlBuilder = new StringBuilder();
htmlBuilder.append("<!DOCTYPE html>");
htmlBuilder.append("<html><head>");
htmlBuilder.append("<meta charset='UTF-8'>");
htmlBuilder.append("<style>");
htmlBuilder.append("@page { size: A4; margin: 0; }");
htmlBuilder.append(
"body { margin: 0; padding: 0; width: 210mm; height: 297mm; position: relative; font-family: Arial, sans-serif; }");
htmlBuilder.append(".element { position: absolute; box-sizing: border-box; overflow: hidden; }");
htmlBuilder.append(".text { white-space: nowrap; overflow: visible; }");
htmlBuilder.append(".line { border-top: 1px solid #333; }");
htmlBuilder.append(".image { overflow: hidden; }");
htmlBuilder.append(".image img { width: 100%; height: 100%; object-fit: contain; display: block; }");
htmlBuilder.append("</style>");
htmlBuilder.append("</head><body>");
if (elements != null && elements.isArray()) { if (elements != null && elements.isArray()) {
for (com.fasterxml.jackson.databind.JsonNode element : elements) { for (com.fasterxml.jackson.databind.JsonNode element : elements) {
String type = element.has("type") ? element.get("type").asText("text") : "text"; String type = element.has("type") ? element.get("type").asText("text") : "text";

View File

@@ -73,21 +73,11 @@ app.version=@project.version@
app.google.maps.api-key=AIzaSyDnbitL06iLp3elmj-WtPudCykX9xvXcVE app.google.maps.api-key=AIzaSyDnbitL06iLp3elmj-WtPudCykX9xvXcVE
# =========================================== # ===========================================
# LLM Configuration (LM Studio) # LLM Configuration (Anthropic Claude)
# =========================================== # ===========================================
app.ai.lmstudio.base-url=${LMSTUDIO_URL} # API-Key kann per Umgebungsvariable ANTHROPIC_API_KEY ueberschrieben werden
app.ai.lmstudio.model=${LMSTUDIO_MODEL} app.ai.anthropic.api-key=${ANTHROPIC_API_KEY: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}
app.ai.lmstudio.htaccess-username=${LMSTUDIO_HTACCESS_USERNAME} app.ai.anthropic.model=${ANTHROPIC_MODEL:claude-opus-4-8}
app.ai.lmstudio.htaccess-password=${LMSTUDIO_HTACCESS_PASSWORD}
# Spring AI OpenAI properties (Pflicht für Auto-Configuration, werden vom LlmRestClient überschrieben)
spring.ai.openai.base-url=${LMSTUDIO_URL}
spring.ai.openai.api-key=not-used
spring.ai.openai.chat.options.model=${LMSTUDIO_MODEL}
spring.ai.openai.chat.options.temperature=0.7
spring.ai.openai.chat.options.stream=false
spring.ai.openai.connect-timeout=10s
spring.ai.openai.read-timeout=120s
# =========================================== # ===========================================
# MCP Server Configuration # MCP Server Configuration

View File

@@ -241,7 +241,7 @@ page.title.dashboard=VotianLT - Dashboard
page.title.appuser.create=Neuen App-Nutzer anlegen page.title.appuser.create=Neuen App-Nutzer anlegen
page.title.messages=Nachrichten page.title.messages=Nachrichten
page.title.register=Bei VotianLT registrieren page.title.register=Bei VotianLT registrieren
page.title.customers=Kunden page.title.customers=Adressbuch
page.title.customer.edit=Adresse bearbeiten page.title.customer.edit=Adresse bearbeiten
page.title.verwaltung=Verwaltung page.title.verwaltung=Verwaltung
page.title.company.create=Neue Firma anlegen page.title.company.create=Neue Firma anlegen
@@ -249,7 +249,7 @@ page.title.imprint=Impressum
page.title.profile.edit=Profil bearbeiten page.title.profile.edit=Profil bearbeiten
page.title.admin.dashboard=Admin Dashboard page.title.admin.dashboard=Admin Dashboard
page.title.invoice.create=Rechnung erstellen page.title.invoice.create=Rechnung erstellen
page.title.customer.create=Neuen Kunden anlegen page.title.customer.create=Neue Adresse anlegen
page.title.login=Bei VotianLT anmelden page.title.login=Bei VotianLT anmelden
page.title.jobs=Aufträge page.title.jobs=Aufträge
page.title.appuser.edit=App-Nutzer bearbeiten page.title.appuser.edit=App-Nutzer bearbeiten
@@ -328,9 +328,9 @@ editappuser.dialog.delete.text=Möchten Sie diesen App-Nutzer wirklich löschen?
editappuser.dialog.delete.confirm=Löschen editappuser.dialog.delete.confirm=Löschen
# Customers # Customers
customers.title=Kunden customers.title=Adressbuch
customers.button.add=Neuen Kunden hinzufügen customers.button.add=Neue Adresse anlegen
customers.hint.click=Klicken Sie auf einen Kunden, um Details zu sehen customers.hint.click=Klicken Sie auf einen Adresseintrag, um Details zu sehen
customers.column.company=Firma customers.column.company=Firma
customers.column.name=Name customers.column.name=Name
customers.column.email=E-Mail customers.column.email=E-Mail
@@ -349,8 +349,8 @@ editcustomer.dialog.delete.text=Möchten Sie diese Adresse wirklich löschen?
editcustomer.dialog.delete.confirm=Löschen editcustomer.dialog.delete.confirm=Löschen
# Add Customer # Add Customer
addcustomer.title=Neuen Kunden anlegen addcustomer.title=Neue Adresse anlegen
addcustomer.button.submit=Kunden anlegen addcustomer.button.submit=Adresse anlegen
addcustomer.notification.validation=Bitte füllen Sie alle Pflichtfelder aus addcustomer.notification.validation=Bitte füllen Sie alle Pflichtfelder aus
addcustomer.notification.success=Kunde erfolgreich angelegt addcustomer.notification.success=Kunde erfolgreich angelegt
addcustomer.notification.check=Bitte überprüfen Sie Ihre Eingaben addcustomer.notification.check=Bitte überprüfen Sie Ihre Eingaben
@@ -1056,3 +1056,24 @@ adminpricetable.field.revenue=Umsatzbeteiligung
adminpricetable.notification.saved=Preistabelle wurde gespeichert adminpricetable.notification.saved=Preistabelle wurde gespeichert
adminpricetable.notification.save.error=Fehler beim Speichern: {0} adminpricetable.notification.save.error=Fehler beim Speichern: {0}
adminpricetable.notification.load.error=Fehler beim Laden: {0} adminpricetable.notification.load.error=Fehler beim Laden: {0}
# Rechnungsgenerator - System-Template (Rechnungen an Nutzer)
invoicegenerator.issuer.header=Rechnungssteller
invoicegenerator.issuer.website=Webseite
invoicegenerator.issuer.senderline=Absenderzeile
invoicegenerator.issuer.paymentterms=Zahlungsbedingungen
invoicegenerator.issuer.footer=Fußzeile
invoicegenerator.issuer.invoicedate=Rechnungsdatum
invoicegenerator.recipient.header=Empfänger (Nutzer)
invoicegenerator.recipient.company=Nutzer-Firma
invoicegenerator.recipient.name=Nutzer-Name
invoicegenerator.recipient.street=Nutzer-Straße
invoicegenerator.recipient.city=Nutzer-PLZ/Ort
invoicegenerator.recipient.email=Nutzer-E-Mail
invoicegenerator.template.saved=Template gespeichert
invoicegenerator.template.save.error=Fehler beim Speichern des Templates: {0}
invoicegenerator.button.clear=Leeren
invoicegenerator.notification.cleared=Canvas geleert
invoicegenerator.notification.canvas.error=Canvas-Daten konnten nicht gelesen werden
invoicegenerator.notification.preview.error=Fehler bei der Vorschau: {0}
invoicegenerator.pdf.preview.title=PDF-Vorschau

View File

@@ -213,7 +213,7 @@ page.title.dashboard=VotianLT - T\u00f6\u00f6laud
page.title.appuser.create=Uue \u00e4pikasutaja loomine page.title.appuser.create=Uue \u00e4pikasutaja loomine
page.title.messages=S\u00f5numid page.title.messages=S\u00f5numid
page.title.register=VotianLT-sse registreerimine page.title.register=VotianLT-sse registreerimine
page.title.customers=Kliendid page.title.customers=Aadressiraamat
page.title.customer.edit=Kliendi muutmine page.title.customer.edit=Kliendi muutmine
page.title.verwaltung=Haldus page.title.verwaltung=Haldus
page.title.company.create=Uue ettev\u00f5tte loomine page.title.company.create=Uue ettev\u00f5tte loomine
@@ -221,7 +221,7 @@ page.title.imprint=Impressum
page.title.profile.edit=Profiili muutmine page.title.profile.edit=Profiili muutmine
page.title.admin.dashboard=Administraatori t\u00f6\u00f6laud page.title.admin.dashboard=Administraatori t\u00f6\u00f6laud
page.title.invoice.create=Arve koostamine page.title.invoice.create=Arve koostamine
page.title.customer.create=Uue kliendi loomine page.title.customer.create=Uue aadressi loomine
page.title.login=VotianLT-sse sisselogimine page.title.login=VotianLT-sse sisselogimine
page.title.jobs=Tellimused page.title.jobs=Tellimused
page.title.appuser.edit=\u00c4pikasutaja muutmine page.title.appuser.edit=\u00c4pikasutaja muutmine
@@ -292,9 +292,9 @@ editappuser.notification.password.enter=Palun sisestage uus parool
editappuser.notification.deleted=\u00c4pikasutaja edukalt kustutatud editappuser.notification.deleted=\u00c4pikasutaja edukalt kustutatud
editappuser.dialog.delete.text=Kas soovite selle \u00e4pikasutaja t\u00f5esti kustutada? editappuser.dialog.delete.text=Kas soovite selle \u00e4pikasutaja t\u00f5esti kustutada?
editappuser.dialog.delete.confirm=Kustuta editappuser.dialog.delete.confirm=Kustuta
customers.title=Kliendid customers.title=Aadressiraamat
customers.button.add=Lisa uus klient customers.button.add=Lisa uus aadress
customers.hint.click=Kl\u00f5psake kliendil, et n\u00e4ha \u00fcksikasju customers.hint.click=Kl\u00f5psake aadressikirjel, et n\u00e4ha \u00fcksikasju
customers.column.company=Ettev\u00f5te customers.column.company=Ettev\u00f5te
customers.column.name=Nimi customers.column.name=Nimi
customers.column.email=E-post customers.column.email=E-post
@@ -309,8 +309,8 @@ editcustomer.notification.check=Palun kontrollige oma sisestusi
editcustomer.notification.deleted=Klient edukalt kustutatud editcustomer.notification.deleted=Klient edukalt kustutatud
editcustomer.dialog.delete.text=Kas soovite selle kliendi t\u00f5esti kustutada? editcustomer.dialog.delete.text=Kas soovite selle kliendi t\u00f5esti kustutada?
editcustomer.dialog.delete.confirm=Kustuta editcustomer.dialog.delete.confirm=Kustuta
addcustomer.title=Uue kliendi loomine addcustomer.title=Uue aadressi loomine
addcustomer.button.submit=Loo klient addcustomer.button.submit=Loo aadress
addcustomer.notification.validation=Palun t\u00e4itke k\u00f5ik kohustuslikud v\u00e4ljad addcustomer.notification.validation=Palun t\u00e4itke k\u00f5ik kohustuslikud v\u00e4ljad
addcustomer.notification.success=Klient edukalt loodud addcustomer.notification.success=Klient edukalt loodud
addcustomer.notification.check=Palun kontrollige oma sisestusi addcustomer.notification.check=Palun kontrollige oma sisestusi
@@ -886,3 +886,24 @@ adminpricetable.field.revenue=K\u00e4ibest osalus
adminpricetable.notification.saved=Hinnatabel on salvestatud adminpricetable.notification.saved=Hinnatabel on salvestatud
adminpricetable.notification.save.error=Salvestamisel ilmnes viga: {0} adminpricetable.notification.save.error=Salvestamisel ilmnes viga: {0}
adminpricetable.notification.load.error=Laadimisel ilmnes viga: {0} adminpricetable.notification.load.error=Laadimisel ilmnes viga: {0}
# Arvegeneraator - süsteemi mall (arved kasutajatele)
invoicegenerator.issuer.header=Arve väljastaja
invoicegenerator.issuer.website=Veebileht
invoicegenerator.issuer.senderline=Saatja rida
invoicegenerator.issuer.paymentterms=Maksetingimused
invoicegenerator.issuer.footer=Jalus
invoicegenerator.issuer.invoicedate=Arve kuupäev
invoicegenerator.recipient.header=Saaja (kasutaja)
invoicegenerator.recipient.company=Kasutaja firma
invoicegenerator.recipient.name=Kasutaja nimi
invoicegenerator.recipient.street=Kasutaja tänav
invoicegenerator.recipient.city=Kasutaja linn
invoicegenerator.recipient.email=Kasutaja e-post
invoicegenerator.template.saved=Mall salvestatud
invoicegenerator.template.save.error=Viga malli salvestamisel: {0}
invoicegenerator.button.clear=Tühjenda
invoicegenerator.notification.cleared=Lõuend tühjendatud
invoicegenerator.notification.canvas.error=Lõuendi andmeid ei õnnestunud lugeda
invoicegenerator.notification.preview.error=Eelvaate viga: {0}
invoicegenerator.pdf.preview.title=PDF-i eelvaade

View File

@@ -241,7 +241,7 @@ page.title.dashboard=VotianLT - Dashboard
page.title.appuser.create=Create New App User page.title.appuser.create=Create New App User
page.title.messages=Messages page.title.messages=Messages
page.title.register=Register at VotianLT page.title.register=Register at VotianLT
page.title.customers=Customers page.title.customers=Address Book
page.title.customer.edit=Edit Customer page.title.customer.edit=Edit Customer
page.title.verwaltung=Management page.title.verwaltung=Management
page.title.company.create=Create New Company page.title.company.create=Create New Company
@@ -249,7 +249,7 @@ page.title.imprint=Imprint
page.title.profile.edit=Edit Profile page.title.profile.edit=Edit Profile
page.title.admin.dashboard=Admin Dashboard page.title.admin.dashboard=Admin Dashboard
page.title.invoice.create=Create Invoice page.title.invoice.create=Create Invoice
page.title.customer.create=Create New Customer page.title.customer.create=Create New Address
page.title.login=Log In to VotianLT page.title.login=Log In to VotianLT
page.title.jobs=Jobs page.title.jobs=Jobs
page.title.appuser.edit=Edit App User page.title.appuser.edit=Edit App User
@@ -328,9 +328,9 @@ editappuser.dialog.delete.text=Are you sure you want to delete this app user?
editappuser.dialog.delete.confirm=Delete editappuser.dialog.delete.confirm=Delete
# Customers # Customers
customers.title=Customers customers.title=Address Book
customers.button.add=Add New Customer customers.button.add=Add New Address
customers.hint.click=Click on a customer to see details customers.hint.click=Click on an address entry to see details
customers.column.company=Company customers.column.company=Company
customers.column.name=Name customers.column.name=Name
customers.column.email=Email customers.column.email=Email
@@ -349,8 +349,8 @@ editcustomer.dialog.delete.text=Are you sure you want to delete this customer?
editcustomer.dialog.delete.confirm=Delete editcustomer.dialog.delete.confirm=Delete
# Add Customer # Add Customer
addcustomer.title=Create New Customer addcustomer.title=Create New Address
addcustomer.button.submit=Create Customer addcustomer.button.submit=Create Address
addcustomer.notification.validation=Please fill in all required fields addcustomer.notification.validation=Please fill in all required fields
addcustomer.notification.success=Customer created successfully addcustomer.notification.success=Customer created successfully
addcustomer.notification.check=Please check your input addcustomer.notification.check=Please check your input
@@ -1056,3 +1056,24 @@ adminpricetable.field.revenue=Revenue Share
adminpricetable.notification.saved=Price table has been saved adminpricetable.notification.saved=Price table has been saved
adminpricetable.notification.save.error=Error saving: {0} adminpricetable.notification.save.error=Error saving: {0}
adminpricetable.notification.load.error=Error loading: {0} adminpricetable.notification.load.error=Error loading: {0}
# Invoice generator - system template (invoices to users)
invoicegenerator.issuer.header=Invoice issuer
invoicegenerator.issuer.website=Website
invoicegenerator.issuer.senderline=Sender line
invoicegenerator.issuer.paymentterms=Payment terms
invoicegenerator.issuer.footer=Footer
invoicegenerator.issuer.invoicedate=Invoice date
invoicegenerator.recipient.header=Recipient (user)
invoicegenerator.recipient.company=User company
invoicegenerator.recipient.name=User name
invoicegenerator.recipient.street=User street
invoicegenerator.recipient.city=User city
invoicegenerator.recipient.email=User email
invoicegenerator.template.saved=Template saved
invoicegenerator.template.save.error=Error saving template: {0}
invoicegenerator.button.clear=Clear
invoicegenerator.notification.cleared=Canvas cleared
invoicegenerator.notification.canvas.error=Could not read canvas data
invoicegenerator.notification.preview.error=Preview error: {0}
invoicegenerator.pdf.preview.title=PDF preview

View File

@@ -240,7 +240,7 @@ page.title.dashboard=VotianLT - Panel de control
page.title.appuser.create=Crear nuevo usuario de la app page.title.appuser.create=Crear nuevo usuario de la app
page.title.messages=Mensajes page.title.messages=Mensajes
page.title.register=Registrarse en VotianLT page.title.register=Registrarse en VotianLT
page.title.customers=Clientes page.title.customers=Libreta de direcciones
page.title.customer.edit=Editar cliente page.title.customer.edit=Editar cliente
page.title.verwaltung=Administraci\u00f3n page.title.verwaltung=Administraci\u00f3n
page.title.company.create=Crear nueva empresa page.title.company.create=Crear nueva empresa
@@ -248,7 +248,7 @@ page.title.imprint=Aviso legal
page.title.profile.edit=Editar perfil page.title.profile.edit=Editar perfil
page.title.admin.dashboard=Panel de administraci\u00f3n page.title.admin.dashboard=Panel de administraci\u00f3n
page.title.invoice.create=Crear factura page.title.invoice.create=Crear factura
page.title.customer.create=Crear nuevo cliente page.title.customer.create=Crear nueva dirección
page.title.login=Iniciar sesi\u00f3n en VotianLT page.title.login=Iniciar sesi\u00f3n en VotianLT
page.title.jobs=Pedidos page.title.jobs=Pedidos
page.title.appuser.edit=Editar usuario de la app page.title.appuser.edit=Editar usuario de la app
@@ -327,9 +327,9 @@ editappuser.dialog.delete.text=\u00bfDesea realmente eliminar este usuario de la
editappuser.dialog.delete.confirm=Eliminar editappuser.dialog.delete.confirm=Eliminar
# Customers # Customers
customers.title=Clientes customers.title=Libreta de direcciones
customers.button.add=A\u00f1adir nuevo cliente customers.button.add=A\u00f1adir nueva direcci\u00f3n
customers.hint.click=Haga clic en un cliente para ver los detalles customers.hint.click=Haga clic en una entrada de direcci\u00f3n para ver los detalles
customers.column.company=Empresa customers.column.company=Empresa
customers.column.name=Nombre customers.column.name=Nombre
customers.column.email=Correo electr\u00f3nico customers.column.email=Correo electr\u00f3nico
@@ -348,8 +348,8 @@ editcustomer.dialog.delete.text=\u00bfDesea realmente eliminar este cliente?
editcustomer.dialog.delete.confirm=Eliminar editcustomer.dialog.delete.confirm=Eliminar
# Add Customer # Add Customer
addcustomer.title=Crear nuevo cliente addcustomer.title=Crear nueva dirección
addcustomer.button.submit=Crear cliente addcustomer.button.submit=Crear dirección
addcustomer.notification.validation=Por favor, complete todos los campos obligatorios addcustomer.notification.validation=Por favor, complete todos los campos obligatorios
addcustomer.notification.success=Cliente creado correctamente addcustomer.notification.success=Cliente creado correctamente
addcustomer.notification.check=Por favor, revise sus datos addcustomer.notification.check=Por favor, revise sus datos
@@ -987,3 +987,24 @@ adminpricetable.field.revenue=Participaci\u00f3n en los ingresos
adminpricetable.notification.saved=La tabla de precios ha sido guardada adminpricetable.notification.saved=La tabla de precios ha sido guardada
adminpricetable.notification.save.error=Error al guardar: {0} adminpricetable.notification.save.error=Error al guardar: {0}
adminpricetable.notification.load.error=Error al cargar: {0} adminpricetable.notification.load.error=Error al cargar: {0}
# Generador de facturas - plantilla del sistema (facturas a usuarios)
invoicegenerator.issuer.header=Emisor de la factura
invoicegenerator.issuer.website=Sitio web
invoicegenerator.issuer.senderline=Línea de remitente
invoicegenerator.issuer.paymentterms=Condiciones de pago
invoicegenerator.issuer.footer=Pie de página
invoicegenerator.issuer.invoicedate=Fecha de la factura
invoicegenerator.recipient.header=Destinatario (usuario)
invoicegenerator.recipient.company=Empresa del usuario
invoicegenerator.recipient.name=Nombre del usuario
invoicegenerator.recipient.street=Calle del usuario
invoicegenerator.recipient.city=Ciudad del usuario
invoicegenerator.recipient.email=Correo del usuario
invoicegenerator.template.saved=Plantilla guardada
invoicegenerator.template.save.error=Error al guardar la plantilla: {0}
invoicegenerator.button.clear=Vaciar
invoicegenerator.notification.cleared=Lienzo vaciado
invoicegenerator.notification.canvas.error=No se pudieron leer los datos del lienzo
invoicegenerator.notification.preview.error=Error de vista previa: {0}
invoicegenerator.pdf.preview.title=Vista previa del PDF

View File

@@ -240,7 +240,7 @@ page.title.dashboard=VotianLT - Tableau de bord
page.title.appuser.create=Cr\u00e9er un nouvel utilisateur d'app page.title.appuser.create=Cr\u00e9er un nouvel utilisateur d'app
page.title.messages=Messages page.title.messages=Messages
page.title.register=S'inscrire sur VotianLT page.title.register=S'inscrire sur VotianLT
page.title.customers=Clients page.title.customers=Carnet d'adresses
page.title.customer.edit=Modifier le client page.title.customer.edit=Modifier le client
page.title.verwaltung=Administration page.title.verwaltung=Administration
page.title.company.create=Cr\u00e9er une nouvelle entreprise page.title.company.create=Cr\u00e9er une nouvelle entreprise
@@ -248,7 +248,7 @@ page.title.imprint=Mentions l\u00e9gales
page.title.profile.edit=Modifier le profil page.title.profile.edit=Modifier le profil
page.title.admin.dashboard=Tableau de bord administrateur page.title.admin.dashboard=Tableau de bord administrateur
page.title.invoice.create=Cr\u00e9er une facture page.title.invoice.create=Cr\u00e9er une facture
page.title.customer.create=Cr\u00e9er un nouveau client page.title.customer.create=Cr\u00e9er une nouvelle adresse
page.title.login=Se connecter \u00e0 VotianLT page.title.login=Se connecter \u00e0 VotianLT
page.title.jobs=Missions page.title.jobs=Missions
page.title.appuser.edit=Modifier l'utilisateur d'app page.title.appuser.edit=Modifier l'utilisateur d'app
@@ -327,9 +327,9 @@ editappuser.dialog.delete.text=Voulez-vous vraiment supprimer cet utilisateur d'
editappuser.dialog.delete.confirm=Supprimer editappuser.dialog.delete.confirm=Supprimer
# Customers # Customers
customers.title=Clients customers.title=Carnet d'adresses
customers.button.add=Ajouter un nouveau client customers.button.add=Ajouter une nouvelle adresse
customers.hint.click=Cliquez sur un client pour voir les d\u00e9tails customers.hint.click=Cliquez sur une entr\u00e9e d'adresse pour voir les d\u00e9tails
customers.column.company=Entreprise customers.column.company=Entreprise
customers.column.name=Nom customers.column.name=Nom
customers.column.email=E-mail customers.column.email=E-mail
@@ -348,8 +348,8 @@ editcustomer.dialog.delete.text=Voulez-vous vraiment supprimer ce client ?
editcustomer.dialog.delete.confirm=Supprimer editcustomer.dialog.delete.confirm=Supprimer
# Add Customer # Add Customer
addcustomer.title=Cr\u00e9er un nouveau client addcustomer.title=Cr\u00e9er une nouvelle adresse
addcustomer.button.submit=Cr\u00e9er le client addcustomer.button.submit=Cr\u00e9er l'adresse
addcustomer.notification.validation=Veuillez remplir tous les champs obligatoires addcustomer.notification.validation=Veuillez remplir tous les champs obligatoires
addcustomer.notification.success=Client cr\u00e9\u00e9 avec succ\u00e8s addcustomer.notification.success=Client cr\u00e9\u00e9 avec succ\u00e8s
addcustomer.notification.check=Veuillez v\u00e9rifier vos saisies addcustomer.notification.check=Veuillez v\u00e9rifier vos saisies
@@ -987,3 +987,24 @@ adminpricetable.field.revenue=Participation au chiffre d'affaires
adminpricetable.notification.saved=Le tableau des prix a \u00e9t\u00e9 enregistr\u00e9 adminpricetable.notification.saved=Le tableau des prix a \u00e9t\u00e9 enregistr\u00e9
adminpricetable.notification.save.error=Erreur lors de l'enregistrement : {0} adminpricetable.notification.save.error=Erreur lors de l'enregistrement : {0}
adminpricetable.notification.load.error=Erreur lors du chargement : {0} adminpricetable.notification.load.error=Erreur lors du chargement : {0}
# Générateur de factures - modèle système (factures aux utilisateurs)
invoicegenerator.issuer.header=Émetteur de la facture
invoicegenerator.issuer.website=Site web
invoicegenerator.issuer.senderline=Ligne d'expéditeur
invoicegenerator.issuer.paymentterms=Conditions de paiement
invoicegenerator.issuer.footer=Pied de page
invoicegenerator.issuer.invoicedate=Date de la facture
invoicegenerator.recipient.header=Destinataire (utilisateur)
invoicegenerator.recipient.company=Société de l'utilisateur
invoicegenerator.recipient.name=Nom de l'utilisateur
invoicegenerator.recipient.street=Rue de l'utilisateur
invoicegenerator.recipient.city=Ville de l'utilisateur
invoicegenerator.recipient.email=E-mail de l'utilisateur
invoicegenerator.template.saved=Modèle enregistré
invoicegenerator.template.save.error=Erreur lors de l''enregistrement du modèle : {0}
invoicegenerator.button.clear=Vider
invoicegenerator.notification.cleared=Canevas vidé
invoicegenerator.notification.canvas.error=Impossible de lire les données du canevas
invoicegenerator.notification.preview.error=Erreur d''aperçu : {0}
invoicegenerator.pdf.preview.title=Aperçu PDF

View File

@@ -240,7 +240,7 @@ page.title.dashboard=VotianLT - Valdymo skydas
page.title.appuser.create=Sukurti naują programėlės naudotoją page.title.appuser.create=Sukurti naują programėlės naudotoją
page.title.messages=Žinutės page.title.messages=Žinutės
page.title.register=Registracija VotianLT page.title.register=Registracija VotianLT
page.title.customers=Klientai page.title.customers=Adresų knyga
page.title.customer.edit=Redaguoti klientą page.title.customer.edit=Redaguoti klientą
page.title.verwaltung=Administravimas page.title.verwaltung=Administravimas
page.title.company.create=Sukurti naują įmonę page.title.company.create=Sukurti naują įmonę
@@ -248,7 +248,7 @@ page.title.imprint=Informacija
page.title.profile.edit=Redaguoti profilį page.title.profile.edit=Redaguoti profilį
page.title.admin.dashboard=Administratoriaus valdymo skydas page.title.admin.dashboard=Administratoriaus valdymo skydas
page.title.invoice.create=Sukurti sąskaitą page.title.invoice.create=Sukurti sąskaitą
page.title.customer.create=Sukurti naują klientą page.title.customer.create=Sukurti naują adresą
page.title.login=Prisijungti prie VotianLT page.title.login=Prisijungti prie VotianLT
page.title.jobs=Užsakymai page.title.jobs=Užsakymai
page.title.appuser.edit=Redaguoti programėlės naudotoją page.title.appuser.edit=Redaguoti programėlės naudotoją
@@ -327,9 +327,9 @@ editappuser.dialog.delete.text=Ar tikrai norite ištrinti šį programėlės nau
editappuser.dialog.delete.confirm=Ištrinti editappuser.dialog.delete.confirm=Ištrinti
# Customers # Customers
customers.title=Klientai customers.title=Adresų knyga
customers.button.add=Pridėti naują klientą customers.button.add=Pridėti naują adresą
customers.hint.click=Spustelėkite klientą, kad pamatytumėte informaciją customers.hint.click=Spustelėkite adreso įrašą, kad pamatytumėte informaciją
customers.column.company=Įmonė customers.column.company=Įmonė
customers.column.name=Pavadinimas customers.column.name=Pavadinimas
customers.column.email=El. paštas customers.column.email=El. paštas
@@ -348,8 +348,8 @@ editcustomer.dialog.delete.text=Ar tikrai norite ištrinti šį klientą?
editcustomer.dialog.delete.confirm=Ištrinti editcustomer.dialog.delete.confirm=Ištrinti
# Add Customer # Add Customer
addcustomer.title=Sukurti naują klientą addcustomer.title=Sukurti naują adresą
addcustomer.button.submit=Sukurti klientą addcustomer.button.submit=Sukurti adresą
addcustomer.notification.validation=Prašome užpildyti visus privalomus laukus addcustomer.notification.validation=Prašome užpildyti visus privalomus laukus
addcustomer.notification.success=Klientas sėkmingai sukurtas addcustomer.notification.success=Klientas sėkmingai sukurtas
addcustomer.notification.check=Prašome patikrinti savo įvestį addcustomer.notification.check=Prašome patikrinti savo įvestį
@@ -989,3 +989,24 @@ adminpricetable.field.revenue=Pajamų dalis
adminpricetable.notification.saved=Kainų lentelė išsaugota adminpricetable.notification.saved=Kainų lentelė išsaugota
adminpricetable.notification.save.error=Klaida išsaugant: {0} adminpricetable.notification.save.error=Klaida išsaugant: {0}
adminpricetable.notification.load.error=Klaida įkeliant: {0} adminpricetable.notification.load.error=Klaida įkeliant: {0}
# Sąskaitų generatorius - sistemos šablonas (sąskaitos naudotojams)
invoicegenerator.issuer.header=Sąskaitos išrašytojas
invoicegenerator.issuer.website=Svetainė
invoicegenerator.issuer.senderline=Siuntėjo eilutė
invoicegenerator.issuer.paymentterms=Mokėjimo sąlygos
invoicegenerator.issuer.footer=Poraštė
invoicegenerator.issuer.invoicedate=Sąskaitos data
invoicegenerator.recipient.header=Gavėjas (naudotojas)
invoicegenerator.recipient.company=Naudotojo įmonė
invoicegenerator.recipient.name=Naudotojo vardas
invoicegenerator.recipient.street=Naudotojo gatvė
invoicegenerator.recipient.city=Naudotojo miestas
invoicegenerator.recipient.email=Naudotojo el. paštas
invoicegenerator.template.saved=Šablonas išsaugotas
invoicegenerator.template.save.error=Klaida išsaugant šabloną: {0}
invoicegenerator.button.clear=Išvalyti
invoicegenerator.notification.cleared=Drobė išvalyta
invoicegenerator.notification.canvas.error=Nepavyko nuskaityti drobės duomenų
invoicegenerator.notification.preview.error=Peržiūros klaida: {0}
invoicegenerator.pdf.preview.title=PDF peržiūra

View File

@@ -240,7 +240,7 @@ page.title.dashboard=VotianLT - Informācijas panelis
page.title.appuser.create=Izveidot jaunu lietotnes lietotāju page.title.appuser.create=Izveidot jaunu lietotnes lietotāju
page.title.messages=Ziņojumi page.title.messages=Ziņojumi
page.title.register=Reģistrēties VotianLT page.title.register=Reģistrēties VotianLT
page.title.customers=Klienti page.title.customers=Adrešu grāmata
page.title.customer.edit=Rediģēt klientu page.title.customer.edit=Rediģēt klientu
page.title.verwaltung=Pārvaldība page.title.verwaltung=Pārvaldība
page.title.company.create=Izveidot jaunu uzņēmumu page.title.company.create=Izveidot jaunu uzņēmumu
@@ -248,7 +248,7 @@ page.title.imprint=Impressum
page.title.profile.edit=Rediģēt profilu page.title.profile.edit=Rediģēt profilu
page.title.admin.dashboard=Administrācijas panelis page.title.admin.dashboard=Administrācijas panelis
page.title.invoice.create=Izveidot rēķinu page.title.invoice.create=Izveidot rēķinu
page.title.customer.create=Izveidot jaunu klientu page.title.customer.create=Izveidot jaunu adresi
page.title.login=Pieteikties VotianLT page.title.login=Pieteikties VotianLT
page.title.jobs=Uzdevumi page.title.jobs=Uzdevumi
page.title.appuser.edit=Rediģēt lietotnes lietotāju page.title.appuser.edit=Rediģēt lietotnes lietotāju
@@ -327,9 +327,9 @@ editappuser.dialog.delete.text=Vai tiešām vēlaties dzēst šo lietotnes lieto
editappuser.dialog.delete.confirm=Dzēst editappuser.dialog.delete.confirm=Dzēst
# Customers # Customers
customers.title=Klienti customers.title=Adrešu grāmata
customers.button.add=Pievienot jaunu klientu customers.button.add=Pievienot jaunu adresi
customers.hint.click=Noklikšķiniet uz klienta, lai redzētu detaļas customers.hint.click=Noklikšķiniet uz adreses ieraksta, lai redzētu detaļas
customers.column.company=Uzņēmums customers.column.company=Uzņēmums
customers.column.name=Nosaukums customers.column.name=Nosaukums
customers.column.email=E-pasts customers.column.email=E-pasts
@@ -348,8 +348,8 @@ editcustomer.dialog.delete.text=Vai tiešām vēlaties dzēst šo klientu?
editcustomer.dialog.delete.confirm=Dzēst editcustomer.dialog.delete.confirm=Dzēst
# Add Customer # Add Customer
addcustomer.title=Izveidot jaunu klientu addcustomer.title=Izveidot jaunu adresi
addcustomer.button.submit=Izveidot klientu addcustomer.button.submit=Izveidot adresi
addcustomer.notification.validation=Lūdzu, aizpildiet visus obligātos laukus addcustomer.notification.validation=Lūdzu, aizpildiet visus obligātos laukus
addcustomer.notification.success=Klients veiksmīgi izveidots addcustomer.notification.success=Klients veiksmīgi izveidots
addcustomer.notification.check=Lūdzu, pārbaudiet savus ievadītos datus addcustomer.notification.check=Lūdzu, pārbaudiet savus ievadītos datus
@@ -987,3 +987,24 @@ adminpricetable.field.revenue=Ieņēmumu daļa
adminpricetable.notification.saved=Cenu tabula saglabāta adminpricetable.notification.saved=Cenu tabula saglabāta
adminpricetable.notification.save.error=Kļūda saglabājot: {0} adminpricetable.notification.save.error=Kļūda saglabājot: {0}
adminpricetable.notification.load.error=Kļūda ielādējot: {0} adminpricetable.notification.load.error=Kļūda ielādējot: {0}
# Rēķinu ģenerators - sistēmas veidne (rēķini lietotājiem)
invoicegenerator.issuer.header=Rēķina izrakstītājs
invoicegenerator.issuer.website=Tīmekļa vietne
invoicegenerator.issuer.senderline=Sūtītāja rinda
invoicegenerator.issuer.paymentterms=Apmaksas nosacījumi
invoicegenerator.issuer.footer=Kājene
invoicegenerator.issuer.invoicedate=Rēķina datums
invoicegenerator.recipient.header=Saņēmējs (lietotājs)
invoicegenerator.recipient.company=Lietotāja uzņēmums
invoicegenerator.recipient.name=Lietotāja vārds
invoicegenerator.recipient.street=Lietotāja iela
invoicegenerator.recipient.city=Lietotāja pilsēta
invoicegenerator.recipient.email=Lietotāja e-pasts
invoicegenerator.template.saved=Veidne saglabāta
invoicegenerator.template.save.error=Kļūda saglabājot veidni: {0}
invoicegenerator.button.clear=Notīrīt
invoicegenerator.notification.cleared=Audekls notīrīts
invoicegenerator.notification.canvas.error=Neizdevās nolasīt audekla datus
invoicegenerator.notification.preview.error=Priekšskatījuma kļūda: {0}
invoicegenerator.pdf.preview.title=PDF priekšskatījums

View File

@@ -240,7 +240,7 @@ page.title.dashboard=VotianLT - Panel g\u0142\u00f3wny
page.title.appuser.create=Dodaj nowego u\u017cytkownika aplikacji page.title.appuser.create=Dodaj nowego u\u017cytkownika aplikacji
page.title.messages=Wiadomo\u015bci page.title.messages=Wiadomo\u015bci
page.title.register=Rejestracja w VotianLT page.title.register=Rejestracja w VotianLT
page.title.customers=Klienci page.title.customers=Książka adresowa
page.title.customer.edit=Edytuj klienta page.title.customer.edit=Edytuj klienta
page.title.verwaltung=Zarz\u0105dzanie page.title.verwaltung=Zarz\u0105dzanie
page.title.company.create=Dodaj now\u0105 firm\u0119 page.title.company.create=Dodaj now\u0105 firm\u0119
@@ -248,7 +248,7 @@ page.title.imprint=Impressum
page.title.profile.edit=Edytuj profil page.title.profile.edit=Edytuj profil
page.title.admin.dashboard=Panel administracyjny page.title.admin.dashboard=Panel administracyjny
page.title.invoice.create=Utw\u00f3rz faktur\u0119 page.title.invoice.create=Utw\u00f3rz faktur\u0119
page.title.customer.create=Dodaj nowego klienta page.title.customer.create=Dodaj nowy adres
page.title.login=Logowanie do VotianLT page.title.login=Logowanie do VotianLT
page.title.jobs=Zlecenia page.title.jobs=Zlecenia
page.title.appuser.edit=Edytuj u\u017cytkownika aplikacji page.title.appuser.edit=Edytuj u\u017cytkownika aplikacji
@@ -327,9 +327,9 @@ editappuser.dialog.delete.text=Czy na pewno chcesz usun\u0105\u0107 tego u\u017c
editappuser.dialog.delete.confirm=Usu\u0144 editappuser.dialog.delete.confirm=Usu\u0144
# Customers # Customers
customers.title=Klienci customers.title=Ksi\u0105\u017cka adresowa
customers.button.add=Dodaj nowego klienta customers.button.add=Dodaj nowy adres
customers.hint.click=Kliknij klienta, aby zobaczy\u0107 szczeg\u00f3\u0142y customers.hint.click=Kliknij wpis adresowy, aby zobaczy\u0107 szczeg\u00f3\u0142y
customers.column.company=Firma customers.column.company=Firma
customers.column.name=Nazwa customers.column.name=Nazwa
customers.column.email=E-mail customers.column.email=E-mail
@@ -348,8 +348,8 @@ editcustomer.dialog.delete.text=Czy na pewno chcesz usun\u0105\u0107 tego klient
editcustomer.dialog.delete.confirm=Usu\u0144 editcustomer.dialog.delete.confirm=Usu\u0144
# Add Customer # Add Customer
addcustomer.title=Dodaj nowego klienta addcustomer.title=Dodaj nowy adres
addcustomer.button.submit=Dodaj klienta addcustomer.button.submit=Dodaj adres
addcustomer.notification.validation=Prosz\u0119 wype\u0142ni\u0107 wszystkie wymagane pola addcustomer.notification.validation=Prosz\u0119 wype\u0142ni\u0107 wszystkie wymagane pola
addcustomer.notification.success=Klient zosta\u0142 pomy\u015blnie dodany addcustomer.notification.success=Klient zosta\u0142 pomy\u015blnie dodany
addcustomer.notification.check=Prosz\u0119 sprawdzi\u0107 wprowadzone dane addcustomer.notification.check=Prosz\u0119 sprawdzi\u0107 wprowadzone dane
@@ -987,3 +987,24 @@ adminpricetable.field.revenue=Udzia\u0142 w przychodach
adminpricetable.notification.saved=Cennik zosta\u0142 zapisany adminpricetable.notification.saved=Cennik zosta\u0142 zapisany
adminpricetable.notification.save.error=B\u0142\u0105d podczas zapisywania: {0} adminpricetable.notification.save.error=B\u0142\u0105d podczas zapisywania: {0}
adminpricetable.notification.load.error=B\u0142\u0105d podczas \u0142adowania: {0} adminpricetable.notification.load.error=B\u0142\u0105d podczas \u0142adowania: {0}
# Generator faktur - szablon systemowy (faktury dla użytkowników)
invoicegenerator.issuer.header=Wystawca faktury
invoicegenerator.issuer.website=Strona internetowa
invoicegenerator.issuer.senderline=Linia nadawcy
invoicegenerator.issuer.paymentterms=Warunki płatności
invoicegenerator.issuer.footer=Stopka
invoicegenerator.issuer.invoicedate=Data faktury
invoicegenerator.recipient.header=Odbiorca (użytkownik)
invoicegenerator.recipient.company=Firma użytkownika
invoicegenerator.recipient.name=Nazwisko użytkownika
invoicegenerator.recipient.street=Ulica użytkownika
invoicegenerator.recipient.city=Miasto użytkownika
invoicegenerator.recipient.email=E-mail użytkownika
invoicegenerator.template.saved=Szablon zapisany
invoicegenerator.template.save.error=Błąd podczas zapisywania szablonu: {0}
invoicegenerator.button.clear=Wyczyść
invoicegenerator.notification.cleared=Obszar roboczy wyczyszczony
invoicegenerator.notification.canvas.error=Nie można odczytać danych obszaru roboczego
invoicegenerator.notification.preview.error=Błąd podglądu: {0}
invoicegenerator.pdf.preview.title=Podgląd PDF

View File

@@ -240,7 +240,7 @@ page.title.dashboard=VotianLT - Панель управления
page.title.appuser.create=Создать нового пользователя приложения page.title.appuser.create=Создать нового пользователя приложения
page.title.messages=Сообщения page.title.messages=Сообщения
page.title.register=Регистрация в VotianLT page.title.register=Регистрация в VotianLT
page.title.customers=Клиенты page.title.customers=Адресная книга
page.title.customer.edit=Редактирование клиента page.title.customer.edit=Редактирование клиента
page.title.verwaltung=Управление page.title.verwaltung=Управление
page.title.company.create=Создать новую компанию page.title.company.create=Создать новую компанию
@@ -248,7 +248,7 @@ page.title.imprint=Выходные данные
page.title.profile.edit=Редактирование профиля page.title.profile.edit=Редактирование профиля
page.title.admin.dashboard=Панель администратора page.title.admin.dashboard=Панель администратора
page.title.invoice.create=Создать счёт page.title.invoice.create=Создать счёт
page.title.customer.create=Создать нового клиента page.title.customer.create=Создать новый адрес
page.title.login=Вход в VotianLT page.title.login=Вход в VotianLT
page.title.jobs=Заказы page.title.jobs=Заказы
page.title.appuser.edit=Редактирование пользователя приложения page.title.appuser.edit=Редактирование пользователя приложения
@@ -327,9 +327,9 @@ editappuser.dialog.delete.text=Вы действительно хотите уд
editappuser.dialog.delete.confirm=Удалить editappuser.dialog.delete.confirm=Удалить
# Customers # Customers
customers.title=Клиенты customers.title=Адресная книга
customers.button.add=Добавить нового клиента customers.button.add=Добавить новый адрес
customers.hint.click=Нажмите на клиента, чтобы увидеть подробности customers.hint.click=Нажмите на запись адреса, чтобы увидеть подробности
customers.column.company=Компания customers.column.company=Компания
customers.column.name=Имя customers.column.name=Имя
customers.column.email=Эл. почта customers.column.email=Эл. почта
@@ -348,8 +348,8 @@ editcustomer.dialog.delete.text=Вы действительно хотите у
editcustomer.dialog.delete.confirm=Удалить editcustomer.dialog.delete.confirm=Удалить
# Add Customer # Add Customer
addcustomer.title=Создать нового клиента addcustomer.title=Создать новый адрес
addcustomer.button.submit=Создать клиента addcustomer.button.submit=Создать адрес
addcustomer.notification.validation=Пожалуйста, заполните все обязательные поля addcustomer.notification.validation=Пожалуйста, заполните все обязательные поля
addcustomer.notification.success=Клиент успешно создан addcustomer.notification.success=Клиент успешно создан
addcustomer.notification.check=Пожалуйста, проверьте ваши данные addcustomer.notification.check=Пожалуйста, проверьте ваши данные
@@ -987,3 +987,24 @@ adminpricetable.field.revenue=Участие в выручке
adminpricetable.notification.saved=Таблица цен сохранена adminpricetable.notification.saved=Таблица цен сохранена
adminpricetable.notification.save.error=Ошибка при сохранении: {0} adminpricetable.notification.save.error=Ошибка при сохранении: {0}
adminpricetable.notification.load.error=Ошибка при загрузке: {0} adminpricetable.notification.load.error=Ошибка при загрузке: {0}
# Генератор счетов - системный шаблон (счета пользователям)
invoicegenerator.issuer.header=Выставитель счёта
invoicegenerator.issuer.website=Веб-сайт
invoicegenerator.issuer.senderline=Строка отправителя
invoicegenerator.issuer.paymentterms=Условия оплаты
invoicegenerator.issuer.footer=Нижний колонтитул
invoicegenerator.issuer.invoicedate=Дата счёта
invoicegenerator.recipient.header=Получатель (пользователь)
invoicegenerator.recipient.company=Компания пользователя
invoicegenerator.recipient.name=Имя пользователя
invoicegenerator.recipient.street=Улица пользователя
invoicegenerator.recipient.city=Город пользователя
invoicegenerator.recipient.email=E-mail пользователя
invoicegenerator.template.saved=Шаблон сохранён
invoicegenerator.template.save.error=Ошибка при сохранении шаблона: {0}
invoicegenerator.button.clear=Очистить
invoicegenerator.notification.cleared=Холст очищен
invoicegenerator.notification.canvas.error=Не удалось прочитать данные холста
invoicegenerator.notification.preview.error=Ошибка предпросмотра: {0}
invoicegenerator.pdf.preview.title=Предпросмотр PDF

View File

@@ -240,7 +240,7 @@ page.title.dashboard=VotianLT - Kontrol Paneli
page.title.appuser.create=Yeni Uygulama Kullan\u0131c\u0131s\u0131 Olu\u015ftur page.title.appuser.create=Yeni Uygulama Kullan\u0131c\u0131s\u0131 Olu\u015ftur
page.title.messages=Mesajlar page.title.messages=Mesajlar
page.title.register=VotianLT'ye Kay\u0131t Ol page.title.register=VotianLT'ye Kay\u0131t Ol
page.title.customers=M\u00fc\u015fteriler page.title.customers=Adres defteri
page.title.customer.edit=M\u00fc\u015fteriyi D\u00fczenle page.title.customer.edit=M\u00fc\u015fteriyi D\u00fczenle
page.title.verwaltung=Y\u00f6netim page.title.verwaltung=Y\u00f6netim
page.title.company.create=Yeni \u015eirket Olu\u015ftur page.title.company.create=Yeni \u015eirket Olu\u015ftur
@@ -248,7 +248,7 @@ page.title.imprint=K\u00fcnye
page.title.profile.edit=Profili D\u00fczenle page.title.profile.edit=Profili D\u00fczenle
page.title.admin.dashboard=Y\u00f6netici Kontrol Paneli page.title.admin.dashboard=Y\u00f6netici Kontrol Paneli
page.title.invoice.create=Fatura Olu\u015ftur page.title.invoice.create=Fatura Olu\u015ftur
page.title.customer.create=Yeni M\u00fc\u015fteri Olu\u015ftur page.title.customer.create=Yeni Adres Olu\u015ftur
page.title.login=VotianLT'ye Giri\u015f Yap page.title.login=VotianLT'ye Giri\u015f Yap
page.title.jobs=\u0130\u015fler page.title.jobs=\u0130\u015fler
page.title.appuser.edit=Uygulama Kullan\u0131c\u0131s\u0131n\u0131 D\u00fczenle page.title.appuser.edit=Uygulama Kullan\u0131c\u0131s\u0131n\u0131 D\u00fczenle
@@ -327,9 +327,9 @@ editappuser.dialog.delete.text=Bu uygulama kullan\u0131c\u0131s\u0131n\u0131 sil
editappuser.dialog.delete.confirm=Sil editappuser.dialog.delete.confirm=Sil
# Customers # Customers
customers.title=M\u00fc\u015fteriler customers.title=Adres defteri
customers.button.add=Yeni M\u00fc\u015fteri Ekle customers.button.add=Yeni Adres Ekle
customers.hint.click=Ayr\u0131nt\u0131lar\u0131 g\u00f6rmek i\u00e7in bir m\u00fc\u015fteriye t\u0131klay\u0131n customers.hint.click=Ayr\u0131nt\u0131lar\u0131 g\u00f6rmek i\u00e7in bir adres giri\u015fine t\u0131klay\u0131n
customers.column.company=\u015eirket customers.column.company=\u015eirket
customers.column.name=Ad customers.column.name=Ad
customers.column.email=E-Posta customers.column.email=E-Posta
@@ -348,8 +348,8 @@ editcustomer.dialog.delete.text=Bu m\u00fc\u015fteriyi silmek istedi\u011finizde
editcustomer.dialog.delete.confirm=Sil editcustomer.dialog.delete.confirm=Sil
# Add Customer # Add Customer
addcustomer.title=Yeni M\u00fc\u015fteri Olu\u015ftur addcustomer.title=Yeni Adres Olu\u015ftur
addcustomer.button.submit=M\u00fc\u015fteri Olu\u015ftur addcustomer.button.submit=Adres Olu\u015ftur
addcustomer.notification.validation=L\u00fctfen t\u00fcm zorunlu alanlar\u0131 doldurun addcustomer.notification.validation=L\u00fctfen t\u00fcm zorunlu alanlar\u0131 doldurun
addcustomer.notification.success=M\u00fc\u015fteri ba\u015far\u0131yla olu\u015fturuldu addcustomer.notification.success=M\u00fc\u015fteri ba\u015far\u0131yla olu\u015fturuldu
addcustomer.notification.check=L\u00fctfen giri\u015flerinizi kontrol edin addcustomer.notification.check=L\u00fctfen giri\u015flerinizi kontrol edin
@@ -987,3 +987,24 @@ adminpricetable.field.revenue=Gelir Pay\u0131
adminpricetable.notification.saved=Fiyat tablosu kaydedildi adminpricetable.notification.saved=Fiyat tablosu kaydedildi
adminpricetable.notification.save.error=Kaydetme hatas\u0131: {0} adminpricetable.notification.save.error=Kaydetme hatas\u0131: {0}
adminpricetable.notification.load.error=Y\u00fckleme hatas\u0131: {0} adminpricetable.notification.load.error=Y\u00fckleme hatas\u0131: {0}
# Fatura oluşturucu - sistem şablonu (kullanıcılara faturalar)
invoicegenerator.issuer.header=Fatura düzenleyen
invoicegenerator.issuer.website=Web sitesi
invoicegenerator.issuer.senderline=Gönderen satırı
invoicegenerator.issuer.paymentterms=Ödeme koşulları
invoicegenerator.issuer.footer=Alt bilgi
invoicegenerator.issuer.invoicedate=Fatura tarihi
invoicegenerator.recipient.header=Alıcı (kullanıcı)
invoicegenerator.recipient.company=Kullanıcı firması
invoicegenerator.recipient.name=Kullanıcı adı
invoicegenerator.recipient.street=Kullanıcı sokağı
invoicegenerator.recipient.city=Kullanıcı şehri
invoicegenerator.recipient.email=Kullanıcı e-postası
invoicegenerator.template.saved=Şablon kaydedildi
invoicegenerator.template.save.error=Şablon kaydedilirken hata: {0}
invoicegenerator.button.clear=Temizle
invoicegenerator.notification.cleared=Tuval temizlendi
invoicegenerator.notification.canvas.error=Tuval verileri okunamadı
invoicegenerator.notification.preview.error=Önizleme hatası: {0}
invoicegenerator.pdf.preview.title=PDF önizleme