<?php
namespace App\Service;
use App\Entity\User;
use App\Entity\Currency;
use App\Entity\Department;
use App\Entity\ExpenseRequest;
use App\Entity\ExpenseRequestInvoice;
use App\Entity\SalesOrder;
use App\Entity\XeroAuth;
use App\Entity\Invoice;
use App\Entity\VendorInvoice;
use App\Entity\XeroCreditNoteAllocation;
use App\Entity\XeroLineItem;
use App\Entity\XeroContact;
use App\Entity\XeroCreditNote;
use App\Entity\XeroWebhook;
use App\Entity\XeroUser;
use App\Entity\XeroOrganisation;
use App\Entity\Project;
use App\Service\CurrencyService;
use App\Service\MailgunService;
use App\Service\ProjectService;
use Doctrine\ORM\EntityManagerInterface;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Contracts\Translation\TranslatorInterface;
use Exception;
use XeroAPI\XeroPHP\Configuration;
use XeroAPI\XeroPHP\AccountingObjectSerializer;
use XeroAPI\XeroPHP\Models\Accounting\Contact;
use XeroAPI\XeroPHP\Models\Accounting\Contacts;
class XeroService
{
private $em;
private $params;
private $logger;
private $currencyService;
private $mailgunService;
private $projectService;
private $urlGenerator;
private $router;
private $arr_item_codes;
private $arr_excluded_item_codes;
private $mattermostService;
private $translator;
private $projectIdPattern;
public function __construct(
EntityManagerInterface $entityManager,
ParameterBagInterface $params,
LoggerInterface $logger,
CurrencyService $currencyService,
MailgunService $mailgunService,
ProjectService $projectService,
UrlGeneratorInterface $urlGenerator,
RouterInterface $router,
MattermostService $mattermostService,
TranslatorInterface $translator
) {
$this->em = $entityManager;
$this->params = $params;
$this->logger = $logger;
$this->currencyService = $currencyService;
$this->mailgunService = $mailgunService;
$this->projectService = $projectService;
$this->urlGenerator = $urlGenerator;
$this->router = $router;
$this->translator = $translator;
$this->arr_item_codes = [
"account agency fees" => 14, // 14 = Account Management Dept.
"account agency fee" => 14, // Renamed to Account Agency fees
"strategy" => 14,
"content" => 2, // 2 = Creative Dept.
"content production" => 2,
"creative production" => 2,
"edm" => 2,
"photo" => 2,
"photography" => 2, // to prevent typo
"video" => 2, // Renamed
"videography" => 2,
"d&a services" => 9, // 9 = Data Dept.
"media agency fees" => 3, // 3 = Media Dept.
"media buy" => 3,
"tracking" => 3,
"social media services" => 3,
"3rd party" => null,
"incentive management-pt" => null,
"incentive mgmt-agency fee(mc)" => 14,
"incentive mgmt-platformfee(mc)" => 14,
"incentive mgmt-platform fee(mc)" => 14,
"platform fee (mc)" => 14,
"inter company billing" => null, // need to be checked
"research" => 19, // 19 = Research Dept.
"market research" => 19,
"social" => null,
"training" => null,
"seo" => 8, // 8 = SEO Dept.
"hosting" => 1, // 1 = Tech Dept.
"maintenance" => 1,
"mobile app" => 1,
"mobile dev" => 1, // Renamed to Mobile App
"mobile development" => 1, // Renamed to Mobile App
"web development" => 1,
"discount" => null,
"uxui audit & analysis" => 20,
"uxui workshop" => 20,
"uxui strategy" => 20,
"uxui design" => 20,
"uxui training" => 20,
"uxui research" => 20,
"uxui fullstack programme" => 20,
"uxui project management" => 20,
// "Branding" => 14, // Old Code
// "Community Management" => 14, // Old Code
// "Computer" => null, // Old Code
// "Taxi" => null, // Old Code
// "Travel" => null, // Old Code
];
$this->arr_excluded_item_codes = ["3rd party", "discount", "media buy", "tracking", "incentive management-pt"];
$this->mattermostService = $mattermostService;
$this->projectIdPattern = '/Project\s*Id\s*:?\s*(\d{4}-\d{4})/i';
}
public function getSession()
{
return $this->em->getRepository(XeroAuth::class)->findAll()[0];
}
public function setToken($token, $expires = null, $tenantId, $refreshToken, $idToken)
{
$xeroAuth = $this->getSession();
if (!$xeroAuth) {
$xeroAuth = new XeroAuth();
}
$xeroAuth->setToken($token);
$xeroAuth->setTenantId($tenantId);
$xeroAuth->setExpires($expires);
$xeroAuth->setRefreshToken($refreshToken);
$xeroAuth->setIdToken($idToken);
$this->em->persist($xeroAuth);
$this->em->flush();
$this->em->refresh($xeroAuth);
}
public function getToken()
{
if (is_null($this->getSession())) {
return null;
}
if ($this->getSession()->getExpires() <= time()) {
return null;
}
return $this->getSession();
}
public function getAccessToken()
{
return $this->getSession()->getAccessToken();
}
public function getRefreshToken()
{
return $this->getSession()->getRefreshToken();
}
public function getExpires()
{
return $this->getSession()->getExpires();
}
public function getTenantId($countryId)
{
$tenantId = null;
switch ($countryId) {
case 'ID':
$tenantId = $this->params->get('xeroTenantIdBali');
break;
case 'HK':
$tenantId = $this->params->get('xeroTenantIdHK');
break;
default:
$tenantId = $this->params->get('xeroTenantId');
break;
}
return $tenantId;
}
public function fetchOrganisation($id)
{
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$tenantId = (strlen($id) <= 2) ? $this->getTenantId($id) : $id;
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$accountingApi = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
try {
$xeroCurrencies = $accountingApi->getCurrencies($tenantId);
$xeroCurrencies = $xeroCurrencies->getCurrencies();
$organizations = $accountingApi->getOrganisations($tenantId);
$organization = $organizations->getOrganisations()[0];
$xeroOrganisation = $this->em->getRepository(XeroOrganisation::class)->findOneBy(['tenantId' => $tenantId]);
if (!$xeroOrganisation) {
$xeroOrganisation = new XeroOrganisation();
$xeroOrganisation->setTenantId($tenantId);
$xeroOrganisation->setName($organization->getName());
$xeroOrganisation->setCountryCode($organization->getCountryCode());
}
$xeroOrganisation->setBaseCurrency($organization->getBaseCurrency());
$baseCurrency = $organization->getBaseCurrency();
$currencies = array_map(function ($currency) use ($baseCurrency) {
return [
'code' => $currency->getCode(),
'description' => $currency->getDescription(),
'base' => $currency->getCode() == $baseCurrency,
];
}, $xeroCurrencies);
$xeroOrganisation->setCurrencies($currencies);
$xeroTrackingCategories = $this->fetchTrackingCategories($tenantId);
$xeroTrackingCategories = $xeroTrackingCategories->getTrackingCategories();
$totalTrackingOptions = 0;
$trackingCategories = array_map(function ($category) use (&$totalTrackingOptions) {
return [
// 'id' => $category->getTrackingCategoryID(),
'name' => $category->getName(),
'options' => array_map(function ($option) use (&$totalTrackingOptions) {
if ($option->getStatus() != 'ACTIVE') return null;
$totalTrackingOptions++;
return $option->getName();
// 'id' => $option->getTrackingOptionID(),
}, $category->getOptions()),
];
}, $xeroTrackingCategories);
$xeroOrganisation->setTrackingCategories($trackingCategories);
if ($xeroOrganisation->getId() == null) {
$this->em->persist($xeroOrganisation);
};
$this->em->flush();
$response['status'] = 'OK';
$response['organisation'] = $organization->getName();
$response['currencies'] = count($currencies);
$response['trackingCategories'] = $totalTrackingOptions;
} catch (\Exception $e) {
$response['status'] = 'ERROR';
$response['message'] = 'Exception when calling AccountingApi->getCurrencies: ' . $e->getMessage() . PHP_EOL;
}
return $response;
}
public function fetchAccountInfo($id)
{
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$apiInstance = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$tenantId = (strlen($id) <= 2) ? $this->getTenantId($id) : $id;
try {
$result = $apiInstance->getAccounts($tenantId);
return $result;
} catch (Exception $e) {
return 'Exception when calling AccountingApi->getAccount: ' . $e->getMessage() . PHP_EOL;
return null;
}
}
public function fetchTrackingCategories($id)
{
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$apiInstance = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$tenantId = (strlen($id) <= 2) ? $this->getTenantId($id) : $id;
try {
$result = $apiInstance->getTrackingCategories($tenantId);
return $result;
} catch (Exception $e) {
return 'Exception when calling AccountingApi->getTrackingCategories: ' . $e->getMessage() . PHP_EOL;
}
}
public function getXeroTenantId()
{
return $this->getSession()->getTenantId();
}
public function getIdToken()
{
return $this->getSession()->getIdToken();
}
public function getHasExpired()
{
if ($this->getSession()) {
if (time() > $this->getExpires()) {
return true;
} else {
return false;
}
} else {
return true;
}
}
public function renewToken($params)
{
$provider = new \League\OAuth2\Client\Provider\GenericProvider([
'clientId' => $params->get('xeroClientId'),
'clientSecret' => $params->get('xeroClientSecret'),
'redirectUri' => $params->get('xeroRedirectURI'),
'urlAuthorize' => 'https://login.xero.com/identity/connect/authorize',
'urlAccessToken' => 'https://identity.xero.com/connect/token',
'urlResourceOwnerDetails' => 'https://identity.xero.com/resources'
]);
try {
$newAccessToken = $provider->getAccessToken('refresh_token', [
'refresh_token' => $this->getRefreshToken()
]);
// Save my token, expiration and refresh token
$this->setToken(
$newAccessToken->getToken(),
$newAccessToken->getExpires(),
$params->get('xeroTenantId'),
$newAccessToken->getRefreshToken(),
$newAccessToken->getValues()["id_token"]
);
return true;
} catch (\Exception $e) {
return false;
}
}
public function stayAlive()
{
return $this->fetchInvoices(null, "30a11b22-9605-4343-a9e9-6bf7e8e2e4fe");
}
/**
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function fetchInvoices($invoice_numbers = null, $invoice_ids = null, $tenantId = "771ca798-d2cf-4735-ab1b-1449300eda82", $modified_since = null, $additionalStatus = null, $returnInvoice = false)
{
set_time_limit(90);
// We will automatically mark Vendor Invoices from these Xero Contacts as included
$arr_include_contacts = [
"33af4d42-dba2-40a8-8af4-67b835d59569", //IPP World
"3887d1b2-09fc-4958-aee9-1510ad19bb47", //Clicks2Customers Pte Limited
"1cac3e9a-e360-4afb-a616-0a1085c63fd9", //Google Asia Pacific Pte. Ltd
"9e7c2d06-8cf6-4744-a797-61b51e51adf1", //Facebook Ireland Limited
"fbbbf68a-8931-40d2-a9d0-a6ef55ed296a", //LinkedIn Singapore Pte Ltd
"3887d1b2-09fc-4958-aee9-1510ad19bb47", //Clicks2Customers Pte Limited
"d6bf0a80-3d77-4286-a37d-19c9c4d55cd3", //Pinterest Europe Limited
"11f73751-1ad0-4a47-bc5d-71d316f3cf22", //TikTok Pte. Ltd.
"a9aa4905-dcdd-43a2-9f36-cc10a6db3e79", //Shutterstock Inc
"ed6380e1-44f3-4d91-9004-ee81004b49dc", //Adobe Systems Software Ireland Ltd
"c66e2e0d-69bb-4019-99c0-9d98c40b78e2", //Eyeota Pte Ltd
"a2ed1311-e992-477b-88d2-87f506a17f4d", //DoubleVerify Inc.
];
// Vendor Invoices from these Xero Contacts will be excluded from the Blotter
$arr_exclude_contacts = [
"f47abb0b-c791-47f6-b8a4-129f63905691", //DB Ventures Pte Ltd
"9c3db7c9-c52b-4e0c-9073-559348fdfa22", //Mediatropy (HK) Limited
"1b2a8c72-adce-4c38-899f-ed4747ed2cd9", //PT. Mediatropy Bali Digital
"d976bb37-2931-4ebd-8e07-103d71885733", //CHARM PATH LIMITED
"e843acd3-8a8d-4e28-b9ca-287fd989e2f8", //Payroll
"0b91dec9-4104-4b71-9145-2d18ecbb9bab", //Payroll Freelancers SG
"eb82dddf-162d-48ce-b8ee-f6a679c196f0", //Payroll Bali
"94a2eea6-d9b9-4598-a9fb-ba83007ecd6e", //Payroll HK
"fd24e4f6-bb23-41fd-afcc-956441863768", //the Hive Coworking Space Singapore Pte Ltd
"d5ed7eb3-4abd-4496-996f-8194a61520ea", //Borderless Hub Pte Ltd
];
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$entityManager = $this->em;
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$accountingApi = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
// $if_modified_since = new \DateTime("2023-01-01T00:00:00.000");
$if_modified_since = $modified_since ? new \DateTime($modified_since) : new \DateTime("-30 days");
if ($invoice_numbers || $invoice_ids) {
$if_modified_since = new \DateTime("2020-01-01T00:00:00.000");
}
$where = null;
$order = null; // string
$contact_ids = null; // string[] | Filter by a comma-separated list of ContactIDs.
$statuses = array("PAID", "AUTHORISED", "SUBMITTED", "DRAFT");
if ($additionalStatus)
$statuses = array_unique(array_merge($statuses, $additionalStatus)); // add additional status if needed
$page = 1; // int | e.g. page=1 – Up to 100 invoices will be returned in a single API call with line items
$include_archived = null; // bool | e.g. includeArchived=true - Contacts with a status of ARCHIVED will be included
$created_by_my_app = null; // bool | When set to true you'll only retrieve Invoices created by your app
$unitdp = null; // int | e.g. unitdp=4 – You can opt in to use four decimal places for unit amounts
$stopQuery = false;
$response['inv-add'] = 0;
$response['inv-update'] = 0;
$response['inv-not-found'] = 0;
$response['inv-lineitems'] = 0;
$response['inv-removed-lineitems'] = 0;
try {
$debug = "";
while (!$stopQuery) {
$apiResponse = $accountingApi->getInvoices($tenantId, $if_modified_since, $where, $order, $invoice_ids, $invoice_numbers, $contact_ids, $statuses, $page, $include_archived, $created_by_my_app, $unitdp);
if (sizeof($apiResponse) < 100) {
$stopQuery = true;
}
if (sizeof($apiResponse) == 0) {
$response['inv-not-found']++;
if($invoice_numbers) $stopQuery = true;
continue;
};
$vendorInvoiceRepository = $this->em->getRepository(VendorInvoice::class);
$invoiceRepository = $this->em->getRepository(Invoice::class);
$xeroLineItemRepository = $this->em->getRepository(XeroLineItem::class);
$departmentRepository = $this->em->getRepository(Department::class);
$currencyRepository = $this->em->getRepository(Currency::class);
$type = "";
foreach ($apiResponse as $invoice) {
$objInv = false;
if ($invoice['type'] === "ACCPAY") {
$type = 'vi';
$objInv = $vendorInvoiceRepository->findOneBy(['invoiceId' => $invoice['invoice_id']]);
// INVOICE
} elseif ($invoice['type'] === "ACCREC") {
$type = 'i';
$objInv = $invoiceRepository->findOneBy(['invoiceId' => $invoice['invoice_id']]);
} else {
continue;
}
if ($invoice['invoice_number'] == null) {
continue;
}
if (!$objInv) {
// We create a new invoice
if ($type === "i") {
$objInv = new Invoice();
} else {
$objInv = new VendorInvoice();
$objInv->setBlotter("IGNORED");
if (in_array($invoice['contact']['contact_id'], $arr_include_contacts)) {
$objInv->setBlotter("IMPORTED");
}
if (in_array($invoice['contact']['contact_id'], $arr_exclude_contacts)) {
$objInv->setBlotter("HIDDEN");
}
}
$objInv->setCreatedAt(new \DateTime());
$objInv->setDescription('');
$response['inv-add']++;
} else {
$objInv->setUpdatedAt(new \DateTime());
$response['inv-update']++;
}
if (isset($invoice['contact'])) {
$objInv->setXeroContactId($invoice['contact']['contact_id']);
}
$objInv->setInvoiceNo($invoice['invoice_number']);
$objInv->setTenantId($tenantId);
$currency_code = $invoice['currency_code'];
$currency = $currencyRepository->findOneBy(['iso' => $currency_code]);
if (!$currency) {
$currency = new Currency();
$currency->setIso($currency_code);
$currency->setName($currency_code);
$currency->setCreatedAt(new \DateTime());
$currency->setSymbol('-');
$entityManager->persist($currency);
$entityManager->flush();
}
$objInv->setCurrency($currency);
// $objInv->setCurrencyRate($invoice['currency_rate']);
if (!($objInv instanceof VendorInvoice)) {
$objInv->setCurrencyRate($invoice['currency_rate']);
}
$text_date = $invoice['date'];
preg_match('#^/Date\((\d{10})#', $text_date, $matches);
if (array_key_exists(1, $matches)) {
$dt = new \DateTime('@' . $matches[1]);
} else {
$dt = new \DateTime();
}
$objInv->setInvoiceDate($dt);
$objInv->setTotal($invoice['total']);
$current_date = new \DateTime();
if ($dt > $current_date) {
$objInv->setTotalUsd($this->currencyService->convertAtDate(date_format($current_date, "Y-m-d"), $currency_code, "USD", $invoice['total']));
$objInv->setSubTotalUsd($this->currencyService->convertAtDate(date_format($current_date, "Y-m-d"), $currency_code, "USD", $invoice['sub_total']));
} else {
$objInv->setTotalUsd($this->currencyService->convertAtDate(date_format($dt, "Y-m-d"), $currency_code, "USD", $invoice['total']));
$objInv->setSubTotalUsd($this->currencyService->convertAtDate(date_format($dt, "Y-m-d"), $currency_code, "USD", $invoice['sub_total']));
}
$objInv->setAmountDue($invoice['amount_due']);
$objInv->setAmountPaid($invoice['amount_paid']);
$objInv->setSubtotal($invoice['sub_total']);
$objInv->setReference($invoice['reference']);
$objInv->setXeroId($invoice['invoice_id']);
$objInv->setInvoiceId($invoice['invoice_id']);
$objInv->setXeroStatus($invoice['status']);
$text_date2 = $invoice['due_date'];
preg_match('#^/Date\((\d{10})#', $text_date2, $matches2);
if (array_key_exists(1, $matches2)) {
$dt2 = new \DateTime('@' . $matches2[1]);
$objInv->setDueDate($dt2);
}
$entityManager->persist($objInv);
$entityManager->flush();
$entityManager->refresh($objInv);
if (method_exists($objInv, 'getCreator') && $objInv->getCreator() == null) { // INV Creator
$creator = $this->fetchInvoiceHistory($objInv->getXeroId());
$objInv->setCreator($creator);
$entityManager->flush();
$entityManager->refresh($objInv);
}
if ($type == 'i' && $invoice['line_items']) { // INV Line Items
$availableLineItems = [];
foreach ($invoice['line_items'] as $lineItem) {
//if($lineItem['item_code'] == null || !array_key_exists($lineItem['item_code'], $this->arr_item_codes)) continue;
// if(!array_key_exists($lineItem['item_code'], $arr_item_codes)) continue;
if ($lineItem['item_code'] != null && !array_key_exists(strtolower($lineItem['item_code']), $this->arr_item_codes))
continue;
if ($lineItem['item_code'] == null && !preg_match($this->projectIdPattern, $lineItem['description']) && ($lineItem['line_amount'] == null || $lineItem['line_amount'] == 0))
continue;
$lineItemId = $lineItem['line_item_id'];
$lineItemTotal = $lineItem['line_amount'] + $lineItem['tax_amount'];
$lineItemSubTotal = $lineItem['line_amount'];
$availableLineItems[] = $lineItemId;
$objLineItem = $xeroLineItemRepository->findOneBy(['xeroId' => $lineItemId]) ?? new XeroLineItem();
if ($objLineItem->getId() == null) {
$objLineItem->setCreatedAt(new \DateTimeImmutable());
} else {
$objLineItem->setUpdatedAt(new \DateTimeImmutable());
}
if ($lineItem['item_code'] == null) {
$lineItemDepartment = null; // default blank department
$itemCode = null; // default blank item code
$isIncluded = false;
} else {
$lineItemDepartment = $departmentRepository->findOneBy(['id' => $this->arr_item_codes[strtolower($lineItem['item_code'])]]) ?? null;
$isIncluded = !in_array(strtolower($lineItem['item_code']), $this->arr_excluded_item_codes);
$itemCode = $lineItem['item_code'];
};
$objLineItem->setParentNo($objInv->getInvoiceNo());
if (method_exists($objInv, 'getCreator'))
$objLineItem->setInvoice($objInv);
$objLineItem->setXeroId($lineItemId);
$objLineItem->setTenantId($tenantId);
$objLineItem->setIsIncluded($isIncluded);
$objLineItem->setDepartment($lineItemDepartment);
$objLineItem->setDescription($lineItem['description']);
$objLineItem->setQuantity($lineItem['quantity']);
$objLineItem->setUnitAmount($lineItem['unit_amount']);
$objLineItem->setItemCode($itemCode);
$objLineItem->setAccountCode($lineItem['account_code']);
$objLineItem->setAccountId($lineItem['account_id']);
$objLineItem->setTotal($lineItemTotal);
$objLineItem->setSubtotal($lineItemSubTotal);
if ($dt > $current_date) {
$objLineItem->setTotalUsd($this->currencyService->convertAtDate(date_format($current_date, "Y-m-d"), $currency_code, "USD", $lineItemTotal));
$objLineItem->setSubTotalUsd($this->currencyService->convertAtDate(date_format($current_date, "Y-m-d"), $currency_code, "USD", $lineItemSubTotal));
} else {
$objLineItem->setTotalUsd($this->currencyService->convertAtDate(date_format($dt, "Y-m-d"), $currency_code, "USD", $lineItemTotal));
$objLineItem->setSubTotalUsd($this->currencyService->convertAtDate(date_format($dt, "Y-m-d"), $currency_code, "USD", $lineItemSubTotal));
}
$entityManager->persist($objLineItem);
$entityManager->flush();
$response['inv-lineitems']++;
}
$entityManager->refresh($objInv);
foreach ($objInv->getXeroLineItems() as $lineItem) {
if (in_array($lineItem->getXeroId(), $availableLineItems))
continue;
$entityManager->remove($lineItem);
$entityManager->flush();
$response['inv-removed-lineitems']++;
}
}
// $this->projectService->projectXeroAllocationEmail($objInv);
if ($returnInvoice && ($invoice_ids || $invoice_numbers)) {
return $objInv;
}
}
//
$page++;
//
}
} catch (\Exception $exception) {
// dd($exception);
$response['status'] = 'ERROR';
$response['message'] = 'API exception: ' . $exception->getMessage();
return $response;
}
$response['status'] = 'OK';
return $response;
}
public function fetchSalesOrderHistory($soId)
{
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$accountingApi = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$apiResponse = $accountingApi->getQuoteHistory($this->params->get('xeroTenantId'), $soId);
if (sizeof($apiResponse) > 0) {
return $apiResponse[sizeof($apiResponse) - 1]['user'];
} else {
return "";
}
}
public function fetchInvoiceHistory($invId)
{
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$accountingApi = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$apiResponse = $accountingApi->getInvoiceHistory($this->params->get('xeroTenantId'), $invId);
if (sizeof($apiResponse) > 0) {
return $apiResponse[sizeof($apiResponse) - 1]['user'];
} else {
return "";
}
}
public function fetchSoCreators()
{
$salesOrderRepository = $this->em->getRepository(SalesOrder::class);
$allSo = $salesOrderRepository->findBy(array('creator' => NULL));
$i = 0;
foreach ($allSo as $so) {
$creator = $this->fetchSalesOrderHistory($so->getXeroId());
$so->setCreator($creator);
$this->em->persist($so);
$i++;
if ($i > 50) {
break;
}
}
$this->em->flush();
return $i;
}
public function fetchInvCreators()
{
$salesOrderRepository = $this->em->getRepository(Invoice::class);
$allInv = $salesOrderRepository->findBy(array('creator' => NULL));
$i = 0;
foreach ($allInv as $inv) {
$creator = $this->fetchInvoiceHistory($inv->getXeroId());
$inv->setCreator($creator);
$this->em->persist($inv);
$i++;
if ($i > 50) {
break;
}
}
$this->em->flush();
return $i;
}
public function fetchCreditNotes($cnNumbers = null, $tenantId = "771ca798-d2cf-4735-ab1b-1449300eda82", $date = null)
{
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$entityManager = $this->em;
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$accountingApi = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$order = null; // string
$contact_ids = null; // string[] | Filter by a comma-separated list of ContactIDs.
$statuses = null; //array("SUBMITTED", "PAID", "AUTHORISED", "VOIDED", "DRAFT", "DELETED");
$page = 1; // int | e.g. page=1 – Up to 100 invoices will be returned in a single API call with line items
$dateFrom = $date == null ? new \DateTime("2022-01-01") : new \DateTime($date);
$allApiResponses = [];
$stopQuery = false;
$where = null;
$unitdp = 4;
$response['total_add'] = 0;
$response['total_update'] = 0;
$response['total_lineitem_add'] = 0;
$response['total_lineitem_update'] = 0;
$response['total_allocation_add'] = 0;
$response['total_allocation_remove'] = 0;
$xeroCreditNoteRepository = $entityManager->getRepository(XeroCreditNote::class);
$xeroLineItemRepository = $entityManager->getRepository(XeroLineItem::class);
$xeroCreditNoteAllocationRepositorty = $entityManager->getRepository(XeroCreditNoteAllocation::class);
$invoiceRepository = $entityManager->getRepository(Invoice::class);
$departmentRepository = $entityManager->getRepository(Department::class);
$currencyRepository = $entityManager->getRepository(Currency::class);
try {
//todo this is needed but maybe need to rate limit somehow
while (!$stopQuery) {
$apiResponse = $accountingApi->getCreditNotes($this->params->get('xeroTenantId'), $dateFrom, $where, $order, $page, $unitdp);
if (sizeof($apiResponse) < 100) {
$stopQuery = true;
}
$page++;
foreach ($apiResponse as $cn) {
if ($cn['status'] == 'DELETED')
continue;
$objCn = $xeroCreditNoteRepository->findOneBy(['creditNoteId' => $cn['credit_note_id']]);
if (!$objCn) {
$objCn = new XeroCreditNote();
$objCn->setCreatedAt(new \DateTimeImmutable());
$response['total_add']++;
} else {
$objCn->setUpdatedAt(new \DateTimeImmutable());
$response['total_update']++;
}
$objCn->setCreditNoteId($cn['credit_note_id']);
$objCn->setCreditNoteNo($cn['credit_note_number']);
$objCn->setXeroStatus($cn['status']);
$objCn->setReference($cn['reference']);
$objCn->setXeroContactId($cn['contact']['contact_id']);
$currency_code = $cn['currency_code'];
$currency = $currencyRepository->findOneBy(['iso' => $currency_code]);
if (!$currency) {
$currency = new Currency();
$currency->setIso($currency_code);
$currency->setName($currency_code);
$currency->setCreatedAt(new \DateTime());
$currency->setSymbol('-');
$entityManager->persist($currency);
$entityManager->flush();
}
$objCn->setCurrency($currency);
$text_date = $cn['date'];
preg_match('#^/Date\((\d{10})#', $text_date, $matches);
if (array_key_exists(1, $matches)) {
$dt = new \DateTimeImmutable('@' . $matches[1]);
} else {
$dt = new \DateTimeImmutable();
}
$objCn->setCreditNoteDate($dt);
$objCn->setTotal($cn['total']);
$objCn->setSubTotal($cn['sub_total']);
$objCn->setRemainingCredit($cn['remaining_credit']);
$current_date = new \DateTime();
if ($dt > $current_date) {
$objCn->setTotalUsd($this->currencyService->convertAtDate(date_format($current_date, "Y-m-d"), $currency_code, "USD", $cn['total']));
$objCn->setSubTotalUsd($this->currencyService->convertAtDate(date_format($current_date, "Y-m-d"), $currency_code, "USD", $cn['sub_total']));
$objCn->setRemainingCreditUsd($this->currencyService->convertAtDate(date_format($current_date, "Y-m-d"), $currency_code, "USD", $cn['remaining_credit']));
} else {
$objCn->setTotalUsd($this->currencyService->convertAtDate(date_format($dt, "Y-m-d"), $currency_code, "USD", $cn['total']));
$objCn->setSubTotalUsd($this->currencyService->convertAtDate(date_format($dt, "Y-m-d"), $currency_code, "USD", $cn['sub_total']));
$objCn->setRemainingCreditUsd($this->currencyService->convertAtDate(date_format($dt, "Y-m-d"), $currency_code, "USD", $cn['remaining_credit']));
}
if ($cn['line_items']) {
$availableLineItems = [];
foreach ($cn['line_items'] as $lineItem) {
if ($lineItem['item_code'] != null && !array_key_exists(strtolower($lineItem['item_code']), $this->arr_item_codes))
continue;
if ($lineItem['item_code'] == null && ($lineItem['line_amount'] == null || $lineItem['line_amount'] == 0))
continue;
$lineItemId = $lineItem['line_item_id'];
$lineItemTotal = $lineItem['line_amount'] + $lineItem['tax_amount'];
$lineItemSubTotal = $lineItem['line_amount'];
$availableLineItems[] = $lineItemId;
$objLineItem = $xeroLineItemRepository->findOneBy(['xeroId' => $lineItemId]) ?? new XeroLineItem();
if ($objLineItem->getId() == null) {
$objLineItem->setCreatedAt(new \DateTimeImmutable());
} else {
$objLineItem->setUpdatedAt(new \DateTimeImmutable());
}
// if($lineItem['item_code'] != null && array_key_exists(strtolower($lineItem['item_code'], $this->arr_item_codes)){
if ($lineItem['item_code'] == null) {
// $lineItemDepartment = $departmentRepository->findOneBy(['id' => $this->arr_item_codes[strtolower("Discount")]]) ?? null; // default blank department
// $itemCode = "Discount"; // default blank item code
// $isIncluded = true;
$lineItemDepartment = null; // default blank department
$itemCode = null; // default blank item code
$isIncluded = false;
} else {
$lineItemDepartment = $departmentRepository->findOneBy(['id' => $this->arr_item_codes[strtolower($lineItem['item_code'])]]) ?? null;
$isIncluded = !in_array(strtolower($lineItem['item_code']), $this->arr_excluded_item_codes);
$itemCode = $lineItem['item_code'];
};
$objLineItem->setParentNo($objCn->getCreditNoteNo());
$objLineItem->setCreditNote($objCn);
$objLineItem->setXeroId($lineItemId);
$objLineItem->setTenantId($tenantId);
$objLineItem->setIsIncluded($isIncluded);
$objLineItem->setDepartment($lineItemDepartment);
$objLineItem->setDescription($lineItem['description']);
$objLineItem->setQuantity($lineItem['quantity']);
$objLineItem->setUnitAmount($lineItem['unit_amount']);
$objLineItem->setItemCode($itemCode);
$objLineItem->setAccountCode($lineItem['account_code']);
$objLineItem->setAccountId($lineItem['account_id']);
$objLineItem->setTotal($lineItemTotal);
$objLineItem->setSubtotal($lineItemSubTotal);
$objLineItem->setTotalUsd($this->currencyService->convertAtDate(date_format($objCn->getCreditNoteDate(), "Y-m-d"), $objCn->getCurrency()->getIso(), "USD", $lineItemTotal));
$objLineItem->setSubTotalUsd($this->currencyService->convertAtDate(date_format($objCn->getCreditNoteDate(), "Y-m-d"), $objCn->getCurrency()->getIso(), "USD", $lineItemSubTotal));
$entityManager->persist($objLineItem);
$entityManager->flush();
$response['total_lineitem_add']++;
}
$entityManager->refresh($objLineItem);
foreach ($objCn->getXeroLineItems() as $lineItem) {
if (in_array($lineItem->getXeroId(), $availableLineItems))
continue;
$entityManager->remove($lineItem);
$entityManager->flush();
$response['inv-removed-lineitems']++;
}
}
foreach ($cn['allocations'] as $allocation) {
$objInv = $invoiceRepository->findOneBy(['invoiceId' => $allocation['invoice']['invoice_id']]);
if ($objInv == null)
continue;
$objAllocation = $xeroCreditNoteAllocationRepositorty->findOneBy(['xeroCreditNote' => $objCn, 'invoice' => $objInv]) ?? new XeroCreditNoteAllocation();
$objAllocation->setXeroCreditNote($objCn);
$objAllocation->setInvoice($objInv);
$ratio = $allocation['amount'] / $cn['total'];
$amountAllocated = $objCn->getSubTotal() * $ratio;
$amountAllocatedUsd = $objCn->getSubTotalUsd() * $ratio;
$objAllocation->setAmount($amountAllocated);
$objAllocation->setAmountUsd($amountAllocatedUsd);
$response['total_allocation_add']++;
$entityManager->persist($objAllocation);
$entityManager->flush();
}
$entityManager->persist($objCn);
$entityManager->flush();
$entityManager->refresh($objCn);
if ($objCn->getXeroCreditNoteAllocation()) {
foreach ($objCn->getXeroCreditNoteAllocation() as $objAllocation) {
$found = false;
foreach ($cn['allocations'] as $allocation) {
if ($allocation['invoice']['invoice_id'] == $allocation->getInvoiceId()) {
$found = true;
break;
}
}
if (!$found) {
$entityManager->remove($objAllocation);
$response['total_allocation_remove']++;
}
}
}
}
}
} catch (\Exception $exception) {
$response['status'] = 'ERROR';
$response['message'] = 'API exception: ' . $exception->getMessage();
return $response;
}
$response['status'] = 'OK';
return $response;
}
public function populateLineItem($type = null, $limit = 99)
{
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$accountingApi = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$tenantId = "771ca798-d2cf-4735-ab1b-1449300eda82";
$if_modified_since = null;
$where = null;
$order = null; // string
$contact_ids = null; // string[] | Filter by a comma-separated list of ContactIDs.
$page = 1; // int | e.g. page=1 – Up to 100 invoices will be returned in a single API call with line items
$include_archived = null; // bool | e.g. includeArchived=true - Contacts with a status of ARCHIVED will be included
$created_by_my_app = null; // bool | When set to true you'll only retrieve Invoices created by your app
$unitdp = null; // int | e.g. unitdp=4 – You can opt in to use four decimal places for unit amounts
$entityManager = $this->em;
$xeroLineItemRepository = $entityManager->getRepository(XeroLineItem::class);
$departmentRepository = $entityManager->getRepository(Department::class);
if ($type == 'inv' || $type == null) {
$invoice_numbers = null;
$invoice_ids = null;
$statuses = array("PAID", "AUTHORISED", "SUBMITTED", "DRAFT");
$invoiceRepository = $entityManager->getRepository(Invoice::class);
$allObjInv = $invoiceRepository->findAllByEmptyLineItems($limit);
foreach ($allObjInv as $objInv) {
if ($objInv->getXeroId() == null || $objInv->getXeroId() == 'china')
continue;
$invoice_ids = $objInv->getXeroId();
$tenantId = $objInv->getTenantId();
try {
$apiResponse = $accountingApi->getInvoices($tenantId, $if_modified_since, $where, $order, $invoice_ids, $invoice_numbers, $contact_ids, $statuses, $page, $include_archived, $created_by_my_app, $unitdp);
foreach ($apiResponse as $invoice) {
if ($invoice['line_items']) {
foreach ($invoice['line_items'] as $lineItem) {
if ($lineItem['item_code'] != null && !array_key_exists(strtolower($lineItem['item_code']), $this->arr_item_codes))
continue;
if ($lineItem['item_code'] == null && ($lineItem['line_amount'] == null || $lineItem['line_amount'] == 0))
continue;
$lineItemId = $lineItem['line_item_id'];
$lineItemTotal = $lineItem['line_amount'] + $lineItem['tax_amount'];
$lineItemSubTotal = $lineItem['line_amount'];
$objLineItem = $xeroLineItemRepository->findOneBy(['xeroId' => $lineItemId]) ?? new XeroLineItem();
if ($objLineItem->getId() == null) {
$objLineItem->setCreatedAt(new \DateTimeImmutable());
} else {
$objLineItem->setUpdatedAt(new \DateTimeImmutable());
}
// if($lineItem['item_code'] != null && array_key_exists(strtolower($lineItem['item_code'], $this->arr_item_codes)){
if ($lineItem['item_code'] == null) {
// $lineItemDepartment = $departmentRepository->findOneBy(['id' => $this->arr_item_codes[strtolower("Discount")]]) ?? null; // default blank department
// $itemCode = "Discount"; // default blank item code
// $isIncluded = true;
$lineItemDepartment = null; // default blank department
$itemCode = null; // default blank item code
$isIncluded = false;
} else {
$lineItemDepartment = $departmentRepository->findOneBy(['id' => $this->arr_item_codes[strtolower($lineItem['item_code'])]]) ?? null;
$isIncluded = !in_array(strtolower($lineItem['item_code']), $this->arr_excluded_item_codes);
$itemCode = $lineItem['item_code'];
};
$objLineItem->setParentNo($objInv->getInvoiceNo());
$objLineItem->setInvoice($objInv);
$objLineItem->setXeroId($lineItemId);
$objLineItem->setTenantId($tenantId);
$objLineItem->setIsIncluded($isIncluded);
$objLineItem->setDepartment($lineItemDepartment);
$objLineItem->setDescription($lineItem['description']);
$objLineItem->setQuantity($lineItem['quantity']);
$objLineItem->setUnitAmount($lineItem['unit_amount']);
$objLineItem->setItemCode($itemCode);
$objLineItem->setAccountCode($lineItem['account_code']);
$objLineItem->setAccountId($lineItem['account_id']);
$objLineItem->setTotal($lineItemTotal);
$objLineItem->setSubtotal($lineItemSubTotal);
$objLineItem->setTotalUsd($this->currencyService->convertAtDate(date_format($objInv->getInvoiceDate(), "Y-m-d"), $objInv->getCurrency()->getIso(), "USD", $lineItemTotal));
$objLineItem->setSubTotalUsd($this->currencyService->convertAtDate(date_format($objInv->getInvoiceDate(), "Y-m-d"), $objInv->getCurrency()->getIso(), "USD", $lineItemSubTotal));
$entityManager->persist($objLineItem);
$entityManager->flush();
}
$response['inv-success'][] = $objInv->getInvoiceNo();
}
}
// $page++;
} catch (\Exception $exception) {
// dd($exception);
$response['inv-status'] = 'ERROR';
$response['inv-message'] = 'API exception: ' . $exception->getMessage();
// return $result;
// $response['error'] = $invoice_ids.' ['.$exception->getMessage().']';
// return $response;
}
};
}
if ($type == 'so' || $type == null) {
$soNumbers = null;
$statuses = null;
$salesOrderRepository = $entityManager->getRepository(SalesOrder::class);
$allObjSo = $salesOrderRepository->findAllByEmptyLineItems($limit);
foreach ($allObjSo as $objSo) {
$soNumbers = $objSo->getSalesOrderNo();
$tenantId = $objSo->getTenantId();
try {
$apiResponse = $accountingApi->getQuotes($tenantId, null, null, null, null, null, $contact_ids, $statuses, $page, $order, $soNumbers);
foreach ($apiResponse as $so) {
if ($so['line_items']) {
foreach ($so['line_items'] as $lineItem) {
// if($lineItem['item_code'] == null || !array_key_exists($lineItem['item_code'], $this->arr_item_codes)) continue;
if ($lineItem['item_code'] != null && !array_key_exists(strtolower($lineItem['item_code']), $this->arr_item_codes))
continue;
if ($lineItem['item_code'] == null && ($lineItem['line_amount'] == null || $lineItem['line_amount'] == 0))
continue;
$lineItemId = $lineItem['line_item_id'];
$lineItemTotal = $lineItem['line_amount'] + $lineItem['tax_amount'];
$lineItemSubTotal = $lineItem['line_amount'];
$objLineItem = $xeroLineItemRepository->findOneBy(['xeroId' => $lineItemId]) ?? new XeroLineItem();
if ($objLineItem->getId() == null) {
$objLineItem->setCreatedAt(new \DateTimeImmutable());
} else {
$objLineItem->setUpdatedAt(new \DateTimeImmutable());
}
if ($lineItem['item_code'] == null) {
$lineItemDepartment = null; // default blank department
$itemCode = null; // default blank item code
$isIncluded = false;
} else {
$lineItemDepartment = $departmentRepository->findOneBy(['id' => $this->arr_item_codes[strtolower($lineItem['item_code'])]]) ?? null;
$isIncluded = !in_array(strtolower($lineItem['item_code']), $this->arr_excluded_item_codes);
$itemCode = $lineItem['item_code'];
};
$objLineItem->setParentNo($soNumbers);
$objLineItem->setSalesOrder($objSo);
$objLineItem->setXeroId($lineItemId);
$objLineItem->setTenantId($tenantId);
$objLineItem->setIsIncluded($isIncluded);
$objLineItem->setDepartment($lineItemDepartment);
$objLineItem->setDescription($lineItem['description']);
$objLineItem->setQuantity($lineItem['quantity']);
$objLineItem->setUnitAmount($lineItem['unit_amount']);
$objLineItem->setItemCode($itemCode);
$objLineItem->setAccountCode($lineItem['account_code']);
$objLineItem->setAccountId($lineItem['account_id']);
$objLineItem->setTotal($lineItemTotal);
$objLineItem->setSubtotal($lineItemSubTotal);
$objLineItem->setTotalUsd($this->currencyService->convertAtDate(date_format($objSo->getQuoteDate(), "Y-m-d"), $objSo->getCurrency()->getIso(), "USD", $lineItemTotal));
$objLineItem->setSubTotalUsd($this->currencyService->convertAtDate(date_format($objSo->getQuoteDate(), "Y-m-d"), $objSo->getCurrency()->getIso(), "USD", $lineItemSubTotal));
$entityManager->persist($objLineItem);
$entityManager->flush();
}
$response['so-success'][] = $soNumbers;
}
}
// $page++;
} catch (\Exception $exception) {
// dd($exception);
$response['so-status'] = 'ERROR';
$response['so-message'] = 'API exception: ' . $exception->getMessage();
// return $result;
// $response['error'] = $invoice_ids.' ['.$exception->getMessage().']';
// return $response;
}
};
}
$response['status'] = 'OK';
return $response;
}
public function populateCreditNote($datetime, $limit = 99)
{
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$accountingApi = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$entityManager = $this->em;
$invoiceRepository = $entityManager->getRepository(Invoice::class);
$xeroLineItemRepository = $entityManager->getRepository(XeroLineItem::class);
$departmentRepository = $entityManager->getRepository(Department::class);
$invoiceRepository = $entityManager->getRepository(Invoice::class);
$allObjInv = $invoiceRepository->findAllByEmptyCreditNotes($datetime, $limit);
$response = [];
$response['has-credit-notes'] = 0;
$response['no-credit-notes'] = 0;
foreach ($allObjInv as $objInv) {
if ($objInv->getXeroId() == null || $objInv->getXeroId() == 'china')
continue;
$invoice_ids = $objInv->getXeroId();
$tenantId = $objInv->getTenantId();
try {
$apiResponse = $accountingApi->getInvoices($tenantId, null, null, null, $invoice_ids, null, null, null, null, null, null, null);
foreach ($apiResponse as $invoice) {
if ($invoice['credit_notes']) {
$response['has-credit-notes']++;
continue;
foreach ($invoice['line_items'] as $lineItem) {
if ($lineItem['item_code'] != null && !array_key_exists(strtolower($lineItem['item_code']), $this->arr_item_codes))
continue;
if ($lineItem['item_code'] == null && ($lineItem['line_amount'] == null || $lineItem['line_amount'] == 0))
continue;
$lineItemId = $lineItem['line_item_id'];
$lineItemTotal = $lineItem['line_amount'] + $lineItem['tax_amount'];
$lineItemSubTotal = $lineItem['line_amount'];
$objLineItem = $xeroLineItemRepository->findOneBy(['xeroId' => $lineItemId]) ?? new XeroLineItem();
if ($objLineItem->getId() == null) {
$objLineItem->setCreatedAt(new \DateTimeImmutable());
} else {
$objLineItem->setUpdatedAt(new \DateTimeImmutable());
}
if ($lineItem['item_code'] == null) {
$lineItemDepartment = null; // default blank department
$itemCode = null; // default blank item code
$isIncluded = false;
} else {
$lineItemDepartment = $departmentRepository->findOneBy(['id' => $this->arr_item_codes[strtolower($lineItem['item_code'])]]) ?? null;
$isIncluded = !in_array(strtolower($lineItem['item_code']), $this->arr_excluded_item_codes);
$itemCode = $lineItem['item_code'];
};
$objLineItem->setParentNo($objInv->getInvoiceNo());
$objLineItem->setInvoice($objInv);
$objLineItem->setXeroId($lineItemId);
$objLineItem->setTenantId($tenantId);
$objLineItem->setIsIncluded($isIncluded);
$objLineItem->setDepartment($lineItemDepartment);
$objLineItem->setDescription($lineItem['description']);
$objLineItem->setQuantity($lineItem['quantity']);
$objLineItem->setUnitAmount($lineItem['unit_amount']);
$objLineItem->setItemCode($itemCode);
$objLineItem->setAccountCode($lineItem['account_code']);
$objLineItem->setAccountId($lineItem['account_id']);
$objLineItem->setTotal($lineItemTotal);
$objLineItem->setSubtotal($lineItemSubTotal);
$objLineItem->setTotalUsd($this->currencyService->convertAtDate(date_format($objInv->getInvoiceDate(), "Y-m-d"), $objInv->getCurrency()->getIso(), "USD", $lineItemTotal));
$objLineItem->setSubTotalUsd($this->currencyService->convertAtDate(date_format($objInv->getInvoiceDate(), "Y-m-d"), $objInv->getCurrency()->getIso(), "USD", $lineItemSubTotal));
$entityManager->persist($objLineItem);
$entityManager->flush();
}
$response['inv-success'][] = $objInv->getInvoiceNo();
} else {
$response['no-credit-notes']++;
$objInv->setUpdatedAt(new \DateTime());
$entityManager->flush();
}
}
} catch (\Exception $exception) {
$response['status'] = 'ERROR';
$response['message'] = 'API exception: ' . $exception->getMessage();
}
};
$response['status'] = 'OK';
return $response;
}
public function checkStatus($type = null, $datetime = null, $limit = 99)
{
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$accountingApi = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$entityManager = $this->em;
$response = [];
if ($type == 'inv' || $type == null) {
$invoice_ids = null;
$invoiceRepository = $entityManager->getRepository(Invoice::class);
$allObjInv = $invoiceRepository->findAllUpdatedBefore($datetime, $limit);
foreach ($allObjInv as $objInv) {
if ($objInv->getXeroId() == null || $objInv->getXeroId() == 'china')
continue;
$invoice_ids = $objInv->getXeroId();
$tenantId = $objInv->getTenantId();
try {
$apiResponse = $accountingApi->getInvoices($tenantId, null, null, null, $invoice_ids, null, null, null, null, null, null, null);
foreach ($apiResponse as $invoice) {
if ($invoice['status'] != $objInv->getXeroStatus()) {
$objInv->setXeroStatus($invoice['status']);
if (!isset($response['inv-updated'][$invoice['status']])) {
$response['inv-updated'][$invoice['status']] = 1;
} else {
$response['inv-updated'][$invoice['status']]++;
}
} else {
if (!isset($response['inv-current'][$invoice['status']])) {
$response['inv-current'][$invoice['status']] = 1;
} else {
$response['inv-current'][$invoice['status']]++;
}
}
$objInv->setUpdatedAt(new \DateTime());
$entityManager->flush();
}
} catch (\Exception $exception) {
$response['inv-status'] = 'ERROR';
$response['inv-message'] = 'API exception: ' . $exception->getMessage();
}
}
$response['inv-status'] = 'OK';
}
if ($type == 'vinv' || $type == null) {
$invoice_ids = null;
$vendorInvoiceRepository = $entityManager->getRepository(VendorInvoice::class);
$allObjVinv = $vendorInvoiceRepository->findAllUpdatedBefore($datetime, $limit);
foreach ($allObjVinv as $objInv) {
if ($objInv->getXeroId() == null || $objInv->getXeroId() == 'china')
continue;
$invoice_ids = $objInv->getXeroId();
$tenantId = $objInv->getTenantId();
try {
$apiResponse = $accountingApi->getInvoices($tenantId, null, null, null, $invoice_ids, null, null, null, null, null, null, null);
foreach ($apiResponse as $invoice) {
if ($invoice['status'] != $objInv->getXeroStatus()) {
$objInv->setXeroStatus($invoice['status']);
if (!isset($response['vinv-updated'][$invoice['status']])) {
$response['vinv-updated'][$invoice['status']] = 1;
} else {
$response['vinv-updated'][$invoice['status']]++;
}
} else {
if (!isset($response['vinv-current'][$invoice['status']])) {
$response['vinv-current'][$invoice['status']] = 1;
} else {
$response['vinv-current'][$invoice['status']]++;
}
}
$objInv->setUpdatedAt(new \DateTime());
$entityManager->flush();
}
} catch (\Exception $exception) {
$response['vinv-status'] = 'ERROR';
$response['vinv-message'] = 'API exception: ' . $exception->getMessage();
}
}
$response['vinv-status'] = 'OK';
}
return $response;
}
// defaults to SG tenant ID (MT SG)
public function fetchSalesOrders($soNumbers = null, $tenantId = "771ca798-d2cf-4735-ab1b-1449300eda82", $dateFrom = null)
{
set_time_limit(90);
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$entityManager = $this->em;
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$accountingApi = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$order = null; // string
$contact_ids = null; // string[] | Filter by a comma-separated list of ContactIDs.
$statuses = null; //array("SUBMITTED", "PAID", "AUTHORISED", "VOIDED", "DRAFT", "DELETED");
$page = 1; // int | e.g. page=1 – Up to 100 invoices will be returned in a single API call with line items
// $dateFrom = new \DateTime($dateFrom) ?? new \DateTime("2023-01-01");
$dateFrom = new \DateTime($dateFrom) ?? new \DateTime('-24 hours');
if ($soNumbers) {
$dateFrom = new \DateTime("2020-01-01");
}
$allApiResponses = [];
$stopQuery = false;
$response['so-add'] = 0;
$response['so-update'] = 0;
$response['so-not-found'] = 0;
$response['so-lineitems'] = 0;
$response['so-removed-lineitems'] = 0;
try {
while (!$stopQuery) {
$apiResponse = $accountingApi->getQuotes($tenantId, null, $dateFrom, null, null, null, $contact_ids, $statuses, $page, $order, $soNumbers);
if (sizeof($apiResponse) < 100) {
$stopQuery = true;
}
if (sizeof($apiResponse) == 0) {
$response['so-not-found']++;
if($soNumbers) $stopQuery = true;
continue;
};
$page++;
$salesOrderRepository = $this->em->getRepository(SalesOrder::class);
$currencyRepository = $this->em->getRepository(Currency::class);
$xeroLineItemRepository = $entityManager->getRepository(XeroLineItem::class);
$departmentRepository = $entityManager->getRepository(Department::class);
foreach ($apiResponse as $so) {
$objSo = $salesOrderRepository->findOneBy(['salesOrderId' => $so['quote_id']]);
if (!$objSo) {
// We create a new invoice
$objSo = new SalesOrder();
$objSo->setCreatedAt(new \DateTime());
$response['so-add']++;
} else {
$objSo->setUpdatedAt(new \DateTime());
$response['so-update']++;
}
if (isset($so['contact'])) {
$objSo->setXeroContactId($so['contact']['contact_id']);
}
$objSo->setSalesOrderNo($so['quote_number']);
$currency_code = $so['currency_code'];
//print_r($currency_code);
$currency = $currencyRepository->findOneBy(['iso' => $currency_code]);
//print_r($currency);
if (!$currency) {
$currency = new Currency();
$currency->setIso($currency_code);
$currency->setName($currency_code);
$currency->setCreatedAt(new \DateTime());
$currency->setSymbol('-');
$entityManager->persist($currency);
$entityManager->flush();
$entityManager->refresh($currency);
}
$objSo->setCurrency($currency);
$text_date = $so['date'];
preg_match('#^/Date\((\d{10})#', $text_date, $matches);
$dt = new \DateTime('@' . $matches[1]);
$objSo->setQuoteDate($dt);
$objSo->setTotal($so['total']);
$objSo->setTotalUsd($this->currencyService->convertAtDate(date_format($dt, "Y-m-d"), $currency_code, "USD", $so['total']));
$objSo->setSubTotalUsd($this->currencyService->convertAtDate(date_format($dt, "Y-m-d"), $currency_code, "USD", $so['sub_total']));
$objSo->setSubtotal($so['sub_total']);
if (is_null($so['reference'])) {
$objSo->setReference('');
} else {
$objSo->setReference($so['reference']);
}
$objSo->setXeroId($so['quote_id']);
$objSo->setSalesOrderId($so['quote_id']);
$objSo->setXeroStatus($so['status']);
$objSo->setTenantId($tenantId);
$entityManager->persist($objSo);
$entityManager->flush();
if ($objSo->getCreator() == null) { // INV Creator
$creator = $this->fetchInvoiceHistory($objSo->getXeroId());
$objSo->setCreator($creator);
$entityManager->flush();
$entityManager->refresh($objSo);
}
if ($so['line_items']) { // INV Line Items
$availableLineItems = [];
// $response['so-lineitems'] = 0;
// $response['so-removed-lineitems'] = 0;
foreach ($so['line_items'] as $lineItem) {
// if($lineItem['item_code'] == null || !array_key_exists($lineItem['item_code'], $this->arr_item_codes)) continue;
// if(!array_key_exists($lineItem['item_code'], $arr_item_codes)) continue;
if ($lineItem['item_code'] != null && !array_key_exists(strtolower($lineItem['item_code']), $this->arr_item_codes))
continue;
if ($lineItem['item_code'] == null && !preg_match($this->projectIdPattern, $lineItem['description']) && ($lineItem['line_amount'] == null || $lineItem['line_amount'] == 0))
continue;
$lineItemId = $lineItem['line_item_id'];
$lineItemTotal = $lineItem['line_amount'] + $lineItem['tax_amount'];
$lineItemSubTotal = $lineItem['line_amount'];
$availableLineItems[] = $lineItemId;
$objLineItem = $xeroLineItemRepository->findOneBy(['xeroId' => $lineItemId]) ?? new XeroLineItem();
if ($objLineItem->getId() == null) {
$objLineItem->setCreatedAt(new \DateTimeImmutable());
} else {
$objLineItem->setUpdatedAt(new \DateTimeImmutable());
}
if ($lineItem['item_code'] == null) {
$lineItemDepartment = null; // default blank department
$itemCode = null; // default blank item code
$isIncluded = false;
} else {
$lineItemDepartment = $departmentRepository->findOneBy(['id' => $this->arr_item_codes[strtolower($lineItem['item_code'])]]) ?? null;
$isIncluded = !in_array(strtolower($lineItem['item_code']), $this->arr_excluded_item_codes);
$itemCode = $lineItem['item_code'];
};
$objLineItem->setParentNo($objSo->getSalesOrderNo());
$objLineItem->setSalesOrder($objSo);
$objLineItem->setXeroId($lineItemId);
$objLineItem->setTenantId($tenantId);
$objLineItem->setIsIncluded($isIncluded);
$objLineItem->setDepartment($lineItemDepartment);
$objLineItem->setDescription($lineItem['description']);
$objLineItem->setQuantity($lineItem['quantity']);
$objLineItem->setUnitAmount($lineItem['unit_amount']);
$objLineItem->setItemCode($itemCode);
$objLineItem->setAccountCode($lineItem['account_code']);
$objLineItem->setAccountId($lineItem['account_id']);
$objLineItem->setTotal($lineItemTotal);
$objLineItem->setSubtotal($lineItemSubTotal);
$objLineItem->setTotalUsd($this->currencyService->convertAtDate(date_format($dt, "Y-m-d"), $currency_code, "USD", $lineItemTotal));
$objLineItem->setSubTotalUsd($this->currencyService->convertAtDate(date_format($dt, "Y-m-d"), $currency_code, "USD", $lineItemSubTotal));
$entityManager->persist($objLineItem);
$entityManager->flush();
$response['so-lineitems']++;
}
$entityManager->refresh($objSo);
foreach ($objSo->getXeroLineItems() as $lineItem) {
if (in_array($lineItem->getXeroId(), $availableLineItems))
continue;
$entityManager->remove($lineItem);
$entityManager->flush();
$response['so-removed-lineitems']++;
}
}
// $this->projectService->projectXeroAllocationEmail($objSo);
}
}
} catch (\Exception $exception) {
$response['status'] = 'ERROR';
$response['message'] = 'API exception: ' . $exception->getMessage();
return $response;
}
$response['status'] = 'OK';
return $response;
}
public function fetchContacts($contactIds = null, $tenantId = "771ca798-d2cf-4735-ab1b-1449300eda82", $date = null)
{
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$entityManager = $this->em;
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$accountingApi = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$page = 1; // int | e.g. page=1 – Up to 100 contacts will be returned in a single API call with line items
$stopQuery = false;
$response = ["Added" => 0, "Updated" => 0, "Update date filled" => 0];
try {
while (!$stopQuery) {
$apiResponse = $accountingApi->getContacts($tenantId, $date, null, null, $contactIds, $page, null, false, null);
if (sizeof($apiResponse) < 25) {
$stopQuery = true;
}
$xeroContactRepository = $this->em->getRepository(XeroContact::class);
foreach ($apiResponse as $xc) {
$objContact = $xeroContactRepository->findOneBy(['xeroContactId' => $xc['contact_id']]);
preg_match('#^/Date\((\d{10})#', $xc->getUpdatedDateUtc(), $matches);
if (array_key_exists(1, $matches)) {
$updatedDateUtc = new \DateTime('@' . $matches[1]);
} else {
$updatedDateUtc = new \DateTime();
}
if (!$objContact) {
// We create a new contact
$objContact = new XeroContact();
$objContact->setXeroContactId($xc['contact_id']);
$objContact->setCreatedAt(new \DateTime());
$response["Added"]++;
} elseif ($objContact->getUpdatedAt()) {
// $objContact->setUpdatedAt(new \DateTime());
// $response["Updated"]++;
if ($objContact->getUpdatedAt()->format('Y-m-d H:i:s') < $updatedDateUtc->format('Y-m-d H:i:s')) {
$response['Updated']++;
} else {
continue;
}
} else {
$response['Update date filled']++;
};
$objContact->setName($xc['name']);
$objContact->setTenantId($tenantId);
$objContact->setUpdatedAt($updatedDateUtc);
if ($objContact->getId() === null) {
$entityManager->persist($objContact);
}
$entityManager->flush();
}
$page++;
} //end while
} catch (\Exception $exception) {
$response['status'] = 'ERROR';
$response['message'] = 'API exception: ' . $exception->getMessage();
return $response;
}
$response['status'] = 'OK';
return $response;
}
public function processWebhookObjects()
{
$webhook_objects = $this->em->getRepository(XeroWebhook::class)->findBy(['completed' => 0]);
$response['status'] = 'OK';
$response['total'] = 0;
foreach ($webhook_objects as $who) {
try {
$obj = json_decode($who->getObject());
foreach ($obj->events as $event) {
if ($event->eventCategory === "INVOICE") {
$xeroContactRepository = $this->em->getRepository(XeroContact::class);
$resp = $this->fetchInvoices(null, $event->resourceId, $event->tenantId, null, ['VOIDED'], true);
if ($resp) {
$who->setCompleted(1);
$this->em->persist($who);
$this->em->flush();
$response['total']++;
} else {
$response['status'] = 'ERROR';
// $response['message'] = $resp['message'];
}
if (!is_object($resp)) continue;
$contact = $xeroContactRepository->findOneBy(['xeroContactId' => $resp->getXeroContactId()]);
if (!$contact) {
// fetch contact first if not yet in db
$this->fetchContacts($resp->getXeroContactId(), $event->tenantId);
$contact = $xeroContactRepository->findOneBy(['xeroContactId' => $resp->getXeroContactId()]);
}
$this->mattermostService->sendPostRequestToMattermost($resp, $event, $contact);
} else if ($event->eventCategory === "CONTACT") {
$resp = $this->fetchContacts($event->resourceId, $event->tenantId);
if ($resp['status'] == 'OK') {
$who->setCompleted(1);
$this->em->persist($who);
$this->em->flush();
$response['total']++;
} else {
$response['status'] = 'ERROR';
$response['message'] = $resp['message'];
}
}
}
} catch (\Exception | GuzzleException $exception) {
$response['status'] = 'ERROR';
return $response;
}
}
return $response;
}
public function checkCredential($test)
{
$isValid = $this->getHasExpired() ? $this->renewToken($this->params) : true;
if (!$test && !$isValid) {
$provider = new \League\OAuth2\Client\Provider\GenericProvider([
'clientId' => $this->params->get('xeroClientId'),
'clientSecret' => $this->params->get('xeroClientSecret'),
'redirectUri' => $this->params->get('xeroRedirectURI'),
'urlAuthorize' => 'https://login.xero.com/identity/connect/authorize',
'urlAccessToken' => 'https://identity.xero.com/connect/token',
'urlResourceOwnerDetails' => 'https://api.xero.com/api.xro/2.0/Organisation'
]);
// Scope defines the data your app has permission to access.
// Learn more about scopes at https://developer.xero.com/documentation/oauth2/scopes
$options = [
'scope' => ['openid email profile offline_access accounting.settings.read accounting.transactions.read accounting.contacts.read accounting.journals.read accounting.reports.read accounting.attachments.read']
];
// This returns the authorizeUrl with necessary parameters applied (e.g. state).
// $authorizationUrl = $provider->getAuthorizationUrl($options);
// generate url from route: xero_authorize
$authorizationUrl = $this->urlGenerator->generate('xero_authorize', [], UrlGeneratorInterface::ABSOLUTE_URL);
$message = "Please login using Xero credential here: " . $authorizationUrl;
$to = $_SERVER['APP_ENV'] == "prod" ? ['devops@mediatropy.com'] : [$this->params->get('systemEmailOps')];
$this->mailgunService->sendEmailAdvanced(
[
'subject' => "WARNING: Xero Credential Expired, Login Required",
'to' => $to, // TODO: Might need to put it as parameter in the future (for devops)
'template' => 'email/email-internal/default.html.twig',
'params' => [
'message' => $message,
]
],
false
);
}
return $isValid;
}
public function fetchXeroUsers($id)
{
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$apiInstance = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$tenantId = (strlen($id) <= 2) ? $this->getTenantId($id) : $id;
$ifModifiedSince = new \DateTime("2014-02-06T12:17:43.202-08:00");
$where = ""; //IsSubscriber==true
$order = "LastName ASC";
try {
$results = $apiInstance->getUsers($tenantId, $ifModifiedSince, $where, $order);
// var_dump($result);
// foreach ($results as $result) {
// var_dump($result->getUpdatedDateUtc());
// };
// die;
$response = ['Added' => 0, 'Updated' => 0, 'Attached to User' => 0, 'Detached from User' => 0, 'message' => ''];
$xeroUserRepository = $this->em->getRepository(XeroUser::class);
foreach ($results as $result) {
$xeroUser = $xeroUserRepository->findOneBy(['xeroUserId' => $result->getUserID()]);
preg_match('#^/Date\((\d{10})#', $result->getUpdatedDateUtc(), $matches);
if (array_key_exists(1, $matches)) {
$updatedDateUtc = new \DateTimeImmutable('@' . $matches[1]);
} else {
$updatedDateUtc = new \DateTimeImmutable();
}
if (!$xeroUser) {
$xeroUser = new XeroUser();
$xeroUser->setXeroUserId($result->getUserID());
$xeroUser->setCreatedAt(new \DateTimeImmutable());
$response['Added']++;
} else {
if ($xeroUser->getUpdatedAt() != null && $xeroUser->getUpdatedAt()->format('Y-m-d H:i:s') < $updatedDateUtc->format('Y-m-d H:i:s')) {
$response['Updated']++;
} else {
continue;
}
}
$xeroUser->setFirstName($result->getFirstName());
$xeroUser->setLastName($result->getLastName());
$xeroUser->setEmail($result->getEmailAddress());
$xeroUser->setRole($result->getOrganisationRole());
$xeroUser->setUpdatedAt($updatedDateUtc);
$xeroUser->setTenantId($tenantId);
if ($xeroUser->getId() === null) {
$this->em->persist($xeroUser);
} else {
if ($xeroUser->getUser() && $xeroUser->getUser()->getEmail() !== $result->getEmailAddress()) {
$xeroUser->getUser()->removeXeroUser($xeroUser);
$response['Detached from User']++;
}
}
$user = $this->em->getRepository(User::class)->findOneBy(['email' => $result->getEmailAddress()]);
if ($user) {
$user->addXeroUser($xeroUser);
$response['Attached to User']++;
}
$this->em->flush();
};
$response['status'] = 'OK';
} catch (Exception $e) {
$response['status'] = 'ERROR';
$response['message'] = 'Exception when calling AccountingApi->getUsers: ' . $e->getMessage() . PHP_EOL;
}
return $response;
}
public function getXeroUser($id, $email)
{
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$apiInstance = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$tenantId = (strlen($id) <= 2) ? $this->getTenantId($id) : $id;
try {
$apiResponses = $apiInstance->getUsers($tenantId, null, 'EmailAddress=="' . $email . '"');
if (count($apiResponses) > 0) {
foreach ($apiResponses as $apiResponse) {
$xeroUser = $this->em->getRepository(XeroUser::class)->findOneBy(['xeroUserId' => $apiResponse->getUserID()]);
if (!$xeroUser) {
preg_match('#^/Date\((\d{10})#', $apiResponse->getUpdatedDateUtc(), $matches);
if (array_key_exists(1, $matches)) {
$updatedDateUtc = new \DateTimeImmutable('@' . $matches[1]);
} else {
$updatedDateUtc = new \DateTimeImmutable();
}
$xeroUser = new XeroUser();
$xeroUser->setXeroUserId($apiResponse->getUserID());
$xeroUser->setCreatedAt(new \DateTimeImmutable());
};
$xeroUser->setFirstName($apiResponse->getFirstName());
$xeroUser->setLastName($apiResponse->getLastName());
$xeroUser->setEmail($apiResponse->getEmailAddress());
$xeroUser->setRole($apiResponse->getOrganisationRole());
$xeroUser->setUpdatedAt($updatedDateUtc);
$xeroUser->setTenantId($tenantId);
if ($xeroUser->getId() === null) {
$this->em->persist($xeroUser);
};
$user = $this->em->getRepository(User::class)->findOneBy(['email' => $apiResponse->getEmailAddress()]);
if ($user) {
$user->addXeroUser($xeroUser);
}
$this->em->flush();
}
$response['status'] = 'OK';
$response['message'] = 'Xero user found and linked.';
} else {
$response['status'] = 'ERROR';
$response['message'] = 'Xero user not found.';
}
} catch (Exception $e) {
$response['status'] = 'ERROR';
$response['message'] = 'Exception when calling AccountingApi->getUser: ' . $e->getMessage() . PHP_EOL;
}
return $response;
}
public function createXeroContact($id, $vendor, $name)
{
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$apiInstance = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$tenantId = (strlen($id) <= 2) ? $this->getTenantId($id) : $id;
$summarizeErrors = true;
$contact = new \XeroAPI\XeroPHP\Models\Accounting\Contact;
$contact->setName($name);
$contacts = new \XeroAPI\XeroPHP\Models\Accounting\Contacts;
$arr_contacts = [];
array_push($arr_contacts, $contact);
$contacts->setContacts($arr_contacts);
try {
$results = $apiInstance->createContacts($tenantId, $contacts, $summarizeErrors);
foreach ($results->getContacts() as $result) {
try {
preg_match('#^/Date\((\d{10})#', $result->getUpdatedDateUtc(), $matches);
if (array_key_exists(1, $matches)) {
$updatedDateUtc = new \DateTime('@' . $matches[1]);
} else {
$updatedDateUtc = new \DateTime();
}
$objContact = new XeroContact();
$objContact->setXeroContactId($result->getContactId());
$objContact->setCreatedAt(new \DateTime());
$objContact->setName($result->getName());
$objContact->setTenantId($tenantId);
$objContact->setUpdatedAt($updatedDateUtc);
$objContact->setVendor($vendor);
$this->em->persist($objContact);
$this->em->flush();
} catch (Exception $e) {
$response['message'] = 'Exception when creating contact on HRP: ' . $e->getMessage() . PHP_EOL;
};
$response['status'] = 'OK';
$response['result'] = $result;
}
} catch (Exception $e) {
$response['status'] = 'ERROR';
$response['message'] = 'Exception when calling AccountingApi->createContacts: ' . $e->getMessage() . PHP_EOL;
}
return $response;
}
public function createReceipt($id, $contactId, $userId, $receiptDetails)
{
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$apiInstance = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$tenantId = (strlen($id) <= 2) ? $this->getTenantId($id) : $id;
$unitdp = 2;
$contact = new \XeroAPI\XeroPHP\Models\Accounting\Contact;
$contact->setContactID($contactId);
$user = new \XeroAPI\XeroPHP\Models\Accounting\User;
$user->setUserID($userId);
$lineItem = new \XeroAPI\XeroPHP\Models\Accounting\LineItem;
// $lineItem->setDescription('Test from HRP xero integration - ignore');
// $lineItem->setUnitAmount(1.0);
// $lineItem->setAccountCode('438');
// $lineItem->setTaxType('NONE');
// $lineItem->setTaxType('INPUT');
$lineItem->setQuantity(1.0);
$lineItem->setDescription(($_SERVER['APP_ENV'] !== 'prod' ? "[TEST] " : "") . $receiptDetails['description']);
$lineItem->setUnitAmount($receiptDetails['unit_amount']);
$lineItem->setAccountCode($receiptDetails['account_code']);
$lineItem->setTaxType($receiptDetails['tax_type']);
if ($receiptDetails['tracking'] != null) {
$lineItemTracking = new \XeroAPI\XeroPHP\Models\Accounting\LineItemTracking;
$lineItemTracking->setName($receiptDetails['tracking']['name']);
$lineItemTracking->setOption($receiptDetails['tracking']['option']);
$lineItem->setTracking([$lineItemTracking]);
};
$receipt = new \XeroAPI\XeroPHP\Models\Accounting\Receipt;
$receipt->setContact($contact);
$receipt->setUser($user);
$receipt->setLineItems([$lineItem]);
$receipt->setLineAmountTypes(\XeroAPI\XeroPHP\Models\Accounting\LineAmountTypes::INCLUSIVE);
$receipt->setStatus(\XeroAPI\XeroPHP\Models\Accounting\Receipt::STATUS_DRAFT);
$receipt->setDate($receiptDetails['date'] instanceof \DateTimeImmutable ? $receiptDetails['date']->format('Y-m-d') : $receiptDetails['date']);
$receipt->setReference($receiptDetails['reference']);
$receipts = new \XeroAPI\XeroPHP\Models\Accounting\Receipts;
$receipts->setReceipts([$receipt]);
try {
$result = $apiInstance->createReceipt($tenantId, $receipts, $unitdp);
foreach ($result->getReceipts() as $result) {
$response['status'] = 'OK';
$response['result'] = $result;
}
} catch (Exception $e) {
$response['status'] = 'ERROR';
$response['message'] = 'Exception when calling AccountingApi->createReceipt: ' . $e->getMessage() . PHP_EOL;
}
return $response;
}
public function getReceipts($id, $date)
{
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$apiInstance = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$tenantId = (strlen($id) <= 2) ? $this->getTenantId($id) : $id;
try {
$result = $apiInstance->getReceipts($tenantId, $date);
$response['status'] = 'OK';
$response['result'] = $result;
} catch (Exception $e) {
$response['status'] = 'ERROR';
$response['message'] = 'Exception when calling AccountingApi->getReceipts: ' . $e->getMessage() . PHP_EOL;
}
return $response;
}
public function createReceiptAttachment($id, $receiptId, $fileName, $filePath)
{
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
// 1ff96b79-b653-43ec-8514-b2890beb9225
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$apiInstance = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$tenantId = (strlen($id) <= 2) ? $this->getTenantId($id) : $id;
$body = file_get_contents($filePath);
try {
$result = $apiInstance->createReceiptAttachmentByFileName($tenantId, $receiptId, $fileName, $body);
foreach ($result->getAttachments() as $result) {
$response['status'] = 'OK';
$response['result'] = $result;
}
} catch (Exception $e) {
$response['status'] = 'ERROR';
$response['message'] = 'Exception when calling AccountingApi->createReceiptAttachmentByFileName: ' . $e->getMessage() . PHP_EOL;
}
return $response;
}
public function createExpenseClaims($id, $userId, $receiptId)
{
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$apiInstance = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$tenantId = (strlen($id) <= 2) ? $this->getTenantId($id) : $id;
$currDate = new \DateTime();
$user = new \XeroAPI\XeroPHP\Models\Accounting\User;
$user->setUserID($userId);
$receipt = new \XeroAPI\XeroPHP\Models\Accounting\Receipt;
$receipt->setReceiptID($receiptId);
$receipt->setDate($currDate);
$receipts = [];
array_push($receipts, $receipt);
$expenseClaim = new \XeroAPI\XeroPHP\Models\Accounting\ExpenseClaim;
$expenseClaim->setStatus(\XeroAPI\XeroPHP\Models\Accounting\ExpenseClaim::STATUS_SUBMITTED);
$expenseClaim->setUser($user);
$expenseClaim->setReceipts($receipts);
$expenseClaims = new \XeroAPI\XeroPHP\Models\Accounting\ExpenseClaims;
$arr_expense_claims = [];
array_push($arr_expense_claims, $expenseClaim);
$expenseClaims->setExpenseClaims($arr_expense_claims);
try {
$result = $apiInstance->createExpenseClaims($tenantId, $expenseClaims);
foreach ($result->getExpenseClaims() as $result) {
$response['status'] = 'OK';
$response['result'] = $result;
}
} catch (Exception $e) {
$response['status'] = 'ERROR';
$response['message'] = 'Exception when calling AccountingApi->createExpenseClaims: ' . $e->getMessage() . PHP_EOL;
}
return $response;
}
public function fetchExpenseClaims($id, $date = null)
{
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$apiInstance = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$tenantId = (strlen($id) <= 2) ? $this->getTenantId($id) : $id;
try {
$result = $apiInstance->getExpenseClaims($tenantId, $date);
$response['status'] = 'OK';
$response['result'] = $result;
} catch (Exception $e) {
$response['status'] = 'ERROR';
$response['message'] = 'Exception when calling AccountingApi->getExpenseClaims: ' . $e->getMessage() . PHP_EOL;
}
return $response;
}
public function updateXeroClaim($id, $claimId)
{
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$apiInstance = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$tenantId = (strlen($id) <= 2) ? $this->getTenantId($id) : $id;
try {
$expenseClaim = $apiInstance->getExpenseClaim($tenantId, $claimId);
// Need to update editable fields
$result = $apiInstance->updateExpenseClaim($tenantId, $claimId, $expenseClaim);
return $result;
} catch (Exception $e) {
return 'Exception when calling AccountingApi->updateExpenseClaim: ' . $e->getMessage() . PHP_EOL;
}
}
public function getExpenseClaimStatus($id, $claimId)
{
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$apiInstance = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$tenantId = (strlen($id) <= 2) ? $this->getTenantId($id) : $id;
try {
$apiResponse = $apiInstance->getExpenseClaim($tenantId, $claimId);
return $apiResponse->getExpenseClaims()[0]->getStatus();
} catch (Exception $e) {
return null;
}
}
public function deleteExpenseClaim($id, $expenseClaimId)
{
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$apiInstance = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$tenantId = (strlen($id) <= 2) ? $this->getTenantId($id) : $id;
try {
$expenseClaim = $apiInstance->getExpenseClaim($tenantId, $expenseClaimId);
$expenseClaim->setStatus('DELETED');
$result = $apiInstance->updateExpenseClaim($tenantId, $expenseClaimId, $expenseClaim);
return ['status' => 'OK', 'result' => $result];
} catch (\Exception $e) {
echo 'Exception when calling AccountingApi->updateExpenseClaim: ', $e->getMessage(), PHP_EOL;
return null;
}
}
public function getLatestBill($id, $date, $status = null, $filename = null, $inv = null)
{
if (!in_array($id, ['SG', 'ID', 'HK'])) {
if ($inv) {
$expenseInvoice = $this->em->getRepository(ExpenseRequestInvoice::class)->find($inv);
if ($expenseInvoice) {
$expenseInvoice->setXeroActionId('not-applicable');
$expenseInvoice->setXeroStatus('DRAFT');
$this->em->flush();
}
}
$response['status'] = 'ERROR';
$response['message'] = 'Linking to Xero not applicable';
return $response;
};
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$apiInstance = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$tenantId = (strlen($id) <= 2) ? $this->getTenantId($id) : $id;
$response = [];
$invoices = $apiInstance->getInvoices($tenantId, $date, 'Type=="ACCPAY"', 'Date DESC', null, null, null, $status, 1);
foreach ($invoices as $invoice) {
$invoiceId = $invoice->getInvoiceID();
$history = $apiInstance->getInvoiceHistory($tenantId, $invoiceId);
if ($invoice->getHasAttachments() == true && $history[0]->getDetails() == 'Received through Email-to-Bill') {
if ($this->em->getRepository(ExpenseRequestInvoice::class)->findOneBy(['xeroActionId' => $invoiceId])) {
continue;
}
if ($filename) {
$attachments = $apiInstance->getInvoiceAttachments($tenantId, $invoiceId);
foreach ($attachments as $attachment) {
if ($attachment->getFileName() == $filename) {
$response['status'] = 'OK';
$response['message'] = 'Bill found';
$response['result'] = $invoice;
if ($inv) {
$expenseInvoice = $this->em->getRepository(ExpenseRequestInvoice::class)->find($inv);
if ($expenseInvoice) {
$expenseInvoice->setXeroActionId($invoice->getInvoiceId());
$expenseInvoice->setXeroStatus($invoice->getStatus());
$this->em->flush();
$response['message'] = 'Bill found and linked to Expense Request';
} else {
$response['message'] = 'Bill found but not linked to Expense Request';
}
}
}
}
}
}
}
$response['status'] = 'OK';
if (!isset($response['result'])) {
if ($filename) {
$response['message'] = 'Bill not found';
} else {
$response['result'] = $invoices;
}
}
return $response;
}
public function getBillStatus($id, $claimId)
{
if ($this->getHasExpired()) {
$this->renewToken($this->params);
}
$config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string) $this->getSession()->getToken());
$accountingApi = new \XeroAPI\XeroPHP\Api\AccountingApi(
new \GuzzleHttp\Client(),
$config
);
$tenantId = (strlen($id) <= 2) ? $this->getTenantId($id) : $id;
try {
$apiResponse = $accountingApi->getInvoice($tenantId, $claimId);
return $apiResponse->getInvoices()[0]->getStatus();
} catch (\Exception $e) {
return null;
}
return null;
}
public function refreshExpenseClaimsStatus($testMode = false)
{
$response = [];
$expenseClaims = $this->em->getRepository(ExpenseRequest::class)->findAllElligibleForPayment();
foreach ($expenseClaims as $expenseClaim) {
if (count($expenseClaim->getExpenseRequestInvoices()) == 0) continue;
$invoices = [];
foreach ($expenseClaim->getExpenseRequestInvoices() as $expenseRequestInvoice) {
$update = false;
if ($expenseRequestInvoice->getSentHistory() == null || count($expenseRequestInvoice->getSentHistory()) == 0) continue;
$latestAction = end($expenseRequestInvoice->getSentHistory()['history']);
switch (strtoupper($latestAction['action'])) {
case 'BILL':
$apiResponse = $this->getBillStatus($latestAction['to'], $expenseRequestInvoice->getXeroActionId());
break;
case 'CLAIM':
$apiResponse = $this->getExpenseClaimStatus($latestAction['to'], $expenseRequestInvoice->getXeroActionId());
break;
default:
$apiResponse = null;
break;
}
if ($expenseRequestInvoice->getXeroStatus() != $apiResponse) {
$status = $expenseRequestInvoice->getXeroStatus() ? $expenseRequestInvoice->getXeroStatus() . ' > ' . $apiResponse : $apiResponse;
$expenseRequestInvoice->setXeroStatus($apiResponse);
$update = true;
if (!$testMode) $this->em->flush();
} else {
$status = $apiResponse;
}
$invoices[] = [
'action' => $latestAction['action'],
'xeroId' => $expenseRequestInvoice->getXeroActionId(),
'status' => $status,
'update' => $update
];
}
$response[] = [
'title' => $expenseClaim->getTitle(),
'invoices' => $invoices
];
}
return $response;
}
}