瀏覽代碼

wip: Accounting Module

3.x
wallo 2 年之前
父節點
當前提交
df54a41330
共有 34 個文件被更改,包括 496 次插入234 次删除
  1. 3
    6
      app/Filament/Resources/AccountResource.php
  2. 47
    31
      app/Filament/Resources/CurrencyResource.php
  3. 4
    8
      app/Filament/Resources/CustomerResource.php
  4. 2
    1
      app/Filament/Resources/InvoiceResource.php
  5. 17
    1
      app/Http/Livewire/DefaultSetting.php
  6. 0
    16
      app/Models/Banking/Account.php
  7. 9
    0
      app/Models/Setting/Currency.php
  8. 48
    0
      app/Models/Setting/DefaultSetting.php
  9. 0
    67
      app/Observers/AccountObserver.php
  10. 67
    0
      app/Policies/CurrencyPolicy.php
  11. 0
    11
      app/Providers/EventServiceProvider.php
  12. 55
    0
      app/Services/CurrencyService.php
  13. 6
    6
      composer.lock
  14. 11
    48
      database/factories/CurrencyFactory.php
  15. 2
    1
      database/factories/DefaultSettingFactory.php
  16. 9
    1
      database/factories/DiscountFactory.php
  17. 1
    0
      database/factories/TaxFactory.php
  18. 1
    1
      database/migrations/2023_05_10_040940_create_currencies_table.php
  19. 1
    1
      database/migrations/2023_05_11_044321_create_accounts_table.php
  20. 1
    1
      database/migrations/2023_05_12_042255_create_categories_table.php
  21. 2
    2
      database/migrations/2023_05_19_042232_create_contacts_table.php
  22. 4
    4
      database/migrations/2023_05_20_080131_create_taxes_table.php
  23. 4
    4
      database/migrations/2023_05_21_163808_create_discounts_table.php
  24. 4
    4
      database/migrations/2023_05_22_073252_create_items_table.php
  25. 4
    4
      database/migrations/2023_05_23_141215_create_documents_table.php
  26. 3
    3
      database/migrations/2023_05_23_151550_create_document_items_table.php
  27. 4
    4
      database/migrations/2023_05_23_173412_create_document_totals_table.php
  28. 2
    0
      database/migrations/2023_07_03_054805_create_default_settings_table.php
  29. 0
    1
      database/seeders/AccountSeeder.php
  30. 0
    1
      database/seeders/CategorySeeder.php
  31. 9
    4
      database/seeders/CurrencySeeder.php
  32. 111
    0
      database/seeders/DatabaseSeeder.php
  33. 65
    0
      database/seeders/DiscountSeeder.php
  34. 0
    3
      database/seeders/TaxSeeder.php

+ 3
- 6
app/Filament/Resources/AccountResource.php 查看文件

2
 
2
 
3
 namespace App\Filament\Resources;
3
 namespace App\Filament\Resources;
4
 
4
 
5
-use Akaunting\Money\Currency;
6
-use Akaunting\Money\Money;
7
 use App\Actions\Banking\CreateCurrencyFromAccount;
5
 use App\Actions\Banking\CreateCurrencyFromAccount;
8
 use App\Filament\Resources\AccountResource\Pages;
6
 use App\Filament\Resources\AccountResource\Pages;
9
 use App\Filament\Resources\AccountResource\RelationManagers;
7
 use App\Filament\Resources\AccountResource\RelationManagers;
10
 use App\Models\Banking\Account;
8
 use App\Models\Banking\Account;
9
+use App\Models\Setting\Currency;
11
 use Closure;
10
 use Closure;
12
 use Exception;
11
 use Exception;
13
 use Filament\Forms;
12
 use Filament\Forms;
18
 use Filament\Resources\Table;
17
 use Filament\Resources\Table;
19
 use Filament\Tables;
18
 use Filament\Tables;
20
 use Illuminate\Database\Eloquent\Builder;
19
 use Illuminate\Database\Eloquent\Builder;
21
-use Illuminate\Database\Eloquent\Model;
22
-use Illuminate\Database\Eloquent\SoftDeletingScope;
23
 use Illuminate\Support\Collection;
20
 use Illuminate\Support\Collection;
24
 use Illuminate\Support\Facades\Auth;
21
 use Illuminate\Support\Facades\Auth;
25
 use Illuminate\Support\Facades\DB;
22
 use Illuminate\Support\Facades\DB;
81
                                     ->label('Currency')
78
                                     ->label('Currency')
82
                                     ->relationship('currency', 'name', static fn (Builder $query) => $query->where('company_id', Auth::user()->currentCompany->id))
79
                                     ->relationship('currency', 'name', static fn (Builder $query) => $query->where('company_id', Auth::user()->currentCompany->id))
83
                                     ->preload()
80
                                     ->preload()
84
-                                    ->default(Account::getDefaultCurrencyCode())
81
+                                    ->default(Currency::getDefaultCurrency())
85
                                     ->searchable()
82
                                     ->searchable()
86
                                     ->reactive()
83
                                     ->reactive()
87
                                     ->required()
84
                                     ->required()
89
                                         Forms\Components\Select::make('currency.code')
86
                                         Forms\Components\Select::make('currency.code')
90
                                             ->label('Code')
87
                                             ->label('Code')
91
                                             ->searchable()
88
                                             ->searchable()
92
-                                            ->options(Account::getCurrencyCodes())
89
+                                            ->options(Currency::getCurrencyCodes())
93
                                             ->reactive()
90
                                             ->reactive()
94
                                             ->afterStateUpdated(static function (callable $set, $state) {
91
                                             ->afterStateUpdated(static function (callable $set, $state) {
95
                                                 $code = $state;
92
                                                 $code = $state;

+ 47
- 31
app/Filament/Resources/CurrencyResource.php 查看文件

6
 use App\Filament\Resources\CurrencyResource\RelationManagers;
6
 use App\Filament\Resources\CurrencyResource\RelationManagers;
7
 use App\Models\Banking\Account;
7
 use App\Models\Banking\Account;
8
 use App\Models\Setting\Currency;
8
 use App\Models\Setting\Currency;
9
+use App\Services\CurrencyService;
9
 use Closure;
10
 use Closure;
10
 use Exception;
11
 use Exception;
11
 use Filament\Forms;
12
 use Filament\Forms;
15
 use Filament\Resources\Table;
16
 use Filament\Resources\Table;
16
 use Filament\Tables;
17
 use Filament\Tables;
17
 use Illuminate\Database\Eloquent\Builder;
18
 use Illuminate\Database\Eloquent\Builder;
18
-use Illuminate\Database\Eloquent\Model;
19
-use Illuminate\Database\Eloquent\SoftDeletingScope;
20
 use Illuminate\Support\Collection;
19
 use Illuminate\Support\Collection;
21
 use Illuminate\Support\Facades\Auth;
20
 use Illuminate\Support\Facades\Auth;
22
-use Illuminate\Support\Facades\Blade;
23
-use Illuminate\Support\HtmlString;
21
+use Illuminate\Support\Facades\Cache;
24
 use Wallo\FilamentSelectify\Components\ToggleButton;
22
 use Wallo\FilamentSelectify\Components\ToggleButton;
25
 
23
 
26
 class CurrencyResource extends Resource
24
 class CurrencyResource extends Resource
41
         return $form
39
         return $form
42
             ->schema([
40
             ->schema([
43
                 Forms\Components\Section::make('General')
41
                 Forms\Components\Section::make('General')
44
-                    ->description('The default currency is used for all transactions and reports and cannot be deleted. Currency precision determines the number of decimal places to display when formatting currency amounts. The currency rate is set to 1 for the default currency and is utilized as the basis for setting exchange rates for all other currencies.')
42
+                    ->description('Upon selecting a currency code, the corresponding values based on real-world currencies will auto-populate. The default currency is used for all transactions and reports and cannot be deleted. Currency precision determines the number of decimal places to display when formatting currency amounts. The currency rate is set to 1 for the default currency and is utilized as the basis for setting exchange rates for all other currencies. Alterations to default values are allowed but manage such changes wisely as any confusion or discrepancies are your responsibility.')
45
                     ->schema([
43
                     ->schema([
46
                         Forms\Components\Select::make('code')
44
                         Forms\Components\Select::make('code')
47
                             ->label('Code')
45
                             ->label('Code')
49
                             ->searchable()
47
                             ->searchable()
50
                             ->placeholder('Select a currency code...')
48
                             ->placeholder('Select a currency code...')
51
                             ->reactive()
49
                             ->reactive()
50
+                            ->hidden(static fn (Closure $get): bool => $get('enabled'))
52
                             ->afterStateUpdated(static function (Closure $set, $state) {
51
                             ->afterStateUpdated(static function (Closure $set, $state) {
52
+                                if ($state === null) {
53
+                                    return;
54
+                                }
55
+
53
                                 $code = $state;
56
                                 $code = $state;
54
-                                $name = config("money.{$code}.name");
55
-                                $precision = config("money.{$code}.precision");
56
-                                $symbol = config("money.{$code}.symbol");
57
-                                $symbol_first = config("money.{$code}.symbol_first");
58
-                                $decimal_mark = config("money.{$code}.decimal_mark");
59
-                                $thousands_separator = config("money.{$code}.thousands_separator");
60
-
61
-                                $set('name', $name);
62
-                                $set('precision', $precision);
63
-                                $set('symbol', $symbol);
64
-                                $set('symbol_first', $symbol_first);
65
-                                $set('decimal_mark', $decimal_mark);
66
-                                $set('thousands_separator', $thousands_separator);
57
+                                $currencyConfig = config("money.{$code}", []);
58
+                                $currencyService = app(CurrencyService::class);
59
+
60
+                                $defaultCurrency = Currency::getDefaultCurrency();
61
+
62
+                                $rate = 1;
63
+
64
+                                if ($defaultCurrency !== null) {
65
+                                   $rate = $currencyService->getCachedExchangeRate($defaultCurrency, $code);
66
+                                }
67
+
68
+                                $set('name', $currencyConfig['name'] ?? '');
69
+                                $set('rate', $rate);
70
+                                $set('precision', $currencyConfig['precision'] ?? '');
71
+                                $set('symbol', $currencyConfig['symbol'] ?? '');
72
+                                $set('symbol_first', $currencyConfig['symbol_first'] ?? '');
73
+                                $set('decimal_mark', $currencyConfig['decimal_mark'] ?? '');
74
+                                $set('thousands_separator', $currencyConfig['thousands_separator'] ?? '');
67
                             })
75
                             })
68
                             ->required(),
76
                             ->required(),
77
+                        Forms\Components\TextInput::make('code')
78
+                            ->label('Code')
79
+                            ->hidden(static fn (Closure $get): bool => !$get('enabled'))
80
+                            ->disabled(static fn (Closure $get): bool => $get('enabled'))
81
+                            ->required(),
69
                         Forms\Components\TextInput::make('name')
82
                         Forms\Components\TextInput::make('name')
70
                             ->translateLabel()
83
                             ->translateLabel()
71
                             ->maxLength(100)
84
                             ->maxLength(100)
72
                             ->required(),
85
                             ->required(),
73
                         Forms\Components\TextInput::make('rate')
86
                         Forms\Components\TextInput::make('rate')
74
                             ->label('Rate')
87
                             ->label('Rate')
75
-                            ->dehydrateStateUsing(static fn (Closure $get, $state): bool => $get('enabled') === true ? '1' : $state) // rate is 1 when enabled is true
88
+                            ->dehydrateStateUsing(static fn (Closure $get, $state) => $get('enabled') ? '1' : $state)
76
                             ->numeric()
89
                             ->numeric()
77
                             ->reactive()
90
                             ->reactive()
78
-                            ->disabled(static fn (Closure $get): bool => $get('enabled') === true) // disabled is true when enabled is true
79
-                            ->mask(static fn (Forms\Components\TextInput\Mask $mask) => $mask
80
-                                ->numeric()
81
-                                ->decimalPlaces(4)
82
-                                ->signed(false)
83
-                                ->padFractionalZeros(false)
84
-                                ->normalizeZeros(false)
85
-                                ->minValue(0.0001)
86
-                                ->maxValue(999999.9999)
87
-                                ->lazyPlaceholder(false))
91
+                            ->disabled(static fn (Closure $get): bool => $get('enabled'))
88
                             ->required(),
92
                             ->required(),
89
                         Forms\Components\Select::make('precision')
93
                         Forms\Components\Select::make('precision')
90
                             ->label('Precision')
94
                             ->label('Precision')
114
                             ->reactive()
118
                             ->reactive()
115
                             ->offColor('danger')
119
                             ->offColor('danger')
116
                             ->onColor('primary')
120
                             ->onColor('primary')
117
-                            ->afterStateUpdated(static fn (Closure $set, $state) => $state ? $set('rate', '1') : null),
121
+                            ->afterStateUpdated(static function (Closure $set, Closure $get, $state) {
122
+                                $enabled = $state;
123
+                                $code = $get('code');
124
+                                $currencyService = app(CurrencyService::class);
125
+
126
+                                if ($enabled) {
127
+                                    $rate = 1;
128
+                                } else {
129
+                                    $defaultCurrency = Currency::getDefaultCurrency();
130
+                                    $rate = $defaultCurrency ? $currencyService->getCachedExchangeRate($defaultCurrency, $code) : 1;
131
+                                }
132
+
133
+                                $set('rate', $rate);
134
+                            }),
118
                     ])->columns(),
135
                     ])->columns(),
119
             ]);
136
             ]);
120
     }
137
     }
144
                     ->sortable(),
161
                     ->sortable(),
145
                 Tables\Columns\TextColumn::make('rate')
162
                 Tables\Columns\TextColumn::make('rate')
146
                     ->label('Rate')
163
                     ->label('Rate')
147
-                    ->formatStateUsing(static fn ($state) => str_contains($state, '.') ? rtrim(rtrim($state, '0'), '.') : null)
148
                     ->searchable()
164
                     ->searchable()
149
                     ->sortable(),
165
                     ->sortable(),
150
             ])
166
             ])

+ 4
- 8
app/Filament/Resources/CustomerResource.php 查看文件

5
 use App\Actions\Banking\CreateCurrencyFromAccount;
5
 use App\Actions\Banking\CreateCurrencyFromAccount;
6
 use App\Filament\Resources\CustomerResource\Pages;
6
 use App\Filament\Resources\CustomerResource\Pages;
7
 use App\Filament\Resources\CustomerResource\RelationManagers;
7
 use App\Filament\Resources\CustomerResource\RelationManagers;
8
+use App\Models\Setting\Currency;
8
 use Wallo\FilamentSelectify\Components\ButtonGroup;
9
 use Wallo\FilamentSelectify\Components\ButtonGroup;
9
-use App\Models\Banking\Account;
10
 use App\Models\Contact;
10
 use App\Models\Contact;
11
 use Filament\Forms;
11
 use Filament\Forms;
12
 use Filament\Resources\Form;
12
 use Filament\Resources\Form;
14
 use Filament\Resources\Table;
14
 use Filament\Resources\Table;
15
 use Filament\Tables;
15
 use Filament\Tables;
16
 use Illuminate\Database\Eloquent\Builder;
16
 use Illuminate\Database\Eloquent\Builder;
17
-use Illuminate\Database\Eloquent\SoftDeletingScope;
18
 use Illuminate\Support\Facades\Auth;
17
 use Illuminate\Support\Facades\Auth;
19
 use Illuminate\Support\Facades\DB;
18
 use Illuminate\Support\Facades\DB;
20
-use Squire\Models\Country;
21
-use Squire\Models\Region;
22
 
19
 
23
 class CustomerResource extends Resource
20
 class CustomerResource extends Resource
24
 {
21
 {
55
                                     ])
52
                                     ])
56
                                     ->gridDirection('column')
53
                                     ->gridDirection('column')
57
                                     ->default('individual')
54
                                     ->default('individual')
58
-                                    ->columnSpan(1)
59
-                                    ->required(),
55
+                                    ->columnSpan(1),
60
                                 Forms\Components\Grid::make()
56
                                 Forms\Components\Grid::make()
61
                                     ->schema([
57
                                     ->schema([
62
                                         Forms\Components\TextInput::make('name')
58
                                         Forms\Components\TextInput::make('name')
94
                             ->label('Currency')
90
                             ->label('Currency')
95
                             ->relationship('currency', 'name', static fn (Builder $query) => $query->where('company_id', Auth::user()->currentCompany->id))
91
                             ->relationship('currency', 'name', static fn (Builder $query) => $query->where('company_id', Auth::user()->currentCompany->id))
96
                             ->preload()
92
                             ->preload()
97
-                            ->default(Account::getDefaultCurrencyCode())
93
+                            ->default(Currency::getDefaultCurrency())
98
                             ->searchable()
94
                             ->searchable()
99
                             ->reactive()
95
                             ->reactive()
100
                             ->required()
96
                             ->required()
102
                                 Forms\Components\Select::make('currency.code')
98
                                 Forms\Components\Select::make('currency.code')
103
                                     ->label('Code')
99
                                     ->label('Code')
104
                                     ->searchable()
100
                                     ->searchable()
105
-                                    ->options(Account::getCurrencyCodes())
101
+                                    ->options(Currency::getCurrencyCodes())
106
                                     ->reactive()
102
                                     ->reactive()
107
                                     ->afterStateUpdated(static function (callable $set, $state) {
103
                                     ->afterStateUpdated(static function (callable $set, $state) {
108
                                         $code = $state;
104
                                         $code = $state;

+ 2
- 1
app/Filament/Resources/InvoiceResource.php 查看文件

4
 
4
 
5
 use App\Filament\Resources\InvoiceResource\Pages;
5
 use App\Filament\Resources\InvoiceResource\Pages;
6
 use App\Filament\Resources\InvoiceResource\RelationManagers;
6
 use App\Filament\Resources\InvoiceResource\RelationManagers;
7
+use App\Models\Setting\Currency;
7
 use Wallo\FilamentSelectify\Components\ButtonGroup;
8
 use Wallo\FilamentSelectify\Components\ButtonGroup;
8
 use App\Models\Banking\Account;
9
 use App\Models\Banking\Account;
9
 use App\Models\Document\Document;
10
 use App\Models\Document\Document;
75
                                             ->label('Currency')
76
                                             ->label('Currency')
76
                                             ->relationship('currency', 'name', static fn (Builder $query) => $query->where('company_id', Auth::user()->currentCompany->id))
77
                                             ->relationship('currency', 'name', static fn (Builder $query) => $query->where('company_id', Auth::user()->currentCompany->id))
77
                                             ->preload()
78
                                             ->preload()
78
-                                            ->default(Account::getDefaultCurrencyCode())
79
+                                            ->default(Currency::getDefaultCurrency())
79
                                             ->searchable()
80
                                             ->searchable()
80
                                             ->reactive()
81
                                             ->reactive()
81
                                             ->required(),
82
                                             ->required(),

+ 17
- 1
app/Http/Livewire/DefaultSetting.php 查看文件

56
                         ->validationAttribute('Currency')
56
                         ->validationAttribute('Currency')
57
                         ->required(),
57
                         ->required(),
58
                 ])->columns(),
58
                 ])->columns(),
59
-            Section::make('Taxes')
59
+            Section::make('Taxes & Discounts')
60
                 ->schema([
60
                 ->schema([
61
                     Select::make('sales_tax_id')
61
                     Select::make('sales_tax_id')
62
                         ->label('Sales Tax')
62
                         ->label('Sales Tax')
72
                         ->searchable()
72
                         ->searchable()
73
                         ->validationAttribute('Purchase Tax')
73
                         ->validationAttribute('Purchase Tax')
74
                         ->required(),
74
                         ->required(),
75
+                    Select::make('sales_discount_id')
76
+                        ->label('Sales Discount')
77
+                        ->options(Defaults::getSalesDiscounts())
78
+                        ->default(Defaults::getDefaultSalesDiscount())
79
+                        ->searchable()
80
+                        ->validationAttribute('Sales Discount')
81
+                        ->required(),
82
+                    Select::make('purchase_discount_id')
83
+                        ->label('Purchase Discount')
84
+                        ->options(Defaults::getPurchaseDiscounts())
85
+                        ->default(Defaults::getDefaultPurchaseDiscount())
86
+                        ->searchable()
87
+                        ->validationAttribute('Purchase Discount')
88
+                        ->required(),
75
                 ])->columns(),
89
                 ])->columns(),
76
             Section::make('Categories')
90
             Section::make('Categories')
77
                 ->schema([
91
                 ->schema([
121
             'currency_code' => [Currency::class, 'code'],
135
             'currency_code' => [Currency::class, 'code'],
122
             'sales_tax_id' => [Tax::class, 'id', 'sales'],
136
             'sales_tax_id' => [Tax::class, 'id', 'sales'],
123
             'purchase_tax_id' => [Tax::class, 'id', 'purchase'],
137
             'purchase_tax_id' => [Tax::class, 'id', 'purchase'],
138
+            'sales_discount_id' => [Tax::class, 'id', 'sales'],
139
+            'purchase_discount_id' => [Tax::class, 'id', 'purchase'],
124
             'income_category_id' => [Category::class, 'id', 'income'],
140
             'income_category_id' => [Category::class, 'id', 'income'],
125
             'expense_category_id' => [Category::class, 'id', 'expense'],
141
             'expense_category_id' => [Category::class, 'id', 'expense'],
126
         ];
142
         ];

+ 0
- 16
app/Models/Banking/Account.php 查看文件

101
         ];
101
         ];
102
     }
102
     }
103
 
103
 
104
-    public static function getCurrencyCodes(): array
105
-    {
106
-        $codes = array_keys(Config::get('money'));
107
-
108
-        return array_combine($codes, $codes);
109
-    }
110
-
111
-    public static function getDefaultCurrencyCode(): ?string
112
-    {
113
-        $defaultCurrency = Currency::where('enabled', true)
114
-            ->where('company_id', Auth::user()->currentCompany->id)
115
-            ->first();
116
-
117
-        return $defaultCurrency?->code;
118
-    }
119
-
120
     protected static function newFactory(): Factory
104
     protected static function newFactory(): Factory
121
     {
105
     {
122
         return AccountFactory::new();
106
         return AccountFactory::new();

+ 9
- 0
app/Models/Setting/Currency.php 查看文件

63
         return array_combine($codes, $codes);
63
         return array_combine($codes, $codes);
64
     }
64
     }
65
 
65
 
66
+    public static function getDefaultCurrency(): ?string
67
+    {
68
+        $defaultCurrency = self::where('enabled', true)
69
+            ->where('company_id', Auth::user()->currentCompany->id)
70
+            ->first();
71
+
72
+        return $defaultCurrency->code ?? null;
73
+    }
74
+
66
     protected static function newFactory(): Factory
75
     protected static function newFactory(): Factory
67
     {
76
     {
68
         return CurrencyFactory::new();
77
         return CurrencyFactory::new();

+ 48
- 0
app/Models/Setting/DefaultSetting.php 查看文件

21
         'currency_code',
21
         'currency_code',
22
         'sales_tax_id',
22
         'sales_tax_id',
23
         'purchase_tax_id',
23
         'purchase_tax_id',
24
+        'sales_discount_id',
25
+        'purchase_discount_id',
24
         'income_category_id',
26
         'income_category_id',
25
         'expense_category_id',
27
         'expense_category_id',
26
         'updated_by',
28
         'updated_by',
51
         return $this->belongsTo(Tax::class,'purchase_tax_id', 'id');
53
         return $this->belongsTo(Tax::class,'purchase_tax_id', 'id');
52
     }
54
     }
53
 
55
 
56
+    public function salesDiscount(): BelongsTo
57
+    {
58
+        return $this->belongsTo(Discount::class,'sales_discount_id', 'id');
59
+    }
60
+
61
+    public function purchaseDiscount(): BelongsTo
62
+    {
63
+        return $this->belongsTo(Discount::class,'purchase_discount_id', 'id');
64
+    }
65
+
54
     public function incomeCategory(): BelongsTo
66
     public function incomeCategory(): BelongsTo
55
     {
67
     {
56
         return $this->belongsTo(Category::class,'income_category_id', 'id');
68
         return $this->belongsTo(Category::class,'income_category_id', 'id');
96
             ->toArray();
108
             ->toArray();
97
     }
109
     }
98
 
110
 
111
+    public static function getSalesDiscounts(): array
112
+    {
113
+        return Discount::where('company_id', Auth::user()->currentCompany->id)
114
+            ->where('type', 'sales')
115
+            ->pluck('name', 'id')
116
+            ->toArray();
117
+    }
118
+
119
+    public static function getPurchaseDiscounts(): array
120
+    {
121
+        return Discount::where('company_id', Auth::user()->currentCompany->id)
122
+            ->where('type', 'purchase')
123
+            ->pluck('name', 'id')
124
+            ->toArray();
125
+    }
126
+
99
     public static function getIncomeCategories(): array
127
     public static function getIncomeCategories(): array
100
     {
128
     {
101
         return Category::where('company_id', Auth::user()->currentCompany->id)
129
         return Category::where('company_id', Auth::user()->currentCompany->id)
150
         return $defaultPurchaseTax->id ?? null;
178
         return $defaultPurchaseTax->id ?? null;
151
     }
179
     }
152
 
180
 
181
+    public static function getDefaultSalesDiscount()
182
+    {
183
+        $defaultSalesDiscount = Discount::where('enabled', true)
184
+            ->where('company_id', Auth::user()->currentCompany->id)
185
+            ->where('type', 'sales')
186
+            ->first();
187
+
188
+        return $defaultSalesDiscount->id ?? null;
189
+    }
190
+
191
+    public static function getDefaultPurchaseDiscount()
192
+    {
193
+        $defaultPurchaseDiscount = Discount::where('enabled', true)
194
+            ->where('company_id', Auth::user()->currentCompany->id)
195
+            ->where('type', 'purchase')
196
+            ->first();
197
+
198
+        return $defaultPurchaseDiscount->id ?? null;
199
+    }
200
+
153
     public static function getDefaultIncomeCategory()
201
     public static function getDefaultIncomeCategory()
154
     {
202
     {
155
         $defaultIncomeCategory = Category::where('enabled', true)
203
         $defaultIncomeCategory = Category::where('enabled', true)

+ 0
- 67
app/Observers/AccountObserver.php 查看文件

1
-<?php
2
-
3
-namespace App\Observers;
4
-
5
-use App\Models\Banking\Account;
6
-use Illuminate\Support\Facades\Auth;
7
-
8
-class AccountObserver
9
-{
10
-    /**
11
-     * Handle the account "creating" event.
12
-     */
13
-    public function creating(Account $account): void
14
-    {
15
-        $account->company()->associate(Auth::user()->currentCompany->id);
16
-        $account->company_id = Auth::user()->currentCompany->id;
17
-        $account->created_by = Auth::id();
18
-    }
19
-
20
-    /**
21
-     * Handle the Account "created" event.
22
-     */
23
-    public function created(Account $account): void
24
-    {
25
-        //
26
-    }
27
-
28
-    /**
29
-     * Handle the Account "updating" event.
30
-     */
31
-    public function updating(Account $account): void
32
-    {
33
-        $account->updated_by = Auth::id();
34
-    }
35
-
36
-    /**
37
-     * Handle the Account "updated" event.
38
-     */
39
-    public function updated(Account $account): void
40
-    {
41
-        //
42
-    }
43
-
44
-    /**
45
-     * Handle the Account "deleted" event.
46
-     */
47
-    public function deleted(Account $account): void
48
-    {
49
-        //
50
-    }
51
-
52
-    /**
53
-     * Handle the Account "restored" event.
54
-     */
55
-    public function restored(Account $account): void
56
-    {
57
-        //
58
-    }
59
-
60
-    /**
61
-     * Handle the Account "force deleted" event.
62
-     */
63
-    public function forceDeleted(Account $account): void
64
-    {
65
-        //
66
-    }
67
-}

+ 67
- 0
app/Policies/CurrencyPolicy.php 查看文件

1
+<?php
2
+
3
+namespace App\Policies;
4
+
5
+use App\Models\Setting\Currency;
6
+use App\Models\User;
7
+
8
+class CurrencyPolicy
9
+{
10
+    /**
11
+     * Determine whether the user can view any models.
12
+     */
13
+    public function viewAny(User $user): bool
14
+    {
15
+        return true;
16
+    }
17
+
18
+    /**
19
+     * Determine whether the user can view the model.
20
+     */
21
+    public function view(User $user, Currency $currency): bool
22
+    {
23
+        return $user->belongsToCompany($currency->company);
24
+    }
25
+
26
+    /**
27
+     * Determine whether the user can create models.
28
+     */
29
+    public function create(User $user): bool
30
+    {
31
+        return true;
32
+    }
33
+
34
+    /**
35
+     * Determine whether the user can update the model.
36
+     */
37
+    public function update(User $user, Currency $currency): bool
38
+    {
39
+        $defaultCurrency = Currency::getDefaultCurrency();
40
+
41
+        return $user->belongsToCompany($currency->company) && $currency->code !== $defaultCurrency;
42
+    }
43
+
44
+    /**
45
+     * Determine whether the user can delete the model.
46
+     */
47
+    public function delete(User $user, Currency $currency): bool
48
+    {
49
+        return $user->belongsToCompany($currency->company);
50
+    }
51
+
52
+    /**
53
+     * Determine whether the user can restore the model.
54
+     */
55
+    public function restore(User $user): bool
56
+    {
57
+        return true;
58
+    }
59
+
60
+    /**
61
+     * Determine whether the user can permanently delete the model.
62
+     */
63
+    public function forceDelete(User $user): bool
64
+    {
65
+        return true;
66
+    }
67
+}

+ 0
- 11
app/Providers/EventServiceProvider.php 查看文件

2
 
2
 
3
 namespace App\Providers;
3
 namespace App\Providers;
4
 
4
 
5
-use App\Models\Banking\Account;
6
-use App\Observers\AccountObserver;
7
 use Illuminate\Auth\Events\Registered;
5
 use Illuminate\Auth\Events\Registered;
8
 use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
6
 use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
9
 use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
7
 use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
37
     {
35
     {
38
         return false;
36
         return false;
39
     }
37
     }
40
-
41
-    /**
42
-     * The model observers for the application.
43
-     *
44
-     * @var array
45
-     */
46
-    protected $observers = [
47
-        Account::class => [AccountObserver::class],
48
-    ];
49
 }
38
 }

+ 55
- 0
app/Services/CurrencyService.php 查看文件

1
+<?php
2
+
3
+namespace App\Services;
4
+
5
+use Illuminate\Support\Carbon;
6
+use Illuminate\Support\Facades\Cache;
7
+use Illuminate\Support\Facades\Http;
8
+
9
+class CurrencyService
10
+{
11
+    public function getExchangeRate($from, $to)
12
+    {
13
+        $date = Carbon::today()->format('Y-m-d');
14
+
15
+        $req_url = 'https://api.exchangerate.host/convert?from=' . $from . '&to=' . $to . '&date=' . $date;
16
+
17
+        $response = Http::get($req_url);
18
+
19
+        if ($response->successful()) {
20
+            $responseData = $response->json();
21
+            if ($responseData['success'] === true) {
22
+                return $responseData['info']['rate'];
23
+            }
24
+        }
25
+
26
+        return null;
27
+    }
28
+
29
+    public function getCachedExchangeRate(string $defaultCurrencyCode, string $code): ?float
30
+    {
31
+        // Include both the default currency code and the target currency code in the cache key
32
+        $cacheKey = 'currency_data_' . $defaultCurrencyCode . '_' . $code;
33
+
34
+        // Attempt to retrieve the cached exchange rate
35
+        $cachedData = Cache::get($cacheKey);
36
+
37
+        // If the cached exchange rate exists, return it
38
+        if ($cachedData !== null) {
39
+            return $cachedData['rate'];
40
+        }
41
+
42
+        // If the cached exchange rate does not exist, retrieve it from the API
43
+        $rate = $this->getExchangeRate($defaultCurrencyCode, $code);
44
+
45
+        // If the API call was successful, cache the exchange rate
46
+        if ($rate !== null) {
47
+            // Store the exchange rate in the cache for 24 hours
48
+            $dataToCache = compact('rate');
49
+            $expirationTimeInSeconds = 60 * 60 * 24; // 24 hours
50
+            Cache::put($cacheKey, $dataToCache, $expirationTimeInSeconds);
51
+        }
52
+
53
+        return $rate;
54
+    }
55
+}

+ 6
- 6
composer.lock 查看文件

9373
         },
9373
         },
9374
         {
9374
         {
9375
             "name": "phpunit/phpunit",
9375
             "name": "phpunit/phpunit",
9376
-            "version": "10.2.3",
9376
+            "version": "10.2.4",
9377
             "source": {
9377
             "source": {
9378
                 "type": "git",
9378
                 "type": "git",
9379
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
9379
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
9380
-                "reference": "35c8cac1734ede2ae354a6644f7088356ff5b08e"
9380
+                "reference": "68484779b5a2ed711fbdeba6ca01910d87acdff2"
9381
             },
9381
             },
9382
             "dist": {
9382
             "dist": {
9383
                 "type": "zip",
9383
                 "type": "zip",
9384
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/35c8cac1734ede2ae354a6644f7088356ff5b08e",
9385
-                "reference": "35c8cac1734ede2ae354a6644f7088356ff5b08e",
9384
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/68484779b5a2ed711fbdeba6ca01910d87acdff2",
9385
+                "reference": "68484779b5a2ed711fbdeba6ca01910d87acdff2",
9386
                 "shasum": ""
9386
                 "shasum": ""
9387
             },
9387
             },
9388
             "require": {
9388
             "require": {
9454
             "support": {
9454
             "support": {
9455
                 "issues": "https://github.com/sebastianbergmann/phpunit/issues",
9455
                 "issues": "https://github.com/sebastianbergmann/phpunit/issues",
9456
                 "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
9456
                 "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
9457
-                "source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.3"
9457
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.4"
9458
             },
9458
             },
9459
             "funding": [
9459
             "funding": [
9460
                 {
9460
                 {
9470
                     "type": "tidelift"
9470
                     "type": "tidelift"
9471
                 }
9471
                 }
9472
             ],
9472
             ],
9473
-            "time": "2023-06-30T06:17:38+00:00"
9473
+            "time": "2023-07-10T04:06:08+00:00"
9474
         },
9474
         },
9475
         {
9475
         {
9476
             "name": "psr/cache",
9476
             "name": "psr/cache",

+ 11
- 48
database/factories/CurrencyFactory.php 查看文件

3
 namespace Database\Factories;
3
 namespace Database\Factories;
4
 
4
 
5
 use App\Models\Setting\Currency;
5
 use App\Models\Setting\Currency;
6
+use Config;
6
 use Illuminate\Database\Eloquent\Factories\Factory;
7
 use Illuminate\Database\Eloquent\Factories\Factory;
7
 
8
 
8
 /**
9
 /**
17
      */
18
      */
18
     protected $model = Currency::class;
19
     protected $model = Currency::class;
19
 
20
 
20
-
21
     /**
21
     /**
22
      * Define the model's default state.
22
      * Define the model's default state.
23
      *
23
      *
25
      */
25
      */
26
     public function definition(): array
26
     public function definition(): array
27
     {
27
     {
28
-        $currencies = config('money');
29
-
30
-        $existingCodes = Currency::query()->pluck('code')->toArray();
31
-
32
-        foreach ($existingCodes as $code) {
33
-            unset($currencies[$code]);
34
-        }
35
-
36
-        $randomCode = $this->faker->randomElement(array_keys($currencies));
37
-
38
-        $code = $randomCode;
39
-
40
-        $currency = $currencies[$randomCode];
28
+        $defaultCurrency = Config::get('money.USD');
41
 
29
 
42
         return [
30
         return [
43
-            'name' => $currency['name'],
44
-            'code' => $code,
45
-            'rate' => $this->faker->randomFloat($currency['precision'], 1, 10),
46
-            'precision' => $currency['precision'],
47
-            'symbol' => $currency['symbol'],
48
-            'symbol_first' => $currency['symbol_first'],
49
-            'decimal_mark' => $currency['decimal_mark'],
50
-            'thousands_separator' => $currency['thousands_separator'],
51
-            'enabled' => $this->faker->boolean,
52
-            'company_id' => $this->company->id,
31
+            'name' => $defaultCurrency['name'],
32
+            'code' => 'USD',
33
+            'rate' => 1,
34
+            'precision' => $defaultCurrency['precision'],
35
+            'symbol' => $defaultCurrency['symbol'],
36
+            'symbol_first' => $defaultCurrency['symbol_first'],
37
+            'decimal_mark' => $defaultCurrency['decimal_mark'],
38
+            'thousands_separator' => $defaultCurrency['thousands_separator'],
39
+            'enabled' => true,
53
         ];
40
         ];
54
     }
41
     }
55
-
56
-    /**
57
-     * Indicate that the currency is enabled.
58
-     */
59
-    public function enabled(): Factory
60
-    {
61
-        return $this->state(static function (array $attributes) {
62
-            return [
63
-                'enabled' => true,
64
-            ];
65
-        });
66
-    }
67
-
68
-    /**
69
-     * Indicate that the currency is disabled.
70
-     */
71
-    public function disabled(): Factory
72
-    {
73
-        return $this->state(static function (array $attributes) {
74
-            return [
75
-                'enabled' => false,
76
-            ];
77
-        });
78
-    }
79
 }
42
 }

+ 2
- 1
database/factories/DefaultSettingFactory.php 查看文件

2
 
2
 
3
 namespace Database\Factories;
3
 namespace Database\Factories;
4
 
4
 
5
+use App\Models\Setting\DefaultSetting;
5
 use Illuminate\Database\Eloquent\Factories\Factory;
6
 use Illuminate\Database\Eloquent\Factories\Factory;
6
 
7
 
7
 /**
8
 /**
8
- * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\DefaultSetting>
9
+ * @extends Factory<DefaultSetting>
9
  */
10
  */
10
 class DefaultSettingFactory extends Factory
11
 class DefaultSettingFactory extends Factory
11
 {
12
 {

+ 9
- 1
database/factories/DiscountFactory.php 查看文件

24
      */
24
      */
25
     public function definition(): array
25
     public function definition(): array
26
     {
26
     {
27
+        $startDate = $this->faker->dateTimeBetween('now', '+1 year');
28
+        $endDate = $this->faker->dateTimeBetween($startDate, strtotime('+1 year'));
29
+
27
         return [
30
         return [
28
-            //
31
+            'description' => $this->faker->sentence,
32
+            'rate' => $this->faker->randomFloat(4, 0, 20),
33
+            'computation' => $this->faker->randomElement(Discount::getComputationTypes()),
34
+            'scope' => $this->faker->randomElement(Discount::getDiscountScopes()),
35
+            'start_date' => $startDate,
36
+            'end_date' => $endDate,
29
         ];
37
         ];
30
     }
38
     }
31
 }
39
 }

+ 1
- 0
database/factories/TaxFactory.php 查看文件

25
     public function definition(): array
25
     public function definition(): array
26
     {
26
     {
27
         return [
27
         return [
28
+            'description' => $this->faker->sentence,
28
             'rate' => $this->faker->randomFloat(4, 0, 20),
29
             'rate' => $this->faker->randomFloat(4, 0, 20),
29
             'computation' => $this->faker->randomElement(Tax::getComputationTypes()),
30
             'computation' => $this->faker->randomElement(Tax::getComputationTypes()),
30
             'scope' => $this->faker->randomElement(Tax::getTaxScopes()),
31
             'scope' => $this->faker->randomElement(Tax::getTaxScopes()),

+ 1
- 1
database/migrations/2023_05_10_040940_create_currencies_table.php 查看文件

16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
17
             $table->string('name', 100);
17
             $table->string('name', 100);
18
             $table->string('code')->index();
18
             $table->string('code')->index();
19
-            $table->decimal('rate', 15, 8);
19
+            $table->double('rate', 15, 8);
20
             $table->unsignedTinyInteger('precision')->default(2);
20
             $table->unsignedTinyInteger('precision')->default(2);
21
             $table->string('symbol')->default('$');
21
             $table->string('symbol')->default('$');
22
             $table->boolean('symbol_first')->default(true);
22
             $table->boolean('symbol_first')->default(true);

+ 1
- 1
database/migrations/2023_05_11_044321_create_accounts_table.php 查看文件

18
             $table->string('name', 100)->index();
18
             $table->string('name', 100)->index();
19
             $table->string('number', 20);
19
             $table->string('number', 20);
20
             $table->string('currency_code')->default('USD');
20
             $table->string('currency_code')->default('USD');
21
-            $table->double('opening_balance', 15, 4)->default(0.00);
21
+            $table->double('opening_balance', 15, 4)->default(0.0000);
22
             $table->string('description')->nullable();
22
             $table->string('description')->nullable();
23
             $table->text('notes')->nullable();
23
             $table->text('notes')->nullable();
24
             $table->string('status')->default('open');
24
             $table->string('status')->default('open');

+ 1
- 1
database/migrations/2023_05_12_042255_create_categories_table.php 查看文件

15
             $table->id();
15
             $table->id();
16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
17
             $table->string('name')->index();
17
             $table->string('name')->index();
18
-            $table->string('type'); // expense, income, item, other
18
+            $table->string('type');
19
             $table->string('color');
19
             $table->string('color');
20
             $table->boolean('enabled')->default(true);
20
             $table->boolean('enabled')->default(true);
21
             $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
21
             $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();

+ 2
- 2
database/migrations/2023_05_19_042232_create_contacts_table.php 查看文件

14
         Schema::create('contacts', function (Blueprint $table) {
14
         Schema::create('contacts', function (Blueprint $table) {
15
             $table->id();
15
             $table->id();
16
             $table->foreignId('company_id')->constrained()->onDelete('cascade');
16
             $table->foreignId('company_id')->constrained()->onDelete('cascade');
17
-            $table->string('entity')->default('company'); // company, individual (person)
18
-            $table->string('type'); // vendor, customer, employee
17
+            $table->string('entity')->default('individual');
18
+            $table->string('type');
19
             $table->string('name');
19
             $table->string('name');
20
             $table->string('email')->nullable();
20
             $table->string('email')->nullable();
21
             $table->string('tax_number')->nullable();
21
             $table->string('tax_number')->nullable();

+ 4
- 4
database/migrations/2023_05_20_080131_create_taxes_table.php 查看文件

16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
17
             $table->string('name');
17
             $table->string('name');
18
             $table->text('description')->nullable();
18
             $table->text('description')->nullable();
19
-            $table->decimal('rate', 15, 4);
20
-            $table->string('computation')->default('percentage'); // percentage, fixed, compound, inclusive, withholding
21
-            $table->string('type')->default('sales'); // sales, purchases
22
-            $table->string('scope')->nullable(); // product, service, none
19
+            $table->double('rate', 15, 4);
20
+            $table->string('computation')->default('percentage');
21
+            $table->string('type')->default('sales');
22
+            $table->string('scope')->nullable();
23
             $table->boolean('enabled')->default(true);
23
             $table->boolean('enabled')->default(true);
24
             $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
24
             $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
25
             $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
25
             $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();

+ 4
- 4
database/migrations/2023_05_21_163808_create_discounts_table.php 查看文件

16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
17
             $table->string('name');
17
             $table->string('name');
18
             $table->text('description')->nullable();
18
             $table->text('description')->nullable();
19
-            $table->decimal('rate', 15, 4);
20
-            $table->string('computation')->default('percentage'); // percentage, fixed
21
-            $table->string('type')->default('sales'); // sales, purchases
22
-            $table->string('scope')->nullable(); // product, service, none
19
+            $table->double('rate', 15, 4);
20
+            $table->string('computation')->default('percentage');
21
+            $table->string('type')->default('sales');
22
+            $table->string('scope')->nullable();
23
             $table->dateTime('start_date')->nullable();
23
             $table->dateTime('start_date')->nullable();
24
             $table->dateTime('end_date')->nullable();
24
             $table->dateTime('end_date')->nullable();
25
             $table->boolean('enabled')->default(true);
25
             $table->boolean('enabled')->default(true);

+ 4
- 4
database/migrations/2023_05_22_073252_create_items_table.php 查看文件

14
         Schema::create('items', function (Blueprint $table) {
14
         Schema::create('items', function (Blueprint $table) {
15
             $table->id();
15
             $table->id();
16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
17
-            $table->string('type'); // product, service
17
+            $table->string('type');
18
             $table->string('name');
18
             $table->string('name');
19
             $table->string('sku')->unique();
19
             $table->string('sku')->unique();
20
             $table->string('description')->nullable();
20
             $table->string('description')->nullable();
21
-            $table->decimal('sale_price', 15, 4);
22
-            $table->decimal('purchase_price', 15, 4);
21
+            $table->double('sale_price', 15, 4);
22
+            $table->double('purchase_price', 15, 4);
23
             $table->integer('quantity')->default(1);
23
             $table->integer('quantity')->default(1);
24
-            $table->foreignId('category_id')->default(1)->constrained()->restrictOnDelete();
24
+            $table->foreignId('category_id')->nullable()->constrained()->nullOnDelete();
25
             $table->foreignId('tax_id')->nullable()->constrained()->nullOnDelete();
25
             $table->foreignId('tax_id')->nullable()->constrained()->nullOnDelete();
26
             $table->foreignId('discount_id')->nullable()->constrained()->nullOnDelete();
26
             $table->foreignId('discount_id')->nullable()->constrained()->nullOnDelete();
27
             $table->boolean('enabled')->default(true);
27
             $table->boolean('enabled')->default(true);

+ 4
- 4
database/migrations/2023_05_23_141215_create_documents_table.php 查看文件

14
         Schema::create('documents', function (Blueprint $table) {
14
         Schema::create('documents', function (Blueprint $table) {
15
             $table->id();
15
             $table->id();
16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
17
-            $table->string('type'); // invoice, bill
17
+            $table->string('type');
18
             $table->string('document_number');
18
             $table->string('document_number');
19
             $table->string('order_number')->nullable();
19
             $table->string('order_number')->nullable();
20
-            $table->string('status'); // draft, sent, paid, cancelled, approved
20
+            $table->string('status');
21
             $table->dateTime('document_date');
21
             $table->dateTime('document_date');
22
             $table->dateTime('due_date');
22
             $table->dateTime('due_date');
23
             $table->dateTime('paid_date')->nullable();
23
             $table->dateTime('paid_date')->nullable();
24
-            $table->decimal('amount', 15, 4);
24
+            $table->double('amount', 15, 4);
25
             $table->foreignId('tax_id')->nullable()->constrained()->nullOnDelete();
25
             $table->foreignId('tax_id')->nullable()->constrained()->nullOnDelete();
26
             $table->foreignId('discount_id')->nullable()->constrained()->nullOnDelete();
26
             $table->foreignId('discount_id')->nullable()->constrained()->nullOnDelete();
27
             $table->string('reference')->nullable();
27
             $table->string('reference')->nullable();
28
             $table->string('currency_code')->default('USD');
28
             $table->string('currency_code')->default('USD');
29
-            $table->foreignId('category_id')->default(1)->constrained()->restrictOnDelete();
29
+            $table->foreignId('category_id')->nullable()->constrained()->nullOnDelete();
30
             $table->foreignId('contact_id')->nullable()->constrained()->nullOnDelete();
30
             $table->foreignId('contact_id')->nullable()->constrained()->nullOnDelete();
31
             $table->text('notes')->nullable();
31
             $table->text('notes')->nullable();
32
             $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
32
             $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();

+ 3
- 3
database/migrations/2023_05_23_151550_create_document_items_table.php 查看文件

19
             $table->string('type');
19
             $table->string('type');
20
             $table->string('name');
20
             $table->string('name');
21
             $table->text('description')->nullable();
21
             $table->text('description')->nullable();
22
-            $table->decimal('quantity', 7, 2);
23
-            $table->decimal('price', 15, 4);
22
+            $table->double('quantity', 7, 2);
23
+            $table->double('price', 15, 4);
24
             $table->foreignId('tax_id')->nullable()->constrained()->nullOnDelete();
24
             $table->foreignId('tax_id')->nullable()->constrained()->nullOnDelete();
25
             $table->foreignId('discount_id')->nullable()->constrained()->nullOnDelete();
25
             $table->foreignId('discount_id')->nullable()->constrained()->nullOnDelete();
26
-            $table->decimal('total', 15, 4);
26
+            $table->double('total', 15, 4);
27
             $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
27
             $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
28
             $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
28
             $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
29
             $table->timestamps();
29
             $table->timestamps();

+ 4
- 4
database/migrations/2023_05_23_173412_create_document_totals_table.php 查看文件

18
             $table->string('type');
18
             $table->string('type');
19
             $table->string('code');
19
             $table->string('code');
20
             $table->string('name');
20
             $table->string('name');
21
-            $table->decimal('subtotal', 15, 4)->default(0);
22
-            $table->decimal('discount', 15, 4)->default(0);
23
-            $table->decimal('tax', 15, 4)->default(0);
24
-            $table->decimal('total', 15, 4);
21
+            $table->double('subtotal', 15, 4)->default(0);
22
+            $table->double('discount', 15, 4)->default(0);
23
+            $table->double('tax', 15, 4)->default(0);
24
+            $table->double('total', 15, 4);
25
             $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
25
             $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
26
             $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
26
             $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
27
             $table->timestamps();
27
             $table->timestamps();

+ 2
- 0
database/migrations/2023_07_03_054805_create_default_settings_table.php 查看文件

18
             $table->string('currency_code')->default('USD');
18
             $table->string('currency_code')->default('USD');
19
             $table->foreignId('sales_tax_id')->constrained('taxes')->restrictOnDelete();
19
             $table->foreignId('sales_tax_id')->constrained('taxes')->restrictOnDelete();
20
             $table->foreignId('purchase_tax_id')->constrained('taxes')->restrictOnDelete();
20
             $table->foreignId('purchase_tax_id')->constrained('taxes')->restrictOnDelete();
21
+            $table->foreignId('sales_discount_id')->constrained('discounts')->restrictOnDelete();
22
+            $table->foreignId('purchase_discount_id')->constrained('discounts')->restrictOnDelete();
21
             $table->foreignId('income_category_id')->constrained('categories')->restrictOnDelete();
23
             $table->foreignId('income_category_id')->constrained('categories')->restrictOnDelete();
22
             $table->foreignId('expense_category_id')->constrained('categories')->restrictOnDelete();
24
             $table->foreignId('expense_category_id')->constrained('categories')->restrictOnDelete();
23
             $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
25
             $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();

+ 0
- 1
database/seeders/AccountSeeder.php 查看文件

3
 namespace Database\Seeders;
3
 namespace Database\Seeders;
4
 
4
 
5
 use App\Models\Banking\Account;
5
 use App\Models\Banking\Account;
6
-use Illuminate\Database\Console\Seeds\WithoutModelEvents;
7
 use Illuminate\Database\Seeder;
6
 use Illuminate\Database\Seeder;
8
 
7
 
9
 class AccountSeeder extends Seeder
8
 class AccountSeeder extends Seeder

+ 0
- 1
database/seeders/CategorySeeder.php 查看文件

3
 namespace Database\Seeders;
3
 namespace Database\Seeders;
4
 
4
 
5
 use App\Models\Setting\Category;
5
 use App\Models\Setting\Category;
6
-use Illuminate\Database\Console\Seeds\WithoutModelEvents;
7
 use Illuminate\Database\Seeder;
6
 use Illuminate\Database\Seeder;
8
 use Illuminate\Support\Facades\DB;
7
 use Illuminate\Support\Facades\DB;
9
 
8
 

+ 9
- 4
database/seeders/CurrencySeeder.php 查看文件

3
 namespace Database\Seeders;
3
 namespace Database\Seeders;
4
 
4
 
5
 use App\Models\Setting\Currency;
5
 use App\Models\Setting\Currency;
6
-use Illuminate\Database\Console\Seeds\WithoutModelEvents;
7
 use Illuminate\Database\Seeder;
6
 use Illuminate\Database\Seeder;
7
+use Illuminate\Support\Facades\DB;
8
 
8
 
9
 class CurrencySeeder extends Seeder
9
 class CurrencySeeder extends Seeder
10
 {
10
 {
13
      */
13
      */
14
     public function run(): void
14
     public function run(): void
15
     {
15
     {
16
-        Currency::factory()
17
-            ->count(5)
18
-            ->create();
16
+        $companyId = DB::table('companies')->first()->id;
17
+        $userId = DB::table('users')->first()->id;
18
+
19
+        Currency::factory()->create([
20
+            'company_id' => $companyId,
21
+            'created_by' => $userId,
22
+            'updated_by' => $userId,
23
+        ]);
19
     }
24
     }
20
 }
25
 }

+ 111
- 0
database/seeders/DatabaseSeeder.php 查看文件

1
+<?php
2
+
3
+namespace Database\Seeders;
4
+
5
+use App\Models\User;
6
+use Illuminate\Database\Seeder;
7
+
8
+class DatabaseSeeder extends Seeder
9
+{
10
+    /**
11
+     * Seed the application's database.
12
+     */
13
+    public function run(): void
14
+    {
15
+        $startDate = today()->startOfYear();
16
+        $endDate = today();
17
+
18
+        // Change Company Name to ERPSAAS after Login
19
+        $firstCompanyOwner = User::factory()
20
+            ->withPersonalCompany()
21
+            ->create([
22
+                'name' => 'Admin',
23
+                'email' => 'admin@gmail.com',
24
+                'password' => bcrypt('password'),
25
+                'current_company_id' => 1,
26
+                'created_at' => $startDate->copy(),
27
+            ]);
28
+
29
+        $firstCompanyOwner->ownedCompanies->first()->update(['created_at' => $startDate->copy()]);
30
+
31
+        // Function to create employees for a company (also creates companies for the employees)
32
+        $createUsers = static function ($company_id, $userCount, $minPercentage, $maxPercentage) use ($endDate, $startDate) {
33
+            $users = User::factory($userCount)
34
+                ->withPersonalCompany()
35
+                ->create([
36
+                    'password' => bcrypt('password'),
37
+                    'current_company_id' => $company_id,
38
+                ]);
39
+
40
+            $dateRange = $endDate->diffInMinutes($startDate);
41
+            $minOffset = (int) ($dateRange * $minPercentage);
42
+            $maxOffset = (int) ($dateRange * $maxPercentage);
43
+
44
+            for ($i = 0; $i < $userCount; $i++) {
45
+                $increment = (int) ($minOffset + ($i * ($maxOffset - $minOffset) / $userCount));
46
+                $userCreatedAt = $startDate->copy()->addMinutes($increment);
47
+
48
+                $user = $users[$i];
49
+
50
+                // Randomly assign a role to the user
51
+                $roles = ['editor', 'admin'];
52
+                $role = $roles[array_rand($roles)];
53
+
54
+                $user->companies()->attach($company_id, compact('role'));
55
+
56
+                $user->update(['created_at' => $userCreatedAt]);
57
+                $user->ownedCompanies->first()?->update(['created_at' => $userCreatedAt]);
58
+
59
+                // Generate random created_at date for the company_user pivot table (for employees)
60
+                $user->companies->first()?->users()->updateExistingPivot($user->id, ['created_at' => $userCreatedAt]);
61
+            }
62
+        };
63
+
64
+        // Users for the first company (excluding the first company owner)
65
+        $createUsers(1, 10, 0, 0.1);
66
+
67
+        // Second company owner
68
+        $secondCompanyOwner = User::factory()
69
+            ->withPersonalCompany()
70
+            ->create([
71
+                'password' => bcrypt('admin2'),
72
+                'current_company_id' => 2,
73
+                'created_at' => $startDate->addMinutes($endDate->diffInMinutes($startDate) * 0.1),
74
+            ]);
75
+
76
+        $secondCompanyOwner->ownedCompanies->first()->update(['created_at' => $startDate->addMinutes($endDate->diffInMinutes($startDate) * 0.1)]);
77
+
78
+        // Users for the second company (excluding the second company owner)
79
+        $createUsers(2, 20, 0.1, 0.2);
80
+
81
+        // Third company owner
82
+        $thirdCompanyOwner = User::factory()
83
+            ->withPersonalCompany()
84
+            ->create([
85
+                'password' => bcrypt('admin3'),
86
+                'current_company_id' => 3,
87
+                'created_at' => $startDate->addMinutes($endDate->diffInMinutes($startDate) * 0.2),
88
+            ]);
89
+
90
+        $thirdCompanyOwner->ownedCompanies->first()->update(['created_at' => $startDate->addMinutes($endDate->diffInMinutes($startDate) * 0.2)]);
91
+
92
+        // Users for the third company (excluding the third company owner)
93
+        $createUsers(3, 40, 0.2, 0.3);
94
+
95
+        // Create employees for each company (each employee has a company)
96
+        $createUsers(4, 20, 0.3, 0.4);
97
+        $createUsers(5, 10, 0.4, 0.5);
98
+        $createUsers(6, 25, 0.5, 0.6);
99
+        $createUsers(7, 60, 0.6, 0.7);
100
+        $createUsers(8, 40, 0.7, 0.8);
101
+        $createUsers(9, 15, 0.8, 0.9);
102
+        $createUsers(10, 50, 0.9, 1);
103
+
104
+        $this->call([
105
+            CurrencySeeder::class,
106
+            CategorySeeder::class,
107
+            TaxSeeder::class,
108
+            DiscountSeeder::class,
109
+        ]);
110
+    }
111
+}

+ 65
- 0
database/seeders/DiscountSeeder.php 查看文件

1
+<?php
2
+
3
+namespace Database\Seeders;
4
+
5
+use App\Models\Setting\Discount;
6
+use Illuminate\Database\Seeder;
7
+use Illuminate\Support\Facades\DB;
8
+
9
+class DiscountSeeder extends Seeder
10
+{
11
+    /**
12
+     * Run the database seeds.
13
+     */
14
+    public function run(): void
15
+    {
16
+        $companyId = DB::table('companies')->first()->id;
17
+        $userId = DB::table('users')->first()->id;
18
+
19
+        $salesDiscounts = [
20
+            '4th of July Sale',
21
+            'End of Year Sale',
22
+            'Black Friday Sale',
23
+            'Cyber Monday Sale',
24
+            'Christmas Sale',
25
+        ];
26
+
27
+        $purchaseDiscounts = [
28
+            'Bulk Purchase Bargain',
29
+            'Early Payment Discount',
30
+            'First Time Buyer Special',
31
+            'Recurring Purchase Reward',
32
+            'Referral Program Discount',
33
+        ];
34
+
35
+        $shuffledDiscounts = [
36
+            ...array_map(static fn($name) => ['name' => $name, 'type' => 'sales'], $salesDiscounts),
37
+            ...array_map(static fn($name) => ['name' => $name, 'type' => 'purchase'], $purchaseDiscounts)
38
+        ];
39
+
40
+        shuffle($shuffledDiscounts);
41
+
42
+        $allDiscounts = $shuffledDiscounts;
43
+
44
+        foreach ($allDiscounts as $discount) {
45
+            Discount::factory()->create([
46
+                'company_id' => $companyId,
47
+                'name' => $discount['name'],
48
+                'type' => $discount['type'],
49
+                'enabled' => false,
50
+                'created_by' => $userId,
51
+                'updated_by' => $userId,
52
+            ]);
53
+        }
54
+
55
+        Discount::where('type', 'sales')
56
+            ->where('company_id', $companyId)
57
+            ->first()
58
+            ->update(['enabled' => true]);
59
+
60
+        Discount::where('type', 'purchase')
61
+            ->where('company_id', $companyId)
62
+            ->first()
63
+            ->update(['enabled' => true]);
64
+    }
65
+}

+ 0
- 3
database/seeders/TaxSeeder.php 查看文件

32
             'Environmental Tax',
32
             'Environmental Tax',
33
         ];
33
         ];
34
 
34
 
35
-        // Merge and shuffle the sales and purchase taxes
36
         $shuffledTaxes = [
35
         $shuffledTaxes = [
37
             ...array_map(static fn($name) => ['name' => $name, 'type' => 'sales'], $salesTaxes),
36
             ...array_map(static fn($name) => ['name' => $name, 'type' => 'sales'], $salesTaxes),
38
             ...array_map(static fn($name) => ['name' => $name, 'type' => 'purchase'], $purchaseTaxes)
37
             ...array_map(static fn($name) => ['name' => $name, 'type' => 'purchase'], $purchaseTaxes)
53
             ]);
52
             ]);
54
         }
53
         }
55
 
54
 
56
-        // Set the first sales tax as enabled
57
         Tax::where('type', 'sales')
55
         Tax::where('type', 'sales')
58
             ->where('company_id', $companyId)
56
             ->where('company_id', $companyId)
59
             ->first()
57
             ->first()
60
             ->update(['enabled' => true]);
58
             ->update(['enabled' => true]);
61
 
59
 
62
-        // Set the first purchase tax as enabled
63
         Tax::where('type', 'purchase')
60
         Tax::where('type', 'purchase')
64
             ->where('company_id', $companyId)
61
             ->where('company_id', $companyId)
65
             ->first()
62
             ->first()

Loading…
取消
儲存