diff --git a/Modules/CircleApps/App/Http/Controllers/CircleAppsController.php b/Modules/CircleApps/App/Http/Controllers/CircleAppsController.php index 41dde8a..e1e57e0 100644 --- a/Modules/CircleApps/App/Http/Controllers/CircleAppsController.php +++ b/Modules/CircleApps/App/Http/Controllers/CircleAppsController.php @@ -8,6 +8,7 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use Modules\CircleApps\App\Models\App; +use Nwidart\Modules\Facades\Module; use ProtoneMedia\Splade\Facades\Toast; class CircleAppsController extends Controller @@ -100,6 +101,16 @@ public function show(App $app) public function install(App $app) { + if($app->required){ + $checkRequiredApps = auth('accounts')->user()->apps()->whereIn('id', $app->required)->count(); + if($checkRequiredApps !== count($app->required)){ + $appRequired = App::whereIn('id', $app->required)->first(); + + auth('accounts')->user()->apps()->attach($appRequired->id); + } + } + + if($app->account_id && $app->account_id !== auth('accounts')->id()){ $app->account->notifyDB( message: __(auth('accounts')->user()->username . " " . __('is install your app') .' '. $app->name), @@ -115,6 +126,21 @@ public function install(App $app) public function uninstall(App $app) { + $appRequiredThisApp = App::whereJsonContains('required', (string)$app->id)->get(); + if($appRequiredThisApp->count() > 0){ + $getRequiredApp = null; + foreach ($appRequiredThisApp as $requiredApp){ + if(has_app($requiredApp->key)){ + $getRequiredApp = $requiredApp; + } + } + + if($getRequiredApp){ + Toast::danger(__('You need to uninstall required apps first!'))->autoDismiss(2); + return redirect()->to(url('apps/'.$getRequiredApp->id)); + } + } + if($app->account_id && $app->account_id !== auth('accounts')->id()){ $app->account->notifyDB( message: __(auth('accounts')->user()->username . " " . __('is Uninstall your app') .' '. $app->name), diff --git a/Modules/CircleApps/App/Models/App.php b/Modules/CircleApps/App/Models/App.php index da9b26d..784582d 100644 --- a/Modules/CircleApps/App/Models/App.php +++ b/Modules/CircleApps/App/Models/App.php @@ -52,12 +52,14 @@ class App extends Model implements HasMedia 'price_per', 'discount', 'discount_to', + 'required', 'is_free' ]; protected $casts = [ 'is_active' => 'boolean', - 'is_free' => 'boolean' + 'is_free' => 'boolean', + 'required' => 'json', ]; public function account() diff --git a/Modules/CircleApps/Database/migrations/2024_03_30_134635_update_apps_table.php b/Modules/CircleApps/Database/migrations/2024_03_30_134635_update_apps_table.php new file mode 100644 index 0000000..6e66d93 --- /dev/null +++ b/Modules/CircleApps/Database/migrations/2024_03_30_134635_update_apps_table.php @@ -0,0 +1,28 @@ +json('required')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('apps', function (Blueprint $table) { + $table->dropColumn('required'); + }); + } +}; diff --git a/Modules/CircleApps/resources/views/apps/create.blade.php b/Modules/CircleApps/resources/views/apps/create.blade.php index c841781..920d26a 100644 --- a/Modules/CircleApps/resources/views/apps/create.blade.php +++ b/Modules/CircleApps/resources/views/apps/create.blade.php @@ -40,7 +40,12 @@ - + + @php $apps = \Modules\CircleApps\App\Models\App::where('is_active', true)->get(); @endphp + @foreach($apps as $app) + + @endforeach +
diff --git a/Modules/CircleApps/resources/views/apps/edit.blade.php b/Modules/CircleApps/resources/views/apps/edit.blade.php index 2dc2da9..b61113b 100644 --- a/Modules/CircleApps/resources/views/apps/edit.blade.php +++ b/Modules/CircleApps/resources/views/apps/edit.blade.php @@ -38,7 +38,12 @@ - + + @php $apps = \Modules\CircleApps\App\Models\App::where('is_active', true)->get(); @endphp + @foreach($apps as $app) + + @endforeach +
diff --git a/Modules/CircleApps/resources/views/components/app-card.blade.php b/Modules/CircleApps/resources/views/components/app-card.blade.php index 66d958c..dc494fb 100644 --- a/Modules/CircleApps/resources/views/components/app-card.blade.php +++ b/Modules/CircleApps/resources/views/components/app-card.blade.php @@ -1,16 +1,27 @@
-
-
+
+
+
-
- -
-

{{$item->name}}

- @if($item->account) - {{$item->account->name}} +
+ +
+

{{$item->name}}

+ @if($item->account) + {{$item->account->name}} + @endif +
+
+
+
+ @if(auth('accounts')->user()) + @if(!has_app($item->key)) + {{__('Install')}} + @else + {{__('Uninstall')}} @endif -
- + @endif +
@if($item->is_free) diff --git a/Modules/CircleApps/resources/views/submit.blade.php b/Modules/CircleApps/resources/views/submit.blade.php index abccb4a..febdd20 100644 --- a/Modules/CircleApps/resources/views/submit.blade.php +++ b/Modules/CircleApps/resources/views/submit.blade.php @@ -25,6 +25,12 @@ + + @php $apps = \Modules\CircleApps\App\Models\App::where('is_active', true)->get(); @endphp + @foreach($apps as $app) + + @endforeach +
diff --git a/Modules/CircleContacts/App/Providers/CircleContactsServiceProvider.php b/Modules/CircleContacts/App/Providers/CircleContactsServiceProvider.php index e6414e3..7e96ea6 100644 --- a/Modules/CircleContacts/App/Providers/CircleContactsServiceProvider.php +++ b/Modules/CircleContacts/App/Providers/CircleContactsServiceProvider.php @@ -6,6 +6,7 @@ use Illuminate\Support\ServiceProvider; use Modules\CircleApps\App\Facades\CircleAppsMenu; use Modules\CircleContacts\App\Console\CircleContactsInstall; +use Modules\CircleContacts\App\Console\CircleInovicesInstall; use TomatoPHP\TomatoAdmin\Services\Contracts\Menu; class CircleContactsServiceProvider extends ServiceProvider diff --git a/Modules/CircleContacts/resources/views/contacts/index.blade.php b/Modules/CircleContacts/resources/views/contacts/index.blade.php index 75be1c3..4e88bcc 100644 --- a/Modules/CircleContacts/resources/views/contacts/index.blade.php +++ b/Modules/CircleContacts/resources/views/contacts/index.blade.php @@ -21,47 +21,3 @@
@endif @endsection - -{{----}} -{{-- {{ __('CircleXoContact') }}--}} - -{{-- --}} -{{-- {{trans('tomato-admin::global.crud.create-new')}} {{__('CircleXoContact')}}--}} -{{-- --}} - -{{--
--}} -{{--
--}} -{{-- --}} -{{-- --}} -{{-- --}} -{{-- --}} -{{-- --}} -{{-- --}} -{{-- --}} -{{-- --}} -{{-- --}} -{{-- --}} - -{{-- --}} -{{--
--}} -{{-- --}} -{{-- --}} -{{-- --}} -{{-- --}} -{{-- --}} -{{-- --}} -{{-- --}} -{{-- --}} -{{-- --}} -{{--
--}} -{{--
--}} -{{--
--}} -{{--
--}} -{{--
--}} -{{--
--}} diff --git a/Modules/CircleInvoices/.github/FUNDING.yml b/Modules/CircleInvoices/.github/FUNDING.yml new file mode 100644 index 0000000..892ba05 --- /dev/null +++ b/Modules/CircleInvoices/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [3x1io] diff --git a/Modules/CircleInvoices/App/Console/.gitkeep b/Modules/CircleInvoices/App/Console/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/App/Console/CircleInvoicesInstall.php b/Modules/CircleInvoices/App/Console/CircleInvoicesInstall.php new file mode 100644 index 0000000..fb55585 --- /dev/null +++ b/Modules/CircleInvoices/App/Console/CircleInvoicesInstall.php @@ -0,0 +1,65 @@ +info('Install App'); + $app = App::where('key', 'circle-invoices')->first(); + if(!$app){ + $app = new App(); + $app->key = 'circle-invoices'; + $app->name = 'Circle Invoices'; + $app->description = 'Invoices Generator With Custom Templates, to generate public invoices and your customer can download it'; + $app->is_active = true; + $app->is_free = true; + $app->status = "active"; + $app->homepage = "https://www.github.com/tomatophp/circle-invoices"; + $app->github = "https://www.github.com/tomatophp/circle-invoices"; + $app->docs = "https://www.github.com/tomatophp/circle-invoices"; + $app->privacy = "https://www.github.com/tomatophp/circle-invoices"; + $app->faq = "https://www.github.com/tomatophp/circle-invoices"; + $app->email = "info@3x1.io"; + $getContactsApp = App::where('key', 'circle-contacts')->first(); + $app->required = [$getContactsApp->id]; + $app->save(); + } + $this->callSilent('optimize:clear'); + $this->artisanCommand(["migrate"]); + $this->artisanCommand(["optimize:clear"]); + $this->info('Circle Contacts App installed successfully.'); + } +} diff --git a/Modules/CircleInvoices/App/Forms/.gitkeep b/Modules/CircleInvoices/App/Forms/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/App/Http/Controllers/.gitkeep b/Modules/CircleInvoices/App/Http/Controllers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/App/Http/Controllers/CircleXoInvoiceController.php b/Modules/CircleInvoices/App/Http/Controllers/CircleXoInvoiceController.php new file mode 100644 index 0000000..c3c99e9 --- /dev/null +++ b/Modules/CircleInvoices/App/Http/Controllers/CircleXoInvoiceController.php @@ -0,0 +1,313 @@ +model = \Modules\CircleInvoices\App\Models\CircleXoInvoice::class; + } + + /** + * @param Request $request + * @return View|JsonResponse + */ + public function index(Request $request): View|JsonResponse + { + $query = CircleXoInvoice::query(); + $query->where('account_id', auth('accounts')->user()->id); + + return Tomato::index( + request: $request, + model: $this->model, + view: 'circle-invoices::invoices.index', + table: \Modules\CircleInvoices\App\Tables\CircleXoInvoiceTable::class, + query: $query + ); + } + + /** + * @param Request $request + * @return JsonResponse + */ + public function api(Request $request): JsonResponse + { + $query = CircleXoInvoice::query(); + $query->where('account_id', auth('accounts')->user()->id); + return Tomato::json( + request: $request, + model: \Modules\CircleInvoices\App\Models\CircleXoInvoice::class, + query: $query + ); + } + + /** + * @return View + */ + public function create(): View + { + return Tomato::create( + view: 'circle-invoices::invoices.create', + ); + } + + /** + * @param \Modules\CircleInvoices\App\Http\Requests\CircleXoInvoice\CircleXoInvoiceStoreRequest $request + * @return RedirectResponse|JsonResponse + */ + public function store(\Modules\CircleInvoices\App\Http\Requests\CircleXoInvoice\CircleXoInvoiceStoreRequest $request): RedirectResponse|JsonResponse + { + $request->merge([ + "account_id" => auth('accounts')->user()->id, + "total" => collect($request->get('items'))->sum('total'), + "tax" => collect($request->get('items'))->map(function ($item) { + return $item['tax'] * $item['qty']; + })->sum(), + "discount" => collect($request->get('items'))->map(function ($item) { + return $item['discount'] * $item['qty']; + })->sum(), + ]); + + + if($request->has('contact_id')){ + $contact = CircleXoContact::find($request->get('contact_id')); + if($contact){ + $billedTo = $contact->name . "\n" . $contact->address . "\n" . $contact->email . "\n" . $contact->phone . "\n" . $contact->company; + } + + $request->merge([ + "billed_to" => $billedTo ?? null, + "shipped_to" => $billedTo ?? null, + ]); + } + + $response = Tomato::store( + request: $request, + model: \Modules\CircleInvoices\App\Models\CircleXoInvoice::class, + message: __('Invoice saved successfully'), + redirect: 'profile.invoices.index', + hasMedia: true, + collection: [ + 'logo' => false + ] + ); + + foreach ($request->items as $item) { + $response->record->items()->create($item); + } + + auth('accounts')->user()->meta('billed_from', $request->billed_from); + if($request->hasFile('logo') && auth('accounts')->user()->getMedia('logo')->count() < 1){ + auth('accounts')->user()->addMedia($request->logo)->toMediaCollection('logo'); + } + + if($response instanceof JsonResponse){ + return $response; + } + + return redirect()->route('profile.invoices.show', $response->record->id); + } + + /** + * @param \Modules\CircleInvoices\App\Models\CircleXoInvoice $model + * @return View|JsonResponse + */ + public function show(\Modules\CircleInvoices\App\Models\CircleXoInvoice $model): View|JsonResponse + { + if(!has_app('circle-invoices', $model->account_id)){ + abort(403); + } + + return Tomato::get( + model: $model, + view: 'circle-invoices::invoices.show', + hasMedia: true, + collection: [ + 'logo' => false + ] + ); + } + + + public function print(\Modules\CircleInvoices\App\Models\CircleXoInvoice $model): View|JsonResponse + { + if(!has_app('circle-invoices', $model->account_id)){ + abort(403); + } + + + return Tomato::get( + model: $model, + view: 'circle-invoices::invoices.print', + hasMedia: true, + collection: [ + 'logo' => false + ] + ); + } + + public function showPublic($invoice): View|JsonResponse + { + $invoice = CircleXoInvoice::where('uuid', $invoice)->first(); + + if($invoice){ + if($invoice->is_public){ + return view('circle-invoices::invoices.public', [ + 'invoice' => $invoice + ]); + } + else { + abort(403); + } + } + else { + abort(404); + } + + } + + public function printPublic($invoice): View|JsonResponse + { + $invoice = CircleXoInvoice::where('uuid', $invoice)->first(); + + if($invoice){ + if($invoice->is_public){ + return view('circle-invoices::invoices.print-public', [ + 'invoice' => $invoice + ]); + } + else { + abort(403); + } + } + else { + abort(404); + } + } + + /** + * @param \Modules\CircleInvoices\App\Models\CircleXoInvoice $model + * @return View + */ + public function edit(\Modules\CircleInvoices\App\Models\CircleXoInvoice $model): View + { + if(!has_app('circle-invoices', $model->account_id)){ + abort(403); + } + + + $model->items = $model->items; + return Tomato::get( + model: $model, + view: 'circle-invoices::invoices.edit', + hasMedia: true, + collection: [ + 'logo' => false + ] + ); + } + + /** + * @param \Modules\CircleInvoices\App\Http\Requests\CircleXoInvoice\CircleXoInvoiceUpdateRequest $request + * @param \Modules\CircleInvoices\App\Models\CircleXoInvoice $model + * @return RedirectResponse|JsonResponse + */ + public function update(\Modules\CircleInvoices\App\Http\Requests\CircleXoInvoice\CircleXoInvoiceUpdateRequest $request, \Modules\CircleInvoices\App\Models\CircleXoInvoice $model): RedirectResponse|JsonResponse + { + if(!has_app('circle-invoices', $model->account_id)){ + abort(403); + } + + + $request->merge([ + "total" => collect($request->get('items'))->sum('total'), + "tax" => collect($request->get('items'))->map(function ($item) { + return $item['tax'] * $item['qty']; + })->sum(), + "discount" => collect($request->get('items'))->map(function ($item) { + return $item['discount'] * $item['qty']; + })->sum(), + ]); + + if($request->has('contact_id') && empty($request->billed_to) && empty($request->shipped_to)){ + $contact = CircleXoContact::find($request->get('contact_id')); + if($contact){ + $billedTo = $contact->name . "\n" . $contact->address . "\n" . $contact->email . "\n" . $contact->phone . "\n" . $contact->company; + } + + $request->merge([ + "billed_to" => $billedTo ?? null, + "shipped_to" => $billedTo ?? null, + ]); + } + + + $response = Tomato::update( + request: $request, + model: $model, + message: __('Invoice updated successfully'), + redirect: 'profile.invoices.index', + hasMedia: true, + collection: [ + 'logo' => false + ] + ); + + $response->record->items()->delete(); + foreach ($request->items as $item) { + $response->record->items()->create($item); + } + + auth('accounts')->user()->meta('billed_from', $request->billed_from); + if($request->hasFile('logo') && $request->file('logo')->getClientOriginalName() !== 'blob'){ + auth('accounts')->user()->clearMediaCollection('logo'); + auth('accounts')->user()->addMedia($request->logo)->toMediaCollection('logo'); + } + + if($response instanceof JsonResponse){ + return $response; + } + + return $response->redirect; + } + + /** + * @param \Modules\CircleInvoices\App\Models\CircleXoInvoice $model + * @return RedirectResponse|JsonResponse + */ + public function destroy(\Modules\CircleInvoices\App\Models\CircleXoInvoice $model): RedirectResponse|JsonResponse + { + if(!has_app('circle-invoices', $model->account_id)){ + abort(403); + } + + $model->items()->delete(); + $response = Tomato::destroy( + model: $model, + message: __('Invoice deleted successfully'), + redirect: 'profile.invoices.index', + hasMedia: true, + collection: [ + 'logo' => false + ] + ); + + if($response instanceof JsonResponse){ + return $response; + } + + return $response->redirect; + } +} diff --git a/Modules/CircleInvoices/App/Http/Controllers/CircleXoInvoiceItemController.php b/Modules/CircleInvoices/App/Http/Controllers/CircleXoInvoiceItemController.php new file mode 100644 index 0000000..7828cfe --- /dev/null +++ b/Modules/CircleInvoices/App/Http/Controllers/CircleXoInvoiceItemController.php @@ -0,0 +1,162 @@ +model = \Modules\CircleInvoices\App\Models\CircleXoInvoiceItem::class; + } + + /** + * @param Request $request + * @return View|JsonResponse + */ + public function index(Request $request): View|JsonResponse + { + return Tomato::index( + request: $request, + model: $this->model, + view: 'circleinvoices::circle-xo-invoice-items.index', + table: \Modules\CircleInvoices\App\Tables\CircleXoInvoiceItemTable::class + ); + } + + /** + * @param Request $request + * @return JsonResponse + */ + public function api(Request $request): JsonResponse + { + return Tomato::json( + request: $request, + model: \Modules\CircleInvoices\App\Models\CircleXoInvoiceItem::class, + ); + } + + /** + * @return View + */ + public function create(): View + { + return Tomato::create( + view: 'circleinvoices::circle-xo-invoice-items.create', + ); + } + + /** + * @param Request $request + * @return RedirectResponse|JsonResponse + */ + public function store(Request $request): RedirectResponse|JsonResponse + { + $response = Tomato::store( + request: $request, + model: \Modules\CircleInvoices\App\Models\CircleXoInvoiceItem::class, + validation: [ + 'item' => 'required|max:255|string', + 'price' => 'nullable', + 'discount' => 'nullable', + 'vat' => 'nullable', + 'qty' => 'nullable', + 'total' => 'nullable', + 'is_free' => 'nullable', + 'invoice_id' => 'required|exists:circle_xo_invoices,id', + 'description' => 'nullable|max:255|string' + ], + message: __('CircleXoInvoiceItem updated successfully'), + redirect: 'admin.circle-xo-invoice-items.index', + ); + + if($response instanceof JsonResponse){ + return $response; + } + + return $response->redirect; + } + + /** + * @param \Modules\CircleInvoices\App\Models\CircleXoInvoiceItem $model + * @return View|JsonResponse + */ + public function show(\Modules\CircleInvoices\App\Models\CircleXoInvoiceItem $model): View|JsonResponse + { + return Tomato::get( + model: $model, + view: 'circleinvoices::circle-xo-invoice-items.show', + ); + } + + /** + * @param \Modules\CircleInvoices\App\Models\CircleXoInvoiceItem $model + * @return View + */ + public function edit(\Modules\CircleInvoices\App\Models\CircleXoInvoiceItem $model): View + { + return Tomato::get( + model: $model, + view: 'circleinvoices::circle-xo-invoice-items.edit', + ); + } + + /** + * @param Request $request + * @param \Modules\CircleInvoices\App\Models\CircleXoInvoiceItem $model + * @return RedirectResponse|JsonResponse + */ + public function update(Request $request, \Modules\CircleInvoices\App\Models\CircleXoInvoiceItem $model): RedirectResponse|JsonResponse + { + $response = Tomato::update( + request: $request, + model: $model, + validation: [ + 'item' => 'sometimes|max:255|string', + 'price' => 'nullable', + 'discount' => 'nullable', + 'vat' => 'nullable', + 'qty' => 'nullable', + 'total' => 'nullable', + 'is_free' => 'nullable', + 'invoice_id' => 'sometimes|exists:circle_xo_invoices,id', + 'description' => 'nullable|max:255|string' + ], + message: __('CircleXoInvoiceItem updated successfully'), + redirect: 'admin.circle-xo-invoice-items.index', + ); + + if($response instanceof JsonResponse){ + return $response; + } + + return $response->redirect; + } + + /** + * @param \Modules\CircleInvoices\App\Models\CircleXoInvoiceItem $model + * @return RedirectResponse|JsonResponse + */ + public function destroy(\Modules\CircleInvoices\App\Models\CircleXoInvoiceItem $model): RedirectResponse|JsonResponse + { + $response = Tomato::destroy( + model: $model, + message: __('CircleXoInvoiceItem deleted successfully'), + redirect: 'admin.circle-xo-invoice-items.index', + ); + + if($response instanceof JsonResponse){ + return $response; + } + + return $response->redirect; + } +} diff --git a/Modules/CircleInvoices/App/Http/Requests/.gitkeep b/Modules/CircleInvoices/App/Http/Requests/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/App/Http/Requests/CircleXoInvoice/.gitkeep b/Modules/CircleInvoices/App/Http/Requests/CircleXoInvoice/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/App/Http/Requests/CircleXoInvoice/CircleXoInvoiceStoreRequest.php b/Modules/CircleInvoices/App/Http/Requests/CircleXoInvoice/CircleXoInvoiceStoreRequest.php new file mode 100644 index 0000000..1c0e56f --- /dev/null +++ b/Modules/CircleInvoices/App/Http/Requests/CircleXoInvoice/CircleXoInvoiceStoreRequest.php @@ -0,0 +1,51 @@ + + */ + public function rules() + { + return [ + 'logo' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048', + 'items' => 'required|array|min:1', + 'uuid' => 'required|max:255|string|unique:circle_xo_invoices,uuid', + 'billed_from' => 'required|string', + 'billed_to' => 'nullable|string', + 'shipped_to' => 'nullable|string', + 'contact_id' => 'nullable|exists:circle_xo_contacts,id', + 'due_date' => 'required|date', + 'invoice_date' => 'required|date', + 'paid_amount' => 'nullable', + 'payment_method' => 'required|max:255|string', + 'payment_method_details' => 'nullable', + 'total' => 'nullable', + 'shipping' => 'nullable', + 'discount' => 'nullable', + 'tax' => 'nullable', + 'type' => 'nullable|max:255|string', + 'status' => 'nullable|max:255|string', + 'currency' => 'nullable|max:255|string', + 'notes' => 'nullable', + 'is_public' => 'nullable|boolean', + 'template' => 'nullable|max:255|string' + ]; + } +} diff --git a/Modules/CircleInvoices/App/Http/Requests/CircleXoInvoice/CircleXoInvoiceUpdateRequest.php b/Modules/CircleInvoices/App/Http/Requests/CircleXoInvoice/CircleXoInvoiceUpdateRequest.php new file mode 100644 index 0000000..6265dc5 --- /dev/null +++ b/Modules/CircleInvoices/App/Http/Requests/CircleXoInvoice/CircleXoInvoiceUpdateRequest.php @@ -0,0 +1,48 @@ + + */ + public function rules() + { + return [ + 'billed_from' => 'required|string', + 'billed_to' => 'nullable|string', + 'shipped_to' => 'nullable|string', + 'contact_id' => 'nullable|exists:circle_xo_contacts,id', + 'due_date' => 'nullable', + 'invoice_date' => 'nullable', + 'paid_amount' => 'nullable', + 'payment_method' => 'nullable|max:255|string', + 'payment_method_details' => 'nullable', + 'total' => 'nullable', + 'shipping' => 'nullable', + 'discount' => 'nullable', + 'tax' => 'nullable', + 'type' => 'nullable|max:255|string', + 'status' => 'nullable|max:255|string', + 'currency' => 'nullable|max:255|string', + 'notes' => 'nullable', + 'is_public' => 'nullable|boolean', + 'template' => 'nullable|max:255|string' + ]; + } +} diff --git a/Modules/CircleInvoices/App/Http/Requests/CircleXoInvoiceItem/.gitkeep b/Modules/CircleInvoices/App/Http/Requests/CircleXoInvoiceItem/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/App/Models/.gitkeep b/Modules/CircleInvoices/App/Models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/App/Models/CircleXoInvoice.php b/Modules/CircleInvoices/App/Models/CircleXoInvoice.php new file mode 100644 index 0000000..abb2026 --- /dev/null +++ b/Modules/CircleInvoices/App/Models/CircleXoInvoice.php @@ -0,0 +1,88 @@ + 'json', + 'is_public' => 'boolean' + ]; + + + + + public function account() + { + return $this->belongsTo(\App\Models\Account::class); + } + + public function contact() + { + return $this->belongsTo(\Modules\CircleContacts\App\Models\CircleXoContact::class); + } + + public function items() + { + return $this->hasMany(\Modules\CircleInvoices\App\Models\CircleXoInvoiceItem::class, 'invoice_id', 'id'); + } +} diff --git a/Modules/CircleInvoices/App/Models/CircleXoInvoiceItem.php b/Modules/CircleInvoices/App/Models/CircleXoInvoiceItem.php new file mode 100644 index 0000000..f0f44ef --- /dev/null +++ b/Modules/CircleInvoices/App/Models/CircleXoInvoiceItem.php @@ -0,0 +1,47 @@ + 'boolean' + ]; + + + + public function invoice() + { + return $this->belongsTo(\Modules\CircleInvoices\App\Models\CircleXoInvoice::class); + } +} diff --git a/Modules/CircleInvoices/App/Providers/.gitkeep b/Modules/CircleInvoices/App/Providers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/App/Providers/CircleInvoicesServiceProvider.php b/Modules/CircleInvoices/App/Providers/CircleInvoicesServiceProvider.php new file mode 100644 index 0000000..7ad18ec --- /dev/null +++ b/Modules/CircleInvoices/App/Providers/CircleInvoicesServiceProvider.php @@ -0,0 +1,128 @@ +registerCommands(); + $this->registerCommandSchedules(); + $this->registerTranslations(); + $this->registerConfig(); + $this->registerViews(); + $this->loadMigrationsFrom(module_path($this->moduleName, 'Database/migrations')); + + CircleAppsMenu::register([ + \TomatoPHP\TomatoAdmin\Services\Contracts\Menu::make() + ->group('circle-invoices') + ->label(__('Invoices')) + ->icon('bx bxs-receipt') + ->route('profile.invoices.index'), + ]); + + + } + + /** + * Register the service provider. + */ + public function register(): void + { + $this->app->register(RouteServiceProvider::class); + } + + /** + * Register commands in the format of Command::class + */ + protected function registerCommands(): void + { + $this->commands([ + CircleInvoicesInstall::class + ]); + } + + /** + * Register command Schedules. + */ + protected function registerCommandSchedules(): void + { + // $this->app->booted(function () { + // $schedule = $this->app->make(Schedule::class); + // $schedule->command('inspire')->hourly(); + // }); + } + + /** + * Register translations. + */ + public function registerTranslations(): void + { + $langPath = resource_path('lang/modules/'.$this->moduleNameLower); + + if (is_dir($langPath)) { + $this->loadTranslationsFrom($langPath, $this->moduleNameLower); + $this->loadJsonTranslationsFrom($langPath); + } else { + $this->loadTranslationsFrom(module_path($this->moduleName, 'lang'), $this->moduleNameLower); + $this->loadJsonTranslationsFrom(module_path($this->moduleName, 'lang')); + } + } + + /** + * Register config. + */ + protected function registerConfig(): void + { + $this->publishes([module_path($this->moduleName, 'config/config.php') => config_path($this->moduleNameLower.'.php')], 'config'); + $this->mergeConfigFrom(module_path($this->moduleName, 'config/config.php'), $this->moduleNameLower); + } + + /** + * Register views. + */ + public function registerViews(): void + { + $viewPath = resource_path('views/modules/'.$this->moduleNameLower); + $sourcePath = module_path($this->moduleName, 'resources/views'); + + $this->publishes([$sourcePath => $viewPath], ['views', $this->moduleNameLower.'-module-views']); + + $this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), $this->moduleNameLower); + + $componentNamespace = str_replace('/', '\\', config('modules.namespace').'\\'.$this->moduleName.'\\'.config('modules.paths.generator.component-class.path')); + Blade::componentNamespace($componentNamespace, $this->moduleNameLower); + } + + /** + * Get the services provided by the provider. + */ + public function provides(): array + { + return []; + } + + private function getPublishableViewPaths(): array + { + $paths = []; + foreach (config('view.paths') as $path) { + if (is_dir($path.'/modules/'.$this->moduleNameLower)) { + $paths[] = $path.'/modules/'.$this->moduleNameLower; + } + } + + return $paths; + } +} diff --git a/Modules/CircleInvoices/App/Providers/RouteServiceProvider.php b/Modules/CircleInvoices/App/Providers/RouteServiceProvider.php new file mode 100644 index 0000000..2154a5d --- /dev/null +++ b/Modules/CircleInvoices/App/Providers/RouteServiceProvider.php @@ -0,0 +1,59 @@ +mapApiRoutes(); + + $this->mapWebRoutes(); + } + + /** + * Define the "web" routes for the application. + * + * These routes all receive session state, CSRF protection, etc. + */ + protected function mapWebRoutes(): void + { + Route::middleware('web') + ->namespace($this->moduleNamespace) + ->group(module_path('CircleInvoices', '/routes/web.php')); + } + + /** + * Define the "api" routes for the application. + * + * These routes are typically stateless. + */ + protected function mapApiRoutes(): void + { + Route::prefix('api') + ->middleware('api') + ->namespace($this->moduleNamespace) + ->group(module_path('CircleInvoices', '/routes/api.php')); + } +} diff --git a/Modules/CircleInvoices/App/Tables/.gitkeep b/Modules/CircleInvoices/App/Tables/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/App/Tables/CircleXoInvoiceTable.php b/Modules/CircleInvoices/App/Tables/CircleXoInvoiceTable.php new file mode 100644 index 0000000..7f31806 --- /dev/null +++ b/Modules/CircleInvoices/App/Tables/CircleXoInvoiceTable.php @@ -0,0 +1,184 @@ +query = \Modules\CircleInvoices\App\Models\CircleXoInvoice::query(); + } + } + + /** + * Determine if the user is authorized to perform bulk actions and exports. + * + * @return bool + */ + public function authorize(Request $request) + { + return true; + } + + /** + * The resource or query builder. + * + * @return mixed + */ + public function for() + { + return $this->query; + } + + /** + * Configure the given SpladeTable. + * + * @param \ProtoneMedia\Splade\SpladeTable $table + * @return void + */ + public function configure(SpladeTable $table) + { + $table + ->withGlobalSearch( + label: trans('tomato-admin::global.search'), + columns: ['id','uuid','name','email','phone',] + ) + ->bulkAction( + label: trans('tomato-admin::global.crud.delete'), + each: fn (\Modules\CircleInvoices\App\Models\CircleXoInvoice $model) => $model->delete(), + after: fn () => Toast::danger(__('CircleXoInvoice Has Been Deleted'))->autoDismiss(2), + confirm: true + ) + ->defaultSort('id', 'desc') + ->column( + key: 'id', + label: __('Id'), + sortable: true + ) + ->column( + key: 'account_id', + label: __('Account id'), + sortable: true + ) + ->column( + key: 'uuid', + label: __('Uuid'), + sortable: true + ) + ->column( + key: 'name', + label: __('Name'), + sortable: true + ) + ->column( + key: 'email', + label: __('Email'), + sortable: true + ) + ->column( + key: 'phone', + label: __('Phone'), + sortable: true + ) + ->column( + key: 'address', + label: __('Address'), + sortable: true + ) + ->column( + key: 'company', + label: __('Company'), + sortable: true + ) + ->column( + key: 'contact_id', + label: __('Contact id'), + sortable: true + ) + ->column( + key: 'due_date', + label: __('Due date'), + sortable: true + ) + ->column( + key: 'invoice_date', + label: __('Invoice date'), + sortable: true + ) + ->column( + key: 'paid_amount', + label: __('Paid amount'), + sortable: true + ) + ->column( + key: 'payment_method', + label: __('Payment method'), + sortable: true + ) + ->column( + key: 'payment_method_details', + label: __('Payment method details'), + sortable: true + ) + ->column( + key: 'total', + label: __('Total'), + sortable: true + ) + ->column( + key: 'shipping', + label: __('Shipping'), + sortable: true + ) + ->column( + key: 'discount', + label: __('Discount'), + sortable: true + ) + ->column( + key: 'vat', + label: __('Vat'), + sortable: true + ) + ->column( + key: 'type', + label: __('Type'), + sortable: true + ) + ->column( + key: 'status', + label: __('Status'), + sortable: true + ) + ->column( + key: 'currency', + label: __('Currency'), + sortable: true + ) + ->column( + key: 'notes', + label: __('Notes'), + sortable: true + ) + ->column( + key: 'template', + label: __('Template'), + sortable: true + ) + ->column(key: 'actions',label: trans('tomato-admin::global.crud.actions')) + ->export() + ->paginate(10); + } +} diff --git a/Modules/CircleInvoices/App/resources/.gitkeep b/Modules/CircleInvoices/App/resources/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/App/resources/CircleXoInvoicesResource.php b/Modules/CircleInvoices/App/resources/CircleXoInvoicesResource.php new file mode 100644 index 0000000..d61844d --- /dev/null +++ b/Modules/CircleInvoices/App/resources/CircleXoInvoicesResource.php @@ -0,0 +1,44 @@ + $this->id, + 'uuid' => $this->uuid, + 'name' => $this->name, + 'email' => $this->email, + 'phone' => $this->phone, + 'address' => $this->address, + 'company' => $this->company, + 'contact' => $this->id, + 'due_date' => $this->due_date, + 'invoice_date' => $this->invoice_date, + 'paid_amount' => $this->paid_amount, + 'payment_method' => $this->payment_method, + 'payment_method_details' => $this->payment_method_details, + 'total' => $this->total, + 'shipping' => $this->shipping, + 'discount' => $this->discount, + 'vat' => $this->vat, + 'type' => $this->type, + 'status' => $this->status, + 'currency' => $this->currency, + 'notes' => $this->notes, + 'template' => $this->template, + + ]; + } +} diff --git a/Modules/CircleInvoices/CHANGELOG.md b/Modules/CircleInvoices/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/Database/Seeders/.gitkeep b/Modules/CircleInvoices/Database/Seeders/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/Database/Seeders/CircleInvoicesDatabaseSeeder.php b/Modules/CircleInvoices/Database/Seeders/CircleInvoicesDatabaseSeeder.php new file mode 100644 index 0000000..bd6acfb --- /dev/null +++ b/Modules/CircleInvoices/Database/Seeders/CircleInvoicesDatabaseSeeder.php @@ -0,0 +1,16 @@ +call([]); + } +} diff --git a/Modules/CircleInvoices/Database/migrations/2024_03_30_02_0303_0505_create_circle_xo_invoices_table.php b/Modules/CircleInvoices/Database/migrations/2024_03_30_02_0303_0505_create_circle_xo_invoices_table.php new file mode 100644 index 0000000..a16cf04 --- /dev/null +++ b/Modules/CircleInvoices/Database/migrations/2024_03_30_02_0303_0505_create_circle_xo_invoices_table.php @@ -0,0 +1,49 @@ +id(); + $table->foreignId("account_id")->references('id')->on('accounts')->onDelete('cascade'); + $table->foreignId("contact_id")->nullable()->references('id')->on('circle_xo_contacts')->onDelete('cascade'); + $table->string("uuid")->unique()->index(); + $table->text("billed_from")->nullable(); + $table->text("billed_to")->nullable(); + $table->text("shipped_to")->nullable(); + $table->date("due_date")->nullable(); + $table->date("invoice_date")->nullable(); + $table->double("paid_amount")->nullable()->unsigned(); + $table->string("payment_method")->default('cash')->nullable(); + $table->json("payment_method_details")->nullable(); + $table->double("total")->nullable()->unsigned(); + $table->double("shipping")->nullable()->unsigned(); + $table->double("discount")->nullable()->unsigned(); + $table->double("tax")->nullable()->unsigned(); + $table->string("type")->default('invoice')->nullable(); + $table->string("status")->default('pending')->nullable(); + $table->string("currency")->default('$')->nullable(); + $table->text("notes")->nullable(); + $table->string("template")->default('main')->nullable(); + $table->boolean('is_public')->default(false)->nullable(); + $table->timestamps(); + + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('circle_xo_invoices'); + } +}; diff --git a/Modules/CircleInvoices/Database/migrations/2024_03_30_02_0303_0808_create_circle_xo_invoice_items_table.php b/Modules/CircleInvoices/Database/migrations/2024_03_30_02_0303_0808_create_circle_xo_invoice_items_table.php new file mode 100644 index 0000000..6128d55 --- /dev/null +++ b/Modules/CircleInvoices/Database/migrations/2024_03_30_02_0303_0808_create_circle_xo_invoice_items_table.php @@ -0,0 +1,37 @@ +id(); + $table->string("item"); + $table->double("price")->nullable()->unsigned(); + $table->double("discount")->nullable()->unsigned(); + $table->double("tax")->nullable()->unsigned(); + $table->double("qty")->default(1)->nullable()->unsigned(); + $table->double("total")->nullable()->unsigned(); + $table->boolean("is_free")->default(false)->nullable(); + $table->foreignId("invoice_id")->references('id')->on('circle_xo_invoices')->onDelete('cascade'); + $table->string("description")->nullable(); + $table->timestamps(); + + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('circle_xo_invoice_items'); + } +}; diff --git a/Modules/CircleInvoices/LICENSE.md b/Modules/CircleInvoices/LICENSE.md new file mode 100644 index 0000000..a77082f --- /dev/null +++ b/Modules/CircleInvoices/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Fady Mondy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Modules/CircleInvoices/README.md b/Modules/CircleInvoices/README.md new file mode 100644 index 0000000..7c297f8 --- /dev/null +++ b/Modules/CircleInvoices/README.md @@ -0,0 +1,29 @@ +# Circle Invoices + +Invoices Generator With Custom Templates, to generate public invoices and your customer can download it + +## Installation + +```bash +composer require tomatophp/circle-invoices-module +``` + +## Support + +you can join our discord server to get support [TomatoPHP](https://discord.gg/Xqmt35Uh) + +## Docs + +you can check docs of this package on [Docs](https://docs.tomatophp.com/plugins/tomato-themes) + +## Changelog + +Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. + +## Security + +Please see [SECURITY](SECURITY.md) for more information about security. + +## License + +The MIT License (MIT). Please see [License File](LICENSE.md) for more information. diff --git a/Modules/CircleInvoices/SECURITY.md b/Modules/CircleInvoices/SECURITY.md new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/art/logo.png b/Modules/CircleInvoices/art/logo.png new file mode 100644 index 0000000..2076c01 Binary files /dev/null and b/Modules/CircleInvoices/art/logo.png differ diff --git a/Modules/CircleInvoices/composer.json b/Modules/CircleInvoices/composer.json new file mode 100644 index 0000000..bfb34b0 --- /dev/null +++ b/Modules/CircleInvoices/composer.json @@ -0,0 +1,33 @@ +{ + "name": "tomatophp/circle-invoices-module", + "type": "laravel-module", + "description": "Invoices Generator With Custom Templates, to generate public invoices and your customer can download it", + "license": "MIT", + "authors": [ + { + "name": "Fady Mondy", + "email": "info@3x1.io" + } + ], + "extra": { + "laravel": { + "providers": [], + "aliases": { + + } + } + }, + "autoload": { + "psr-4": { + "Modules\\CircleInvoices\\": "", + "Modules\\CircleInvoices\\App\\": "app/", + "Modules\\CircleInvoices\\Database\\Factories\\": "database/factories/", + "Modules\\CircleInvoices\\Database\\Seeders\\": "database/seeders/" + } + }, + "autoload-dev": { + "psr-4": { + "Modules\\CircleInvoices\\Tests\\": "tests/" + } + } +} diff --git a/Modules/CircleInvoices/config/.gitkeep b/Modules/CircleInvoices/config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/config/config.php b/Modules/CircleInvoices/config/config.php new file mode 100644 index 0000000..d27252f --- /dev/null +++ b/Modules/CircleInvoices/config/config.php @@ -0,0 +1,5 @@ + 'CircleInvoices', +]; diff --git a/Modules/CircleInvoices/module.json b/Modules/CircleInvoices/module.json new file mode 100644 index 0000000..86c88d3 --- /dev/null +++ b/Modules/CircleInvoices/module.json @@ -0,0 +1,29 @@ +{ + "name": "CircleInvoices", + "alias": "circle-invoices", + "description": { + "ar": "Invoices Generator With Custom Templates, to generate public invoices and your customer can download it", + "en": "Invoices Generator With Custom Templates, to generate public invoices and your customer can download it", + "gr": "Invoices Generator With Custom Templates, to generate public invoices and your customer can download it", + "sp": "Invoices Generator With Custom Templates, to generate public invoices and your customer can download it" + }, + "keywords": [], + "priority": 0, + "providers": [ + "Modules\\CircleInvoices\\App\\Providers\\CircleInvoicesServiceProvider" + ], + "files": [], + "title": { + "ar": "Circle Invoices", + "en": "Circle Invoices", + "gr": "Circle Invoices", + "sp": "Circle Invoices" + }, + "color": "#2980B9", + "icon": "bx bxs-receipt", + "placeholder": "placeholder.webp", + "type": "plugin", + "version": "v1.0", + "required": ["CircleContacts"], + "apps": ["circle-contacts"] +} diff --git a/Modules/CircleInvoices/package.json b/Modules/CircleInvoices/package.json new file mode 100644 index 0000000..d6fbfc8 --- /dev/null +++ b/Modules/CircleInvoices/package.json @@ -0,0 +1,15 @@ +{ + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build" + }, + "devDependencies": { + "axios": "^1.1.2", + "laravel-vite-plugin": "^0.7.5", + "sass": "^1.69.5", + "postcss": "^8.3.7", + "vite": "^4.0.0" + } +} diff --git a/Modules/CircleInvoices/resources/assets/js/app.js b/Modules/CircleInvoices/resources/assets/js/app.js new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/resources/assets/sass/app.scss b/Modules/CircleInvoices/resources/assets/sass/app.scss new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/resources/views/.gitkeep b/Modules/CircleInvoices/resources/views/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/resources/views/invoices/.gitkeep b/Modules/CircleInvoices/resources/views/invoices/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/resources/views/invoices/create.blade.php b/Modules/CircleInvoices/resources/views/invoices/create.blade.php new file mode 100644 index 0000000..262b388 --- /dev/null +++ b/Modules/CircleInvoices/resources/views/invoices/create.blade.php @@ -0,0 +1,258 @@ + +@extends('circle-xo::layouts.app') + +@section('title', __('New Invoice')) + +@section('content') + +
+
+ + + + + +
+
+ +
+
+
+
+

{{__('INVOICE')}}

+ + + # + + + +
+
+
{{__('Date')}}
+
+ +
+
+
+
{{__('Due Date')}}
+
+ +
+
+
+
{{__('Paid Amount')}}
+
+ +
+
+
+
{{__('Shipping Price')}}
+
+ +
+
+
+
{{__('Payment Method')}}
+
+ + + + + + +
+
+
+
{{__('Payment Status')}}
+
+ + + + + +
+
+
+
{{__('Invoice Type')}}
+
+ + + + + +
+
+
+
{{__('Currency')}}
+
+ @php $currencies = \Modules\TomatoLocations\App\Models\Currency::all(); @endphp + + @foreach($currencies as $currency) + + @endforeach + +
+
+
+
{{__('Invoice Template')}}
+
+ + + +
+
+
+
{{__('Invoice Template')}}
+
+ +
+
+
+
+
+
+
+ +
+
+ {{__('Item')}} +
+
+ {{__('Price')}} +
+
+ {{__('Discount')}} +
+
+ {{__('Tax')}} +
+
+ {{__('QTY')}} +
+
+ {{__('Total')}} +
+
+
+
+
+
+ +
+
+ + + + + + + + +
+
+
+
+
+ {{__('Tax')}} +
+
+ @{{ items.tax }} +
+
+
+
+ {{__('Sub Total')}} +
+
+ @{{ items.price }} +
+
+
+
+ {{__('Discount')}} +
+
+ @{{ items.discount }} +
+
+
+
+ {{__('Total')}} +
+
+ @{{ items.total }} +
+
+
+
+ + + +
+ + +
+
+@endsection diff --git a/Modules/CircleInvoices/resources/views/invoices/edit.blade.php b/Modules/CircleInvoices/resources/views/invoices/edit.blade.php new file mode 100644 index 0000000..c28e101 --- /dev/null +++ b/Modules/CircleInvoices/resources/views/invoices/edit.blade.php @@ -0,0 +1,246 @@ +@extends('circle-xo::layouts.app') + +@section('title', __('Edit Invoice') . ' #' . $model->id) + +@section('content') + + +
+
+ + + + + +
+
+ +
+
+
+
+

{{__('INVOICE')}}

+ + + # + + + +
+
+
{{__('Date')}}
+
+ +
+
+
+
{{__('Due Date')}}
+
+ +
+
+
+
{{__('Paid Amount')}}
+
+ +
+
+
+
{{__('Shipping Price')}}
+
+ +
+
+
+
{{__('Payment Method')}}
+
+ + + + + + +
+
+
+
{{__('Payment Status')}}
+
+ + + + + +
+
+
+
{{__('Invoice Type')}}
+
+ + + + + +
+
+
+
{{__('Currency')}}
+
+ @php $currencies = \Modules\TomatoLocations\App\Models\Currency::all(); @endphp + + @foreach($currencies as $currency) + + @endforeach + +
+
+
+
{{__('Invoice Template')}}
+
+ + + +
+
+
+
{{__('Invoice Template')}}
+
+ +
+
+
+
+
+
+
+ +
+
+ {{__('Item')}} +
+
+ {{__('Price')}} +
+
+ {{__('Discount')}} +
+
+ {{__('Tax')}} +
+
+ {{__('QTY')}} +
+
+ {{__('Total')}} +
+
+
+
+
+
+ +
+
+ + + + + + + + +
+
+
+
+
+ {{__('Tax')}} +
+
+ @{{ items.tax }} +
+
+
+
+ {{__('Sub Total')}} +
+
+ @{{ items.price }} +
+
+
+
+ {{__('Discount')}} +
+
+ @{{ items.discount }} +
+
+
+
+ {{__('Total')}} +
+
+ @{{ items.total }} +
+
+
+
+ + +
+ + + + +
+
+@endsection diff --git a/Modules/CircleInvoices/resources/views/invoices/index.blade.php b/Modules/CircleInvoices/resources/views/invoices/index.blade.php new file mode 100644 index 0000000..61d6ba3 --- /dev/null +++ b/Modules/CircleInvoices/resources/views/invoices/index.blade.php @@ -0,0 +1,9 @@ +@extends('circle-invoices::layout.app') + +@section('content') +
+ + + +
+@endsection diff --git a/Modules/CircleInvoices/resources/views/invoices/list.blade.php b/Modules/CircleInvoices/resources/views/invoices/list.blade.php new file mode 100644 index 0000000..d22331b --- /dev/null +++ b/Modules/CircleInvoices/resources/views/invoices/list.blade.php @@ -0,0 +1,103 @@ +@if($table->resource->count() > 0) +
+ @if(!request()->has('page') || (request()->has('page') && request()->get('page') == 1)) +
+ +
+ +

{{__('Add Invoice')}}

+
+
+
+ @endif + + @foreach($table->resource as $itemKey => $item) + @php $itemPrimaryKey = $table->findPrimaryKey($item) @endphp +
+
+ + +

{{ $item->uuid }}

+ @if($item->status === 'pending') +
+ +
+ +
+
+
+ @elseif($item->status === 'active') +
+ +
+ +
+
+
+ @elseif($item->status === 'paid') +
+ +
+ +
+
+
+ @endif +
+
{{($item->total+$item->shipping)}}{{ $item->currency }}
+
+ @if($item->is_public) + + + + + + @endif + + + + + + + + + + + + + + + +
+
+ {{__('Created')}} {{ $item->created_at->diffForHumans() }} +
+
+
+ @endforeach +
+@else +
+
+ +

{{__("You don't have any invoice please add one")}}

+
+ + {{__('Create Invoice')}} + +
+
+
+@endif diff --git a/Modules/CircleInvoices/resources/views/invoices/print-public.blade.php b/Modules/CircleInvoices/resources/views/invoices/print-public.blade.php new file mode 100644 index 0000000..38a79e5 --- /dev/null +++ b/Modules/CircleInvoices/resources/views/invoices/print-public.blade.php @@ -0,0 +1,174 @@ +
+
+
+
+
+ +
+
+ @if($invoice->billed_from) +
+
+ {{__('Bill From')}}: +
+
+                    {{$invoice->billed_from}}
+                
+
+ @endif + @if($invoice->billed_to) +
+
+
+ {{__('Bill To')}}: +
+
+                        {{$invoice->billed_to}}
+                    
+ +
+
+ @endif +
+
+
+
+
+

{{__('INVOICE')}}

+
+
+ {{__('Invoice')}}# {{$invoice->uuid}} +
+
+
+
+
+
+
+
{{__('Issue Date')}} :
+
{{$invoice->invoice_date}}
+
+
+
{{__('Due Date')}} :
+
{{$invoice->due_date}}
+
+
+
{{__('Status')}} :
+
{{$invoice->status}}
+
+
+
{{__('Type')}} :
+
{{$invoice->type}}
+
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + @foreach($invoice->items as $key=>$item) + + + + + + + + + + @endforeach + +
#{{__('Item')}}{{__('Price')}}{{__('Discount')}}{{__('Tax')}}{{__('QTY')}}{{__('Total')}}
{{ $key+1 }}{{ $item->item }}{{ number_format($item->price, 2) }}{{ $invoice->currency }}{{ number_format($item->discount, 2) }}{{ $invoice->currency }}{{ number_format($item->tax, 2) }}{{ $invoice->currency }}{{ number_format($item->qty, 2) }}{{ number_format($item->total, 2) }}{{ $invoice->currency }}
+ +
+
+ +
+
+
+
+ {{__('Sub Total')}} +
+
+ {{ number_format(($invoice->total + $invoice->discount) - ($invoice->tax + $invoice->shipping), 2) }}{{ $invoice->currency }} +
+
+
+
+ {{__('Tax')}} +
+
+ {{ number_format($invoice->tax, 2) }}{{ $invoice->currency }} +
+
+
+
+ {{__('Discount')}} +
+
+ {{ number_format($invoice->discount, 2) }}{{ $invoice->currency }} +
+
+
+
+ {{__('Paid')}} +
+
+ {{ number_format($invoice->paid_amount, 2) }}{{ $invoice->currency }} +
+
+
+
+ {{__('Shipping')}} +
+
+ {{ number_format($invoice->shipping, 2) }}{{ $invoice->currency }} +
+
+
+
+ {{__('Balance Due')}} +
+
+ {{ number_format($invoice->total+$invoice->shipping, 2) }}{{ $invoice->currency }} +
+
+
+
+ + + @if($invoice->notes) +
+ +
+
+ {{__('Notes')}} +
+
+ {!! $invoice->notes !!} +
+
+ @endif +
+ + + setTimeout(function(){ + window.print(); + }, 1000); + + +
diff --git a/Modules/CircleInvoices/resources/views/invoices/print.blade.php b/Modules/CircleInvoices/resources/views/invoices/print.blade.php new file mode 100644 index 0000000..e9ab5e7 --- /dev/null +++ b/Modules/CircleInvoices/resources/views/invoices/print.blade.php @@ -0,0 +1,174 @@ +
+
+
+
+
+ +
+
+ @if($model->billed_from) +
+
+ {{__('Bill From')}}: +
+
+                    {{$model->billed_from}}
+                
+
+ @endif + @if($model->billed_to) +
+
+
+ {{__('Bill To')}}: +
+
+                        {{$model->billed_to}}
+                    
+ +
+
+ @endif +
+
+
+
+
+

{{__('INVOICE')}}

+
+
+ {{__('Invoice')}}# {{$model->uuid}} +
+
+
+
+
+
+
+
{{__('Issue Date')}} :
+
{{$model->invoice_date}}
+
+
+
{{__('Due Date')}} :
+
{{$model->due_date}}
+
+
+
{{__('Status')}} :
+
{{$model->status}}
+
+
+
{{__('Type')}} :
+
{{$model->type}}
+
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + @foreach($model->items as $key=>$item) + + + + + + + + + + @endforeach + +
#{{__('Item')}}{{__('Price')}}{{__('Discount')}}{{__('Tax')}}{{__('QTY')}}{{__('Total')}}
{{ $key+1 }}{{ $item->item }}{{ number_format($item->price, 2) }}{{ $model->currency }}{{ number_format($item->discount, 2) }}{{ $model->currency }}{{ number_format($item->tax, 2) }}{{ $model->currency }}{{ number_format($item->qty, 2) }}{{ number_format($item->total, 2) }}{{ $model->currency }}
+ +
+
+ +
+
+
+
+ {{__('Sub Total')}} +
+
+ {{ number_format(($model->total + $model->discount) - ($model->tax + $model->shipping), 2) }}{{ $model->currency }} +
+
+
+
+ {{__('Tax')}} +
+
+ {{ number_format($model->tax, 2) }}{{ $model->currency }} +
+
+
+
+ {{__('Discount')}} +
+
+ {{ number_format($model->discount, 2) }}{{ $model->currency }} +
+
+
+
+ {{__('Paid')}} +
+
+ {{ number_format($model->paid_amount, 2) }}{{ $model->currency }} +
+
+
+
+ {{__('Shipping')}} +
+
+ {{ number_format($model->shipping, 2) }}{{ $model->currency }} +
+
+
+
+ {{__('Balance Due')}} +
+
+ {{ number_format($model->total+$model->shipping, 2) }}{{ $model->currency }} +
+
+
+
+ + + @if($model->notes) +
+ +
+
+ {{__('Notes')}} +
+
+ {!! $model->notes !!} +
+
+ @endif +
+ + + setTimeout(function(){ + window.print(); + }, 1000); + + +
diff --git a/Modules/CircleInvoices/resources/views/invoices/public.blade.php b/Modules/CircleInvoices/resources/views/invoices/public.blade.php new file mode 100644 index 0000000..e43f1d7 --- /dev/null +++ b/Modules/CircleInvoices/resources/views/invoices/public.blade.php @@ -0,0 +1,184 @@ +@seoTitle(__('Invoice') . ' #' . $invoice->uuid . ' ' . __('From') . ' ' . $invoice->account->username . ' | ' . setting('site_name')) +@seoDescription($invoice->account->bio ?? setting('site_description')) +@seoKeywords(setting('site_keywords')) + + + +
+
+
+ {{ __('Invoice') . ' #' . $invoice->uuid . ' ' . __('From') . ' ' . $invoice->account->username }} +
+
+ +
+
+
+
+ +
+
+ @if($invoice->billed_from) +
+
+ {{__('Bill From')}}: +
+
+                    {{$invoice->billed_from}}
+                
+
+ @endif + @if($invoice->billed_to) +
+
+
+ {{__('Bill To')}}: +
+
+                        {{$invoice->billed_to}}
+                    
+ +
+
+ @endif +
+
+
+
+
+

{{__('INVOICE')}}

+
+
+ {{__('Invoice')}}# {{$invoice->uuid}} +
+
+
+
+
+
+
+
{{__('Issue Date')}} :
+
{{$invoice->invoice_date}}
+
+
+
{{__('Due Date')}} :
+
{{$invoice->due_date}}
+
+
+
{{__('Status')}} :
+
{{$invoice->status}}
+
+
+
{{__('Type')}} :
+
{{$invoice->type}}
+
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + @foreach($invoice->items as $key=>$item) + + + + + + + + + + @endforeach + +
#{{__('Item')}}{{__('Price')}}{{__('Discount')}}{{__('Tax')}}{{__('QTY')}}{{__('Total')}}
{{ $key+1 }}{{ $item->item }}{{ number_format($item->price, 2) }}{{ $invoice->currency }}{{ number_format($item->discount, 2) }}{{ $invoice->currency }}{{ number_format($item->tax, 2) }}{{ $invoice->currency }}{{ number_format($item->qty, 2) }}{{ number_format($item->total, 2) }}{{ $invoice->currency }}
+
+
+ +
+
+
+
+ {{__('Sub Total')}} +
+
+ {{ number_format(($invoice->total + $invoice->discount) - ($invoice->tax + $invoice->shipping), 2) }}{{ $invoice->currency }} +
+
+
+
+ {{__('Tax')}} +
+
+ {{ number_format($invoice->tax, 2) }}{{ $invoice->currency }} +
+
+
+
+ {{__('Discount')}} +
+
+ {{ number_format($invoice->discount, 2) }}{{ $invoice->currency }} +
+
+
+
+ {{__('Paid')}} +
+
+ {{ number_format($invoice->paid_amount, 2) }}{{ $invoice->currency }} +
+
+
+
+ {{__('Shipping')}} +
+
+ {{ number_format($invoice->shipping, 2) }}{{ $invoice->currency }} +
+
+
+
+ {{__('Balance Due')}} +
+
+ {{ number_format($invoice->total+$invoice->shipping, 2) }}{{ $invoice->currency }} +
+
+
+
+ + @if($invoice->notes) +
+
+
+ {{__('Notes')}} +
+
+ {!! $invoice->notes !!} +
+
+ @endif +
+ + +
+ +
+
+
+
+
diff --git a/Modules/CircleInvoices/resources/views/invoices/show.blade.php b/Modules/CircleInvoices/resources/views/invoices/show.blade.php new file mode 100644 index 0000000..a35c0bb --- /dev/null +++ b/Modules/CircleInvoices/resources/views/invoices/show.blade.php @@ -0,0 +1,182 @@ + +@extends('circle-xo::layouts.app') + +@section('title', __('Show Invoice') . ' #' . $model->id) + +@section('content') + +
+
+
+
+ +
+
+ @if($model->billed_from) +
+
+ {{__('Bill From')}}: +
+
+                    {{$model->billed_from}}
+                
+
+ @endif + @if($model->billed_to) +
+
+
+ {{__('Bill To')}}: +
+
+                        {{$model->billed_to}}
+                    
+ +
+
+ @endif +
+
+
+
+
+

{{__('INVOICE')}}

+
+
+ {{__('Invoice')}}# {{$model->uuid}} +
+
+
+
+
+
+
+
{{__('Issue Date')}} :
+
{{$model->invoice_date}}
+
+
+
{{__('Due Date')}} :
+
{{$model->due_date}}
+
+
+
{{__('Status')}} :
+
{{$model->status}}
+
+
+
{{__('Type')}} :
+
{{$model->type}}
+
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + @foreach($model->items as $key=>$item) + + + + + + + + + + @endforeach + +
#{{__('Item')}}{{__('Price')}}{{__('Discount')}}{{__('Tax')}}{{__('QTY')}}{{__('Total')}}
{{ $key+1 }}{{ $item->item }}{{ number_format($item->price, 2) }}{{ $model->currency }}{{ number_format($item->discount, 2) }}{{ $model->currency }}{{ number_format($item->tax, 2) }}{{ $model->currency }}{{ number_format($item->qty, 2) }}{{ number_format($item->total, 2) }}{{ $model->currency }}
+
+
+ +
+
+
+
+ {{__('Sub Total')}} +
+
+ {{ number_format(($model->total + $model->discount) - ($model->tax + $model->shipping), 2) }}{{ $model->currency }} +
+
+
+
+ {{__('Tax')}} +
+
+ {{ number_format($model->tax, 2) }}{{ $model->currency }} +
+
+
+
+ {{__('Discount')}} +
+
+ {{ number_format($model->discount, 2) }}{{ $model->currency }} +
+
+
+
+ {{__('Paid')}} +
+
+ {{ number_format($model->paid_amount, 2) }}{{ $model->currency }} +
+
+
+
+ {{__('Shipping')}} +
+
+ {{ number_format($model->shipping, 2) }}{{ $model->currency }} +
+
+
+
+ {{__('Balance Due')}} +
+
+ {{ number_format($model->total+$model->shipping, 2) }}{{ $model->currency }} +
+
+
+
+ + @if($model->notes) +
+
+
+ {{__('Notes')}} +
+
+ {!! $model->notes !!} +
+
+ @endif +
+ +
+ + + + +
+@endsection diff --git a/Modules/CircleInvoices/resources/views/layout/app.blade.php b/Modules/CircleInvoices/resources/views/layout/app.blade.php new file mode 100644 index 0000000..d78d7de --- /dev/null +++ b/Modules/CircleInvoices/resources/views/layout/app.blade.php @@ -0,0 +1,62 @@ + +
+
+ + +
+ +
+
+ +
+
+
+ {{ __('All Invoices') }} +
+ +
+
+ +
+ +
+
+ +
+
+
+ {{ __('Pending') }} +
+ +
+
+
+
+
+ @yield('content') +
+
+
diff --git a/Modules/CircleInvoices/resources/views/layout/parts/sidebar.blade.php b/Modules/CircleInvoices/resources/views/layout/parts/sidebar.blade.php new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/routes/.gitkeep b/Modules/CircleInvoices/routes/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/CircleInvoices/routes/api.php b/Modules/CircleInvoices/routes/api.php new file mode 100644 index 0000000..fbd2221 --- /dev/null +++ b/Modules/CircleInvoices/routes/api.php @@ -0,0 +1,15 @@ +name('profile.')->group(function () { + Route::get('profile/invoices', [\Modules\CircleInvoices\App\Http\Controllers\CircleXoInvoiceController::class, 'index'])->name('invoices.index'); + Route::get('profile/invoices/api', [\Modules\CircleInvoices\App\Http\Controllers\CircleXoInvoiceController::class, 'api'])->name('invoices.api'); + Route::get('profile/invoices/create', [\Modules\CircleInvoices\App\Http\Controllers\CircleXoInvoiceController::class, 'create'])->name('invoices.create'); + Route::post('profile/invoices', [\Modules\CircleInvoices\App\Http\Controllers\CircleXoInvoiceController::class, 'store'])->name('invoices.store'); + Route::get('profile/invoices/{model}', [\Modules\CircleInvoices\App\Http\Controllers\CircleXoInvoiceController::class, 'show'])->name('invoices.show'); + Route::get('profile/invoices/{model}/edit', [\Modules\CircleInvoices\App\Http\Controllers\CircleXoInvoiceController::class, 'edit'])->name('invoices.edit'); + Route::get('profile/invoices/{model}/print', [\Modules\CircleInvoices\App\Http\Controllers\CircleXoInvoiceController::class, 'print'])->name('invoices.print'); + Route::post('profile/invoices/{model}', [\Modules\CircleInvoices\App\Http\Controllers\CircleXoInvoiceController::class, 'update'])->name('invoices.update'); + Route::delete('profile/invoices/{model}', [\Modules\CircleInvoices\App\Http\Controllers\CircleXoInvoiceController::class, 'destroy'])->name('invoices.destroy'); +}); + + +Route::middleware(['web', 'splade'])->name('invoices.')->group(function () { + Route::get('invoices/{model}/show', [\Modules\CircleInvoices\App\Http\Controllers\CircleXoInvoiceController::class, 'showPublic'])->name('public.show'); + Route::get('invoices//{model}/print', [\Modules\CircleInvoices\App\Http\Controllers\CircleXoInvoiceController::class, 'printPublic'])->name('public.print'); +}); + diff --git a/Modules/CircleInvoices/vite.config.js b/Modules/CircleInvoices/vite.config.js new file mode 100644 index 0000000..9fd1983 --- /dev/null +++ b/Modules/CircleInvoices/vite.config.js @@ -0,0 +1,26 @@ +import { defineConfig } from 'vite'; +import laravel from 'laravel-vite-plugin'; + +export default defineConfig({ + build: { + outDir: '../../public/build-circleinvoices', + emptyOutDir: true, + manifest: true, + }, + plugins: [ + laravel({ + publicDirectory: '../../public', + buildDirectory: 'build-circleinvoices', + input: [ + __dirname + '/resources/assets/sass/app.scss', + __dirname + '/resources/assets/js/app.js' + ], + refresh: true, + }), + ], +}); + +//export const paths = [ +// 'Modules/$STUDLY_NAME$/resources/assets/sass/app.scss', +// 'Modules/$STUDLY_NAME$/resources/assets/js/app.js', +//]; \ No newline at end of file diff --git a/modules_statuses.json b/modules_statuses.json index 0d296f4..c9a3171 100644 --- a/modules_statuses.json +++ b/modules_statuses.json @@ -14,5 +14,6 @@ "TomatoSupport": true, "TomatoSections": true, "TomatoMenus": true, - "CircleContacts": true -} + "CircleContacts": true, + "CircleInvoices": true +} \ No newline at end of file diff --git a/resources/views/root.blade.php b/resources/views/root.blade.php index 1331984..2425108 100644 --- a/resources/views/root.blade.php +++ b/resources/views/root.blade.php @@ -11,7 +11,7 @@ } - + @splade