您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

ChartOfAccountsService.php 5.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. <?php
  2. namespace App\Services;
  3. use App\Enums\Accounting\AccountType;
  4. use App\Enums\Banking\BankAccountType;
  5. use App\Models\Accounting\AccountSubtype;
  6. use App\Models\Accounting\Adjustment;
  7. use App\Models\Banking\BankAccount;
  8. use App\Models\Company;
  9. use App\Utilities\Currency\CurrencyAccessor;
  10. use Exception;
  11. class ChartOfAccountsService
  12. {
  13. public function createChartOfAccounts(Company $company): void
  14. {
  15. $chartOfAccounts = config('chart-of-accounts.default');
  16. foreach ($chartOfAccounts as $type => $subtypes) {
  17. foreach ($subtypes as $subtypeName => $subtypeConfig) {
  18. $subtype = $company->accountSubtypes()
  19. ->createQuietly([
  20. 'multi_currency' => $subtypeConfig['multi_currency'] ?? false,
  21. 'inverse_cash_flow' => $subtypeConfig['inverse_cash_flow'] ?? false,
  22. 'category' => AccountType::from($type)->getCategory()->value,
  23. 'type' => $type,
  24. 'name' => $subtypeName,
  25. 'description' => $subtypeConfig['description'] ?? 'No description available.',
  26. ]);
  27. try {
  28. $this->createDefaultAccounts($company, $subtype, $subtypeConfig);
  29. } catch (Exception $e) {
  30. // Log the error
  31. logger()->alert('Failed to create a company with its defaults, blocking critical business functionality.', [
  32. 'error' => $e->getMessage(),
  33. 'userId' => $company->owner->id,
  34. 'companyId' => $company->id,
  35. ]);
  36. throw $e;
  37. }
  38. }
  39. }
  40. }
  41. private function createDefaultAccounts(Company $company, AccountSubtype $subtype, array $subtypeConfig): void
  42. {
  43. if (isset($subtypeConfig['accounts']) && is_array($subtypeConfig['accounts'])) {
  44. $baseCode = $subtypeConfig['base_code'];
  45. $defaultCurrencyCode = CurrencyAccessor::getDefaultCurrency();
  46. if (empty($defaultCurrencyCode)) {
  47. throw new Exception('No default currency available for creating accounts.');
  48. }
  49. foreach ($subtypeConfig['accounts'] as $accountName => $accountDetails) {
  50. // Create the Account without directly setting bank_account_id
  51. $account = $company->accounts()->createQuietly([
  52. 'subtype_id' => $subtype->id,
  53. 'category' => $subtype->type->getCategory()->value,
  54. 'type' => $subtype->type->value,
  55. 'code' => $baseCode++,
  56. 'name' => $accountName,
  57. 'currency_code' => $defaultCurrencyCode,
  58. 'description' => $accountDetails['description'] ?? 'No description available.',
  59. 'default' => true,
  60. 'created_by' => $company->owner->id,
  61. 'updated_by' => $company->owner->id,
  62. ]);
  63. // Check if we need to create a BankAccount for this Account
  64. if ($subtypeConfig['multi_currency'] && isset($subtypeConfig['bank_account_type'])) {
  65. $bankAccount = $this->createBankAccountForMultiCurrency($company, $subtypeConfig['bank_account_type']);
  66. // Associate the BankAccount with the Account
  67. $bankAccount->account()->associate($account);
  68. $bankAccount->saveQuietly();
  69. }
  70. if (isset($subtypeConfig['adjustment_category'], $subtypeConfig['adjustment_type'])) {
  71. $adjustment = $this->createAdjustmentForAccount($company, $subtypeConfig['adjustment_category'], $subtypeConfig['adjustment_type']);
  72. // Associate the Adjustment with the Account
  73. $adjustment->account()->associate($account);
  74. $adjustment->saveQuietly();
  75. }
  76. }
  77. }
  78. }
  79. private function createBankAccountForMultiCurrency(Company $company, string $bankAccountType): BankAccount
  80. {
  81. $noDefaultBankAccount = $company->bankAccounts()->where('enabled', true)->doesntExist();
  82. return $company->bankAccounts()->createQuietly([
  83. 'type' => BankAccountType::from($bankAccountType) ?? BankAccountType::Other,
  84. 'enabled' => $noDefaultBankAccount,
  85. 'created_by' => $company->owner->id,
  86. 'updated_by' => $company->owner->id,
  87. ]);
  88. }
  89. private function createAdjustmentForAccount(Company $company, string $category, string $type): Adjustment
  90. {
  91. $noDefaultAdjustmentType = $company->adjustments()->where('category', $category)
  92. ->where('type', $type)
  93. ->where('enabled', true)
  94. ->doesntExist();
  95. $defaultRate = match ([$category, $type]) {
  96. ['tax', 'sales'] => 8, // Default 8% for Sales Tax
  97. ['tax', 'purchase'] => 8, // Default 8% for Purchase Tax
  98. ['discount', 'sales'] => 5, // Default 5% for Sales Discount
  99. ['discount', 'purchase'] => 5, // Default 5% for Purchase Discount
  100. default => 0, // Default to 0 if unspecified
  101. };
  102. return $company->adjustments()->createQuietly([
  103. 'category' => $category,
  104. 'type' => $type,
  105. 'rate' => $defaultRate,
  106. 'computation' => 'percentage',
  107. 'enabled' => $noDefaultAdjustmentType,
  108. 'created_by' => $company->owner->id,
  109. 'updated_by' => $company->owner->id,
  110. ]);
  111. }
  112. }