Refactor for quickbooks settings map

This commit is contained in:
David Bomba
2026-01-28 15:40:46 +11:00
parent 1c0686833a
commit eb28b38534
5 changed files with 57 additions and 61 deletions

View File

@@ -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) {

View File

@@ -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,
];
}
}

View File

@@ -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++;
}

View File

@@ -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,
]);

View File

@@ -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);