Andrew Wallo 10 月之前
父節點
當前提交
d7b721cb94

+ 11
- 0
app/Concerns/RedirectToListPage.php 查看文件

@@ -0,0 +1,11 @@
1
+<?php
2
+
3
+namespace App\Concerns;
4
+
5
+trait RedirectToListPage
6
+{
7
+    protected function getRedirectUrl(): string
8
+    {
9
+        return $this->getResource()::getUrl('index');
10
+    }
11
+}

+ 10
- 1
app/Enums/Common/OfferingType.php 查看文件

@@ -3,8 +3,9 @@
3 3
 namespace App\Enums\Common;
4 4
 
5 5
 use Filament\Support\Contracts\HasLabel;
6
+use JaOcero\RadioDeck\Contracts\HasIcons;
6 7
 
7
-enum OfferingType: string implements HasLabel
8
+enum OfferingType: string implements HasIcons, HasLabel
8 9
 {
9 10
     case Product = 'product';
10 11
     case Service = 'service';
@@ -13,4 +14,12 @@ enum OfferingType: string implements HasLabel
13 14
     {
14 15
         return $this->name;
15 16
     }
17
+
18
+    public function getIcons(): ?string
19
+    {
20
+        return match ($this) {
21
+            self::Product => 'heroicon-o-cube-transparent',
22
+            self::Service => 'heroicon-o-briefcase',
23
+        };
24
+    }
16 25
 }

+ 22
- 0
app/Filament/Company/Pages/CreateCompany.php 查看文件

@@ -11,6 +11,8 @@ use App\Utilities\Currency\CurrencyAccessor;
11 11
 use Filament\Forms\Components\Select;
12 12
 use Filament\Forms\Components\TextInput;
13 13
 use Filament\Forms\Form;
14
+use Filament\Support\Enums\MaxWidth;
15
+use Illuminate\Contracts\Support\Htmlable;
14 16
 use Illuminate\Database\Eloquent\Model;
15 17
 use Illuminate\Support\Facades\Auth;
16 18
 use Illuminate\Support\Facades\DB;
@@ -23,6 +25,25 @@ class CreateCompany extends FilamentCreateCompany
23 25
 {
24 26
     protected bool $hasTopbar = false;
25 27
 
28
+    protected static string $view = 'filament.company.pages.create-company';
29
+
30
+    protected static string $layout = 'components.company.layout.custom-simple';
31
+
32
+    public function getHeading(): string | Htmlable
33
+    {
34
+        return '';
35
+    }
36
+
37
+    public function getMaxWidth(): MaxWidth | string | null
38
+    {
39
+        return MaxWidth::FourExtraLarge;
40
+    }
41
+
42
+    public function hasLogo(): bool
43
+    {
44
+        return true;
45
+    }
46
+
26 47
     public function form(Form $form): Form
27 48
     {
28 49
         return $form
@@ -58,6 +79,7 @@ class CreateCompany extends FilamentCreateCompany
58 79
                     ->optionsLimit(5)
59 80
                     ->softRequired(),
60 81
             ])
82
+            ->columns()
61 83
             ->model(FilamentCompanies::companyModel())
62 84
             ->statePath('data');
63 85
     }

+ 83
- 29
app/Filament/Company/Resources/Common/OfferingResource.php 查看文件

@@ -2,8 +2,11 @@
2 2
 
3 3
 namespace App\Filament\Company\Resources\Common;
4 4
 
5
+use App\Enums\Accounting\AccountCategory;
6
+use App\Enums\Accounting\AccountType;
5 7
 use App\Enums\Common\OfferingType;
6 8
 use App\Filament\Company\Resources\Common\OfferingResource\Pages;
9
+use App\Models\Accounting\Account;
7 10
 use App\Models\Common\Offering;
8 11
 use App\Utilities\Currency\CurrencyAccessor;
9 12
 use Filament\Forms;
@@ -11,93 +14,144 @@ use Filament\Forms\Form;
11 14
 use Filament\Resources\Resource;
12 15
 use Filament\Tables;
13 16
 use Filament\Tables\Table;
17
+use Illuminate\Database\Eloquent\Builder;
18
+use JaOcero\RadioDeck\Forms\Components\RadioDeck;
14 19
 
15 20
 class OfferingResource extends Resource
16 21
 {
17 22
     protected static ?string $model = Offering::class;
18 23
 
24
+    protected static ?string $modelLabel = 'Offering';
25
+
26
+    protected static ?string $navigationIcon = 'heroicon-o-square-3-stack-3d';
27
+
19 28
     public static function form(Form $form): Form
20 29
     {
21 30
         return $form
22 31
             ->schema([
23 32
                 Forms\Components\Section::make('General')
24 33
                     ->schema([
34
+                        RadioDeck::make('type')
35
+                            ->options(OfferingType::class)
36
+                            ->default(OfferingType::Product)
37
+                            ->icons(OfferingType::class)
38
+                            ->color('primary')
39
+                            ->columns()
40
+                            ->required(),
25 41
                         Forms\Components\TextInput::make('name')
26 42
                             ->autofocus()
27 43
                             ->required()
44
+                            ->columnStart(1)
28 45
                             ->maxLength(255),
29
-                        Forms\Components\Textarea::make('description')
30
-                            ->label('Description')
31
-                            ->autosize(),
32
-                        Forms\Components\Select::make('type')
33
-                            ->options(OfferingType::class)
34
-                            ->searchable()
35
-                            ->required(),
36 46
                         Forms\Components\TextInput::make('price')
37 47
                             ->required()
38 48
                             ->money(CurrencyAccessor::getDefaultCurrency()),
49
+                        Forms\Components\Textarea::make('description')
50
+                            ->label('Description')
51
+                            ->columnSpan(2)
52
+                            ->rows(3),
39 53
                         Forms\Components\Checkbox::make('sellable')
40 54
                             ->label('Sellable')
41 55
                             ->live()
42
-                            ->dehydrated(false)
43
-                            ->default(false),
56
+                            ->columnStart(1)
57
+                            ->accepted(fn (Forms\Get $get) => ! $get('purchasable'))
58
+                            ->validationMessages([
59
+                                'accepted' => 'The offering must be either sellable or purchasable.',
60
+                            ]),
44 61
                         Forms\Components\Checkbox::make('purchasable')
45 62
                             ->label('Purchasable')
46 63
                             ->live()
47
-                            ->dehydrated(false)
48
-                            ->default(false),
49
-                    ]),
64
+                            ->columnStart(1)
65
+                            ->accepted(fn (Forms\Get $get) => ! $get('sellable'))
66
+                            ->validationMessages([
67
+                                'accepted' => 'The offering must be either sellable or purchasable.',
68
+                            ]),
69
+                    ])->columns(),
50 70
                 // Sellable Section
51
-                Forms\Components\Section::make('Sellable Configuration')
71
+                Forms\Components\Section::make('Sale Information')
52 72
                     ->schema([
53 73
                         Forms\Components\Select::make('income_account_id')
54
-                            ->relationship('incomeAccount', 'name')
74
+                            ->label('Income Account')
75
+                            ->options(Account::query()
76
+                                ->where('category', AccountCategory::Revenue)
77
+                                ->where('type', AccountType::OperatingRevenue)
78
+                                ->pluck('name', 'id')
79
+                                ->toArray())
55 80
                             ->searchable()
56
-                            ->preload(),
57
-                        Forms\Components\Select::make('sales_taxes')
81
+                            ->preload()
82
+                            ->requiredIfAccepted('sellable')
83
+                            ->validationMessages([
84
+                                'required_if_accepted' => 'The income account is required for sellable offerings.',
85
+                            ]),
86
+                        Forms\Components\Select::make('salesTaxes')
87
+                            ->label('Sales Tax')
58 88
                             ->relationship('salesTaxes', 'name')
59 89
                             ->preload()
60 90
                             ->multiple(),
61
-                        Forms\Components\Select::make('sales_discounts')
91
+                        Forms\Components\Select::make('salesDiscounts')
92
+                            ->label('Sales Discount')
62 93
                             ->relationship('salesDiscounts', 'name')
63 94
                             ->preload()
64 95
                             ->multiple(),
65 96
                     ])
66
-                    ->columns(2)
97
+                    ->columns()
67 98
                     ->visible(fn (Forms\Get $get) => $get('sellable')),
68 99
 
69 100
                 // Purchasable Section
70
-                Forms\Components\Section::make('Purchasable Configuration')
101
+                Forms\Components\Section::make('Purchase Information')
71 102
                     ->schema([
72 103
                         Forms\Components\Select::make('expense_account_id')
73
-                            ->relationship('expenseAccount', 'name')
104
+                            ->label('Expense Account')
105
+                            ->options(Account::query()
106
+                                ->where('category', AccountCategory::Expense)
107
+                                ->where('type', AccountType::OperatingExpense)
108
+                                ->orderBy('name')
109
+                                ->pluck('name', 'id')
110
+                                ->toArray())
74 111
                             ->searchable()
75
-                            ->preload(),
76
-                        Forms\Components\Select::make('purchase_taxes')
112
+                            ->preload()
113
+                            ->requiredIfAccepted('purchasable')
114
+                            ->validationMessages([
115
+                                'required_if_accepted' => 'The expense account is required for purchasable offerings.',
116
+                            ]),
117
+                        Forms\Components\Select::make('purchaseTaxes')
118
+                            ->label('Purchase Tax')
77 119
                             ->relationship('purchaseTaxes', 'name')
78 120
                             ->preload()
79 121
                             ->multiple(),
80
-                        Forms\Components\Select::make('purchase_discounts')
122
+                        Forms\Components\Select::make('purchaseDiscounts')
123
+                            ->label('Purchase Discount')
81 124
                             ->relationship('purchaseDiscounts', 'name')
82 125
                             ->preload()
83 126
                             ->multiple(),
84 127
                     ])
85
-                    ->columns(2)
128
+                    ->columns()
86 129
                     ->visible(fn (Forms\Get $get) => $get('purchasable')),
87
-            ])->columns(1);
130
+            ])->columns();
88 131
     }
89 132
 
90 133
     public static function table(Table $table): Table
91 134
     {
92 135
         return $table
136
+            ->modifyQueryUsing(function (Builder $query) {
137
+                $query->selectRaw("
138
+                        *,
139
+                        CONCAT_WS(' & ',
140
+                            CASE WHEN sellable THEN 'Sellable' END,
141
+                            CASE WHEN purchasable THEN 'Purchasable' END
142
+                        ) AS attributes
143
+                    ");
144
+            })
93 145
             ->columns([
94
-                Tables\Columns\ViewColumn::make('name')
95
-                    ->label('Name')
96
-                    ->view('filament.company.components.tables.columns.offering-status'),
146
+                Tables\Columns\TextColumn::make('name')
147
+                    ->label('Name'),
148
+                Tables\Columns\TextColumn::make('attributes')
149
+                    ->label('Attributes')
150
+                    ->badge(),
97 151
                 Tables\Columns\TextColumn::make('type')
98 152
                     ->searchable(),
99 153
                 Tables\Columns\TextColumn::make('price')
100
-                    ->money()
154
+                    ->currency(CurrencyAccessor::getDefaultCurrency(), true)
101 155
                     ->sortable(),
102 156
             ])
103 157
             ->filters([

+ 3
- 0
app/Filament/Company/Resources/Common/OfferingResource/Pages/CreateOffering.php 查看文件

@@ -2,10 +2,13 @@
2 2
 
3 3
 namespace App\Filament\Company\Resources\Common\OfferingResource\Pages;
4 4
 
5
+use App\Concerns\RedirectToListPage;
5 6
 use App\Filament\Company\Resources\Common\OfferingResource;
6 7
 use Filament\Resources\Pages\CreateRecord;
7 8
 
8 9
 class CreateOffering extends CreateRecord
9 10
 {
11
+    use RedirectToListPage;
12
+
10 13
     protected static string $resource = OfferingResource::class;
11 14
 }

+ 3
- 0
app/Filament/Company/Resources/Common/OfferingResource/Pages/EditOffering.php 查看文件

@@ -2,12 +2,15 @@
2 2
 
3 3
 namespace App\Filament\Company\Resources\Common\OfferingResource\Pages;
4 4
 
5
+use App\Concerns\RedirectToListPage;
5 6
 use App\Filament\Company\Resources\Common\OfferingResource;
6 7
 use Filament\Actions;
7 8
 use Filament\Resources\Pages\EditRecord;
8 9
 
9 10
 class EditOffering extends EditRecord
10 11
 {
12
+    use RedirectToListPage;
13
+
11 14
     protected static string $resource = OfferingResource::class;
12 15
 
13 16
     protected function getHeaderActions(): array

+ 1
- 1
app/Filament/Company/Resources/Sales/SellableOfferingResource/Pages/ListSellableOfferings.php 查看文件

@@ -14,7 +14,7 @@ class ListSellableOfferings extends ListRecords
14 14
     protected function getHeaderActions(): array
15 15
     {
16 16
         return [
17
-            Actions\CreateAction::make(),
17
+            Actions\CreateAction::make()->label('New Product or Service'),
18 18
         ];
19 19
     }
20 20
 

+ 39
- 0
app/Models/Common/Offering.php 查看文件

@@ -10,11 +10,14 @@ use App\Enums\Accounting\AdjustmentType;
10 10
 use App\Enums\Common\OfferingType;
11 11
 use App\Models\Accounting\Account;
12 12
 use App\Models\Accounting\Adjustment;
13
+use App\Observers\OfferingObserver;
14
+use Illuminate\Database\Eloquent\Attributes\ObservedBy;
13 15
 use Illuminate\Database\Eloquent\Factories\HasFactory;
14 16
 use Illuminate\Database\Eloquent\Model;
15 17
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
16 18
 use Illuminate\Database\Eloquent\Relations\MorphToMany;
17 19
 
20
+#[ObservedBy(OfferingObserver::class)]
18 21
 class Offering extends Model
19 22
 {
20 23
     use Blamable;
@@ -27,6 +30,8 @@ class Offering extends Model
27 30
         'description',
28 31
         'type',
29 32
         'price',
33
+        'sellable',
34
+        'purchasable',
30 35
         'income_account_id',
31 36
         'expense_account_id',
32 37
         'created_by',
@@ -36,8 +41,32 @@ class Offering extends Model
36 41
     protected $casts = [
37 42
         'type' => OfferingType::class,
38 43
         'price' => MoneyCast::class,
44
+        'sellable' => 'boolean',
45
+        'purchasable' => 'boolean',
39 46
     ];
40 47
 
48
+    public function clearSellableAdjustments(): void
49
+    {
50
+        if (! $this->sellable) {
51
+            $this->income_account_id = null;
52
+
53
+            $adjustmentIds = $this->salesAdjustments()->pluck('adjustment_id');
54
+
55
+            $this->adjustments()->detach($adjustmentIds);
56
+        }
57
+    }
58
+
59
+    public function clearPurchasableAdjustments(): void
60
+    {
61
+        if (! $this->purchasable) {
62
+            $this->expense_account_id = null;
63
+
64
+            $adjustmentIds = $this->purchaseAdjustments()->pluck('adjustment_id');
65
+
66
+            $this->adjustments()->detach($adjustmentIds);
67
+        }
68
+    }
69
+
41 70
     public function incomeAccount(): BelongsTo
42 71
     {
43 72
         return $this->belongsTo(Account::class, 'income_account_id');
@@ -53,6 +82,16 @@ class Offering extends Model
53 82
         return $this->morphToMany(Adjustment::class, 'adjustmentable', 'adjustmentables');
54 83
     }
55 84
 
85
+    public function salesAdjustments(): MorphToMany
86
+    {
87
+        return $this->adjustments()->where('type', AdjustmentType::Sales);
88
+    }
89
+
90
+    public function purchaseAdjustments(): MorphToMany
91
+    {
92
+        return $this->adjustments()->where('type', AdjustmentType::Purchase);
93
+    }
94
+
56 95
     public function salesTaxes(): MorphToMany
57 96
     {
58 97
         return $this->adjustments()->where('category', AdjustmentCategory::Tax)->where('type', AdjustmentType::Sales);

+ 54
- 0
app/Observers/OfferingObserver.php 查看文件

@@ -0,0 +1,54 @@
1
+<?php
2
+
3
+namespace App\Observers;
4
+
5
+use App\Models\Common\Offering;
6
+
7
+class OfferingObserver
8
+{
9
+    /**
10
+     * Handle the Offering "created" event.
11
+     */
12
+    public function created(Offering $offering): void
13
+    {
14
+        //
15
+    }
16
+
17
+    public function saving(Offering $offering): void
18
+    {
19
+        $offering->clearSellableAdjustments();
20
+        $offering->clearPurchasableAdjustments();
21
+    }
22
+
23
+    /**
24
+     * Handle the Offering "updated" event.
25
+     */
26
+    public function updated(Offering $offering): void
27
+    {
28
+        //
29
+    }
30
+
31
+    /**
32
+     * Handle the Offering "deleted" event.
33
+     */
34
+    public function deleted(Offering $offering): void
35
+    {
36
+        //
37
+    }
38
+
39
+    /**
40
+     * Handle the Offering "restored" event.
41
+     */
42
+    public function restored(Offering $offering): void
43
+    {
44
+        //
45
+    }
46
+
47
+    /**
48
+     * Handle the Offering "force deleted" event.
49
+     */
50
+    public function forceDeleted(Offering $offering): void
51
+    {
52
+        //
53
+    }
54
+}

+ 3
- 4
app/Providers/FilamentCompaniesServiceProvider.php 查看文件

@@ -26,7 +26,7 @@ use App\Filament\Company\Pages\Reports;
26 26
 use App\Filament\Company\Pages\Service\ConnectedAccount;
27 27
 use App\Filament\Company\Pages\Service\LiveCurrency;
28 28
 use App\Filament\Company\Resources\Banking\AccountResource;
29
-use App\Filament\Company\Resources\Core\DepartmentResource;
29
+use App\Filament\Company\Resources\Common\OfferingResource;
30 30
 use App\Filament\Company\Resources\Purchases\BuyableOfferingResource;
31 31
 use App\Filament\Company\Resources\Sales\SellableOfferingResource;
32 32
 use App\Filament\Components\PanelShiftDropdown;
@@ -121,6 +121,7 @@ class FilamentCompaniesServiceProvider extends PanelProvider
121 121
                         ...Dashboard::getNavigationItems(),
122 122
                         ...Reports::getNavigationItems(),
123 123
                         ...Settings::getNavigationItems(),
124
+                        ...OfferingResource::getNavigationItems(),
124 125
                     ])
125 126
                     ->groups([
126 127
                         NavigationGroup::make('Sales & Payments')
@@ -143,9 +144,6 @@ class FilamentCompaniesServiceProvider extends PanelProvider
143 144
                             ->localizeLabel()
144 145
                             ->icon('heroicon-o-building-library')
145 146
                             ->items(AccountResource::getNavigationItems()),
146
-                        NavigationGroup::make('HR')
147
-                            ->icon('heroicon-o-user-group')
148
-                            ->items(DepartmentResource::getNavigationItems()),
149 147
                         NavigationGroup::make('Services')
150 148
                             ->localizeLabel()
151 149
                             ->icon('heroicon-o-wrench-screwdriver')
@@ -155,6 +153,7 @@ class FilamentCompaniesServiceProvider extends PanelProvider
155 153
                             ]),
156 154
                     ]);
157 155
             })
156
+            ->sidebarCollapsibleOnDesktop()
158 157
             ->viteTheme('resources/css/filament/company/theme.css')
159 158
             ->brandLogo(static fn () => view('components.icons.logo'))
160 159
             ->tenant(Company::class)

+ 1
- 0
composer.json 查看文件

@@ -20,6 +20,7 @@
20 20
         "filament/filament": "^3.2.115",
21 21
         "guava/filament-clusters": "^1.1",
22 22
         "guzzlehttp/guzzle": "^7.8",
23
+        "jaocero/radio-deck": "^1.2",
23 24
         "laravel/framework": "^11.0",
24 25
         "laravel/sanctum": "^4.0",
25 26
         "laravel/tinker": "^2.9",

+ 80
- 6
composer.lock 查看文件

@@ -4,7 +4,7 @@
4 4
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 5
         "This file is @generated automatically"
6 6
     ],
7
-    "content-hash": "16a2f09cb501fed46d806dc5c4cc51f7",
7
+    "content-hash": "6f7204f976352f049caf598df4454ace",
8 8
     "packages": [
9 9
         {
10 10
             "name": "akaunting/laravel-money",
@@ -2776,6 +2776,80 @@
2776 2776
             ],
2777 2777
             "time": "2023-12-03T19:50:20+00:00"
2778 2778
         },
2779
+        {
2780
+            "name": "jaocero/radio-deck",
2781
+            "version": "v1.2.8",
2782
+            "source": {
2783
+                "type": "git",
2784
+                "url": "https://github.com/199ocero/radio-deck.git",
2785
+                "reference": "23815595cfdb441794964f0729e0ea57eec48f5e"
2786
+            },
2787
+            "dist": {
2788
+                "type": "zip",
2789
+                "url": "https://api.github.com/repos/199ocero/radio-deck/zipball/23815595cfdb441794964f0729e0ea57eec48f5e",
2790
+                "reference": "23815595cfdb441794964f0729e0ea57eec48f5e",
2791
+                "shasum": ""
2792
+            },
2793
+            "require": {
2794
+                "filament/forms": "^3.0",
2795
+                "illuminate/contracts": "^10.0|^11.0",
2796
+                "php": "^8.1",
2797
+                "spatie/laravel-package-tools": "^1.15.0"
2798
+            },
2799
+            "require-dev": {
2800
+                "nunomaduro/collision": "^7.9",
2801
+                "orchestra/testbench": "^8.0|^9.0",
2802
+                "pestphp/pest": "^2.0",
2803
+                "pestphp/pest-plugin-arch": "^2.0",
2804
+                "pestphp/pest-plugin-laravel": "^2.0"
2805
+            },
2806
+            "type": "library",
2807
+            "extra": {
2808
+                "laravel": {
2809
+                    "providers": [
2810
+                        "JaOcero\\RadioDeck\\RadioDeckServiceProvider"
2811
+                    ]
2812
+                }
2813
+            },
2814
+            "autoload": {
2815
+                "psr-4": {
2816
+                    "JaOcero\\RadioDeck\\": "src/",
2817
+                    "JaOcero\\RadioDeck\\Database\\Factories\\": "database/factories/"
2818
+                }
2819
+            },
2820
+            "notification-url": "https://packagist.org/downloads/",
2821
+            "license": [
2822
+                "MIT"
2823
+            ],
2824
+            "authors": [
2825
+                {
2826
+                    "name": "Jay-Are Ocero",
2827
+                    "email": "199ocero@gmail.com",
2828
+                    "role": "Developer"
2829
+                }
2830
+            ],
2831
+            "description": "Turn filament default radio button into a selectable card with icons, title and description.",
2832
+            "homepage": "https://github.com/jaocero/radio-deck",
2833
+            "keywords": [
2834
+                "filament-form",
2835
+                "filament-plugin",
2836
+                "filamentphp",
2837
+                "jaocero",
2838
+                "laravel",
2839
+                "radio-deck"
2840
+            ],
2841
+            "support": {
2842
+                "issues": "https://github.com/jaocero/radio-deck/issues",
2843
+                "source": "https://github.com/jaocero/radio-deck"
2844
+            },
2845
+            "funding": [
2846
+                {
2847
+                    "url": "https://github.com/jaocero",
2848
+                    "type": "github"
2849
+                }
2850
+            ],
2851
+            "time": "2024-06-25T23:06:42+00:00"
2852
+        },
2779 2853
         {
2780 2854
             "name": "kirschbaum-development/eloquent-power-joins",
2781 2855
             "version": "4.0.0",
@@ -10785,16 +10859,16 @@
10785 10859
         },
10786 10860
         {
10787 10861
             "name": "phpstan/phpstan",
10788
-            "version": "1.12.10",
10862
+            "version": "1.12.11",
10789 10863
             "source": {
10790 10864
                 "type": "git",
10791 10865
                 "url": "https://github.com/phpstan/phpstan.git",
10792
-                "reference": "fc463b5d0fe906dcf19689be692c65c50406a071"
10866
+                "reference": "0d1fc20a962a91be578bcfe7cf939e6e1a2ff733"
10793 10867
             },
10794 10868
             "dist": {
10795 10869
                 "type": "zip",
10796
-                "url": "https://api.github.com/repos/phpstan/phpstan/zipball/fc463b5d0fe906dcf19689be692c65c50406a071",
10797
-                "reference": "fc463b5d0fe906dcf19689be692c65c50406a071",
10870
+                "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0d1fc20a962a91be578bcfe7cf939e6e1a2ff733",
10871
+                "reference": "0d1fc20a962a91be578bcfe7cf939e6e1a2ff733",
10798 10872
                 "shasum": ""
10799 10873
             },
10800 10874
             "require": {
@@ -10839,7 +10913,7 @@
10839 10913
                     "type": "github"
10840 10914
                 }
10841 10915
             ],
10842
-            "time": "2024-11-11T15:37:09+00:00"
10916
+            "time": "2024-11-17T14:08:01+00:00"
10843 10917
         },
10844 10918
         {
10845 10919
             "name": "phpunit/php-code-coverage",

+ 2
- 0
database/migrations/2024_11_13_214149_create_offerings_table.php 查看文件

@@ -18,6 +18,8 @@ return new class extends Migration
18 18
             $table->string('description')->nullable();
19 19
             $table->string('type')->nullable(); // product, service, etc.
20 20
             $table->integer('price')->default(0);
21
+            $table->boolean('sellable')->default(false);
22
+            $table->boolean('purchasable')->default(false);
21 23
             $table->foreignId('income_account_id')->nullable()->constrained('accounts')->nullOnDelete(); // income account e.g. sales/invoice
22 24
             $table->foreignId('expense_account_id')->nullable()->constrained('accounts')->nullOnDelete(); // expense account e.g. purchase/bill
23 25
             $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();

+ 1
- 0
resources/css/filament/company/tailwind.config.js 查看文件

@@ -13,6 +13,7 @@ export default {
13 13
         './resources/views/vendor/**/*.blade.php',
14 14
         './vendor/bezhansalleh/filament-panel-switch/resources/views/panel-switch-menu.blade.php',
15 15
         './vendor/awcodes/filament-table-repeater/resources/**/*.blade.php',
16
+        './vendor/jaocero/radio-deck/resources/views/**/*.blade.php',
16 17
     ],
17 18
     theme: {
18 19
         extend: {

+ 3
- 1
resources/data/lang/en.json 查看文件

@@ -197,5 +197,7 @@
197 197
     "Adjustment Details": "Adjustment Details",
198 198
     "Adjustments": "Adjustments",
199 199
     "Sellable Configuration": "Sellable Configuration",
200
-    "Purchasable Configuration": "Purchasable Configuration"
200
+    "Purchasable Configuration": "Purchasable Configuration",
201
+    "Sale Information": "Sale Information",
202
+    "Purchase Information": "Purchase Information"
201 203
 }

+ 60
- 0
resources/views/components/company/layout/custom-simple.blade.php 查看文件

@@ -0,0 +1,60 @@
1
+@php
2
+    use Filament\Support\Enums\MaxWidth;
3
+@endphp
4
+
5
+<x-filament-panels::layout.base :livewire="$livewire">
6
+    @props([
7
+        'after' => null,
8
+        'heading' => null,
9
+        'subheading' => null,
10
+    ])
11
+
12
+    <div class="fi-simple-layout flex min-h-screen">
13
+        @if (($hasTopbar ?? true) && filament()->auth()->check())
14
+            <div
15
+                class="absolute end-0 top-0 flex h-16 items-center gap-x-4 pe-4 md:pe-6 lg:pe-8"
16
+            >
17
+                @if (filament()->hasDatabaseNotifications())
18
+                    @livewire(Filament\Livewire\DatabaseNotifications::class, [
19
+                        'lazy' => filament()->hasLazyLoadedDatabaseNotifications()
20
+                    ])
21
+                @endif
22
+
23
+                <x-filament-panels::user-menu/>
24
+            </div>
25
+        @endif
26
+
27
+        <div class="relative hidden w-0 flex-1 lg:block">
28
+            <img class="absolute inset-0 size-full object-cover"
29
+                 src="https://img.freepik.com/free-vector/abstract-cyber-background-with-flowing-particles-design_1048-15243.jpg?t=st=1731895585~exp=1731899185~hmac=3397976be08b5dabed940a75fb1fd957cee7f1a32cd1da1f5951435265cf36f9&w=2000"
30
+                 alt="">
31
+        </div>
32
+
33
+        <div
34
+            class="fi-simple-main-ctn flex flex-1 flex-col justify-center px-4 py-12 sm:px-6 lg:flex-none lg:px-20 xl:px-24 w-1/2 bg-white"
35
+        >
36
+            <main
37
+                @class([
38
+                    'fi-simple-main mx-auto w-full',
39
+                    match ($maxWidth ?? null) {
40
+                        MaxWidth::ExtraSmall, 'xs' => 'sm:max-w-xs',
41
+                        MaxWidth::Small, 'sm' => 'sm:max-w-sm',
42
+                        MaxWidth::Medium, 'md' => 'sm:max-w-md',
43
+                        MaxWidth::ExtraLarge, 'xl' => 'sm:max-w-xl',
44
+                        MaxWidth::TwoExtraLarge, '2xl' => 'sm:max-w-2xl',
45
+                        MaxWidth::ThreeExtraLarge, '3xl' => 'sm:max-w-3xl',
46
+                        MaxWidth::FourExtraLarge, '4xl' => 'sm:max-w-4xl',
47
+                        MaxWidth::FiveExtraLarge, '5xl' => 'sm:max-w-5xl',
48
+                        MaxWidth::SixExtraLarge, '6xl' => 'sm:max-w-6xl',
49
+                        MaxWidth::SevenExtraLarge, '7xl' => 'sm:max-w-7xl',
50
+                        default => 'sm:max-w-lg',
51
+                    },
52
+                ])
53
+            >
54
+                {{ $slot }}
55
+            </main>
56
+        </div>
57
+
58
+        {{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::FOOTER, scopes: $livewire->getRenderHookScopes()) }}
59
+    </div>
60
+</x-filament-panels::layout.base>

+ 24
- 0
resources/views/components/company/page/custom-simple.blade.php 查看文件

@@ -0,0 +1,24 @@
1
+@props([
2
+    'heading' => null,
3
+    'subheading' => null,
4
+])
5
+
6
+<div {{ $attributes->class(['fi-simple-page flex min-h-screen']) }}>
7
+    {{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::SIMPLE_PAGE_START, scopes: $this->getRenderHookScopes()) }}
8
+
9
+    <section class="grid auto-cols-fr gap-y-6">
10
+        <x-filament-panels::header.simple
11
+            :heading="$heading ??= $this->getHeading()"
12
+            :logo="$this->hasLogo()"
13
+            :subheading="$subheading ??= $this->getSubHeading()"
14
+        />
15
+
16
+        {{ $slot }}
17
+    </section>
18
+
19
+    @if (! $this instanceof \Filament\Tables\Contracts\HasTable)
20
+        <x-filament-actions::modals/>
21
+    @endif
22
+
23
+    {{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::SIMPLE_PAGE_END, scopes: $this->getRenderHookScopes()) }}
24
+</div>

+ 24
- 0
resources/views/filament/company/components/page/custom-simple.blade.php 查看文件

@@ -0,0 +1,24 @@
1
+@props([
2
+    'heading' => null,
3
+    'subheading' => null,
4
+])
5
+
6
+<div {{ $attributes->class(['fi-simple-page flex min-h-screen']) }}>
7
+    {{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::SIMPLE_PAGE_START, scopes: $this->getRenderHookScopes()) }}
8
+
9
+    <section class="grid auto-cols-fr gap-y-6">
10
+        <x-filament-panels::header.simple
11
+            :heading="$heading ??= $this->getHeading()"
12
+            :logo="$this->hasLogo()"
13
+            :subheading="$subheading ??= $this->getSubHeading()"
14
+        />
15
+
16
+        {{ $slot }}
17
+    </section>
18
+
19
+    @if (! $this instanceof \Filament\Tables\Contracts\HasTable)
20
+        <x-filament-actions::modals/>
21
+    @endif
22
+
23
+    {{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::SIMPLE_PAGE_END, scopes: $this->getRenderHookScopes()) }}
24
+</div>

+ 10
- 0
resources/views/filament/company/pages/create-company.blade.php 查看文件

@@ -0,0 +1,10 @@
1
+<x-filament-panels::page.simple>
2
+    <x-filament-panels::form wire:submit="register">
3
+        {{ $this->form }}
4
+
5
+        <x-filament-panels::form.actions
6
+            :actions="$this->getCachedFormActions()"
7
+            :full-width="$this->hasFullWidthFormActions()"
8
+        />
9
+    </x-filament-panels::form>
10
+</x-filament-panels::page.simple>

+ 0
- 0
resources/views/vendor/radio-deck/.gitkeep 查看文件


+ 113
- 0
resources/views/vendor/radio-deck/forms/components/radio-deck.blade.php 查看文件

@@ -0,0 +1,113 @@
1
+@php
2
+    use Filament\Support\Enums\IconPosition;
3
+    use Filament\Support\Enums\Alignment;
4
+    use Filament\Support\Enums\IconSize;
5
+
6
+    $id = $getId();
7
+    $isDisabled = $isDisabled();
8
+    $isMultiple = $isMultiple();
9
+    $statePath = $getStatePath();
10
+@endphp
11
+
12
+<x-dynamic-component :component="$getFieldWrapperView()" :field="$field">
13
+    <x-filament::grid :default="$getColumns('default')" :sm="$getColumns('sm')" :md="$getColumns('md')"
14
+                      :lg="$getColumns('lg')" :xl="$getColumns('xl')"
15
+                      :two-xl="$getColumns('2xl')" is-grid @class(['gap-5'])>
16
+        @foreach ($getOptions() as $value => $label)
17
+            @php
18
+                $shouldOptionBeDisabled = $isDisabled || $isOptionDisabled($value, $label);
19
+            @endphp
20
+
21
+            <label class="flex cursor-pointer gap-x-3">
22
+                <input @disabled($shouldOptionBeDisabled) id="{{ $id }}-{{ $value }}"
23
+                       @if (! $isMultiple)
24
+                           name="{{ $id }}"
25
+                       @endif
26
+                       type="{{  $isMultiple ? 'checkbox' : 'radio' }}"
27
+                       value="{{ $value }}" wire:loading.attr="disabled"
28
+                {{ $applyStateBindingModifiers('wire:model') }}="{{ $statePath }}"
29
+                {{ $getExtraInputAttributeBag()->class(['peer hidden']) }} />
30
+
31
+                @php
32
+                    $iconExists = $hasIcons($value);
33
+                    $iconPosition = $getIconPosition();
34
+                    $alignment = $getAlignment();
35
+                    $direction = $getDirection();
36
+                    $gap = $getGap();
37
+                    $padding = $getPadding();
38
+                    $color = $getColor();
39
+                    $icon = $getIcon($value);
40
+                    $iconSize = $getIconSize();
41
+                    $iconSizeSm = $getIconSizes('sm');
42
+                    $iconSizeMd = $getIconSizes('md');
43
+                    $iconSizeLg = $getIconSizes('lg');
44
+                    $descriptionExists = $hasDescription($value);
45
+                    $description = $getDescription($value);
46
+                @endphp
47
+                <div {{ $getExtraCardsAttributeBag()->class([
48
+                    'flex w-full text-sm leading-6 rounded-lg bg-white dark:bg-gray-900',
49
+                    $padding ?: 'px-4 py-2',
50
+                    $gap ?: 'gap-5',
51
+                    match ($direction) {
52
+                        'column' => 'flex-col',
53
+                        default => 'flex-row',
54
+                    },
55
+                    $iconExists
56
+                        ? match ($iconPosition) {
57
+                            IconPosition::Before, 'before' => 'justify-start',
58
+                            IconPosition::After, 'after' => 'justify-between flex-row-reverse',
59
+                            default => 'justify-start',
60
+                        }
61
+                        : 'justify-start',
62
+                    match ($alignment) {
63
+                        Alignment::Center, 'center' => 'items-center',
64
+                        Alignment::Start, 'start' => 'items-start',
65
+                        Alignment::End, 'end' => 'items-end',
66
+                        default => 'items-center',
67
+                    },
68
+                    'ring-1 ring-gray-950/10 dark:ring-white/20 peer-checked:ring-2',
69
+                    'peer-disabled:bg-gray-100/50 dark:peer-disabled:bg-gray-700/50 peer-disabled:cursor-not-allowed',
70
+                    match ($color) {
71
+                        'gray' => 'peer-checked:ring-gray-600 dark:peer-checked:ring-gray-500',
72
+                        default
73
+                            => 'fi-color-custom peer-checked:ring-custom-600 dark:peer-checked:ring-custom-500',
74
+                    },
75
+                ]) }} @style([
76
+                    \Filament\Support\get_color_css_variables($color, shades: [600, 500]) => $color !== 'gray',
77
+                ])>
78
+                    @if ($iconExists)
79
+                        <x-filament::icon :icon="$icon" @class([
80
+                            'flex-shrink-0',
81
+                            match ($iconSize) {
82
+                                IconSize::Small => $iconSizeSm ?: 'h-8 w-8',
83
+                                'sm' => $iconSizeSm ?: 'h-8 w-8',
84
+                                IconSize::Medium => $iconSizeMd ?: 'h-9 w-9',
85
+                                'md' => $iconSizeMd ?: 'h-9 w-9',
86
+                                IconSize::Large => $iconSizeLg ?: 'h-10 w-10',
87
+                                'lg' => $iconSizeLg ?: 'h-10 w-10',
88
+                                default => 'h-8 w-8',
89
+                            },
90
+                            match ($color) {
91
+                                'gray' => 'fi-color-gray text-gray-600 dark:text-gray-500',
92
+                                default => 'fi-color-custom text-custom-600 dark:text-custom-500',
93
+                            },
94
+                        ]) @style([
95
+                            \Filament\Support\get_color_css_variables($color, shades: [600, 500]) => $color !== 'gray',
96
+                        ]) />
97
+                    @endif
98
+                    <div {{ $getExtraOptionsAttributeBag()->merge(['class' =>'place-items-start']) }}>
99
+                        <span class="font-medium text-gray-950 dark:text-white">
100
+                            {{ $label }}
101
+                        </span>
102
+
103
+                        @if ($descriptionExists)
104
+                            <p {{ $getExtraDescriptionsAttributeBag()->merge(['class' =>'text-gray-500 dark:text-gray-400']) }}>
105
+                                {{ $description }}
106
+                            </p>
107
+                        @endif
108
+                    </div>
109
+                </div>
110
+            </label>
111
+        @endforeach
112
+    </x-filament::grid>
113
+</x-dynamic-component>

Loading…
取消
儲存