Andrew Wallo 5 月之前
父節點
當前提交
88ad988e42

+ 5
- 3
app/Services/ChartOfAccountsService.php 查看文件

9
 use App\Models\Accounting\Adjustment;
9
 use App\Models\Accounting\Adjustment;
10
 use App\Models\Banking\BankAccount;
10
 use App\Models\Banking\BankAccount;
11
 use App\Models\Company;
11
 use App\Models\Company;
12
-use App\Utilities\Currency\CurrencyAccessor;
13
 use Exception;
12
 use Exception;
14
 
13
 
15
 class ChartOfAccountsService
14
 class ChartOfAccountsService
16
 {
15
 {
17
-    public function createChartOfAccounts(Company $company): void
16
+    private string $currencyCode;
17
+
18
+    public function createChartOfAccounts(Company $company, string $currencyCode = 'USD'): void
18
     {
19
     {
20
+        $this->currencyCode = $currencyCode;
19
         $chartOfAccounts = config('chart-of-accounts.default');
21
         $chartOfAccounts = config('chart-of-accounts.default');
20
 
22
 
21
         // Always create a non-recoverable "Purchase Tax" adjustment, even without an account
23
         // Always create a non-recoverable "Purchase Tax" adjustment, even without an account
53
     {
55
     {
54
         if (isset($subtypeConfig['accounts']) && is_array($subtypeConfig['accounts'])) {
56
         if (isset($subtypeConfig['accounts']) && is_array($subtypeConfig['accounts'])) {
55
             $baseCode = $subtypeConfig['base_code'];
57
             $baseCode = $subtypeConfig['base_code'];
56
-            $defaultCurrencyCode = CurrencyAccessor::getDefaultCurrency();
58
+            $defaultCurrencyCode = $this->currencyCode;
57
 
59
 
58
             if (empty($defaultCurrencyCode)) {
60
             if (empty($defaultCurrencyCode)) {
59
                 throw new Exception('No default currency available for creating accounts.');
61
                 throw new Exception('No default currency available for creating accounts.');

+ 1
- 1
app/Services/CompanyDefaultService.php 查看文件

17
 
17
 
18
             // Create Chart of Accounts
18
             // Create Chart of Accounts
19
             $chartOfAccountsService = app(ChartOfAccountsService::class);
19
             $chartOfAccountsService = app(ChartOfAccountsService::class);
20
-            $chartOfAccountsService->createChartOfAccounts($company);
20
+            $chartOfAccountsService->createChartOfAccounts($company, $currencyCode);
21
 
21
 
22
             // Get the default bank account and update the company default record
22
             // Get the default bank account and update the company default record
23
             $defaultBankAccount = $company->bankAccounts()->where('enabled', true)->firstOrFail();
23
             $defaultBankAccount = $company->bankAccounts()->where('enabled', true)->firstOrFail();

+ 27
- 36
database/factories/Accounting/DocumentLineItemFactory.php 查看文件

43
         return $this
43
         return $this
44
             ->for($invoice, 'documentable')
44
             ->for($invoice, 'documentable')
45
             ->for($invoice->company, 'company')
45
             ->for($invoice->company, 'company')
46
-            ->state(function (array $attributes) {
47
-                $offering = Offering::where('sellable', true)
46
+            ->afterCreating(function (DocumentLineItem $lineItem) {
47
+                $offering = Offering::query()
48
+                    ->where('company_id', $lineItem->company_id)
49
+                    ->where('sellable', true)
48
                     ->inRandomOrder()
50
                     ->inRandomOrder()
49
-                    ->first();
51
+                    ->firstOrFail();
50
 
52
 
51
-                return [
53
+                $lineItem->updateQuietly([
52
                     'offering_id' => $offering->id,
54
                     'offering_id' => $offering->id,
53
                     'unit_price' => $offering->price,
55
                     'unit_price' => $offering->price,
54
-                ];
55
-            })
56
-            ->afterCreating(function (DocumentLineItem $lineItem) {
57
-                $offering = $lineItem->offering;
56
+                ]);
58
 
57
 
59
-                if ($offering) {
60
-                    $lineItem->salesTaxes()->syncWithoutDetaching($offering->salesTaxes->pluck('id')->toArray());
61
-                    $lineItem->salesDiscounts()->syncWithoutDetaching($offering->salesDiscounts->pluck('id')->toArray());
62
-                }
58
+                $lineItem->salesTaxes()->syncWithoutDetaching($offering->salesTaxes->pluck('id')->toArray());
59
+                $lineItem->salesDiscounts()->syncWithoutDetaching($offering->salesDiscounts->pluck('id')->toArray());
63
 
60
 
64
                 $lineItem->refresh();
61
                 $lineItem->refresh();
65
 
62
 
78
         return $this
75
         return $this
79
             ->for($estimate, 'documentable')
76
             ->for($estimate, 'documentable')
80
             ->for($estimate->company, 'company')
77
             ->for($estimate->company, 'company')
81
-            ->state(function (array $attributes) {
82
-                $offering = Offering::where('sellable', true)
78
+            ->afterCreating(function (DocumentLineItem $lineItem) {
79
+                $offering = Offering::query()
80
+                    ->where('company_id', $lineItem->company_id)
81
+                    ->where('sellable', true)
83
                     ->inRandomOrder()
82
                     ->inRandomOrder()
84
-                    ->first();
83
+                    ->firstOrFail();
85
 
84
 
86
-                return [
85
+                $lineItem->updateQuietly([
87
                     'offering_id' => $offering->id,
86
                     'offering_id' => $offering->id,
88
                     'unit_price' => $offering->price,
87
                     'unit_price' => $offering->price,
89
-                ];
90
-            })
91
-            ->afterCreating(function (DocumentLineItem $lineItem) {
92
-                $offering = $lineItem->offering;
88
+                ]);
93
 
89
 
94
-                if ($offering) {
95
-                    $lineItem->salesTaxes()->syncWithoutDetaching($offering->salesTaxes->pluck('id')->toArray());
96
-                    $lineItem->salesDiscounts()->syncWithoutDetaching($offering->salesDiscounts->pluck('id')->toArray());
97
-                }
90
+                $lineItem->salesTaxes()->syncWithoutDetaching($offering->salesTaxes->pluck('id')->toArray());
91
+                $lineItem->salesDiscounts()->syncWithoutDetaching($offering->salesDiscounts->pluck('id')->toArray());
98
 
92
 
99
                 $lineItem->refresh();
93
                 $lineItem->refresh();
100
 
94
 
113
         return $this
107
         return $this
114
             ->for($bill, 'documentable')
108
             ->for($bill, 'documentable')
115
             ->for($bill->company, 'company')
109
             ->for($bill->company, 'company')
116
-            ->state(function (array $attributes) {
117
-                $offering = Offering::where('purchasable', true)
110
+            ->afterCreating(function (DocumentLineItem $lineItem) {
111
+                $offering = Offering::query()
112
+                    ->where('company_id', $lineItem->company_id)
113
+                    ->where('purchasable', true)
118
                     ->inRandomOrder()
114
                     ->inRandomOrder()
119
-                    ->first();
115
+                    ->firstOrFail();
120
 
116
 
121
-                return [
117
+                $lineItem->updateQuietly([
122
                     'offering_id' => $offering->id,
118
                     'offering_id' => $offering->id,
123
                     'unit_price' => $offering->price,
119
                     'unit_price' => $offering->price,
124
-                ];
125
-            })
126
-            ->afterCreating(function (DocumentLineItem $lineItem) {
127
-                $offering = $lineItem->offering;
120
+                ]);
128
 
121
 
129
-                if ($offering) {
130
-                    $lineItem->purchaseTaxes()->syncWithoutDetaching($offering->purchaseTaxes->pluck('id')->toArray());
131
-                    $lineItem->purchaseDiscounts()->syncWithoutDetaching($offering->purchaseDiscounts->pluck('id')->toArray());
132
-                }
122
+                $lineItem->purchaseTaxes()->syncWithoutDetaching($offering->purchaseTaxes->pluck('id')->toArray());
123
+                $lineItem->purchaseDiscounts()->syncWithoutDetaching($offering->purchaseDiscounts->pluck('id')->toArray());
133
 
124
 
134
                 $lineItem->refresh();
125
                 $lineItem->refresh();
135
 
126
 

+ 41
- 57
database/factories/Common/OfferingFactory.php 查看文件

34
             'description' => $this->faker->sentence,
34
             'description' => $this->faker->sentence,
35
             'type' => $this->faker->randomElement(OfferingType::cases()),
35
             'type' => $this->faker->randomElement(OfferingType::cases()),
36
             'price' => $this->faker->numberBetween(5, 1000),
36
             'price' => $this->faker->numberBetween(5, 1000),
37
-            'sellable' => $this->faker->boolean(80),
38
-            'purchasable' => $this->faker->boolean(80),
39
-            'income_account_id' => function (array $attributes) {
40
-                return $attributes['sellable'] ? 10 : null;
41
-            },
42
-            'expense_account_id' => function (array $attributes) {
43
-                return $attributes['purchasable'] ? $this->faker->numberBetween(17, 35) : null;
44
-            },
37
+            'sellable' => false,
38
+            'purchasable' => false,
39
+            'income_account_id' => null,
40
+            'expense_account_id' => null,
45
             'created_by' => 1,
41
             'created_by' => 1,
46
             'updated_by' => 1,
42
             'updated_by' => 1,
47
         ];
43
         ];
48
     }
44
     }
49
 
45
 
50
-    public function sellable(): self
46
+    public function withSalesAdjustments(): self
51
     {
47
     {
52
-        $incomeAccount = Account::query()
53
-            ->where('category', AccountCategory::Revenue)
54
-            ->where('type', AccountType::OperatingRevenue)
55
-            ->inRandomOrder()
56
-            ->first();
48
+        return $this->afterCreating(function (Offering $offering) {
49
+            $incomeAccount = Account::query()
50
+                ->where('company_id', $offering->company_id)
51
+                ->where('category', AccountCategory::Revenue)
52
+                ->where('type', AccountType::OperatingRevenue)
53
+                ->inRandomOrder()
54
+                ->firstOrFail();
57
 
55
 
58
-        return $this->state(function (array $attributes) use ($incomeAccount) {
59
-            return [
56
+            $offering->updateQuietly([
60
                 'sellable' => true,
57
                 'sellable' => true,
61
-                'income_account_id' => $incomeAccount?->id ?? 10,
62
-            ];
63
-        });
64
-    }
65
-
66
-    public function purchasable(): self
67
-    {
68
-        $expenseAccount = Account::query()
69
-            ->where('category', AccountCategory::Expense)
70
-            ->where('type', AccountType::OperatingExpense)
71
-            ->inRandomOrder()
72
-            ->first();
73
-
74
-        return $this->state(function (array $attributes) use ($expenseAccount) {
75
-            return [
76
-                'purchasable' => true,
77
-                'expense_account_id' => $expenseAccount?->id ?? $this->faker->numberBetween(17, 35),
78
-            ];
79
-        });
80
-    }
58
+                'income_account_id' => $incomeAccount->id,
59
+            ]);
81
 
60
 
82
-    public function withSalesAdjustments(): self
83
-    {
84
-        return $this->afterCreating(function (Offering $offering) {
85
-            if ($offering->sellable) {
86
-                $adjustments = $offering->company?->adjustments()
87
-                    ->where('type', AdjustmentType::Sales)
88
-                    ->pluck('id');
61
+            $adjustments = $offering->company?->adjustments()
62
+                ->where('type', AdjustmentType::Sales)
63
+                ->pluck('id');
89
 
64
 
90
-                $adjustmentsToAttach = $adjustments->isNotEmpty()
91
-                    ? $adjustments->random(min(2, $adjustments->count()))
92
-                    : Adjustment::factory()->salesTax()->count(2)->create()->pluck('id');
65
+            $adjustmentsToAttach = $adjustments->isNotEmpty()
66
+                ? $adjustments->random(min(2, $adjustments->count()))
67
+                : Adjustment::factory()->salesTax()->count(2)->create()->pluck('id');
93
 
68
 
94
-                $offering->salesAdjustments()->attach($adjustmentsToAttach);
95
-            }
69
+            $offering->salesAdjustments()->attach($adjustmentsToAttach);
96
         });
70
         });
97
     }
71
     }
98
 
72
 
99
     public function withPurchaseAdjustments(): self
73
     public function withPurchaseAdjustments(): self
100
     {
74
     {
101
         return $this->afterCreating(function (Offering $offering) {
75
         return $this->afterCreating(function (Offering $offering) {
102
-            if ($offering->purchasable) {
103
-                $adjustments = $offering->company?->adjustments()
104
-                    ->where('type', AdjustmentType::Purchase)
105
-                    ->pluck('id');
76
+            $expenseAccount = Account::query()
77
+                ->where('company_id', $offering->company_id)
78
+                ->where('category', AccountCategory::Expense)
79
+                ->where('type', AccountType::OperatingExpense)
80
+                ->inRandomOrder()
81
+                ->firstOrFail();
82
+
83
+            $offering->updateQuietly([
84
+                'purchasable' => true,
85
+                'expense_account_id' => $expenseAccount->id,
86
+            ]);
87
+
88
+            $adjustments = $offering->company?->adjustments()
89
+                ->where('type', AdjustmentType::Purchase)
90
+                ->pluck('id');
106
 
91
 
107
-                $adjustmentsToAttach = $adjustments->isNotEmpty()
108
-                    ? $adjustments->random(min(2, $adjustments->count()))
109
-                    : Adjustment::factory()->purchaseTax()->count(2)->create()->pluck('id');
92
+            $adjustmentsToAttach = $adjustments->isNotEmpty()
93
+                ? $adjustments->random(min(2, $adjustments->count()))
94
+                : Adjustment::factory()->purchaseTax()->count(2)->create()->pluck('id');
110
 
95
 
111
-                $offering->purchaseAdjustments()->attach($adjustmentsToAttach);
112
-            }
96
+            $offering->purchaseAdjustments()->attach($adjustmentsToAttach);
113
         });
97
         });
114
     }
98
     }
115
 }
99
 }

+ 0
- 2
database/factories/CompanyFactory.php 查看文件

111
         return $this->afterCreating(function (Company $company) use ($count) {
111
         return $this->afterCreating(function (Company $company) use ($count) {
112
             Offering::factory()
112
             Offering::factory()
113
                 ->count($count)
113
                 ->count($count)
114
-                ->sellable()
115
                 ->withSalesAdjustments()
114
                 ->withSalesAdjustments()
116
-                ->purchasable()
117
                 ->withPurchaseAdjustments()
115
                 ->withPurchaseAdjustments()
118
                 ->create([
116
                 ->create([
119
                     'company_id' => $company->id,
117
                     'company_id' => $company->id,

Loading…
取消
儲存