mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2026-03-03 00:47:02 +00:00
Refactor for quickbooks settings map
This commit is contained in:
@@ -24,9 +24,31 @@ class QuickbooksSettingsCast implements CastsAttributes
|
||||
}
|
||||
|
||||
$data = json_decode($value, true);
|
||||
$data = $this->normalizeSettingsForHydration($data);
|
||||
return QuickbooksSettings::fromArray($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize nested settings for hydration.
|
||||
* income_account_map: int keys (Product::PRODUCT_TYPE_*), values QuickBooks account ID (string) or null (unset).
|
||||
*/
|
||||
private function normalizeSettingsForHydration(array $data): array
|
||||
{
|
||||
if (! isset($data['settings']['income_account_map']) || ! is_array($data['settings']['income_account_map'])) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$out = [];
|
||||
foreach ($data['settings']['income_account_map'] as $k => $v) {
|
||||
if ($v === null || (is_string($v) && $v !== '')) {
|
||||
$out[(int) $k] = $v;
|
||||
}
|
||||
}
|
||||
$data['settings']['income_account_map'] = $out;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function set($model, string $key, $value, array $attributes)
|
||||
{
|
||||
if ($value instanceof QuickbooksSettings) {
|
||||
|
||||
@@ -18,8 +18,9 @@ use App\Models\Product;
|
||||
* QuickbooksSync.
|
||||
*
|
||||
* Product type to income account mapping:
|
||||
* Keys are Product::PRODUCT_TYPE_* constants (int). Values are income account names (string).
|
||||
* Example: [Product::PRODUCT_TYPE_SERVICE => 'Service Income', Product::PRODUCT_TYPE_PHYSICAL => 'Sales of Product Income']
|
||||
* Keys are Product::PRODUCT_TYPE_* constants (int). Values are QuickBooks account IDs (string|null).
|
||||
* Example: [Product::PRODUCT_TYPE_SERVICE => '123', Product::PRODUCT_TYPE_PHYSICAL => '456']
|
||||
* Null values indicate the account has not been configured for that product type.
|
||||
*/
|
||||
class QuickbooksSync
|
||||
{
|
||||
@@ -41,17 +42,14 @@ class QuickbooksSync
|
||||
|
||||
public QuickbooksSyncMap $expense;
|
||||
|
||||
public string $default_income_account = '';
|
||||
|
||||
public string $default_expense_account = '';
|
||||
|
||||
/**
|
||||
* Map of product type id (Product::PRODUCT_TYPE_*) to income account name.
|
||||
* E.g. [2 => 'Service Income', 1 => 'Sales of Product Income']
|
||||
* Map of product type id (Product::PRODUCT_TYPE_*) to QuickBooks income account ID.
|
||||
* E.g. [2 => '123', 1 => '456']
|
||||
* Null values indicate the account has not been configured for that product type.
|
||||
*
|
||||
* @var array<int, string>
|
||||
* @var array<int, string|null>
|
||||
*/
|
||||
public array $product_type_income_account_map = [];
|
||||
public array $income_account_map = [];
|
||||
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
@@ -64,46 +62,29 @@ class QuickbooksSync
|
||||
$this->product = new QuickbooksSyncMap($attributes['product'] ?? []);
|
||||
$this->payment = new QuickbooksSyncMap($attributes['payment'] ?? []);
|
||||
$this->expense = new QuickbooksSyncMap($attributes['expense'] ?? []);
|
||||
$this->default_income_account = $attributes['default_income_account'] ?? '';
|
||||
$this->default_expense_account = $attributes['default_expense_account'] ?? '';
|
||||
$map = $attributes['product_type_income_account_map'] ?? [];
|
||||
$this->product_type_income_account_map = self::normalizeProductTypeIncomeAccountMap($map);
|
||||
$this->income_account_map = $attributes['income_account_map'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize product_type_income_account_map so keys are int (product type ids).
|
||||
*/
|
||||
private static function normalizeProductTypeIncomeAccountMap(mixed $map): array
|
||||
{
|
||||
if (! is_array($map)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($map as $k => $v) {
|
||||
if (is_string($v) && $v !== '') {
|
||||
$out[(int) $k] = $v;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suggested default mapping of Product::PRODUCT_TYPE_* to common QuickBooks income account names.
|
||||
* Suggested default mapping of Product::PRODUCT_TYPE_* to QuickBooks income account IDs.
|
||||
* Returns null for all types, indicating they need to be configured.
|
||||
* Use when building UI defaults or onboarding; stored config overrides these.
|
||||
*
|
||||
* @return array<int, null>
|
||||
*/
|
||||
public static function defaultProductTypeIncomeAccountMap(): array
|
||||
{
|
||||
return [
|
||||
Product::PRODUCT_TYPE_PHYSICAL => 'Sales of Product Income',
|
||||
Product::PRODUCT_TYPE_SERVICE => 'Service Income',
|
||||
Product::PRODUCT_TYPE_DIGITAL => 'Sales of Product Income',
|
||||
Product::PRODUCT_TYPE_SHIPPING => 'Shipping and Delivery Income',
|
||||
Product::PRODUCT_TYPE_EXEMPT => 'Sales of Product Income',
|
||||
Product::PRODUCT_TYPE_REDUCED_TAX => 'Sales of Product Income',
|
||||
Product::PRODUCT_TYPE_OVERRIDE_TAX => 'Sales of Product Income',
|
||||
Product::PRODUCT_TYPE_ZERO_RATED => 'Sales of Product Income',
|
||||
Product::PRODUCT_TYPE_REVERSE_TAX => 'Sales of Product Income',
|
||||
Product::PRODUCT_INTRA_COMMUNITY => 'Sales of Product Income',
|
||||
Product::PRODUCT_TYPE_PHYSICAL => null,
|
||||
Product::PRODUCT_TYPE_SERVICE => null,
|
||||
Product::PRODUCT_TYPE_DIGITAL => null,
|
||||
Product::PRODUCT_TYPE_SHIPPING => null,
|
||||
Product::PRODUCT_TYPE_EXEMPT => null,
|
||||
Product::PRODUCT_TYPE_REDUCED_TAX => null,
|
||||
Product::PRODUCT_TYPE_OVERRIDE_TAX => null,
|
||||
Product::PRODUCT_TYPE_ZERO_RATED => null,
|
||||
Product::PRODUCT_TYPE_REVERSE_TAX => null,
|
||||
Product::PRODUCT_INTRA_COMMUNITY => null,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -119,9 +100,7 @@ class QuickbooksSync
|
||||
'product' => $this->product->toArray(),
|
||||
'payment' => $this->payment->toArray(),
|
||||
'expense' => $this->expense->toArray(),
|
||||
'default_income_account' => $this->default_income_account,
|
||||
'default_expense_account' => $this->default_expense_account,
|
||||
'product_type_income_account_map' => $this->product_type_income_account_map,
|
||||
'income_account_map' => $this->income_account_map,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class InvoiceTransformer extends BaseTransformer
|
||||
}
|
||||
}
|
||||
|
||||
$line_items[] = [
|
||||
$line_payload = [
|
||||
'LineNum' => $line_num,
|
||||
'DetailType' => 'SalesItemLineDetail',
|
||||
'SalesItemLineDetail' => [
|
||||
@@ -80,6 +80,12 @@ class InvoiceTransformer extends BaseTransformer
|
||||
'Amount' => $line_item->line_total ?? ($line_item->cost * ($line_item->quantity ?? 1)),
|
||||
];
|
||||
|
||||
|
||||
//check here if we need to inject the income account reference
|
||||
// $line_payload['AccountRef'] = ['value' => $income_account_qb_id];
|
||||
|
||||
$line_items[] = $line_payload;
|
||||
|
||||
$line_num++;
|
||||
}
|
||||
|
||||
|
||||
@@ -77,9 +77,7 @@ class QuickbooksTest extends TestCase
|
||||
"Taxable" => true,
|
||||
"UnitPrice" => $non_inventory_product->price,
|
||||
"Type" => "NonInventory",
|
||||
"IncomeAccountRef" => [
|
||||
"value" => $this->qb->settings->default_income_account, // Replace with your actual income account ID
|
||||
],
|
||||
|
||||
// "AssetAccountRef" => [
|
||||
// "value" => "81", // Replace with your actual asset account ID
|
||||
// "name" => "Inventory Asset"
|
||||
@@ -109,9 +107,7 @@ class QuickbooksTest extends TestCase
|
||||
"Taxable" => true,
|
||||
"UnitPrice" => $service_product->price,
|
||||
"Type" => "Service",
|
||||
"IncomeAccountRef" => [
|
||||
"value" => $this->qb->settings->default_income_account, // Replace with your actual income account ID
|
||||
],
|
||||
|
||||
"TrackQtyOnHand" => false,
|
||||
|
||||
]);
|
||||
|
||||
@@ -109,8 +109,7 @@ class QuickbooksSettingsSerializationTest extends TestCase
|
||||
'client' => [
|
||||
'direction' => SyncDirection::PULL->value,
|
||||
],
|
||||
'default_income_account' => 'income_account_123',
|
||||
'default_expense_account' => 'expense_account_456',
|
||||
|
||||
],
|
||||
]);
|
||||
|
||||
@@ -119,9 +118,7 @@ class QuickbooksSettingsSerializationTest extends TestCase
|
||||
// Verify nested QuickbooksSync structure
|
||||
$this->assertIsArray($array['settings']);
|
||||
$this->assertArrayHasKey('client', $array['settings']);
|
||||
$this->assertArrayHasKey('default_income_account', $array['settings']);
|
||||
$this->assertEquals('income_account_123', $array['settings']['default_income_account']);
|
||||
$this->assertEquals('expense_account_456', $array['settings']['default_expense_account']);
|
||||
$this->assertArrayHasKey('income_account_map', $array['settings']);
|
||||
|
||||
// Verify nested QuickbooksSyncMap structure
|
||||
$this->assertIsArray($array['settings']['client']);
|
||||
@@ -151,8 +148,6 @@ class QuickbooksSettingsSerializationTest extends TestCase
|
||||
'product' => [
|
||||
'direction' => SyncDirection::BIDIRECTIONAL->value,
|
||||
],
|
||||
'default_income_account' => 'income_123',
|
||||
'default_expense_account' => 'expense_456',
|
||||
],
|
||||
]);
|
||||
|
||||
@@ -184,8 +179,6 @@ class QuickbooksSettingsSerializationTest extends TestCase
|
||||
|
||||
// Verify nested settings are preserved
|
||||
$this->assertInstanceOf(QuickbooksSync::class, $deserialized->settings);
|
||||
$this->assertEquals('income_123', $deserialized->settings->default_income_account);
|
||||
$this->assertEquals('expense_456', $deserialized->settings->default_expense_account);
|
||||
|
||||
// Verify enum values are preserved correctly
|
||||
$this->assertInstanceOf(QuickbooksSyncMap::class, $deserialized->settings->client);
|
||||
|
||||
Reference in New Issue
Block a user