office-alexander-logistics/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoAPIService.java

1000 lines
44 KiB
Java

package com.alexanderlogistics.zoho;
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.imixs.workflow.FileData;
import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.exceptions.PluginException;
import com.alexanderlogistics.InvoiceUtil;
import com.alexanderlogistics.zoho.dto.ZohoBill;
import com.alexanderlogistics.zoho.dto.ZohoBillCreateResponse;
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;
import com.alexanderlogistics.zoho.dto.ZohoTax;
import com.alexanderlogistics.zoho.dto.ZohoTaxListResponse;
import com.alexanderlogistics.zoho.dto.ZohoVendorCredit;
import com.alexanderlogistics.zoho.dto.ZohoVendorCreditCreateResponse;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.security.DeclareRoles;
import jakarta.annotation.security.RolesAllowed;
import jakarta.annotation.security.RunAs;
import jakarta.ejb.Singleton;
import jakarta.inject.Inject;
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;
/**
* Der ZohoAPIService stellt methoden für den Zugriff auf die Zoho API bereit.
*/
@DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS",
"org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
"org.imixs.ACCESSLEVEL.MANAGERACCESS" })
@RolesAllowed({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS",
"org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
"org.imixs.ACCESSLEVEL.MANAGERACCESS" })
@Singleton
@RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS")
public class ZohoAPIService {
private static Logger logger = Logger.getLogger(ZohoAPIService.class.getName());
private HttpClient httpClient;
private Jsonb jsonb;
@Inject
ZohoOAuthManager zohoOAuthManager;
@PostConstruct
void init() {
this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
JsonbConfig config = new JsonbConfig().withDateFormat("yyyy-MM-dd", Locale.getDefault())
.setProperty("jsonb.fail-on-unknown-properties", false); // Ignoriert unbekannte Felder
this.jsonb = JsonbBuilder.create(config);
}
/**
* This finder method returns the contact ID to a given company name.
* The method returns null if no company with this name exists
*
* @param companyName
* @param contactType
* @param accessToken
* @return
* @throws PluginException
*/
public String findContactIDByCompanyName(String companyName, String contactType, String accessToken)
throws PluginException {
// Print JSON to console for verification
logger.info("├── Zoho lookup contact (" + contactType + ") '" + companyName + "'...");
try {
// https://www.zohoapis.com/books/v3/contacts?organization_id=10234695&company_name=xxx'
String uri = zohoOAuthManager.getBaseURI() + "contacts?organization_id="
+ zohoOAuthManager.getOrganization() + "&company_name="
+ URLEncoder.encode(companyName, StandardCharsets.UTF_8);
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").GET().build();
HttpResponse<String> response;
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
// Print response details to console
logger.fine("├── Zoho Response:");
logger.fine("│ ├── Status Code: " + response.statusCode());
logger.fine("│ ├── Response Body: " + response.body());
if (response.statusCode() >= 300) {
throw new PluginException(this.getClass().getName(), "Zoho API error",
"Failed to find Contacts - Status code: " + response.statusCode() + ", Response: "
+ response.body());
}
ZohoContactFindResponse contactsResponse = jsonb.fromJson(response.body(), ZohoContactFindResponse.class);
if (contactsResponse != null && contactsResponse.getContacts() != null) {
// Durchsuche die Ergebnisse nach exakter Übereinstimmung
for (ZohoContact contact : contactsResponse.getContacts()) {
if (companyName.equalsIgnoreCase(contact.getCompanyName())
&& contactType.equalsIgnoreCase((contact.getContactType()))) {
logger.info("│ ├── contactID= " + contact.getContactId());
return contact.getContactId();
}
}
}
} catch (IOException | InterruptedException e) {
throw new PluginException(this.getClass().getName(), "Zoho API error",
"Failed to find Contacts: " + e.getMessage());
}
return null;
}
/**
* Exports an invocie document
*
* @see https://www.zoho.com/books/api/v3/invoices/#create-an-invoice
*
* @param invoice
* @throws PluginException
*/
public ZohoInvoice exportInvoice(ItemCollection invoice, String contactID, String accessToken)
throws PluginException {
logger.info("├── Zoho Export Invoice " + invoice.getItemValueString("invoice.number") + "...");
String currencyID = lookupCurrency(invoice.getItemValueString("invoice.currency"), accessToken);
// Konvertiere ItemCollection zu ZohoInvoiceDTO
ZohoInvoice zohoInvoice = convertToZohoInvoice(invoice, contactID, currencyID);
try {
String requestBody = jsonb.toJson(zohoInvoice);
// Print JSON to console for verification
logger.fine("├── Zoho Invoice JSON Request...");
logger.fine("│ ├── " + requestBody);
// https://www.zohoapis.com/books/v3/invoices?organization_id=10234695'
String uri = zohoOAuthManager.getBaseURI() + "invoices?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 invoice. Status code: " + response.statusCode() + ", Response: "
+ response.body());
}
// Erfolgreiche Erstellung
String bodyContent = response.body();
logger.fine("│ ├── Response Body: " + bodyContent);
System.out.print(bodyContent);
ZohoInvoiceCreateResponse createResponse = jsonb.fromJson(bodyContent, ZohoInvoiceCreateResponse.class);
if (createResponse != null) {
logger.fine("│ ├── OK - customerID=" + createResponse.getInvoice().getCustomerId());
logger.info("│ ├── OK - invoiceID=" + createResponse.getInvoice().getInvoiceId());
zohoInvoice = createResponse.getInvoice();
// now mark the invoice as send
markInvoiceAsSend(zohoInvoice.getInvoiceId(), "invoices", accessToken);
logger.info("│ ├── Invoice Export successful.");
return zohoInvoice;
}
} catch (IOException | InterruptedException e) {
throw new PluginException(this.getClass().getName(), "Zoho API error",
"Failed to export invoice to Zoho: " + e.getMessage(), e);
}
return null;
}
/**
* 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") + "...");
String currencyID = lookupCurrency(invoice.getItemValueString("invoice.currency"), accessToken);
// Konvertiere ItemCollection zu ZohoInvoiceDTO
ZohoCreditnote zohoCreditnote = convertToZohoCreditnote(invoice, contactID, currencyID);
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
*
* @see https://www.zoho.com/books/api/v3/expenses/#create-an-expense
*
* @param invoice
* @throws PluginException
*/
public ZohoBill exportBill(ItemCollection invoice, String contactID, String accountID,
String accessToken) throws PluginException {
logger.info("├── Zoho Export Bill " + invoice.getItemValueString("invoice.number") + "...");
String currencyID = lookupCurrency(invoice.getItemValueString("invoice.currency"), accessToken);
// Konvertiere ItemCollection zu ZohoInvoiceDTO
ZohoBill zohoBill = convertToZohoBill(invoice, contactID, accountID, currencyID);
try {
String requestBody = jsonb.toJson(zohoBill);
// Print JSON to console for verification
logger.fine("├── Zoho Bill JSON Request...");
logger.fine("│ ├── " + requestBody);
// https://www.zohoapis.com/books/v3/expenses?organization_id=10234695'
String uri = zohoOAuthManager.getBaseURI() + "bills?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 expense. Status code: " + response.statusCode() + ", Response: "
+ response.body());
}
// Erfolgreiche Erstellung
String bodyContent = response.body();
logger.fine("│ ├── Response Body: " + bodyContent);
System.out.print(bodyContent);
ZohoBillCreateResponse createResponse = jsonb.fromJson(bodyContent, ZohoBillCreateResponse.class);
if (createResponse != null) {
logger.info("│ ├── OK - customerID=" + createResponse.getBill().getVendorId());
logger.info("│ ├── OK - billID=" + createResponse.getBill().getBillId());
zohoBill = createResponse.getBill();
// now mark the invoice as send
logger.info("│ ├── Bill Export successful.");
return zohoBill;
}
} catch (IOException | InterruptedException e) {
throw new PluginException(this.getClass().getName(), "Zoho API error",
"Failed to export bill to Zoho: " + e.getMessage(), e);
}
return null;
}
/**
* Exports an Eingangsrechnung as a Vendor Credit
*
* @see https://www.zoho.com/books/api/v3/expenses/#create-an-expense
*
* @param invoice
* @throws PluginException
*/
public ZohoVendorCredit exportVendorCredit(ItemCollection invoice, String contactID, String accountID,
String accessToken) throws PluginException {
logger.info("├── Zoho Export VendorCredit " + invoice.getItemValueString("invoice.number") + "...");
String currencyID = lookupCurrency(invoice.getItemValueString("invoice.currency"), accessToken);
// Konvertiere ItemCollection zu ZohoInvoiceDTO
ZohoVendorCredit zohoVendorCredit = convertToZohoVendorCredit(invoice, contactID, accountID, currencyID);
try {
String requestBody = jsonb.toJson(zohoVendorCredit);
// Print JSON to console for verification
logger.fine("├── Zoho VendorCredit JSON Request...");
logger.fine("│ ├── " + requestBody);
// https://www.zohoapis.com/books/v3/expenses?organization_id=10234695'
String uri = zohoOAuthManager.getBaseURI() + "vendorcredits?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 expense. Status code: " + response.statusCode() + ", Response: "
+ response.body());
}
// Erfolgreiche Erstellung
String bodyContent = response.body();
logger.fine("│ ├── Response Body: " + bodyContent);
System.out.print(bodyContent);
ZohoVendorCreditCreateResponse createResponse = jsonb.fromJson(bodyContent,
ZohoVendorCreditCreateResponse.class);
if (createResponse != null) {
logger.info("│ ├── OK - customerID=" + createResponse.getVendorCredit().getVendorId());
logger.info("│ ├── OK - creditID=" + createResponse.getVendorCredit().getCreditId());
zohoVendorCredit = createResponse.getVendorCredit();
// now mark the invoice as send
logger.info("│ ├── VendorCredit Export successful.");
return zohoVendorCredit;
}
} catch (IOException | InterruptedException e) {
throw new PluginException(this.getClass().getName(), "Zoho API error",
"Failed to export vendor credit to Zoho: " + e.getMessage(), e);
}
return null;
}
/**
* Erstellt einen neuen Contact in Zoho Books
*
* @param companyName Der Firmenname für den neuen Contact
* @param contactType - customer/vendor
* @return Der erstellte ZohoContact mit contact_id
* @throws PluginException Wenn der API-Aufruf fehlschlägt
*/
public ZohoContact createContact(String companyName, String contactType, String currencyID, String accessToken)
throws PluginException {
logger.info("├── Zoho create contact '" + companyName + "'...");
try {
// Contact-DTO vorbereiten
ZohoContact newContact = new ZohoContact();
newContact.setContactName(companyName); // Wir verwenden den Firmennamen auch als Contact Name
newContact.setCompanyName(companyName);
newContact.setContactType(contactType);
// set currency?
if (currencyID != null && !currencyID.isEmpty()) {
newContact.setCurrencyId(currencyID);
}
// JSON serialisieren
String requestBody = jsonb.toJson(newContact);
logger.fine("│ ├── " + requestBody);
String uri = zohoOAuthManager.getBaseURI() + "contacts?organization_id="
+ zohoOAuthManager.getOrganization();
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());
logger.fine("├── Zoho Response:");
logger.fine("│ ├── Status Code: " + response.statusCode());
logger.fine("│ ├── Response Body: " + response.body());
if (response.statusCode() >= 300) {
throw new PluginException(this.getClass().getName(), "Zoho API error",
"Failed to create invoice. Status code: " + response.statusCode() + ", Response: "
+ response.body());
}
// Erfolgreiche Erstellung
ZohoContactCreateResponse createResponse = jsonb.fromJson(response.body(), ZohoContactCreateResponse.class);
if (createResponse != null) {
logger.info("│ ├── OK - contactID=" + createResponse.getContact().getContactId());
return createResponse.getContact();
} else {
throw new PluginException(this.getClass().getName(), "Zoho API error",
"Response could not be resolved!");
}
} catch (IOException | InterruptedException e) {
throw new PluginException(this.getClass().getName(), "Zoho API error",
"Failed to create contact: " + e.getMessage(), e);
}
}
/**
* Ausgangsrechnung
*
* @param invoice
* @param contactID
* @param currencyID
* @return
* @throws PluginException
*/
private ZohoInvoice convertToZohoInvoice(ItemCollection invoice, String contactID, String currencyID)
throws PluginException {
ZohoInvoice zohoInvoice = new ZohoInvoice();
// Basisinformationen
zohoInvoice.setInvoiceNumber(invoice.getItemValueString("invoice.number"));
zohoInvoice.setDate(
LocalDate.ofInstant(invoice.getItemValueDate("invoice.date").toInstant(), ZoneId.systemDefault()));
zohoInvoice.setDueDate(
LocalDate.ofInstant(invoice.getItemValueDate("invoice.duedate").toInstant(), ZoneId.systemDefault()));
zohoInvoice.setCustomerId(contactID);
// set currency?
if (currencyID != null && !currencyID.isEmpty()) {
zohoInvoice.setCurrencyId(currencyID);
}
// convert total by rate
zohoInvoice.setTotal(invoice.getItemValueDouble("invoice.total"));
// set exchange Rate
double exchangeRate = invoice.getItemValueDouble("invoice.rate");
if (exchangeRate != 0.0) {
zohoInvoice.setExchangeRate(exchangeRate);
}
// add line items...
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(itemCol.getItemValueDouble("datev.umsatz"));
lineItem.setQuantity(1);
return lineItem;
}).collect(Collectors.toList());
zohoInvoice.setLineItems(lineItems);
return zohoInvoice;
}
/**
* Gutschrift Ausgang
*
* @param invoice
* @param contactID
* @param currencyID
* @return
* @throws PluginException
*/
private ZohoCreditnote convertToZohoCreditnote(ItemCollection invoice, String contactID, String currencyID)
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);
// set currency?
if (currencyID != null && !currencyID.isEmpty()) {
zohoCreditnote.setCurrencyId(currencyID);
}
// convert total by rate - negativer betrag!
zohoCreditnote.setTotal(Math.abs(invoice.getItemValueDouble("invoice.total")));
// set exchange Rate
double exchangeRate = invoice.getItemValueDouble("invoice.rate");
if (exchangeRate != 0.0) {
zohoCreditnote.setExchangeRate(exchangeRate);
}
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(itemCol.getItemValueDouble("datev.umsatz"));
lineItem.setQuantity(1);
return lineItem;
}).collect(Collectors.toList());
zohoCreditnote.setLineItems(lineItems);
return zohoCreditnote;
}
/**
* Diese Hilfsmethode rechnet den Rechnungsbetrag in Fremdwährung auf AED um
*
*
* Herr Hoelzl 15.05.2025
* However the backend reports the P&L & Balance sheet will reflect in AED as
* AGL Dubai books are maintained in AED therefore we need to have an
* exchange rate which can be fixed @3.6725 AED for 1 USD.
*
* Aus diesem Grund rechnen wir direkt in AED um!
*
* @return
*/
@Deprecated
public double xxxxconvertInvoiceTotalToAED(ItemCollection invoice, double total) {
// convert total by fixed rate?
String currency = invoice.getItemValueString("invoice.currency");
if (!currency.isEmpty() && !currency.equalsIgnoreCase("AED")) {
// es ist eine Fremdwährung
// Herr Hoelzl 15.05.2025
// However the backend reports the P&L & Balance sheet will reflect in AED as
// AGL Dubai books are maintained in AED therefore we need to have an exchange
// rate which can be fixed @3.6725 AED for 1 USD.
//
// Aus diesem Grund rechnen wir direkt in AED um!
// wir verwenden unsere Rate
// double rate=3.6725;
double rate = 0;
if (invoice.getModelVersion().startsWith("rechnungsausgang-")) {
rate = invoice.getItemValueDouble("invoice.rate");
} else {
rate = invoice.getItemValueDouble("invoice.exchangerate");
}
if (rate == 0) {
rate = 0.27229; // (3.6725) = default rate
}
double aedBetrag = total / rate;
logger.info("│ ├── convert into AED=" + aedBetrag);
return aedBetrag;
} else {
return total;
}
}
private ZohoBill convertToZohoBill(ItemCollection invoice, String contactID, String accountID, String currencyID)
throws PluginException {
ZohoBill zohoBill = new ZohoBill();
// Kunden-ID (muss angepasst werden je nachdem wie du sie speicherst)
zohoBill.setVendorId(contactID);
// set currency?
if (currencyID != null && !currencyID.isEmpty()) {
zohoBill.setCurrencyId(currencyID);
}
// Basisinformationen
zohoBill.setInvoiceNumber(invoice.getItemValueString("invoice.number"));
zohoBill.setDate(
LocalDate.ofInstant(invoice.getItemValueDate("invoice.date").toInstant(), ZoneId.systemDefault()));
// convert total by rate
// zohoBill.setAmount(invoice.getItemValueDouble("invoice.total"));
// set exchange Rate
double exchangeRate = invoice.getItemValueDouble("invoice.exchangerate");
if (exchangeRate != 0.0) {
zohoBill.setExchangeRate(exchangeRate);
}
// zohoBill.setPaid_through_account_id("1700");
List<ItemCollection> positionen = InvoiceUtil.explodeChildList(invoice);
// {amount=[11825.00], total=[11825.00], numpos=[1], name=[AB-DEFGHI-1234-567],
// tax=[0], category=[STORAGE], invoice.period=[]}
List<ZohoBillItem> lineItems = positionen.stream().map(item -> {
ItemCollection itemCol = (ItemCollection) item;
ZohoBillItem lineItem = new ZohoBillItem();
lineItem.setName(itemCol.getItemValueString("name"));
lineItem.setAccountId(accountID);
lineItem.setQuantity(1);
lineItem.setRate(itemCol.getItemValueDouble("amount"));
// tax - tax_id, tax_percentage
try {
float tax = itemCol.getItemValueFloat("tax");
String taxID = lookupTaxID(tax, accountID);
if (taxID != null && !taxID.isEmpty()) {
lineItem.setTaxId(taxID);
}
} catch (PluginException e) {
logger.info("│ ├── failed to compute taxId: " + e.getMessage());
}
return lineItem;
}).collect(Collectors.toList());
zohoBill.setLineItems(lineItems);
return zohoBill;
}
private ZohoVendorCredit convertToZohoVendorCredit(ItemCollection invoice, String contactID,
String accountID, String currencyID)
throws PluginException {
ZohoVendorCredit zohoVendorCredit = new ZohoVendorCredit();
// Basisinformationen
zohoVendorCredit.setInvoiceNumber(invoice.getItemValueString("invoice.number"));
zohoVendorCredit.setDate(
LocalDate.ofInstant(invoice.getItemValueDate("invoice.date").toInstant(), ZoneId.systemDefault()));
// convert total by rate
// zohoVendorCredit.setAmount(convertInvoiceTotalToAED(invoice,
// invoice.getItemValueDouble("invoice.total")));
// Kunden-ID (muss angepasst werden je nachdem wie du sie speicherst)
zohoVendorCredit.setVendorId(contactID);
// set currency?
if (currencyID != null && !currencyID.isEmpty()) {
zohoVendorCredit.setCurrencyId(currencyID);
}
// set exchange Rate
double exchangeRate = invoice.getItemValueDouble("invoice.exchangerate");
if (exchangeRate != 0.0) {
zohoVendorCredit.setExchangeRate(exchangeRate);
}
// zohoBill.setPaid_through_account_id("1700");
List<ItemCollection> positionen = InvoiceUtil.explodeChildList(invoice);
// {amount=[11825.00], total=[11825.00], numpos=[1], name=[AB-DEFGHI-1234-567],
// tax=[0], category=[STORAGE], invoice.period=[]}
List<ZohoBillItem> lineItems = positionen.stream().map(item -> {
ItemCollection itemCol = (ItemCollection) item;
ZohoBillItem lineItem = new ZohoBillItem();
lineItem.setName(itemCol.getItemValueString("name"));
lineItem.setAccountId(accountID);
lineItem.setQuantity(1);
lineItem.setRate(itemCol.getItemValueDouble("amount"));
// tax - tax_id, tax_percentage
try {
float tax = itemCol.getItemValueFloat("tax");
String taxID = lookupTaxID(tax, accountID);
if (taxID != null && !taxID.isEmpty()) {
lineItem.setTaxId(taxID);
}
} catch (PluginException e) {
logger.info("│ ├── failed to compute taxId: " + e.getMessage());
}
return lineItem;
}).collect(Collectors.toList());
zohoVendorCredit.setLineItems(lineItems);
return zohoVendorCredit;
}
/**
* This method adds an attachment to an invoice
*
* @param invoiceId The Zoho invoice ID
* @param file The FileData object containing the attachment
* @param uriPattern The API endpoint pattern (e.g., "invoices")
* @param accessToken The OAuth access token
* @throws PluginException If the API call fails
*/
public void attacheFileData(String invoiceId, FileData file, String uriPattern, String accessToken)
throws PluginException {
String uri = zohoOAuthManager.getBaseURI() + uriPattern + "/" + //
invoiceId + //
"/attachment" + //
"?organization_id=" + zohoOAuthManager.getOrganization();
logger.info("│ ├── add attachment '" + file.getName() + "'...");
logger.fine("│ ├── ...uri: " + uri + "...");
try {
// Generate a unique boundary
String boundary = "----WebKitFormBoundary" + System.currentTimeMillis();
// Build multipart request body
byte[] header = ("--" + boundary + "\r\n"
+ "Content-Disposition: form-data; name=\"attachment\"; filename=\"" + file.getName() + "\"\r\n"
+ "Content-Type: " + file.getContentType() + "\r\n\r\n").getBytes(StandardCharsets.UTF_8);
byte[] footer = ("\r\n--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8);
logger.fine("│ ├── content size=" + file.getContent().length);
HttpRequest.BodyPublisher bodyPublisher = HttpRequest.BodyPublishers
.ofByteArrays(Arrays.asList(header, file.getContent(), footer));
// Create and send the request
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(uri))
.header("Authorization", "Zoho-oauthtoken " + accessToken)
.header("Content-Type", "multipart/form-data; boundary=" + boundary).POST(bodyPublisher).build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
logger.fine("├── Zoho Response:");
logger.fine("│ ├── Status Code: " + response.statusCode());
logger.fine("│ ├── Response Body: " + response.body());
if (response.statusCode() >= 300) {
throw new PluginException(this.getClass().getName(), "Zoho API error",
"Failed to add attachment. Status code: " + response.statusCode() + ", Response: "
+ response.body());
}
logger.info("│ ├── OK - Attachment added successfully");
} catch (IOException | InterruptedException e) {
throw new PluginException(this.getClass().getName(), "Zoho API error",
"Failed to add attachment: " + e.getMessage(), e);
}
}
/**
* This method marks an invoice as send
*
*
* curl --request POST \ --url
* 'https://www.zohoapis.com/books/v3/invoices/982000000567114/status/sent?organization_id=10234695'
* \ --header 'Authorization: Zoho-oauthtoken
* 1000.41d9xxxxxxxxxxxxxxxxxxxxxxxxc2d1.8fccxxxxxxxxxxxxxxxxxxxxxxxx125f'
*
*
* @param invoiceId The Zoho invoice ID
* @param uriPattern The API endpoint pattern (e.g., "invoices")
* @param accessToken The OAuth access token
* @throws PluginException If the API call fails
*/
public void markInvoiceAsSend(String invoiceId, String uriPattern, String accessToken) throws PluginException {
String uri = zohoOAuthManager.getBaseURI() + uriPattern + "/" + //
invoiceId + //
"/status/sent" + //
"?organization_id=" + zohoOAuthManager.getOrganization();
logger.fine("│ ├── mark invoice as sent '" + invoiceId + "'...");
logger.fine("│ ├── ...uri: " + uri + "...");
try {
// JSON serialisieren
String requestBody = "";
logger.fine("│ ├── " + requestBody);
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());
logger.fine("├── Zoho Response:");
logger.fine("│ ├── Status Code: " + response.statusCode());
logger.fine("│ ├── Response Body: " + response.body());
if (response.statusCode() >= 300) {
throw new PluginException(this.getClass().getName(), "Zoho API error",
"Failed to create invoice. Status code: " + response.statusCode() + ", Response: "
+ response.body());
}
logger.info("│ ├── OK - status update 'send' successfully");
} catch (IOException | InterruptedException e) {
throw new PluginException(this.getClass().getName(), "Zoho API error",
"Failed to add attachment: " + e.getMessage(), e);
}
}
/**
* Sucht eine currency ID. Die Methode läd die Liste aller Currencies und parsed
* the currency ID mit einer Regex
*
* @param currency
* @param accessToken
* @return
* @throws PluginException
*/
public String lookupCurrency(String currency, String accessToken) throws PluginException {
// Print JSON to console for verification
logger.info("├── Zoho lookup currency '" + currency + "'...");
try {
// https://www.zohoapis.com/books/v3/currencies?organization_id=10234695'
String uri = zohoOAuthManager.getBaseURI() + "settings/currencies?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").GET().build();
HttpResponse<String> response;
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
// Print response details to console
logger.fine("├── Zoho Response:");
logger.fine("│ ├── Status Code: " + response.statusCode());
logger.fine("│ ├── Response Body: " + response.body());
if (response.statusCode() >= 300) {
throw new PluginException(this.getClass().getName(), "Zoho API error",
"Failed to find currencies - Status code: " + response.statusCode() + ", Response: "
+ response.body());
}
String json = response.body();
String pattern = "\"currency_code\":\"" + Pattern.quote(currency) + "\".*?\"currency_id\":\"(\\d+)\"";
Matcher matcher = Pattern.compile(pattern).matcher(json);
String result = matcher.find() ? matcher.group(1) : null;
logger.info("│ ├── Currency ID=" + result);
return result;
} catch (IOException | InterruptedException e) {
throw new PluginException(this.getClass().getName(), "Zoho API error",
"Failed to find currency: " + e.getMessage());
}
}
/**
* This method lookups a Contact by name.
* If no contact with the given name exists, the method creats a new one. In
* this case the defaultCurrencyID is mandatory!
*
* @param companyName
* @param contactType
* @param defaultCurrencyID
* @param accessToken
* @return
* @throws PluginException
*/
public String lookupContact(String companyName, String contactType, String defaultCurrencyID, String accessToken)
throws PluginException {
// first lookup contact!
String contactID = "";
contactID = findContactIDByCompanyName(companyName, contactType, accessToken);
if (contactID == null) {
logger.info("│ ├── contact '" + companyName + "' not found");
ZohoContact contact = createContact(companyName, contactType, defaultCurrencyID, accessToken);
contactID = contact.getContactId();
}
return contactID;
}
/**
* This finder method returns the tax ID to a given percentage.
* The method returns null if no tax with this percentage exists
*
* @param tax_percentage
* @param accessToken
* @return taxID
* @throws PluginException
*/
public String lookupTaxID(float tax_percentage, String accessToken)
throws PluginException {
if (tax_percentage == 0.0) {
return null;
}
// Print JSON to console for verification
logger.info("├── Zoho lookup tax '" + tax_percentage + "'...");
try {
// https://www.zohoapis.com/books/v3/settings/taxes?organization_id=10234695'
String uri = zohoOAuthManager.getBaseURI() + "settings/taxes?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").GET().build();
HttpResponse<String> response;
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
// Print response details to console
logger.fine("├── Zoho Response:");
logger.fine("│ ├── Status Code: " + response.statusCode());
logger.fine("│ ├── Response Body: " + response.body());
if (response.statusCode() >= 300) {
throw new PluginException(this.getClass().getName(), "Zoho API error",
"Failed to find Contacts - Status code: " + response.statusCode() + ", Response: "
+ response.body());
}
ZohoTaxListResponse taxListResponse = jsonb.fromJson(response.body(), ZohoTaxListResponse.class);
if (taxListResponse != null && taxListResponse.getTaxes() != null) {
// Durchsuche die Ergebnisse nach exakter Übereinstimmung
for (ZohoTax tax : taxListResponse.getTaxes()) {
if (tax_percentage == tax.getRate()) {
logger.info("│ ├── taxID= " + tax.getTaxId());
return tax.getTaxId();
}
}
}
logger.warning("│ ├── No tax with " + tax_percentage + "% found in Zoho taxes!");
} catch (IOException | InterruptedException e) {
throw new PluginException(this.getClass().getName(), "Zoho API error",
"Failed to find tax: " + e.getMessage());
}
return null;
}
}