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:
Binary file not shown.
@@ -1,124 +1,57 @@
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
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
|
||||
* exposes an OpenAI-compatible API at {@code /v1/chat/completions}.
|
||||
* Configuration for LLM integration via the Anthropic Claude API.
|
||||
*/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class LlmConfig {
|
||||
|
||||
@Value("${app.ai.lmstudio.base-url}")
|
||||
private String lmstudioBaseUrl;
|
||||
@Value("${app.ai.anthropic.api-key}")
|
||||
private String apiKey;
|
||||
|
||||
@Value("${app.ai.lmstudio.model}")
|
||||
private String lmstudioModel;
|
||||
|
||||
@Value("${app.ai.lmstudio.htaccess-username}")
|
||||
private String lmstudioHtaccessUsername;
|
||||
|
||||
@Value("${app.ai.lmstudio.htaccess-password}")
|
||||
private String lmstudioHtaccessPassword;
|
||||
@Value("${app.ai.anthropic.model:claude-opus-4-8}")
|
||||
private String model;
|
||||
|
||||
@PostConstruct
|
||||
public void logConfig() {
|
||||
log.info("=== LLM Configuration ===");
|
||||
log.info("Provider: lmstudio");
|
||||
log.info("Base URL: {}", lmstudioBaseUrl);
|
||||
log.info("Model: {}", lmstudioModel);
|
||||
log.info("HTACCESS auth: {}", hasHtaccessCredentials() ? "configured" : "not configured");
|
||||
testConnection(lmstudioBaseUrl, lmstudioModel);
|
||||
log.info("Provider: Anthropic Claude");
|
||||
log.info("Model: {}", model);
|
||||
log.info("API key: {}", apiKey != null && !apiKey.isBlank() ? "configured" : "NOT configured");
|
||||
testConnection();
|
||||
}
|
||||
|
||||
private void testConnection(String baseUrl, String model) {
|
||||
log.info("Testing LLM connection to: {}", baseUrl);
|
||||
|
||||
// Test 1: Models endpoint
|
||||
testEndpoint(baseUrl + "/v1/models", "GET", null);
|
||||
|
||||
// Test 2: Chat completions (no streaming)
|
||||
String testPayload = "{\"model\":\"" + model
|
||||
+ "\",\"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) {
|
||||
/**
|
||||
* Validate API key and connectivity using the free count_tokens endpoint (no
|
||||
* tokens are billed).
|
||||
*/
|
||||
private void testConnection() {
|
||||
if (apiKey == null || apiKey.isBlank()) {
|
||||
log.warn("Skipping Claude connection test - no API key configured");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
log.info("Testing endpoint: {} {}", method, endpoint);
|
||||
URL url = URI.create(endpoint).toURL();
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod(method);
|
||||
connection.setConnectTimeout(5000);
|
||||
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());
|
||||
AnthropicClient client = AnthropicOkHttpClient.builder().apiKey(apiKey).build();
|
||||
MessageTokensCount count = client.messages()
|
||||
.countTokens(MessageCountTokensParams.builder().model(model).addUserMessage("ping").build());
|
||||
log.info("Claude connection test -> SUCCESS (count_tokens returned {} input tokens)",
|
||||
count.inputTokens());
|
||||
} 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() {
|
||||
return lmstudioModel;
|
||||
}
|
||||
|
||||
public boolean hasHtaccessCredentials() {
|
||||
return lmstudioHtaccessUsername != null && !lmstudioHtaccessUsername.isBlank()
|
||||
&& lmstudioHtaccessPassword != null && !lmstudioHtaccessPassword.isBlank();
|
||||
}
|
||||
|
||||
public String getLmstudioHtaccessUsername() {
|
||||
return lmstudioHtaccessUsername;
|
||||
}
|
||||
|
||||
public String getLmstudioHtaccessPassword() {
|
||||
return lmstudioHtaccessPassword;
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
package de.assecutor.votianlt.ai.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.anthropic.client.AnthropicClient;
|
||||
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
|
||||
import com.anthropic.models.messages.Message;
|
||||
import com.anthropic.models.messages.MessageCreateParams;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.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.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Direct REST client for LM Studio LLM API. Communicates via the
|
||||
* OpenAI-compatible /v1/chat/completions endpoint.
|
||||
* LLM client backed by the Anthropic Claude API (official Java SDK). Keeps the
|
||||
* same public interface as the previous LM-Studio-based implementation so
|
||||
* callers (AiStatisticsService, TranslationService) stay unchanged.
|
||||
*/
|
||||
@Component
|
||||
@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_TAG_PATTERN = Pattern.compile("(?is)</?think>");
|
||||
|
||||
private final WebClient webClient;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final AnthropicClient client;
|
||||
private final String model;
|
||||
|
||||
public LlmRestClient(@Value("${app.ai.lmstudio.base-url}") String lmstudioBaseUrl,
|
||||
@Value("${app.ai.lmstudio.model}") String lmstudioModel,
|
||||
@Value("${app.ai.lmstudio.htaccess-username}") String lmstudioHtaccessUsername,
|
||||
@Value("${app.ai.lmstudio.htaccess-password}") String lmstudioHtaccessPassword, ObjectMapper objectMapper) {
|
||||
|
||||
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();
|
||||
public LlmRestClient(@Value("${app.ai.anthropic.api-key}") String apiKey,
|
||||
@Value("${app.ai.anthropic.model:claude-opus-4-8}") String model) {
|
||||
this.model = model;
|
||||
this.client = AnthropicOkHttpClient.builder().apiKey(apiKey).build();
|
||||
log.info("LlmRestClient initialized - Provider: Anthropic Claude, Model: {}", model);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,32 +54,45 @@ public class LlmRestClient {
|
||||
* @param userMessage
|
||||
* User message/question
|
||||
* @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
|
||||
* Maximum tokens in response
|
||||
* @return LLM response text, or null on error
|
||||
*/
|
||||
public String chat(String systemPrompt, String userMessage, double temperature, int maxTokens) {
|
||||
try {
|
||||
Map<String, Object> request = Map.of("model", model, "messages",
|
||||
List.of(Map.of("role", "system", "content", systemPrompt != null ? systemPrompt : ""),
|
||||
Map.of("role", "user", "content", userMessage)),
|
||||
"temperature", temperature, "max_tokens", maxTokens, "stream", false);
|
||||
MessageCreateParams.Builder builder = MessageCreateParams.builder().model(model).maxTokens(maxTokens);
|
||||
if (systemPrompt != null && !systemPrompt.isBlank()) {
|
||||
builder.system(systemPrompt);
|
||||
}
|
||||
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();
|
||||
|
||||
String response = webClient.post().contentType(MediaType.APPLICATION_JSON).bodyValue(request).retrieve()
|
||||
.bodyToMono(String.class).timeout(Duration.ofSeconds(120)).block();
|
||||
Message response = client.messages().create(builder.build());
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
log.info("LLM response received in {}ms", duration);
|
||||
log.debug("LLM response payload received ({} chars)", response != null ? response.length() : 0);
|
||||
log.info("Claude response received in {}ms (stop_reason: {})", duration, response.stopReason());
|
||||
|
||||
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) {
|
||||
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()) {
|
||||
log.debug("Full stack trace:", e);
|
||||
}
|
||||
@@ -122,35 +111,6 @@ public class LlmRestClient {
|
||||
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) {
|
||||
String sanitized = THINK_BLOCK_PATTERN.matcher(content).replaceAll(" ");
|
||||
sanitized = THINK_TAG_PATTERN.matcher(sanitized).replaceAll(" ");
|
||||
|
||||
@@ -128,8 +128,10 @@ public class MessageController {
|
||||
|
||||
log.info("[JOBS] Retrieving assigned jobs for appUserId: {}", appUserId);
|
||||
|
||||
List<Job> assignedJobs = jobRepository.findByAppUser(appUserId);
|
||||
log.info("[JOBS] Found {} jobs for appUserId: {}", assignedJobs.size(), appUserId);
|
||||
List<Job> assignedJobs = jobRepository.findByAppUser(appUserId).stream()
|
||||
.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<CargoItem> cargoItems = cargoItemRepository.findByJobId(job.getId());
|
||||
@@ -462,15 +464,19 @@ public class MessageController {
|
||||
*/
|
||||
public void handleStationCompleted(String appUserId, Map<String, Object> payload) {
|
||||
try {
|
||||
String jobIdStr = payload.get("jobId") != null ? payload.get("jobId").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()) {
|
||||
log.warn("[STATION] Job with jobNumber {} not found", jobNumber);
|
||||
log.warn("[STATION] Job not found for station_completed (jobId={}, jobNumber={})", jobIdStr,
|
||||
jobNumber);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -31,11 +31,13 @@ import de.assecutor.votianlt.model.JobServiceSelection;
|
||||
import de.assecutor.votianlt.model.JobStatus;
|
||||
import de.assecutor.votianlt.model.Service;
|
||||
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.ViewToolbar;
|
||||
import de.assecutor.votianlt.repository.ServiceRepository;
|
||||
import de.assecutor.votianlt.repository.JobRepository;
|
||||
import de.assecutor.votianlt.security.SecurityService;
|
||||
import de.assecutor.votianlt.service.ClientConnectionService;
|
||||
import de.assecutor.votianlt.service.JobHistoryService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -47,6 +49,7 @@ import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
@Route(value = "job_manual_complete", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
|
||||
@RolesAllowed("USER")
|
||||
@@ -67,6 +70,8 @@ public class JobManualCompleteView extends Main implements HasUrlParameter<Strin
|
||||
private final JobHistoryService jobHistoryService;
|
||||
private final SecurityService securityService;
|
||||
private final ServiceRepository serviceRepository;
|
||||
private final ClientConnectionService clientConnectionService;
|
||||
private final MessagingPublisher messagingPublisher;
|
||||
private final VerticalLayout content;
|
||||
|
||||
private Job job;
|
||||
@@ -80,11 +85,14 @@ public class JobManualCompleteView extends Main implements HasUrlParameter<Strin
|
||||
private Integer manualDurationSeconds;
|
||||
|
||||
public JobManualCompleteView(JobRepository jobRepository, JobHistoryService jobHistoryService,
|
||||
SecurityService securityService, ServiceRepository serviceRepository) {
|
||||
SecurityService securityService, ServiceRepository serviceRepository,
|
||||
ClientConnectionService clientConnectionService, MessagingPublisher messagingPublisher) {
|
||||
this.jobRepository = jobRepository;
|
||||
this.jobHistoryService = jobHistoryService;
|
||||
this.securityService = securityService;
|
||||
this.serviceRepository = serviceRepository;
|
||||
this.clientConnectionService = clientConnectionService;
|
||||
this.messagingPublisher = messagingPublisher;
|
||||
|
||||
setSizeFull();
|
||||
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"),
|
||||
description, currentUser, JobHistoryType.STATUS_CHANGE);
|
||||
|
||||
notifyClientJobDeleted(job);
|
||||
|
||||
Notification
|
||||
.show(getTranslation("jobsummary.notification.completed", job.getJobNumber()), 3000,
|
||||
Notification.Position.BOTTOM_END)
|
||||
@@ -695,4 +705,26 @@ public class JobManualCompleteView extends Main implements HasUrlParameter<Strin
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ public class CustomerInvoiceService {
|
||||
* representation of the canvas elements and converts it to PDF.
|
||||
*/
|
||||
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)
|
||||
@@ -265,27 +265,6 @@ public class CustomerInvoiceService {
|
||||
String invoicePrefix, BigDecimal vatRate) throws Exception {
|
||||
BigDecimal effectiveVatRate = vatRate != null ? vatRate
|
||||
: (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
|
||||
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.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()) {
|
||||
for (com.fasterxml.jackson.databind.JsonNode element : elements) {
|
||||
String type = element.has("type") ? element.get("type").asText("text") : "text";
|
||||
|
||||
@@ -73,21 +73,11 @@ app.version=@project.version@
|
||||
app.google.maps.api-key=AIzaSyDnbitL06iLp3elmj-WtPudCykX9xvXcVE
|
||||
|
||||
# ===========================================
|
||||
# LLM Configuration (LM Studio)
|
||||
# LLM Configuration (Anthropic Claude)
|
||||
# ===========================================
|
||||
app.ai.lmstudio.base-url=${LMSTUDIO_URL}
|
||||
app.ai.lmstudio.model=${LMSTUDIO_MODEL}
|
||||
app.ai.lmstudio.htaccess-username=${LMSTUDIO_HTACCESS_USERNAME}
|
||||
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
|
||||
# API-Key kann per Umgebungsvariable ANTHROPIC_API_KEY ueberschrieben werden
|
||||
app.ai.anthropic.api-key=${ANTHROPIC_API_KEY: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}
|
||||
app.ai.anthropic.model=${ANTHROPIC_MODEL:claude-opus-4-8}
|
||||
|
||||
# ===========================================
|
||||
# MCP Server Configuration
|
||||
|
||||
@@ -241,7 +241,7 @@ page.title.dashboard=VotianLT - Dashboard
|
||||
page.title.appuser.create=Neuen App-Nutzer anlegen
|
||||
page.title.messages=Nachrichten
|
||||
page.title.register=Bei VotianLT registrieren
|
||||
page.title.customers=Kunden
|
||||
page.title.customers=Adressbuch
|
||||
page.title.customer.edit=Adresse bearbeiten
|
||||
page.title.verwaltung=Verwaltung
|
||||
page.title.company.create=Neue Firma anlegen
|
||||
@@ -249,7 +249,7 @@ page.title.imprint=Impressum
|
||||
page.title.profile.edit=Profil bearbeiten
|
||||
page.title.admin.dashboard=Admin Dashboard
|
||||
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.jobs=Aufträge
|
||||
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
|
||||
|
||||
# Customers
|
||||
customers.title=Kunden
|
||||
customers.button.add=Neuen Kunden hinzufügen
|
||||
customers.hint.click=Klicken Sie auf einen Kunden, um Details zu sehen
|
||||
customers.title=Adressbuch
|
||||
customers.button.add=Neue Adresse anlegen
|
||||
customers.hint.click=Klicken Sie auf einen Adresseintrag, um Details zu sehen
|
||||
customers.column.company=Firma
|
||||
customers.column.name=Name
|
||||
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
|
||||
|
||||
# Add Customer
|
||||
addcustomer.title=Neuen Kunden anlegen
|
||||
addcustomer.button.submit=Kunden anlegen
|
||||
addcustomer.title=Neue Adresse anlegen
|
||||
addcustomer.button.submit=Adresse anlegen
|
||||
addcustomer.notification.validation=Bitte füllen Sie alle Pflichtfelder aus
|
||||
addcustomer.notification.success=Kunde erfolgreich angelegt
|
||||
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.save.error=Fehler beim Speichern: {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
|
||||
|
||||
@@ -213,7 +213,7 @@ page.title.dashboard=VotianLT - T\u00f6\u00f6laud
|
||||
page.title.appuser.create=Uue \u00e4pikasutaja loomine
|
||||
page.title.messages=S\u00f5numid
|
||||
page.title.register=VotianLT-sse registreerimine
|
||||
page.title.customers=Kliendid
|
||||
page.title.customers=Aadressiraamat
|
||||
page.title.customer.edit=Kliendi muutmine
|
||||
page.title.verwaltung=Haldus
|
||||
page.title.company.create=Uue ettev\u00f5tte loomine
|
||||
@@ -221,7 +221,7 @@ page.title.imprint=Impressum
|
||||
page.title.profile.edit=Profiili muutmine
|
||||
page.title.admin.dashboard=Administraatori t\u00f6\u00f6laud
|
||||
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.jobs=Tellimused
|
||||
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.dialog.delete.text=Kas soovite selle \u00e4pikasutaja t\u00f5esti kustutada?
|
||||
editappuser.dialog.delete.confirm=Kustuta
|
||||
customers.title=Kliendid
|
||||
customers.button.add=Lisa uus klient
|
||||
customers.hint.click=Kl\u00f5psake kliendil, et n\u00e4ha \u00fcksikasju
|
||||
customers.title=Aadressiraamat
|
||||
customers.button.add=Lisa uus aadress
|
||||
customers.hint.click=Kl\u00f5psake aadressikirjel, et n\u00e4ha \u00fcksikasju
|
||||
customers.column.company=Ettev\u00f5te
|
||||
customers.column.name=Nimi
|
||||
customers.column.email=E-post
|
||||
@@ -309,8 +309,8 @@ editcustomer.notification.check=Palun kontrollige oma sisestusi
|
||||
editcustomer.notification.deleted=Klient edukalt kustutatud
|
||||
editcustomer.dialog.delete.text=Kas soovite selle kliendi t\u00f5esti kustutada?
|
||||
editcustomer.dialog.delete.confirm=Kustuta
|
||||
addcustomer.title=Uue kliendi loomine
|
||||
addcustomer.button.submit=Loo klient
|
||||
addcustomer.title=Uue aadressi loomine
|
||||
addcustomer.button.submit=Loo aadress
|
||||
addcustomer.notification.validation=Palun t\u00e4itke k\u00f5ik kohustuslikud v\u00e4ljad
|
||||
addcustomer.notification.success=Klient edukalt loodud
|
||||
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.save.error=Salvestamisel 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
|
||||
|
||||
@@ -241,7 +241,7 @@ page.title.dashboard=VotianLT - Dashboard
|
||||
page.title.appuser.create=Create New App User
|
||||
page.title.messages=Messages
|
||||
page.title.register=Register at VotianLT
|
||||
page.title.customers=Customers
|
||||
page.title.customers=Address Book
|
||||
page.title.customer.edit=Edit Customer
|
||||
page.title.verwaltung=Management
|
||||
page.title.company.create=Create New Company
|
||||
@@ -249,7 +249,7 @@ page.title.imprint=Imprint
|
||||
page.title.profile.edit=Edit Profile
|
||||
page.title.admin.dashboard=Admin Dashboard
|
||||
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.jobs=Jobs
|
||||
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
|
||||
|
||||
# Customers
|
||||
customers.title=Customers
|
||||
customers.button.add=Add New Customer
|
||||
customers.hint.click=Click on a customer to see details
|
||||
customers.title=Address Book
|
||||
customers.button.add=Add New Address
|
||||
customers.hint.click=Click on an address entry to see details
|
||||
customers.column.company=Company
|
||||
customers.column.name=Name
|
||||
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
|
||||
|
||||
# Add Customer
|
||||
addcustomer.title=Create New Customer
|
||||
addcustomer.button.submit=Create Customer
|
||||
addcustomer.title=Create New Address
|
||||
addcustomer.button.submit=Create Address
|
||||
addcustomer.notification.validation=Please fill in all required fields
|
||||
addcustomer.notification.success=Customer created successfully
|
||||
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.save.error=Error saving: {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
|
||||
|
||||
@@ -240,7 +240,7 @@ page.title.dashboard=VotianLT - Panel de control
|
||||
page.title.appuser.create=Crear nuevo usuario de la app
|
||||
page.title.messages=Mensajes
|
||||
page.title.register=Registrarse en VotianLT
|
||||
page.title.customers=Clientes
|
||||
page.title.customers=Libreta de direcciones
|
||||
page.title.customer.edit=Editar cliente
|
||||
page.title.verwaltung=Administraci\u00f3n
|
||||
page.title.company.create=Crear nueva empresa
|
||||
@@ -248,7 +248,7 @@ page.title.imprint=Aviso legal
|
||||
page.title.profile.edit=Editar perfil
|
||||
page.title.admin.dashboard=Panel de administraci\u00f3n
|
||||
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.jobs=Pedidos
|
||||
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
|
||||
|
||||
# Customers
|
||||
customers.title=Clientes
|
||||
customers.button.add=A\u00f1adir nuevo cliente
|
||||
customers.hint.click=Haga clic en un cliente para ver los detalles
|
||||
customers.title=Libreta de direcciones
|
||||
customers.button.add=A\u00f1adir nueva direcci\u00f3n
|
||||
customers.hint.click=Haga clic en una entrada de direcci\u00f3n para ver los detalles
|
||||
customers.column.company=Empresa
|
||||
customers.column.name=Nombre
|
||||
customers.column.email=Correo electr\u00f3nico
|
||||
@@ -348,8 +348,8 @@ editcustomer.dialog.delete.text=\u00bfDesea realmente eliminar este cliente?
|
||||
editcustomer.dialog.delete.confirm=Eliminar
|
||||
|
||||
# Add Customer
|
||||
addcustomer.title=Crear nuevo cliente
|
||||
addcustomer.button.submit=Crear cliente
|
||||
addcustomer.title=Crear nueva dirección
|
||||
addcustomer.button.submit=Crear dirección
|
||||
addcustomer.notification.validation=Por favor, complete todos los campos obligatorios
|
||||
addcustomer.notification.success=Cliente creado correctamente
|
||||
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.save.error=Error al guardar: {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
|
||||
|
||||
@@ -240,7 +240,7 @@ page.title.dashboard=VotianLT - Tableau de bord
|
||||
page.title.appuser.create=Cr\u00e9er un nouvel utilisateur d'app
|
||||
page.title.messages=Messages
|
||||
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.verwaltung=Administration
|
||||
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.admin.dashboard=Tableau de bord administrateur
|
||||
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.jobs=Missions
|
||||
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
|
||||
|
||||
# Customers
|
||||
customers.title=Clients
|
||||
customers.button.add=Ajouter un nouveau client
|
||||
customers.hint.click=Cliquez sur un client pour voir les d\u00e9tails
|
||||
customers.title=Carnet d'adresses
|
||||
customers.button.add=Ajouter une nouvelle adresse
|
||||
customers.hint.click=Cliquez sur une entr\u00e9e d'adresse pour voir les d\u00e9tails
|
||||
customers.column.company=Entreprise
|
||||
customers.column.name=Nom
|
||||
customers.column.email=E-mail
|
||||
@@ -348,8 +348,8 @@ editcustomer.dialog.delete.text=Voulez-vous vraiment supprimer ce client ?
|
||||
editcustomer.dialog.delete.confirm=Supprimer
|
||||
|
||||
# Add Customer
|
||||
addcustomer.title=Cr\u00e9er un nouveau client
|
||||
addcustomer.button.submit=Cr\u00e9er le client
|
||||
addcustomer.title=Cr\u00e9er une nouvelle adresse
|
||||
addcustomer.button.submit=Cr\u00e9er l'adresse
|
||||
addcustomer.notification.validation=Veuillez remplir tous les champs obligatoires
|
||||
addcustomer.notification.success=Client cr\u00e9\u00e9 avec succ\u00e8s
|
||||
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.save.error=Erreur lors de l'enregistrement : {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
|
||||
|
||||
@@ -240,7 +240,7 @@ page.title.dashboard=VotianLT - Valdymo skydas
|
||||
page.title.appuser.create=Sukurti naują programėlės naudotoją
|
||||
page.title.messages=Žinutės
|
||||
page.title.register=Registracija VotianLT
|
||||
page.title.customers=Klientai
|
||||
page.title.customers=Adresų knyga
|
||||
page.title.customer.edit=Redaguoti klientą
|
||||
page.title.verwaltung=Administravimas
|
||||
page.title.company.create=Sukurti naują įmonę
|
||||
@@ -248,7 +248,7 @@ page.title.imprint=Informacija
|
||||
page.title.profile.edit=Redaguoti profilį
|
||||
page.title.admin.dashboard=Administratoriaus valdymo skydas
|
||||
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.jobs=Užsakymai
|
||||
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
|
||||
|
||||
# Customers
|
||||
customers.title=Klientai
|
||||
customers.button.add=Pridėti naują klientą
|
||||
customers.hint.click=Spustelėkite klientą, kad pamatytumėte informaciją
|
||||
customers.title=Adresų knyga
|
||||
customers.button.add=Pridėti naują adresą
|
||||
customers.hint.click=Spustelėkite adreso įrašą, kad pamatytumėte informaciją
|
||||
customers.column.company=Įmonė
|
||||
customers.column.name=Pavadinimas
|
||||
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
|
||||
|
||||
# Add Customer
|
||||
addcustomer.title=Sukurti naują klientą
|
||||
addcustomer.button.submit=Sukurti klientą
|
||||
addcustomer.title=Sukurti naują adresą
|
||||
addcustomer.button.submit=Sukurti adresą
|
||||
addcustomer.notification.validation=Prašome užpildyti visus privalomus laukus
|
||||
addcustomer.notification.success=Klientas sėkmingai sukurtas
|
||||
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.save.error=Klaida išsaugant: {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
|
||||
|
||||
@@ -240,7 +240,7 @@ page.title.dashboard=VotianLT - Informācijas panelis
|
||||
page.title.appuser.create=Izveidot jaunu lietotnes lietotāju
|
||||
page.title.messages=Ziņojumi
|
||||
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.verwaltung=Pārvaldība
|
||||
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.admin.dashboard=Administrācijas panelis
|
||||
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.jobs=Uzdevumi
|
||||
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
|
||||
|
||||
# Customers
|
||||
customers.title=Klienti
|
||||
customers.button.add=Pievienot jaunu klientu
|
||||
customers.hint.click=Noklikšķiniet uz klienta, lai redzētu detaļas
|
||||
customers.title=Adrešu grāmata
|
||||
customers.button.add=Pievienot jaunu adresi
|
||||
customers.hint.click=Noklikšķiniet uz adreses ieraksta, lai redzētu detaļas
|
||||
customers.column.company=Uzņēmums
|
||||
customers.column.name=Nosaukums
|
||||
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
|
||||
|
||||
# Add Customer
|
||||
addcustomer.title=Izveidot jaunu klientu
|
||||
addcustomer.button.submit=Izveidot klientu
|
||||
addcustomer.title=Izveidot jaunu adresi
|
||||
addcustomer.button.submit=Izveidot adresi
|
||||
addcustomer.notification.validation=Lūdzu, aizpildiet visus obligātos laukus
|
||||
addcustomer.notification.success=Klients veiksmīgi izveidots
|
||||
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.save.error=Kļūda saglabā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
|
||||
|
||||
@@ -240,7 +240,7 @@ page.title.dashboard=VotianLT - Panel g\u0142\u00f3wny
|
||||
page.title.appuser.create=Dodaj nowego u\u017cytkownika aplikacji
|
||||
page.title.messages=Wiadomo\u015bci
|
||||
page.title.register=Rejestracja w VotianLT
|
||||
page.title.customers=Klienci
|
||||
page.title.customers=Książka adresowa
|
||||
page.title.customer.edit=Edytuj klienta
|
||||
page.title.verwaltung=Zarz\u0105dzanie
|
||||
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.admin.dashboard=Panel administracyjny
|
||||
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.jobs=Zlecenia
|
||||
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
|
||||
|
||||
# Customers
|
||||
customers.title=Klienci
|
||||
customers.button.add=Dodaj nowego klienta
|
||||
customers.hint.click=Kliknij klienta, aby zobaczy\u0107 szczeg\u00f3\u0142y
|
||||
customers.title=Ksi\u0105\u017cka adresowa
|
||||
customers.button.add=Dodaj nowy adres
|
||||
customers.hint.click=Kliknij wpis adresowy, aby zobaczy\u0107 szczeg\u00f3\u0142y
|
||||
customers.column.company=Firma
|
||||
customers.column.name=Nazwa
|
||||
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
|
||||
|
||||
# Add Customer
|
||||
addcustomer.title=Dodaj nowego klienta
|
||||
addcustomer.button.submit=Dodaj klienta
|
||||
addcustomer.title=Dodaj nowy adres
|
||||
addcustomer.button.submit=Dodaj adres
|
||||
addcustomer.notification.validation=Prosz\u0119 wype\u0142ni\u0107 wszystkie wymagane pola
|
||||
addcustomer.notification.success=Klient zosta\u0142 pomy\u015blnie dodany
|
||||
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.save.error=B\u0142\u0105d podczas zapisywania: {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
|
||||
|
||||
@@ -240,7 +240,7 @@ page.title.dashboard=VotianLT - Панель управления
|
||||
page.title.appuser.create=Создать нового пользователя приложения
|
||||
page.title.messages=Сообщения
|
||||
page.title.register=Регистрация в VotianLT
|
||||
page.title.customers=Клиенты
|
||||
page.title.customers=Адресная книга
|
||||
page.title.customer.edit=Редактирование клиента
|
||||
page.title.verwaltung=Управление
|
||||
page.title.company.create=Создать новую компанию
|
||||
@@ -248,7 +248,7 @@ page.title.imprint=Выходные данные
|
||||
page.title.profile.edit=Редактирование профиля
|
||||
page.title.admin.dashboard=Панель администратора
|
||||
page.title.invoice.create=Создать счёт
|
||||
page.title.customer.create=Создать нового клиента
|
||||
page.title.customer.create=Создать новый адрес
|
||||
page.title.login=Вход в VotianLT
|
||||
page.title.jobs=Заказы
|
||||
page.title.appuser.edit=Редактирование пользователя приложения
|
||||
@@ -327,9 +327,9 @@ editappuser.dialog.delete.text=Вы действительно хотите уд
|
||||
editappuser.dialog.delete.confirm=Удалить
|
||||
|
||||
# Customers
|
||||
customers.title=Клиенты
|
||||
customers.button.add=Добавить нового клиента
|
||||
customers.hint.click=Нажмите на клиента, чтобы увидеть подробности
|
||||
customers.title=Адресная книга
|
||||
customers.button.add=Добавить новый адрес
|
||||
customers.hint.click=Нажмите на запись адреса, чтобы увидеть подробности
|
||||
customers.column.company=Компания
|
||||
customers.column.name=Имя
|
||||
customers.column.email=Эл. почта
|
||||
@@ -348,8 +348,8 @@ editcustomer.dialog.delete.text=Вы действительно хотите у
|
||||
editcustomer.dialog.delete.confirm=Удалить
|
||||
|
||||
# Add Customer
|
||||
addcustomer.title=Создать нового клиента
|
||||
addcustomer.button.submit=Создать клиента
|
||||
addcustomer.title=Создать новый адрес
|
||||
addcustomer.button.submit=Создать адрес
|
||||
addcustomer.notification.validation=Пожалуйста, заполните все обязательные поля
|
||||
addcustomer.notification.success=Клиент успешно создан
|
||||
addcustomer.notification.check=Пожалуйста, проверьте ваши данные
|
||||
@@ -987,3 +987,24 @@ adminpricetable.field.revenue=Участие в выручке
|
||||
adminpricetable.notification.saved=Таблица цен сохранена
|
||||
adminpricetable.notification.save.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
|
||||
|
||||
@@ -240,7 +240,7 @@ page.title.dashboard=VotianLT - Kontrol Paneli
|
||||
page.title.appuser.create=Yeni Uygulama Kullan\u0131c\u0131s\u0131 Olu\u015ftur
|
||||
page.title.messages=Mesajlar
|
||||
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.verwaltung=Y\u00f6netim
|
||||
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.admin.dashboard=Y\u00f6netici Kontrol Paneli
|
||||
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.jobs=\u0130\u015fler
|
||||
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
|
||||
|
||||
# Customers
|
||||
customers.title=M\u00fc\u015fteriler
|
||||
customers.button.add=Yeni M\u00fc\u015fteri Ekle
|
||||
customers.hint.click=Ayr\u0131nt\u0131lar\u0131 g\u00f6rmek i\u00e7in bir m\u00fc\u015fteriye t\u0131klay\u0131n
|
||||
customers.title=Adres defteri
|
||||
customers.button.add=Yeni Adres Ekle
|
||||
customers.hint.click=Ayr\u0131nt\u0131lar\u0131 g\u00f6rmek i\u00e7in bir adres giri\u015fine t\u0131klay\u0131n
|
||||
customers.column.company=\u015eirket
|
||||
customers.column.name=Ad
|
||||
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
|
||||
|
||||
# Add Customer
|
||||
addcustomer.title=Yeni M\u00fc\u015fteri Olu\u015ftur
|
||||
addcustomer.button.submit=M\u00fc\u015fteri Olu\u015ftur
|
||||
addcustomer.title=Yeni Adres Olu\u015ftur
|
||||
addcustomer.button.submit=Adres Olu\u015ftur
|
||||
addcustomer.notification.validation=L\u00fctfen t\u00fcm zorunlu alanlar\u0131 doldurun
|
||||
addcustomer.notification.success=M\u00fc\u015fteri ba\u015far\u0131yla olu\u015fturuldu
|
||||
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.save.error=Kaydetme 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
|
||||
|
||||
Reference in New Issue
Block a user