diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index f2a4e1001d84..9b606b07c807 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -11,11 +11,18 @@ namespace App\Export\CSV; +use App\Utils\Number; use App\Models\Client; use App\Models\Invoice; +use App\Models\GatewayType; +use App\Models\Payment; +use League\Fractal\Manager; use Illuminate\Support\Carbon; use App\Utils\Traits\MakesHash; +use App\Transformers\ClientTransformer; +use App\Transformers\PaymentTransformer; use Illuminate\Database\Eloquent\Builder; +use League\Fractal\Serializer\ArraySerializer; class BaseExport { @@ -35,6 +42,161 @@ class BaseExport public array $forced_keys = []; + protected array $client_report_keys = [ + "name" => "client.name", + "user" => "client.user_id", + "balance" => "client.balance", + "paid_to_date" => "client.paid_to_date", + "currency" => "client.currency_id", + "website" => "client.website", + "private_notes" => "client.private_notes", + "industry" => "client.industry_id", + "size" => "client.size_id", + "work_phone" => "client.phone", + "address1" => "client.address1", + "address2" => "client.address2", + "city" => "client.city", + "state" => "client.state", + "postal_code" => "client.postal_code", + "country" => "client.country_id", + "custom_value4" => "contact.custom_value4", + "shipping_address1" => "client.shipping_address1", + "shipping_address2" => "client.shipping_address2", + "shipping_city" => "client.shipping_city", + "shipping_state" => "client.shipping_state", + "shipping_postal_code" => "client.shipping_postal_code", + "shipping_country" => "client.shipping_country_id", + "payment_terms" => "client.payment_terms", + "vat_number" => "client.vat_number", + "id_number" => "client.id_number", + "public_notes" => "client.public_notes", + "phone" => "contact.phone", + "first_name" => "contact.first_name", + "last_name" => "contact.last_name", + "email" => "contact.email", + ]; + + protected array $invoice_report_keys = [ + "invoice_number" => "invoice.number", + "amount" => "invoice.amount", + "balance" => "invoice.balance", + "paid_to_date" => "invoice.paid_to_date", + "po_number" => "invoice.po_number", + "date" => "invoice.date", + "due_date" => "invoice.due_date", + "terms" => "invoice.terms", + "footer" => "invoice.footer", + "status" => "invoice.status", + "public_notes" => "invoice.public_notes", + "private_notes" => "invoice.private_notes", + "uses_inclusive_taxes" => "invoice.uses_inclusive_taxes", + "is_amount_discount" => "invoice.is_amount_discount", + "partial" => "invoice.partial", + "partial_due_date" => "invoice.partial_due_date", + "surcharge1" => "invoice.custom_surcharge1", + "surcharge2" => "invoice.custom_surcharge2", + "surcharge3" => "invoice.custom_surcharge3", + "surcharge4" => "invoice.custom_surcharge4", + "exchange_rate" => "invoice.exchange_rate", + "tax_amount" => "invoice.total_taxes", + "assigned_user" => "invoice.assigned_user_id", + "user" => "invoice.user_id", + ]; + + protected array $item_report_keys = [ + "quantity" => "item.quantity", + "cost" => "item.cost", + "product_key" => "item.product_key", + "notes" => "item.notes", + "item_tax1" => "item.tax_name1", + "item_tax_rate1" => "item.tax_rate1", + "item_tax2" => "item.tax_name2", + "item_tax_rate2" => "item.tax_rate2", + "item_tax3" => "item.tax_name3", + "item_tax_rate3" => "item.tax_rate3", + "custom_value1" => "item.custom_value1", + "custom_value2" => "item.custom_value2", + "custom_value3" => "item.custom_value3", + "custom_value4" => "item.custom_value4", + "discount" => "item.discount", + "type" => "item.type_id", + "tax_category" => "item.tax_id", + ]; + + protected array $quote_report_keys = [ + "quote_number" => "quote.number", + "amount" => "quote.amount", + "balance" => "quote.balance", + "paid_to_date" => "quote.paid_to_date", + "po_number" => "quote.po_number", + "date" => "quote.date", + "due_date" => "quote.due_date", + "terms" => "quote.terms", + "footer" => "quote.footer", + "status" => "quote.status", + "public_notes" => "quote.public_notes", + "private_notes" => "quote.private_notes", + "uses_inclusive_taxes" => "quote.uses_inclusive_taxes", + "is_amount_discount" => "quote.is_amount_discount", + "partial" => "quote.partial", + "partial_due_date" => "quote.partial_due_date", + "surcharge1" => "quote.custom_surcharge1", + "surcharge2" => "quote.custom_surcharge2", + "surcharge3" => "quote.custom_surcharge3", + "surcharge4" => "quote.custom_surcharge4", + "exchange_rate" => "quote.exchange_rate", + "tax_amount" => "quote.total_taxes", + "assigned_user" => "quote.assigned_user_id", + "user" => "quote.user_id", + ]; + + protected array $credit_report_keys = [ + "credit_number" => "credit.number", + "amount" => "credit.amount", + "balance" => "credit.balance", + "paid_to_date" => "credit.paid_to_date", + "po_number" => "credit.po_number", + "date" => "credit.date", + "due_date" => "credit.due_date", + "terms" => "credit.terms", + "footer" => "credit.footer", + "status" => "credit.status", + "public_notes" => "credit.public_notes", + "private_notes" => "credit.private_notes", + "uses_inclusive_taxes" => "credit.uses_inclusive_taxes", + "is_amount_discount" => "credit.is_amount_discount", + "partial" => "credit.partial", + "partial_due_date" => "credit.partial_due_date", + "surcharge1" => "credit.custom_surcharge1", + "surcharge2" => "credit.custom_surcharge2", + "surcharge3" => "credit.custom_surcharge3", + "surcharge4" => "credit.custom_surcharge4", + "exchange_rate" => "credit.exchange_rate", + "tax_amount" => "credit.total_taxes", + "assigned_user" => "credit.assigned_user_id", + "user" => "credit.user_id", + ]; + + protected array $payment_report_keys = [ + "date" => "payment.date", + "amount" => "payment.amount", + "refunded" => "payment.refunded", + "applied" => "payment.applied", + "transaction_reference" => "payment.transaction_reference", + "currency" => "payment.currency", + "exchange_rate" => "payment.exchange_rate", + "number" => "payment.number", + "method" => "payment.method", + "status" => "payment.status", + "private_notes" => "payment.private_notes", + "custom_value1" => "payment.custom_value1", + "custom_value2" => "payment.custom_value2", + "custom_value3" => "payment.custom_value3", + "custom_value4" => "payment.custom_value4", + "user" => "payment.user_id", + "assigned_user" => "payment.assigned_user_id", + ]; + protected function filterByClients($query) { if (isset($this->input['client_id']) && $this->input['client_id'] != 'all') { @@ -50,6 +212,170 @@ class BaseExport return $query; } + protected function resolveKey($key, $entity, $transformer) :string + { + $parts = explode(".", $key); + + if(!is_array($parts) || count($parts) < 2) + return ''; + + match($parts[0]) { + 'contact' => $value = $this->resolveClientContactKey($parts[1], $entity, $transformer), + 'client' => $value = $this->resolveClientKey($parts[1], $entity, $transformer), + 'invoice' => $value = $this->resolveInvoiceKey($parts[1], $entity, $transformer), + 'payment' => $value = $this->resolvePaymentKey($parts[1], $entity, $transformer), + default => $value = '' + }; + + return $value; + } + + private function resolveClientContactKey($column, $entity, $transformer) + { + + $primary_contact = $entity->client->primary_contact()->first() ?? $entity->client->contacts()->first(); + + return $primary_contact?->{$column} ?? ''; + + } + + private function resolveClientKey($column, $entity, $transformer) + { + $transformed_client = $transformer->includeClient($entity); + + $manager = new Manager(); + $manager->setSerializer(new ArraySerializer()); + $transformed_client = $manager->createData($transformed_client)->toArray(); + + if($column == 'name') + return $transformed_client['display_name']; + + if($column == 'user_id') + return $entity->client->user->present()->name(); + + if($column == 'country_id') + return $entity->client->country ? ctrans("texts.country_{$entity->client->country->name}") : ''; + + if($column == 'shipping_country_id') + return $entity->client->shipping_country ? ctrans("texts.country_{$entity->client->shipping_country->name}") : ''; + + if($column == 'size_id') + return $entity->client->size?->name ?? ''; + + if($column == 'industry_id') + return $entity->client->industry?->name ?? ''; + + if ($column == 'currency_id') { + return $entity->client->currency() ? $entity->client->currency()->code : $entity->company->currency()->code; + } + + if($column == 'client.payment_terms') { + return $entity->client->getSetting('payment_terms'); + } + + if(array_key_exists($column, $transformed_client)) + return $transformed_client[$column]; + + nlog("export: Could not resolve client key: {$column}"); + + return ''; + + } + + private function resolveInvoiceKey($column, $entity, $transformer) + { + nlog("searching for {$column}"); + + if($transformer instanceof PaymentTransformer) { + $transformed_invoices = $transformer->includeInvoices($entity); + + $manager = new Manager(); + $manager->setSerializer(new ArraySerializer()); + $transformed_invoices = $manager->createData($transformed_invoices)->toArray(); + + if(!isset($transformed_invoices['App\\Models\\Invoice'])) + return ''; + + $transformed_invoices = $transformed_invoices['App\\Models\\Invoice']; + + nlog(count($transformed_invoices)); + nlog(array_key_exists($column, $transformed_invoices[0])); + + if(count($transformed_invoices) == 1 && array_key_exists($column, $transformed_invoices[0])) + return $transformed_invoices[0][$column]; + + if(count($transformed_invoices) > 1 && array_key_exists($column, $transformed_invoices[0])) + return implode(', ', array_column($transformed_invoices, $column)); + + return ""; + + } + + $transformed_invoice = $transformer->transform($entity); + + if($column == 'status') + return $entity->stringStatus($entity->status_id); + + return ''; + } + + private function resolvePaymentKey($column, $entity, $transformer) + { + if($entity instanceof Payment){ + + $transformed_payment = $transformer->transform($entity); + + if(array_key_exists($column, $transformed_payment)) { + return $transformed_payment[$column]; + } elseif (array_key_exists(str_replace("payment.", "", $column), $transformed_payment)) { + return $transformed_payment[$column]; + } + + nlog("export: Could not resolve payment key: {$column}"); + + return ''; + + } + + if($column == 'amount') + return $entity->payments()->exists() ? Number::formatMoney($entity->payments()->sum('paymentables.amount'), $entity->company) : ctrans('texts.unpaid'); + + if($column == 'refunded') { + return $entity->payments()->exists() ? Number::formatMoney($entity->payments()->sum('paymentables.refunded'), $entity->company) : 0; + } + + if($column == 'applied') { + $refunded = $entity->payments()->sum('paymentables.refunded'); + $amount = $entity->payments()->sum('paymentables.amount'); + + return $entity->payments()->exists() ? Number::formatMoney(($amount - $refunded), $entity->company) : 0; + } + + $payment = $entity->payments()->first(); + + if(!$payment) + return ''; + + if($column == 'method') + return $payment->translatedType(); + + if($column == 'currency') + return $payment?->currency?->code ?? ''; + + $payment_transformer = new PaymentTransformer(); + $transformed_payment = $payment_transformer->transform($payment); + + if($column == 'status'){ + return $payment->stringStatus($transformed_payment['status_id']); + } + + if(array_key_exists($column, $transformed_payment)) + return $transformed_payment[$column]; + + return ''; + + } + protected function addInvoiceStatusFilter($query, $status): Builder { @@ -173,14 +499,51 @@ class BaseExport $header = []; foreach (array_merge($this->input['report_keys'], $this->forced_keys) as $value) { + $key = array_search($value, $this->entity_keys); + if(!$key) { + $prefix = stripos($value, 'client.') !== false ? ctrans('texts.client') : ctrans('texts.contact'); + $key = array_search($value, $this->client_report_keys); + } + + if(!$key) { + $prefix = ctrans('texts.invoice'); + $key = array_search($value, $this->invoice_report_keys); + } + + if(!$key) { + $prefix = ctrans('texts.payment'); + $key = array_search($value, $this->payment_report_keys); + } + + + if(!$key) { + $prefix = ctrans('texts.quote'); + $key = array_search($value, $this->quote_report_keys); + } + + if(!$key) { + $prefix = ctrans('texts.credit'); + $key = array_search($value, $this->credit_report_keys); + } + + if(!$key) { + $prefix = ctrans('texts.item'); + $key = array_search($value, $this->item_report_keys); + } + + if(!$key) { + $prefix = ''; + } + $key = str_replace('item.', '', $key); $key = str_replace('invoice.', '', $key); $key = str_replace('client.', '', $key); $key = str_replace('contact.', '', $key); + $key = str_replace('payment.', '', $key); - $header[] = ctrans("texts.{$key}"); + $header[] = "{$prefix} " . ctrans("texts.{$key}"); } return $header; diff --git a/app/Export/CSV/ClientExport.php b/app/Export/CSV/ClientExport.php index 7d539795a9d5..1f45c2b97569 100644 --- a/app/Export/CSV/ClientExport.php +++ b/app/Export/CSV/ClientExport.php @@ -143,9 +143,9 @@ class ClientExport extends BaseExport $keyval = array_search($key, $this->entity_keys); - if ($parts[0] == 'client' && array_key_exists($parts[1], $transformed_client)) { + if (is_array($parts) && $parts[0] == 'client' && array_key_exists($parts[1], $transformed_client)) { $entity[$keyval] = $transformed_client[$parts[1]]; - } elseif ($parts[0] == 'contact' && array_key_exists($parts[1], $transformed_contact)) { + } elseif (is_array($parts) && $parts[0] == 'contact' && array_key_exists($parts[1], $transformed_contact)) { $entity[$keyval] = $transformed_contact[$parts[1]]; } else { $entity[$keyval] = ''; diff --git a/app/Export/CSV/CreditExport.php b/app/Export/CSV/CreditExport.php index e21ec1f5ec60..67b259971003 100644 --- a/app/Export/CSV/CreditExport.php +++ b/app/Export/CSV/CreditExport.php @@ -123,10 +123,19 @@ class CreditExport extends BaseExport foreach (array_values($this->input['report_keys']) as $key) { $keyval = array_search($key, $this->entity_keys); + if(!$keyval) + $keyval = array_search(str_replace("credit.", "", $key), $this->entity_keys) ?? $key; + + if(!$keyval) + $keyval = $key; + if (array_key_exists($key, $transformed_credit)) { $entity[$keyval] = $transformed_credit[$key]; - } else { - $entity[$keyval] = ''; + } elseif (array_key_exists($keyval, $transformed_credit)) { + $entity[$keyval] = $transformed_credit[$keyval]; + } + else { + $entity[$keyval] = $this->resolveKey($keyval, $credit, $this->credit_transformer); } } @@ -138,9 +147,9 @@ class CreditExport extends BaseExport if (in_array('country_id', $this->input['report_keys'])) { $entity['country'] = $credit->client->country ? ctrans("texts.country_{$credit->client->country->name}") : ''; } - + if (in_array('currency_id', $this->input['report_keys'])) { - $entity['currency_id'] = $credit->client->currency() ? $credit->client->currency()->code : $invoice->company->currency()->code; + $entity['currency_id'] = $credit->client->currency() ? $credit->client->currency()->code : $credit->company->currency()->code; } if (in_array('invoice_id', $this->input['report_keys'])) { @@ -155,6 +164,10 @@ class CreditExport extends BaseExport $entity['status'] = $credit->stringStatus($credit->status_id); } + if(in_array('credit.status', $this->input['report_keys'])) { + $entity['credit.status'] = $credit->stringStatus($credit->status_id); + } + return $entity; } } diff --git a/app/Export/CSV/InvoiceExport.php b/app/Export/CSV/InvoiceExport.php index 2141358fa134..98ed8498a6c3 100644 --- a/app/Export/CSV/InvoiceExport.php +++ b/app/Export/CSV/InvoiceExport.php @@ -80,6 +80,7 @@ class InvoiceExport extends BaseExport 'project', ]; + public function __construct(Company $company, array $input) { $this->company = $company; @@ -134,10 +135,21 @@ class InvoiceExport extends BaseExport foreach (array_values($this->input['report_keys']) as $key) { $keyval = array_search($key, $this->entity_keys); + if(!$keyval) { + $keyval = array_search(str_replace("invoice.", "", $key), $this->entity_keys) ?? $key; + } + + if(!$keyval) { + $keyval = $key; + } + if (array_key_exists($key, $transformed_invoice)) { $entity[$keyval] = $transformed_invoice[$key]; - } else { - $entity[$keyval] = ''; + } elseif (array_key_exists($keyval, $transformed_invoice)) { + $entity[$keyval] = $transformed_invoice[$keyval]; + } + else { + $entity[$keyval] = $this->resolveKey($keyval, $invoice, $this->invoice_transformer); } } @@ -162,15 +174,15 @@ class InvoiceExport extends BaseExport $entity['status'] = $invoice->stringStatus($invoice->status_id); } - $payment_exists = $invoice->payments()->exists(); + // $payment_exists = $invoice->payments()->exists(); - $entity['payment_number'] = $payment_exists ? $invoice->payments()->pluck('number')->implode(',') : ''; + // $entity['payment_number'] = $payment_exists ? $invoice->payments()->pluck('number')->implode(',') : ''; - $entity['payment_date'] = $payment_exists ? $invoice->payments()->pluck('date')->implode(',') : ''; + // $entity['payment_date'] = $payment_exists ? $invoice->payments()->pluck('date')->implode(',') : ''; - $entity['payment_amount'] = $payment_exists ? Number::formatMoney($invoice->payments()->sum('paymentables.amount'), $invoice->company) : ctrans('texts.unpaid'); + // $entity['payment_amount'] = $payment_exists ? Number::formatMoney($invoice->payments()->sum('paymentables.amount'), $invoice->company) : ctrans('texts.unpaid'); - $entity['method'] = $payment_exists ? $invoice->payments()->first()->translatedType() : ""; + // $entity['method'] = $payment_exists ? $invoice->payments()->first()->translatedType() : ""; return $entity; } diff --git a/app/Export/CSV/PaymentExport.php b/app/Export/CSV/PaymentExport.php index 78592b763c86..e7aaf1abb9ed 100644 --- a/app/Export/CSV/PaymentExport.php +++ b/app/Export/CSV/PaymentExport.php @@ -112,10 +112,21 @@ class PaymentExport extends BaseExport foreach (array_values($this->input['report_keys']) as $key) { $keyval = array_search($key, $this->entity_keys); + if(!$keyval) { + $keyval = array_search(str_replace("payment.", "", $key), $this->entity_keys) ?? $key; + } + + if(!$keyval) { + $keyval = $key; + } + if (array_key_exists($key, $transformed_entity)) { $entity[$keyval] = $transformed_entity[$key]; - } else { - $entity[$keyval] = ''; + } elseif (array_key_exists($keyval, $transformed_entity)) { + $entity[$keyval] = $transformed_entity[$keyval]; + } + else { + $entity[$keyval] = $this->resolveKey($keyval, $payment, $this->entity_transformer); } } @@ -140,6 +151,10 @@ class PaymentExport extends BaseExport $entity['currency'] = $payment->currency()->exists() ? $payment->currency->code : ''; } + if (in_array('payment.currency', $this->input['report_keys'])) { + $entity['payment.currency'] = $payment->currency()->exists() ? $payment->currency->code : ''; + } + if (in_array('exchange_currency_id', $this->input['report_keys'])) { $entity['exchange_currency'] = $payment->exchange_currency()->exists() ? $payment->exchange_currency->code : ''; } @@ -152,11 +167,19 @@ class PaymentExport extends BaseExport $entity['type'] = $payment->translatedType(); } + if (in_array('payment.method', $this->input['report_keys'])) { + $entity['payment.method'] = $payment->translatedType(); + } + + if (in_array('payment.status', $this->input['report_keys'])) { + $entity['payment.status'] = $payment->stringStatus($payment->status_id); + } + if (in_array('gateway_type_id', $this->input['report_keys'])) { $entity['gateway'] = $payment->gateway_type ? $payment->gateway_type->name : 'Unknown Type'; } - $entity['invoices'] = $payment->invoices()->exists() ? $payment->invoices->pluck('number')->implode(',') : ''; + // $entity['invoices'] = $payment->invoices()->exists() ? $payment->invoices->pluck('number')->implode(',') : ''; return $entity; } diff --git a/app/Models/Client.php b/app/Models/Client.php index 6365b493ba31..6feeb9e26ec0 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -464,6 +464,11 @@ class Client extends BaseModel implements HasLocalePreference return $this->belongsTo(Industry::class); } + public function size() + { + return $this->belongsTo(Size::class); + } + public function locale() { if (! $this->language()) { diff --git a/app/Models/PaymentType.php b/app/Models/PaymentType.php index 05c98be7db1e..7372da8b98fe 100644 --- a/app/Models/PaymentType.php +++ b/app/Models/PaymentType.php @@ -34,7 +34,8 @@ class PaymentType extends StaticModel */ public $timestamps = false; - const CREDIT = 32; + const BANK_TRANSFER = 1; + const CASH = 2; const ACH = 4; const VISA = 5; const MASTERCARD = 6; @@ -53,11 +54,14 @@ class PaymentType extends StaticModel const MAESTRO = 20; const SOLO = 21; const SWITCH = 22; + const VENMO = 24; const ALIPAY = 27; const SOFORT = 28; const SEPA = 29; const GOCARDLESS = 30; const CRYPTO = 31; + const CREDIT = 32; + const ZELLE = 33; const MOLLIE_BANK_TRANSFER = 34; const KBC = 35; const BANCONTACT = 36; @@ -76,10 +80,12 @@ class PaymentType extends StaticModel const BACS = 49; const STRIPE_BANK_TRANSFER = 50; const CASH_APP = 51; - const VENMO = 24; public array $type_names = [ + self::BANK_TRANSFER => 'payment_type_Bank Transfer', + self::CASH => 'payment_type_Cash', self::CREDIT => 'payment_type_Credit', + self::ZELLE => 'payment_type_Zelle', self::ACH => 'payment_type_ACH', self::VISA => 'payment_type_Visa Card', self::MASTERCARD => 'payment_type_MasterCard', diff --git a/app/PaymentDrivers/Eway/CreditCard.php b/app/PaymentDrivers/Eway/CreditCard.php index 969aa3ec670b..11dbafdcfe2b 100644 --- a/app/PaymentDrivers/Eway/CreditCard.php +++ b/app/PaymentDrivers/Eway/CreditCard.php @@ -70,16 +70,14 @@ class CreditCard $response = $this->eway_driver->init()->eway->createCustomer(\Eway\Rapid\Enum\ApiMethod::DIRECT, $transaction); - if(property_exists($response, 'ResponseMessage')) - $response_status = ErrorCode::getStatus($response->ResponseMessage); - - if (! $response_status['success']) { + if($response->getErrors()) { + + $response_status['message'] = \Eway\Rapid::getMessage($response->getErrors()[0]); $this->eway_driver->sendFailureMail($response_status['message']); $this->logResponse($response); - throw new PaymentFailed($response_status['message'] ?? 'Unknown response from gateway, please contact you merchant.', 400); }