Updates for Quickbooks imports

This commit is contained in:
David Bomba 2025-02-12 15:21:55 +11:00
parent a12a025b28
commit ced4f771ee
8 changed files with 2815 additions and 350 deletions

View File

@ -0,0 +1,47 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Casts;
use App\DataMapper\PaymentSync;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class PaymentSyncCast implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes)
{
if (is_null($value)) {
return null; // Return null if the value is null
}
$data = json_decode($value, true);
if (!is_array($data)) {
return null;
}
$is = new PaymentSync();
$is->qb_id = $data['qb_id'];
return $is;
}
public function set($model, string $key, $value, array $attributes)
{
return [
$key => json_encode([
'qb_id' => $value->qb_id,
])
];
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper;
use App\Casts\PaymentSyncCast;
use Illuminate\Contracts\Database\Eloquent\Castable;
/**
* PaymentSync.
*/
class PaymentSync implements Castable
{
public string $qb_id;
public function __construct(array $attributes = [])
{
$this->qb_id = $attributes['qb_id'] ?? '';
}
/**
* Get the name of the caster class to use when casting from / to this cast target.
*
* @param array<string, mixed> $arguments
*/
public static function castUsing(array $arguments): string
{
return PaymentSyncCast::class;
}
public static function fromArray(array $data): self
{
return new self($data);
}
}

View File

@ -11,15 +11,16 @@
namespace App\Models;
use App\Events\Payment\PaymentWasRefunded;
use App\Events\Payment\PaymentWasVoided;
use App\Services\Ledger\LedgerService;
use App\Services\Payment\PaymentService;
use App\Utils\Ninja;
use App\Utils\Number;
use App\Utils\Traits\Inviteable;
use App\DataMapper\PaymentSync;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\Inviteable;
use App\Services\Ledger\LedgerService;
use App\Events\Payment\PaymentWasVoided;
use App\Services\Payment\PaymentService;
use App\Utils\Traits\Payment\Refundable;
use App\Events\Payment\PaymentWasRefunded;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
@ -64,6 +65,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property string|null $custom_value4
* @property int|null $transaction_id
* @property string|null $idempotency_key
* @property object|null $sync
* @property-read \App\Models\User|null $assigned_user
* @property-read \App\Models\Client $client
* @property-read \App\Models\Company $company
@ -168,6 +170,7 @@ class Payment extends BaseModel
'is_deleted' => 'bool',
'meta' => 'object',
'refund_meta' => 'array',
'sync' => PaymentSync::class,
];
protected $with = [

View File

@ -44,25 +44,7 @@ class QbPayment implements SyncInterface
$ninja_payment = $payment_transformer->buildPayment($payment);
$ninja_payment->service()->applyNumber()->save();
$invoice = Invoice::query()
->withTrashed()
->where('company_id', $this->service->company->id)
->where('sync->qb_id', $payment['invoice_id'])
->first();
if ($invoice) {
$paymentable = new \App\Models\Paymentable();
$paymentable->payment_id = $ninja_payment->id;
$paymentable->paymentable_id = $invoice->id;
$paymentable->paymentable_type = 'invoices';
$paymentable->amount = $transformed['applied'] + $ninja_payment->credits->sum('amount');
$paymentable->created_at = $ninja_payment->date; //@phpstan-ignore-line
$paymentable->save();
$invoice->service()->applyPayment($ninja_payment, $paymentable->amount);
}
$payment_transformer->associatePaymentToInvoice($ninja_payment, $payment);
}

View File

@ -17,6 +17,7 @@ use App\Models\Company;
use App\Models\Invoice;
use App\Models\Product;
use App\DataMapper\InvoiceItem;
use App\Models\TaxRate;
/**
* Class InvoiceTransformer.
@ -52,6 +53,7 @@ class InvoiceTransformer extends BaseTransformer
'tax_rate1' => $rate = $this->calculateTotalTax($qb_data),
'tax_name1' => $rate > 0 ? "Sales Tax" : "",
'custom_surcharge1' => $this->checkIfDiscountAfterTax($qb_data),
'balance' => data_get($qb_data, 'Balance', 0),
] : false;
}
@ -81,19 +83,49 @@ class InvoiceTransformer extends BaseTransformer
private function calculateTotalTax($qb_data)
{
$taxLines = data_get($qb_data, 'TxnTaxDetail.TaxLine', []);
$total_tax = data_get($qb_data,'TxnTaxDetail.TotalTax', false);
if (!is_array($taxLines)) {
if($total_tax == "0") {
return 0;
}
$taxLines = data_get($qb_data, 'TxnTaxDetail.TaxLine', []) ?? [];
if (!empty($taxLines) && !isset($taxLines[0])) {
$taxLines = [$taxLines];
}
$totalTaxRate = 0;
nlog($taxLines);
foreach ($taxLines as $taxLine) {
$taxRate = data_get($taxLine, 'TaxLineDetail.TaxPercent', 0);
$totalTaxRate += $taxRate;
}
if ($totalTaxRate > 0) {
$formattedTaxRate = rtrim(rtrim(number_format($totalTaxRate, 6), '0'), '.');
$formattedTaxRate = trim($formattedTaxRate);
$tr = \App\Models\TaxRate::firstOrNew(
[
'company_id' => $this->company->id,
'rate' => $formattedTaxRate,
],
[
'name' => "Sales Tax [{$formattedTaxRate}]",
'rate' => $formattedTaxRate,
]
);
$tr->company_id = $this->company->id;
$tr->user_id = $this->company->owner()->id;
$tr->save();
}
// ... exi
return (float)$totalTaxRate;
}

View File

@ -11,10 +11,12 @@
namespace App\Services\Quickbooks\Transformers;
use App\Models\Company;
use App\Models\Payment;
use App\Factory\PaymentFactory;
use App\Models\Credit;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\Payment;
use App\DataMapper\PaymentSync;
use App\Factory\PaymentFactory;
/**
*
@ -35,6 +37,7 @@ class PaymentTransformer extends BaseTransformer
{
return [
'id' => data_get($qb_data, 'Id', null),
'date' => data_get($qb_data, 'TxnDate', now()->format('Y-m-d')),
'amount' => floatval(data_get($qb_data, 'TotalAmt', 0)),
'applied' => data_get($qb_data, 'TotalAmt', 0) - data_get($qb_data, 'UnappliedAmt', 0),
@ -45,15 +48,72 @@ class PaymentTransformer extends BaseTransformer
];
}
public function associatePaymentToInvoice(Payment $payment, mixed $qb_data)
{
$invoice = Invoice::query()
->withTrashed()
->where('company_id', $this->company->id)
->where('sync->qb_id', data_get($qb_data, 'invoice_id'))
->first();
if(!$invoice)
return;
$lines = data_get($qb_data, 'Line', []) ?? [];
if(!empty($lines) && !isset($lines[0])) {
$lines = [$lines];
}
foreach($lines as $item) {
$id = data_get($item, 'LinkedTxn.TxnId', false);
$tx_type = data_get($item, 'LinkedTxn.TxnType', false);
$amount = data_get($item, 'Amount', 0);
if($tx_type == 'Invoice' && $id == $invoice->sync->qb_id) {
$paymentable = new \App\Models\Paymentable();
$paymentable->payment_id = $payment->id;
$paymentable->paymentable_id = $invoice->id;
$paymentable->paymentable_type = 'invoices';
$paymentable->amount = $related_invoice['amount'];
$paymentable->created_at = $payment->date; //@phpstan-ignore-line
$paymentable->save();
$invoice->service()->applyPayment($payment, $paymentable->amount);
return;
}
}
}
public function buildPayment($qb_data): ?Payment
{
$ninja_payment_data = $this->transform($qb_data);
$search_payment = Payment::query()
->withTrashed()
->where('company_id', $this->company->id)
->where('sync->qb_id', $ninja_payment_data['id'])
->first();
if($search_payment) {
return $search_payment;
}
if ($ninja_payment_data['client_id']) {
$payment = PaymentFactory::create($this->company->id, $this->company->owner()->id, $ninja_payment_data['client_id']);
$payment->amount = $ninja_payment_data['amount'];
$payment->applied = $ninja_payment_data['applied'];
$payment->status_id = 4;
$sync = new PaymentSync();
$sync->qb_id = $ninja_payment_data['id'];
$payment->sync = $sync;
$payment->fill($ninja_payment_data);
$payment->save();
@ -77,7 +137,9 @@ class PaymentTransformer extends BaseTransformer
{
$credit_line = null;
foreach ($qb_data->Line as $item) {
$credit_array = data_get($qb_data, 'Line', []);
foreach ($credit_array as $item) {
if (data_get($item, 'LinkedTxn.TxnType', null) == 'CreditMemo') {
$credit_line = $item;
@ -95,14 +157,14 @@ class PaymentTransformer extends BaseTransformer
$line = new \App\DataMapper\InvoiceItem();
$line->quantity = 1;
$line->cost = $credit_line->Amount;
$line->cost = data_get($credit_line, 'Amount', 0);
$line->product_key = 'CREDITMEMO';
$line->notes = $payment->private_notes;
$credit->date = $qb_data->TxnDate;
$credit->date = data_get($qb_data, 'TxnDate', now()->format('Y-m-d'));
$credit->status_id = 4;
$credit->amount = $credit_line->Amount;
$credit->paid_to_date = $credit_line->Amount;
$credit->amount = data_get($credit_line, 'Amount', 0);
$credit->paid_to_date = data_get($credit_line, 'Amount', 0);
$credit->balance = 0;
$credit->line_items = [$line];
$credit->save();

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('payments', function (Blueprint $table) {
$table->text('sync')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
}
};

File diff suppressed because it is too large Load Diff