zoho credit note

This commit is contained in:
Ralph Soika 2025-06-10 16:34:50 +02:00
parent 8d236df375
commit d4f1bd318e
5 changed files with 296 additions and 11 deletions

View file

@ -30,6 +30,8 @@ import com.alexanderlogistics.zoho.dto.ZohoBillItem;
import com.alexanderlogistics.zoho.dto.ZohoContact; import com.alexanderlogistics.zoho.dto.ZohoContact;
import com.alexanderlogistics.zoho.dto.ZohoContactCreateResponse; import com.alexanderlogistics.zoho.dto.ZohoContactCreateResponse;
import com.alexanderlogistics.zoho.dto.ZohoContactFindResponse; 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.ZohoInvoice;
import com.alexanderlogistics.zoho.dto.ZohoInvoiceCreateResponse; import com.alexanderlogistics.zoho.dto.ZohoInvoiceCreateResponse;
import com.alexanderlogistics.zoho.dto.ZohoInvoiceItem; 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<String> 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 * Exports an Eingangsrechnung document
* *
@ -421,6 +493,7 @@ public class ZohoAPIService {
zohoInvoice.setTotal(convertInvoiceTotalToAED(invoice, invoice.getItemValueDouble("invoice.total"))); zohoInvoice.setTotal(convertInvoiceTotalToAED(invoice, invoice.getItemValueDouble("invoice.total")));
/* /*
* Change: 10.06.2025
* Das Cargosoft unlogische Positionen sendet, ZOHO diese aber erzwingt, * Das Cargosoft unlogische Positionen sendet, ZOHO diese aber erzwingt,
* erzeugen wir nur noch eine einzige Zeile mit dem tatsächlichen Betrag! * erzeugen wir nur noch eine einzige Zeile mit dem tatsächlichen Betrag!
* *
@ -448,6 +521,54 @@ public class ZohoAPIService {
return zohoInvoice; 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<ItemCollection> positionen = InvoiceUtil.explodeChildList(invoice);
// List<ZohoInvoiceItem> 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<ZohoInvoiceItem> 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 * Diese Hilfsmethode rechnet den Rechnungsbetrag in Fremdwährung auf AED um
* *

View file

@ -13,6 +13,7 @@ import org.imixs.workflow.exceptions.AdapterException;
import org.imixs.workflow.exceptions.PluginException; import org.imixs.workflow.exceptions.PluginException;
import com.alexanderlogistics.zoho.dto.ZohoBill; import com.alexanderlogistics.zoho.dto.ZohoBill;
import com.alexanderlogistics.zoho.dto.ZohoCreditnote;
import com.alexanderlogistics.zoho.dto.ZohoInvoice; import com.alexanderlogistics.zoho.dto.ZohoInvoice;
import com.alexanderlogistics.zoho.dto.ZohoVendorCredit; import com.alexanderlogistics.zoho.dto.ZohoVendorCredit;
@ -90,21 +91,46 @@ public class ZohoExportAdapter implements SignalAdapter {
// String currencyID = // String currencyID =
// zohoAPIService.lookupCurrency(document.getItemValueString("invoice.currency"), // zohoAPIService.lookupCurrency(document.getItemValueString("invoice.currency"),
// accessToken); // accessToken);
ZohoInvoice zohoInvoice = zohoAPIService.exportInvoice(document, contactID, accessToken);
// Attache Files.... // Gutschrift??
List<FileData> files = null; if (document.getItemValueDouble("invoice.total") < 0) {
ItemCollection snapshot = snapshotService.findSnapshot(document); // create credit note!
if (snapshot != null) { logger.info("├── CreditNote Export");
files = snapshot.getFileData(); ZohoCreditnote zohoCreditnote = zohoAPIService.exportCreditnote(document, contactID, accessToken);
if (files != null) {
for (FileData file : files) { // Attache Files....
if (file.getName().toLowerCase().endsWith(".pdf")) { List<FileData> files = null;
zohoAPIService.attacheFileData(zohoInvoice.getInvoiceId(), file, "invoices", accessToken); 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<FileData> 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);
}
} }
} }
} }
} }
} }
/** /**

View file

@ -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<ZohoInvoiceItem> 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<ZohoInvoiceItem> getLineItems() {
return lineItems;
}
public void setLineItems(List<ZohoInvoiceItem> 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;
}
}

View file

@ -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;
}
}

View file

@ -137,7 +137,7 @@
Generieren Sie einen neuen Access Code Generieren Sie einen neuen Access Code
<br /> <br />
Scope: Scope:
<code>ZohoBooks.contacts.ALL,ZohoBooks.invoices.ALL,ZohoBooks.bills.ALL,ZohoBooks.debitnotes.ALL,ZohoBooks.settings.READ</code> <code>ZohoBooks.contacts.ALL,ZohoBooks.invoices.ALL,ZohoBooks.creditnotes.ALL,ZohoBooks.bills.ALL,ZohoBooks.debitnotes.ALL,ZohoBooks.settings.READ</code>
</li> </li>
<li> <li>
Code über den Button "Create" anfordern und in das Feld "Access Code" Code über den Button "Create" anfordern und in das Feld "Access Code"