Compare commits
19 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
72695ea172 | |
|
|
1ef23eb45e | |
|
|
ad5bdd07fb | |
|
|
1e57833724 | |
|
|
7fc17244fd | |
|
|
eea85c9da4 | |
|
|
71c9848810 | |
|
|
8b76f59804 | |
|
|
f5a57a9d16 | |
|
|
78629989e4 | |
|
|
2640622685 | |
|
|
5143a69f52 | |
|
|
1679211f0e | |
|
|
accc3637ac | |
|
|
c70f113a07 | |
|
|
5af0457686 | |
|
|
766a1a8c2e | |
|
|
552cd34b13 | |
|
|
290d5e6cce |
|
|
@ -1,4 +1,4 @@
|
||||||
FROM php:8.3.36-fpm AS php
|
FROM php:8.3.11-fpm AS php
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN ls /app
|
RUN ls /app
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ steps:
|
||||||
- composer install --ignore-platform-reqs --no-dev
|
- composer install --ignore-platform-reqs --no-dev
|
||||||
|
|
||||||
- name: compress
|
- name: compress
|
||||||
image: php:8.3.36
|
image: php:8.1.6
|
||||||
commands:
|
commands:
|
||||||
- apt-get update -yqq
|
- apt-get update -yqq
|
||||||
- apt-get install -yqq zip tar
|
- apt-get install -yqq zip tar
|
||||||
|
|
@ -210,7 +210,7 @@ services:
|
||||||
MYSQL_DATABASE: nextcloud
|
MYSQL_DATABASE: nextcloud
|
||||||
MARIADB_AUTO_UPGRADE: 1
|
MARIADB_AUTO_UPGRADE: 1
|
||||||
- name: nextcloudserver
|
- name: nextcloudserver
|
||||||
image: nextcloud
|
image: nextcloud:31.0.13
|
||||||
environment:
|
environment:
|
||||||
MYSQL_PASSWORD: nextcloud
|
MYSQL_PASSWORD: nextcloud
|
||||||
MYSQL_DATABASE: nextcloud
|
MYSQL_DATABASE: nextcloud
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
# Letzte Änderungen
|
# Letzte Änderungen
|
||||||
|
|
||||||
### 1.12.29
|
|
||||||
|
|
||||||
- Veranstaltungen können nun "bereinigt", sprich alle TN gelöscht werden
|
|
||||||
|
|
||||||
### 1.12.27
|
### 1.12.27
|
||||||
|
|
||||||
- Bei Rechnungs-und Erinnerungsmails wird nun der Stammesname angezeigt. Zudm kann eine ReplyTo gesetzt werden, wenn gewünscht
|
- Bei Rechnungs-und Erinnerungsmails wird nun der Stammesname angezeigt. Zudm kann eine ReplyTo gesetzt werden, wenn gewünscht
|
||||||
|
|
|
||||||
57
CLAUDE.md
57
CLAUDE.md
|
|
@ -1,57 +0,0 @@
|
||||||
# CLAUDE.md
|
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
||||||
|
|
||||||
## Project
|
|
||||||
|
|
||||||
AdReMa ("AddressManagement") is a Laravel 11 + Inertia/Vue 3 app for German Scout groups (DPSG) to manage member data, courses, invoicing/contributions, and syncs with the external NaMi member database. Backend is PHP 8.3, frontend is Vue 3 + TypeScript rendered via Inertia (no separate SPA API).
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
### PHP / backend
|
|
||||||
|
|
||||||
- Run all tests: `php artisan test` (or `vendor/bin/pest`)
|
|
||||||
- Run a single test file: `php artisan test tests/Feature/Member/SomeTest.php`
|
|
||||||
- Run a single test by name: `php artisan test --filter=test_name`
|
|
||||||
- Static analysis: `vendor/bin/phpstan analyse` (larastan, level 6, config in `phpstan.neon`)
|
|
||||||
- Test suites are split in `phpunit.xml`: `Unit`, `Feature`, `Fileshare`, `Arch` (architecture rules in `tests/Arch.php`), `NamiUnit` (packages/laravel-nami), `EndToEnd`
|
|
||||||
|
|
||||||
### JS / frontend
|
|
||||||
|
|
||||||
- Dev server: `npm run dev` (alias for `vite`)
|
|
||||||
- Production build: `npm run prod`
|
|
||||||
- Lint: `npm run lint`, autofix: `npm run fix`
|
|
||||||
- Rebuild SVG sprite: `npm run img`
|
|
||||||
- The `packages/adrema-form` package (public event-registration form, embeddable) has its own `npm run build` / `build-import` (import mode is used when embedded in the main app) and is built separately.
|
|
||||||
|
|
||||||
### Environment
|
|
||||||
|
|
||||||
- Local dev runs via Docker Compose (`docker-compose.yml`); `.app.env` holds environment config, copied from `.app.env.example`.
|
|
||||||
- Submodules must be initialized: `git submodule update --init`.
|
|
||||||
- CI (`.drone.yml`) runs: composer install → npm build (main app + `adrema-form`) → `php artisan migrate` → `php artisan test` → `vendor/bin/phpstan analyse`.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Backend structure
|
|
||||||
|
|
||||||
Code under `app/` is organized by **domain module**, not by technical layer (no global `app/Http/Controllers` for most features). Each domain (e.g. `Member`, `Activity`, `Form`, `Invoice`, `Course`, `Fileshare`, `Contribution`, `Mailman`, `Mailgateway`, `Efz`, `Prevention`) typically contains its own `Actions/`, models, requests, and resources together. Older/shared code still lives in `app/Actions`.
|
|
||||||
|
|
||||||
- **Actions pattern**: business logic lives in single-purpose Action classes (`lorisleiva/laravel-actions`, `use AsAction`) with a `handle()` method, invoked directly from routes in `routes/web.php` rather than through traditional controllers.
|
|
||||||
- **NaMi integration**: `packages/laravel-nami` is a local Composer package (symlinked via `repositories` path in `composer.json`) wrapping the external NaMi API (members, courses, memberships, confessions, fees, regions, etc.). App-level `Pull*Action`/`Insert*Action` classes (`app/Actions/PullMemberAction.php`, `InsertMemberAction.php`, etc.) sync NaMi data into local models. - Please never edit files here, unless explicitly specified.
|
|
||||||
- **Local packages** (all under `packages/`, wired via Composer path repositories): `laravel-nami` (NaMi API client), `table-document` (PDF/table document generation), `flysystem-webdav` (WebDAV filesystem driver, used for cloud file storage/CardDAV-adjacent features), `tex` (LaTeX rendering, used for invoices/Bescheinigungen), `medialibrary-helper` (Vue components + backend helpers for file uploads, e.g. `FSinglefile`/`FMultiplefiles`), `adrema-form` (standalone Vite/Vue app for public event registration forms, built separately and embedded/imported into the main app). - Please never edit files here, unless explicitly specified
|
|
||||||
- **Modules feature flag**: `App\Module\Module` enum (`bill`, `course`, `event`) + `ModuleSettings` gate optional features per-installation; checked server-side via `hasModule()` and client-side via the `hasModule` Vue mixin (`resources/js/mixins/hasModule.js`).
|
|
||||||
- Data transfer uses `spatie/laravel-data` (`Data` classes) in several domains instead of plain arrays/DTOs.
|
|
||||||
- PHPStan type aliases for complex array shapes (e.g. contribution API payloads) are defined centrally in `phpstan.neon`.
|
|
||||||
|
|
||||||
### Frontend structure
|
|
||||||
|
|
||||||
- Entry point `resources/js/app.js` bootstraps Inertia + Pinia + Vue; pages are Vue SFCs under `resources/js/views/**`, resolved by path via Inertia's `resolve`.
|
|
||||||
- `resources/js/layouts/AppLayout.vue` is the default page layout, applied automatically unless a page sets its own `layout`.
|
|
||||||
- Global mixins (`hasModule`, `hasFlash`) are applied app-wide instead of per-component imports.
|
|
||||||
- State is managed with Pinia stores (`resources/js/stores`).
|
|
||||||
|
|
||||||
### Testing conventions
|
|
||||||
|
|
||||||
- Tests use Pest (`pestphp/pest`), with `tests/TestCase.php` / `tests/EndToEndTestCase.php` / `tests/FileshareTestCase.php` as base cases for different suites.
|
|
||||||
- `tests/RequestFactories` (worksome/request-factories) and `tests/Datasets` provide reusable test data/request builders.
|
|
||||||
- `tests/Arch.php` enforces architectural constraints via Pest's arch testing.
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
@servers(['docker' => ['stamm-silva@zoomyboy.de', 'stammgallier@stamm-gallier.de', 'dpsg-lennep@zoomyboy.de', 'dpsgbergischland@zoomyboy.de', 'dpsg@adrema.dpsg-koeln.de']])
|
@servers(['docker' => ['stamm-silva@zoomyboy.de', 'stammgallier@stamm-gallier.de', 'dpsg-lennep@zoomyboy.de', 'dpsgbergischland@zoomyboy.de', 'dpsg-koeln@dpsg-koeln.de']])
|
||||||
|
|
||||||
@task('deploy', ['on' => 'docker'])
|
@task('deploy', ['on' => 'docker'])
|
||||||
cd $ADREMA_PATH
|
cd $ADREMA_PATH
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ abstract class ConnectionType extends Data
|
||||||
{
|
{
|
||||||
$filesystem = $this->getFilesystem();
|
$filesystem = $this->getFilesystem();
|
||||||
|
|
||||||
return collect($filesystem->directories($parent ?: '/'))->sort()->values()->toArray();
|
return $filesystem->directories($parent ?: '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Form\Actions;
|
|
||||||
|
|
||||||
use App\Form\Models\Form;
|
|
||||||
use App\Lib\Events\Succeeded;
|
|
||||||
use Illuminate\Http\JsonResponse;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
|
||||||
|
|
||||||
class FormCleanupAction
|
|
||||||
{
|
|
||||||
use AsAction;
|
|
||||||
|
|
||||||
public function handle(Form $form): void
|
|
||||||
{
|
|
||||||
$form->participants()->get()->each->delete();
|
|
||||||
$form->save();
|
|
||||||
ClearFrontendCacheAction::run();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function asController(Form $form): JsonResponse
|
|
||||||
{
|
|
||||||
$this->handle($form);
|
|
||||||
|
|
||||||
Succeeded::message('Teilnehmende gelöscht.')->dispatch();
|
|
||||||
return response()->json([]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -190,8 +190,7 @@ class Form extends Model implements HasMedia
|
||||||
return Sorting::from($this->meta['sorting']);
|
return Sorting::from($this->meta['sorting']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isInDates(): bool
|
public function isInDates(): bool {
|
||||||
{
|
|
||||||
if ($this->registration_from && $this->registration_from->gt(now())) {
|
if ($this->registration_from && $this->registration_from->gt(now())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,6 @@ class FormResource extends JsonResource
|
||||||
'participant_root_index' => route('form.participant.index', ['form' => $this->getModel(), 'parent' => -1]),
|
'participant_root_index' => route('form.participant.index', ['form' => $this->getModel(), 'parent' => -1]),
|
||||||
'update' => route('form.update', $this->getModel()),
|
'update' => route('form.update', $this->getModel()),
|
||||||
'destroy' => route('form.destroy', $this->getModel()),
|
'destroy' => route('form.destroy', $this->getModel()),
|
||||||
'cleanup' => route('form.cleanup', $this->getModel()),
|
|
||||||
'is_dirty' => route('form.is-dirty', $this->getModel()),
|
'is_dirty' => route('form.is-dirty', $this->getModel()),
|
||||||
'frontend' => str(app(FormSettings::class)->registerUrl)->replace('{slug}', $this->slug),
|
'frontend' => str(app(FormSettings::class)->registerUrl)->replace('{slug}', $this->slug),
|
||||||
'export' => route('form.export', $this->getModel()),
|
'export' => route('form.export', $this->getModel()),
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,13 @@ trait HasValidation
|
||||||
'status' => ['required', 'string', 'max:255', Rule::in(InvoiceStatus::values())],
|
'status' => ['required', 'string', 'max:255', Rule::in(InvoiceStatus::values())],
|
||||||
'via' => ['required', 'string', 'max:255', Rule::in(BillKind::values())],
|
'via' => ['required', 'string', 'max:255', Rule::in(BillKind::values())],
|
||||||
'usage' => 'required|max:255|string',
|
'usage' => 'required|max:255|string',
|
||||||
'mail_email' => 'nullable|string|max:255|email',
|
|
||||||
'to' => 'array',
|
'to' => 'array',
|
||||||
'to.address' => 'required|string|max:255',
|
'to.address' => 'required|string|max:255',
|
||||||
'to.location' => 'required|string|max:255',
|
'to.location' => 'required|string|max:255',
|
||||||
'to.zip' => 'required|string|max:255',
|
'to.zip' => 'required|string|max:255',
|
||||||
'to.name' => 'required|string|max:255',
|
'to.name' => 'required|string|max:255',
|
||||||
'greeting' => 'required|string|max:255',
|
'to.email' => 'nullable|string|max:255|email',
|
||||||
|
'to.greeting' => 'required|string|max:255',
|
||||||
'positions' => 'array',
|
'positions' => 'array',
|
||||||
'positions.*.description' => 'required|string|max:300',
|
'positions.*.description' => 'required|string|max:300',
|
||||||
'positions.*.price' => 'required|integer|min:0',
|
'positions.*.price' => 'required|integer|min:0',
|
||||||
|
|
@ -42,6 +42,8 @@ trait HasValidation
|
||||||
'to.name' => 'Name',
|
'to.name' => 'Name',
|
||||||
'to.zip' => 'PLZ',
|
'to.zip' => 'PLZ',
|
||||||
'to.location' => 'Ort',
|
'to.location' => 'Ort',
|
||||||
|
'to.greeting' => 'Anrede',
|
||||||
|
'to.email' => 'E-Mail-Adresse',
|
||||||
'status' => 'Status',
|
'status' => 'Status',
|
||||||
'via' => 'Rechnungsweg',
|
'via' => 'Rechnungsweg',
|
||||||
'usage' => 'Verwendungszweck',
|
'usage' => 'Verwendungszweck',
|
||||||
|
|
|
||||||
|
|
@ -37,15 +37,15 @@ class InvoiceSendAction
|
||||||
foreach (Invoice::whereNeedsBill()->where('via', BillKind::EMAIL)->get() as $invoice) {
|
foreach (Invoice::whereNeedsBill()->where('via', BillKind::EMAIL)->get() as $invoice) {
|
||||||
$document = BillDocument::fromInvoice($invoice);
|
$document = BillDocument::fromInvoice($invoice);
|
||||||
$path = Storage::disk('temp')->path(Tex::compile($document)->storeIn('', 'temp'));
|
$path = Storage::disk('temp')->path(Tex::compile($document)->storeIn('', 'temp'));
|
||||||
Mail::to($invoice->getMailRecipient())->send(new BillMail($invoice, $path));
|
Mail::to($invoice->getRecipient())->send(new BillMail($invoice, $path));
|
||||||
$invoice->sent($document);
|
$document->sent($invoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Invoice::whereNeedsRemember()->where('via', BillKind::EMAIL)->get() as $invoice) {
|
foreach (Invoice::whereNeedsRemember()->where('via', BillKind::EMAIL)->get() as $invoice) {
|
||||||
$document = RememberDocument::fromInvoice($invoice);
|
$document = RememberDocument::fromInvoice($invoice);
|
||||||
$path = Storage::disk('temp')->path(Tex::compile($document)->storeIn('', 'temp'));
|
$path = Storage::disk('temp')->path(Tex::compile($document)->storeIn('', 'temp'));
|
||||||
Mail::to($invoice->getMailRecipient())->send(new RememberMail($invoice, $path));
|
Mail::to($invoice->getRecipient())->send(new RememberMail($invoice, $path));
|
||||||
$invoice->sent($document);
|
$document->sent($invoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,13 @@ class MassPostPdfAction
|
||||||
foreach (Invoice::whereNeedsBill()->where('via', BillKind::POST)->get() as $invoice) {
|
foreach (Invoice::whereNeedsBill()->where('via', BillKind::POST)->get() as $invoice) {
|
||||||
$document = BillDocument::fromInvoice($invoice);
|
$document = BillDocument::fromInvoice($invoice);
|
||||||
$documents[] = $document;
|
$documents[] = $document;
|
||||||
$invoice->sent($document);
|
$document->sent($invoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Invoice::whereNeedsRemember()->where('via', BillKind::POST)->get() as $invoice) {
|
foreach (Invoice::whereNeedsRemember()->where('via', BillKind::POST)->get() as $invoice) {
|
||||||
$document = RememberDocument::fromInvoice($invoice);
|
$document = RememberDocument::fromInvoice($invoice);
|
||||||
$documents[] = $document;
|
$documents[] = $document;
|
||||||
$invoice->sent($document);
|
$document->sent($invoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!count($documents)) {
|
if (!count($documents)) {
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,17 @@ class MassStoreAction
|
||||||
$invoices = collect([]);
|
$invoices = collect([]);
|
||||||
|
|
||||||
$memberGroup = Member::payable()->get()
|
$memberGroup = Member::payable()->get()
|
||||||
->groupBy(fn ($member) => "{$member->bill_kind->value}{$member->lastname}{$member->address}{$member->zip}{$member->location}");
|
->groupBy(fn($member) => "{$member->bill_kind->value}{$member->lastname}{$member->address}{$member->zip}{$member->location}");
|
||||||
|
|
||||||
foreach ($memberGroup as $members) {
|
foreach ($memberGroup as $members) {
|
||||||
$invoice = Invoice::createForMember($members->first(), $members, $year);
|
$factory = $members->first()->getInvoiceFactory()
|
||||||
|
->withFamilyMembers($members)
|
||||||
|
->year($year);
|
||||||
|
|
||||||
|
$invoice = Invoice::createFromFactory($factory);
|
||||||
$invoice->save();
|
$invoice->save();
|
||||||
$invoice->positions()->createMany($invoice->positions->toArray());
|
$invoice->positions()->createMany($invoice->getRelationValue('positions'));
|
||||||
$invoices->push($invoice->fresh('positions'));
|
$invoices->push($invoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
event(new InvoicesMassStored($year, $invoices));
|
event(new InvoicesMassStored($year, $invoices));
|
||||||
|
|
|
||||||
|
|
@ -30,12 +30,17 @@ class MemberNewInvoiceAction
|
||||||
*/
|
*/
|
||||||
public function handle(Member $member, Subscription $subscription, int $year): array
|
public function handle(Member $member, Subscription $subscription, int $year): array
|
||||||
{
|
{
|
||||||
$invoice = Invoice::createForMember($member, collect([$member]), $year, $subscription);
|
$factory = $member->first()->getInvoiceFactory()
|
||||||
|
->forSingleMember()
|
||||||
|
->year($year)
|
||||||
|
->withSubscription($subscription);
|
||||||
|
|
||||||
|
$invoice = Invoice::createFromFactory($factory);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...$invoice->getAttributes(),
|
...$invoice->getAttributes(),
|
||||||
'to' => $invoice->to,
|
'to' => $invoice->to,
|
||||||
'positions' => $invoice->getRelationValue('positions')->toArray(),
|
'positions' => $invoice->getRelationValue('positions'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
|
|
||||||
namespace App\Invoice;
|
namespace App\Invoice;
|
||||||
|
|
||||||
use App\Payment\Payment;
|
use App\Invoice\Models\Invoice;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
||||||
|
|
||||||
class BillDocument extends InvoiceDocument
|
class BillDocument extends InvoiceDocument
|
||||||
{
|
{
|
||||||
|
|
@ -17,4 +16,8 @@ class BillDocument extends InvoiceDocument
|
||||||
{
|
{
|
||||||
return 'tex.invoice.bill';
|
return 'tex.invoice.bill';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function sent(Invoice $invoice): void {
|
||||||
|
$invoice->sentNow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Invoice\Creators;
|
||||||
|
|
||||||
|
use App\Invoice\BillKind;
|
||||||
|
use App\Invoice\Data\PositionData;
|
||||||
|
use App\Invoice\Data\ReceiverData;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
interface InvoiceCreator {
|
||||||
|
|
||||||
|
public function getVia(): BillKind;
|
||||||
|
|
||||||
|
public function getUsage(): string;
|
||||||
|
|
||||||
|
/** @return Collection<int, PositionData> */
|
||||||
|
public function getPositions(): Collection;
|
||||||
|
|
||||||
|
public function getReceiver(): ReceiverData;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Invoice\Data;
|
||||||
|
|
||||||
|
use Spatie\LaravelData\Attributes\MapOutputName;
|
||||||
|
use Spatie\LaravelData\Data;
|
||||||
|
|
||||||
|
class PositionData extends Data
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $description,
|
||||||
|
public int $price,
|
||||||
|
#[MapOutputName('member_id')]
|
||||||
|
public int $memberId,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Invoice\Data;
|
||||||
|
|
||||||
|
use Spatie\LaravelData\Data;
|
||||||
|
|
||||||
|
class ReceiverData extends Data
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $name,
|
||||||
|
public string $address,
|
||||||
|
public string $zip,
|
||||||
|
public string $location,
|
||||||
|
public ?string $email,
|
||||||
|
public string $greeting,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public static function default(): self
|
||||||
|
{
|
||||||
|
return new self(
|
||||||
|
name: '',
|
||||||
|
address: '',
|
||||||
|
zip: '',
|
||||||
|
location: '',
|
||||||
|
email: null,
|
||||||
|
greeting: ''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ abstract class InvoiceDocument extends Document
|
||||||
{
|
{
|
||||||
abstract public function getSubject(): string;
|
abstract public function getSubject(): string;
|
||||||
abstract public function view(): string;
|
abstract public function view(): string;
|
||||||
|
abstract public function sent(Invoice $invoice): void;
|
||||||
|
|
||||||
public string $until;
|
public string $until;
|
||||||
public string $filename;
|
public string $filename;
|
||||||
|
|
@ -34,12 +35,13 @@ abstract class InvoiceDocument extends Document
|
||||||
|
|
||||||
public static function fromInvoice(Invoice $invoice): self
|
public static function fromInvoice(Invoice $invoice): self
|
||||||
{
|
{
|
||||||
|
$recipient = $invoice->getRecipient();
|
||||||
return static::factory()->withoutMagicalCreation()->from([
|
return static::factory()->withoutMagicalCreation()->from([
|
||||||
'toName' => $invoice->to['name'],
|
'toName' => $recipient->name,
|
||||||
'toAddress' => $invoice->to['address'],
|
'toAddress' => $recipient->address,
|
||||||
'toZip' => $invoice->to['zip'],
|
'toZip' => $recipient->zip,
|
||||||
'toLocation' => $invoice->to['location'],
|
'toLocation' => $recipient->location,
|
||||||
'greeting' => $invoice->greeting,
|
'greeting' => $recipient->greeting,
|
||||||
'positions' => static::renderPositions($invoice),
|
'positions' => static::renderPositions($invoice),
|
||||||
'usage' => $invoice->usage,
|
'usage' => $invoice->usage,
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -4,20 +4,18 @@ namespace App\Invoice\Models;
|
||||||
|
|
||||||
use App\Invoice\BillDocument;
|
use App\Invoice\BillDocument;
|
||||||
use App\Invoice\BillKind;
|
use App\Invoice\BillKind;
|
||||||
|
use App\Invoice\Creators\InvoiceCreator;
|
||||||
|
use App\Invoice\Data\ReceiverData;
|
||||||
use App\Invoice\Enums\InvoiceStatus;
|
use App\Invoice\Enums\InvoiceStatus;
|
||||||
use App\Invoice\InvoiceDocument;
|
use App\Invoice\InvoiceDocument;
|
||||||
use App\Invoice\InvoiceSettings;
|
use App\Invoice\InvoiceSettings;
|
||||||
use App\Invoice\RememberDocument;
|
use App\Invoice\RememberDocument;
|
||||||
use App\Member\Member;
|
|
||||||
use App\Payment\Subscription;
|
|
||||||
use Database\Factories\Invoice\Models\InvoiceFactory;
|
use Database\Factories\Invoice\Models\InvoiceFactory;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Laravel\Scout\Searchable;
|
use Laravel\Scout\Searchable;
|
||||||
use stdClass;
|
|
||||||
|
|
||||||
class Invoice extends Model
|
class Invoice extends Model
|
||||||
{
|
{
|
||||||
|
|
@ -28,7 +26,7 @@ class Invoice extends Model
|
||||||
public $guarded = [];
|
public $guarded = [];
|
||||||
|
|
||||||
public $casts = [
|
public $casts = [
|
||||||
'to' => 'json',
|
'to' => ReceiverData::class,
|
||||||
'status' => InvoiceStatus::class,
|
'status' => InvoiceStatus::class,
|
||||||
'via' => BillKind::class,
|
'via' => BillKind::class,
|
||||||
'sent_at' => 'datetime',
|
'sent_at' => 'datetime',
|
||||||
|
|
@ -43,38 +41,16 @@ class Invoice extends Model
|
||||||
return $this->hasMany(InvoicePosition::class);
|
return $this->hasMany(InvoicePosition::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static function createFromFactory(InvoiceCreator $factory): self
|
||||||
* @param Collection<int, Member> $members
|
|
||||||
*/
|
|
||||||
public static function createForMember(Member $member, Collection $members, int $year, ?Subscription $subscription = null): self
|
|
||||||
{
|
{
|
||||||
$invoice = new self([
|
$invoice = new self([
|
||||||
'to' => [
|
'to' => $factory->getReceiver(),
|
||||||
'name' => 'Familie ' . $member->lastname,
|
|
||||||
'address' => $member->address,
|
|
||||||
'zip' => $member->zip,
|
|
||||||
'location' => $member->location,
|
|
||||||
],
|
|
||||||
'greeting' => 'Liebe Familie ' . $member->lastname,
|
|
||||||
'status' => InvoiceStatus::NEW,
|
'status' => InvoiceStatus::NEW,
|
||||||
'via' => $member->bill_kind,
|
'via' => $factory->getVia(),
|
||||||
'usage' => 'Mitgliedsbeitrag für ' . $member->lastname,
|
'usage' => $factory->getUsage(),
|
||||||
'mail_email' => $member->email_parents ?: $member->email,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$positions = collect([]);
|
$invoice->setRelation('positions', $factory->getPositions()->toArray());
|
||||||
foreach ($members as $member) {
|
|
||||||
$memberSubscription = $subscription ?: $member->subscription;
|
|
||||||
foreach ($memberSubscription->children as $child) {
|
|
||||||
$positions->push([
|
|
||||||
'description' => str($child->name)->replace('{name}', $member->firstname . ' ' . $member->lastname)->replace('{year}', (string) $year),
|
|
||||||
'price' => $child->amount,
|
|
||||||
'member_id' => $member->id,
|
|
||||||
'id' => null,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$invoice->setRelation('positions', $positions);
|
|
||||||
|
|
||||||
return $invoice;
|
return $invoice;
|
||||||
}
|
}
|
||||||
|
|
@ -121,30 +97,16 @@ class Invoice extends Model
|
||||||
->where('last_remembered_at', '<=', now()->subWeeks($weeks));
|
->where('last_remembered_at', '<=', now()->subWeeks($weeks));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMailRecipient(): stdClass
|
public function getRecipient(): ReceiverData {
|
||||||
{
|
return $this->to;
|
||||||
return (object) [
|
|
||||||
'email' => $this->mail_email,
|
|
||||||
'name' => $this->to['name']
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sent(InvoiceDocument $document): void
|
public function rememberedNow(): void {
|
||||||
{
|
$this->update(['last_remembered_at' => now(), 'status' => InvoiceStatus::SENT]);
|
||||||
if (is_a($document, BillDocument::class)) {
|
}
|
||||||
$this->update([
|
|
||||||
'sent_at' => now(),
|
|
||||||
'status' => InvoiceStatus::SENT,
|
|
||||||
'last_remembered_at' => now(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_a($document, RememberDocument::class)) {
|
public function sentNow(): void {
|
||||||
$this->update([
|
$this->update(['sent_at' => now(), 'status' => InvoiceStatus::SENT, 'last_remembered_at' => now()]);
|
||||||
'last_remembered_at' => now(),
|
|
||||||
'status' => InvoiceStatus::SENT,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -155,10 +117,8 @@ class Invoice extends Model
|
||||||
public function toSearchableArray(): array
|
public function toSearchableArray(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'to' => implode(', ', $this->to),
|
'to' => implode(', ', $this->getRecipient()->toArray()),
|
||||||
'usage' => $this->usage,
|
'usage' => $this->usage,
|
||||||
'greeting' => $this->greeting,
|
|
||||||
'mail_email' => $this->mail_email,
|
|
||||||
'status' => $this->status->value,
|
'status' => $this->status->value,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
|
|
||||||
namespace App\Invoice;
|
namespace App\Invoice;
|
||||||
|
|
||||||
use App\Payment\Payment;
|
use App\Invoice\Models\Invoice;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
||||||
|
|
||||||
class RememberDocument extends InvoiceDocument
|
class RememberDocument extends InvoiceDocument
|
||||||
{
|
{
|
||||||
|
|
@ -17,4 +16,8 @@ class RememberDocument extends InvoiceDocument
|
||||||
{
|
{
|
||||||
return 'tex.invoice.remember';
|
return 'tex.invoice.remember';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function sent(Invoice $invoice): void {
|
||||||
|
$invoice->rememberedNow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Invoice\Resources;
|
namespace App\Invoice\Resources;
|
||||||
|
|
||||||
use App\Invoice\BillKind;
|
use App\Invoice\BillKind;
|
||||||
|
use App\Invoice\Data\ReceiverData;
|
||||||
use App\Invoice\Enums\InvoiceStatus;
|
use App\Invoice\Enums\InvoiceStatus;
|
||||||
use App\Invoice\Models\Invoice;
|
use App\Invoice\Models\Invoice;
|
||||||
use App\Invoice\Scopes\InvoiceFilterScope;
|
use App\Invoice\Scopes\InvoiceFilterScope;
|
||||||
|
|
@ -35,9 +36,7 @@ class InvoiceResource extends JsonResource
|
||||||
'status' => $this->status->value,
|
'status' => $this->status->value,
|
||||||
'via' => $this->via->value,
|
'via' => $this->via->value,
|
||||||
'positions' => InvoicePositionResource::collection($this->whenLoaded('positions')),
|
'positions' => InvoicePositionResource::collection($this->whenLoaded('positions')),
|
||||||
'greeting' => $this->greeting,
|
|
||||||
'usage' => $this->usage,
|
'usage' => $this->usage,
|
||||||
'mail_email' => $this->mail_email,
|
|
||||||
'links' => [
|
'links' => [
|
||||||
'pdf' => route('invoice.pdf', ['invoice' => $this->getModel()]),
|
'pdf' => route('invoice.pdf', ['invoice' => $this->getModel()]),
|
||||||
'rememberpdf' => route('invoice.rememberpdf', ['invoice' => $this->getModel()]),
|
'rememberpdf' => route('invoice.rememberpdf', ['invoice' => $this->getModel()]),
|
||||||
|
|
@ -65,18 +64,11 @@ class InvoiceResource extends JsonResource
|
||||||
'subscriptions' => Subscription::forSelect(),
|
'subscriptions' => Subscription::forSelect(),
|
||||||
'filter' => InvoiceFilterScope::fromRequest(request()->input('filter', '')),
|
'filter' => InvoiceFilterScope::fromRequest(request()->input('filter', '')),
|
||||||
'default' => [
|
'default' => [
|
||||||
'to' => [
|
'to' => ReceiverData::default(),
|
||||||
'name' => '',
|
|
||||||
'address' => '',
|
|
||||||
'zip' => '',
|
|
||||||
'location' => '',
|
|
||||||
],
|
|
||||||
'positions' => [],
|
'positions' => [],
|
||||||
'greeting' => '',
|
|
||||||
'status' => InvoiceStatus::NEW->value,
|
'status' => InvoiceStatus::NEW->value,
|
||||||
'via' => null,
|
'via' => null,
|
||||||
'usage' => '',
|
'usage' => '',
|
||||||
'mail_email' => '',
|
|
||||||
],
|
],
|
||||||
'default_position' => [
|
'default_position' => [
|
||||||
'id' => null,
|
'id' => null,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Member\Factories;
|
||||||
|
|
||||||
|
use App\Invoice\BillKind;
|
||||||
|
use App\Invoice\Creators\InvoiceCreator;
|
||||||
|
use App\Invoice\Data\PositionData;
|
||||||
|
use App\Invoice\Data\ReceiverData;
|
||||||
|
use App\Member\Member;
|
||||||
|
use App\Payment\Subscription;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
class MemberInvoiceFactory implements InvoiceCreator
|
||||||
|
{
|
||||||
|
|
||||||
|
/** @var Collection<int, Member> */
|
||||||
|
private Collection $positions;
|
||||||
|
|
||||||
|
private int $year;
|
||||||
|
|
||||||
|
private ?Subscription $subscription = null;
|
||||||
|
|
||||||
|
public function __construct(private Member $member) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection<int, Member> $members
|
||||||
|
*/
|
||||||
|
public function withFamilyMembers(Collection $members): self
|
||||||
|
{
|
||||||
|
$this->positions = $members;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function forSingleMember(): self
|
||||||
|
{
|
||||||
|
$this->positions = collect([$this->member]);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function year(int $year): self
|
||||||
|
{
|
||||||
|
$this->year = $year;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withSubscription(Subscription $subscription): self
|
||||||
|
{
|
||||||
|
$this->subscription = $subscription;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getReceiver(): ReceiverData {
|
||||||
|
return ReceiverData::from([
|
||||||
|
'name' => 'Familie ' . $this->member->lastname,
|
||||||
|
'address' => $this->member->address,
|
||||||
|
'zip' => $this->member->zip,
|
||||||
|
'location' => $this->member->location,
|
||||||
|
'greeting' => 'Liebe Familie ' . $this->member->lastname,
|
||||||
|
'email' => $this->member->email_parents ?: $this->member->email,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVia(): BillKind {
|
||||||
|
return $this->member->bill_kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUsage(): string {
|
||||||
|
return 'Mitgliedsbeitrag für ' . $this->member->lastname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPositions(): Collection {
|
||||||
|
/** @var Collection<int, PositionData> */
|
||||||
|
$positions = collect([]);
|
||||||
|
foreach ($this->positions as $member) {
|
||||||
|
$memberSubscription = $this->subscription ?: $member->subscription;
|
||||||
|
foreach ($memberSubscription->children as $child) {
|
||||||
|
$positions->push(PositionData::from([
|
||||||
|
'description' => str($child->name)->replace('{name}', $member->firstname . ' ' . $member->lastname)->replace('{year}', (string) $this->year),
|
||||||
|
'price' => $child->amount,
|
||||||
|
'memberId' => $member->id,
|
||||||
|
'id' => null,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $positions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ use App\Gender;
|
||||||
use App\Group;
|
use App\Group;
|
||||||
use App\Invoice\BillKind;
|
use App\Invoice\BillKind;
|
||||||
use App\Invoice\Models\InvoicePosition;
|
use App\Invoice\Models\InvoicePosition;
|
||||||
|
use App\Member\Factories\MemberInvoiceFactory;
|
||||||
use App\Nami\HasNamiField;
|
use App\Nami\HasNamiField;
|
||||||
use App\Nationality;
|
use App\Nationality;
|
||||||
use App\Payment\Subscription;
|
use App\Payment\Subscription;
|
||||||
|
|
@ -385,6 +386,10 @@ class Member extends Model implements Geolocatable, Preventable
|
||||||
return $query->where('bill_kind', '!=', null)->where('subscription_id', '!=', null);
|
return $query->where('bill_kind', '!=', null)->where('subscription_id', '!=', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getInvoiceFactory(): MemberInvoiceFactory {
|
||||||
|
return new MemberInvoiceFactory($this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -156,10 +156,10 @@ return [
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
Invoice::class => [
|
Invoice::class => [
|
||||||
'filterableAttributes' => ['to', 'usage', 'greeting', 'mail_email', 'status', 'id'],
|
'filterableAttributes' => ['to', 'usage', 'status', 'id'],
|
||||||
'searchableAttributes' => ['to', 'usage', 'greeting', 'mail_email', 'status', 'id'],
|
'searchableAttributes' => ['to', 'usage', 'status', 'id'],
|
||||||
'sortableAttributes' => [],
|
'sortableAttributes' => [],
|
||||||
'displayedAttributes' => ['to', 'usage', 'greeting', 'mail_email', 'status', 'id'],
|
'displayedAttributes' => ['to', 'usage', 'status', 'id'],
|
||||||
'pagination' => [
|
'pagination' => [
|
||||||
'maxTotalHits' => 1000000,
|
'maxTotalHits' => 1000000,
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,10 @@ class InvoiceFactory extends Factory
|
||||||
public function definition()
|
public function definition()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'greeting' => $this->faker->words(4, true),
|
|
||||||
'to' => ReceiverRequestFactory::new()->create(),
|
'to' => ReceiverRequestFactory::new()->create(),
|
||||||
'status' => InvoiceStatus::NEW->value,
|
'status' => InvoiceStatus::NEW->value,
|
||||||
'via' => BillKind::POST->value,
|
'via' => BillKind::POST->value,
|
||||||
'usage' => $this->faker->words(4, true),
|
'usage' => $this->faker->words(4, true),
|
||||||
'mail_email' => $this->faker->safeEmail(),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
foreach (DB::table('invoices')->get() as $invoice) {
|
||||||
|
$to = json_decode($invoice->to);
|
||||||
|
$position = DB::table('invoice_positions')->where('invoice_id', $invoice->id)->first();
|
||||||
|
|
||||||
|
if ($position) {
|
||||||
|
$member = DB::table('members')->where('id', $position->member_id)->first();
|
||||||
|
if ($member) {
|
||||||
|
$to->email = $member->email_parents ?: $member->email;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$to->greeting = 'Liebe '.$to->name;
|
||||||
|
|
||||||
|
DB::table('invoices')->where('id', $invoice->id)->update(['to' => json_encode($to)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Schema::table('invoices', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('greeting');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('invoices', function (Blueprint $table) {
|
||||||
|
$table->string('greeting');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -30,7 +30,10 @@ export default typescriptEslint.config(
|
||||||
'vue/multi-word-component-names': 'off',
|
'vue/multi-word-component-names': 'off',
|
||||||
'vue/max-attributes-per-line': 'off',
|
'vue/max-attributes-per-line': 'off',
|
||||||
'vue/singleline-html-element-content-newline': 'off',
|
'vue/singleline-html-element-content-newline': 'off',
|
||||||
'vue/first-attribute-linebreak': 'off',
|
"vue/first-attribute-linebreak": ["error", {
|
||||||
|
"singleline": "beside",
|
||||||
|
"multiline": "beside"
|
||||||
|
}],
|
||||||
'vue/no-undef-properties': ['error', {
|
'vue/no-undef-properties': ['error', {
|
||||||
'ignores': ['/^\\$/']
|
'ignores': ['/^\\$/']
|
||||||
}]
|
}]
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="height: 512px; width: 512px;"><g class="" transform="translate(0,0)" style=""><path d="m153.654 18 52.57 134.734c1.698 3.994 4.05 5.83 7.243 6.977 3.2 1.15 7.36 1.2 11.058.17 3.698-1.03 6.71-3.146 7.996-4.915 1.288-1.77 1.634-2.564.505-5.24l-.046-.112L181.57 18h-27.916zm94.168 120.143 1.88 4.81-.09-.223c3.346 7.937 1.828 16.822-2.532 22.82-4.36 5.996-10.773 9.734-17.723 11.67-6.95 1.937-14.653 2.065-21.98-.57-7.327-2.634-14.155-8.447-17.742-16.923l-.05-.118-1.757-4.5c-31.31 19.804-42.47 42.026-35.367 68.89 1.24 4.681 3.422 12.364 5.964 22.13 74.37-5.274 139.945-23.872 199.808-51.6-10.297-13.867-22.5-25.83-38.232-34.53-20.505-11.34-47.652-20.157-72.178-21.857zm120.557 71.52c-61.497 28.81-129.173 48.378-205.575 54.196 2.03 8.683 4.08 18.28 5.95 28.495 89.592-10.084 163.043-26.22 217.755-48.767-5.743-11.72-11.593-23.19-18.13-33.924zm26.04 50.16c-57.093 23.772-131.99 40.087-222.73 50.322C180.697 371.423 179.614 446.752 128 480c16.27 0 31.892-.152 46.926-.45 17.84-25.554 31.27-66.222 32.08-86.146 8.27 16.793 3.297 59.32-5.36 85.434 2.735-.093 5.435-.193 8.127-.297 11.824-12.397 11.724-28.632 14.72-47.284 3.324 14.92 7 32.967 9.505 46.156 11.273-.616 22.152-1.34 32.606-2.183 16.38-20.358 21.65-49.604 18.63-85.48 4.226 29.1 9.116 62.138 11.873 82.55a771.724 771.724 0 0 0 27.807-3.614c5.04-18.787-4.1-48.444-2.072-69.54 11.123 43.113 22.247 55.45 33.37 64.043a456.166 456.166 0 0 0 15.733-3.526c-4.7-13.95 1.573-22.497 1.18-39.986 5.647 18.99 14.625 26.958 24.428 32.816 6.506-2.1 12.66-4.336 18.492-6.697-10.538-6.57-10.113-26.374-12.38-42.926 5.954 21.703 14.413 32.418 24.083 37.816 29.124-13.8 48.69-31.534 60.398-53.657-9.078-3.82-18.674-13.002-28.068-20.092 13.214 7.477 23.684 10.614 32.37 10.93a112.386 112.386 0 0 0 3.552-9.868c-56.326-19.528-80.07-64.018-101.58-108.178z" fill="#fff" fill-opacity="1"></path></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.9 KiB |
|
|
@ -23,54 +23,33 @@
|
||||||
<div v-show="active === 0" class="grid grid-cols-4 gap-3">
|
<div v-show="active === 0" class="grid grid-cols-4 gap-3">
|
||||||
<div class="flex space-x-3 col-span-2">
|
<div class="flex space-x-3 col-span-2">
|
||||||
<f-text id="name" v-model="single.name" class="grow" label="Name" required />
|
<f-text id="name" v-model="single.name" class="grow" label="Name" required />
|
||||||
<f-switch
|
<f-switch id="is_active" v-model="single.is_active" name="is_active" label="Aktiv" hint="Inaktive Veranstaltungen werden außerhalb von Adrema wie nicht existierende Veranstaltungen betrachtet. Insbesondere ist eine Anmeldung dann nicht möglich und die Veranstaltung erscheint auch nicht in der Veranstaltungs-Übersicht." />
|
||||||
id="is_active"
|
<f-switch id="is_private" v-model="single.is_private" name="is_private" label="Privat" hint="Ist eine Veranstaltung privat, so wird diese nicht auf der Website angezeigt. Eine Anmeldung ist jedoch trotzdem möglich, wenn man über den Anmelde-Link verfügt." />
|
||||||
v-model="single.is_active"
|
|
||||||
name="is_active"
|
|
||||||
label="Aktiv"
|
|
||||||
hint="Inaktive Veranstaltungen werden außerhalb von Adrema wie nicht existierende Veranstaltungen betrachtet. Insbesondere ist eine Anmeldung dann nicht möglich und die Veranstaltung erscheint auch nicht in der Veranstaltungs-Übersicht."
|
|
||||||
/>
|
|
||||||
<f-switch
|
|
||||||
id="is_private"
|
|
||||||
v-model="single.is_private"
|
|
||||||
name="is_private"
|
|
||||||
label="Privat"
|
|
||||||
hint="Ist eine Veranstaltung privat, so wird diese nicht auf der Website angezeigt. Eine Anmeldung ist jedoch trotzdem möglich, wenn man über den Anmelde-Link verfügt."
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<f-singlefile
|
<f-singlefile id="header_image"
|
||||||
id="header_image"
|
v-model="single.header_image"
|
||||||
v-model="single.header_image"
|
class="col-span-2"
|
||||||
class="col-span-2"
|
label="Bild"
|
||||||
label="Bild"
|
name="header_image"
|
||||||
name="header_image"
|
parent-name="form"
|
||||||
parent-name="form"
|
:parent-id="single.id"
|
||||||
:parent-id="single.id"
|
collection="headerImage"
|
||||||
collection="headerImage"
|
required
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
<f-text id="from" v-model="single.from" type="date" label="Von" required />
|
<f-text id="from" v-model="single.from" type="date" label="Von" required />
|
||||||
<f-text id="to" v-model="single.to" type="date" label="Bis" required />
|
<f-text id="to" v-model="single.to" type="date" label="Bis" required />
|
||||||
<f-text id="zip" v-model="single.zip" label="PLZ" />
|
<f-text id="zip" v-model="single.zip" label="PLZ" />
|
||||||
<f-text id="location" v-model="single.location" label="Ort" />
|
<f-text id="location" v-model="single.location" label="Ort" />
|
||||||
<f-select id="country" v-model="single.country" class="col-span-2" name="country" label="Land" :options="meta.countries" />
|
<f-select id="country" v-model="single.country" class="col-span-2" name="country" label="Land" :options="meta.countries" />
|
||||||
<f-text
|
<f-text id="registration_from" v-model="single.registration_from" type="datetime-local" label="Registrierung von" hint="Ist eine Anmeldung laut dieser zwei Datumsangaben möglich, kann man sich anmelden. Andernfalls wird die Veranstaltung (mit Beschreibungstext) auf der Übersichtsseite angezeigt, man kommt allerdings nicht zum Anmeldeformular." required />
|
||||||
id="registration_from"
|
|
||||||
v-model="single.registration_from"
|
|
||||||
type="datetime-local"
|
|
||||||
label="Registrierung von"
|
|
||||||
hint="Ist eine Anmeldung laut dieser zwei Datumsangaben möglich, kann man sich anmelden. Andernfalls wird die Veranstaltung (mit Beschreibungstext) auf der Übersichtsseite angezeigt, man kommt allerdings nicht zum Anmeldeformular."
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<f-text id="registration_until" v-model="single.registration_until" type="datetime-local" label="Registrierung bis" required />
|
<f-text id="registration_until" v-model="single.registration_until" type="datetime-local" label="Registrierung bis" required />
|
||||||
<f-textarea
|
<f-textarea id="excerpt"
|
||||||
id="excerpt"
|
v-model="single.excerpt"
|
||||||
v-model="single.excerpt"
|
hint="Gebe hier eine kurze Beschreibung für die Veranstaltungs-Übersicht ein (Maximal 130 Zeichen)."
|
||||||
hint="Gebe hier eine kurze Beschreibung für die Veranstaltungs-Übersicht ein (Maximal 130 Zeichen)."
|
label="Auszug"
|
||||||
label="Auszug"
|
:rows="5"
|
||||||
:rows="5"
|
class="col-span-full"
|
||||||
class="col-span-full"
|
required
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="active === 1">
|
<div v-if="active === 1">
|
||||||
|
|
@ -82,9 +61,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-show="active === 3" class="grid grid-cols-[1fr_300px] gap-3">
|
<div v-show="active === 3" class="grid grid-cols-[1fr_300px] gap-3">
|
||||||
<ui-note class="mt-2 col-span-full">
|
<ui-note class="mt-2 col-span-full">
|
||||||
Hier kannst du die E-Mail anpassen, die nach der Anmeldung an den Teilnehmer verschickt wird.<br />
|
Hier kannst du die E-Mail anpassen, die nach der Anmeldung an den Teilnehmer verschickt wird.<br>
|
||||||
Es gibt dafür einen ersten E-Mail-Teil und einen zweiten E-Mail-Teil. Dazwischen werden die Daten des Teilnehmers aufgelistet.<br />
|
Es gibt dafür einen ersten E-Mail-Teil und einen zweiten E-Mail-Teil. Dazwischen werden die Daten des Teilnehmers aufgelistet.<br>
|
||||||
Die Anrede ("Hallo Max Mustermann") wird automatisch an den Anfang gesetzt.<br />
|
Die Anrede ("Hallo Max Mustermann") wird automatisch an den Anfang gesetzt.<br>
|
||||||
Außerdem kannst du Dateien hochladen, die automatisch mit angehangen werden.
|
Außerdem kannst du Dateien hochladen, die automatisch mit angehangen werden.
|
||||||
</ui-note>
|
</ui-note>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -100,15 +79,14 @@
|
||||||
</template>
|
</template>
|
||||||
</f-editor>
|
</f-editor>
|
||||||
</div>
|
</div>
|
||||||
<f-multiplefiles
|
<f-multiplefiles id="mailattachments"
|
||||||
id="mailattachments"
|
v-model="single.mailattachments"
|
||||||
v-model="single.mailattachments"
|
label="Anhänge"
|
||||||
label="Anhänge"
|
name="mailattachments"
|
||||||
name="mailattachments"
|
parent-name="form"
|
||||||
parent-name="form"
|
:parent-id="single.id"
|
||||||
:parent-id="single.id"
|
collection="mailattachments"
|
||||||
collection="mailattachments"
|
class="row-span-2"
|
||||||
class="row-span-2"
|
|
||||||
>
|
>
|
||||||
<template #buttons="{file, buttonClass, iconClass}">
|
<template #buttons="{file, buttonClass, iconClass}">
|
||||||
<a v-tooltip="`Bedingungen`" href="#" :class="[buttonClass, 'bg-blue-200', 'relative']" @click.prevent="fileSettingPopup = file">
|
<a v-tooltip="`Bedingungen`" href="#" :class="[buttonClass, 'bg-blue-200', 'relative']" @click.prevent="fileSettingPopup = file">
|
||||||
|
|
@ -127,12 +105,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-show="active === 5" class="grid grid-cols-2 gap-3">
|
<div v-show="active === 5" class="grid grid-cols-2 gap-3">
|
||||||
<f-switch id="needs_prevention" v-model="single.needs_prevention" name="needs_prevention" label="Prävention" />
|
<f-switch id="needs_prevention" v-model="single.needs_prevention" name="needs_prevention" label="Prävention" />
|
||||||
<f-editor
|
<f-editor id="prevention_text"
|
||||||
id="prevention_text"
|
v-model="single.prevention_text"
|
||||||
v-model="single.prevention_text"
|
hint="Wird an die Präventions-Email angehangen, die Teilnehmende dieser Veranstaltung erhalten"
|
||||||
hint="Wird an die Präventions-Email angehangen, die Teilnehmende dieser Veranstaltung erhalten"
|
:rows="6"
|
||||||
:rows="6"
|
label="Präventions-Hinweis"
|
||||||
label="Präventions-Hinweis"
|
|
||||||
/>
|
/>
|
||||||
<ui-box heading="Bedingung für Präventions-Unterlagen">
|
<ui-box heading="Bedingung für Präventions-Unterlagen">
|
||||||
<conditions id="prevention_conditions" v-model="single.prevention_conditions" :single="single" />
|
<conditions id="prevention_conditions" v-model="single.prevention_conditions" :single="single" />
|
||||||
|
|
@ -205,8 +182,7 @@
|
||||||
<ui-action-button tooltip="Nachmelde-Link kopieren" class="btn-info" icon="externallink" @click="copyLaterLink(form)" />
|
<ui-action-button tooltip="Nachmelde-Link kopieren" class="btn-info" icon="externallink" @click="copyLaterLink(form)" />
|
||||||
<ui-action-button tooltip="Zuschuss-Liste erstellen" class="btn-info" icon="contribution" @click="onGenerateContribution(form)" />
|
<ui-action-button tooltip="Zuschuss-Liste erstellen" class="btn-info" icon="contribution" @click="onGenerateContribution(form)" />
|
||||||
<ui-action-button :href="form.links.export" target="_BLANK" tooltip="als Tabellendokument exportieren" class="btn-info" icon="document" />
|
<ui-action-button :href="form.links.export" target="_BLANK" tooltip="als Tabellendokument exportieren" class="btn-info" icon="document" />
|
||||||
<ui-action-button tooltip="alle Teilnehmende löschen" class="btn-danger" icon="broom" @click.prevent="onCleanup(form)" />
|
<ui-action-button tooltip="Löschen" class="btn-danger" icon="trash" @click.prevent="onDelete(form)" />
|
||||||
<ui-action-button tooltip="Veranstaltung löschen" class="btn-danger" icon="trash" @click.prevent="onDelete(form)" />
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -219,19 +195,19 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js" setup>
|
<script lang="js" setup>
|
||||||
import {ref, inject, computed} from 'vue';
|
import { ref, inject, computed } from 'vue';
|
||||||
import {indexProps, useIndex} from '../../composables/useInertiaApiIndex.js';
|
import { indexProps, useIndex } from '../../composables/useInertiaApiIndex.js';
|
||||||
import FormBuilder from '../formtemplate/FormBuilder.vue';
|
import FormBuilder from '../formtemplate/FormBuilder.vue';
|
||||||
import Participants from './Participants.vue';
|
import Participants from './Participants.vue';
|
||||||
import Conditions from './Conditions.vue';
|
import Conditions from './Conditions.vue';
|
||||||
import ConditionsForm from './ConditionsForm.vue';
|
import ConditionsForm from './ConditionsForm.vue';
|
||||||
import {useToast} from 'vue-toastification';
|
import { useToast } from 'vue-toastification';
|
||||||
import useSwal from '@/stores/swalStore.ts';
|
import useSwal from '@/stores/swalStore.ts';
|
||||||
import useDownloads from '@/composables/useDownloads.ts';
|
import useDownloads from '@/composables/useDownloads.ts';
|
||||||
import useClipboard from 'vue-clipboard3';
|
import useClipboard from 'vue-clipboard3';
|
||||||
|
|
||||||
const props = defineProps(indexProps);
|
const props = defineProps(indexProps);
|
||||||
const {meta, data, reloadPage, reload, create, single, edit, cancel, submit, remove, getFilter, setFilter} = useIndex(props.data, 'form');
|
const { meta, data, reloadPage, reload, create, single, edit, cancel, submit, remove, getFilter, setFilter } = useIndex(props.data, 'form');
|
||||||
const axios = inject('axios');
|
const axios = inject('axios');
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const {download} = useDownloads();
|
const {download} = useDownloads();
|
||||||
|
|
@ -242,8 +218,8 @@ const fileSettingPopup = ref(null);
|
||||||
|
|
||||||
const active = ref(0);
|
const active = ref(0);
|
||||||
const activeMailTab = ref(0);
|
const activeMailTab = ref(0);
|
||||||
const tabs = [{title: 'Allgemeines'}, {title: 'Beschreibung'}, {title: 'Formular'}, {title: 'Bestätigungs-E-Mail'}, {title: 'Export'}, {title: 'Prävention'}, {title: 'Zuschüsse'}];
|
const tabs = [{ title: 'Allgemeines' }, { title: 'Beschreibung' }, { title: 'Formular' }, { title: 'Bestätigungs-E-Mail' }, { title: 'Export' }, { title: 'Prävention' }, {title: 'Zuschüsse'}];
|
||||||
const mailTabs = [{title: 'vor Daten'}, {title: 'nach Daten'}];
|
const mailTabs = [{ title: 'vor Daten' }, { title: 'nach Daten' }];
|
||||||
const swal = useSwal();
|
const swal = useSwal();
|
||||||
|
|
||||||
const allFields = computed(() => {
|
const allFields = computed(() => {
|
||||||
|
|
@ -254,7 +230,7 @@ const allFields = computed(() => {
|
||||||
const result = [];
|
const result = [];
|
||||||
single.value.config.sections.forEach((section) => {
|
single.value.config.sections.forEach((section) => {
|
||||||
section.fields.forEach((field) => {
|
section.fields.forEach((field) => {
|
||||||
result.push({id: field.key, name: field.name});
|
result.push({ id: field.key, name: field.name });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -262,28 +238,21 @@ const allFields = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
async function onCopy(form) {
|
async function onCopy(form) {
|
||||||
await swal.confirm(
|
await swal.confirm('Diese Veranstaltung kopieren?', 'Nach dem Kopieren wird die Veranstaltung auf inaktiv gesetzt. Bitte aktiviere den Filter "inaktive zeigen", um die kopierte Veranstaltung zu sehen.');
|
||||||
'Diese Veranstaltung kopieren?',
|
|
||||||
'Nach dem Kopieren wird die Veranstaltung auf inaktiv gesetzt. Bitte aktiviere den Filter "inaktive zeigen", um die kopierte Veranstaltung zu sehen.',
|
|
||||||
);
|
|
||||||
await axios.post(form.links.copy, {});
|
await axios.post(form.links.copy, {});
|
||||||
reload(false);
|
reload(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onGenerateContribution(form) {
|
async function onGenerateContribution(form) {
|
||||||
const response = await swal.ask(
|
const response = await swal.ask('Zuschussliste erstellen', 'Hiermit erstellst du eine Zuschussliste mit allen angemeldeten Mitgliedern. Bite wähle aus, für welche Organisation du eine Liste erstellen willst.', [
|
||||||
'Zuschussliste erstellen',
|
{
|
||||||
'Hiermit erstellst du eine Zuschussliste mit allen angemeldeten Mitgliedern. Bite wähle aus, für welche Organisation du eine Liste erstellen willst.',
|
name: 'type',
|
||||||
[
|
label: 'Organisation',
|
||||||
{
|
required: true,
|
||||||
name: 'type',
|
type: 'select',
|
||||||
label: 'Organisation',
|
options: meta.value.contribution_types,
|
||||||
required: true,
|
}
|
||||||
type: 'select',
|
]);
|
||||||
options: meta.value.contribution_types,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
);
|
|
||||||
await download(form.links.contribution, {type: response.type, validate: '1'});
|
await download(form.links.contribution, {type: response.type, validate: '1'});
|
||||||
await download(form.links.contribution, {type: response.type});
|
await download(form.links.contribution, {type: response.type});
|
||||||
}
|
}
|
||||||
|
|
@ -293,12 +262,6 @@ async function onDelete(form) {
|
||||||
await remove(form);
|
await remove(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onCleanup(form) {
|
|
||||||
await swal.confirm('Teilnehmende löschen?', `Alle Teilnehmenden der Veranstaltung ${form.name} werden unwiderruflich gelöscht. Die Veranstaltung selbst bleibt erhalten.`);
|
|
||||||
await axios.delete(form.links.cleanup);
|
|
||||||
reload(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTemplate(template) {
|
function setTemplate(template) {
|
||||||
active.value = 0;
|
active.value = 0;
|
||||||
single.value.config = template.config;
|
single.value.config = template.config;
|
||||||
|
|
|
||||||
|
|
@ -41,16 +41,16 @@
|
||||||
<ui-icon-button class="btn-primary self-end mb-2" icon="save" @click="saveForMember">Speichern</ui-icon-button>
|
<ui-icon-button class="btn-primary self-end mb-2" icon="save" @click="saveForMember">Speichern</ui-icon-button>
|
||||||
</ui-box>
|
</ui-box>
|
||||||
<ui-box heading=" Empfänger" container-class="grid grid-cols-2 gap-3 col-span-full">
|
<ui-box heading=" Empfänger" container-class="grid grid-cols-2 gap-3 col-span-full">
|
||||||
<f-text id="to_name" v-model="single.to.name" label="Name" class="col-span-full" required />
|
<f-text id="to_name" v-model="single.to.name" label="Name" required />
|
||||||
<f-text id="to_address" v-model="single.to.address" label="Adresse" class="col-span-full" required />
|
<f-text id="to_address" v-model="single.to.address" label="Adresse" required />
|
||||||
<f-text id="to_zip" v-model="single.to.zip" label="PLZ" required />
|
<f-text id="to_zip" v-model="single.to.zip" label="PLZ" required />
|
||||||
<f-text id="to_location" v-model="single.to.location" label="Ort" required />
|
<f-text id="to_location" v-model="single.to.location" label="Ort" required />
|
||||||
<f-text id="mail_email" v-model="single.mail_email" label="E-Mail-Adresse" class="col-span-full" />
|
<f-text id="mail_email" v-model="single.to.email" label="E-Mail-Adresse" />
|
||||||
|
<f-text id="greeting" v-model="single.to.greeting" label="Anrede" required />
|
||||||
</ui-box>
|
</ui-box>
|
||||||
<ui-box heading="Status" container-class="grid gap-3">
|
<ui-box heading="Status" container-class="grid gap-3">
|
||||||
<f-select id="status" v-model="single.status" :options="meta.statuses" name="status" label="Status" required />
|
<f-select id="status" v-model="single.status" :options="meta.statuses" name="status" label="Status" required />
|
||||||
<f-select id="via" v-model="single.via" :options="meta.vias" name="via" label="Rechnungsweg" required />
|
<f-select id="via" v-model="single.via" :options="meta.vias" name="via" label="Rechnungsweg" required />
|
||||||
<f-text id="greeting" v-model="single.greeting" label="Anrede" required />
|
|
||||||
<f-text id="usage" v-model="single.usage" label="Verwendungszweck" required />
|
<f-text id="usage" v-model="single.usage" label="Verwendungszweck" required />
|
||||||
</ui-box>
|
</ui-box>
|
||||||
<ui-box heading="Positionen" class="col-span-full" container-class="grid gap-3">
|
<ui-box heading="Positionen" class="col-span-full" container-class="grid gap-3">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
@component('mail::message')
|
@component('mail::message')
|
||||||
# {{ $invoice->greeting }},
|
# {{ $invoice->to->greeting }},
|
||||||
|
|
||||||
Im Anhang findet ihr die aktuelle Rechnung an {{$settings->from}} für das laufende Jahr. Bitte begleicht diese bis zum angegebenen Datum.
|
Im Anhang findet ihr die aktuelle Rechnung an {{$settings->from}} für das laufende Jahr. Bitte begleicht diese bis zum angegebenen Datum.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
@component('mail::message')
|
@component('mail::message')
|
||||||
# {{ $invoice->greeting }},
|
# {{ $invoice->to->greeting }},
|
||||||
|
|
||||||
Hiermit möchten wir euch an die noch ausstehenden Mitgliedsbeiträge an {{$settings->from}} für das laufende Jahr erinnern. Bitte begleicht diese bis zum angegebenen Datum.
|
Hiermit möchten wir euch an die noch ausstehenden Mitgliedsbeiträge an {{$settings->from}} für das laufende Jahr erinnern. Bitte begleicht diese bis zum angegebenen Datum.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ use App\Fileshare\Actions\FileshareStoreAction;
|
||||||
use App\Fileshare\Actions\FileshareUpdateAction;
|
use App\Fileshare\Actions\FileshareUpdateAction;
|
||||||
use App\Fileshare\Actions\ListFilesAction;
|
use App\Fileshare\Actions\ListFilesAction;
|
||||||
use App\Form\Actions\ExportAction as ActionsExportAction;
|
use App\Form\Actions\ExportAction as ActionsExportAction;
|
||||||
use App\Form\Actions\FormCleanupAction;
|
|
||||||
use App\Form\Actions\FormCopyAction;
|
use App\Form\Actions\FormCopyAction;
|
||||||
use App\Form\Actions\FormDestroyAction;
|
use App\Form\Actions\FormDestroyAction;
|
||||||
use App\Form\Actions\FormGenerateLaterlinkAction;
|
use App\Form\Actions\FormGenerateLaterlinkAction;
|
||||||
|
|
@ -168,7 +167,6 @@ Route::group(['middleware' => 'auth:web'], function (): void {
|
||||||
Route::get('/form', FormIndexAction::class)->name('form.index');
|
Route::get('/form', FormIndexAction::class)->name('form.index');
|
||||||
Route::patch('/form/{form}', FormUpdateAction::class)->name('form.update');
|
Route::patch('/form/{form}', FormUpdateAction::class)->name('form.update');
|
||||||
Route::delete('/form/{form}', FormDestroyAction::class)->name('form.destroy');
|
Route::delete('/form/{form}', FormDestroyAction::class)->name('form.destroy');
|
||||||
Route::delete('/form/{form}/cleanup', FormCleanupAction::class)->name('form.cleanup');
|
|
||||||
Route::post('/formtemplate', FormtemplateStoreAction::class)->name('formtemplate.store');
|
Route::post('/formtemplate', FormtemplateStoreAction::class)->name('formtemplate.store');
|
||||||
Route::patch('/formtemplate/{formtemplate}', FormtemplateUpdateAction::class)->name('formtemplate.update');
|
Route::patch('/formtemplate/{formtemplate}', FormtemplateUpdateAction::class)->name('formtemplate.update');
|
||||||
Route::delete('/formtemplate/{formtemplate}', FormtemplateDestroyAction::class)->name('formtemplate.destroy');
|
Route::delete('/formtemplate/{formtemplate}', FormtemplateDestroyAction::class)->name('formtemplate.destroy');
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ it('testItDisplaysInvoices', function () {
|
||||||
$invoice = Invoice::factory()
|
$invoice = Invoice::factory()
|
||||||
->has(InvoicePosition::factory()->price(1100)->for($member)->state(['description' => 'lala']), 'positions')
|
->has(InvoicePosition::factory()->price(1100)->for($member)->state(['description' => 'lala']), 'positions')
|
||||||
->has(InvoicePosition::factory()->price(2200)->withMember(), 'positions')
|
->has(InvoicePosition::factory()->price(2200)->withMember(), 'positions')
|
||||||
->to(ReceiverRequestFactory::new()->name('Familie Blabla'))
|
->to(ReceiverRequestFactory::new()->name('Familie Blabla')->email('a@b.de')->greeting('Liebe Fam'))
|
||||||
->sentAt(now()->subDay())
|
->sentAt(now()->subDay())
|
||||||
->via(BillKind::POST)
|
->via(BillKind::POST)
|
||||||
->status(InvoiceStatus::SENT)
|
->status(InvoiceStatus::SENT)
|
||||||
|
|
@ -36,9 +36,9 @@ it('testItDisplaysInvoices', function () {
|
||||||
->assertInertiaPath('data.data.0.sent_at_human', now()->subDay()->format('d.m.Y'))
|
->assertInertiaPath('data.data.0.sent_at_human', now()->subDay()->format('d.m.Y'))
|
||||||
->assertInertiaPath('data.data.0.status', 'Rechnung gestellt')
|
->assertInertiaPath('data.data.0.status', 'Rechnung gestellt')
|
||||||
->assertInertiaPath('data.data.0.via', 'Post')
|
->assertInertiaPath('data.data.0.via', 'Post')
|
||||||
->assertInertiaPath('data.data.0.mail_email', 'a@b.de')
|
->assertInertiaPath('data.data.0.to.email', 'a@b.de')
|
||||||
->assertInertiaPath('data.data.0.usage', 'Usa')
|
->assertInertiaPath('data.data.0.usage', 'Usa')
|
||||||
->assertInertiaPath('data.data.0.greeting', $invoice->greeting)
|
->assertInertiaPath('data.data.0.to.greeting', 'Liebe Fam')
|
||||||
->assertInertiaPath('data.data.0.positions.0.price', 1100)
|
->assertInertiaPath('data.data.0.positions.0.price', 1100)
|
||||||
->assertInertiaPath('data.data.0.positions.0.member_id', $member->id)
|
->assertInertiaPath('data.data.0.positions.0.member_id', $member->id)
|
||||||
->assertInertiaPath('data.data.0.positions.0.description', 'lala')
|
->assertInertiaPath('data.data.0.positions.0.description', 'lala')
|
||||||
|
|
@ -62,13 +62,13 @@ it('testItDisplaysInvoices', function () {
|
||||||
'address' => '',
|
'address' => '',
|
||||||
'zip' => '',
|
'zip' => '',
|
||||||
'location' => '',
|
'location' => '',
|
||||||
|
'email' => null,
|
||||||
|
'greeting' => '',
|
||||||
],
|
],
|
||||||
'positions' => [],
|
'positions' => [],
|
||||||
'greeting' => '',
|
|
||||||
'status' => InvoiceStatus::NEW->value,
|
'status' => InvoiceStatus::NEW->value,
|
||||||
'via' => null,
|
'via' => null,
|
||||||
'usage' => '',
|
'usage' => '',
|
||||||
'mail_email' => '',
|
|
||||||
])
|
])
|
||||||
->assertInertiaPath('data.meta.default_position', [
|
->assertInertiaPath('data.meta.default_position', [
|
||||||
'id' => null,
|
'id' => null,
|
||||||
|
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Feature\Form;
|
|
||||||
|
|
||||||
use App\Form\Models\Form;
|
|
||||||
use App\Form\Models\Participant;
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
||||||
use Tests\Lib\CreatesFormFields;
|
|
||||||
|
|
||||||
uses(DatabaseTransactions::class);
|
|
||||||
uses(CreatesFormFields::class);
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
test()->fakeMessages();
|
|
||||||
test()->setUpForm();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('deletes all participants of a form', function () {
|
|
||||||
test()->login()->loginNami()->withoutExceptionHandling();
|
|
||||||
$form = Form::factory()
|
|
||||||
->has(Participant::factory()->count(2))
|
|
||||||
->sections([])
|
|
||||||
->create();
|
|
||||||
|
|
||||||
test()->deleteJson(route('form.cleanup', ['form' => $form]))
|
|
||||||
->assertOk();
|
|
||||||
|
|
||||||
test()->assertDatabaseCount('participants', 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not delete the form itself', function () {
|
|
||||||
test()->login()->loginNami()->withoutExceptionHandling();
|
|
||||||
$form = Form::factory()
|
|
||||||
->has(Participant::factory())
|
|
||||||
->sections([])
|
|
||||||
->create();
|
|
||||||
|
|
||||||
test()->deleteJson(route('form.cleanup', ['form' => $form]))
|
|
||||||
->assertOk();
|
|
||||||
|
|
||||||
test()->assertDatabaseHas('forms', ['id' => $form->id]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not delete participants of other forms', function () {
|
|
||||||
test()->login()->loginNami()->withoutExceptionHandling();
|
|
||||||
$form = Form::factory()
|
|
||||||
->has(Participant::factory())
|
|
||||||
->sections([])
|
|
||||||
->create();
|
|
||||||
$otherForm = Form::factory()
|
|
||||||
->has(Participant::factory())
|
|
||||||
->sections([])
|
|
||||||
->create();
|
|
||||||
|
|
||||||
test()->deleteJson(route('form.cleanup', ['form' => $form]))
|
|
||||||
->assertOk();
|
|
||||||
|
|
||||||
test()->assertDatabaseCount('participants', 1);
|
|
||||||
test()->assertDatabaseHas('participants', ['id' => $otherForm->participants->first()->id]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('deletes participants that are children of other participants', function () {
|
|
||||||
test()->login()->loginNami()->withoutExceptionHandling();
|
|
||||||
$form = Form::factory()->sections([])->create();
|
|
||||||
$parent = Participant::factory()->for($form)->create();
|
|
||||||
Participant::factory()->for($form)->create(['parent_id' => $parent->id]);
|
|
||||||
|
|
||||||
test()->deleteJson(route('form.cleanup', ['form' => $form]))
|
|
||||||
->assertOk();
|
|
||||||
|
|
||||||
test()->assertDatabaseCount('participants', 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('clears the frontend cache', function () {
|
|
||||||
test()->login()->loginNami()->withoutExceptionHandling();
|
|
||||||
$form = Form::factory()
|
|
||||||
->has(Participant::factory())
|
|
||||||
->sections([])
|
|
||||||
->create();
|
|
||||||
|
|
||||||
test()->deleteJson(route('form.cleanup', ['form' => $form]))
|
|
||||||
->assertOk();
|
|
||||||
|
|
||||||
test()->assertFrontendCacheCleared();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows success message', function () {
|
|
||||||
test()->login()->loginNami()->withoutExceptionHandling();
|
|
||||||
$form = Form::factory()
|
|
||||||
->has(Participant::factory())
|
|
||||||
->sections([])
|
|
||||||
->create();
|
|
||||||
|
|
||||||
test()->deleteJson(route('form.cleanup', ['form' => $form]))
|
|
||||||
->assertOk();
|
|
||||||
|
|
||||||
test()->assertSuccessMessage('Teilnehmende gelöscht.');
|
|
||||||
});
|
|
||||||
|
|
@ -15,7 +15,6 @@ class InvoiceRequestFactory extends RequestFactory
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'to' => ReceiverRequestFactory::new(),
|
'to' => ReceiverRequestFactory::new(),
|
||||||
'greeting' => 'Hallo Familie',
|
|
||||||
'status' => InvoiceStatus::NEW->value,
|
'status' => InvoiceStatus::NEW->value,
|
||||||
'via' => BillKind::EMAIL->value,
|
'via' => BillKind::EMAIL->value,
|
||||||
'positions' => [],
|
'positions' => [],
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@ class InvoiceSendActionTest extends TestCase
|
||||||
Storage::fake('temp');
|
Storage::fake('temp');
|
||||||
$this->withoutExceptionHandling()->login()->loginNami();
|
$this->withoutExceptionHandling()->login()->loginNami();
|
||||||
$invoice = Invoice::factory()
|
$invoice = Invoice::factory()
|
||||||
->to(ReceiverRequestFactory::new()->name('Familie Muster'))
|
->to(ReceiverRequestFactory::new()->name('Familie Muster')->email('max@muster.de'))
|
||||||
->has(InvoicePosition::factory()->withMember(), 'positions')
|
->has(InvoicePosition::factory()->withMember(), 'positions')
|
||||||
->via(BillKind::EMAIL)
|
->via(BillKind::EMAIL)
|
||||||
->create(['mail_email' => 'max@muster.de']);
|
->create();
|
||||||
|
|
||||||
InvoiceSendAction::run();
|
InvoiceSendAction::run();
|
||||||
|
|
||||||
|
|
@ -42,6 +42,28 @@ class InvoiceSendActionTest extends TestCase
|
||||||
$this->assertEquals(now()->format('Y-m-d'), $invoice->fresh()->sent_at->format('Y-m-d'));
|
$this->assertEquals(now()->format('Y-m-d'), $invoice->fresh()->sent_at->format('Y-m-d'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testItRendersBillMail(): void
|
||||||
|
{
|
||||||
|
$this->withoutExceptionHandling()->login()->loginNami();
|
||||||
|
$invoice = Invoice::factory()
|
||||||
|
->to(ReceiverRequestFactory::new()->email('max@muster.de')->greeting('Liebe Familie Doe'))
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$mail = new BillMail($invoice, 'file.txt');
|
||||||
|
$mail->assertSeeInText('Liebe Familie Doe');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItRendersRememberMail(): void
|
||||||
|
{
|
||||||
|
$this->withoutExceptionHandling()->login()->loginNami();
|
||||||
|
$invoice = Invoice::factory()
|
||||||
|
->to(ReceiverRequestFactory::new()->email('max@muster.de')->greeting('Liebe Familie Doe'))
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$mail = new RememberMail($invoice, 'file.txt');
|
||||||
|
$mail->assertSeeInText('Liebe Familie Doe');
|
||||||
|
}
|
||||||
|
|
||||||
public function testItRemembersInvoices(): void
|
public function testItRemembersInvoices(): void
|
||||||
{
|
{
|
||||||
Mail::fake();
|
Mail::fake();
|
||||||
|
|
@ -50,11 +72,11 @@ class InvoiceSendActionTest extends TestCase
|
||||||
$this->withoutExceptionHandling()->login()->loginNami();
|
$this->withoutExceptionHandling()->login()->loginNami();
|
||||||
app(InvoiceSettings::class)->fill(['from_long' => 'Stammname', 'replyTo' => 'reply@mail.com'])->save();
|
app(InvoiceSettings::class)->fill(['from_long' => 'Stammname', 'replyTo' => 'reply@mail.com'])->save();
|
||||||
$invoice = Invoice::factory()
|
$invoice = Invoice::factory()
|
||||||
->to(ReceiverRequestFactory::new()->name('Familie Muster'))
|
->to(ReceiverRequestFactory::new()->name('Familie Muster')->email('max@muster.de'))
|
||||||
->has(InvoicePosition::factory()->withMember(), 'positions')
|
->has(InvoicePosition::factory()->withMember(), 'positions')
|
||||||
->via(BillKind::EMAIL)
|
->via(BillKind::EMAIL)
|
||||||
->status(InvoiceStatus::SENT)
|
->status(InvoiceStatus::SENT)
|
||||||
->create(['sent_at' => now()->subMonths(6), 'mail_email' => 'max@muster.de', 'last_remembered_at' => now()->subMonths(6)]);
|
->create(['sent_at' => now()->subMonths(6), 'last_remembered_at' => now()->subMonths(6)]);
|
||||||
|
|
||||||
InvoiceSendAction::run();
|
InvoiceSendAction::run();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,36 +24,32 @@ class InvoiceStoreActionTest extends TestCase
|
||||||
$response = $this->postJson(
|
$response = $this->postJson(
|
||||||
route('invoice.store'),
|
route('invoice.store'),
|
||||||
InvoiceRequestFactory::new()
|
InvoiceRequestFactory::new()
|
||||||
->to(ReceiverRequestFactory::new()->name('Familie Blabla')->address('Musterstr 44')->zip('22222')->location('Solingen'))
|
->to(ReceiverRequestFactory::new()->name('Familie Blabla')->address('Musterstr 44')->zip('22222')->location('Solingen')->greeting('Hallo Familie')->email('a@b.de'))
|
||||||
->status(InvoiceStatus::PAID)
|
->status(InvoiceStatus::PAID)
|
||||||
->via(BillKind::POST)
|
->via(BillKind::POST)
|
||||||
->state([
|
|
||||||
'greeting' => 'Hallo Familie',
|
|
||||||
])
|
|
||||||
->position(InvoicePositionRequestFactory::new()->description('Beitrag Abc')->price(3250)->member($member))
|
->position(InvoicePositionRequestFactory::new()->description('Beitrag Abc')->price(3250)->member($member))
|
||||||
->create(['mail_email' => 'a@b.de'])
|
->create()
|
||||||
);
|
);
|
||||||
|
|
||||||
$response->assertOk();
|
$response->assertOk();
|
||||||
$this->assertDatabaseHas('invoices', [
|
$this->assertDatabaseHas('invoices', [
|
||||||
'greeting' => 'Hallo Familie',
|
|
||||||
'via' => BillKind::POST->value,
|
'via' => BillKind::POST->value,
|
||||||
'status' => InvoiceStatus::PAID->value,
|
'status' => InvoiceStatus::PAID->value,
|
||||||
'mail_email' => 'a@b.de',
|
|
||||||
]);
|
]);
|
||||||
$invoice = Invoice::firstWhere('greeting', 'Hallo Familie');
|
$invoice = Invoice::firstWhere('to->greeting', 'Hallo Familie');
|
||||||
$this->assertDatabaseHas('invoice_positions', [
|
$this->assertDatabaseHas('invoice_positions', [
|
||||||
'invoice_id' => $invoice->id,
|
'invoice_id' => $invoice->id,
|
||||||
'member_id' => $member->id,
|
'member_id' => $member->id,
|
||||||
'price' => 3250,
|
'price' => 3250,
|
||||||
'description' => 'Beitrag Abc',
|
'description' => 'Beitrag Abc',
|
||||||
]);
|
]);
|
||||||
$this->assertEquals([
|
|
||||||
'name' => 'Familie Blabla',
|
$this->assertEquals('Familie Blabla', $invoice->to->name);
|
||||||
'address' => 'Musterstr 44',
|
$this->assertEquals('Musterstr 44', $invoice->to->address);
|
||||||
'zip' => '22222',
|
$this->assertEquals('22222', $invoice->to->zip);
|
||||||
'location' => 'Solingen',
|
$this->assertEquals('Solingen', $invoice->to->location);
|
||||||
], $invoice->to);
|
$this->assertEquals('Hallo Familie', $invoice->to->greeting);
|
||||||
|
$this->assertEquals('a@b.de', $invoice->to->email);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function validationDataProvider(): Generator
|
public static function validationDataProvider(): Generator
|
||||||
|
|
|
||||||
|
|
@ -25,29 +25,27 @@ class InvoiceUpdateActionTest extends TestCase
|
||||||
$this->patchJson(
|
$this->patchJson(
|
||||||
route('invoice.update', ['invoice' => $invoice]),
|
route('invoice.update', ['invoice' => $invoice]),
|
||||||
InvoiceRequestFactory::new()
|
InvoiceRequestFactory::new()
|
||||||
->to(ReceiverRequestFactory::new()->name('Familie Blabla')->address('Musterstr 44')->zip('22222')->location('Solingen'))
|
->to(ReceiverRequestFactory::new()->name('Familie Blabla')->address('Musterstr 44')->zip('22222')->location('Solingen')->greeting('Hallo Familie Blabla'))
|
||||||
->status(InvoiceStatus::PAID)
|
->status(InvoiceStatus::PAID)
|
||||||
->via(BillKind::POST)
|
->via(BillKind::POST)
|
||||||
->state([
|
->state([
|
||||||
'greeting' => 'Hallo Familie',
|
|
||||||
])
|
])
|
||||||
->create()
|
->create()
|
||||||
)->assertOk();
|
)->assertOk();
|
||||||
|
|
||||||
$this->assertDatabaseCount('invoices', 1);
|
$this->assertDatabaseCount('invoices', 1);
|
||||||
$this->assertDatabaseHas('invoices', [
|
$this->assertDatabaseHas('invoices', [
|
||||||
'greeting' => 'Hallo Familie',
|
|
||||||
'via' => BillKind::POST->value,
|
'via' => BillKind::POST->value,
|
||||||
'status' => InvoiceStatus::PAID->value,
|
'status' => InvoiceStatus::PAID->value,
|
||||||
'id' => $invoice->id,
|
'id' => $invoice->id,
|
||||||
]);
|
]);
|
||||||
$invoice = Invoice::firstWhere('greeting', 'Hallo Familie');
|
$invoice = Invoice::firstWhere('to->greeting', 'Hallo Familie Blabla');
|
||||||
$this->assertEquals([
|
$this->assertEquals('Familie Blabla', $invoice->to->name);
|
||||||
'name' => 'Familie Blabla',
|
$this->assertEquals('Musterstr 44', $invoice->to->address);
|
||||||
'address' => 'Musterstr 44',
|
$this->assertEquals('22222', $invoice->to->zip);
|
||||||
'zip' => '22222',
|
$this->assertEquals('Solingen', $invoice->to->location);
|
||||||
'location' => 'Solingen',
|
$this->assertEquals('Hallo Familie Blabla', $invoice->to->greeting);
|
||||||
], $invoice->to);
|
$this->assertNull($invoice->to->email);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItAddsAPosition(): void
|
public function testItAddsAPosition(): void
|
||||||
|
|
|
||||||
|
|
@ -57,14 +57,13 @@ class MassStoreActionTest extends TestCase
|
||||||
|
|
||||||
$invoice = Invoice::first();
|
$invoice = Invoice::first();
|
||||||
$this->assertNotNull($invoice);
|
$this->assertNotNull($invoice);
|
||||||
$this->assertEquals([
|
$this->assertEquals('Familie Muster', $invoice->to->name);
|
||||||
'name' => 'Familie Muster',
|
$this->assertEquals('Maxstr 4', $invoice->to->address);
|
||||||
'address' => 'Maxstr 4',
|
$this->assertEquals('33445', $invoice->to->zip);
|
||||||
'zip' => '33445',
|
$this->assertEquals('Solingen', $invoice->to->location);
|
||||||
'location' => 'Solingen',
|
$this->assertEquals('Liebe Familie Muster', $invoice->to->greeting);
|
||||||
], $invoice->to);
|
$this->assertEquals('lala@b.de', $invoice->to->email);
|
||||||
$this->assertEquals('Mitgliedsbeitrag für Muster', $invoice->usage);
|
$this->assertEquals('Mitgliedsbeitrag für Muster', $invoice->usage);
|
||||||
$this->assertEquals('lala@b.de', $invoice->mail_email);
|
|
||||||
$this->assertEquals(BillKind::EMAIL, $invoice->via);
|
$this->assertEquals(BillKind::EMAIL, $invoice->via);
|
||||||
$this->assertDatabaseHas('invoice_positions', [
|
$this->assertDatabaseHas('invoice_positions', [
|
||||||
'invoice_id' => $invoice->id,
|
'invoice_id' => $invoice->id,
|
||||||
|
|
|
||||||
|
|
@ -32,14 +32,14 @@ class MemberNewInvoiceActionTest extends TestCase
|
||||||
|
|
||||||
$this->post(route('invoice.new-invoice-attributes'), ['member_id' => $member->id, 'year' => 2019, 'subscription_id' => $subscription->id])
|
$this->post(route('invoice.new-invoice-attributes'), ['member_id' => $member->id, 'year' => 2019, 'subscription_id' => $subscription->id])
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertJsonPath('greeting', 'Liebe Familie Muster')
|
->assertJsonPath('to.greeting', 'Liebe Familie Muster')
|
||||||
->assertJsonPath('to.address', 'Maxstr 4')
|
->assertJsonPath('to.address', 'Maxstr 4')
|
||||||
->assertJsonPath('to.location', 'Solingen')
|
->assertJsonPath('to.location', 'Solingen')
|
||||||
->assertJsonPath('to.zip', '33445')
|
->assertJsonPath('to.zip', '33445')
|
||||||
->assertJsonPath('to.name', 'Familie Muster')
|
->assertJsonPath('to.name', 'Familie Muster')
|
||||||
|
->assertJsonPath('to.email', 'lala@b.de')
|
||||||
->assertJsonPath('usage', 'Mitgliedsbeitrag für Muster')
|
->assertJsonPath('usage', 'Mitgliedsbeitrag für Muster')
|
||||||
->assertJsonPath('via', 'E-Mail')
|
->assertJsonPath('via', 'E-Mail')
|
||||||
->assertJsonPath('mail_email', 'lala@b.de')
|
|
||||||
->assertJsonPath('status', 'Neu')
|
->assertJsonPath('status', 'Neu')
|
||||||
->assertJsonPath('positions.0.description', 'beitrag Max Muster')
|
->assertJsonPath('positions.0.description', 'beitrag Max Muster')
|
||||||
->assertJsonPath('positions.0.member_id', $member->id)
|
->assertJsonPath('positions.0.member_id', $member->id)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ class ReceiverRequestFactory extends RequestFactory
|
||||||
'address' => 'Musterstr 44',
|
'address' => 'Musterstr 44',
|
||||||
'zip' => '22222',
|
'zip' => '22222',
|
||||||
'location' => 'Solingen',
|
'location' => 'Solingen',
|
||||||
|
'greeting' => 'Liebe Familie Blabla',
|
||||||
|
'email' => null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,4 +37,14 @@ class ReceiverRequestFactory extends RequestFactory
|
||||||
{
|
{
|
||||||
return $this->state(['location' => $location]);
|
return $this->state(['location' => $location]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function greeting(string $greeting): self
|
||||||
|
{
|
||||||
|
return $this->state(['greeting' => $greeting]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function email(string $email): self
|
||||||
|
{
|
||||||
|
return $this->state(['email' => $email]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ services:
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 5
|
retries: 5
|
||||||
volumes:
|
volumes:
|
||||||
|
- ./oc_tmp/files:/mnt/data
|
||||||
- ./08-skeleton.sh:/etc/entrypoint.d/08-skeleton.sh
|
- ./08-skeleton.sh:/etc/entrypoint.d/08-skeleton.sh
|
||||||
|
|
||||||
mariadb:
|
mariadb:
|
||||||
|
|
@ -43,6 +44,8 @@ services:
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
volumes:
|
||||||
|
- ./oc_tmp/db:/var/lib/mysql
|
||||||
|
|
||||||
nextclouddb:
|
nextclouddb:
|
||||||
image: mariadb:10.6
|
image: mariadb:10.6
|
||||||
|
|
@ -52,6 +55,8 @@ services:
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
volumes:
|
||||||
|
- ./oc_tmp/ncdb:/var/lib/mysql
|
||||||
environment:
|
environment:
|
||||||
- MYSQL_ROOT_PASSWORD=secret
|
- MYSQL_ROOT_PASSWORD=secret
|
||||||
- MYSQL_PASSWORD=secret
|
- MYSQL_PASSWORD=secret
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue