| package pl.hackerspace.service; |
| |
| import com.lowagie.text.pdf.BaseFont; |
| import lombok.AccessLevel; |
| import lombok.NoArgsConstructor; |
| import lombok.extern.slf4j.Slf4j; |
| import org.jsoup.Jsoup; |
| import org.jsoup.nodes.Document; |
| import org.springframework.stereotype.Service; |
| import org.xhtmlrenderer.layout.SharedContext; |
| import org.xhtmlrenderer.pdf.ITextRenderer; |
| import pl.hackerspace.domain.Client; |
| import pl.hackerspace.dto.CustomInvoiceDataDto; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.math.BigDecimal; |
| import java.math.RoundingMode; |
| import java.text.DecimalFormat; |
| import java.time.LocalDateTime; |
| import java.time.format.DateTimeFormatter; |
| import java.util.HashSet; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.function.Function; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| @Slf4j |
| @Service |
| @NoArgsConstructor(access = AccessLevel.PRIVATE) |
| public class TemplateService { |
| |
| public static final DecimalFormat MONEY_FORMAT = twoDecimalsFormatter(); |
| public static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM"); |
| public static final String HTML_TEMPLATE_FILE = "invoiceTemplates/invoice_one_service.html"; |
| |
| public static <A> A withTemplate(Function<String, A> execute) throws IOException { |
| try (InputStream resourceAsStream = TemplateService.class.getClassLoader().getResourceAsStream(HTML_TEMPLATE_FILE)) { |
| String template = new String(resourceAsStream.readAllBytes()); |
| return execute.apply(template); |
| } |
| } |
| |
| static String populateTemplate(String unprocessedTemplate, Client client, final String subscriptionMonth, |
| final LocalDateTime invoiceCreationDate, final String invoiceTitle, |
| CustomInvoiceDataDto customInvoiceData) { |
| boolean isCustom = customInvoiceData != null; |
| BigDecimal price = isCustom ? customInvoiceData.getCustomPrice() : client.getPrice(); |
| BigDecimal amount = isCustom ? customInvoiceData.getCustomAmount() : client.getAmount(); |
| BigDecimal vat = isCustom ? customInvoiceData.getCustomVat() : client.getVat(); |
| String serviceName = isCustom ? customInvoiceData.getCustomServiceName() : client.getServiceName(); |
| BigDecimal totalNet = amount.multiply(price); |
| String processedHtml = unprocessedTemplate |
| .replace("%client_name%", client.getName()) |
| .replace("%invoice_title%", invoiceTitle) |
| .replace("%client_price%", formatAsMoney(price)) |
| .replace("%client_addressLine1%", client.getAddressLine1()) |
| .replace("%client_addressLine2%", client.getAddressLine2()) |
| .replace("%client_service_name%", serviceName) |
| .replace("%client_nip%", Optional.ofNullable(client.getNip()).map(nip -> "NIP: " + nip).orElse("")) |
| .replace("%client_total_net%", formatAsMoney(totalNet)) |
| .replace("%client_total_gross%", formatAsMoney(totalNet.add(percentageValue(totalNet, vat)))) |
| .replace("%client_amount%", formatAsMoney(amount)) |
| .replace("%client_vat%", formatAsMoney(vat)) |
| .replace("%client_total_tax%", formatAsMoney(percentageValue(totalNet, vat))) |
| .replace("%invoice_month_string%", subscriptionMonth) |
| .replace("%client_payment_date%", formatAsDate(invoiceCreationDate.plusDays(client.getPaymentOffsetDays()))) |
| .replace("%invoice_date%", formatAsDate(invoiceCreationDate)); |
| validateAllPlaceholdersSubstituted(processedHtml); |
| return processedHtml; |
| } |
| |
| private static String formatAsMoney(BigDecimal totalNet) { |
| return MONEY_FORMAT.format(totalNet); |
| } |
| |
| private static String formatAsDate(LocalDateTime invoiceCreationDate) { |
| return invoiceCreationDate.format(DateTimeFormatter.ISO_LOCAL_DATE); |
| } |
| |
| private static void validateAllPlaceholdersSubstituted(String processed) { |
| Set<String> allMatches = new HashSet<>(); |
| Matcher m = Pattern.compile("%(\\w*?)%").matcher(processed); |
| while (m.find()) { |
| allMatches.add(m.group()); |
| } |
| if (!allMatches.isEmpty()) { |
| throw new IllegalStateException("There are unsubstituted placeholders in the template: " + allMatches); |
| } |
| } |
| |
| private static DecimalFormat twoDecimalsFormatter() { |
| DecimalFormat df = new DecimalFormat(); |
| df.setMaximumFractionDigits(2); |
| df.setMinimumFractionDigits(2); |
| df.setGroupingUsed(false); |
| return df; |
| } |
| |
| public static BigDecimal percentageValue(BigDecimal base, BigDecimal pct) { |
| return base.multiply(pct).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP); |
| } |
| |
| static byte[] convertHtmlToPdf(String unprocessedTemplate, Client client, final LocalDateTime creationDate, |
| final String invoiceTitle, final String monthOfInvoice, CustomInvoiceDataDto customInvoiceData) { |
| String processedHtml = populateTemplate(unprocessedTemplate, client, monthOfInvoice, creationDate, invoiceTitle, |
| customInvoiceData); |
| try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { |
| ITextRenderer renderer = new ITextRenderer(); |
| SharedContext sharedContext = renderer.getSharedContext(); |
| sharedContext.setPrint(true); |
| sharedContext.setInteractive(false); |
| renderer.getFontResolver().addFont("fonts/calibri.ttf", BaseFont.IDENTITY_H, true); |
| renderer.setDocumentFromString(htmlToXhtml(processedHtml)); |
| renderer.layout(); |
| renderer.createPDF(outputStream); |
| return outputStream.toByteArray(); |
| } catch (IOException e) { |
| throw new RuntimeException("Exception during pdf conversion", e); |
| } |
| } |
| |
| private static String htmlToXhtml(String html) { |
| Document document = Jsoup.parse(html); |
| document.outputSettings().syntax(Document.OutputSettings.Syntax.xml); |
| return document.html(); |
| } |
| } |