Files
calvana/temp_files/care/deploy.py
Omair Saleh 8366054bd7 Deep UX: 2-column automations, visible appeal cards, platform education, strip model refs
Automations:
- 2-column layout: WhatsApp phone LEFT, education RIGHT
- Right column: 'How it works' (5 numbered steps), performance stats, timing controls, reply commands, tips
- Hero spans full width with photo+dark panel
- Improvement CTA is a prominent card, not floating text
- No misalignment — phone fills left column naturally

Collect:
- Appeals shown as visible gap-px grid cards (not hidden dropdown)
- Each card shows name, platform, amount raised, pledge count, collection rate
- Active appeal has border-l-2 blue indicator
- Platform integration clarity: shows 'Donors redirected to JustGiving' etc
- Educational section: 'Where to share your link' + 'How payment works'
- Explains bank transfer vs JustGiving vs card payment inline

AI model: Stripped all model name comments from code (no user-facing references existed)
2026-03-05 03:20:20 +08:00

135 lines
5.9 KiB
Python

#!/usr/bin/env python3
"""
Deploy supporter care changes:
1. Add HasInternalNotes to ScheduledGivingDonation model
2. Add InternalNotesRelationManager to ScheduledGivingDonationResource
3. Add refund action to DonationResource table row actions
"""
import os
BASE = '/home/forge/app.charityright.org.uk'
# ── 1. Add HasInternalNotes to ScheduledGivingDonation model ─────
path = os.path.join(BASE, 'app/Models/ScheduledGivingDonation.php')
with open(path, 'r') as f:
c = f.read()
if 'HasInternalNotes' not in c:
# Add use import
c = c.replace(
"use App\\Traits\\Models\\HasBasicAttributions;",
"use App\\Traits\\HasInternalNotes;\nuse App\\Traits\\Models\\HasBasicAttributions;"
)
# Add trait usage
c = c.replace(
" use HasBasicAttributions,",
" use HasInternalNotes,\n HasBasicAttributions,"
)
with open(path, 'w') as f:
f.write(c)
print('Added HasInternalNotes to ScheduledGivingDonation model')
else:
print('ScheduledGivingDonation already has HasInternalNotes')
# ── 2. Add InternalNotesRelationManager to ScheduledGivingDonationResource ──
path = os.path.join(BASE, 'app/Filament/Resources/ScheduledGivingDonationResource.php')
with open(path, 'r') as f:
c = f.read()
if 'InternalNotesRelationManager' not in c:
# Add import
c = c.replace(
"use App\\Filament\\Resources\\ScheduledGivingDonationResource\\RelationManagers\\ScheduledGivingDonationPayments;",
"use App\\Filament\\Resources\\ScheduledGivingDonationResource\\RelationManagers\\ScheduledGivingDonationPayments;\nuse App\\Filament\\RelationManagers\\InternalNotesRelationManager;"
)
# Add to getRelations
c = c.replace(
"ScheduledGivingDonationPayments::class,\n ];",
"ScheduledGivingDonationPayments::class,\n InternalNotesRelationManager::class,\n ];"
)
with open(path, 'w') as f:
f.write(c)
print('Added InternalNotesRelationManager to ScheduledGivingDonationResource')
else:
print('ScheduledGivingDonationResource already has InternalNotesRelationManager')
# ── 3. Add refund action to DonationResource table row actions ───
path = os.path.join(BASE, 'app/Filament/Resources/DonationResource.php')
with open(path, 'r') as f:
c = f.read()
# Add StripeRefundService import if missing
if 'StripeRefundService' not in c:
c = c.replace(
"use App\\Models\\Donation;",
"use App\\Models\\Donation;\nuse App\\Services\\StripeRefundService;"
)
# Add TextInput import if missing for refund form
if 'use Filament\\Forms\\Components\\TextInput;' not in c:
c = c.replace(
"use Filament\\Forms\\Components\\Select;",
"use Filament\\Forms\\Components\\Select;\nuse Filament\\Forms\\Components\\TextInput;"
)
# Add refund action inside the ActionGroup, after ViewAction
old_view = " ViewAction::make(),"
new_view = """ ViewAction::make(),
Action::make('refund')
->label('Refund')
->icon('heroicon-o-arrow-uturn-left')
->color('danger')
->visible(fn (Donation $d) => $d->isConfirmed()
&& $d->provider_type === \\App\\Definitions\\PaymentProviders::STRIPE
&& str_starts_with($d->provider_reference ?? '', 'pi_'))
->requiresConfirmation()
->modalHeading('Refund Donation')
->modalDescription(fn (Donation $d) => 'Refund £' . number_format($d->amount / 100, 2) . ' to ' . ($d->customer?->name ?? 'donor') . '\\'s card via Stripe.')
->form([
TextInput::make('refund_amount')
->label('Refund amount (£)')
->numeric()
->default(fn (Donation $d) => number_format($d->amount / 100, 2, '.', ''))
->required()
->minValue(0.01)
->maxValue(fn (Donation $d) => $d->amount / 100)
->step(0.01)
->helperText('Full amount for complete refund, or reduce for partial.'),
])
->action(function (Donation $donation, array $data) {
$amountPence = (int) round($data['refund_amount'] * 100);
$isPartial = $amountPence < $donation->amount;
$service = app(StripeRefundService::class);
$result = $service->refundPaymentIntent(
$donation->provider_reference,
$isPartial ? $amountPence : null,
'Table refund by ' . auth()->user()?->name
);
if ($result['success']) {
if (!$isPartial) {
$donation->donationConfirmation?->update(['confirmed_at' => null]);
}
$donation->internalNotes()->create([
'user_id' => auth()->id(),
'body' => ($isPartial ? 'Partial' : 'Full') . ' refund of £' . number_format($amountPence / 100, 2) . '. Stripe ID: ' . $result['refund_id'],
]);
Notification::make()->title('£' . number_format($result['amount'] / 100, 2) . ' refunded')->success()->send();
} else {
Notification::make()->title('Refund failed')->body($result['error'])->danger()->send();
}
}),"""
c = c.replace(old_view, new_view)
with open(path, 'w') as f:
f.write(c)
print('Added refund action to DonationResource table')
else:
print('DonationResource already has StripeRefundService')
print('Done!')