Mitgliedschaft: Erinnerungen und eigenes Verlaengerungs-Produkt
Die Erinnerungsmails vor Ablauf der Mitgliedschaft beschrieben noch den abgeschafften SEPA-Einzug und verlinkten auf die Hauptdomain, wo /user/membership nicht existiert. Das Produkt fuer die Verlaengerung war zudem dasselbe wie bei der Registrierung und sprach deshalb vom Neuabschluss. - Erinnerungen: nur noch Stufen 31/33/34/35, SEPA-Stufen 32/36/37 samt :price und :pay_date entfallen; Button verlinkt auf my.mivita.care; fehlt ein Text zum Status, wird die Mail geloggt statt roh versendet. - Alle Erinnerungstexte (de/en/es/fr) erklaeren jetzt, dass ohne Zahlung der Account pausiert und offene Provisionen ausgezahlt werden. - Eigenes Produkt fuer die Verlaengerung (identifier show_renewal) als Kopie des Mitgliedschafts-Produkts inkl. Preisen, Kategorien und Uebersetzungen fuer en/es/fr; die Mitgliedschaftsseite faellt ohne dieses Produkt auf das bisherige zurueck. - Mitgliedschaftsseite auf ein Paket reduziert: Upgrade-, Wechsel- und Abo-Optionen entfernt, Texte auf die manuelle Verlaengerung umgestellt. - Alte Cron-Weboberflaeche entfernt, der Versand laeuft ueber den Scheduler (payments:check-accounts). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0bdb6eabc6
commit
30fb031d12
30 changed files with 810 additions and 1223 deletions
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Gleicht Schema-Drift der products-Tabelle aus.
|
||||
*
|
||||
* slug (Sluggable-Trait) und die trans_*-Übersetzungsspalten existieren in der
|
||||
* Produktivdatenbank und werden vom Product-Model genutzt, fehlten aber in den
|
||||
* Migrationen. Frisch aufgebaute Datenbanken – unter anderem die Testdatenbank –
|
||||
* hatten sie deshalb nicht.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('products', function (Blueprint $table): void {
|
||||
if (! Schema::hasColumn('products', 'slug')) {
|
||||
$table->string('slug', 191)->nullable()->index();
|
||||
}
|
||||
foreach (['trans_name', 'trans_title'] as $column) {
|
||||
if (! Schema::hasColumn('products', $column)) {
|
||||
$table->text($column)->nullable();
|
||||
}
|
||||
}
|
||||
foreach (['trans_copy', 'trans_description', 'trans_usage', 'trans_ingredients'] as $column) {
|
||||
if (! Schema::hasColumn('products', $column)) {
|
||||
$table->mediumText($column)->nullable();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// Spalten bleiben bestehen: sie werden produktiv genutzt.
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
use App\Models\CountryPrice;
|
||||
use App\Models\Product;
|
||||
use App\Models\ProductCategory;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
/**
|
||||
* Eigenes Produkt für die Mitgliedschafts-Verlängerung.
|
||||
*
|
||||
* Das bisherige Mitgliedschafts-Produkt (identifier show_order) wird bei der
|
||||
* Registrierung angezeigt und beschreibt entsprechend den Neuabschluss
|
||||
* ("Du kannst Dich jetzt mit der Jahresgebühr registrieren ..."). Für die
|
||||
* Verlängerung passt dieser Text nicht, deshalb bekommt sie eine Kopie mit
|
||||
* eigenem Inhalt.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
private const RENEWAL_IDENTIFIER = 'show_renewal';
|
||||
|
||||
private const RENEWAL_SLUG = 'mivita-business-paket-verlaengerung';
|
||||
|
||||
private const RENEWAL_TITLE = 'Verlängerung – 12 Monate Laufzeit';
|
||||
|
||||
private const RENEWAL_COPY = '<p>Du verlängerst Deine MIVITA Mitgliedschaft um weitere 12 Monate.</p>'
|
||||
.'<p>Damit bleiben Dir erhalten:</p>'
|
||||
.'<ul>'
|
||||
.'<li>Dein persönlicher Berater-Rabatt auf alle Einkäufe</li>'
|
||||
.'<li>Dein personalisierter mivita Webshop</li>'
|
||||
.'<li>Die Gutschrift von Punkten und Provisionen gemäß Marketingplan</li>'
|
||||
.'</ul>'
|
||||
.'<p>Die Verlängerung schließt direkt an Deine aktuelle Laufzeit an und wird aktiv, sobald Deine Zahlung bei uns eingegangen ist. Eine automatische Verlängerung gibt es nicht.</p>'
|
||||
.'<p><b>Bitte beachte:</b> Ohne Zahlung läuft Deine Mitgliedschaft aus und Dein Berater-Account wird pausiert. Es werden Dir dann keine Punkte und keine Provisionen mehr gutgeschrieben. Bereits erarbeitete, noch offene Provisionen zahlen wir Dir selbstverständlich im nächsten Zahlungslauf aus.</p>';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
if (Product::withTrashed()->where('identifier', self::RENEWAL_IDENTIFIER)->exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$membershipProduct = Product::where('identifier', 'show_order')
|
||||
->whereJsonContains('show_on', '7')
|
||||
->whereJsonContains('show_on', '8')
|
||||
->orderBy('pos', 'ASC')
|
||||
->first();
|
||||
|
||||
if (! $membershipProduct) {
|
||||
return;
|
||||
}
|
||||
|
||||
$renewalProduct = $membershipProduct->replicate();
|
||||
$renewalProduct->identifier = self::RENEWAL_IDENTIFIER;
|
||||
$renewalProduct->show_on = ['8']; // nur Mitgliedschaft, nicht Registrierung
|
||||
$renewalProduct->slug = self::RENEWAL_SLUG;
|
||||
$renewalProduct->title = self::RENEWAL_TITLE;
|
||||
$renewalProduct->copy = self::RENEWAL_COPY;
|
||||
$renewalProduct->pos = (int) $membershipProduct->pos + 1;
|
||||
$renewalProduct->active = true;
|
||||
|
||||
// Eigene Artikelnummern: wp_number ist unique, ean darf sich nicht doppeln.
|
||||
$renewalProduct->wp_number = null;
|
||||
$renewalProduct->ean = null;
|
||||
$renewalProduct->number = $membershipProduct->number ? $membershipProduct->number.'-V' : null;
|
||||
|
||||
// Uebersetzungen des Registrierungs-Textes passen inhaltlich nicht.
|
||||
foreach (['trans_title', 'trans_copy'] as $attribute) {
|
||||
if (array_key_exists($attribute, $renewalProduct->getAttributes())) {
|
||||
$renewalProduct->{$attribute} = null;
|
||||
}
|
||||
}
|
||||
|
||||
$renewalProduct->save();
|
||||
|
||||
foreach ($membershipProduct->country_prices as $countryPrice) {
|
||||
CountryPrice::create([
|
||||
'product_id' => $renewalProduct->id,
|
||||
'country_id' => $countryPrice->country_id,
|
||||
'c_price' => $countryPrice->c_price,
|
||||
'c_tax' => $countryPrice->c_tax,
|
||||
'c_price_old' => $countryPrice->c_price_old,
|
||||
'c_currency' => $countryPrice->c_currency,
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($membershipProduct->product_categories as $productCategory) {
|
||||
ProductCategory::create([
|
||||
'product_id' => $renewalProduct->id,
|
||||
'category_id' => $productCategory->category_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Product::where('identifier', self::RENEWAL_IDENTIFIER)
|
||||
->get()
|
||||
->each(function (Product $product): void {
|
||||
$product->active = false;
|
||||
$product->save();
|
||||
$product->delete();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Product;
|
||||
use App\Models\TransProduct;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
/**
|
||||
* Übersetzungen für das Verlängerungs-Produkt (identifier show_renewal).
|
||||
*
|
||||
* Product::getLang() liest die Texte aus trans_products und fällt sonst auf den
|
||||
* deutschen Wert zurück. Die Produkt-Kopie aus 2026_08_25_120000 bringt keine
|
||||
* eigenen Zeilen mit, deshalb sahen en/es/fr den deutschen Verlängerungstext.
|
||||
* Der Produktname bleibt identisch zum Mitgliedschafts-Produkt und wird von dort
|
||||
* übernommen; die Beschreibung beschreibt die Verlängerung statt der Registrierung.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
private const RENEWAL_IDENTIFIER = 'show_renewal';
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private const RENEWAL_COPY = [
|
||||
'en' => '<p>You are renewing your MIVITA membership for another 12 months.</p>'
|
||||
.'<p>This keeps in place:</p>'
|
||||
.'<ul>'
|
||||
.'<li>Your personal consultant discount on all purchases</li>'
|
||||
.'<li>Your personalised mivita web shop</li>'
|
||||
.'<li>Points and commissions credited in accordance with the marketing plan</li>'
|
||||
.'</ul>'
|
||||
.'<p>The renewal follows on directly from your current term and becomes active as soon as we have received your payment. There is no automatic renewal.</p>'
|
||||
.'<p><b>Please note:</b> Without payment your membership expires and your consultant account will be paused. From then on you will no longer be credited with any points or commissions. Commissions you have already earned and that are still outstanding will of course be paid out to you in the next payment run.</p>',
|
||||
|
||||
'es' => '<p>Amplías tu afiliación MIVITA por otros 12 meses.</p>'
|
||||
.'<p>Así conservas:</p>'
|
||||
.'<ul>'
|
||||
.'<li>Tu descuento personal de asesor en todas las compras</li>'
|
||||
.'<li>Tu tienda online mivita personalizada</li>'
|
||||
.'<li>El abono de puntos y comisiones según el plan de marketing</li>'
|
||||
.'</ul>'
|
||||
.'<p>La ampliación enlaza directamente con tu plazo actual y se activa en cuanto recibamos tu pago. No existe una renovación automática.</p>'
|
||||
.'<p><b>Ten en cuenta:</b> Sin pago tu afiliación caduca y tu cuenta de asesor queda en pausa. A partir de ese momento ya no se te abonarán puntos ni comisiones. Las comisiones ya generadas y aún pendientes se te pagarán, por supuesto, en la siguiente ejecución de pago.</p>',
|
||||
|
||||
'fr' => '<p>Vous renouvelez votre adhésion MIVITA pour 12 mois supplémentaires.</p>'
|
||||
.'<p>Vous conservez ainsi :</p>'
|
||||
.'<ul>'
|
||||
.'<li>Votre remise personnelle de conseiller sur tous vos achats</li>'
|
||||
.'<li>Votre boutique en ligne mivita personnalisée</li>'
|
||||
.'<li>Le crédit de points et de commissions conformément au plan marketing</li>'
|
||||
.'</ul>'
|
||||
.'<p>Le renouvellement fait directement suite à votre durée actuelle et devient actif dès que nous avons reçu votre paiement. Il n’y a pas de renouvellement automatique.</p>'
|
||||
.'<p><b>Veuillez noter :</b> Sans paiement, votre adhésion expire et votre compte conseiller est mis en pause. Aucun point ni aucune commission ne vous seront alors plus crédités. Les commissions déjà acquises et encore ouvertes vous seront bien entendu versées lors du prochain cycle de paiement.</p>',
|
||||
];
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
$renewalProduct = Product::where('identifier', self::RENEWAL_IDENTIFIER)->first();
|
||||
|
||||
if (! $renewalProduct) {
|
||||
return;
|
||||
}
|
||||
|
||||
$membershipProduct = Product::where('identifier', 'show_order')
|
||||
->whereJsonContains('show_on', '7')
|
||||
->whereJsonContains('show_on', '8')
|
||||
->orderBy('pos', 'ASC')
|
||||
->first();
|
||||
|
||||
foreach (self::RENEWAL_COPY as $language => $copy) {
|
||||
$name = $membershipProduct ? $membershipProduct->getTrans('name', $language) : '';
|
||||
|
||||
if ($name !== '') {
|
||||
$this->addTranslation($renewalProduct, $language, 'name', $name);
|
||||
}
|
||||
|
||||
$this->addTranslation($renewalProduct, $language, 'copy', $copy);
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$renewalProduct = Product::where('identifier', self::RENEWAL_IDENTIFIER)->first();
|
||||
|
||||
if (! $renewalProduct) {
|
||||
return;
|
||||
}
|
||||
|
||||
TransProduct::where('product_id', $renewalProduct->id)
|
||||
->whereIn('language', array_keys(self::RENEWAL_COPY))
|
||||
->whereIn('key', ['name', 'copy'])
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bestehende Übersetzungen bleiben unangetastet: sie können im Backend
|
||||
* nachträglich angepasst worden sein.
|
||||
*/
|
||||
private function addTranslation(Product $product, string $language, string $key, string $value): void
|
||||
{
|
||||
TransProduct::firstOrCreate(
|
||||
['language' => $language, 'product_id' => $product->id, 'key' => $key],
|
||||
['value' => $value],
|
||||
);
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue