feat: ZUGFeRD-E-Rechnung über externen PDF-Tool-Service mit ZIP-Download und Versand aus der PDF-Vorschau

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 11:28:58 +02:00
parent f4c2fa6ba3
commit 4e411c727e
13 changed files with 322 additions and 11 deletions

View File

@@ -34,6 +34,7 @@ import de.assecutor.votianlt.repository.UserRepository;
import de.assecutor.votianlt.service.CustomerInvoiceService;
import de.assecutor.votianlt.service.EmailService;
import de.assecutor.votianlt.service.InvoiceTemplateService;
import de.assecutor.votianlt.service.PdfToolService;
import de.assecutor.votianlt.service.SystemCompanyProfileService;
import de.assecutor.votianlt.util.DateTimeFormatUtil;
import jakarta.annotation.security.RolesAllowed;
@@ -76,6 +77,7 @@ public class AdminCreateInvoicesView extends Main implements HasDynamicTitle {
private final SystemCompanyProfileService systemCompanyProfileService;
private final EmailService emailService;
private final SystemInvoiceDispatchRepository dispatchRepository;
private final PdfToolService pdfToolService;
private final Grid<BillingInfo> grid = new Grid<>();
@@ -99,7 +101,8 @@ public class AdminCreateInvoicesView extends Main implements HasDynamicTitle {
public AdminCreateInvoicesView(UserRepository userRepository, PriceTableRepository priceTableRepository,
AppUserRepository appUserRepository, InvoiceTemplateService invoiceTemplateService,
CustomerInvoiceService customerInvoiceService, SystemCompanyProfileService systemCompanyProfileService,
EmailService emailService, SystemInvoiceDispatchRepository dispatchRepository) {
EmailService emailService, SystemInvoiceDispatchRepository dispatchRepository,
PdfToolService pdfToolService) {
this.userRepository = userRepository;
this.priceTableRepository = priceTableRepository;
this.appUserRepository = appUserRepository;
@@ -108,6 +111,7 @@ public class AdminCreateInvoicesView extends Main implements HasDynamicTitle {
this.systemCompanyProfileService = systemCompanyProfileService;
this.emailService = emailService;
this.dispatchRepository = dispatchRepository;
this.pdfToolService = pdfToolService;
setSizeFull();
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
@@ -427,7 +431,7 @@ public class AdminCreateInvoicesView extends Main implements HasDynamicTitle {
try {
byte[] pdfBytes = customerInvoiceService.generatePdfFromCanvasTemplateWithData(templateData,
buildInvoiceVariables(info), null);
showPdfInDialog(pdfBytes, buildInvoiceNumber(info.user()));
showPdfInDialog(new InvoicePreview(info, buildInvoiceNumber(info.user()), pdfBytes));
} catch (Exception ex) {
log.error("Error creating system invoice for user {}: {}", info.user().getEmail(), ex.getMessage(), ex);
Notification
@@ -494,6 +498,8 @@ public class AdminCreateInvoicesView extends Main implements HasDynamicTitle {
nextButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
Button deleteButton = new Button(getTranslation("admincreateinvoices.dialog.button.delete"));
deleteButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_ERROR);
Button zugferdButton = new Button(getTranslation("admincreateinvoices.button.zugferd"));
zugferdButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
Button sendButton = new Button(getTranslation("admincreateinvoices.dialog.button.send"));
sendButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
Button closeButton = new Button(getTranslation("button.close"), e -> dialog.close());
@@ -532,6 +538,7 @@ public class AdminCreateInvoicesView extends Main implements HasDynamicTitle {
index[0] = Math.min(index[0], previews.size() - 1);
updateView.run();
});
zugferdButton.addClickListener(e -> downloadZugferdZip(previews.get(index[0])));
sendButton.addClickListener(e -> {
sendButton.setEnabled(false);
sendInvoices(previews);
@@ -540,7 +547,7 @@ public class AdminCreateInvoicesView extends Main implements HasDynamicTitle {
updateView.run();
dialog.add(DialogStylingHelper.wrapContent(content, true));
dialog.getFooter().add(previousButton, nextButton, deleteButton, sendButton, closeButton);
dialog.getFooter().add(previousButton, nextButton, deleteButton, zugferdButton, sendButton, closeButton);
dialog.open();
}
@@ -692,7 +699,7 @@ public class AdminCreateInvoicesView extends Main implements HasDynamicTitle {
return "SYS-" + monthPart + "-" + userPart;
}
private void showPdfInDialog(byte[] pdfBytes, String invoiceNumber) {
private void showPdfInDialog(InvoicePreview preview) {
Dialog pdfDialog = DialogStylingHelper.createStyledDialog(getTranslation("invoicegenerator.pdf.preview.title"),
"90vw");
pdfDialog.setHeight("90vh");
@@ -701,7 +708,7 @@ public class AdminCreateInvoicesView extends Main implements HasDynamicTitle {
pdfContainer.setWidth("100%");
pdfContainer.setHeight("100%");
String base64Pdf = Base64.getEncoder().encodeToString(pdfBytes);
String base64Pdf = Base64.getEncoder().encodeToString(preview.pdf());
IFrame pdfFrame = new IFrame();
pdfFrame.setWidth("100%");
@@ -710,18 +717,145 @@ public class AdminCreateInvoicesView extends Main implements HasDynamicTitle {
pdfContainer.add(pdfFrame);
Button closeButton = new Button(getTranslation("button.close"), e -> pdfDialog.close());
closeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
closeButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
Button downloadButton = new Button(getTranslation("button.download"), e -> getElement()
.executeJs("const link = document.createElement('a');" + "link.href = 'data:application/pdf;base64,"
+ base64Pdf + "';" + "link.download = '" + invoiceNumber + ".pdf';" + "link.click();"));
downloadButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
Button zugferdButton = new Button(getTranslation("admincreateinvoices.button.zugferd"),
e -> downloadZugferdZip(preview));
zugferdButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
Button sendButton = new Button(getTranslation("admincreateinvoices.pdf.button.send"), e -> {
e.getSource().setEnabled(false);
sendInvoices(List.of(preview));
pdfDialog.close();
});
sendButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
pdfDialog.add(DialogStylingHelper.wrapContent(pdfContainer, true));
pdfDialog.getFooter().add(downloadButton, closeButton);
pdfDialog.getFooter().add(zugferdButton, sendButton, closeButton);
pdfDialog.open();
}
// ==========================================
// ZUGFeRD e-invoice (external PDF Tool)
// ==========================================
/**
* Converts the invoice via the external PDF Tool into a ZUGFeRD e-invoice
* and offers the resulting ZIP (signed PDF/A-3, factur-x.xml, validation
* report) as browser download.
*/
private void downloadZugferdZip(InvoicePreview preview) {
try {
PdfToolService.SignedInvoice signed = pdfToolService.signInvoice(preview.pdf(),
buildZugferdMetadata(preview));
String base64Zip = Base64.getEncoder().encodeToString(signed.zip());
getElement().executeJs("const link = document.createElement('a');"
+ "link.href = 'data:application/zip;base64," + base64Zip + "';" + "link.download = '"
+ signed.fileName().replace("'", "") + "';"
+ "document.body.appendChild(link); link.click(); document.body.removeChild(link);");
if (signed.valid()) {
Notification
.show(getTranslation("admincreateinvoices.notification.zugferd.created",
preview.invoiceNumber()), 5000, Notification.Position.BOTTOM_CENTER)
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
} else {
Notification
.show(getTranslation("admincreateinvoices.notification.zugferd.invalid"), 7000,
Notification.Position.BOTTOM_CENTER)
.addThemeVariants(NotificationVariant.LUMO_WARNING);
}
} catch (Exception ex) {
log.error("Error creating ZUGFeRD e-invoice {}: {}", preview.invoiceNumber(), ex.getMessage(), ex);
Notification
.show(getTranslation("admincreateinvoices.notification.zugferd.error", ex.getMessage()), 7000,
Notification.Position.BOTTOM_CENTER)
.addThemeVariants(NotificationVariant.LUMO_ERROR);
}
}
/**
* Builds the EN16931 metadata for the PDF Tool from the billing data: the
* system operator as sender, the customer as recipient and the billed
* positions (including a possible negative discount position) as lines.
*/
private PdfToolService.InvoiceMetadata buildZugferdMetadata(InvoicePreview preview) throws Exception {
SystemInvoiceData issuer = systemCompanyProfileService.createSystemInvoiceData();
BillingInfo info = preview.info();
User user = info.user();
String[] senderZipCity = splitZipCity(issuer.getCompanyCity());
String senderName = (safe(issuer.getCompanyName()) + " " + safe(issuer.getCompanySubtitle())).trim();
String footer = safe(issuer.getFooterText());
PdfToolService.Party sender = new PdfToolService.Party(senderName, safe(issuer.getCompanyStreet()),
senderZipCity[0], senderZipCity[1], "DE", extractByPattern(footer, "USt-IdNr\\.?:?\\s*([A-Z]{2}[0-9A-Za-z]{2,12})"),
safe(issuer.getCompanyEmail()), safe(issuer.getCompanyPhone()));
PdfToolService.Party recipient;
if (user.isDiffInvoiceAddress()) {
recipient = new PdfToolService.Party(
firstNonBlank(user.getInvCompany(), user.getCompany(),
(safe(user.getInvFirstname()) + " " + safe(user.getInvLastname())).trim()),
(safe(user.getInvStreet()) + " " + safe(user.getInvHouseNumber())).trim(), safe(user.getInvZip()),
safe(user.getInvCity()), "DE", null, safe(user.getEmail()), safe(user.getPhone()));
} else {
recipient = new PdfToolService.Party(
firstNonBlank(user.getCompany(), (safe(user.getFirstname()) + " " + safe(user.getName())).trim()),
(safe(user.getStreet()) + " " + safe(user.getHouseNumber())).trim(), safe(user.getZip()),
safe(user.getCity()), "DE", null, safe(user.getEmail()), safe(user.getPhone()));
}
BigDecimal vatPercent = SYSTEM_VAT_RATE.multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP);
List<PdfToolService.LineItem> items = new ArrayList<>();
for (Map<String, String> position : info.positions()) {
BigDecimal amount = parseSignedAmount(position.get("netAmount"));
if (amount == null) {
// Non-monetary positions cannot be part of an EN16931 invoice
continue;
}
items.add(new PdfToolService.LineItem(position.get("name"), BigDecimal.ONE, amount, vatPercent));
}
if (items.isEmpty()) {
throw new IllegalStateException(getTranslation("admincreateinvoices.notification.zugferd.nopositions"));
}
return new PdfToolService.InvoiceMetadata(preview.invoiceNumber(), LocalDate.now(),
billingMonth.atEndOfMonth(), info.dueDate(), "EUR", safe(issuer.getPaymentTerms()), null,
extractByPattern(footer, "IBAN\\s*:?\\s*([A-Z]{2}[0-9A-Za-z]{13,32})"),
extractByPattern(footer, "BIC\\s*:?\\s*([A-Za-z0-9]{8,11})"), sender, recipient, items);
}
/**
* Parses a formatted position amount like "99,00" or "-49,50" including
* its sign. Returns null for non-monetary values such as "-" or "15 %".
*/
private BigDecimal parseSignedAmount(String value) {
if (value == null || value.isBlank() || value.contains("%")) {
return null;
}
boolean negative = value.trim().startsWith("-");
BigDecimal amount = parseAmount(value.trim().replaceFirst("^-", ""));
if (amount == null) {
return null;
}
return negative ? amount.negate() : amount;
}
/** Splits a combined "zip city" value like "21502 Geesthacht". */
private String[] splitZipCity(String zipCity) {
String trimmed = safe(zipCity).trim();
int space = trimmed.indexOf(' ');
if (space < 0) {
return new String[] { trimmed, trimmed };
}
return new String[] { trimmed.substring(0, space), trimmed.substring(space + 1).trim() };
}
/** Returns the first regex group found in the text, or null. */
private String extractByPattern(String text, String pattern) {
Matcher matcher = Pattern.compile(pattern).matcher(text);
return matcher.find() ? matcher.group(1) : null;
}
// ==========================================
// Helpers
// ==========================================

View File

@@ -0,0 +1,114 @@
package de.assecutor.votianlt.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientResponseException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
/**
* Client for the external PDF Tool (ZUGFeRD converter). Sends an invoice PDF
* together with the structured invoice data to {@code POST /api/invoices/sign}
* and receives a ZIP file containing the signed e-invoice (PDF/A-3 with
* embedded EN16931 XML), the factur-x.xml and the Mustang validation report.
*/
@Service
@Slf4j
public class PdfToolService {
/** Response header of the PDF Tool with the Mustang validation result. */
private static final String VALIDATION_HEADER = "X-Zugferd-Valid";
private final RestClient restClient;
private final ObjectMapper objectMapper;
public PdfToolService(@Value("${app.pdftool.base-url:http://localhost:8083}") String baseUrl,
ObjectMapper objectMapper) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(5000);
// PDF/A conversion and veraPDF validation can take a while
requestFactory.setReadTimeout(120000);
this.restClient = RestClient.builder().baseUrl(baseUrl).requestFactory(requestFactory).build();
this.objectMapper = objectMapper;
}
/** Party (sender or recipient) of the invoice metadata. */
public record Party(String name, String street, String zip, String city, String countryCode, String vatId,
String email, String phone) {
}
/** One invoice line of the invoice metadata. */
public record LineItem(String description, BigDecimal quantity, BigDecimal unitPriceNet, BigDecimal vatPercent) {
}
/** Invoice metadata as expected by the PDF Tool (EN16931 fields). */
public record InvoiceMetadata(String invoiceNumber, LocalDate issueDate, LocalDate deliveryDate, LocalDate dueDate,
String currency, String paymentTerms, String buyerReference, String iban, String bic, Party sender,
Party recipient, List<LineItem> items) {
}
/** Result of a sign call: the ZIP, its file name and the validation flag. */
public record SignedInvoice(byte[] zip, String fileName, boolean valid) {
}
/**
* Converts the given invoice PDF into a ZUGFeRD e-invoice. Returns the ZIP
* provided by the PDF Tool; throws with the tool's error message if the
* conversion is rejected (e.g. incomplete metadata).
*/
public SignedInvoice signInvoice(byte[] pdfBytes, InvoiceMetadata metadata) throws Exception {
String metadataJson = objectMapper.writeValueAsString(metadata);
MultipartBodyBuilder body = new MultipartBodyBuilder();
body.part("file", new ByteArrayResource(pdfBytes) {
@Override
public String getFilename() {
return metadata.invoiceNumber() + ".pdf";
}
}, MediaType.APPLICATION_PDF);
body.part("metadata", metadataJson, MediaType.APPLICATION_JSON);
ResponseEntity<byte[]> response;
try {
response = restClient.post().uri("/api/invoices/sign").contentType(MediaType.MULTIPART_FORM_DATA)
.body(body.build()).retrieve().toEntity(byte[].class);
} catch (RestClientResponseException ex) {
throw new IllegalStateException(extractErrorMessage(ex), ex);
}
byte[] zip = response.getBody();
if (zip == null || zip.length == 0) {
throw new IllegalStateException("PDF Tool returned an empty response");
}
String fileName = response.getHeaders().getContentDisposition().getFilename();
if (fileName == null || fileName.isBlank()) {
fileName = metadata.invoiceNumber() + "-zugferd.zip";
}
boolean valid = Boolean.parseBoolean(response.getHeaders().getFirst(VALIDATION_HEADER));
return new SignedInvoice(zip, fileName, valid);
}
/** The PDF Tool reports errors as {@code {"error": "..."}} with HTTP 400. */
private String extractErrorMessage(RestClientResponseException ex) {
try {
JsonNode node = objectMapper.readTree(ex.getResponseBodyAsString());
if (node.hasNonNull("error")) {
return node.get("error").asText();
}
} catch (Exception ignored) {
// fall through to the generic message
}
return ex.getStatusText() + " (" + ex.getStatusCode().value() + ")";
}
}

View File

@@ -75,6 +75,9 @@ app.version=@project.version@
# Google Maps API Key
app.google.maps.api-key=AIzaSyDnbitL06iLp3elmj-WtPudCykX9xvXcVE
# PDF Tool (ZUGFeRD-E-Rechnungs-Konverter), Basis-URL des externen Service
app.pdftool.base-url=${PDFTOOL_BASE_URL:http://localhost:8083}
# ===========================================
# LLM Configuration (Anthropic Claude)
# ===========================================

View File

@@ -1142,8 +1142,14 @@ admincreateinvoices.dialog.button.previous=Zurück
admincreateinvoices.dialog.button.next=Weiter
admincreateinvoices.dialog.button.delete=Aus Liste entfernen
admincreateinvoices.dialog.button.send=Rechnungen senden
admincreateinvoices.pdf.button.send=Senden
admincreateinvoices.dialog.notification.removed=Rechnung aus der Liste entfernt
admincreateinvoices.mail.subject=Ihre Rechnung {0}
admincreateinvoices.mail.body=Sehr geehrte Damen und Herren,\n\nanbei erhalten Sie Ihre Rechnung {0} für den Abrechnungszeitraum {1}.\n\nMit freundlichen Grüßen\n{2}
admincreateinvoices.notification.sent={0} Rechnung(en) per E-Mail versendet
admincreateinvoices.notification.sendfailed={0} Rechnung(en) konnten nicht versendet werden
admincreateinvoices.button.zugferd=E-Rechnung (ZIP)
admincreateinvoices.notification.zugferd.created=E-Rechnung {0} erstellt
admincreateinvoices.notification.zugferd.invalid=E-Rechnung erstellt, aber die Validierung ist fehlgeschlagen. Details im Prüfbericht in der ZIP-Datei.
admincreateinvoices.notification.zugferd.error=Fehler beim Erstellen der E-Rechnung: {0}
admincreateinvoices.notification.zugferd.nopositions=Keine abrechenbaren Positionen für die E-Rechnung vorhanden

View File

@@ -972,8 +972,14 @@ admincreateinvoices.dialog.button.previous=Tagasi
admincreateinvoices.dialog.button.next=Edasi
admincreateinvoices.dialog.button.delete=Eemalda loendist
admincreateinvoices.dialog.button.send=Saada arved
admincreateinvoices.pdf.button.send=Saada
admincreateinvoices.dialog.notification.removed=Arve eemaldati loendist
admincreateinvoices.mail.subject=Teie arve {0}
admincreateinvoices.mail.body=Lugupeetud klient,\n\nmanuses on Teie arve {0} arveldusperioodi {1} eest.\n\nLugupidamisega\n{2}
admincreateinvoices.notification.sent={0} arve(t) saadeti e-postiga
admincreateinvoices.notification.sendfailed={0} arve(t) ei õnnestunud saata
admincreateinvoices.button.zugferd=E-arve (ZIP)
admincreateinvoices.notification.zugferd.created=E-arve {0} loodud
admincreateinvoices.notification.zugferd.invalid=E-arve on loodud, kuid valideerimine ebaõnnestus. Üksikasjad on ZIP-failis olevas valideerimisaruandes.
admincreateinvoices.notification.zugferd.error=Viga e-arve loomisel: {0}
admincreateinvoices.notification.zugferd.nopositions=E-arve jaoks puuduvad arveldatavad positsioonid

View File

@@ -1142,8 +1142,14 @@ admincreateinvoices.dialog.button.previous=Back
admincreateinvoices.dialog.button.next=Next
admincreateinvoices.dialog.button.delete=Remove from list
admincreateinvoices.dialog.button.send=Send invoices
admincreateinvoices.pdf.button.send=Send
admincreateinvoices.dialog.notification.removed=Invoice removed from the list
admincreateinvoices.mail.subject=Your invoice {0}
admincreateinvoices.mail.body=Dear Sir or Madam,\n\nplease find attached your invoice {0} for the billing period {1}.\n\nKind regards\n{2}
admincreateinvoices.notification.sent={0} invoice(s) sent by email
admincreateinvoices.notification.sendfailed={0} invoice(s) could not be sent
admincreateinvoices.button.zugferd=E-invoice (ZIP)
admincreateinvoices.notification.zugferd.created=E-invoice {0} created
admincreateinvoices.notification.zugferd.invalid=E-invoice created, but validation failed. See the validation report in the ZIP file.
admincreateinvoices.notification.zugferd.error=Error creating the e-invoice: {0}
admincreateinvoices.notification.zugferd.nopositions=No billable positions available for the e-invoice

View File

@@ -1073,8 +1073,14 @@ admincreateinvoices.dialog.button.previous=Atrás
admincreateinvoices.dialog.button.next=Siguiente
admincreateinvoices.dialog.button.delete=Quitar de la lista
admincreateinvoices.dialog.button.send=Enviar facturas
admincreateinvoices.pdf.button.send=Enviar
admincreateinvoices.dialog.notification.removed=Factura eliminada de la lista
admincreateinvoices.mail.subject=Su factura {0}
admincreateinvoices.mail.body=Estimados señores,\n\nadjunto encontrará su factura {0} correspondiente al período de facturación {1}.\n\nAtentamente\n{2}
admincreateinvoices.notification.sent={0} factura(s) enviada(s) por correo electrónico
admincreateinvoices.notification.sendfailed=No se pudieron enviar {0} factura(s)
admincreateinvoices.button.zugferd=Factura electrónica (ZIP)
admincreateinvoices.notification.zugferd.created=Factura electrónica {0} creada
admincreateinvoices.notification.zugferd.invalid=Factura electrónica creada, pero la validación ha fallado. Consulte el informe de validación en el archivo ZIP.
admincreateinvoices.notification.zugferd.error=Error al crear la factura electrónica: {0}
admincreateinvoices.notification.zugferd.nopositions=No hay posiciones facturables disponibles para la factura electrónica

View File

@@ -1073,8 +1073,14 @@ admincreateinvoices.dialog.button.previous=Retour
admincreateinvoices.dialog.button.next=Suivant
admincreateinvoices.dialog.button.delete=Retirer de la liste
admincreateinvoices.dialog.button.send=Envoyer les factures
admincreateinvoices.pdf.button.send=Envoyer
admincreateinvoices.dialog.notification.removed=Facture retirée de la liste
admincreateinvoices.mail.subject=Votre facture {0}
admincreateinvoices.mail.body=Madame, Monsieur,\n\nveuillez trouver ci-joint votre facture {0} pour la période de facturation {1}.\n\nCordialement\n{2}
admincreateinvoices.notification.sent={0} facture(s) envoyée(s) par e-mail
admincreateinvoices.notification.sendfailed={0} facture(s) n''ont pas pu être envoyées
admincreateinvoices.button.zugferd=Facture électronique (ZIP)
admincreateinvoices.notification.zugferd.created=Facture électronique {0} créée
admincreateinvoices.notification.zugferd.invalid=Facture électronique créée, mais la validation a échoué. Détails dans le rapport de validation du fichier ZIP.
admincreateinvoices.notification.zugferd.error=Erreur lors de la création de la facture électronique : {0}
admincreateinvoices.notification.zugferd.nopositions=Aucune position facturable disponible pour la facture électronique

View File

@@ -1075,8 +1075,14 @@ admincreateinvoices.dialog.button.previous=Atgal
admincreateinvoices.dialog.button.next=Toliau
admincreateinvoices.dialog.button.delete=Pašalinti iš sąrašo
admincreateinvoices.dialog.button.send=Siųsti sąskaitas
admincreateinvoices.pdf.button.send=Siųsti
admincreateinvoices.dialog.notification.removed=Sąskaita pašalinta iš sąrašo
admincreateinvoices.mail.subject=Jūsų sąskaita {0}
admincreateinvoices.mail.body=Gerbiami klientai,\n\npridedame Jūsų sąskaitą {0} už atsiskaitymo laikotarpį {1}.\n\nPagarbiai\n{2}
admincreateinvoices.notification.sent=El. paštu išsiųsta sąskaitų: {0}
admincreateinvoices.notification.sendfailed=Nepavyko išsiųsti sąskaitų: {0}
admincreateinvoices.button.zugferd=E. sąskaita (ZIP)
admincreateinvoices.notification.zugferd.created=E. sąskaita {0} sukurta
admincreateinvoices.notification.zugferd.invalid=E. sąskaita sukurta, tačiau patikra nepavyko. Išsami informacija patikros ataskaitoje ZIP faile.
admincreateinvoices.notification.zugferd.error=Klaida kuriant e. sąskaitą: {0}
admincreateinvoices.notification.zugferd.nopositions=E. sąskaitai nėra apmokestinamų pozicijų

View File

@@ -1073,8 +1073,14 @@ admincreateinvoices.dialog.button.previous=Atpakaļ
admincreateinvoices.dialog.button.next=Tālāk
admincreateinvoices.dialog.button.delete=Noņemt no saraksta
admincreateinvoices.dialog.button.send=Sūtīt rēķinus
admincreateinvoices.pdf.button.send=Sūtīt
admincreateinvoices.dialog.notification.removed=Rēķins noņemts no saraksta
admincreateinvoices.mail.subject=Jūsu rēķins {0}
admincreateinvoices.mail.body=Cienījamie klienti,\n\npielikumā ir Jūsu rēķins {0} par norēķinu periodu {1}.\n\nAr cieņu\n{2}
admincreateinvoices.notification.sent=Pa e-pastu nosūtīti rēķini: {0}
admincreateinvoices.notification.sendfailed=Neizdevās nosūtīt rēķinus: {0}
admincreateinvoices.button.zugferd=E-rēķins (ZIP)
admincreateinvoices.notification.zugferd.created=E-rēķins {0} izveidots
admincreateinvoices.notification.zugferd.invalid=E-rēķins izveidots, bet validācija neizdevās. Detaļas skatiet validācijas atskaitē ZIP failā.
admincreateinvoices.notification.zugferd.error=Kļūda, veidojot e-rēķinu: {0}
admincreateinvoices.notification.zugferd.nopositions=E-rēķinam nav pieejamu norēķinu pozīciju

View File

@@ -1073,8 +1073,14 @@ admincreateinvoices.dialog.button.previous=Wstecz
admincreateinvoices.dialog.button.next=Dalej
admincreateinvoices.dialog.button.delete=Usuń z listy
admincreateinvoices.dialog.button.send=Wyślij faktury
admincreateinvoices.pdf.button.send=Wyślij
admincreateinvoices.dialog.notification.removed=Faktura usunięta z listy
admincreateinvoices.mail.subject=Państwa faktura {0}
admincreateinvoices.mail.body=Szanowni Państwo,\n\nw załączeniu przesyłamy fakturę {0} za okres rozliczeniowy {1}.\n\nZ poważaniem\n{2}
admincreateinvoices.notification.sent=Wysłano e-mailem faktur: {0}
admincreateinvoices.notification.sendfailed=Nie udało się wysłać faktur: {0}
admincreateinvoices.button.zugferd=E-faktura (ZIP)
admincreateinvoices.notification.zugferd.created=Utworzono e-fakturę {0}
admincreateinvoices.notification.zugferd.invalid=E-faktura została utworzona, ale walidacja nie powiodła się. Szczegóły w raporcie walidacji w pliku ZIP.
admincreateinvoices.notification.zugferd.error=Błąd podczas tworzenia e-faktury: {0}
admincreateinvoices.notification.zugferd.nopositions=Brak pozycji rozliczeniowych dla e-faktury

View File

@@ -1073,8 +1073,14 @@ admincreateinvoices.dialog.button.previous=Назад
admincreateinvoices.dialog.button.next=Далее
admincreateinvoices.dialog.button.delete=Удалить из списка
admincreateinvoices.dialog.button.send=Отправить счета
admincreateinvoices.pdf.button.send=Отправить
admincreateinvoices.dialog.notification.removed=Счет удален из списка
admincreateinvoices.mail.subject=Ваш счет {0}
admincreateinvoices.mail.body=Уважаемые дамы и господа!\n\nВо вложении Вы найдете счет {0} за расчетный период {1}.\n\nС уважением\n{2}
admincreateinvoices.notification.sent=Отправлено счетов по эл. почте: {0}
admincreateinvoices.notification.sendfailed=Не удалось отправить счетов: {0}
admincreateinvoices.button.zugferd=Электронный счёт (ZIP)
admincreateinvoices.notification.zugferd.created=Электронный счёт {0} создан
admincreateinvoices.notification.zugferd.invalid=Электронный счёт создан, но проверка не пройдена. Подробности в отчёте о проверке в ZIP-файле.
admincreateinvoices.notification.zugferd.error=Ошибка при создании электронного счёта: {0}
admincreateinvoices.notification.zugferd.nopositions=Нет позиций для выставления электронного счёта

View File

@@ -1073,8 +1073,14 @@ admincreateinvoices.dialog.button.previous=Geri
admincreateinvoices.dialog.button.next=İleri
admincreateinvoices.dialog.button.delete=Listeden kaldır
admincreateinvoices.dialog.button.send=Faturaları gönder
admincreateinvoices.pdf.button.send=Gönder
admincreateinvoices.dialog.notification.removed=Fatura listeden kaldırıldı
admincreateinvoices.mail.subject=Faturanız {0}
admincreateinvoices.mail.body=Sayın yetkili,\n\n{1} fatura dönemine ait {0} numaralı faturanız ektedir.\n\nSaygılarımızla\n{2}
admincreateinvoices.notification.sent={0} fatura e-posta ile gönderildi
admincreateinvoices.notification.sendfailed={0} fatura gönderilemedi
admincreateinvoices.button.zugferd=E-fatura (ZIP)
admincreateinvoices.notification.zugferd.created={0} numaralı e-fatura oluşturuldu
admincreateinvoices.notification.zugferd.invalid=E-fatura oluşturuldu ancak doğrulama başarısız oldu. Ayrıntılar ZIP dosyasındaki doğrulama raporunda.
admincreateinvoices.notification.zugferd.error=E-fatura oluşturulurken hata: {0}
admincreateinvoices.notification.zugferd.nopositions=E-fatura için faturalandırılabilir pozisyon yok