diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoAPIService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoAPIService.java index 6c48462..b79a346 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoAPIService.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoAPIService.java @@ -30,6 +30,8 @@ import com.alexanderlogistics.zoho.dto.ZohoBillItem; import com.alexanderlogistics.zoho.dto.ZohoContact; import com.alexanderlogistics.zoho.dto.ZohoContactCreateResponse; import com.alexanderlogistics.zoho.dto.ZohoContactFindResponse; +import com.alexanderlogistics.zoho.dto.ZohoCreditnote; +import com.alexanderlogistics.zoho.dto.ZohoCreditnoteCreateResponse; import com.alexanderlogistics.zoho.dto.ZohoInvoice; import com.alexanderlogistics.zoho.dto.ZohoInvoiceCreateResponse; import com.alexanderlogistics.zoho.dto.ZohoInvoiceItem; @@ -197,6 +199,76 @@ public class ZohoAPIService { } + /** + * Exports an Credit Note - outgoing invoice total < 0,00 + * + * @see https://www.zoho.com/books/api/v3/expenses/#create-an-expense + * + * @param invoice + * @throws PluginException + */ + public ZohoCreditnote exportCreditnote(ItemCollection invoice, String contactID, + String accessToken) throws PluginException { + logger.info("├── Zoho Export creditnote " + invoice.getItemValueString("invoice.number") + "..."); + + // Konvertiere ItemCollection zu ZohoInvoiceDTO + ZohoCreditnote zohoCreditnote = convertToZohoCreditnote(invoice, contactID); + + try { + String requestBody = jsonb.toJson(zohoCreditnote); + + // Print JSON to console for verification + logger.fine("├── Zoho CreditNote JSON Request..."); + logger.fine("│ ├── " + requestBody); + + // https://www.zohoapis.com/books/v3/creditnotes?organization_id=10234695' + String uri = zohoOAuthManager.getBaseURI() + "creditnotes?organization_id=" + + zohoOAuthManager.getOrganization(); + logger.fine("│ ├── uri= " + uri); + logger.fine("│ ├── accessToken=" + accessToken); + HttpRequest request = HttpRequest.newBuilder().uri(URI.create(uri)) + .header("Authorization", "Zoho-oauthtoken " + accessToken) + .header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(requestBody)) + .build(); + + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + // Print response details to console + logger.fine("├── Zoho Response:"); + logger.fine("│ ├── Status Code: " + response.statusCode()); + + if (response.statusCode() >= 300) { + throw new PluginException(this.getClass().getName(), "Zoho API error", + "Failed to create creditnote. Status code: " + response.statusCode() + ", Response: " + + response.body()); + } + + // Erfolgreiche Erstellung + String bodyContent = response.body(); + logger.fine("│ ├── Response Body: " + bodyContent); + + System.out.print(bodyContent); + ZohoCreditnoteCreateResponse createResponse = jsonb.fromJson(bodyContent, + ZohoCreditnoteCreateResponse.class); + + if (createResponse != null) { + logger.info("│ ├── OK - customerID=" + createResponse.getCreditnote().getCustomerId()); + logger.info("│ ├── OK - creditNoteID=" + createResponse.getCreditnote().getCreditNoteNumber()); + zohoCreditnote = createResponse.getCreditnote(); + + // now mark the invoice as send + logger.info("│ ├── VendorCredit Export successful."); + return zohoCreditnote; + } + + } catch (IOException | InterruptedException e) { + throw new PluginException(this.getClass().getName(), "Zoho API error", + "Failed to export creditnote to Zoho: " + e.getMessage(), e); + } + return null; + + } + /** * Exports an Eingangsrechnung document * @@ -421,6 +493,7 @@ public class ZohoAPIService { zohoInvoice.setTotal(convertInvoiceTotalToAED(invoice, invoice.getItemValueDouble("invoice.total"))); /* + * Change: 10.06.2025 * Das Cargosoft unlogische Positionen sendet, ZOHO diese aber erzwingt, * erzeugen wir nur noch eine einzige Zeile mit dem tatsächlichen Betrag! * @@ -448,6 +521,54 @@ public class ZohoAPIService { return zohoInvoice; } + private ZohoCreditnote convertToZohoCreditnote(ItemCollection invoice, String contactID) + throws PluginException { + + ZohoCreditnote zohoCreditnote = new ZohoCreditnote(); + // Basisinformationen + + zohoCreditnote.setCreditNoteNumber(invoice.getItemValueString("invoice.number")); + zohoCreditnote.setDate( + LocalDate.ofInstant(invoice.getItemValueDate("invoice.date").toInstant(), ZoneId.systemDefault())); + zohoCreditnote.setDueDate( + LocalDate.ofInstant(invoice.getItemValueDate("invoice.duedate").toInstant(), ZoneId.systemDefault())); + + zohoCreditnote.setCustomerId(contactID); + + // convert total by rate - negativer betrag! + zohoCreditnote + .setTotal(convertInvoiceTotalToAED(invoice, Math.abs(invoice.getItemValueDouble("invoice.total")))); + + /* + * Change: 10.06.2025 + * Das Cargosoft unlogische Positionen sendet, ZOHO diese aber erzwingt, + * erzeugen wir nur noch eine einzige Zeile mit dem tatsächlichen Betrag! + * + */ + // List positionen = InvoiceUtil.explodeChildList(invoice); + // List lineItems = positionen.stream().map(item -> { + // ItemCollection itemCol = (ItemCollection) item; + // ZohoInvoiceItem lineItem = new ZohoInvoiceItem(); + // lineItem.setName(itemCol.getItemValueString("datev.text")); + // lineItem.setDescription(itemCol.getItemValueString("billingtext")); + // lineItem.setRate(convertInvoiceTotalToAED(invoice, + // itemCol.getItemValueDouble("datev.umsatz"))); + // lineItem.setQuantity(1); + // return lineItem; + // }).collect(Collectors.toList()); + List lineItems = new ArrayList<>(); + ZohoInvoiceItem lineItem = new ZohoInvoiceItem(); + lineItem.setName(invoice.getItemValueString("invoice.text")); + lineItem.setDescription(""); + // negativer Betrag! + lineItem.setRate(convertInvoiceTotalToAED(invoice, Math.abs(invoice.getItemValueDouble("invoice.total")))); + lineItem.setQuantity(1); + lineItems.add(lineItem); + + zohoCreditnote.setLineItems(lineItems); + return zohoCreditnote; + } + /** * Diese Hilfsmethode rechnet den Rechnungsbetrag in Fremdwährung auf AED um * diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoExportAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoExportAdapter.java index 0b5d505..f569f15 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoExportAdapter.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoExportAdapter.java @@ -13,6 +13,7 @@ import org.imixs.workflow.exceptions.AdapterException; import org.imixs.workflow.exceptions.PluginException; import com.alexanderlogistics.zoho.dto.ZohoBill; +import com.alexanderlogistics.zoho.dto.ZohoCreditnote; import com.alexanderlogistics.zoho.dto.ZohoInvoice; import com.alexanderlogistics.zoho.dto.ZohoVendorCredit; @@ -90,21 +91,46 @@ public class ZohoExportAdapter implements SignalAdapter { // String currencyID = // zohoAPIService.lookupCurrency(document.getItemValueString("invoice.currency"), // accessToken); - ZohoInvoice zohoInvoice = zohoAPIService.exportInvoice(document, contactID, accessToken); - // Attache Files.... - List files = null; - ItemCollection snapshot = snapshotService.findSnapshot(document); - if (snapshot != null) { - files = snapshot.getFileData(); - if (files != null) { - for (FileData file : files) { - if (file.getName().toLowerCase().endsWith(".pdf")) { - zohoAPIService.attacheFileData(zohoInvoice.getInvoiceId(), file, "invoices", accessToken); + // Gutschrift?? + if (document.getItemValueDouble("invoice.total") < 0) { + // create credit note! + logger.info("├── CreditNote Export"); + ZohoCreditnote zohoCreditnote = zohoAPIService.exportCreditnote(document, contactID, accessToken); + + // Attache Files.... + List files = null; + ItemCollection snapshot = snapshotService.findSnapshot(document); + if (snapshot != null) { + files = snapshot.getFileData(); + if (files != null) { + for (FileData file : files) { + if (file.getName().toLowerCase().endsWith(".pdf")) { + zohoAPIService.attacheFileData(zohoCreditnote.getCreditnoteId(), file, "creditnotes", + accessToken); + } + } + } + } + } else { + logger.info("├── Invoice Export"); + ZohoInvoice zohoInvoice = zohoAPIService.exportInvoice(document, contactID, accessToken); + + // Attache Files.... + List files = null; + ItemCollection snapshot = snapshotService.findSnapshot(document); + if (snapshot != null) { + files = snapshot.getFileData(); + if (files != null) { + for (FileData file : files) { + if (file.getName().toLowerCase().endsWith(".pdf")) { + zohoAPIService.attacheFileData(zohoInvoice.getInvoiceId(), file, "invoices", accessToken); + } } } } } + } /** diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/dto/ZohoCreditnote.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/dto/ZohoCreditnote.java new file mode 100644 index 0000000..2451ad3 --- /dev/null +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/dto/ZohoCreditnote.java @@ -0,0 +1,99 @@ +package com.alexanderlogistics.zoho.dto; + +import java.time.LocalDate; +import java.util.List; + +import jakarta.json.bind.annotation.JsonbDateFormat; +import jakarta.json.bind.annotation.JsonbProperty; + +public class ZohoCreditnote { + @JsonbProperty("creditnote_id") + private String creditnoteId; + + @JsonbProperty("customer_id") + private String customerId; + + @JsonbProperty("currency_id") + private String currencyId; + + @JsonbDateFormat("yyyy-MM-dd") + private LocalDate date; + + @JsonbProperty("due_date") + @JsonbDateFormat("yyyy-MM-dd") + private LocalDate dueDate; + + @JsonbProperty("creditnote_number") + private String creditNoteNumber; + + @JsonbProperty("line_items") + private List lineItems; + + private double total; + + // Getter und Setter + + public String getCreditnoteId() { + return creditnoteId; + } + + public void setCreditnoteId(String creditnoteId) { + this.creditnoteId = creditnoteId; + } + + public String getCustomerId() { + return customerId; + } + + public void setCustomerId(String customerId) { + this.customerId = customerId; + } + + public LocalDate getDate() { + return date; + } + + public void setDate(LocalDate date) { + this.date = date; + } + + public LocalDate getDueDate() { + return dueDate; + } + + public void setDueDate(LocalDate duedate) { + this.dueDate = duedate; + } + + public String getCreditNoteNumber() { + return creditNoteNumber; + } + + public void setCreditNoteNumber(String creditNoteNumber) { + this.creditNoteNumber = creditNoteNumber; + } + + public List getLineItems() { + return lineItems; + } + + public void setLineItems(List lineItems) { + this.lineItems = lineItems; + } + + public double getTotal() { + return total; + } + + public void setTotal(double total) { + this.total = total; + } + + public String getCurrencyId() { + return currencyId; + } + + public void setCurrencyId(String currencyId) { + this.currencyId = currencyId; + } +} diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/dto/ZohoCreditnoteCreateResponse.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/dto/ZohoCreditnoteCreateResponse.java new file mode 100644 index 0000000..60cbe99 --- /dev/null +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/dto/ZohoCreditnoteCreateResponse.java @@ -0,0 +1,39 @@ +package com.alexanderlogistics.zoho.dto; + +import jakarta.json.bind.annotation.JsonbProperty; + +public class ZohoCreditnoteCreateResponse { + @JsonbProperty("code") + private int code; + + @JsonbProperty("message") + private String message; + + @JsonbProperty("creditnote") + private ZohoCreditnote creditnote; + + // Getter und Setter + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public ZohoCreditnote getCreditnote() { + return creditnote; + } + + public void setCreditnote(ZohoCreditnote creditnote) { + this.creditnote = creditnote; + } +} diff --git a/office-alexander-logistics-app/src/main/webapp/pages/admin/zoho.xhtml b/office-alexander-logistics-app/src/main/webapp/pages/admin/zoho.xhtml index f820aa0..bf02827 100644 --- a/office-alexander-logistics-app/src/main/webapp/pages/admin/zoho.xhtml +++ b/office-alexander-logistics-app/src/main/webapp/pages/admin/zoho.xhtml @@ -137,7 +137,7 @@ Generieren Sie einen neuen Access Code
Scope: - ZohoBooks.contacts.ALL,ZohoBooks.invoices.ALL,ZohoBooks.bills.ALL,ZohoBooks.debitnotes.ALL,ZohoBooks.settings.READ + ZohoBooks.contacts.ALL,ZohoBooks.invoices.ALL,ZohoBooks.creditnotes.ALL,ZohoBooks.bills.ALL,ZohoBooks.debitnotes.ALL,ZohoBooks.settings.READ
  • Code über den Button "Create" anfordern und in das Feld "Access Code"