mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2026-03-03 02:57:01 +00:00
Fixes for negative invoice proxying for credits / peppol
This commit is contained in:
@@ -67,7 +67,7 @@ class BatchPushToQuickbooks implements ShouldQueue
|
||||
public int $company_id
|
||||
) {
|
||||
// Set queue to dedicated QB queue
|
||||
$this->onQueue('quickbooks');
|
||||
// $this->onQueue('quickbooks');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,7 +46,7 @@ class FlushQuickbooksBatch implements ShouldQueue
|
||||
private int $companyId,
|
||||
private string $priority = QuickbooksBatchCollector::PRIORITY_NORMAL
|
||||
) {
|
||||
$this->onQueue('quickbooks');
|
||||
// $this->onQueue('quickbooks');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1081,7 +1081,7 @@ class Company extends BaseModel
|
||||
// FASTEST CHECK: Raw database column (no object instantiation, no JSON decode)
|
||||
// This is the cheapest possible check - just a null comparison
|
||||
// For companies without QuickBooks, this returns immediately with ~0.001ms overhead
|
||||
if (is_null($this->getRawOriginal('quickbooks')) || !$this->company->account->isPaid()) {
|
||||
if (is_null($this->getRawOriginal('quickbooks')) || !$this->account->isPaid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -299,7 +299,7 @@ class BaseRepository
|
||||
$model = $model->calc()->getInvoice();
|
||||
|
||||
/* Check if the model has been changed in a way that is relevant to Quickbooks */
|
||||
$qb_model_changes = $model->wasChanged(['amount', 'line_items', 'total_taxes']);
|
||||
$qb_model_changes = $model->wasChanged(['amount', 'line_items', 'total_taxes', 'status_id']);
|
||||
|
||||
/* We use this to compare to our starting amount */
|
||||
$state['finished_amount'] = $model->balance;
|
||||
@@ -341,8 +341,10 @@ class BaseRepository
|
||||
|
||||
if ($qb_model_changes && $model->company->quickbooks && $model->company->shouldPushToQuickbooks('invoice')) {
|
||||
|
||||
nlog("base repo changes detected");
|
||||
|
||||
if($model->company->quickbooks->settings->automatic_taxes){
|
||||
|
||||
nlog("immediate sync");
|
||||
try{
|
||||
(new \App\Jobs\Quickbooks\PushToQuickbooks('invoice', $model->id, $model->company->db))->handle();
|
||||
}
|
||||
@@ -351,8 +353,8 @@ class BaseRepository
|
||||
}
|
||||
}
|
||||
elseif($model->status_id != Invoice::STATUS_DRAFT){
|
||||
|
||||
\App\Services\Quickbooks\QuickbooksBatchCollector::collect('invoice', $model->id, $model->company->db, $model->company->id);
|
||||
nlog("batch sync");
|
||||
\App\Services\Quickbooks\QuickbooksBatchCollector::collect('invoice', $model->id, $model->company->db, $model->company_id);
|
||||
|
||||
// \App\Jobs\Quickbooks\PushToQuickbooks::dispatch(
|
||||
// 'invoice',
|
||||
|
||||
@@ -126,7 +126,11 @@ class StorecoveAdapter
|
||||
$e = new \InvoiceNinja\EInvoice\EInvoice();
|
||||
$peppolInvoice = $e->decode('Peppol', $p, 'xml');
|
||||
|
||||
$parent = $invoice instanceof \App\Models\Credit ? \App\Services\EDocument\Gateway\Storecove\Models\Credit::class : \App\Services\EDocument\Gateway\Storecove\Models\Invoice::class;
|
||||
// $parent = $invoice instanceof \App\Models\Credit ? \App\Services\EDocument\Gateway\Storecove\Models\Credit::class : \App\Services\EDocument\Gateway\Storecove\Models\Invoice::class;
|
||||
$parent = ($invoice instanceof \App\Models\Credit || $peppolInvoice instanceof \InvoiceNinja\EInvoice\Models\Peppol\CreditNote)
|
||||
? \App\Services\EDocument\Gateway\Storecove\Models\Credit::class
|
||||
: \App\Services\EDocument\Gateway\Storecove\Models\Invoice::class;
|
||||
|
||||
$peppolInvoice = $e->encode($peppolInvoice, 'json');
|
||||
$this->storecove_invoice = $serializer->deserialize($peppolInvoice, $parent, 'json', $context);
|
||||
|
||||
|
||||
@@ -59,8 +59,14 @@ class SendEDocument implements ShouldQueue
|
||||
|
||||
nlog("trying");
|
||||
|
||||
$model = $this->entity::withTrashed()->firstOrFail($this->id);
|
||||
$model = $this->entity::withTrashed()->find($this->id);
|
||||
|
||||
if(!$model){
|
||||
|
||||
nlog("model not found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($model->backup->guid) && is_string($model->backup->guid) && strlen($model->backup->guid) > 3) {
|
||||
nlog("already sent!");
|
||||
return;
|
||||
|
||||
@@ -29,17 +29,17 @@ use App\Jobs\Quickbooks\BatchPushToQuickbooks;
|
||||
class QuickbooksBatchCollector
|
||||
{
|
||||
/**
|
||||
* Batch size thresholds
|
||||
* Dispatch immediately if batch reaches this size (don't wait for timer)
|
||||
*/
|
||||
private const MAX_BATCH_SIZE = 50;
|
||||
private const MIN_BATCH_SIZE = 5;
|
||||
|
||||
/**
|
||||
* Time windows for collecting entities (seconds) by priority
|
||||
* Time windows for collecting entities (seconds) by priority.
|
||||
* After this window a FlushQuickbooksBatch job fires and pushes
|
||||
* whatever has accumulated — even a single entity.
|
||||
*/
|
||||
private const COLLECTION_WINDOW_IMMEDIATE = 2; // High priority: 2 seconds max
|
||||
private const COLLECTION_WINDOW_NORMAL = 10; // Normal priority: 10 seconds max
|
||||
private const COLLECTION_WINDOW_LOW = 30; // Low priority: 30 seconds max
|
||||
private const COLLECTION_WINDOW_NORMAL = 10;
|
||||
private const COLLECTION_WINDOW_LOW = 30;
|
||||
|
||||
/**
|
||||
* Priority levels
|
||||
@@ -76,6 +76,8 @@ class QuickbooksBatchCollector
|
||||
string $priority = self::PRIORITY_NORMAL,
|
||||
bool $forceImmediate = false
|
||||
): void {
|
||||
nlog("QB Batch: Collecting {$entityType} {$entityId} for company {$companyId} with priority {$priority}");
|
||||
|
||||
// Force immediate dispatch for high-priority operations
|
||||
if ($forceImmediate || $priority === self::PRIORITY_IMMEDIATE) {
|
||||
nlog("QB Batch: Immediate dispatch for {$entityType} {$entityId}");
|
||||
@@ -95,21 +97,18 @@ class QuickbooksBatchCollector
|
||||
'priority' => $priority,
|
||||
];
|
||||
|
||||
// Get collection window based on priority
|
||||
$collectionWindow = self::getCollectionWindow($priority);
|
||||
|
||||
// Store batch with expiry
|
||||
Cache::put($key, $batch, now()->addSeconds($collectionWindow));
|
||||
// Store batch — TTL must outlive the flush job delay so data is
|
||||
// still in cache when FlushQuickbooksBatch runs.
|
||||
Cache::put($key, $batch, now()->addSeconds($collectionWindow + 30));
|
||||
|
||||
// Check if we should dispatch now
|
||||
if (count($batch) >= self::MAX_BATCH_SIZE) {
|
||||
// Batch is full, dispatch right now
|
||||
self::dispatchBatch($entityType, $db, $companyId, $priority);
|
||||
} elseif (count($batch) >= self::MIN_BATCH_SIZE) {
|
||||
// Schedule dispatch if not already scheduled
|
||||
} else {
|
||||
// Schedule a delayed flush (idempotent — won't double-schedule)
|
||||
self::scheduleDispatch($entityType, $db, $companyId, $priority, $collectionWindow);
|
||||
} elseif ($priority === self::PRIORITY_IMMEDIATE && count($batch) >= 1) {
|
||||
// For immediate priority, dispatch even small batches quickly
|
||||
self::scheduleDispatch($entityType, $db, $companyId, $priority, 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +185,6 @@ class QuickbooksBatchCollector
|
||||
private static function getCollectionWindow(string $priority): int
|
||||
{
|
||||
return match ($priority) {
|
||||
self::PRIORITY_IMMEDIATE => self::COLLECTION_WINDOW_IMMEDIATE,
|
||||
self::PRIORITY_LOW => self::COLLECTION_WINDOW_LOW,
|
||||
default => self::COLLECTION_WINDOW_NORMAL,
|
||||
};
|
||||
|
||||
@@ -61,15 +61,13 @@ class InvoiceTransformer extends BaseTransformer
|
||||
// Get product's QuickBooks ID (business logic handled by QbProduct)
|
||||
$product_qb_id = $qb_service->product->findOrCreateProduct($line_item);
|
||||
|
||||
$tax_code_id = 'NON'; // Default to non-taxable
|
||||
$tax_code_id = 'NON';
|
||||
|
||||
// Check if tax_id is set and is exempt/zero rate (5 = exempt, 8 = zero rate)
|
||||
if (isset($line_item->tax_id) && in_array($line_item->tax_id, ['5', '8'])) {
|
||||
$tax_code_id = 'NON';
|
||||
} elseif ($ast) { // Automatic taxes are enabled
|
||||
} elseif ($ast) {
|
||||
$tax_code_id = 'TAX';
|
||||
} elseif (isset($line_item->tax_id) && !in_array($line_item->tax_id, ['5', '8'])) {
|
||||
// Only use 'TAX' if there are actual tax rates applied to this line item
|
||||
} else {
|
||||
$has_tax_rate = (
|
||||
(isset($line_item->tax_rate1) && $line_item->tax_rate1 > 0)
|
||||
|| (isset($line_item->tax_rate2) && $line_item->tax_rate2 > 0)
|
||||
|
||||
Reference in New Issue
Block a user